git@vger.kernel.org mailing list mirror (one of many)
 help / color / mirror / code / Atom feed
From: "Kristian Høgsberg" <krh@redhat.com>
To: git@vger.kernel.org
Cc: "Kristian Høgsberg" <krh@redhat.com>
Subject: [PATCH] Implement git commit as a builtin.
Date: Wed, 18 Jul 2007 15:19:39 -0400	[thread overview]
Message-ID: <11847863792344-git-send-email-krh@redhat.com> (raw)

---
Here's another update on the work in progress.  At this point, the C
version is almost complete, there's only a few issues left (look for
FIXME in builtin-commit.c).  I've added a commit test case which should
be split out in a patch on its own, but the good news is that it
successfully exercises most of the command line options and the C version
passes.

My plan for the remainder of the work is still to wrap up the last few
pieces of functionality and then start taking this big patch apart in a
number of more manageable pieces.  However, the bulk of this work will
still be a big patch that removes git-commit.sh and adds builtin-commit
in one swoop.

Kristian


 Makefile              |    9 +-
 builtin-add.c         |   14 +-
 builtin-commit-tree.c |  120 +++++---
 builtin-commit.c      |  746 +++++++++++++++++++++++++++++++++++++++++++++++++
 builtin.h             |    3 +-
 cache.h               |    3 +-
 color.c               |   18 +-
 color.h               |    4 +-
 commit.h              |    9 +
 git-commit.sh         |  658 -------------------------------------------
 git.c                 |    3 +-
 mktag.c               |    8 +-
 sha1_file.c           |   44 ++-
 t/t7800-commit.sh     |  126 +++++++++
 wt-status.c           |   86 +++---
 wt-status.h           |    4 +
 16 files changed, 1066 insertions(+), 789 deletions(-)
 create mode 100644 builtin-commit.c
 delete mode 100755 git-commit.sh
 create mode 100644 t/t7800-commit.sh

diff --git a/Makefile b/Makefile
index 0f75955..967d5a5 100644
--- a/Makefile
+++ b/Makefile
@@ -198,7 +198,7 @@ BASIC_LDFLAGS =
 
 SCRIPT_SH = \
 	git-bisect.sh git-checkout.sh \
-	git-clean.sh git-clone.sh git-commit.sh \
+	git-clean.sh git-clone.sh \
 	git-fetch.sh \
 	git-ls-remote.sh \
 	git-merge-one-file.sh git-mergetool.sh git-parse-remote.sh \
@@ -257,7 +257,7 @@ EXTRA_PROGRAMS =
 BUILT_INS = \
 	git-format-patch$X git-show$X git-whatchanged$X git-cherry$X \
 	git-get-tar-commit-id$X git-init$X git-repo-config$X \
-	git-fsck-objects$X git-cherry-pick$X \
+	git-fsck-objects$X git-cherry-pick$X git-status$X\
 	$(patsubst builtin-%.o,git-%$X,$(BUILTIN_OBJS))
 
 # what 'all' will build and 'install' will install, in gitexecdir
@@ -332,6 +332,7 @@ BUILTIN_OBJS = \
 	builtin-check-attr.o \
 	builtin-checkout-index.o \
 	builtin-check-ref-format.o \
+	builtin-commit.o \
 	builtin-commit-tree.o \
 	builtin-count-objects.o \
 	builtin-describe.o \
@@ -367,7 +368,6 @@ BUILTIN_OBJS = \
 	builtin-rev-parse.o \
 	builtin-revert.o \
 	builtin-rm.o \
-	builtin-runstatus.o \
 	builtin-shortlog.o \
 	builtin-show-branch.o \
 	builtin-stripspace.o \
@@ -791,9 +791,6 @@ $(patsubst %.perl,%,$(SCRIPT_PERL)): % : %.perl
 	chmod +x $@+ && \
 	mv $@+ $@
 
-git-status: git-commit
-	$(QUIET_GEN)cp $< $@+ && mv $@+ $@
-
 gitweb/gitweb.cgi: gitweb/gitweb.perl
 	$(QUIET_GEN)rm -f $@ $@+ && \
 	sed -e '1s|#!.*perl|#!$(PERL_PATH_SQ)|' \
diff --git a/builtin-add.c b/builtin-add.c
index 1591171..bcd796d 100644
--- a/builtin-add.c
+++ b/builtin-add.c
@@ -8,6 +8,7 @@
 #include "dir.h"
 #include "exec_cmd.h"
 #include "cache-tree.h"
+#include "run-command.h"
 #include "diff.h"
 #include "diffcore.h"
 #include "commit.h"
@@ -148,6 +149,13 @@ static int git_add_config(const char *var, const char *value)
 	return git_default_config(var, value);
 }
 
+int interactive_add(void)
+{
+	const char *argv[2] = { "add--interactive", NULL };
+
+	return run_command_v_opt(argv, RUN_GIT_CMD);
+}
+
 static struct lock_file lock_file;
 
 static const char ignore_warning[] =
@@ -167,11 +175,9 @@ int cmd_add(int argc, const char **argv, const char *prefix)
 			add_interactive++;
 	}
 	if (add_interactive) {
-		const char *args[] = { "add--interactive", NULL };
-
-		if (add_interactive != 1 || argc != 2)
+		if (argc != 2)
 			die("add --interactive does not take any parameters");
-		execv_git_cmd(args);
+		interactive_add();
 		exit(1);
 	}
 
diff --git a/builtin-commit-tree.c b/builtin-commit-tree.c
index ccbcbe3..bb20470 100644
--- a/builtin-commit-tree.c
+++ b/builtin-commit-tree.c
@@ -20,17 +20,11 @@ static void init_buffer(char **bufp, unsigned int *sizep)
 	*sizep = 0;
 }
 
-static void add_buffer(char **bufp, unsigned int *sizep, const char *fmt, ...)
+static void add_chunk(char **bufp, unsigned int *sizep, const char *data, int len)
 {
-	char one_line[2048];
-	va_list args;
-	int len;
 	unsigned long alloc, size, newsize;
 	char *buf;
 
-	va_start(args, fmt);
-	len = vsnprintf(one_line, sizeof(one_line), fmt, args);
-	va_end(args);
 	size = *sizep;
 	newsize = size + len + 1;
 	alloc = (size + 32767) & ~32767;
@@ -41,7 +35,19 @@ static void add_buffer(char **bufp, unsigned int *sizep, const char *fmt, ...)
 		*bufp = buf;
 	}
 	*sizep = newsize - 1;
-	memcpy(buf + size, one_line, len);
+	memcpy(buf + size, data, len);
+}
+
+static void add_buffer(char **bufp, unsigned int *sizep, const char *fmt, ...)
+{
+	char one_line[2048];
+	va_list args;
+	int len;
+
+	va_start(args, fmt);
+	len = vsnprintf(one_line, sizeof(one_line), fmt, args);
+	va_end(args);
+	add_chunk(bufp, sizep, one_line, len);
 }
 
 static void check_valid(unsigned char *sha1, enum object_type expect)
@@ -81,39 +87,16 @@ static const char commit_utf8_warn[] =
 "You may want to amend it after fixing the message, or set the config\n"
 "variable i18n.commitencoding to the encoding your project uses.\n";
 
