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