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