Commits

Anonymous committed 5ab2c0a Merge

Merge branch 'js/lsfix'

* js/lsfix:
Initialize lock_file struct to all zero.
Make git-update-ref a builtin
Make git-update-index a builtin
Make git-stripspace a builtin
Make git-mailinfo a builtin
Make git-mailsplit a builtin
Make git-write-tree a builtin

  • Participants
  • Parent commits c5c2374, 928e47e

Comments (0)

Files changed (15)

 
 # The ones that do not have to link with lcrypto, lz nor xdiff.
 SIMPLE_PROGRAMS = \
-	git-mailsplit$X \
-	git-stripspace$X git-daemon$X
+	git-daemon$X
 
 # ... and all the rest that could be moved out of bindir to gitexecdir
 PROGRAMS = \
 	git-checkout-index$X \
 	git-convert-objects$X git-fetch-pack$X git-fsck-objects$X \
 	git-hash-object$X git-index-pack$X git-local-fetch$X \
-	git-mailinfo$X git-merge-base$X \
+	git-merge-base$X \
 	git-merge-index$X git-mktag$X git-mktree$X git-pack-objects$X git-patch-id$X \
 	git-peek-remote$X git-prune-packed$X git-receive-pack$X \
 	git-send-pack$X git-shell$X \
 	git-show-index$X git-ssh-fetch$X \
 	git-ssh-upload$X git-unpack-file$X \
-	git-unpack-objects$X git-update-index$X git-update-server-info$X \
-	git-upload-pack$X git-verify-pack$X git-write-tree$X \
-	git-update-ref$X git-symbolic-ref$X \
+	git-unpack-objects$X git-update-server-info$X \
+	git-upload-pack$X git-verify-pack$X \
+	git-symbolic-ref$X \
 	git-name-rev$X git-pack-redundant$X git-repo-config$X git-var$X \
 	git-describe$X git-merge-tree$X git-blame$X git-imap-send$X
 
-BUILT_INS = git-log$X git-whatchanged$X git-show$X \
-	git-count-objects$X git-diff$X git-push$X \
-	git-grep$X git-add$X git-rm$X git-rev-list$X \
-	git-check-ref-format$X git-rev-parse$X \
+BUILT_INS = git-log$X git-whatchanged$X git-show$X git-update-ref$X \
+	git-count-objects$X git-diff$X git-push$X git-mailsplit$X \
+	git-grep$X git-add$X git-rm$X git-rev-list$X git-stripspace$X \
+	git-check-ref-format$X git-rev-parse$X git-mailinfo$X \
 	git-init-db$X git-tar-tree$X git-upload-tar$X git-format-patch$X \
 	git-ls-files$X git-ls-tree$X git-get-tar-commit-id$X \
-	git-read-tree$X git-commit-tree$X \
-	git-apply$X git-show-branch$X git-diff-files$X \
+	git-read-tree$X git-commit-tree$X git-write-tree$X \
+	git-apply$X git-show-branch$X git-diff-files$X git-update-index$X \
 	git-diff-index$X git-diff-stages$X git-diff-tree$X git-cat-file$X
 
 # what 'all' will build and 'install' will install, in gitexecdir
 	builtin-log.o builtin-help.o builtin-count.o builtin-diff.o builtin-push.o \
 	builtin-grep.o builtin-add.o builtin-rev-list.o builtin-check-ref-format.o \
 	builtin-rm.o builtin-init-db.o builtin-rev-parse.o \
-	builtin-tar-tree.o builtin-upload-tar.o \
-	builtin-ls-files.o builtin-ls-tree.o \
-	builtin-read-tree.o builtin-commit-tree.o \
+	builtin-tar-tree.o builtin-upload-tar.o builtin-update-index.o \
+	builtin-ls-files.o builtin-ls-tree.o builtin-write-tree.o \
+	builtin-read-tree.o builtin-commit-tree.o builtin-mailinfo.o \
 	builtin-apply.o builtin-show-branch.o builtin-diff-files.o \
 	builtin-diff-index.o builtin-diff-stages.o builtin-diff-tree.o \
-	builtin-cat-file.o
+	builtin-cat-file.o builtin-mailsplit.o builtin-stripspace.o \
+	builtin-update-ref.o
 
 GITLIBS = $(LIB_FILE) $(XDIFF_LIB)
 LIBS = $(GITLIBS) -lz
 	else
 		ICONV_LINK =
 	endif
-	LIB_4_ICONV = $(ICONV_LINK) -liconv
-else
-	LIB_4_ICONV =
+	LIBS += $(ICONV_LINK) -liconv
 endif
 ifdef NEEDS_SOCKET
 	LIBS += -lsocket
 	$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \
 		$(LIB_FILE) $(SIMPLE_LIB)
 
-git-mailinfo$X: mailinfo.o $(LIB_FILE)
-	$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \
-		$(LIB_FILE) $(SIMPLE_LIB) $(LIB_4_ICONV)
-
 git-local-fetch$X: fetch.o
 git-ssh-fetch$X: rsh.o fetch.o
 git-ssh-upload$X: rsh.o

File builtin-mailinfo.c

