PageRenderTime 96ms CodeModel.GetById 27ms app.highlight 62ms RepoModel.GetById 1ms app.codeStats 0ms

/contrib/cvs/src/logmsg.c

https://bitbucket.org/freebsd/freebsd-head/
C | 988 lines | 666 code | 104 blank | 218 comment | 221 complexity | c590bc013fc8b639e8fe57f0884a2036 MD5 | raw file
  1/*
  2 * Copyright (C) 1986-2005 The Free Software Foundation, Inc.
  3 *
  4 * Portions Copyright (C) 1998-2005 Derek Price, Ximbiot <http://ximbiot.com>,
  5 *                                  and others.
  6 *
  7 * Portions Copyright (C) 1992, Brian Berliner and Jeff Polk
  8 * Portions Copyright (C) 1989-1992, Brian Berliner
  9 * 
 10 * You may distribute under the terms of the GNU General Public License as
 11 * specified in the README file that comes with the CVS source distribution.
 12 *
 13 * $FreeBSD$
 14 */
 15
 16#include <assert.h>
 17
 18#include "cvs.h"
 19#include "getline.h"
 20
 21static int find_type PROTO((Node * p, void *closure));
 22static int fmt_proc PROTO((Node * p, void *closure));
 23static int logfile_write PROTO((const char *repository, const char *filter,
 24                                const char *message, FILE * logfp,
 25                                List * changes));
 26static int rcsinfo_proc PROTO((const char *repository, const char *template));
 27static int title_proc PROTO((Node * p, void *closure));
 28static int update_logfile_proc PROTO((const char *repository,
 29                                      const char *filter));
 30static void setup_tmpfile PROTO((FILE * xfp, char *xprefix, List * changes));
 31static int editinfo_proc PROTO((const char *repository, const char *template));
 32static int verifymsg_proc PROTO((const char *repository, const char *script));
 33
 34static FILE *fp;
 35static char *str_list;
 36static char *str_list_format;	/* The format for str_list's contents. */
 37static char *editinfo_editor;
 38static char *verifymsg_script;
 39static Ctype type;
 40
 41/* 
 42 * Should the logmsg be re-read during the do_verify phase?
 43 * RereadLogAfterVerify=no|stat|yes
 44 * LOGMSG_REREAD_NEVER  - never re-read the logmsg
 45 * LOGMSG_REREAD_STAT   - re-read the logmsg only if it has changed
 46 * LOGMSG_REREAD_ALWAYS - always re-read the logmsg
 47 */
 48int RereadLogAfterVerify = LOGMSG_REREAD_ALWAYS;
 49
 50/*
 51 * Puts a standard header on the output which is either being prepared for an
 52 * editor session, or being sent to a logfile program.  The modified, added,
 53 * and removed files are included (if any) and formatted to look pretty. */
 54static char *prefix;
 55static int col;
 56static char *tag;
 57static void
 58setup_tmpfile (xfp, xprefix, changes)
 59    FILE *xfp;
 60    char *xprefix;
 61    List *changes;
 62{
 63    /* set up statics */
 64    fp = xfp;
 65    prefix = xprefix;
 66
 67    type = T_MODIFIED;
 68    if (walklist (changes, find_type, NULL) != 0)
 69    {
 70	(void) fprintf (fp, "%sModified Files:\n", prefix);
 71	col = 0;
 72	(void) walklist (changes, fmt_proc, NULL);
 73	(void) fprintf (fp, "\n");
 74	if (tag != NULL)
 75	{
 76	    free (tag);
 77	    tag = NULL;
 78	}
 79    }
 80    type = T_ADDED;
 81    if (walklist (changes, find_type, NULL) != 0)
 82    {
 83	(void) fprintf (fp, "%sAdded Files:\n", prefix);
 84	col = 0;
 85	(void) walklist (changes, fmt_proc, NULL);
 86	(void) fprintf (fp, "\n");
 87	if (tag != NULL)
 88	{
 89	    free (tag);
 90	    tag = NULL;
 91	}
 92    }
 93    type = T_REMOVED;
 94    if (walklist (changes, find_type, NULL) != 0)
 95    {
 96	(void) fprintf (fp, "%sRemoved Files:\n", prefix);
 97	col = 0;
 98	(void) walklist (changes, fmt_proc, NULL);
 99	(void) fprintf (fp, "\n");
100	if (tag != NULL)
101	{
102	    free (tag);
103	    tag = NULL;
104	}
105    }
106}
107
108/*
109 * Looks for nodes of a specified type and returns 1 if found
110 */
111static int
112find_type (p, closure)
113    Node *p;
114    void *closure;
115{
116    struct logfile_info *li = p->data;
117
118    if (li->type == type)
119	return (1);
120    else
121	return (0);
122}
123
124/*
125 * Breaks the files list into reasonable sized lines to avoid line wrap...
126 * all in the name of pretty output.  It only works on nodes whose types
127 * match the one we're looking for
128 */
129static int
130fmt_proc (p, closure)
131    Node *p;
132    void *closure;
133{
134    struct logfile_info *li;
135
136    li = p->data;
137    if (li->type == type)
138    {
139        if (li->tag == NULL
140	    ? tag != NULL
141	    : tag == NULL || strcmp (tag, li->tag) != 0)
142	{
143	    if (col > 0)
144	        (void) fprintf (fp, "\n");
145	    (void) fputs (prefix, fp);
146	    col = strlen (prefix);
147	    while (col < 6)
148	    {
149	        (void) fprintf (fp, " ");
150		++col;
151	    }
152
153	    if (li->tag == NULL)
154	        (void) fprintf (fp, "No tag");
155	    else
156	        (void) fprintf (fp, "Tag: %s", li->tag);
157
158	    if (tag != NULL)
159	        free (tag);
160	    tag = xstrdup (li->tag);
161
162	    /* Force a new line.  */
163	    col = 70;
164	}
165
166	if (col == 0)
167	{
168	    (void) fprintf (fp, "%s\t", prefix);
169	    col = 8;
170	}
171	else if (col > 8 && (col + (int) strlen (p->key)) > 70)
172	{
173	    (void) fprintf (fp, "\n%s\t", prefix);
174	    col = 8;
175	}
176	(void) fprintf (fp, "%s ", p->key);
177	col += strlen (p->key) + 1;
178    }
179    return (0);
180}
181
182/*
183 * Builds a temporary file using setup_tmpfile() and invokes the user's
184 * editor on the file.  The header garbage in the resultant file is then
185 * stripped and the log message is stored in the "message" argument.
186 * 
187 * If REPOSITORY is non-NULL, process rcsinfo for that repository; if it
188 * is NULL, use the CVSADM_TEMPLATE file instead.  REPOSITORY should be
189 * NULL when running in client mode.
190 */
191void
192do_editor (dir, messagep, repository, changes)
193    const char *dir;
194    char **messagep;
195    const char *repository;
196    List *changes;
197{
198    static int reuse_log_message = 0;
199    char *line;
200    int line_length;
201    size_t line_chars_allocated;
202    char *fname;
203    struct stat pre_stbuf, post_stbuf;
204    int retcode = 0;
205
206    assert (!current_parsed_root->isremote != !repository);
207
208    if (noexec || reuse_log_message)
209	return;
210
211    /* Abort creation of temp file if no editor is defined */
212    if (strcmp (Editor, "") == 0 && !editinfo_editor)
213	error(1, 0, "no editor defined, must use -e or -m");
214
215    /* Create a temporary file */
216    /* FIXME - It's possible we should be relying on cvs_temp_file to open
217     * the file here - we get race conditions otherwise.
218     */
219    fname = cvs_temp_name ();
220  again:
221    if ((fp = CVS_FOPEN (fname, "w+")) == NULL)
222	error (1, 0, "cannot create temporary file %s", fname);
223
224    if (*messagep)
225    {
226	(void) fputs (*messagep, fp);
227
228	if ((*messagep)[0] == '\0' ||
229	    (*messagep)[strlen (*messagep) - 1] != '\n')
230	    (void) fprintf (fp, "\n");
231    }
232    else
233	(void) fprintf (fp, "\n");
234
235    if (repository != NULL)
236	/* tack templates on if necessary */
237	(void) Parse_Info (CVSROOTADM_RCSINFO, repository, rcsinfo_proc, 1);
238    else
239    {
240	FILE *tfp;
241	char buf[1024];
242	size_t n;
243	size_t nwrite;
244
245	/* Why "b"?  */
246	tfp = CVS_FOPEN (CVSADM_TEMPLATE, "rb");
247	if (tfp == NULL)
248	{
249	    if (!existence_error (errno))
250		error (1, errno, "cannot read %s", CVSADM_TEMPLATE);
251	}
252	else
253	{
254	    while (!feof (tfp))
255	    {
256		char *p = buf;
257		n = fread (buf, 1, sizeof buf, tfp);
258		nwrite = n;
259		while (nwrite > 0)
260		{
261		    n = fwrite (p, 1, nwrite, fp);
262		    nwrite -= n;
263		    p += n;
264		}
265		if (ferror (tfp))
266		    error (1, errno, "cannot read %s", CVSADM_TEMPLATE);
267	    }
268	    if (fclose (tfp) < 0)
269		error (0, errno, "cannot close %s", CVSADM_TEMPLATE);
270	}
271    }
272
273    (void) fprintf (fp,
274  "%s----------------------------------------------------------------------\n",
275		    CVSEDITPREFIX);
276    (void) fprintf (fp,
277  "%sEnter Log.  Lines beginning with `%.*s' are removed automatically\n%s\n",
278		    CVSEDITPREFIX, CVSEDITPREFIXLEN, CVSEDITPREFIX,
279		    CVSEDITPREFIX);
280    if (dir != NULL && *dir)
281	(void) fprintf (fp, "%sCommitting in %s\n%s\n", CVSEDITPREFIX,
282			dir, CVSEDITPREFIX);
283    if (changes != NULL)
284	setup_tmpfile (fp, CVSEDITPREFIX, changes);
285    (void) fprintf (fp,
286  "%s----------------------------------------------------------------------\n",
287		    CVSEDITPREFIX);
288
289    /* finish off the temp file */
290    if (fclose (fp) == EOF)
291        error (1, errno, "%s", fname);
292    if ( CVS_STAT (fname, &pre_stbuf) == -1)
293	pre_stbuf.st_mtime = 0;
294
295    if (editinfo_editor)
296	free (editinfo_editor);
297    editinfo_editor = (char *) NULL;
298    if (!current_parsed_root->isremote && repository != NULL)
299	(void) Parse_Info (CVSROOTADM_EDITINFO, repository, editinfo_proc, 0);
300
301    /* run the editor */
302    run_setup (editinfo_editor ? editinfo_editor : Editor);
303    run_arg (fname);
304    if ((retcode = run_exec (RUN_TTY, RUN_TTY, RUN_TTY,
305			     RUN_NORMAL | RUN_SIGIGNORE)) != 0)
306	error (editinfo_editor ? 1 : 0, retcode == -1 ? errno : 0,
307	       editinfo_editor ? "Logfile verification failed" :
308	       "warning: editor session failed");
309
310    /* put the entire message back into the *messagep variable */
311
312    fp = open_file (fname, "r");
313
314    if (*messagep)
315	free (*messagep);
316
317    if ( CVS_STAT (fname, &post_stbuf) != 0)
318	    error (1, errno, "cannot find size of temp file %s", fname);
319
320    if (post_stbuf.st_size == 0)
321	*messagep = NULL;
322    else
323    {
324	/* On NT, we might read less than st_size bytes, but we won't
325	   read more.  So this works.  */
326	*messagep = (char *) xmalloc (post_stbuf.st_size + 1);
327 	(*messagep)[0] = '\0';
328    }
329
330    line = NULL;
331    line_chars_allocated = 0;
332
333    if (*messagep)
334    {
335	size_t message_len = post_stbuf.st_size + 1;
336	size_t offset = 0;
337	while (1)
338	{
339	    line_length = getline (&line, &line_chars_allocated, fp);
340	    if (line_length == -1)
341	    {
342		if (ferror (fp))
343		    error (0, errno, "warning: cannot read %s", fname);
344		break;
345	    }
346	    if (strncmp (line, CVSEDITPREFIX, CVSEDITPREFIXLEN) == 0)
347		continue;
348	    if (offset + line_length >= message_len)
349		expand_string (messagep, &message_len,
350				offset + line_length + 1);
351	    (void) strcpy (*messagep + offset, line);
352	    offset += line_length;
353	}
354    }
355    if (fclose (fp) < 0)
356	error (0, errno, "warning: cannot close %s", fname);
357
358    /* canonicalize emply messages */
359    if (*messagep != NULL &&
360        (**messagep == '\0' || strcmp (*messagep, "\n") == 0))
361    {
362	free (*messagep);
363	*messagep = NULL;
364    }
365
366    if (pre_stbuf.st_mtime == post_stbuf.st_mtime || *messagep == NULL)
367    {
368	for (;;)
369	{
370	    (void) printf ("\nLog message unchanged or not specified\n");
371	    (void) printf ("a)bort, c)ontinue, e)dit, !)reuse this message unchanged for remaining dirs\n");
372	    (void) printf ("Action: (continue) ");
373	    (void) fflush (stdout);
374	    line_length = getline (&line, &line_chars_allocated, stdin);
375	    if (line_length < 0)
376	    {
377		error (0, errno, "cannot read from stdin");
378		if (unlink_file (fname) < 0)
379		    error (0, errno,
380			   "warning: cannot remove temp file %s", fname);
381		error (1, 0, "aborting");
382	    }
383	    else if (line_length == 0
384		     || *line == '\n' || *line == 'c' || *line == 'C')
385		break;
386	    if (*line == 'a' || *line == 'A')
387		{
388		    if (unlink_file (fname) < 0)
389			error (0, errno, "warning: cannot remove temp file %s", fname);
390		    error (1, 0, "aborted by user");
391		}
392	    if (*line == 'e' || *line == 'E')
393		goto again;
394	    if (*line == '!')
395	    {
396		reuse_log_message = 1;
397		break;
398	    }
399	    (void) printf ("Unknown input\n");
400	}
401    }
402    if (line)
403	free (line);
404    if (unlink_file (fname) < 0)
405	error (0, errno, "warning: cannot remove temp file %s", fname);
406    free (fname);
407}
408
409/* Runs the user-defined verification script as part of the commit or import 
410   process.  This verification is meant to be run whether or not the user 
411   included the -m atribute.  unlike the do_editor function, this is 
412   independant of the running of an editor for getting a message.
413 */
414void
415do_verify (messagep, repository)
416    char **messagep;
417    const char *repository;
418{
419    FILE *fp;
420    char *fname;
421    int retcode = 0;
422
423    struct stat pre_stbuf, post_stbuf;
424
425    if (current_parsed_root->isremote)
426	/* The verification will happen on the server.  */
427	return;
428
429    /* FIXME? Do we really want to skip this on noexec?  What do we do
430       for the other administrative files?  */
431    if (noexec || repository == NULL)
432	return;
433
434    /* Get the name of the verification script to run  */
435
436    if (Parse_Info (CVSROOTADM_VERIFYMSG, repository, verifymsg_proc, 0) > 0)
437	error (1, 0, "Message verification failed");
438
439    if (!verifymsg_script)
440	return;
441
442    /* open a temporary file, write the message to the 
443       temp file, and close the file.  */
444
445    if ((fp = cvs_temp_file (&fname)) == NULL)
446	error (1, errno, "cannot create temporary file %s",
447	       fname ? fname : "(null)");
448
449    if (*messagep != NULL)
450	fputs (*messagep, fp);
451    if (*messagep == NULL ||
452	(*messagep)[0] == '\0' ||
453	(*messagep)[strlen (*messagep) - 1] != '\n')
454	putc ('\n', fp);
455    if (fclose (fp) == EOF)
456	error (1, errno, "%s", fname);
457
458    if (RereadLogAfterVerify == LOGMSG_REREAD_STAT)
459    {
460	/* Remember the status of the temp file for later */
461	if ( CVS_STAT (fname, &pre_stbuf) != 0 )
462	    error (1, errno, "cannot stat temp file %s", fname);
463
464	/*
465	 * See if we need to sleep before running the verification
466	 * script to avoid time-stamp races.
467	 */
468	sleep_past (pre_stbuf.st_mtime);
469    }
470
471    run_setup (verifymsg_script);
472    run_arg (fname);
473    if ((retcode = run_exec (RUN_TTY, RUN_TTY, RUN_TTY,
474			     RUN_NORMAL | RUN_SIGIGNORE)) != 0)
475    {
476	/* Since following error() exits, delete the temp file now.  */
477	if (unlink_file (fname) < 0)
478	    error (0, errno, "cannot remove %s", fname);
479
480	error (1, retcode == -1 ? errno : 0, 
481	       "Message verification failed");
482    }
483
484    /* Get the mod time and size of the possibly new log message
485     * in always and stat modes.
486     */
487    if (RereadLogAfterVerify == LOGMSG_REREAD_ALWAYS ||
488	RereadLogAfterVerify == LOGMSG_REREAD_STAT)
489    {
490	if ( CVS_STAT (fname, &post_stbuf) != 0 )
491	    error (1, errno, "cannot find size of temp file %s", fname);
492    }
493
494    /* And reread the log message in `always' mode or in `stat' mode when it's
495     * changed
496     */
497    if (RereadLogAfterVerify == LOGMSG_REREAD_ALWAYS ||
498	(RereadLogAfterVerify == LOGMSG_REREAD_STAT &&
499	    (pre_stbuf.st_mtime != post_stbuf.st_mtime ||
500	     pre_stbuf.st_size != post_stbuf.st_size)))
501    {
502	/* put the entire message back into the *messagep variable */
503
504	if (*messagep) free (*messagep);
505
506	if (post_stbuf.st_size == 0)
507	    *messagep = NULL;
508	else
509	{
510	    char *line = NULL;
511	    int line_length;
512	    size_t line_chars_allocated = 0;
513	    char *p;
514
515	    if ( (fp = open_file (fname, "r")) == NULL )
516		error (1, errno, "cannot open temporary file %s", fname);
517
518	    /* On NT, we might read less than st_size bytes,
519	       but we won't read more.  So this works.  */
520	    p = *messagep = (char *) xmalloc (post_stbuf.st_size + 1);
521	    *messagep[0] = '\0';
522
523	    while (1)
524	    {
525		line_length = getline (&line,
526				       &line_chars_allocated,
527				       fp);
528		if (line_length == -1)
529		{
530		    if (ferror (fp))
531			/* Fail in this case because otherwise we will have no
532			 * log message
533			 */
534			error (1, errno, "cannot read %s", fname);
535		    break;
536		}
537		if (strncmp (line, CVSEDITPREFIX, CVSEDITPREFIXLEN) == 0)
538		    continue;
539		(void) strcpy (p, line);
540		p += line_length;
541	    }
542	    if (line) free (line);
543	    if (fclose (fp) < 0)
544	        error (0, errno, "warning: cannot close %s", fname);
545	}
546    }
547
548    /* Delete the temp file  */
549
550    if (unlink_file (fname) < 0)
551	error (0, errno, "cannot remove %s", fname);
552    free (fname);
553    free (verifymsg_script);
554    verifymsg_script = NULL;
555}
556
557/*
558 * callback proc for Parse_Info for rcsinfo templates this routine basically
559 * copies the matching template onto the end of the tempfile we are setting
560 * up
561 */
562/* ARGSUSED */
563static int
564rcsinfo_proc (repository, template)
565    const char *repository;
566    const char *template;
567{
568    static char *last_template;
569    FILE *tfp;
570
571    /* nothing to do if the last one included is the same as this one */
572    if (last_template && strcmp (last_template, template) == 0)
573	return (0);
574    if (last_template)
575	free (last_template);
576    last_template = xstrdup (template);
577
578    if ((tfp = CVS_FOPEN (template, "r")) != NULL)
579    {
580	char *line = NULL;
581	size_t line_chars_allocated = 0;
582
583	while (getline (&line, &line_chars_allocated, tfp) >= 0)
584	    (void) fputs (line, fp);
585	if (ferror (tfp))
586	    error (0, errno, "warning: cannot read %s", template);
587	if (fclose (tfp) < 0)
588	    error (0, errno, "warning: cannot close %s", template);
589	if (line)
590	    free (line);
591	return (0);
592    }
593    else
594    {
595	error (0, errno, "Couldn't open rcsinfo template file %s", template);
596	return (1);
597    }
598}
599
600/*
601 * Uses setup_tmpfile() to pass the updated message on directly to any
602 * logfile programs that have a regular expression match for the checked in
603 * directory in the source repository.  The log information is fed into the
604 * specified program as standard input.
605 */
606static FILE *logfp;
607static const char *message;
608static List *changes;
609
610void
611Update_Logfile (repository, xmessage, xlogfp, xchanges)
612    const char *repository;
613    const char *xmessage;
614    FILE *xlogfp;
615    List *xchanges;
616{
617    /* nothing to do if the list is empty */
618    if (xchanges == NULL || xchanges->list->next == xchanges->list)
619	return;
620
621    /* set up static vars for update_logfile_proc */
622    message = xmessage;
623    logfp = xlogfp;
624    changes = xchanges;
625
626    /* call Parse_Info to do the actual logfile updates */
627    (void) Parse_Info (CVSROOTADM_LOGINFO, repository, update_logfile_proc, 1);
628}
629
630
631
632/*
633 * callback proc to actually do the logfile write from Update_Logfile
634 */
635static int
636update_logfile_proc (repository, filter)
637    const char *repository;
638    const char *filter;
639{
640    return logfile_write (repository, filter, message, logfp, changes);
641}
642
643
644
645/*
646 * concatenate each filename/version onto str_list
647 */
648static int
649title_proc (p, closure)
650    Node *p;
651    void *closure;
652{
653    char *c;
654    struct logfile_info *li = p->data;
655
656    if (li->type == type)
657    {
658	/* Until we decide on the correct logging solution when we add
659	   directories or perform imports, T_TITLE nodes will only
660	   tack on the name provided, regardless of the format string.
661	   You can verify that this assumption is safe by checking the
662	   code in add.c (add_directory) and import.c (import). */
663
664	str_list = xrealloc (str_list, strlen (str_list) + 5);
665	(void) strcat (str_list, " ");
666
667	if (li->type == T_TITLE)
668	{
669	    str_list = xrealloc (str_list,
670				 strlen (str_list) + strlen (p->key) + 5);
671	    (void) strcat (str_list, p->key);
672	}
673	else
674	{
675	    /* All other nodes use the format string. */
676
677	    for (c = str_list_format; *c != '\0'; c++)
678	    {
679		switch (*c)
680		{
681		case 's':
682		    str_list =
683			xrealloc (str_list,
684				  strlen (str_list) + strlen (p->key) + 5);
685		    (void) strcat (str_list, p->key);
686		    break;
687		case 'V':
688		    str_list =
689			xrealloc (str_list,
690				  (strlen (str_list)
691				   + (li->rev_old ? strlen (li->rev_old) : 0)
692				   + 10)
693				  );
694		    (void) strcat (str_list, (li->rev_old
695					      ? li->rev_old : "NONE"));
696		    break;
697		case 'v':
698		    str_list =
699			xrealloc (str_list,
700				  (strlen (str_list)
701				   + (li->rev_new ? strlen (li->rev_new) : 0)
702				   + 10)
703				  );
704		    (void) strcat (str_list, (li->rev_new
705					      ? li->rev_new : "NONE"));
706		    break;
707		/* All other characters, we insert an empty field (but
708		   we do put in the comma separating it from other
709		   fields).  This way if future CVS versions add formatting
710		   characters, one can write a loginfo file which at least
711		   won't blow up on an old CVS.  */
712		/* Note that people who have to deal with spaces in file
713		   and directory names are using space to get a known
714		   delimiter for the directory name, so it's probably
715		   not a good idea to ever define that as a formatting
716		   character.  */
717		}
718		if (*(c + 1) != '\0')
719		{
720		    str_list = xrealloc (str_list, strlen (str_list) + 5);
721		    (void) strcat (str_list, ",");
722		}
723	    }
724	}
725    }
726    return (0);
727}
728
729/*
730 * Writes some stuff to the logfile "filter" and returns the status of the
731 * filter program.
732 */
733static int
734logfile_write (repository, filter, message, logfp, changes)
735    const char *repository;
736    const char *filter;
737    const char *message;
738    FILE *logfp;
739    List *changes;
740{
741    FILE *pipefp;
742    char *prog;
743    char *cp;
744    int c;
745    int pipestatus;
746    char *fmt_percent;		/* the location of the percent sign
747				   that starts the format string. */
748
749    assert (repository);
750
751    /* The user may specify a format string as part of the filter.
752       Originally, `%s' was the only valid string.  The string that
753       was substituted for it was:
754
755         <repository-name> <file1> <file2> <file3> ...
756
757       Each file was either a new directory/import (T_TITLE), or a
758       added (T_ADDED), modified (T_MODIFIED), or removed (T_REMOVED)
759       file.
760
761       It is desirable to preserve that behavior so lots of commitlog
762       scripts won't die when they get this new code.  At the same
763       time, we'd like to pass other information about the files (like
764       version numbers, statuses, or checkin times).
765
766       The solution is to allow a format string that allows us to
767       specify those other pieces of information.  The format string
768       will be composed of `%' followed by a single format character,
769       or followed by a set of format characters surrounded by `{' and
770       `}' as separators.  The format characters are:
771
772         s = file name
773	 V = old version number (pre-checkin)
774	 v = new version number (post-checkin)
775
776       For example, valid format strings are:
777
778         %{}
779	 %s
780	 %{s}
781	 %{sVv}
782
783       There's no reason that more items couldn't be added (like
784       modification date or file status [added, modified, updated,
785       etc.]) -- the code modifications would be minimal (logmsg.c
786       (title_proc) and commit.c (check_fileproc)).
787
788       The output will be a string of tokens separated by spaces.  For
789       backwards compatibility, the the first token will be the
790       repository name.  The rest of the tokens will be
791       comma-delimited lists of the information requested in the
792       format string.  For example, if `/u/src/master' is the
793       repository, `%{sVv}' is the format string, and three files
794       (ChangeLog, Makefile, foo.c) were modified, the output might
795       be:
796
797         /u/src/master ChangeLog,1.1,1.2 Makefile,1.3,1.4 foo.c,1.12,1.13
798
799       Why this duplicates the old behavior when the format string is
800       `%s' is left as an exercise for the reader. */
801
802    fmt_percent = strchr (filter, '%');
803    if (fmt_percent)
804    {
805	int len;
806	const char *srepos;
807	char *fmt_begin, *fmt_end;	/* beginning and end of the
808					   format string specified in
809					   filter. */
810	char *fmt_continue;		/* where the string continues
811					   after the format string (we
812					   might skip a '}') somewhere
813					   in there... */
814
815	/* Grab the format string. */
816
817	if ((*(fmt_percent + 1) == ' ') || (*(fmt_percent + 1) == '\0'))
818	{
819	    /* The percent stands alone.  This is an error.  We could
820	       be treating ' ' like any other formatting character, but
821	       using it as a formatting character seems like it would be
822	       a mistake.  */
823
824	    /* Would be nice to also be giving the line number.  */
825	    error (0, 0, "loginfo: '%%' not followed by formatting character");
826	    fmt_begin = fmt_percent + 1;
827	    fmt_end = fmt_begin;
828	    fmt_continue = fmt_begin;
829	}
830	else if (*(fmt_percent + 1) == '{')
831	{
832	    /* The percent has a set of characters following it. */
833
834	    fmt_begin = fmt_percent + 2;
835	    fmt_end = strchr (fmt_begin, '}');
836	    if (fmt_end)
837	    {
838		/* Skip over the '}' character. */
839
840		fmt_continue = fmt_end + 1;
841	    }
842	    else
843	    {
844		/* There was no close brace -- assume that format
845                   string continues to the end of the line. */
846
847		/* Would be nice to also be giving the line number.  */
848		error (0, 0, "loginfo: '}' missing");
849		fmt_end = fmt_begin + strlen (fmt_begin);
850		fmt_continue = fmt_end;
851	    }
852	}
853	else
854	{
855	    /* The percent has a single character following it.  FIXME:
856	       %% should expand to a regular percent sign.  */
857
858	    fmt_begin = fmt_percent + 1;
859	    fmt_end = fmt_begin + 1;
860	    fmt_continue = fmt_end;
861	}
862
863	len = fmt_end - fmt_begin;
864	str_list_format = xmalloc (len + 1);
865	strncpy (str_list_format, fmt_begin, len);
866	str_list_format[len] = '\0';
867
868	/* Allocate an initial chunk of memory.  As we build up the string
869	   we will realloc it.  */
870	if (!str_list)
871	    str_list = xmalloc (1);
872	str_list[0] = '\0';
873
874	/* Add entries to the string.  Don't bother looking for
875           entries if the format string is empty. */
876
877	if (str_list_format[0] != '\0')
878	{
879	    type = T_TITLE;
880	    (void) walklist (changes, title_proc, NULL);
881	    type = T_ADDED;
882	    (void) walklist (changes, title_proc, NULL);
883	    type = T_MODIFIED;
884	    (void) walklist (changes, title_proc, NULL);
885	    type = T_REMOVED;
886	    (void) walklist (changes, title_proc, NULL);
887	}
888
889	free (str_list_format);
890	
891	/* Construct the final string. */
892
893	srepos = Short_Repository (repository);
894
895	prog = cp = xmalloc ((fmt_percent - filter) + 2 * strlen (srepos)
896			+ 2 * strlen (str_list) + strlen (fmt_continue)
897			+ 10);
898	(void) memcpy (cp, filter, fmt_percent - filter);
899	cp += fmt_percent - filter;
900	*cp++ = '"';
901	cp = shell_escape (cp, srepos);
902	cp = shell_escape (cp, str_list);
903	*cp++ = '"';
904	(void) strcpy (cp, fmt_continue);
905	    
906	/* To be nice, free up some memory. */
907
908	free (str_list);
909	str_list = (char *) NULL;
910    }
911    else
912    {
913	/* There's no format string. */
914	prog = xstrdup (filter);
915    }
916
917    if ((pipefp = run_popen (prog, "w")) == NULL)
918    {
919	if (!noexec)
920	    error (0, 0, "cannot write entry to log filter: %s", prog);
921	free (prog);
922	return (1);
923    }
924    (void) fprintf (pipefp, "Update of %s\n", repository);
925    (void) fprintf (pipefp, "In directory %s:", hostname);
926    cp = xgetwd ();
927    if (cp == NULL)
928	fprintf (pipefp, "<cannot get working directory: %s>\n\n",
929		 strerror (errno));
930    else
931    {
932	fprintf (pipefp, "%s\n\n", cp);
933	free (cp);
934    }
935
936    setup_tmpfile (pipefp, "", changes);
937    (void) fprintf (pipefp, "Log Message:\n%s\n", (message) ? message : "");
938    if (logfp != (FILE *) 0)
939    {
940	(void) fprintf (pipefp, "Status:\n");
941	rewind (logfp);
942	while ((c = getc (logfp)) != EOF)
943	    (void) putc ((char) c, pipefp);
944    }
945    free (prog);
946    pipestatus = pclose (pipefp);
947    return ((pipestatus == -1) || (pipestatus == 127)) ? 1 : 0;
948}
949
950/*
951 * We choose to use the *last* match within the editinfo file for this
952 * repository.  This allows us to have a global editinfo program for the
953 * root of some hierarchy, for example, and different ones within different
954 * sub-directories of the root (like a special checker for changes made to
955 * the "src" directory versus changes made to the "doc" or "test"
956 * directories.
957 */
958/* ARGSUSED */
959static int
960editinfo_proc(repository, editor)
961    const char *repository;
962    const char *editor;
963{
964    /* nothing to do if the last match is the same as this one */
965    if (editinfo_editor && strcmp (editinfo_editor, editor) == 0)
966	return (0);
967    if (editinfo_editor)
968	free (editinfo_editor);
969
970    editinfo_editor = xstrdup (editor);
971    return (0);
972}
973
974/*  This routine is calld by Parse_Info.  it asigns the name of the
975 *  message verification script to the global variable verify_script
976 */
977static int
978verifymsg_proc (repository, script)
979    const char *repository;
980    const char *script;
981{
982    if (verifymsg_script && strcmp (verifymsg_script, script) == 0)
983	return (0);
984    if (verifymsg_script)
985	free (verifymsg_script);
986    verifymsg_script = xstrdup (script);
987    return (0);
988}