2 * Copyright (c) 1992, Brian Berliner and Jeff Polk
3 * Copyright (c) 1989-1992, Brian Berliner
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.
16 static int find_type PROTO((Node * p, void *closure));
17 static int fmt_proc PROTO((Node * p, void *closure));
18 static int logfile_write PROTO((const char *repository, const char *filter,
19 const char *message, FILE * logfp,
21 static int rcsinfo_proc PROTO((const char *repository, const char *template));
22 static int title_proc PROTO((Node * p, void *closure));
23 static int update_logfile_proc PROTO((const char *repository,
25 static void setup_tmpfile PROTO((FILE * xfp, char *xprefix, List * changes));
26 static int editinfo_proc PROTO((const char *repository, const char *template));
27 static int verifymsg_proc PROTO((const char *repository, const char *script));
30 static char *str_list;
31 static char *str_list_format; /* The format for str_list's contents. */
32 static char *editinfo_editor;
33 static char *verifymsg_script;
37 * Should the logmsg be re-read during the do_verify phase?
38 * RereadLogAfterVerify=no|stat|yes
39 * LOGMSG_REREAD_NEVER - never re-read the logmsg
40 * LOGMSG_REREAD_STAT - re-read the logmsg only if it has changed
41 * LOGMSG_REREAD_ALWAYS - always re-read the logmsg
43 int RereadLogAfterVerify = LOGMSG_REREAD_ALWAYS;
46 * Puts a standard header on the output which is either being prepared for an
47 * editor session, or being sent to a logfile program. The modified, added,
48 * and removed files are included (if any) and formatted to look pretty. */
53 setup_tmpfile (xfp, xprefix, changes)
63 if (walklist (changes, find_type, NULL) != 0)
65 (void) fprintf (fp, "%sModified Files:\n", prefix);
67 (void) walklist (changes, fmt_proc, NULL);
68 (void) fprintf (fp, "\n");
76 if (walklist (changes, find_type, NULL) != 0)
78 (void) fprintf (fp, "%sAdded Files:\n", prefix);
80 (void) walklist (changes, fmt_proc, NULL);
81 (void) fprintf (fp, "\n");
89 if (walklist (changes, find_type, NULL) != 0)
91 (void) fprintf (fp, "%sRemoved Files:\n", prefix);
93 (void) walklist (changes, fmt_proc, NULL);
94 (void) fprintf (fp, "\n");
104 * Looks for nodes of a specified type and returns 1 if found
107 find_type (p, closure)
111 struct logfile_info *li = p->data;
113 if (li->type == type)
120 * Breaks the files list into reasonable sized lines to avoid line wrap...
121 * all in the name of pretty output. It only works on nodes whose types
122 * match the one we're looking for
125 fmt_proc (p, closure)
129 struct logfile_info *li;
132 if (li->type == type)
136 : tag == NULL || strcmp (tag, li->tag) != 0)
139 (void) fprintf (fp, "\n");
140 (void) fputs (prefix, fp);
141 col = strlen (prefix);
144 (void) fprintf (fp, " ");
149 (void) fprintf (fp, "No tag");
151 (void) fprintf (fp, "Tag: %s", li->tag);
155 tag = xstrdup (li->tag);
157 /* Force a new line. */
163 (void) fprintf (fp, "%s\t", prefix);
166 else if (col > 8 && (col + (int) strlen (p->key)) > 70)
168 (void) fprintf (fp, "\n%s\t", prefix);
171 (void) fprintf (fp, "%s ", p->key);
172 col += strlen (p->key) + 1;
178 * Builds a temporary file using setup_tmpfile() and invokes the user's
179 * editor on the file. The header garbage in the resultant file is then
180 * stripped and the log message is stored in the "message" argument.
182 * If REPOSITORY is non-NULL, process rcsinfo for that repository; if it
183 * is NULL, use the CVSADM_TEMPLATE file instead. REPOSITORY should be
184 * NULL when running in client mode.
187 do_editor (dir, messagep, repository, changes)
190 const char *repository;
193 static int reuse_log_message = 0;
196 size_t line_chars_allocated;
198 struct stat pre_stbuf, post_stbuf;
201 #ifdef CLIENT_SUPPORT
202 assert (!current_parsed_root->isremote != !repository);
207 if (noexec || reuse_log_message)
210 /* Abort creation of temp file if no editor is defined */
211 if (strcmp (Editor, "") == 0 && !editinfo_editor)
212 error(1, 0, "no editor defined, must use -e or -m");
214 /* Create a temporary file */
215 /* FIXME - It's possible we should be relying on cvs_temp_file to open
216 * the file here - we get race conditions otherwise.
218 fname = cvs_temp_name ();
220 if ((fp = CVS_FOPEN (fname, "w+")) == NULL)
221 error (1, 0, "cannot create temporary file %s", fname);
225 (void) fputs (*messagep, fp);
227 if ((*messagep)[0] == '\0' ||
228 (*messagep)[strlen (*messagep) - 1] != '\n')
229 (void) fprintf (fp, "\n");
232 (void) fprintf (fp, "\n");
234 if (repository != NULL)
235 /* tack templates on if necessary */
236 (void) Parse_Info (CVSROOTADM_RCSINFO, repository, rcsinfo_proc, 1);
245 tfp = CVS_FOPEN (CVSADM_TEMPLATE, "rb");
248 if (!existence_error (errno))
249 error (1, errno, "cannot read %s", CVSADM_TEMPLATE);
256 n = fread (buf, 1, sizeof buf, tfp);
260 n = fwrite (p, 1, nwrite, fp);
265 error (1, errno, "cannot read %s", CVSADM_TEMPLATE);
267 if (fclose (tfp) < 0)
268 error (0, errno, "cannot close %s", CVSADM_TEMPLATE);
273 "%s----------------------------------------------------------------------\n",
276 "%sEnter Log. Lines beginning with `%.*s' are removed automatically\n%s\n",
277 CVSEDITPREFIX, CVSEDITPREFIXLEN, CVSEDITPREFIX,
279 if (dir != NULL && *dir)
280 (void) fprintf (fp, "%sCommitting in %s\n%s\n", CVSEDITPREFIX,
283 setup_tmpfile (fp, CVSEDITPREFIX, changes);
285 "%s----------------------------------------------------------------------\n",
288 /* finish off the temp file */
289 if (fclose (fp) == EOF)
290 error (1, errno, "%s", fname);
291 if ( CVS_STAT (fname, &pre_stbuf) == -1)
292 pre_stbuf.st_mtime = 0;
295 free (editinfo_editor);
296 editinfo_editor = (char *) NULL;
297 #ifdef CLIENT_SUPPORT
298 if (current_parsed_root->isremote)
299 ; /* nothing, leave editinfo_editor NULL */
302 if (repository != NULL)
303 (void) Parse_Info (CVSROOTADM_EDITINFO, repository, editinfo_proc, 0);
306 run_setup (editinfo_editor ? editinfo_editor : Editor);
308 if ((retcode = run_exec (RUN_TTY, RUN_TTY, RUN_TTY,
309 RUN_NORMAL | RUN_SIGIGNORE)) != 0)
310 error (editinfo_editor ? 1 : 0, retcode == -1 ? errno : 0,
311 editinfo_editor ? "Logfile verification failed" :
312 "warning: editor session failed");
314 /* put the entire message back into the *messagep variable */
316 fp = open_file (fname, "r");
321 if ( CVS_STAT (fname, &post_stbuf) != 0)
322 error (1, errno, "cannot find size of temp file %s", fname);
324 if (post_stbuf.st_size == 0)
328 /* On NT, we might read less than st_size bytes, but we won't
329 read more. So this works. */
330 *messagep = (char *) xmalloc (post_stbuf.st_size + 1);
331 (*messagep)[0] = '\0';
335 line_chars_allocated = 0;
339 size_t message_len = post_stbuf.st_size + 1;
343 line_length = getline (&line, &line_chars_allocated, fp);
344 if (line_length == -1)
347 error (0, errno, "warning: cannot read %s", fname);
350 if (strncmp (line, CVSEDITPREFIX, CVSEDITPREFIXLEN) == 0)
352 if (offset + line_length >= message_len)
353 expand_string (messagep, &message_len,
354 offset + line_length + 1);
355 (void) strcpy (*messagep + offset, line);
356 offset += line_length;
360 error (0, errno, "warning: cannot close %s", fname);
362 /* canonicalize emply messages */
363 if (*messagep != NULL &&
364 (**messagep == '\0' || strcmp (*messagep, "\n") == 0))
370 if (pre_stbuf.st_mtime == post_stbuf.st_mtime || *messagep == NULL)
374 (void) printf ("\nLog message unchanged or not specified\n");
375 (void) printf ("a)bort, c)ontinue, e)dit, !)reuse this message unchanged for remaining dirs\n");
376 (void) printf ("Action: (continue) ");
377 (void) fflush (stdout);
378 line_length = getline (&line, &line_chars_allocated, stdin);
381 error (0, errno, "cannot read from stdin");
382 if (unlink_file (fname) < 0)
384 "warning: cannot remove temp file %s", fname);
385 error (1, 0, "aborting");
387 else if (line_length == 0
388 || *line == '\n' || *line == 'c' || *line == 'C')
390 if (*line == 'a' || *line == 'A')
392 if (unlink_file (fname) < 0)
393 error (0, errno, "warning: cannot remove temp file %s", fname);
394 error (1, 0, "aborted by user");
396 if (*line == 'e' || *line == 'E')
400 reuse_log_message = 1;
403 (void) printf ("Unknown input\n");
408 if (unlink_file (fname) < 0)
409 error (0, errno, "warning: cannot remove temp file %s", fname);
413 /* Runs the user-defined verification script as part of the commit or import
414 process. This verification is meant to be run whether or not the user
415 included the -m atribute. unlike the do_editor function, this is
416 independant of the running of an editor for getting a message.
419 do_verify (messagep, repository)
421 const char *repository;
427 struct stat pre_stbuf, post_stbuf;
429 #ifdef CLIENT_SUPPORT
430 if (current_parsed_root->isremote)
431 /* The verification will happen on the server. */
435 /* FIXME? Do we really want to skip this on noexec? What do we do
436 for the other administrative files? */
437 if (noexec || repository == NULL)
440 /* Get the name of the verification script to run */
442 if (Parse_Info (CVSROOTADM_VERIFYMSG, repository, verifymsg_proc, 0) > 0)
443 error (1, 0, "Message verification failed");
445 if (!verifymsg_script)
448 /* open a temporary file, write the message to the
449 temp file, and close the file. */
451 if ((fp = cvs_temp_file (&fname)) == NULL)
452 error (1, errno, "cannot create temporary file %s", fname);
454 if (*messagep != NULL)
455 fputs (*messagep, fp);
456 if (*messagep == NULL ||
457 (*messagep)[0] == '\0' ||
458 (*messagep)[strlen (*messagep) - 1] != '\n')
460 if (fclose (fp) == EOF)
461 error (1, errno, "%s", fname);
463 if (RereadLogAfterVerify == LOGMSG_REREAD_STAT)
465 /* Remember the status of the temp file for later */
466 if ( CVS_STAT (fname, &pre_stbuf) != 0 )
467 error (1, errno, "cannot stat temp file %s", fname);
470 * See if we need to sleep before running the verification
471 * script to avoid time-stamp races.
473 sleep_past (pre_stbuf.st_mtime);
476 run_setup (verifymsg_script);
478 if ((retcode = run_exec (RUN_TTY, RUN_TTY, RUN_TTY,
479 RUN_NORMAL | RUN_SIGIGNORE)) != 0)
481 /* Since following error() exits, delete the temp file now. */
482 if (unlink_file (fname) < 0)
483 error (0, errno, "cannot remove %s", fname);
485 error (1, retcode == -1 ? errno : 0,
486 "Message verification failed");
489 /* Get the mod time and size of the possibly new log message
490 * in always and stat modes.
492 if (RereadLogAfterVerify == LOGMSG_REREAD_ALWAYS ||
493 RereadLogAfterVerify == LOGMSG_REREAD_STAT)
495 if ( CVS_STAT (fname, &post_stbuf) != 0 )
496 error (1, errno, "cannot find size of temp file %s", fname);
499 /* And reread the log message in `always' mode or in `stat' mode when it's
502 if (RereadLogAfterVerify == LOGMSG_REREAD_ALWAYS ||
503 (RereadLogAfterVerify == LOGMSG_REREAD_STAT &&
504 (pre_stbuf.st_mtime != post_stbuf.st_mtime ||
505 pre_stbuf.st_size != post_stbuf.st_size)))
507 /* put the entire message back into the *messagep variable */
509 if (*messagep) free (*messagep);
511 if (post_stbuf.st_size == 0)
517 size_t line_chars_allocated = 0;
520 if ( (fp = open_file (fname, "r")) == NULL )
521 error (1, errno, "cannot open temporary file %s", fname);
523 /* On NT, we might read less than st_size bytes,
524 but we won't read more. So this works. */
525 p = *messagep = (char *) xmalloc (post_stbuf.st_size + 1);
530 line_length = getline (&line,
531 &line_chars_allocated,
533 if (line_length == -1)
536 /* Fail in this case because otherwise we will have no
539 error (1, errno, "cannot read %s", fname);
542 if (strncmp (line, CVSEDITPREFIX, CVSEDITPREFIXLEN) == 0)
544 (void) strcpy (p, line);
547 if (line) free (line);
549 error (0, errno, "warning: cannot close %s", fname);
553 /* Delete the temp file */
555 if (unlink_file (fname) < 0)
556 error (0, errno, "cannot remove %s", fname);
558 free( verifymsg_script );
559 verifymsg_script = NULL;
563 * callback proc for Parse_Info for rcsinfo templates this routine basically
564 * copies the matching template onto the end of the tempfile we are setting
569 rcsinfo_proc (repository, template)
570 const char *repository;
571 const char *template;
573 static char *last_template;
576 /* nothing to do if the last one included is the same as this one */
577 if (last_template && strcmp (last_template, template) == 0)
580 free (last_template);
581 last_template = xstrdup (template);
583 if ((tfp = CVS_FOPEN (template, "r")) != NULL)
586 size_t line_chars_allocated = 0;
588 while (getline (&line, &line_chars_allocated, tfp) >= 0)
589 (void) fputs (line, fp);
591 error (0, errno, "warning: cannot read %s", template);
592 if (fclose (tfp) < 0)
593 error (0, errno, "warning: cannot close %s", template);
600 error (0, errno, "Couldn't open rcsinfo template file %s", template);
606 * Uses setup_tmpfile() to pass the updated message on directly to any
607 * logfile programs that have a regular expression match for the checked in
608 * directory in the source repository. The log information is fed into the
609 * specified program as standard input.
612 static const char *message;
613 static List *changes;
616 Update_Logfile (repository, xmessage, xlogfp, xchanges)
617 const char *repository;
618 const char *xmessage;
622 /* nothing to do if the list is empty */
623 if (xchanges == NULL || xchanges->list->next == xchanges->list)
626 /* set up static vars for update_logfile_proc */
631 /* call Parse_Info to do the actual logfile updates */
632 (void) Parse_Info (CVSROOTADM_LOGINFO, repository, update_logfile_proc, 1);
638 * callback proc to actually do the logfile write from Update_Logfile
641 update_logfile_proc (repository, filter)
642 const char *repository;
645 return logfile_write (repository, filter, message, logfp, changes);
651 * concatenate each filename/version onto str_list
654 title_proc (p, closure)
659 struct logfile_info *li = p->data;
661 if (li->type == type)
663 /* Until we decide on the correct logging solution when we add
664 directories or perform imports, T_TITLE nodes will only
665 tack on the name provided, regardless of the format string.
666 You can verify that this assumption is safe by checking the
667 code in add.c (add_directory) and import.c (import). */
669 str_list = xrealloc (str_list, strlen (str_list) + 5);
670 (void) strcat (str_list, " ");
672 if (li->type == T_TITLE)
674 str_list = xrealloc (str_list,
675 strlen (str_list) + strlen (p->key) + 5);
676 (void) strcat (str_list, p->key);
680 /* All other nodes use the format string. */
682 for (c = str_list_format; *c != '\0'; c++)
689 strlen (str_list) + strlen (p->key) + 5);
690 (void) strcat (str_list, p->key);
696 + (li->rev_old ? strlen (li->rev_old) : 0)
699 (void) strcat (str_list, (li->rev_old
700 ? li->rev_old : "NONE"));
706 + (li->rev_new ? strlen (li->rev_new) : 0)
709 (void) strcat (str_list, (li->rev_new
710 ? li->rev_new : "NONE"));
712 /* All other characters, we insert an empty field (but
713 we do put in the comma separating it from other
714 fields). This way if future CVS versions add formatting
715 characters, one can write a loginfo file which at least
716 won't blow up on an old CVS. */
717 /* Note that people who have to deal with spaces in file
718 and directory names are using space to get a known
719 delimiter for the directory name, so it's probably
720 not a good idea to ever define that as a formatting
723 if (*(c + 1) != '\0')
725 str_list = xrealloc (str_list, strlen (str_list) + 5);
726 (void) strcat (str_list, ",");
735 * Writes some stuff to the logfile "filter" and returns the status of the
739 logfile_write (repository, filter, message, logfp, changes)
740 const char *repository;
751 char *fmt_percent; /* the location of the percent sign
752 that starts the format string. */
754 /* The user may specify a format string as part of the filter.
755 Originally, `%s' was the only valid string. The string that
756 was substituted for it was:
758 <repository-name> <file1> <file2> <file3> ...
760 Each file was either a new directory/import (T_TITLE), or a
761 added (T_ADDED), modified (T_MODIFIED), or removed (T_REMOVED)
764 It is desirable to preserve that behavior so lots of commitlog
765 scripts won't die when they get this new code. At the same
766 time, we'd like to pass other information about the files (like
767 version numbers, statuses, or checkin times).
769 The solution is to allow a format string that allows us to
770 specify those other pieces of information. The format string
771 will be composed of `%' followed by a single format character,
772 or followed by a set of format characters surrounded by `{' and
773 `}' as separators. The format characters are:
776 V = old version number (pre-checkin)
777 v = new version number (post-checkin)
779 For example, valid format strings are:
786 There's no reason that more items couldn't be added (like
787 modification date or file status [added, modified, updated,
788 etc.]) -- the code modifications would be minimal (logmsg.c
789 (title_proc) and commit.c (check_fileproc)).
791 The output will be a string of tokens separated by spaces. For
792 backwards compatibility, the the first token will be the
793 repository name. The rest of the tokens will be
794 comma-delimited lists of the information requested in the
795 format string. For example, if `/u/src/master' is the
796 repository, `%{sVv}' is the format string, and three files
797 (ChangeLog, Makefile, foo.c) were modified, the output might
800 /u/src/master ChangeLog,1.1,1.2 Makefile,1.3,1.4 foo.c,1.12,1.13
802 Why this duplicates the old behavior when the format string is
803 `%s' is left as an exercise for the reader. */
805 fmt_percent = strchr (filter, '%');
810 char *fmt_begin, *fmt_end; /* beginning and end of the
811 format string specified in
813 char *fmt_continue; /* where the string continues
814 after the format string (we
815 might skip a '}') somewhere
818 /* Grab the format string. */
820 if ((*(fmt_percent + 1) == ' ') || (*(fmt_percent + 1) == '\0'))
822 /* The percent stands alone. This is an error. We could
823 be treating ' ' like any other formatting character, but
824 using it as a formatting character seems like it would be
827 /* Would be nice to also be giving the line number. */
828 error (0, 0, "loginfo: '%%' not followed by formatting character");
829 fmt_begin = fmt_percent + 1;
831 fmt_continue = fmt_begin;
833 else if (*(fmt_percent + 1) == '{')
835 /* The percent has a set of characters following it. */
837 fmt_begin = fmt_percent + 2;
838 fmt_end = strchr (fmt_begin, '}');
841 /* Skip over the '}' character. */
843 fmt_continue = fmt_end + 1;
847 /* There was no close brace -- assume that format
848 string continues to the end of the line. */
850 /* Would be nice to also be giving the line number. */
851 error (0, 0, "loginfo: '}' missing");
852 fmt_end = fmt_begin + strlen (fmt_begin);
853 fmt_continue = fmt_end;
858 /* The percent has a single character following it. FIXME:
859 %% should expand to a regular percent sign. */
861 fmt_begin = fmt_percent + 1;
862 fmt_end = fmt_begin + 1;
863 fmt_continue = fmt_end;
866 len = fmt_end - fmt_begin;
867 str_list_format = xmalloc (len + 1);
868 strncpy (str_list_format, fmt_begin, len);
869 str_list_format[len] = '\0';
871 /* Allocate an initial chunk of memory. As we build up the string
872 we will realloc it. */
874 str_list = xmalloc (1);
877 /* Add entries to the string. Don't bother looking for
878 entries if the format string is empty. */
880 if (str_list_format[0] != '\0')
883 (void) walklist (changes, title_proc, NULL);
885 (void) walklist (changes, title_proc, NULL);
887 (void) walklist (changes, title_proc, NULL);
889 (void) walklist (changes, title_proc, NULL);
892 free (str_list_format);
894 /* Construct the final string. */
896 srepos = Short_Repository (repository);
898 prog = cp = xmalloc ((fmt_percent - filter) + 2 * strlen (srepos)
899 + 2 * strlen (str_list) + strlen (fmt_continue)
901 (void) memcpy (cp, filter, fmt_percent - filter);
902 cp += fmt_percent - filter;
904 cp = shell_escape (cp, srepos);
905 cp = shell_escape (cp, str_list);
907 (void) strcpy (cp, fmt_continue);
909 /* To be nice, free up some memory. */
912 str_list = (char *) NULL;
916 /* There's no format string. */
917 prog = xstrdup (filter);
920 if ((pipefp = run_popen (prog, "w")) == NULL)
923 error (0, 0, "cannot write entry to log filter: %s", prog);
927 (void) fprintf (pipefp, "Update of %s\n", repository);
928 (void) fprintf (pipefp, "In directory %s:", hostname);
931 fprintf (pipefp, "<cannot get working directory: %s>\n\n",
935 fprintf (pipefp, "%s\n\n", cp);
939 setup_tmpfile (pipefp, "", changes);
940 (void) fprintf (pipefp, "Log Message:\n%s\n", (message) ? message : "");
941 if (logfp != (FILE *) 0)
943 (void) fprintf (pipefp, "Status:\n");
945 while ((c = getc (logfp)) != EOF)
946 (void) putc ((char) c, pipefp);
949 pipestatus = pclose (pipefp);
950 return ((pipestatus == -1) || (pipestatus == 127)) ? 1 : 0;
954 * We choose to use the *last* match within the editinfo file for this
955 * repository. This allows us to have a global editinfo program for the
956 * root of some hierarchy, for example, and different ones within different
957 * sub-directories of the root (like a special checker for changes made to
958 * the "src" directory versus changes made to the "doc" or "test"
963 editinfo_proc(repository, editor)
964 const char *repository;
967 /* nothing to do if the last match is the same as this one */
968 if (editinfo_editor && strcmp (editinfo_editor, editor) == 0)
971 free (editinfo_editor);
973 editinfo_editor = xstrdup (editor);
977 /* This routine is calld by Parse_Info. it asigns the name of the
978 * message verification script to the global variable verify_script
981 verifymsg_proc (repository, script)
982 const char *repository;
985 if (verifymsg_script && strcmp (verifymsg_script, script) == 0)
987 if (verifymsg_script)
988 free (verifymsg_script);
989 verifymsg_script = xstrdup (script);