Git development
 help / color / mirror / Atom feed
* Re: Significant performance waste in git-svn and friends
From: Shawn O. Pearce @ 2007-09-06  2:19 UTC (permalink / raw)
  To: David Kastrup; +Cc: Junio C Hamano, Mike Hommey, git
In-Reply-To: <85bqcghktl.fsf@lola.goethe.zz>

David Kastrup <dak@gnu.org> wrote:
> Junio C Hamano <gitster@pobox.com> writes:
> 
> > Mike Hommey <mh@glandium.org> writes:
> >
> >> The same things obviously apply to git-cvsimport and other scripts
> >> calling git-hash-object a lot.
> >
> > I wonder if letting fast-import handle the object creation is an
> > option, though.
> 
> I think it would be saner to give git-hash-object an operation mode
> that makes it usable as a pipe-controlled daemon, so that one needs
> not fork and exec for interning another object.  That way, porcelain
> commands could keep one bidirectional pipe (feed object type and
> source and whether to use -w into git-hash-project, receive object id)
> to git-hash-object around until they finish.

Aside from getting the hashes back from fast-import, that's what
fast-import is for.  I could also make it disable writing.  Hmm.
Junio and I were just talking about making fast-import send the
marks table back out on stdout.  This would make it easier for a
frontend process to stream a whole bunch of objects into the process,
then get back all of their SHA-1s.  Less context switches and more
parallel operation.

-- 
Shawn.

^ permalink raw reply

* Re: Significant performance waste in git-svn and friends
From: Shawn O. Pearce @ 2007-09-06  2:16 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: Mike Hommey, git
In-Reply-To: <7vd4wwj16d.fsf@gitster.siamese.dyndns.org>

Junio C Hamano <gitster@pobox.com> wrote:
> Mike Hommey <mh@glandium.org> writes:
> 
> > The same things obviously apply to git-cvsimport and other scripts
> > calling git-hash-object a lot.
> 
> I *obviously* hate this patch, as it makes this Porcelain
> command to be aware of the internal representation too much.

Me too.  I think its bad enough that fast-import knows the
internal representation of a packfile.  And its plumbing!
 
> I wonder if letting fast-import handle the object creation is an
> option, though.

I agree.  This is *exactly* what fast-import was built for.  It
really would be the better tool to use here.  Much better than
reinventing the wheel.  Its already been invented twice for you
in Git (normal object creation path and fast-import path).  No
need to do it a third time.

-- 
Shawn.

^ permalink raw reply

* [PATCH] Cleanup unnecessary file modifications in t1400-update-ref
From: Shawn O. Pearce @ 2007-09-06  2:15 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git, krh

Kristian Høgsberg pointed out that the two file modifications
we were doing during the 'creating initial files' step are not even
used within the test suite.  This was actually confusing as we do
not even need these changes for the tests to pass.  All that really
matters here is the specific commit dates are used so that these
appear in the branch's reflog, and that the dates are different so
that the branch will update when asked and the reflog entry is
also updated.  There is no need for the file modification.

Signed-off-by: Shawn O. Pearce <spearce@spearce.org>
---
 t/t1400-update-ref.sh |    2 --
 1 files changed, 0 insertions(+), 2 deletions(-)

diff --git a/t/t1400-update-ref.sh b/t/t1400-update-ref.sh
index c4c0dfa..ce045b2 100755
--- a/t/t1400-update-ref.sh
+++ b/t/t1400-update-ref.sh
@@ -198,11 +198,9 @@ test_expect_success \
 	 GIT_AUTHOR_DATE="2005-05-26 23:41" \
 	 GIT_COMMITTER_DATE="2005-05-26 23:41" git-commit -F M -a &&
 	 h_OTHER=$(git rev-parse --verify HEAD) &&
-	 echo FIXED >F &&
 	 GIT_AUTHOR_DATE="2005-05-26 23:44" \
 	 GIT_COMMITTER_DATE="2005-05-26 23:44" git-commit --amend &&
 	 h_FIXED=$(git rev-parse --verify HEAD) &&
-	 echo TEST+FIXED >F &&
 	 echo Merged initial commit and a later commit. >M &&
 	 echo $h_TEST >.git/MERGE_HEAD &&
 	 GIT_AUTHOR_DATE="2005-05-26 23:45" \
-- 
1.5.3.1.840.g0fedbc

^ permalink raw reply related

* Re: builtin commit series sent
From: Shawn O. Pearce @ 2007-09-06  2:09 UTC (permalink / raw)
  To: Kristian Høgsberg; +Cc: Git Mailing List
In-Reply-To: <1189039527.20311.24.camel@hinata.boston.redhat.com>

Kristian Hgsberg <krh@redhat.com> wrote:
> There's one test that doesn't look right to me:
> t1400-update-ref.sh.  The 'creating initial files' case does a git
> commit --amend and apparently expect to commit the changed contents of
> the file F, even though it hasn't been added to the index.  Similarly,
> there's a git commit -F M just below it that does the same thing.  I'm
> not sure how they pass with the shell script commit...

Yea.  It doesn't actually matter in this test.  The following
patch can be applied and the test will still pass:

diff --git a/t/t1400-update-ref.sh b/t/t1400-update-ref.sh
index c4c0dfa..ce045b2 100755
--- a/t/t1400-update-ref.sh
+++ b/t/t1400-update-ref.sh
@@ -198,11 +198,9 @@ test_expect_success \
 	 GIT_AUTHOR_DATE="2005-05-26 23:41" \
 	 GIT_COMMITTER_DATE="2005-05-26 23:41" git-commit -F M -a &&
 	 h_OTHER=$(git rev-parse --verify HEAD) &&
-	 echo FIXED >F &&
 	 GIT_AUTHOR_DATE="2005-05-26 23:44" \
 	 GIT_COMMITTER_DATE="2005-05-26 23:44" git-commit --amend &&
 	 h_FIXED=$(git rev-parse --verify HEAD) &&
-	 echo TEST+FIXED >F &&
 	 echo Merged initial commit and a later commit. >M &&
 	 echo $h_TEST >.git/MERGE_HEAD &&
 	 GIT_AUTHOR_DATE="2005-05-26 23:45" \

The reason is the test is looking for a very specific date and
time in the branch's reflog.  The reflog entry is not impacted by
editing F.  Since the commit date is different here the --amend
created a different commit object, which was all that mattered.

Actually I think we probably should just apply the above patch
to clean up this test case.  I'll submit it under a different
cover so Junio can more easily apply it.

-- 
Shawn.

^ permalink raw reply related

* Re: [PATCH] allow bulk import to be packed
From: Junio C Hamano @ 2007-09-06  1:36 UTC (permalink / raw)
  To: git; +Cc: Shawn O Pearce
In-Reply-To: <7vps0wfv8g.fsf@gitster.siamese.dyndns.org>

Junio C Hamano <gitster@pobox.com> writes:

> This will use fast-import as a backend to dump blobs resulting
> from a huge initial import into a packfile.

This needs major work to be usable.

Although fast-import knows object name of each blob, being a
quick-and-dirty patch, git-add does not ask for it and still
computes the blob names by itself.  The only thing this
proof-of-concept patch buys is that the resulting object
database is packed, and does not get loose objects.

^ permalink raw reply

* Re: Git's database structure
From: Kyle Moffett @ 2007-09-06  1:27 UTC (permalink / raw)
  To: Julian Phillips
  Cc: Jon Smirl, Andreas Ericsson, Theodore Tso, Junio C Hamano,
	Git Mailing List
In-Reply-To: <Pine.LNX.4.64.0709051823470.26016@reaper.quantumfyre.co.uk>

On Sep 05, 2007, at 13:31:43, Julian Phillips wrote:
> And this is advantaged by having the path in the blob how?  The  
> important information here is knowing which commits touched the  
> file - this information is expensive in git because it is snapshot  
> based.  You have to go back through all the commits looking for  
> changes to the given path. The information you might want to cache  
> is which commits touched the file, which you could do without  
> changing the current data storage. Presumably you are suggesting  
> that such a cache would be cleaner with the filename in the blob?   
> Or do you think that it would somehow be faster to create?  If so,  
> how?

The only possible reason I can think of for moving data into the blob  
would be to make a POSIX-compliant git-like filesystem, and EVEN THEN  
you would NOT move the path out of the tree objects.  In order to  
have somewhat consistent inodes (and also for performance when  
changing 4 bytes in a 40GB file) you would want to have 3 different  
types of "inode" objects:

1)  4-64k of (metadata + filedata)
2)  4-64k of (metadata + list of 4-64k filedata blobs)
3)  4-64k of (metadata + list of 4-64k lists of filedata blobs)

On the other hand... that isn't GIT, it's something completely  
different with a very different usage pattern and set of  
requirements.  And you still don't put the path name in the objects,  
just the permissions and other attributes/metadata.

<Random Thought Experiment>
You would of course want to better define those 4-64k limits for  
allocation and performance reasons, but a double-indirect table of  
SHA128s with 64kb chunks lets you address up to 1TB of file data, and  
for each additional power-of-two increase in the chunk size you get 8  
times the storage space.  Furthermore, the actual double-indirect  
tables for an 8TB file using 128k chunks would be all of 64MB, for a  
more reasonable 4GB file with 32k tables (max of 128GB) it would be  
maybe 128kB of indirect SHA1 hash tables.
</Random Thought Experiment>

Cheers,
Kyle Moffett

^ permalink raw reply

* [PATCH] allow bulk import to be packed
From: Junio C Hamano @ 2007-09-06  1:17 UTC (permalink / raw)
  To: Shawn O Pearce; +Cc: git

This will use fast-import as a backend to dump blobs resulting
from a huge initial import into a packfile.

Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
 builtin-add.c |   79 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 79 insertions(+), 0 deletions(-)

diff --git a/builtin-add.c b/builtin-add.c
index 105a9f0..f512108 100644
--- a/builtin-add.c
+++ b/builtin-add.c
@@ -12,6 +12,7 @@
 #include "diffcore.h"
 #include "commit.h"
 #include "revision.h"
+#include "run-command.h"
 
 static const char builtin_add_usage[] =
 "git-add [-n] [-v] [-f] [--interactive | -i] [-u] [--refresh] [--] <filepattern>...";
@@ -153,6 +154,82 @@ static int git_add_config(const char *var, const char *value)
 	return git_default_config(var, value);
 }
 
+static void pack_one_blob(FILE *g, size_t size, const char *path)
+{
+	void *buf = NULL;
+	unsigned long nsize;
+	char *nbuf;
+	int re_allocated = 0;
+
+	if (size) {
+		int fd = open(path, O_RDONLY);
+		if (fd < 0)
+			return;
+		buf = xmmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0);
+		close(fd);
+	}
+	nbuf = convert_to_git(path, buf, &nsize);
+	if (nbuf) {
+		munmap(buf, size);
+		size = nsize;
+		buf = nbuf;
+		re_allocated = 1;
+	}
+
+	fprintf(g, "blob\ndata %lu\n", size);
+	fwrite(buf, 1, size, g);
+	fputc('\n', g);
+}
+
+#ifndef BULK_ADD_LIMIT
+#define BULK_ADD_LIMIT 200
+#endif
+
+static void pack_bluk_blob(struct dir_struct *dir, int verbose)
+{
+	struct child_process gfi;
+	const char *gfi_argv[] = { "fast-import", NULL };
+	int i, cnt;
+	FILE *g;
+	size_t *addmap;
+
+	if (dir->nr < BULK_ADD_LIMIT)
+		return;
+
+	addmap = xcalloc(dir->nr, sizeof(size_t));
+	for (i = cnt = 0; i < dir->nr; i++) {
+		struct stat st;
+		const char *name = dir->entries[i]->name;
+		if (!lstat(name, &st) && S_ISREG(st.st_mode)) {
+			cnt++;
+			addmap[i] = xsize_t(st.st_size);
+		}
+	}
+	if (cnt < BULK_ADD_LIMIT)
+		return;
+
+	memset(&gfi, 0, sizeof(gfi));
+	gfi.argv = gfi_argv;
+	gfi.no_stdout = 1;
+	gfi.in = -1;
+	gfi.git_cmd = 1;
+
+	if (start_command(&gfi)) {
+		warning("could not run fast-import");
+		return;
+	}
+	g = fdopen(gfi.in, "w");
+
+	for (i = 0; i < dir->nr; i++) {
+		if (!addmap[i])
+			continue;
+		pack_one_blob(g, addmap[i], dir->entries[i]->name);
+	}
+	fflush(g);
+	finish_command(&gfi);
+	reprepare_packed_git();
+}
+
 static struct lock_file lock_file;
 
 static const char ignore_error[] =
@@ -258,6 +335,8 @@ int cmd_add(int argc, const char **argv, const char *prefix)
 		die("no files added");
 	}
 
+	pack_bluk_blob(&dir, verbose);
+
 	for (i = 0; i < dir.nr; i++)
 		add_file_to_cache(dir.entries[i]->name, verbose);
 

^ permalink raw reply related

* Re: Significant performance waste in git-svn and friends
From: Patrick Doyle @ 2007-09-06  1:07 UTC (permalink / raw)
  To: git
In-Reply-To: <85bqcghktl.fsf@lola.goethe.zz>

I'll add my hearty vote of support, and perhaps even a patch or two,
to anything that would make git-svn faster.  Just last week, I gave up
waiting for it to complete the import of a (rather large) uClinux
distribution, falling back on SVN.  I'm looking forward to trying it
again with Mike's patch, or perhaps even implementing and trying out
David's idea.

Hmmm.... I'm supposed to be on a couple of planes for a rather longish
period of time next week.  Perhaps I'll give it a shot.  (But don't
count on much -- the laptop battery is pretty shot nowadays and, given
the nature of the rest of the participitants on this list, it'll
probably be implemented and tested by the time I pack up for the
trip.)

--wpd

^ permalink raw reply

* builtin commit series sent
From: Kristian Høgsberg @ 2007-09-06  0:45 UTC (permalink / raw)
  To: Git Mailing List

Hi,

I finally sat down and finished the last details in my git-commit.sh
port and made it pass the test suite.  The test suite is cruel, but
awesome.  There's one test that doesn't look right to me:
t1400-update-ref.sh.  The 'creating initial files' case does a git
commit --amend and apparently expect to commit the changed contents of
the file F, even though it hasn't been added to the index.  Similarly,
there's a git commit -F M just below it that does the same thing.  I'm
not sure how they pass with the shell script commit...

Also, there is some overlap in the patch set with Pierre's nice strbuf
work, but it's mostly just naming, and should be easy to work out.  I
have a couple of places where I manually add a NUL terminator, but I
like Pierres approach of always implicitly doing that.

I didn't work on this for a couple of weeks, and keeping up-to-date with
the various git-commit.sh changes that happened in the meantime
(templates, different option semantics etc) wasn't fun.  Also I don't
really have the time to maintain this out of tree, these days, so I'd
really appreciate, now that the work is finished, if we can get it in.

cheers,
Kristian

^ permalink raw reply

* Re: [PATCH 3/3] Makefile: Add cache-tree.h to the public headers list
From: Dmitry V. Levin @ 2007-09-06  0:37 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: Git Mailing List
In-Reply-To: <7vy7fkfy1c.fsf@gitster.siamese.dyndns.org>

