Merge branch 'fix'

* fix:
  git-format-patch: Use rfc2822 compliant date.
diff --git a/Documentation/git-repo-config.txt b/Documentation/git-repo-config.txt
index 71f96bd..566cfa1 100644
--- a/Documentation/git-repo-config.txt
+++ b/Documentation/git-repo-config.txt
@@ -15,6 +15,7 @@
 'git-repo-config' [type] --get-all name [value_regex]
 'git-repo-config' [type] --unset name [value_regex]
 'git-repo-config' [type] --unset-all name [value_regex]
+'git-repo-config' -l | --list
 
 DESCRIPTION
 -----------
@@ -64,6 +65,9 @@
 --unset-all::
 	Remove all matching lines from .git/config.
 
+-l, --list::
+	List all variables set in .git/config.
+
 
 EXAMPLE
 -------
diff --git a/Documentation/git-var.txt b/Documentation/git-var.txt
index 379571e..a5b1a0d 100644
--- a/Documentation/git-var.txt
+++ b/Documentation/git-var.txt
@@ -19,7 +19,8 @@
 -l::
 	Cause the logical variables to be listed. In addition, all the
 	variables of the git configuration file .git/config are listed
-	as well.
+	as well. (However, the configuration variables listing functionality
+	is deprecated in favor of `git-repo-config -l`.)
 
 EXAMPLE
 --------
diff --git a/Makefile b/Makefile
index 8aed3af..8ce27a6 100644
--- a/Makefile
+++ b/Makefile
@@ -199,7 +199,7 @@
 	tree-walk.h log-tree.h
 
 DIFF_OBJS = \
-	diff.o diffcore-break.o diffcore-order.o \
+	diff.o diff-lib.o diffcore-break.o diffcore-order.o \
 	diffcore-pickaxe.o diffcore-rename.o tree-diff.o combine-diff.o \
 	diffcore-delta.o log-tree.o
 
@@ -213,6 +213,9 @@
 	fetch-clone.o revision.o pager.o tree-walk.o xdiff-interface.o \
 	$(DIFF_OBJS)
 
+BUILTIN_OBJS = \
+	builtin-log.o builtin-help.o
+
 GITLIBS = $(LIB_FILE) $(XDIFF_LIB)
 LIBS = $(GITLIBS) -lz
 
@@ -462,10 +465,12 @@
 strip: $(PROGRAMS) git$X
 	$(STRIP) $(STRIP_OPTS) $(PROGRAMS) git$X
 
-git$X: git.c common-cmds.h $(GITLIBS)
+git$X: git.c common-cmds.h $(BUILTIN_OBJS) $(GITLIBS)
 	$(CC) -DGIT_VERSION='"$(GIT_VERSION)"' \
 		$(ALL_CFLAGS) -o $@ $(filter %.c,$^) \
-		$(ALL_LDFLAGS) $(LIBS)
+		$(BUILTIN_OBJS) $(ALL_LDFLAGS) $(LIBS)
+
+builtin-help.o: common-cmds.h
 
 $(BUILT_INS): git$X
 	rm -f $@ && ln git$X $@
@@ -565,17 +570,17 @@
 	$(CC) -c $(ALL_CFLAGS) \
 		-DDEFAULT_GIT_TEMPLATE_DIR='"$(template_dir_SQ)"' $*.c
 
-$(LIB_OBJS): $(LIB_H)
+$(LIB_OBJS) $(BUILTIN_OBJS): $(LIB_H)
 $(patsubst git-%$X,%.o,$(PROGRAMS)): $(GITLIBS)
 $(DIFF_OBJS): diffcore.h
 
 $(LIB_FILE): $(LIB_OBJS)
-	$(AR) rcs $@ $(LIB_OBJS)
+	rm -f $@ && $(AR) rcs $@ $(LIB_OBJS)
 
 XDIFF_OBJS=xdiff/xdiffi.o xdiff/xprepare.o xdiff/xutils.o xdiff/xemit.o
 
 $(XDIFF_LIB): $(XDIFF_OBJS)
-	$(AR) rcs $@ $(XDIFF_OBJS)
+	rm -f $@ && $(AR) rcs $@ $(XDIFF_OBJS)
 
 
 doc:
diff --git a/builtin-help.c b/builtin-help.c
new file mode 100644
index 0000000..7470faa
--- /dev/null
+++ b/builtin-help.c
@@ -0,0 +1,242 @@
+/*
+ * builtin-help.c
+ *
+ * Builtin help-related commands (help, usage, version)
+ */
+#include <sys/ioctl.h>
+#include "cache.h"
+#include "builtin.h"
+#include "exec_cmd.h"
+#include "common-cmds.h"
+
+static const char git_usage[] =
+	"Usage: git [--version] [--exec-path[=GIT_EXEC_PATH]] [--help] COMMAND [ ARGS ]";
+
+/* most gui terms set COLUMNS (although some don't export it) */
+static int term_columns(void)
+{
+	char *col_string = getenv("COLUMNS");
+	int n_cols = 0;
+
+	if (col_string && (n_cols = atoi(col_string)) > 0)
+		return n_cols;
+
+#ifdef TIOCGWINSZ
+	{
+		struct winsize ws;
+		if (!ioctl(1, TIOCGWINSZ, &ws)) {
+			if (ws.ws_col)
+				return ws.ws_col;
+		}
+	}
+#endif
+
+	return 80;
+}
+
+static void oom(void)
+{
+	fprintf(stderr, "git: out of memory\n");
+	exit(1);
+}
+
+static inline void mput_char(char c, unsigned int num)
+{
+	while(num--)
+		putchar(c);
+}
+
+static struct cmdname {
+	size_t len;
+	char name[1];
+} **cmdname;
+static int cmdname_alloc, cmdname_cnt;
+
+static void add_cmdname(const char *name, int len)
+{
+	struct cmdname *ent;
+	if (cmdname_alloc <= cmdname_cnt) {
+		cmdname_alloc = cmdname_alloc + 200;
+		cmdname = realloc(cmdname, cmdname_alloc * sizeof(*cmdname));
+		if (!cmdname)
+			oom();
+	}
+	ent = malloc(sizeof(*ent) + len);
+	if (!ent)
+		oom();
+	ent->len = len;
+	memcpy(ent->name, name, len);
+	ent->name[len] = 0;
+	cmdname[cmdname_cnt++] = ent;
+}
+
+static int cmdname_compare(const void *a_, const void *b_)
+{
+	struct cmdname *a = *(struct cmdname **)a_;
+	struct cmdname *b = *(struct cmdname **)b_;
+	return strcmp(a->name, b->name);
+}
+
+static void pretty_print_string_list(struct cmdname **cmdname, int longest)
+{
+	int cols = 1, rows;
+	int space = longest + 1; /* min 1 SP between words */
+	int max_cols = term_columns() - 1; /* don't print *on* the edge */
+	int i, j;
+
+	if (space < max_cols)
+		cols = max_cols / space;
+	rows = (cmdname_cnt + cols - 1) / cols;
+
+	qsort(cmdname, cmdname_cnt, sizeof(*cmdname), cmdname_compare);
+
+	for (i = 0; i < rows; i++) {
+		printf("  ");
+
+		for (j = 0; j < cols; j++) {
+			int n = j * rows + i;
+			int size = space;
+			if (n >= cmdname_cnt)
+				break;
+			if (j == cols-1 || n + rows >= cmdname_cnt)
+				size = 1;
+			printf("%-*s", size, cmdname[n]->name);
+		}
+		putchar('\n');
+	}
+}
+
+static void list_commands(const char *exec_path, const char *pattern)
+{
+	unsigned int longest = 0;
+	char path[PATH_MAX];
+	int dirlen;
+	DIR *dir = opendir(exec_path);
+	struct dirent *de;
+
+	if (!dir) {
+		fprintf(stderr, "git: '%s': %s\n", exec_path, strerror(errno));
+		exit(1);
+	}
+
+	dirlen = strlen(exec_path);
+	if (PATH_MAX - 20 < dirlen) {
+		fprintf(stderr, "git: insanely long exec-path '%s'\n",
+			exec_path);
+		exit(1);
+	}
+
+	memcpy(path, exec_path, dirlen);
+	path[dirlen++] = '/';
+
+	while ((de = readdir(dir)) != NULL) {
+		struct stat st;
+		int entlen;
+
+		if (strncmp(de->d_name, "git-", 4))
+			continue;
+		strcpy(path+dirlen, de->d_name);
+		if (stat(path, &st) || /* stat, not lstat */
+		    !S_ISREG(st.st_mode) ||
+		    !(st.st_mode & S_IXUSR))
+			continue;
+
+		entlen = strlen(de->d_name);
+		if (4 < entlen && !strcmp(de->d_name + entlen - 4, ".exe"))
+			entlen -= 4;
+
+		if (longest < entlen)
+			longest = entlen;
+
+		add_cmdname(de->d_name + 4, entlen-4);
+	}
+	closedir(dir);
+
+	printf("git commands available in '%s'\n", exec_path);
+	printf("----------------------------");
+	mput_char('-', strlen(exec_path));
+	putchar('\n');
+	pretty_print_string_list(cmdname, longest - 4);
+	putchar('\n');
+}
+
+static void list_common_cmds_help(void)
+{
+	int i, longest = 0;
+
+	for (i = 0; i < ARRAY_SIZE(common_cmds); i++) {
+		if (longest < strlen(common_cmds[i].name))
+			longest = strlen(common_cmds[i].name);
+	}
+
+	puts("The most commonly used git commands are:");
+	for (i = 0; i < ARRAY_SIZE(common_cmds); i++) {
+		printf("    %s", common_cmds[i].name);
+		mput_char(' ', longest - strlen(common_cmds[i].name) + 4);
+		puts(common_cmds[i].help);
+	}
+	puts("(use 'git help -a' to get a list of all installed git commands)");
+}
+
+void cmd_usage(int show_all, const char *exec_path, const char *fmt, ...)
+{
+	if (fmt) {
+		va_list ap;
+
+		va_start(ap, fmt);
+		printf("git: ");
+		vprintf(fmt, ap);
+		va_end(ap);
+		putchar('\n');
+	}
+	else
+		puts(git_usage);
+
+	if (exec_path) {
+		putchar('\n');
+		if (show_all)
+			list_commands(exec_path, "git-*");
+		else
+			list_common_cmds_help();
+        }
+
+	exit(1);
+}
+
+static void show_man_page(const char *git_cmd)
+{
+	const char *page;
+
+	if (!strncmp(git_cmd, "git", 3))
+		page = git_cmd;
+	else {
+		int page_len = strlen(git_cmd) + 4;
+		char *p = malloc(page_len + 1);
+		strcpy(p, "git-");
+		strcpy(p + 4, git_cmd);
+		p[page_len] = 0;
+		page = p;
+	}
+
+	execlp("man", "man", page, NULL);
+}
+
+int cmd_version(int argc, const char **argv, char **envp)
+{
+	printf("git version %s\n", git_version_string);
+	return 0;
+}
+
+int cmd_help(int argc, const char **argv, char **envp)
+{
+	const char *help_cmd = argv[1];
+	if (!help_cmd)
+		cmd_usage(0, git_exec_path(), NULL);
+	else if (!strcmp(help_cmd, "--all") || !strcmp(help_cmd, "-a"))
+		cmd_usage(1, git_exec_path(), NULL);
+	else
+		show_man_page(help_cmd);
+	return 0;
+}
+
+
diff --git a/builtin-log.c b/builtin-log.c
new file mode 100644
index 0000000..69f2911
--- /dev/null
+++ b/builtin-log.c
@@ -0,0 +1,69 @@
+/*
+ * Builtin "git log" and related commands (show, whatchanged)
+ *
+ * (C) Copyright 2006 Linus Torvalds
+ *		 2006 Junio Hamano
+ */
+#include "cache.h"
+#include "commit.h"
+#include "diff.h"
+#include "revision.h"
+#include "log-tree.h"
+
+static int cmd_log_wc(int argc, const char **argv, char **envp,
+		      struct rev_info *rev)
+{
+	struct commit *commit;
+
+	rev->abbrev = DEFAULT_ABBREV;
+	rev->commit_format = CMIT_FMT_DEFAULT;
+	rev->verbose_header = 1;
+	argc = setup_revisions(argc, argv, rev, "HEAD");
+
+	if (argc > 1)
+		die("unrecognized argument: %s", argv[1]);
+
+	prepare_revision_walk(rev);
+	setup_pager();
+	while ((commit = get_revision(rev)) != NULL) {
+		log_tree_commit(rev, commit);
+		free(commit->buffer);
+		commit->buffer = NULL;
+	}
+	return 0;
+}
+
+int cmd_whatchanged(int argc, const char **argv, char **envp)
+{
+	struct rev_info rev;
+
+	init_revisions(&rev);
+	rev.diff = 1;
+	rev.diffopt.recursive = 1;
+	return cmd_log_wc(argc, argv, envp, &rev);
+}
+
+int cmd_show(int argc, const char **argv, char **envp)
+{
+	struct rev_info rev;
+
+	init_revisions(&rev);
+	rev.diff = 1;
+	rev.diffopt.recursive = 1;
+	rev.combine_merges = 1;
+	rev.dense_combined_merges = 1;
+	rev.always_show_header = 1;
+	rev.ignore_merges = 0;
+	rev.no_walk = 1;
+	return cmd_log_wc(argc, argv, envp, &rev);
+}
+
+int cmd_log(int argc, const char **argv, char **envp)
+{
+	struct rev_info rev;
+
+	init_revisions(&rev);
+	rev.always_show_header = 1;
+	rev.diffopt.recursive = 1;
+	return cmd_log_wc(argc, argv, envp, &rev);
+}
diff --git a/builtin.h b/builtin.h
new file mode 100644
index 0000000..47408a0
--- /dev/null
+++ b/builtin.h
@@ -0,0 +1,23 @@
+#ifndef BUILTIN_H
+#define BUILTIN_H
+
+#ifndef PATH_MAX
+# define PATH_MAX 4096
+#endif
+
+extern const char git_version_string[];
+
+void cmd_usage(int show_all, const char *exec_path, const char *fmt, ...)
+#ifdef __GNUC__
+	__attribute__((__format__(__printf__, 3, 4), __noreturn__))
+#endif
+	;
+
+extern int cmd_help(int argc, const char **argv, char **envp);
+extern int cmd_version(int argc, const char **argv, char **envp);
+
+extern int cmd_whatchanged(int argc, const char **argv, char **envp);
+extern int cmd_show(int argc, const char **argv, char **envp);
+extern int cmd_log(int argc, const char **argv, char **envp);
+
+#endif
diff --git a/combine-diff.c b/combine-diff.c
index 9445e86..ca36f5d 100644
--- a/combine-diff.c
+++ b/combine-diff.c
@@ -5,6 +5,7 @@
 #include "diffcore.h"
 #include "quote.h"
 #include "xdiff-interface.h"
+#include "log-tree.h"
 
 static int uninteresting(struct diff_filepair *p)
 {
@@ -584,10 +585,20 @@
 	sline->p_lno[i] = sline->p_lno[j];
 }
 
