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