+/*
+ * Another stupid program, this one parsing the headers of an
+ * email to figure out authorship and subject
+ */
+#define _GNU_SOURCE
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#ifndef NO_ICONV
+#include <iconv.h>
+#endif
+#include "git-compat-util.h"
+#include "cache.h"
+#include "builtin.h"
+
+static FILE *cmitmsg, *patchfile, *fin, *fout;
+
+static int keep_subject = 0;
+static const char *metainfo_charset = NULL;
+static char line[1000];
+static char date[1000];
+static char name[1000];
+static char email[1000];
+static char subject[1000];
+
+static enum  {
+	TE_DONTCARE, TE_QP, TE_BASE64,
+} transfer_encoding;
+static char charset[256];
+
+static char multipart_boundary[1000];
+static int multipart_boundary_len;
+static int patch_lines = 0;
+
+static char *sanity_check(char *name, char *email)
+{
+	int len = strlen(name);
+	if (len < 3 || len > 60)
+		return email;
+	if (strchr(name, '@') || strchr(name, '<') || strchr(name, '>'))
+		return email;
+	return name;
+}
+
+static int bogus_from(char *line)
+{
+	/* John Doe <johndoe> */
+	char *bra, *ket, *dst, *cp;
+
+	/* This is fallback, so do not bother if we already have an
+	 * e-mail address.
+	 */
+	if (*email)
+		return 0;
+
+	bra = strchr(line, '<');
+	if (!bra)
+		return 0;
+	ket = strchr(bra, '>');
+	if (!ket)
+		return 0;
+
+	for (dst = email, cp = bra+1; cp < ket; )
+		*dst++ = *cp++;
+	*dst = 0;
+	for (cp = line; isspace(*cp); cp++)
+		;
+	for (bra--; isspace(*bra); bra--)
+		*bra = 0;
+	cp = sanity_check(cp, email);
+	strcpy(name, cp);
+	return 1;
+}
+
+static int handle_from(char *in_line)
+{
+	char line[1000];
+	char *at;
+	char *dst;
+
+	strcpy(line, in_line);
+	at = strchr(line, '@');
+	if (!at)
+		return bogus_from(line);
+
+	/*
+	 * If we already have one email, don't take any confusing lines
+	 */
+	if (*email && strchr(at+1, '@'))
+		return 0;
+
+	/* Pick up the string around '@', possibly delimited with <>
+	 * pair; that is the email part.  White them out while copying.
+	 */
+	while (at > line) {
+		char c = at[-1];
+		if (isspace(c))
+			break;
+		if (c == '<') {
+			at[-1] = ' ';
+			break;
+		}
+		at--;
+	}
+	dst = email;
+	for (;;) {
+		unsigned char c = *at;
+		if (!c || c == '>' || isspace(c)) {
+			if (c == '>')
+				*at = ' ';
+			break;
+		}
+		*at++ = ' ';
+		*dst++ = c;
+	}
+	*dst++ = 0;
+
+	/* The remainder is name.  It could be "John Doe <john.doe@xz>"
+	 * or "john.doe@xz (John Doe)", but we have whited out the
+	 * email part, so trim from both ends, possibly removing
+	 * the () pair at the end.
+	 */
+	at = line + strlen(line);
+	while (at > line) {
+		unsigned char c = *--at;
+		if (!isspace(c)) {
+			at[(c == ')') ? 0 : 1] = 0;
+			break;
+		}
+	}
+
+	at = line;
+	for (;;) {
+		unsigned char c = *at;
+		if (!c || !isspace(c)) {
+			if (c == '(')
+				at++;
+			break;
+		}
+		at++;
+	}
+	at = sanity_check(at, email);
+	strcpy(name, at);
+	return 1;
+}
+
+static int handle_date(char *line)
+{
+	strcpy(date, line);
+	return 0;
+}
+
+static int handle_subject(char *line)
+{
+	strcpy(subject, line);
+	return 0;
+}
+
+/* NOTE NOTE NOTE.  We do not claim we do full MIME.  We just attempt
+ * to have enough heuristics to grok MIME encoded patches often found
+ * on our mailing lists.  For example, we do not even treat header lines
+ * case insensitively.
+ */
+
+static int slurp_attr(const char *line, const char *name, char *attr)
+{
+	char *ends, *ap = strcasestr(line, name);
+	size_t sz;
+
+	if (!ap) {
+		*attr = 0;
+		return 0;
+	}
+	ap += strlen(name);
+	if (*ap == '"') {
+		ap++;
+		ends = "\"";
+	}
+	else
+		ends = "; \t";
+	sz = strcspn(ap, ends);
+	memcpy(attr, ap, sz);
+	attr[sz] = 0;
+	return 1;
+}
+
+static int handle_subcontent_type(char *line)
+{
+	/* We do not want to mess with boundary.  Note that we do not
+	 * handle nested multipart.
+	 */
+	if (strcasestr(line, "boundary=")) {
+		fprintf(stderr, "Not handling nested multipart message.\n");
+		exit(1);
+	}
+	slurp_attr(line, "charset=", charset);
+	if (*charset) {
+		int i, c;
+		for (i = 0; (c = charset[i]) != 0; i++)
+			charset[i] = tolower(c);
+	}
+	return 0;
+}
+
+static int handle_content_type(char *line)
+{
+	*multipart_boundary = 0;
+	if (slurp_attr(line, "boundary=", multipart_boundary + 2)) {
+		memcpy(multipart_boundary, "--", 2);
+		multipart_boundary_len = strlen(multipart_boundary);
+	}
+	slurp_attr(line, "charset=", charset);
+	return 0;
+}
+
+static int handle_content_transfer_encoding(char *line)
+{
+	if (strcasestr(line, "base64"))
+		transfer_encoding = TE_BASE64;
+	else if (strcasestr(line, "quoted-printable"))
+		transfer_encoding = TE_QP;
+	else
+		transfer_encoding = TE_DONTCARE;
+	return 0;
+}
+
+static int is_multipart_boundary(const char *line)
+{
+	return (!memcmp(line, multipart_boundary, multipart_boundary_len));
+}
+
+static int eatspace(char *line)
+{
+	int len = strlen(line);
+	while (len > 0 && isspace(line[len-1]))
+		line[--len] = 0;
+	return len;
+}
+
+#define SEEN_FROM 01
+#define SEEN_DATE 02
+#define SEEN_SUBJECT 04
+#define SEEN_BOGUS_UNIX_FROM 010
+#define SEEN_PREFIX  020
+
+/* First lines of body can have From:, Date:, and Subject: or empty */
+static void handle_inbody_header(int *seen, char *line)
+{
+	if (*seen & SEEN_PREFIX)
+		return;
+	if (isspace(*line)) {
+		char *cp;
+		for (cp = line + 1; *cp; cp++) {
+			if (!isspace(*cp))
+				break;
+		}
+		if (!*cp)
+			return;
+	}
+	if (!memcmp(">From", line, 5) && isspace(line[5])) {
+		if (!(*seen & SEEN_BOGUS_UNIX_FROM)) {
+			*seen |= SEEN_BOGUS_UNIX_FROM;
+			return;
+		}
+	}
+	if (!memcmp("From:", line, 5) && isspace(line[5])) {
+		if (!(*seen & SEEN_FROM) && handle_from(line+6)) {
+			*seen |= SEEN_FROM;
+			return;
+		}
+	}
+	if (!memcmp("Date:", line, 5) && isspace(line[5])) {
+		if (!(*seen & SEEN_DATE)) {
+			handle_date(line+6);
+			*seen |= SEEN_DATE;
+			return;
+		}
+	}
+	if (!memcmp("Subject:", line, 8) && isspace(line[8])) {
+		if (!(*seen & SEEN_SUBJECT)) {
+			handle_subject(line+9);
+			*seen |= SEEN_SUBJECT;
+			return;
+		}
+	}
+	if (!memcmp("[PATCH]", line, 7) && isspace(line[7])) {
+		if (!(*seen & SEEN_SUBJECT)) {
+			handle_subject(line);
+			*seen |= SEEN_SUBJECT;
+			return;
+		}
+	}
+	*seen |= SEEN_PREFIX;
+}
+
+static char *cleanup_subject(char *subject)
+{
+	if (keep_subject)
+		return subject;
+	for (;;) {
+		char *p;
+		int len, remove;
+		switch (*subject) {
+		case 'r': case 'R':
+			if (!memcmp("e:", subject+1, 2)) {
+				subject +=3;
+				continue;
+			}
+			break;
+		case ' ': case '\t': case ':':
+			subject++;
+			continue;
+
+		case '[':
+			p = strchr(subject, ']');
+			if (!p) {
+				subject++;
+				continue;
+			}
+			len = strlen(p);
+			remove = p - subject;
+			if (remove <= len *2) {
+				subject = p+1;
+				continue;
+			}
+			break;
+		}
+		eatspace(subject);
+		return subject;
+	}
+}
+
+static void cleanup_space(char *buf)
+{
+	unsigned char c;
+	while ((c = *buf) != 0) {
+		buf++;
+		if (isspace(c)) {
+			buf[-1] = ' ';
+			c = *buf;
+			while (isspace(c)) {
+				int len = strlen(buf);
+				memmove(buf, buf+1, len);
+				c = *buf;
+			}
+		}
+	}
+}
+
+static void decode_header_bq(char *it);
+typedef int (*header_fn_t)(char *);
+struct header_def {
+	const char *name;
+	header_fn_t func;
+	int namelen;
+};
+
+static void check_header(char *line, struct header_def *header)
+{
+	int i;
+
+	if (header[0].namelen <= 0) {
+		for (i = 0; header[i].name; i++)
+			header[i].namelen = strlen(header[i].name);
+	}
+	for (i = 0; header[i].name; i++) {
+		int len = header[i].namelen;
+		if (!strncasecmp(line, header[i].name, len) &&
+		    line[len] == ':' && isspace(line[len + 1])) {
+			/* Unwrap inline B and Q encoding, and optionally
+			 * normalize the meta information to utf8.
+			 */
+			decode_header_bq(line + len + 2);
+			header[i].func(line + len + 2);
+			break;
+		}
+	}
+}
+
+static void check_subheader_line(char *line)
+{
+	static struct header_def header[] = {
+		{ "Content-Type", handle_subcontent_type },
+		{ "Content-Transfer-Encoding",
+		  handle_content_transfer_encoding },
+		{ NULL },
+	};
+	check_header(line, header);
+}
+static void check_header_line(char *line)
+{
+	static struct header_def header[] = {
+		{ "From", handle_from },
+		{ "Date", handle_date },
+		{ "Subject", handle_subject },
+		{ "Content-Type", handle_content_type },
+		{ "Content-Transfer-Encoding",
+		  handle_content_transfer_encoding },
+		{ NULL },
+	};
+	check_header(line, header);
+}
+
+static int is_rfc2822_header(char *line)
+{
+	/*
+	 * The section that defines the loosest possible
+	 * field name is "3.6.8 Optional fields".
+	 *
+	 * optional-field = field-name ":" unstructured CRLF
+	 * field-name = 1*ftext
+	 * ftext = %d33-57 / %59-126
+	 */
+	int ch;
+	char *cp = line;
+	while ((ch = *cp++)) {
+		if (ch == ':')
+			return cp != line;
+		if ((33 <= ch && ch <= 57) ||
+		    (59 <= ch && ch <= 126))
+			continue;
+		break;
+	}
+	return 0;
+}
+
+static int read_one_header_line(char *line, int sz, FILE *in)
+{
+	int ofs = 0;
+	while (ofs < sz) {
+		int peek, len;
+		if (fgets(line + ofs, sz - ofs, in) == NULL)
+			break;
+		len = eatspace(line + ofs);
+		if ((len == 0) || !is_rfc2822_header(line)) {
+			/* Re-add the newline */
+			line[ofs + len] = '\n';
+			line[ofs + len + 1] = '\0';
+			break;
+		}
+		ofs += len;
+		/* Yuck, 2822 header "folding" */
+		peek = fgetc(in); ungetc(peek, in);
+		if (peek != ' ' && peek != '\t')
+			break;
+	}
+	/* Count mbox From headers as headers */
+	if (!ofs && !memcmp(line, "From ", 5))
+		ofs = 1;
+	return ofs;
+}
+
+static unsigned hexval(int c)
+{
+	if (c >= '0' && c <= '9')
+		return c - '0';
+	if (c >= 'a' && c <= 'f')
+		return c - 'a' + 10;
+	if (c >= 'A' && c <= 'F')
+		return c - 'A' + 10;
+	return ~0;
+}
+
+static int decode_q_segment(char *in, char *ot, char *ep, int rfc2047)
+{
+	int c;
+	while ((c = *in++) != 0 && (in <= ep)) {
+		if (c == '=') {
+			int d = *in++;
+			if (d == '\n' || !d)
+				break; /* drop trailing newline */
+			*ot++ = ((hexval(d) << 4) | hexval(*in++));
+			continue;
+		}
+		if (rfc2047 && c == '_') /* rfc2047 4.2 (2) */
+			c = 0x20;
+		*ot++ = c;
+	}
+	*ot = 0;
+	return 0;
+}
+
+static int decode_b_segment(char *in, char *ot, char *ep)
+{
+	/* Decode in..ep, possibly in-place to ot */
+	int c, pos = 0, acc = 0;
+
+	while ((c = *in++) != 0 && (in <= ep)) {
+		if (c == '+')
+			c = 62;
+		else if (c == '/')
+			c = 63;
+		else if ('A' <= c && c <= 'Z')
+			c -= 'A';
+		else if ('a' <= c && c <= 'z')
+			c -= 'a' - 26;
+		else if ('0' <= c && c <= '9')
+			c -= '0' - 52;
+		else if (c == '=') {
+			/* padding is almost like (c == 0), except we do
+			 * not output NUL resulting only from it;
+			 * for now we just trust the data.
+			 */
+			c = 0;
+		}
+		else
+			continue; /* garbage */
+		switch (pos++) {
+		case 0:
+			acc = (c << 2);
+			break;
+		case 1:
+			*ot++ = (acc | (c >> 4));
+			acc = (c & 15) << 4;
+			break;
+		case 2:
+			*ot++ = (acc | (c >> 2));
+			acc = (c & 3) << 6;
+			break;
+		case 3:
+			*ot++ = (acc | c);
+			acc = pos = 0;
+			break;
+		}
+	}
+	*ot = 0;
+	return 0;
+}
+
+static void convert_to_utf8(char *line, char *charset)
+{
+#ifndef NO_ICONV
+	char *in, *out;
+	size_t insize, outsize, nrc;
+	char outbuf[4096]; /* cheat */
+	static char latin_one[] = "latin1";
+	char *input_charset = *charset ? charset : latin_one;
+	iconv_t conv = iconv_open(metainfo_charset, input_charset);
+
+	if (conv == (iconv_t) -1) {
+		static int warned_latin1_once = 0;
+		if (input_charset != latin_one) {
+			fprintf(stderr, "cannot convert from %s to %s\n",
+				input_charset, metainfo_charset);
+			*charset = 0;
+		}
+		else if (!warned_latin1_once) {
+			warned_latin1_once = 1;
+			fprintf(stderr, "tried to convert from %s to %s, "
+				"but your iconv does not work with it.\n",
+				input_charset, metainfo_charset);
+		}
+		return;
+	}
+	in = line;
+	insize = strlen(in);
+	out = outbuf;
+	outsize = sizeof(outbuf);
+	nrc = iconv(conv, &in, &insize, &out, &outsize);
+	iconv_close(conv);
+	if (nrc == (size_t) -1)
+		return;
+	*out = 0;
+	strcpy(line, outbuf);
+#endif
+}
+
+static void decode_header_bq(char *it)
+{
+	char *in, *out, *ep, *cp, *sp;
+	char outbuf[1000];
+
+	in = it;
+	out = outbuf;
+	while ((ep = strstr(in, "=?")) != NULL) {
+		int sz, encoding;
+		char charset_q[256], piecebuf[256];
+		if (in != ep) {
+			sz = ep - in;
+			memcpy(out, in, sz);
+			out += sz;
+			in += sz;
+		}
+		/* E.g.
+		 * ep : "=?iso-2022-jp?B?GyR...?= foo"
+		 * ep : "=?ISO-8859-1?Q?Foo=FCbar?= baz"
+		 */
+		ep += 2;
+		cp = strchr(ep, '?');
+		if (!cp)
+			return; /* no munging */
+		for (sp = ep; sp < cp; sp++)
+			charset_q[sp - ep] = tolower(*sp);
+		charset_q[cp - ep] = 0;
+		encoding = cp[1];
+		if (!encoding || cp[2] != '?')
+			return; /* no munging */
+		ep = strstr(cp + 3, "?=");
+		if (!ep)
+			return; /* no munging */
+		switch (tolower(encoding)) {
+		default:
+			return; /* no munging */
+		case 'b':
+			sz = decode_b_segment(cp + 3, piecebuf, ep);
+			break;
+		case 'q':
+			sz = decode_q_segment(cp + 3, piecebuf, ep, 1);
+			break;
+		}
+		if (sz < 0)
+			return;
+		if (metainfo_charset)
+			convert_to_utf8(piecebuf, charset_q);
+		strcpy(out, piecebuf);
+		out += strlen(out);
+		in = ep + 2;
+	}
+	strcpy(out, in);
+	strcpy(it, outbuf);
+}
+
+static void decode_transfer_encoding(char *line)
+{
+	char *ep;
+
+	switch (transfer_encoding) {
+	case TE_QP:
+		ep = line + strlen(line);
+		decode_q_segment(line, line, ep, 0);
+		break;
+	case TE_BASE64:
+		ep = line + strlen(line);
+		decode_b_segment(line, line, ep);
+		break;
+	case TE_DONTCARE:
+		break;
+	}
+}
+
+static void handle_info(void)
+{
+	char *sub;
+
+	sub = cleanup_subject(subject);
+	cleanup_space(name);
+	cleanup_space(date);
+	cleanup_space(email);
+	cleanup_space(sub);
+
+	fprintf(fout, "Author: %s\nEmail: %s\nSubject: %s\nDate: %s\n\n",
+	       name, email, sub, date);
+}
+
+/* We are inside message body and have read line[] already.
+ * Spit out the commit log.
+ */
+static int handle_commit_msg(int *seen)
+{
+	if (!cmitmsg)
+		return 0;
+	do {
+		if (!memcmp("diff -", line, 6) ||
+		    !memcmp("---", line, 3) ||
+		    !memcmp("Index: ", line, 7))
+			break;
+		if ((multipart_boundary[0] && is_multipart_boundary(line))) {
+			/* We come here when the first part had only
+			 * the commit message without any patch.  We
+			 * pretend we have not seen this line yet, and
+			 * go back to the loop.
+			 */
+			return 1;
+		}
+
+		/* Unwrap transfer encoding and optionally
+		 * normalize the log message to UTF-8.
+		 */
+		decode_transfer_encoding(line);
+		if (metainfo_charset)
+			convert_to_utf8(line, charset);
+
+		handle_inbody_header(seen, line);
+		if (!(*seen & SEEN_PREFIX))
+			continue;
+
+		fputs(line, cmitmsg);
+	} while (fgets(line, sizeof(line), fin) != NULL);
+	fclose(cmitmsg);
+	cmitmsg = NULL;
+	return 0;
+}
+
+/* We have done the commit message and have the first
+ * line of the patch in line[].
+ */
+static void handle_patch(void)
+{
+	do {
+		if (multipart_boundary[0] && is_multipart_boundary(line))
+			break;
+		/* Only unwrap transfer encoding but otherwise do not
+		 * do anything.  We do *NOT* want UTF-8 conversion
+		 * here; we are dealing with the user payload.
+		 */
+		decode_transfer_encoding(line);
+		fputs(line, patchfile);
+		patch_lines++;
+	} while (fgets(line, sizeof(line), fin) != NULL);
+}
+
+/* multipart boundary and transfer encoding are set up for us, and we
+ * are at the end of the sub header.  do equivalent of handle_body up
+ * to the next boundary without closing patchfile --- we will expect
+ * that the first part to contain commit message and a patch, and
+ * handle other parts as pure patches.
+ */
+static int handle_multipart_one_part(int *seen)
+{
+	int n = 0;
+
+	while (fgets(line, sizeof(line), fin) != NULL) {
+	again:
+		n++;
+		if (is_multipart_boundary(line))
+			break;
+		if (handle_commit_msg(seen))
+			goto again;
+		handle_patch();
+		break;
+	}
+	if (n == 0)
+		return -1;
+	return 0;
+}
+
+static void handle_multipart_body(void)
+{
+	int seen = 0;
+	int part_num = 0;
+
+	/* Skip up to the first boundary */
+	while (fgets(line, sizeof(line), fin) != NULL)
+		if (is_multipart_boundary(line)) {
+			part_num = 1;
+			break;
+		}
+	if (!part_num)
+		return;
+	/* We are on boundary line.  Start slurping the subhead. */
+	while (1) {
+		int hdr = read_one_header_line(line, sizeof(line), fin);
+		if (!hdr) {
+			if (handle_multipart_one_part(&seen) < 0)
+				return;
+			/* Reset per part headers */
+			transfer_encoding = TE_DONTCARE;
+			charset[0] = 0;
+		}
+		else
+			check_subheader_line(line);
+	}
+	fclose(patchfile);
+	if (!patch_lines) {
+		fprintf(stderr, "No patch found\n");
+		exit(1);
+	}
+}
+
+/* Non multipart message */
+static void handle_body(void)
+{
+	int seen = 0;
+
+	handle_commit_msg(&seen);
+	handle_patch();
+	fclose(patchfile);
+	if (!patch_lines) {
+		fprintf(stderr, "No patch found\n");
+		exit(1);
+	}
+}
+
+int mailinfo(FILE *in, FILE *out, int ks, const char *encoding,
+	     const char *msg, const char *patch)
+{
+	keep_subject = ks;
+	metainfo_charset = encoding;
+	fin = in;
+	fout = out;
+
+	cmitmsg = fopen(msg, "w");
+	if (!cmitmsg) {
+		perror(msg);
+		return -1;
+	}
+	patchfile = fopen(patch, "w");
+	if (!patchfile) {
+		perror(patch);
+		fclose(cmitmsg);
+		return -1;
+	}
+	while (1) {
+		int hdr = read_one_header_line(line, sizeof(line), fin);
+		if (!hdr) {
+			if (multipart_boundary[0])
+				handle_multipart_body();
+			else
+				handle_body();
+			handle_info();
+			break;
+		}
+		check_header_line(line);
+	}
+
+	return 0;
+}
+
+static const char mailinfo_usage[] =
+	"git-mailinfo [-k] [-u | --encoding=<encoding>] msg patch <mail >info";
+
+int cmd_mailinfo(int argc, const char **argv, char **envp)
+{
+	/* NEEDSWORK: might want to do the optional .git/ directory
+	 * discovery
+	 */
+	git_config(git_default_config);
+
+	while (1 < argc && argv[1][0] == '-') {
+		if (!strcmp(argv[1], "-k"))
+			keep_subject = 1;
+		else if (!strcmp(argv[1], "-u"))
+			metainfo_charset = git_commit_encoding;
+		else if (!strncmp(argv[1], "--encoding=", 11))
+			metainfo_charset = argv[1] + 11;
+		else
+			usage(mailinfo_usage);
+		argc--; argv++;
+	}
+
+	if (argc != 3)
+		usage(mailinfo_usage);
+
+	return !!mailinfo(stdin, stdout, keep_subject, metainfo_charset, argv[1], argv[2]);
+}

