#include "cvs.h"
#include "getline.h"
static int find_type (Node * p, void *closure);
static int fmt_proc (Node * p, void *closure);
static int logfile_write (const char *repository, const char *filter,
const char *message, FILE * logfp, List * changes);
static int logmsg_list_to_args_proc (Node *p, void *closure);
static int rcsinfo_proc (const char *repository, const char *template,
void *closure );
static int update_logfile_proc (const char *repository, const char *filter,
void *closure);
static void setup_tmpfile (FILE * xfp, char *xprefix, List * changes);
static int verifymsg_proc (const char *repository, const char *script,
void *closure );
static FILE *fp;
static Ctype type;
struct verifymsg_proc_data
{
char *fname;
char *message;
struct stat pre_stbuf;
List *changes;
};
static char *prefix;
static int col;
static char *tag;
static void
setup_tmpfile (FILE *xfp, char *xprefix, List *changes)
{
fp = xfp;
prefix = xprefix;
type = T_MODIFIED;
if (walklist (changes, find_type, NULL) != 0)
{
(void) fprintf (fp, "%sModified Files:\n", prefix);
col = 0;
(void) walklist (changes, fmt_proc, NULL);
(void) fprintf (fp, "\n");
if (tag != NULL)
{
free (tag);
tag = NULL;
}
}
type = T_ADDED;
if (walklist (changes, find_type, NULL) != 0)
{
(void) fprintf (fp, "%sAdded Files:\n", prefix);
col = 0;
(void) walklist (changes, fmt_proc, NULL);
(void) fprintf (fp, "\n");
if (tag != NULL)
{
free (tag);
tag = NULL;
}
}
type = T_REMOVED;
if (walklist (changes, find_type, NULL) != 0)
{
(void) fprintf (fp, "%sRemoved Files:\n", prefix);
col = 0;
(void) walklist (changes, fmt_proc, NULL);
(void) fprintf (fp, "\n");
if (tag != NULL)
{
free (tag);
tag = NULL;
}
}
}
static int
find_type (Node *p, void *closure)
{
struct logfile_info *li = p->data;
if (li->type == type)
return (1);
else
return (0);
}
static int
fmt_proc (Node *p, void *closure)
{
struct logfile_info *li;
li = p->data;
if (li->type == type)
{
if (li->tag == NULL
? tag != NULL
: tag == NULL || strcmp (tag, li->tag) != 0)
{
if (col > 0)
(void) fprintf (fp, "\n");
(void) fputs (prefix, fp);
col = strlen (prefix);
while (col < 6)
{
(void) fprintf (fp, " ");
++col;
}
if (li->tag == NULL)
(void) fprintf (fp, "No tag");
else
(void) fprintf (fp, "Tag: %s", li->tag);
if (tag != NULL)
free (tag);
tag = xstrdup (li->tag);
col = 70;
}
if (col == 0)
{
(void) fprintf (fp, "%s\t", prefix);
col = 8;
}
else if (col > 8 && (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);
}
void
do_editor (const char *dir, char **messagep, const char *repository,
List *changes)
{
static int reuse_log_message = 0;
char *line;
int line_length;
size_t line_chars_allocated;
char *fname;
struct stat pre_stbuf, post_stbuf;
int retcode = 0;
assert (!current_parsed_root->isremote != !repository);
if (noexec || reuse_log_message)
return;
if (strcmp (Editor, "") == 0)
error(1, 0, "no editor defined, must use -e or -m");
again:
if( ( fp = cvs_temp_file( &fname ) ) == NULL )
error( 1, errno, "cannot create temporary file" );
if (*messagep)
{
(void) fputs (*messagep, fp);
if ((*messagep)[0] == '\0' ||
(*messagep)[strlen (*messagep) - 1] != '\n')
(void) fprintf (fp, "\n");
}
if (repository != NULL)
(void) Parse_Info (CVSROOTADM_RCSINFO, repository, rcsinfo_proc,
PIOPT_ALL, NULL);
else
{
FILE *tfp;
char buf[1024];
size_t n;
size_t nwrite;
tfp = CVS_FOPEN (CVSADM_TEMPLATE, "rb");
if (tfp == NULL)
{
if (!existence_error (errno))
error (1, errno, "cannot read %s", CVSADM_TEMPLATE);
}
else
{
while (!feof (tfp))
{
char *p = buf;
n = fread (buf, 1, sizeof buf, tfp);
nwrite = n;
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, CVSEDITPREFIXLEN, 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);
if (fclose (fp) == EOF)
error (1, errno, "%s", fname);
if (stat (fname, &pre_stbuf) == -1)
pre_stbuf.st_mtime = 0;
run_setup (Editor);
run_add_arg (fname);
if ((retcode = run_exec (RUN_TTY, RUN_TTY, RUN_TTY,
RUN_NORMAL | RUN_SIGIGNORE)) != 0)
error (0, retcode == -1 ? errno : 0, "warning: editor session failed");
fp = xfopen (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';
}
line = NULL;
line_chars_allocated = 0;
if (*messagep)
{
size_t message_len = post_stbuf.st_size + 1;
size_t offset = 0;
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, CVSEDITPREFIXLEN) == 0)
continue;
if (offset + line_length >= message_len)
expand_string (messagep, &message_len,
offset + line_length + 1);
(void) strcpy (*messagep + offset, line);
offset += line_length;
}
}
if (fclose (fp) < 0)
error (0, errno, "warning: cannot close %s", fname);
if (*messagep != NULL &&
(**messagep == '\0' || strcmp (*messagep, "\n") == 0))
{
free (*messagep);
*messagep = NULL;
}
if (pre_stbuf.st_mtime == post_stbuf.st_mtime || *messagep == NULL)
{
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)
{
error (0, errno, "cannot read from stdin");
if (unlink_file (fname) < 0)
error (0, errno,
"warning: cannot remove temp file %s", fname);
error (1, 0, "aborting");
}
else if (line_length == 0
|| *line == '\n' || *line == 'c' || *line == 'C')
break;
if (*line == 'a' || *line == 'A')
{
if (unlink_file (fname) < 0)
error (0, errno, "warning: cannot remove temp file %s", fname);
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);
free (fname);
}
void
do_verify (char **messagep, const char *repository, List *changes)
{
int err;
struct verifymsg_proc_data data;
struct stat post_stbuf;
if (current_parsed_root->isremote)
return;
if (noexec || repository == NULL)
return;
data.message = *messagep;
data.fname = NULL;
data.changes = changes;
if ((err = Parse_Info (CVSROOTADM_VERIFYMSG, repository,
verifymsg_proc, 0, &data)) != 0)
{
int saved_errno = errno;
if (data.fname != NULL && unlink_file( data.fname ) < 0)
error (0, errno, "cannot remove %s", data.fname);
free (data.fname);
errno = saved_errno;
error (1, err == -1 ? errno : 0, "Message verification failed");
}
if (data.fname == NULL)
return;
if (config->RereadLogAfterVerify == LOGMSG_REREAD_ALWAYS ||
config->RereadLogAfterVerify == LOGMSG_REREAD_STAT)
{
if(stat (data.fname, &post_stbuf) != 0)
error (1, errno, "cannot find size of temp file %s", data.fname);
}
if (config->RereadLogAfterVerify == LOGMSG_REREAD_ALWAYS ||
(config->RereadLogAfterVerify == LOGMSG_REREAD_STAT &&
(data.pre_stbuf.st_mtime != post_stbuf.st_mtime ||
data.pre_stbuf.st_size != post_stbuf.st_size)))
{
if (*messagep) free (*messagep);
if (post_stbuf.st_size == 0)
*messagep = NULL;
else
{
char *line = NULL;
int line_length;
size_t line_chars_allocated = 0;
char *p;
FILE *fp;
fp = xfopen (data.fname, "r");
p = *messagep = (char *) xmalloc (post_stbuf.st_size + 1);
*messagep[0] = '\0';
for (;;)
{
line_length = getline( &line,
&line_chars_allocated,
fp);
if (line_length == -1)
{
if (ferror (fp))
error (1, errno, "cannot read %s", data.fname);
break;
}
if (strncmp (line, CVSEDITPREFIX, CVSEDITPREFIXLEN) == 0)
continue;
(void) strcpy (p, line);
p += line_length;
}
if (line) free (line);
if (fclose (fp) < 0)
error (0, errno, "warning: cannot close %s", data.fname);
}
}
if (unlink_file (data.fname) < 0)
error (0, errno, "cannot remove `%s'", data.fname);
free (data.fname);
}
static int
rcsinfo_proc (const char *repository, const char *template, void *closure)
{
static char *last_template;
FILE *tfp;
if (last_template && strcmp (last_template, template) == 0)
return (0);
if (last_template)
free (last_template);
last_template = xstrdup (template);
if ((tfp = CVS_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);
}
}
struct ulp_data {
FILE *logfp;
const char *message;
List *changes;
};
void
Update_Logfile (const char *repository, const char *xmessage, FILE *xlogfp,
List *xchanges)
{
struct ulp_data ud;
if (xchanges == NULL || xchanges->list->next == xchanges->list)
return;
ud.message = xmessage;
ud.logfp = xlogfp;
ud.changes = xchanges;
(void) Parse_Info (CVSROOTADM_LOGINFO, repository, update_logfile_proc,
PIOPT_ALL, &ud);
}
static int
update_logfile_proc (const char *repository, const char *filter, void *closure)
{
struct ulp_data *udp = closure;
TRACE (TRACE_FUNCTION, "update_logfile_proc(%s,%s)", repository, filter);
return logfile_write (repository, filter, udp->message, udp->logfp,
udp->changes);
}
static int
logmsg_list_to_args_proc (Node *p, void *closure)
{
struct format_cmdline_walklist_closure *c = closure;
struct logfile_info *li;
char *arg = NULL;
const char *f;
char *d;
size_t doff;
if (p->data == NULL) return 1;
f = c->format;
d = *c->d;
while (*f)
{
switch (*f++)
{
case 's':
arg = p->key;
break;
case 'T':
li = p->data;
arg = li->tag ? li->tag : "";
break;
case 'V':
li = p->data;
arg = li->rev_old ? li->rev_old : "NONE";
break;
case 'v':
li = p->data;
arg = li->rev_new ? li->rev_new : "NONE";
break;
default:
#ifdef SUPPORT_OLD_INFO_FMT_STRINGS
if (c->onearg)
{
arg = "\0";
}
else
#endif
error (1, 0,
"Unknown format character or not a list attribute: %c", f[-1]);
break;
}
#ifdef SUPPORT_OLD_INFO_FMT_STRINGS
if (c->onearg)
{
if (c->firstpass)
{
c->firstpass = 0;
doff = d - *c->buf;
expand_string (c->buf, c->length,
doff + strlen (c->srepos) + 1);
d = *c->buf + doff;
strncpy (d, c->srepos, strlen (c->srepos));
d += strlen (c->srepos);
*d++ = ' ';
}
}
else
#endif
{
if (c->quotes)
{
arg = cmdlineescape (c->quotes, arg);
}
else
{
arg = cmdlinequote ('"', arg);
}
}
doff = d - *c->buf;
expand_string (c->buf, c->length, doff + strlen (arg));
d = *c->buf + doff;
strncpy (d, arg, strlen (arg));
d += strlen (arg);
#ifdef SUPPORT_OLD_INFO_FMT_STRINGS
if (!c->onearg)
#endif
free (arg);
doff = d - *c->buf;
expand_string (c->buf, c->length, doff + 1);
d = *c->buf + doff;
#ifdef SUPPORT_OLD_INFO_FMT_STRINGS
if (c->onearg && *f) *d++ = ',';
else
#endif
*d++ = ' ';
}
*c->d = d;
return 0;
}
static int
logfile_write (const char *repository, const char *filter, const char *message,
FILE *logfp, List *changes)
{
char *cmdline;
FILE *pipefp;
char *cp;
int c;
int pipestatus;
const char *srepos = Short_Repository (repository);
assert (repository);
cmdline = format_cmdline (
#ifdef SUPPORT_OLD_INFO_FMT_STRINGS
!config->UseNewInfoFmtStrings, srepos,
#endif
filter,
"c", "s", cvs_cmd_name,
#ifdef SERVER_SUPPORT
"R", "s", referrer ? referrer->original : "NONE",
#endif
"p", "s", srepos,
"r", "s", current_parsed_root->directory,
"sVv", ",", changes,
logmsg_list_to_args_proc, (void *) NULL,
(char *) NULL);
if (!cmdline || !strlen (cmdline))
{
if (cmdline) free (cmdline);
error (0, 0, "logmsg proc resolved to the empty string!");
return 1;
}
if ((pipefp = run_popen (cmdline, "w")) == NULL)
{
if (!noexec)
error (0, 0, "cannot write entry to log filter: %s", cmdline);
free (cmdline);
return 1;
}
(void) fprintf (pipefp, "Update of %s\n", repository);
(void) fprintf (pipefp, "In directory %s:", hostname);
cp = xgetcwd ();
if (cp == NULL)
fprintf (pipefp, "<cannot get working directory: %s>\n\n",
strerror (errno));
else
{
fprintf (pipefp, "%s\n\n", cp);
free (cp);
}
setup_tmpfile (pipefp, "", changes);
(void) fprintf (pipefp, "Log Message:\n%s\n", (message) ? message : "");
if (logfp)
{
(void) fprintf (pipefp, "Status:\n");
rewind (logfp);
while ((c = getc (logfp)) != EOF)
(void) putc (c, pipefp);
}
free (cmdline);
pipestatus = pclose (pipefp);
return ((pipestatus == -1) || (pipestatus == 127)) ? 1 : 0;
}
static int
verifymsg_proc (const char *repository, const char *script, void *closure)
{
char *verifymsg_script;
#ifdef SUPPORT_OLD_INFO_FMT_STRINGS
char *newscript = NULL;
#endif
struct verifymsg_proc_data *vpd = closure;
const char *srepos = Short_Repository (repository);
#ifdef SUPPORT_OLD_INFO_FMT_STRINGS
if (!strchr (script, '%'))
{
error (0, 0,
"warning: verifymsg line doesn't contain any format strings:\n"
" \"%s\"\n"
"Appending default format string (\" %%l\"), but be aware that this usage is\n"
"deprecated.", script);
script = newscript = Xasprintf ("%s %%l", script);
}
#endif
if (vpd->fname == NULL)
{
FILE *fp;
if ((fp = cvs_temp_file (&(vpd->fname))) == NULL)
error (1, errno, "cannot create temporary file %s", vpd->fname);
if (vpd->message != NULL)
fputs (vpd->message, fp);
if (vpd->message == NULL ||
(vpd->message)[0] == '\0' ||
(vpd->message)[strlen (vpd->message) - 1] != '\n')
putc ('\n', fp);
if (fclose (fp) == EOF)
error (1, errno, "%s", vpd->fname);
if (config->RereadLogAfterVerify == LOGMSG_REREAD_STAT)
{
if (stat (vpd->fname, &(vpd->pre_stbuf)) != 0)
error (1, errno, "cannot stat temp file %s", vpd->fname);
sleep_past (vpd->pre_stbuf.st_mtime);
}
}
verifymsg_script = format_cmdline (
#ifdef SUPPORT_OLD_INFO_FMT_STRINGS
false, srepos,
#endif
script,
"c", "s", cvs_cmd_name,
#ifdef SERVER_SUPPORT
"R", "s", referrer
? referrer->original : "NONE",
#endif
"p", "s", srepos,
"r", "s",
current_parsed_root->directory,
"l", "s", vpd->fname,
"sV", ",", vpd->changes,
logmsg_list_to_args_proc, (void *) NULL,
(char *) NULL);
#ifdef SUPPORT_OLD_INFO_FMT_STRINGS
if (newscript) free (newscript);
#endif
if (!verifymsg_script || !strlen (verifymsg_script))
{
if (verifymsg_script) free (verifymsg_script);
verifymsg_script = NULL;
error (0, 0, "verifymsg proc resolved to the empty string!");
return 1;
}
run_setup (verifymsg_script);
free (verifymsg_script);
return abs (run_exec (RUN_TTY, RUN_TTY, RUN_TTY,
RUN_NORMAL | RUN_SIGIGNORE));
}