[-- Attachment #1: Type: text/plain, Size: 364 bytes --]

On Wed, Sep 05, 2007 at 05:16:47PM -0700, Junio C Hamano wrote:
[...]
> I am however not convinced it is a good idea to treat libgit.a
> as if it is a library.
> It is not a library in the usual sense of the word.

I'd certainly prefer libgit.so with stable interface instead, but
parsecvs needs -lgit now, it cannot wait for "good library".


-- 
ldv

[-- Attachment #2: Type: application/pgp-signature, Size: 189 bytes --]

^ permalink raw reply

* [PATCH 9/9] Implement git commit as a builtin command.
From: Kristian Høgsberg @ 2007-09-06  0:23 UTC (permalink / raw)
  To: git; +Cc: Kristian Høgsberg
In-Reply-To: <11890382264046-git-send-email-krh@redhat.com>

Move git-commit.sh to contrib/examples.

Signed-off-by: Kristian Høgsberg <krh@redhat.com>
---
 Makefile                       |    9 +-
 builtin-commit.c               |  742 ++++++++++++++++++++++++++++++++++++++++
 builtin.h                      |    3 +-
 contrib/examples/git-commit.sh |  665 +++++++++++++++++++++++++++++++++++
 git-commit.sh                  |  665 -----------------------------------
 git.c                          |    3 +-
 6 files changed, 1414 insertions(+), 673 deletions(-)
 create mode 100644 builtin-commit.c
 create mode 100755 contrib/examples/git-commit.sh
 delete mode 100755 git-commit.sh

diff --git a/Makefile b/Makefile
index 4eb4637..beb3cbb 100644
--- a/Makefile
+++ b/Makefile
@@ -201,7 +201,7 @@ BASIC_LDFLAGS =
 
 SCRIPT_SH = \
 	git-bisect.sh git-checkout.sh \
-	git-clean.sh git-clone.sh git-commit.sh \
+	git-clean.sh git-clone.sh \
 	git-fetch.sh \
 	git-ls-remote.sh \
 	git-merge-one-file.sh git-mergetool.sh git-parse-remote.sh \
@@ -250,7 +250,7 @@ EXTRA_PROGRAMS =
 BUILT_INS = \
 	git-format-patch$X git-show$X git-whatchanged$X git-cherry$X \
 	git-get-tar-commit-id$X git-init$X git-repo-config$X \
-	git-fsck-objects$X git-cherry-pick$X \
+	git-fsck-objects$X git-cherry-pick$X git-status$X\
 	$(patsubst builtin-%.o,git-%$X,$(BUILTIN_OBJS))
 
 # what 'all' will build and 'install' will install, in gitexecdir
@@ -322,6 +322,7 @@ BUILTIN_OBJS = \
 	builtin-check-attr.o \
 	builtin-checkout-index.o \
 	builtin-check-ref-format.o \
+	builtin-commit.o \
 	builtin-commit-tree.o \
 	builtin-count-objects.o \
 	builtin-describe.o \
@@ -357,7 +358,6 @@ BUILTIN_OBJS = \
 	builtin-rev-parse.o \
 	builtin-revert.o \
 	builtin-rm.o \
-	builtin-runstatus.o \
 	builtin-shortlog.o \
 	builtin-show-branch.o \
 	builtin-stripspace.o \
@@ -797,9 +797,6 @@ $(patsubst %.perl,%,$(SCRIPT_PERL)): % : %.perl
 	chmod +x $@+ && \
 	mv $@+ $@
 
-git-status: git-commit
-	$(QUIET_GEN)cp $< $@+ && mv $@+ $@
-
 gitweb/gitweb.cgi: gitweb/gitweb.perl
 	$(QUIET_GEN)$(RM) $@ $@+ && \
 	sed -e '1s|#!.*perl|#!$(PERL_PATH_SQ)|' \
diff --git a/builtin-commit.c b/builtin-commit.c
new file mode 100644
index 0000000..aca5ff8
--- /dev/null
+++ b/builtin-commit.c
@@ -0,0 +1,742 @@
+/*
+ * Builtin "git commit"
+ *
+ * Copyright (c) 2007 Kristian Høgsberg <krh@redhat.com>
+ * Based on git-commit.sh by Junio C Hamano and Linus Torvalds
+ */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include "cache.h"
+#include "cache-tree.h"
+#include "builtin.h"
+#include "diff.h"
+#include "diffcore.h"
+#include "commit.h"
+#include "revision.h"
+#include "wt-status.h"
+#include "run-command.h"
+#include "refs.h"
+#include "log-tree.h"
+#include "strbuf.h"
+#include "utf8.h"
+
+void launch_editor(const char *path, struct strbuf *sb);
+int rerere(void);
+
+static const char builtin_commit_usage[] =
+	"[-a | --interactive] [-s] [-v] [--no-verify] [-m <message> | -F <logfile> | (-C|-c) <commit> | --amend] [-u] [-e] [--author <author>] [--template <file>] [[-i | -o] <path>...]";
+
+static unsigned char head_sha1[20], merge_head_sha1[20];
+static char *use_message_buffer;
+static const char commit_editmsg[] = "COMMIT_EDITMSG";
+static struct lock_file lock_file;
+
+enum option_type {
+    OPTION_NONE,
+    OPTION_STRING,
+    OPTION_INTEGER,
+    OPTION_LAST,
+};
+
+struct option {
+    enum option_type type;
+    const char *long_name;
+    char short_name;
+    void *value;
+};
+
+static int scan_options(const char ***argv, struct option *options)
+{
+	const char *value, *eq;
+	int i;
+
+	if (**argv == NULL)
+		return 0;
+	if ((**argv)[0] != '-')
+		return 0;
+	if (!strcmp(**argv, "--"))
+		return 0;
+
+	value = NULL;
+	for (i = 0; options[i].type != OPTION_LAST; i++) {
+		if ((**argv)[1] == '-') {
+			if (!prefixcmp(options[i].long_name, **argv + 2)) {
+				if (options[i].type != OPTION_NONE)
+					value = *++(*argv);
+				goto match;
+			}
+
+			eq = strchr(**argv + 2, '=');
+			if (eq && options[i].type != OPTION_NONE &&
+			    !strncmp(**argv + 2, 
+				     options[i].long_name, eq - **argv - 2)) {
+				value = eq + 1;
+				goto match;
+			}
+		}
+
+		if ((**argv)[1] == options[i].short_name) {
+			if ((**argv)[2] == '\0') {
+				if (options[i].type != OPTION_NONE)
+					value = *++(*argv);
+				goto match;
+			}
+
+			if (options[i].type != OPTION_NONE) {
+				value = **argv + 2;
+				goto match;
+			}
+		}
+	}
+
+	usage(builtin_commit_usage);
+
+ match:
+	switch (options[i].type) {
+	case OPTION_NONE:
+		*(int *)options[i].value = 1;
+		break;
+	case OPTION_STRING:
+		if (value == NULL)
+			die("option %s requires a value.", (*argv)[-1]);
+		*(const char **)options[i].value = value;
+		break;
+	case OPTION_INTEGER:
+		if (value == NULL)
+			die("option %s requires a value.", (*argv)[-1]);
+		*(int *)options[i].value = atoi(value);
+		break;
+	default:
+		assert(0);
+	}
+
+	(*argv)++;
+
+	return 1;
+}
+
+static char *logfile, *force_author, *message, *template_file;
+static char *edit_message, *use_message;
+static int all, edit_flag, also, interactive, only, no_verify, amend, signoff;
+static int quiet, verbose, untracked_files;
+
+static int no_edit, initial_commit, in_merge;
+const char *only_include_assumed;
+
+static struct option commit_options[] = {
+	{ OPTION_STRING, "file", 'F', (void *) &logfile },
+	{ OPTION_NONE, "all", 'a', &all },
+	{ OPTION_STRING, "author", 0, (void *) &force_author },
+	{ OPTION_NONE, "edit", 'e', &edit_flag },
+	{ OPTION_NONE, "include", 'i', &also },
+	{ OPTION_NONE, "interactive", 0, &interactive },
+	{ OPTION_NONE, "only", 'o', &only },
+	{ OPTION_STRING, "message", 'm', &message },
+	{ OPTION_NONE, "no-verify", 'n', &no_verify },
+	{ OPTION_NONE, "amend", 0, &amend },
+	{ OPTION_STRING, "reedit-message", 'c', &edit_message },
+	{ OPTION_STRING, "reuse-message", 'C', &use_message },
+	{ OPTION_NONE, "signoff", 's', &signoff },
+	{ OPTION_NONE, "quiet", 'q', &quiet },
+	{ OPTION_NONE, "verbose", 'v', &verbose },
+	{ OPTION_NONE, "untracked-files", 0, &untracked_files },
+	{ OPTION_STRING, "template", 't', &template_file },
+	{ OPTION_LAST },
+};
+
+/* FIXME: Taken from builtin-add, should be shared. */
+
+static void update_callback(struct diff_queue_struct *q,
+			    struct diff_options *opt, void *cbdata)
+{
+	int i, verbose;
+
+	verbose = *((int *)cbdata);
+	for (i = 0; i < q->nr; i++) {
+		struct diff_filepair *p = q->queue[i];
+		const char *path = p->one->path;
+		switch (p->status) {
+		default:
+			die("unexpacted diff status %c", p->status);
+		case DIFF_STATUS_UNMERGED:
+		case DIFF_STATUS_MODIFIED:
+		case DIFF_STATUS_TYPE_CHANGED:
+			add_file_to_cache(path, verbose);
+			break;
+		case DIFF_STATUS_DELETED:
+			remove_file_from_cache(path);
+			cache_tree_invalidate_path(active_cache_tree, path);
+			if (verbose)
+				printf("remove '%s'\n", path);
+			break;
+		}
+	}
+}
+
+static void
+add_files_to_cache(int fd, const char **files, const char *prefix)
+{
+	struct rev_info rev;
+
+	init_revisions(&rev, "");
+	setup_revisions(0, NULL, &rev, NULL);
+	rev.prune_data = get_pathspec(prefix, files);
+	rev.diffopt.output_format |= DIFF_FORMAT_CALLBACK;
+	rev.diffopt.format_callback = update_callback;
+	rev.diffopt.format_callback_data = &verbose;
+
+	run_diff_files(&rev, 0);
+	refresh_cache(REFRESH_QUIET);
+
+	if (write_cache(fd, active_cache, active_nr) || close(fd))
+		die("unable to write new index file");
+}
+
+static char *
+prepare_index(const char **files, const char *prefix)
+{
+	int fd;
+	struct tree *tree;
+	struct lock_file *next_index_lock;
+
+	fd = hold_locked_index(&lock_file, 1);
+	if (read_cache() < 0)
+		die("index file corrupt");
+
+	if (all) {
+		add_files_to_cache(fd, files, NULL);
+		return lock_file.filename;
+	} else if (also) {
+		add_files_to_cache(fd, files, prefix);
+		return lock_file.filename;
+	}
+
+	if (interactive)
+		interactive_add();
+
+	if (*files == NULL) {
+		/* Commit index as-is. */
+		rollback_lock_file(&lock_file);
+		return get_index_file();
+	}
+
+	/*
+	 * FIXME: Warn on unknown files.  Shell script does
+	 *
+	 *   commit_only=`git-ls-files --error-unmatch -- "$@"`
+	 */
+
+	/*
+	 * FIXME: shell script does
+	 *
+	 *   git-read-tree --index-output="$TMP_INDEX" -i -m HEAD
+	 *
+	 * which warns about unmerged files in the index.
+	 */
+
+	/* update the user index file */
+	add_files_to_cache(fd, files, prefix);
+
+	if (!initial_commit) {
+		tree = parse_tree_indirect(head_sha1);
+		if (!tree)
+			die("failed to unpack HEAD tree object");
+		if (read_tree(tree, 0, NULL))
+			die("failed to read HEAD tree object");
+	}
+
+	/* Uh oh, abusing lock_file to create a garbage collected file */
+	next_index_lock = xmalloc(sizeof(*next_index_lock));
+	fd = hold_lock_file_for_update(next_index_lock,
+				       git_path("next-index-%d", getpid()), 1);
+	add_files_to_cache(fd, files, prefix);
+
+	return next_index_lock->filename;
+}
+
+static int run_status(FILE *fp, const char *index_file)
+{
+	struct wt_status s;
+
+	wt_status_prepare(&s);
+
+	if (amend) {
+		s.amend = 1;
+		s.reference = "HEAD^1";
+	}
+	s.verbose = verbose;
+	s.untracked = untracked_files;
+	s.index_file = index_file;
+	s.fp = fp;
+
+	wt_status_print(&s);
+
+	return s.commitable;
+}
+
+static const char sign_off_header[] = "Signed-off-by: ";
+
+static int prepare_log_message(const char *index_file)
+{
+	struct stat statbuf;
+	int commitable;
+	struct strbuf sb;
+	char *buffer;
+	FILE *fp;
+
+	strbuf_init(&sb);
+	if (message) {
+		strbuf_add(&sb, message, strlen(message));
+	} else if (logfile && !strcmp(logfile, "-")) {
+		if (isatty(0))
+			fprintf(stderr, "(reading log message from standard input)\n");
+		if (strbuf_read_fd(&sb, 0) < 0)
+			die("could not read log from standard input");
+	} else if (logfile) {
+		if (strbuf_read_path(&sb, logfile) < 0)
+			die("could not read log file '%s': %s",
+			    logfile, strerror(errno));
+	} else if (use_message) {
+		buffer = strstr(use_message_buffer, "\n\n");
+		if (!buffer || buffer[2] == '\0')
+			die("commit has empty message");
+		strbuf_add(&sb, buffer + 2, strlen(buffer + 2));
+	} else if (!stat(git_path("MERGE_MSG"), &statbuf)) {
+		if (strbuf_read_path(&sb, git_path("MERGE_MSG")) < 0)
+			die("could not read MERGE_MSG: %s", strerror(errno));
+	} else if (!stat(git_path("SQUASH_MSG"), &statbuf)) {
+		if (strbuf_read_path(&sb, git_path("SQUASH_MSG")) < 0)
+			die("could not read SQUASH_MSG: %s", strerror(errno));
+	} else if (!stat(template_file, &statbuf)) {
+		if (strbuf_read_path(&sb, template_file) < 0)
+			die("could not read %s: %s",
+			    template_file, strerror(errno));
+	}
+
+	fp = fopen(git_path(commit_editmsg), "w");
+	if (fp == NULL)
+		die("could not open %s\n", git_path(commit_editmsg));
+		
+	stripspace(&sb, 0);
+	if (fwrite(sb.buf, 1, sb.len, fp) < sb.len)
+		die("could not write commit template: %s\n",
+		    strerror(errno));
+
+	if (signoff) {
+		const char *info, *bol;
+
+		info = git_committer_info(1);
+		strbuf_add_char(&sb, '\0');
+		bol = strrchr(sb.buf + sb.len - 1, '\n');
+		if (!bol || prefixcmp(bol, sign_off_header))
+			fprintf(fp, "\n");
+		fprintf(fp, "Signed-off-by: %s\n", git_committer_info(1));
+	}
+
+	strbuf_release(&sb);
+
+	if (in_merge && !no_edit) {
+		fprintf(fp,
+			"#\n"
+			"# It looks like you may be committing a MERGE.\n"
+			"# If this is not correct, please remove the file\n"
+			"#	%s\n"
+			"# and try again.\n"
+			"#\n",
+			git_path("MERGE_HEAD"));
+	}
+
+	fprintf(fp,
+		"\n"
+		"# Please enter the commit message for your changes.\n"
+		"# (Comment lines starting with '#' will not be included)\n");
+	if (only_include_assumed)
+		fprintf(fp, "# %s\n", only_include_assumed);
+
+	commitable = run_status(fp, index_file);
+
+	fclose(fp);
+
+	return commitable;
+}
+
+/* Find out if the message starting at position 'start' in the strbuf
+ * contains only whitespace and Signed-off-by lines. */
+static int message_is_empty(struct strbuf *sb, int start)
+{
+	static const char signed_off_by[] = "Signed-off-by: ";
+	struct strbuf tmpl;
+	const char *nl;
+	int eol, i;
+
+	/* See if the template is just a prefix of the message. */
+	strbuf_init(&tmpl);
+	if (template_file && strbuf_read_path(&tmpl, template_file) > 0) {
+		stripspace(&tmpl, 1);
+		if (start + tmpl.len <= sb->len &&
+		    memcmp(tmpl.buf, sb->buf + start, tmpl.len) == 0)
+			start += tmpl.len;
+	}
+	strbuf_release(&tmpl);
+
+	/* Check if the rest is just whitespace and Signed-of-by's. */
+	for (i = start; i < sb->len; i++) {
+		nl = memchr(sb->buf + i, '\n', sb->len - i);
+		if (nl)
+			eol = nl - sb->buf;
+		else
+			eol = sb->len;
+
+		if (strlen(signed_off_by) <= eol - i &&
+		    !prefixcmp(sb->buf + i, signed_off_by)) {
+			i = eol;
+			continue;
+		}
+		while (i < eol)
+			if (!isspace(sb->buf[i++]))
+				return 0;
+	}
+
+	return 1;
+}
+
+static void determine_author_info(struct strbuf *sb)
+{
+	char *p, *eol;
+	char *name = NULL, *email = NULL;
+
+	if (use_message) {
+		p = strstr(use_message_buffer, "\nauthor");
+		if (!p)
+			die("invalid commit: %s\n", use_message);
+		p++;
+		eol = strchr(p, '\n');
+		if (!eol)
+			die("invalid commit: %s\n", use_message);
+
+		strbuf_add(sb, p, eol + 1 - p);
+	} else if (force_author) {
+		const char *eoname = strstr(force_author, " <");
+		const char *eomail = strchr(force_author, '>');
+
+		if (!eoname || !eomail)
+			die("malformed --author parameter\n");
+		name = xstrndup(force_author, eoname - force_author);
+		email = xstrndup(eoname + 2, eomail - eoname - 2);
+		strbuf_printf(sb, "author %s\n",
+			      fmt_ident(name, email, 
+					getenv("GIT_AUTHOR_DATE"), 1));
+		free(name);
+		free(email);
+	} else {
+		strbuf_printf(sb, "author %s\n", git_author_info(1));
+	}
+}
+
+static void parse_and_validate_options(const char ***argv)
+{
+	int f = 0;
+
+	(*argv)++;
+	while (scan_options(argv, commit_options))
+		;
+
+	if (logfile || message || use_message)
+		no_edit = 1;
+	if (edit_flag)
+		no_edit = 0;
+
+	if (get_sha1("HEAD", head_sha1))
+		initial_commit = 1;
+
+	if (!get_sha1("MERGE_HEAD", merge_head_sha1))
+		in_merge = 1;
+
+	/* Sanity check options */
+	if (amend && initial_commit)
+		die("You have nothing to amend.");
+	if (amend && in_merge)
+		die("You are in the middle of a merger -- cannot amend.");
+
+	if (use_message)
+		f++;
+	if (edit_message)
+		f++;
+	if (logfile)
+		f++;
+	if (f > 1)
+		die("Only one of -c/-C/-F can be used.");
+	if (message && f > 0)
+		die("Option -m cannot be combined with -c/-C/-F.");
+	if (edit_message)
+		use_message = edit_message;
+	if (amend)
+		use_message = "HEAD";
+	if (use_message) {
+		unsigned char sha1[20];
+		static char utf8[] = "UTF-8";
+		char *enc, *end, *out_enc;
+		struct commit *commit;
+
+		if (get_sha1(use_message, sha1))
+			die("could not lookup commit %s", use_message);
+		commit = lookup_commit(sha1);
+		if (!commit || parse_commit(commit))
+			die("could not parse commit %s", use_message);
+
+		enc = strstr(commit->buffer, "\nencoding");
+		if (enc) {
+			end = strchr(enc + 10, '\n');
+			enc = xstrndup(enc + 10, end - (enc + 10));
+		} else {
+			enc = utf8;
+		}
+		out_enc = git_commit_encoding ? git_commit_encoding : utf8;
+
+		use_message_buffer =
+			reencode_string(commit->buffer, out_enc, enc);
+		if (enc != utf8)
+			free(enc);
+	}
+
+	if (also && only)
+		die("Only one of --include/--only can be used.");
+	if (!*argv && (also || (only && !amend)))
+		die("No paths with --include/--only does not make sense.");
+	if (!*argv && only && amend)
+		only_include_assumed = "Clever... amending the last one with dirty index.";
+	if (*argv && !also && !only) {
+		only_include_assumed = "Explicit paths specified without -i nor -o; assuming --only paths...";
+		also = 0;
+	}
+
+	if (all && interactive)
+		die("Cannot use -a, --interactive or -i at the same time.");
+	else if (all && **argv)
+		die("Paths with -a does not make sense.");
+	else if (interactive && **argv)
+		die("Paths with --interactive does not make sense.");
+}
+
+int cmd_status(int argc, const char **argv, const char *prefix)
+{
+	const char *index_file;
+	int commitable;
+
+	git_config(git_status_config);
+
+	parse_and_validate_options(&argv);
+
+	index_file = prepare_index(argv, prefix);
+
+	commitable = run_status(stdout, index_file);
+
+	rollback_lock_file(&lock_file);
+
+	return commitable ? 0 : 1;
+}
+
+static int run_hook(const char *index_file, const char *name, const char *arg)
+{
+	struct child_process hook;
+	const char *argv[3], *env[2];
+	char index[PATH_MAX];
+
+	argv[0] = git_path("hooks/%s", name);
+	argv[1] = arg;
+	argv[2] = NULL;
+	snprintf(index, sizeof(index), "GIT_INDEX_FILE=%s", index_file);
+	env[0] = index;
+	env[1] = NULL;
+
+	if (access(argv[0], X_OK) < 0)
+		return 0;
+
+	memset(&hook, 0, sizeof(hook));
+	hook.argv = argv;
+	hook.no_stdin = 1;
+	hook.stdout_to_stderr = 1;
+	hook.env = env;
+
+	return run_command(&hook);
+}
+
+static void print_summary(const char *prefix, const unsigned char *sha1)
+{
+	struct rev_info rev;
+	struct commit *commit;
+
+	commit = lookup_commit(sha1);
+	if (!commit)
+		die("couldn't look up newly created commit\n");
+	if (!commit || parse_commit(commit))
+		die("could not parse newly created commit");
+
+	init_revisions(&rev, prefix);
+	setup_revisions(0, NULL, &rev, NULL);
+
+	rev.abbrev = 0;
+	rev.diff = 1;
+	rev.diffopt.output_format =
+		DIFF_FORMAT_SHORTSTAT | DIFF_FORMAT_SUMMARY;
+
+	rev.verbose_header = 1;
+	rev.show_root_diff = 1;
+	rev.commit_format = get_commit_format("format:%h: %s");
+	rev.always_show_header = 1;
+		
+	printf("Created %scommit ", initial_commit ? "initial " : "");
+
+	log_tree_commit(&rev, commit);
+}
+
+int git_commit_config(const char *k, const char *v)
+{
+	if (!strcmp(k, "commit.template")) {
+		template_file = xstrdup(v);
+		return 0;
+	}
+
+	return git_status_config(k, v);
+}
+
+static const char commit_utf8_warn[] =
+"Warning: commit message does not conform to UTF-8.\n"
+"You may want to amend it after fixing the message, or set the config\n"
+"variable i18n.commitencoding to the encoding your project uses.\n";
+
+int cmd_commit(int argc, const char **argv, const char *prefix)
+{
+	int header_len, parent_count = 0;
+	struct strbuf sb;
+	const char *index_file, *reflog_msg;
+	char *nl, *header_line;
+	unsigned char commit_sha1[20];
+	struct ref_lock *ref_lock;
+
+	git_config(git_commit_config);
+
+	parse_and_validate_options(&argv);
+
+	index_file = prepare_index(argv, prefix);
+
+	if (run_hook(index_file, "pre-commit", NULL))
+		exit(1);
+
+	if (!prepare_log_message(index_file)) {
+		run_status(stdout, index_file);
+		unlink(commit_editmsg);
+		return 1;
+	}
+
+	strbuf_init(&sb);
+
+	/* Start building up the commit header */
+	read_cache_from(index_file);
+	active_cache_tree = cache_tree();
+	if (cache_tree_update(active_cache_tree,
+			      active_cache, active_nr, 0, 0) < 0)
+		die("Error building trees");
+	strbuf_printf(&sb, "tree %s\n",
+		      sha1_to_hex(active_cache_tree->sha1));
+
+	/* Determine parents */
+	if (initial_commit) {
+		reflog_msg = "commit (initial)"; 
+		parent_count = 0;
+	} else if (amend) {
+		struct commit_list *c;
+		struct commit *commit;
+
+		reflog_msg = "commit (amend)";
+		commit = lookup_commit(head_sha1);
+		if (!commit || parse_commit(commit))
+			die("could not parse HEAD commit");
+
+		for (c = commit->parents; c; c = c->next)
+			strbuf_printf(&sb, "parent %s\n",
+				      sha1_to_hex(c->item->object.sha1));
+	} else if (in_merge) {
+		struct strbuf m;
+		FILE *fp;
+
+		reflog_msg = "commit (merge)";
+		strbuf_printf(&sb, "parent %s\n", sha1_to_hex(head_sha1));
+		strbuf_init(&m);
+		fp = fopen(git_path("MERGE_HEAD"), "r");
+		if (fp == NULL)
+			die("could not open %s for reading: %s",
+			    git_path("MERGE_HEAD"), strerror(errno));
+		while (!m.eof) {
+			read_line(&m, fp, '\n');
+			if (!m.eof)
+				strbuf_printf(&sb, "parent %s\n", m.buf);
+		}
+		fclose(fp);
+		strbuf_release(&m);
+	} else {
+		reflog_msg = "commit";
+		strbuf_printf(&sb, "parent %s\n", sha1_to_hex(head_sha1));
+	}
+
+	determine_author_info(&sb);
+	strbuf_printf(&sb, "committer %s\n", git_committer_info(1));
+	if (!is_encoding_utf8(git_commit_encoding))
+		strbuf_printf(&sb, "encoding %s\n", git_commit_encoding);
+	strbuf_add_char(&sb, '\n');
+
+	/* Get the commit message and validate it */
+	header_len = sb.len;
+	if (!no_edit) {
+		fprintf(stderr, "launching editor, log %s\n", logfile);
+		launch_editor(git_path(commit_editmsg), &sb);
+	}
+	else if (strbuf_read_path(&sb, git_path(commit_editmsg)) < 0)
+		die("could not read commit message\n");
+	if (run_hook(index_file, "commit-msg", commit_editmsg))
+		exit(1);
+	if (stripspace(&sb, 1) < header_len ||
+	    message_is_empty(&sb, header_len))
+		die("* no commit message?  aborting commit.");
+	strbuf_add_char(&sb, '\0');
+	if (is_encoding_utf8(git_commit_encoding) && !is_utf8(sb.buf))
+		fprintf(stderr, commit_utf8_warn);
+
+	if (write_sha1_file(sb.buf, sb.len - 1, commit_type, commit_sha1))
+		die("failed to write commit object");
+		       
+	ref_lock = lock_any_ref_for_update("HEAD",
+					   initial_commit ? NULL : head_sha1,
+					   0);
+
+	nl = strchr(sb.buf + header_len, '\n');
+	header_line = xstrndup(sb.buf + header_len,
+			       nl - (sb.buf + header_len));
+	strbuf_release(&sb);
+	strbuf_printf(&sb, "%s: %s\n", reflog_msg, header_line);
+	strbuf_add_char(&sb, '\0');
+	free(header_line);
+
+	if (!ref_lock)
+		die("cannot lock HEAD ref");
+	if (write_ref_sha1(ref_lock, commit_sha1, sb.buf) < 0)
+		die("cannot update HEAD ref");
+
+	unlink(git_path("MERGE_HEAD"));
+	unlink(git_path("MERGE_MSG"));
+
+	if (lock_file.filename[0] && commit_locked_index(&lock_file))
+		die("failed to write new index");
+
+	rerere();
+
+	run_hook(index_file, "post-commit", NULL);
+
+	if (!quiet)
+		print_summary(prefix, commit_sha1);
+
+	return 0;
+}
diff --git a/builtin.h b/builtin.h
index 91bc595..c23cd6d 100644
--- a/builtin.h
+++ b/builtin.h
@@ -23,6 +23,7 @@ extern int cmd_check_attr(int argc, const char **argv, const char *prefix);
 extern int cmd_check_ref_format(int argc, const char **argv, const char *prefix);
 extern int cmd_cherry(int argc, const char **argv, const char *prefix);
 extern int cmd_cherry_pick(int argc, const char **argv, const char *prefix);
+extern int cmd_commit(int argc, const char **argv, const char *prefix);
 extern int cmd_commit_tree(int argc, const char **argv, const char *prefix);
 extern int cmd_count_objects(int argc, const char **argv, const char *prefix);
 extern int cmd_describe(int argc, const char **argv, const char *prefix);
@@ -63,10 +64,10 @@ extern int cmd_rev_list(int argc, const char **argv, const char *prefix);
 extern int cmd_rev_parse(int argc, const char **argv, const char *prefix);
 extern int cmd_revert(int argc, const char **argv, const char *prefix);
 extern int cmd_rm(int argc, const char **argv, const char *prefix);
-extern int cmd_runstatus(int argc, const char **argv, const char *prefix);
 extern int cmd_shortlog(int argc, const char **argv, const char *prefix);
 extern int cmd_show(int argc, const char **argv, const char *prefix);
 extern int cmd_show_branch(int argc, const char **argv, const char *prefix);
+extern int cmd_status(int argc, const char **argv, const char *prefix);
 extern int cmd_stripspace(int argc, const char **argv, const char *prefix);
 extern int cmd_symbolic_ref(int argc, const char **argv, const char *prefix);
 extern int cmd_tag(int argc, const char **argv, const char *prefix);
diff --git a/contrib/examples/git-commit.sh b/contrib/examples/git-commit.sh
new file mode 100755
index 0000000..d7e7028
--- /dev/null
+++ b/contrib/examples/git-commit.sh
@@ -0,0 +1,665 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Linus Torvalds
+# Copyright (c) 2006 Junio C Hamano
+
+USAGE='[-a | --interactive] [-s] [-v] [--no-verify] [-m <message> | -F <logfile> | (-C|-c) <commit> | --amend] [-u] [-e] [--author <author>] [--template <file>] [[-i | -o] <path>...]'
+SUBDIRECTORY_OK=Yes
+. git-sh-setup
+require_work_tree
+
+git rev-parse --verify HEAD >/dev/null 2>&1 || initial_commit=t
+
+case "$0" in
+*status)
+	status_only=t
+	;;
+*commit)
+	status_only=
+	;;
+esac
+
+refuse_partial () {
+	echo >&2 "$1"
+	echo >&2 "You might have meant to say 'git commit -i paths...', perhaps?"
+	exit 1
+}
+
+THIS_INDEX="$GIT_DIR/index"
+NEXT_INDEX="$GIT_DIR/next-index$$"
+rm -f "$NEXT_INDEX"
+save_index () {
+	cp -p "$THIS_INDEX" "$NEXT_INDEX"
+}
+
+run_status () {
+	# If TMP_INDEX is defined, that means we are doing
+	# "--only" partial commit, and that index file is used
+	# to build the tree for the commit.  Otherwise, if
+	# NEXT_INDEX exists, that is the index file used to
+	# make the commit.  Otherwise we are using as-is commit
+	# so the regular index file is what we use to compare.
+	if test '' != "$TMP_INDEX"
+	then
+		GIT_INDEX_FILE="$TMP_INDEX"
+		export GIT_INDEX_FILE
+	elif test -f "$NEXT_INDEX"
+	then
+		GIT_INDEX_FILE="$NEXT_INDEX"
+		export GIT_INDEX_FILE
+	fi
+
+	case "$status_only" in
+	t) color= ;;
+	*) color=--nocolor ;;
+	esac
+	git runstatus ${color} \
+		${verbose:+--verbose} \
+		${amend:+--amend} \
+		${untracked_files:+--untracked}
+}
+
+trap '
+	test -z "$TMP_INDEX" || {
+		test -f "$TMP_INDEX" && rm -f "$TMP_INDEX"
+	}
+	rm -f "$NEXT_INDEX"
+' 0
+
+################################################################
+# Command line argument parsing and sanity checking
+
+all=
+also=
+interactive=
+only=
+logfile=
+use_commit=
+amend=
+edit_flag=
+no_edit=
+log_given=
+log_message=
+verify=t
+quiet=
+verbose=
+signoff=
+force_author=
+only_include_assumed=
+untracked_files=
+templatefile="`git config commit.template`"
+while case "$#" in 0) break;; esac
+do
+	case "$1" in
+	-F|--F|-f|--f|--fi|--fil|--file)
+		case "$#" in 1) usage ;; esac
+		shift
+		no_edit=t
+		log_given=t$log_given
+		logfile="$1"
+		shift
+		;;
+	-F*|-f*)
+		no_edit=t
+		log_given=t$log_given
+		logfile=`expr "z$1" : 'z-[Ff]\(.*\)'`
+		shift
+		;;
+	--F=*|--f=*|--fi=*|--fil=*|--file=*)
+		no_edit=t
+		log_given=t$log_given
+		logfile=`expr "z$1" : 'z-[^=]*=\(.*\)'`
+		shift
+		;;
+	-a|--a|--al|--all)
+		all=t
+		shift
+		;;
+	--au=*|--aut=*|--auth=*|--autho=*|--author=*)
+		force_author=`expr "z$1" : 'z-[^=]*=\(.*\)'`
+		shift
+		;;
+	--au|--aut|--auth|--autho|--author)
+		case "$#" in 1) usage ;; esac
+		shift
+		force_author="$1"
+		shift
+		;;
+	-e|--e|--ed|--edi|--edit)
+		edit_flag=t
+		shift
+		;;
+	-i|--i|--in|--inc|--incl|--inclu|--includ|--include)
+		also=t
+		shift
+		;;
+	--int|--inte|--inter|--intera|--interac|--interact|--interacti|\
+	--interactiv|--interactive)
+		interactive=t
+		shift
+		;;
+	-o|--o|--on|--onl|--only)
+		only=t
+		shift
+		;;
+	-m|--m|--me|--mes|--mess|--messa|--messag|--message)
+		case "$#" in 1) usage ;; esac
+		shift
+		log_given=m$log_given
+		if test "$log_message" = ''
+		then
+		    log_message="$1"
+		else
+		    log_message="$log_message
+
+$1"
+		fi
+		no_edit=t
+		shift
+		;;
+	-m*)
+		log_given=m$log_given
+		if test "$log_message" = ''
+		then
+		    log_message=`expr "z$1" : 'z-m\(.*\)'`
+		else
+		    log_message="$log_message
+
+`expr "z$1" : 'z-m\(.*\)'`"
+		fi
+		no_edit=t
+		shift
+		;;
+	--m=*|--me=*|--mes=*|--mess=*|--messa=*|--messag=*|--message=*)
+		log_given=m$log_given
+		if test "$log_message" = ''
+		then
+		    log_message=`expr "z$1" : 'z-[^=]*=\(.*\)'`
+		else
+		    log_message="$log_message
+
+`expr "z$1" : 'zq-[^=]*=\(.*\)'`"
+		fi
+		no_edit=t
+		shift
+		;;
+	-n|--n|--no|--no-|--no-v|--no-ve|--no-ver|--no-veri|--no-verif|\
+	--no-verify)
+		verify=
+		shift
+		;;
+	--a|--am|--ame|--amen|--amend)
+		amend=t
+		use_commit=HEAD
+		shift
+		;;
+	-c)
+		case "$#" in 1) usage ;; esac
+		shift
+		log_given=t$log_given
+		use_commit="$1"
+		no_edit=
+		shift
+		;;
+	--ree=*|--reed=*|--reedi=*|--reedit=*|--reedit-=*|--reedit-m=*|\
+	--reedit-me=*|--reedit-mes=*|--reedit-mess=*|--reedit-messa=*|\
+	--reedit-messag=*|--reedit-message=*)
+		log_given=t$log_given
+		use_commit=`expr "z$1" : 'z-[^=]*=\(.*\)'`
+		no_edit=
+		shift
+		;;
+	--ree|--reed|--reedi|--reedit|--reedit-|--reedit-m|--reedit-me|\
+	--reedit-mes|--reedit-mess|--reedit-messa|--reedit-messag|\
+	--reedit-message)
+		case "$#" in 1) usage ;; esac
+		shift
+		log_given=t$log_given
+		use_commit="$1"
+		no_edit=
+		shift
+		;;
+	-C)
+		case "$#" in 1) usage ;; esac
+		shift
+		log_given=t$log_given
+		use_commit="$1"
+		no_edit=t
+		shift
+		;;
+	--reu=*|--reus=*|--reuse=*|--reuse-=*|--reuse-m=*|--reuse-me=*|\
+	--reuse-mes=*|--reuse-mess=*|--reuse-messa=*|--reuse-messag=*|\
+	--reuse-message=*)
+		log_given=t$log_given
+		use_commit=`expr "z$1" : 'z-[^=]*=\(.*\)'`
+		no_edit=t
+		shift
+		;;
+	--reu|--reus|--reuse|--reuse-|--reuse-m|--reuse-me|--reuse-mes|\
+	--reuse-mess|--reuse-messa|--reuse-messag|--reuse-message)
+		case "$#" in 1) usage ;; esac
+		shift
+		log_given=t$log_given
+		use_commit="$1"
+		no_edit=t
+		shift
+		;;
+	-s|--s|--si|--sig|--sign|--signo|--signof|--signoff)
+		signoff=t
+		shift
+		;;
+	-t|--t|--te|--tem|--temp|--templ|--templa|--templat|--template)
+		case "$#" in 1) usage ;; esac
+		shift
+		templatefile="$1"
+		no_edit=
+		shift
+		;;
+	-q|--q|--qu|--qui|--quie|--quiet)
+		quiet=t
+		shift
+		;;
+	-v|--v|--ve|--ver|--verb|--verbo|--verbos|--verbose)
+		verbose=t
+		shift
+		;;
+	-u|--u|--un|--unt|--untr|--untra|--untrac|--untrack|--untracke|\
+	--untracked|--untracked-|--untracked-f|--untracked-fi|--untracked-fil|\
+	--untracked-file|--untracked-files)
+		untracked_files=t
+		shift
+		;;
+	--)
+		shift
+		break
+		;;
+	-*)
+		usage
+		;;
+	*)
+		break
+		;;
+	esac
+done
+case "$edit_flag" in t) no_edit= ;; esac
+
+################################################################
+# Sanity check options
+
+case "$amend,$initial_commit" in
+t,t)
+	die "You do not have anything to amend." ;;
+t,)
+	if [ -f "$GIT_DIR/MERGE_HEAD" ]; then
+		die "You are in the middle of a merge -- cannot amend."
+	fi ;;
+esac
+
+case "$log_given" in
+tt*)
+	die "Only one of -c/-C/-F can be used." ;;
+*tm*|*mt*)
+	die "Option -m cannot be combined with -c/-C/-F." ;;
+esac
+
+case "$#,$also,$only,$amend" in
+*,t,t,*)
+	die "Only one of --include/--only can be used." ;;
+0,t,,* | 0,,t,)
+	die "No paths with --include/--only does not make sense." ;;
+0,,t,t)
+	only_include_assumed="# Clever... amending the last one with dirty index." ;;
+0,,,*)
+	;;
+*,,,*)
+	only_include_assumed="# Explicit paths specified without -i nor -o; assuming --only paths..."
+	also=
+	;;
+esac
+unset only
+case "$all,$interactive,$also,$#" in
+*t,*t,*)
+	die "Cannot use -a, --interactive or -i at the same time." ;;
+t,,[1-9]*)
+	die "Paths with -a does not make sense." ;;
+,t,[1-9]*)
+	die "Paths with --interactive does not make sense." ;;
+,,t,0)
+	die "No paths with -i does not make sense." ;;
+esac
+
+if test ! -z "$templatefile" -a -z "$log_given"
+then
+	if test ! -f "$templatefile"
+	then
+		die "Commit template file does not exist."
+	fi
+fi
+
+################################################################
+# Prepare index to have a tree to be committed
+
+case "$all,$also" in
+t,)
+	if test ! -f "$THIS_INDEX"
+	then
+		die 'nothing to commit (use "git add file1 file2" to include for commit)'
+	fi
+	save_index &&
+	(
+		cd_to_toplevel &&
+		GIT_INDEX_FILE="$NEXT_INDEX" &&
+		export GIT_INDEX_FILE &&
+		git diff-files --name-only -z |
+		git update-index --remove -z --stdin
+	) || exit
+	;;
+,t)
+	save_index &&
+	git ls-files --error-unmatch -- "$@" >/dev/null || exit
+
+	git diff-files --name-only -z -- "$@"  |
+	(
+		cd_to_toplevel &&
+		GIT_INDEX_FILE="$NEXT_INDEX" &&
+		export GIT_INDEX_FILE &&
+		git update-index --remove -z --stdin
+	) || exit
+	;;
+,)
+	if test "$interactive" = t; then
+		git add --interactive || exit
+	fi
+	case "$#" in
+	0)
+		;; # commit as-is
+	*)
+		if test -f "$GIT_DIR/MERGE_HEAD"
+		then
+			refuse_partial "Cannot do a partial commit during a merge."
+		fi
+		TMP_INDEX="$GIT_DIR/tmp-index$$"
+		commit_only=`git ls-files --error-unmatch -- "$@"` || exit
+
+		# Build a temporary index and update the real index
+		# the same way.
+		if test -z "$initial_commit"
+		then
+			GIT_INDEX_FILE="$THIS_INDEX" \
+			git read-tree --index-output="$TMP_INDEX" -i -m HEAD
+		else
+			rm -f "$TMP_INDEX"
+		fi || exit
+
+		printf '%s\n' "$commit_only" |
+		GIT_INDEX_FILE="$TMP_INDEX" \
+		git update-index --add --remove --stdin &&
+
+		save_index &&
+		printf '%s\n' "$commit_only" |
+		(
+			GIT_INDEX_FILE="$NEXT_INDEX"
+			export GIT_INDEX_FILE
+			git update-index --remove --stdin
+		) || exit
+		;;
+	esac
+	;;
+esac
+
+################################################################
+# If we do as-is commit, the index file will be THIS_INDEX,
+# otherwise NEXT_INDEX after we make this commit.  We leave
+# the index as is if we abort.
+
+if test -f "$NEXT_INDEX"
+then
+	USE_INDEX="$NEXT_INDEX"
+else
+	USE_INDEX="$THIS_INDEX"
+fi
+
+case "$status_only" in
+t)
+	# This will silently fail in a read-only repository, which is
+	# what we want.
+	GIT_INDEX_FILE="$USE_INDEX" git update-index -q --unmerged --refresh
+	run_status
+	exit $?
+	;;
+'')
+	GIT_INDEX_FILE="$USE_INDEX" git update-index -q --refresh || exit
+	;;
+esac
+
+################################################################
+# Grab commit message, write out tree and make commit.
+
+if test t = "$verify" && test -x "$GIT_DIR"/hooks/pre-commit
+then
+	if test "$TMP_INDEX"
+	then
+		GIT_INDEX_FILE="$TMP_INDEX" "$GIT_DIR"/hooks/pre-commit
+	else
+		GIT_INDEX_FILE="$USE_INDEX" "$GIT_DIR"/hooks/pre-commit
+	fi || exit
+fi
+
+if test "$log_message" != ''
+then
+	printf '%s\n' "$log_message"
+elif test "$logfile" != ""
+then
+	if test "$logfile" = -
+	then
+		test -t 0 &&
+		echo >&2 "(reading log message from standard input)"
+		cat
+	else
+		cat <"$logfile"
+	fi
+elif test "$use_commit" != ""
+then
+	encoding=$(git config i18n.commitencoding || echo UTF-8)
+	git show -s --pretty=raw --encoding="$encoding" "$use_commit" |
+	sed -e '1,/^$/d' -e 's/^    //'
+elif test -f "$GIT_DIR/MERGE_MSG"
+then
+	cat "$GIT_DIR/MERGE_MSG"
+elif test -f "$GIT_DIR/SQUASH_MSG"
+then
+	cat "$GIT_DIR/SQUASH_MSG"
+elif test "$templatefile" != ""
+then
+	cat "$templatefile"
+fi | git stripspace >"$GIT_DIR"/COMMIT_EDITMSG
+
+case "$signoff" in
+t)
+	sign=$(git-var GIT_COMMITTER_IDENT | sed -e '
+		s/>.*/>/
+		s/^/Signed-off-by: /
+		')
+	blank_before_signoff=
+	tail -n 1 "$GIT_DIR"/COMMIT_EDITMSG |
+	grep 'Signed-off-by:' >/dev/null || blank_before_signoff='
+'
+	tail -n 1 "$GIT_DIR"/COMMIT_EDITMSG |
+	grep "$sign"$ >/dev/null ||
+	printf '%s%s\n' "$blank_before_signoff" "$sign" \
+		>>"$GIT_DIR"/COMMIT_EDITMSG
+	;;
+esac
+
+if test -f "$GIT_DIR/MERGE_HEAD" && test -z "$no_edit"; then
+	echo "#"
+	echo "# It looks like you may be committing a MERGE."
+	echo "# If this is not correct, please remove the file"
+	printf '%s\n' "#	$GIT_DIR/MERGE_HEAD"
+	echo "# and try again"
+	echo "#"
+fi >>"$GIT_DIR"/COMMIT_EDITMSG
+
+# Author
+if test '' != "$use_commit"
+then
+	eval "$(get_author_ident_from_commit "$use_commit")"
+	export GIT_AUTHOR_NAME GIT_AUTHOR_EMAIL GIT_AUTHOR_DATE
+fi
+if test '' != "$force_author"
+then
+	GIT_AUTHOR_NAME=`expr "z$force_author" : 'z\(.*[^ ]\) *<.*'` &&
+	GIT_AUTHOR_EMAIL=`expr "z$force_author" : '.*\(<.*\)'` &&
+	test '' != "$GIT_AUTHOR_NAME" &&
+	test '' != "$GIT_AUTHOR_EMAIL" ||
+	die "malformed --author parameter"
+	export GIT_AUTHOR_NAME GIT_AUTHOR_EMAIL
+fi
+
+PARENTS="-p HEAD"
+if test -z "$initial_commit"
+then
+	rloga='commit'
+	if [ -f "$GIT_DIR/MERGE_HEAD" ]; then
+		rloga='commit (merge)'
+		PARENTS="-p HEAD "`sed -e 's/^/-p /' "$GIT_DIR/MERGE_HEAD"`
+	elif test -n "$amend"; then
+		rloga='commit (amend)'
+		PARENTS=$(git cat-file commit HEAD |
+			sed -n -e '/^$/q' -e 's/^parent /-p /p')
+	fi
+	current="$(git rev-parse --verify HEAD)"
+else
+	if [ -z "$(git ls-files)" ]; then
+		echo >&2 'nothing to commit (use "git add file1 file2" to include for commit)'
+		exit 1
+	fi
+	PARENTS=""
+	rloga='commit (initial)'
+	current=''
+fi
+set_reflog_action "$rloga"
+
+if test -z "$no_edit"
+then
+	{
+		echo ""
+		echo "# Please enter the commit message for your changes."
+		echo "# (Comment lines starting with '#' will not be included)"
+		test -z "$only_include_assumed" || echo "$only_include_assumed"
+		run_status
+	} >>"$GIT_DIR"/COMMIT_EDITMSG
+else
+	# we need to check if there is anything to commit
+	run_status >/dev/null
+fi
+if [ "$?" != "0" -a ! -f "$GIT_DIR/MERGE_HEAD" -a -z "$amend" ]
+then
+	rm -f "$GIT_DIR/COMMIT_EDITMSG" "$GIT_DIR/SQUASH_MSG"
+	run_status
+	exit 1
+fi
+
+case "$no_edit" in
+'')
+	git-var GIT_AUTHOR_IDENT > /dev/null  || die
+	git-var GIT_COMMITTER_IDENT > /dev/null  || die
+	git_editor "$GIT_DIR/COMMIT_EDITMSG"
+	;;
+esac
+
+case "$verify" in
+t)
+	if test -x "$GIT_DIR"/hooks/commit-msg
+	then
+		"$GIT_DIR"/hooks/commit-msg "$GIT_DIR"/COMMIT_EDITMSG || exit
+	fi
+esac
+
+if test -z "$no_edit"
+then
+    sed -e '
+        /^diff --git a\/.*/{
+	    s///
+	    q
+	}
+	/^#/d
+    ' "$GIT_DIR"/COMMIT_EDITMSG
+else
+    cat "$GIT_DIR"/COMMIT_EDITMSG
+fi |
+git stripspace >"$GIT_DIR"/COMMIT_MSG
+
+# Test whether the commit message has any content we didn't supply.
+have_commitmsg=
+grep -v -i '^Signed-off-by' "$GIT_DIR"/COMMIT_MSG |
+	git stripspace > "$GIT_DIR"/COMMIT_BAREMSG
+
+# Is the commit message totally empty?
+if test -s "$GIT_DIR"/COMMIT_BAREMSG
+then
+	if test "$templatefile" != ""
+	then
+		# Test whether this is just the unaltered template.
+		if cnt=`sed -e '/^#/d' < "$templatefile" |
+			git stripspace |
+			diff "$GIT_DIR"/COMMIT_BAREMSG - |
+			wc -l` &&
+		   test 0 -lt $cnt
+		then
+			have_commitmsg=t
+		fi
+	else
+		# No template, so the content in the commit message must
+		# have come from the user.
+		have_commitmsg=t
+	fi
+fi
+
+rm -f "$GIT_DIR"/COMMIT_BAREMSG
+
+if test "$have_commitmsg" = "t"
+then
+	if test -z "$TMP_INDEX"
+	then
+		tree=$(GIT_INDEX_FILE="$USE_INDEX" git write-tree)
+	else
+		tree=$(GIT_INDEX_FILE="$TMP_INDEX" git write-tree) &&
+		rm -f "$TMP_INDEX"
+	fi &&
+	commit=$(git commit-tree $tree $PARENTS <"$GIT_DIR/COMMIT_MSG") &&
+	rlogm=$(sed -e 1q "$GIT_DIR"/COMMIT_MSG) &&
+	git update-ref -m "$GIT_REFLOG_ACTION: $rlogm" HEAD $commit "$current" &&
+	rm -f -- "$GIT_DIR/MERGE_HEAD" "$GIT_DIR/MERGE_MSG" &&
+	if test -f "$NEXT_INDEX"
+	then
+		mv "$NEXT_INDEX" "$THIS_INDEX"
+	else
+		: ;# happy
+	fi
+else
+	echo >&2 "* no commit message?  aborting commit."
+	false
+fi
+ret="$?"
+rm -f "$GIT_DIR/COMMIT_MSG" "$GIT_DIR/COMMIT_EDITMSG" "$GIT_DIR/SQUASH_MSG"
+
+cd_to_toplevel
+
+git rerere
+
+if test "$ret" = 0
+then
+	if test -x "$GIT_DIR"/hooks/post-commit
+	then
+		"$GIT_DIR"/hooks/post-commit
+	fi
+	if test -z "$quiet"
+	then
+		commit=`git diff-tree --always --shortstat --pretty="format:%h: %s"\
+		       --summary --root HEAD --`
+		echo "Created${initial_commit:+ initial} commit $commit"
+	fi
+fi
+
+exit "$ret"
diff --git a/git-commit.sh b/git-commit.sh
deleted file mode 100755
index d7e7028..0000000
--- a/git-commit.sh
+++ /dev/null
@@ -1,665 +0,0 @@
-#!/bin/sh
-#
-# Copyright (c) 2005 Linus Torvalds
-# Copyright (c) 2006 Junio C Hamano
-
-USAGE='[-a | --interactive] [-s] [-v] [--no-verify] [-m <message> | -F <logfile> | (-C|-c) <commit> | --amend] [-u] [-e] [--author <author>] [--template <file>] [[-i | -o] <path>...]'
-SUBDIRECTORY_OK=Yes
-. git-sh-setup
-require_work_tree
-
-git rev-parse --verify HEAD >/dev/null 2>&1 || initial_commit=t
-
-case "$0" in
-*status)
-	status_only=t
-	;;
-*commit)
-	status_only=
-	;;
-esac
-
-refuse_partial () {
-	echo >&2 "$1"
-	echo >&2 "You might have meant to say 'git commit -i paths...', perhaps?"
-	exit 1
-}
-
-THIS_INDEX="$GIT_DIR/index"
-NEXT_INDEX="$GIT_DIR/next-index$$"
-rm -f "$NEXT_INDEX"
-save_index () {
-	cp -p "$THIS_INDEX" "$NEXT_INDEX"
-}
-
-run_status () {
-	# If TMP_INDEX is defined, that means we are doing
-	# "--only" partial commit, and that index file is used
-	# to build the tree for the commit.  Otherwise, if
-	# NEXT_INDEX exists, that is the index file used to
-	# make the commit.  Otherwise we are using as-is commit
-	# so the regular index file is what we use to compare.
-	if test '' != "$TMP_INDEX"
-	then
-		GIT_INDEX_FILE="$TMP_INDEX"
-		export GIT_INDEX_FILE
-	elif test -f "$NEXT_INDEX"
-	then
-		GIT_INDEX_FILE="$NEXT_INDEX"
-		export GIT_INDEX_FILE
-	fi
-
-	case "$status_only" in
-	t) color= ;;
-	*) color=--nocolor ;;
-	esac
-	git runstatus ${color} \
-		${verbose:+--verbose} \
-		${amend:+--amend} \
-		${untracked_files:+--untracked}
-}
-
-trap '
-	test -z "$TMP_INDEX" || {
-		test -f "$TMP_INDEX" && rm -f "$TMP_INDEX"
-	}
-	rm -f "$NEXT_INDEX"
-' 0
-
-################################################################
-# Command line argument parsing and sanity checking
-
-all=
-also=
-interactive=
-only=
-logfile=
-use_commit=
-amend=
-edit_flag=
-no_edit=
-log_given=
-log_message=
-verify=t
-quiet=
-verbose=
-signoff=
-force_author=
-only_include_assumed=
-untracked_files=
-templatefile="`git config commit.template`"
-while case "$#" in 0) break;; esac
-do
-	case "$1" in
-	-F|--F|-f|--f|--fi|--fil|--file)
-		case "$#" in 1) usage ;; esac
-		shift
-		no_edit=t
-		log_given=t$log_given
-		logfile="$1"
-		shift
-		;;
-	-F*|-f*)
-		no_edit=t
-		log_given=t$log_given
-		logfile=`expr "z$1" : 'z-[Ff]\(.*\)'`
-		shift
-		;;
-	--F=*|--f=*|--fi=*|--fil=*|--file=*)
-		no_edit=t
-		log_given=t$log_given
-		logfile=`expr "z$1" : 'z-[^=]*=\(.*\)'`
-		shift
-		;;
-	-a|--a|--al|--all)
-		all=t
-		shift
-		;;
-	--au=*|--aut=*|--auth=*|--autho=*|--author=*)
-		force_author=`expr "z$1" : 'z-[^=]*=\(.*\)'`
-		shift
-		;;
-	--au|--aut|--auth|--autho|--author)
-		case "$#" in 1) usage ;; esac
-		shift
-		force_author="$1"
-		shift
-		;;
-	-e|--e|--ed|--edi|--edit)
-		edit_flag=t
-		shift
-		;;
-	-i|--i|--in|--inc|--incl|--inclu|--includ|--include)
-		also=t
-		shift
-		;;
-	--int|--inte|--inter|--intera|--interac|--interact|--interacti|\
-	--interactiv|--interactive)
-		interactive=t
-		shift
-		;;
-	-o|--o|--on|--onl|--only)
-		only=t
-		shift
-		;;
-	-m|--m|--me|--mes|--mess|--messa|--messag|--message)
-		case "$#" in 1) usage ;; esac
-		shift
-		log_given=m$log_given
-		if test "$log_message" = ''
-		then
-		    log_message="$1"
-		else
-		    log_message="$log_message
-
-$1"
-		fi
-		no_edit=t
-		shift
-		;;
-	-m*)
-		log_given=m$log_given
-		if test "$log_message" = ''
-		then
-		    log_message=`expr "z$1" : 'z-m\(.*\)'`
-		else
-		    log_message="$log_message
-
-`expr "z$1" : 'z-m\(.*\)'`"
-		fi
-		no_edit=t
-		shift
-		;;
-	--m=*|--me=*|--mes=*|--mess=*|--messa=*|--messag=*|--message=*)
-		log_given=m$log_given
-		if test "$log_message" = ''
-		then
-		    log_message=`expr "z$1" : 'z-[^=]*=\(.*\)'`
-		else
-		    log_message="$log_message
-
-`expr "z$1" : 'zq-[^=]*=\(.*\)'`"
-		fi
-		no_edit=t
-		shift
-		;;
-	-n|--n|--no|--no-|--no-v|--no-ve|--no-ver|--no-veri|--no-verif|\
-	--no-verify)
-		verify=
-		shift
-		;;
-	--a|--am|--ame|--amen|--amend)
-		amend=t
-		use_commit=HEAD
-		shift
-		;;
-	-c)
-		case "$#" in 1) usage ;; esac
-		shift
-		log_given=t$log_given
-		use_commit="$1"
-		no_edit=
-		shift
-		;;
-	--ree=*|--reed=*|--reedi=*|--reedit=*|--reedit-=*|--reedit-m=*|\
-	--reedit-me=*|--reedit-mes=*|--reedit-mess=*|--reedit-messa=*|\
-	--reedit-messag=*|--reedit-message=*)
-		log_given=t$log_given
-		use_commit=`expr "z$1" : 'z-[^=]*=\(.*\)'`
-		no_edit=
-		shift
-		;;
-	--ree|--reed|--reedi|--reedit|--reedit-|--reedit-m|--reedit-me|\
-	--reedit-mes|--reedit-mess|--reedit-messa|--reedit-messag|\
-	--reedit-message)
-		case "$#" in 1) usage ;; esac
-		shift
-		log_given=t$log_given
-		use_commit="$1"
-		no_edit=
-		shift
-		;;
-	-C)
-		case "$#" in 1) usage ;; esac
-		shift
-		log_given=t$log_given
-		use_commit="$1"
-		no_edit=t
-		shift
-		;;
-	--reu=*|--reus=*|--reuse=*|--reuse-=*|--reuse-m=*|--reuse-me=*|\
-	--reuse-mes=*|--reuse-mess=*|--reuse-messa=*|--reuse-messag=*|\
-	--reuse-message=*)
-		log_given=t$log_given
-		use_commit=`expr "z$1" : 'z-[^=]*=\(.*\)'`
-		no_edit=t
-		shift
-		;;
-	--reu|--reus|--reuse|--reuse-|--reuse-m|--reuse-me|--reuse-mes|\
-	--reuse-mess|--reuse-messa|--reuse-messag|--reuse-message)
-		case "$#" in 1) usage ;; esac
-		shift
-		log_given=t$log_given
-		use_commit="$1"
-		no_edit=t
-		shift
-		;;
-	-s|--s|--si|--sig|--sign|--signo|--signof|--signoff)
-		signoff=t
-		shift
-		;;
-	-t|--t|--te|--tem|--temp|--templ|--templa|--templat|--template)
-		case "$#" in 1) usage ;; esac
-		shift
-		templatefile="$1"
-		no_edit=
-		shift
-		;;
-	-q|--q|--qu|--qui|--quie|--quiet)
-		quiet=t
-		shift
-		;;
-	-v|--v|--ve|--ver|--verb|--verbo|--verbos|--verbose)
-		verbose=t
-		shift
-		;;
-	-u|--u|--un|--unt|--untr|--untra|--untrac|--untrack|--untracke|\
-	--untracked|--untracked-|--untracked-f|--untracked-fi|--untracked-fil|\
-	--untracked-file|--untracked-files)
-		untracked_files=t
-		shift
-		;;
-	--)
-		shift
-		break
-		;;
-	-*)
-		usage
-		;;
-	*)
-		break
-		;;
-	esac
-done
-case "$edit_flag" in t) no_edit= ;; esac
-
-################################################################
-# Sanity check options
-
-case "$amend,$initial_commit" in
-t,t)
-	die "You do not have anything to amend." ;;
-t,)
-	if [ -f "$GIT_DIR/MERGE_HEAD" ]; then
-		die "You are in the middle of a merge -- cannot amend."
-	fi ;;
-esac
-
-case "$log_given" in
-tt*)
-	die "Only one of -c/-C/-F can be used." ;;
-*tm*|*mt*)
-	die "Option -m cannot be combined with -c/-C/-F." ;;
-esac
-
-case "$#,$also,$only,$amend" in
-*,t,t,*)
-	die "Only one of --include/--only can be used." ;;
-0,t,,* | 0,,t,)
-	die "No paths with --include/--only does not make sense." ;;
-0,,t,t)
-	only_include_assumed="# Clever... amending the last one with dirty index." ;;
-0,,,*)
-	;;
-*,,,*)
-	only_include_assumed="# Explicit paths specified without -i nor -o; assuming --only paths..."
-	also=
-	;;
-esac
-unset only
-case "$all,$interactive,$also,$#" in
-*t,*t,*)
-	die "Cannot use -a, --interactive or -i at the same time." ;;
-t,,[1-9]*)
-	die "Paths with -a does not make sense." ;;
-,t,[1-9]*)
-	die "Paths with --interactive does not make sense." ;;
-,,t,0)
-	die "No paths with -i does not make sense." ;;
-esac
-
-if test ! -z "$templatefile" -a -z "$log_given"
-then
-	if test ! -f "$templatefile"
-	then
-		die "Commit template file does not exist."
-	fi
-fi
-
-################################################################
-# Prepare index to have a tree to be committed
-
-case "$all,$also" in
-t,)
-	if test ! -f "$THIS_INDEX"
-	then
-		die 'nothing to commit (use "git add file1 file2" to include for commit)'
-	fi
-	save_index &&
-	(
-		cd_to_toplevel &&
-		GIT_INDEX_FILE="$NEXT_INDEX" &&
-		export GIT_INDEX_FILE &&
-		git diff-files --name-only -z |
-		git update-index --remove -z --stdin
-	) || exit
-	;;
-,t)
-	save_index &&
-	git ls-files --error-unmatch -- "$@" >/dev/null || exit
-
-	git diff-files --name-only -z -- "$@"  |
-	(
-		cd_to_toplevel &&
-		GIT_INDEX_FILE="$NEXT_INDEX" &&
-		export GIT_INDEX_FILE &&
-		git update-index --remove -z --stdin
-	) || exit
-	;;
-,)
-	if test "$interactive" = t; then
-		git add --interactive || exit
-	fi
-	case "$#" in
-	0)
-		;; # commit as-is
-	*)
-		if test -f "$GIT_DIR/MERGE_HEAD"
-		then
-			refuse_partial "Cannot do a partial commit during a merge."
-		fi
-		TMP_INDEX="$GIT_DIR/tmp-index$$"
-		commit_only=`git ls-files --error-unmatch -- "$@"` || exit
-
-		# Build a temporary index and update the real index
-		# the same way.
-		if test -z "$initial_commit"
-		then
-			GIT_INDEX_FILE="$THIS_INDEX" \
-			git read-tree --index-output="$TMP_INDEX" -i -m HEAD
-		else
-			rm -f "$TMP_INDEX"
-		fi || exit
-
-		printf '%s\n' "$commit_only" |
-		GIT_INDEX_FILE="$TMP_INDEX" \
-		git update-index --add --remove --stdin &&
-
-		save_index &&
-		printf '%s\n' "$commit_only" |
-		(
-			GIT_INDEX_FILE="$NEXT_INDEX"
-			export GIT_INDEX_FILE
-			git update-index --remove --stdin
-		) || exit
-		;;
-	esac
-	;;
-esac
-
-################################################################
-# If we do as-is commit, the index file will be THIS_INDEX,
-# otherwise NEXT_INDEX after we make this commit.  We leave
-# the index as is if we abort.
-
-if test -f "$NEXT_INDEX"
-then
-	USE_INDEX="$NEXT_INDEX"
-else
-	USE_INDEX="$THIS_INDEX"
-fi
-
-case "$status_only" in
-t)
-	# This will silently fail in a read-only repository, which is
-	# what we want.
-	GIT_INDEX_FILE="$USE_INDEX" git update-index -q --unmerged --refresh
-	run_status
-	exit $?
-	;;
-'')
-	GIT_INDEX_FILE="$USE_INDEX" git update-index -q --refresh || exit
-	;;
-esac
-
-################################################################
-# Grab commit message, write out tree and make commit.
-
-if test t = "$verify" && test -x "$GIT_DIR"/hooks/pre-commit
-then
-	if test "$TMP_INDEX"
-	then
-		GIT_INDEX_FILE="$TMP_INDEX" "$GIT_DIR"/hooks/pre-commit
-	else
-		GIT_INDEX_FILE="$USE_INDEX" "$GIT_DIR"/hooks/pre-commit
-	fi || exit
-fi
-
-if test "$log_message" != ''
-then
-	printf '%s\n' "$log_message"
-elif test "$logfile" != ""
-then
-	if test "$logfile" = -
-	then
-		test -t 0 &&
-		echo >&2 "(reading log message from standard input)"
-		cat
-	else
-		cat <"$logfile"
-	fi
-elif test "$use_commit" != ""
-then
-	encoding=$(git config i18n.commitencoding || echo UTF-8)
-	git show -s --pretty=raw --encoding="$encoding" "$use_commit" |
-	sed -e '1,/^$/d' -e 's/^    //'
-elif test -f "$GIT_DIR/MERGE_MSG"
-then
-	cat "$GIT_DIR/MERGE_MSG"
-elif test -f "$GIT_DIR/SQUASH_MSG"
-then
-	cat "$GIT_DIR/SQUASH_MSG"
-elif test "$templatefile" != ""
-then
-	cat "$templatefile"
-fi | git stripspace >"$GIT_DIR"/COMMIT_EDITMSG
-
-case "$signoff" in
-t)
-	sign=$(git-var GIT_COMMITTER_IDENT | sed -e '
-		s/>.*/>/
-		s/^/Signed-off-by: /
-		')
-	blank_before_signoff=
-	tail -n 1 "$GIT_DIR"/COMMIT_EDITMSG |
-	grep 'Signed-off-by:' >/dev/null || blank_before_signoff='
-'
-	tail -n 1 "$GIT_DIR"/COMMIT_EDITMSG |
-	grep "$sign"$ >/dev/null ||
-	printf '%s%s\n' "$blank_before_signoff" "$sign" \
-		>>"$GIT_DIR"/COMMIT_EDITMSG
-	;;
-esac
-
-if test -f "$GIT_DIR/MERGE_HEAD" && test -z "$no_edit"; then
-	echo "#"
-	echo "# It looks like you may be committing a MERGE."
-	echo "# If this is not correct, please remove the file"
-	printf '%s\n' "#	$GIT_DIR/MERGE_HEAD"
-	echo "# and try again"
-	echo "#"
-fi >>"$GIT_DIR"/COMMIT_EDITMSG
-
-# Author
-if test '' != "$use_commit"
-then
-	eval "$(get_author_ident_from_commit "$use_commit")"
-	export GIT_AUTHOR_NAME GIT_AUTHOR_EMAIL GIT_AUTHOR_DATE
-fi
-if test '' != "$force_author"
-then
-	GIT_AUTHOR_NAME=`expr "z$force_author" : 'z\(.*[^ ]\) *<.*'` &&
-	GIT_AUTHOR_EMAIL=`expr "z$force_author" : '.*\(<.*\)'` &&
-	test '' != "$GIT_AUTHOR_NAME" &&
-	test '' != "$GIT_AUTHOR_EMAIL" ||
-	die "malformed --author parameter"
-	export GIT_AUTHOR_NAME GIT_AUTHOR_EMAIL
-fi
-
-PARENTS="-p HEAD"
-if test -z "$initial_commit"
-then
-	rloga='commit'
-	if [ -f "$GIT_DIR/MERGE_HEAD" ]; then
-		rloga='commit (merge)'
-		PARENTS="-p HEAD "`sed -e 's/^/-p /' "$GIT_DIR/MERGE_HEAD"`
-	elif test -n "$amend"; then
-		rloga='commit (amend)'
-		PARENTS=$(git cat-file commit HEAD |
-			sed -n -e '/^$/q' -e 's/^parent /-p /p')
-	fi
-	current="$(git rev-parse --verify HEAD)"
-else
-	if [ -z "$(git ls-files)" ]; then
-		echo >&2 'nothing to commit (use "git add file1 file2" to include for commit)'
-		exit 1
-	fi
-	PARENTS=""
-	rloga='commit (initial)'
-	current=''
-fi
-set_reflog_action "$rloga"
-
-if test -z "$no_edit"
-then
-	{
-		echo ""
-		echo "# Please enter the commit message for your changes."
-		echo "# (Comment lines starting with '#' will not be included)"
-		test -z "$only_include_assumed" || echo "$only_include_assumed"
-		run_status
-	} >>"$GIT_DIR"/COMMIT_EDITMSG
-else
-	# we need to check if there is anything to commit
-	run_status >/dev/null
-fi
-if [ "$?" != "0" -a ! -f "$GIT_DIR/MERGE_HEAD" -a -z "$amend" ]
-then
-	rm -f "$GIT_DIR/COMMIT_EDITMSG" "$GIT_DIR/SQUASH_MSG"
-	run_status
-	exit 1
-fi
-
-case "$no_edit" in
-'')
-	git-var GIT_AUTHOR_IDENT > /dev/null  || die
-	git-var GIT_COMMITTER_IDENT > /dev/null  || die
-	git_editor "$GIT_DIR/COMMIT_EDITMSG"
-	;;
-esac
-
-case "$verify" in
-t)
-	if test -x "$GIT_DIR"/hooks/commit-msg
-	then
-		"$GIT_DIR"/hooks/commit-msg "$GIT_DIR"/COMMIT_EDITMSG || exit
-	fi
-esac
-
-if test -z "$no_edit"
-then
-    sed -e '
-        /^diff --git a\/.*/{
-	    s///
-	    q
-	}
-	/^#/d
-    ' "$GIT_DIR"/COMMIT_EDITMSG
-else
-    cat "$GIT_DIR"/COMMIT_EDITMSG
-fi |
-git stripspace >"$GIT_DIR"/COMMIT_MSG
-
-# Test whether the commit message has any content we didn't supply.
-have_commitmsg=
-grep -v -i '^Signed-off-by' "$GIT_DIR"/COMMIT_MSG |
-	git stripspace > "$GIT_DIR"/COMMIT_BAREMSG
-
-# Is the commit message totally empty?
-if test -s "$GIT_DIR"/COMMIT_BAREMSG
-then
-	if test "$templatefile" != ""
-	then
-		# Test whether this is just the unaltered template.
-		if cnt=`sed -e '/^#/d' < "$templatefile" |
-			git stripspace |
-			diff "$GIT_DIR"/COMMIT_BAREMSG - |
-			wc -l` &&
-		   test 0 -lt $cnt
-		then
-			have_commitmsg=t
-		fi
-	else
-		# No template, so the content in the commit message must
-		# have come from the user.
-		have_commitmsg=t
-	fi
-fi
-
-rm -f "$GIT_DIR"/COMMIT_BAREMSG
-
-if test "$have_commitmsg" = "t"
-then
-	if test -z "$TMP_INDEX"
-	then
-		tree=$(GIT_INDEX_FILE="$USE_INDEX" git write-tree)
-	else
-		tree=$(GIT_INDEX_FILE="$TMP_INDEX" git write-tree) &&
-		rm -f "$TMP_INDEX"
-	fi &&
-	commit=$(git commit-tree $tree $PARENTS <"$GIT_DIR/COMMIT_MSG") &&
-	rlogm=$(sed -e 1q "$GIT_DIR"/COMMIT_MSG) &&
-	git update-ref -m "$GIT_REFLOG_ACTION: $rlogm" HEAD $commit "$current" &&
-	rm -f -- "$GIT_DIR/MERGE_HEAD" "$GIT_DIR/MERGE_MSG" &&
-	if test -f "$NEXT_INDEX"
-	then
-		mv "$NEXT_INDEX" "$THIS_INDEX"
-	else
-		: ;# happy
-	fi
-else
-	echo >&2 "* no commit message?  aborting commit."
-	false
-fi
-ret="$?"
-rm -f "$GIT_DIR/COMMIT_MSG" "$GIT_DIR/COMMIT_EDITMSG" "$GIT_DIR/SQUASH_MSG"
-
-cd_to_toplevel
-
-git rerere
-
-if test "$ret" = 0
-then
-	if test -x "$GIT_DIR"/hooks/post-commit
-	then
-		"$GIT_DIR"/hooks/post-commit
-	fi
-	if test -z "$quiet"
-	then
-		commit=`git diff-tree --always --shortstat --pretty="format:%h: %s"\
-		       --summary --root HEAD --`
-		echo "Created${initial_commit:+ initial} commit $commit"
-	fi
-fi
-
-exit "$ret"
diff --git a/git.c b/git.c
index cab0e72..62859a2 100644
--- a/git.c
+++ b/git.c
@@ -321,6 +321,7 @@ static void handle_internal_command(int argc, const char **argv)
 		{ "check-attr", cmd_check_attr, RUN_SETUP | NEED_WORK_TREE },
 		{ "cherry", cmd_cherry, RUN_SETUP },
 		{ "cherry-pick", cmd_cherry_pick, RUN_SETUP | NEED_WORK_TREE },
