117721Speter/*
2175274Sobrien * Copyright (C) 1986-2005 The Free Software Foundation, Inc.
3175274Sobrien *
4175274Sobrien * Portions Copyright (C) 1998-2005 Derek Price, Ximbiot <http://ximbiot.com>,
5175274Sobrien *                                  and others.
6175274Sobrien *
7175274Sobrien * Portions Copyright (C) 1992, Brian Berliner and Jeff Polk
8175274Sobrien * Portions Copyright (C) 1989-1992, Brian Berliner
917721Speter *
1017721Speter * You may distribute under the terms of the GNU General Public License as
1132788Speter * specified in the README file that comes with the CVS source distribution.
1254431Speter *
1354431Speter * $FreeBSD$
1417721Speter */
1517721Speter
16102843Speter#include <assert.h>
17102843Speter
1817721Speter#include "cvs.h"
1917721Speter#include "getline.h"
2017721Speter
2117721Speterstatic int find_type PROTO((Node * p, void *closure));
2217721Speterstatic int fmt_proc PROTO((Node * p, void *closure));
23128269Speterstatic int logfile_write PROTO((const char *repository, const char *filter,
24128269Speter                                const char *message, FILE * logfp,
25128269Speter                                List * changes));
26128269Speterstatic int rcsinfo_proc PROTO((const char *repository, const char *template));
2717721Speterstatic int title_proc PROTO((Node * p, void *closure));
28128269Speterstatic int update_logfile_proc PROTO((const char *repository,
29128269Speter                                      const char *filter));
3017721Speterstatic void setup_tmpfile PROTO((FILE * xfp, char *xprefix, List * changes));
31128269Speterstatic int editinfo_proc PROTO((const char *repository, const char *template));
32128269Speterstatic int verifymsg_proc PROTO((const char *repository, const char *script));
3317721Speter
3417721Speterstatic FILE *fp;
3517721Speterstatic char *str_list;
3625839Speterstatic char *str_list_format;	/* The format for str_list's contents. */
3717721Speterstatic char *editinfo_editor;
3825839Speterstatic char *verifymsg_script;
3917721Speterstatic Ctype type;
4017721Speter
41102843Speter/*
42102843Speter * Should the logmsg be re-read during the do_verify phase?
43102843Speter * RereadLogAfterVerify=no|stat|yes
44102843Speter * LOGMSG_REREAD_NEVER  - never re-read the logmsg
45102843Speter * LOGMSG_REREAD_STAT   - re-read the logmsg only if it has changed
46102843Speter * LOGMSG_REREAD_ALWAYS - always re-read the logmsg
47102843Speter */
48102843Speterint RereadLogAfterVerify = LOGMSG_REREAD_ALWAYS;
49102843Speter
5017721Speter/*
5117721Speter * Puts a standard header on the output which is either being prepared for an
5217721Speter * editor session, or being sent to a logfile program.  The modified, added,
5317721Speter * and removed files are included (if any) and formatted to look pretty. */
5417721Speterstatic char *prefix;
5517721Speterstatic int col;
5625839Speterstatic char *tag;
5717721Speterstatic void
5817721Spetersetup_tmpfile (xfp, xprefix, changes)
5917721Speter    FILE *xfp;
6017721Speter    char *xprefix;
6117721Speter    List *changes;
6217721Speter{
6317721Speter    /* set up statics */
6417721Speter    fp = xfp;
6517721Speter    prefix = xprefix;
6617721Speter
6717721Speter    type = T_MODIFIED;
6817721Speter    if (walklist (changes, find_type, NULL) != 0)
6917721Speter    {
7017721Speter	(void) fprintf (fp, "%sModified Files:\n", prefix);
7125839Speter	col = 0;
7217721Speter	(void) walklist (changes, fmt_proc, NULL);
7317721Speter	(void) fprintf (fp, "\n");
7425839Speter	if (tag != NULL)
7525839Speter	{
7625839Speter	    free (tag);
7725839Speter	    tag = NULL;
7825839Speter	}
7917721Speter    }
8017721Speter    type = T_ADDED;
8117721Speter    if (walklist (changes, find_type, NULL) != 0)
8217721Speter    {
8317721Speter	(void) fprintf (fp, "%sAdded Files:\n", prefix);
8425839Speter	col = 0;
8517721Speter	(void) walklist (changes, fmt_proc, NULL);
8617721Speter	(void) fprintf (fp, "\n");
8725839Speter	if (tag != NULL)
8825839Speter	{
8925839Speter	    free (tag);
9025839Speter	    tag = NULL;
9125839Speter	}
9217721Speter    }
9317721Speter    type = T_REMOVED;
9417721Speter    if (walklist (changes, find_type, NULL) != 0)
9517721Speter    {
9617721Speter	(void) fprintf (fp, "%sRemoved Files:\n", prefix);
9725839Speter	col = 0;
9817721Speter	(void) walklist (changes, fmt_proc, NULL);
9917721Speter	(void) fprintf (fp, "\n");
10025839Speter	if (tag != NULL)
10125839Speter	{
10225839Speter	    free (tag);
10325839Speter	    tag = NULL;
10425839Speter	}
10517721Speter    }
10617721Speter}
10717721Speter
10817721Speter/*
10917721Speter * Looks for nodes of a specified type and returns 1 if found
11017721Speter */
11117721Speterstatic int
11217721Speterfind_type (p, closure)
11317721Speter    Node *p;
11417721Speter    void *closure;
11517721Speter{
116128269Speter    struct logfile_info *li = p->data;
11725839Speter
11825839Speter    if (li->type == type)
11917721Speter	return (1);
12017721Speter    else
12117721Speter	return (0);
12217721Speter}
12317721Speter
12417721Speter/*
12517721Speter * Breaks the files list into reasonable sized lines to avoid line wrap...
12617721Speter * all in the name of pretty output.  It only works on nodes whose types
12717721Speter * match the one we're looking for
12817721Speter */
12917721Speterstatic int
13017721Speterfmt_proc (p, closure)
13117721Speter    Node *p;
13217721Speter    void *closure;
13317721Speter{
13425839Speter    struct logfile_info *li;
13525839Speter
136128269Speter    li = p->data;
13725839Speter    if (li->type == type)
13817721Speter    {
13925839Speter        if (li->tag == NULL
14025839Speter	    ? tag != NULL
14125839Speter	    : tag == NULL || strcmp (tag, li->tag) != 0)
14217721Speter	{
14325839Speter	    if (col > 0)
14425839Speter	        (void) fprintf (fp, "\n");
145107487Speter	    (void) fputs (prefix, fp);
14625839Speter	    col = strlen (prefix);
14725839Speter	    while (col < 6)
14825839Speter	    {
14925839Speter	        (void) fprintf (fp, " ");
15025839Speter		++col;
15125839Speter	    }
15225839Speter
15325839Speter	    if (li->tag == NULL)
15425839Speter	        (void) fprintf (fp, "No tag");
15525839Speter	    else
15625839Speter	        (void) fprintf (fp, "Tag: %s", li->tag);
15725839Speter
15825839Speter	    if (tag != NULL)
15925839Speter	        free (tag);
16025839Speter	    tag = xstrdup (li->tag);
16125839Speter
16225839Speter	    /* Force a new line.  */
16325839Speter	    col = 70;
16425839Speter	}
16525839Speter
16625839Speter	if (col == 0)
16725839Speter	{
16825839Speter	    (void) fprintf (fp, "%s\t", prefix);
16925839Speter	    col = 8;
17025839Speter	}
17125839Speter	else if (col > 8 && (col + (int) strlen (p->key)) > 70)
17225839Speter	{
17317721Speter	    (void) fprintf (fp, "\n%s\t", prefix);
17417721Speter	    col = 8;
17517721Speter	}
17617721Speter	(void) fprintf (fp, "%s ", p->key);
17717721Speter	col += strlen (p->key) + 1;
17817721Speter    }
17917721Speter    return (0);
18017721Speter}
18117721Speter
18217721Speter/*
18317721Speter * Builds a temporary file using setup_tmpfile() and invokes the user's
18417721Speter * editor on the file.  The header garbage in the resultant file is then
18517721Speter * stripped and the log message is stored in the "message" argument.
18617721Speter *
18717721Speter * If REPOSITORY is non-NULL, process rcsinfo for that repository; if it
188102843Speter * is NULL, use the CVSADM_TEMPLATE file instead.  REPOSITORY should be
189102843Speter * NULL when running in client mode.
19017721Speter */
19117721Spetervoid
19217721Speterdo_editor (dir, messagep, repository, changes)
193128269Speter    const char *dir;
19417721Speter    char **messagep;
195128269Speter    const char *repository;
19617721Speter    List *changes;
19717721Speter{
19817721Speter    static int reuse_log_message = 0;
19917721Speter    char *line;
20017721Speter    int line_length;
20117721Speter    size_t line_chars_allocated;
20225839Speter    char *fname;
20317721Speter    struct stat pre_stbuf, post_stbuf;
20417721Speter    int retcode = 0;
20517721Speter
206107487Speter    assert (!current_parsed_root->isremote != !repository);
207102843Speter
20817721Speter    if (noexec || reuse_log_message)
20917721Speter	return;
21017721Speter
21117721Speter    /* Abort creation of temp file if no editor is defined */
21217721Speter    if (strcmp (Editor, "") == 0 && !editinfo_editor)
21317721Speter	error(1, 0, "no editor defined, must use -e or -m");
21417721Speter
21517721Speter    /* Create a temporary file */
21681407Speter    /* FIXME - It's possible we should be relying on cvs_temp_file to open
21781407Speter     * the file here - we get race conditions otherwise.
21881407Speter     */
21925839Speter    fname = cvs_temp_name ();
22017721Speter  again:
22125839Speter    if ((fp = CVS_FOPEN (fname, "w+")) == NULL)
22217721Speter	error (1, 0, "cannot create temporary file %s", fname);
22317721Speter
22417721Speter    if (*messagep)
22517721Speter    {
226107487Speter	(void) fputs (*messagep, fp);
22717721Speter
22825839Speter	if ((*messagep)[0] == '\0' ||
22925839Speter	    (*messagep)[strlen (*messagep) - 1] != '\n')
23017721Speter	    (void) fprintf (fp, "\n");
23117721Speter    }
23281598Speter    else
23381598Speter	(void) fprintf (fp, "\n");
23417721Speter
23517721Speter    if (repository != NULL)
23617721Speter	/* tack templates on if necessary */
23717721Speter	(void) Parse_Info (CVSROOTADM_RCSINFO, repository, rcsinfo_proc, 1);
23817721Speter    else
23917721Speter    {
24017721Speter	FILE *tfp;
24117721Speter	char buf[1024];
24217721Speter	size_t n;
24317721Speter	size_t nwrite;
24417721Speter
24517721Speter	/* Why "b"?  */
24625839Speter	tfp = CVS_FOPEN (CVSADM_TEMPLATE, "rb");
24717721Speter	if (tfp == NULL)
24817721Speter	{
24917721Speter	    if (!existence_error (errno))
25017721Speter		error (1, errno, "cannot read %s", CVSADM_TEMPLATE);
25117721Speter	}
25217721Speter	else
25317721Speter	{
25417721Speter	    while (!feof (tfp))
25517721Speter	    {
25666528Speter		char *p = buf;
25717721Speter		n = fread (buf, 1, sizeof buf, tfp);
25817721Speter		nwrite = n;
25917721Speter		while (nwrite > 0)
26017721Speter		{
26117721Speter		    n = fwrite (p, 1, nwrite, fp);
26217721Speter		    nwrite -= n;
26317721Speter		    p += n;
26417721Speter		}
26517721Speter		if (ferror (tfp))
26617721Speter		    error (1, errno, "cannot read %s", CVSADM_TEMPLATE);
26717721Speter	    }
26817721Speter	    if (fclose (tfp) < 0)
26917721Speter		error (0, errno, "cannot close %s", CVSADM_TEMPLATE);
27017721Speter	}
27117721Speter    }
27217721Speter
27317721Speter    (void) fprintf (fp,
27417721Speter  "%s----------------------------------------------------------------------\n",
27517721Speter		    CVSEDITPREFIX);
27617721Speter    (void) fprintf (fp,
27725839Speter  "%sEnter Log.  Lines beginning with `%.*s' are removed automatically\n%s\n",
27825839Speter		    CVSEDITPREFIX, CVSEDITPREFIXLEN, CVSEDITPREFIX,
27925839Speter		    CVSEDITPREFIX);
28017721Speter    if (dir != NULL && *dir)
28117721Speter	(void) fprintf (fp, "%sCommitting in %s\n%s\n", CVSEDITPREFIX,
28217721Speter			dir, CVSEDITPREFIX);
28317721Speter    if (changes != NULL)
28417721Speter	setup_tmpfile (fp, CVSEDITPREFIX, changes);
28517721Speter    (void) fprintf (fp,
28617721Speter  "%s----------------------------------------------------------------------\n",
28717721Speter		    CVSEDITPREFIX);
28817721Speter
28917721Speter    /* finish off the temp file */
29017721Speter    if (fclose (fp) == EOF)
29117721Speter        error (1, errno, "%s", fname);
29225839Speter    if ( CVS_STAT (fname, &pre_stbuf) == -1)
29317721Speter	pre_stbuf.st_mtime = 0;
29417721Speter
29517721Speter    if (editinfo_editor)
29617721Speter	free (editinfo_editor);
29717721Speter    editinfo_editor = (char *) NULL;
298175274Sobrien    if (!current_parsed_root->isremote && repository != NULL)
29917721Speter	(void) Parse_Info (CVSROOTADM_EDITINFO, repository, editinfo_proc, 0);
30017721Speter
30117721Speter    /* run the editor */
30232788Speter    run_setup (editinfo_editor ? editinfo_editor : Editor);
30317721Speter    run_arg (fname);
30417721Speter    if ((retcode = run_exec (RUN_TTY, RUN_TTY, RUN_TTY,
30517721Speter			     RUN_NORMAL | RUN_SIGIGNORE)) != 0)
30617721Speter	error (editinfo_editor ? 1 : 0, retcode == -1 ? errno : 0,
30717721Speter	       editinfo_editor ? "Logfile verification failed" :
30817721Speter	       "warning: editor session failed");
30917721Speter
31017721Speter    /* put the entire message back into the *messagep variable */
31117721Speter
31217721Speter    fp = open_file (fname, "r");
31317721Speter
31417721Speter    if (*messagep)
31517721Speter	free (*messagep);
31617721Speter
31725839Speter    if ( CVS_STAT (fname, &post_stbuf) != 0)
31817721Speter	    error (1, errno, "cannot find size of temp file %s", fname);
31917721Speter
32017721Speter    if (post_stbuf.st_size == 0)
32117721Speter	*messagep = NULL;
32217721Speter    else
32317721Speter    {
32417721Speter	/* On NT, we might read less than st_size bytes, but we won't
32517721Speter	   read more.  So this works.  */
32617721Speter	*messagep = (char *) xmalloc (post_stbuf.st_size + 1);
327102843Speter 	(*messagep)[0] = '\0';
32817721Speter    }
32917721Speter
33017721Speter    line = NULL;
33117721Speter    line_chars_allocated = 0;
33217721Speter
33317721Speter    if (*messagep)
33417721Speter    {
33566528Speter	size_t message_len = post_stbuf.st_size + 1;
33666528Speter	size_t offset = 0;
33717721Speter	while (1)
33817721Speter	{
33917721Speter	    line_length = getline (&line, &line_chars_allocated, fp);
34017721Speter	    if (line_length == -1)
34117721Speter	    {
34217721Speter		if (ferror (fp))
34317721Speter		    error (0, errno, "warning: cannot read %s", fname);
34417721Speter		break;
34517721Speter	    }
34625839Speter	    if (strncmp (line, CVSEDITPREFIX, CVSEDITPREFIXLEN) == 0)
34717721Speter		continue;
34866528Speter	    if (offset + line_length >= message_len)
34966528Speter		expand_string (messagep, &message_len,
35066528Speter				offset + line_length + 1);
35166528Speter	    (void) strcpy (*messagep + offset, line);
35266528Speter	    offset += line_length;
35317721Speter	}
35417721Speter    }
35517721Speter    if (fclose (fp) < 0)
35617721Speter	error (0, errno, "warning: cannot close %s", fname);
35717721Speter
358107487Speter    /* canonicalize emply messages */
359107487Speter    if (*messagep != NULL &&
360107487Speter        (**messagep == '\0' || strcmp (*messagep, "\n") == 0))
36117721Speter    {
362107487Speter	free (*messagep);
363107487Speter	*messagep = NULL;
364107487Speter    }
365107487Speter
366107487Speter    if (pre_stbuf.st_mtime == post_stbuf.st_mtime || *messagep == NULL)
367107487Speter    {
36817721Speter	for (;;)
36917721Speter	{
37017721Speter	    (void) printf ("\nLog message unchanged or not specified\n");
37117721Speter	    (void) printf ("a)bort, c)ontinue, e)dit, !)reuse this message unchanged for remaining dirs\n");
37217721Speter	    (void) printf ("Action: (continue) ");
37317721Speter	    (void) fflush (stdout);
37417721Speter	    line_length = getline (&line, &line_chars_allocated, stdin);
37532788Speter	    if (line_length < 0)
37632788Speter	    {
37732788Speter		error (0, errno, "cannot read from stdin");
37832788Speter		if (unlink_file (fname) < 0)
37932788Speter		    error (0, errno,
38032788Speter			   "warning: cannot remove temp file %s", fname);
38132788Speter		error (1, 0, "aborting");
38232788Speter	    }
38332788Speter	    else if (line_length == 0
38432788Speter		     || *line == '\n' || *line == 'c' || *line == 'C')
38517721Speter		break;
38617721Speter	    if (*line == 'a' || *line == 'A')
38725839Speter		{
38825839Speter		    if (unlink_file (fname) < 0)
38925839Speter			error (0, errno, "warning: cannot remove temp file %s", fname);
39025839Speter		    error (1, 0, "aborted by user");
39125839Speter		}
39217721Speter	    if (*line == 'e' || *line == 'E')
39317721Speter		goto again;
39417721Speter	    if (*line == '!')
39517721Speter	    {
39617721Speter		reuse_log_message = 1;
39717721Speter		break;
39817721Speter	    }
39917721Speter	    (void) printf ("Unknown input\n");
40017721Speter	}
40117721Speter    }
40217721Speter    if (line)
40317721Speter	free (line);
40417721Speter    if (unlink_file (fname) < 0)
40517721Speter	error (0, errno, "warning: cannot remove temp file %s", fname);
40625839Speter    free (fname);
40717721Speter}
40817721Speter
40925839Speter/* Runs the user-defined verification script as part of the commit or import
41025839Speter   process.  This verification is meant to be run whether or not the user
41125839Speter   included the -m atribute.  unlike the do_editor function, this is
41225839Speter   independant of the running of an editor for getting a message.
41325839Speter */
41425839Spetervoid
41526151Speterdo_verify (messagep, repository)
41626151Speter    char **messagep;
417128269Speter    const char *repository;
41825839Speter{
41925839Speter    FILE *fp;
42025839Speter    char *fname;
42125839Speter    int retcode = 0;
42225839Speter
423102843Speter    struct stat pre_stbuf, post_stbuf;
42426151Speter
42581407Speter    if (current_parsed_root->isremote)
42625839Speter	/* The verification will happen on the server.  */
42725839Speter	return;
42825839Speter
42925839Speter    /* FIXME? Do we really want to skip this on noexec?  What do we do
43025839Speter       for the other administrative files?  */
431128269Speter    if (noexec || repository == NULL)
43225839Speter	return;
43325839Speter
434107487Speter    /* Get the name of the verification script to run  */
435107487Speter
436128269Speter    if (Parse_Info (CVSROOTADM_VERIFYMSG, repository, verifymsg_proc, 0) > 0)
437128269Speter	error (1, 0, "Message verification failed");
438128269Speter
439107487Speter    if (!verifymsg_script)
44025839Speter	return;
44125839Speter
44281407Speter    /* open a temporary file, write the message to the
44325839Speter       temp file, and close the file.  */
44425839Speter
44581407Speter    if ((fp = cvs_temp_file (&fname)) == NULL)
446175274Sobrien	error (1, errno, "cannot create temporary file %s",
447175274Sobrien	       fname ? fname : "(null)");
448102843Speter
449107487Speter    if (*messagep != NULL)
450107487Speter	fputs (*messagep, fp);
451107487Speter    if (*messagep == NULL ||
452107487Speter	(*messagep)[0] == '\0' ||
453102843Speter	(*messagep)[strlen (*messagep) - 1] != '\n')
454107487Speter	putc ('\n', fp);
455102843Speter    if (fclose (fp) == EOF)
456102843Speter	error (1, errno, "%s", fname);
457102843Speter
458102843Speter    if (RereadLogAfterVerify == LOGMSG_REREAD_STAT)
45925839Speter    {
460102843Speter	/* Remember the status of the temp file for later */
461102843Speter	if ( CVS_STAT (fname, &pre_stbuf) != 0 )
462102843Speter	    error (1, errno, "cannot stat temp file %s", fname);
46325839Speter
464102843Speter	/*
465102843Speter	 * See if we need to sleep before running the verification
466102843Speter	 * script to avoid time-stamp races.
467102843Speter	 */
468102843Speter	sleep_past (pre_stbuf.st_mtime);
469102843Speter    }
47025839Speter
471107487Speter    run_setup (verifymsg_script);
472107487Speter    run_arg (fname);
473107487Speter    if ((retcode = run_exec (RUN_TTY, RUN_TTY, RUN_TTY,
474107487Speter			     RUN_NORMAL | RUN_SIGIGNORE)) != 0)
475102843Speter    {
476107487Speter	/* Since following error() exits, delete the temp file now.  */
477107487Speter	if (unlink_file (fname) < 0)
478107487Speter	    error (0, errno, "cannot remove %s", fname);
47932788Speter
480107487Speter	error (1, retcode == -1 ? errno : 0,
481107487Speter	       "Message verification failed");
482102843Speter    }
48325839Speter
484102843Speter    /* Get the mod time and size of the possibly new log message
485102843Speter     * in always and stat modes.
486102843Speter     */
487102843Speter    if (RereadLogAfterVerify == LOGMSG_REREAD_ALWAYS ||
488102843Speter	RereadLogAfterVerify == LOGMSG_REREAD_STAT)
489102843Speter    {
490102843Speter	if ( CVS_STAT (fname, &post_stbuf) != 0 )
491102843Speter	    error (1, errno, "cannot find size of temp file %s", fname);
492102843Speter    }
493102843Speter
494102843Speter    /* And reread the log message in `always' mode or in `stat' mode when it's
495102843Speter     * changed
496102843Speter     */
497102843Speter    if (RereadLogAfterVerify == LOGMSG_REREAD_ALWAYS ||
498102843Speter	(RereadLogAfterVerify == LOGMSG_REREAD_STAT &&
499102843Speter	    (pre_stbuf.st_mtime != post_stbuf.st_mtime ||
500102843Speter	     pre_stbuf.st_size != post_stbuf.st_size)))
501102843Speter    {
50226151Speter	/* put the entire message back into the *messagep variable */
50326151Speter
504102843Speter	if (*messagep) free (*messagep);
50526151Speter
506102843Speter	if (post_stbuf.st_size == 0)
50726151Speter	    *messagep = NULL;
50826151Speter	else
50926151Speter	{
510107487Speter	    char *line = NULL;
511107487Speter	    int line_length;
512107487Speter	    size_t line_chars_allocated = 0;
513107487Speter	    char *p;
514107487Speter
515107487Speter	    if ( (fp = open_file (fname, "r")) == NULL )
516107487Speter		error (1, errno, "cannot open temporary file %s", fname);
517107487Speter
518102843Speter	    /* On NT, we might read less than st_size bytes,
519102843Speter	       but we won't read more.  So this works.  */
520107487Speter	    p = *messagep = (char *) xmalloc (post_stbuf.st_size + 1);
52126151Speter	    *messagep[0] = '\0';
52226151Speter
52326151Speter	    while (1)
52426151Speter	    {
525102843Speter		line_length = getline (&line,
526102843Speter				       &line_chars_allocated,
527102843Speter				       fp);
52826151Speter		if (line_length == -1)
52926151Speter		{
53026151Speter		    if (ferror (fp))
531102843Speter			/* Fail in this case because otherwise we will have no
532102843Speter			 * log message
533102843Speter			 */
534102843Speter			error (1, errno, "cannot read %s", fname);
53526151Speter		    break;
53626151Speter		}
53726151Speter		if (strncmp (line, CVSEDITPREFIX, CVSEDITPREFIXLEN) == 0)
53826151Speter		    continue;
53926151Speter		(void) strcpy (p, line);
54026151Speter		p += line_length;
54126151Speter	    }
542102843Speter	    if (line) free (line);
543107487Speter	    if (fclose (fp) < 0)
544107487Speter	        error (0, errno, "warning: cannot close %s", fname);
54526151Speter	}
546102843Speter    }
54726151Speter
548102843Speter    /* Delete the temp file  */
54925839Speter
550102843Speter    if (unlink_file (fname) < 0)
551102843Speter	error (0, errno, "cannot remove %s", fname);
552102843Speter    free (fname);
553175274Sobrien    free (verifymsg_script);
554128269Speter    verifymsg_script = NULL;
55525839Speter}
55625839Speter
55717721Speter/*
55817721Speter * callback proc for Parse_Info for rcsinfo templates this routine basically
55917721Speter * copies the matching template onto the end of the tempfile we are setting
56017721Speter * up
56117721Speter */
56217721Speter/* ARGSUSED */
56317721Speterstatic int
56417721Speterrcsinfo_proc (repository, template)
565128269Speter    const char *repository;
566128269Speter    const char *template;
56717721Speter{
56817721Speter    static char *last_template;
56917721Speter    FILE *tfp;
57017721Speter
57117721Speter    /* nothing to do if the last one included is the same as this one */
57217721Speter    if (last_template && strcmp (last_template, template) == 0)
57317721Speter	return (0);
57417721Speter    if (last_template)
57517721Speter	free (last_template);
57617721Speter    last_template = xstrdup (template);
57717721Speter
57825839Speter    if ((tfp = CVS_FOPEN (template, "r")) != NULL)
57917721Speter    {
58017721Speter	char *line = NULL;
58117721Speter	size_t line_chars_allocated = 0;
58217721Speter
58317721Speter	while (getline (&line, &line_chars_allocated, tfp) >= 0)
58417721Speter	    (void) fputs (line, fp);
58517721Speter	if (ferror (tfp))
58617721Speter	    error (0, errno, "warning: cannot read %s", template);
58717721Speter	if (fclose (tfp) < 0)
58817721Speter	    error (0, errno, "warning: cannot close %s", template);
58917721Speter	if (line)
59017721Speter	    free (line);
59117721Speter	return (0);
59217721Speter    }
59317721Speter    else
59417721Speter    {
59517721Speter	error (0, errno, "Couldn't open rcsinfo template file %s", template);
59617721Speter	return (1);
59717721Speter    }
59817721Speter}
59917721Speter
60017721Speter/*
60117721Speter * Uses setup_tmpfile() to pass the updated message on directly to any
60217721Speter * logfile programs that have a regular expression match for the checked in
60317721Speter * directory in the source repository.  The log information is fed into the
60417721Speter * specified program as standard input.
60517721Speter */
60617721Speterstatic FILE *logfp;
607128269Speterstatic const char *message;
60817721Speterstatic List *changes;
60917721Speter
61017721Spetervoid
61125839SpeterUpdate_Logfile (repository, xmessage, xlogfp, xchanges)
612128269Speter    const char *repository;
613128269Speter    const char *xmessage;
61417721Speter    FILE *xlogfp;
61517721Speter    List *xchanges;
61617721Speter{
61717721Speter    /* nothing to do if the list is empty */
61817721Speter    if (xchanges == NULL || xchanges->list->next == xchanges->list)
61917721Speter	return;
62017721Speter
62117721Speter    /* set up static vars for update_logfile_proc */
62217721Speter    message = xmessage;
62317721Speter    logfp = xlogfp;
62417721Speter    changes = xchanges;
62517721Speter
62617721Speter    /* call Parse_Info to do the actual logfile updates */
62717721Speter    (void) Parse_Info (CVSROOTADM_LOGINFO, repository, update_logfile_proc, 1);
62817721Speter}
62917721Speter
630128269Speter
631128269Speter
63217721Speter/*
63317721Speter * callback proc to actually do the logfile write from Update_Logfile
63417721Speter */
63517721Speterstatic int
63617721Speterupdate_logfile_proc (repository, filter)
637128269Speter    const char *repository;
638128269Speter    const char *filter;
63917721Speter{
640128269Speter    return logfile_write (repository, filter, message, logfp, changes);
64117721Speter}
64217721Speter
643128269Speter
644128269Speter
64517721Speter/*
64625839Speter * concatenate each filename/version onto str_list
64717721Speter */
64817721Speterstatic int
64917721Spetertitle_proc (p, closure)
65017721Speter    Node *p;
65117721Speter    void *closure;
65217721Speter{
65325839Speter    char *c;
654128269Speter    struct logfile_info *li = p->data;
65525839Speter
65625839Speter    if (li->type == type)
65717721Speter    {
65825839Speter	/* Until we decide on the correct logging solution when we add
65925839Speter	   directories or perform imports, T_TITLE nodes will only
66025839Speter	   tack on the name provided, regardless of the format string.
66125839Speter	   You can verify that this assumption is safe by checking the
66225839Speter	   code in add.c (add_directory) and import.c (import). */
66325839Speter
66425839Speter	str_list = xrealloc (str_list, strlen (str_list) + 5);
66517721Speter	(void) strcat (str_list, " ");
66625839Speter
66725839Speter	if (li->type == T_TITLE)
66825839Speter	{
66925839Speter	    str_list = xrealloc (str_list,
67025839Speter				 strlen (str_list) + strlen (p->key) + 5);
67125839Speter	    (void) strcat (str_list, p->key);
67225839Speter	}
67325839Speter	else
67425839Speter	{
67525839Speter	    /* All other nodes use the format string. */
67625839Speter
67725839Speter	    for (c = str_list_format; *c != '\0'; c++)
67825839Speter	    {
67925839Speter		switch (*c)
68025839Speter		{
68125839Speter		case 's':
68225839Speter		    str_list =
68325839Speter			xrealloc (str_list,
68425839Speter				  strlen (str_list) + strlen (p->key) + 5);
68525839Speter		    (void) strcat (str_list, p->key);
68625839Speter		    break;
68725839Speter		case 'V':
68825839Speter		    str_list =
68925839Speter			xrealloc (str_list,
69025839Speter				  (strlen (str_list)
69125839Speter				   + (li->rev_old ? strlen (li->rev_old) : 0)
69225839Speter				   + 10)
69325839Speter				  );
69425839Speter		    (void) strcat (str_list, (li->rev_old
69525839Speter					      ? li->rev_old : "NONE"));
69625839Speter		    break;
69725839Speter		case 'v':
69825839Speter		    str_list =
69925839Speter			xrealloc (str_list,
70025839Speter				  (strlen (str_list)
70125839Speter				   + (li->rev_new ? strlen (li->rev_new) : 0)
70225839Speter				   + 10)
70325839Speter				  );
70425839Speter		    (void) strcat (str_list, (li->rev_new
70525839Speter					      ? li->rev_new : "NONE"));
70625839Speter		    break;
70725839Speter		/* All other characters, we insert an empty field (but
70825839Speter		   we do put in the comma separating it from other
70925839Speter		   fields).  This way if future CVS versions add formatting
71025839Speter		   characters, one can write a loginfo file which at least
71125839Speter		   won't blow up on an old CVS.  */
712107487Speter		/* Note that people who have to deal with spaces in file
713107487Speter		   and directory names are using space to get a known
714107487Speter		   delimiter for the directory name, so it's probably
715107487Speter		   not a good idea to ever define that as a formatting
716107487Speter		   character.  */
71725839Speter		}
71825839Speter		if (*(c + 1) != '\0')
71925839Speter		{
72025839Speter		    str_list = xrealloc (str_list, strlen (str_list) + 5);
72125839Speter		    (void) strcat (str_list, ",");
72225839Speter		}
72325839Speter	    }
72425839Speter	}
72517721Speter    }
72617721Speter    return (0);
72717721Speter}
72817721Speter
72917721Speter/*
73017721Speter * Writes some stuff to the logfile "filter" and returns the status of the
73117721Speter * filter program.
73217721Speter */
73317721Speterstatic int
73425839Speterlogfile_write (repository, filter, message, logfp, changes)
735128269Speter    const char *repository;
736128269Speter    const char *filter;
737128269Speter    const char *message;
73817721Speter    FILE *logfp;
73917721Speter    List *changes;
74017721Speter{
74117721Speter    FILE *pipefp;
74225839Speter    char *prog;
74317721Speter    char *cp;
74417721Speter    int c;
74517721Speter    int pipestatus;
74625839Speter    char *fmt_percent;		/* the location of the percent sign
74725839Speter				   that starts the format string. */
74817721Speter
749175274Sobrien    assert (repository);
750175274Sobrien
75125839Speter    /* The user may specify a format string as part of the filter.
75225839Speter       Originally, `%s' was the only valid string.  The string that
75325839Speter       was substituted for it was:
75425839Speter
75525839Speter         <repository-name> <file1> <file2> <file3> ...
75625839Speter
75725839Speter       Each file was either a new directory/import (T_TITLE), or a
75825839Speter       added (T_ADDED), modified (T_MODIFIED), or removed (T_REMOVED)
75925839Speter       file.
76025839Speter
76125839Speter       It is desirable to preserve that behavior so lots of commitlog
76225839Speter       scripts won't die when they get this new code.  At the same
76325839Speter       time, we'd like to pass other information about the files (like
76425839Speter       version numbers, statuses, or checkin times).
76525839Speter
76625839Speter       The solution is to allow a format string that allows us to
76725839Speter       specify those other pieces of information.  The format string
76825839Speter       will be composed of `%' followed by a single format character,
76925839Speter       or followed by a set of format characters surrounded by `{' and
77025839Speter       `}' as separators.  The format characters are:
77125839Speter
77225839Speter         s = file name
77325839Speter	 V = old version number (pre-checkin)
77425839Speter	 v = new version number (post-checkin)
77525839Speter
77625839Speter       For example, valid format strings are:
77725839Speter
77825839Speter         %{}
77925839Speter	 %s
78025839Speter	 %{s}
78125839Speter	 %{sVv}
78225839Speter
78325839Speter       There's no reason that more items couldn't be added (like
78425839Speter       modification date or file status [added, modified, updated,
78525839Speter       etc.]) -- the code modifications would be minimal (logmsg.c
78625839Speter       (title_proc) and commit.c (check_fileproc)).
78725839Speter
78825839Speter       The output will be a string of tokens separated by spaces.  For
78925839Speter       backwards compatibility, the the first token will be the
79025839Speter       repository name.  The rest of the tokens will be
79125839Speter       comma-delimited lists of the information requested in the
79225839Speter       format string.  For example, if `/u/src/master' is the
79325839Speter       repository, `%{sVv}' is the format string, and three files
79425839Speter       (ChangeLog, Makefile, foo.c) were modified, the output might
79525839Speter       be:
79625839Speter
79725839Speter         /u/src/master ChangeLog,1.1,1.2 Makefile,1.3,1.4 foo.c,1.12,1.13
79825839Speter
79925839Speter       Why this duplicates the old behavior when the format string is
80025839Speter       `%s' is left as an exercise for the reader. */
80125839Speter
80225839Speter    fmt_percent = strchr (filter, '%');
80325839Speter    if (fmt_percent)
80425839Speter    {
80525839Speter	int len;
806128269Speter	const char *srepos;
80725839Speter	char *fmt_begin, *fmt_end;	/* beginning and end of the
80825839Speter					   format string specified in
80925839Speter					   filter. */
81025839Speter	char *fmt_continue;		/* where the string continues
81125839Speter					   after the format string (we
81225839Speter					   might skip a '}') somewhere
81325839Speter					   in there... */
81425839Speter
81525839Speter	/* Grab the format string. */
81625839Speter
81725839Speter	if ((*(fmt_percent + 1) == ' ') || (*(fmt_percent + 1) == '\0'))
81825839Speter	{
81925839Speter	    /* The percent stands alone.  This is an error.  We could
82025839Speter	       be treating ' ' like any other formatting character, but
82125839Speter	       using it as a formatting character seems like it would be
82225839Speter	       a mistake.  */
82325839Speter
82425839Speter	    /* Would be nice to also be giving the line number.  */
82525839Speter	    error (0, 0, "loginfo: '%%' not followed by formatting character");
82625839Speter	    fmt_begin = fmt_percent + 1;
82725839Speter	    fmt_end = fmt_begin;
82825839Speter	    fmt_continue = fmt_begin;
82925839Speter	}
83025839Speter	else if (*(fmt_percent + 1) == '{')
83125839Speter	{
83225839Speter	    /* The percent has a set of characters following it. */
83325839Speter
83425839Speter	    fmt_begin = fmt_percent + 2;
83525839Speter	    fmt_end = strchr (fmt_begin, '}');
83625839Speter	    if (fmt_end)
83725839Speter	    {
83825839Speter		/* Skip over the '}' character. */
83925839Speter
84025839Speter		fmt_continue = fmt_end + 1;
84125839Speter	    }
84225839Speter	    else
84325839Speter	    {
84425839Speter		/* There was no close brace -- assume that format
84525839Speter                   string continues to the end of the line. */
84625839Speter
84725839Speter		/* Would be nice to also be giving the line number.  */
84825839Speter		error (0, 0, "loginfo: '}' missing");
84925839Speter		fmt_end = fmt_begin + strlen (fmt_begin);
85025839Speter		fmt_continue = fmt_end;
85125839Speter	    }
85225839Speter	}
85325839Speter	else
85425839Speter	{
85525839Speter	    /* The percent has a single character following it.  FIXME:
85625839Speter	       %% should expand to a regular percent sign.  */
85725839Speter
85825839Speter	    fmt_begin = fmt_percent + 1;
85925839Speter	    fmt_end = fmt_begin + 1;
86025839Speter	    fmt_continue = fmt_end;
86125839Speter	}
86225839Speter
86325839Speter	len = fmt_end - fmt_begin;
86466528Speter	str_list_format = xmalloc (len + 1);
86525839Speter	strncpy (str_list_format, fmt_begin, len);
86625839Speter	str_list_format[len] = '\0';
86725839Speter
86825839Speter	/* Allocate an initial chunk of memory.  As we build up the string
86925839Speter	   we will realloc it.  */
87025839Speter	if (!str_list)
87125839Speter	    str_list = xmalloc (1);
87225839Speter	str_list[0] = '\0';
87325839Speter
87425839Speter	/* Add entries to the string.  Don't bother looking for
87525839Speter           entries if the format string is empty. */
87625839Speter
87725839Speter	if (str_list_format[0] != '\0')
87825839Speter	{
87925839Speter	    type = T_TITLE;
88025839Speter	    (void) walklist (changes, title_proc, NULL);
88125839Speter	    type = T_ADDED;
88225839Speter	    (void) walklist (changes, title_proc, NULL);
88325839Speter	    type = T_MODIFIED;
88425839Speter	    (void) walklist (changes, title_proc, NULL);
88525839Speter	    type = T_REMOVED;
88625839Speter	    (void) walklist (changes, title_proc, NULL);
88725839Speter	}
88825839Speter
88926065Speter	free (str_list_format);
89026065Speter
89125839Speter	/* Construct the final string. */
89225839Speter
89325839Speter	srepos = Short_Repository (repository);
89425839Speter
89581407Speter	prog = cp = xmalloc ((fmt_percent - filter) + 2 * strlen (srepos)
89681407Speter			+ 2 * strlen (str_list) + strlen (fmt_continue)
89725839Speter			+ 10);
89881407Speter	(void) memcpy (cp, filter, fmt_percent - filter);
89981407Speter	cp += fmt_percent - filter;
90081407Speter	*cp++ = '"';
90181407Speter	cp = shell_escape (cp, srepos);
90281407Speter	cp = shell_escape (cp, str_list);
90381407Speter	*cp++ = '"';
90481407Speter	(void) strcpy (cp, fmt_continue);
90525839Speter
90625839Speter	/* To be nice, free up some memory. */
90725839Speter
90825839Speter	free (str_list);
90925839Speter	str_list = (char *) NULL;
91025839Speter    }
91125839Speter    else
91225839Speter    {
91325839Speter	/* There's no format string. */
91425839Speter	prog = xstrdup (filter);
91525839Speter    }
91625839Speter
91717721Speter    if ((pipefp = run_popen (prog, "w")) == NULL)
91817721Speter    {
91917721Speter	if (!noexec)
92017721Speter	    error (0, 0, "cannot write entry to log filter: %s", prog);
92117721Speter	free (prog);
92217721Speter	return (1);
92317721Speter    }
92417721Speter    (void) fprintf (pipefp, "Update of %s\n", repository);
92525839Speter    (void) fprintf (pipefp, "In directory %s:", hostname);
92625839Speter    cp = xgetwd ();
92725839Speter    if (cp == NULL)
92825839Speter	fprintf (pipefp, "<cannot get working directory: %s>\n\n",
92925839Speter		 strerror (errno));
93025839Speter    else
93125839Speter    {
93225839Speter	fprintf (pipefp, "%s\n\n", cp);
93325839Speter	free (cp);
93425839Speter    }
93525839Speter
93617721Speter    setup_tmpfile (pipefp, "", changes);
937128269Speter    (void) fprintf (pipefp, "Log Message:\n%s\n", (message) ? message : "");
93817721Speter    if (logfp != (FILE *) 0)
93917721Speter    {
94017721Speter	(void) fprintf (pipefp, "Status:\n");
94117721Speter	rewind (logfp);
94217721Speter	while ((c = getc (logfp)) != EOF)
94317721Speter	    (void) putc ((char) c, pipefp);
94417721Speter    }
94517721Speter    free (prog);
94617721Speter    pipestatus = pclose (pipefp);
94717721Speter    return ((pipestatus == -1) || (pipestatus == 127)) ? 1 : 0;
94817721Speter}
94917721Speter
95017721Speter/*
95117721Speter * We choose to use the *last* match within the editinfo file for this
95217721Speter * repository.  This allows us to have a global editinfo program for the
95317721Speter * root of some hierarchy, for example, and different ones within different
95417721Speter * sub-directories of the root (like a special checker for changes made to
95517721Speter * the "src" directory versus changes made to the "doc" or "test"
95617721Speter * directories.
95717721Speter */
95817721Speter/* ARGSUSED */
95917721Speterstatic int
96017721Spetereditinfo_proc(repository, editor)
961128269Speter    const char *repository;
962128269Speter    const char *editor;
96317721Speter{
96417721Speter    /* nothing to do if the last match is the same as this one */
96517721Speter    if (editinfo_editor && strcmp (editinfo_editor, editor) == 0)
96617721Speter	return (0);
96717721Speter    if (editinfo_editor)
96817721Speter	free (editinfo_editor);
96917721Speter
97017721Speter    editinfo_editor = xstrdup (editor);
97117721Speter    return (0);
97217721Speter}
97325839Speter
97425839Speter/*  This routine is calld by Parse_Info.  it asigns the name of the
97525839Speter *  message verification script to the global variable verify_script
97625839Speter */
97725839Speterstatic int
97825839Speterverifymsg_proc (repository, script)
979128269Speter    const char *repository;
980128269Speter    const char *script;
98125839Speter{
98225839Speter    if (verifymsg_script && strcmp (verifymsg_script, script) == 0)
98325839Speter	return (0);
98425839Speter    if (verifymsg_script)
98525839Speter	free (verifymsg_script);
98625839Speter    verifymsg_script = xstrdup (script);
98725839Speter    return (0);
98825839Speter}
989