xref: /openbsd/gnu/usr.bin/cvs/src/logmsg.c (revision 34a6a163)
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
setup_tmpfile(xfp,xprefix,changes)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
find_type(p,closure)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
fmt_proc(p,closure)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
do_editor(dir,messagep,repository,changes)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 
186     if (noexec || reuse_log_message)
187 	return;
188 
189     /* Abort creation of temp file if no editor is defined */
190     if (strcmp (Editor, "") == 0 && !editinfo_editor)
191 	error(1, 0, "no editor defined, must use -e or -m");
192 
193     /* Create a temporary file */
194     /* FIXME - It's possible we should be relying on cvs_temp_file to open
195      * the file here - we get race conditions otherwise.
196      */
197     fname = cvs_temp_name ();
198   again:
199     if ((fp = CVS_FOPEN (fname, "w+")) == NULL)
200 	error (1, 0, "cannot create temporary file %s", fname);
201 
202     if (*messagep)
203     {
204 	(void) fprintf (fp, "%s", *messagep);
205 
206 	if ((*messagep)[0] == '\0' ||
207 	    (*messagep)[strlen (*messagep) - 1] != '\n')
208 	    (void) fprintf (fp, "\n");
209     }
210     else
211 	(void) fprintf (fp, "\n");
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 = get_line (&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: (abort) ");
350 	    (void) fflush (stdout);
351 	    line_length = get_line (&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 == 'a' || *line == 'A')
362 		{
363 		    if (unlink_file (fname) < 0)
364 			error (0, errno, "warning: cannot remove temp file %s", fname);
365 		    error (1, 0, "aborted by user");
366 		}
367 	    if (*line == 'c' || *line == 'C')
368 		break;
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  */
391 void
do_verify(message,repository)392 do_verify (message, repository)
393     char *message;
394     char *repository;
395 {
396     FILE *fp;
397     char *fname;
398     int retcode = 0;
399 
400 #ifdef CLIENT_SUPPORT
401     if (current_parsed_root->isremote)
402 	/* The verification will happen on the server.  */
403 	return;
404 #endif
405 
406     /* FIXME? Do we really want to skip this on noexec?  What do we do
407        for the other administrative files?  */
408     if (noexec)
409 	return;
410 
411     /* If there's no message, then we have nothing to verify.  Can this
412        case happen?  And if so why would we print a message?  */
413     if (message == NULL)
414     {
415 	cvs_output ("No message to verify\n", 0);
416 	return;
417     }
418 
419     /* open a temporary file, write the message to the
420        temp file, and close the file.  */
421 
422     if ((fp = cvs_temp_file (&fname)) == NULL)
423 	error (1, errno, "cannot create temporary file %s", fname);
424     else
425     {
426 	fprintf (fp, "%s", message);
427 	if ((message)[0] == '\0' ||
428 	    (message)[strlen (message) - 1] != '\n')
429 	    (void) fprintf (fp, "%s", "\n");
430 	if (fclose (fp) == EOF)
431 	    error (1, errno, "%s", fname);
432 
433 	/* Get the name of the verification script to run  */
434 
435 	if (repository != NULL)
436 	    (void) Parse_Info (CVSROOTADM_VERIFYMSG, repository,
437 			       verifymsg_proc, 0);
438 
439 	/* Run the verification script  */
440 
441 	if (verifymsg_script)
442 	{
443 	    run_setup (verifymsg_script);
444 	    run_arg (fname);
445 	    if ((retcode = run_exec (RUN_TTY, RUN_TTY, RUN_TTY,
446 				     RUN_NORMAL | RUN_SIGIGNORE)) != 0)
447 	    {
448 		/* Since following error() exits, delete the temp file
449 		   now.  */
450 		if (unlink_file (fname) < 0)
451 		    error (0, errno, "cannot remove %s", fname);
452 
453 		error (1, retcode == -1 ? errno : 0,
454 		       "Message verification failed");
455 	    }
456 	}
457 
458 	/* Delete the temp file  */
459 
460 	if (unlink_file (fname) < 0)
461 	    error (0, errno, "cannot remove %s", fname);
462 	free (fname);
463     }
464 }
465 
466 /*
467  * callback proc for Parse_Info for rcsinfo templates this routine basically
468  * copies the matching template onto the end of the tempfile we are setting
469  * up
470  */
471 /* ARGSUSED */
472 static int
rcsinfo_proc(repository,template)473 rcsinfo_proc (repository, template)
474     char *repository;
475     char *template;
476 {
477     static char *last_template;
478     FILE *tfp;
479 
480     /* nothing to do if the last one included is the same as this one */
481     if (last_template && strcmp (last_template, template) == 0)
482 	return (0);
483     if (last_template)
484 	free (last_template);
485     last_template = xstrdup (template);
486 
487     if ((tfp = CVS_FOPEN (template, "r")) != NULL)
488     {
489 	char *line = NULL;
490 	size_t line_chars_allocated = 0;
491 
492 	while (get_line (&line, &line_chars_allocated, tfp) >= 0)
493 	    (void) fputs (line, fp);
494 	if (ferror (tfp))
495 	    error (0, errno, "warning: cannot read %s", template);
496 	if (fclose (tfp) < 0)
497 	    error (0, errno, "warning: cannot close %s", template);
498 	if (line)
499 	    free (line);
500 	return (0);
501     }
502     else
503     {
504 	error (0, errno, "Couldn't open rcsinfo template file %s", template);
505 	return (1);
506     }
507 }
508 
509 /*
510  * Uses setup_tmpfile() to pass the updated message on directly to any
511  * logfile programs that have a regular expression match for the checked in
512  * directory in the source repository.  The log information is fed into the
513  * specified program as standard input.
514  */
515 static FILE *logfp;
516 static char *message;
517 static List *changes;
518 
519 void
Update_Logfile(repository,xmessage,xlogfp,xchanges)520 Update_Logfile (repository, xmessage, xlogfp, xchanges)
521     char *repository;
522     char *xmessage;
523     FILE *xlogfp;
524     List *xchanges;
525 {
526     /* nothing to do if the list is empty */
527     if (xchanges == NULL || xchanges->list->next == xchanges->list)
528 	return;
529 
530     /* set up static vars for update_logfile_proc */
531     message = xmessage;
532     logfp = xlogfp;
533     changes = xchanges;
534 
535     /* call Parse_Info to do the actual logfile updates */
536     (void) Parse_Info (CVSROOTADM_LOGINFO, repository, update_logfile_proc, 1);
537 }
538 
539 /*
540  * callback proc to actually do the logfile write from Update_Logfile
541  */
542 static int
update_logfile_proc(repository,filter)543 update_logfile_proc (repository, filter)
544     char *repository;
545     char *filter;
546 {
547     return (logfile_write (repository, filter, message, logfp, changes));
548 }
549 
550 /*
551  * concatenate each filename/version onto str_list
552  */
553 static int
title_proc(p,closure)554 title_proc (p, closure)
555     Node *p;
556     void *closure;
557 {
558     struct logfile_info *li;
559     char *c;
560 
561     li = (struct logfile_info *) p->data;
562     if (li->type == type)
563     {
564 	/* Until we decide on the correct logging solution when we add
565 	   directories or perform imports, T_TITLE nodes will only
566 	   tack on the name provided, regardless of the format string.
567 	   You can verify that this assumption is safe by checking the
568 	   code in add.c (add_directory) and import.c (import). */
569 
570 	str_list = xrealloc (str_list, strlen (str_list) + 5);
571 	(void) strcat (str_list, " ");
572 
573 	if (li->type == T_TITLE)
574 	{
575 	    str_list = xrealloc (str_list,
576 				 strlen (str_list) + strlen (p->key) + 5);
577 	    (void) strcat (str_list, p->key);
578 	}
579 	else
580 	{
581 	    /* All other nodes use the format string. */
582 
583 	    for (c = str_list_format; *c != '\0'; c++)
584 	    {
585 		switch (*c)
586 		{
587 		case 's':
588 		    str_list =
589 			xrealloc (str_list,
590 				  strlen (str_list) + strlen (p->key) + 5);
591 		    (void) strcat (str_list, p->key);
592 		    break;
593 		case 't':
594 		    str_list =
595 			xrealloc (str_list,
596 				  (strlen (str_list)
597 				   + (li->tag ? strlen (li->tag) : 0)
598 				   + 10)
599 				  );
600 		    (void) strcat (str_list, (li->tag ? li->tag : ""));
601 		    break;
602 		case 'V':
603 		    str_list =
604 			xrealloc (str_list,
605 				  (strlen (str_list)
606 				   + (li->rev_old ? strlen (li->rev_old) : 0)
607 				   + 10)
608 				  );
609 		    (void) strcat (str_list, (li->rev_old
610 					      ? li->rev_old : "NONE"));
611 		    break;
612 		case 'v':
613 		    str_list =
614 			xrealloc (str_list,
615 				  (strlen (str_list)
616 				   + (li->rev_new ? strlen (li->rev_new) : 0)
617 				   + 10)
618 				  );
619 		    (void) strcat (str_list, (li->rev_new
620 					      ? li->rev_new : "NONE"));
621 		    break;
622 		/* All other characters, we insert an empty field (but
623 		   we do put in the comma separating it from other
624 		   fields).  This way if future CVS versions add formatting
625 		   characters, one can write a loginfo file which at least
626 		   won't blow up on an old CVS.  */
627 		}
628 		if (*(c + 1) != '\0')
629 		{
630 		    str_list = xrealloc (str_list, strlen (str_list) + 5);
631 		    (void) strcat (str_list, ",");
632 		}
633 	    }
634 	}
635     }
636     return (0);
637 }
638 
639 /*
640  * Writes some stuff to the logfile "filter" and returns the status of the
641  * filter program.
642  */
643 static int
logfile_write(repository,filter,message,logfp,changes)644 logfile_write (repository, filter, message, logfp, changes)
645     char *repository;
646     char *filter;
647     char *message;
648     FILE *logfp;
649     List *changes;
650 {
651     FILE *pipefp;
652     char *prog;
653     char *cp;
654     int c;
655     int pipestatus;
656     char *fmt_percent;		/* the location of the percent sign
657 				   that starts the format string. */
658 
659     /* The user may specify a format string as part of the filter.
660        Originally, `%s' was the only valid string.  The string that
661        was substituted for it was:
662 
663          <repository-name> <file1> <file2> <file3> ...
664 
665        Each file was either a new directory/import (T_TITLE), or a
666        added (T_ADDED), modified (T_MODIFIED), or removed (T_REMOVED)
667        file.
668 
669        It is desirable to preserve that behavior so lots of commitlog
670        scripts won't die when they get this new code.  At the same
671        time, we'd like to pass other information about the files (like
672        version numbers, statuses, or checkin times).
673 
674        The solution is to allow a format string that allows us to
675        specify those other pieces of information.  The format string
676        will be composed of `%' followed by a single format character,
677        or followed by a set of format characters surrounded by `{' and
678        `}' as separators.  The format characters are:
679 
680          s = file name
681          t = tag name
682 	 V = old version number (pre-checkin)
683 	 v = new version number (post-checkin)
684 
685        For example, valid format strings are:
686 
687          %{}
688 	 %s
689 	 %{s}
690 	 %{sVv}
691 	 %{Vvts}
692 
693        There's no reason that more items couldn't be added (like
694        modification date or file status [added, modified, updated,
695        etc.]) -- the code modifications would be minimal (logmsg.c
696        (title_proc) and commit.c (check_fileproc)).
697 
698        The output will be a string of tokens separated by spaces.  For
699        backwards compatibility, the the first token will be the
700        repository name.  The rest of the tokens will be
701        comma-delimited lists of the information requested in the
702        format string.  For example, if `/u/src/master' is the
703        repository, `%{sVv}' is the format string, and three files
704        (ChangeLog, Makefile, foo.c) were modified, the output might
705        be:
706 
707          /u/src/master ChangeLog,1.1,1.2 Makefile,1.3,1.4 foo.c,1.12,1.13
708 
709        Why this duplicates the old behavior when the format string is
710        `%s' is left as an exercise for the reader. */
711 
712     fmt_percent = strchr (filter, '%');
713     if (fmt_percent)
714     {
715 	int len;
716 	char *srepos;
717 	char *fmt_begin, *fmt_end;	/* beginning and end of the
718 					   format string specified in
719 					   filter. */
720 	char *fmt_continue;		/* where the string continues
721 					   after the format string (we
722 					   might skip a '}') somewhere
723 					   in there... */
724 
725 	/* Grab the format string. */
726 
727 	if ((*(fmt_percent + 1) == ' ') || (*(fmt_percent + 1) == '\0'))
728 	{
729 	    /* The percent stands alone.  This is an error.  We could
730 	       be treating ' ' like any other formatting character, but
731 	       using it as a formatting character seems like it would be
732 	       a mistake.  */
733 
734 	    /* Would be nice to also be giving the line number.  */
735 	    error (0, 0, "loginfo: '%%' not followed by formatting character");
736 	    fmt_begin = fmt_percent + 1;
737 	    fmt_end = fmt_begin;
738 	    fmt_continue = fmt_begin;
739 	}
740 	else if (*(fmt_percent + 1) == '{')
741 	{
742 	    /* The percent has a set of characters following it. */
743 
744 	    fmt_begin = fmt_percent + 2;
745 	    fmt_end = strchr (fmt_begin, '}');
746 	    if (fmt_end)
747 	    {
748 		/* Skip over the '}' character. */
749 
750 		fmt_continue = fmt_end + 1;
751 	    }
752 	    else
753 	    {
754 		/* There was no close brace -- assume that format
755                    string continues to the end of the line. */
756 
757 		/* Would be nice to also be giving the line number.  */
758 		error (0, 0, "loginfo: '}' missing");
759 		fmt_end = fmt_begin + strlen (fmt_begin);
760 		fmt_continue = fmt_end;
761 	    }
762 	}
763 	else
764 	{
765 	    /* The percent has a single character following it.  FIXME:
766 	       %% should expand to a regular percent sign.  */
767 
768 	    fmt_begin = fmt_percent + 1;
769 	    fmt_end = fmt_begin + 1;
770 	    fmt_continue = fmt_end;
771 	}
772 
773 	len = fmt_end - fmt_begin;
774 	str_list_format = xmalloc (len + 1);
775 	strncpy (str_list_format, fmt_begin, len);
776 	str_list_format[len] = '\0';
777 
778 	/* Allocate an initial chunk of memory.  As we build up the string
779 	   we will realloc it.  */
780 	if (!str_list)
781 	    str_list = xmalloc (1);
782 	str_list[0] = '\0';
783 
784 	/* Add entries to the string.  Don't bother looking for
785            entries if the format string is empty. */
786 
787 	if (str_list_format[0] != '\0')
788 	{
789 	    type = T_TITLE;
790 	    (void) walklist (changes, title_proc, NULL);
791 	    type = T_ADDED;
792 	    (void) walklist (changes, title_proc, NULL);
793 	    type = T_MODIFIED;
794 	    (void) walklist (changes, title_proc, NULL);
795 	    type = T_REMOVED;
796 	    (void) walklist (changes, title_proc, NULL);
797 	}
798 
799 	free (str_list_format);
800 
801 	/* Construct the final string. */
802 
803 	srepos = Short_Repository (repository);
804 
805 	prog = cp = xmalloc ((fmt_percent - filter) + 2 * strlen (srepos)
806 			+ 2 * strlen (str_list) + strlen (fmt_continue)
807 			+ 10);
808 	(void) memcpy (cp, filter, fmt_percent - filter);
809 	cp += fmt_percent - filter;
810 	*cp++ = '"';
811 	cp = shell_escape (cp, srepos);
812 	cp = shell_escape (cp, str_list);
813 	*cp++ = '"';
814 	(void) strcpy (cp, fmt_continue);
815 
816 	/* To be nice, free up some memory. */
817 
818 	free (str_list);
819 	str_list = (char *) NULL;
820     }
821     else
822     {
823 	/* There's no format string. */
824 	prog = xstrdup (filter);
825     }
826 
827     if ((pipefp = run_popen (prog, "w")) == NULL)
828     {
829 	if (!noexec)
830 	    error (0, 0, "cannot write entry to log filter: %s", prog);
831 	free (prog);
832 	return (1);
833     }
834     (void) fprintf (pipefp, "Update of %s\n", repository);
835     (void) fprintf (pipefp, "In directory %s:", hostname);
836     cp = xgetwd ();
837     if (cp == NULL)
838 	fprintf (pipefp, "<cannot get working directory: %s>\n\n",
839 		 strerror (errno));
840     else
841     {
842 	fprintf (pipefp, "%s\n\n", cp);
843 	free (cp);
844     }
845 
846     setup_tmpfile (pipefp, "", changes);
847     (void) fprintf (pipefp, "Log Message:\n%s\n", message);
848     if (logfp != (FILE *) 0)
849     {
850 	(void) fprintf (pipefp, "Status:\n");
851 	rewind (logfp);
852 	while ((c = getc (logfp)) != EOF)
853 	    (void) putc ((char) c, pipefp);
854     }
855     free (prog);
856     pipestatus = pclose (pipefp);
857     return ((pipestatus == -1) || (pipestatus == 127)) ? 1 : 0;
858 }
859 
860 /*
861  * We choose to use the *last* match within the editinfo file for this
862  * repository.  This allows us to have a global editinfo program for the
863  * root of some hierarchy, for example, and different ones within different
864  * sub-directories of the root (like a special checker for changes made to
865  * the "src" directory versus changes made to the "doc" or "test"
866  * directories.
867  */
868 /* ARGSUSED */
869 static int
editinfo_proc(repository,editor)870 editinfo_proc(repository, editor)
871     char *repository;
872     char *editor;
873 {
874     /* nothing to do if the last match is the same as this one */
875     if (editinfo_editor && strcmp (editinfo_editor, editor) == 0)
876 	return (0);
877     if (editinfo_editor)
878 	free (editinfo_editor);
879 
880     editinfo_editor = xstrdup (editor);
881     return (0);
882 }
883 
884 /*  This routine is calld by Parse_Info.  it asigns the name of the
885  *  message verification script to the global variable verify_script
886  */
887 static int
verifymsg_proc(repository,script)888 verifymsg_proc (repository, script)
889     char *repository;
890     char *script;
891 {
892     if (verifymsg_script && strcmp (verifymsg_script, script) == 0)
893 	return (0);
894     if (verifymsg_script)
895 	free (verifymsg_script);
896     verifymsg_script = xstrdup (script);
897     return (0);
898 }
899