+		{ "commit", cmd_commit, RUN_SETUP | NEED_WORK_TREE },
 		{ "commit-tree", cmd_commit_tree, RUN_SETUP },
 		{ "config", cmd_config },
 		{ "count-objects", cmd_count_objects, RUN_SETUP },
@@ -363,10 +364,10 @@ static void handle_internal_command(int argc, const char **argv)
 		{ "rev-parse", cmd_rev_parse, RUN_SETUP },
 		{ "revert", cmd_revert, RUN_SETUP | NEED_WORK_TREE },
 		{ "rm", cmd_rm, RUN_SETUP | NEED_WORK_TREE },
-		{ "runstatus", cmd_runstatus, RUN_SETUP | NEED_WORK_TREE },
 		{ "shortlog", cmd_shortlog, RUN_SETUP | USE_PAGER },
 		{ "show-branch", cmd_show_branch, RUN_SETUP },
 		{ "show", cmd_show, RUN_SETUP | USE_PAGER },
+		{ "status", cmd_status, RUN_SETUP | NEED_WORK_TREE },
 		{ "stripspace", cmd_stripspace },
 		{ "symbolic-ref", cmd_symbolic_ref, RUN_SETUP },
 		{ "tag", cmd_tag, RUN_SETUP },
-- 
1.5.2.GIT

