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