File builtin-mailsplit.c

+/*
+ * Totally braindamaged mbox splitter program.
+ *
+ * It just splits a mbox into a list of files: "0001" "0002" ..
+ * so you can process them further from there.
+ */
+#include <unistd.h>
+#include <stdlib.h>
+#include <fcntl.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <string.h>
+#include <stdio.h>
+#include "cache.h"
+#include "builtin.h"
+
+static const char git_mailsplit_usage[] =
+"git-mailsplit [-d<prec>] [-f<n>] [-b] -o<directory> <mbox>...";
+
+static int is_from_line(const char *line, int len)
+{
+	const char *colon;
+
+	if (len < 20 || memcmp("From ", line, 5))
+		return 0;
+
+	colon = line + len - 2;
+	line += 5;
+	for (;;) {
+		if (colon < line)
+			return 0;
+		if (*--colon == ':')
+			break;
+	}
+
+	if (!isdigit(colon[-4]) ||
+	    !isdigit(colon[-2]) ||
+	    !isdigit(colon[-1]) ||
+	    !isdigit(colon[ 1]) ||
+	    !isdigit(colon[ 2]))
+		return 0;
+
+	/* year */
+	if (strtol(colon+3, NULL, 10) <= 90)
+		return 0;
+
+	/* Ok, close enough */
+	return 1;
+}
+
+/* Could be as small as 64, enough to hold a Unix "From " line. */
+static char buf[4096];
+
+/* Called with the first line (potentially partial)
+ * already in buf[] -- normally that should begin with
+ * the Unix "From " line.  Write it into the specified
+ * file.
+ */
+static int split_one(FILE *mbox, const char *name, int allow_bare)
+{
+	FILE *output = NULL;
+	int len = strlen(buf);
+	int fd;
+	int status = 0;
+	int is_bare = !is_from_line(buf, len);
+
+	if (is_bare && !allow_bare)
+		goto corrupt;
+
+	fd = open(name, O_WRONLY | O_CREAT | O_EXCL, 0666);
+	if (fd < 0)
+		die("cannot open output file %s", name);
+	output = fdopen(fd, "w");
+
+	/* Copy it out, while searching for a line that begins with
+	 * "From " and having something that looks like a date format.
+	 */
+	for (;;) {
+		int is_partial = (buf[len-1] != '\n');
+
+		if (fputs(buf, output) == EOF)
+			die("cannot write output");
+
+		if (fgets(buf, sizeof(buf), mbox) == NULL) {
+			if (feof(mbox)) {
+				status = 1;
+				break;
+			}
+			die("cannot read mbox");
+		}
+		len = strlen(buf);
+		if (!is_partial && !is_bare && is_from_line(buf, len))
+			break; /* done with one message */
+	}
+	fclose(output);
+	return status;
+
+ corrupt:
+	if (output)
+		fclose(output);
+	unlink(name);
+	fprintf(stderr, "corrupt mailbox\n");
+	exit(1);
+}
+
+int split_mbox(const char **mbox, const char *dir, int allow_bare, int nr_prec, int skip)
+{
+	char *name = xmalloc(strlen(dir) + 2 + 3 * sizeof(skip));
+	int ret = -1;
+
+	while (*mbox) {
+		const char *file = *mbox++;
+		FILE *f = !strcmp(file, "-") ? stdin : fopen(file, "r");
+		int file_done = 0;
+
+		if ( !f ) {
+			error("cannot open mbox %s", file);
+			goto out;
+		}
+
+		if (fgets(buf, sizeof(buf), f) == NULL) {
+			if (f == stdin)
+				break; /* empty stdin is OK */
+			error("cannot read mbox %s", file);
+			goto out;
+		}
+
+		while (!file_done) {
+			sprintf(name, "%s/%0*d", dir, nr_prec, ++skip);
+			file_done = split_one(f, name, allow_bare);
+		}
+
+		if (f != stdin)
+			fclose(f);
+	}
+	ret = skip;
+out:
+	free(name);
+	return ret;
+}
+int cmd_mailsplit(int argc, const char **argv, char **envp)
+{
+	int nr = 0, nr_prec = 4, ret;
+	int allow_bare = 0;
+	const char *dir = NULL;
+	const char **argp;
+	static const char *stdin_only[] = { "-", NULL };
+
+	for (argp = argv+1; *argp; argp++) {
+		const char *arg = *argp;
+
+		if (arg[0] != '-')
+			break;
+		/* do flags here */
+		if ( arg[1] == 'd' ) {
+			nr_prec = strtol(arg+2, NULL, 10);
+			if (nr_prec < 3 || 10 <= nr_prec)
+				usage(git_mailsplit_usage);
+			continue;
+		} else if ( arg[1] == 'f' ) {
+			nr = strtol(arg+2, NULL, 10);
+		} else if ( arg[1] == 'b' && !arg[2] ) {
+			allow_bare = 1;
+		} else if ( arg[1] == 'o' && arg[2] ) {
+			dir = arg+2;
+		} else if ( arg[1] == '-' && !arg[2] ) {
+			argp++;	/* -- marks end of options */
+			break;
+		} else {
+			die("unknown option: %s", arg);
+		}
+	}
+
+	if ( !dir ) {
+		/* Backwards compatibility: if no -o specified, accept
+		   <mbox> <dir> or just <dir> */
+		switch (argc - (argp-argv)) {
+		case 1:
+			dir = argp[0];
+			argp = stdin_only;
+			break;
+		case 2:
+			stdin_only[0] = argp[0];
+			dir = argp[1];
+			argp = stdin_only;
+			break;
+		default:
+			usage(git_mailsplit_usage);
+		}
+	} else {
+		/* New usage: if no more argument, parse stdin */
+		if ( !*argp )
+			argp = stdin_only;
+	}
+
+	ret = split_mbox(argp, dir, allow_bare, nr_prec, nr);
+	if (ret != -1)
+		printf("%d\n", ret);
+
+	return ret == -1;
+}