-int cmd_commit_tree(int argc, const char **argv, const char *prefix)
+const unsigned char *
+create_commit(const unsigned char *tree_sha1,
+	      unsigned char parent_sha1[][20], int parents,
+	      const char *author_info, const char *committer_info,
+	      const char *message, int length)
 {
-	int i;
-	int parents = 0;
-	unsigned char tree_sha1[20];
-	unsigned char commit_sha1[20];
-	char comment[1000];
+	static unsigned char commit_sha1[20];
+	int encoding_is_utf8, i;
 	char *buffer;
 	unsigned int size;
-	int encoding_is_utf8;
-
-	git_config(git_default_config);
-
-	if (argc < 2)
-		usage(commit_tree_usage);
-	if (get_sha1(argv[1], tree_sha1))
-		die("Not a valid object name %s", argv[1]);
-
-	check_valid(tree_sha1, OBJ_TREE);
-	for (i = 2; i < argc; i += 2) {
-		const char *a, *b;
-		a = argv[i]; b = argv[i+1];
-		if (!b || strcmp(a, "-p"))
-			usage(commit_tree_usage);
-
-		if (parents >= MAXPARENT)
-			die("Too many parents (%d max)", MAXPARENT);
-		if (get_sha1(b, parent_sha1[parents]))
-			die("Not a valid object name %s", b);
-		check_valid(parent_sha1[parents], OBJ_COMMIT);
-		if (new_parent(parents))
-			parents++;
-	}
 
 	/* Not having i18n.commitencoding is the same as having utf-8 */
 	encoding_is_utf8 = is_encoding_utf8(git_commit_encoding);
@@ -130,26 +113,71 @@ int cmd_commit_tree(int argc, const char **argv, const char *prefix)
 		add_buffer(&buffer, &size, "parent %s\n", sha1_to_hex(parent_sha1[i]));
 
 	/* Person/date information */
-	add_buffer(&buffer, &size, "author %s\n", git_author_info(1));
-	add_buffer(&buffer, &size, "committer %s\n", git_committer_info(1));
+	add_buffer(&buffer, &size, "author %s\n", author_info);
+	add_buffer(&buffer, &size, "committer %s\n", committer_info);
 	if (!encoding_is_utf8)
 		add_buffer(&buffer, &size,
 				"encoding %s\n", git_commit_encoding);
 	add_buffer(&buffer, &size, "\n");
 
 	/* And add the comment */
-	while (fgets(comment, sizeof(comment), stdin) != NULL)
-		add_buffer(&buffer, &size, "%s", comment);
+	add_chunk(&buffer, &size, message, length);
 
 	/* And check the encoding */
 	buffer[size] = '\0';
 	if (encoding_is_utf8 && !is_utf8(buffer))
 		fprintf(stderr, commit_utf8_warn);
 
-	if (!write_sha1_file(buffer, size, commit_type, commit_sha1)) {
-		printf("%s\n", sha1_to_hex(commit_sha1));
-		return 0;
+	if (!write_sha1_file(buffer, size, commit_type, commit_sha1))
+		return commit_sha1;
+
+	return NULL;
+}
+
+int cmd_commit_tree(int argc, const char **argv, const char *prefix)
+{
+	int i;
+	int parents = 0;
+	unsigned char tree_sha1[20];
+	char *buffer;
+	const unsigned char *commit_sha1;
+	unsigned long length;
+
+	git_config(git_default_config);
+
+	if (argc < 2)
+		usage(commit_tree_usage);
+	if (get_sha1(argv[1], tree_sha1))
+		die("Not a valid object name %s", argv[1]);
+
+	check_valid(tree_sha1, OBJ_TREE);
+	for (i = 2; i < argc; i += 2) {
+		const char *a, *b;
+		a = argv[i]; b = argv[i+1];
+		if (!b || strcmp(a, "-p"))
+			usage(commit_tree_usage);
+
+		if (parents >= MAXPARENT)
+			die("Too many parents (%d max)", MAXPARENT);
+		if (get_sha1(b, parent_sha1[parents]))
+			die("Not a valid object name %s", b);
+		check_valid(parent_sha1[parents], OBJ_COMMIT);
+		if (new_parent(parents))
+			parents++;
 	}
-	else
+
+	if (read_fd(0, &buffer, &length))
+		die("Could not read commit message from standard input");
+
+	commit_sha1 = create_commit(tree_sha1,
+				    parent_sha1, parents,
+				    git_author_info(1),	    
+				    git_committer_info(1),	    
+				    buffer, length);
+
+	if (!commit_sha1)
 		return 1;
+
+	printf("%s\n", sha1_to_hex(commit_sha1));
+	return 0;
 }
diff --git a/builtin-commit.c b/builtin-commit.c
new file mode 100644
index 0000000..198d5af
--- /dev/null
+++ b/builtin-commit.c
@@ -0,0 +1,746 @@
+/*
+ * Builtin "git commit"
+ *
+ * Copyright (c) 2007 Kristian Høgsberg <krh@redhat.com>
+ * Based on git-commit.sh by Junio C Hamano and Linus Torvalds
+ */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include "cache.h"
+#include "cache-tree.h"
+#include "builtin.h"
+#include "diff.h"
+#include "diffcore.h"
+#include "commit.h"
+#include "revision.h"
+#include "wt-status.h"
+#include "run-command.h"
+#include "refs.h"
+#include "log-tree.h"
+
+static const char builtin_commit_usage[] =
+	"[-a | --interactive] [-s] [-v] [--no-verify] [-m <message> | -F <logfile> | (-C|-c) <commit> | --amend] [-u] [-e] [--author <author>] [[-i | -o] <path>...]";
+
+static unsigned char head_sha1[20], merge_head_sha1[20];
+static struct commit *use_message_commit;
+static const char commit_editmsg[] = "COMMIT_EDITMSG";
+static struct lock_file lock_file;
+
+enum option_type {
+    OPTION_NONE,
+    OPTION_STRING,
+    OPTION_INTEGER,
+    OPTION_LAST,
+};
+
+struct option {
+    enum option_type type;
+    const char *long_name;
+    char short_name;
+    void *value;
+};
+
+static int scan_options(const char ***argv, struct option *options)
+{
+	const char *value, *eq;
+	int i;
+
+	if (**argv == NULL)
+		return 0;
+	if ((**argv)[0] != '-')
+		return 0;
+	if (!strcmp(**argv, "--"))
+		return 0;
+
+	value = NULL;
+	for (i = 0; options[i].type != OPTION_LAST; i++) {
+		if ((**argv)[1] == '-') {
+			if (!prefixcmp(options[i].long_name, **argv + 2)) {
+				if (options[i].type != OPTION_NONE)
+					value = *++(*argv);
+				goto match;
+			}
+
+			eq = strchr(**argv + 2, '=');
+			if (eq && options[i].type != OPTION_NONE &&
+			    !strncmp(**argv + 2, 
+				     options[i].long_name, eq - **argv - 2)) {
+				value = eq + 1;
+				goto match;
+			}
+		}
+
+		if ((**argv)[1] == options[i].short_name) {
+			if ((**argv)[2] == '\0') {
+				if (options[i].type != OPTION_NONE)
+					value = *++(*argv);
+				goto match;
+			}
+
+			if (options[i].type != OPTION_NONE) {
+				value = **argv + 2;
+				goto match;
+			}
+		}
+	}
+
+	usage(builtin_commit_usage);
+
+ match:
+	switch (options[i].type) {
+	case OPTION_NONE:
+		*(int *)options[i].value = 1;
+		break;
+	case OPTION_STRING:
+		if (value == NULL)
+			die("option %s requires a value.", (*argv)[-1]);
+		*(const char **)options[i].value = value;
+		break;
+	case OPTION_INTEGER:
+		if (value == NULL)
+			die("option %s requires a value.", (*argv)[-1]);
+		*(int *)options[i].value = atoi(value);
+		break;
+	default:
+		assert(0);
+	}
+
+	(*argv)++;
+
+	return 1;
+}
+
+static char *logfile, *force_author, *message;
+static char *edit_message, *use_message;
+static int all, edit_flag, also, interactive, only, no_verify, amend, signoff;
+static int quiet, verbose, untracked_files;
+
+static int no_edit, initial_commit, in_merge;
+const char *only_include_assumed;
+
+static struct option commit_options[] = {
+	{ OPTION_STRING, "file", 'F', (void *) &logfile },
+	{ OPTION_NONE, "all", 'a', &all },
+	{ OPTION_STRING, "author", 0, (void *) &force_author },
+	{ OPTION_NONE, "edit", 0, &edit_flag },
+	{ OPTION_NONE, "include", 'i', &also },
+	{ OPTION_NONE, "interactive", 0, &interactive },
+	{ OPTION_NONE, "only", 'o', &only },
+	{ OPTION_STRING, "message", 'm', &message },
+	{ OPTION_NONE, "no-verify", 'n', &no_verify },
+	{ OPTION_NONE, "amend", 0, &amend },
+	{ OPTION_STRING, "reedit-message", 'c', &edit_message },
+	{ OPTION_STRING, "reuse-message", 'C', &use_message },
+	{ OPTION_NONE, "signoff", 's', &signoff },
+	{ OPTION_NONE, "quiet", 'q', &quiet },
+	{ OPTION_NONE, "verbose", 'v', &verbose },
+	{ OPTION_NONE, "untracked-files", 0, &untracked_files },
+	{ OPTION_LAST },
+};
+
+/* FIXME: Taken from builtin-add, should be shared. */
+
+static void update_callback(struct diff_queue_struct *q,
+			    struct diff_options *opt, void *cbdata)
+{
+	int i, verbose;
+
+	verbose = *((int *)cbdata);
+	for (i = 0; i < q->nr; i++) {
+		struct diff_filepair *p = q->queue[i];
+		const char *path = p->one->path;
+		switch (p->status) {
+		default:
+			die("unexpacted diff status %c", p->status);
+		case DIFF_STATUS_UNMERGED:
+		case DIFF_STATUS_MODIFIED:
+			add_file_to_cache(path, verbose);
+			break;
+		case DIFF_STATUS_DELETED:
+			remove_file_from_cache(path);
+			if (verbose)
+				printf("remove '%s'\n", path);
+			break;
+		}
+	}
+}
+
+static void
+add_files_to_cache(int fd, const char **files, const char *prefix)
+{
+	struct rev_info rev;
+
+	init_revisions(&rev, "");
+	setup_revisions(0, NULL, &rev, NULL);
+	rev.prune_data = get_pathspec(prefix, files);
+	rev.diffopt.output_format |= DIFF_FORMAT_CALLBACK;
+	rev.diffopt.format_callback = update_callback;
+	rev.diffopt.format_callback_data = &verbose;
+
+	run_diff_files(&rev, 0);
+
+	if (write_cache(fd, active_cache, active_nr) || close(fd))
+		die("unable to write new index file");
+}
+
+static char *
+prepare_index(const char **files, const char *prefix)
+{
+	int fd;
+	struct tree *tree;
+	struct lock_file *next_index_lock;
+
+	fd = hold_locked_index(&lock_file, 1);
+	if (read_cache() < 0)
+		die("index file corrupt");
+
+	if (all) {
+		add_files_to_cache(fd, files, NULL);
+		return lock_file.filename;
+	} else if (also) {
+		add_files_to_cache(fd, files, prefix);
+		return lock_file.filename;
+	}
+
+	if (interactive)
+		interactive_add();
+
+	if (*files == NULL) {
+		/* Commit index as-is. */
+		rollback_lock_file(&lock_file);
+		return get_index_file();
+	}
+
+	/*
+	 * FIXME: Warn on unknown files.  Shell script does
+	 *
+	 *   commit_only=`git-ls-files --error-unmatch -- "$@"`
+	 */
+
+	/*
+	 * FIXME: shell script does
+	 *
+	 *   git-read-tree --index-output="$TMP_INDEX" -i -m HEAD
+	 *
+	 * which warns about unmerged files in the index.
+	 */
+
+	/* update the user index file */
+	add_files_to_cache(fd, files, prefix);
+
+	tree = parse_tree_indirect(head_sha1);
+	if (!tree)
+		die("failed to unpack HEAD tree object");
+	if (read_tree(tree, 0, NULL))
+		die("failed to read HEAD tree object");
+
+	/* Uh oh, abusing lock_file to create a garbage collected file */
+	next_index_lock = xmalloc(sizeof(*next_index_lock));
+	fd = hold_lock_file_for_update(next_index_lock,
+				       git_path("next-index-%d", getpid()), 1);
+	add_files_to_cache(fd, files, prefix);
+
+	return next_index_lock->filename;
+}
+
+static int strip_lines(char *buffer, int len)
+{
+	int blank_lines, i, j;
+	char *eol;
+
+	blank_lines = 1;
+	for (i = 0, j = 0; i < len; i++) {
+		if (blank_lines > 0 && buffer[i] == '#') {
+			eol = strchr(buffer + i, '\n');
+			if (!eol)
+				break;
+
+			i = eol - buffer;
+			continue;
+		}
+
+		if (buffer[i] == '\n') {
+			blank_lines++;
+			if (blank_lines > 1)
+				continue;
+		} else {
+			if (blank_lines > 2)
+				buffer[j++] = '\n';
+			blank_lines = 0;
+		}
+
+		buffer[j++] = buffer[i];
+	}
+
+	if (buffer[j - 1] != '\n')
+               buffer[j++] = '\n';
+
+	return j;
+}
+
+static int run_status(FILE *fp, const char *index_file)
+{
+	struct wt_status s;
+
+	wt_status_prepare(&s);
+
+	if (amend) {
+		s.amend = 1;
+		s.reference = "HEAD^1";
+	}
+	s.verbose = verbose;
+	s.untracked = untracked_files;
+	s.index_file = index_file;
+	s.fp = fp;
+
+	wt_status_print(&s);
+
+	return s.commitable;
+}
+
+static const char sign_off_header[] = "Signed-off-by: ";
+
+static int prepare_log_message(const char *index_file)
+{
+	char *buffer = NULL;
+	struct stat statbuf;
+	int commitable;
+	unsigned long len;
+	FILE *fp;
+
+	if (message) {
+		buffer = message;
+		len = strlen(message);
+	} else if (logfile && !strcmp(logfile, "-")) {
+		if (isatty(0))
+			fprintf(stderr, "(reading log message from standard input)\n");
+		if (read_fd(0, &buffer, &len))
+			die("could not read log from standard input");
+	} else if (logfile) {
+		if (read_path(logfile, &buffer, &len))
+			die("could not read log file '%s': %s",
+			    logfile, strerror(errno));
+	} else if (use_message) {
+		/* FIXME: encoding */
+		buffer = strstr(use_message_commit->buffer, "\n\n");
+		if (!buffer || buffer[2] == '\0')
+			die("commit has empty message");
+		buffer += 2;
+		len = strlen(buffer);
+	} else if (!stat(git_path("MERGE_MSG"), &statbuf)) {
+		if (read_path(git_path("MERGE_MSG"), &buffer, &len))
+			die("could not read MERGE_MSG: %s", strerror(errno));
+	} else if (!stat(git_path("SQUASH_MSG"), &statbuf)) {
+		if (read_path(git_path("SQUASH_MSG"), &buffer, &len))
+			die("could not read SQUASH_MSG: %s", strerror(errno));
+	}
+
+	fp = fopen(git_path(commit_editmsg), "w");
+	if (fp == NULL)
+		die("could not open %s\n", git_path(commit_editmsg));
+		
+	if (buffer) {
+		len = strip_lines(buffer, len);
+
+		if (fwrite(buffer, 1, len, fp) < len)
+			die("could not write commit template: %s\n",
+			    strerror(errno));
+	}
+
+	if (signoff) {
+		const char *info, *bol;
+
+		info = git_committer_info(1);
+		if (buffer) {
+			bol = strrchr(buffer + len - 1, '\n');
+			if (!bol || prefixcmp(bol, sign_off_header))
+				fprintf(fp, "\n");
+		}
+		fprintf(fp, "Signed-off-by: %s\n", git_committer_info(1));
+	}
+
+	if (in_merge && !no_edit) {
+		fprintf(fp,
+			"#\n"
+			"# It looks like you may be committing a MERGE.\n"
+			"# If this is not correct, please remove the file\n"
+			"#	%s\n"
+			"# and try again.\n"
+			"#\n",
+			git_path("MERGE_HEAD"));
+	}
+
+	fprintf(fp,
+		"\n"
+		"# Please enter the commit message for your changes.\n"
+		"# (Comment lines starting with '#' will not be included)\n");
+	if (only_include_assumed)
+		fprintf(fp, "# %s\n", only_include_assumed);
+
+	commitable = run_status(fp, index_file);
+
+	fclose(fp);
+
+	return commitable;
+}
+
+struct commit_info_strings {
+	const char *header, *name_env, *email_env, *date_env;
+} author_info_strings = {
+	"\nauthor ",
+	"GIT_AUTHOR_NAME", "GIT_AUTHOR_EMAIL", "GIT_AUTHOR_DATE"
+}, committer_info_strings = {
+	"\ncommitter ",
+	"GIT_COMMITTER_NAME", "GIT_COMMITER_EMAIL", "GIT_COMMITTER_DATE"
+};
+
+static char *determine_info(struct commit_info_strings *strings)
+{
+	char *p, *eol;
+	char *name = NULL, *email = NULL, *date = NULL;
+
+	if (use_message) {
+		p = strstr(use_message_commit->buffer, strings->header);
+		if (!p)
+			die("invalid commit: %s\n", use_message);
+		p += strlen(strings->header);
+		eol = strchr(p, '\n');
+		if (!eol)
+			die("invalid commit: %s\n", use_message);
+
+		return xstrndup(p, eol - p);
+	} else if (force_author && strings == &author_info_strings) {
+		const char *eoname = strstr(force_author, " <");
+		const char *eomail = strchr(force_author, '>');
+
+		if (!eoname || !eomail)
+			die("malformed --author parameter\n");
+		name = xstrndup(force_author, eoname - force_author);
+		email = xstrndup(eoname + 2, eomail - eoname - 2);
+	}
+
+	if (name == NULL)
+		name = getenv(strings->name_env);
+	if (email == NULL)
+		email = getenv(strings->email_env);
+	if (date == NULL)
+		date = getenv(strings->date_env);
+
+	return xstrdup(fmt_ident(name, email, date, 1));
+}
+
+static void parse_and_validate_options(const char ***argv)
+{
+	int f = 0;
+
+	git_config(git_status_config);
+
+	(*argv)++;
+	while (scan_options(argv, commit_options))
+		;
+
+	if (logfile || message || use_message)
+		no_edit = 1;
+
+	if (get_sha1("HEAD", head_sha1))
+		initial_commit = 1;
+
+	if (!get_sha1("MERGE_HEAD", merge_head_sha1))
+		in_merge = 1;
+
+	/* Sanity check options */
+	if (amend && initial_commit)
+		die("You have nothing to amend.");
+	if (amend && in_merge)
+		die("You are in the middle of a merger -- cannot amend.");
+
+	if (use_message)
+		f++;
+	if (edit_message)
+		f++;
+	if (logfile)
+		f++;
+	if (amend)
+		f++;
+	if (f > 1)
+		die("Only one of -c/-C/-F/--amend can be used.");
+	if (message && f > 0)
+		die("Option -m cannot be combined with -c/-C/-F/--amend.");
+	if (edit_message)
+		use_message = edit_message;
+	if (amend)
+		use_message = "HEAD";
+	if (use_message) {
+		unsigned char sha1[20];
+
+		if (get_sha1(use_message, sha1))
+			die("could not lookup commit %s", use_message);
+		use_message_commit = lookup_commit(sha1);
+		if (!use_message_commit || parse_commit(use_message_commit))
+			die("could not parse commit %s", use_message);
+	}
+
+	if (also && only)
+		die("Only one of --include/--only can be used.");
+	if (!*argv && (also || (only && !amend)))
+		die("No paths with --include/--only does not make sense.");
+	if (!*argv && only && amend)
+		only_include_assumed = "Clever... amending the last one with dirty index.";
+	if (*argv && !also && !only) {
+		only_include_assumed = "Explicit paths specified without -i nor -o; assuming --only paths...";
+		also = 0;
+	}
+
+	if (all && interactive)
+		die("Cannot use -a, --interactive or -i at the same time.");
+	else if (all && **argv)
+		die("Paths with -a does not make sense.");
+	else if (interactive && **argv)
+		die("Paths with --interactive does not make sense.");
+}
+
+int cmd_status(int argc, const char **argv, const char *prefix)
+{
+	const char *index_file;
+	int commitable;
+
+	parse_and_validate_options(&argv);
+
+	index_file = prepare_index(argv, prefix);
+
+	commitable = run_status(stdout, index_file);
+
+	rollback_lock_file(&lock_file);
+
+	return commitable ? 0 : 1;
+}
+
+static void launch_editor(const char *path)
+{
+	const char *editor, *terminal;
+	struct child_process child;
+	const char *args[3];
+
+	editor = getenv("VISUAL");
+	if (!editor)
+		editor = getenv("EDITOR");
+
+	terminal = getenv("TERM");
+	if (!editor && (!terminal || !strcmp(terminal, "dumb"))) {
+		fprintf(stderr, 
+			"Terminal is dumb but no VISUAL nor EDITOR defined.\n"
+			"Please supply the commit log message using either\n"
+			"-m or -F option.  A boilerplate log message has\n"
+			"been prepared in $GIT_DIR/COMMIT_EDITMSG\n");
+		exit(1);
+	}
+
+	if (!editor)
+		editor = "vi";
+	    
+	memset(&child, 0, sizeof(child));
+	child.argv = args;
+	args[0] = editor;
+	args[1] = path;
+	args[2] = NULL;
+
+	if (run_command(&child))
+		die("could not launch editor %s.", editor);
+}
+
+static int message_is_empty(const char *buffer, int len)
+{
+	static const char signed_off_by[] = "Signed-off-by: ";
+	const char *nl;
+	int eol, i;
+
+	for (i = 0; i < len; i++) {
+		nl = memchr(buffer + i, '\n', len - i);
+		if (nl)
+			eol = nl - buffer;
+		else
+			eol = len;
+
+		if (strlen(signed_off_by) <= eol - i &&
+		    !prefixcmp(buffer + i, signed_off_by)) {
+			i = eol;
+			continue;
+		}
+		while (i < eol)
+			if (!isspace(buffer[i++]))
+			    return 0;
+	}
+
+	return 1;
+}
+
+static int run_hook(const char *index_file, const char *name, const char *arg)
+{
+	struct child_process hook;
+	const char *argv[3], *env[2];
+	char index[PATH_MAX];
+
+	argv[0] = git_path("hooks/%s", name);
+	argv[1] = arg;
+	argv[2] = NULL;
+	snprintf(index, sizeof(index), "GIT_INDEX_FILE=%s", index_file);
+	env[0] = index;
+	env[1] = NULL;
+
+	if (access(argv[0], X_OK) < 0)
+		return 0;
+
+	memset(&hook, 0, sizeof(hook));
+	hook.argv = argv;
+	hook.no_stdin = 1;
+	hook.stdout_to_stderr = 1;
+	hook.env = env;
+
+	return run_command(&hook);
+}
+
+static void print_summary(const char *prefix, const unsigned char *sha1)
+{
+	struct rev_info rev;
+	struct commit *commit;
+
+	commit = lookup_commit(sha1);
+	if (!commit)
+		die("couldn't look up newly created commit\n");
+	if (!commit || parse_commit(commit))
+		die("could not parse newly created commit");
+
+	init_revisions(&rev, prefix);
+	setup_revisions(0, NULL, &rev, NULL);
+
+	rev.abbrev = 0;
+	rev.diff = 1;
+	rev.diffopt.output_format =
+		DIFF_FORMAT_SHORTSTAT | DIFF_FORMAT_SUMMARY;
+
+	rev.verbose_header = 1;
+	rev.show_root_diff = 1;
+	rev.commit_format = get_commit_format("format:%h: %s");
+	rev.always_show_header = 1;
+		
+	printf("Created %scommit ", initial_commit ? "initial " : "");
+
+	log_tree_commit(&rev, commit);
+}
+
+#define MAXPARENT (16)
+static unsigned char parent_sha1[MAXPARENT][20];
+
+int cmd_commit(int argc, const char **argv, const char *prefix)
+{
+	int parent_count = 0;
+	unsigned long len;
+	char *buffer;
+	const char *index_file, *reflog_msg;
+	const unsigned char *commit_sha1;
+	struct ref_lock *ref_lock;
+
+	parse_and_validate_options(&argv);
+
+	index_file = prepare_index(argv, prefix);
+
+	if (run_hook(index_file, "pre-commit", NULL))
+		exit(1);
+
+	if (!prepare_log_message(index_file)) {
+		run_status(stdout, index_file);
+		unlink(commit_editmsg);
+		return 1;
+	}
+
+	if (!no_edit)
+		launch_editor(git_path(commit_editmsg));
+
+	if (run_hook(index_file, "commit-msg", commit_editmsg))
+		exit(1);
+
+	if (read_path(git_path(commit_editmsg), &buffer, &len))
+		die("could not read commit message file '%s': %s",
+		    git_path(commit_editmsg), strerror(errno));
+
+	len = strip_lines(buffer, len);
+
+	if (message_is_empty(buffer, len))
+		die("* no commit message?  aborting commit.");
+
+	/* Determine parents */
+	if (initial_commit) {
+		reflog_msg = "commit (initial)"; 
+		parent_count = 0;
+	} else if (amend) {
+		struct commit_list *c;
+		struct commit *commit;
+		int i = 0;
+
+		reflog_msg = "commit (amend)";
+		commit = lookup_commit(head_sha1);
+		if (!commit || parse_commit(commit))
+			die("could not parse HEAD commit");
+
+		for (c = commit->parents; c; c = c->next) {
+			hashcpy(parent_sha1[i++], c->item->object.sha1);
+			if (i == MAXPARENT)
+				die("Too many parents (%d max)", MAXPARENT);
+		}
+		parent_count = i;
+	} else if (in_merge) {
+		/* FIXME: how are merges with more than two parents handled? */
+		reflog_msg = "commit (merge)";
+		hashcpy(parent_sha1[0], head_sha1);
+		hashcpy(parent_sha1[1], merge_head_sha1);
+		parent_count = 2;
+	} else {
+		reflog_msg = "commit";
+		hashcpy(parent_sha1[0], head_sha1);
+		parent_count = 1;
+	}
+
+	read_cache_from(index_file);
+	active_cache_tree = cache_tree();
+	if (cache_tree_update(active_cache_tree,
+			      active_cache, active_nr, 0, 0) < 0)
+		die("Error building trees");
+
+	commit_sha1 = create_commit(active_cache_tree->sha1,
+				    parent_sha1, parent_count,
+				    determine_info(&author_info_strings),
+				    determine_info(&committer_info_strings),
+				    buffer, len);
+		       
+	ref_lock = lock_any_ref_for_update("HEAD",
+					   initial_commit ? NULL : head_sha1,
+					   0);
+	if (!ref_lock)
+		die("cannot lock HEAD ref");
+	if (write_ref_sha1(ref_lock, commit_sha1, reflog_msg) < 0)
+		die("cannot update HEAD ref");
+
+	unlink(git_path("MERGE_HEAD"));
+	unlink(git_path("MERGE_MSG"));
+
+	if (lock_file.filename[0] && commit_locked_index(&lock_file))
+		die("failed to write new index");
+
+	/*
+	 * FIXME:
+	 *	if test -d "$GIT_DIR/rr-cache"
+	 *	then
+	 *		git-rerere
+	 *	fi
+	 */
+
+	run_hook(index_file, "post-commit", NULL);
+
+	if (!quiet)
+		print_summary(prefix, commit_sha1);
+
+	return 0;
+}
diff --git a/builtin.h b/builtin.h
index da4834c..7f395da 100644
--- a/builtin.h
+++ b/builtin.h
@@ -23,6 +23,7 @@ extern int cmd_check_attr(int argc, const char **argv, const char *prefix);
 extern int cmd_check_ref_format(int argc, const char **argv, const char *prefix);
 extern int cmd_cherry(int argc, const char **argv, const char *prefix);
 extern int cmd_cherry_pick(int argc, const char **argv, const char *prefix);
+extern int cmd_commit(int argc, const char **argv, const char *prefix);
 extern int cmd_commit_tree(int argc, const char **argv, const char *prefix);
 extern int cmd_count_objects(int argc, const char **argv, const char *prefix);
 extern int cmd_describe(int argc, const char **argv, const char *prefix);
@@ -63,10 +64,10 @@ extern int cmd_rev_list(int argc, const char **argv, const char *prefix);
 extern int cmd_rev_parse(int argc, const char **argv, const char *prefix);
 extern int cmd_revert(int argc, const char **argv, const char *prefix);
 extern int cmd_rm(int argc, const char **argv, const char *prefix);
-extern int cmd_runstatus(int argc, const char **argv, const char *prefix);
 extern int cmd_shortlog(int argc, const char **argv, const char *prefix);
 extern int cmd_show(int argc, const char **argv, const char *prefix);
 extern int cmd_show_branch(int argc, const char **argv, const char *prefix);
+extern int cmd_status(int argc, const char **argv, const char *prefix);
 extern int cmd_stripspace(int argc, const char **argv, const char *prefix);
 extern int cmd_symbolic_ref(int argc, const char **argv, const char *prefix);
 extern int cmd_tar_tree(int argc, const char **argv, const char *prefix);
diff --git a/cache.h b/cache.h
index 5e7381e..0403ada 100644
--- a/cache.h
+++ b/cache.h
@@ -245,7 +245,8 @@ extern int ie_match_stat(struct index_state *, struct cache_entry *, struct stat
 extern int ie_modified(struct index_state *, struct cache_entry *, struct stat *, int);
 extern int ce_path_match(const struct cache_entry *ce, const char **pathspec);
 extern int index_fd(unsigned char *sha1, int fd, struct stat *st, int write_object, enum object_type type, const char *path);
-extern int read_pipe(int fd, char** return_buf, unsigned long* return_size);
+extern int read_fd(int fd, char** return_buf, unsigned long* return_size);
+extern int read_path(const char *path, char** return_buf, unsigned long* return_size);
 extern int index_pipe(unsigned char *sha1, int fd, const char *type, int write_object);
 extern int index_path(unsigned char *sha1, const char *path, struct stat *st, int write_object);
 extern void fill_stat_cache_info(struct cache_entry *ce, struct stat *st);
diff --git a/color.c b/color.c
index 09d82ee..124ba33 100644
--- a/color.c
+++ b/color.c
@@ -135,39 +135,39 @@ int git_config_colorbool(const char *var, const char *value)
 	return git_config_bool(var, value);
 }
 
-static int color_vprintf(const char *color, const char *fmt,
+static int color_vfprintf(FILE *fp, const char *color, const char *fmt,
 		va_list args, const char *trail)
 {
 	int r = 0;
 
 	if (*color)
-		r += printf("%s", color);
-	r += vprintf(fmt, args);
+		r += fprintf(fp, "%s", color);
+	r += vfprintf(fp, fmt, args);
 	if (*color)
-		r += printf("%s", COLOR_RESET);
+		r += fprintf(fp, "%s", COLOR_RESET);
 	if (trail)
-		r += printf("%s", trail);
+		r += fprintf(fp, "%s", trail);
 	return r;
 }
 
 
 
-int color_printf(const char *color, const char *fmt, ...)
+int color_fprintf(FILE *fp, const char *color, const char *fmt, ...)
 {
 	va_list args;
 	int r;
 	va_start(args, fmt);
-	r = color_vprintf(color, fmt, args, NULL);
+	r = color_vfprintf(fp, color, fmt, args, NULL);
 	va_end(args);
 	return r;
 }
 
-int color_printf_ln(const char *color, const char *fmt, ...)
+int color_fprintf_ln(FILE *fp, const char *color, const char *fmt, ...)
 {
 	va_list args;
 	int r;
 	va_start(args, fmt);
-	r = color_vprintf(color, fmt, args, "\n");
+	r = color_vfprintf(fp, color, fmt, args, "\n");
 	va_end(args);
 	return r;
 }
diff --git a/color.h b/color.h
index 88bb8ff..6809800 100644
--- a/color.h
+++ b/color.h
@@ -6,7 +6,7 @@
 
 int git_config_colorbool(const char *var, const char *value);
 void color_parse(const char *var, const char *value, char *dst);
-int color_printf(const char *color, const char *fmt, ...);
-int color_printf_ln(const char *color, const char *fmt, ...);
+int color_fprintf(FILE *fp, const char *color, const char *fmt, ...);
+int color_fprintf_ln(FILE *fp, const char *color, const char *fmt, ...);
 
 #endif /* COLOR_H */
diff --git a/commit.h b/commit.h
index a313b53..a07e379 100644
--- a/commit.h
+++ b/commit.h
@@ -122,4 +122,13 @@ extern struct commit_list *get_shallow_commits(struct object_array *heads,
 		int depth, int shallow_flag, int not_shallow_flag);
 
 int in_merge_bases(struct commit *, struct commit **, int);
+
+const unsigned char *
+create_commit(const unsigned char *tree_sha1,
+	      unsigned char parent_sha1[][20], int parents,
+	      const char *author_info, const char *committer_info,
+	      const char *message, int length);
+
+int interactive_add(void);
+
 #endif /* COMMIT_H */
diff --git a/git-commit.sh b/git-commit.sh
deleted file mode 100755
index 5547a02..0000000
--- a/git-commit.sh
+++ /dev/null
@@ -1,658 +0,0 @@
-#!/bin/sh
-#
-# Copyright (c) 2005 Linus Torvalds
-# Copyright (c) 2006 Junio C Hamano
-
-USAGE='[-a | --interactive] [-s] [-v] [--no-verify] [-m <message> | -F <logfile> | (-C|-c) <commit> | --amend] [-u] [-e] [--author <author>] [[-i | -o] <path>...]'
-SUBDIRECTORY_OK=Yes
-. git-sh-setup
-require_work_tree
-
-git-rev-parse --verify HEAD >/dev/null 2>&1 || initial_commit=t
-
-case "$0" in
-*status)
-	status_only=t
-	;;
-*commit)
-	status_only=
-	;;
-esac
-
-refuse_partial () {
-	echo >&2 "$1"
-	echo >&2 "You might have meant to say 'git commit -i paths...', perhaps?"
-	exit 1
-}
-
-THIS_INDEX="$GIT_DIR/index"
-NEXT_INDEX="$GIT_DIR/next-index$$"
-rm -f "$NEXT_INDEX"
-save_index () {
-	cp -p "$THIS_INDEX" "$NEXT_INDEX"
-}
-
-run_status () {
-	# If TMP_INDEX is defined, that means we are doing
-	# "--only" partial commit, and that index file is used
-	# to build the tree for the commit.  Otherwise, if
-	# NEXT_INDEX exists, that is the index file used to
-	# make the commit.  Otherwise we are using as-is commit
-	# so the regular index file is what we use to compare.
-	if test '' != "$TMP_INDEX"
-	then
-		GIT_INDEX_FILE="$TMP_INDEX"
-		export GIT_INDEX_FILE
-	elif test -f "$NEXT_INDEX"
-	then
-		GIT_INDEX_FILE="$NEXT_INDEX"
-		export GIT_INDEX_FILE
-	fi
-
-	case "$status_only" in
-	t) color= ;;
-	*) color=--nocolor ;;
-	esac
-	git-runstatus ${color} \
-		${verbose:+--verbose} \
-		${amend:+--amend} \
-		${untracked_files:+--untracked}
-}
-
-trap '
-	test -z "$TMP_INDEX" || {
-		test -f "$TMP_INDEX" && rm -f "$TMP_INDEX"
-	}
-	rm -f "$NEXT_INDEX"
-' 0
-
-################################################################
-# Command line argument parsing and sanity checking
-
-all=
-also=
-interactive=
-only=
-logfile=
-use_commit=
-amend=
-edit_flag=
-no_edit=
-log_given=
-log_message=
-verify=t
-quiet=
-verbose=
-signoff=
-force_author=
-only_include_assumed=
-untracked_files=
-while case "$#" in 0) break;; esac
-do
-	case "$1" in
-	-F|--F|-f|--f|--fi|--fil|--file)
-		case "$#" in 1) usage ;; esac
-		shift
-		no_edit=t
-		log_given=t$log_given
-		logfile="$1"
-		shift
-		;;
-	-F*|-f*)
-		no_edit=t
-		log_given=t$log_given
-		logfile=`expr "z$1" : 'z-[Ff]\(.*\)'`
-		shift
-		;;
-	--F=*|--f=*|--fi=*|--fil=*|--file=*)
-		no_edit=t
-		log_given=t$log_given
-		logfile=`expr "z$1" : 'z-[^=]*=\(.*\)'`
-		shift
-		;;
-	-a|--a|--al|--all)
-		all=t
-		shift
-		;;
-	--au=*|--aut=*|--auth=*|--autho=*|--author=*)
-		force_author=`expr "z$1" : 'z-[^=]*=\(.*\)'`
-		shift
-		;;
-	--au|--aut|--auth|--autho|--author)
-		case "$#" in 1) usage ;; esac
-		shift
-		force_author="$1"
-		shift
-		;;
-	-e|--e|--ed|--edi|--edit)
-		edit_flag=t
-		shift
-		;;
-	-i|--i|--in|--inc|--incl|--inclu|--includ|--include)
-		also=t
-		shift
-		;;
-	--int|--inte|--inter|--intera|--interac|--interact|--interacti|\
-	--interactiv|--interactive)
-		interactive=t
-		shift
-		;;
-	-o|--o|--on|--onl|--only)
-		only=t
-		shift
-		;;
-	-m|--m|--me|--mes|--mess|--messa|--messag|--message)
-		case "$#" in 1) usage ;; esac
-		shift
-		log_given=m$log_given
-		if test "$log_message" = ''
-		then
-		    log_message="$1"
-		else
-		    log_message="$log_message
-
-$1"
-		fi
-		no_edit=t
-		shift
-		;;
-	-m*)
-		log_given=m$log_given
-		if test "$log_message" = ''
-		then
-		    log_message=`expr "z$1" : 'z-m\(.*\)'`
-		else
-		    log_message="$log_message
-
-`expr "z$1" : 'z-m\(.*\)'`"
-		fi
-		no_edit=t
-		shift
-		;;
-	--m=*|--me=*|--mes=*|--mess=*|--messa=*|--messag=*|--message=*)
-		log_given=m$log_given
-		if test "$log_message" = ''
-		then
-		    log_message=`expr "z$1" : 'z-[^=]*=\(.*\)'`
-		else
-		    log_message="$log_message
-
-`expr "z$1" : 'zq-[^=]*=\(.*\)'`"
-		fi
-		no_edit=t
-		shift
-		;;
-	-n|--n|--no|--no-|--no-v|--no-ve|--no-ver|--no-veri|--no-verif|\
-	--no-verify)
-		verify=
-		shift
-		;;
-	--a|--am|--ame|--amen|--amend)
-		amend=t
-		log_given=t$log_given
-		use_commit=HEAD
-		shift
-		;;
-	-c)
-		case "$#" in 1) usage ;; esac
-		shift
-		log_given=t$log_given
-		use_commit="$1"
-		no_edit=
-		shift
-		;;
-	--ree=*|--reed=*|--reedi=*|--reedit=*|--reedit-=*|--reedit-m=*|\
-	--reedit-me=*|--reedit-mes=*|--reedit-mess=*|--reedit-messa=*|\
-	--reedit-messag=*|--reedit-message=*)
-		log_given=t$log_given
-		use_commit=`expr "z$1" : 'z-[^=]*=\(.*\)'`
-		no_edit=
-		shift
-		;;
-	--ree|--reed|--reedi|--reedit|--reedit-|--reedit-m|--reedit-me|\
-	--reedit-mes|--reedit-mess|--reedit-messa|--reedit-messag|\
-	--reedit-message)
-		case "$#" in 1) usage ;; esac
-		shift
-		log_given=t$log_given
-		use_commit="$1"
-		no_edit=
-		shift
-		;;
-	-C)
-		case "$#" in 1) usage ;; esac
-		shift
-		log_given=t$log_given
-		use_commit="$1"
-		no_edit=t
-		shift
-		;;
-	--reu=*|--reus=*|--reuse=*|--reuse-=*|--reuse-m=*|--reuse-me=*|\
-	--reuse-mes=*|--reuse-mess=*|--reuse-messa=*|--reuse-messag=*|\
-	--reuse-message=*)
-		log_given=t$log_given
-		use_commit=`expr "z$1" : 'z-[^=]*=\(.*\)'`
-		no_edit=t
-		shift
-		;;
-	--reu|--reus|--reuse|--reuse-|--reuse-m|--reuse-me|--reuse-mes|\
-	--reuse-mess|--reuse-messa|--reuse-messag|--reuse-message)
-		case "$#" in 1) usage ;; esac
-		shift
-		log_given=t$log_given
-		use_commit="$1"
-		no_edit=t
-		shift
-		;;
-	-s|--s|--si|--sig|--sign|--signo|--signof|--signoff)
-		signoff=t
-		shift
-		;;
-	-q|--q|--qu|--qui|--quie|--quiet)
-		quiet=t
-		shift
-		;;
-	-v|--v|--ve|--ver|--verb|--verbo|--verbos|--verbose)
-		verbose=t
-		shift
-		;;
-	-u|--u|--un|--unt|--untr|--untra|--untrac|--untrack|--untracke|\
-	--untracked|--untracked-|--untracked-f|--untracked-fi|--untracked-fil|\
-	--untracked-file|--untracked-files)
-		untracked_files=t
-		shift
-		;;
-	--)
-		shift
-		break
-		;;
-	-*)
-		usage
-		;;
-	*)
-		break
-		;;
-	esac
-done
-case "$edit_flag" in t) no_edit= ;; esac
-
-################################################################
-# Sanity check options
-
-case "$amend,$initial_commit" in
-t,t)
-	die "You do not have anything to amend." ;;
-t,)
-	if [ -f "$GIT_DIR/MERGE_HEAD" ]; then
-		die "You are in the middle of a merge -- cannot amend."
-	fi ;;
-esac
-
-case "$log_given" in
-tt*)
-	die "Only one of -c/-C/-F/--amend can be used." ;;
-*tm*|*mt*)
-	die "Option -m cannot be combined with -c/-C/-F/--amend." ;;
-esac
-
-case "$#,$also,$only,$amend" in
-*,t,t,*)
-	die "Only one of --include/--only can be used." ;;
-0,t,,* | 0,,t,)
-	die "No paths with --include/--only does not make sense." ;;
-0,,t,t)
-	only_include_assumed="# Clever... amending the last one with dirty index." ;;
-0,,,*)
-	;;
-*,,,*)
-	only_include_assumed="# Explicit paths specified without -i nor -o; assuming --only paths..."
-	also=
-	;;
-esac
-unset only
-case "$all,$interactive,$also,$#" in
-*t,*t,*)
-	die "Cannot use -a, --interactive or -i at the same time." ;;
-t,,[1-9]*)
-	die "Paths with -a does not make sense." ;;
-,t,[1-9]*)
-	die "Paths with --interactive does not make sense." ;;
-,,t,0)
-	die "No paths with -i does not make sense." ;;
-esac
-
-################################################################
-# Prepare index to have a tree to be committed
-
-case "$all,$also" in
-t,)
-	if test ! -f "$THIS_INDEX"
-	then
-		die 'nothing to commit (use "git add file1 file2" to include for commit)'
-	fi
-	save_index &&
-	(
-		cd_to_toplevel &&
-		GIT_INDEX_FILE="$NEXT_INDEX" &&
-		export GIT_INDEX_FILE &&
-		git-diff-files --name-only -z |
-		git-update-index --remove -z --stdin
-	) || exit
-	;;
-,t)
-	save_index &&
-	git-ls-files --error-unmatch -- "$@" >/dev/null || exit
-
-	git-diff-files --name-only -z -- "$@"  |
-	(
-		cd_to_toplevel &&
-		GIT_INDEX_FILE="$NEXT_INDEX" &&
-		export GIT_INDEX_FILE &&
-		git-update-index --remove -z --stdin
-	) || exit
-	;;
-,)
-	if test "$interactive" = t; then
-		git add --interactive || exit
-	fi
-	case "$#" in
-	0)
-		;; # commit as-is
-	*)
-		if test -f "$GIT_DIR/MERGE_HEAD"
-		then
-			refuse_partial "Cannot do a partial commit during a merge."
-		fi
-		TMP_INDEX="$GIT_DIR/tmp-index$$"
-		commit_only=`git-ls-files --error-unmatch -- "$@"` || exit
-
-		# Build a temporary index and update the real index
-		# the same way.
-		if test -z "$initial_commit"
-		then
-			GIT_INDEX_FILE="$THIS_INDEX" \
-			git-read-tree --index-output="$TMP_INDEX" -i -m HEAD
-		else
-			rm -f "$TMP_INDEX"
-		fi || exit
-
-		printf '%s\n' "$commit_only" |
-		GIT_INDEX_FILE="$TMP_INDEX" \
-		git-update-index --add --remove --stdin &&
-
-		save_index &&
-		printf '%s\n' "$commit_only" |
-		(
-			GIT_INDEX_FILE="$NEXT_INDEX"
-			export GIT_INDEX_FILE
-			git-update-index --remove --stdin
-		) || exit
-		;;
-	esac
-	;;
-esac
-
-################################################################
-# If we do as-is commit, the index file will be THIS_INDEX,
-# otherwise NEXT_INDEX after we make this commit.  We leave
-# the index as is if we abort.
-
-if test -f "$NEXT_INDEX"
-then
-	USE_INDEX="$NEXT_INDEX"
-else
-	USE_INDEX="$THIS_INDEX"
-fi
-
-case "$status_only" in
-t)
-	# This will silently fail in a read-only repository, which is
-	# what we want.
-	GIT_INDEX_FILE="$USE_INDEX" git-update-index -q --unmerged --refresh
-	run_status
-	exit $?
-	;;
-'')
-	GIT_INDEX_FILE="$USE_INDEX" git-update-index -q --refresh || exit
-	;;
-esac
-
-################################################################
-# Grab commit message, write out tree and make commit.
-
-if test t = "$verify" && test -x "$GIT_DIR"/hooks/pre-commit
-then
-	if test "$TMP_INDEX"
-	then
-		GIT_INDEX_FILE="$TMP_INDEX" "$GIT_DIR"/hooks/pre-commit
-	else
-		GIT_INDEX_FILE="$USE_INDEX" "$GIT_DIR"/hooks/pre-commit
-	fi || exit
-fi
-
-if test "$log_message" != ''
-then
-	printf '%s\n' "$log_message"
-elif test "$logfile" != ""
-then
-	if test "$logfile" = -
-	then
-		test -t 0 &&
-		echo >&2 "(reading log message from standard input)"
-		cat
-	else
-		cat <"$logfile"
-	fi
-elif test "$use_commit" != ""
-then
-	encoding=$(git config i18n.commitencoding || echo UTF-8)
-	git show -s --pretty=raw --encoding="$encoding" "$use_commit" |
-	sed -e '1,/^$/d' -e 's/^    //'
-elif test -f "$GIT_DIR/MERGE_MSG"
-then
-	cat "$GIT_DIR/MERGE_MSG"
-elif test -f "$GIT_DIR/SQUASH_MSG"
-then
-	cat "$GIT_DIR/SQUASH_MSG"
-fi | git-stripspace >"$GIT_DIR"/COMMIT_EDITMSG
-
-case "$signoff" in
-t)
-	need_blank_before_signoff=
-	tail -n 1 "$GIT_DIR"/COMMIT_EDITMSG |
-	grep 'Signed-off-by:' >/dev/null || need_blank_before_signoff=yes
-	{
-		test -z "$need_blank_before_signoff" || echo
-		git-var GIT_COMMITTER_IDENT | sed -e '
-			s/>.*/>/
-			s/^/Signed-off-by: /
-		'
-	} >>"$GIT_DIR"/COMMIT_EDITMSG
-	;;
-esac
-
-if test -f "$GIT_DIR/MERGE_HEAD" && test -z "$no_edit"; then
-	echo "#"
-	echo "# It looks like you may be committing a MERGE."
-	echo "# If this is not correct, please remove the file"
-	printf '%s\n' "#	$GIT_DIR/MERGE_HEAD"
-	echo "# and try again"
-	echo "#"
-fi >>"$GIT_DIR"/COMMIT_EDITMSG
-
-# Author
-if test '' != "$use_commit"
-then
-	pick_author_script='
-	/^author /{
-		s/'\''/'\''\\'\'\''/g
-		h
-		s/^author \([^<]*\) <[^>]*> .*$/\1/
-		s/'\''/'\''\'\'\''/g
-		s/.*/GIT_AUTHOR_NAME='\''&'\''/p
-
-		g
-		s/^author [^<]* <\([^>]*\)> .*$/\1/
-		s/'\''/'\''\'\'\''/g
-		s/.*/GIT_AUTHOR_EMAIL='\''&'\''/p
-
-		g
-		s/^author [^<]* <[^>]*> \(.*\)$/\1/
-		s/'\''/'\''\'\'\''/g
-		s/.*/GIT_AUTHOR_DATE='\''&'\''/p
-
-		q
-	}
-	'
-	encoding=$(git config i18n.commitencoding || echo UTF-8)
-	set_author_env=`git show -s --pretty=raw --encoding="$encoding" "$use_commit" |
-	LANG=C LC_ALL=C sed -ne "$pick_author_script"`
-	eval "$set_author_env"
-	export GIT_AUTHOR_NAME
-	export GIT_AUTHOR_EMAIL
-	export GIT_AUTHOR_DATE
-fi
-if test '' != "$force_author"
-then
-	GIT_AUTHOR_NAME=`expr "z$force_author" : 'z\(.*[^ ]\) *<.*'` &&
-	GIT_AUTHOR_EMAIL=`expr "z$force_author" : '.*\(<.*\)'` &&
-	test '' != "$GIT_AUTHOR_NAME" &&
-	test '' != "$GIT_AUTHOR_EMAIL" ||
-	die "malformed --author parameter"
-	export GIT_AUTHOR_NAME GIT_AUTHOR_EMAIL
-fi
-
-PARENTS="-p HEAD"
-if test -z "$initial_commit"
-then
-	rloga='commit'
-	if [ -f "$GIT_DIR/MERGE_HEAD" ]; then
-		rloga='commit (merge)'
-		PARENTS="-p HEAD "`sed -e 's/^/-p /' "$GIT_DIR/MERGE_HEAD"`
-	elif test -n "$amend"; then
-		rloga='commit (amend)'
-		PARENTS=$(git-cat-file commit HEAD |
-			sed -n -e '/^$/q' -e 's/^parent /-p /p')
-	fi
-	current="$(git-rev-parse --verify HEAD)"
-else
-	if [ -z "$(git-ls-files)" ]; then
-		echo >&2 'nothing to commit (use "git add file1 file2" to include for commit)'
-		exit 1
-	fi
-	PARENTS=""
-	rloga='commit (initial)'
-	current=''
-fi
-set_reflog_action "$rloga"
-
-if test -z "$no_edit"
-then
-	{
-		echo ""
-		echo "# Please enter the commit message for your changes."
-		echo "# (Comment lines starting with '#' will not be included)"
-		test -z "$only_include_assumed" || echo "$only_include_assumed"
-		run_status
-	} >>"$GIT_DIR"/COMMIT_EDITMSG
-else
-	# we need to check if there is anything to commit
-	run_status >/dev/null
-fi
-if [ "$?" != "0" -a ! -f "$GIT_DIR/MERGE_HEAD" -a -z "$amend" ]
-then
-	rm -f "$GIT_DIR/COMMIT_EDITMSG" "$GIT_DIR/SQUASH_MSG"
-	run_status
-	exit 1
-fi
-
-case "$no_edit" in
-'')
-	case "${VISUAL:-$EDITOR},$TERM" in
-	,dumb)
-		echo >&2 "Terminal is dumb but no VISUAL nor EDITOR defined."
-		echo >&2 "Please supply the commit log message using either"
-		echo >&2 "-m or -F option.  A boilerplate log message has"
-		echo >&2 "been prepared in $GIT_DIR/COMMIT_EDITMSG"
-		exit 1
-		;;
-	esac
-	git-var GIT_AUTHOR_IDENT > /dev/null  || die
-	git-var GIT_COMMITTER_IDENT > /dev/null  || die
-	${VISUAL:-${EDITOR:-vi}} "$GIT_DIR/COMMIT_EDITMSG"
-	;;
-esac
-
-case "$verify" in
-t)
-	if test -x "$GIT_DIR"/hooks/commit-msg
-	then
-		"$GIT_DIR"/hooks/commit-msg "$GIT_DIR"/COMMIT_EDITMSG || exit
-	fi
-esac
-
-if test -z "$no_edit"
-then
-    sed -e '
-        /^diff --git a\/.*/{
-	    s///
-	    q
-	}
-	/^#/d
-    ' "$GIT_DIR"/COMMIT_EDITMSG
-else
-    cat "$GIT_DIR"/COMMIT_EDITMSG
-fi |
-git-stripspace >"$GIT_DIR"/COMMIT_MSG
-
-if cnt=`grep -v -i '^Signed-off-by' "$GIT_DIR"/COMMIT_MSG |
-	git-stripspace |
-	wc -l` &&
-   test 0 -lt $cnt
-then
-	if test -z "$TMP_INDEX"
-	then
-		tree=$(GIT_INDEX_FILE="$USE_INDEX" git-write-tree)
-	else
-		tree=$(GIT_INDEX_FILE="$TMP_INDEX" git-write-tree) &&
-		rm -f "$TMP_INDEX"
-	fi &&
-	commit=$(cat "$GIT_DIR"/COMMIT_MSG | git-commit-tree $tree $PARENTS) &&
-	rlogm=$(sed -e 1q "$GIT_DIR"/COMMIT_MSG) &&
-	git-update-ref -m "$GIT_REFLOG_ACTION: $rlogm" HEAD $commit "$current" &&
-	rm -f -- "$GIT_DIR/MERGE_HEAD" "$GIT_DIR/MERGE_MSG" &&
-	if test -f "$NEXT_INDEX"
-	then
-		mv "$NEXT_INDEX" "$THIS_INDEX"
-	else
-		: ;# happy
-	fi
-else
-	echo >&2 "* no commit message?  aborting commit."
-	false
-fi
-ret="$?"
-rm -f "$GIT_DIR/COMMIT_MSG" "$GIT_DIR/COMMIT_EDITMSG" "$GIT_DIR/SQUASH_MSG"
-
-cd_to_toplevel
-
-if test -d "$GIT_DIR/rr-cache"
-then
-	git-rerere
-fi
-
-if test "$ret" = 0
-then
-	if test -x "$GIT_DIR"/hooks/post-commit
-	then
-		"$GIT_DIR"/hooks/post-commit
-	fi
-	if test -z "$quiet"
-	then
-		commit=`git-diff-tree --always --shortstat --pretty="format:%h: %s"\
-		       --summary --root HEAD --`
-		echo "Created${initial_commit:+ initial} commit $commit"
-	fi
-fi
-
-exit "$ret"
diff --git a/git.c b/git.c
index 29b55a1..4018e3c 100644
--- a/git.c
+++ b/git.c
@@ -237,6 +237,7 @@ static void handle_internal_command(int argc, const char **argv, char **envp)
 		{ "check-attr", cmd_check_attr, RUN_SETUP | NOT_BARE },
 		{ "cherry", cmd_cherry, RUN_SETUP },
 		{ "cherry-pick", cmd_cherry_pick, RUN_SETUP | NOT_BARE },