^ permalink raw reply related

* [PATCH 7/9] Add strbuf_read_path().
From: Kristian Høgsberg @ 2007-09-06  0:23 UTC (permalink / raw)
  To: git; +Cc: Kristian Høgsberg
In-Reply-To: <1189038225525-git-send-email-krh@redhat.com>

Signed-off-by: Kristian Høgsberg <krh@redhat.com>
---
 builtin-tag.c |    7 +------
 strbuf.c      |   15 +++++++++++++++
 strbuf.h      |    1 +
 3 files changed, 17 insertions(+), 6 deletions(-)

diff --git a/builtin-tag.c b/builtin-tag.c
index 1aff952..8724d49 100644
--- a/builtin-tag.c
+++ b/builtin-tag.c
@@ -53,14 +53,9 @@ static void launch_editor(const char *path, struct strbuf *sb)
 	if (run_command(&child))
 		die("There was a problem with the editor %s.", editor);
 
-	fd = open(path, O_RDONLY);
-	if (fd < 0)
-		die("could not open '%s': %s", path, strerror(errno));
-	if (strbuf_read_fd(sb, fd) < 0) {
+	if (strbuf_read_path(sb, path) < 0)
 		die("could not read message file '%s': %s",
 		    path, strerror(errno));
-	}
-	close(fd);
 }
 
 struct tag_filter {
diff --git a/strbuf.c b/strbuf.c
index ed2afea..1951a5b 100644
--- a/strbuf.c
+++ b/strbuf.c
@@ -70,6 +70,21 @@ int strbuf_read_fd(struct strbuf *sb, int fd)
 	return total;
 }
 