File builtin-stripspace.c

+#include <stdio.h>
+#include <string.h>
+#include <ctype.h>
+#include "builtin.h"
+
+/*
+ * Remove empty lines from the beginning and end.
+ *
+ * Turn multiple consecutive empty lines into just one
+ * empty line.  Return true if it is an incomplete line.
+ */
+static int cleanup(char *line)
+{
+	int len = strlen(line);
+
+	if (len && line[len-1] == '\n') {
+		if (len == 1)
+			return 0;
+		do {
+			unsigned char c = line[len-2];
+			if (!isspace(c))
+				break;
+			line[len-2] = '\n';
+			len--;
+			line[len] = 0;
+		} while (len > 1);
+		return 0;
+	}
+	return 1;
+}
+
+void stripspace(FILE *in, FILE *out)
+{
+	int empties = -1;
+	int incomplete = 0;
+	char line[1024];
+
+	while (fgets(line, sizeof(line), in)) {
+		incomplete = cleanup(line);
+
+		/* Not just an empty line? */
+		if (line[0] != '\n') {
+			if (empties > 0)
+				fputc('\n', out);
+			empties = 0;
+			fputs(line, out);
+			continue;
+		}
+		if (empties < 0)
+			continue;
+		empties++;
+	}
+	if (incomplete)
+		fputc('\n', out);
+}
+
+int cmd_stripspace(int argc, const char **argv, char **envp)
+{
+	stripspace(stdin, stdout);
+	return 0;
+}

