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