186d7f5d3SJohn Marino /*
286d7f5d3SJohn Marino * Copyright (C) 1986-2005 The Free Software Foundation, Inc.
386d7f5d3SJohn Marino *
486d7f5d3SJohn Marino * Portions Copyright (C) 1998-2005 Derek Price, Ximbiot <http://ximbiot.com>,
586d7f5d3SJohn Marino * and others.
686d7f5d3SJohn Marino *
786d7f5d3SJohn Marino * Portions Copyright (C) 1992, Brian Berliner and Jeff Polk
886d7f5d3SJohn Marino * Portions Copyright (C) 1989-1992, Brian Berliner
986d7f5d3SJohn Marino *
1086d7f5d3SJohn Marino * You may distribute under the terms of the GNU General Public License as
1186d7f5d3SJohn Marino * specified in the README file that comes with the CVS source distribution.
1286d7f5d3SJohn Marino */
1386d7f5d3SJohn Marino
1486d7f5d3SJohn Marino
1586d7f5d3SJohn Marino #include "cvs.h"
1686d7f5d3SJohn Marino #include "getline.h"
1786d7f5d3SJohn Marino
1886d7f5d3SJohn Marino static int find_type (Node * p, void *closure);
1986d7f5d3SJohn Marino static int fmt_proc (Node * p, void *closure);
2086d7f5d3SJohn Marino static int logfile_write (const char *repository, const char *filter,
2186d7f5d3SJohn Marino const char *message, FILE * logfp, List * changes);
2286d7f5d3SJohn Marino static int logmsg_list_to_args_proc (Node *p, void *closure);
2386d7f5d3SJohn Marino static int rcsinfo_proc (const char *repository, const char *template,
2486d7f5d3SJohn Marino void *closure );
2586d7f5d3SJohn Marino static int update_logfile_proc (const char *repository, const char *filter,
2686d7f5d3SJohn Marino void *closure);
2786d7f5d3SJohn Marino static void setup_tmpfile (FILE * xfp, char *xprefix, List * changes);
2886d7f5d3SJohn Marino static int verifymsg_proc (const char *repository, const char *script,
2986d7f5d3SJohn Marino void *closure );
3086d7f5d3SJohn Marino
3186d7f5d3SJohn Marino static FILE *fp;
3286d7f5d3SJohn Marino static Ctype type;
3386d7f5d3SJohn Marino
3486d7f5d3SJohn Marino struct verifymsg_proc_data
3586d7f5d3SJohn Marino {
3686d7f5d3SJohn Marino /* The name of the temp file storing the log message to be verified. This
3786d7f5d3SJohn Marino * is initially NULL and verifymsg_proc() writes message into it so that it
3886d7f5d3SJohn Marino * can be shared when multiple verifymsg scripts exist. do_verify() is
3986d7f5d3SJohn Marino * responsible for rereading the message from the file when
4086d7f5d3SJohn Marino * RereadLogAfterVerify is in effect and the file has changed.
4186d7f5d3SJohn Marino */
4286d7f5d3SJohn Marino char *fname;
4386d7f5d3SJohn Marino /* The initial message text to be verified.
4486d7f5d3SJohn Marino */
4586d7f5d3SJohn Marino char *message;
4686d7f5d3SJohn Marino /* The initial stats of the temp file so we can tell that the temp file has
4786d7f5d3SJohn Marino * been changed when RereadLogAfterVerify is STAT.
4886d7f5d3SJohn Marino */
4986d7f5d3SJohn Marino struct stat pre_stbuf;
5086d7f5d3SJohn Marino /* The list of files being changed, with new and old version numbers.
5186d7f5d3SJohn Marino */
5286d7f5d3SJohn Marino List *changes;
5386d7f5d3SJohn Marino };
5486d7f5d3SJohn Marino
5586d7f5d3SJohn Marino /*
5686d7f5d3SJohn Marino * Puts a standard header on the output which is either being prepared for an
5786d7f5d3SJohn Marino * editor session, or being sent to a logfile program. The modified, added,
5886d7f5d3SJohn Marino * and removed files are included (if any) and formatted to look pretty. */
5986d7f5d3SJohn Marino static char *prefix;
6086d7f5d3SJohn Marino static int col;
6186d7f5d3SJohn Marino static char *tag;
6286d7f5d3SJohn Marino static void
setup_tmpfile(FILE * xfp,char * xprefix,List * changes)6386d7f5d3SJohn Marino setup_tmpfile (FILE *xfp, char *xprefix, List *changes)
6486d7f5d3SJohn Marino {
6586d7f5d3SJohn Marino /* set up statics */
6686d7f5d3SJohn Marino fp = xfp;
6786d7f5d3SJohn Marino prefix = xprefix;
6886d7f5d3SJohn Marino
6986d7f5d3SJohn Marino type = T_MODIFIED;
7086d7f5d3SJohn Marino if (walklist (changes, find_type, NULL) != 0)
7186d7f5d3SJohn Marino {
7286d7f5d3SJohn Marino (void) fprintf (fp, "%sModified Files:\n", prefix);
7386d7f5d3SJohn Marino col = 0;
7486d7f5d3SJohn Marino (void) walklist (changes, fmt_proc, NULL);
7586d7f5d3SJohn Marino (void) fprintf (fp, "\n");
7686d7f5d3SJohn Marino if (tag != NULL)
7786d7f5d3SJohn Marino {
7886d7f5d3SJohn Marino free (tag);
7986d7f5d3SJohn Marino tag = NULL;
8086d7f5d3SJohn Marino }
8186d7f5d3SJohn Marino }
8286d7f5d3SJohn Marino type = T_ADDED;
8386d7f5d3SJohn Marino if (walklist (changes, find_type, NULL) != 0)
8486d7f5d3SJohn Marino {
8586d7f5d3SJohn Marino (void) fprintf (fp, "%sAdded Files:\n", prefix);
8686d7f5d3SJohn Marino col = 0;
8786d7f5d3SJohn Marino (void) walklist (changes, fmt_proc, NULL);
8886d7f5d3SJohn Marino (void) fprintf (fp, "\n");
8986d7f5d3SJohn Marino if (tag != NULL)
9086d7f5d3SJohn Marino {
9186d7f5d3SJohn Marino free (tag);
9286d7f5d3SJohn Marino tag = NULL;
9386d7f5d3SJohn Marino }
9486d7f5d3SJohn Marino }
9586d7f5d3SJohn Marino type = T_REMOVED;
9686d7f5d3SJohn Marino if (walklist (changes, find_type, NULL) != 0)
9786d7f5d3SJohn Marino {
9886d7f5d3SJohn Marino (void) fprintf (fp, "%sRemoved Files:\n", prefix);
9986d7f5d3SJohn Marino col = 0;
10086d7f5d3SJohn Marino (void) walklist (changes, fmt_proc, NULL);
10186d7f5d3SJohn Marino (void) fprintf (fp, "\n");
10286d7f5d3SJohn Marino if (tag != NULL)
10386d7f5d3SJohn Marino {
10486d7f5d3SJohn Marino free (tag);
10586d7f5d3SJohn Marino tag = NULL;
10686d7f5d3SJohn Marino }
10786d7f5d3SJohn Marino }
10886d7f5d3SJohn Marino }
10986d7f5d3SJohn Marino
11086d7f5d3SJohn Marino /*
11186d7f5d3SJohn Marino * Looks for nodes of a specified type and returns 1 if found
11286d7f5d3SJohn Marino */
11386d7f5d3SJohn Marino static int
find_type(Node * p,void * closure)11486d7f5d3SJohn Marino find_type (Node *p, void *closure)
11586d7f5d3SJohn Marino {
11686d7f5d3SJohn Marino struct logfile_info *li = p->data;
11786d7f5d3SJohn Marino
11886d7f5d3SJohn Marino if (li->type == type)
11986d7f5d3SJohn Marino return (1);
12086d7f5d3SJohn Marino else
12186d7f5d3SJohn Marino return (0);
12286d7f5d3SJohn Marino }
12386d7f5d3SJohn Marino
12486d7f5d3SJohn Marino /*
12586d7f5d3SJohn Marino * Breaks the files list into reasonable sized lines to avoid line wrap...
12686d7f5d3SJohn Marino * all in the name of pretty output. It only works on nodes whose types
12786d7f5d3SJohn Marino * match the one we're looking for
12886d7f5d3SJohn Marino */
12986d7f5d3SJohn Marino static int
fmt_proc(Node * p,void * closure)13086d7f5d3SJohn Marino fmt_proc (Node *p, void *closure)
13186d7f5d3SJohn Marino {
13286d7f5d3SJohn Marino struct logfile_info *li;
13386d7f5d3SJohn Marino
13486d7f5d3SJohn Marino li = p->data;
13586d7f5d3SJohn Marino if (li->type == type)
13686d7f5d3SJohn Marino {
13786d7f5d3SJohn Marino if (li->tag == NULL
13886d7f5d3SJohn Marino ? tag != NULL
13986d7f5d3SJohn Marino : tag == NULL || strcmp (tag, li->tag) != 0)
14086d7f5d3SJohn Marino {
14186d7f5d3SJohn Marino if (col > 0)
14286d7f5d3SJohn Marino (void) fprintf (fp, "\n");
14386d7f5d3SJohn Marino (void) fputs (prefix, fp);
14486d7f5d3SJohn Marino col = strlen (prefix);
14586d7f5d3SJohn Marino while (col < 6)
14686d7f5d3SJohn Marino {
14786d7f5d3SJohn Marino (void) fprintf (fp, " ");
14886d7f5d3SJohn Marino ++col;
14986d7f5d3SJohn Marino }
15086d7f5d3SJohn Marino
15186d7f5d3SJohn Marino if (li->tag == NULL)
15286d7f5d3SJohn Marino (void) fprintf (fp, "No tag");
15386d7f5d3SJohn Marino else
15486d7f5d3SJohn Marino (void) fprintf (fp, "Tag: %s", li->tag);
15586d7f5d3SJohn Marino
15686d7f5d3SJohn Marino if (tag != NULL)
15786d7f5d3SJohn Marino free (tag);
15886d7f5d3SJohn Marino tag = xstrdup (li->tag);
15986d7f5d3SJohn Marino
16086d7f5d3SJohn Marino /* Force a new line. */
16186d7f5d3SJohn Marino col = 70;
16286d7f5d3SJohn Marino }
16386d7f5d3SJohn Marino
16486d7f5d3SJohn Marino if (col == 0)
16586d7f5d3SJohn Marino {
16686d7f5d3SJohn Marino (void) fprintf (fp, "%s\t", prefix);
16786d7f5d3SJohn Marino col = 8;
16886d7f5d3SJohn Marino }
16986d7f5d3SJohn Marino else if (col > 8 && (col + (int) strlen (p->key)) > 70)
17086d7f5d3SJohn Marino {
17186d7f5d3SJohn Marino (void) fprintf (fp, "\n%s\t", prefix);
17286d7f5d3SJohn Marino col = 8;
17386d7f5d3SJohn Marino }
17486d7f5d3SJohn Marino (void) fprintf (fp, "%s ", p->key);
17586d7f5d3SJohn Marino col += strlen (p->key) + 1;
17686d7f5d3SJohn Marino }
17786d7f5d3SJohn Marino return (0);
17886d7f5d3SJohn Marino }
17986d7f5d3SJohn Marino
18086d7f5d3SJohn Marino /*
18186d7f5d3SJohn Marino * Builds a temporary file using setup_tmpfile() and invokes the user's
18286d7f5d3SJohn Marino * editor on the file. The header garbage in the resultant file is then
18386d7f5d3SJohn Marino * stripped and the log message is stored in the "message" argument.
18486d7f5d3SJohn Marino *
18586d7f5d3SJohn Marino * If REPOSITORY is non-NULL, process rcsinfo for that repository; if it
18686d7f5d3SJohn Marino * is NULL, use the CVSADM_TEMPLATE file instead. REPOSITORY should be
18786d7f5d3SJohn Marino * NULL when running in client mode.
18886d7f5d3SJohn Marino *
18986d7f5d3SJohn Marino * GLOBALS
19086d7f5d3SJohn Marino * Editor Set to a default value by configure and overridable using the
19186d7f5d3SJohn Marino * -e option to the CVS executable.
19286d7f5d3SJohn Marino */
19386d7f5d3SJohn Marino void
do_editor(const char * dir,char ** messagep,const char * repository,List * changes)19486d7f5d3SJohn Marino do_editor (const char *dir, char **messagep, const char *repository,
19586d7f5d3SJohn Marino List *changes)
19686d7f5d3SJohn Marino {
19786d7f5d3SJohn Marino static int reuse_log_message = 0;
19886d7f5d3SJohn Marino char *line;
19986d7f5d3SJohn Marino int line_length;
20086d7f5d3SJohn Marino size_t line_chars_allocated;
20186d7f5d3SJohn Marino char *fname;
20286d7f5d3SJohn Marino struct stat pre_stbuf, post_stbuf;
20386d7f5d3SJohn Marino int retcode = 0;
20486d7f5d3SJohn Marino
20586d7f5d3SJohn Marino assert (!current_parsed_root->isremote != !repository);
20686d7f5d3SJohn Marino
20786d7f5d3SJohn Marino if (noexec || reuse_log_message)
20886d7f5d3SJohn Marino return;
20986d7f5d3SJohn Marino
21086d7f5d3SJohn Marino /* Abort before creation of the temp file if no editor is defined. */
21186d7f5d3SJohn Marino if (strcmp (Editor, "") == 0)
21286d7f5d3SJohn Marino error(1, 0, "no editor defined, must use -e or -m");
21386d7f5d3SJohn Marino
21486d7f5d3SJohn Marino again:
21586d7f5d3SJohn Marino /* Create a temporary file. */
21686d7f5d3SJohn Marino if( ( fp = cvs_temp_file( &fname ) ) == NULL )
21786d7f5d3SJohn Marino error( 1, errno, "cannot create temporary file" );
21886d7f5d3SJohn Marino
21986d7f5d3SJohn Marino if (*messagep)
22086d7f5d3SJohn Marino {
22186d7f5d3SJohn Marino (void) fputs (*messagep, fp);
22286d7f5d3SJohn Marino
22386d7f5d3SJohn Marino if ((*messagep)[0] == '\0' ||
22486d7f5d3SJohn Marino (*messagep)[strlen (*messagep) - 1] != '\n')
22586d7f5d3SJohn Marino (void) fprintf (fp, "\n");
22686d7f5d3SJohn Marino }
22786d7f5d3SJohn Marino
22886d7f5d3SJohn Marino if (repository != NULL)
22986d7f5d3SJohn Marino /* tack templates on if necessary */
23086d7f5d3SJohn Marino (void) Parse_Info (CVSROOTADM_RCSINFO, repository, rcsinfo_proc,
23186d7f5d3SJohn Marino PIOPT_ALL, NULL);
23286d7f5d3SJohn Marino else
23386d7f5d3SJohn Marino {
23486d7f5d3SJohn Marino FILE *tfp;
23586d7f5d3SJohn Marino char buf[1024];
23686d7f5d3SJohn Marino size_t n;
23786d7f5d3SJohn Marino size_t nwrite;
23886d7f5d3SJohn Marino
23986d7f5d3SJohn Marino /* Why "b"? */
24086d7f5d3SJohn Marino tfp = CVS_FOPEN (CVSADM_TEMPLATE, "rb");
24186d7f5d3SJohn Marino if (tfp == NULL)
24286d7f5d3SJohn Marino {
24386d7f5d3SJohn Marino if (!existence_error (errno))
24486d7f5d3SJohn Marino error (1, errno, "cannot read %s", CVSADM_TEMPLATE);
24586d7f5d3SJohn Marino }
24686d7f5d3SJohn Marino else
24786d7f5d3SJohn Marino {
24886d7f5d3SJohn Marino while (!feof (tfp))
24986d7f5d3SJohn Marino {
25086d7f5d3SJohn Marino char *p = buf;
25186d7f5d3SJohn Marino n = fread (buf, 1, sizeof buf, tfp);
25286d7f5d3SJohn Marino nwrite = n;
25386d7f5d3SJohn Marino while (nwrite > 0)
25486d7f5d3SJohn Marino {
25586d7f5d3SJohn Marino n = fwrite (p, 1, nwrite, fp);
25686d7f5d3SJohn Marino nwrite -= n;
25786d7f5d3SJohn Marino p += n;
25886d7f5d3SJohn Marino }
25986d7f5d3SJohn Marino if (ferror (tfp))
26086d7f5d3SJohn Marino error (1, errno, "cannot read %s", CVSADM_TEMPLATE);
26186d7f5d3SJohn Marino }
26286d7f5d3SJohn Marino if (fclose (tfp) < 0)
26386d7f5d3SJohn Marino error (0, errno, "cannot close %s", CVSADM_TEMPLATE);
26486d7f5d3SJohn Marino }
26586d7f5d3SJohn Marino }
26686d7f5d3SJohn Marino
26786d7f5d3SJohn Marino (void) fprintf (fp,
26886d7f5d3SJohn Marino "%s----------------------------------------------------------------------\n",
26986d7f5d3SJohn Marino CVSEDITPREFIX);
27086d7f5d3SJohn Marino (void) fprintf (fp,
27186d7f5d3SJohn Marino "%sEnter Log. Lines beginning with `%.*s' are removed automatically\n%s\n",
27286d7f5d3SJohn Marino CVSEDITPREFIX, CVSEDITPREFIXLEN, CVSEDITPREFIX,
27386d7f5d3SJohn Marino CVSEDITPREFIX);
27486d7f5d3SJohn Marino if (dir != NULL && *dir)
27586d7f5d3SJohn Marino (void) fprintf (fp, "%sCommitting in %s\n%s\n", CVSEDITPREFIX,
27686d7f5d3SJohn Marino dir, CVSEDITPREFIX);
27786d7f5d3SJohn Marino if (changes != NULL)
27886d7f5d3SJohn Marino setup_tmpfile (fp, CVSEDITPREFIX, changes);
27986d7f5d3SJohn Marino (void) fprintf (fp,
28086d7f5d3SJohn Marino "%s----------------------------------------------------------------------\n",
28186d7f5d3SJohn Marino CVSEDITPREFIX);
28286d7f5d3SJohn Marino
28386d7f5d3SJohn Marino /* finish off the temp file */
28486d7f5d3SJohn Marino if (fclose (fp) == EOF)
28586d7f5d3SJohn Marino error (1, errno, "%s", fname);
28686d7f5d3SJohn Marino if (stat (fname, &pre_stbuf) == -1)
28786d7f5d3SJohn Marino pre_stbuf.st_mtime = 0;
28886d7f5d3SJohn Marino
28986d7f5d3SJohn Marino /* run the editor */
29086d7f5d3SJohn Marino run_setup (Editor);
29186d7f5d3SJohn Marino run_add_arg (fname);
29286d7f5d3SJohn Marino if ((retcode = run_exec (RUN_TTY, RUN_TTY, RUN_TTY,
29386d7f5d3SJohn Marino RUN_NORMAL | RUN_SIGIGNORE)) != 0)
29486d7f5d3SJohn Marino error (0, retcode == -1 ? errno : 0, "warning: editor session failed");
29586d7f5d3SJohn Marino
29686d7f5d3SJohn Marino /* put the entire message back into the *messagep variable */
29786d7f5d3SJohn Marino
29886d7f5d3SJohn Marino fp = xfopen (fname, "r");
29986d7f5d3SJohn Marino
30086d7f5d3SJohn Marino if (*messagep)
30186d7f5d3SJohn Marino free (*messagep);
30286d7f5d3SJohn Marino
30386d7f5d3SJohn Marino if (stat (fname, &post_stbuf) != 0)
30486d7f5d3SJohn Marino error (1, errno, "cannot find size of temp file %s", fname);
30586d7f5d3SJohn Marino
30686d7f5d3SJohn Marino if (post_stbuf.st_size == 0)
30786d7f5d3SJohn Marino *messagep = NULL;
30886d7f5d3SJohn Marino else
30986d7f5d3SJohn Marino {
31086d7f5d3SJohn Marino /* On NT, we might read less than st_size bytes, but we won't
31186d7f5d3SJohn Marino read more. So this works. */
31286d7f5d3SJohn Marino *messagep = (char *) xmalloc (post_stbuf.st_size + 1);
31386d7f5d3SJohn Marino (*messagep)[0] = '\0';
31486d7f5d3SJohn Marino }
31586d7f5d3SJohn Marino
31686d7f5d3SJohn Marino line = NULL;
31786d7f5d3SJohn Marino line_chars_allocated = 0;
31886d7f5d3SJohn Marino
31986d7f5d3SJohn Marino if (*messagep)
32086d7f5d3SJohn Marino {
32186d7f5d3SJohn Marino size_t message_len = post_stbuf.st_size + 1;
32286d7f5d3SJohn Marino size_t offset = 0;
32386d7f5d3SJohn Marino while (1)
32486d7f5d3SJohn Marino {
32586d7f5d3SJohn Marino line_length = getline (&line, &line_chars_allocated, fp);
32686d7f5d3SJohn Marino if (line_length == -1)
32786d7f5d3SJohn Marino {
32886d7f5d3SJohn Marino if (ferror (fp))
32986d7f5d3SJohn Marino error (0, errno, "warning: cannot read %s", fname);
33086d7f5d3SJohn Marino break;
33186d7f5d3SJohn Marino }
33286d7f5d3SJohn Marino if (strncmp (line, CVSEDITPREFIX, CVSEDITPREFIXLEN) == 0)
33386d7f5d3SJohn Marino continue;
33486d7f5d3SJohn Marino if (offset + line_length >= message_len)
33586d7f5d3SJohn Marino expand_string (messagep, &message_len,
33686d7f5d3SJohn Marino offset + line_length + 1);
33786d7f5d3SJohn Marino (void) strcpy (*messagep + offset, line);
33886d7f5d3SJohn Marino offset += line_length;
33986d7f5d3SJohn Marino }
34086d7f5d3SJohn Marino }
34186d7f5d3SJohn Marino if (fclose (fp) < 0)
34286d7f5d3SJohn Marino error (0, errno, "warning: cannot close %s", fname);
34386d7f5d3SJohn Marino
34486d7f5d3SJohn Marino /* canonicalize emply messages */
34586d7f5d3SJohn Marino if (*messagep != NULL &&
34686d7f5d3SJohn Marino (**messagep == '\0' || strcmp (*messagep, "\n") == 0))
34786d7f5d3SJohn Marino {
34886d7f5d3SJohn Marino free (*messagep);
34986d7f5d3SJohn Marino *messagep = NULL;
35086d7f5d3SJohn Marino }
35186d7f5d3SJohn Marino
35286d7f5d3SJohn Marino if (pre_stbuf.st_mtime == post_stbuf.st_mtime || *messagep == NULL)
35386d7f5d3SJohn Marino {
35486d7f5d3SJohn Marino for (;;)
35586d7f5d3SJohn Marino {
35686d7f5d3SJohn Marino (void) printf ("\nLog message unchanged or not specified\n");
35786d7f5d3SJohn Marino (void) printf ("a)bort, c)ontinue, e)dit, !)reuse this message unchanged for remaining dirs\n");
35886d7f5d3SJohn Marino (void) printf ("Action: (continue) ");
35986d7f5d3SJohn Marino (void) fflush (stdout);
36086d7f5d3SJohn Marino line_length = getline (&line, &line_chars_allocated, stdin);
36186d7f5d3SJohn Marino if (line_length < 0)
36286d7f5d3SJohn Marino {
36386d7f5d3SJohn Marino error (0, errno, "cannot read from stdin");
36486d7f5d3SJohn Marino if (unlink_file (fname) < 0)
36586d7f5d3SJohn Marino error (0, errno,
36686d7f5d3SJohn Marino "warning: cannot remove temp file %s", fname);
36786d7f5d3SJohn Marino error (1, 0, "aborting");
36886d7f5d3SJohn Marino }
36986d7f5d3SJohn Marino else if (line_length == 0
37086d7f5d3SJohn Marino || *line == '\n' || *line == 'c' || *line == 'C')
37186d7f5d3SJohn Marino break;
37286d7f5d3SJohn Marino if (*line == 'a' || *line == 'A')
37386d7f5d3SJohn Marino {
37486d7f5d3SJohn Marino if (unlink_file (fname) < 0)
37586d7f5d3SJohn Marino error (0, errno, "warning: cannot remove temp file %s", fname);
37686d7f5d3SJohn Marino error (1, 0, "aborted by user");
37786d7f5d3SJohn Marino }
37886d7f5d3SJohn Marino if (*line == 'e' || *line == 'E')
37986d7f5d3SJohn Marino goto again;
38086d7f5d3SJohn Marino if (*line == '!')
38186d7f5d3SJohn Marino {
38286d7f5d3SJohn Marino reuse_log_message = 1;
38386d7f5d3SJohn Marino break;
38486d7f5d3SJohn Marino }
38586d7f5d3SJohn Marino (void) printf ("Unknown input\n");
38686d7f5d3SJohn Marino }
38786d7f5d3SJohn Marino }
38886d7f5d3SJohn Marino if (line)
38986d7f5d3SJohn Marino free (line);
39086d7f5d3SJohn Marino if (unlink_file (fname) < 0)
39186d7f5d3SJohn Marino error (0, errno, "warning: cannot remove temp file %s", fname);
39286d7f5d3SJohn Marino free (fname);
39386d7f5d3SJohn Marino }
39486d7f5d3SJohn Marino
39586d7f5d3SJohn Marino /* Runs the user-defined verification script as part of the commit or import
39686d7f5d3SJohn Marino process. This verification is meant to be run whether or not the user
39786d7f5d3SJohn Marino included the -m attribute. unlike the do_editor function, this is
39886d7f5d3SJohn Marino independant of the running of an editor for getting a message.
39986d7f5d3SJohn Marino */
40086d7f5d3SJohn Marino void
do_verify(char ** messagep,const char * repository,List * changes)40186d7f5d3SJohn Marino do_verify (char **messagep, const char *repository, List *changes)
40286d7f5d3SJohn Marino {
40386d7f5d3SJohn Marino int err;
40486d7f5d3SJohn Marino struct verifymsg_proc_data data;
40586d7f5d3SJohn Marino struct stat post_stbuf;
40686d7f5d3SJohn Marino
40786d7f5d3SJohn Marino if (current_parsed_root->isremote)
40886d7f5d3SJohn Marino /* The verification will happen on the server. */
40986d7f5d3SJohn Marino return;
41086d7f5d3SJohn Marino
41186d7f5d3SJohn Marino /* FIXME? Do we really want to skip this on noexec? What do we do
41286d7f5d3SJohn Marino for the other administrative files? */
41386d7f5d3SJohn Marino /* EXPLAIN: Why do we check for repository == NULL here? */
41486d7f5d3SJohn Marino if (noexec || repository == NULL)
41586d7f5d3SJohn Marino return;
41686d7f5d3SJohn Marino
41786d7f5d3SJohn Marino /* Get the name of the verification script to run */
41886d7f5d3SJohn Marino
41986d7f5d3SJohn Marino data.message = *messagep;
42086d7f5d3SJohn Marino data.fname = NULL;
42186d7f5d3SJohn Marino data.changes = changes;
42286d7f5d3SJohn Marino if ((err = Parse_Info (CVSROOTADM_VERIFYMSG, repository,
42386d7f5d3SJohn Marino verifymsg_proc, 0, &data)) != 0)
42486d7f5d3SJohn Marino {
42586d7f5d3SJohn Marino int saved_errno = errno;
42686d7f5d3SJohn Marino /* Since following error() exits, delete the temp file now. */
42786d7f5d3SJohn Marino if (data.fname != NULL && unlink_file( data.fname ) < 0)
42886d7f5d3SJohn Marino error (0, errno, "cannot remove %s", data.fname);
42986d7f5d3SJohn Marino free (data.fname);
43086d7f5d3SJohn Marino
43186d7f5d3SJohn Marino errno = saved_errno;
43286d7f5d3SJohn Marino error (1, err == -1 ? errno : 0, "Message verification failed");
43386d7f5d3SJohn Marino }
43486d7f5d3SJohn Marino
43586d7f5d3SJohn Marino /* Return if no temp file was created. That means that we didn't call any
43686d7f5d3SJohn Marino * verifymsg scripts.
43786d7f5d3SJohn Marino */
43886d7f5d3SJohn Marino if (data.fname == NULL)
43986d7f5d3SJohn Marino return;
44086d7f5d3SJohn Marino
44186d7f5d3SJohn Marino /* Get the mod time and size of the possibly new log message
44286d7f5d3SJohn Marino * in always and stat modes.
44386d7f5d3SJohn Marino */
44486d7f5d3SJohn Marino if (config->RereadLogAfterVerify == LOGMSG_REREAD_ALWAYS ||
44586d7f5d3SJohn Marino config->RereadLogAfterVerify == LOGMSG_REREAD_STAT)
44686d7f5d3SJohn Marino {
44786d7f5d3SJohn Marino if(stat (data.fname, &post_stbuf) != 0)
44886d7f5d3SJohn Marino error (1, errno, "cannot find size of temp file %s", data.fname);
44986d7f5d3SJohn Marino }
45086d7f5d3SJohn Marino
45186d7f5d3SJohn Marino /* And reread the log message in `always' mode or in `stat' mode when it's
45286d7f5d3SJohn Marino * changed.
45386d7f5d3SJohn Marino */
45486d7f5d3SJohn Marino if (config->RereadLogAfterVerify == LOGMSG_REREAD_ALWAYS ||
45586d7f5d3SJohn Marino (config->RereadLogAfterVerify == LOGMSG_REREAD_STAT &&
45686d7f5d3SJohn Marino (data.pre_stbuf.st_mtime != post_stbuf.st_mtime ||
45786d7f5d3SJohn Marino data.pre_stbuf.st_size != post_stbuf.st_size)))
45886d7f5d3SJohn Marino {
45986d7f5d3SJohn Marino /* put the entire message back into the *messagep variable */
46086d7f5d3SJohn Marino
46186d7f5d3SJohn Marino if (*messagep) free (*messagep);
46286d7f5d3SJohn Marino
46386d7f5d3SJohn Marino if (post_stbuf.st_size == 0)
46486d7f5d3SJohn Marino *messagep = NULL;
46586d7f5d3SJohn Marino else
46686d7f5d3SJohn Marino {
46786d7f5d3SJohn Marino char *line = NULL;
46886d7f5d3SJohn Marino int line_length;
46986d7f5d3SJohn Marino size_t line_chars_allocated = 0;
47086d7f5d3SJohn Marino char *p;
47186d7f5d3SJohn Marino FILE *fp;
47286d7f5d3SJohn Marino
47386d7f5d3SJohn Marino fp = xfopen (data.fname, "r");
47486d7f5d3SJohn Marino
47586d7f5d3SJohn Marino /* On NT, we might read less than st_size bytes,
47686d7f5d3SJohn Marino but we won't read more. So this works. */
47786d7f5d3SJohn Marino p = *messagep = (char *) xmalloc (post_stbuf.st_size + 1);
47886d7f5d3SJohn Marino *messagep[0] = '\0';
47986d7f5d3SJohn Marino
48086d7f5d3SJohn Marino for (;;)
48186d7f5d3SJohn Marino {
48286d7f5d3SJohn Marino line_length = getline( &line,
48386d7f5d3SJohn Marino &line_chars_allocated,
48486d7f5d3SJohn Marino fp);
48586d7f5d3SJohn Marino if (line_length == -1)
48686d7f5d3SJohn Marino {
48786d7f5d3SJohn Marino if (ferror (fp))
48886d7f5d3SJohn Marino /* Fail in this case because otherwise we will have no
48986d7f5d3SJohn Marino * log message
49086d7f5d3SJohn Marino */
49186d7f5d3SJohn Marino error (1, errno, "cannot read %s", data.fname);
49286d7f5d3SJohn Marino break;
49386d7f5d3SJohn Marino }
49486d7f5d3SJohn Marino if (strncmp (line, CVSEDITPREFIX, CVSEDITPREFIXLEN) == 0)
49586d7f5d3SJohn Marino continue;
49686d7f5d3SJohn Marino (void) strcpy (p, line);
49786d7f5d3SJohn Marino p += line_length;
49886d7f5d3SJohn Marino }
49986d7f5d3SJohn Marino if (line) free (line);
50086d7f5d3SJohn Marino if (fclose (fp) < 0)
50186d7f5d3SJohn Marino error (0, errno, "warning: cannot close %s", data.fname);
50286d7f5d3SJohn Marino }
50386d7f5d3SJohn Marino }
50486d7f5d3SJohn Marino /* Delete the temp file */
50586d7f5d3SJohn Marino if (unlink_file (data.fname) < 0)
50686d7f5d3SJohn Marino error (0, errno, "cannot remove `%s'", data.fname);
50786d7f5d3SJohn Marino free (data.fname);
50886d7f5d3SJohn Marino }
50986d7f5d3SJohn Marino
51086d7f5d3SJohn Marino
51186d7f5d3SJohn Marino
51286d7f5d3SJohn Marino /*
51386d7f5d3SJohn Marino * callback proc for Parse_Info for rcsinfo templates this routine basically
51486d7f5d3SJohn Marino * copies the matching template onto the end of the tempfile we are setting
51586d7f5d3SJohn Marino * up
51686d7f5d3SJohn Marino */
51786d7f5d3SJohn Marino /* ARGSUSED */
51886d7f5d3SJohn Marino static int
rcsinfo_proc(const char * repository,const char * template,void * closure)51986d7f5d3SJohn Marino rcsinfo_proc (const char *repository, const char *template, void *closure)
52086d7f5d3SJohn Marino {
52186d7f5d3SJohn Marino static char *last_template;
52286d7f5d3SJohn Marino FILE *tfp;
52386d7f5d3SJohn Marino
52486d7f5d3SJohn Marino /* nothing to do if the last one included is the same as this one */
52586d7f5d3SJohn Marino if (last_template && strcmp (last_template, template) == 0)
52686d7f5d3SJohn Marino return (0);
52786d7f5d3SJohn Marino if (last_template)
52886d7f5d3SJohn Marino free (last_template);
52986d7f5d3SJohn Marino last_template = xstrdup (template);
53086d7f5d3SJohn Marino
53186d7f5d3SJohn Marino if ((tfp = CVS_FOPEN (template, "r")) != NULL)
53286d7f5d3SJohn Marino {
53386d7f5d3SJohn Marino char *line = NULL;
53486d7f5d3SJohn Marino size_t line_chars_allocated = 0;
53586d7f5d3SJohn Marino
53686d7f5d3SJohn Marino while (getline (&line, &line_chars_allocated, tfp) >= 0)
53786d7f5d3SJohn Marino (void) fputs (line, fp);
53886d7f5d3SJohn Marino if (ferror (tfp))
53986d7f5d3SJohn Marino error (0, errno, "warning: cannot read %s", template);
54086d7f5d3SJohn Marino if (fclose (tfp) < 0)
54186d7f5d3SJohn Marino error (0, errno, "warning: cannot close %s", template);
54286d7f5d3SJohn Marino if (line)
54386d7f5d3SJohn Marino free (line);
54486d7f5d3SJohn Marino return (0);
54586d7f5d3SJohn Marino }
54686d7f5d3SJohn Marino else
54786d7f5d3SJohn Marino {
54886d7f5d3SJohn Marino error (0, errno, "Couldn't open rcsinfo template file %s", template);
54986d7f5d3SJohn Marino return (1);
55086d7f5d3SJohn Marino }
55186d7f5d3SJohn Marino }
55286d7f5d3SJohn Marino
55386d7f5d3SJohn Marino /*
55486d7f5d3SJohn Marino * Uses setup_tmpfile() to pass the updated message on directly to any
55586d7f5d3SJohn Marino * logfile programs that have a regular expression match for the checked in
55686d7f5d3SJohn Marino * directory in the source repository. The log information is fed into the
55786d7f5d3SJohn Marino * specified program as standard input.
55886d7f5d3SJohn Marino */
55986d7f5d3SJohn Marino struct ulp_data {
56086d7f5d3SJohn Marino FILE *logfp;
56186d7f5d3SJohn Marino const char *message;
56286d7f5d3SJohn Marino List *changes;
56386d7f5d3SJohn Marino };
56486d7f5d3SJohn Marino
56586d7f5d3SJohn Marino
56686d7f5d3SJohn Marino
56786d7f5d3SJohn Marino void
Update_Logfile(const char * repository,const char * xmessage,FILE * xlogfp,List * xchanges)56886d7f5d3SJohn Marino Update_Logfile (const char *repository, const char *xmessage, FILE *xlogfp,
56986d7f5d3SJohn Marino List *xchanges)
57086d7f5d3SJohn Marino {
57186d7f5d3SJohn Marino struct ulp_data ud;
57286d7f5d3SJohn Marino
57386d7f5d3SJohn Marino /* nothing to do if the list is empty */
57486d7f5d3SJohn Marino if (xchanges == NULL || xchanges->list->next == xchanges->list)
57586d7f5d3SJohn Marino return;
57686d7f5d3SJohn Marino
57786d7f5d3SJohn Marino /* set up vars for update_logfile_proc */
57886d7f5d3SJohn Marino ud.message = xmessage;
57986d7f5d3SJohn Marino ud.logfp = xlogfp;
58086d7f5d3SJohn Marino ud.changes = xchanges;
58186d7f5d3SJohn Marino
58286d7f5d3SJohn Marino /* call Parse_Info to do the actual logfile updates */
58386d7f5d3SJohn Marino (void) Parse_Info (CVSROOTADM_LOGINFO, repository, update_logfile_proc,
58486d7f5d3SJohn Marino PIOPT_ALL, &ud);
58586d7f5d3SJohn Marino }
58686d7f5d3SJohn Marino
58786d7f5d3SJohn Marino
58886d7f5d3SJohn Marino
58986d7f5d3SJohn Marino /*
59086d7f5d3SJohn Marino * callback proc to actually do the logfile write from Update_Logfile
59186d7f5d3SJohn Marino */
59286d7f5d3SJohn Marino static int
update_logfile_proc(const char * repository,const char * filter,void * closure)59386d7f5d3SJohn Marino update_logfile_proc (const char *repository, const char *filter, void *closure)
59486d7f5d3SJohn Marino {
59586d7f5d3SJohn Marino struct ulp_data *udp = closure;
59686d7f5d3SJohn Marino TRACE (TRACE_FUNCTION, "update_logfile_proc(%s,%s)", repository, filter);
59786d7f5d3SJohn Marino return logfile_write (repository, filter, udp->message, udp->logfp,
59886d7f5d3SJohn Marino udp->changes);
59986d7f5d3SJohn Marino }
60086d7f5d3SJohn Marino
60186d7f5d3SJohn Marino
60286d7f5d3SJohn Marino
60386d7f5d3SJohn Marino /* static int
60486d7f5d3SJohn Marino * logmsg_list_to_args_proc( Node *p, void *closure )
60586d7f5d3SJohn Marino * This function is intended to be passed into walklist() with a list of tags
60686d7f5d3SJohn Marino * (nodes in the same format as pretag_list_proc() accepts - p->key = tagname
60786d7f5d3SJohn Marino * and p->data = a revision.
60886d7f5d3SJohn Marino *
60986d7f5d3SJohn Marino * closure will be a struct format_cmdline_walklist_closure
61086d7f5d3SJohn Marino * where closure is undefined.
61186d7f5d3SJohn Marino */
61286d7f5d3SJohn Marino static int
logmsg_list_to_args_proc(Node * p,void * closure)61386d7f5d3SJohn Marino logmsg_list_to_args_proc (Node *p, void *closure)
61486d7f5d3SJohn Marino {
61586d7f5d3SJohn Marino struct format_cmdline_walklist_closure *c = closure;
61686d7f5d3SJohn Marino struct logfile_info *li;
61786d7f5d3SJohn Marino char *arg = NULL;
61886d7f5d3SJohn Marino const char *f;
61986d7f5d3SJohn Marino char *d;
62086d7f5d3SJohn Marino size_t doff;
62186d7f5d3SJohn Marino
62286d7f5d3SJohn Marino if (p->data == NULL) return 1;
62386d7f5d3SJohn Marino
62486d7f5d3SJohn Marino f = c->format;
62586d7f5d3SJohn Marino d = *c->d;
62686d7f5d3SJohn Marino /* foreach requested attribute */
62786d7f5d3SJohn Marino while (*f)
62886d7f5d3SJohn Marino {
62986d7f5d3SJohn Marino switch (*f++)
63086d7f5d3SJohn Marino {
63186d7f5d3SJohn Marino case 's':
63286d7f5d3SJohn Marino arg = p->key;
63386d7f5d3SJohn Marino break;
63486d7f5d3SJohn Marino case 'T':
63586d7f5d3SJohn Marino li = p->data;
63686d7f5d3SJohn Marino arg = li->tag ? li->tag : "";
63786d7f5d3SJohn Marino break;
63886d7f5d3SJohn Marino case 'V':
63986d7f5d3SJohn Marino li = p->data;
64086d7f5d3SJohn Marino arg = li->rev_old ? li->rev_old : "NONE";
64186d7f5d3SJohn Marino break;
64286d7f5d3SJohn Marino case 'v':
64386d7f5d3SJohn Marino li = p->data;
64486d7f5d3SJohn Marino arg = li->rev_new ? li->rev_new : "NONE";
64586d7f5d3SJohn Marino break;
64686d7f5d3SJohn Marino default:
64786d7f5d3SJohn Marino #ifdef SUPPORT_OLD_INFO_FMT_STRINGS
64886d7f5d3SJohn Marino if (c->onearg)
64986d7f5d3SJohn Marino {
65086d7f5d3SJohn Marino /* The old deafult was to print the empty string for
65186d7f5d3SJohn Marino * unknown args.
65286d7f5d3SJohn Marino */
65386d7f5d3SJohn Marino arg = "\0";
65486d7f5d3SJohn Marino }
65586d7f5d3SJohn Marino else
65686d7f5d3SJohn Marino #endif /* SUPPORT_OLD_INFO_FMT_STRINGS */
65786d7f5d3SJohn Marino error (1, 0,
65886d7f5d3SJohn Marino "Unknown format character or not a list attribute: %c", f[-1]);
65986d7f5d3SJohn Marino /* NOTREACHED */
66086d7f5d3SJohn Marino break;
66186d7f5d3SJohn Marino }
66286d7f5d3SJohn Marino /* copy the attribute into an argument */
66386d7f5d3SJohn Marino #ifdef SUPPORT_OLD_INFO_FMT_STRINGS
66486d7f5d3SJohn Marino if (c->onearg)
66586d7f5d3SJohn Marino {
66686d7f5d3SJohn Marino if (c->firstpass)
66786d7f5d3SJohn Marino {
66886d7f5d3SJohn Marino c->firstpass = 0;
66986d7f5d3SJohn Marino doff = d - *c->buf;
67086d7f5d3SJohn Marino expand_string (c->buf, c->length,
67186d7f5d3SJohn Marino doff + strlen (c->srepos) + 1);
67286d7f5d3SJohn Marino d = *c->buf + doff;
67386d7f5d3SJohn Marino strncpy (d, c->srepos, strlen (c->srepos));
67486d7f5d3SJohn Marino d += strlen (c->srepos);
67586d7f5d3SJohn Marino *d++ = ' ';
67686d7f5d3SJohn Marino }
67786d7f5d3SJohn Marino }
67886d7f5d3SJohn Marino else /* c->onearg */
67986d7f5d3SJohn Marino #endif /* SUPPORT_OLD_INFO_FMT_STRINGS */
68086d7f5d3SJohn Marino {
68186d7f5d3SJohn Marino if (c->quotes)
68286d7f5d3SJohn Marino {
68386d7f5d3SJohn Marino arg = cmdlineescape (c->quotes, arg);
68486d7f5d3SJohn Marino }
68586d7f5d3SJohn Marino else
68686d7f5d3SJohn Marino {
68786d7f5d3SJohn Marino arg = cmdlinequote ('"', arg);
68886d7f5d3SJohn Marino }
68986d7f5d3SJohn Marino } /* !c->onearg */
69086d7f5d3SJohn Marino doff = d - *c->buf;
69186d7f5d3SJohn Marino expand_string (c->buf, c->length, doff + strlen (arg));
69286d7f5d3SJohn Marino d = *c->buf + doff;
69386d7f5d3SJohn Marino strncpy (d, arg, strlen (arg));
69486d7f5d3SJohn Marino d += strlen (arg);
69586d7f5d3SJohn Marino #ifdef SUPPORT_OLD_INFO_FMT_STRINGS
69686d7f5d3SJohn Marino if (!c->onearg)
69786d7f5d3SJohn Marino #endif /* SUPPORT_OLD_INFO_FMT_STRINGS */
69886d7f5d3SJohn Marino free (arg);
69986d7f5d3SJohn Marino
70086d7f5d3SJohn Marino /* Always put the extra space on. we'll have to back up a char
70186d7f5d3SJohn Marino * when we're done, but that seems most efficient.
70286d7f5d3SJohn Marino */
70386d7f5d3SJohn Marino doff = d - *c->buf;
70486d7f5d3SJohn Marino expand_string (c->buf, c->length, doff + 1);
70586d7f5d3SJohn Marino d = *c->buf + doff;
70686d7f5d3SJohn Marino #ifdef SUPPORT_OLD_INFO_FMT_STRINGS
70786d7f5d3SJohn Marino if (c->onearg && *f) *d++ = ',';
70886d7f5d3SJohn Marino else
70986d7f5d3SJohn Marino #endif /* SUPPORT_OLD_INFO_FMT_STRINGS */
71086d7f5d3SJohn Marino *d++ = ' ';
71186d7f5d3SJohn Marino }
71286d7f5d3SJohn Marino /* correct our original pointer into the buff */
71386d7f5d3SJohn Marino *c->d = d;
71486d7f5d3SJohn Marino return 0;
71586d7f5d3SJohn Marino }
71686d7f5d3SJohn Marino
71786d7f5d3SJohn Marino
71886d7f5d3SJohn Marino
71986d7f5d3SJohn Marino /*
72086d7f5d3SJohn Marino * Writes some stuff to the logfile "filter" and returns the status of the
72186d7f5d3SJohn Marino * filter program.
72286d7f5d3SJohn Marino */
72386d7f5d3SJohn Marino static int
logfile_write(const char * repository,const char * filter,const char * message,FILE * logfp,List * changes)72486d7f5d3SJohn Marino logfile_write (const char *repository, const char *filter, const char *message,
72586d7f5d3SJohn Marino FILE *logfp, List *changes)
72686d7f5d3SJohn Marino {
72786d7f5d3SJohn Marino char *cmdline;
72886d7f5d3SJohn Marino FILE *pipefp;
72986d7f5d3SJohn Marino char *cp;
73086d7f5d3SJohn Marino int c;
73186d7f5d3SJohn Marino int pipestatus;
73286d7f5d3SJohn Marino const char *srepos = Short_Repository (repository);
73386d7f5d3SJohn Marino
73486d7f5d3SJohn Marino assert (repository);
73586d7f5d3SJohn Marino
73686d7f5d3SJohn Marino /* The user may specify a format string as part of the filter.
73786d7f5d3SJohn Marino Originally, `%s' was the only valid string. The string that
73886d7f5d3SJohn Marino was substituted for it was:
73986d7f5d3SJohn Marino
74086d7f5d3SJohn Marino <repository-name> <file1> <file2> <file3> ...
74186d7f5d3SJohn Marino
74286d7f5d3SJohn Marino Each file was either a new directory/import (T_TITLE), or a
74386d7f5d3SJohn Marino added (T_ADDED), modified (T_MODIFIED), or removed (T_REMOVED)
74486d7f5d3SJohn Marino file.
74586d7f5d3SJohn Marino
74686d7f5d3SJohn Marino It is desirable to preserve that behavior so lots of commitlog
74786d7f5d3SJohn Marino scripts won't die when they get this new code. At the same
74886d7f5d3SJohn Marino time, we'd like to pass other information about the files (like
74986d7f5d3SJohn Marino version numbers, statuses, or checkin times).
75086d7f5d3SJohn Marino
75186d7f5d3SJohn Marino The solution is to allow a format string that allows us to
75286d7f5d3SJohn Marino specify those other pieces of information. The format string
75386d7f5d3SJohn Marino will be composed of `%' followed by a single format character,
75486d7f5d3SJohn Marino or followed by a set of format characters surrounded by `{' and
75586d7f5d3SJohn Marino `}' as separators. The format characters are:
75686d7f5d3SJohn Marino
75786d7f5d3SJohn Marino s = file name
75886d7f5d3SJohn Marino V = old version number (pre-checkin)
75986d7f5d3SJohn Marino v = new version number (post-checkin)
76086d7f5d3SJohn Marino
76186d7f5d3SJohn Marino For example, valid format strings are:
76286d7f5d3SJohn Marino
76386d7f5d3SJohn Marino %{}
76486d7f5d3SJohn Marino %s
76586d7f5d3SJohn Marino %{s}
76686d7f5d3SJohn Marino %{sVv}
76786d7f5d3SJohn Marino
76886d7f5d3SJohn Marino There's no reason that more items couldn't be added (like
76986d7f5d3SJohn Marino modification date or file status [added, modified, updated,
77086d7f5d3SJohn Marino etc.]) -- the code modifications would be minimal (logmsg.c
77186d7f5d3SJohn Marino (title_proc) and commit.c (check_fileproc)).
77286d7f5d3SJohn Marino
77386d7f5d3SJohn Marino The output will be a string of tokens separated by spaces. For
77486d7f5d3SJohn Marino backwards compatibility, the the first token will be the
77586d7f5d3SJohn Marino repository name. The rest of the tokens will be
77686d7f5d3SJohn Marino comma-delimited lists of the information requested in the
77786d7f5d3SJohn Marino format string. For example, if `/u/src/master' is the
77886d7f5d3SJohn Marino repository, `%{sVv}' is the format string, and three files
77986d7f5d3SJohn Marino (ChangeLog, Makefile, foo.c) were modified, the output might
78086d7f5d3SJohn Marino be:
78186d7f5d3SJohn Marino
78286d7f5d3SJohn Marino /u/src/master ChangeLog,1.1,1.2 Makefile,1.3,1.4 foo.c,1.12,1.13
78386d7f5d3SJohn Marino
78486d7f5d3SJohn Marino Why this duplicates the old behavior when the format string is
78586d7f5d3SJohn Marino `%s' is left as an exercise for the reader. */
78686d7f5d3SJohn Marino
78786d7f5d3SJohn Marino /* %c = cvs_cmd_name
78886d7f5d3SJohn Marino * %p = shortrepos
78986d7f5d3SJohn Marino * %r = repository
79086d7f5d3SJohn Marino * %{sVv} = file name, old revision (precommit), new revision (postcommit)
79186d7f5d3SJohn Marino */
79286d7f5d3SJohn Marino /*
79386d7f5d3SJohn Marino * Cast any NULL arguments as appropriate pointers as this is an
79486d7f5d3SJohn Marino * stdarg function and we need to be certain the caller gets what
79586d7f5d3SJohn Marino * is expected.
79686d7f5d3SJohn Marino */
79786d7f5d3SJohn Marino cmdline = format_cmdline (
79886d7f5d3SJohn Marino #ifdef SUPPORT_OLD_INFO_FMT_STRINGS
79986d7f5d3SJohn Marino !config->UseNewInfoFmtStrings, srepos,
80086d7f5d3SJohn Marino #endif /* SUPPORT_OLD_INFO_FMT_STRINGS */
80186d7f5d3SJohn Marino filter,
80286d7f5d3SJohn Marino "c", "s", cvs_cmd_name,
80386d7f5d3SJohn Marino #ifdef SERVER_SUPPORT
80486d7f5d3SJohn Marino "R", "s", referrer ? referrer->original : "NONE",
80586d7f5d3SJohn Marino #endif /* SERVER_SUPPORT */
80686d7f5d3SJohn Marino "p", "s", srepos,
80786d7f5d3SJohn Marino "r", "s", current_parsed_root->directory,
80886d7f5d3SJohn Marino "sVv", ",", changes,
80986d7f5d3SJohn Marino logmsg_list_to_args_proc, (void *) NULL,
81086d7f5d3SJohn Marino (char *) NULL);
81186d7f5d3SJohn Marino if (!cmdline || !strlen (cmdline))
81286d7f5d3SJohn Marino {
81386d7f5d3SJohn Marino if (cmdline) free (cmdline);
81486d7f5d3SJohn Marino error (0, 0, "logmsg proc resolved to the empty string!");
81586d7f5d3SJohn Marino return 1;
81686d7f5d3SJohn Marino }
81786d7f5d3SJohn Marino
81886d7f5d3SJohn Marino if ((pipefp = run_popen (cmdline, "w")) == NULL)
81986d7f5d3SJohn Marino {
82086d7f5d3SJohn Marino if (!noexec)
82186d7f5d3SJohn Marino error (0, 0, "cannot write entry to log filter: %s", cmdline);
82286d7f5d3SJohn Marino free (cmdline);
82386d7f5d3SJohn Marino return 1;
82486d7f5d3SJohn Marino }
82586d7f5d3SJohn Marino (void) fprintf (pipefp, "Update of %s\n", repository);
82686d7f5d3SJohn Marino (void) fprintf (pipefp, "In directory %s:", hostname);
82786d7f5d3SJohn Marino cp = xgetcwd ();
82886d7f5d3SJohn Marino if (cp == NULL)
82986d7f5d3SJohn Marino fprintf (pipefp, "<cannot get working directory: %s>\n\n",
83086d7f5d3SJohn Marino strerror (errno));
83186d7f5d3SJohn Marino else
83286d7f5d3SJohn Marino {
83386d7f5d3SJohn Marino fprintf (pipefp, "%s\n\n", cp);
83486d7f5d3SJohn Marino free (cp);
83586d7f5d3SJohn Marino }
83686d7f5d3SJohn Marino
83786d7f5d3SJohn Marino setup_tmpfile (pipefp, "", changes);
83886d7f5d3SJohn Marino (void) fprintf (pipefp, "Log Message:\n%s\n", (message) ? message : "");
83986d7f5d3SJohn Marino if (logfp)
84086d7f5d3SJohn Marino {
84186d7f5d3SJohn Marino (void) fprintf (pipefp, "Status:\n");
84286d7f5d3SJohn Marino rewind (logfp);
84386d7f5d3SJohn Marino while ((c = getc (logfp)) != EOF)
84486d7f5d3SJohn Marino (void) putc (c, pipefp);
84586d7f5d3SJohn Marino }
84686d7f5d3SJohn Marino free (cmdline);
84786d7f5d3SJohn Marino pipestatus = pclose (pipefp);
84886d7f5d3SJohn Marino return ((pipestatus == -1) || (pipestatus == 127)) ? 1 : 0;
84986d7f5d3SJohn Marino }
85086d7f5d3SJohn Marino
85186d7f5d3SJohn Marino
85286d7f5d3SJohn Marino
85386d7f5d3SJohn Marino /* This routine is called by Parse_Info. It runs the
85486d7f5d3SJohn Marino * message verification script.
85586d7f5d3SJohn Marino */
85686d7f5d3SJohn Marino static int
verifymsg_proc(const char * repository,const char * script,void * closure)85786d7f5d3SJohn Marino verifymsg_proc (const char *repository, const char *script, void *closure)
85886d7f5d3SJohn Marino {
85986d7f5d3SJohn Marino char *verifymsg_script;
86086d7f5d3SJohn Marino #ifdef SUPPORT_OLD_INFO_FMT_STRINGS
86186d7f5d3SJohn Marino char *newscript = NULL;
86286d7f5d3SJohn Marino #endif /* SUPPORT_OLD_INFO_FMT_STRINGS */
86386d7f5d3SJohn Marino struct verifymsg_proc_data *vpd = closure;
86486d7f5d3SJohn Marino const char *srepos = Short_Repository (repository);
86586d7f5d3SJohn Marino
86686d7f5d3SJohn Marino #ifdef SUPPORT_OLD_INFO_FMT_STRINGS
86786d7f5d3SJohn Marino if (!strchr (script, '%'))
86886d7f5d3SJohn Marino {
86986d7f5d3SJohn Marino error (0, 0,
87086d7f5d3SJohn Marino "warning: verifymsg line doesn't contain any format strings:\n"
87186d7f5d3SJohn Marino " \"%s\"\n"
87286d7f5d3SJohn Marino "Appending default format string (\" %%l\"), but be aware that this usage is\n"
87386d7f5d3SJohn Marino "deprecated.", script);
87486d7f5d3SJohn Marino script = newscript = Xasprintf ("%s %%l", script);
87586d7f5d3SJohn Marino }
87686d7f5d3SJohn Marino #endif /* SUPPORT_OLD_INFO_FMT_STRINGS */
87786d7f5d3SJohn Marino
87886d7f5d3SJohn Marino /* If we don't already have one, open a temporary file, write the message
87986d7f5d3SJohn Marino * to the temp file, and close the file.
88086d7f5d3SJohn Marino *
88186d7f5d3SJohn Marino * We do this here so that we only create the file when there is a
88286d7f5d3SJohn Marino * verifymsg script specified and we only create it once when there is
88386d7f5d3SJohn Marino * more than one verifymsg script specified.
88486d7f5d3SJohn Marino */
88586d7f5d3SJohn Marino if (vpd->fname == NULL)
88686d7f5d3SJohn Marino {
88786d7f5d3SJohn Marino FILE *fp;
88886d7f5d3SJohn Marino if ((fp = cvs_temp_file (&(vpd->fname))) == NULL)
88986d7f5d3SJohn Marino error (1, errno, "cannot create temporary file %s", vpd->fname);
89086d7f5d3SJohn Marino
89186d7f5d3SJohn Marino if (vpd->message != NULL)
89286d7f5d3SJohn Marino fputs (vpd->message, fp);
89386d7f5d3SJohn Marino if (vpd->message == NULL ||
89486d7f5d3SJohn Marino (vpd->message)[0] == '\0' ||
89586d7f5d3SJohn Marino (vpd->message)[strlen (vpd->message) - 1] != '\n')
89686d7f5d3SJohn Marino putc ('\n', fp);
89786d7f5d3SJohn Marino if (fclose (fp) == EOF)
89886d7f5d3SJohn Marino error (1, errno, "%s", vpd->fname);
89986d7f5d3SJohn Marino
90086d7f5d3SJohn Marino if (config->RereadLogAfterVerify == LOGMSG_REREAD_STAT)
90186d7f5d3SJohn Marino {
90286d7f5d3SJohn Marino /* Remember the status of the temp file for later */
90386d7f5d3SJohn Marino if (stat (vpd->fname, &(vpd->pre_stbuf)) != 0)
90486d7f5d3SJohn Marino error (1, errno, "cannot stat temp file %s", vpd->fname);
90586d7f5d3SJohn Marino
90686d7f5d3SJohn Marino /*
90786d7f5d3SJohn Marino * See if we need to sleep before running the verification
90886d7f5d3SJohn Marino * script to avoid time-stamp races.
90986d7f5d3SJohn Marino */
91086d7f5d3SJohn Marino sleep_past (vpd->pre_stbuf.st_mtime);
91186d7f5d3SJohn Marino }
91286d7f5d3SJohn Marino } /* if (vpd->fname == NULL) */
91386d7f5d3SJohn Marino
91486d7f5d3SJohn Marino /*
91586d7f5d3SJohn Marino * Cast any NULL arguments as appropriate pointers as this is an
91686d7f5d3SJohn Marino * stdarg function and we need to be certain the caller gets what
91786d7f5d3SJohn Marino * is expected.
91886d7f5d3SJohn Marino */
91986d7f5d3SJohn Marino verifymsg_script = format_cmdline (
92086d7f5d3SJohn Marino #ifdef SUPPORT_OLD_INFO_FMT_STRINGS
92186d7f5d3SJohn Marino false, srepos,
92286d7f5d3SJohn Marino #endif /* SUPPORT_OLD_INFO_FMT_STRINGS */
92386d7f5d3SJohn Marino script,
92486d7f5d3SJohn Marino "c", "s", cvs_cmd_name,
92586d7f5d3SJohn Marino #ifdef SERVER_SUPPORT
92686d7f5d3SJohn Marino "R", "s", referrer
92786d7f5d3SJohn Marino ? referrer->original : "NONE",
92886d7f5d3SJohn Marino #endif /* SERVER_SUPPORT */
92986d7f5d3SJohn Marino "p", "s", srepos,
93086d7f5d3SJohn Marino "r", "s",
93186d7f5d3SJohn Marino current_parsed_root->directory,
93286d7f5d3SJohn Marino "l", "s", vpd->fname,
93386d7f5d3SJohn Marino "sV", ",", vpd->changes,
93486d7f5d3SJohn Marino logmsg_list_to_args_proc, (void *) NULL,
93586d7f5d3SJohn Marino (char *) NULL);
93686d7f5d3SJohn Marino
93786d7f5d3SJohn Marino #ifdef SUPPORT_OLD_INFO_FMT_STRINGS
93886d7f5d3SJohn Marino if (newscript) free (newscript);
93986d7f5d3SJohn Marino #endif /* SUPPORT_OLD_INFO_FMT_STRINGS */
94086d7f5d3SJohn Marino
94186d7f5d3SJohn Marino if (!verifymsg_script || !strlen (verifymsg_script))
94286d7f5d3SJohn Marino {
94386d7f5d3SJohn Marino if (verifymsg_script) free (verifymsg_script);
94486d7f5d3SJohn Marino verifymsg_script = NULL;
94586d7f5d3SJohn Marino error (0, 0, "verifymsg proc resolved to the empty string!");
94686d7f5d3SJohn Marino return 1;
94786d7f5d3SJohn Marino }
94886d7f5d3SJohn Marino
94986d7f5d3SJohn Marino run_setup (verifymsg_script);
95086d7f5d3SJohn Marino
95186d7f5d3SJohn Marino free (verifymsg_script);
95286d7f5d3SJohn Marino
95386d7f5d3SJohn Marino /* FIXME - because run_exec can return negative values and Parse_Info adds
95486d7f5d3SJohn Marino * the values of each call to this function to get a total error, we are
95586d7f5d3SJohn Marino * calling abs on the value of run_exec to ensure two errors do not sum to
95686d7f5d3SJohn Marino * zero.
95786d7f5d3SJohn Marino *
95886d7f5d3SJohn Marino * The only REALLY obnoxious thing about this, I guess, is that a -1 return
95986d7f5d3SJohn Marino * code from run_exec can mean we failed to call the process for some
96086d7f5d3SJohn Marino * reason and should care about errno or that the process we called
96186d7f5d3SJohn Marino * returned -1 and the value of errno is undefined. In other words,
96286d7f5d3SJohn Marino * run_exec should probably be rewritten to have two return codes. one
96386d7f5d3SJohn Marino * which is its own exit status and one which is the child process's. So
96486d7f5d3SJohn Marino * there. :P
96586d7f5d3SJohn Marino *
96686d7f5d3SJohn Marino * Once run_exec is returning two error codes, we should probably be
96786d7f5d3SJohn Marino * failing here with an error message including errno when we get the
96886d7f5d3SJohn Marino * return code which means we care about errno, in case you missed that
96986d7f5d3SJohn Marino * little tidbit.
97086d7f5d3SJohn Marino *
97186d7f5d3SJohn Marino * I do happen to know we just fail for a non-zero value anyway and I
97286d7f5d3SJohn Marino * believe the docs actually state that if the verifymsg_proc returns a
97386d7f5d3SJohn Marino * "non-zero" value we will fail.
97486d7f5d3SJohn Marino */
97586d7f5d3SJohn Marino return abs (run_exec (RUN_TTY, RUN_TTY, RUN_TTY,
97686d7f5d3SJohn Marino RUN_NORMAL | RUN_SIGIGNORE));
97786d7f5d3SJohn Marino }
978