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