File builtin-update-index.c

+/*
+ * GIT - The information manager from hell
+ *
+ * Copyright (C) Linus Torvalds, 2005
+ */
+#include "cache.h"
+#include "strbuf.h"
+#include "quote.h"
+#include "cache-tree.h"
+#include "tree-walk.h"
+#include "builtin.h"
+
+/*
+ * Default to not allowing changes to the list of files. The
+ * tool doesn't actually care, but this makes it harder to add
+ * files to the revision control by mistake by doing something
+ * like "git-update-index *" and suddenly having all the object
+ * files be revision controlled.
+ */
+static int allow_add;
+static int allow_remove;
+static int allow_replace;
+static int info_only;
+static int force_remove;
+static int verbose;
+static int mark_valid_only = 0;
+#define MARK_VALID 1
+#define UNMARK_VALID 2
+
+static void report(const char *fmt, ...)
+{
+	va_list vp;
+
+	if (!verbose)
+		return;
+
+	va_start(vp, fmt);
+	vprintf(fmt, vp);
+	putchar('\n');
+	va_end(vp);
+}
+
+static int mark_valid(const char *path)
+{
+	int namelen = strlen(path);
+	int pos = cache_name_pos(path, namelen);
+	if (0 <= pos) {
+		switch (mark_valid_only) {
+		case MARK_VALID:
+			active_cache[pos]->ce_flags |= htons(CE_VALID);
+			break;
+		case UNMARK_VALID:
+			active_cache[pos]->ce_flags &= ~htons(CE_VALID);
+			break;
+		}
+		cache_tree_invalidate_path(active_cache_tree, path);
+		active_cache_changed = 1;
+		return 0;
+	}
+	return -1;
+}
+
+static int add_file_to_cache(const char *path)
+{
+	int size, namelen, option, status;
+	struct cache_entry *ce;
+	struct stat st;
+
+	status = lstat(path, &st);
+
+	/* We probably want to do this in remove_file_from_cache() and
+	 * add_cache_entry() instead...
+	 */
+	cache_tree_invalidate_path(active_cache_tree, path);
+
+	if (status < 0 || S_ISDIR(st.st_mode)) {
+		/* When we used to have "path" and now we want to add
+		 * "path/file", we need a way to remove "path" before
+		 * being able to add "path/file".  However,
+		 * "git-update-index --remove path" would not work.
+		 * --force-remove can be used but this is more user
+		 * friendly, especially since we can do the opposite
+		 * case just fine without --force-remove.
+		 */
+		if (status == 0 || (errno == ENOENT || errno == ENOTDIR)) {
+			if (allow_remove) {
+				if (remove_file_from_cache(path))
+					return error("%s: cannot remove from the index",
+					             path);
+				else
+					return 0;
+			} else if (status < 0) {
+				return error("%s: does not exist and --remove not passed",
+				             path);
+			}
+		}
+		if (0 == status)
+			return error("%s: is a directory - add files inside instead",
+			             path);
+		else
+			return error("lstat(\"%s\"): %s", path,
+				     strerror(errno));
+	}
+
+	namelen = strlen(path);
+	size = cache_entry_size(namelen);
+	ce = xcalloc(1, size);
+	memcpy(ce->name, path, namelen);
+	ce->ce_flags = htons(namelen);
+	fill_stat_cache_info(ce, &st);
+
+	ce->ce_mode = create_ce_mode(st.st_mode);
+	if (!trust_executable_bit) {
+		/* If there is an existing entry, pick the mode bits
+		 * from it.
+		 */
+		int pos = cache_name_pos(path, namelen);
+		if (0 <= pos)
+			ce->ce_mode = active_cache[pos]->ce_mode;
+	}
+
+	if (index_path(ce->sha1, path, &st, !info_only))
+		return -1;
+	option = allow_add ? ADD_CACHE_OK_TO_ADD : 0;
+	option |= allow_replace ? ADD_CACHE_OK_TO_REPLACE : 0;
+	if (add_cache_entry(ce, option))
+		return error("%s: cannot add to the index - missing --add option?",
+			     path);
+	return 0;
+}
+
+static int add_cacheinfo(unsigned int mode, const unsigned char *sha1,
+			 const char *path, int stage)
+{
+	int size, len, option;
+	struct cache_entry *ce;
+
+	if (!verify_path(path))
+		return -1;
+
+	len = strlen(path);
+	size = cache_entry_size(len);
+	ce = xcalloc(1, size);
+
+	memcpy(ce->sha1, sha1, 20);
+	memcpy(ce->name, path, len);
+	ce->ce_flags = create_ce_flags(len, stage);
+	ce->ce_mode = create_ce_mode(mode);
+	if (assume_unchanged)
+		ce->ce_flags |= htons(CE_VALID);
+	option = allow_add ? ADD_CACHE_OK_TO_ADD : 0;
+	option |= allow_replace ? ADD_CACHE_OK_TO_REPLACE : 0;
+	if (add_cache_entry(ce, option))
+		return error("%s: cannot add to the index - missing --add option?",
+			     path);
+	report("add '%s'", path);
+	cache_tree_invalidate_path(active_cache_tree, path);
+	return 0;
+}
+
+static void chmod_path(int flip, const char *path)
+{
+	int pos;
+	struct cache_entry *ce;
+	unsigned int mode;
+
+	pos = cache_name_pos(path, strlen(path));
+	if (pos < 0)
+		goto fail;
+	ce = active_cache[pos];
+	mode = ntohl(ce->ce_mode);
+	if (!S_ISREG(mode))
+		goto fail;
+	switch (flip) {
+	case '+':
+		ce->ce_mode |= htonl(0111); break;
+	case '-':
+		ce->ce_mode &= htonl(~0111); break;
+	default:
+		goto fail;
+	}
+	cache_tree_invalidate_path(active_cache_tree, path);
+	active_cache_changed = 1;
+	report("chmod %cx '%s'", flip, path);
+	return;
+ fail:
+	die("git-update-index: cannot chmod %cx '%s'", flip, path);
+}
+
+static void update_one(const char *path, const char *prefix, int prefix_length)
+{
+	const char *p = prefix_path(prefix, prefix_length, path);
+	if (!verify_path(p)) {
+		fprintf(stderr, "Ignoring path %s\n", path);
+		goto free_return;
+	}
+	if (mark_valid_only) {
+		if (mark_valid(p))
+			die("Unable to mark file %s", path);
+		goto free_return;
+	}
+	cache_tree_invalidate_path(active_cache_tree, path);
+
+	if (force_remove) {
+		if (remove_file_from_cache(p))
+			die("git-update-index: unable to remove %s", path);
+		report("remove '%s'", path);
+		goto free_return;
+	}
+	if (add_file_to_cache(p))
+		die("Unable to process file %s", path);
+	report("add '%s'", path);
+ free_return:
+	if (p < path || p > path + strlen(path))
+		free((char*)p);
+}
+
+static void read_index_info(int line_termination)
+{
+	struct strbuf buf;
+	strbuf_init(&buf);
+	while (1) {
+		char *ptr, *tab;
+		char *path_name;
+		unsigned char sha1[20];
+		unsigned int mode;
+		int stage;
+
+		/* This reads lines formatted in one of three formats:
+		 *
+		 * (1) mode         SP sha1          TAB path
+		 * The first format is what "git-apply --index-info"
+		 * reports, and used to reconstruct a partial tree
+		 * that is used for phony merge base tree when falling
+		 * back on 3-way merge.
+		 *
+		 * (2) mode SP type SP sha1          TAB path
+		 * The second format is to stuff git-ls-tree output
+		 * into the index file.
+		 *
+		 * (3) mode         SP sha1 SP stage TAB path
+		 * This format is to put higher order stages into the
+		 * index file and matches git-ls-files --stage output.
+		 */
+		read_line(&buf, stdin, line_termination);
+		if (buf.eof)
+			break;
+
+		mode = strtoul(buf.buf, &ptr, 8);
+		if (ptr == buf.buf || *ptr != ' ')
+			goto bad_line;
+
+		tab = strchr(ptr, '\t');
+		if (!tab || tab - ptr < 41)
+			goto bad_line;
+
+		if (tab[-2] == ' ' && '0' <= tab[-1] && tab[-1] <= '3') {
+			stage = tab[-1] - '0';
+			ptr = tab + 1; /* point at the head of path */
+			tab = tab - 2; /* point at tail of sha1 */
+		}
+		else {
+			stage = 0;
+			ptr = tab + 1; /* point at the head of path */
+		}
+
+		if (get_sha1_hex(tab - 40, sha1) || tab[-41] != ' ')
+			goto bad_line;
+
+		if (line_termination && ptr[0] == '"')
+			path_name = unquote_c_style(ptr, NULL);
+		else
+			path_name = ptr;
+
+		if (!verify_path(path_name)) {
+			fprintf(stderr, "Ignoring path %s\n", path_name);
+			if (path_name != ptr)
+				free(path_name);
+			continue;
+		}
+		cache_tree_invalidate_path(active_cache_tree, path_name);
+
+		if (!mode) {
+			/* mode == 0 means there is no such path -- remove */
+			if (remove_file_from_cache(path_name))
+				die("git-update-index: unable to remove %s",
+				    ptr);
+		}
+		else {
+			/* mode ' ' sha1 '\t' name
+			 * ptr[-1] points at tab,
+			 * ptr[-41] is at the beginning of sha1
+			 */
+			ptr[-42] = ptr[-1] = 0;
+			if (add_cacheinfo(mode, sha1, path_name, stage))
+				die("git-update-index: unable to update %s",
+				    path_name);
+		}
+		if (path_name != ptr)
+			free(path_name);
+		continue;
+
+	bad_line:
+		die("malformed index info %s", buf.buf);
+	}
+}
+
+static const char update_index_usage[] =
+"git-update-index [-q] [--add] [--replace] [--remove] [--unmerged] [--refresh] [--really-refresh] [--cacheinfo] [--chmod=(+|-)x] [--assume-unchanged] [--info-only] [--force-remove] [--stdin] [--index-info] [--unresolve] [--again] [--ignore-missing] [-z] [--verbose] [--] <file>...";
+
+static unsigned char head_sha1[20];
+static unsigned char merge_head_sha1[20];
+
+static struct cache_entry *read_one_ent(const char *which,
+					unsigned char *ent, const char *path,
+					int namelen, int stage)
+{
+	unsigned mode;
+	unsigned char sha1[20];
+	int size;
+	struct cache_entry *ce;
+
+	if (get_tree_entry(ent, path, sha1, &mode)) {
+		if (which)
+			error("%s: not in %s branch.", path, which);
+		return NULL;
+	}
+	if (mode == S_IFDIR) {
+		if (which)
+			error("%s: not a blob in %s branch.", path, which);
+		return NULL;
+	}
+	size = cache_entry_size(namelen);
+	ce = xcalloc(1, size);
+
+	memcpy(ce->sha1, sha1, 20);
+	memcpy(ce->name, path, namelen);
+	ce->ce_flags = create_ce_flags(namelen, stage);
+	ce->ce_mode = create_ce_mode(mode);
+	return ce;
+}
+
+static int unresolve_one(const char *path)
+{
+	int namelen = strlen(path);
+	int pos;
+	int ret = 0;
+	struct cache_entry *ce_2 = NULL, *ce_3 = NULL;
+
+	/* See if there is such entry in the index. */
+	pos = cache_name_pos(path, namelen);
+	if (pos < 0) {
+		/* If there isn't, either it is unmerged, or
+		 * resolved as "removed" by mistake.  We do not
+		 * want to do anything in the former case.
+		 */
+		pos = -pos-1;
+		if (pos < active_nr) {
+			struct cache_entry *ce = active_cache[pos];
+			if (ce_namelen(ce) == namelen &&
+			    !memcmp(ce->name, path, namelen)) {
+				fprintf(stderr,
+					"%s: skipping still unmerged path.\n",
+					path);
+				goto free_return;
+			}
+		}
+	}
+
+	/* Grab blobs from given path from HEAD and MERGE_HEAD,
+	 * stuff HEAD version in stage #2,
+	 * stuff MERGE_HEAD version in stage #3.
+	 */
+	ce_2 = read_one_ent("our", head_sha1, path, namelen, 2);
+	ce_3 = read_one_ent("their", merge_head_sha1, path, namelen, 3);
+
+	if (!ce_2 || !ce_3) {
+		ret = -1;
+		goto free_return;
+	}
+	if (!memcmp(ce_2->sha1, ce_3->sha1, 20) &&
+	    ce_2->ce_mode == ce_3->ce_mode) {
+		fprintf(stderr, "%s: identical in both, skipping.\n",
+			path);
+		goto free_return;
+	}
+
+	cache_tree_invalidate_path(active_cache_tree, path);
+	remove_file_from_cache(path);
+	if (add_cache_entry(ce_2, ADD_CACHE_OK_TO_ADD)) {
+		error("%s: cannot add our version to the index.", path);
+		ret = -1;
+		goto free_return;
+	}
+	if (!add_cache_entry(ce_3, ADD_CACHE_OK_TO_ADD))
+		return 0;
+	error("%s: cannot add their version to the index.", path);
+	ret = -1;
+ free_return:
+	free(ce_2);
+	free(ce_3);
+	return ret;
+}
+
+static void read_head_pointers(void)
+{
+	if (read_ref(git_path("HEAD"), head_sha1))
+		die("No HEAD -- no initial commit yet?\n");
+	if (read_ref(git_path("MERGE_HEAD"), merge_head_sha1)) {
+		fprintf(stderr, "Not in the middle of a merge.\n");
+		exit(0);
+	}
+}
+
+static int do_unresolve(int ac, const char **av,
+			const char *prefix, int prefix_length)
+{
+	int i;
+	int err = 0;
+
+	/* Read HEAD and MERGE_HEAD; if MERGE_HEAD does not exist, we
+	 * are not doing a merge, so exit with success status.
+	 */
+	read_head_pointers();
+
+	for (i = 1; i < ac; i++) {
+		const char *arg = av[i];
+		const char *p = prefix_path(prefix, prefix_length, arg);
+		err |= unresolve_one(p);
+		if (p < arg || p > arg + strlen(arg))
+			free((char*)p);
+	}
+	return err;
+}
+
+static int do_reupdate(int ac, const char **av,
+		       const char *prefix, int prefix_length)
+{
+	/* Read HEAD and run update-index on paths that are
+	 * merged and already different between index and HEAD.
+	 */
+	int pos;
+	int has_head = 1;
+	const char **pathspec = get_pathspec(prefix, av + 1);
+
+	if (read_ref(git_path("HEAD"), head_sha1))
+		/* If there is no HEAD, that means it is an initial
+		 * commit.  Update everything in the index.
+		 */
+		has_head = 0;
+ redo:
+	for (pos = 0; pos < active_nr; pos++) {
+		struct cache_entry *ce = active_cache[pos];
+		struct cache_entry *old = NULL;
+		int save_nr;
+
+		if (ce_stage(ce) || !ce_path_match(ce, pathspec))
+			continue;
+		if (has_head)
+			old = read_one_ent(NULL, head_sha1,
+					   ce->name, ce_namelen(ce), 0);
+		if (old && ce->ce_mode == old->ce_mode &&
+		    !memcmp(ce->sha1, old->sha1, 20)) {
+			free(old);
+			continue; /* unchanged */
+		}
+		/* Be careful.  The working tree may not have the
+		 * path anymore, in which case, under 'allow_remove',
+		 * or worse yet 'allow_replace', active_nr may decrease.
+		 */
+		save_nr = active_nr;
+		update_one(ce->name + prefix_length, prefix, prefix_length);
+		if (save_nr != active_nr)
+			goto redo;
+	}
+	return 0;
+}
+
+int cmd_update_index(int argc, const char **argv, char **envp)
+{
+	int i, newfd, entries, has_errors = 0, line_termination = '\n';
+	int allow_options = 1;
+	int read_from_stdin = 0;
+	const char *prefix = setup_git_directory();
+	int prefix_length = prefix ? strlen(prefix) : 0;
+	char set_executable_bit = 0;
+	unsigned int refresh_flags = 0;
+	struct lock_file *lock_file;
+
+	git_config(git_default_config);
+
+	/* We can't free this memory, it becomes part of a linked list parsed atexit() */
+	lock_file = xcalloc(1, sizeof(struct lock_file));
+
+	newfd = hold_lock_file_for_update(lock_file, get_index_file());
+	if (newfd < 0)
+		die("unable to create new cachefile");
+
+	entries = read_cache();
+	if (entries < 0)
+		die("cache corrupted");
+
+	for (i = 1 ; i < argc; i++) {
+		const char *path = argv[i];
+
+		if (allow_options && *path == '-') {
+			if (!strcmp(path, "--")) {
+				allow_options = 0;
+				continue;
+			}
+			if (!strcmp(path, "-q")) {
+				refresh_flags |= REFRESH_QUIET;
+				continue;
+			}
+			if (!strcmp(path, "--add")) {
+				allow_add = 1;
+				continue;
+			}
+			if (!strcmp(path, "--replace")) {
+				allow_replace = 1;
+				continue;
+			}
+			if (!strcmp(path, "--remove")) {
+				allow_remove = 1;
+				continue;
+			}
+			if (!strcmp(path, "--unmerged")) {
+				refresh_flags |= REFRESH_UNMERGED;
+				continue;
+			}
+			if (!strcmp(path, "--refresh")) {
+				has_errors |= refresh_cache(refresh_flags);
+				continue;
+			}
+			if (!strcmp(path, "--really-refresh")) {
+				has_errors |= refresh_cache(REFRESH_REALLY | refresh_flags);
+				continue;
+			}
+			if (!strcmp(path, "--cacheinfo")) {
+				unsigned char sha1[20];
+				unsigned int mode;
+
+				if (i+3 >= argc)
+					die("git-update-index: --cacheinfo <mode> <sha1> <path>");
+
+				if ((sscanf(argv[i+1], "%o", &mode) != 1) ||
+				    get_sha1_hex(argv[i+2], sha1) ||
+				    add_cacheinfo(mode, sha1, argv[i+3], 0))
+					die("git-update-index: --cacheinfo"
+					    " cannot add %s", argv[i+3]);
+				i += 3;
+				continue;
+			}
+			if (!strcmp(path, "--chmod=-x") ||
+			    !strcmp(path, "--chmod=+x")) {
+				if (argc <= i+1)
+					die("git-update-index: %s <path>", path);
+				set_executable_bit = path[8];
+				continue;
+			}
+			if (!strcmp(path, "--assume-unchanged")) {
+				mark_valid_only = MARK_VALID;
+				continue;
+			}
+			if (!strcmp(path, "--no-assume-unchanged")) {
+				mark_valid_only = UNMARK_VALID;
+				continue;
+			}
+			if (!strcmp(path, "--info-only")) {
+				info_only = 1;
+				continue;
+			}
+			if (!strcmp(path, "--force-remove")) {
+				force_remove = 1;
+				continue;
+			}
+			if (!strcmp(path, "-z")) {
+				line_termination = 0;
+				continue;
+			}
+			if (!strcmp(path, "--stdin")) {
+				if (i != argc - 1)
+					die("--stdin must be at the end");
+				read_from_stdin = 1;
+				break;
+			}
+			if (!strcmp(path, "--index-info")) {
+				if (i != argc - 1)
+					die("--index-info must be at the end");
+				allow_add = allow_replace = allow_remove = 1;
+				read_index_info(line_termination);
+				break;
+			}
+			if (!strcmp(path, "--unresolve")) {
+				has_errors = do_unresolve(argc - i, argv + i,
+							  prefix, prefix_length);
+				if (has_errors)
+					active_cache_changed = 0;
+				goto finish;
+			}
+			if (!strcmp(path, "--again")) {
+				has_errors = do_reupdate(argc - i, argv + i,
+							 prefix, prefix_length);
+				if (has_errors)
+					active_cache_changed = 0;
+				goto finish;
+			}
+			if (!strcmp(path, "--ignore-missing")) {
+				refresh_flags |= REFRESH_IGNORE_MISSING;
+				continue;
+			}
+			if (!strcmp(path, "--verbose")) {
+				verbose = 1;
+				continue;
+			}
+			if (!strcmp(path, "-h") || !strcmp(path, "--help"))
+				usage(update_index_usage);
+			die("unknown option %s", path);
+		}
+		update_one(path, prefix, prefix_length);
+		if (set_executable_bit)
+			chmod_path(set_executable_bit, path);
+	}
+	if (read_from_stdin) {
+		struct strbuf buf;
+		strbuf_init(&buf);
+		while (1) {
+			char *path_name;
+			const char *p;
+			read_line(&buf, stdin, line_termination);
+			if (buf.eof)
+				break;
+			if (line_termination && buf.buf[0] == '"')
+				path_name = unquote_c_style(buf.buf, NULL);
+			else
+				path_name = buf.buf;
+			p = prefix_path(prefix, prefix_length, path_name);
+			update_one(p, NULL, 0);
+			if (set_executable_bit)
+				chmod_path(set_executable_bit, p);
+			if (p < path_name || p > path_name + strlen(path_name))
+				free((char*) p);
+			if (path_name != buf.buf)
+				free(path_name);
+		}
+	}
+
+ finish:
+	if (active_cache_changed) {
+		if (write_cache(newfd, active_cache, active_nr) ||
+		    commit_lock_file(lock_file))
+			die("Unable to write new index file");
+	}
+
+	rollback_lock_file(lock_file);
+
+	return has_errors ? 1 : 0;
+}

