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