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