-static int show_patch_diff(struct combine_diff_path *elem, int num_parent,
-			   int dense, const char *header,
-			   struct diff_options *opt)
+static void dump_quoted_path(const char *prefix, const char *path)
 {
+	fputs(prefix, stdout);
+	if (quote_c_style(path, NULL, NULL, 0))
+		quote_c_style(path, NULL, stdout, 0);
+	else
+		printf("%s", path);
+	putchar('\n');
+}
+
+static int show_patch_diff(struct combine_diff_path *elem, int num_parent,
+			   int dense, struct rev_info *rev)
+{
+	struct diff_options *opt = &rev->diffopt;
 	unsigned long result_size, cnt, lno;
 	char *result, *cp;
 	struct sline *sline; /* survived lines */
@@ -688,16 +699,9 @@
 	if (show_hunks || mode_differs || working_tree_file) {
 		const char *abb;
 
-		if (header) {
-			shown_header++;
-			printf("%s%c", header, opt->line_termination);
-		}
-		printf("diff --%s ", dense ? "cc" : "combined");
-		if (quote_c_style(elem->path, NULL, NULL, 0))
-			quote_c_style(elem->path, NULL, stdout, 0);
-		else
-			printf("%s", elem->path);
-		putchar('\n');
+		if (rev->loginfo)
+			show_log(rev, rev->loginfo, "\n");
+		dump_quoted_path(dense ? "diff --cc " : "diff --combined ", elem->path);
 		printf("index ");
 		for (i = 0; i < num_parent; i++) {
 			abb = find_unique_abbrev(elem->parent[i].sha1,
@@ -728,6 +732,8 @@
 			}
 			putchar('\n');
 		}
+		dump_quoted_path("--- a/", elem->path);
+		dump_quoted_path("+++ b/", elem->path);
 		dump_sline(sline, cnt, num_parent);
 	}
 	free(result);
@@ -749,8 +755,9 @@
 
 #define COLONS "::::::::::::::::::::::::::::::::"
 
-static void show_raw_diff(struct combine_diff_path *p, int num_parent, const char *header, struct diff_options *opt)
+static void show_raw_diff(struct combine_diff_path *p, int num_parent, struct rev_info *rev)
 {
+	struct diff_options *opt = &rev->diffopt;
 	int i, offset;
 	const char *prefix;
 	int line_termination, inter_name_termination;
@@ -760,8 +767,8 @@
 	if (!line_termination)
 		inter_name_termination = 0;
 
-	if (header)
-		printf("%s%c", header, line_termination);
+	if (rev->loginfo)
+		show_log(rev, rev->loginfo, "\n");
 
 	if (opt->output_format == DIFF_FORMAT_RAW) {
 		offset = strlen(COLONS) - num_parent;
@@ -802,40 +809,44 @@
 	}
 }
 
-int show_combined_diff(struct combine_diff_path *p,
+void show_combined_diff(struct combine_diff_path *p,
 		       int num_parent,
 		       int dense,
-		       const char *header,
-		       struct diff_options *opt)
+		       struct rev_info *rev)
 {
+	struct diff_options *opt = &rev->diffopt;
 	if (!p->len)
-		return 0;
+		return;
 	switch (opt->output_format) {
 	case DIFF_FORMAT_RAW:
 	case DIFF_FORMAT_NAME_STATUS:
 	case DIFF_FORMAT_NAME:
-		show_raw_diff(p, num_parent, header, opt);
-		return 1;
-
-	default:
+		show_raw_diff(p, num_parent, rev);
+		return;
 	case DIFF_FORMAT_PATCH:
-		return show_patch_diff(p, num_parent, dense, header, opt);
+		show_patch_diff(p, num_parent, dense, rev);
+		return;
+	default:
+		return;
 	}
 }
 
-const char *diff_tree_combined_merge(const unsigned char *sha1,
-			     const char *header, int dense,
-			     struct diff_options *opt)
+void diff_tree_combined_merge(const unsigned char *sha1,
+			     int dense, struct rev_info *rev)
 {
+	struct diff_options *opt = &rev->diffopt;
 	struct commit *commit = lookup_commit(sha1);
 	struct diff_options diffopts;
 	struct commit_list *parents;
 	struct combine_diff_path *p, *paths = NULL;
 	int num_parent, i, num_paths;
+	int do_diffstat;
 
+	do_diffstat = (opt->output_format == DIFF_FORMAT_DIFFSTAT ||
+		       opt->with_stat);
 	diffopts = *opt;
-	diffopts.output_format = DIFF_FORMAT_NO_OUTPUT;
 	diffopts.with_raw = 0;
+	diffopts.with_stat = 0;
 	diffopts.recursive = 1;
 
 	/* count parents */
@@ -849,11 +860,24 @@
 	     parents;
 	     parents = parents->next, i++) {
 		struct commit *parent = parents->item;
+		/* show stat against the first parent even
+		 * when doing combined diff.
+		 */
+		if (i == 0 && do_diffstat)
+			diffopts.output_format = DIFF_FORMAT_DIFFSTAT;
+		else
+			diffopts.output_format = DIFF_FORMAT_NO_OUTPUT;
 		diff_tree_sha1(parent->object.sha1, commit->object.sha1, "",
 			       &diffopts);
 		diffcore_std(&diffopts);
 		paths = intersect_paths(paths, i, num_parent);
+
+		if (do_diffstat && rev->loginfo)
+			show_log(rev, rev->loginfo,
+				 opt->with_stat ? "---\n" : "\n");
 		diff_flush(&diffopts);
+		if (opt->with_stat)
+			putchar('\n');
 	}
 
 	/* find out surviving paths */
@@ -866,17 +890,13 @@
 			int saved_format = opt->output_format;
 			opt->output_format = DIFF_FORMAT_RAW;
 			for (p = paths; p; p = p->next) {
-				if (show_combined_diff(p, num_parent, dense,
-						       header, opt))
-					header = NULL;
+				show_combined_diff(p, num_parent, dense, rev);
 			}
 			opt->output_format = saved_format;
 			putchar(opt->line_termination);
 		}
 		for (p = paths; p; p = p->next) {
-			if (show_combined_diff(p, num_parent, dense,
-					       header, opt))
-				header = NULL;
+			show_combined_diff(p, num_parent, dense, rev);
 		}
 	}
 
@@ -886,5 +906,4 @@
 		paths = paths->next;
 		free(tmp);
 	}
-	return header;
 }
diff --git a/commit-tree.c b/commit-tree.c
index 2595850..bad72e8 100644
--- a/commit-tree.c
+++ b/commit-tree.c
@@ -91,7 +91,7 @@
 
 	git_config(git_default_config);
 
-	if (argc < 2 || get_sha1_hex(argv[1], tree_sha1) < 0)
+	if (argc < 2 || get_sha1(argv[1], tree_sha1) < 0)
 		usage(commit_tree_usage);
 
 	check_valid(tree_sha1, tree_type);
diff --git a/commit.h b/commit.h
index 918c9ab..de142af 100644
--- a/commit.h
+++ b/commit.h
@@ -45,6 +45,8 @@
 	CMIT_FMT_FULL,
 	CMIT_FMT_FULLER,
 	CMIT_FMT_ONELINE,
+
+	CMIT_FMT_UNSPECIFIED,
 };
 
 extern enum cmit_fmt get_commit_format(const char *arg);
