/* * Copyright 1999-2002 Ben Smithurst * 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 #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); }