+int strbuf_read_path(struct strbuf *sb, const char *path)
+{
+	int fd, len;
+
+	fd = open(path, O_RDONLY);
+	if (fd < 0)
+		return -1;
+	len = strbuf_read_fd(sb, fd);
+	if (len < 0)
+		return -1;
+	close(fd);
+
+	return len;
+}
+
 void strbuf_printf(struct strbuf *sb, const char *fmt, ...)
 {
 	char buffer[2048];
diff --git a/strbuf.h b/strbuf.h
index a93b9e1..e852070 100644
--- a/strbuf.h
+++ b/strbuf.h
@@ -14,6 +14,7 @@ extern void strbuf_add(struct strbuf *sb, const char *data, size_t len);
 extern void strbuf_add_char(struct strbuf *sb, int ch);
 extern void strbuf_printf(struct strbuf *sb, const char *fmt, ...);
 extern int strbuf_read_fd(struct strbuf *sb, int fd);
+extern int strbuf_read_path(struct strbuf *sb, const char *path);
 extern size_t stripspace(struct strbuf *sb, int skip_comments);
 
 #endif /* STRBUF_H */
-- 
1.5.2.GIT

^ permalink raw reply related

* [PATCH 8/9] Export rerere() and launch_editor().
From: Kristian Høgsberg @ 2007-09-06  0:23 UTC (permalink / raw)
  To: git; +Cc: Kristian Høgsberg
In-Reply-To: <11890382262161-git-send-email-krh@redhat.com>

Signed-off-by: Kristian Høgsberg <krh@redhat.com>
---
 builtin-rerere.c |   16 ++++++++++++++++
 builtin-tag.c    |    3 +--
 commit.h         |    1 +
 strbuf.h         |    1 +
 4 files changed, 19 insertions(+), 2 deletions(-)

diff --git a/builtin-rerere.c b/builtin-rerere.c
index 29d057c..eb22a28 100644
--- a/builtin-rerere.c
+++ b/builtin-rerere.c
@@ -415,6 +415,22 @@ static int is_rerere_enabled(void)
 	return 1;
 }
 