diff --git a/contrib/colordiff/README b/contrib/colordiff/README
new file mode 100644
index 0000000..2678fdf
--- /dev/null
+++ b/contrib/colordiff/README
@@ -0,0 +1,2 @@
+This is "colordiff" (http://colordiff.sourceforge.net/) by Dave
+Ewart <davee@sungate.co.uk>, modified specifically for git.
diff --git a/contrib/colordiff/colordiff.perl b/contrib/colordiff/colordiff.perl
new file mode 100755
index 0000000..5789cfb
--- /dev/null
+++ b/contrib/colordiff/colordiff.perl
@@ -0,0 +1,196 @@
+#!/usr/bin/perl -w
+#
+# $Id: colordiff.pl,v 1.4.2.10 2004/01/04 15:02:59 daveewart Exp $
+
+########################################################################
+#                                                                      #
+# ColorDiff - a wrapper/replacment for 'diff' producing                #
+#             colourful output                                         #
+#                                                                      #
+# Copyright (C)2002-2004 Dave Ewart (davee@sungate.co.uk)              #
+#                                                                      #
+########################################################################
+#                                                                      #
+# This program is free software; you can redistribute it and/or modify #
+# it under the terms of the GNU General Public License as published by #
+# the Free Software Foundation; either version 2 of the License, or    #
+# (at your option) any later version.                                  #
+#                                                                      #
+# This program is distributed in the hope that it will be useful,      #
+# but WITHOUT ANY WARRANTY; without even the implied warranty of       #
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        #
+# GNU General Public License for more details.                         #
+#                                                                      #
+# You should have received a copy of the GNU General Public License    #
+# along with this program; if not, write to the Free Software          #
+# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.            #
+#                                                                      #
+########################################################################
+
+use strict;
+use Getopt::Long qw(:config pass_through);
+use IPC::Open2;
+
+my $app_name     = 'colordiff';
+my $version      = '1.0.4';
+my $author       = 'Dave Ewart';
+my $author_email = 'davee@sungate.co.uk';
+my $app_www      = 'http://colordiff.sourceforge.net/';
+my $copyright    = '(C)2002-2004';
+my $show_banner  = 1;
+
+# ANSI sequences for colours
+my %colour;
+$colour{white}       = "\033[1;37m";
+$colour{yellow}      = "\033[1;33m";
+$colour{green}       = "\033[1;32m";
+$colour{blue}        = "\033[1;34m";
+$colour{cyan}        = "\033[1;36m";
+$colour{red}         = "\033[1;31m";
+$colour{magenta}     = "\033[1;35m";
+$colour{black}       = "\033[1;30m";
+$colour{darkwhite}   = "\033[0;37m";
+$colour{darkyellow}  = "\033[0;33m";
+$colour{darkgreen}   = "\033[0;32m";
+$colour{darkblue}    = "\033[0;34m";
+$colour{darkcyan}    = "\033[0;36m";
+$colour{darkred}     = "\033[0;31m";
+$colour{darkmagenta} = "\033[0;35m";
+$colour{darkblack}   = "\033[0;30m";
+$colour{OFF}         = "\033[0;0m";
+
+# Default colours if /etc/colordiffrc or ~/.colordiffrc do not exist
+my $plain_text = $colour{OFF};
+my $file_old   = $colour{red};
+my $file_new   = $colour{blue};
+my $diff_stuff = $colour{magenta};
+
+# Locations for personal and system-wide colour configurations
+my $HOME   = $ENV{HOME};
+my $etcdir = '/etc';
+
+my ($setting, $value);
+my @config_files = ("$etcdir/colordiffrc", "$HOME/.colordiffrc");
+my $config_file;
+
+foreach $config_file (@config_files) {
+    if (open(COLORDIFFRC, "<$config_file")) {
+        while (<COLORDIFFRC>) {
+            chop;
+            next if (/^#/ || /^$/);
+            s/\s+//g;
+            ($setting, $value) = split ('=');
+            if ($setting eq 'banner') {
+                if ($value eq 'no') {
+                    $show_banner = 0;
+                }
+                next;
+            }
+            if (!defined $colour{$value}) {
+                print "Invalid colour specification ($value) in $config_file\n";
+                next;
+            }
+            if ($setting eq 'plain') {
+                $plain_text = $colour{$value};
+            }
+            elsif ($setting eq 'oldtext') {
+                $file_old = $colour{$value};
+            }
+            elsif ($setting eq 'newtext') {
+                $file_new = $colour{$value};
+            }
+            elsif ($setting eq 'diffstuff') {
+                $diff_stuff = $colour{$value};
+            }
+            else {
+                print "Unknown option in $etcdir/colordiffrc: $setting\n";
+            }
+        }
+        close COLORDIFFRC;
+    }
+}
+
+# colordiff specfic options here.  Need to pre-declare if using variables
+GetOptions(
+    "no-banner" => sub { $show_banner = 0 },
+    "plain-text=s" => \&set_color,
+    "file-old=s"   => \&set_color,
+    "file-new=s"   => \&set_color,
+    "diff-stuff=s" => \&set_color
+);
+
+if ($show_banner == 1) {
+    print STDERR "$app_name $version ($app_www)\n";
+    print STDERR "$copyright $author, $author_email\n\n";
+}
+
+if (defined $ARGV[0]) {
+    # More reliable way of pulling in arguments
+    open2(\*INPUTSTREAM, undef, "git", "diff", @ARGV);
+}
+else {
+    *INPUTSTREAM = \*STDIN;
+}
+
+my $record;
+my $nrecs           = 0;
+my $inside_file_old = 1;
+my $nparents        = undef;
+
+while (<INPUTSTREAM>) {
+    $nrecs++;
+    if (/^(\@\@+) -[-+0-9, ]+ \1/) {
+	    print "$diff_stuff";
+	    $nparents = length($1) - 1;
+    }
+    elsif (/^diff -/ || /^index / ||
+	   /^old mode / || /^new mode / ||
+	   /^deleted file mode / || /^new file mode / ||
+	   /^similarity index / || /^dissimilarity index / ||
+	   /^copy from / || /^copy to / ||
+	   /^rename from / || /^rename to /) {
+	    $nparents = undef;
+	    print "$diff_stuff";
+    }
+    elsif (defined $nparents) {
+	    if ($nparents == 1) {
+		    if (/^\+/) {
+			    print $file_new;
+		    }
+		    elsif (/^-/) {
+			    print $file_old;
+		    }
+		    else {
+			    print $plain_text;
+		    }
+	    }
+	    elsif (/^ {$nparents}/) {
+		    print "$plain_text";
+	    }
+	    elsif (/^[+ ]{$nparents}/) {
+		    print "$file_new";
+	    }
+	    elsif (/^[- ]{$nparents}/) {
+		    print "$file_old";
+	    }
+	    else {
+		    print $plain_text;
+	    }
+    }
+    elsif (/^--- / || /^\+\+\+ /) {
+	    print $diff_stuff;
+    }
+    else {
+	    print "$plain_text";
+    }
+    s/$/$colour{OFF}/;
+    print "$_";
+}
+close INPUTSTREAM;
+
+sub set_color {
+    my ($type, $color) = @_;
+
+    $type =~ s/-/_/;
+    eval "\$$type = \$colour{$color}";
+}
diff --git a/diff-files.c b/diff-files.c
index 3e7f5f1..b9d193d 100644
--- a/diff-files.c
+++ b/diff-files.c
@@ -5,209 +5,50 @@
  */
 #include "cache.h"
 #include "diff.h"
+#include "commit.h"
+#include "revision.h"
 
 static const char diff_files_usage[] =
 "git-diff-files [-q] [-0/-1/2/3 |-c|--cc] [<common diff options>] [<path>...]"
 COMMON_DIFF_OPTIONS_HELP;
 
-static struct diff_options diff_options;
-static int silent = 0;
-static int diff_unmerged_stage = 2;
-static int combine_merges = 0;
-static int dense_combined_merges = 0;
-
-static void show_unmerge(const char *path)
-{
-	diff_unmerge(&diff_options, path);
-}
-
-static void show_file(int pfx, struct cache_entry *ce)
-{
-	diff_addremove(&diff_options, pfx, ntohl(ce->ce_mode),
-		       ce->sha1, ce->name, NULL);
-}
-
-static void show_modified(int oldmode, int mode,
-			  const unsigned char *old_sha1, const unsigned char *sha1,
-			  char *path)
-{
-	diff_change(&diff_options, oldmode, mode, old_sha1, sha1, path, NULL);
-}
-
 int main(int argc, const char **argv)
 {
-	const char **pathspec;
-	const char *prefix = setup_git_directory();
-	int entries, i;
+	struct rev_info rev;
+	int silent = 0;
 
 	git_config(git_diff_config);
-	diff_setup(&diff_options);
+	init_revisions(&rev);
+	rev.abbrev = 0;
+
+	argc = setup_revisions(argc, argv, &rev, NULL);
 	while (1 < argc && argv[1][0] == '-') {
-		if (!strcmp(argv[1], "--")) {
-			argv++;
-			argc--;
-			break;
-		}
-		if (!strcmp(argv[1], "-0"))
-			diff_unmerged_stage = 0;
-		else if (!strcmp(argv[1], "-1"))
-			diff_unmerged_stage = 1;
-		else if (!strcmp(argv[1], "-2"))
-			diff_unmerged_stage = 2;
-		else if (!strcmp(argv[1], "-3"))
-			diff_unmerged_stage = 3;
-		else if (!strcmp(argv[1], "--base"))
-			diff_unmerged_stage = 1;
+		if (!strcmp(argv[1], "--base"))
+			rev.max_count = 1;
 		else if (!strcmp(argv[1], "--ours"))
-			diff_unmerged_stage = 2;
+			rev.max_count = 2;
 		else if (!strcmp(argv[1], "--theirs"))
-			diff_unmerged_stage = 3;
+			rev.max_count = 3;
 		else if (!strcmp(argv[1], "-q"))
 			silent = 1;
-		else if (!strcmp(argv[1], "-r"))
-			; /* no-op */
-		else if (!strcmp(argv[1], "-s"))
-			; /* no-op */
-		else if (!strcmp(argv[1], "-c"))
-			combine_merges = 1;
-		else if (!strcmp(argv[1], "--cc"))
-			dense_combined_merges = combine_merges = 1;
-		else {
-			int diff_opt_cnt;
-			diff_opt_cnt = diff_opt_parse(&diff_options,
-						      argv+1, argc-1);
-			if (diff_opt_cnt < 0)
-				usage(diff_files_usage);
-			else if (diff_opt_cnt) {
-				argv += diff_opt_cnt;
-				argc -= diff_opt_cnt;
-				continue;
-			}
-			else
-				usage(diff_files_usage);
-		}
+		else
+			usage(diff_files_usage);
 		argv++; argc--;
 	}
-	if (dense_combined_merges)
-		diff_options.output_format = DIFF_FORMAT_PATCH;
-
-	/* Find the directory, and set up the pathspec */
-	pathspec = get_pathspec(prefix, argv + 1);
-	entries = read_cache();
-
-	if (diff_setup_done(&diff_options) < 0)
-		usage(diff_files_usage);
-
-	/* At this point, if argc == 1, then we are doing everything.
-	 * Otherwise argv[1] .. argv[argc-1] have the explicit paths.
+	/*
+	 * Make sure there are NO revision (i.e. pending object) parameter,
+	 * rev.max_count is reasonable (0 <= n <= 3),
+	 * there is no other revision filtering parameters.
 	 */
-	if (entries < 0) {
-		perror("read_cache");
-		exit(1);
-	}
-
-	for (i = 0; i < entries; i++) {
-		struct stat st;
-		unsigned int oldmode, newmode;
-		struct cache_entry *ce = active_cache[i];
-		int changed;
-
-		if (!ce_path_match(ce, pathspec))
-			continue;
-
-		if (ce_stage(ce)) {
-			struct {
-				struct combine_diff_path p;
-				struct combine_diff_parent filler[5];
-			} combine;
-			int num_compare_stages = 0;
-
-			combine.p.next = NULL;
-			combine.p.len = ce_namelen(ce);
-			combine.p.path = xmalloc(combine.p.len + 1);
-			memcpy(combine.p.path, ce->name, combine.p.len);
-			combine.p.path[combine.p.len] = 0;
-			combine.p.mode = 0;
-			memset(combine.p.sha1, 0, 20);
-			memset(&combine.p.parent[0], 0,
-			       sizeof(combine.filler));
-
-			while (i < entries) {
-				struct cache_entry *nce = active_cache[i];
-				int stage;
-
-				if (strcmp(ce->name, nce->name))
-					break;
-
-				/* Stage #2 (ours) is the first parent,
-				 * stage #3 (theirs) is the second.
-				 */
-				stage = ce_stage(nce);
-				if (2 <= stage) {
-					int mode = ntohl(nce->ce_mode);
-					num_compare_stages++;
-					memcpy(combine.p.parent[stage-2].sha1,
-					       nce->sha1, 20);
-					combine.p.parent[stage-2].mode =
-						canon_mode(mode);
-					combine.p.parent[stage-2].status =
-						DIFF_STATUS_MODIFIED;
-				}
-
-				/* diff against the proper unmerged stage */
-				if (stage == diff_unmerged_stage)
-					ce = nce;
-				i++;
-			}
-			/*
-			 * Compensate for loop update
-			 */
-			i--;
-
-			if (combine_merges && num_compare_stages == 2) {
-				show_combined_diff(&combine.p, 2,
-						   dense_combined_merges,
-						   NULL,
-						   &diff_options);
-				free(combine.p.path);
-				continue;
-			}
-			free(combine.p.path);
-
-			/*
-			 * Show the diff for the 'ce' if we found the one
-			 * from the desired stage.
-			 */
-			show_unmerge(ce->name);
-			if (ce_stage(ce) != diff_unmerged_stage)
-				continue;
-		}
-
-		if (lstat(ce->name, &st) < 0) {
-			if (errno != ENOENT && errno != ENOTDIR) {
-				perror(ce->name);
-				continue;
-			}
-			if (silent)
-				continue;
-			show_file('-', ce);
-			continue;
-		}
-		changed = ce_match_stat(ce, &st, 0);
-		if (!changed && !diff_options.find_copies_harder)
-			continue;
-		oldmode = ntohl(ce->ce_mode);
-
-		newmode = canon_mode(st.st_mode);
-		if (!trust_executable_bit &&
-		    S_ISREG(newmode) && S_ISREG(oldmode) &&
-		    ((newmode ^ oldmode) == 0111))
-			newmode = oldmode;
-		show_modified(oldmode, newmode,
-			      ce->sha1, (changed ? null_sha1 : ce->sha1),
-			      ce->name);
-	}
-	diffcore_std(&diff_options);
-	diff_flush(&diff_options);
-	return 0;
+	if (rev.pending_objects ||
+	    rev.min_age != -1 || rev.max_age != -1)
+		usage(diff_files_usage);
+	/*
+	 * Backward compatibility wart - "diff-files -s" used to
+	 * defeat the common diff option "-s" which asked for
+	 * DIFF_FORMAT_NO_OUTPUT.
+	 */
+	if (rev.diffopt.output_format == DIFF_FORMAT_NO_OUTPUT)
+		rev.diffopt.output_format = DIFF_FORMAT_RAW;
+	return run_diff_files(&rev, silent);
 }
diff --git a/diff-index.c b/diff-index.c
index e376d65..8c9f601 100644
--- a/diff-index.c
+++ b/diff-index.c
@@ -1,166 +1,7 @@
 #include "cache.h"
-#include "tree.h"
 #include "diff.h"
-
-static int cached_only = 0;
-static int match_nonexisting = 0;
-static struct diff_options diff_options;
-
-/* A file entry went away or appeared */
-static void show_file(const char *prefix,
-		      struct cache_entry *ce,
-		      unsigned char *sha1, unsigned int mode)
-{
-	diff_addremove(&diff_options, prefix[0], ntohl(mode),
-		       sha1, ce->name, NULL);
-}
-
-static int get_stat_data(struct cache_entry *ce,
-			 unsigned char ** sha1p, unsigned int *modep)
-{
-	unsigned char *sha1 = ce->sha1;
-	unsigned int mode = ce->ce_mode;
-
-	if (!cached_only) {
-		static unsigned char no_sha1[20];
-		int changed;
-		struct stat st;
-		if (lstat(ce->name, &st) < 0) {
-			if (errno == ENOENT && match_nonexisting) {
-				*sha1p = sha1;
-				*modep = mode;
-				return 0;
-			}
-			return -1;
-		}
-		changed = ce_match_stat(ce, &st, 0);
-		if (changed) {
-			mode = create_ce_mode(st.st_mode);
-			if (!trust_executable_bit && S_ISREG(st.st_mode))
-				mode = ce->ce_mode;
-			sha1 = no_sha1;
-		}
-	}
-
-	*sha1p = sha1;
-	*modep = mode;
-	return 0;
-}
-
-static void show_new_file(struct cache_entry *new)
-{
-	unsigned char *sha1;
-	unsigned int mode;
-
-	/* New file in the index: it might actually be different in
-	 * the working copy.
-	 */
-	if (get_stat_data(new, &sha1, &mode) < 0)
-		return;
-
-	show_file("+", new, sha1, mode);
-}
-
-static int show_modified(struct cache_entry *old,
-			 struct cache_entry *new,
-			 int report_missing)
-{
-	unsigned int mode, oldmode;
-	unsigned char *sha1;
-
-	if (get_stat_data(new, &sha1, &mode) < 0) {
-		if (report_missing)
-			show_file("-", old, old->sha1, old->ce_mode);
-		return -1;
-	}
-
-	oldmode = old->ce_mode;
-	if (mode == oldmode && !memcmp(sha1, old->sha1, 20) &&
-	    !diff_options.find_copies_harder)
-		return 0;
-
-	mode = ntohl(mode);
-	oldmode = ntohl(oldmode);
-
-	diff_change(&diff_options, oldmode, mode,
-		    old->sha1, sha1, old->name, NULL);
-	return 0;
-}
-
-static int diff_cache(struct cache_entry **ac, int entries, const char **pathspec)
-{
-	while (entries) {
-		struct cache_entry *ce = *ac;
-		int same = (entries > 1) && ce_same_name(ce, ac[1]);
-
-		if (!ce_path_match(ce, pathspec))
-			goto skip_entry;
-
-		switch (ce_stage(ce)) {
-		case 0:
-			/* No stage 1 entry? That means it's a new file */
-			if (!same) {
-				show_new_file(ce);
-				break;
-			}
-			/* Show difference between old and new */
-			show_modified(ac[1], ce, 1);
-			break;
-		case 1:
-			/* No stage 3 (merge) entry? That means it's been deleted */
-			if (!same) {
-				show_file("-", ce, ce->sha1, ce->ce_mode);
-				break;
-			}
-			/* We come here with ce pointing at stage 1
-			 * (original tree) and ac[1] pointing at stage
-			 * 3 (unmerged).  show-modified with
-			 * report-missing set to false does not say the
-			 * file is deleted but reports true if work
-			 * tree does not have it, in which case we
-			 * fall through to report the unmerged state.
-			 * Otherwise, we show the differences between
-			 * the original tree and the work tree.
-			 */
-			if (!cached_only && !show_modified(ce, ac[1], 0))
-				break;
-			/* fallthru */
-		case 3:
-			diff_unmerge(&diff_options, ce->name);
-			break;
-
-		default:
-			die("impossible cache entry stage");
-		}
-
-skip_entry:
-		/*
-		 * Ignore all the different stages for this file,
-		 * we've handled the relevant cases now.
-		 */
-		do {
-			ac++;
-			entries--;
-		} while (entries && ce_same_name(ce, ac[0]));
-	}
-	return 0;
-}
-
-/*
- * This turns all merge entries into "stage 3". That guarantees that
- * when we read in the new tree (into "stage 1"), we won't lose sight
- * of the fact that we had unmerged entries.
- */
-static void mark_merge_entries(void)
-{
-	int i;
-	for (i = 0; i < active_nr; i++) {
-		struct cache_entry *ce = active_cache[i];
-		if (!ce_stage(ce))
-			continue;
-		ce->ce_flags |= htons(CE_STAGEMASK);
-	}
-}
+#include "commit.h"
+#include "revision.h"
 
 static const char diff_cache_usage[] =
 "git-diff-index [-m] [--cached] "
@@ -169,85 +10,29 @@
 
 int main(int argc, const char **argv)
 {
-	const char *tree_name = NULL;
-	unsigned char sha1[20];
-	const char *prefix = setup_git_directory();
-	const char **pathspec = NULL;
-	struct tree *tree;
-	int ret;
-	int allow_options = 1;
+	struct rev_info rev;
+	int cached = 0;
 	int i;
 
 	git_config(git_diff_config);
-	diff_setup(&diff_options);
+	init_revisions(&rev);
+	rev.abbrev = 0;
+
+	argc = setup_revisions(argc, argv, &rev, NULL);
 	for (i = 1; i < argc; i++) {
 		const char *arg = argv[i];
-		int diff_opt_cnt;
-
-		if (!allow_options || *arg != '-') {
-			if (tree_name)
-				break;
-			tree_name = arg;
-			continue;
-		}
 			
-		if (!strcmp(arg, "--")) {
-			allow_options = 0;
-			continue;
-		}
-		if (!strcmp(arg, "-r")) {
-			/* We accept the -r flag just to look like git-diff-tree */
-			continue;
-		}
-		if (!strcmp(arg, "--cc"))
-			/*
-			 * I _think_ "diff-index --cached HEAD" with an
-			 * unmerged index could show something else
-			 * later, but pretend --cc is the same as -p for
-			 * now.  "git diff" uses --cc by default.
-			 */
-			argv[i] = arg = "-p";
-		diff_opt_cnt = diff_opt_parse(&diff_options, argv + i,
-					      argc - i);
-		if (diff_opt_cnt < 0)
+		if (!strcmp(arg, "--cached"))
+			cached = 1;
+		else
 			usage(diff_cache_usage);
-		else if (diff_opt_cnt) {
-			i += diff_opt_cnt - 1;
-			continue;
-		}
-
-		if (!strcmp(arg, "-m")) {
-			match_nonexisting = 1;
-			continue;
-		}
-		if (!strcmp(arg, "--cached")) {
-			cached_only = 1;
-			continue;
-		}
-		usage(diff_cache_usage);
 	}
-
-	pathspec = get_pathspec(prefix, argv + i);
-
-	if (diff_setup_done(&diff_options) < 0)
+	/*
+	 * Make sure there is one revision (i.e. pending object),
+	 * and there is no revision filtering parameters.
+	 */
+	if (!rev.pending_objects || rev.pending_objects->next ||
+	    rev.max_count != -1 || rev.min_age != -1 || rev.max_age != -1)
 		usage(diff_cache_usage);
-
-	if (!tree_name || get_sha1(tree_name, sha1))
-		usage(diff_cache_usage);
-
-	read_cache();
-
-	mark_merge_entries();
-
-	tree = parse_tree_indirect(sha1);
-	if (!tree)
-		die("bad tree object %s", tree_name);
-	if (read_tree(tree, 1, pathspec))
-		die("unable to read tree object %s", tree_name);
-
-	ret = diff_cache(active_cache, active_nr, pathspec);
-
-	diffcore_std(&diff_options);
-	diff_flush(&diff_options);
-	return ret;
+	return run_diff_index(&rev, cached);
 }
diff --git a/diff-lib.c b/diff-lib.c
new file mode 100644
index 0000000..2183b41
--- /dev/null
+++ b/diff-lib.c
@@ -0,0 +1,344 @@
+/*
+ * Copyright (C) 2005 Junio C Hamano
+ */
+#include "cache.h"
+#include "quote.h"
+#include "commit.h"
+#include "diff.h"
+#include "diffcore.h"
+#include "revision.h"
+
+/*
+ * diff-files
+ */
+
+int run_diff_files(struct rev_info *revs, int silent_on_removed)
+{
+	int entries, i;
+	int diff_unmerged_stage = revs->max_count;
+
+	if (diff_unmerged_stage < 0)
+		diff_unmerged_stage = 2;
+	entries = read_cache();
+	if (entries < 0) {
+		perror("read_cache");
+		return -1;
+	}
+	for (i = 0; i < entries; i++) {
+		struct stat st;
+		unsigned int oldmode, newmode;
+		struct cache_entry *ce = active_cache[i];
+		int changed;
+
+		if (!ce_path_match(ce, revs->prune_data))
+			continue;
+
+		if (ce_stage(ce)) {
+			struct {
+				struct combine_diff_path p;
+				struct combine_diff_parent filler[5];
+			} combine;
+			int num_compare_stages = 0;
+
+			combine.p.next = NULL;
+			combine.p.len = ce_namelen(ce);
+			combine.p.path = xmalloc(combine.p.len + 1);
+			memcpy(combine.p.path, ce->name, combine.p.len);
+			combine.p.path[combine.p.len] = 0;
+			combine.p.mode = 0;
+			memset(combine.p.sha1, 0, 20);
+			memset(&combine.p.parent[0], 0,
+			       sizeof(combine.filler));
+
+			while (i < entries) {
+				struct cache_entry *nce = active_cache[i];
+				int stage;
+
+				if (strcmp(ce->name, nce->name))
+					break;
+
+				/* Stage #2 (ours) is the first parent,
+				 * stage #3 (theirs) is the second.
+				 */
+				stage = ce_stage(nce);
+				if (2 <= stage) {
+					int mode = ntohl(nce->ce_mode);
+					num_compare_stages++;
+					memcpy(combine.p.parent[stage-2].sha1,
+					       nce->sha1, 20);
+					combine.p.parent[stage-2].mode =
+						canon_mode(mode);
+					combine.p.parent[stage-2].status =
+						DIFF_STATUS_MODIFIED;
+				}
+
+				/* diff against the proper unmerged stage */
+				if (stage == diff_unmerged_stage)
+					ce = nce;
+				i++;
+			}
+			/*
+			 * Compensate for loop update
+			 */
+			i--;
+
+			if (revs->combine_merges && num_compare_stages == 2) {
+				show_combined_diff(&combine.p, 2,
+						   revs->dense_combined_merges,
+						   revs);
+				free(combine.p.path);
+				continue;
+			}
+			free(combine.p.path);
+
+			/*
+			 * Show the diff for the 'ce' if we found the one
+			 * from the desired stage.
+			 */
+			diff_unmerge(&revs->diffopt, ce->name);
+			if (ce_stage(ce) != diff_unmerged_stage)
+				continue;
+		}
+
+		if (lstat(ce->name, &st) < 0) {
+			if (errno != ENOENT && errno != ENOTDIR) {
+				perror(ce->name);
+				continue;
+			}
+			if (silent_on_removed)
+				continue;
+			diff_addremove(&revs->diffopt, '-', ntohl(ce->ce_mode),
+				       ce->sha1, ce->name, NULL);
+			continue;
+		}
+		changed = ce_match_stat(ce, &st, 0);
+		if (!changed && !revs->diffopt.find_copies_harder)
+			continue;
+		oldmode = ntohl(ce->ce_mode);
+
+		newmode = canon_mode(st.st_mode);
+		if (!trust_executable_bit &&
+		    S_ISREG(newmode) && S_ISREG(oldmode) &&
+		    ((newmode ^ oldmode) == 0111))
+			newmode = oldmode;
+		diff_change(&revs->diffopt, oldmode, newmode,
+			    ce->sha1, (changed ? null_sha1 : ce->sha1),
+			    ce->name, NULL);
+
+	}
+	diffcore_std(&revs->diffopt);
+	diff_flush(&revs->diffopt);
+	return 0;
+}
+
+/*
+ * diff-index
+ */
+
+/* A file entry went away or appeared */
+static void diff_index_show_file(struct rev_info *revs,
+				 const char *prefix,
+				 struct cache_entry *ce,
+				 unsigned char *sha1, unsigned int mode)
+{
+	diff_addremove(&revs->diffopt, prefix[0], ntohl(mode),
+		       sha1, ce->name, NULL);
+}
+
+static int get_stat_data(struct cache_entry *ce,
+			 unsigned char **sha1p,
+			 unsigned int *modep,
+			 int cached, int match_missing)
+{
+	unsigned char *sha1 = ce->sha1;
+	unsigned int mode = ce->ce_mode;
+
+	if (!cached) {
+		static unsigned char no_sha1[20];
+		int changed;
+		struct stat st;
+		if (lstat(ce->name, &st) < 0) {
+			if (errno == ENOENT && match_missing) {
+				*sha1p = sha1;
+				*modep = mode;
+				return 0;
+			}
+			return -1;
+		}
+		changed = ce_match_stat(ce, &st, 0);
+		if (changed) {
+			mode = create_ce_mode(st.st_mode);
+			if (!trust_executable_bit && S_ISREG(st.st_mode))
+				mode = ce->ce_mode;
+			sha1 = no_sha1;
+		}
+	}
+
+	*sha1p = sha1;
+	*modep = mode;
+	return 0;
+}
+
+static void show_new_file(struct rev_info *revs,
+			  struct cache_entry *new,
+			  int cached, int match_missing)
+{
+	unsigned char *sha1;
+	unsigned int mode;
+
+	/* New file in the index: it might actually be different in
+	 * the working copy.
+	 */
+	if (get_stat_data(new, &sha1, &mode, cached, match_missing) < 0)
+		return;
+
+	diff_index_show_file(revs, "+", new, sha1, mode);
+}
+
+static int show_modified(struct rev_info *revs,
+			 struct cache_entry *old,
+			 struct cache_entry *new,
+			 int report_missing,
+			 int cached, int match_missing)
+{
+	unsigned int mode, oldmode;
+	unsigned char *sha1;
+
+	if (get_stat_data(new, &sha1, &mode, cached, match_missing) < 0) {
+		if (report_missing)
+			diff_index_show_file(revs, "-", old,
+					     old->sha1, old->ce_mode);
+		return -1;
+	}
+
+	oldmode = old->ce_mode;
+	if (mode == oldmode && !memcmp(sha1, old->sha1, 20) &&
+	    !revs->diffopt.find_copies_harder)
+		return 0;
+
+	mode = ntohl(mode);
+	oldmode = ntohl(oldmode);
+
+	diff_change(&revs->diffopt, oldmode, mode,
+		    old->sha1, sha1, old->name, NULL);
+	return 0;
+}
+
+static int diff_cache(struct rev_info *revs,
+		      struct cache_entry **ac, int entries,
+		      const char **pathspec,
+		      int cached, int match_missing)
+{
+	while (entries) {
+		struct cache_entry *ce = *ac;
+		int same = (entries > 1) && ce_same_name(ce, ac[1]);
+
+		if (!ce_path_match(ce, pathspec))
+			goto skip_entry;
+
+		switch (ce_stage(ce)) {
+		case 0:
+			/* No stage 1 entry? That means it's a new file */
+			if (!same) {
+				show_new_file(revs, ce, cached, match_missing);
+				break;
+			}
+			/* Show difference between old and new */
+			show_modified(revs,ac[1], ce, 1,
+				      cached, match_missing);
+			break;
+		case 1:
+			/* No stage 3 (merge) entry?
+			 * That means it's been deleted.
+			 */
+			if (!same) {
+				diff_index_show_file(revs, "-", ce,
+						     ce->sha1, ce->ce_mode);
+				break;
+			}
+			/* We come here with ce pointing at stage 1
+			 * (original tree) and ac[1] pointing at stage
+			 * 3 (unmerged).  show-modified with
+			 * report-missing set to false does not say the
+			 * file is deleted but reports true if work
+			 * tree does not have it, in which case we
+			 * fall through to report the unmerged state.
+			 * Otherwise, we show the differences between
+			 * the original tree and the work tree.
+			 */
+			if (!cached &&
+			    !show_modified(revs, ce, ac[1], 0,
+					   cached, match_missing))
+				break;
+			/* fallthru */
+		case 3:
+			diff_unmerge(&revs->diffopt, ce->name);
+			break;
+
+		default:
+			die("impossible cache entry stage");
+		}
+
+skip_entry:
+		/*
+		 * Ignore all the different stages for this file,
+		 * we've handled the relevant cases now.
+		 */
+		do {
+			ac++;
+			entries--;
+		} while (entries && ce_same_name(ce, ac[0]));
+	}
+	return 0;
+}
+
+/*
+ * This turns all merge entries into "stage 3". That guarantees that
+ * when we read in the new tree (into "stage 1"), we won't lose sight
+ * of the fact that we had unmerged entries.
+ */
+static void mark_merge_entries(void)
+{
+	int i;
+	for (i = 0; i < active_nr; i++) {
+		struct cache_entry *ce = active_cache[i];
+		if (!ce_stage(ce))
+			continue;
+		ce->ce_flags |= htons(CE_STAGEMASK);
+	}
+}
+
+int run_diff_index(struct rev_info *revs, int cached)
+{
+	int ret;
+	struct object *ent;
+	struct tree *tree;
+	const char *tree_name;
+	int match_missing = 0;
+
+	/* 
+	 * Backward compatibility wart - "diff-index -m" does
+	 * not mean "do not ignore merges", but totally different.
+	 */
+	if (!revs->ignore_merges)
+		match_missing = 1;
+
+	if (read_cache() < 0) {
+		perror("read_cache");
+		return -1;
+	}
+	mark_merge_entries();
+
+	ent = revs->pending_objects->item;
+	tree_name = revs->pending_objects->name;
+	tree = parse_tree_indirect(ent->sha1);
+	if (!tree)
+		return error("bad tree object %s", tree_name);
+	if (read_tree(tree, 1, revs->prune_data))
+		return error("unable to read tree object %s", tree_name);
+	ret = diff_cache(revs, active_cache, active_nr, revs->prune_data,
+			 cached, match_missing);
+	diffcore_std(&revs->diffopt);
+	diff_flush(&revs->diffopt);
+	return ret;
+}
diff --git a/diff-tree.c b/diff-tree.c
index d1c61c8..7207867 100644
--- a/diff-tree.c
+++ b/diff-tree.c
@@ -3,7 +3,7 @@
 #include "commit.h"
 #include "log-tree.h"
 
-static struct log_tree_opt log_tree_opt;
+static struct rev_info log_tree_opt;
 
 static int diff_tree_commit_sha1(const unsigned char *sha1)
 {
@@ -62,47 +62,21 @@
 {
 	int nr_sha1;
 	char line[1000];
-	unsigned char sha1[2][20];
-	const char *prefix = setup_git_directory();
-	static struct log_tree_opt *opt = &log_tree_opt;
+	struct object *tree1, *tree2;
+	static struct rev_info *opt = &log_tree_opt;
+	struct object_list *list;
 	int read_stdin = 0;
 
 	git_config(git_diff_config);
 	nr_sha1 = 0;
-	init_log_tree_opt(opt);
+	init_revisions(opt);
+	opt->abbrev = 0;
+	opt->diff = 1;
+	argc = setup_revisions(argc, argv, opt, NULL);
 
-	for (;;) {
-		int opt_cnt;
-		const char *arg;
+	while (--argc > 0) {
+		const char *arg = *++argv;
 
-		argv++;
-		argc--;
-		arg = *argv;
-		if (!arg)
-			break;
-
-		if (*arg != '-') {
-			if (nr_sha1 < 2 && !get_sha1(arg, sha1[nr_sha1])) {
-				nr_sha1++;
-				continue;
-			}
-			break;
-		}
-
-		opt_cnt = log_tree_opt_parse(opt, argv, argc);
-		if (opt_cnt < 0)
-			usage(diff_tree_usage);
-		else if (opt_cnt) {
-			argv += opt_cnt - 1;
-			argc -= opt_cnt - 1;
-			continue;
-		}
-
-		if (!strcmp(arg, "--")) {
-			argv++;
-			argc--;
-			break;
-		}
 		if (!strcmp(arg, "--stdin")) {
 			read_stdin = 1;
 			continue;
@@ -110,15 +84,36 @@
 		usage(diff_tree_usage);
 	}
 
-	if (opt->combine_merges)
-		opt->ignore_merges = 0;
-
-	/* We can only do dense combined merges with diff output */
-	if (opt->dense_combined_merges)
-		opt->diffopt.output_format = DIFF_FORMAT_PATCH;
-
-	diff_tree_setup_paths(get_pathspec(prefix, argv), &opt->diffopt);
-	diff_setup_done(&opt->diffopt);
+	/*
+	 * NOTE! "setup_revisions()" will have inserted the revisions
+	 * it parsed in reverse order. So if you do
+	 *
+	 *	git-diff-tree a b
+	 *
+	 * the commit list will be "b" -> "a" -> NULL, so we reverse
+	 * the order of the objects if the first one is not marked
+	 * UNINTERESTING.
+	 */
+	nr_sha1 = 0;
+	list = opt->pending_objects;
+	if (list) {
+		nr_sha1++;
+		tree1 = list->item;
+		list = list->next;
+		if (list) {
+			nr_sha1++;
+			tree2 = tree1;
+			tree1 = list->item;
+			if (list->next)
+				usage(diff_tree_usage);
+			/* Switch them around if the second one was uninteresting.. */
+			if (tree2->flags & UNINTERESTING) {
+				struct object *tmp = tree2;
+				tree2 = tree1;
+				tree1 = tmp;
+			}
+		}
+	}
 
 	switch (nr_sha1) {
 	case 0:
@@ -126,10 +121,12 @@
 			usage(diff_tree_usage);
 		break;
 	case 1:
-		diff_tree_commit_sha1(sha1[0]);
+		diff_tree_commit_sha1(tree1->sha1);
 		break;
 	case 2:
-		diff_tree_sha1(sha1[0], sha1[1], "", &opt->diffopt);
+		diff_tree_sha1(tree1->sha1,
+			       tree2->sha1,
+			       "", &opt->diffopt);
 		log_tree_diff_flush(opt);
 		break;
 	}
diff --git a/diff.c b/diff.c
index 903afa1..6762fce 100644
--- a/diff.c
+++ b/diff.c
@@ -195,6 +195,56 @@
 	return 0;
 }
 
+static char *pprint_rename(const char *a, const char *b)
+{
+	const char *old = a;
+	const char *new = b;
+	char *name = NULL;
+	int pfx_length, sfx_length;
+	int len_a = strlen(a);
+	int len_b = strlen(b);
+
+	/* Find common prefix */
+	pfx_length = 0;
+	while (*old && *new && *old == *new) {
+		if (*old == '/')
+			pfx_length = old - a + 1;
+		old++;
+		new++;
+	}
+
+	/* Find common suffix */
+	old = a + len_a;
+	new = b + len_b;
+	sfx_length = 0;
+	while (a <= old && b <= new && *old == *new) {
+		if (*old == '/')
+			sfx_length = len_a - (old - a);
+		old--;
+		new--;
+	}
+
+	/*
+	 * pfx{mid-a => mid-b}sfx
+	 * {pfx-a => pfx-b}sfx
+	 * pfx{sfx-a => sfx-b}
+	 * name-a => name-b
+	 */
+	if (pfx_length + sfx_length) {
+		name = xmalloc(len_a + len_b - pfx_length - sfx_length + 7);
+		sprintf(name, "%.*s{%.*s => %.*s}%s",
+			pfx_length, a,
+			len_a - pfx_length - sfx_length, a + pfx_length,
+			len_b - pfx_length - sfx_length, b + pfx_length,
+			a + len_a - sfx_length);
+	}
+	else {
+		name = xmalloc(len_a + len_b + 5);
+		sprintf(name, "%s => %s", a, b);
+	}
+	return name;
+}
+
 struct diffstat_t {
 	struct xdiff_emit_state xm;
 
@@ -204,12 +254,14 @@
 		char *name;
 		unsigned is_unmerged:1;
 		unsigned is_binary:1;
+		unsigned is_renamed:1;
 		unsigned int added, deleted;
 	} **files;
 };
 
 static struct diffstat_file *diffstat_add(struct diffstat_t *diffstat,
-		const char *name)
+					  const char *name_a,
+					  const char *name_b)
 {
 	struct diffstat_file *x;
 	x = xcalloc(sizeof (*x), 1);
@@ -219,7 +271,12 @@
 				diffstat->alloc * sizeof(x));
 	}
 	diffstat->files[diffstat->nr++] = x;
-	x->name = strdup(name);
+	if (name_b) {
+		x->name = pprint_rename(name_a, name_b);
+		x->is_renamed = 1;
+	}
+	else
+		x->name = strdup(name_a);
 	return x;
 }
 
@@ -305,7 +362,8 @@
 			printf(" %s%-*s |  Unmerged\n", prefix, len, name);
 			goto free_diffstat_file;
 		}
