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