+/* Export for builtin-commit. */
+int rerere(void)
+{
+	struct path_list merge_rr = { NULL, 0, 0, 1 };
+	int fd;
+
+	git_config(git_rerere_config);
+	if (!is_rerere_enabled())
+		return 0;
+
+	merge_rr_path = xstrdup(git_path("rr-cache/MERGE_RR"));
+	fd = hold_lock_file_for_update(&write_lock, merge_rr_path, 1);
+	read_rr(&merge_rr);
+	return do_plain_rerere(&merge_rr, fd);
+}
+
 int cmd_rerere(int argc, const char **argv, const char *prefix)
 {
 	struct path_list merge_rr = { NULL, 0, 0, 1 };
diff --git a/builtin-tag.c b/builtin-tag.c
index 8724d49..3b181f5 100644
--- a/builtin-tag.c
+++ b/builtin-tag.c
@@ -18,12 +18,11 @@ static const char builtin_tag_usage[] =
 
 static char signingkey[1000];
 
-static void launch_editor(const char *path, struct strbuf *sb)
+void launch_editor(const char *path, struct strbuf *sb)
 {
 	const char *editor, *terminal;
 	struct child_process child;
 	const char *args[3];
-	int fd;
 
 	editor = getenv("GIT_EDITOR");
 	if (!editor && editor_program)
diff --git a/commit.h b/commit.h
index 64e1d4b..39934cc 100644
--- a/commit.h
+++ b/commit.h
@@ -130,5 +130,6 @@ create_commit(const unsigned char *tree_sha1,
 	      const char *message, int length);
 
 extern int interactive_add(void);
+extern int rerere(void);
 
 #endif /* COMMIT_H */
diff --git a/strbuf.h b/strbuf.h
index e852070..b009b4b 100644
--- a/strbuf.h
+++ b/strbuf.h
@@ -16,5 +16,6 @@ extern void strbuf_printf(struct strbuf *sb, const char *fmt, ...);
 extern int strbuf_read_fd(struct strbuf *sb, int fd);
 extern int strbuf_read_path(struct strbuf *sb, const char *path);
 extern size_t stripspace(struct strbuf *sb, int skip_comments);
+extern void launch_editor(const char *path, struct strbuf *sb);
 
 #endif /* STRBUF_H */
-- 
1.5.2.GIT

^ permalink raw reply related

* [PATCH 6/9] Rewrite launch_editor, create_tag and stripspace to use strbufs.
From: Kristian Høgsberg @ 2007-09-06  0:23 UTC (permalink / raw)
  To: git; +Cc: Kristian Høgsberg
In-Reply-To: <11890382252522-git-send-email-krh@redhat.com>

Signed-off-by: Kristian Høgsberg <krh@redhat.com>
---
 builtin-stripspace.c |   45 +++++++++++++-------------
 builtin-tag.c        |   84 +++++++++++++++++++++-----------------------------
 builtin.h            |    1 -
 strbuf.c             |   43 +++++---------------------
 strbuf.h             |    2 +
 5 files changed, 68 insertions(+), 107 deletions(-)

diff --git a/builtin-stripspace.c b/builtin-stripspace.c
index 916355c..f0264a8 100644
--- a/builtin-stripspace.c
+++ b/builtin-stripspace.c
@@ -1,5 +1,6 @@
 #include "builtin.h"
 #include "cache.h"
+#include "strbuf.h"
 
 /*
  * Returns the length of a line, without trailing spaces.
@@ -34,34 +35,34 @@ static size_t cleanup(char *line, size_t len)
  * If the input has only empty lines and spaces,
  * no output will be produced.
  *
- * If last line has a newline at the end, it will be removed.
+ * If the last line does not have a newline, one will be added.
  *
  * Enable skip_comments to skip every line starting with "#".
  */
-size_t stripspace(char *buffer, size_t length, int skip_comments)
+size_t stripspace(struct strbuf *sb, int skip_comments)
 {
 	int empties = -1;
 	size_t i, j, len, newlen;
 	char *eol;
 
-	for (i = j = 0; i < length; i += len, j += newlen) {
-		eol = memchr(buffer + i, '\n', length - i);
-		len = eol ? eol - (buffer + i) + 1 : length - i;
+	for (i = j = 0; i < sb->len; i += len, j += newlen) {
+		eol = memchr(sb->buf + i, '\n', sb->len - i);
+		len = eol ? eol - (sb->buf + i) + 1 : sb->len - i;
 
-		if (skip_comments && len && buffer[i] == '#') {
+		if (skip_comments && len && sb->buf[i] == '#') {
 			newlen = 0;
 			continue;
 		}
-		newlen = cleanup(buffer + i, len);
+		newlen = cleanup(sb->buf + i, len);
 
 		/* Not just an empty line? */
 		if (newlen) {
 			if (empties != -1)
-				buffer[j++] = '\n';
+				sb->buf[j++] = '\n';
 			if (empties > 0)
-				buffer[j++] = '\n';
+				sb->buf[j++] = '\n';
 			empties = 0;
-			memmove(buffer + j, buffer + i, newlen);
+			memmove(sb->buf + j, sb->buf + i, newlen);
 			continue;
 		}
 		if (empties < 0)
@@ -69,31 +70,31 @@ size_t stripspace(char *buffer, size_t length, int skip_comments)
 		empties++;
 	}
 
-	return j;
+	sb->len = j;
+	if (j > 0)
+		strbuf_add_char(sb, '\n');
+
+	return sb->len;
 }
 
 int cmd_stripspace(int argc, const char **argv, const char *prefix)
 {
-	char *buffer;
-	unsigned long size;
+	struct strbuf sb;
 	int strip_comments = 0;
 
 	if (argc > 1 && (!strcmp(argv[1], "-s") ||
 				!strcmp(argv[1], "--strip-comments")))
 		strip_comments = 1;
 
-	size = 1024;
-	buffer = xmalloc(size);
-	if (read_fd(0, &buffer, &size)) {
-		free(buffer);
+	strbuf_init(&sb);
+	if (strbuf_read_fd(&sb, 0) < 0) {
+		strbuf_release(&sb);
 		die("could not read the input");
 	}
 
-	size = stripspace(buffer, size, strip_comments);
-	write_or_die(1, buffer, size);
-	if (size)
-		putc('\n', stdout);
+	stripspace(&sb, strip_comments);
+	write_or_die(1, sb.buf, sb.len);
+	strbuf_release(&sb);
 
-	free(buffer);
 	return 0;
 }
diff --git a/builtin-tag.c b/builtin-tag.c
index d6d38ad..1aff952 100644
--- a/builtin-tag.c
+++ b/builtin-tag.c
@@ -11,13 +11,14 @@
 #include "refs.h"
 #include "tag.h"
 #include "run-command.h"
+#include "strbuf.h"
 
 static const char builtin_tag_usage[] =
   "git-tag [-n [<num>]] -l [<pattern>] | [-a | -s | -u <key-id>] [-f | -d | -v] [-m <msg> | -F <file>] <tagname> [<head>]";
 
 static char signingkey[1000];
 
-static void launch_editor(const char *path, char **buffer, unsigned long *len)
+static void launch_editor(const char *path, struct strbuf *sb)
 {
 	const char *editor, *terminal;
 	struct child_process child;
@@ -55,10 +56,9 @@ static void launch_editor(const char *path, char **buffer, unsigned long *len)
 	fd = open(path, O_RDONLY);
 	if (fd < 0)
 		die("could not open '%s': %s", path, strerror(errno));
-	if (read_fd(fd, buffer, len)) {
-		free(*buffer);
+	if (strbuf_read_fd(sb, fd) < 0) {
 		die("could not read message file '%s': %s",
-						path, strerror(errno));
+		    path, strerror(errno));
 	}
 	close(fd);
 }
@@ -191,7 +191,7 @@ static int verify_tag(const char *name, const char *ref,
 	return 0;
 }
 
-static ssize_t do_sign(char *buffer, size_t size, size_t max)
+static int do_sign(struct strbuf *sb)
 {
 	struct child_process gpg;
 	const char *args[4];
@@ -219,17 +219,13 @@ static ssize_t do_sign(char *buffer, size_t size, size_t max)
 	if (start_command(&gpg))
 		return error("could not run gpg.");
 
-	write_or_die(gpg.in, buffer, size);
+	write_or_die(gpg.in, sb->buf, sb->len);
 	close(gpg.in);
 	gpg.close_in = 0;
-	len = read_in_full(gpg.out, buffer + size, max - size);
-
+	len = strbuf_read_fd(sb, gpg.out);
 	finish_command(&gpg);
 
-	if (len == max - size)
-		return error("could not read the entire signature from gpg.");
-
-	return size + len;
+	return len;
 }
 
 static const char tag_template[] =
@@ -258,26 +254,24 @@ static void create_tag(const unsigned char *object, const char *tag,
 		       char *message, int sign, unsigned char *result)
 {
 	enum object_type type;
-	char header_buf[1024], *buffer = NULL;
-	int header_len, max_size;
-	unsigned long size = 0;
+	struct strbuf sb;
+	int header_len;
 
 	type = sha1_object_info(object, NULL);
 	if (type <= OBJ_NONE)
 	    die("bad object type.");
 
-	header_len = snprintf(header_buf, sizeof(header_buf),
-			  "object %s\n"
-			  "type %s\n"
-			  "tag %s\n"
-			  "tagger %s\n\n",
-			  sha1_to_hex(object),
-			  typename(type),
-			  tag,
-			  git_committer_info(1));
-
-	if (header_len > sizeof(header_buf) - 1)
-		die("tag header too big.");
+	strbuf_init(&sb);
+	strbuf_printf(&sb,
+		      "object %s\n"
+		      "type %s\n"
+		      "tag %s\n"
+		      "tagger %s\n\n",
+		      sha1_to_hex(object),
+		      typename(type),
+		      tag,
+		      git_committer_info(1));
+	header_len = sb.len;
 
 	if (!message) {
 		char *path;
@@ -292,39 +286,31 @@ static void create_tag(const unsigned char *object, const char *tag,
 		write_or_die(fd, tag_template, strlen(tag_template));
 		close(fd);
 
-		launch_editor(path, &buffer, &size);
+		launch_editor(path, &sb);
 
 		unlink(path);
 		free(path);
 	}
 	else {
-		buffer = message;
-		size = strlen(message);
+		strbuf_add(&sb, message, strlen(message));
 	}
 
-	size = stripspace(buffer, size, 1);
-
-	if (!message && !size)
+	if (stripspace(&sb, 1) <= header_len && !message)
 		die("no tag message?");
 
-	/* insert the header and add the '\n' if needed: */
-	max_size = header_len + size + (sign ? MAX_SIGNATURE_LENGTH : 0) + 1;
-	buffer = xrealloc(buffer, max_size);
-	if (size)
-		buffer[size++] = '\n';
-	memmove(buffer + header_len, buffer, size);
-	memcpy(buffer, header_buf, header_len);
-	size += header_len;
-
-	if (sign) {
-		size = do_sign(buffer, size, max_size);
-		if (size < 0)
-			die("unable to sign the tag");
-	}
+	/* Debatable, but makes t7401-tag.sh pass.  Creates an extra
+	 * blank line after the header block for tags with empty
+	 * messages. */
+	if (sb.len < header_len)
+		strbuf_add_char(&sb, '\n');
 
-	if (write_sha1_file(buffer, size, tag_type, result) < 0)
+	if (sign && do_sign(&sb) < 0)
+		die("unable to sign the tag");
+
+	if (write_sha1_file(sb.buf, sb.len, tag_type, result) < 0)
 		die("unable to write tag file");
-	free(buffer);
+
+	strbuf_release(&sb);
 }
 
 int cmd_tag(int argc, const char **argv, const char *prefix)
diff --git a/builtin.h b/builtin.h
index bb72000..91bc595 100644
--- a/builtin.h
+++ b/builtin.h
@@ -7,7 +7,6 @@ extern const char git_version_string[];
 extern const char git_usage_string[];
 
 extern void help_unknown_cmd(const char *cmd);
-extern size_t stripspace(char *buffer, size_t length, int skip_comments);
 extern int write_tree(unsigned char *sha1, int missing_ok, const char *prefix);
 extern void prune_packed_objects(int);
 
diff --git a/strbuf.c b/strbuf.c
index fcfc05e..ed2afea 100644
--- a/strbuf.c
+++ b/strbuf.c
@@ -6,7 +6,8 @@ void strbuf_init(struct strbuf *sb) {
 	sb->eof = sb->alloc = sb->len = 0;
 }
 
-static void strbuf_begin(struct strbuf *sb) {
+void strbuf_release(struct strbuf *sb)
+{
 	free(sb->buf);
 	strbuf_init(sb);
 }
@@ -35,7 +36,7 @@ static void strbuf_end(struct strbuf *sb) {
 
 void read_line(struct strbuf *sb, FILE *fp, int term) {
 	int ch;
-	strbuf_begin(sb);
+	strbuf_release(sb);
 	if (feof(fp)) {
 		sb->eof = 1;
 		return;
@@ -73,43 +74,15 @@ void strbuf_printf(struct strbuf *sb, const char *fmt, ...)
 {
 	char buffer[2048];
 	va_list args;
-	int len, size = 2 * sizeof buffer;
+	int len;
 
 	va_start(args, fmt);
 	len = vsnprintf(buffer, sizeof(buffer), fmt, args);
 	va_end(args);
 
-	if (len > sizeof(buffer)) {
-		/*
-		 * Didn't fit in the buffer, but this vsnprintf at
-		 * least gives us the required length back.  Grow the
-		 * buffer acccordingly and try again.
-		 */
-		strbuf_grow(sb, len);
-		va_start(args, fmt);
-		len = vsnprintf(sb->buf + sb->len,
-				sb->alloc - sb->len, fmt, args);
-		va_end(args);
-	} else if (len >= 0) {
-		/*
-		 * The initial vsnprintf fit in the temp buffer, just
-		 * copy it to the strbuf.
-		 */
-		strbuf_add(sb, buffer, len);
-	} else {
-		/*
-		 * This vnsprintf sucks and just returns -1 when the
-		 * buffer is too small.  Keep doubling the size until
-		 * it fits.
-		 */
-		while (len < 0) {
-			strbuf_grow(sb, size);
-			va_start(args, fmt);
-			len = vsnprintf(sb->buf + sb->len,
-					sb->alloc - sb->len, fmt, args);
-			va_end(args);
-			size *= 2;
-		}
-	}
+	if (len > sizeof(buffer) || len < 0)
+		die("out of buffer space\n");
+
+	strbuf_add(sb, buffer, len);
 }
 
diff --git a/strbuf.h b/strbuf.h
index 6e630ea..a93b9e1 100644
--- a/strbuf.h
+++ b/strbuf.h
@@ -8,10 +8,12 @@ struct strbuf {
 };
 
 extern void strbuf_init(struct strbuf *);
+extern void strbuf_release(struct strbuf *);
 extern void read_line(struct strbuf *, FILE *, int);
 extern void strbuf_add(struct strbuf *sb, const char *data, size_t len);
 extern void strbuf_add_char(struct strbuf *sb, int ch);
 extern void strbuf_printf(struct strbuf *sb, const char *fmt, ...);
 extern int strbuf_read_fd(struct strbuf *sb, int fd);
+extern size_t stripspace(struct strbuf *sb, int skip_comments);
 
 #endif /* STRBUF_H */
-- 
1.5.2.GIT

^ permalink raw reply related

* [PATCH 4/9] Introduce entry point for launching add--interactive.
From: Kristian Høgsberg @ 2007-09-06  0:23 UTC (permalink / raw)
  To: git; +Cc: Kristian Høgsberg
In-Reply-To: <11890382243290-git-send-email-krh@redhat.com>

This refactors builtin-add.c a little to provide a unique entry point
for launching git add --interactive, which will be used by
builtin-commit too.  If we later want to make add --interactive a
builtin or change how it is launched, we just start from this function.

Signed-off-by: Kristian Høgsberg <krh@redhat.com>
---
 builtin-add.c |   15 ++++++++++-----
 commit.h      |    9 +++++++++
 2 files changed, 19 insertions(+), 5 deletions(-)

diff --git a/builtin-add.c b/builtin-add.c
index 3dd4ded..e79e8f7 100644
--- a/builtin-add.c
+++ b/builtin-add.c
@@ -12,6 +12,7 @@
 #include "diffcore.h"
 #include "commit.h"
 #include "revision.h"
+#include "run-command.h"
 
 static const char builtin_add_usage[] =
 "git-add [-n] [-v] [-f] [--interactive | -i] [-u] [--] <filepattern>...";
@@ -153,6 +154,13 @@ static int git_add_config(const char *var, const char *value)
 	return git_default_config(var, value);
 }
 
+int interactive_add(void)
+{
+	const char *argv[2] = { "add--interactive", NULL };
+
+	return run_command_v_opt(argv, RUN_GIT_CMD);
+}
+
 static struct lock_file lock_file;
 
 static const char ignore_warning[] =
@@ -172,12 +180,9 @@ int cmd_add(int argc, const char **argv, const char *prefix)
 			add_interactive++;
 	}
 	if (add_interactive) {
-		const char *args[] = { "add--interactive", NULL };
-
-		if (add_interactive != 1 || argc != 2)
+		if (argc != 2)
 			die("add --interactive does not take any parameters");
-		execv_git_cmd(args);
-		exit(1);
+		exit(interactive_add());
 	}
 
 	git_config(git_add_config);
diff --git a/commit.h b/commit.h
index 467872e..64e1d4b 100644
--- a/commit.h
+++ b/commit.h
@@ -122,4 +122,13 @@ extern struct commit_list *get_shallow_commits(struct object_array *heads,
 		int depth, int shallow_flag, int not_shallow_flag);
 
 int in_merge_bases(struct commit *, struct commit **, int);
+
+extern const unsigned char *
+create_commit(const unsigned char *tree_sha1,
+	      unsigned char parent_sha1[][20], int parents,
+	      const char *author_info, const char *committer_info,
+	      const char *message, int length);
+
+extern int interactive_add(void);
+
 #endif /* COMMIT_H */
-- 
1.5.2.GIT

^ permalink raw reply related

* [PATCH 5/9] Introduce strbuf_read_fd().
From: Kristian Høgsberg @ 2007-09-06  0:23 UTC (permalink / raw)
  To: git; +Cc: Kristian Høgsberg
In-Reply-To: <11890382253220-git-send-email-krh@redhat.com>

This function reads from a given fd into a strbuf until end of file.

Signed-off-by: Kristian Høgsberg <krh@redhat.com>
---
 strbuf.c |   19 +++++++++++++++++++
 strbuf.h |    1 +
 2 files changed, 20 insertions(+), 0 deletions(-)

diff --git a/strbuf.c b/strbuf.c
index 2805c11..fcfc05e 100644
--- a/strbuf.c
+++ b/strbuf.c
@@ -50,6 +50,25 @@ void read_line(struct strbuf *sb, FILE *fp, int term) {
 	strbuf_end(sb);
 }
 
+int strbuf_read_fd(struct strbuf *sb, int fd)
+{
+	int len, total = 0;
+
+	do {
+		strbuf_grow(sb, 1024);
+		len = xread(fd, sb->buf + sb->len, sb->alloc - sb->len);
+		if (len > 0) {
+			total += len;
+			sb->len += len;
+		}
+	} while (len > 0);
+
+	if (len < 0)
+		return len;
+
+	return total;
+}
+
 void strbuf_printf(struct strbuf *sb, const char *fmt, ...)
 {
 	char buffer[2048];
diff --git a/strbuf.h b/strbuf.h
index 1e5d09e..6e630ea 100644
--- a/strbuf.h
+++ b/strbuf.h
@@ -12,5 +12,6 @@ extern void read_line(struct strbuf *, FILE *, int);
 extern void strbuf_add(struct strbuf *sb, const char *data, size_t len);
 extern void strbuf_add_char(struct strbuf *sb, int ch);
 extern void strbuf_printf(struct strbuf *sb, const char *fmt, ...);
+extern int strbuf_read_fd(struct strbuf *sb, int fd);
 
 #endif /* STRBUF_H */
-- 
1.5.2.GIT

^ permalink raw reply related

* [PATCH 3/9] Add strbuf_printf() to do formatted printing to a strbuf.
From: Kristian Høgsberg @ 2007-09-06  0:23 UTC (permalink / raw)
  To: git; +Cc: Kristian Høgsberg
In-Reply-To: <11890382242333-git-send-email-krh@redhat.com>

Also, expose strbuf_add() and strbuf_add_char() to add raw data to the buffer.

Signed-off-by: Kristian Høgsberg <krh@redhat.com>
---
 strbuf.c |   69 +++++++++++++++++++++++++++++++++++++++++++++++++++++++------
 strbuf.h |    3 ++
 2 files changed, 65 insertions(+), 7 deletions(-)

diff --git a/strbuf.c b/strbuf.c
index e33d06b..2805c11 100644
--- a/strbuf.c
+++ b/strbuf.c
@@ -11,16 +11,26 @@ static void strbuf_begin(struct strbuf *sb) {
 	strbuf_init(sb);
 }
 
-static void inline strbuf_add(struct strbuf *sb, int ch) {
-	if (sb->alloc <= sb->len) {
-		sb->alloc = sb->alloc * 3 / 2 + 16;
-		sb->buf = xrealloc(sb->buf, sb->alloc);
-	}
+static void inline strbuf_grow(struct strbuf *sb, size_t extra)
+{
+	ALLOC_GROW(sb->buf, sb->len + extra, sb->alloc);
+}
+
+void strbuf_add(struct strbuf *sb, const char *data, size_t len)
+{
+	strbuf_grow(sb, len);
+	memcpy(sb->buf + sb->len, data, len);
+	sb->len += len;
+}
+
+void strbuf_add_char(struct strbuf *sb, int ch)
+{
+	strbuf_grow(sb, 1);
 	sb->buf[sb->len++] = ch;
 }
 
 static void strbuf_end(struct strbuf *sb) {
-	strbuf_add(sb, 0);
+	strbuf_add_char(sb, 0);
 }
 
 void read_line(struct strbuf *sb, FILE *fp, int term) {
@@ -33,9 +43,54 @@ void read_line(struct strbuf *sb, FILE *fp, int term) {
 	while ((ch = fgetc(fp)) != EOF) {
 		if (ch == term)
 			break;
-		strbuf_add(sb, ch);
+		strbuf_add_char(sb, ch);
 	}
 	if (ch == EOF && sb->len == 0)
 		sb->eof = 1;
 	strbuf_end(sb);
 }
+
+void strbuf_printf(struct strbuf *sb, const char *fmt, ...)
+{
+	char buffer[2048];
+	va_list args;
+	int len, size = 2 * sizeof buffer;
+
+	va_start(args, fmt);
+	len = vsnprintf(buffer, sizeof(buffer), fmt, args);
+	va_end(args);
+
+	if (len > sizeof(buffer)) {
+		/*
+		 * Didn't fit in the buffer, but this vsnprintf at
+		 * least gives us the required length back.  Grow the
+		 * buffer acccordingly and try again.
+		 */
+		strbuf_grow(sb, len);
+		va_start(args, fmt);
+		len = vsnprintf(sb->buf + sb->len,
+				sb->alloc - sb->len, fmt, args);
+		va_end(args);
+	} else if (len >= 0) {
+		/*
+		 * The initial vsnprintf fit in the temp buffer, just
+		 * copy it to the strbuf.
+		 */
+		strbuf_add(sb, buffer, len);
+	} else {
+		/*
+		 * This vnsprintf sucks and just returns -1 when the
+		 * buffer is too small.  Keep doubling the size until
+		 * it fits.
+		 */
+		while (len < 0) {
+			strbuf_grow(sb, size);
+			va_start(args, fmt);
+			len = vsnprintf(sb->buf + sb->len,
+					sb->alloc - sb->len, fmt, args);
+			va_end(args);
+			size *= 2;
+		}
+	}
+}
+
diff --git a/strbuf.h b/strbuf.h
index 74cc012..1e5d09e 100644
--- a/strbuf.h
+++ b/strbuf.h
@@ -9,5 +9,8 @@ struct strbuf {
 
 extern void strbuf_init(struct strbuf *);
 extern void read_line(struct strbuf *, FILE *, int);
+extern void strbuf_add(struct strbuf *sb, const char *data, size_t len);
+extern void strbuf_add_char(struct strbuf *sb, int ch);
+extern void strbuf_printf(struct strbuf *sb, const char *fmt, ...);
 
 #endif /* STRBUF_H */
-- 
1.5.2.GIT

^ permalink raw reply related

* [PATCH 2/9] Enable wt-status to run against non-standard index file.
From: Kristian Høgsberg @ 2007-09-06  0:23 UTC (permalink / raw)
  To: git; +Cc: Kristian Høgsberg
In-Reply-To: <11890382183913-git-send-email-krh@redhat.com>

We still default to get_index_file(), but this can be overridden
by setting wt_status.index_file after calling wt_status_prepare().

Signed-off-by: Kristian Høgsberg <krh@redhat.com>
---
 wt-status.c |    3 ++-
 wt-status.h |    1 +
 2 files changed, 3 insertions(+), 1 deletions(-)

diff --git a/wt-status.c b/wt-status.c
index 65a7259..0cf9b81 100644
--- a/wt-status.c
+++ b/wt-status.c
@@ -53,6 +53,7 @@ void wt_status_prepare(struct wt_status *s)
 	s->branch = head ? xstrdup(head) : NULL;
 	s->reference = "HEAD";
 	s->fp = stdout;
+	s->index_file = get_index_file();
 }
 
 static void wt_status_print_cached_header(struct wt_status *s)
@@ -198,7 +199,7 @@ static void wt_status_print_changed_cb(struct diff_queue_struct *q,
 static void wt_read_cache(struct wt_status *s)
 {
 	discard_cache();
-	read_cache();
+	read_cache_from(s->index_file);
 }
 
 static void wt_status_print_initial(struct wt_status *s)
diff --git a/wt-status.h b/wt-status.h
index 4f3a615..7744932 100644
--- a/wt-status.h
+++ b/wt-status.h
@@ -21,6 +21,7 @@ struct wt_status {
 	int commitable;
 	int workdir_dirty;
 	int workdir_untracked;
+	const char *index_file;
 	FILE *fp;
 };
 
-- 
1.5.2.GIT

^ permalink raw reply related

* [PATCH 1/9] Enable wt-status output to a given FILE pointer.
From: Kristian Høgsberg @ 2007-09-06  0:23 UTC (permalink / raw)
  To: git; +Cc: Kristian Høgsberg

Still defaults to stdout, but you can now override wt_status.fp after
calling wt_status_prepare().

Signed-off-by: Kristian Høgsberg <krh@redhat.com>
---
 color.c     |   18 ++++++------
 color.h     |    4 +-
 wt-status.c |   85 ++++++++++++++++++++++++++++++----------------------------
 wt-status.h |    3 ++
 4 files changed, 58 insertions(+), 52 deletions(-)

diff --git a/color.c b/color.c
index 09d82ee..124ba33 100644
--- a/color.c
+++ b/color.c
@@ -135,39 +135,39 @@ int git_config_colorbool(const char *var, const char *value)
 	return git_config_bool(var, value);
 }
 
-static int color_vprintf(const char *color, const char *fmt,
+static int color_vfprintf(FILE *fp, const char *color, const char *fmt,
 		va_list args, const char *trail)
 {
 	int r = 0;
 
 	if (*color)
-		r += printf("%s", color);
-	r += vprintf(fmt, args);
+		r += fprintf(fp, "%s", color);
+	r += vfprintf(fp, fmt, args);
 	if (*color)
-		r += printf("%s", COLOR_RESET);
+		r += fprintf(fp, "%s", COLOR_RESET);
 	if (trail)
-		r += printf("%s", trail);
+		r += fprintf(fp, "%s", trail);
 	return r;
 }
 
 
 
-int color_printf(const char *color, const char *fmt, ...)
+int color_fprintf(FILE *fp, const char *color, const char *fmt, ...)
 {
 	va_list args;
 	int r;
 	va_start(args, fmt);
-	r = color_vprintf(color, fmt, args, NULL);
+	r = color_vfprintf(fp, color, fmt, args, NULL);
 	va_end(args);
 	return r;
 }
 
-int color_printf_ln(const char *color, const char *fmt, ...)
+int color_fprintf_ln(FILE *fp, const char *color, const char *fmt, ...)
 {
 	va_list args;
 	int r;
 	va_start(args, fmt);
-	r = color_vprintf(color, fmt, args, "\n");
+	r = color_vfprintf(fp, color, fmt, args, "\n");
 	va_end(args);
 	return r;
 }
diff --git a/color.h b/color.h
index 88bb8ff..6809800 100644
--- a/color.h
+++ b/color.h
@@ -6,7 +6,7 @@
 
 int git_config_colorbool(const char *var, const char *value);
 void color_parse(const char *var, const char *value, char *dst);
-int color_printf(const char *color, const char *fmt, ...);
-int color_printf_ln(const char *color, const char *fmt, ...);
+int color_fprintf(FILE *fp, const char *color, const char *fmt, ...);
+int color_fprintf_ln(FILE *fp, const char *color, const char *fmt, ...);
 
 #endif /* COLOR_H */
diff --git a/wt-status.c b/wt-status.c
index 5205420..65a7259 100644
--- a/wt-status.c
+++ b/wt-status.c
@@ -52,31 +52,33 @@ void wt_status_prepare(struct wt_status *s)
 	head = resolve_ref("HEAD", sha1, 0, NULL);
 	s->branch = head ? xstrdup(head) : NULL;
 	s->reference = "HEAD";
+	s->fp = stdout;
 }
 
-static void wt_status_print_cached_header(const char *reference)
+static void wt_status_print_cached_header(struct wt_status *s)
 {
 	const char *c = color(WT_STATUS_HEADER);
-	color_printf_ln(c, "# Changes to be committed:");
-	if (reference) {
-		color_printf_ln(c, "#   (use \"git reset %s <file>...\" to unstage)", reference);
+	color_fprintf_ln(s->fp, c, "# Changes to be committed:");
+	if (s->reference) {
+		color_fprintf_ln(s->fp, c, "#   (use \"git reset %s <file>...\" to unstage)", s->reference);
 	} else {
-		color_printf_ln(c, "#   (use \"git rm --cached <file>...\" to unstage)");
+		color_fprintf_ln(s->fp, c, "#   (use \"git rm --cached <file>...\" to unstage)");
 	}
-	color_printf_ln(c, "#");
+	color_fprintf_ln(s->fp, c, "#");
 }
 
-static void wt_status_print_header(const char *main, const char *sub)
+static void wt_status_print_header(struct wt_status *s,
+				   const char *main, const char *sub)
 {
 	const char *c = color(WT_STATUS_HEADER);
-	color_printf_ln(c, "# %s:", main);
-	color_printf_ln(c, "#   (%s)", sub);
-	color_printf_ln(c, "#");
+	color_fprintf_ln(s->fp, c, "# %s:", main);
+	color_fprintf_ln(s->fp, c, "#   (%s)", sub);
+	color_fprintf_ln(s->fp, c, "#");
 }
 
-static void wt_status_print_trailer(void)
+static void wt_status_print_trailer(struct wt_status *s)
 {
-	color_printf_ln(color(WT_STATUS_HEADER), "#");
+	color_fprintf_ln(s->fp, color(WT_STATUS_HEADER), "#");
 }
 
 static const char *quote_crlf(const char *in, char *buf, size_t sz)
@@ -108,7 +110,8 @@ static const char *quote_crlf(const char *in, char *buf, size_t sz)
 	return ret;
 }
 
-static void wt_status_print_filepair(int t, struct diff_filepair *p)
+static void wt_status_print_filepair(struct wt_status *s,
+				     int t, struct diff_filepair *p)
 {
 	const char *c = color(t);
 	const char *one, *two;
@@ -117,36 +120,36 @@ static void wt_status_print_filepair(int t, struct diff_filepair *p)
 	one = quote_crlf(p->one->path, onebuf, sizeof(onebuf));
 	two = quote_crlf(p->two->path, twobuf, sizeof(twobuf));
 
-	color_printf(color(WT_STATUS_HEADER), "#\t");
+	color_fprintf(s->fp, color(WT_STATUS_HEADER), "#\t");
 	switch (p->status) {
 	case DIFF_STATUS_ADDED:
-		color_printf(c, "new file:   %s", one);
+		color_fprintf(s->fp, c, "new file:   %s", one);
 		break;
 	case DIFF_STATUS_COPIED:
-		color_printf(c, "copied:     %s -> %s", one, two);
+		color_fprintf(s->fp, c, "copied:     %s -> %s", one, two);
 		break;
 	case DIFF_STATUS_DELETED:
-		color_printf(c, "deleted:    %s", one);
+		color_fprintf(s->fp, c, "deleted:    %s", one);
 		break;
 	case DIFF_STATUS_MODIFIED:
-		color_printf(c, "modified:   %s", one);
+		color_fprintf(s->fp, c, "modified:   %s", one);
 		break;
 	case DIFF_STATUS_RENAMED:
-		color_printf(c, "renamed:    %s -> %s", one, two);
+		color_fprintf(s->fp, c, "renamed:    %s -> %s", one, two);
 		break;
 	case DIFF_STATUS_TYPE_CHANGED:
-		color_printf(c, "typechange: %s", one);
+		color_fprintf(s->fp, c, "typechange: %s", one);
 		break;
 	case DIFF_STATUS_UNKNOWN:
-		color_printf(c, "unknown:    %s", one);
+		color_fprintf(s->fp, c, "unknown:    %s", one);
 		break;
 	case DIFF_STATUS_UNMERGED:
-		color_printf(c, "unmerged:   %s", one);
+		color_fprintf(s->fp, c, "unmerged:   %s", one);
 		break;
 	default:
 		die("bug: unhandled diff status %c", p->status);
 	}
-	printf("\n");
+	fprintf(s->fp, "\n");
 }
 
 static void wt_status_print_updated_cb(struct diff_queue_struct *q,
@@ -160,14 +163,14 @@ static void wt_status_print_updated_cb(struct diff_queue_struct *q,
 		if (q->queue[i]->status == 'U')
 			continue;
 		if (!shown_header) {
-			wt_status_print_cached_header(s->reference);
+			wt_status_print_cached_header(s);
 			s->commitable = 1;
 			shown_header = 1;
 		}
-		wt_status_print_filepair(WT_STATUS_UPDATED, q->queue[i]);
+		wt_status_print_filepair(s, WT_STATUS_UPDATED, q->queue[i]);
 	}
 	if (shown_header)
-		wt_status_print_trailer();
+		wt_status_print_trailer(s);
 }
 
 static void wt_status_print_changed_cb(struct diff_queue_struct *q,
@@ -184,12 +187,12 @@ static void wt_status_print_changed_cb(struct diff_queue_struct *q,
 				msg = use_add_rm_msg;
 				break;
 			}
-		wt_status_print_header("Changed but not updated", msg);
+		wt_status_print_header(s, "Changed but not updated", msg);
 	}
 	for (i = 0; i < q->nr; i++)
-		wt_status_print_filepair(WT_STATUS_CHANGED, q->queue[i]);
+		wt_status_print_filepair(s, WT_STATUS_CHANGED, q->queue[i]);
 	if (q->nr)
-		wt_status_print_trailer();
+		wt_status_print_trailer(s);
 }
 
 static void wt_read_cache(struct wt_status *s)
@@ -206,16 +209,16 @@ static void wt_status_print_initial(struct wt_status *s)
 	wt_read_cache(s);
 	if (active_nr) {
 		s->commitable = 1;
-		wt_status_print_cached_header(NULL);
+		wt_status_print_cached_header(s);
 	}
 	for (i = 0; i < active_nr; i++) {
-		color_printf(color(WT_STATUS_HEADER), "#\t");
-		color_printf_ln(color(WT_STATUS_UPDATED), "new file: %s",
+		color_fprintf(s->fp, color(WT_STATUS_HEADER), "#\t");
+		color_fprintf_ln(s->fp, color(WT_STATUS_UPDATED), "new file: %s",
 				quote_crlf(active_cache[i]->name,
 					   buf, sizeof(buf)));
 	}
 	if (active_nr)
-		wt_status_print_trailer();
+		wt_status_print_trailer(s);
 }
 
 static void wt_status_print_updated(struct wt_status *s)
@@ -281,12 +284,12 @@ static void wt_status_print_untracked(struct wt_status *s)
 		}
 		if (!shown_header) {
 			s->workdir_untracked = 1;
-			wt_status_print_header("Untracked files",
+			wt_status_print_header(s, "Untracked files",
 					       use_add_to_include_msg);
 			shown_header = 1;
 		}
-		color_printf(color(WT_STATUS_HEADER), "#\t");
-		color_printf_ln(color(WT_STATUS_UNTRACKED), "%.*s",
+		color_fprintf(s->fp, color(WT_STATUS_HEADER), "#\t");
+		color_fprintf_ln(s->fp, color(WT_STATUS_UNTRACKED), "%.*s",
 				ent->len, ent->name);
 	}
 }
@@ -316,14 +319,14 @@ void wt_status_print(struct wt_status *s)
 			branch_name = "";
 			on_what = "Not currently on any branch.";
 		}
-		color_printf_ln(color(WT_STATUS_HEADER),
+		color_fprintf_ln(s->fp, color(WT_STATUS_HEADER),
 			"# %s%s", on_what, branch_name);
 	}
 
 	if (s->is_initial) {
-		color_printf_ln(color(WT_STATUS_HEADER), "#");
-		color_printf_ln(color(WT_STATUS_HEADER), "# Initial commit");
-		color_printf_ln(color(WT_STATUS_HEADER), "#");
+		color_fprintf_ln(s->fp, color(WT_STATUS_HEADER), "#");
+		color_fprintf_ln(s->fp, color(WT_STATUS_HEADER), "# Initial commit");
+		color_fprintf_ln(s->fp, color(WT_STATUS_HEADER), "#");
 		wt_status_print_initial(s);
 	}
 	else {
@@ -337,7 +340,7 @@ void wt_status_print(struct wt_status *s)
 		wt_status_print_verbose(s);
 	if (!s->commitable) {
 		if (s->amend)
-			printf("# No changes\n");
+			fprintf(s->fp, "# No changes\n");
 		else if (s->workdir_dirty)
 			printf("no changes added to commit (use \"git add\" and/or \"git commit -a\")\n");
 		else if (s->workdir_untracked)
diff --git a/wt-status.h b/wt-status.h
index cfea4ae..4f3a615 100644
--- a/wt-status.h
+++ b/wt-status.h
@@ -1,6 +1,8 @@
 #ifndef STATUS_H
 #define STATUS_H
 
+#include <stdio.h>
+
 enum color_wt_status {
 	WT_STATUS_HEADER,
 	WT_STATUS_UPDATED,
@@ -19,6 +21,7 @@ struct wt_status {
 	int commitable;
 	int workdir_dirty;
 	int workdir_untracked;
+	FILE *fp;
 };
 
 int git_status_config(const char *var, const char *value);
-- 
1.5.2.GIT

^ permalink raw reply related

* Re: People unaware of the importance of "git gc"?
From: Carlos Rica @ 2007-09-06  0:27 UTC (permalink / raw)
  To: Junio C Hamano
  Cc: Nicolas Pitre, Nix, Steven Grimm, Linus Torvalds,
	Git Mailing List
In-Reply-To: <7v3axshe6q.fsf@gitster.siamese.dyndns.org>

2007/9/6, Junio C Hamano <gitster@pobox.com>:
> Nicolas Pitre <nico@cam.org> writes:
> > The more I think of it, the less I like automatic repack.  There is
> > always a bad case for it somewhere.
>
> I tend to agree, but at the same time, I think the long term
> goal should be not to have bad cases.

The best solution is make "git gc" unnecessary.
At the long term, and without loss of efficiency.

^ permalink raw reply

* Re: [PATCH 3/3] Makefile: Add cache-tree.h to the public headers list
From: Junio C Hamano @ 2007-09-06  0:16 UTC (permalink / raw)
  To: Dmitry V. Levin; +Cc: Git Mailing List
In-Reply-To: <20070905232251.GC331@nomad.office.altlinux.org>

"Dmitry V. Levin" <ldv@altlinux.org> writes:

> Some external projects (e.g. parsecvs) need cache-tree.h file.

Your patch shows that our Makefile has been lacking a necessary
dependency for a long time.  LIB_H is not "public headers list",
but more like "the headers everybody depends on", and the header
file should have been on that list.  Thanks.

I am however not convinced it is a good idea to treat libgit.a
as if it is a library.  It is not a library in the usual sense
of the word.  Originally we did libgit.a primarily so that we do
not have to list all the *.o dependencies in the Makefile out of
laziness ;-)

^ permalink raw reply

* Re: People unaware of the importance of "git gc"?
From: Jeff King @ 2007-09-05 23:56 UTC (permalink / raw)
  To: Matthieu Moy; +Cc: Git Mailing List
In-Reply-To: <vpq1wddkohr.fsf@bauges.imag.fr>

On Wed, Sep 05, 2007 at 07:31:44PM +0200, Matthieu Moy wrote:

> I have ~/teaching/some-course/.git (well, almost) and ~/etc/.git which
> are two unrelated projects, and to "git gc" both of them, I need
> either a script, or two manual invocations.
>
> (yes, I'm really talking about something trivial)

I tend to have a lot of small projects, so I have on the order of 80 git
repositories on each machine I use, most of which have a 'mothership'
origin on a central, backed-up machine.

When I sit down to work, I want to see which repositories
have changes that need to be pulled. And when I get up to leave, I want
to see which repositories have changes that need to be pushed. Not to
mention files that need committed, loose objects that need packed, etc.

So I wrote the 'git-stale' script, included below. It's not especially
user-friendly, but you might find it useful, as it solves the exact
problem you are talking about (and much more).

It reads 'repository specifications' from ~/.gitstale, one per line,
which are either of the form:

  /path/to/repo

which specifies a repo to check, or:

  r:/path/to/many/repos

which specifies a hierarchy in which to recursively find repos.

My .gitstale looks something like this:

  /home/peff/compile/git
  /home/peff/compile/tig
  r:/home/peff/work

and I get output something like this (edited for brevity):

Checking (1/77) /home/peff/compile/git...
Checking (2/77) /home/peff/compile/tig...
[...]
Checking (77/77) /home/peff/work/foo...
MERGE:next /home/peff/compile/git
COMMIT: /home/peff/work/foo
PACK: /home/peff/work/foo
PUSH:master /home/peff/work/bar

which translates to:
  - the git repo has commits in 'origin/next' that are not in 'next'
    (and you might want to merge them in)
  - there are uncommitted files in 'foo'
  - 'foo' needs packing
  - in the 'bar' repo there are commits in master that are not in origin
    (and you might want to push)

Hopefully it will be useful to you, though I think it is probably too
specific to my workflow to be part of git.

-Peff

-- >8 --
#!/usr/bin/perl

use strict;
use Getopt::Long;

my $CONFIG_FILE = "$ENV{HOME}/.gitstale";

my $nofetch = $ENV{GITSTALE_NOFETCH};
Getopt::Long::Configure(qw(bundling));
GetOptions('nofetch|n!' => \$nofetch) or exit 100;

my @projects = process_spec(@ARGV ? @ARGV : cat($CONFIG_FILE));

my $n = 1;
my $total = @projects;
my %errors;
foreach my $p (@projects) {
  print "Checking ($n/$total) $p...\n";
  $errors{$p} = [check_git($p)];
  $n++;
}

my $errcount;
foreach my $p (@projects) {
  foreach my $e (@{$errors{$p}}) {
    print "$e: $p\n";
  }
}

exit $errcount ? 1 : 0;

sub cat {
  my $fn = shift;
  open(my $fh, '<', $fn)
    or die "unable to open $fn: $!\n";
  return map { chomp; length($_) ? $_ : () } <$fh>;
}

sub process_spec {
  my @dirs;
  my @roots;
  my @exclude;

  foreach (@_) {
    if(/^r:(.*)/) { push @roots, $1 }
    elsif(/^d:(.*)/) { push @dirs, $1 }
    elsif(/^-(.*)/) { push @exclude, qr#(^|/)$1($|/)# }
    else { push @dirs, $_ }
  }

  use File::Find;
  find({
      no_chdir => 1,
      preprocess => sub { sort @_ },
      wanted => sub {
        return unless -d $_ && $_ =~ m#/.git$#;
        foreach my $e (@exclude) { return if $_ =~ $e }
        my $d = $_;
        $d =~ s#/\.git$##;
        push @dirs, $d;
      }
    }, @roots) if @roots;
  return @dirs;
}

sub count_zero {
  open(my $fh, '-|', @_) or die "unable to fork: $!\n";
  my $line = <$fh>;
  return length($line) == 0;
}

sub check_git {
  my $d = shift;

  chdir($d) or return 'CHDIR';

  my @r;
  count_zero(qw(
        git-ls-files -m -o -d --exclude-per-directory=.gitignore
        --directory --no-empty-directory
  )) or push @r, 'COMMIT';

  if(has_origin()) {
    push @r, 'FETCH' if !$nofetch && system('git-fetch');

    foreach my $p (branch_pairs()) {
      count_zero('git-rev-list', "$p->[0]..$p->[1]")
        or push @r, "MERGE:$p->[0]";
      count_zero('git-rev-list', "$p->[1]..$p->[0]")
        or push @r, "PUSH:$p->[0]";
    }
  }
  else {
    push @r, 'ORIGIN';
  }

  push @r, 'PACK' if unpacked_objects() > 1000;

  return @r;
}

sub unpacked_objects {
  my $objects = `git-count-objects`;
  $objects =~ /^(\d+)/;
  return $1;
}

sub branch_pairs {
  my %config;
  foreach my $line (`git-repo-config --get-regexp 'branch..*..*'`) {
    $line =~ m#^branch\.([^.]+)\.([^ ]+) (?:refs/heads/)?(.*)#
      or die "confusing git-repo-config output: $line\n";
    $config{$1}{$2} = $3;
  }

  return [qw(master origin)] if -e '.git/refs/heads/origin';

  return
    (-e '.git/refs/heads/origin' ? [qw(master origin)] : ()),
    map {
      $config{$_}{remote} && $config{$_}{merge} ?
        [$_, $config{$_}{remote} . '/' . $config{$_}{merge}] :
        ()
    } sort keys(%config);
}

sub has_origin {
  return
    -e '.git/branches/origin' ||
    -e '.git/remotes/origin' ||
    !count_zero(qw(git-repo-config --get remote.origin.url));
}
__END__

^ permalink raw reply

* [PATCH 1/3] git-gui/Makefile: Replace libdir with gitgui_libdir
From: Dmitry V. Levin @ 2007-09-05 23:21 UTC (permalink / raw)
  To: Git Mailing List; +Cc: Junio C Hamano

On GNU/Linux, libdir is used to mean "/usr/lib or /usr/lib64"
depending on architecture.  Different libdir meaning breaks
idiomatic expressions like rpm specfile "make libdir=%_libdir".

Signed-off-by: Dmitry V. Levin <ldv@altlinux.org>
---
 git-gui/Makefile |   16 ++++++++--------
 1 files changed, 8 insertions(+), 8 deletions(-)

diff --git a/git-gui/Makefile b/git-gui/Makefile
index 1bac6fe..f143b2c 100644
--- a/git-gui/Makefile
+++ b/git-gui/Makefile
@@ -76,8 +76,8 @@ SHELL_PATH_SQ = $(subst ','\'',$(SHELL_PATH))
 TCL_PATH_SQ = $(subst ','\'',$(TCL_PATH))
 TCLTK_PATH_SQ = $(subst ','\'',$(TCLTK_PATH))
 
-libdir   ?= $(sharedir)/git-gui/lib
-libdir_SQ = $(subst ','\'',$(libdir))
+gitgui_libdir   ?= $(sharedir)/git-gui/lib
+gitgui_libdir_SQ = $(subst ','\'',$(gitgui_libdir))
 
 exedir    = $(dir $(gitexecdir))share/git-gui/lib
 exedir_SQ = $(subst ','\'',$(exedir))
@@ -85,7 +85,7 @@ exedir_SQ = $(subst ','\'',$(exedir))
 $(patsubst %.sh,%,$(SCRIPT_SH)) : % : %.sh
 	$(QUIET_GEN)rm -f $@ $@+ && \
 	GITGUI_RELATIVE= && \
-	if test '$(exedir_SQ)' = '$(libdir_SQ)'; then \
+	if test '$(exedir_SQ)' = '$(gitgui_libdir_SQ)'; then \
 		if test "$(uname_O)" = Cygwin; \
 		then GITGUI_RELATIVE= ; \
 		else GITGUI_RELATIVE=1; \
@@ -95,7 +95,7 @@ $(patsubst %.sh,%,$(SCRIPT_SH)) : % : %.sh
 		-e 's|^ exec wish "$$0"| exec $(subst |,'\|',$(TCLTK_PATH_SQ)) "$$0"|' \
 		-e 's/@@GITGUI_VERSION@@/$(GITGUI_VERSION)/g' \
 		-e 's|@@GITGUI_RELATIVE@@|'$$GITGUI_RELATIVE'|' \
-		-e $$GITGUI_RELATIVE's|@@GITGUI_LIBDIR@@|$(libdir_SQ)|' \
+		-e $$GITGUI_RELATIVE's|@@GITGUI_LIBDIR@@|$(gitgui_libdir_SQ)|' \
 		$@.sh >$@+ && \
 	chmod +x $@+ && \
 	mv $@+ $@
@@ -126,7 +126,7 @@ TRACK_VARS = \
 	$(subst ','\'',TCL_PATH='$(TCL_PATH_SQ)') \
 	$(subst ','\'',TCLTK_PATH='$(TCLTK_PATH_SQ)') \
 	$(subst ','\'',gitexecdir='$(gitexecdir_SQ)') \
-	$(subst ','\'',libdir='$(libdir_SQ)') \
+	$(subst ','\'',gitgui_libdir='$(gitgui_libdir_SQ)') \
 #end TRACK_VARS
 
 GIT-GUI-VARS: .FORCE-GIT-GUI-VARS
@@ -142,9 +142,9 @@ install: all
 	$(QUIET)$(INSTALL_D0)'$(DESTDIR_SQ)$(gitexecdir_SQ)' $(INSTALL_D1)
 	$(QUIET)$(INSTALL_X0)git-gui $(INSTALL_X1) '$(DESTDIR_SQ)$(gitexecdir_SQ)'
 	$(QUIET)$(foreach p,$(GITGUI_BUILT_INS), $(INSTALL_L0)'$(DESTDIR_SQ)$(gitexecdir_SQ)/$p' $(INSTALL_L1)'$(DESTDIR_SQ)$(gitexecdir_SQ)/git-gui' $(INSTALL_L2)'$(DESTDIR_SQ)$(gitexecdir_SQ)/$p' $(INSTALL_L3) &&) true
-	$(QUIET)$(INSTALL_D0)'$(DESTDIR_SQ)$(libdir_SQ)' $(INSTALL_D1)
-	$(QUIET)$(INSTALL_R0)lib/tclIndex $(INSTALL_R1) '$(DESTDIR_SQ)$(libdir_SQ)'
-	$(QUIET)$(foreach p,$(ALL_LIBFILES), $(INSTALL_R0)$p $(INSTALL_R1) '$(DESTDIR_SQ)$(libdir_SQ)' &&) true
+	$(QUIET)$(INSTALL_D0)'$(DESTDIR_SQ)$(gitgui_libdir_SQ)' $(INSTALL_D1)
+	$(QUIET)$(INSTALL_R0)lib/tclIndex $(INSTALL_R1) '$(DESTDIR_SQ)$(gitgui_libdir_SQ)'
+	$(QUIET)$(foreach p,$(ALL_LIBFILES), $(INSTALL_R0)$p $(INSTALL_R1) '$(DESTDIR_SQ)$(gitgui_libdir_SQ)' &&) true
 
 dist-version:
 	@mkdir -p $(TARDIR)
-- 
ldv

^ permalink raw reply related

* [PATCH 2/3] Makefile: Add install-lib and install-include targets
From: Dmitry V. Levin @ 2007-09-05 23:22 UTC (permalink / raw)
  To: Git Mailing List; +Cc: Junio C Hamano

Several external projects (e.g. parsecvs) need libgit library
and related header files.

Signed-off-by: Dmitry V. Levin <ldv@altlinux.org>
---
 Makefile |   11 +++++++++++
 1 files changed, 11 insertions(+), 0 deletions(-)

diff --git a/Makefile b/Makefile
index 51af531..d50e30b 100644
--- a/Makefile
+++ b/Makefile
@@ -143,6 +143,8 @@ STRIP ?= strip
 
 prefix = $(HOME)
 bindir = $(prefix)/bin
+libdir = $(prefix)/lib
+includedir = $(prefix)/include
 gitexecdir = $(bindir)
 sharedir = $(prefix)/share
 template_dir = $(sharedir)/git-core/templates
@@ -702,6 +704,8 @@ ETC_GITCONFIG_SQ = $(subst ','\'',$(ETC_GITCONFIG))
 
 DESTDIR_SQ = $(subst ','\'',$(DESTDIR))
 bindir_SQ = $(subst ','\'',$(bindir))
+libdir_SQ = $(subst ','\'',$(libdir))
+includedir_SQ = $(subst ','\'',$(includedir))
 gitexecdir_SQ = $(subst ','\'',$(gitexecdir))
 template_dir_SQ = $(subst ','\'',$(template_dir))
 prefix_SQ = $(subst ','\'',$(prefix))
@@ -1017,6 +1021,13 @@ install-info:
 quick-install-doc:
 	$(MAKE) -C Documentation quick-install
 
+install-lib: $(LIB_FILE)
+	$(INSTALL) -d -m755 '$(DESTDIR_SQ)$(libdir_SQ)'
+	$(INSTALL) -p -m644 $(LIB_FILE) '$(DESTDIR_SQ)$(libdir_SQ)/'
+
+install-include: $(LIB_H)
+	$(INSTALL) -d -m755 '$(DESTDIR_SQ)$(includedir_SQ)/git'
+	$(INSTALL) -p -m644 $(LIB_H) '$(DESTDIR_SQ)$(includedir_SQ)/git/'
 
 
 ### Maintainer's dist rules
-- 
ldv

^ permalink raw reply related

* [PATCH 3/3] Makefile: Add cache-tree.h to the public headers list
From: Dmitry V. Levin @ 2007-09-05 23:22 UTC (permalink / raw)
  To: Git Mailing List; +Cc: Junio C Hamano

Some external projects (e.g. parsecvs) need cache-tree.h file.

Signed-off-by: Dmitry V. Levin <ldv@altlinux.org>
---
 Makefile |    2 +-
 1 files changed, 1 insertions(+), 1 deletions(-)

diff --git a/Makefile b/Makefile
index d50e30b..2b04829 100644
--- a/Makefile
+++ b/Makefile
@@ -283,7 +283,7 @@ LIB_FILE=libgit.a
 XDIFF_LIB=xdiff/lib.a
 
 LIB_H = \
-	archive.h blob.h cache.h commit.h csum-file.h delta.h grep.h \
+	archive.h blob.h cache.h cache-tree.h commit.h csum-file.h delta.h grep.h \
 	diff.h object.h pack.h pkt-line.h quote.h refs.h list-objects.h sideband.h \
 	run-command.h strbuf.h tag.h tree.h git-compat-util.h revision.h \
 	tree-walk.h log-tree.h dir.h path-list.h unpack-trees.h builtin.h \
-- 
ldv

^ permalink raw reply related


This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox