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