+		{ "commit", cmd_commit, RUN_SETUP },
 		{ "commit-tree", cmd_commit_tree, RUN_SETUP },
 		{ "config", cmd_config },
 		{ "count-objects", cmd_count_objects, RUN_SETUP },
@@ -279,10 +280,10 @@ static void handle_internal_command(int argc, const char **argv, char **envp)
 		{ "rev-parse", cmd_rev_parse, RUN_SETUP },
 		{ "revert", cmd_revert, RUN_SETUP | NOT_BARE },
 		{ "rm", cmd_rm, RUN_SETUP | NOT_BARE },
-		{ "runstatus", cmd_runstatus, RUN_SETUP | NOT_BARE },
 		{ "shortlog", cmd_shortlog, RUN_SETUP | USE_PAGER },
 		{ "show-branch", cmd_show_branch, RUN_SETUP },
 		{ "show", cmd_show, RUN_SETUP | USE_PAGER },
+		{ "status", cmd_status, RUN_SETUP | NOT_BARE },
 		{ "stripspace", cmd_stripspace },
 		{ "symbolic-ref", cmd_symbolic_ref, RUN_SETUP },
 		{ "tar-tree", cmd_tar_tree },
diff --git a/mktag.c b/mktag.c
index b82e377..26b9ebf 100644
--- a/mktag.c
+++ b/mktag.c
@@ -111,8 +111,8 @@ static int verify_tag(char *buffer, unsigned long size)
 
 int main(int argc, char **argv)
 {
-	unsigned long size = 4096;
-	char *buffer = xmalloc(size);
+	unsigned long size;
+	char *buffer;
 	unsigned char result_sha1[20];
 
 	if (argc != 1)
@@ -120,10 +120,8 @@ int main(int argc, char **argv)
 
 	setup_git_directory();
 
-	if (read_pipe(0, &buffer, &size)) {
-		free(buffer);
+	if (read_fd(0, &buffer, &size))
 		die("could not read from stdin");
-	}
 
 	/* Verify it for some basic sanity: it needs to start with
 	   "object <sha1>\ntype\ntagger " */
diff --git a/sha1_file.c b/sha1_file.c
index 2b86086..91e8854 100644
--- a/sha1_file.c
+++ b/sha1_file.c
@@ -2286,18 +2286,17 @@ int has_sha1_file(const unsigned char *sha1)
 }
 
 /*
- * reads from fd as long as possible into a supplied buffer of size bytes.
- * If necessary the buffer's size is increased using realloc()
+ * reads from fd as long as possible and allocates a buffer to hold
+ * the contents.  The buffer and size of the contents is returned in
+ * *return_buf and *return_size.  In case of failure, the allocated
+ * buffers are freed, otherwise, the buffer must be freed using xfree.
  *
  * returns 0 if anything went fine and -1 otherwise
- *
- * NOTE: both buf and size may change, but even when -1 is returned
- * you still have to free() it yourself.
  */