-		else if (added + deleted == 0) {
+		else if (!data->files[i]->is_renamed &&
+			 (added + deleted == 0)) {
 			total_files--;
 			goto free_diffstat_file;
 		}
@@ -425,19 +483,27 @@
 }
 
 static void builtin_diffstat(const char *name_a, const char *name_b,
-		struct diff_filespec *one, struct diff_filespec *two,
-		struct diffstat_t *diffstat)
+			     struct diff_filespec *one,
+			     struct diff_filespec *two,
+			     struct diffstat_t *diffstat,
+			     int complete_rewrite)
 {
 	mmfile_t mf1, mf2;
 	struct diffstat_file *data;
 
-	data = diffstat_add(diffstat, name_a ? name_a : name_b);
+	data = diffstat_add(diffstat, name_a, name_b);
 
 	if (!one || !two) {
 		data->is_unmerged = 1;
 		return;
 	}
-
+	if (complete_rewrite) {
+		diff_populate_filespec(one, 0);
+		diff_populate_filespec(two, 0);
+		data->deleted = count_lines(one->data, one->size);
+		data->added = count_lines(two->data, two->size);
+		return;
+	}
 	if (fill_mmfile(&mf1, one) < 0 || fill_mmfile(&mf2, two) < 0)
 		die("unable to read files to diff");
 
@@ -992,14 +1058,15 @@
 }
 
 static void run_diffstat(struct diff_filepair *p, struct diff_options *o,
-		struct diffstat_t *diffstat)
+			 struct diffstat_t *diffstat)
 {
 	const char *name;
 	const char *other;
+	int complete_rewrite = 0;
 
 	if (DIFF_PAIR_UNMERGED(p)) {
 		/* unmerged */
-		builtin_diffstat(p->one->path, NULL, NULL, NULL, diffstat);
+		builtin_diffstat(p->one->path, NULL, NULL, NULL, diffstat, 0);
 		return;
 	}
 
@@ -1009,7 +1076,9 @@
 	diff_fill_sha1_info(p->one);
 	diff_fill_sha1_info(p->two);
 
-	builtin_diffstat(name, other, p->one, p->two, diffstat);
+	if (p->status == DIFF_STATUS_MODIFIED && p->score)
+		complete_rewrite = 1;
+	builtin_diffstat(name, other, p->one, p->two, diffstat, complete_rewrite);
 }
 
 void diff_setup(struct diff_options *options)
