/*
* Copyright 1999-2002 Ben Smithurst <ben@smithurst.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
/*
* Add "links" to the cvsweb.cgi script to FreeBSD commit messages.
*/
static const char rcsid[] =
"$BCPS: src/mailutils/cvsmail.c,v 1.51 2003/01/19 19:18:25 ben Exp $";
#include "misc.h"
#include <fetch.h>
#define FPUTS(s, f) { \
if (fputs(s, f) == EOF) \
xerr(1, "fputs"); \
}
void usage(void);
void get_diff(FILE *, const char *);
void diff_link(FILE *, const char *, const char *, const char *, const char *);
void checkout_link(FILE *, const char *, const char *, const char *);
static int coloured_diffs, context_diffs, include_diffs, group_diffs, urls_both_places;
struct {
char *get, *show;
} *diff_urls;
static size_t dsize, dcount;
#define add_diff_url(g, s) do { \
if (dcount >= dsize) { \
if (dsize == 0) \
dsize = 16; \
else \
dsize <<= 1; \
if ((diff_urls = realloc(diff_urls, \
dsize * sizeof (*diff_urls))) == NULL) \
xerr(1, "realloc"); \
} \
if ((diff_urls[dcount].get = strdup(g)) == NULL) \
xerr(1, "strdup"); \
if ((diff_urls[dcount].show = strdup(s)) == NULL) \
xerr(1, "strdup"); \
dcount++; \
} while (0)
int
main(int argc, char *argv[]) {
FILE *fp;
int ch, header, r, start, fd, maildir = 0;
size_t len, d;
char *dp, *file, *line, *p, *pe;
char *rev, prev[32], *path;
int fflag = 0;
const char *url_base, *url_extra;
int newfile, deadfile, added, removed;
char tmp[256], buf[8192];
regex_t rcp; /* revision/changes/path */
regex_t changes; /* a changes line */
regmatch_t matches[6];
url_base = "http://cvsweb.FreeBSD.org/";
url_extra = NULL;
while ((ch = getopt(argc, argv, "CcdfGgqvu:U:")) != -1)
switch (ch) {
case 'C':
/* default is unified... */
context_diffs = 1;
break;
case 'c':
coloured_diffs = 1;
break;
case 'd':
include_diffs = 1;
break;
case 'f':
fflag = 1;
break;
case 'G':
urls_both_places = 1;
break;
case 'g':
group_diffs = 1;
break;
case 'q':
quiet = 1;
break;
case 'u':
url_base = optarg;
break;
case 'U':
url_extra = optarg;
break;
case 'v':
quiet = 0;
break;
default:
usage();
}
argv += optind;
argc -= optind;
/* If -q or -v not specified, assume -q if stderr is not a terminal */
if (quiet == -1)
quiet = !isatty(STDERR_FILENO);
if (urls_both_places && !include_diffs)
urls_both_places = 0; /* makes no sense and causes problems later */
if (argc > 1)
usage();
if (argc == 0) {
file = NULL;
fp = stdout;
} else if (include_diffs) {
/* write to a temporary file first, otherwise we'll have the
* mailbox locked while downloading diffs, which could take
* a while.
*/
strlcpy(tmp, _PATH_TMP"cvsmailXXXXXXXXXX", sizeof tmp);
if ((fd = mkstemp(tmp)) < 0)
xerr(1, "mkstemp");
unlink(tmp);
if ((fp = fdopen(fd, "w+")) == NULL)
xerr(1, "fdopen");
} else {
fp = open_mailbox(argv[0], &file, &maildir);
if (fp == NULL)
xerr(1, "open_mailbox %s", argv[0]);
}
start = 0;
header = 1;
/* Check the first line is a From_ line -- if not, add a fake one */
if ((line = gl_getline(stdin)) == NULL) {
/* No data read -- just exit */
fclose(fp);
if (file != NULL)
mailboxlock(file, maildir, -1, LF_REL);
exit(0);
} else
len = strlen(line);
if (fflag && (len < 5 || !is_from(line, 0)))
/* Not a From_ line, so add one */
FPUTS("From root@localhost Fri Sep 03 00:00:00 1999\n", fp);
/* Now write the real first line */
if (fwrite(line, 1, len, fp) != len)
xerr(1, "fwrite");
/* compile the regexes */
if (regcomp(&rcp, "^Revision[ \t]+Changes[ \t]+Path[ \t]*$", REG_EXTENDED) != 0)
xerrx(1, "regcomp failure");
if (regcomp(&changes, "^([0-9.]+)[ \t]+\\+([0-9]+)[ \t]+-([0-9]+)[ \t]+([^ \t]+)(.*)$",
REG_EXTENDED) != 0)
xerrx(1, "regcomp failure");
while ((line = gl_getline(stdin)) != NULL) {
len = strlen(line);
/* Check for a From_ line in the body */
if (fflag && !header && len >= 5 && is_from(line, 0)) {
if (fputc('>', fp) == EOF)
xerr(1, "fputc");
}
/* We always write the line out first */
if (fwrite(line, 1, len, fp) != len)
xerr(1, "fwrite");
chop(line, len);
/* Check for end of headers or blank line in body */
if (len == 0) {
header = start = 0;
continue;
}
p = line;
if (!isspace(*p++))
continue;
while (isspace(*p))
p++;
/* Check for empty lines: reset start in these cases */
if (*p == '\0') {
start = 0;
continue;
}
newfile = deadfile = added = removed = 0;
if (!start) {
/* Check for the "revision changes path" line. */
start = (regexec(&rcp, p, 0, NULL, 0) == 0);
continue;
}
/*
* At this stage, the line should be of the form:
* "a.b.c +n -m foo/bar/bax"
*/
if (regexec(&changes, p,
(sizeof matches / sizeof matches[0]), matches, 0) != 0) {
FPUTS(" Line didn't match regular expression\n", fp);
continue;
}
/* set 'rev' pointer */
rev = p + matches[1].rm_so;
rev[matches[1].rm_eo - matches[1].rm_so] = '\0';
/* copy 'rev' to 'prev' */
if (strlen(rev) >= sizeof prev) {
FPUTS(" (revision string too long)\n", fp);
continue;
}
strcpy(prev, rev);
/* handle " +n -m " */
added = atoi(p + matches[2].rm_so);
removed = atoi(p + matches[3].rm_so);
/* look for new/dead markers */
pe = p + matches[5].rm_so;
if (strstr(pe, "new") != NULL)
newfile = 1;
else if (strstr(pe, "dead") != NULL)
deadfile = 1;
/* set 'path' pointer */
path = p + matches[4].rm_so;
path[matches[4].rm_eo - matches[4].rm_so] = '\0';
/*
* Calculate the numeric value of the last part of the
* revision string, then terminate that string at the
* last dot.
*/
dp = strrchr(prev, '.');
if (dp == NULL) {
FPUTS(" (bad format: no dot in revision string)\n",
fp);
continue;
}
r = atoi(dp + 1);
if (r <= 1 && !newfile) {
/* chop off the last two numbers in this case. */
*dp = '\0';
if ((dp = strrchr(prev, '.')) == NULL) {
FPUTS(" (cannot determine "
"previous revision)\n", fp);
continue;
}
*dp = '\0';
} else if (!newfile)
/* there must be room for this; r > 1, so r - 1
* cannot take any more room than r did.
*/
sprintf(dp + 1, "%d", r - 1);
if (newfile) {
checkout_link(fp, url_base, path, rev);
checkout_link(fp, url_extra, path, rev);
} else if (deadfile) {
checkout_link(fp, url_base, path, prev);
checkout_link(fp, url_extra, path, prev);
} else if (added != 0 || removed != 0) {
diff_link(fp, url_base, path, rev, prev);
diff_link(fp, url_extra, path, rev, prev);
}
}
gl_destroy(stdin);
for (d = 0; d < dcount; d++) {
xfprintf(fp, "%s\n", diff_urls[d].show);
if (include_diffs)
get_diff(fp, diff_urls[d].get);
free(diff_urls[d].show);
free(diff_urls[d].get);
}
free(diff_urls);
/* copy to the real mailbox in the include_diffs case (see above) */
if (include_diffs && argc == 1) {
FILE *tmpfp = fp;
fp = open_mailbox(argv[0], &file, &maildir);
if (fp == NULL)
xerr(1, "open_mailbox %s", argv[0]);
rewind(tmpfp);
while ((len = fread(buf, 1, sizeof buf, tmpfp)) > 0)
if (fwrite(buf, 1, len, fp) != len)
xerr(1, "fwrite");
/* continue below to close mailbox and unlock... */
}
if (fclose(fp) != 0)
xerr(1, "fclose");
if (file != NULL)
mailboxlock(file, maildir, -1, LF_REL);
return (0);
}
void
get_diff(FILE *fp, const char *url) {
FILE *sp;
char buf[1024];
if ((sp = fetchGetURL(url, "")) == NULL) {
xfprintf(fp, "| fetch %s failed\n", url);
return;
}
while (fgets(buf, sizeof buf, sp) != NULL)
xfprintf(fp, "| %s", buf);
fclose(sp);
}
void
diff_link(FILE *fp, const char *url, const char *path, const char *rev, const char *prev) {
char show_url[512], full_url[512];
if (url != NULL) {
snprintf(show_url, sizeof show_url, "%s%s.diff?r1=%s&r2=%s%s",
url, path, prev, rev, coloured_diffs ? "&f=h" : context_diffs ? "&f=c" : "");
snprintf(full_url, sizeof full_url, "%s%s.diff?r1=%s&r2=%s%s",
url, path, prev, rev, context_diffs ? "&f=c" : "");
if (group_diffs)
add_diff_url(full_url, show_url);
if (!group_diffs || urls_both_places)
xfprintf(fp, "%s\n", show_url);
if (include_diffs && !group_diffs)
get_diff(fp, full_url);
}
}
void
checkout_link(FILE *fp, const char *url, const char *path, const char *rev) {
if (url != NULL)
xfprintf(fp, "%s%s?rev=%s&content-type=text/%s\n",
url, path, rev, coloured_diffs ? "x-cvsweb-markup" : "plain");
}
void
usage(void) {
fprintf(stderr, "usage: cvsmail [-CcdfGgqv] [-u url] [-U url] [output]\n");
exit(EX_USAGE);
}
syntax highlighted by Code2HTML, v. 0.9.1