[The enclosed patch adds customizable states to GNATS; see the enclosed documentation patches for details. Known issues: 1. Based on inspection of the code it does not seem to preserve the behavior of the GNATS emacs interface in selecting a default next state on C-c C-s. The current behavior is: suspended | V open -> analyzed -> feedback -> closed whereas the patch would seem to change this to open -> analyzed -> feedback -> suspended -> closed The fix to this would presumably be to extend the states file to have a "next state" field like: feedback:closed: Problem solved, now awaiting originator's reaction to fix. suspended:analyzed: No solution yet, work on it also suspended for the time being. 2. There is a bug in state_numeric() which can cause query-pr -i to always return a state of 0. The last_idx variable needs to be initialized to the same value as idx in the start of the for loop. Updated Jan 1998] *** gnats/ChangeLog Mon Dec 15 13:29:51 1997 --- gnats/ChangeLog Thu Dec 18 16:42:50 1997 *************** *** 1,3 **** --- 1,113 ---- + Thu Dec 18 15:54:28 1997 Karl Fogel + + Changes to implement customizable PR states: + + * Makefile.in (DISTFILES): added `states' file. + (install-gnats-arch-indep): install the `states' file to gnats-adm + with everything else, if not already installed. + (install-tools-arch-indep): fixed typo, so gnats.elc installs. + + * states: new admin file, old hardcoded states put in as + defaults. + + * pr.c (get_pr_states): new func, reads states from an init file + if possible, else returns what used to be hardcoded. + (get_default_state, get_final_state): new funcs, retrieve first/last + state from "this | kind | of | string". + (init_pr): use above new funcs to init `enum_value', + `default_value', and `final_value', in pr[STATE]. + + * pr.h (PR_entry): new member char * `final_value'. For states, + holds the string which signifies a "closed" PR (this may or may + not be the literal string "closed", depending on the last entry in + the new gnats-adm/states file). No other PR fields use + `final_value' yet. + + * config.h (STATES): new #define, for `states' file in + gnats_root/gnats_adm/. + + * getclose.c (do_prlist): use pr[STATE].final_value, instead of + hardcoding "closed". + + * pr-stat.c (do_category): same as above. + + * query.c (pr_matches): same as above. + (state_numeric): new func. + (sql_type): use above new func for numeric value of a state. + + * pr-addr.c (is_daemon): Add definition to 0 (necessary for above + changes to compile, don't even THINK of asking why). + + * at-pr.sh (STATES_FILE): new var, init according to $GNATS_ROOT. + (OPEN_STATE): new var, use instead of hardcoded "open". + (get_default_state): new function, use to above new var + OPEN_STATE. Defaults to "open". + + * p-admin.texi (Local configuration): add `states file' to menu. + Update references everywhere for changes below: + (states file): new node. + (config file): replaces `config' node. + (categories file): replaces `categories' node. + (responsible file): replaces `responsible' node. + (submitters file): replaces `submitters' node. + (addresses file): replaces `addresses' node. + + * p-usage.texi (Reporting): note mapping of numbers to states may + not hold if administrator made custom states; explain meaning of + "0". + (Invoking query-pr): document "--list-*" options, including new + "--list-states", and document their short versions as well. + + * gnats.texi (GNATS_ROOT): mention the new `states' file. + (defaults): same. + + * query-pr.c (long_options): associate 'T' option with + list-states. + (main): handle 'T' option. + (usage): include "--list-states" option. + (query_pr): use pr[STATE].final_value, instead of hardcoding + "closed". + (print_pr): format state as a string not a number. + (sql_types): don't handle states anymore -- the administrator now + has too much flexibility with states for us to assume they're + ordered. + + * nquery-pr.c (long_options): added "list-states" option ('T' is + short option). + (main): handle new 'T' option to get states, in getopt() and in + switch-case. + (usage): include "--list-states" option. + (ask_about): add case for LIST_STATES. + + * query.h (LIST_STATES): new #define. + + * lists.c (get_gnats_file): add case for STATES. + + * cmds.c (GNATS_lsta): new func, gets states file. + (GNATS_help): add help msg for "LSTA". + (get_reply): handle CODE_INVALID_STATE, although currently no one + sends it. + + * pcodes.h (CODE_INVALID_STATE): new code, unused but available + for those who like that sort of thing. + + * gnatsd.h: declare new func GNATS_lsta(). + + * gnatsd.c (cmds): added "LSTA" + + * gnats-el.in (gnats::states): new var. + (gnats::default-states): new constant var, contains old hardcoded + states as a fallback. + (gnats::states-sans-descriptions): new func. + (gnats::state-following): new func replaces var of same name. + (gnats::fields): use above new funcs instead of hardcoding + state names. + Moved to end of file so everything it needs is defined before it + gets set. + (gnats::set-states): new func, passes new noerror arg to below. + (gnats::get-list-from-file): take optional noerror arg. + (gnats::get-alist): fix regexp to handle comments, blank lines. + Sun Nov 30 21:05:29 1997 Karl Fogel * Makefile.in (getdate.c): handle bisons that output *** send-pr/ChangeLog Mon Dec 15 13:29:50 1997 --- send-pr/ChangeLog Thu Dec 18 16:42:50 1997 *************** *** 1,3 **** --- 1,13 ---- + Thu Dec 18 16:30:03 1997 Karl Fogel + + * Makefile.in (TEXINPUTS): include ../gnats, since states.texi + refers to nodes from there. + + * states.texi (States): reference customizable states. + + * fields.texi (Problem Report fields): reference "categories + file", not "categories". + Tue Nov 25 16:00:00 1997 Abe Feldman * s-usage.texi: Added note about deriving the submitter ID *** gnats/Makefile.in Mon Dec 15 13:29:51 1997 --- gnats/Makefile.in Thu Dec 18 16:42:50 1997 *************** *** 198,204 **** LIBOBJS = edit.o files.o getdate.o headers.o internal.o misc.o pr.o xmalloc.o index.o lists.o query.o qvariable.o config.o version.o $(EXTRA_OBJS) DISTFILES= $(SOURCES) COPYING ChangeLog INSTALL Makefile.in README \ ! categories responsible submitters addresses regex.c regex.h \ at-pr.sh config.h configure configure.in error.c files.h getdate.y \ getdate.c globals.h headers.h index.c mkcat.sh pathmax.h pr.h getclose.c \ pr-addr.c pr-age.c pr-mail.c pr-stat.c gnats-dirs.h gnats-el.in gnats.h \ --- 198,204 ---- LIBOBJS = edit.o files.o getdate.o headers.o internal.o misc.o pr.o xmalloc.o index.o lists.o query.o qvariable.o config.o version.o $(EXTRA_OBJS) DISTFILES= $(SOURCES) COPYING ChangeLog INSTALL Makefile.in README \ ! categories responsible submitters states addresses regex.c regex.h \ at-pr.sh config.h configure configure.in error.c files.h getdate.y \ getdate.c globals.h headers.h index.c mkcat.sh pathmax.h pr.h getclose.c \ pr-addr.c pr-age.c pr-mail.c pr-stat.c gnats-dirs.h gnats-el.in gnats.h \ *************** *** 470,476 **** install-tools-arch-indep: all-tools $(SHELL) $(srcdir)/../mkinstalldirs $(datadir)/gnats $(INSTALL_DATA) gnats.el $(lispdir)/gnats.el ! -$(INSTALL_DATA) gnats.elc $(lispdir)/gnats.elc install-tools-bin: all-tools $(INSTALL_PROGRAM) query-pr $(bindir)/query-pr --- 470,476 ---- install-tools-arch-indep: all-tools $(SHELL) $(srcdir)/../mkinstalldirs $(datadir)/gnats $(INSTALL_DATA) gnats.el $(lispdir)/gnats.el ! $(INSTALL_DATA) gnats.elc $(lispdir)/gnats.elc install-tools-bin: all-tools $(INSTALL_PROGRAM) query-pr $(bindir)/query-pr *************** *** 535,541 **** echo "Not putting config file in gnats-adm, it's already there." ; \ true ; \ else \ ! $(INSTALL_DATA) -o $(GNATS_USER) config $(GNATS_ROOT)/gnats-adm/config ; \ fi @echo "*** Don't forget to run $(libexecdir)/gnats/mkcat as $(GNATS_USER)." --- 535,549 ---- echo "Not putting config file in gnats-adm, it's already there." ; \ true ; \ else \ ! $(INSTALL_DATA) -o $(GNATS_USER) $(srcdir)/config \ ! $(GNATS_ROOT)/gnats-adm/config ; \ ! fi ! @if [ -f $(GNATS_ROOT)/gnats-adm/states ]; then \ ! echo "Not putting states file in gnats-adm, it's already there." ; \ ! true ; \ ! else \ ! $(INSTALL_DATA) -o $(GNATS_USER) $(srcdir)/states \ ! $(GNATS_ROOT)/gnats-adm/states ; \ fi @echo "*** Don't forget to run $(libexecdir)/gnats/mkcat as $(GNATS_USER)." *** gnats/at-pr.sh Mon Dec 15 13:27:06 1997 --- gnats/at-pr.sh Thu Dec 18 16:42:50 1997 *************** *** 37,42 **** --- 37,44 ---- MAIL_AGENT="xMAIL_AGENTx" GNATS_USER="xGNATS_USERx" + STATES_FILE=${GNATS_ROOT}/gnats-adm/states + # Newer config information? [ -f ${GNATS_ROOT}/gnats-adm/config ] && . ${GNATS_ROOT}/gnats-adm/config *************** *** 70,76 **** " fi ! if [ "$STATE" = "open" ]; then $MAIL_AGENT << __EOF__ ${STEALTH_HEADER}From: $GNATS_USER (GNATS Management) To: $6, $RESP_ADDR, $7 --- 72,108 ---- " fi ! # Read the default (first) state from the gnats-adm/states file. ! function get_default_state () ! { ! if [ ! -f ${STATES_FILE} ]; then ! echo "open"; ! return 0; ! fi ! ! LINES=`grep '^[A-Za-z0-9_.-]' ${STATES_FILE} 2>/dev/null` ! ! if [ -z "${LINES}" ]; then ! echo "open" ! return 0; ! fi ! ! # else get the first state ! ! # Yes, this is as gross as you think it is. ! # Tried to use `read', but that didn't work... ! SAVED_IFS=${IFS} ! IFS="${IFS}:" ! for name in ${LINES} ! do ! echo "${name}" ! IFS=${SAVED_IFS} ! break ! done ! } ! OPEN_STATE=`get_default_state` ! ! if [ "$STATE" = "${OPEN_STATE}" ]; then $MAIL_AGENT << __EOF__ ${STEALTH_HEADER}From: $GNATS_USER (GNATS Management) To: $6, $RESP_ADDR, $7 *** gnats/client.c Mon Dec 15 13:27:06 1997 --- gnats/client.c Thu Dec 18 16:42:50 1997 *************** *** 230,235 **** --- 230,243 ---- safe_exit (); break; + case CODE_INVALID_STATE: + s = strchr (r->text, '.'); s[0] = '\0'; + s = strrchr (r->text, ' ') + 1; + fprintf (stderr, "%s: no such state %s\n", + program_name, s); + safe_exit (); + break; + case CODE_INVALID_RESPONSIBLE: s = strchr (r->text, '.'); s[0] = '\0'; s = strrchr (r->text, ' ') + 1; *** gnats/cmds.c Mon Dec 15 13:27:06 1997 --- gnats/cmds.c Thu Dec 18 16:42:50 1997 *************** *** 1469,1474 **** --- 1469,1489 ---- } void + GNATS_lsta (ac, av) + int ac; + char **av; + { + if (ac) + { + noargs ("LSTA"); + return; + } + printf ("%d List follows.\r\n", CODE_PR_READY); + if (get_gnats_file (LIST_STATES, NULL) == 0) + printf (".\r\n"); + } + + void GNATS_help (ac, av) int ac; char **av; *************** *** 1484,1489 **** --- 1499,1506 ---- printf ("%d- LRES list the available responsible names\r\n", CODE_INFORMATION); printf ("%d- LSUB list the available submitters\r\n", + CODE_INFORMATION); + printf ("%d- LSTA list the available states\r\n", CODE_INFORMATION); printf ("%d- CATG search for PRs in \r\n", CODE_INFORMATION); *** gnats/config.h Mon Dec 15 13:27:06 1997 --- gnats/config.h Thu Dec 18 16:42:51 1997 *************** *** 53,58 **** --- 53,61 ---- /* These files are currently live under GNATS_ADM. */ + /* Ordered list of possible states, with optional brief descriptions. */ + #define STATES "/states" + /* List of possible categories, with notification information. */ #define CATEGORIES "/categories" *** gnats/getclose.c Mon Dec 15 13:27:07 1997 --- gnats/getclose.c Thu Dec 18 16:42:51 1997 *************** *** 128,134 **** int len = strlen (gnats_root); for (i = index_chain; i ; i = i->next) ! if (strcmp (i->state, "closed") == 0 && regcmp ("no", i->confidential) == 0) { sprintf (path, "%s/%s/%s", gnats_root, i->category, i->number); --- 128,134 ---- int len = strlen (gnats_root); for (i = index_chain; i ; i = i->next) ! if (strcmp (i->state, pr[STATE].final_value) == 0 && regcmp ("no", i->confidential) == 0) { sprintf (path, "%s/%s/%s", gnats_root, i->category, i->number); *** gnats/gnats-el.in Mon Dec 15 13:27:06 1997 --- gnats/gnats-el.in Thu Dec 18 16:42:51 1997 *************** *** 133,138 **** --- 133,145 ---- (defvar gnats::submitters nil "List of GNATS submitters; Computed at runtime.") + (defvar gnats::states nil + "List of GNATS states; Computed at runtime.") + + (defconst gnats::default-states + '(("open") ("analyzed") ("feedback") ("suspended") ("closed")) + "List of fallback GNATS states; not used if `states' file available.") + (defvar gnats::mode-name nil "Name of the GNATS mode.") *************** *** 189,233 **** (defvar gnats:::types nil "Alist of each type of GNATS database and its root and libdir settings.") ! (defconst gnats::fields ! (let (fields) ! (setq ! fields ! ;; Duplicate send-pr::fields, don't just include it. ! ;; is there a better way than this? ! (append (read (prin1-to-string send-pr::fields)) ! '(("Arrival-Date" nil nil text) ! ("Customer-Id") ! ("Number" nil nil number) ! ("Responsible" gnats::set-responsibles nil enum ! gnats::update-audit-trail) ! ("State" ! (("open") ("analyzed") ("feedback") ("suspended") ("closed")) ! (lambda (x) (or (cdr (assoc x gnats::state-following)) "")) ! enum gnats::update-audit-trail)))) ! ;; (setf (second (assoc "Category" fields)) 'gnats::set-categories) ! (setcar (cdr (assoc "Category" fields)) 'gnats::set-categories) ! (setcdr (nthcdr 3 (assoc "Category" fields)) ! '(gnats::update-responsible)) ! (setcdr (nthcdr 3 (assoc "Severity" fields)) ! '(gnats::update-audit-trail)) ! (setcdr (nthcdr 3 (assoc "Priority" fields)) ! '(gnats::update-audit-trail)) ! (setcar (cdr (assoc "Class" fields)) ! '(("sw-bug") ("doc-bug") ("change-request") ("support") ! ("mistaken") ("duplicate"))) ! (setcdr (assoc "Submitter-Id" fields) '(gnats::set-submitters t enum)) ! (setcdr (assoc "Customer-Id" fields) (cdr (assoc "Submitter-Id" fields))) ! fields) ! "AList of one-line PR fields and their possible values.") ! ! (defconst gnats::state-following ! '(("open" . "analyzed") ! ("analyzed" . "feedback") ! ("feedback" . "closed") ! ("suspended" . "analyzed")) ! "A list of states and possible following states (does not describe all ! possibilities).") (defvar gnats::query-pr-history nil "Past arguments passed to the query-pr program.") --- 196,212 ---- (defvar gnats:::types nil "Alist of each type of GNATS database and its root and libdir settings.") ! (defun gnats::states-sans-descriptions () ! ;; return a list of states in the usual form, that is ! ;; '(("open") ("analyzed") ...) ! ;; If you just use gnats::set-states, it would look like this: ! ;; '(("open" "descriptive text") ;; ("analyzed" "desc text...") ...) ! (mapcar 'list (mapcar 'car (gnats::set-states)))) ! ! (defun gnats::state-following (state) ! ;; Return next state, or "" if none ! (let ((lst (member (list state) (gnats::states-sans-descriptions)))) ! (or (car (car (cdr lst))) ""))) (defvar gnats::query-pr-history nil "Past arguments passed to the query-pr program.") *************** *** 1337,1342 **** --- 1316,1333 ---- "sub" "submitters") "submitters")))) + (defun gnats::set-states () + ;; Get states from states file; if no file, return hardcoded + ;; defaults. + (or gnats::states + (setq gnats::states + (gnats::get-list-from-file + (if gnats:network-server + "sta" + "states") + "states" t)) + gnats::default-states)) + (defun gnats::get-list (buffer) (let (result) (save-excursion *************** *** 1363,1390 **** (save-excursion (set-buffer buffer) (goto-char (point-min)) ! (while (re-search-forward "^[^#]" nil t) (gnats::push (gnats::parse-line) result))) (reverse result))) ! (defun gnats::get-list-from-file (filename desc) (let ((buf nil) ! (result nil)) ! (message "Parsing the %s file..." desc) ! (save-excursion ! (let ((bn gnats:::backupname)) ! (setq buf (get-buffer-create " *gnats-grok*")) ! (set-buffer buf) ! (setq buffer-read-only nil) ! (erase-buffer) ! (insert-file-contents ! (if gnats:network-server ! (concat bn "." filename) ! (format "%s/gnats-adm/%s" gnats:root filename))) ! (setq result (gnats::get-alist buf)) ! (kill-buffer buf)) ! (message "Parsing the %s file...done." desc) ! result))) (defun gnats::get-pr-category (number) "Return the category for the problem report NUMBER." --- 1354,1386 ---- (save-excursion (set-buffer buffer) (goto-char (point-min)) ! (while (re-search-forward "^[^#\n\t ]" nil t) (gnats::push (gnats::parse-line) result))) (reverse result))) ! (defun gnats::get-list-from-file (filename desc &optional noerror) ! ;; noerror means just return nil if no such file. (let ((buf nil) ! (result nil) ! (file (format "%s/gnats-adm/%s" gnats:root filename))) ! (if (and noerror (not (file-readable-p file))) ! (setq result nil) ! ;; Else ! (message "Parsing the %s file..." desc) ! (save-excursion ! (let ((bn gnats:::backupname)) ! (setq buf (get-buffer-create " *gnats-grok*")) ! (set-buffer buf) ! (setq buffer-read-only nil) ! (erase-buffer) ! (insert-file-contents ! (if gnats:network-server ! (concat bn "." filename) ! file)) ! (setq result (gnats::get-alist buf)) ! (kill-buffer buf))) ! (message "Parsing the %s file...done." desc)) ! result)) (defun gnats::get-pr-category (number) "Return the category for the problem report NUMBER." *************** *** 2044,2048 **** --- 2040,2084 ---- ; ) (kill-buffer buf) )) + + + + ;;;;------------------------------------------------------------------------;; + ;; Put this last because it's a complex constant and might depend on + ;; arbitrary things from above. + + (defconst gnats::fields + (let (fields) + (setq + fields + ;; Duplicate send-pr::fields, don't just include it. + ;; is there a better way than this? + (append (read (prin1-to-string send-pr::fields)) + '(("Arrival-Date" nil nil text)) + '(("Customer-Id")) + '(("Number" nil nil number)) + '(("Responsible" gnats::set-responsibles nil enum + gnats::update-audit-trail)) + (list + (list + "State" + (gnats::states-sans-descriptions) + (lambda (x) (gnats::state-following x)) + 'enum 'gnats::update-audit-trail)))) + ;; (setf (second (assoc "Category" fields)) 'gnats::set-categories) + (setcar (cdr (assoc "Category" fields)) 'gnats::set-categories) + (setcdr (nthcdr 3 (assoc "Category" fields)) + '(gnats::update-responsible)) + (setcdr (nthcdr 3 (assoc "Severity" fields)) + '(gnats::update-audit-trail)) + (setcdr (nthcdr 3 (assoc "Priority" fields)) + '(gnats::update-audit-trail)) + (setcar (cdr (assoc "Class" fields)) + '(("sw-bug") ("doc-bug") ("change-request") ("support") + ("mistaken") ("duplicate"))) + (setcdr (assoc "Submitter-Id" fields) '(gnats::set-submitters t enum)) + (setcdr (assoc "Customer-Id" fields) (cdr (assoc "Submitter-Id" fields))) + fields) + "AList of one-line PR fields and their possible values.") ;;;; end of gnats.el *** gnats/gnats.texi Mon Dec 15 13:29:49 1997 --- gnats/gnats.texi Thu Dec 18 16:42:51 1997 *************** *** 456,465 **** @noindent Administrative data files reside in @w{@file{@var{GNATS_ROOT}/gnats-adm}}. These include @file{categories}, @file{submitters}, @file{responsible}, ! and @file{config}, as well as two files generated and maintained by ! @sc{gnats}, @file{index} and @file{current}. @xref{Local configuration,, ! Changing your local configuration}, and @ref{Admin files,,Administrative ! data files}. @node defaults @section Default installation locations --- 456,465 ---- @noindent Administrative data files reside in @w{@file{@var{GNATS_ROOT}/gnats-adm}}. These include @file{categories}, @file{submitters}, @file{responsible}, ! @file{states}, and @file{config}, as well as two files generated ! and maintained by @sc{gnats}, @file{index} and @file{current}. ! @xref{Local configuration,, Changing your local configuration}, and ! @ref{Admin files,,Administrative data files}. @node defaults @section Default installation locations *************** *** 545,551 **** @table @var @item site The local list of valid categories, used by @code{send-pr}; @var{site} ! is the value of @samp{GNATS_SITE} (@pxref{config,,The @code{config} file}). @end table --- 545,551 ---- @table @var @item site The local list of valid categories, used by @code{send-pr}; @var{site} ! is the value of @samp{GNATS_SITE} (@pxref{config file,,The @code{config} file}). @end table *************** *** 606,611 **** --- 606,612 ---- @item categories @item submitters @item responsible + @item states @item gnatsd.conf @item index (@emph{This file is created by @sc{gnats}.}) *** gnats/gnatsd.c Mon Dec 15 13:27:07 1997 --- gnats/gnatsd.c Thu Dec 18 16:42:51 1997 *************** *** 99,107 **** { "RESP", GNATS_resp, FLAG_NEED_AUTHORIZATION }, { "CATG", GNATS_catg, FLAG_NEED_AUTHORIZATION }, { "SYNP", GNATS_synp, FLAG_NEED_AUTHORIZATION }, { "CONF", GNATS_conf, 0 }, { "SVTY", GNATS_svty, 0 }, - { "STAT", GNATS_stat, 0 }, { "ORIG", GNATS_orig, FLAG_NEED_AUTHORIZATION|FLAG_ONE_ARG }, { "RLSE", GNATS_rlse, FLAG_NEED_AUTHORIZATION }, { "CLSS", GNATS_clss, FLAG_ONE_ARG }, --- 99,107 ---- { "RESP", GNATS_resp, FLAG_NEED_AUTHORIZATION }, { "CATG", GNATS_catg, FLAG_NEED_AUTHORIZATION }, { "SYNP", GNATS_synp, FLAG_NEED_AUTHORIZATION }, + { "STAT", GNATS_stat, FLAG_NEED_AUTHORIZATION }, { "CONF", GNATS_conf, 0 }, { "SVTY", GNATS_svty, 0 }, { "ORIG", GNATS_orig, FLAG_NEED_AUTHORIZATION|FLAG_ONE_ARG }, { "RLSE", GNATS_rlse, FLAG_NEED_AUTHORIZATION }, { "CLSS", GNATS_clss, FLAG_ONE_ARG }, *************** *** 121,126 **** --- 121,127 ---- { "LCAT", GNATS_lcat, FLAG_NEED_AUTHORIZATION }, { "LRES", GNATS_lres, FLAG_NEED_AUTHORIZATION }, { "LSUB", GNATS_lsub, FLAG_NEED_AUTHORIZATION }, + { "LSTA", GNATS_lsta, FLAG_NEED_AUTHORIZATION }, /* Stop and start the database. */ { "LKDB", GNATS_lkdb, FLAG_NEED_AUTHORIZATION }, *** gnats/gnatsd.h Mon Dec 15 13:27:07 1997 --- gnats/gnatsd.h Thu Dec 18 16:42:51 1997 *************** *** 81,86 **** --- 81,87 ---- extern void GNATS_lcat PARAMS((int, char**)); extern void GNATS_lres PARAMS((int, char**)); extern void GNATS_lsub PARAMS((int, char**)); + extern void GNATS_lsta PARAMS((int, char**)); extern void GNATS_load PARAMS((int, char**)); extern void GNATS_chst PARAMS((int, char**)); extern void GNATS_chre PARAMS((int, char**)); *** gnats/lists.c Mon Dec 15 13:27:07 1997 --- gnats/lists.c Thu Dec 18 16:42:51 1997 *************** *** 60,65 **** --- 60,71 ---- strcat (outf, ".res"); infile = RESPONSIBLE_FILE; } + else if (type == LIST_STATES) + { + if (outf) + strcat (outf, ".sta"); + infile = STATES; + } else { if (outf) *** gnats/nquery-pr.c Mon Dec 15 13:27:07 1997 --- gnats/nquery-pr.c Thu Dec 18 16:42:51 1997 *************** *** 133,138 **** --- 133,139 ---- {"list-categories", 0, NULL, 'j'}, {"list-responsible", 0, NULL, 'k'}, {"list-submitters", 0, NULL, 'l'}, + {"list-states", 0, NULL, 'T'}, {"version", 0, NULL, 'V'}, {NULL, 0, NULL, 0} }; *************** *** 170,175 **** --- 171,182 ---- fprintf (stderr, "%s: writing `LSUB'\n", program_name); fprintf (serv_write, "LSUB\r\n"); } + else if (list_format == LIST_STATES) + { + if (debug) + fprintf (stderr, "%s: writing `LSTA'\n", program_name); + fprintf (serv_write, "LSTA\r\n"); + } return; } *************** *** 401,409 **** bzero ((Index *) info, sizeof (Index)); #ifdef GNATS_RELEASE_BASED ! while ((optc = getopt_long (argc, argv, "A:c:C:Dd:e:K:L:m:o:O:p:PQ:s:S:r:t:u:U:y:VFixhqH:Rjkl", #else ! while ((optc = getopt_long (argc, argv, "A:c:C:Dd:e:L:m:o:O:p:Ps:S:r:t:y:VFixhqH:Rjkl", #endif long_options, (int *) 0)) != EOF) { --- 408,416 ---- bzero ((Index *) info, sizeof (Index)); #ifdef GNATS_RELEASE_BASED ! while ((optc = getopt_long (argc, argv, "A:c:C:Dd:e:K:L:m:o:O:p:PQ:s:S:r:t:u:U:y:VFixhqH:RjklT", #else ! while ((optc = getopt_long (argc, argv, "A:c:C:Dd:e:L:m:o:O:p:Ps:S:r:t:y:VFixhqH:RjklT", #endif long_options, (int *) 0)) != EOF) { *************** *** 556,561 **** --- 563,573 ---- lists++; break; + case 'T': + list_format = LIST_STATES; + lists++; + break; + #ifdef GNATS_RELEASE_BASED case 'u': required_before_string = optarg; *************** *** 631,637 **** [--quarter=quarter] [--keywords=regexp]\n\ [--required-before=date] [--required-after=date]\n\ [--arrived-before=date] [--arrived-after=date] [--debug]\n\ ! [--list-categories] [--list-responsible] [--list-submitters]\n\ [--text=text] [--multitext=mtext] [--host=server] [--port=port] [PR] [PR]...\n", program_name); #else --- 643,650 ---- [--quarter=quarter] [--keywords=regexp]\n\ [--required-before=date] [--required-after=date]\n\ [--arrived-before=date] [--arrived-after=date] [--debug]\n\ ! [--list-categories] [--list-responsible]\n\ ! [--list-states] [--list-submitters]\n\ [--text=text] [--multitext=mtext] [--host=server] [--port=port] [PR] [PR]...\n", program_name); #else *************** *** 647,653 **** [--severity=severity] [--state=state] [--submitter=submitter]\n\ [--responsible=person] [--release=release] [--restricted]\n\ [--arrived-before=date] [--arrived-after=date] [--debug]\n\ ! [--list-categories] [--list-responsible] [--list-submitters]\n\ [--text=text] [--multitext=mtext] [--host=server] [--port=port] [PR] [PR]...\n", program_name); #endif --- 660,667 ---- [--severity=severity] [--state=state] [--submitter=submitter]\n\ [--responsible=person] [--release=release] [--restricted]\n\ [--arrived-before=date] [--arrived-after=date] [--debug]\n\ ! [--list-categories] [--list-responsible]\n\ ! [--list-states] [--list-submitters]\n\ [--text=text] [--multitext=mtext] [--host=server] [--port=port] [PR] [PR]...\n", program_name); #endif *** gnats/p-admin.texi Mon Dec 15 13:29:50 1997 --- gnats/p-admin.texi Thu Dec 18 16:42:51 1997 *************** *** 43,49 **** to every machine on your network that has @code{send-pr} installed, and make sure you advise remote submitters that the category list has changed. @xref{mkcat,,Adding a problem category}. Also ! @ref{categories,,The @code{categories} file}. @item removing categories @cindex removing a problem category --- 43,49 ---- to every machine on your network that has @code{send-pr} installed, and make sure you advise remote submitters that the category list has changed. @xref{mkcat,,Adding a problem category}. Also ! @ref{categories file,,The @code{categories} file}. @item removing categories @cindex removing a problem category *************** *** 67,78 **** to every machine on your network that has @code{send-pr} installed, and make sure you advise remote submitters that the category list has changed. @xref{rmcat,,Removing a problem category}. Also ! @ref{categories,,The @code{categories} file}. @item adding and removing maintainers @cindex adding and removing maintainers Edit the @file{responsible} file to add a new maintainer or to remove an ! existing maintainer. @xref{responsible,,The @code{responsible} file}. @item building a distribution of @code{send-pr} @cindex building a distribution of @code{send-pr} --- 67,78 ---- to every machine on your network that has @code{send-pr} installed, and make sure you advise remote submitters that the category list has changed. @xref{rmcat,,Removing a problem category}. Also ! @ref{categories file,,The @code{categories} file}. @item adding and removing maintainers @cindex adding and removing maintainers Edit the @file{responsible} file to add a new maintainer or to remove an ! existing maintainer. @xref{responsible file,,The @code{responsible} file}. @item building a distribution of @code{send-pr} @cindex building a distribution of @code{send-pr} *************** *** 133,139 **** the other hosts. This categories list is @w{@file{@var{prefix}/share/gnats/@var{site}}}, where @var{site} is the name tag for your local site, as specified in the @file{config} file as ! @samp{GNATS_SITE} (@pxref{config,,The @code{config} file}). It is also important to note that only your local @code{send-pr} has access to this new information; any copies of @code{send-pr} which you --- 133,139 ---- the other hosts. This categories list is @w{@file{@var{prefix}/share/gnats/@var{site}}}, where @var{site} is the name tag for your local site, as specified in the @file{config} file as ! @samp{GNATS_SITE} (@pxref{config file,,The @code{config} file}). It is also important to note that only your local @code{send-pr} has access to this new information; any copies of @code{send-pr} which you *************** *** 164,170 **** @table @code @cindex @code{config} file @item config ! Variables which control certain behavior. @xref{config,,The @code{config} file}. Behaviors you can change here include @itemize @bullet --- 164,170 ---- @table @code @cindex @code{config} file @item config ! Variables which control certain behavior. @xref{config file,,The @code{config} file}. Behaviors you can change here include @itemize @bullet *************** *** 191,197 **** @item Whether or not to remind maintainers if a requisite time period has passed before they change the state of a Problem Report to ! @samp{analyzed}. (Also see @ref{submitters,,The @code{submitters} file}, and @ref{at-pr,,Timely Reminders}. @item --- 191,197 ---- @item Whether or not to remind maintainers if a requisite time period has passed before they change the state of a Problem Report to ! @samp{analyzed}. (Also see @ref{submitters file,,The @code{submitters} file}, and @ref{at-pr,,Timely Reminders}. @item *************** *** 224,230 **** category. Update this file whenever you have a new category, or whenever a category is no longer valid. You must also update this file whenever responsiblility for a category changes, or if a maintainer is ! no longer valid. @xref{categories,,The @code{categories} file}. Also see @ref{mkcat,,Adding a new problem category}, and @ref{rmcat,,Removing a problem category}. --- 224,230 ---- category. Update this file whenever you have a new category, or whenever a category is no longer valid. You must also update this file whenever responsiblility for a category changes, or if a maintainer is ! no longer valid. @xref{categories file,,The @code{categories} file}. Also see @ref{mkcat,,Adding a new problem category}, and @ref{rmcat,,Removing a problem category}. *************** *** 232,244 **** @item responsible The list of maintainers. Update this file whenever you have a new maintainer, or whenever a maintainer is no longer valid. ! @xref{responsible,,The @code{responsible} file}. @cindex @code{submitters} file @item submitters The list of Submitter Sites from whom @sc{gnats} accepts Problem Reports. This file is mandatory, although the feature it provides is not; see ! @ref{submitters,,The @code{submitters} file}. @cindex @code{addresses} file @item addresses --- 232,244 ---- @item responsible The list of maintainers. Update this file whenever you have a new maintainer, or whenever a maintainer is no longer valid. ! @xref{responsible file,,The @code{responsible} file}. @cindex @code{submitters} file @item submitters The list of Submitter Sites from whom @sc{gnats} accepts Problem Reports. This file is mandatory, although the feature it provides is not; see ! @ref{submitters file,,The @code{submitters} file}. @cindex @code{addresses} file @item addresses *************** *** 250,260 **** @menu * default behavior:: ! * config:: The `config' file ! * categories:: The `categories' file ! * responsible:: The `responsible' file ! * submitters:: The `submitters' file ! * addresses:: The `addresses' file @end menu @node default behavior --- 250,261 ---- @menu * default behavior:: ! * config file:: ! * categories file:: ! * responsible file:: ! * submitters file:: ! * states file:: ! * addresses file:: @end menu @node default behavior *************** *** 313,319 **** @end itemize ! @node config @subsection The @code{config} file @cindex @code{config} file --- 314,320 ---- @end itemize ! @node config file @subsection The @code{config} file @cindex @code{config} file *************** *** 395,401 **** @ref{at-pr,,Timely Reminders}. This requisite time is determined for each submitter individually; see ! @ref{submitters,,The @code{submitters} file}. The time is measured in @dfn{business hours}, which by default are 8:00am to 5:00pm, Monday through Friday. Business hours can be redefined by changing the variables @code{BDAY_START}, @code{BDAY_END}, @code{BWEEK_START}, and --- 396,402 ---- @ref{at-pr,,Timely Reminders}. This requisite time is determined for each submitter individually; see ! @ref{submitters file,,The @code{submitters} file}. The time is measured in @dfn{business hours}, which by default are 8:00am to 5:00pm, Monday through Friday. Business hours can be redefined by changing the variables @code{BDAY_START}, @code{BDAY_END}, @code{BWEEK_START}, and *************** *** 439,445 **** @item DEFAULT_SUBMITTER="submitter-id" The value @sc{gnats} assigns to PRs which come in with missing or unknown values for the @samp{>Submitter-Id:} field. This value must also appear ! in the @file{submitters} file; see @ref{submitters,,The @code{submitters} file}. @cindex disabling @var{submitter-id} --- 440,446 ---- @item DEFAULT_SUBMITTER="submitter-id" The value @sc{gnats} assigns to PRs which come in with missing or unknown values for the @samp{>Submitter-Id:} field. This value must also appear ! in the @file{submitters} file; see @ref{submitters file,,The @code{submitters} file}. @cindex disabling @var{submitter-id} *************** *** 513,519 **** @end table ! @node categories @subsection The @code{categories} file @cindex @code{categories} file --- 514,520 ---- @end table ! @node categories file @subsection The @code{categories} file @cindex @code{categories} file *************** *** 566,572 **** @item responsible The name tag of the party responsible for this category of problems, as ! listed in the @file{responsible} file (@pxref{responsible,,The @code{responsible} file}). @item notify --- 567,573 ---- @item responsible The name tag of the party responsible for this category of problems, as ! listed in the @file{responsible} file (@pxref{responsible file,,The @code{responsible} file}). @item notify *************** *** 591,597 **** In the above example, the nametags @samp{myboss}, @samp{me}, @samp{fred}, and @samp{barney} must be defined in the @file{responsible} ! file (@pxref{responsible,,The @code{responsible} file}). Problem Reports with a category of @samp{doc} are sent to the local mail address (or alias) @samp{myboss}, and also sent to the addresses --- 592,598 ---- In the above example, the nametags @samp{myboss}, @samp{me}, @samp{fred}, and @samp{barney} must be defined in the @file{responsible} ! file (@pxref{responsible file,,The @code{responsible} file}). Problem Reports with a category of @samp{doc} are sent to the local mail address (or alias) @samp{myboss}, and also sent to the addresses *************** *** 617,623 **** @w{@samp{make install}} (@pxref{Configure and make,,Configuring and compiling the software}). ! @node responsible @subsection The @code{responsible} file @cindex @code{responsible} file --- 618,624 ---- @w{@samp{make install}} (@pxref{Configure and make,,Configuring and compiling the software}). ! @node responsible file @subsection The @code{responsible} file @cindex @code{responsible} file *************** *** 665,671 **** (@code{gnats-admin} is a mail alias, so for this purpose @code{gnats-admin} is a local address.) ! @node submitters @subsection The @code{submitters} file @cindex @code{submitters} file --- 666,672 ---- (@code{gnats-admin} is a mail alias, so for this purpose @code{gnats-admin} is a local address.) ! @node submitters file @subsection The @code{submitters} file @cindex @code{submitters} file *************** *** 712,720 **** @item contact The name tag of the main @dfn{contact} at the Support Site for this submitter. This contact should be in the @file{responsible} file ! (@pxref{responsible,,The @code{responsible} file}). Incoming bugs from ! @var{submitter} are sent to this contact. Optionally, this field can be ! left blank. @item notify Any other parties who should receive copies of Problem Reports sent in --- 713,721 ---- @item contact The name tag of the main @dfn{contact} at the Support Site for this submitter. This contact should be in the @file{responsible} file ! (@pxref{responsible file,,The @code{responsible} file}). Incoming bugs ! from @var{submitter} are sent to this contact. Optionally, this field ! can be left blank. @item notify Any other parties who should receive copies of Problem Reports sent in *************** *** 745,754 **** @samp{>Submitter-Id:}, simply alter the @file{submitters} file to only contain the @var{submitter-id} value which appears as the @samp{DEFAULT_SUBMITTER} value in the @file{config} file ! (@pxref{config,,The @code{config} file}), and instruct your submitters ! to ignore the field. ! @node addresses @subsection The @code{addresses} file @cindex @code{addresses} file --- 746,791 ---- @samp{>Submitter-Id:}, simply alter the @file{submitters} file to only contain the @var{submitter-id} value which appears as the @samp{DEFAULT_SUBMITTER} value in the @file{config} file ! (@pxref{config file,,The @code{config} file}), and instruct your ! submitters to ignore the field. ! @node states file ! @subsection The @code{states} file ! @cindex @code{states} file ! ! This file lists the possible states for Problem Reports. Each line ! consists of a state, followed optionally by colon and a one-line ! description of what the state means. Lines beginning with @samp{#} will ! be ignored. ! ! @smallexample ! @var{state}: @var{optional descriptive text} ! @end smallexample ! ! State names can contain any alphanumeric character, "-" (hyphen), "_" ! (underscore), or "." (period), but no other characters. The state name ! ends with a newline, or else with a colon followed by optional ! descriptive text. The descriptive text, if present, can contain any ! character except newline, which marks the end of the description. Empty ! or all-whitespace descriptions are allowed (though it's hard to imagine ! why one would want such a thing). Even though @sc{gnats} does not ! currently use this descriptive text, other external tools may, so you ! probably still want to include it. ! ! The first state listed will be the state automatically assigned to ! Problem Reports when they arrive; by default this is named "open". The ! last state listed is the end state for Problem Reports --- one should ! usually assume that a PR in this state is not being actively worked on; ! by default this state is named "closed". ! ! It is probably best to leave "open" as the first state and "closed" as ! the last, otherwise some external tools looking for those two states by ! name may be fooled. ! ! If the @file{states} file cannot be read, or contains formatting errors, ! @sc{gnats} uses the states described in @ref{States,,States}. ! ! @node addresses file @subsection The @code{addresses} file @cindex @code{addresses} file *************** *** 887,893 **** @item Add a line to the @file{categories} file under @w{@file{@var{GNATS_ROOT}/gnats-adm}} for each new category. ! @xref{categories,,The @code{categories} file}. @item Run @code{mkcat}. @code{mkcat} creates a directory under --- 924,930 ---- @item Add a line to the @file{categories} file under @w{@file{@var{GNATS_ROOT}/gnats-adm}} for each new category. ! @xref{categories file,,The @code{categories} file}. @item Run @code{mkcat}. @code{mkcat} creates a directory under *************** *** 1146,1152 **** If the @samp{>Category:} field does not contain a valid category value (i.e., matching a line in the @code{categories} file; ! @pxref{categories,,The @code{categories} file}), the PR is assigned to the default category, as set in the @code{config} file. If there is no default category defined, the PR is given a @samp{>Category:} value of @samp{pending} and is placed in the --- 1183,1189 ---- If the @samp{>Category:} field does not contain a valid category value (i.e., matching a line in the @code{categories} file; ! @pxref{categories file,,The @code{categories} file}), the PR is assigned to the default category, as set in the @code{config} file. If there is no default category defined, the PR is given a @samp{>Category:} value of @samp{pending} and is placed in the *************** *** 1157,1165 **** files it in the @sc{gnats} database (under the default, if the @samp{>Category:} field contains an invalid category), and sends acknowledgements to appropriate parties. The person responsible for ! that category of problem (@pxref{categories,,The @code{categories} file}) and the person responsible for the submitter site where the PR ! originated (@pxref{submitters,,The @code{submitters} file}) receive a copy of the PR in its entirety. Optionally, the originator of the PR receives an acknowledgement that the PR arrived and was filed (@pxref{Local configuration,,Changing your local configuration}). --- 1194,1202 ---- files it in the @sc{gnats} database (under the default, if the @samp{>Category:} field contains an invalid category), and sends acknowledgements to appropriate parties. The person responsible for ! that category of problem (@pxref{categories file,,The @code{categories} file}) and the person responsible for the submitter site where the PR ! originated (@pxref{submitters file,,The @code{submitters} file}) receive a copy of the PR in its entirety. Optionally, the originator of the PR receives an acknowledgement that the PR arrived and was filed (@pxref{Local configuration,,Changing your local configuration}). *************** *** 1211,1221 **** has changed from @samp{open}. The @file{submitters} file contains the response time for each ! @w{@code{>Submitter-Id:}} (@pxref{submitters,,The @code{submitters} file}). ! The time is determined in @dfn{business hours}, which are defined by ! default as 8:00am to 5:00pm Monday through Friday, local time. These ! hours are defined in the @file{config} file (@pxref{config,,The ! @code{config} file}). If the PR is still open after the requisite time period has passed, @code{at-pr} sends a reminder to the @sc{gnats} administrator, to the --- 1248,1258 ---- has changed from @samp{open}. The @file{submitters} file contains the response time for each ! @w{@code{>Submitter-Id:}} (@pxref{submitters file,,The @code{submitters} ! file}). The time is determined in @dfn{business hours}, which are ! defined by default as 8:00am to 5:00pm Monday through Friday, local ! time. These hours are defined in the @file{config} file (@pxref{config ! file,,The @code{config} file}). If the PR is still open after the requisite time period has passed, @code{at-pr} sends a reminder to the @sc{gnats} administrator, to the *************** *** 1341,1347 **** @cindex @code{pr-addr} @code{pr-addr} returns an electronic mail address when given a valid @dfn{nametag}, as ! it appears in the @file{responsible} file (@pxref{responsible,,The @code{responsible} file}). If @var{nametag} is not valid, @code{pr-addr} will tell the user that it could not find the requested address. --- 1378,1384 ---- @cindex @code{pr-addr} @code{pr-addr} returns an electronic mail address when given a valid @dfn{nametag}, as ! it appears in the @file{responsible} file (@pxref{responsible file,,The @code{responsible} file}). If @var{nametag} is not valid, @code{pr-addr} will tell the user that it could not find the requested address. *** gnats/p-inst.texi Mon Dec 15 13:27:07 1997 --- gnats/p-inst.texi Thu Dec 18 16:42:51 1997 *************** *** 420,426 **** @cindex alias for your @emph{site} Create an alias for your site. This alias should be the same nametag indicated by the variable @w{@samp{GNATS_SITE}} in the file ! @w{@file{@var{GNATS_ROOT}/gnats-adm/config}} (@pxref{config,,The @code{config} file}), with an added suffix @samp{-gnats}. This alias, @w{@samp{@var{GNATS_SITE}-gnats}}, should point toward the local submission address. For instance, if your site is Tofu Technologies, --- 420,426 ---- @cindex alias for your @emph{site} Create an alias for your site. This alias should be the same nametag indicated by the variable @w{@samp{GNATS_SITE}} in the file ! @w{@file{@var{GNATS_ROOT}/gnats-adm/config}} (@pxref{config file,,The @code{config} file}), with an added suffix @samp{-gnats}. This alias, @w{@samp{@var{GNATS_SITE}-gnats}}, should point toward the local submission address. For instance, if your site is Tofu Technologies, *************** *** 641,647 **** @var{prefix}). This is the list of valid categories that @w{@code{send-pr}} uses (@pxref{Locations,,Where @sc{gnats} lives}). @var{site} is your local site, the value of @w{@samp{GNATS_SITE}} in the ! @file{config} file (@pxref{config,,The @code{config} file}). @end enumerate --- 641,647 ---- @var{prefix}). This is the list of valid categories that @w{@code{send-pr}} uses (@pxref{Locations,,Where @sc{gnats} lives}). @var{site} is your local site, the value of @w{@samp{GNATS_SITE}} in the ! @file{config} file (@pxref{config file,,The @code{config} file}). @end enumerate *** gnats/p-usage.texi Mon Dec 15 13:29:49 1997 --- gnats/p-usage.texi Thu Dec 18 16:42:51 1997 *************** *** 462,468 **** [ -F | --full ] [ -q | --summary ] [ -i | --sql ] [ -P | --print-path ] [ -d @var{directory} | --directory=@var{directory} ] ! [ -o @var{outfile} | --output=@var{outfile} ] [ -V | --version ] [ -h | --help ] @end smallexample --- 462,471 ---- [ -F | --full ] [ -q | --summary ] [ -i | --sql ] [ -P | --print-path ] [ -d @var{directory} | --directory=@var{directory} ] ! [ -j | --list-categories ] ! [ -k | --list-responsible ] ! [ -l | --list-submitters ] ! [ -T | --list-states ] [ -V | --version ] [ -h | --help ] @end smallexample *************** *** 889,904 **** non-critical 3 low 3 >State: >Class: ! open 1 sw-bug 1 ! analyzed 2 doc-bug 2 ! suspended 3 support 3 ! feedback 4 change-request 4 ! closed 5 mistaken 5 ! duplicate 6 @end smallexample This makes sorting on these values easy, when combined with @code{sort}. It is left as an exercise for the reader to figure out how to do this. @ignore @c it works something like... --- 892,915 ---- non-critical 3 low 3 >State: >Class: ! (unknown) 0 sw-bug 1 ! open 1 doc-bug 2 ! analyzed 2 support 3 ! suspended 3 change-request 4 ! feedback 4 mistaken 5 ! closed 5 duplicate 6 @end smallexample This makes sorting on these values easy, when combined with @code{sort}. It is left as an exercise for the reader to figure out how to do this. + + Note that the mapping of states to numbers could be different at your + site, if the @sc{gnats} administrator has changed the list of possible + states (see @pxref{states file,,The @file{states} file}). The state + ``0'' simply means @sc{gnats} does not recognize this PR's state. This + situation can arise when a PR is assigned a state which is later removed + from the list of possible states --- the PR retains the state, but + @sc{gnats} no longer knows what that state means. @ignore @c it works something like... *** gnats/pcodes.h Mon Dec 15 13:27:07 1997 --- gnats/pcodes.h Thu Dec 18 16:42:51 1997 *************** *** 35,40 **** --- 35,41 ---- #define CODE_NO_KERBEROS 450 #define CODE_AUTH_TYPE_UNSUP 451 #define CODE_INVALID_SUBMITTER 460 + #define CODE_INVALID_STATE 461 #define CODE_INVALID_RESPONSIBLE 465 #define CODE_INVALID_DATE 468 #define CODE_NO_INDEX 470 *** gnats/pr-addr.c Mon Dec 15 13:27:07 1997 --- gnats/pr-addr.c Thu Dec 18 16:42:51 1997 *************** *** 26,31 **** --- 26,34 ---- void usage (), version (); + /* If 1, we're running the daemon. */ + int is_daemon = 0; + struct option long_options[] = { {"version", 0, NULL, 'V'}, *** gnats/pr-stat.c Mon Dec 15 13:27:07 1997 --- gnats/pr-stat.c Thu Dec 18 16:42:51 1997 *************** *** 144,150 **** char *path = (char *) xmalloc (PATH_MAX); for (i = index_chain; i ; i = i->next) ! if (strcmp (i->category, c) == 0 && strcmp (i->state, "closed") == 0) { sprintf (path, "%s/%s", c, i->number); do_stat (path); --- 144,151 ---- char *path = (char *) xmalloc (PATH_MAX); for (i = index_chain; i ; i = i->next) ! if ((strcmp (i->category, c) == 0) ! && (strcmp (i->state, pr[STATE].final_value)) == 0) { sprintf (path, "%s/%s", c, i->number); do_stat (path); *** gnats/pr.c Mon Dec 15 13:29:51 1997 --- gnats/pr.c Thu Dec 18 16:42:51 1997 *************** *** 279,284 **** --- 279,492 ---- } } + char * + get_pr_states () + { + char *path = (char *) xmalloc (PATH_MAX); + char *states; + /* Init len to something > 4, due to assumptions below. */ + int len = 0, capacity = 80; + int meaningful_line; + FILE *fp; + char c; + /* At last resort, use the old, hardcoded GNATS states: */ + char *default_states = "open | analyzed | feedback | suspended | closed"; + + sprintf (path, "%s/gnats-adm/%s", gnats_root, STATES); + fp = fopen (path, "r"); + + if (fp == NULL) + return default_states; + + /* else */ + + states = xmalloc (capacity); + meaningful_line = 0; + while ((c = getc (fp)) != EOF) + { + /* '#' at the beginning of a line is the comment character */ + if ((meaningful_line == 0) && (c == '#')) + while (((c = getc (fp)) != '\n') && (c != EOF)) + /* eat until end of line */ + ; + + /* Also ignore all whitespace lines, because it's pointless + hassle for the user if we die on them. */ + if ((meaningful_line == 0) && ((c == ' ') || (c == '\t'))) + { + while (((c = getc (fp)) == ' ') + || (c == '\t')) + /* eat until the whitespace is gone */ + ; + if ((c != '\n') && (c != EOF)) + { + punt (0, "unexpected leading whitespace in %s", path); + return default_states; + } + } + + /* Skip empty lines too. */ + if ((meaningful_line == 0) && (c == '\n')) + continue; + + /* No way can ':' be the first character on a line. */ + if ((meaningful_line == 0) && (c == ':')) + { + punt (0, "empty state name in %s", path); + return default_states; + } + + /* There may be a one-line description after the state, but we + ignore that right now. */ + if ((meaningful_line == 1) && (c == ':')) + while (((c = getc (fp)) != '\n') && (c != EOF)) + /* eat until end of line */ + ; + + /* Check again -- we might have read EOF during one of the inner + `while' loops above. */ + if (c == EOF) + { + c = '\n'; + ungetc (EOF, fp); + } + + /* At last admit that we might be onto something. */ + meaningful_line = 1; + + /* If we've finished reading a state name, delimit it with what + the caller expects */ + if (c == '\n') + { + if (len >= (capacity - 4)) + states = xrealloc (states, (capacity *= 2)); + + /* caller separates states with " | " */ + states[len++] = ' '; + states[len++] = '|'; + states[len++] = ' '; + + /* We're done with this line. */ + meaningful_line = 0; + } + else if (! (((c >= '0') && (c <= '9')) + || ((c >= 'A') && (c <= 'Z')) + || ((c >= 'a') && (c <= 'z')) + || (c == '-') + || (c == '_') + || (c == '.'))) + { + /* It seems wise to enforce such restrictions starting now, + * even though at this time we only have concrete reasons to + * prohibit ctrl and 8-bit chars, '/', ':', '|', and '#'. + */ + punt (0, "illegal character `%c' in state name, in %s", c, path); + return default_states; + } + else /* it's an allowable character for a state name */ + { + if (len >= (capacity - 2)) + states = xrealloc (states, (capacity *= 2)); + + states[len++] = c; + } + } + fclose (fp); + + /* Null terminate only after cleaning trailing garbage. */ + while ((states[len - 1] == ' ') || (states[len - 1] == '|')) + states[--len] = '\0'; + + states[len] = '\0'; + + /* todo: necessary to take the last state and make it play the role + of "closed" -- that is, remember to replace hardcoded "closed" + wherever it appears in the code. */ + + if ((states == NULL) || (states[0] == '\0')) + return default_states; + + /* else */ + + return states; + } + + + /* Get the first state from a pipe separated list. + * For example "open | analyzed | ... " ==> "open" + * + * result is malloc'd. + * + * Return "open" if unable to determine first state from list. + */ + char * + get_default_state (list_str) + char *list_str; + { + char *res; + char *end; + + if (list_str == NULL) + return "open"; + + /* else */ + + end = strchr (list_str, ' '); + + if (end == NULL) + return strdup (list_str); + + /* else */ + + res = xmalloc ((end - list_str) + 1); + strncpy (res, list_str, (end - list_str)); + res[(end - list_str)] = '\0'; + + return res; + } + + /* Get the last state from a pipe separated list. + * For example "... | suspended | closed" ==> "closed" + * + * result is malloc'd. + * + * Return "closed" if unable to determine final state from list. + */ + char * + get_final_state (list_str) + char *list_str; + { + char *res = NULL; + char *tmp, *end; + + if (list_str == NULL) + return "closed"; + + res = tmp = list_str; + while ((tmp != NULL) && ((tmp = strchr (tmp, '|')) != NULL)) + { + tmp++; + res = tmp; + } + + if ((res == NULL) || (res[0] == '\0')) + return "closed"; + + /* else */ + + /* inch past the space */ + if (res[0] == ' ') + res++; + + if (res[0] == '\0') + return "closed"; + + /* else */ + + return strdup (res); + } + + /* Init all the fields in the PR. */ void init_pr () *************** *** 364,371 **** State-Changed-Why:"; pr[STATE].name = STATE_STRING; ! pr[STATE].enum_values = "open | analyzed | feedback | suspended | closed"; ! pr[STATE].default_value = "open"; pr[STATE].datatype = Enum; pr[CLASS].name = CLASS_STRING; --- 572,580 ---- State-Changed-Why:"; pr[STATE].name = STATE_STRING; ! pr[STATE].enum_values = get_pr_states (); ! pr[STATE].default_value = get_default_state (pr[STATE].enum_values); ! pr[STATE].final_value = get_final_state (pr[STATE].enum_values); pr[STATE].datatype = Enum; pr[CLASS].name = CLASS_STRING; *** gnats/pr.h Mon Dec 15 13:27:07 1997 --- gnats/pr.h Thu Dec 18 16:42:51 1997 *************** *** 65,70 **** --- 65,72 ---- char *value; /* default value for the field */ char *default_value; + /* the last value of an ordered enum (i.e., "closed" for PR states) */ + char *final_value; /* if variant is enum, what are the values */ char *enum_values; /* type of data that will be accepted */ *** gnats/query-pr.c Mon Dec 15 13:29:49 1997 --- gnats/query-pr.c Thu Dec 18 16:42:51 1997 *************** *** 71,76 **** --- 71,77 ---- {"list-categories", 0, NULL, 'j'}, {"list-responsible", 0, NULL, 'k'}, {"list-submitters", 0, NULL, 'l'}, + {"list-states", 0, NULL, 'T'}, {"version", 0, NULL, 'V'}, {NULL, 0, NULL, 0} }; *************** *** 131,137 **** { if (do_pr_internal (path, p) && ! (skip_closed ! && strcmp (pr[STATE].value, "closed") == 0)) { found = 1; print_pr (path, p, 1); --- 132,138 ---- { if (do_pr_internal (path, p) && ! (skip_closed ! && strcmp (pr[STATE].value, pr[STATE].final_value) == 0)) { found = 1; print_pr (path, p, 1); *************** *** 164,172 **** memset (s, 0, sizeof (Index)); #ifdef GNATS_RELEASE_BASED ! while ((optc = getopt_long (argc, argv, "A:a:B:b:c:C:D:d:e:K:L:M:m:o:O:p:PQ:s:S:r:t:u:U:y:VFixhqRjkl", #else ! while ((optc = getopt_long (argc, argv, "A:a:B:b:c:C:D:d:e:K:L:M:m:o:O:p:Ps:S:r:t:u:U:y:VFixhqRjkl", #endif long_options, (int *) 0)) != EOF) { --- 165,173 ---- memset (s, 0, sizeof (Index)); #ifdef GNATS_RELEASE_BASED ! while ((optc = getopt_long (argc, argv, "A:a:B:b:c:C:D:d:e:K:L:M:m:o:O:p:PQ:s:S:r:t:u:U:y:VFixhqRjklT", #else ! while ((optc = getopt_long (argc, argv, "A:a:B:b:c:C:D:d:e:K:L:M:m:o:O:p:Ps:S:r:t:u:U:y:VFixhqRjklT", #endif long_options, (int *) 0)) != EOF) { *************** *** 348,353 **** --- 349,359 ---- lists++; break; + case 'T': + list_format = LIST_STATES; + lists++; + break; + #ifdef GNATS_RELEASE_BASED case 'u': required_before = get_date (optarg, NULL); *************** *** 464,470 **** [--arrived-before=date] [--arrived-after=date]\n\ [--modified-before=date] [--modified-after=date]\n\ [--severity=severity] [--state=state] [--submitter=submitter]\n\ ! [--list-categories] [--list-responsible] [--list-submitters]\n\ [--synopsis=synopsis] [--text=text] [--multitext=mtext] [PR] [PR]...\n", program_name); #else --- 470,477 ---- [--arrived-before=date] [--arrived-after=date]\n\ [--modified-before=date] [--modified-after=date]\n\ [--severity=severity] [--state=state] [--submitter=submitter]\n\ ! [--list-categories] [--list-responsible]\n\ ! [--list-states] [--list-submitters]\n\ [--synopsis=synopsis] [--text=text] [--multitext=mtext] [PR] [PR]...\n", program_name); #else *************** *** 479,485 **** [--responsible=person] [--release=release] [--restricted]\n\ [--arrived-before=date] [--arrived-after=date]\n\ [--severity=severity] [--state=state] [--submitter=submitter]\n\ ! [--list-categories] [--list-responsible] [--list-submitters]\n\ [--synopsis=synopsis] [--text=text] [--multitext=mtext] [PR] [PR]...\n", program_name); #endif --- 486,493 ---- [--responsible=person] [--release=release] [--restricted]\n\ [--arrived-before=date] [--arrived-after=date]\n\ [--severity=severity] [--state=state] [--submitter=submitter]\n\ ! [--list-categories] [--list-responsible]\n\ ! [--list-states] [--list-submitters]\n\ [--synopsis=synopsis] [--text=text] [--multitext=mtext] [PR] [PR]...\n", program_name); #endif *** gnats/query.c Mon Dec 15 13:29:49 1997 --- gnats/query.c Thu Dec 18 16:43:56 1997 *************** *** 35,40 **** --- 35,85 ---- return str; } + /* Return the numeric equivalent of this state; 0 if not available. */ + int + state_numeric (state) + char *state; + { + char *states; + char *idx, *last_idx; + int count, slen, llen; + + /* Init everybody and perform trivial checks. */ + count = 0; + if ((state == NULL) || (state[0] == '\0')) + return 0; + /* else */ + states = pr[STATE].enum_values; /* gets us a '|'-separated list. */ + if ((states == NULL) || (states[0] == '\0')) + return 0; /* don't enforce format of states here */ + /* else */ + slen = strlen (state); + llen = strlen (states); + + for (idx = states; idx != NULL; idx = strchr (idx, '|')) + { + /* Inch past the bar and its trailing space; but don't do this + if we're still on the first state. */ + if (count > 0) + idx += 2; + + /* Return 0 if match impossible. */ + llen -= (idx - last_idx); + if (slen > llen) + return 0; + + if ((strncmp (idx, state, slen) == 0) + && ((idx[slen] == ' ') || (idx[slen] == '\0'))) + return (count + 1); /* no o-b-o-e playing here! */ + else + count++; + + last_idx = idx; + } + + return 0; + } + int sql_types (p, type) char *p; *************** *** 59,74 **** return 3; break; case State: ! if (tolower(*p) == 'o') ! return 1; ! else if (tolower(*p) == 'a') ! return 2; ! else if (tolower(*p) == 's') ! return 3; ! else if (tolower(*p) == 'f') ! return 4; ! else if (tolower(*p) == 'c') ! return 5; break; case Class: if (tolower(p[1]) == 'w') /* sw-bug */ --- 104,110 ---- return 3; break; case State: ! return state_numeric (p); break; case Class: if (tolower(p[1]) == 'w') /* sw-bug */ *************** *** 301,307 **** Index *s; Index *i; { ! if (skip_closed && strcasecmp (i->state, "closed") == 0) return 0; if (!s || !searching) return 1; return (!s->category || (regcmp (s->category, i->category) == 0)) && (!s->submitter || (regcmp (s->submitter, i->submitter) == 0)) --- 337,344 ---- Index *s; Index *i; { ! if (skip_closed && strcasecmp (i->state, pr[STATE].final_value) == 0) ! return 0; if (!s || !searching) return 1; return (!s->category || (regcmp (s->category, i->category) == 0)) && (!s->submitter || (regcmp (s->submitter, i->submitter) == 0)) *** gnats/query.h Mon Dec 15 13:29:49 1997 --- gnats/query.h Thu Dec 18 16:42:51 1997 *************** *** 33,38 **** --- 33,40 ---- #define LIST_SUBMITTERS (1<<2) /* Query the list of responsible. */ #define LIST_RESPONSIBLE (1<<3) + /* Query the list of states. */ + #define LIST_STATES (1<<4) #define MAXLINE 512 *** gnats/states Wed Dec 31 18:00:00 1969 --- gnats/states Thu Dec 18 16:42:51 1997 *************** *** 0 **** --- 1,46 ---- + # Possible states for a PR. + # + # Any line which begins with a `#' is considered a comment, and GNATS + # will ignore it. + # + # Each entry has the format: + # + # state[:description] + # + # that is, either of the following is okay: + # + # feedback + # or + # feedback: Problem solved, now awaiting originator's reaction to fix. + # + # + # * `state' is the name of the state; it can contain alphanumerics, + # "-", "_", and ".", but no other characters. + # + # * `description' is an optional one-line description of what this + # state means. Any character is okay in the description; a + # newline ends it, however. GNATS does not currently use the + # description, but certain external tools may look for it, so it's + # a good idea include one for every state. + # + # The first listed state is the default state for an incoming Problem + # Report. The last listed state is considered the final ("closed") + # state for a Problem Report -- once a PR is in this state, its + # life-cycle is usually over unless someone re-opens it. + + # It is recommended that the first state always be "open", as some + # external tools may look for this to determine whether or not a PR + # has been acted on. Change at your own risk. + open: Default state for a new problem report. + + # The middle states are what we think is useful; customize them if you + # wish. You can add or delete states here, it is not required that + # there be three of them. + analyzed: Problem examined, understood; difficulty of solution estimated. + feedback: Problem solved, now awaiting originator's reaction to fix. + suspended: No solution yet, work on it also suspended for the time being. + + # Where old PRs go to die. Like "open", there may be external tools + # which look for this state to know if a PR is still active. Change + # at your own risk. + closed: This PR no longer active; it is resolved or otherwise defunct. *** send-pr/Makefile.in Mon Dec 15 13:27:06 1997 --- send-pr/Makefile.in Thu Dec 18 16:42:51 1997 *************** *** 62,68 **** TEXIDIR = $(srcdir)/../texinfo # Where to find sundry TeX/Texinfo files ! TEXINPUTS = $(srcdir):$(srcdir)/../send-pr:$(TEXIDIR) MAKEINFO = makeinfo TEXI2DVI = texi2dvi --- 62,68 ---- TEXIDIR = $(srcdir)/../texinfo # Where to find sundry TeX/Texinfo files ! TEXINPUTS = $(srcdir):$(srcdir)/../gnats:$(srcdir)/../send-pr:$(TEXIDIR) MAKEINFO = makeinfo TEXI2DVI = texi2dvi *** send-pr/fields.texi Mon Dec 15 13:27:06 1997 --- send-pr/fields.texi Thu Dec 18 16:42:52 1997 *************** *** 226,232 **** (@sc{MultiText}) The originator's organization. The default value is set with the variable @w{@code{DEFAULT_ORGANIZATION}} in the @ifclear SENDPR ! @file{config} file (@pxref{config,,The @code{config} file}). @end ifclear @ifset SENDPR @code{send-pr} shell script. --- 226,232 ---- (@sc{MultiText}) The originator's organization. The default value is set with the variable @w{@code{DEFAULT_ORGANIZATION}} in the @ifclear SENDPR ! @file{config} file (@pxref{config file,,The @code{config} file}). @end ifclear @ifset SENDPR @code{send-pr} shell script. *************** *** 314,320 **** the problem lies. The values for this field are defined by the Support Site. @ifclear SENDPR ! @xref{categories,,The @code{categories} file}, for details. @end ifclear @cindex @code{Class} field --- 314,320 ---- the problem lies. The values for this field are defined by the Support Site. @ifclear SENDPR ! @xref{categories file,,The @code{categories} file}, for details. @end ifclear @cindex @code{Class} field *************** *** 458,464 **** (@sc{Text}) The person responsible for this category. @ifclear SENDPR @sc{gnats} retrieves this information from the @file{categories} file ! (@pxref{categories,,The @code{categories} file}). @end ifclear @cindex @code{>Arrival-Date:} --- 458,464 ---- (@sc{Text}) The person responsible for this category. @ifclear SENDPR @sc{gnats} retrieves this information from the @file{categories} file ! (@pxref{categories file,,The @code{categories} file}). @end ifclear @cindex @code{>Arrival-Date:} *** send-pr/states.texi Mon Dec 15 13:27:06 1997 --- send-pr/states.texi Thu Dec 18 16:42:52 1997 *************** *** 10,15 **** --- 10,18 ---- closure. The originator of a PR receives notification automatically of any state changes. + Unless your site has customized states (see @pxref{states + file,,,gnats}), @sc{gnats} uses these states: + @table @dfn @cindex @emph{open} state @cindex initial state (@dfn{open})