@@ -1036,8 +1105,7 @@
 	 * recursive bits for other formats here.
 	 */
 	if ((options->output_format == DIFF_FORMAT_PATCH) ||
-	    (options->output_format == DIFF_FORMAT_DIFFSTAT) ||
-	    (options->with_stat))
+	    (options->output_format == DIFF_FORMAT_DIFFSTAT))
 		options->recursive = 1;
 
 	if (options->detect_rename && options->rename_limit < 0)
@@ -1375,7 +1443,7 @@
 }
 
 static void diff_flush_stat(struct diff_filepair *p, struct diff_options *o,
-		struct diffstat_t *diffstat)
+			    struct diffstat_t *diffstat)
 {
 	if (diff_unmodified_pair(p))
 		return;
@@ -1560,7 +1628,7 @@
 		for (i = 0; i < q->nr; i++) {
 			struct diff_filepair *p = q->queue[i];
 			flush_one_pair(p, DIFF_FORMAT_DIFFSTAT, options,
-					diffstat);
+				       diffstat);
 		}
 		show_stats(diffstat);
 		free(diffstat);
diff --git a/diff.h b/diff.h
index f783bae..7150b90 100644
--- a/diff.h
+++ b/diff.h
@@ -6,6 +6,7 @@
 
 #include "tree-walk.h"
 
+struct rev_info;
 struct diff_options;
 
 typedef void (*change_fn_t)(struct diff_options *options,
@@ -27,10 +28,11 @@
 		 with_raw:1,
 		 with_stat:1,
 		 tree_in_recursive:1,
-		 full_index:1;
+		 full_index:1,
+		 silent_on_remove:1,
+		 find_copies_harder:1;
 	int break_opt;
 	int detect_rename;
-	int find_copies_harder;
 	int line_termination;
 	int output_format;
 	int pickaxe_opts;
@@ -70,11 +72,10 @@
 	(sizeof(struct combine_diff_path) + \
 	 sizeof(struct combine_diff_parent) * (n) + (l) + 1)
 
-extern int show_combined_diff(struct combine_diff_path *elem, int num_parent,
-			      int dense, const char *header,
-			      struct diff_options *);
+extern void show_combined_diff(struct combine_diff_path *elem, int num_parent,
+			      int dense, struct rev_info *);
 
-extern const char *diff_tree_combined_merge(const unsigned char *sha1, const char *, int, struct diff_options *opt);
+extern void diff_tree_combined_merge(const unsigned char *sha1, int, struct rev_info *);
 
 extern void diff_addremove(struct diff_options *,
 			   int addremove,
@@ -168,4 +169,8 @@
 
 extern const char *diff_unique_abbrev(const unsigned char *, int);
 
+extern int run_diff_files(struct rev_info *revs, int silent_on_removed);
+
+extern int run_diff_index(struct rev_info *revs, int cached);
+
 #endif /* DIFF_H */
diff --git a/git-cvsserver.perl b/git-cvsserver.perl
index 7d3f78e..11d153c 100755
--- a/git-cvsserver.perl
+++ b/git-cvsserver.perl
@@ -88,7 +88,7 @@
 $log->debug("Temporary directory is '$TEMP_DIR'");
 
 # if we are called with a pserver argument,
-# deal with the authentication cat before entereing the
+# deal with the authentication cat before entering the
 # main loop
 if (@ARGV && $ARGV[0] eq 'pserver') {
     my $line = <STDIN>; chomp $line;
@@ -117,7 +117,7 @@
 {
     chomp;
 
-    # Check to see if we've seen this method, and call appropiate function.
+    # Check to see if we've seen this method, and call appropriate function.
     if ( /^([\w-]+)(?:\s+(.*))?$/ and defined($methods->{$1}) )
     {
         # use the $methods hash to call the appropriate sub for this command
@@ -171,11 +171,11 @@
        return 0;
     }
 
-    my @gitvars = `git-var -l`;
+    my @gitvars = `git-repo-config -l`;
     if ($?) {
-       print "E problems executing git-var on the server -- this is not a git repository or the PATH is not set correcly.\n";
+       print "E problems executing git-repo-config on the server -- this is not a git repository or the PATH is not set correctly.\n";
         print "E \n";
-        print "error 1 - problem executing git-var\n";
+        print "error 1 - problem executing git-repo-config\n";
        return 0;
     }
     foreach my $line ( @gitvars )
@@ -224,7 +224,7 @@
 sub req_Validresponses
 {
     my ( $cmd, $data ) = @_;
-    $log->debug("req_Validrepsonses : $data");
+    $log->debug("req_Validresponses : $data");
 
     # TODO : re-enable this, currently it's not particularly useful
     #$state->{validresponses} = [ split /\s+/, $data ];
@@ -733,7 +733,7 @@
     argsplit("update");
 
     #
-    # It may just be a client exploring the available heads/modukles
+    # It may just be a client exploring the available heads/modules
     # in that case, list them as top level directories and leave it
     # at that. Eclipse uses this technique to offer you a list of
     # projects (heads in this case) to checkout.
@@ -1731,7 +1731,7 @@
 }
 
 # This method takes a file name, and returns ( $dirpart, $filepart ) which
-# refers to the directory porition and the file portion of the filename
+# refers to the directory portion and the file portion of the filename
 # respectively
 sub filenamesplit
 {
@@ -1790,7 +1790,7 @@
 =head2 new
 
 Creates a new log object, optionally you can specify a filename here to
-indicate the file to log to. If no log file is specified, you can specifiy one
+indicate the file to log to. If no log file is specified, you can specify one
 later with method setfile, or indicate you no longer want logging with method
 nofile.
 
@@ -2595,7 +2595,7 @@
 
 =head2 safe_pipe_capture
 
-an alterative to `command` that allows input to be passed as an array
+an alternative to `command` that allows input to be passed as an array
 to work around shell problems with weird characters in arguments
 
 =cut
diff --git a/git-repack.sh b/git-repack.sh
index a5d349f..e0c9f32 100755
--- a/git-repack.sh
+++ b/git-repack.sh
@@ -5,9 +5,9 @@
 
 USAGE='[-a] [-d] [-f] [-l] [-n] [-q]'
 . git-sh-setup
-	
+
 no_update_info= all_into_one= remove_redundant=
-local= quiet= no_reuse_delta=
+local= quiet= no_reuse_delta= extra=
 while case "$#" in 0) break ;; esac
 do
 	case "$1" in
@@ -17,6 +17,8 @@
 	-q)	quiet=-q ;;
 	-f)	no_reuse_delta=--no-reuse-delta ;;
 	-l)	local=--local ;;
+	--window=*) extra="$extra $1" ;;
+	--depth=*) extra="$extra $1" ;;
 	*)	usage ;;
 	esac
 	shift
