update from MirBSD; for us relevant:
[alioth/cvs.git] / 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
14
15 #include "cvs.h"
16 #include "getline.h"
17
18 __RCSID("$MirOS: src/gnu/usr.bin/cvs/src/logmsg.c,v 1.12 2010/09/19 19:43:05 tg Exp $");
19
20 static int find_type (Node * p, void *closure);
21 static int fmt_proc (Node * p, void *closure);
22 static int logfile_write (const char *repository, const char *filter,
23                           const char *message, FILE * logfp, List * changes);
24 static int logmsg_list_to_args_proc (Node *p, void *closure);
25 static int rcsinfo_proc (const char *repository, const char *template,
26                          void *closure );
27 static int update_logfile_proc (const char *repository, const char *filter,
28                                 void *closure);
29 static void setup_tmpfile (FILE * xfp, char *xprefix, List * changes);
30 static int verifymsg_proc (const char *repository, const char *script,
31                            void *closure );
32
33 static FILE *fp;
34 static Ctype type;
35
36 char *LogMsgFile = NULL;
37
38 struct verifymsg_proc_data
39 {
40     /* The name of the temp file storing the log message to be verified.  This
41      * is initially NULL and verifymsg_proc() writes message into it so that it
42      * can be shared when multiple verifymsg scripts exist.  do_verify() is
43      * responsible for rereading the message from the file when
44      * RereadLogAfterVerify is in effect and the file has changed.
45      */
46     char *fname;
47     /* The initial message text to be verified.
48      */
49     char *message;
50     /* The initial stats of the temp file so we can tell that the temp file has
51      * been changed when RereadLogAfterVerify is STAT.
52      */
53     struct stat pre_stbuf;
54    /* The list of files being changed, with new and old version numbers.
55     */
56    List *changes;
57 };
58
59 /*
60  * Puts a standard header on the output which is either being prepared for an
61  * editor session, or being sent to a logfile program.  The modified, added,
62  * and removed files are included (if any) and formatted to look pretty. */
63 static char *prefix;
64 static int col;
65 static char *tag;
66 static void
67 setup_tmpfile (FILE *xfp, char *xprefix, List *changes)
68 {
69     /* set up statics */
70     fp = xfp;
71     prefix = xprefix;
72
73     type = T_MODIFIED;
74     if (walklist (changes, find_type, NULL) != 0)
75     {
76         (void) fprintf (fp, "%sModified 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     type = T_ADDED;
87     if (walklist (changes, find_type, NULL) != 0)
88     {
89         (void) fprintf (fp, "%sAdded Files:\n", prefix);
90         col = 0;
91         (void) walklist (changes, fmt_proc, NULL);
92         (void) fprintf (fp, "\n");
93         if (tag != NULL)
94         {
95             free (tag);
96             tag = NULL;
97         }
98     }
99     type = T_REMOVED;
100     if (walklist (changes, find_type, NULL) != 0)
101     {
102         (void) fprintf (fp, "%sRemoved Files:\n", prefix);
103         col = 0;
104         (void) walklist (changes, fmt_proc, NULL);
105         (void) fprintf (fp, "\n");
106         if (tag != NULL)
107         {
108             free (tag);
109             tag = NULL;
110         }
111     }
112 }
113
114 /*
115  * Looks for nodes of a specified type and returns 1 if found
116  */
117 static int
118 find_type (Node *p, void *closure)
119 {
120     struct logfile_info *li = p->data;
121
122     if (li->type == type)
123         return (1);
124     else
125         return (0);
126 }
127
128 /*
129  * Breaks the files list into reasonable sized lines to avoid line wrap...
130  * all in the name of pretty output.  It only works on nodes whose types
131  * match the one we're looking for
132  */
133 static int
134 fmt_proc (Node *p, void *closure)
135 {
136     struct logfile_info *li;
137
138     li = p->data;
139     if (li->type == type)
140     {
141         if (li->tag == NULL
142             ? tag != NULL
143             : tag == NULL || strcmp (tag, li->tag) != 0)
144         {
145             if (col > 0)
146                 (void) fprintf (fp, "\n");
147             (void) fputs (prefix, fp);
148             col = strlen (prefix);
149             while (col < 6)
150             {
151                 (void) fprintf (fp, " ");
152                 ++col;
153             }
154
155             if (li->tag == NULL)
156                 (void) fprintf (fp, "No tag");
157             else
158                 (void) fprintf (fp, "Tag: %s", li->tag);
159
160             if (tag != NULL)
161                 free (tag);
162             tag = xstrdup (li->tag);
163
164             /* Force a new line.  */
165             col = 70;
166         }
167
168         if (col == 0)
169         {
170             (void) fprintf (fp, "%s\t", prefix);
171             col = 8;
172         }
173         else if (col > 8 && (col + (int) strlen (p->key)) > 70)
174         {
175             (void) fprintf (fp, "\n%s\t", prefix);
176             col = 8;
177         }
178         (void) fprintf (fp, "%s ", p->key);
179         col += strlen (p->key) + 1;
180     }
181     return (0);
182 }
183
184 /*
185  * Builds a temporary file using setup_tmpfile() and invokes the user's
186  * editor on the file.  The header garbage in the resultant file is then
187  * stripped and the log message is stored in the "message" argument.
188  * 
189  * If REPOSITORY is non-NULL, process rcsinfo for that repository; if it
190  * is NULL, use the CVSADM_TEMPLATE file instead.  REPOSITORY should be
191  * NULL when running in client mode.
192  *
193  * GLOBALS
194  *   Editor     Set to a default value by configure and overridable using the
195  *              -e option to the CVS executable.
196  */
197 void
198 do_editor (const char *dir, char **messagep, const char *repository,
199            List *changes)
200 {
201     static int reuse_log_message = 0;
202     char *line;
203     int line_length;
204     size_t line_chars_allocated;
205     char *fname;
206     struct stat pre_stbuf, post_stbuf;
207     int retcode = 0;
208     int finish = 0;
209
210     assert (!current_parsed_root->isremote != !repository);
211
212     if (noexec || reuse_log_message)
213         return;
214
215     /* Abort before creation of the temp file if no editor is defined. */
216     if (strcmp (Editor, "") == 0)
217         error(1, 0, "no editor defined, must use -e or -m");
218
219   again:
220     /* Create a temporary file.  */
221     if( ( fp = cvs_temp_file( &fname ) ) == NULL )
222         error( 1, errno, "cannot create temporary file" );
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) fputc ('\n', fp);
234     if (finish)
235         goto finish_off;
236
237     if (repository != NULL)
238         /* tack templates on if necessary */
239         (void) Parse_Info (CVSROOTADM_RCSINFO, repository, rcsinfo_proc,
240                 PIOPT_ALL, NULL);
241     else
242     {
243         FILE *tfp;
244         char buf[1024];
245         size_t n;
246         size_t nwrite;
247
248         /* Why "b"?  */
249         tfp = CVS_FOPEN (CVSADM_TEMPLATE, "rb");
250         if (tfp == NULL)
251         {
252             if (!existence_error (errno))
253                 error (1, errno, "cannot read %s", CVSADM_TEMPLATE);
254         }
255         else
256         {
257             while (!feof (tfp))
258             {
259                 char *p = buf;
260                 n = fread (buf, 1, sizeof buf, tfp);
261                 nwrite = n;
262                 while (nwrite > 0)
263                 {
264                     n = fwrite (p, 1, nwrite, fp);
265                     nwrite -= n;
266                     p += n;
267                 }
268                 if (ferror (tfp))
269                     error (1, errno, "cannot read %s", CVSADM_TEMPLATE);
270             }
271             if (fclose (tfp) < 0)
272                 error (0, errno, "cannot close %s", CVSADM_TEMPLATE);
273         }
274     }
275
276     (void) fprintf (fp,
277   "%s----------------------------------------------------------------------\n",
278                     CVSEDITPREFIX);
279     if (readonlyfs)
280       (void) fprintf (fp, "%sATTENTION: read-only mode selected!\n",
281                       CVSEDITPREFIX);
282     (void) fprintf (fp,
283   "%sEnter Log.  Lines beginning with `%.*s' are removed automatically\n%s\n",
284                     CVSEDITPREFIX, CVSEDITPREFIXLEN, CVSEDITPREFIX,
285                     CVSEDITPREFIX);
286     if (dir != NULL && *dir)
287         (void) fprintf (fp, "%sCommitting in %s\n%s\n", CVSEDITPREFIX,
288                         dir, CVSEDITPREFIX);
289     if (changes != NULL)
290         setup_tmpfile (fp, CVSEDITPREFIX, changes);
291     (void) fprintf (fp,
292   "%s----------------------------------------------------------------------\n",
293                     CVSEDITPREFIX);
294
295  finish_off:
296     /* finish off the temp file */
297     if (fclose (fp) == EOF)
298         error (1, errno, "%s", fname);
299     if (LogMsgFile)
300     {
301         if (unlink_file (LogMsgFile) < 0)
302             error (0, errno, "warning: cannot remove temp file %s", LogMsgFile);
303         free (LogMsgFile);
304     }
305     LogMsgFile = fname;
306     if (finish)
307         return;
308     if (stat (LogMsgFile, &pre_stbuf) == -1)
309         pre_stbuf.st_mtime = 0;
310
311     /* run the editor */
312     run_setup (Editor);
313     run_add_arg (LogMsgFile);
314     if ((retcode = run_exec (RUN_TTY, RUN_TTY, RUN_TTY,
315                              RUN_NORMAL | RUN_SIGIGNORE)) != 0)
316         error (0, retcode == -1 ? errno : 0, "warning: editor session failed");
317
318     /* put the entire message back into the *messagep variable */
319
320     fp = xfopen (LogMsgFile, "r");
321
322     if (*messagep)
323         free (*messagep);
324
325     if (stat (LogMsgFile, &post_stbuf) != 0)
326             error (1, errno, "cannot find size of temp file %s", LogMsgFile);
327
328     if (post_stbuf.st_size == 0)
329         *messagep = NULL;
330     else
331     {
332         /* On NT, we might read less than st_size bytes, but we won't
333            read more.  So this works.  */
334         *messagep = (char *) xmalloc (post_stbuf.st_size + 1);
335         (*messagep)[0] = '\0';
336     }
337
338     line = NULL;
339     line_chars_allocated = 0;
340
341     if (*messagep)
342     {
343         size_t message_len = post_stbuf.st_size + 1;
344         size_t offset = 0;
345         while (1)
346         {
347             line_length = getline (&line, &line_chars_allocated, fp);
348             if (line_length == -1)
349             {
350                 if (ferror (fp))
351                     error (0, errno, "warning: cannot read %s", LogMsgFile);
352                 break;
353             }
354             if (strncmp (line, CVSEDITPREFIX, CVSEDITPREFIXLEN) == 0)
355                 continue;
356             if (offset + line_length >= message_len)
357                 expand_string (messagep, &message_len,
358                                 offset + line_length + 1);
359             (void) strcpy (*messagep + offset, line);
360             offset += line_length;
361         }
362     }
363     if (fclose (fp) < 0)
364         error (0, errno, "warning: cannot close %s", LogMsgFile);
365
366     /* canonicalize emply messages */
367     if (*messagep != NULL &&
368         (**messagep == '\0' || strcmp (*messagep, "\n") == 0))
369     {
370         free (*messagep);
371         *messagep = NULL;
372     }
373
374     if (pre_stbuf.st_mtime == post_stbuf.st_mtime || 
375         *messagep == NULL ||
376         (*messagep)[0] == '\0' ||
377         strcmp (*messagep, "\n") == 0 ||
378         strcmp (*messagep, "\n\n") == 0)
379     {
380         for (;;)
381         {
382             (void) printf ("\nLog message unchanged or not specified\n");
383             (void) printf ("a)bort, c)ontinue, e)dit, !)reuse this message unchanged for remaining dirs\n");
384             (void) printf ("Action: (continue) ");
385             (void) fflush (stdout);
386             line_length = getline (&line, &line_chars_allocated, stdin);
387             if (line_length < 0)
388             {
389                 error (0, errno, "cannot read from stdin");
390                 if (unlink_file (LogMsgFile) < 0)
391                     error (0, errno,
392                            "warning: cannot remove temp file %s", LogMsgFile);
393                 free (LogMsgFile);
394                 LogMsgFile = NULL;
395                 error (1, 0, "aborting");
396             }
397             else if (line_length == 0
398                      || *line == '\n' || *line == 'c' || *line == 'C')
399                 break;
400             if (*line == 'a' || *line == 'A')
401                 {
402                     if (unlink_file (LogMsgFile) < 0)
403                         error (0, errno, "warning: cannot remove temp file %s", LogMsgFile);
404                     free (LogMsgFile);
405                     LogMsgFile = NULL;
406                     error (1, 0, "aborted by user");
407                 }
408             if (*line == 'e' || *line == 'E')
409                 goto again;
410             if (*line == '!')
411             {
412                 reuse_log_message = 1;
413                 break;
414             }
415             (void) printf ("Unknown input\n");
416         }
417     }
418     if (line)
419         free (line);
420     finish = 1;
421     goto again;
422 }
423
424 /* Runs the user-defined verification script as part of the commit or import 
425    process.  This verification is meant to be run whether or not the user 
426    included the -m attribute.  unlike the do_editor function, this is 
427    independent of the running of an editor for getting a message.
428  */
429 void
430 do_verify (char **messagep, const char *repository, List *changes)
431 {
432     int err;
433     struct verifymsg_proc_data data;
434     struct stat post_stbuf;
435
436     if (current_parsed_root->isremote)
437         /* The verification will happen on the server.  */
438         return;
439
440     /* FIXME? Do we really want to skip this on noexec?  What do we do
441        for the other administrative files?  */
442     /* EXPLAIN: Why do we check for repository == NULL here? */
443     if (noexec || repository == NULL)
444         return;
445
446     /* Get the name of the verification script to run  */
447
448     data.message = *messagep;
449     data.fname = NULL;
450     data.changes = changes;
451     if ((err = Parse_Info (CVSROOTADM_VERIFYMSG, repository,
452                           verifymsg_proc, 0, &data)) != 0)
453     {
454         int saved_errno = errno;
455         /* Since following error() exits, delete the temp file now.  */
456         if (data.fname != NULL && unlink_file( data.fname ) < 0)
457             error (0, errno, "cannot remove %s", data.fname);
458         free (data.fname);
459
460         errno = saved_errno;
461         error (1, err == -1 ? errno : 0, "Message verification failed");
462     }
463
464     /* Return if no temp file was created.  That means that we didn't call any
465      * verifymsg scripts.
466      */
467     if (data.fname == NULL)
468         return;
469
470     /* Get the mod time and size of the possibly new log message
471      * in always and stat modes.
472      */
473     if (config->RereadLogAfterVerify == LOGMSG_REREAD_ALWAYS ||
474         config->RereadLogAfterVerify == LOGMSG_REREAD_STAT)
475     {
476         if(stat (data.fname, &post_stbuf) != 0)
477             error (1, errno, "cannot find size of temp file %s", data.fname);
478     }
479
480     /* And reread the log message in `always' mode or in `stat' mode when it's
481      * changed.
482      */
483     if (config->RereadLogAfterVerify == LOGMSG_REREAD_ALWAYS ||
484         (config->RereadLogAfterVerify == LOGMSG_REREAD_STAT &&
485           (data.pre_stbuf.st_mtime != post_stbuf.st_mtime ||
486             data.pre_stbuf.st_size != post_stbuf.st_size)))
487     {
488         /* put the entire message back into the *messagep variable */
489
490         if (*messagep) free (*messagep);
491
492         if (post_stbuf.st_size == 0)
493             *messagep = NULL;
494         else
495         {
496             char *line = NULL;
497             int line_length;
498             size_t line_chars_allocated = 0;
499             char *p;
500             FILE *fp;
501
502             fp = xfopen (data.fname, "r");
503
504             /* On NT, we might read less than st_size bytes,
505                but we won't read more.  So this works.  */
506             p = *messagep = (char *) xmalloc (post_stbuf.st_size + 1);
507             *messagep[0] = '\0';
508
509             for (;;)
510             {
511                 line_length = getline( &line,
512                                        &line_chars_allocated,
513                                        fp);
514                 if (line_length == -1)
515                 {
516                     if (ferror (fp))
517                         /* Fail in this case because otherwise we will have no
518                          * log message
519                          */
520                         error (1, errno, "cannot read %s", data.fname);
521                     break;
522                 }
523                 if (strncmp (line, CVSEDITPREFIX, CVSEDITPREFIXLEN) == 0)
524                     continue;
525                 (void) strcpy (p, line);
526                 p += line_length;
527             }
528             if (line) free (line);
529             if (fclose (fp) < 0)
530                 error (0, errno, "warning: cannot close %s", data.fname);
531         }
532     }
533     /* Delete the temp file  */
534     if (unlink_file (data.fname) < 0)
535         error (0, errno, "cannot remove `%s'", data.fname);
536     free (data.fname);
537 }
538
539
540
541 /*
542  * callback proc for Parse_Info for rcsinfo templates this routine basically
543  * copies the matching template onto the end of the tempfile we are setting
544  * up
545  */
546 /* ARGSUSED */
547 static int
548 rcsinfo_proc (const char *repository, const char *template, void *closure)
549 {
550     static char *last_template;
551     FILE *tfp;
552
553     /* nothing to do if the last one included is the same as this one */
554     if (last_template && strcmp (last_template, template) == 0)
555         return (0);
556     if (last_template)
557         free (last_template);
558     last_template = xstrdup (template);
559
560     if ((tfp = CVS_FOPEN (template, "r")) != NULL)
561     {
562         char *line = NULL;
563         size_t line_chars_allocated = 0;
564
565         while (getline (&line, &line_chars_allocated, tfp) >= 0)
566             (void) fputs (line, fp);
567         if (ferror (tfp))
568             error (0, errno, "warning: cannot read %s", template);
569         if (fclose (tfp) < 0)
570             error (0, errno, "warning: cannot close %s", template);
571         if (line)
572             free (line);
573         return (0);
574     }
575     else
576     {
577         error (0, errno, "Couldn't open rcsinfo template file %s", template);
578         return (1);
579     }
580 }
581
582 /*
583  * Uses setup_tmpfile() to pass the updated message on directly to any
584  * logfile programs that have a regular expression match for the checked in
585  * directory in the source repository.  The log information is fed into the
586  * specified program as standard input.
587  */
588 struct ulp_data {
589     FILE *logfp;
590     const char *message;
591     List *changes;
592 };
593
594
595
596 void
597 Update_Logfile (const char *repository, const char *xmessage, FILE *xlogfp,
598                 List *xchanges)
599 {
600     struct ulp_data ud;
601
602     /* nothing to do if the list is empty */
603     if (xchanges == NULL || xchanges->list->next == xchanges->list)
604         return;
605
606     /* set up vars for update_logfile_proc */
607     ud.message = xmessage;
608     ud.logfp = xlogfp;
609     ud.changes = xchanges;
610
611     /* call Parse_Info to do the actual logfile updates */
612     (void) Parse_Info (CVSROOTADM_LOGINFO, repository, update_logfile_proc,
613                        PIOPT_ALL, &ud);
614 }
615
616
617
618 /*
619  * callback proc to actually do the logfile write from Update_Logfile
620  */
621 static int
622 update_logfile_proc (const char *repository, const char *filter, void *closure)
623 {
624     struct ulp_data *udp = closure;
625     TRACE (TRACE_FUNCTION, "update_logfile_proc(%s,%s)", repository, filter);
626     return logfile_write (repository, filter, udp->message, udp->logfp,
627                           udp->changes);
628 }
629
630
631
632 /* static int
633  * logmsg_list_to_args_proc( Node *p, void *closure )
634  * This function is intended to be passed into walklist() with a list of tags
635  * (nodes in the same format as pretag_list_proc() accepts - p->key = tagname
636  * and p->data = a revision.
637  *
638  * closure will be a struct format_cmdline_walklist_closure
639  * where closure is undefined.
640  */
641 static int
642 logmsg_list_to_args_proc (Node *p, void *closure)
643 {
644     struct format_cmdline_walklist_closure *c = closure;
645     struct logfile_info *li;
646     char *arg = NULL;
647     const char *f;
648     char *d;
649     size_t doff;
650
651     if (p->data == NULL) return 1;
652
653     f = c->format;
654     d = *c->d;
655     /* foreach requested attribute */
656     while (*f)
657     {
658         switch (*f++)
659         {
660             case 's':
661                 arg = p->key;
662                 break;
663             case 'T':
664                 li = p->data;
665                 arg = li->tag ? li->tag : "";
666                 break;
667             case 'V':
668                 li = p->data;
669                 arg = li->rev_old ? li->rev_old : "NONE";
670                 break;
671             case 'v':
672                 li = p->data;
673                 arg = li->rev_new ? li->rev_new : "NONE";
674                 break;
675             default:
676 #ifdef SUPPORT_OLD_INFO_FMT_STRINGS
677                 if (c->onearg)
678                 {
679                     /* The old deafult was to print the empty string for
680                      * unknown args.
681                      */
682                     arg = "\0";
683                 }
684                 else
685 #endif /* SUPPORT_OLD_INFO_FMT_STRINGS */
686                     error (1, 0,
687                            "Unknown format character or not a list attribute: %c", f[-1]);
688                 /* NOTREACHED */
689                 break;
690         }
691         /* copy the attribute into an argument */
692 #ifdef SUPPORT_OLD_INFO_FMT_STRINGS
693         if (c->onearg)
694         {
695             if (c->firstpass)
696             {
697                 c->firstpass = 0;
698                 doff = d - *c->buf;
699                 expand_string (c->buf, c->length,
700                                doff + strlen (c->srepos) + 1);
701                 d = *c->buf + doff;
702                 strncpy (d, c->srepos, strlen (c->srepos));
703                 d += strlen (c->srepos);
704                 *d++ = ' ';
705             }
706         }
707         else /* c->onearg */
708 #endif /* SUPPORT_OLD_INFO_FMT_STRINGS */
709         {
710             if (c->quotes)
711             {
712                 arg = cmdlineescape (c->quotes, arg);
713             }
714             else
715             {
716                 arg = cmdlinequote ('"', arg);
717             }
718         } /* !c->onearg */
719         doff = d - *c->buf;
720         expand_string (c->buf, c->length, doff + strlen (arg));
721         d = *c->buf + doff;
722         strncpy (d, arg, strlen (arg));
723         d += strlen (arg);
724 #ifdef SUPPORT_OLD_INFO_FMT_STRINGS
725         if (!c->onearg)
726 #endif /* SUPPORT_OLD_INFO_FMT_STRINGS */
727             free (arg);
728
729         /* Always put the extra space on.  we'll have to back up a char
730          * when we're done, but that seems most efficient.
731          */
732         doff = d - *c->buf;
733         expand_string (c->buf, c->length, doff + 1);
734         d = *c->buf + doff;
735 #ifdef SUPPORT_OLD_INFO_FMT_STRINGS
736         if (c->onearg && *f) *d++ = ',';
737         else
738 #endif /* SUPPORT_OLD_INFO_FMT_STRINGS */
739             *d++ = ' ';
740     }
741     /* correct our original pointer into the buff */
742     *c->d = d;
743     return 0;
744 }
745
746
747
748 /*
749  * Writes some stuff to the logfile "filter" and returns the status of the
750  * filter program.
751  */
752 static int
753 logfile_write (const char *repository, const char *filter, const char *message,
754                FILE *logfp, List *changes)
755 {
756     char *cmdline;
757     FILE *pipefp;
758     char *cp;
759     int c;
760     int pipestatus;
761     const char *srepos = Short_Repository (repository);
762
763     assert (repository);
764
765     /* The user may specify a format string as part of the filter.
766        Originally, `%s' was the only valid string.  The string that
767        was substituted for it was:
768
769          <repository-name> <file1> <file2> <file3> ...
770
771        Each file was either a new directory/import (T_TITLE), or a
772        added (T_ADDED), modified (T_MODIFIED), or removed (T_REMOVED)
773        file.
774
775        It is desirable to preserve that behavior so lots of commitlog
776        scripts won't die when they get this new code.  At the same
777        time, we'd like to pass other information about the files (like
778        version numbers, statuses, or checkin times).
779
780        The solution is to allow a format string that allows us to
781        specify those other pieces of information.  The format string
782        will be composed of `%' followed by a single format character,
783        or followed by a set of format characters surrounded by `{' and
784        `}' as separators.  The format characters are:
785
786          s = file name
787          V = old version number (pre-checkin)
788          v = new version number (post-checkin)
789
790        For example, valid format strings are:
791
792          %{}
793          %s
794          %{s}
795          %{sVv}
796
797        There's no reason that more items couldn't be added (like
798        modification date or file status [added, modified, updated,
799        etc.]) -- the code modifications would be minimal (logmsg.c
800        (title_proc) and commit.c (check_fileproc)).
801
802        The output will be a string of tokens separated by spaces.  For
803        backwards compatibility, the the first token will be the
804        repository name.  The rest of the tokens will be
805        comma-delimited lists of the information requested in the
806        format string.  For example, if `/u/src/master' is the
807        repository, `%{sVv}' is the format string, and three files
808        (ChangeLog, Makefile, foo.c) were modified, the output might
809        be:
810
811          /u/src/master ChangeLog,1.1,1.2 Makefile,1.3,1.4 foo.c,1.12,1.13
812
813        Why this duplicates the old behavior when the format string is
814        `%s' is left as an exercise for the reader. */
815
816     /* %c = cvs_cmd_name
817      * %I = commit ID
818      * %p = shortrepos
819      * %r = repository
820      * %{sVv} = file name, old revision (precommit), new revision (postcommit)
821      */
822     /*
823      * Cast any NULL arguments as appropriate pointers as this is an
824      * stdarg function and we need to be certain the caller gets what
825      * is expected.
826      */
827     cmdline = format_cmdline (
828 #ifdef SUPPORT_OLD_INFO_FMT_STRINGS
829                               !config->UseNewInfoFmtStrings, srepos,
830 #endif /* SUPPORT_OLD_INFO_FMT_STRINGS */
831                               filter,
832                               "c", "s", cvs_cmd_name,
833                               "I", "s", global_session_id,
834 #ifdef SERVER_SUPPORT
835                               "R", "s", referrer ? referrer->original : "NONE",
836 #endif /* SERVER_SUPPORT */
837                               "p", "s", srepos,
838                               "r", "s", current_parsed_root->directory,
839                               "sVv", ",", changes,
840                               logmsg_list_to_args_proc, (void *) NULL,
841                               (char *) NULL);
842     if (!cmdline || !strlen (cmdline))
843     {
844         if (cmdline) free (cmdline);
845         error (0, 0, "logmsg proc resolved to the empty string!");
846         return 1;
847     }
848
849     if ((pipefp = run_popen (cmdline, "w")) == NULL)
850     {
851         if (!noexec)
852             error (0, 0, "cannot write entry to log filter: %s", cmdline);
853         free (cmdline);
854         return 1;
855     }
856     (void) fprintf (pipefp, "Update of %s\n", repository);
857     (void) fprintf (pipefp, "In directory %s:", hostname);
858     cp = xgetcwd ();
859     if (cp == NULL)
860         fprintf (pipefp, "<cannot get working directory: %s>\n\n",
861                  strerror (errno));
862     else
863     {
864         fprintf (pipefp, "%s\n\n", cp);
865         free (cp);
866     }
867
868     setup_tmpfile (pipefp, "", changes);
869     (void) fprintf (pipefp, "Log Message:\n%s\n", (message) ? message : "");
870     if (logfp)
871     {
872         (void) fprintf (pipefp, "Status:\n");
873         rewind (logfp);
874         while ((c = getc (logfp)) != EOF)
875             (void) putc (c, pipefp);
876     }
877     free (cmdline);
878     pipestatus = pclose (pipefp);
879     return ((pipestatus == -1) || (pipestatus == 127)) ? 1 : 0;
880 }
881
882
883
884 /*  This routine is called by Parse_Info.  It runs the
885  *  message verification script.
886  */
887 static int
888 verifymsg_proc (const char *repository, const char *script, void *closure)
889 {
890     char *verifymsg_script;
891 #ifdef SUPPORT_OLD_INFO_FMT_STRINGS
892     char *newscript = NULL;
893 #endif /* SUPPORT_OLD_INFO_FMT_STRINGS */
894     struct verifymsg_proc_data *vpd = closure;
895     const char *srepos = Short_Repository (repository);
896
897 #ifdef SUPPORT_OLD_INFO_FMT_STRINGS
898     if (!strchr (script, '%'))
899     {
900         error (0, 0,
901                "warning: verifymsg line doesn't contain any format strings:\n"
902                "    \"%s\"\n"
903                "Appending default format string (\" %%l\"), but be aware that this usage is\n"
904                "deprecated.", script);
905         script = newscript = Xasprintf ("%s %%l", script);
906     }
907 #endif /* SUPPORT_OLD_INFO_FMT_STRINGS */
908
909     /* If we don't already have one, open a temporary file, write the message
910      * to the temp file, and close the file.
911      *
912      * We do this here so that we only create the file when there is a
913      * verifymsg script specified and we only create it once when there is
914      * more than one verifymsg script specified.
915      */
916     if (vpd->fname == NULL)
917     {
918         FILE *fp;
919         if ((fp = cvs_temp_file (&(vpd->fname))) == NULL)
920             error (1, errno, "cannot create temporary file %s", vpd->fname);
921
922         if (vpd->message != NULL)
923             fputs (vpd->message, fp);
924         if (vpd->message == NULL ||
925             (vpd->message)[0] == '\0' ||
926             (vpd->message)[strlen (vpd->message) - 1] != '\n')
927             putc ('\n', fp);
928         if (fclose (fp) == EOF)
929             error (1, errno, "%s", vpd->fname);
930
931         if (config->RereadLogAfterVerify == LOGMSG_REREAD_STAT)
932         {
933             /* Remember the status of the temp file for later */
934             if (stat (vpd->fname, &(vpd->pre_stbuf)) != 0)
935                 error (1, errno, "cannot stat temp file %s", vpd->fname);
936
937             /*
938              * See if we need to sleep before running the verification
939              * script to avoid time-stamp races.
940              */
941             sleep_past (vpd->pre_stbuf.st_mtime);
942         }
943     } /* if (vpd->fname == NULL) */
944
945     /*
946      * Cast any NULL arguments as appropriate pointers as this is an
947      * stdarg function and we need to be certain the caller gets what
948      * is expected.
949      */
950     verifymsg_script = format_cmdline (
951 #ifdef SUPPORT_OLD_INFO_FMT_STRINGS
952                                        false, srepos,
953 #endif /* SUPPORT_OLD_INFO_FMT_STRINGS */
954                                        script,
955                                        "c", "s", cvs_cmd_name,
956                                        "I", "s", global_session_id,
957 #ifdef SERVER_SUPPORT
958                                        "R", "s", referrer
959                                        ? referrer->original : "NONE",
960 #endif /* SERVER_SUPPORT */
961                                        "p", "s", srepos,
962                                        "r", "s",
963                                        current_parsed_root->directory,
964                                        "l", "s", vpd->fname,
965                                        "sV", ",", vpd->changes,
966                                        logmsg_list_to_args_proc, (void *) NULL,
967                                        (char *) NULL);
968
969 #ifdef SUPPORT_OLD_INFO_FMT_STRINGS
970     if (newscript) free (newscript);
971 #endif /* SUPPORT_OLD_INFO_FMT_STRINGS */
972
973     if (!verifymsg_script || !strlen (verifymsg_script))
974     {
975         if (verifymsg_script) free (verifymsg_script);
976         verifymsg_script = NULL;
977         error (0, 0, "verifymsg proc resolved to the empty string!");
978         return 1;
979     }
980
981     run_setup (verifymsg_script);
982
983     free (verifymsg_script);
984
985     /* FIXME - because run_exec can return negative values and Parse_Info adds
986      * the values of each call to this function to get a total error, we are
987      * calling abs on the value of run_exec to ensure two errors do not sum to
988      * zero.
989      *
990      * The only REALLY obnoxious thing about this, I guess, is that a -1 return
991      * code from run_exec can mean we failed to call the process for some
992      * reason and should care about errno or that the process we called
993      * returned -1 and the value of errno is undefined.  In other words,
994      * run_exec should probably be rewritten to have two return codes.  one
995      * which is its own exit status and one which is the child process's.  So
996      * there.  :P
997      *
998      * Once run_exec is returning two error codes, we should probably be
999      * failing here with an error message including errno when we get the
1000      * return code which means we care about errno, in case you missed that
1001      * little tidbit.
1002      *
1003      * I do happen to know we just fail for a non-zero value anyway and I
1004      * believe the docs actually state that if the verifymsg_proc returns a
1005      * "non-zero" value we will fail.
1006      */
1007     return abs (run_exec (RUN_TTY, RUN_TTY, RUN_TTY,
1008                           RUN_NORMAL | RUN_SIGIGNORE));
1009 }
1010
1011 void
1012 logmsg_cleanup (int err)
1013 {
1014     if (!use_editor || LogMsgFile == NULL)
1015         return;
1016
1017     if (err == 0)
1018     {
1019         if (unlink_file (LogMsgFile) < 0)
1020             error (0, errno, "warning: cannot remove temp file %s", LogMsgFile);
1021     }
1022     else
1023         error (0, 0, "your log message was saved in %s", LogMsgFile);
1024     free (LogMsgFile);
1025     LogMsgFile = NULL;
1026 }