-int read_pipe(int fd, char** return_buf, unsigned long* return_size)
+int read_fd(int fd, char** return_buf, unsigned long* return_size)
 {
-	char* buf = *return_buf;
-	unsigned long size = *return_size;
+	unsigned long size = 4096;
+	char* buf = xmalloc(size);
 	ssize_t iret;
 	unsigned long off = 0;
 
@@ -2315,21 +2314,38 @@ int read_pipe(int fd, char** return_buf, unsigned long* return_size)
 	*return_buf = buf;
 	*return_size = off;
 
-	if (iret < 0)
+	if (iret < 0) {
+		free(buf);
+		return -1;
+	}
+
+	return 0;
+}
+
+int read_path(const char *path, char** return_buf, unsigned long* return_size)
+{
+	int fd; 
+
+	fd = open(path, O_RDONLY);
+	if (fd < 0)
+		return -1;
+	if (read_fd(fd, return_buf, return_size)) {
+		close(fd);
 		return -1;
+	}
+	close(fd);
+
 	return 0;
 }
 
 int index_pipe(unsigned char *sha1, int fd, const char *type, int write_object)
 {
-	unsigned long size = 4096;
-	char *buf = xmalloc(size);
+	unsigned long size;
+	char *buf;
 	int ret;
 
-	if (read_pipe(fd, &buf, &size)) {
-		free(buf);
+	if (read_fd(fd, &buf, &size))
 		return -1;
-	}
 
 	if (!type)
 		type = blob_type;
diff --git a/t/t7800-commit.sh b/t/t7800-commit.sh
new file mode 100644
index 0000000..1f97caa
--- /dev/null
+++ b/t/t7800-commit.sh
@@ -0,0 +1,126 @@
+#!/bin/sh
+#
+# Copyright (c) 2007 Kristian Høgsberg <krh@redhat.com>
+#
+
+# FIXME: Test the various index usages, -i and -o, test reflog,
+# signoff, hooks
+
+test_description='git-commit'
+. ./test-lib.sh
+
+# Pick a date so we get consistent commits. 7/7/07 means good luck!
+export GIT_AUTHOR_DATE="July 7, 2007"
+export GIT_COMMITTER_DATE="July 7, 2007"
+
+echo "bongo bongo" >file
+test_expect_success \
+	"initial status" \
+	"git-add file && \
+	 git-status | grep 'Initial commit'"
+
+test_expect_failure \
+	"fail initial amend" \
+	"git-commit -m initial --amend"
+
+test_expect_success \
+	"initial commit" \
+	"git-commit -m initial"
+
+test_expect_failure \
+	"testing nothing to commit" \
+	"git-commit -m initial"
+
+echo "bongo bongo bongo" >file
+
+test_expect_success \
+	"next commit" \
+	"git-commit -m next -a"
+
+echo "more bongo: bongo bongo bongo bongo" >file
+
+test_expect_failure \
+	"commit message from non-existing file" \
+	"git-commit -F gah -a"
+
+cat >msg <<EOF
+		
+
+  
+Signed-off-by: hula
+EOF
+test_expect_failure \
+	"empty commit message" \
+	"git-commit -F msg -a"
+
+echo "this is the commit message, coming from a file" >msg
+test_expect_success \
+	"commit message from file" \
+	"git-commit -F msg -a"
+
+cat >editor <<\EOF
+#!/bin/sh
+sed -i -e "s/a file/an amend commit/g" $1
+EOF
+chmod 755 editor
+
+test_expect_success \
+	"amend commit" \
+	"VISUAL=./editor git-commit --amend"
+
+echo "enough with the bongos" >file
+test_expect_failure \
+	"passing --amend and -F" \
+	"git-commit -F msg --amend ."
+
+test_expect_success \
+	"using message from other commit" \
+	"git-commit -C HEAD^ ."
+
+cat >editor <<\EOF
+#!/bin/sh
+sed -i -e "s/amend/older/g" $1
+EOF
+chmod 755 editor
+
+echo "hula hula" >file
+test_expect_success \
+	"editing message from other commit" \
+	"VISUAL=./editor git-commit -c HEAD^ -a"
+
+echo "silly new contents" >file
+test_expect_success \
+	"message from stdin" \
+	"echo commit message from stdin | git-commit -F - -a"
+
+echo "gak" >file
+test_expect_success \
+	"overriding author from command line" \
+	"git-commit -m 'author' --author 'Rubber Duck <rduck@convoy.org>' -a"
+
+test_expect_success \
+	"interactive add" \
+	"echo 7 | git-commit --interactive | grep 'What now'"
+
+test_expect_success \
+	"showing committed revisions" \
+	"git-rev-list HEAD >current"
+
+# We could just check the head sha1, but checking each commit makes it
+# easier to isolate bugs.
+
+cat >expected <<\EOF
+9a7ad90c32f43aecc081722a72f26ead981bd6fa
+d5c6c34f0361d64d3d1b1f26736b1ae98e2baa90
+ca0ddb06fc90f3f857dd623f8418804aad75d9fa
+9cc5d9b9e5a29f1c46d0d0cf2dd716fb8241316a
+ca750e97c137587aa181b6b9526b3b04fb9db667
+4515202a60be41cb1b8f6b89edb3f6948130ac1c
+7cc9a125522fe28b84cd3f6c7aeef6e5c62b3f8b
+EOF
+
+test_expect_success \
+    'validate git-rev-list output.' \
+    'diff current expected'
+
+test_done
diff --git a/wt-status.c b/wt-status.c
index 5205420..5bf800a 100644
--- a/wt-status.c
+++ b/wt-status.c
@@ -54,29 +54,30 @@ void wt_status_prepare(struct wt_status *s)
 	s->reference = "HEAD";
 }
 
-static void wt_status_print_cached_header(const char *reference)
+static void wt_status_print_cached_header(struct wt_status *s)
 {
 	const char *c = color(WT_STATUS_HEADER);
-	color_printf_ln(c, "# Changes to be committed:");
-	if (reference) {
-		color_printf_ln(c, "#   (use \"git reset %s <file>...\" to unstage)", reference);
+	color_fprintf_ln(s->fp, c, "# Changes to be committed:");
+	if (s->reference) {
+		color_fprintf_ln(s->fp, c, "#   (use \"git reset %s <file>...\" to unstage)", s->reference);
 	} else {
-		color_printf_ln(c, "#   (use \"git rm --cached <file>...\" to unstage)");
+		color_fprintf_ln(s->fp, c, "#   (use \"git rm --cached <file>...\" to unstage)");
 	}
-	color_printf_ln(c, "#");
+	color_fprintf_ln(s->fp, c, "#");
 }
 
-static void wt_status_print_header(const char *main, const char *sub)
+static void wt_status_print_header(struct wt_status *s,
+				   const char *main, const char *sub)
 {
 	const char *c = color(WT_STATUS_HEADER);
-	color_printf_ln(c, "# %s:", main);
-	color_printf_ln(c, "#   (%s)", sub);
-	color_printf_ln(c, "#");
+	color_fprintf_ln(s->fp, c, "# %s:", main);
+	color_fprintf_ln(s->fp, c, "#   (%s)", sub);
+	color_fprintf_ln(s->fp, c, "#");
 }
 
-static void wt_status_print_trailer(void)
+static void wt_status_print_trailer(struct wt_status *s)
 {
-	color_printf_ln(color(WT_STATUS_HEADER), "#");
+	color_fprintf_ln(s->fp, color(WT_STATUS_HEADER), "#");
 }
 
 static const char *quote_crlf(const char *in, char *buf, size_t sz)
@@ -108,7 +109,8 @@ static const char *quote_crlf(const char *in, char *buf, size_t sz)
 	return ret;
 }
 
-static void wt_status_print_filepair(int t, struct diff_filepair *p)
+static void wt_status_print_filepair(struct wt_status *s,
+				     int t, struct diff_filepair *p)
 {
 	const char *c = color(t);
 	const char *one, *two;
@@ -117,36 +119,36 @@ static void wt_status_print_filepair(int t, struct diff_filepair *p)
 	one = quote_crlf(p->one->path, onebuf, sizeof(onebuf));
 	two = quote_crlf(p->two->path, twobuf, sizeof(twobuf));
 
-	color_printf(color(WT_STATUS_HEADER), "#\t");
+	color_fprintf(s->fp, color(WT_STATUS_HEADER), "#\t");
 	switch (p->status) {
 	case DIFF_STATUS_ADDED:
-		color_printf(c, "new file:   %s", one);
+		color_fprintf(s->fp, c, "new file:   %s", one);
 		break;
 	case DIFF_STATUS_COPIED:
-		color_printf(c, "copied:     %s -> %s", one, two);
+		color_fprintf(s->fp, c, "copied:     %s -> %s", one, two);
 		break;
 	case DIFF_STATUS_DELETED:
-		color_printf(c, "deleted:    %s", one);
+		color_fprintf(s->fp, c, "deleted:    %s", one);
 		break;
 	case DIFF_STATUS_MODIFIED:
-		color_printf(c, "modified:   %s", one);
+		color_fprintf(s->fp, c, "modified:   %s", one);
 		break;
 	case DIFF_STATUS_RENAMED:
-		color_printf(c, "renamed:    %s -> %s", one, two);
+		color_fprintf(s->fp, c, "renamed:    %s -> %s", one, two);
 		break;
 	case DIFF_STATUS_TYPE_CHANGED:
-		color_printf(c, "typechange: %s", one);
+		color_fprintf(s->fp, c, "typechange: %s", one);
 		break;
 	case DIFF_STATUS_UNKNOWN:
-		color_printf(c, "unknown:    %s", one);
+		color_fprintf(s->fp, c, "unknown:    %s", one);
 		break;
 	case DIFF_STATUS_UNMERGED:
-		color_printf(c, "unmerged:   %s", one);
+		color_fprintf(s->fp, c, "unmerged:   %s", one);
 		break;
 	default:
 		die("bug: unhandled diff status %c", p->status);
 	}
-	printf("\n");
+	fprintf(s->fp, "\n");
 }
 
 static void wt_status_print_updated_cb(struct diff_queue_struct *q,
@@ -160,14 +162,14 @@ static void wt_status_print_updated_cb(struct diff_queue_struct *q,
 		if (q->queue[i]->status == 'U')
 			continue;
 		if (!shown_header) {
-			wt_status_print_cached_header(s->reference);
+			wt_status_print_cached_header(s);
 			s->commitable = 1;
 			shown_header = 1;
 		}
-		wt_status_print_filepair(WT_STATUS_UPDATED, q->queue[i]);
+		wt_status_print_filepair(s, WT_STATUS_UPDATED, q->queue[i]);
 	}
 	if (shown_header)
-		wt_status_print_trailer();
+		wt_status_print_trailer(s);
 }
 
 static void wt_status_print_changed_cb(struct diff_queue_struct *q,
@@ -184,18 +186,18 @@ static void wt_status_print_changed_cb(struct diff_queue_struct *q,
 				msg = use_add_rm_msg;
 				break;
 			}
-		wt_status_print_header("Changed but not updated", msg);
+		wt_status_print_header(s, "Changed but not updated", msg);
 	}
 	for (i = 0; i < q->nr; i++)
-		wt_status_print_filepair(WT_STATUS_CHANGED, q->queue[i]);
+		wt_status_print_filepair(s, WT_STATUS_CHANGED, q->queue[i]);
 	if (q->nr)
-		wt_status_print_trailer();
+		wt_status_print_trailer(s);
 }
 
 static void wt_read_cache(struct wt_status *s)
 {
 	discard_cache();
-	read_cache();
+	read_cache_from(s->index_file);
 }
 
 static void wt_status_print_initial(struct wt_status *s)
@@ -206,16 +208,16 @@ static void wt_status_print_initial(struct wt_status *s)
 	wt_read_cache(s);
 	if (active_nr) {
 		s->commitable = 1;
-		wt_status_print_cached_header(NULL);
+		wt_status_print_cached_header(s);
 	}
 	for (i = 0; i < active_nr; i++) {
-		color_printf(color(WT_STATUS_HEADER), "#\t");
-		color_printf_ln(color(WT_STATUS_UPDATED), "new file: %s",
+		color_fprintf(s->fp, color(WT_STATUS_HEADER), "#\t");
+		color_fprintf_ln(s->fp, color(WT_STATUS_UPDATED), "new file: %s",
 				quote_crlf(active_cache[i]->name,
 					   buf, sizeof(buf)));
 	}
 	if (active_nr)
-		wt_status_print_trailer();
+		wt_status_print_trailer(s);
 }
 
 static void wt_status_print_updated(struct wt_status *s)
@@ -281,12 +283,12 @@ static void wt_status_print_untracked(struct wt_status *s)
 		}
 		if (!shown_header) {
 			s->workdir_untracked = 1;
-			wt_status_print_header("Untracked files",
+			wt_status_print_header(s, "Untracked files",
 					       use_add_to_include_msg);
 			shown_header = 1;
 		}
-		color_printf(color(WT_STATUS_HEADER), "#\t");
-		color_printf_ln(color(WT_STATUS_UNTRACKED), "%.*s",
+		color_fprintf(s->fp, color(WT_STATUS_HEADER), "#\t");
+		color_fprintf_ln(s->fp, color(WT_STATUS_UNTRACKED), "%.*s",
 				ent->len, ent->name);
 	}
 }
@@ -316,14 +318,14 @@ void wt_status_print(struct wt_status *s)
 			branch_name = "";
 			on_what = "Not currently on any branch.";
 		}
-		color_printf_ln(color(WT_STATUS_HEADER),
+		color_fprintf_ln(s->fp, color(WT_STATUS_HEADER),
 			"# %s%s", on_what, branch_name);
 	}
 
 	if (s->is_initial) {
-		color_printf_ln(color(WT_STATUS_HEADER), "#");
-		color_printf_ln(color(WT_STATUS_HEADER), "# Initial commit");
-		color_printf_ln(color(WT_STATUS_HEADER), "#");
+		color_fprintf_ln(s->fp, color(WT_STATUS_HEADER), "#");
+		color_fprintf_ln(s->fp, color(WT_STATUS_HEADER), "# Initial commit");
+		color_fprintf_ln(s->fp, color(WT_STATUS_HEADER), "#");
 		wt_status_print_initial(s);
 	}
 	else {
@@ -337,7 +339,7 @@ void wt_status_print(struct wt_status *s)
 		wt_status_print_verbose(s);
 	if (!s->commitable) {
 		if (s->amend)
-			printf("# No changes\n");
+			fprintf(s->fp, "# No changes\n");
 		else if (s->workdir_dirty)
 			printf("no changes added to commit (use \"git add\" and/or \"git commit -a\")\n");
 		else if (s->workdir_untracked)
diff --git a/wt-status.h b/wt-status.h
index cfea4ae..7744932 100644
--- a/wt-status.h
+++ b/wt-status.h
@@ -1,6 +1,8 @@
 #ifndef STATUS_H
 #define STATUS_H
 
+#include <stdio.h>
+
 enum color_wt_status {
 	WT_STATUS_HEADER,
 	WT_STATUS_UPDATED,
@@ -19,6 +21,8 @@ struct wt_status {
 	int commitable;
 	int workdir_dirty;
 	int workdir_untracked;
+	const char *index_file;
+	FILE *fp;
 };
 
 int git_status_config(const char *var, const char *value);
-- 
1.5.2.GIT

             reply	other threads:[~2007-07-18 19:23 UTC|newest]

Thread overview: 4+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2007-07-18 19:19 Kristian Høgsberg [this message]
2007-07-18 19:43 ` [PATCH] Implement git commit as a builtin Nicolas Pitre
2007-07-18 21:27 ` Carlos Rica
2007-07-20 14:45   ` Kristian Høgsberg

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

  List information: http://vger.kernel.org/majordomo-info.html

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=11847863792344-git-send-email-krh@redhat.com \
    --to=krh@redhat.com \
    --cc=git@vger.kernel.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
Code repositories for project(s) associated with this public inbox

	https://80x24.org/mirrors/git.git

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).