File builtin-update-ref.c

+#include "cache.h"
+#include "refs.h"
+#include "builtin.h"
+
+static const char git_update_ref_usage[] =
+"git-update-ref <refname> <value> [<oldval>] [-m <reason>]";
+
+int cmd_update_ref(int argc, const char **argv, char **envp)
+{
+	const char *refname=NULL, *value=NULL, *oldval=NULL, *msg=NULL;
+	struct ref_lock *lock;
+	unsigned char sha1[20], oldsha1[20];
+	int i;
+
+	setup_git_directory();
+	git_config(git_default_config);
+
+	for (i = 1; i < argc; i++) {
+		if (!strcmp("-m", argv[i])) {
+			if (i+1 >= argc)
+				usage(git_update_ref_usage);
+			msg = argv[++i];
+			if (!*msg)
+				die("Refusing to perform update with empty message.");
+			if (strchr(msg, '\n'))
+				die("Refusing to perform update with \\n in message.");
+			continue;
+		}
+		if (!refname) {
+			refname = argv[i];
+			continue;
+		}
+		if (!value) {
+			value = argv[i];
+			continue;
+		}
+		if (!oldval) {
+			oldval = argv[i];
+			continue;
+		}
+	}
+	if (!refname || !value)
+		usage(git_update_ref_usage);
+
+	if (get_sha1(value, sha1))
+		die("%s: not a valid SHA1", value);
+	memset(oldsha1, 0, 20);
+	if (oldval && get_sha1(oldval, oldsha1))
+		die("%s: not a valid old SHA1", oldval);
+
+	lock = lock_any_ref_for_update(refname, oldval ? oldsha1 : NULL, 0);
+	if (!lock)
+		return 1;
+	if (write_ref_sha1(lock, sha1, msg) < 0)
+		return 1;
+
+	/* write_ref_sha1 always unlocks the ref, no need to do it explicitly */
+	return 0;
+}

