2 * Copyright (C) 1986-2005 The Free Software Foundation, Inc.
4 * Portions Copyright (C) 1998-2005 Derek Price, Ximbiot <http://ximbiot.com>,
7 * Portions Copyright (C) 1992, Brian Berliner and Jeff Polk
8 * Portions Copyright (C) 1989-1992, Brian Berliner
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.
21 static int find_type PROTO((Node * p, void *closure));
22 static int fmt_proc PROTO((Node * p, void *closure));
23 static int logfile_write PROTO((const char *repository, const char *filter,
24 const char *message, FILE * logfp,
26 static int rcsinfo_proc PROTO((const char *repository, const char *template));
27 static int title_proc PROTO((Node * p, void *closure));
28 static int update_logfile_proc PROTO((const char *repository,
30 static void setup_tmpfile PROTO((FILE * xfp, char *xprefix, List * changes));
31 static int editinfo_proc PROTO((const char *repository, const char *template));
32 static int verifymsg_proc PROTO((const char *repository, const char *script));
35 static char *str_list;
36 static char *str_list_format; /* The format for str_list's contents. */
37 static char *editinfo_editor;
38 static char *verifymsg_script;
42 * Should the logmsg be re-read during the do_verify phase?
43 * RereadLogAfterVerify=no|stat|yes
44 * LOGMSG_REREAD_NEVER - never re-read the logmsg
45 * LOGMSG_REREAD_STAT - re-read the logmsg only if it has changed
46 * LOGMSG_REREAD_ALWAYS - always re-read the logmsg
48 int RereadLogAfterVerify = LOGMSG_REREAD_ALWAYS;
51 * Puts a standard header on the output which is either being prepared for an
52 * editor session, or being sent to a logfile program. The modified, added,
53 * and removed files are included (if any) and formatted to look pretty. */
58 setup_tmpfile (xfp, xprefix, changes)
68 if (walklist (changes, find_type, NULL) != 0)
70 (void) fprintf (fp, "%sModified Files:\n", prefix);
72 (void) walklist (changes, fmt_proc, NULL);
73 (void) fprintf (fp, "\n");
81 if (walklist (changes, find_type, NULL) != 0)
83 (void) fprintf (fp, "%sAdded Files:\n", prefix);
85 (void) walklist (changes, fmt_proc, NULL);
86 (void) fprintf (fp, "\n");
94 if (walklist (changes, find_type, NULL) != 0)
96 (void) fprintf (fp, "%sRemoved Files:\n", prefix);
98 (void) walklist (changes, fmt_proc, NULL);
99 (void) fprintf (fp, "\n");
109 * Looks for nodes of a specified type and returns 1 if found
112 find_type (p, closure)
116 struct logfile_info *li = p->data;
118 if (li->type == type)
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
130 fmt_proc (p, closure)
134 struct logfile_info *li;
137 if (li->type == type)
141 : tag == NULL || strcmp (tag, li->tag) != 0)
144 (void) fprintf (fp, "\n");
145 (void) fputs (prefix, fp);
146 col = strlen (prefix);
149 (void) fprintf (fp, " ");
154 (void) fprintf (fp, "No tag");
156 (void) fprintf (fp, "Tag: %s", li->tag);
160 tag = xstrdup (li->tag);
162 /* Force a new line. */
168 (void) fprintf (fp, "%s\t", prefix);
171 else if (col > 8 && (col + (int) strlen (p->key)) > 70)
173 (void) fprintf (fp, "\n%s\t", prefix);
176 (void) fprintf (fp, "%s ", p->key);
177 col += strlen (p->key) + 1;
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.
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.
192 do_editor (dir, messagep, repository, changes)
195 const char *repository;
198 static int reuse_log_message = 0;
201 size_t line_chars_allocated;
203 struct stat pre_stbuf, post_stbuf;
206 assert (!current_parsed_root->isremote != !repository);
208 if (noexec || reuse_log_message)
211 /* Abort creation of temp file if no editor is defined */
212 if (strcmp (Editor, "") == 0 && !editinfo_editor)
213 error(1, 0, "no editor defined, must use -e or -m");
215 /* Create a temporary file */
216 /* FIXME - It's possible we should be relying on cvs_temp_file to open
217 * the file here - we get race conditions otherwise.
219 fname = cvs_temp_name ();
221 if ((fp = CVS_FOPEN (fname, "w+")) == NULL)
222 error (1, 0, "cannot create temporary file %s", fname);
226 (void) fputs (*messagep, fp);
228 if ((*messagep)[0] == '\0' ||
229 (*messagep)[strlen (*messagep) - 1] != '\n')
230 (void) fprintf (fp, "\n");
233 (void) fprintf (fp, "\n");
235 if (repository != NULL)
236 /* tack templates on if necessary */
237 (void) Parse_Info (CVSROOTADM_RCSINFO, repository, rcsinfo_proc, 1);
246 tfp = CVS_FOPEN (CVSADM_TEMPLATE, "rb");
249 if (!existence_error (errno))
250 error (1, errno, "cannot read %s", CVSADM_TEMPLATE);
257 n = fread (buf, 1, sizeof buf, tfp);
261 n = fwrite (p, 1, nwrite, fp);
266 error (1, errno, "cannot read %s", CVSADM_TEMPLATE);
268 if (fclose (tfp) < 0)
269 error (0, errno, "cannot close %s", CVSADM_TEMPLATE);
274 "%s----------------------------------------------------------------------\n",
277 "%sEnter Log. Lines beginning with `%.*s' are removed automatically\n%s\n",
278 CVSEDITPREFIX, CVSEDITPREFIXLEN, CVSEDITPREFIX,
280 if (dir != NULL && *dir)
281 (void) fprintf (fp, "%sCommitting in %s\n%s\n", CVSEDITPREFIX,
284 setup_tmpfile (fp, CVSEDITPREFIX, changes);
286 "%s----------------------------------------------------------------------\n",
289 /* finish off the temp file */
290 if (fclose (fp) == EOF)
291 error (1, errno, "%s", fname);
292 if ( CVS_STAT (fname, &pre_stbuf) == -1)
293 pre_stbuf.st_mtime = 0;
296 free (editinfo_editor);
297 editinfo_editor = (char *) NULL;
298 if (!current_parsed_root->isremote && repository != NULL)
299 (void) Parse_Info (CVSROOTADM_EDITINFO, repository, editinfo_proc, 0);
302 run_setup (editinfo_editor ? editinfo_editor : Editor);
304 if ((retcode = run_exec (RUN_TTY, RUN_TTY, RUN_TTY,
305 RUN_NORMAL | RUN_SIGIGNORE)) != 0)
306 error (editinfo_editor ? 1 : 0, retcode == -1 ? errno : 0,
307 editinfo_editor ? "Logfile verification failed" :
308 "warning: editor session failed");
310 /* put the entire message back into the *messagep variable */
312 fp = open_file (fname, "r");
317 if ( CVS_STAT (fname, &post_stbuf) != 0)
318 error (1, errno, "cannot find size of temp file %s", fname);
320 if (post_stbuf.st_size == 0)
324 /* On NT, we might read less than st_size bytes, but we won't
325 read more. So this works. */
326 *messagep = (char *) xmalloc (post_stbuf.st_size + 1);
327 (*messagep)[0] = '\0';
331 line_chars_allocated = 0;
335 size_t message_len = post_stbuf.st_size + 1;
339 line_length = getline (&line, &line_chars_allocated, fp);
340 if (line_length == -1)
343 error (0, errno, "warning: cannot read %s", fname);
346 if (strncmp (line, CVSEDITPREFIX, CVSEDITPREFIXLEN) == 0)
348 if (offset + line_length >= message_len)
349 expand_string (messagep, &message_len,
350 offset + line_length + 1);
351 (void) strcpy (*messagep + offset, line);
352 offset += line_length;
356 error (0, errno, "warning: cannot close %s", fname);
358 /* canonicalize emply messages */
359 if (*messagep != NULL &&
360 (**messagep == '\0' || strcmp (*messagep, "\n") == 0))
366 if (pre_stbuf.st_mtime == post_stbuf.st_mtime || *messagep == NULL)
370 (void) printf ("\nLog message unchanged or not specified\n");
371 (void) printf ("a)bort, c)ontinue, e)dit, !)reuse this message unchanged for remaining dirs\n");
372 (void) printf ("Action: (continue) ");
373 (void) fflush (stdout);
374 line_length = getline (&line, &line_chars_allocated, stdin);
377 error (0, errno, "cannot read from stdin");
378 if (unlink_file (fname) < 0)
380 "warning: cannot remove temp file %s", fname);
381 error (1, 0, "aborting");
383 else if (line_length == 0
384 || *line == '\n' || *line == 'c' || *line == 'C')
386 if (*line == 'a' || *line == 'A')
388 if (unlink_file (fname) < 0)
389 error (0, errno, "warning: cannot remove temp file %s", fname);
390 error (1, 0, "aborted by user");
392 if (*line == 'e' || *line == 'E')
396 reuse_log_message = 1;
399 (void) printf ("Unknown input\n");
404 if (unlink_file (fname) < 0)
405 error (0, errno, "warning: cannot remove temp file %s", fname);
409 /* Runs the user-defined verification script as part of the commit or import
410 process. This verification is meant to be run whether or not the user
411 included the -m atribute. unlike the do_editor function, this is
412 independant of the running of an editor for getting a message.
415 do_verify (messagep, repository)
417 const char *repository;
423 struct stat pre_stbuf, post_stbuf;
425 if (current_parsed_root->isremote)
426 /* The verification will happen on the server. */
429 /* FIXME? Do we really want to skip this on noexec? What do we do
430 for the other administrative files? */
431 if (noexec || repository == NULL)
434 /* Get the name of the verification script to run */
436 if (Parse_Info (CVSROOTADM_VERIFYMSG, repository, verifymsg_proc, 0) > 0)
437 error (1, 0, "Message verification failed");
439 if (!verifymsg_script)
442 /* open a temporary file, write the message to the
443 temp file, and close the file. */
445 if ((fp = cvs_temp_file (&fname)) == NULL)
446 error (1, errno, "cannot create temporary file %s",
447 fname ? fname : "(null)");
449 if (*messagep != NULL)
450 fputs (*messagep, fp);
451 if (*messagep == NULL ||
452 (*messagep)[0] == '\0' ||
453 (*messagep)[strlen (*messagep) - 1] != '\n')
455 if (fclose (fp) == EOF)
456 error (1, errno, "%s", fname);
458 if (RereadLogAfterVerify == LOGMSG_REREAD_STAT)
460 /* Remember the status of the temp file for later */
461 if ( CVS_STAT (fname, &pre_stbuf) != 0 )
462 error (1, errno, "cannot stat temp file %s", fname);
465 * See if we need to sleep before running the verification
466 * script to avoid time-stamp races.
468 sleep_past (pre_stbuf.st_mtime);
471 run_setup (verifymsg_script);
473 if ((retcode = run_exec (RUN_TTY, RUN_TTY, RUN_TTY,
474 RUN_NORMAL | RUN_SIGIGNORE)) != 0)
476 /* Since following error() exits, delete the temp file now. */
477 if (unlink_file (fname) < 0)
478 error (0, errno, "cannot remove %s", fname);
480 error (1, retcode == -1 ? errno : 0,
481 "Message verification failed");
484 /* Get the mod time and size of the possibly new log message
485 * in always and stat modes.
487 if (RereadLogAfterVerify == LOGMSG_REREAD_ALWAYS ||
488 RereadLogAfterVerify == LOGMSG_REREAD_STAT)
490 if ( CVS_STAT (fname, &post_stbuf) != 0 )
491 error (1, errno, "cannot find size of temp file %s", fname);
494 /* And reread the log message in `always' mode or in `stat' mode when it's
497 if (RereadLogAfterVerify == LOGMSG_REREAD_ALWAYS ||
498 (RereadLogAfterVerify == LOGMSG_REREAD_STAT &&
499 (pre_stbuf.st_mtime != post_stbuf.st_mtime ||
500 pre_stbuf.st_size != post_stbuf.st_size)))
502 /* put the entire message back into the *messagep variable */
504 if (*messagep) free (*messagep);
506 if (post_stbuf.st_size == 0)
512 size_t line_chars_allocated = 0;
515 if ( (fp = open_file (fname, "r")) == NULL )
516 error (1, errno, "cannot open temporary file %s", fname);
518 /* On NT, we might read less than st_size bytes,
519 but we won't read more. So this works. */
520 p = *messagep = (char *) xmalloc (post_stbuf.st_size + 1);
525 line_length = getline (&line,
526 &line_chars_allocated,
528 if (line_length == -1)
531 /* Fail in this case because otherwise we will have no
534 error (1, errno, "cannot read %s", fname);
537 if (strncmp (line, CVSEDITPREFIX, CVSEDITPREFIXLEN) == 0)
539 (void) strcpy (p, line);
542 if (line) free (line);
544 error (0, errno, "warning: cannot close %s", fname);
548 /* Delete the temp file */
550 if (unlink_file (fname) < 0)
551 error (0, errno, "cannot remove %s", fname);
553 free (verifymsg_script);
554 verifymsg_script = NULL;
558 * callback proc for Parse_Info for rcsinfo templates this routine basically
559 * copies the matching template onto the end of the tempfile we are setting
564 rcsinfo_proc (repository, template)
565 const char *repository;
566 const char *template;
568 static char *last_template;
571 /* nothing to do if the last one included is the same as this one */
572 if (last_template && strcmp (last_template, template) == 0)
575 free (last_template);
576 last_template = xstrdup (template);
578 if ((tfp = CVS_FOPEN (template, "r")) != NULL)
581 size_t line_chars_allocated = 0;
583 while (getline (&line, &line_chars_allocated, tfp) >= 0)
584 (void) fputs (line, fp);
586 error (0, errno, "warning: cannot read %s", template);
587 if (fclose (tfp) < 0)
588 error (0, errno, "warning: cannot close %s", template);
595 error (0, errno, "Couldn't open rcsinfo template file %s", template);
601 * Uses setup_tmpfile() to pass the updated message on directly to any
602 * logfile programs that have a regular expression match for the checked in
603 * directory in the source repository. The log information is fed into the
604 * specified program as standard input.
607 static const char *message;
608 static List *changes;
611 Update_Logfile (repository, xmessage, xlogfp, xchanges)
612 const char *repository;
613 const char *xmessage;
617 /* nothing to do if the list is empty */
618 if (xchanges == NULL || xchanges->list->next == xchanges->list)
621 /* set up static vars for update_logfile_proc */
626 /* call Parse_Info to do the actual logfile updates */
627 (void) Parse_Info (CVSROOTADM_LOGINFO, repository, update_logfile_proc, 1);
633 * callback proc to actually do the logfile write from Update_Logfile
636 update_logfile_proc (repository, filter)
637 const char *repository;
640 return logfile_write (repository, filter, message, logfp, changes);
646 * concatenate each filename/version onto str_list
649 title_proc (p, closure)
654 struct logfile_info *li = p->data;
656 if (li->type == type)
658 /* Until we decide on the correct logging solution when we add
659 directories or perform imports, T_TITLE nodes will only
660 tack on the name provided, regardless of the format string.
661 You can verify that this assumption is safe by checking the
662 code in add.c (add_directory) and import.c (import). */
664 str_list = xrealloc (str_list, strlen (str_list) + 5);
665 (void) strcat (str_list, " ");
667 if (li->type == T_TITLE)
669 str_list = xrealloc (str_list,
670 strlen (str_list) + strlen (p->key) + 5);
671 (void) strcat (str_list, p->key);
675 /* All other nodes use the format string. */
677 for (c = str_list_format; *c != '\0'; c++)
684 strlen (str_list) + strlen (p->key) + 5);
685 (void) strcat (str_list, p->key);
691 + (li->rev_old ? strlen (li->rev_old) : 0)
694 (void) strcat (str_list, (li->rev_old
695 ? li->rev_old : "NONE"));
701 + (li->rev_new ? strlen (li->rev_new) : 0)
704 (void) strcat (str_list, (li->rev_new
705 ? li->rev_new : "NONE"));
707 /* All other characters, we insert an empty field (but
708 we do put in the comma separating it from other
709 fields). This way if future CVS versions add formatting
710 characters, one can write a loginfo file which at least
711 won't blow up on an old CVS. */
712 /* Note that people who have to deal with spaces in file
713 and directory names are using space to get a known
714 delimiter for the directory name, so it's probably
715 not a good idea to ever define that as a formatting
718 if (*(c + 1) != '\0')
720 str_list = xrealloc (str_list, strlen (str_list) + 5);
721 (void) strcat (str_list, ",");
730 * Writes some stuff to the logfile "filter" and returns the status of the
734 logfile_write (repository, filter, message, logfp, changes)
735 const char *repository;
746 char *fmt_percent; /* the location of the percent sign
747 that starts the format string. */
751 /* The user may specify a format string as part of the filter.
752 Originally, `%s' was the only valid string. The string that
753 was substituted for it was:
755 <repository-name> <file1> <file2> <file3> ...
757 Each file was either a new directory/import (T_TITLE), or a
758 added (T_ADDED), modified (T_MODIFIED), or removed (T_REMOVED)
761 It is desirable to preserve that behavior so lots of commitlog
762 scripts won't die when they get this new code. At the same
763 time, we'd like to pass other information about the files (like
764 version numbers, statuses, or checkin times).
766 The solution is to allow a format string that allows us to
767 specify those other pieces of information. The format string
768 will be composed of `%' followed by a single format character,
769 or followed by a set of format characters surrounded by `{' and
770 `}' as separators. The format characters are:
773 V = old version number (pre-checkin)
774 v = new version number (post-checkin)
776 For example, valid format strings are:
783 There's no reason that more items couldn't be added (like
784 modification date or file status [added, modified, updated,
785 etc.]) -- the code modifications would be minimal (logmsg.c
786 (title_proc) and commit.c (check_fileproc)).
788 The output will be a string of tokens separated by spaces. For
789 backwards compatibility, the the first token will be the
790 repository name. The rest of the tokens will be
791 comma-delimited lists of the information requested in the
792 format string. For example, if `/u/src/master' is the
793 repository, `%{sVv}' is the format string, and three files
794 (ChangeLog, Makefile, foo.c) were modified, the output might
797 /u/src/master ChangeLog,1.1,1.2 Makefile,1.3,1.4 foo.c,1.12,1.13
799 Why this duplicates the old behavior when the format string is
800 `%s' is left as an exercise for the reader. */
802 fmt_percent = strchr (filter, '%');
807 char *fmt_begin, *fmt_end; /* beginning and end of the
808 format string specified in
810 char *fmt_continue; /* where the string continues
811 after the format string (we
812 might skip a '}') somewhere
815 /* Grab the format string. */
817 if ((*(fmt_percent + 1) == ' ') || (*(fmt_percent + 1) == '\0'))
819 /* The percent stands alone. This is an error. We could
820 be treating ' ' like any other formatting character, but
821 using it as a formatting character seems like it would be
824 /* Would be nice to also be giving the line number. */
825 error (0, 0, "loginfo: '%%' not followed by formatting character");
826 fmt_begin = fmt_percent + 1;
828 fmt_continue = fmt_begin;
830 else if (*(fmt_percent + 1) == '{')
832 /* The percent has a set of characters following it. */
834 fmt_begin = fmt_percent + 2;
835 fmt_end = strchr (fmt_begin, '}');
838 /* Skip over the '}' character. */
840 fmt_continue = fmt_end + 1;
844 /* There was no close brace -- assume that format
845 string continues to the end of the line. */
847 /* Would be nice to also be giving the line number. */
848 error (0, 0, "loginfo: '}' missing");
849 fmt_end = fmt_begin + strlen (fmt_begin);
850 fmt_continue = fmt_end;
855 /* The percent has a single character following it. FIXME:
856 %% should expand to a regular percent sign. */
858 fmt_begin = fmt_percent + 1;
859 fmt_end = fmt_begin + 1;
860 fmt_continue = fmt_end;
863 len = fmt_end - fmt_begin;
864 str_list_format = xmalloc (len + 1);
865 strncpy (str_list_format, fmt_begin, len);
866 str_list_format[len] = '\0';
868 /* Allocate an initial chunk of memory. As we build up the string
869 we will realloc it. */
871 str_list = xmalloc (1);
874 /* Add entries to the string. Don't bother looking for
875 entries if the format string is empty. */
877 if (str_list_format[0] != '\0')
880 (void) walklist (changes, title_proc, NULL);
882 (void) walklist (changes, title_proc, NULL);
884 (void) walklist (changes, title_proc, NULL);
886 (void) walklist (changes, title_proc, NULL);
889 free (str_list_format);
891 /* Construct the final string. */
893 srepos = Short_Repository (repository);
895 prog = cp = xmalloc ((fmt_percent - filter) + 2 * strlen (srepos)
896 + 2 * strlen (str_list) + strlen (fmt_continue)
898 (void) memcpy (cp, filter, fmt_percent - filter);
899 cp += fmt_percent - filter;
901 cp = shell_escape (cp, srepos);
902 cp = shell_escape (cp, str_list);
904 (void) strcpy (cp, fmt_continue);
906 /* To be nice, free up some memory. */
909 str_list = (char *) NULL;
913 /* There's no format string. */
914 prog = xstrdup (filter);
917 if ((pipefp = run_popen (prog, "w")) == NULL)
920 error (0, 0, "cannot write entry to log filter: %s", prog);
924 (void) fprintf (pipefp, "Update of %s\n", repository);
925 (void) fprintf (pipefp, "In directory %s:", hostname);
928 fprintf (pipefp, "<cannot get working directory: %s>\n\n",
932 fprintf (pipefp, "%s\n\n", cp);
936 setup_tmpfile (pipefp, "", changes);
937 (void) fprintf (pipefp, "Log Message:\n%s\n", (message) ? message : "");
938 if (logfp != (FILE *) 0)
940 (void) fprintf (pipefp, "Status:\n");
942 while ((c = getc (logfp)) != EOF)
943 (void) putc ((char) c, pipefp);
946 pipestatus = pclose (pipefp);
947 return ((pipestatus == -1) || (pipestatus == 127)) ? 1 : 0;
951 * We choose to use the *last* match within the editinfo file for this
952 * repository. This allows us to have a global editinfo program for the
953 * root of some hierarchy, for example, and different ones within different
954 * sub-directories of the root (like a special checker for changes made to
955 * the "src" directory versus changes made to the "doc" or "test"
960 editinfo_proc(repository, editor)
961 const char *repository;
964 /* nothing to do if the last match is the same as this one */
965 if (editinfo_editor && strcmp (editinfo_editor, editor) == 0)
968 free (editinfo_editor);
970 editinfo_editor = xstrdup (editor);
974 /* This routine is calld by Parse_Info. it asigns the name of the
975 * message verification script to the global variable verify_script
978 verifymsg_proc (repository, script)
979 const char *repository;
982 if (verifymsg_script && strcmp (verifymsg_script, script) == 0)
984 if (verifymsg_script)
985 free (verifymsg_script);
986 verifymsg_script = xstrdup (script);