home *** CD-ROM | disk | FTP | other *** search
- /*
- * Copyright (c) 1992, Brian Berliner and Jeff Polk
- * Copyright (c) 1989-1992, Brian Berliner
- *
- * You may distribute under the terms of the GNU General Public License as
- * specified in the README file that comes with the CVS 1.4 kit.
- */
-
- #include "cvs.h"
- #include "getline.h"
-
- static int find_type PROTO((Node * p, void *closure));
- static int fmt_proc PROTO((Node * p, void *closure));
- static int logfile_write PROTO((char *repository, char *filter, char *title,
- char *message, char *revision, FILE * logfp,
- List * changes));
- static int rcsinfo_proc PROTO((char *repository, char *template));
- static int title_proc PROTO((Node * p, void *closure));
- static int update_logfile_proc PROTO((char *repository, char *filter));
- static void setup_tmpfile PROTO((FILE * xfp, char *xprefix, List * changes));
- static int editinfo_proc PROTO((char *repository, char *template));
-
- static FILE *fp;
- static char *str_list;
- static char *editinfo_editor;
- static Ctype type;
-
- /*
- * Puts a standard header on the output which is either being prepared for an
- * editor session, or being sent to a logfile program. The modified, added,
- * and removed files are included (if any) and formatted to look pretty. */
- static char *prefix;
- static int col;
- static void
- setup_tmpfile (xfp, xprefix, changes)
- FILE *xfp;
- char *xprefix;
- List *changes;
- {
- /* set up statics */
- fp = xfp;
- prefix = xprefix;
-
- type = T_MODIFIED;
- if (walklist (changes, find_type, NULL) != 0)
- {
- (void) fprintf (fp, "%sModified Files:\n", prefix);
- (void) fprintf (fp, "%s\t", prefix);
- col = 8;
- (void) walklist (changes, fmt_proc, NULL);
- (void) fprintf (fp, "\n");
- }
- type = T_ADDED;
- if (walklist (changes, find_type, NULL) != 0)
- {
- (void) fprintf (fp, "%sAdded Files:\n", prefix);
- (void) fprintf (fp, "%s\t", prefix);
- col = 8;
- (void) walklist (changes, fmt_proc, NULL);
- (void) fprintf (fp, "\n");
- }
- type = T_REMOVED;
- if (walklist (changes, find_type, NULL) != 0)
- {
- (void) fprintf (fp, "%sRemoved Files:\n", prefix);
- (void) fprintf (fp, "%s\t", prefix);
- col = 8;
- (void) walklist (changes, fmt_proc, NULL);
- (void) fprintf (fp, "\n");
- }
- }
-
- /*
- * Looks for nodes of a specified type and returns 1 if found
- */
- static int
- find_type (p, closure)
- Node *p;
- void *closure;
- {
- if (p->data == (char *) type)
- return (1);
- else
- return (0);
- }
-
- /*
- * Breaks the files list into reasonable sized lines to avoid line wrap...
- * all in the name of pretty output. It only works on nodes whose types
- * match the one we're looking for
- */
- static int
- fmt_proc (p, closure)
- Node *p;
- void *closure;
- {
- if (p->data == (char *) type)
- {
- if ((col + (int) strlen (p->key)) > 70)
- {
- (void) fprintf (fp, "\n%s\t", prefix);
- col = 8;
- }
- (void) fprintf (fp, "%s ", p->key);
- col += strlen (p->key) + 1;
- }
- return (0);
- }
-
- /*
- * Builds a temporary file using setup_tmpfile() and invokes the user's
- * editor on the file. The header garbage in the resultant file is then
- * stripped and the log message is stored in the "message" argument.
- *
- * If REPOSITORY is non-NULL, process rcsinfo for that repository; if it
- * is NULL, use the CVSADM_TEMPLATE file instead.
- */
- void
- do_editor (dir, messagep, repository, changes)
- char *dir;
- char **messagep;
- char *repository;
- List *changes;
- {
- static int reuse_log_message = 0;
- char *line;
- int line_length;
- size_t line_chars_allocated;
- char fname[L_tmpnam+1];
- struct stat pre_stbuf, post_stbuf;
- int retcode = 0;
- char *p;
-
- if (noexec || reuse_log_message)
- return;
-
- /* Abort creation of temp file if no editor is defined */
- if (strcmp (Editor, "") == 0 && !editinfo_editor)
- error(1, 0, "no editor defined, must use -e or -m");
-
- /* Create a temporary file */
- (void) tmpnam (fname);
- again:
- if ((fp = fopen (fname, "w+")) == NULL)
- error (1, 0, "cannot create temporary file %s", fname);
-
- if (*messagep)
- {
- (void) fprintf (fp, "%s", *messagep);
-
- if ((*messagep)[strlen (*messagep) - 1] != '\n')
- (void) fprintf (fp, "\n");
- }
- else
- (void) fprintf (fp, "\n");
-
- if (repository != NULL)
- /* tack templates on if necessary */
- (void) Parse_Info (CVSROOTADM_RCSINFO, repository, rcsinfo_proc, 1);
- else
- {
- FILE *tfp;
- char buf[1024];
- char *p;
- size_t n;
- size_t nwrite;
-
- /* Why "b"? */
- tfp = fopen (CVSADM_TEMPLATE, "rb");
- if (tfp == NULL)
- {
- if (!existence_error (errno))
- error (1, errno, "cannot read %s", CVSADM_TEMPLATE);
- }
- else
- {
- while (!feof (tfp))
- {
- n = fread (buf, 1, sizeof buf, tfp);
- nwrite = n;
- p = buf;
- while (nwrite > 0)
- {
- n = fwrite (p, 1, nwrite, fp);
- nwrite -= n;
- p += n;
- }
- if (ferror (tfp))
- error (1, errno, "cannot read %s", CVSADM_TEMPLATE);
- }
- if (fclose (tfp) < 0)
- error (0, errno, "cannot close %s", CVSADM_TEMPLATE);
- }
- }
-
- (void) fprintf (fp,
- "%s----------------------------------------------------------------------\n",
- CVSEDITPREFIX);
- (void) fprintf (fp,
- "%sEnter Log. Lines beginning with `%s' are removed automatically\n%s\n",
- CVSEDITPREFIX, CVSEDITPREFIX, CVSEDITPREFIX);
- if (dir != NULL && *dir)
- (void) fprintf (fp, "%sCommitting in %s\n%s\n", CVSEDITPREFIX,
- dir, CVSEDITPREFIX);
- if (changes != NULL)
- setup_tmpfile (fp, CVSEDITPREFIX, changes);
- (void) fprintf (fp,
- "%s----------------------------------------------------------------------\n",
- CVSEDITPREFIX);
-
- /* finish off the temp file */
- if (fclose (fp) == EOF)
- error (1, errno, "%s", fname);
- if (stat (fname, &pre_stbuf) == -1)
- pre_stbuf.st_mtime = 0;
-
- if (editinfo_editor)
- free (editinfo_editor);
- editinfo_editor = (char *) NULL;
- if (repository != NULL)
- (void) Parse_Info (CVSROOTADM_EDITINFO, repository, editinfo_proc, 0);
-
- /* run the editor */
- run_setup ("%s", editinfo_editor ? editinfo_editor : Editor);
- run_arg (fname);
- if ((retcode = run_exec (RUN_TTY, RUN_TTY, RUN_TTY,
- RUN_NORMAL | RUN_SIGIGNORE)) != 0)
- error (editinfo_editor ? 1 : 0, retcode == -1 ? errno : 0,
- editinfo_editor ? "Logfile verification failed" :
- "warning: editor session failed");
-
- /* put the entire message back into the *messagep variable */
-
- fp = open_file (fname, "r");
-
- if (*messagep)
- free (*messagep);
-
- if (stat (fname, &post_stbuf) != 0)
- error (1, errno, "cannot find size of temp file %s", fname);
-
- if (post_stbuf.st_size == 0)
- *messagep = NULL;
- else
- {
- /* On NT, we might read less than st_size bytes, but we won't
- read more. So this works. */
- *messagep = (char *) xmalloc (post_stbuf.st_size + 1);
- *messagep[0] = '\0';
- }
-
- line = NULL;
- line_chars_allocated = 0;
-
- if (*messagep)
- {
- p = *messagep;
- while (1)
- {
- line_length = getline (&line, &line_chars_allocated, fp);
- if (line_length == -1)
- {
- if (ferror (fp))
- error (0, errno, "warning: cannot read %s", fname);
- break;
- }
- if (strncmp (line, CVSEDITPREFIX, sizeof (CVSEDITPREFIX) - 1) == 0)
- continue;
- (void) strcpy (p, line);
- p += line_length;
- }
- }
- if (fclose (fp) < 0)
- error (0, errno, "warning: cannot close %s", fname);
-
- if (pre_stbuf.st_mtime == post_stbuf.st_mtime ||
- *messagep == NULL ||
- strcmp (*messagep, "\n") == 0)
- {
- for (;;)
- {
- (void) printf ("\nLog message unchanged or not specified\n");
- (void) printf ("a)bort, c)ontinue, e)dit, !)reuse this message unchanged for remaining dirs\n");
- (void) printf ("Action: (continue) ");
- (void) fflush (stdout);
- line_length = getline (&line, &line_chars_allocated, stdin);
- if (line_length <= 0
- || *line == '\n' || *line == 'c' || *line == 'C')
- break;
- if (*line == 'a' || *line == 'A')
- error (1, 0, "aborted by user");
- if (*line == 'e' || *line == 'E')
- goto again;
- if (*line == '!')
- {
- reuse_log_message = 1;
- break;
- }
- (void) printf ("Unknown input\n");
- }
- }
- if (line)
- free (line);
- if (unlink_file (fname) < 0)
- error (0, errno, "warning: cannot remove temp file %s", fname);
- }
-
- /*
- * callback proc for Parse_Info for rcsinfo templates this routine basically
- * copies the matching template onto the end of the tempfile we are setting
- * up
- */
- /* ARGSUSED */
- static int
- rcsinfo_proc (repository, template)
- char *repository;
- char *template;
- {
- static char *last_template;
- FILE *tfp;
-
- /* nothing to do if the last one included is the same as this one */
- if (last_template && strcmp (last_template, template) == 0)
- return (0);
- if (last_template)
- free (last_template);
- last_template = xstrdup (template);
-
- if ((tfp = fopen (template, "r")) != NULL)
- {
- char *line = NULL;
- size_t line_chars_allocated = 0;
-
- while (getline (&line, &line_chars_allocated, tfp) >= 0)
- (void) fputs (line, fp);
- if (ferror (tfp))
- error (0, errno, "warning: cannot read %s", template);
- if (fclose (tfp) < 0)
- error (0, errno, "warning: cannot close %s", template);
- if (line)
- free (line);
- return (0);
- }
- else
- {
- error (0, errno, "Couldn't open rcsinfo template file %s", template);
- return (1);
- }
- }
-
- /*
- * Uses setup_tmpfile() to pass the updated message on directly to any
- * logfile programs that have a regular expression match for the checked in
- * directory in the source repository. The log information is fed into the
- * specified program as standard input.
- */
- static char *title;
- static FILE *logfp;
- static char *message;
- static char *revision;
- static List *changes;
-
- void
- Update_Logfile (repository, xmessage, xrevision, xlogfp, xchanges)
- char *repository;
- char *xmessage;
- char *xrevision;
- FILE *xlogfp;
- List *xchanges;
- {
- char *srepos;
-
- /* nothing to do if the list is empty */
- if (xchanges == NULL || xchanges->list->next == xchanges->list)
- return;
-
- /* set up static vars for update_logfile_proc */
- message = xmessage;
- revision = xrevision;
- logfp = xlogfp;
- changes = xchanges;
-
- /* figure out a good title string */
- srepos = Short_Repository (repository);
-
- /* allocate a chunk of memory to hold the title string */
- if (!str_list)
- str_list = xmalloc (MAXLISTLEN);
- str_list[0] = '\0';
-
- type = T_TITLE;
- (void) walklist (changes, title_proc, NULL);
- type = T_ADDED;
- (void) walklist (changes, title_proc, NULL);
- type = T_MODIFIED;
- (void) walklist (changes, title_proc, NULL);
- type = T_REMOVED;
- (void) walklist (changes, title_proc, NULL);
- title = xmalloc (strlen (srepos) + strlen (str_list) + 1 + 2); /* for 's */
- (void) sprintf (title, "'%s%s'", srepos, str_list);
-
- /* to be nice, free up this chunk of memory */
- free (str_list);
- str_list = (char *) NULL;
-
- /* call Parse_Info to do the actual logfile updates */
- (void) Parse_Info (CVSROOTADM_LOGINFO, repository, update_logfile_proc, 1);
-
- /* clean up */
- free (title);
- }
-
- /*
- * callback proc to actually do the logfile write from Update_Logfile
- */
- static int
- update_logfile_proc (repository, filter)
- char *repository;
- char *filter;
- {
- return (logfile_write (repository, filter, title, message, revision,
- logfp, changes));
- }
-
- /*
- * concatenate each name onto str_list
- */
- static int
- title_proc (p, closure)
- Node *p;
- void *closure;
- {
- if (p->data == (char *) type)
- {
- (void) strcat (str_list, " ");
- (void) strcat (str_list, p->key);
- }
- return (0);
- }
-
- /*
- * Since some systems don't define this...
- */
- #ifndef MAXHOSTNAMELEN
- #define MAXHOSTNAMELEN 256
- #endif
-
- /*
- * Writes some stuff to the logfile "filter" and returns the status of the
- * filter program.
- */
- static int
- logfile_write (repository, filter, title, message, revision, logfp, changes)
- char *repository;
- char *filter;
- char *title;
- char *message;
- char *revision;
- FILE *logfp;
- List *changes;
- {
- char cwd[PATH_MAX];
- FILE *pipefp;
- char *prog = xmalloc (MAXPROGLEN);
- char *cp;
- int c;
- int pipestatus;
-
- /*
- * Only 1 %s argument is supported in the filter
- */
- (void) sprintf (prog, filter, title);
- if ((pipefp = run_popen (prog, "w")) == NULL)
- {
- if (!noexec)
- error (0, 0, "cannot write entry to log filter: %s", prog);
- free (prog);
- return (1);
- }
- (void) fprintf (pipefp, "Update of %s\n", repository);
- (void) fprintf (pipefp, "In directory %s:%s\n\n", hostname,
- ((cp = getwd (cwd)) != NULL) ? cp : cwd);
- if (revision && *revision)
- (void) fprintf (pipefp, "Revision/Branch: %s\n\n", revision);
- setup_tmpfile (pipefp, "", changes);
- (void) fprintf (pipefp, "Log Message:\n%s\n", message);
- if (logfp != (FILE *) 0)
- {
- (void) fprintf (pipefp, "Status:\n");
- rewind (logfp);
- while ((c = getc (logfp)) != EOF)
- (void) putc ((char) c, pipefp);
- }
- free (prog);
- pipestatus = pclose (pipefp);
- return ((pipestatus == -1) || (pipestatus == 127)) ? 1 : 0;
- }
-
- /*
- * We choose to use the *last* match within the editinfo file for this
- * repository. This allows us to have a global editinfo program for the
- * root of some hierarchy, for example, and different ones within different
- * sub-directories of the root (like a special checker for changes made to
- * the "src" directory versus changes made to the "doc" or "test"
- * directories.
- */
- /* ARGSUSED */
- static int
- editinfo_proc(repository, editor)
- char *repository;
- char *editor;
- {
- /* nothing to do if the last match is the same as this one */
- if (editinfo_editor && strcmp (editinfo_editor, editor) == 0)
- return (0);
- if (editinfo_editor)
- free (editinfo_editor);
-
- editinfo_editor = xstrdup (editor);
- return (0);
- }
-