File builtin-write-tree.c

+/*
+ * GIT - The information manager from hell
+ *
+ * Copyright (C) Linus Torvalds, 2005
+ */
+#include "builtin.h"
+#include "cache.h"
+#include "tree.h"
+#include "cache-tree.h"
+
+static const char write_tree_usage[] =
+"git-write-tree [--missing-ok] [--prefix=<prefix>/]";
+
+int write_tree(unsigned char *sha1, int missing_ok, const char *prefix)
+{
+	int entries, was_valid, newfd;
+
+	/* We can't free this memory, it becomes part of a linked list parsed atexit() */
+	struct lock_file *lock_file = xcalloc(1, sizeof(struct lock_file));
+
+	newfd = hold_lock_file_for_update(lock_file, get_index_file());
+
+	entries = read_cache();
+	if (entries < 0)
+		die("git-write-tree: error reading cache");
+
+	if (!active_cache_tree)
+		active_cache_tree = cache_tree();
+
+	was_valid = cache_tree_fully_valid(active_cache_tree);
+
+	if (!was_valid) {
+		if (cache_tree_update(active_cache_tree,
+				      active_cache, active_nr,
+				      missing_ok, 0) < 0)
+			die("git-write-tree: error building trees");
+		if (0 <= newfd) {
+			if (!write_cache(newfd, active_cache, active_nr))
+				commit_lock_file(lock_file);
+		}
+		/* Not being able to write is fine -- we are only interested
+		 * in updating the cache-tree part, and if the next caller
+		 * ends up using the old index with unupdated cache-tree part
+		 * it misses the work we did here, but that is just a
+		 * performance penalty and not a big deal.
+		 */
+	}
+
+	if (prefix) {
+		struct cache_tree *subtree =
+			cache_tree_find(active_cache_tree, prefix);
+		memcpy(sha1, subtree->sha1, 20);
+	}
+	else
+		memcpy(sha1, active_cache_tree->sha1, 20);
+
+	rollback_lock_file(lock_file);
+
+	return 0;
+}
+
+int cmd_write_tree(int argc, const char **argv, char **envp)
+{
+	int missing_ok = 0, ret;
+	const char *prefix = NULL;
+	unsigned char sha1[20];
+
+	setup_git_directory();
+
+	while (1 < argc) {
+		const char *arg = argv[1];
+		if (!strcmp(arg, "--missing-ok"))
+			missing_ok = 1;
+		else if (!strncmp(arg, "--prefix=", 9))
+			prefix = arg + 9;
+		else
+			die(write_tree_usage);
+		argc--; argv++;
+	}
+
+	if (argc > 2)
+		die("too many options");
+
+	ret = write_tree(sha1, missing_ok, prefix);
+	printf("%s\n", sha1_to_hex(sha1));
+
+	return ret;
+}
 #ifndef BUILTIN_H
 #define BUILTIN_H
 
