117721Speter/* 2175274Sobrien * Copyright (C) 1986-2005 The Free Software Foundation, Inc. 3175274Sobrien * 4175274Sobrien * Portions Copyright (C) 1998-2005 Derek Price, Ximbiot <http://ximbiot.com>, 5175274Sobrien * and others. 6175274Sobrien * 7175274Sobrien * Portions Copyright (C) 1992, Brian Berliner and Jeff Polk 8175274Sobrien * Portions Copyright (C) 1989-1992, Brian Berliner 917721Speter * 1017721Speter * You may distribute under the terms of the GNU General Public License as 1132788Speter * specified in the README file that comes with the CVS source distribution. 1254431Speter * 1354431Speter * $FreeBSD$ 1417721Speter */ 1517721Speter 16102843Speter#include <assert.h> 17102843Speter 1817721Speter#include "cvs.h" 1917721Speter#include "getline.h" 2017721Speter 2117721Speterstatic int find_type PROTO((Node * p, void *closure)); 2217721Speterstatic int fmt_proc PROTO((Node * p, void *closure)); 23128269Speterstatic int logfile_write PROTO((const char *repository, const char *filter, 24128269Speter const char *message, FILE * logfp, 25128269Speter List * changes)); 26128269Speterstatic int rcsinfo_proc PROTO((const char *repository, const char *template)); 2717721Speterstatic int title_proc PROTO((Node * p, void *closure)); 28128269Speterstatic int update_logfile_proc PROTO((const char *repository, 29128269Speter const char *filter)); 3017721Speterstatic void setup_tmpfile PROTO((FILE * xfp, char *xprefix, List * changes)); 31128269Speterstatic int editinfo_proc PROTO((const char *repository, const char *template)); 32128269Speterstatic int verifymsg_proc PROTO((const char *repository, const char *script)); 3317721Speter 3417721Speterstatic FILE *fp; 3517721Speterstatic char *str_list; 3625839Speterstatic char *str_list_format; /* The format for str_list's contents. */ 3717721Speterstatic char *editinfo_editor; 3825839Speterstatic char *verifymsg_script; 3917721Speterstatic Ctype type; 4017721Speter 41102843Speter/* 42102843Speter * Should the logmsg be re-read during the do_verify phase? 43102843Speter * RereadLogAfterVerify=no|stat|yes 44102843Speter * LOGMSG_REREAD_NEVER - never re-read the logmsg 45102843Speter * LOGMSG_REREAD_STAT - re-read the logmsg only if it has changed 46102843Speter * LOGMSG_REREAD_ALWAYS - always re-read the logmsg 47102843Speter */ 48102843Speterint RereadLogAfterVerify = LOGMSG_REREAD_ALWAYS; 49102843Speter 5017721Speter/* 5117721Speter * Puts a standard header on the output which is either being prepared for an 5217721Speter * editor session, or being sent to a logfile program. The modified, added, 5317721Speter * and removed files are included (if any) and formatted to look pretty. */ 5417721Speterstatic char *prefix; 5517721Speterstatic int col; 5625839Speterstatic char *tag; 5717721Speterstatic void 5817721Spetersetup_tmpfile (xfp, xprefix, changes) 5917721Speter FILE *xfp; 6017721Speter char *xprefix; 6117721Speter List *changes; 6217721Speter{ 6317721Speter /* set up statics */ 6417721Speter fp = xfp; 6517721Speter prefix = xprefix; 6617721Speter 6717721Speter type = T_MODIFIED; 6817721Speter if (walklist (changes, find_type, NULL) != 0) 6917721Speter { 7017721Speter (void) fprintf (fp, "%sModified Files:\n", prefix); 7125839Speter col = 0; 7217721Speter (void) walklist (changes, fmt_proc, NULL); 7317721Speter (void) fprintf (fp, "\n"); 7425839Speter if (tag != NULL) 7525839Speter { 7625839Speter free (tag); 7725839Speter tag = NULL; 7825839Speter } 7917721Speter } 8017721Speter type = T_ADDED; 8117721Speter if (walklist (changes, find_type, NULL) != 0) 8217721Speter { 8317721Speter (void) fprintf (fp, "%sAdded Files:\n", prefix); 8425839Speter col = 0; 8517721Speter (void) walklist (changes, fmt_proc, NULL); 8617721Speter (void) fprintf (fp, "\n"); 8725839Speter if (tag != NULL) 8825839Speter { 8925839Speter free (tag); 9025839Speter tag = NULL; 9125839Speter } 9217721Speter } 9317721Speter type = T_REMOVED; 9417721Speter if (walklist (changes, find_type, NULL) != 0) 9517721Speter { 9617721Speter (void) fprintf (fp, "%sRemoved Files:\n", prefix); 9725839Speter col = 0; 9817721Speter (void) walklist (changes, fmt_proc, NULL); 9917721Speter (void) fprintf (fp, "\n"); 10025839Speter if (tag != NULL) 10125839Speter { 10225839Speter free (tag); 10325839Speter tag = NULL; 10425839Speter } 10517721Speter } 10617721Speter} 10717721Speter 10817721Speter/* 10917721Speter * Looks for nodes of a specified type and returns 1 if found 11017721Speter */ 11117721Speterstatic int 11217721Speterfind_type (p, closure) 11317721Speter Node *p; 11417721Speter void *closure; 11517721Speter{ 116128269Speter struct logfile_info *li = p->data; 11725839Speter 11825839Speter if (li->type == type) 11917721Speter return (1); 12017721Speter else 12117721Speter return (0); 12217721Speter} 12317721Speter 12417721Speter/* 12517721Speter * Breaks the files list into reasonable sized lines to avoid line wrap... 12617721Speter * all in the name of pretty output. It only works on nodes whose types 12717721Speter * match the one we're looking for 12817721Speter */ 12917721Speterstatic int 13017721Speterfmt_proc (p, closure) 13117721Speter Node *p; 13217721Speter void *closure; 13317721Speter{ 13425839Speter struct logfile_info *li; 13525839Speter 136128269Speter li = p->data; 13725839Speter if (li->type == type) 13817721Speter { 13925839Speter if (li->tag == NULL 14025839Speter ? tag != NULL 14125839Speter : tag == NULL || strcmp (tag, li->tag) != 0) 14217721Speter { 14325839Speter if (col > 0) 14425839Speter (void) fprintf (fp, "\n"); 145107487Speter (void) fputs (prefix, fp); 14625839Speter col = strlen (prefix); 14725839Speter while (col < 6) 14825839Speter { 14925839Speter (void) fprintf (fp, " "); 15025839Speter ++col; 15125839Speter } 15225839Speter 15325839Speter if (li->tag == NULL) 15425839Speter (void) fprintf (fp, "No tag"); 15525839Speter else 15625839Speter (void) fprintf (fp, "Tag: %s", li->tag); 15725839Speter 15825839Speter if (tag != NULL) 15925839Speter free (tag); 16025839Speter tag = xstrdup (li->tag); 16125839Speter 16225839Speter /* Force a new line. */ 16325839Speter col = 70; 16425839Speter } 16525839Speter 16625839Speter if (col == 0) 16725839Speter { 16825839Speter (void) fprintf (fp, "%s\t", prefix); 16925839Speter col = 8; 17025839Speter } 17125839Speter else if (col > 8 && (col + (int) strlen (p->key)) > 70) 17225839Speter { 17317721Speter (void) fprintf (fp, "\n%s\t", prefix); 17417721Speter col = 8; 17517721Speter } 17617721Speter (void) fprintf (fp, "%s ", p->key); 17717721Speter col += strlen (p->key) + 1; 17817721Speter } 17917721Speter return (0); 18017721Speter} 18117721Speter 18217721Speter/* 18317721Speter * Builds a temporary file using setup_tmpfile() and invokes the user's 18417721Speter * editor on the file. The header garbage in the resultant file is then 18517721Speter * stripped and the log message is stored in the "message" argument. 18617721Speter * 18717721Speter * If REPOSITORY is non-NULL, process rcsinfo for that repository; if it 188102843Speter * is NULL, use the CVSADM_TEMPLATE file instead. REPOSITORY should be 189102843Speter * NULL when running in client mode. 19017721Speter */ 19117721Spetervoid 19217721Speterdo_editor (dir, messagep, repository, changes) 193128269Speter const char *dir; 19417721Speter char **messagep; 195128269Speter const char *repository; 19617721Speter List *changes; 19717721Speter{ 19817721Speter static int reuse_log_message = 0; 19917721Speter char *line; 20017721Speter int line_length; 20117721Speter size_t line_chars_allocated; 20225839Speter char *fname; 20317721Speter struct stat pre_stbuf, post_stbuf; 20417721Speter int retcode = 0; 20517721Speter 206107487Speter assert (!current_parsed_root->isremote != !repository); 207102843Speter 20817721Speter if (noexec || reuse_log_message) 20917721Speter return; 21017721Speter 21117721Speter /* Abort creation of temp file if no editor is defined */ 21217721Speter if (strcmp (Editor, "") == 0 && !editinfo_editor) 21317721Speter error(1, 0, "no editor defined, must use -e or -m"); 21417721Speter 21517721Speter /* Create a temporary file */ 21681407Speter /* FIXME - It's possible we should be relying on cvs_temp_file to open 21781407Speter * the file here - we get race conditions otherwise. 21881407Speter */ 21925839Speter fname = cvs_temp_name (); 22017721Speter again: 22125839Speter if ((fp = CVS_FOPEN (fname, "w+")) == NULL) 22217721Speter error (1, 0, "cannot create temporary file %s", fname); 22317721Speter 22417721Speter if (*messagep) 22517721Speter { 226107487Speter (void) fputs (*messagep, fp); 22717721Speter 22825839Speter if ((*messagep)[0] == '\0' || 22925839Speter (*messagep)[strlen (*messagep) - 1] != '\n') 23017721Speter (void) fprintf (fp, "\n"); 23117721Speter } 23281598Speter else 23381598Speter (void) fprintf (fp, "\n"); 23417721Speter 23517721Speter if (repository != NULL) 23617721Speter /* tack templates on if necessary */ 23717721Speter (void) Parse_Info (CVSROOTADM_RCSINFO, repository, rcsinfo_proc, 1); 23817721Speter else 23917721Speter { 24017721Speter FILE *tfp; 24117721Speter char buf[1024]; 24217721Speter size_t n; 24317721Speter size_t nwrite; 24417721Speter 24517721Speter /* Why "b"? */ 24625839Speter tfp = CVS_FOPEN (CVSADM_TEMPLATE, "rb"); 24717721Speter if (tfp == NULL) 24817721Speter { 24917721Speter if (!existence_error (errno)) 25017721Speter error (1, errno, "cannot read %s", CVSADM_TEMPLATE); 25117721Speter } 25217721Speter else 25317721Speter { 25417721Speter while (!feof (tfp)) 25517721Speter { 25666528Speter char *p = buf; 25717721Speter n = fread (buf, 1, sizeof buf, tfp); 25817721Speter nwrite = n; 25917721Speter while (nwrite > 0) 26017721Speter { 26117721Speter n = fwrite (p, 1, nwrite, fp); 26217721Speter nwrite -= n; 26317721Speter p += n; 26417721Speter } 26517721Speter if (ferror (tfp)) 26617721Speter error (1, errno, "cannot read %s", CVSADM_TEMPLATE); 26717721Speter } 26817721Speter if (fclose (tfp) < 0) 26917721Speter error (0, errno, "cannot close %s", CVSADM_TEMPLATE); 27017721Speter } 27117721Speter } 27217721Speter 27317721Speter (void) fprintf (fp, 27417721Speter "%s----------------------------------------------------------------------\n", 27517721Speter CVSEDITPREFIX); 27617721Speter (void) fprintf (fp, 27725839Speter "%sEnter Log. Lines beginning with `%.*s' are removed automatically\n%s\n", 27825839Speter CVSEDITPREFIX, CVSEDITPREFIXLEN, CVSEDITPREFIX, 27925839Speter CVSEDITPREFIX); 28017721Speter if (dir != NULL && *dir) 28117721Speter (void) fprintf (fp, "%sCommitting in %s\n%s\n", CVSEDITPREFIX, 28217721Speter dir, CVSEDITPREFIX); 28317721Speter if (changes != NULL) 28417721Speter setup_tmpfile (fp, CVSEDITPREFIX, changes); 28517721Speter (void) fprintf (fp, 28617721Speter "%s----------------------------------------------------------------------\n", 28717721Speter CVSEDITPREFIX); 28817721Speter 28917721Speter /* finish off the temp file */ 29017721Speter if (fclose (fp) == EOF) 29117721Speter error (1, errno, "%s", fname); 29225839Speter if ( CVS_STAT (fname, &pre_stbuf) == -1) 29317721Speter pre_stbuf.st_mtime = 0; 29417721Speter 29517721Speter if (editinfo_editor) 29617721Speter free (editinfo_editor); 29717721Speter editinfo_editor = (char *) NULL; 298175274Sobrien if (!current_parsed_root->isremote && repository != NULL) 29917721Speter (void) Parse_Info (CVSROOTADM_EDITINFO, repository, editinfo_proc, 0); 30017721Speter 30117721Speter /* run the editor */ 30232788Speter run_setup (editinfo_editor ? editinfo_editor : Editor); 30317721Speter run_arg (fname); 30417721Speter if ((retcode = run_exec (RUN_TTY, RUN_TTY, RUN_TTY, 30517721Speter RUN_NORMAL | RUN_SIGIGNORE)) != 0) 30617721Speter error (editinfo_editor ? 1 : 0, retcode == -1 ? errno : 0, 30717721Speter editinfo_editor ? "Logfile verification failed" : 30817721Speter "warning: editor session failed"); 30917721Speter 31017721Speter /* put the entire message back into the *messagep variable */ 31117721Speter 31217721Speter fp = open_file (fname, "r"); 31317721Speter 31417721Speter if (*messagep) 31517721Speter free (*messagep); 31617721Speter 31725839Speter if ( CVS_STAT (fname, &post_stbuf) != 0) 31817721Speter error (1, errno, "cannot find size of temp file %s", fname); 31917721Speter 32017721Speter if (post_stbuf.st_size == 0) 32117721Speter *messagep = NULL; 32217721Speter else 32317721Speter { 32417721Speter /* On NT, we might read less than st_size bytes, but we won't 32517721Speter read more. So this works. */ 32617721Speter *messagep = (char *) xmalloc (post_stbuf.st_size + 1); 327102843Speter (*messagep)[0] = '\0'; 32817721Speter } 32917721Speter 33017721Speter line = NULL; 33117721Speter line_chars_allocated = 0; 33217721Speter 33317721Speter if (*messagep) 33417721Speter { 33566528Speter size_t message_len = post_stbuf.st_size + 1; 33666528Speter size_t offset = 0; 33717721Speter while (1) 33817721Speter { 33917721Speter line_length = getline (&line, &line_chars_allocated, fp); 34017721Speter if (line_length == -1) 34117721Speter { 34217721Speter if (ferror (fp)) 34317721Speter error (0, errno, "warning: cannot read %s", fname); 34417721Speter break; 34517721Speter } 34625839Speter if (strncmp (line, CVSEDITPREFIX, CVSEDITPREFIXLEN) == 0) 34717721Speter continue; 34866528Speter if (offset + line_length >= message_len) 34966528Speter expand_string (messagep, &message_len, 35066528Speter offset + line_length + 1); 35166528Speter (void) strcpy (*messagep + offset, line); 35266528Speter offset += line_length; 35317721Speter } 35417721Speter } 35517721Speter if (fclose (fp) < 0) 35617721Speter error (0, errno, "warning: cannot close %s", fname); 35717721Speter 358107487Speter /* canonicalize emply messages */ 359107487Speter if (*messagep != NULL && 360107487Speter (**messagep == '\0' || strcmp (*messagep, "\n") == 0)) 36117721Speter { 362107487Speter free (*messagep); 363107487Speter *messagep = NULL; 364107487Speter } 365107487Speter 366107487Speter if (pre_stbuf.st_mtime == post_stbuf.st_mtime || *messagep == NULL) 367107487Speter { 36817721Speter for (;;) 36917721Speter { 37017721Speter (void) printf ("\nLog message unchanged or not specified\n"); 37117721Speter (void) printf ("a)bort, c)ontinue, e)dit, !)reuse this message unchanged for remaining dirs\n"); 37217721Speter (void) printf ("Action: (continue) "); 37317721Speter (void) fflush (stdout); 37417721Speter line_length = getline (&line, &line_chars_allocated, stdin); 37532788Speter if (line_length < 0) 37632788Speter { 37732788Speter error (0, errno, "cannot read from stdin"); 37832788Speter if (unlink_file (fname) < 0) 37932788Speter error (0, errno, 38032788Speter "warning: cannot remove temp file %s", fname); 38132788Speter error (1, 0, "aborting"); 38232788Speter } 38332788Speter else if (line_length == 0 38432788Speter || *line == '\n' || *line == 'c' || *line == 'C') 38517721Speter break; 38617721Speter if (*line == 'a' || *line == 'A') 38725839Speter { 38825839Speter if (unlink_file (fname) < 0) 38925839Speter error (0, errno, "warning: cannot remove temp file %s", fname); 39025839Speter error (1, 0, "aborted by user"); 39125839Speter } 39217721Speter if (*line == 'e' || *line == 'E') 39317721Speter goto again; 39417721Speter if (*line == '!') 39517721Speter { 39617721Speter reuse_log_message = 1; 39717721Speter break; 39817721Speter } 39917721Speter (void) printf ("Unknown input\n"); 40017721Speter } 40117721Speter } 40217721Speter if (line) 40317721Speter free (line); 40417721Speter if (unlink_file (fname) < 0) 40517721Speter error (0, errno, "warning: cannot remove temp file %s", fname); 40625839Speter free (fname); 40717721Speter} 40817721Speter 40925839Speter/* Runs the user-defined verification script as part of the commit or import 41025839Speter process. This verification is meant to be run whether or not the user 41125839Speter included the -m atribute. unlike the do_editor function, this is 41225839Speter independant of the running of an editor for getting a message. 41325839Speter */ 41425839Spetervoid 41526151Speterdo_verify (messagep, repository) 41626151Speter char **messagep; 417128269Speter const char *repository; 41825839Speter{ 41925839Speter FILE *fp; 42025839Speter char *fname; 42125839Speter int retcode = 0; 42225839Speter 423102843Speter struct stat pre_stbuf, post_stbuf; 42426151Speter 42581407Speter if (current_parsed_root->isremote) 42625839Speter /* The verification will happen on the server. */ 42725839Speter return; 42825839Speter 42925839Speter /* FIXME? Do we really want to skip this on noexec? What do we do 43025839Speter for the other administrative files? */ 431128269Speter if (noexec || repository == NULL) 43225839Speter return; 43325839Speter 434107487Speter /* Get the name of the verification script to run */ 435107487Speter 436128269Speter if (Parse_Info (CVSROOTADM_VERIFYMSG, repository, verifymsg_proc, 0) > 0) 437128269Speter error (1, 0, "Message verification failed"); 438128269Speter 439107487Speter if (!verifymsg_script) 44025839Speter return; 44125839Speter 44281407Speter /* open a temporary file, write the message to the 44325839Speter temp file, and close the file. */ 44425839Speter 44581407Speter if ((fp = cvs_temp_file (&fname)) == NULL) 446175274Sobrien error (1, errno, "cannot create temporary file %s", 447175274Sobrien fname ? fname : "(null)"); 448102843Speter 449107487Speter if (*messagep != NULL) 450107487Speter fputs (*messagep, fp); 451107487Speter if (*messagep == NULL || 452107487Speter (*messagep)[0] == '\0' || 453102843Speter (*messagep)[strlen (*messagep) - 1] != '\n') 454107487Speter putc ('\n', fp); 455102843Speter if (fclose (fp) == EOF) 456102843Speter error (1, errno, "%s", fname); 457102843Speter 458102843Speter if (RereadLogAfterVerify == LOGMSG_REREAD_STAT) 45925839Speter { 460102843Speter /* Remember the status of the temp file for later */ 461102843Speter if ( CVS_STAT (fname, &pre_stbuf) != 0 ) 462102843Speter error (1, errno, "cannot stat temp file %s", fname); 46325839Speter 464102843Speter /* 465102843Speter * See if we need to sleep before running the verification 466102843Speter * script to avoid time-stamp races. 467102843Speter */ 468102843Speter sleep_past (pre_stbuf.st_mtime); 469102843Speter } 47025839Speter 471107487Speter run_setup (verifymsg_script); 472107487Speter run_arg (fname); 473107487Speter if ((retcode = run_exec (RUN_TTY, RUN_TTY, RUN_TTY, 474107487Speter RUN_NORMAL | RUN_SIGIGNORE)) != 0) 475102843Speter { 476107487Speter /* Since following error() exits, delete the temp file now. */ 477107487Speter if (unlink_file (fname) < 0) 478107487Speter error (0, errno, "cannot remove %s", fname); 47932788Speter 480107487Speter error (1, retcode == -1 ? errno : 0, 481107487Speter "Message verification failed"); 482102843Speter } 48325839Speter 484102843Speter /* Get the mod time and size of the possibly new log message 485102843Speter * in always and stat modes. 486102843Speter */ 487102843Speter if (RereadLogAfterVerify == LOGMSG_REREAD_ALWAYS || 488102843Speter RereadLogAfterVerify == LOGMSG_REREAD_STAT) 489102843Speter { 490102843Speter if ( CVS_STAT (fname, &post_stbuf) != 0 ) 491102843Speter error (1, errno, "cannot find size of temp file %s", fname); 492102843Speter } 493102843Speter 494102843Speter /* And reread the log message in `always' mode or in `stat' mode when it's 495102843Speter * changed 496102843Speter */ 497102843Speter if (RereadLogAfterVerify == LOGMSG_REREAD_ALWAYS || 498102843Speter (RereadLogAfterVerify == LOGMSG_REREAD_STAT && 499102843Speter (pre_stbuf.st_mtime != post_stbuf.st_mtime || 500102843Speter pre_stbuf.st_size != post_stbuf.st_size))) 501102843Speter { 50226151Speter /* put the entire message back into the *messagep variable */ 50326151Speter 504102843Speter if (*messagep) free (*messagep); 50526151Speter 506102843Speter if (post_stbuf.st_size == 0) 50726151Speter *messagep = NULL; 50826151Speter else 50926151Speter { 510107487Speter char *line = NULL; 511107487Speter int line_length; 512107487Speter size_t line_chars_allocated = 0; 513107487Speter char *p; 514107487Speter 515107487Speter if ( (fp = open_file (fname, "r")) == NULL ) 516107487Speter error (1, errno, "cannot open temporary file %s", fname); 517107487Speter 518102843Speter /* On NT, we might read less than st_size bytes, 519102843Speter but we won't read more. So this works. */ 520107487Speter p = *messagep = (char *) xmalloc (post_stbuf.st_size + 1); 52126151Speter *messagep[0] = '\0'; 52226151Speter 52326151Speter while (1) 52426151Speter { 525102843Speter line_length = getline (&line, 526102843Speter &line_chars_allocated, 527102843Speter fp); 52826151Speter if (line_length == -1) 52926151Speter { 53026151Speter if (ferror (fp)) 531102843Speter /* Fail in this case because otherwise we will have no 532102843Speter * log message 533102843Speter */ 534102843Speter error (1, errno, "cannot read %s", fname); 53526151Speter break; 53626151Speter } 53726151Speter if (strncmp (line, CVSEDITPREFIX, CVSEDITPREFIXLEN) == 0) 53826151Speter continue; 53926151Speter (void) strcpy (p, line); 54026151Speter p += line_length; 54126151Speter } 542102843Speter if (line) free (line); 543107487Speter if (fclose (fp) < 0) 544107487Speter error (0, errno, "warning: cannot close %s", fname); 54526151Speter } 546102843Speter } 54726151Speter 548102843Speter /* Delete the temp file */ 54925839Speter 550102843Speter if (unlink_file (fname) < 0) 551102843Speter error (0, errno, "cannot remove %s", fname); 552102843Speter free (fname); 553175274Sobrien free (verifymsg_script); 554128269Speter verifymsg_script = NULL; 55525839Speter} 55625839Speter 55717721Speter/* 55817721Speter * callback proc for Parse_Info for rcsinfo templates this routine basically 55917721Speter * copies the matching template onto the end of the tempfile we are setting 56017721Speter * up 56117721Speter */ 56217721Speter/* ARGSUSED */ 56317721Speterstatic int 56417721Speterrcsinfo_proc (repository, template) 565128269Speter const char *repository; 566128269Speter const char *template; 56717721Speter{ 56817721Speter static char *last_template; 56917721Speter FILE *tfp; 57017721Speter 57117721Speter /* nothing to do if the last one included is the same as this one */ 57217721Speter if (last_template && strcmp (last_template, template) == 0) 57317721Speter return (0); 57417721Speter if (last_template) 57517721Speter free (last_template); 57617721Speter last_template = xstrdup (template); 57717721Speter 57825839Speter if ((tfp = CVS_FOPEN (template, "r")) != NULL) 57917721Speter { 58017721Speter char *line = NULL; 58117721Speter size_t line_chars_allocated = 0; 58217721Speter 58317721Speter while (getline (&line, &line_chars_allocated, tfp) >= 0) 58417721Speter (void) fputs (line, fp); 58517721Speter if (ferror (tfp)) 58617721Speter error (0, errno, "warning: cannot read %s", template); 58717721Speter if (fclose (tfp) < 0) 58817721Speter error (0, errno, "warning: cannot close %s", template); 58917721Speter if (line) 59017721Speter free (line); 59117721Speter return (0); 59217721Speter } 59317721Speter else 59417721Speter { 59517721Speter error (0, errno, "Couldn't open rcsinfo template file %s", template); 59617721Speter return (1); 59717721Speter } 59817721Speter} 59917721Speter 60017721Speter/* 60117721Speter * Uses setup_tmpfile() to pass the updated message on directly to any 60217721Speter * logfile programs that have a regular expression match for the checked in 60317721Speter * directory in the source repository. The log information is fed into the 60417721Speter * specified program as standard input. 60517721Speter */ 60617721Speterstatic FILE *logfp; 607128269Speterstatic const char *message; 60817721Speterstatic List *changes; 60917721Speter 61017721Spetervoid 61125839SpeterUpdate_Logfile (repository, xmessage, xlogfp, xchanges) 612128269Speter const char *repository; 613128269Speter const char *xmessage; 61417721Speter FILE *xlogfp; 61517721Speter List *xchanges; 61617721Speter{ 61717721Speter /* nothing to do if the list is empty */ 61817721Speter if (xchanges == NULL || xchanges->list->next == xchanges->list) 61917721Speter return; 62017721Speter 62117721Speter /* set up static vars for update_logfile_proc */ 62217721Speter message = xmessage; 62317721Speter logfp = xlogfp; 62417721Speter changes = xchanges; 62517721Speter 62617721Speter /* call Parse_Info to do the actual logfile updates */ 62717721Speter (void) Parse_Info (CVSROOTADM_LOGINFO, repository, update_logfile_proc, 1); 62817721Speter} 62917721Speter 630128269Speter 631128269Speter 63217721Speter/* 63317721Speter * callback proc to actually do the logfile write from Update_Logfile 63417721Speter */ 63517721Speterstatic int 63617721Speterupdate_logfile_proc (repository, filter) 637128269Speter const char *repository; 638128269Speter const char *filter; 63917721Speter{ 640128269Speter return logfile_write (repository, filter, message, logfp, changes); 64117721Speter} 64217721Speter 643128269Speter 644128269Speter 64517721Speter/* 64625839Speter * concatenate each filename/version onto str_list 64717721Speter */ 64817721Speterstatic int 64917721Spetertitle_proc (p, closure) 65017721Speter Node *p; 65117721Speter void *closure; 65217721Speter{ 65325839Speter char *c; 654128269Speter struct logfile_info *li = p->data; 65525839Speter 65625839Speter if (li->type == type) 65717721Speter { 65825839Speter /* Until we decide on the correct logging solution when we add 65925839Speter directories or perform imports, T_TITLE nodes will only 66025839Speter tack on the name provided, regardless of the format string. 66125839Speter You can verify that this assumption is safe by checking the 66225839Speter code in add.c (add_directory) and import.c (import). */ 66325839Speter 66425839Speter str_list = xrealloc (str_list, strlen (str_list) + 5); 66517721Speter (void) strcat (str_list, " "); 66625839Speter 66725839Speter if (li->type == T_TITLE) 66825839Speter { 66925839Speter str_list = xrealloc (str_list, 67025839Speter strlen (str_list) + strlen (p->key) + 5); 67125839Speter (void) strcat (str_list, p->key); 67225839Speter } 67325839Speter else 67425839Speter { 67525839Speter /* All other nodes use the format string. */ 67625839Speter 67725839Speter for (c = str_list_format; *c != '\0'; c++) 67825839Speter { 67925839Speter switch (*c) 68025839Speter { 68125839Speter case 's': 68225839Speter str_list = 68325839Speter xrealloc (str_list, 68425839Speter strlen (str_list) + strlen (p->key) + 5); 68525839Speter (void) strcat (str_list, p->key); 68625839Speter break; 68725839Speter case 'V': 68825839Speter str_list = 68925839Speter xrealloc (str_list, 69025839Speter (strlen (str_list) 69125839Speter + (li->rev_old ? strlen (li->rev_old) : 0) 69225839Speter + 10) 69325839Speter ); 69425839Speter (void) strcat (str_list, (li->rev_old 69525839Speter ? li->rev_old : "NONE")); 69625839Speter break; 69725839Speter case 'v': 69825839Speter str_list = 69925839Speter xrealloc (str_list, 70025839Speter (strlen (str_list) 70125839Speter + (li->rev_new ? strlen (li->rev_new) : 0) 70225839Speter + 10) 70325839Speter ); 70425839Speter (void) strcat (str_list, (li->rev_new 70525839Speter ? li->rev_new : "NONE")); 70625839Speter break; 70725839Speter /* All other characters, we insert an empty field (but 70825839Speter we do put in the comma separating it from other 70925839Speter fields). This way if future CVS versions add formatting 71025839Speter characters, one can write a loginfo file which at least 71125839Speter won't blow up on an old CVS. */ 712107487Speter /* Note that people who have to deal with spaces in file 713107487Speter and directory names are using space to get a known 714107487Speter delimiter for the directory name, so it's probably 715107487Speter not a good idea to ever define that as a formatting 716107487Speter character. */ 71725839Speter } 71825839Speter if (*(c + 1) != '\0') 71925839Speter { 72025839Speter str_list = xrealloc (str_list, strlen (str_list) + 5); 72125839Speter (void) strcat (str_list, ","); 72225839Speter } 72325839Speter } 72425839Speter } 72517721Speter } 72617721Speter return (0); 72717721Speter} 72817721Speter 72917721Speter/* 73017721Speter * Writes some stuff to the logfile "filter" and returns the status of the 73117721Speter * filter program. 73217721Speter */ 73317721Speterstatic int 73425839Speterlogfile_write (repository, filter, message, logfp, changes) 735128269Speter const char *repository; 736128269Speter const char *filter; 737128269Speter const char *message; 73817721Speter FILE *logfp; 73917721Speter List *changes; 74017721Speter{ 74117721Speter FILE *pipefp; 74225839Speter char *prog; 74317721Speter char *cp; 74417721Speter int c; 74517721Speter int pipestatus; 74625839Speter char *fmt_percent; /* the location of the percent sign 74725839Speter that starts the format string. */ 74817721Speter 749175274Sobrien assert (repository); 750175274Sobrien 75125839Speter /* The user may specify a format string as part of the filter. 75225839Speter Originally, `%s' was the only valid string. The string that 75325839Speter was substituted for it was: 75425839Speter 75525839Speter <repository-name> <file1> <file2> <file3> ... 75625839Speter 75725839Speter Each file was either a new directory/import (T_TITLE), or a 75825839Speter added (T_ADDED), modified (T_MODIFIED), or removed (T_REMOVED) 75925839Speter file. 76025839Speter 76125839Speter It is desirable to preserve that behavior so lots of commitlog 76225839Speter scripts won't die when they get this new code. At the same 76325839Speter time, we'd like to pass other information about the files (like 76425839Speter version numbers, statuses, or checkin times). 76525839Speter 76625839Speter The solution is to allow a format string that allows us to 76725839Speter specify those other pieces of information. The format string 76825839Speter will be composed of `%' followed by a single format character, 76925839Speter or followed by a set of format characters surrounded by `{' and 77025839Speter `}' as separators. The format characters are: 77125839Speter 77225839Speter s = file name 77325839Speter V = old version number (pre-checkin) 77425839Speter v = new version number (post-checkin) 77525839Speter 77625839Speter For example, valid format strings are: 77725839Speter 77825839Speter %{} 77925839Speter %s 78025839Speter %{s} 78125839Speter %{sVv} 78225839Speter 78325839Speter There's no reason that more items couldn't be added (like 78425839Speter modification date or file status [added, modified, updated, 78525839Speter etc.]) -- the code modifications would be minimal (logmsg.c 78625839Speter (title_proc) and commit.c (check_fileproc)). 78725839Speter 78825839Speter The output will be a string of tokens separated by spaces. For 78925839Speter backwards compatibility, the the first token will be the 79025839Speter repository name. The rest of the tokens will be 79125839Speter comma-delimited lists of the information requested in the 79225839Speter format string. For example, if `/u/src/master' is the 79325839Speter repository, `%{sVv}' is the format string, and three files 79425839Speter (ChangeLog, Makefile, foo.c) were modified, the output might 79525839Speter be: 79625839Speter 79725839Speter /u/src/master ChangeLog,1.1,1.2 Makefile,1.3,1.4 foo.c,1.12,1.13 79825839Speter 79925839Speter Why this duplicates the old behavior when the format string is 80025839Speter `%s' is left as an exercise for the reader. */ 80125839Speter 80225839Speter fmt_percent = strchr (filter, '%'); 80325839Speter if (fmt_percent) 80425839Speter { 80525839Speter int len; 806128269Speter const char *srepos; 80725839Speter char *fmt_begin, *fmt_end; /* beginning and end of the 80825839Speter format string specified in 80925839Speter filter. */ 81025839Speter char *fmt_continue; /* where the string continues 81125839Speter after the format string (we 81225839Speter might skip a '}') somewhere 81325839Speter in there... */ 81425839Speter 81525839Speter /* Grab the format string. */ 81625839Speter 81725839Speter if ((*(fmt_percent + 1) == ' ') || (*(fmt_percent + 1) == '\0')) 81825839Speter { 81925839Speter /* The percent stands alone. This is an error. We could 82025839Speter be treating ' ' like any other formatting character, but 82125839Speter using it as a formatting character seems like it would be 82225839Speter a mistake. */ 82325839Speter 82425839Speter /* Would be nice to also be giving the line number. */ 82525839Speter error (0, 0, "loginfo: '%%' not followed by formatting character"); 82625839Speter fmt_begin = fmt_percent + 1; 82725839Speter fmt_end = fmt_begin; 82825839Speter fmt_continue = fmt_begin; 82925839Speter } 83025839Speter else if (*(fmt_percent + 1) == '{') 83125839Speter { 83225839Speter /* The percent has a set of characters following it. */ 83325839Speter 83425839Speter fmt_begin = fmt_percent + 2; 83525839Speter fmt_end = strchr (fmt_begin, '}'); 83625839Speter if (fmt_end) 83725839Speter { 83825839Speter /* Skip over the '}' character. */ 83925839Speter 84025839Speter fmt_continue = fmt_end + 1; 84125839Speter } 84225839Speter else 84325839Speter { 84425839Speter /* There was no close brace -- assume that format 84525839Speter string continues to the end of the line. */ 84625839Speter 84725839Speter /* Would be nice to also be giving the line number. */ 84825839Speter error (0, 0, "loginfo: '}' missing"); 84925839Speter fmt_end = fmt_begin + strlen (fmt_begin); 85025839Speter fmt_continue = fmt_end; 85125839Speter } 85225839Speter } 85325839Speter else 85425839Speter { 85525839Speter /* The percent has a single character following it. FIXME: 85625839Speter %% should expand to a regular percent sign. */ 85725839Speter 85825839Speter fmt_begin = fmt_percent + 1; 85925839Speter fmt_end = fmt_begin + 1; 86025839Speter fmt_continue = fmt_end; 86125839Speter } 86225839Speter 86325839Speter len = fmt_end - fmt_begin; 86466528Speter str_list_format = xmalloc (len + 1); 86525839Speter strncpy (str_list_format, fmt_begin, len); 86625839Speter str_list_format[len] = '\0'; 86725839Speter 86825839Speter /* Allocate an initial chunk of memory. As we build up the string 86925839Speter we will realloc it. */ 87025839Speter if (!str_list) 87125839Speter str_list = xmalloc (1); 87225839Speter str_list[0] = '\0'; 87325839Speter 87425839Speter /* Add entries to the string. Don't bother looking for 87525839Speter entries if the format string is empty. */ 87625839Speter 87725839Speter if (str_list_format[0] != '\0') 87825839Speter { 87925839Speter type = T_TITLE; 88025839Speter (void) walklist (changes, title_proc, NULL); 88125839Speter type = T_ADDED; 88225839Speter (void) walklist (changes, title_proc, NULL); 88325839Speter type = T_MODIFIED; 88425839Speter (void) walklist (changes, title_proc, NULL); 88525839Speter type = T_REMOVED; 88625839Speter (void) walklist (changes, title_proc, NULL); 88725839Speter } 88825839Speter 88926065Speter free (str_list_format); 89026065Speter 89125839Speter /* Construct the final string. */ 89225839Speter 89325839Speter srepos = Short_Repository (repository); 89425839Speter 89581407Speter prog = cp = xmalloc ((fmt_percent - filter) + 2 * strlen (srepos) 89681407Speter + 2 * strlen (str_list) + strlen (fmt_continue) 89725839Speter + 10); 89881407Speter (void) memcpy (cp, filter, fmt_percent - filter); 89981407Speter cp += fmt_percent - filter; 90081407Speter *cp++ = '"'; 90181407Speter cp = shell_escape (cp, srepos); 90281407Speter cp = shell_escape (cp, str_list); 90381407Speter *cp++ = '"'; 90481407Speter (void) strcpy (cp, fmt_continue); 90525839Speter 90625839Speter /* To be nice, free up some memory. */ 90725839Speter 90825839Speter free (str_list); 90925839Speter str_list = (char *) NULL; 91025839Speter } 91125839Speter else 91225839Speter { 91325839Speter /* There's no format string. */ 91425839Speter prog = xstrdup (filter); 91525839Speter } 91625839Speter 91717721Speter if ((pipefp = run_popen (prog, "w")) == NULL) 91817721Speter { 91917721Speter if (!noexec) 92017721Speter error (0, 0, "cannot write entry to log filter: %s", prog); 92117721Speter free (prog); 92217721Speter return (1); 92317721Speter } 92417721Speter (void) fprintf (pipefp, "Update of %s\n", repository); 92525839Speter (void) fprintf (pipefp, "In directory %s:", hostname); 92625839Speter cp = xgetwd (); 92725839Speter if (cp == NULL) 92825839Speter fprintf (pipefp, "<cannot get working directory: %s>\n\n", 92925839Speter strerror (errno)); 93025839Speter else 93125839Speter { 93225839Speter fprintf (pipefp, "%s\n\n", cp); 93325839Speter free (cp); 93425839Speter } 93525839Speter 93617721Speter setup_tmpfile (pipefp, "", changes); 937128269Speter (void) fprintf (pipefp, "Log Message:\n%s\n", (message) ? message : ""); 93817721Speter if (logfp != (FILE *) 0) 93917721Speter { 94017721Speter (void) fprintf (pipefp, "Status:\n"); 94117721Speter rewind (logfp); 94217721Speter while ((c = getc (logfp)) != EOF) 94317721Speter (void) putc ((char) c, pipefp); 94417721Speter } 94517721Speter free (prog); 94617721Speter pipestatus = pclose (pipefp); 94717721Speter return ((pipestatus == -1) || (pipestatus == 127)) ? 1 : 0; 94817721Speter} 94917721Speter 95017721Speter/* 95117721Speter * We choose to use the *last* match within the editinfo file for this 95217721Speter * repository. This allows us to have a global editinfo program for the 95317721Speter * root of some hierarchy, for example, and different ones within different 95417721Speter * sub-directories of the root (like a special checker for changes made to 95517721Speter * the "src" directory versus changes made to the "doc" or "test" 95617721Speter * directories. 95717721Speter */ 95817721Speter/* ARGSUSED */ 95917721Speterstatic int 96017721Spetereditinfo_proc(repository, editor) 961128269Speter const char *repository; 962128269Speter const char *editor; 96317721Speter{ 96417721Speter /* nothing to do if the last match is the same as this one */ 96517721Speter if (editinfo_editor && strcmp (editinfo_editor, editor) == 0) 96617721Speter return (0); 96717721Speter if (editinfo_editor) 96817721Speter free (editinfo_editor); 96917721Speter 97017721Speter editinfo_editor = xstrdup (editor); 97117721Speter return (0); 97217721Speter} 97325839Speter 97425839Speter/* This routine is calld by Parse_Info. it asigns the name of the 97525839Speter * message verification script to the global variable verify_script 97625839Speter */ 97725839Speterstatic int 97825839Speterverifymsg_proc (repository, script) 979128269Speter const char *repository; 980128269Speter const char *script; 98125839Speter{ 98225839Speter if (verifymsg_script && strcmp (verifymsg_script, script) == 0) 98325839Speter return (0); 98425839Speter if (verifymsg_script) 98525839Speter free (verifymsg_script); 98625839Speter verifymsg_script = xstrdup (script); 98725839Speter return (0); 98825839Speter} 989