@@ -40,7 +42,7 @@
 	    find . -type f \( -name '*.pack' -o -name '*.idx' \) -print`
 	;;
 esac
-pack_objects="$pack_objects $local $quiet $no_reuse_delta"
+pack_objects="$pack_objects $local $quiet $no_reuse_delta$extra"
 name=$(git-rev-list --objects --all $rev_list 2>&1 |
 	git-pack-objects --non-empty $pack_objects .tmp-pack) ||
 	exit 1
diff --git a/git.c b/git.c
index 5209b04..01b7e28 100644
--- a/git.c
+++ b/git.c
@@ -8,218 +8,10 @@
 #include <errno.h>
 #include <limits.h>
 #include <stdarg.h>
-#include <sys/ioctl.h>
 #include "git-compat-util.h"
 #include "exec_cmd.h"
-#include "common-cmds.h"
 
-#include "cache.h"
-#include "commit.h"
-#include "diff.h"
-#include "revision.h"
-#include "log-tree.h"
-
-#ifndef PATH_MAX
-# define PATH_MAX 4096
-#endif
-
-static const char git_usage[] =
-	"Usage: git [--version] [--exec-path[=GIT_EXEC_PATH]] [--help] COMMAND [ ARGS ]";
-
-/* most gui terms set COLUMNS (although some don't export it) */
-static int term_columns(void)
-{
-	char *col_string = getenv("COLUMNS");
-	int n_cols = 0;
-
-	if (col_string && (n_cols = atoi(col_string)) > 0)
-		return n_cols;
-
-#ifdef TIOCGWINSZ
-	{
-		struct winsize ws;
-		if (!ioctl(1, TIOCGWINSZ, &ws)) {
-			if (ws.ws_col)
-				return ws.ws_col;
-		}
-	}
-#endif
-
-	return 80;
-}
-
-static void oom(void)
-{
-	fprintf(stderr, "git: out of memory\n");
-	exit(1);
-}
-
-static inline void mput_char(char c, unsigned int num)
-{
-	while(num--)
-		putchar(c);
-}
-
-static struct cmdname {
-	size_t len;
-	char name[1];
-} **cmdname;
-static int cmdname_alloc, cmdname_cnt;
-
-static void add_cmdname(const char *name, int len)
-{
-	struct cmdname *ent;
-	if (cmdname_alloc <= cmdname_cnt) {
-		cmdname_alloc = cmdname_alloc + 200;
-		cmdname = realloc(cmdname, cmdname_alloc * sizeof(*cmdname));
-		if (!cmdname)
-			oom();
-	}
-	ent = malloc(sizeof(*ent) + len);
-	if (!ent)
-		oom();
-	ent->len = len;
-	memcpy(ent->name, name, len);
-	ent->name[len] = 0;
-	cmdname[cmdname_cnt++] = ent;
-}
-
-static int cmdname_compare(const void *a_, const void *b_)
-{
-	struct cmdname *a = *(struct cmdname **)a_;
-	struct cmdname *b = *(struct cmdname **)b_;
-	return strcmp(a->name, b->name);
-}
-
-static void pretty_print_string_list(struct cmdname **cmdname, int longest)
-{
-	int cols = 1, rows;
-	int space = longest + 1; /* min 1 SP between words */
-	int max_cols = term_columns() - 1; /* don't print *on* the edge */
-	int i, j;
-
-	if (space < max_cols)
-		cols = max_cols / space;
-	rows = (cmdname_cnt + cols - 1) / cols;
-
-	qsort(cmdname, cmdname_cnt, sizeof(*cmdname), cmdname_compare);
-
-	for (i = 0; i < rows; i++) {
-		printf("  ");
-
-		for (j = 0; j < cols; j++) {
-			int n = j * rows + i;
-			int size = space;
-			if (n >= cmdname_cnt)
-				break;
-			if (j == cols-1 || n + rows >= cmdname_cnt)
-				size = 1;
-			printf("%-*s", size, cmdname[n]->name);
-		}
-		putchar('\n');
-	}
-}
-
-static void list_commands(const char *exec_path, const char *pattern)
-{
-	unsigned int longest = 0;
-	char path[PATH_MAX];
-	int dirlen;
-	DIR *dir = opendir(exec_path);
-	struct dirent *de;
-
-	if (!dir) {
-		fprintf(stderr, "git: '%s': %s\n", exec_path, strerror(errno));
-		exit(1);
-	}
-
-	dirlen = strlen(exec_path);
-	if (PATH_MAX - 20 < dirlen) {
-		fprintf(stderr, "git: insanely long exec-path '%s'\n",
-			exec_path);
-		exit(1);
-	}
-
-	memcpy(path, exec_path, dirlen);
-	path[dirlen++] = '/';
-
-	while ((de = readdir(dir)) != NULL) {
-		struct stat st;
-		int entlen;
-
-		if (strncmp(de->d_name, "git-", 4))
-			continue;
-		strcpy(path+dirlen, de->d_name);
-		if (stat(path, &st) || /* stat, not lstat */
-		    !S_ISREG(st.st_mode) ||
-		    !(st.st_mode & S_IXUSR))
-			continue;
-
-		entlen = strlen(de->d_name);
-		if (4 < entlen && !strcmp(de->d_name + entlen - 4, ".exe"))
-			entlen -= 4;
-
-		if (longest < entlen)
-			longest = entlen;
-
-		add_cmdname(de->d_name + 4, entlen-4);
-	}
-	closedir(dir);
-
-	printf("git commands available in '%s'\n", exec_path);
-	printf("----------------------------");
-	mput_char('-', strlen(exec_path));
-	putchar('\n');
-	pretty_print_string_list(cmdname, longest - 4);
-	putchar('\n');
-}
-
-static void list_common_cmds_help(void)
-{
-	int i, longest = 0;
-
-	for (i = 0; i < ARRAY_SIZE(common_cmds); i++) {
-		if (longest < strlen(common_cmds[i].name))
-			longest = strlen(common_cmds[i].name);
-	}
-
-	puts("The most commonly used git commands are:");
-	for (i = 0; i < ARRAY_SIZE(common_cmds); i++) {
-		printf("    %s", common_cmds[i].name);
-		mput_char(' ', longest - strlen(common_cmds[i].name) + 4);
-		puts(common_cmds[i].help);
-	}
-	puts("(use 'git help -a' to get a list of all installed git commands)");
-}
-
-#ifdef __GNUC__
-static void cmd_usage(int show_all, const char *exec_path, const char *fmt, ...)
-	__attribute__((__format__(__printf__, 3, 4), __noreturn__));
-#endif
-static void cmd_usage(int show_all, const char *exec_path, const char *fmt, ...)
-{
-	if (fmt) {
-		va_list ap;
-
-		va_start(ap, fmt);
-		printf("git: ");
-		vprintf(fmt, ap);
-		va_end(ap);
-		putchar('\n');
-	}
-	else
-		puts(git_usage);
-
-	if (exec_path) {
-		putchar('\n');
-		if (show_all)
-			list_commands(exec_path, "git-*");
-		else
-			list_common_cmds_help();
-        }
-
-	exit(1);
-}
+#include "builtin.h"
 
 static void prepend_to_path(const char *dir, int len)
 {
@@ -240,163 +32,7 @@
 	setenv("PATH", path, 1);
 }
 
-static void show_man_page(const char *git_cmd)
-{
-	const char *page;
-
-	if (!strncmp(git_cmd, "git", 3))
-		page = git_cmd;
-	else {
-		int page_len = strlen(git_cmd) + 4;
-		char *p = malloc(page_len + 1);
-		strcpy(p, "git-");
-		strcpy(p + 4, git_cmd);
-		p[page_len] = 0;
-		page = p;
-	}
-
-	execlp("man", "man", page, NULL);
-}
-
-static int cmd_version(int argc, const char **argv, char **envp)
-{
-	printf("git version %s\n", GIT_VERSION);
-	return 0;
-}
-
-static int cmd_help(int argc, const char **argv, char **envp)
-{
-	const char *help_cmd = argv[1];
-	if (!help_cmd)
-		cmd_usage(0, git_exec_path(), NULL);
-	else if (!strcmp(help_cmd, "--all") || !strcmp(help_cmd, "-a"))
-		cmd_usage(1, git_exec_path(), NULL);
-	else
-		show_man_page(help_cmd);
-	return 0;
-}
-
-#define LOGSIZE (65536)
-
-static int cmd_log(int argc, const char **argv, char **envp)
-{
-	struct rev_info rev;
-	struct commit *commit;
-	char *buf = xmalloc(LOGSIZE);
-	static enum cmit_fmt commit_format = CMIT_FMT_DEFAULT;
-	int abbrev = DEFAULT_ABBREV;
-	int abbrev_commit = 0;
-	const char *commit_prefix = "commit ";
-	struct log_tree_opt opt;
-	int shown = 0;
-	int do_diff = 0;
-	int full_diff = 0;
-
-	init_log_tree_opt(&opt);
-	argc = setup_revisions(argc, argv, &rev, "HEAD");
-	while (1 < argc) {
-		const char *arg = argv[1];
-		if (!strncmp(arg, "--pretty", 8)) {
-			commit_format = get_commit_format(arg + 8);
-			if (commit_format == CMIT_FMT_ONELINE)
-				commit_prefix = "";
-		}
-		else if (!strcmp(arg, "--no-abbrev")) {
-			abbrev = 0;
-		}
-		else if (!strcmp(arg, "--abbrev")) {
-			abbrev = DEFAULT_ABBREV;
-		}
-		else if (!strcmp(arg, "--abbrev-commit")) {
-			abbrev_commit = 1;
-		}
-		else if (!strncmp(arg, "--abbrev=", 9)) {
-			abbrev = strtoul(arg + 9, NULL, 10);
-			if (abbrev && abbrev < MINIMUM_ABBREV)
-				abbrev = MINIMUM_ABBREV;
-			else if (40 < abbrev)
-				abbrev = 40;
-		}
-		else if (!strcmp(arg, "--full-diff")) {
-			do_diff = 1;
-			full_diff = 1;
-		}
-		else {
-			int cnt = log_tree_opt_parse(&opt, argv+1, argc-1);
-			if (0 < cnt) {
-				do_diff = 1;
-				argv += cnt;
-				argc -= cnt;
-				continue;
-			}
-			die("unrecognized argument: %s", arg);
-		}
-
-		argc--; argv++;
-	}
-
-	if (do_diff) {
-		opt.diffopt.abbrev = abbrev;
-		opt.verbose_header = 0;
-		opt.always_show_header = 0;
-		opt.no_commit_id = 1;
-		if (opt.combine_merges)
-			opt.ignore_merges = 0;
-		if (opt.dense_combined_merges)
-			opt.diffopt.output_format = DIFF_FORMAT_PATCH;
-		if (!full_diff && rev.prune_data)
-			diff_tree_setup_paths(rev.prune_data, &opt.diffopt);
-		diff_setup_done(&opt.diffopt);
-	}
-
-	prepare_revision_walk(&rev);
-	setup_pager();
-	while ((commit = get_revision(&rev)) != NULL) {
-		if (shown && do_diff && commit_format != CMIT_FMT_ONELINE)
-			putchar('\n');
-		fputs(commit_prefix, stdout);
-		if (abbrev_commit && abbrev)
-			fputs(find_unique_abbrev(commit->object.sha1, abbrev),
-			      stdout);
-		else
-			fputs(sha1_to_hex(commit->object.sha1), stdout);
-		if (rev.parents) {
-			struct commit_list *parents = commit->parents;
-			while (parents) {
-				struct object *o = &(parents->item->object);
-				parents = parents->next;
-				if (o->flags & TMP_MARK)
-					continue;
-				printf(" %s", sha1_to_hex(o->sha1));
-				o->flags |= TMP_MARK;
-			}
-			/* TMP_MARK is a general purpose flag that can
-			 * be used locally, but the user should clean
-			 * things up after it is done with them.
-			 */
-			for (parents = commit->parents;
-			     parents;
-			     parents = parents->next)
-				parents->item->object.flags &= ~TMP_MARK;
-		}
-		if (commit_format == CMIT_FMT_ONELINE)
-			putchar(' ');
-		else
-			putchar('\n');
-		pretty_print_commit(commit_format, commit, ~0, buf,
-				    LOGSIZE, abbrev);
-		printf("%s\n", buf);
-		if (do_diff) {
-			printf("---\n");
-			log_tree_commit(&opt, commit);
-		}
-		shown = 1;
-		free(commit->buffer);
-		commit->buffer = NULL;
-	}
-	free(buf);
-	return 0;
-}
+const char git_version_string[] = GIT_VERSION;
 
 static void handle_internal_command(int argc, const char **argv, char **envp)
 {
@@ -408,6 +44,8 @@
 		{ "version", cmd_version },
 		{ "help", cmd_help },
 		{ "log", cmd_log },
+		{ "whatchanged", cmd_whatchanged },
+		{ "show", cmd_show },
 	};
 	int i;
 
diff --git a/gitk b/gitk
index 87e7162..5362b76 100755
--- a/gitk
+++ b/gitk
@@ -16,22 +16,6 @@
     }
 }
 
-proc parse_args {rargs} {
-    global parsed_args
-
-    if {[catch {
-	set parse_args [concat --default HEAD $rargs]
-	set parsed_args [split [eval exec git-rev-parse $parse_args] "\n"]
-    }]} {
-	# if git-rev-parse failed for some reason...
-	if {$rargs == {}} {
-	    set rargs HEAD
-	}
-	set parsed_args $rargs
-    }
-    return $parsed_args
-}
-
 proc start_rev_list {rlargs} {
     global startmsecs nextupdate ncmupdate
     global commfd leftover tclencoding datemode
@@ -46,7 +30,7 @@
     }
     if {[catch {
 	set commfd [open [concat | git-rev-list --header $order \
-			      --parents --boundary $rlargs] r]
+			      --parents --boundary --default HEAD $rlargs] r]
     } err]} {
 	puts stderr "Error executing git-rev-list: $err"
 	exit 1
@@ -65,7 +49,7 @@
     global phase canv mainfont
 
     set phase getcommits
-    start_rev_list [parse_args $rargs]
+    start_rev_list $rargs
     $canv delete all
     $canv create text 3 3 -anchor nw -text "Reading commits..." \
 	-font $mainfont -tags textitems
diff --git a/http-push.c b/http-push.c
index 114d01b..b4327d9 100644
--- a/http-push.c
+++ b/http-push.c
@@ -2498,6 +2498,7 @@
 			commit_argv[3] = old_sha1_hex;
 			commit_argc++;
 		}
+		init_revisions(&revs);
 		setup_revisions(commit_argc, commit_argv, &revs, NULL);
 		free(new_sha1_hex);
 		if (old_sha1_hex) {
diff --git a/log-tree.c b/log-tree.c
index 3d40482..9634c46 100644
--- a/log-tree.c
+++ b/log-tree.c
@@ -3,59 +3,58 @@
 #include "commit.h"
 #include "log-tree.h"
 
-void init_log_tree_opt(struct log_tree_opt *opt)
+void show_log(struct rev_info *opt, struct log_info *log, const char *sep)
 {
-	memset(opt, 0, sizeof *opt);
-	opt->ignore_merges = 1;
-	opt->header_prefix = "";
-	opt->commit_format = CMIT_FMT_RAW;
-	diff_setup(&opt->diffopt);
+	static char this_header[16384];
+	struct commit *commit = log->commit, *parent = log->parent;
+	int abbrev = opt->diffopt.abbrev;
+	int abbrev_commit = opt->abbrev_commit ? opt->abbrev : 40;
+	const char *extra;
+	int len;
+
+	opt->loginfo = NULL;
+	if (!opt->verbose_header) {
+		puts(sha1_to_hex(commit->object.sha1));
+		return;
+	}
+
+	/*
+	 * The "oneline" format has several special cases:
+	 *  - The pretty-printed commit lacks a newline at the end
+	 *    of the buffer, but we do want to make sure that we
+	 *    have a newline there. If the separator isn't already
+	 *    a newline, add an extra one.
+	 *  - unlike other log messages, the one-line format does
+	 *    not have an empty line between entries.
+	 */
+	extra = "";
+	if (*sep != '\n' && opt->commit_format == CMIT_FMT_ONELINE)
+		extra = "\n";
+	if (opt->shown_one && opt->commit_format != CMIT_FMT_ONELINE)
+		putchar('\n');
+	opt->shown_one = 1;
+
+	/*
+	 * Print header line of header..
+	 */
+	printf("%s%s",
+		opt->commit_format == CMIT_FMT_ONELINE ? "" : "commit ",
+		diff_unique_abbrev(commit->object.sha1, abbrev_commit));
+	if (parent) 
+		printf(" (from %s)", diff_unique_abbrev(parent->object.sha1, abbrev_commit));
+	putchar(opt->commit_format == CMIT_FMT_ONELINE ? ' ' : '\n');
+
+	/*
+	 * And then the pretty-printed message itself
+	 */
+	len = pretty_print_commit(opt->commit_format, commit, ~0u, this_header, sizeof(this_header), abbrev);
+	printf("%s%s%s", this_header, extra, sep);
 }
 
-int log_tree_opt_parse(struct log_tree_opt *opt, const char **av, int ac)
-{
-	const char *arg;
-	int cnt = diff_opt_parse(&opt->diffopt, av, ac);
-	if (0 < cnt)
-		return cnt;
-	arg = *av;
-	if (!strcmp(arg, "-r"))
-		opt->diffopt.recursive = 1;
-	else if (!strcmp(arg, "-t")) {
-		opt->diffopt.recursive = 1;
-		opt->diffopt.tree_in_recursive = 1;
-	}
-	else if (!strcmp(arg, "-m"))
-		opt->ignore_merges = 0;
-	else if (!strcmp(arg, "-c"))
-		opt->combine_merges = 1;
-	else if (!strcmp(arg, "--cc")) {
-		opt->dense_combined_merges = 1;
-		opt->combine_merges = 1;
-	}
-	else if (!strcmp(arg, "-v")) {
-		opt->verbose_header = 1;
-		opt->header_prefix = "diff-tree ";
-	}
-	else if (!strncmp(arg, "--pretty", 8)) {
-		opt->verbose_header = 1;
-		opt->header_prefix = "diff-tree ";
-		opt->commit_format = get_commit_format(arg+8);
-	}
-	else if (!strcmp(arg, "--root"))
-		opt->show_root_diff = 1;
-	else if (!strcmp(arg, "--no-commit-id"))
-		opt->no_commit_id = 1;
-	else if (!strcmp(arg, "--always"))
-		opt->always_show_header = 1;
-	else
-		return 0;
-	return 1;
-}
-
-int log_tree_diff_flush(struct log_tree_opt *opt)
+int log_tree_diff_flush(struct rev_info *opt)
 {
 	diffcore_std(&opt->diffopt);
+
 	if (diff_queue_is_empty()) {
 		int saved_fmt = opt->diffopt.output_format;
 		opt->diffopt.output_format = DIFF_FORMAT_NO_OUTPUT;
@@ -63,17 +62,14 @@
 		opt->diffopt.output_format = saved_fmt;
 		return 0;
 	}
-	if (opt->header) {
-		if (!opt->no_commit_id)
-			printf("%s%c", opt->header,
-			       opt->diffopt.line_termination);
-		opt->header = NULL;
-	}
+
+	if (opt->loginfo && !opt->no_commit_id)
+		show_log(opt, opt->loginfo, opt->diffopt.with_stat ? "---\n" : "\n");
 	diff_flush(&opt->diffopt);
 	return 1;
 }
 
-static int diff_root_tree(struct log_tree_opt *opt,
+static int diff_root_tree(struct rev_info *opt,
 			  const unsigned char *new, const char *base)
 {
 	int retval;
@@ -93,83 +89,78 @@
 	return retval;
 }
 
-static const char *generate_header(struct log_tree_opt *opt,
-				   const unsigned char *commit_sha1,
-				   const unsigned char *parent_sha1,
-				   const struct commit *commit)
-{
-	static char this_header[16384];
-	int offset;
-	unsigned long len;
-	int abbrev = opt->diffopt.abbrev;
-	const char *msg = commit->buffer;
-
-	if (!opt->verbose_header)
-		return sha1_to_hex(commit_sha1);
-
-	len = strlen(msg);
-
-	offset = sprintf(this_header, "%s%s ",
-			 opt->header_prefix,
-			 diff_unique_abbrev(commit_sha1, abbrev));
-	if (commit_sha1 != parent_sha1)
-		offset += sprintf(this_header + offset, "(from %s)\n",
-				  parent_sha1
-				  ? diff_unique_abbrev(parent_sha1, abbrev)
-				  : "root");
-	else
-		offset += sprintf(this_header + offset, "(from parents)\n");
-	offset += pretty_print_commit(opt->commit_format, commit, len,
-				      this_header + offset,
-				      sizeof(this_header) - offset, abbrev);
-	if (opt->always_show_header) {
-		puts(this_header);
-		return NULL;
-	}
-	return this_header;
-}
-
-static int do_diff_combined(struct log_tree_opt *opt, struct commit *commit)
+static int do_diff_combined(struct rev_info *opt, struct commit *commit)
 {
 	unsigned const char *sha1 = commit->object.sha1;
 
-	opt->header = generate_header(opt, sha1, sha1, commit);
-	opt->header = diff_tree_combined_merge(sha1, opt->header,
-						opt->dense_combined_merges,
-						&opt->diffopt);
-	if (!opt->header && opt->verbose_header)
-		opt->header_prefix = "\ndiff-tree ";
-	return 0;
+	diff_tree_combined_merge(sha1, opt->dense_combined_merges, opt);
+	return !opt->loginfo;
 }
 
-int log_tree_commit(struct log_tree_opt *opt, struct commit *commit)
+/*
+ * Show the diff of a commit.
+ *
+ * Return true if we printed any log info messages
+ */
+static int log_tree_diff(struct rev_info *opt, struct commit *commit, struct log_info *log)
 {
+	int showed_log;
 	struct commit_list *parents;
 	unsigned const char *sha1 = commit->object.sha1;
 
+	if (!opt->diff)
+		return 0;
+
 	/* Root commit? */
-	if (opt->show_root_diff && !commit->parents) {
-		opt->header = generate_header(opt, sha1, NULL, commit);
-		diff_root_tree(opt, sha1, "");
+	parents = commit->parents;
+	if (!parents) {
+		if (opt->show_root_diff)
+			diff_root_tree(opt, sha1, "");
+		return !opt->loginfo;
 	}
 
 	/* More than one parent? */
-	if (commit->parents && commit->parents->next) {
+	if (parents && parents->next) {
 		if (opt->ignore_merges)
 			return 0;
 		else if (opt->combine_merges)
 			return do_diff_combined(opt, commit);
+
+		/* If we show individual diffs, show the parent info */
+		log->parent = parents->item;
 	}
 
-	for (parents = commit->parents; parents; parents = parents->next) {
+	showed_log = 0;
+	for (;;) {
 		struct commit *parent = parents->item;
-		unsigned const char *psha1 = parent->object.sha1;
-		opt->header = generate_header(opt, sha1, psha1, commit);
-		diff_tree_sha1(psha1, sha1, "", &opt->diffopt);
-		log_tree_diff_flush(opt);		
 
-		if (!opt->header && opt->verbose_header)
-			opt->header_prefix = "\ndiff-tree ";
+		diff_tree_sha1(parent->object.sha1, sha1, "", &opt->diffopt);
+		log_tree_diff_flush(opt);
+
+		showed_log |= !opt->loginfo;
+
+		/* Set up the log info for the next parent, if any.. */
+		parents = parents->next;
+		if (!parents)
+			break;
+		log->parent = parents->item;
+		opt->loginfo = log;
 	}
+	return showed_log;
+}
+
+int log_tree_commit(struct rev_info *opt, struct commit *commit)
+{
+	struct log_info log;
+
+	log.commit = commit;
+	log.parent = NULL;
+	opt->loginfo = &log;
+
+	if (!log_tree_diff(opt, commit, &log) && opt->loginfo && opt->always_show_header) {
+		log.parent = NULL;
+		show_log(opt, opt->loginfo, "");
+	}
+	opt->loginfo = NULL;
 	return 0;
 }
diff --git a/log-tree.h b/log-tree.h
index da166c6..a26e484 100644
--- a/log-tree.h
+++ b/log-tree.h
@@ -1,23 +1,16 @@
 #ifndef LOG_TREE_H
 #define LOG_TREE_H
 
-struct log_tree_opt {
-	struct diff_options diffopt;
-	int show_root_diff;
-	int no_commit_id;
-	int verbose_header;
-	int ignore_merges;
-	int combine_merges;
-	int dense_combined_merges;
-	int always_show_header;
-	const char *header_prefix;
-	const char *header;
-	enum cmit_fmt commit_format;
+#include "revision.h"
+
+struct log_info {
+	struct commit *commit, *parent;
 };
 
-void init_log_tree_opt(struct log_tree_opt *);
-int log_tree_diff_flush(struct log_tree_opt *);
-int log_tree_commit(struct log_tree_opt *, struct commit *);
-int log_tree_opt_parse(struct log_tree_opt *, const char **, int);
+void init_log_tree_opt(struct rev_info *);
+int log_tree_diff_flush(struct rev_info *);
+int log_tree_commit(struct rev_info *, struct commit *);
+int log_tree_opt_parse(struct rev_info *, const char **, int);
+void show_log(struct rev_info *opt, struct log_info *log, const char *sep);
 
 #endif
diff --git a/pager.c b/pager.c
index f7b8e78..9a30939 100644
--- a/pager.c
+++ b/pager.c
@@ -21,7 +21,7 @@
 		return;
 	if (!pager)
 		pager = "less";
-	else if (!*pager)
+	else if (!*pager || !strcmp(pager, "cat"))
 		return;
 
 	if (pipe(fd) < 0)
diff --git a/repo-config.c b/repo-config.c
index c5ebb76..fa8aba7 100644
--- a/repo-config.c
+++ b/repo-config.c
@@ -2,7 +2,7 @@
 #include <regex.h>
 
 static const char git_config_set_usage[] =
-"git-repo-config [ --bool | --int ] [--get | --get-all | --replace-all | --unset | --unset-all] name [value [value_regex]]";
+"git-repo-config [ --bool | --int ] [--get | --get-all | --replace-all | --unset | --unset-all] name [value [value_regex]] | --list";
 
 static char* key = NULL;
 static char* value = NULL;
@@ -12,6 +12,15 @@
 static int seen = 0;
 static enum { T_RAW, T_INT, T_BOOL } type = T_RAW;
 
+static int show_all_config(const char *key_, const char *value_)
+{
+	if (value_)
+		printf("%s=%s\n", key_, value_);
+	else
+		printf("%s\n", key_);
+	return 0;
+}
+
 static int show_config(const char* key_, const char* value_)
 {
 	if (value_ == NULL)
@@ -67,7 +76,7 @@
 		}
 	}
 
-	i = git_config(show_config);
+	git_config(show_config);
 	if (value) {
 		printf("%s\n", value);
 		free(value);
@@ -99,6 +108,9 @@
 		argv++;
 	}
 
+	if (!strcmp(argv[1], "--list") || !strcmp(argv[1], "-l"))
+		return git_config(show_all_config);
+
 	switch (argc) {
 	case 2:
 		return get_value(argv[1], NULL);
diff --git a/rev-list.c b/rev-list.c
index a8fe83c..8b0ec38 100644
--- a/rev-list.c
+++ b/rev-list.c
@@ -39,24 +39,21 @@
 struct rev_info revs;
 
 static int bisect_list = 0;
-static int verbose_header = 0;
-static int abbrev = DEFAULT_ABBREV;
-static int abbrev_commit = 0;
 static int show_timestamp = 0;
 static int hdr_termination = 0;
-static const char *commit_prefix = "";
-static enum cmit_fmt commit_format = CMIT_FMT_RAW;
+static const char *header_prefix;
 
 static void show_commit(struct commit *commit)
 {
 	if (show_timestamp)
 		printf("%lu ", commit->date);
-	if (commit_prefix[0])
-		fputs(commit_prefix, stdout);
+	if (header_prefix)
+		fputs(header_prefix, stdout);
 	if (commit->object.flags & BOUNDARY)
 		putchar('-');
-	if (abbrev_commit && abbrev)
-		fputs(find_unique_abbrev(commit->object.sha1, abbrev), stdout);
+	if (revs.abbrev_commit && revs.abbrev)
+		fputs(find_unique_abbrev(commit->object.sha1, revs.abbrev),
+		      stdout);
 	else
 		fputs(sha1_to_hex(commit->object.sha1), stdout);
 	if (revs.parents) {
@@ -78,14 +75,16 @@
 		     parents = parents->next)
 			parents->item->object.flags &= ~TMP_MARK;
 	}
-	if (commit_format == CMIT_FMT_ONELINE)
+	if (revs.commit_format == CMIT_FMT_ONELINE)
 		putchar(' ');
 	else
 		putchar('\n');
 
-	if (verbose_header) {
+	if (revs.verbose_header) {
 		static char pretty_header[16384];
-		pretty_print_commit(commit_format, commit, ~0, pretty_header, sizeof(pretty_header), abbrev);
+		pretty_print_commit(revs.commit_format, commit, ~0,
+				    pretty_header, sizeof(pretty_header),
+				    revs.abbrev);
 		printf("%s%c", pretty_header, hdr_termination);
 	}
 	fflush(stdout);
@@ -297,58 +296,16 @@
 	struct commit_list *list;
 	int i;
 
+	init_revisions(&revs);
+	revs.abbrev = 0;
+	revs.commit_format = CMIT_FMT_UNSPECIFIED;
 	argc = setup_revisions(argc, argv, &revs, NULL);
 
 	for (i = 1 ; i < argc; i++) {
 		const char *arg = argv[i];
 
-		/* accept -<digit>, like traditilnal "head" */
-		if ((*arg == '-') && isdigit(arg[1])) {
-			revs.max_count = atoi(arg + 1);
-			continue;
-		}
-		if (!strcmp(arg, "-n")) {
-			if (++i >= argc)
-				die("-n requires an argument");
-			revs.max_count = atoi(argv[i]);
-			continue;
-		}
-		if (!strncmp(arg,"-n",2)) {
-			revs.max_count = atoi(arg + 2);
-			continue;
-		}
 		if (!strcmp(arg, "--header")) {
-			verbose_header = 1;
-			continue;
-		}
-		if (!strcmp(arg, "--no-abbrev")) {
-			abbrev = 0;
-			continue;
-		}
-		if (!strcmp(arg, "--abbrev")) {
-			abbrev = DEFAULT_ABBREV;
-			continue;
-		}
-		if (!strcmp(arg, "--abbrev-commit")) {
-			abbrev_commit = 1;
-			continue;
-		}
-		if (!strncmp(arg, "--abbrev=", 9)) {
-			abbrev = strtoul(arg + 9, NULL, 10);
-			if (abbrev && abbrev < MINIMUM_ABBREV)
-				abbrev = MINIMUM_ABBREV;
-			else if (40 < abbrev)
-				abbrev = 40;
-			continue;
-		}
-		if (!strncmp(arg, "--pretty", 8)) {
-			commit_format = get_commit_format(arg+8);
-			verbose_header = 1;
-			hdr_termination = '\n';
-			if (commit_format == CMIT_FMT_ONELINE)
-				commit_prefix = "";
-			else
-				commit_prefix = "commit ";
+			revs.verbose_header = 1;
 			continue;
 		}
 		if (!strcmp(arg, "--timestamp")) {
@@ -362,14 +319,27 @@
 		usage(rev_list_usage);
 
 	}
+	if (revs.commit_format != CMIT_FMT_UNSPECIFIED) {
+		/* The command line has a --pretty  */
+		hdr_termination = '\n';
+		if (revs.commit_format == CMIT_FMT_ONELINE)
+			header_prefix = "";
+		else
+			header_prefix = "commit ";
+	}
+	else if (revs.verbose_header)
+		/* Only --header was specified */
+		revs.commit_format = CMIT_FMT_RAW;
 
 	list = revs.commits;
 
-	if (!list &&
-	    (!(revs.tag_objects||revs.tree_objects||revs.blob_objects) && !revs.pending_objects))
+	if ((!list &&
+	     (!(revs.tag_objects||revs.tree_objects||revs.blob_objects) &&
+	      !revs.pending_objects)) ||
+	    revs.diff)
 		usage(rev_list_usage);
 
-	save_commit_buffer = verbose_header;
+	save_commit_buffer = revs.verbose_header;
 	track_object_refs = 0;
 	if (bisect_list)
 		revs.limited = 1;
diff --git a/revision.c b/revision.c
index 03dd238..f2a9f25 100644
--- a/revision.c
+++ b/revision.c
@@ -116,21 +116,27 @@
 	add_object(obj, &revs->pending_objects, NULL, name);
 }
 
-static struct commit *get_commit_reference(struct rev_info *revs, const char *name, const unsigned char *sha1, unsigned int flags)
+static struct object *get_reference(struct rev_info *revs, const char *name, const unsigned char *sha1, unsigned int flags)
 {
 	struct object *object;
 
 	object = parse_object(sha1);
 	if (!object)
 		die("bad object %s", name);
+	object->flags |= flags;
+	return object;
+}
+
+static struct commit *handle_commit(struct rev_info *revs, struct object *object, const char *name)
+{
+	unsigned long flags = object->flags;
 
 	/*
 	 * Tag object? Look what it points to..
 	 */
 	while (object->type == tag_type) {
 		struct tag *tag = (struct tag *) object;
-		object->flags |= flags;
-		if (revs->tag_objects && !(object->flags & UNINTERESTING))
+		if (revs->tag_objects && !(flags & UNINTERESTING))
 			add_pending_object(revs, object, tag->tag);
 		object = parse_object(tag->tagged->sha1);
 		if (!object)
@@ -143,10 +149,10 @@
 	 */
 	if (object->type == commit_type) {
 		struct commit *commit = (struct commit *)object;
-		object->flags |= flags;
 		if (parse_commit(commit) < 0)
 			die("unable to parse commit %s", name);
 		if (flags & UNINTERESTING) {
+			commit->object.flags |= UNINTERESTING;
 			mark_parents_uninteresting(commit);
 			revs->limited = 1;
 		}
@@ -241,7 +247,7 @@
 		return REV_TREE_DIFFERENT;
 	tree_difference = REV_TREE_SAME;
 	if (diff_tree_sha1(t1->object.sha1, t2->object.sha1, "",
-			   &revs->diffopt) < 0)
+			   &revs->pruning) < 0)
 		return REV_TREE_DIFFERENT;
 	return tree_difference;
 }
@@ -264,7 +270,7 @@
 	empty.size = 0;
 
 	tree_difference = 0;
-	retval = diff_tree(&empty, &real, "", &revs->diffopt);
+	retval = diff_tree(&empty, &real, "", &revs->pruning);
 	free(tree);
 
 	return retval >= 0 && !tree_difference;
@@ -375,6 +381,9 @@
 	if (revs->prune_fn)
 		revs->prune_fn(revs, commit);
 
+	if (revs->no_walk)
+		return;
+
 	parent = commit->parents;
 	while (parent) {
 		struct commit *p = parent->item;
@@ -451,21 +460,13 @@
 	revs->commits = newlist;
 }
 
-static void add_one_commit(struct commit *commit, struct rev_info *revs)
-{
-	if (!commit || (commit->object.flags & SEEN))
-		return;
-	commit->object.flags |= SEEN;
-	commit_list_insert(commit, &revs->commits);
-}
-
 static int all_flags;
 static struct rev_info *all_revs;
 
 static int handle_one_ref(const char *path, const unsigned char *sha1)
 {
-	struct commit *commit = get_commit_reference(all_revs, path, sha1, all_flags);
-	add_one_commit(commit, all_revs);
+	struct object *object = get_reference(all_revs, path, sha1, all_flags);
+	add_pending_object(all_revs, object, "");
 	return 0;
 }
 
@@ -479,9 +480,12 @@
 void init_revisions(struct rev_info *revs)
 {
 	memset(revs, 0, sizeof(*revs));
-	revs->diffopt.recursive = 1;
-	revs->diffopt.add_remove = file_add_remove;
-	revs->diffopt.change = file_change;
+
+	revs->abbrev = DEFAULT_ABBREV;
+	revs->ignore_merges = 1;
+	revs->pruning.recursive = 1;
+	revs->pruning.add_remove = file_add_remove;
+	revs->pruning.change = file_change;
 	revs->lifo = 1;
 	revs->dense = 1;
 	revs->prefix = setup_git_directory();
@@ -494,6 +498,10 @@
 
 	revs->topo_setter = topo_sort_default_setter;
 	revs->topo_getter = topo_sort_default_getter;
+
+	revs->commit_format = CMIT_FMT_DEFAULT;
+
+	diff_setup(&revs->diffopt);
 }
 
 /*
@@ -509,8 +517,6 @@
 	const char **unrecognized = argv + 1;
 	int left = 1;
 
-	init_revisions(revs);
-
 	/* First, search for "--" */
 	seen_dashdash = 0;
 	for (i = 1; i < argc; i++) {
@@ -526,13 +532,14 @@
 
 	flags = 0;
 	for (i = 1; i < argc; i++) {
-		struct commit *commit;
+		struct object *object;
 		const char *arg = argv[i];
 		unsigned char sha1[20];
 		char *dotdot;
 		int local_flags;
 
 		if (*arg == '-') {
+			int opts;
 			if (!strncmp(arg, "--max-count=", 12)) {
 				revs->max_count = atoi(arg + 12);
 				continue;
@@ -640,6 +647,76 @@
 				revs->unpacked = 1;
 				continue;
 			}
+			if (!strcmp(arg, "-r")) {
+				revs->diff = 1;
+				revs->diffopt.recursive = 1;
+				continue;
+			}
+			if (!strcmp(arg, "-t")) {
+				revs->diff = 1;
+				revs->diffopt.recursive = 1;
+				revs->diffopt.tree_in_recursive = 1;
+				continue;
+			}
+			if (!strcmp(arg, "-m")) {
+				revs->ignore_merges = 0;
+				continue;
+			}
+			if (!strcmp(arg, "-c")) {
+				revs->diff = 1;
+				revs->combine_merges = 1;
+				continue;
+			}
+			if (!strcmp(arg, "--cc")) {
+				revs->diff = 1;
+				revs->dense_combined_merges = 1;
+				revs->combine_merges = 1;
+				continue;
+			}
+			if (!strcmp(arg, "-v")) {
+				revs->verbose_header = 1;
+				continue;
+			}
+			if (!strncmp(arg, "--pretty", 8)) {
+				revs->verbose_header = 1;
+				revs->commit_format = get_commit_format(arg+8);
+				continue;
+			}
+			if (!strcmp(arg, "--root")) {
+				revs->show_root_diff = 1;
+				continue;
+			}
+			if (!strcmp(arg, "--no-commit-id")) {
+				revs->no_commit_id = 1;
+				continue;
+			}
+			if (!strcmp(arg, "--always")) {
+				revs->always_show_header = 1;
+				continue;
+			}
+			if (!strcmp(arg, "--no-abbrev")) {
+				revs->abbrev = 0;
+				continue;
+			}
+			if (!strcmp(arg, "--abbrev")) {
+				revs->abbrev = DEFAULT_ABBREV;
+				continue;
+			}
+			if (!strcmp(arg, "--abbrev-commit")) {
+				revs->abbrev_commit = 1;
+				continue;
+			}
+			if (!strcmp(arg, "--full-diff")) {
+				revs->diff = 1;
+				revs->full_diff = 1;
+				continue;
+			}
+			opts = diff_opt_parse(&revs->diffopt, argv+i, argc-i);
+			if (opts > 0) {
+				revs->diff = 1;
+				i += opts - 1;
+				continue;
+			}
 			*unrecognized++ = arg;
 			left++;
 			continue;
@@ -656,15 +733,15 @@
 				this = "HEAD";
 			if (!get_sha1(this, from_sha1) &&
 			    !get_sha1(next, sha1)) {
-				struct commit *exclude;
-				struct commit *include;
+				struct object *exclude;
+				struct object *include;
 
-				exclude = get_commit_reference(revs, this, from_sha1, flags ^ UNINTERESTING);
-				include = get_commit_reference(revs, next, sha1, flags);
+				exclude = get_reference(revs, this, from_sha1, flags ^ UNINTERESTING);
+				include = get_reference(revs, next, sha1, flags);
 				if (!exclude || !include)
 					die("Invalid revision range %s..%s", arg, next);
-				add_one_commit(exclude, revs);
-				add_one_commit(include, revs);
+				add_pending_object(revs, exclude, this);
+				add_pending_object(revs, include, next);
 				continue;
 			}
 			*dotdot = '.';
@@ -687,32 +764,58 @@
 			revs->prune_data = get_pathspec(revs->prefix, argv + i);
 			break;
 		}
-		commit = get_commit_reference(revs, arg, sha1, flags ^ local_flags);
-		add_one_commit(commit, revs);
+		object = get_reference(revs, arg, sha1, flags ^ local_flags);
+		add_pending_object(revs, object, arg);
 	}
-	if (def && !revs->commits) {
+	if (def && !revs->pending_objects) {
 		unsigned char sha1[20];
-		struct commit *commit;
+		struct object *object;
 		if (get_sha1(def, sha1) < 0)
 			die("bad default revision '%s'", def);
-		commit = get_commit_reference(revs, def, sha1, 0);
-		add_one_commit(commit, revs);
+		object = get_reference(revs, def, sha1, 0);
+		add_pending_object(revs, object, def);
 	}
 
 	if (revs->topo_order || revs->unpacked)
 		revs->limited = 1;
 
 	if (revs->prune_data) {
-		diff_tree_setup_paths(revs->prune_data, &revs->diffopt);
+		diff_tree_setup_paths(revs->prune_data, &revs->pruning);
 		revs->prune_fn = try_to_simplify_commit;
+		if (!revs->full_diff)
+			diff_tree_setup_paths(revs->prune_data, &revs->diffopt);
 	}
+	if (revs->combine_merges) {
+		revs->ignore_merges = 0;
+		if (revs->dense_combined_merges &&
+		    (revs->diffopt.output_format != DIFF_FORMAT_DIFFSTAT))
+			revs->diffopt.output_format = DIFF_FORMAT_PATCH;
+	}
+	revs->diffopt.abbrev = revs->abbrev;
+	diff_setup_done(&revs->diffopt);
 
 	return left;
 }
 
 void prepare_revision_walk(struct rev_info *revs)
 {
-	sort_by_date(&revs->commits);
+	struct object_list *list;
+
+	list = revs->pending_objects;
+	revs->pending_objects = NULL;
+	while (list) {
+		struct commit *commit = handle_commit(revs, list->item, list->name);
+		if (commit) {
+			if (!(commit->object.flags & SEEN)) {
+				commit->object.flags |= SEEN;
+				insert_by_date(commit, &revs->commits);
+			}
+		}
+		list = list->next;
+	}
+
+	if (revs->no_walk)
+		return;
 	if (revs->limited)
 		limit_list(revs);
 	if (revs->topo_order)
diff --git a/revision.h b/revision.h
index 4b27043..48d7b4c 100644
--- a/revision.h
+++ b/revision.h
@@ -11,6 +11,7 @@
 #define ADDED		(1u<<7)	/* Parents already parsed and added? */
 
 struct rev_info;
+struct log_info;
 
 typedef void (prune_fn_t)(struct rev_info *revs, struct commit *commit);
 
@@ -27,6 +28,7 @@
 	/* Traversal flags */
 	unsigned int	dense:1,
 			no_merges:1,
+			no_walk:1,
 			remove_empty_trees:1,
 			lifo:1,
 			topo_order:1,
@@ -39,13 +41,32 @@
 			boundary:1,
 			parents:1;
 
+	/* Diff flags */
+	unsigned int	diff:1,
+			full_diff:1,
+			show_root_diff:1,
+			no_commit_id:1,
+			verbose_header:1,
+			ignore_merges:1,
+			combine_merges:1,
+			dense_combined_merges:1,
+			always_show_header:1;
+
+	/* Format info */
+	unsigned int	shown_one:1,
+			abbrev_commit:1;
+	unsigned int	abbrev;
+	enum cmit_fmt	commit_format;
+	struct log_info *loginfo;
+
 	/* special limits */
 	int max_count;
 	unsigned long max_age;
 	unsigned long min_age;
 
-	/* paths limiting */
+	/* diff info for patches and for paths limiting */
 	struct diff_options diffopt;
+	struct diff_options pruning;
 
 	topo_sort_set_fn_t topo_setter;
 	topo_sort_get_fn_t topo_getter;
diff --git a/sha1_name.c b/sha1_name.c
index 4f92e12..345935b 100644
--- a/sha1_name.c
+++ b/sha1_name.c
@@ -3,6 +3,7 @@
 #include "commit.h"
 #include "tree.h"
 #include "blob.h"
+#include "tree-walk.h"
 
 static int find_short_object_filename(int len, const char *name, unsigned char *sha1)
 {
@@ -455,6 +456,19 @@
  */
 int get_sha1(const char *name, unsigned char *sha1)
 {
+	int ret;
+	unsigned unused;
+
 	prepare_alt_odb();
-	return get_sha1_1(name, strlen(name), sha1);
+	ret = get_sha1_1(name, strlen(name), sha1);
+	if (ret < 0) {
+		const char *cp = strchr(name, ':');
+		if (cp) {
+			unsigned char tree_sha1[20];
+			if (!get_sha1_1(name, cp-name, tree_sha1))
+				return get_tree_entry(tree_sha1, cp+1, sha1,
+						      &unused);
+		}
+	}
+	return ret;
 }
diff --git a/t/t0000-basic.sh b/t/t0000-basic.sh
index 6729a18..cf33989 100755
--- a/t/t0000-basic.sh
+++ b/t/t0000-basic.sh
@@ -174,6 +174,27 @@
     'git-ls-tree -r output for a known tree.' \
     'diff current expected'
 
+# But with -r -t we can have both.
+test_expect_success \
+    'showing tree with git-ls-tree -r -t' \
+    'git-ls-tree -r -t $tree >current'
+cat >expected <<\EOF
+100644 blob f87290f8eb2cbbea7857214459a0739927eab154	path0
+120000 blob 15a98433ae33114b085f3eb3bb03b832b3180a01	path0sym
+040000 tree 58a09c23e2ca152193f2786e06986b7b6712bdbe	path2
+100644 blob 3feff949ed00a62d9f7af97c15cd8a30595e7ac7	path2/file2
+120000 blob d8ce161addc5173867a3c3c730924388daedbc38	path2/file2sym
+040000 tree 21ae8269cacbe57ae09138dcc3a2887f904d02b3	path3
+100644 blob 0aa34cae68d0878578ad119c86ca2b5ed5b28376	path3/file3
+120000 blob 8599103969b43aff7e430efea79ca4636466794f	path3/file3sym
+040000 tree 3c5e5399f3a333eddecce7a9b9465b63f65f51e2	path3/subp3
+100644 blob 00fb5908cb97c2564a9783c0c64087333b3b464f	path3/subp3/file3
+120000 blob 6649a1ebe9e9f1c553b66f5a6e74136a07ccc57c	path3/subp3/file3sym
+EOF
+test_expect_success \
+    'git-ls-tree -r output for a known tree.' \
+    'diff current expected'
+
 ################################################################
 rm .git/index
 test_expect_success \
@@ -205,4 +226,32 @@
     'no diff after checkout and git-update-index --refresh.' \
     'git-diff-files >current && cmp -s current /dev/null'
 
+################################################################
+P=087704a96baf1c2d1c869a8b084481e121c88b5b
+test_expect_success \
+    'git-commit-tree records the correct tree in a commit.' \
+    'commit0=$(echo NO | git-commit-tree $P) &&
+     tree=$(git show --pretty=raw $commit0 |
+	 sed -n -e "s/^tree //p" -e "/^author /q") &&
+     test "z$tree" = "z$P"'
+
+test_expect_success \
+    'git-commit-tree records the correct parent in a commit.' \
+    'commit1=$(echo NO | git-commit-tree $P -p $commit0) &&
+     parent=$(git show --pretty=raw $commit1 |
+	 sed -n -e "s/^parent //p" -e "/^author /q") &&
+     test "z$commit0" = "z$parent"'
+
+test_expect_success \
+    'git-commit-tree omits duplicated parent in a commit.' \
+    'commit2=$(echo NO | git-commit-tree $P -p $commit0 -p $commit0) &&
+     parent=$(git show --pretty=raw $commit2 |
+	 sed -n -e "s/^parent //p" -e "/^author /q" |
+	 sort -u) &&
+     test "z$commit0" = "z$parent" &&
+     numparent=$(git show --pretty=raw $commit2 |
+	 sed -n -e "s/^parent //p" -e "/^author /q" |
+	 wc -l) &&
+     test $numparent = 1'
+
 test_done
diff --git a/t/t1001-read-tree-m-2way.sh b/t/t1001-read-tree-m-2way.sh
index d0ed242..75e4c9a 100755
--- a/t/t1001-read-tree-m-2way.sh
+++ b/t/t1001-read-tree-m-2way.sh
@@ -37,7 +37,7 @@
 }
 
 check_cache_at () {
-	clean_if_empty=`git-diff-files "$1"`
+	clean_if_empty=`git-diff-files -- "$1"`
 	case "$clean_if_empty" in
 	'')  echo "$1: clean" ;;
 	?*)  echo "$1: dirty" ;;
diff --git a/t/t1002-read-tree-m-u-2way.sh b/t/t1002-read-tree-m-u-2way.sh
index 861ef4c..4d175d8 100755
--- a/t/t1002-read-tree-m-u-2way.sh
+++ b/t/t1002-read-tree-m-u-2way.sh
@@ -20,7 +20,7 @@
 }
 
 check_cache_at () {
-	clean_if_empty=`git-diff-files "$1"`
+	clean_if_empty=`git-diff-files -- "$1"`
 	case "$clean_if_empty" in
 	'')  echo "$1: clean" ;;
 	?*)  echo "$1: dirty" ;;
diff --git a/t/t1200-tutorial.sh b/t/t1200-tutorial.sh
index f4d53c0..c7db20e 100755
--- a/t/t1200-tutorial.sh
+++ b/t/t1200-tutorial.sh
@@ -49,7 +49,7 @@
 #test_expect_success 'git-read-tree --reset HEAD' "git-read-tree --reset HEAD ; test \"hello: needs update\" = \"$(git-update-index --refresh)\""
 
 cat > whatchanged.expect << EOF
-diff-tree VARIABLE (from root)
+commit VARIABLE
 Author: VARIABLE
 Date:   VARIABLE
 
@@ -72,7 +72,7 @@
 EOF
 
 git-whatchanged -p --root | \
-	sed -e "1s/^\(.\{10\}\).\{40\}/\1VARIABLE/" \
+	sed -e "1s/^\(.\{7\}\).\{40\}/\1VARIABLE/" \
 		-e "2,3s/^\(.\{8\}\).*$/\1VARIABLE/" \
 > whatchanged.output
 test_expect_success 'git-whatchanged -p --root' 'cmp whatchanged.expect whatchanged.output'
diff --git a/t/t4010-diff-pathspec.sh b/t/t4010-diff-pathspec.sh
index 8db329d..9e1544d 100755
--- a/t/t4010-diff-pathspec.sh
+++ b/t/t4010-diff-pathspec.sh
@@ -28,7 +28,7 @@
 EOF
 test_expect_success \
     'limit to path should show nothing' \
-    'git-diff-index --cached $tree path >current &&
+    'git-diff-index --cached $tree -- path >current &&
      compare_diff_raw current expected'
 
 cat >expected <<\EOF
@@ -36,7 +36,7 @@
 EOF
 test_expect_success \
     'limit to path1 should show path1/file1' \
-    'git-diff-index --cached $tree path1 >current &&
+    'git-diff-index --cached $tree -- path1 >current &&
      compare_diff_raw current expected'
 
 cat >expected <<\EOF
@@ -44,7 +44,7 @@
 EOF
 test_expect_success \
     'limit to path1/ should show path1/file1' \
-    'git-diff-index --cached $tree path1/ >current &&
+    'git-diff-index --cached $tree -- path1/ >current &&
      compare_diff_raw current expected'
 
 cat >expected <<\EOF
@@ -52,14 +52,14 @@
 EOF
 test_expect_success \
     'limit to file0 should show file0' \
-    'git-diff-index --cached $tree file0 >current &&
+    'git-diff-index --cached $tree -- file0 >current &&
      compare_diff_raw current expected'
 
 cat >expected <<\EOF
 EOF
 test_expect_success \
     'limit to file0/ should emit nothing.' \
-    'git-diff-index --cached $tree file0/ >current &&
+    'git-diff-index --cached $tree -- file0/ >current &&
      compare_diff_raw current expected'
 
 test_done
diff --git a/tree-walk.c b/tree-walk.c
index bf8bfdf..9f7abb7 100644
--- a/tree-walk.c
+++ b/tree-walk.c
@@ -115,3 +115,53 @@
 	free(entry);
 }
 
+static int find_tree_entry(struct tree_desc *t, const char *name, unsigned char *result, unsigned *mode)
+{
+	int namelen = strlen(name);
+	while (t->size) {
+		const char *entry;
+		const unsigned char *sha1;
+		int entrylen, cmp;
+
+		sha1 = tree_entry_extract(t, &entry, mode);
+		update_tree_entry(t);
+		entrylen = strlen(entry);
+		if (entrylen > namelen)
+			continue;
+		cmp = memcmp(name, entry, entrylen);
+		if (cmp > 0)
+			continue;
+		if (cmp < 0)
+			break;
+		if (entrylen == namelen) {
+			memcpy(result, sha1, 20);
+			return 0;
+		}
+		if (name[entrylen] != '/')
+			continue;
+		if (!S_ISDIR(*mode))
+			break;
+		if (++entrylen == namelen) {
+			memcpy(result, sha1, 20);
+			return 0;
+		}
+		return get_tree_entry(sha1, name + entrylen, result, mode);
+	}
+	return -1;
+}
+
+int get_tree_entry(const unsigned char *tree_sha1, const char *name, unsigned char *sha1, unsigned *mode)
+{
+	int retval;
+	void *tree;
+	struct tree_desc t;
+
+	tree = read_object_with_reference(tree_sha1, tree_type, &t.size, NULL);
+	if (!tree)
+		return -1;
+	t.buf = tree;
+	retval = find_tree_entry(&t, name, sha1, mode);
+	free(tree);
+	return retval;
+}
+
diff --git a/tree-walk.h b/tree-walk.h
index 76893e3..47438fe 100644
--- a/tree-walk.h
+++ b/tree-walk.h
@@ -22,4 +22,6 @@
 
 void traverse_trees(int n, struct tree_desc *t, const char *base, traverse_callback_t callback);
 
+int get_tree_entry(const unsigned char *, const char *, unsigned char *, unsigned *);
+
 #endif
diff --git a/update-index.c b/update-index.c
index 1efac27..facec8d 100644
--- a/update-index.c
+++ b/update-index.c
@@ -6,6 +6,7 @@
 #include "cache.h"
 #include "strbuf.h"
 #include "quote.h"
+#include "tree-walk.h"
 
 /*
  * Default to not allowing changes to the list of files. The
@@ -328,7 +329,7 @@
 	return 0;
 }
 
-static int chmod_path(int flip, const char *path)
+static void chmod_path(int flip, const char *path)
 {
 	int pos;
 	struct cache_entry *ce;
@@ -336,21 +337,24 @@
 
 	pos = cache_name_pos(path, strlen(path));
 	if (pos < 0)
-		return -1;
+		goto fail;
 	ce = active_cache[pos];
 	mode = ntohl(ce->ce_mode);
 	if (!S_ISREG(mode))
-		return -1;
+		goto fail;
 	switch (flip) {
 	case '+':
 		ce->ce_mode |= htonl(0111); break;
 	case '-':
 		ce->ce_mode &= htonl(~0111); break;
 	default:
-		return -1;
+		goto fail;
 	}
 	active_cache_changed = 1;
-	return 0;
+	report("chmod %cx '%s'", flip, path);
+	return;
+ fail:
+	die("git-update-index: cannot chmod %cx '%s'", flip, path);
 }
 
 static struct cache_file cache_file;
@@ -471,6 +475,124 @@
 static const char update_index_usage[] =
 "git-update-index [-q] [--add] [--replace] [--remove] [--unmerged] [--refresh] [--cacheinfo] [--chmod=(+|-)x] [--info-only] [--force-remove] [--stdin] [--index-info] [--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)) {
+		error("%s: not in %s branch.", path, which);
+		return NULL;
+	}
+	if (mode == S_IFDIR) {
+		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;
+	}
+
+	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)
+{
+	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];
+		err |= unresolve_one(arg);
+	}
+	return err;
+}
+
 int main(int argc, const char **argv)
 {
 	int i, newfd, entries, has_errors = 0, line_termination = '\n';
@@ -478,6 +600,7 @@
 	int read_from_stdin = 0;
 	const char *prefix = setup_git_directory();
 	int prefix_length = prefix ? strlen(prefix) : 0;
+	char set_executable_bit = 0;
 
 	git_config(git_default_config);
 
@@ -544,8 +667,7 @@
 			    !strcmp(path, "--chmod=+x")) {
 				if (argc <= i+1)
 					die("git-update-index: %s <path>", path);
-				if (chmod_path(path[8], argv[++i]))
-					die("git-update-index: %s cannot chmod %s", path, argv[i]);
+				set_executable_bit = path[8];
 				continue;
 			}
 			if (!strcmp(path, "--assume-unchanged")) {
@@ -581,6 +703,12 @@
 				read_index_info(line_termination);
 				break;
 			}
+			if (!strcmp(path, "--unresolve")) {
+				has_errors = do_unresolve(argc - i, argv + i);
+				if (has_errors)
+					active_cache_changed = 0;
+				goto finish;
+			}
 			if (!strcmp(path, "--ignore-missing")) {
 				not_new = 1;
 				continue;
@@ -594,6 +722,8 @@
 			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;
@@ -608,10 +738,16 @@
 			else
 				path_name = buf.buf;
 			update_one(path_name, prefix, prefix_length);
+			if (set_executable_bit) {
+				const char *p = prefix_path(prefix, prefix_length, path_name);
+				chmod_path(set_executable_bit, p);
+			}
 			if (path_name != buf.buf)
 				free(path_name);
 		}
 	}
+
+ finish:
 	if (active_cache_changed) {
 		if (write_cache(newfd, active_cache, active_nr) ||
 		    commit_index_file(&cache_file))