+#include <stdio.h>
+
 #ifndef PATH_MAX
 # define PATH_MAX 4096
 #endif
 extern int cmd_diff_tree(int argc, const char **argv, char **envp);
 extern int cmd_cat_file(int argc, const char **argv, char **envp);
 extern int cmd_rev_parse(int argc, const char **argv, char **envp);
+extern int cmd_update_index(int argc, const char **argv, char **envp);
+extern int cmd_update_ref(int argc, const char **argv, char **envp);
+
+extern int cmd_write_tree(int argc, const char **argv, char **envp);
+extern int write_tree(unsigned char *sha1, int missing_ok, const char *prefix);
+
+extern int cmd_mailsplit(int argc, const char **argv, char **envp);
+extern int split_mbox(const char **mbox, const char *dir, int allow_bare, int nr_prec, int skip);
+
+extern int cmd_mailinfo(int argc, const char **argv, char **envp);
+extern int mailinfo(FILE *in, FILE *out, int ks, const char *encoding, const char *msg, const char *patch);
 
+extern int cmd_stripspace(int argc, const char **argv, char **envp);
+extern void stripspace(FILE *in, FILE *out);
 #endif
 		{ "diff-stages", cmd_diff_stages },
 		{ "diff-tree", cmd_diff_tree },
 		{ "cat-file", cmd_cat_file },
-		{ "rev-parse", cmd_rev_parse }
+		{ "rev-parse", cmd_rev_parse },
+		{ "write-tree", cmd_write_tree },
+		{ "mailsplit", cmd_mailsplit },
+		{ "mailinfo", cmd_mailinfo },
+		{ "stripspace", cmd_stripspace },
+		{ "update-index", cmd_update_index },
+		{ "update-ref", cmd_update_ref }
 	};
 	int i;
 

File mailinfo.c

-/*
- * Another stupid program, this one parsing the headers of an
- * email to figure out authorship and subject
- */
-#define _GNU_SOURCE
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <ctype.h>
-#ifndef NO_ICONV
-#include <iconv.h>
-#endif
-#include "git-compat-util.h"
-#include "cache.h"
-
-static FILE *cmitmsg, *patchfile;
-
-static int keep_subject = 0;
-static char *metainfo_charset = NULL;
-static char line[1000];
-static char date[1000];
-static char name[1000];
-static char email[1000];
-static char subject[1000];
-
-static enum  {
-	TE_DONTCARE, TE_QP, TE_BASE64,
-} transfer_encoding;
-static char charset[256];
-
-static char multipart_boundary[1000];
-static int multipart_boundary_len;
-static int patch_lines = 0;
-
-static char *sanity_check(char *name, char *email)
-{
-	int len = strlen(name);
-	if (len < 3 || len > 60)
-		return email;
-	if (strchr(name, '@') || strchr(name, '<') || strchr(name, '>'))
-		return email;
-	return name;
-}
-
-static int bogus_from(char *line)
-{
-	/* John Doe <johndoe> */
-	char *bra, *ket, *dst, *cp;
-
-	/* This is fallback, so do not bother if we already have an
-	 * e-mail address.
-	 */ 
-	if (*email)
-		return 0;
-
-	bra = strchr(line, '<');
-	if (!bra)
-		return 0;
-	ket = strchr(bra, '>');
-	if (!ket)
-		return 0;
-
-	for (dst = email, cp = bra+1; cp < ket; )
-		*dst++ = *cp++;
-	*dst = 0;
-	for (cp = line; isspace(*cp); cp++)
-		;
-	for (bra--; isspace(*bra); bra--)
-		*bra = 0;
-	cp = sanity_check(cp, email);
-	strcpy(name, cp);
-	return 1;
-}
-
-static int handle_from(char *in_line)
-{
-	char line[1000];
-	char *at;
-	char *dst;
-
-	strcpy(line, in_line);
-	at = strchr(line, '@');
-	if (!at)
-		return bogus_from(line);
-
-	/*
-	 * If we already have one email, don't take any confusing lines
-	 */
-	if (*email && strchr(at+1, '@'))
-		return 0;
-
-	/* Pick up the string around '@', possibly delimited with <>
-	 * pair; that is the email part.  White them out while copying.
-	 */
-	while (at > line) {
-		char c = at[-1];
-		if (isspace(c))
-			break;
-		if (c == '<') {
-			at[-1] = ' ';
-			break;
-		}
-		at--;
-	}
-	dst = email;
-	for (;;) {
-		unsigned char c = *at;
-		if (!c || c == '>' || isspace(c)) {
-			if (c == '>')
-				*at = ' ';
-			break;
-		}
-		*at++ = ' ';
-		*dst++ = c;
-	}
-	*dst++ = 0;
-
-	/* The remainder is name.  It could be "John Doe <john.doe@xz>"
-	 * or "john.doe@xz (John Doe)", but we have whited out the
-	 * email part, so trim from both ends, possibly removing
-	 * the () pair at the end.
-	 */
-	at = line + strlen(line);
-	while (at > line) {
-		unsigned char c = *--at;
-		if (!isspace(c)) {
-			at[(c == ')') ? 0 : 1] = 0;
-			break;
-		}
-	}
-
-	at = line;
-	for (;;) {
-		unsigned char c = *at;
-		if (!c || !isspace(c)) {
-			if (c == '(')
-				at++;
-			break;
-		}
-		at++;