ftp.nice.ch/pub/next/unix/developer/cvs.950905.s.tar.gz#/cvs-1.5.1/src/logmsg.c

This is logmsg.c in view mode; [Download] [Up]

/*
 * 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"

#ifndef lint
static const char rcsid[] = "$CVSid: @(#)logmsg.c 1.48 94/09/29 $";
USE(rcsid)
#endif

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.
 * 
 * rcsinfo - is the name of a file containing lines tacked onto the end of the
 * RCS info offered to the user for editing. If specified, the '-m' flag to
 * "commit" is disabled -- users are forced to run the editor.
 * 
 */
void
do_editor (dir, messagep, repository, changes)
    char *dir;
    char **messagep;
    char *repository;
    List *changes;
{
    static int reuse_log_message = 0;
    char line[MAXLINELEN], fname[L_tmpnam+1];
    struct stat pre_stbuf, post_stbuf;
    int retcode = 0;

    if (noexec || reuse_log_message)
	return;

    /* 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);

    (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 */
    (void) fclose (fp);
    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
    {
	*messagep = (char *) xmalloc (post_stbuf.st_size + 1);
 	*messagep[0] = '\0';
    }

/* !!! XXX FIXME: fgets is broken.  This should not have any line
   length limits. */

    if (*messagep)
    {
	while (fgets (line, sizeof (line), fp) != NULL)
	{
	    if (strncmp (line, CVSEDITPREFIX, sizeof (CVSEDITPREFIX) - 1) == 0)
		continue;
	    (void) strcat (*messagep, line);
	}
    }
    (void) fclose (fp);
    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 = '\0';
	    (void) fgets (line, sizeof (line), stdin);
	    if (*line == '\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");
	}
    }
    (void) unlink_file (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;
    char line[MAXLINELEN];

    /* 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)
    {
	while (fgets (line, sizeof (line), tfp) != NULL)
	    (void) fputs (line, fp);
	(void) fclose (tfp);
	return (0);
    }
    else
    {
	error (0, 0, "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, *Popen ();
    char *prog = xmalloc (MAXPROGLEN);
    char *cp;
    int c;

    /* XXX <woods@web.net> -- this is gross, ugly, and a hack!  FIXME! */
    /*
     * A maximum of 6 %s arguments are supported in the filter
     */
    (void) sprintf (prog, filter, title, title, title, title, title, title);
    if ((pipefp = 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);
    return (pclose (pipefp));
}

/*
 * 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);
}

These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.