Git development
 help / color / mirror / Atom feed
* Equivalent of `svn switch` for git-svn?
From: Adam Roben @ 2007-09-27  6:41 UTC (permalink / raw)
  To: git

Hi all-
   I've recently been informed that the Subversion server I and several 
others have been tracking with git-svn will be switching from using the 
svn+ssh scheme to the http scheme. To handle this, users of svn will be 
running `svn switch` to move their working copies to the new repository 
URL. Is there some way to do the same for git-svn? I suspect the biggest 
complication will come from the git-svn-id: lines in the commit logs, 
since changing that line would require changing the commit hash as well.

   Thanks for any advice!

-Adam

^ permalink raw reply

* Re: git-svn and branches
From: Sam Vilain @ 2007-09-27  6:36 UTC (permalink / raw)
  To: Steven Walter; +Cc: git, normalperson
In-Reply-To: <20070927021252.GA23777@dervierte>

Steven Walter wrote:
> Knowing just enough of what git-svn is doing to be dangerous, I
> whipped up a short little patch.  This patch seems to work for the
> common case, and avoids fetching every file from subversion.  It does
> break sometimes, however, and I don't understand why.
>
> Maybe someone with a better grasps of the code can see what I did
> wrong, or suggest a better means to my end?

Try also with the SVN trunk - the do_switch API has recently been added
and it also avoids this excess checkout.  I'm not sure why a solution
like you post isn't used, perhaps Eric can comment further.

Sam.

> diff --git a/git-svn.perl b/git-svn.perl
> index 484b057..1bc92b6 100755
> --- a/git-svn.perl
> +++ b/git-svn.perl
> @@ -1848,9 +1848,10 @@ sub find_parent_branch {
>                                               $self->full_url, $ed)
>                           or die "SVN connection failed somewhere...\n";
>                 } else {
> +                       $self->assert_index_clean($parent);
>                         print STDERR "Following parent with do_update\n";
>                         $ed = SVN::Git::Fetcher->new($self);
> -                       $self->ra->gs_do_update($rev, $rev, $self, $ed)
> +                       $self->ra->gs_do_update($rev, $r0, $self, $ed)
>                           or die "SVN connection failed somewhere...\n";
>                 }
>                 print STDERR "Successfully followed parent\n";

^ permalink raw reply

* Re: grafts not appearing in manual pages
From: Johannes Sixt @ 2007-09-27  6:22 UTC (permalink / raw)
  To: J. Bruce Fields; +Cc: Mike Hommey, git
In-Reply-To: <20070926210102.GF26099@fieldses.org>

J. Bruce Fields schrieb:
> On Wed, Sep 26, 2007 at 10:24:41PM +0200, Mike Hommey wrote:
>> Hi,
>>
>> The only occurrence of grafts in the manual pages is in the
>> git-filter-branch one. I somehow feel this is wrong not to see it
>> described more "formally" in the manual pages.
>>
>> I wouldn't mind writing a small something, except I have no idea what
>> would be the most appropriate place to talk about it... Does anyone have
>> such an idea ?
> 
> It could go in Documentation/user-manual.txt, but I don't know where.

IMHO grafts should not be made known to a wide audience until send-pack, 
pack-objects, and prune are fixed so that you cannot corrupt your repository 
when there are grafts.

See http://thread.gmane.org/gmane.comp.version-control.git/37744
in particular http://article.gmane.org/gmane.comp.version-control.git/37866
on a sketch how to fix the issues.

-- Hannes

^ permalink raw reply

* Use of strbuf.buf when strbuf.len == 0
From: Junio C Hamano @ 2007-09-27  6:21 UTC (permalink / raw)
  To: git

I am starting to hate some parts of the strbuf API.

Here is an example.  Can you spot what goes wrong?

static int handle_file(const char *path,
	 unsigned char *sha1, const char *output)
{
	SHA_CTX ctx;
	char buf[1024];
	int hunk = 0, hunk_no = 0;
	struct strbuf one, two;
	...
	if (sha1)
		SHA1_Init(&ctx);

	strbuf_init(&one, 0);
	strbuf_init(&two,  0);
	while (fgets(buf, sizeof(buf), f)) {
		if (!prefixcmp(buf, "<<<<<<< "))
			hunk = 1;
		else if (!prefixcmp(buf, "======="))
			hunk = 2;
		else if (!prefixcmp(buf, ">>>>>>> ")) {
			int cmp = strbuf_cmp(&one, &two);

			hunk_no++;
			hunk = 0;
			if (cmp > 0) {
				strbuf_swap(&one, &two);
			}
			if (out) {
				fputs("<<<<<<<\n", out);
				fwrite(one.buf, one.len, 1, out);
				fputs("=======\n", out);
				fwrite(two.buf, two.len, 1, out);
				fputs(">>>>>>>\n", out);
			}
			if (sha1) {
				SHA1_Update(&ctx, one.buf, one.len + 1);
				SHA1_Update(&ctx, two.buf, two.len + 1);
			}
			strbuf_reset(&one);
			strbuf_reset(&two);
		} else if (hunk == 1)
			strbuf_addstr(&one, buf);
		else if (hunk == 2)
			strbuf_addstr(&two, buf);
		else if (out)
			fputs(buf, out);
	}
	strbuf_release(&one);
	strbuf_release(&two);
	...
}

When one or two are empty and the caller asked for checksumming,
the code still relies on one.buf being an allocated memory with
an extra NUL termination and tries to feed the NULL pointer to
SHA1_Update() with length of 1!

An obvious workaround is to say "strbuf_init(&one, !!sha1)" to
force the allocation.  However, because the second parameter to
strbuf_init() is defined to be merely a hint, we should not rely
on strbuf_init() with non-zero hint to have allocation from the
beginning.  It is assuming too much.

A more defensive way would be for the user to do something like:

	SHA1_Update(&ctx, one.buf ? one.buf : "", one.len + 1);
	SHA1_Update(&ctx, two.buf ? two.buf : "", two.len + 1);

which I am leaning towards, but this looks ugly.

I was already bitten by another bug in strbuf_setlen() that
shared the same roots; an empty strbuf can have two internal
representations ("alloc == 0 && buf == NULL" vs "alloc != 0 &&
buf != NULL") and they behave differently.

For example, this happens to be Ok but the validity of it is
subtle.  If a or b is empty, memcmp may get a NULL pointer but
we ask only 0 byte comparison.

        int strbuf_cmp(struct strbuf *a, struct strbuf *b)
        {
                int cmp;
                if (a->len < b->len) {
                        cmp = memcmp(a->buf, b->buf, a->len);
                        return cmp ? cmp : -1;
                } else {
                        cmp = memcmp(a->buf, b->buf, b->len);
                        return cmp ? cmp : a->len != b->len;
                }
        }

It might be an easier and safer fix to define that strbuf_init()
to always have allocation.  Use of a strbuf in the code _and_
not adding any contents to the buffer should be an exception and
avoiding malloc()/free() for that special case feels optimizing
for the wrong case.

However, there are strbuf instances that are not initialized
(i.e. in BSS or initialized by declaring with STRBUF_INIT), so
we still need to handle (.len == 0 && .alloc == 0) case
specially anyway.

It would be appreciated if somebody with a fresh pair of eyes
can go over the strbuf series one more time to make sure that we
do not try to blindly use buf.buf, assuming buf.buf[0] is NUL if
(buf.len == 0).

^ permalink raw reply

* Re: What's cooking in git.git (topics)
From: David Kastrup @ 2007-09-27  6:08 UTC (permalink / raw)
  To: Jeff King; +Cc: Junio C Hamano, git
In-Reply-To: <20070927023633.GA28902@coredump.intra.peff.net>

Jeff King <peff@peff.net> writes:

> On Wed, Sep 26, 2007 at 01:05:59PM -0700, Junio C Hamano wrote:
>
>> * jk/diff-rename (Tue Sep 25 15:29:42 2007 -0400) 1 commit
>>  + diffcore-rename: cache file deltas
>> 
>> Parked in 'next' for now but is 'master' material.
>
> My tests after this patch show that spanhash_find is responsible for
> a large portion of the processing time in large renames, so I am going
> to look into speeding that up.

In itself, it does not look like there is all too much room for
optimization.  One can remove the temporary pointer "optimization" and
see whether this makes strength reduction possible for the compiler.
Making this an endless loop wrapped around a loop on bucket might also
help the compiler in that effect.

But there is really not all too much leeway, and it might be better
spent in the caller.  For example, the search will take something like
r/(1-r) iterations on average where r is the fill ratio of the hash
array.  So one would not want to, say, let r grow above 0.75 or
something like that.

-- 
David Kastrup, Kriemhildstr. 15, 44793 Bochum

^ permalink raw reply

* [StGit PATCH] Let "stg assimilate" handle missing patches
From: Karl Hasselström @ 2007-09-27  5:50 UTC (permalink / raw)
  To: Catalin Marinas; +Cc: git
In-Reply-To: <20070927054833.GA12207@diana.vm.bytemark.co.uk>

If a patch was not mentioned in the applied/unapplied files,
"assimilate" used to ignore it. Now it won't. The only information
loss ensuing from this sequence of commands

  $ rm .git/patches/<branch>/{un,}applied
  $ touch .git/patches/<branch>/{un,}applied
  $ stg assimilate

is now the order of the unapplied patches.

Signed-off-by: Karl Hasselström <kha@treskal.com>

---

 stgit/commands/assimilate.py |   32 ++++++++++++++++++++------------
 1 files changed, 20 insertions(+), 12 deletions(-)


diff --git a/stgit/commands/assimilate.py b/stgit/commands/assimilate.py
index ab2264a..43672fd 100644
--- a/stgit/commands/assimilate.py
+++ b/stgit/commands/assimilate.py
@@ -90,7 +90,7 @@ def read_commit_dag(branch):
     for line in Run('git-show-ref').output_lines():
         id, ref = line.split()
         m = re.match(r'^refs/patches/%s/(.+)$' % branch, ref)
-        if m:
+        if m and not m.group(1).endswith('.log'):
             c = commits[id]
             c.patch = m.group(1)
             patches.add(c)
@@ -173,17 +173,25 @@ def func(parser, options, args):
 
     # Write the applied/unapplied files.
     out.start('Checking patch appliedness')
+    unapplied = patches - set(applied)
     applied_name_set = set(p.patch for p in applied)
-    unapplied_names = []
-    for name in orig_applied:
-        if not name in applied_name_set:
-            out.info('%s is now unapplied' % name)
-            unapplied_names.append(name)
-    for name in orig_unapplied:
-        if name in applied_name_set:
-            out.info('%s is now applied' % name)
-        else:
-            unapplied_names.append(name)
+    unapplied_name_set = set(p.patch for p in unapplied)
+    patches_name_set = set(p.patch for p in patches)
+    orig_patches = orig_applied + orig_unapplied
+    orig_applied_name_set = set(orig_applied)
+    orig_unapplied_name_set = set(orig_unapplied)
+    orig_patches_name_set = set(orig_patches)
+    for name in orig_patches_name_set - patches_name_set:
+        out.info('%s is gone' % name)
+    for name in applied_name_set - orig_applied_name_set:
+        out.info('%s is now applied' % name)
+    for name in unapplied_name_set - orig_unapplied_name_set:
+        out.info('%s is now unapplied' % name)
+    orig_order = dict(zip(orig_patches, xrange(len(orig_patches))))
+    def patchname_cmp(p1, p2):
+        i1 = orig_order.get(p1, len(orig_order))
+        i2 = orig_order.get(p2, len(orig_order))
+        return cmp((i1, p1), (i2, p2))
     crt_series.set_applied(p.patch for p in applied)
-    crt_series.set_unapplied(unapplied_names)
+    crt_series.set_unapplied(sorted(unapplied_name_set, cmp = patchname_cmp))
     out.done()

^ permalink raw reply related

* Re: [StGit PATCH 0/2] "stg assimilate" on steroids
From: Karl Hasselström @ 2007-09-27  5:48 UTC (permalink / raw)
  To: Catalin Marinas; +Cc: git
In-Reply-To: <20070926020911.1202.2580.stgit@yoghurt>

On 2007-09-26 04:15:02 +0200, Karl Hasselström wrote:

> If you have been using that, manually change the stack format
> version back to 2, and create empty "applied" and "unapplied" files,
> and run the new assimilate. That should fix it. I hope.

Not quite. You need this fix first.

-- 
Karl Hasselström, kha@treskal.com
      www.treskal.com/kalle

^ permalink raw reply

* Re: History over-simplification
From: Junio C Hamano @ 2007-09-27  5:34 UTC (permalink / raw)
  To: linux; +Cc: git, spearce
In-Reply-To: <20070927045640.31040.qmail@science.horizon.com>

linux@horizon.com writes:

> These seem like roundabout ways of expressing the requirement.
> The desired rule is "no silent changes in the simplified history".
> E.g. if the revision history of a particular file is:
>
>       +-C-+
>      /     \
> A---B       B---D
>      \     /
>       +-B-+
>
> Then the normal code will notice that there are no changes on the
> lower branch, prune the merge entirely, and simplify to A---B---C---D.
> This is, however, misleading; the true history is A---B---C---B---D.

Do you denote the same content with the same letter in the
above?

If so, wouldn't the simplified history be just A-B-D?

^ permalink raw reply

* Re: History over-simplification
From: linux @ 2007-09-27  4:56 UTC (permalink / raw)
  To: git, spearce; +Cc: gitster, linux

>>  b) The resulting path matches one of the parents but not one of
>>     the others.  In such a case the merge should still be output if
>>     a 3-way read-tree would not have chosen this result by default.

> I am not sure (b) is useful in general.  Merging two branches
> that fix the same issue but in different ways (think: 'maint'
> and 'master' have different infrastructure and a fix initially
> made on 'master' was backported to 'maint', and then later
> 'maint' needed to be merged to 'master' to carry forward other
> fixes) is a norm, and in such cases taking the version from the
> merged-to branch is almost always what happens.
>
> Also it sounds to me by "if read-tree would not have chosen this
> result by default" you mean this feature would not just need to
> run merge-base but also recursive merge-base synthesis, and also
> recreate the structural merge (aka "rename detection") there as
> well.  Even if (b) is useful, it sounds like a very expensive
> option, and the current merge-recursive code is structured in
> such a way to be easily reused for this purpose.

These seem like roundabout ways of expressing the requirement.
The desired rule is "no silent changes in the simplified history".
E.g. if the revision history of a particular file is:

      +-C-+
     /     \
A---B       B---D
     \     /
      +-B-+

Then the normal code will notice that there are no changes on the
lower branch, prune the merge entirely, and simplify to A---B---C---D.
This is, however, misleading; the true history is A---B---C---B---D.
A merge must be shown unless it matches a *non-eliminated* ancestor.

The point is that this isn't expressed in terms of what merge-base would
do, but in terms of what the path limiter is in the process of doing.


Now, I haven't dived into the Deep Magic of revision.c to figure out
where to put this into the code, unfortunately.


Another way to say it is that the desired simplified history
is achieved when you have exhausted the following two rules:
- You may delete any revision which has only one ancestor and
  is identical (after path-limiting) to that ancestor.
  (Descendants are implicitly linked to that ancestor.)
- You may delete any ancestor link A---B if there is an
  alternative directed path between A and B.
  (Fast-forward rule.)
Given unlabeled ancestor links, there is a unique fixed point.

The current code is willing to delete a revision that is
identical to *any* ancestor, even deleted ones.

^ permalink raw reply

* Re: [PATCH] Add --no-rename to git-apply
From: Junio C Hamano @ 2007-09-27  5:12 UTC (permalink / raw)
  To: Robin Rosenberg; +Cc: git
In-Reply-To: <11908420041596-git-send-email-robin.rosenberg@dewire.com>

Robin Rosenberg <robin.rosenberg@dewire.com> writes:

> With this option git-apply can apply a patch with a rename
> onto the original file(s).

This is troubling from both design and implementation point of
view.

 * Why would this be useful?  What's the point of producing the
   renaming patch if you know you would want to apply while
   ignoring the rename?

 * The change looks too special purpose to me.  If you are
   giving the ability to deposit the result to somewhere other
   than where the patch intendes to, why limit it only to the
   preimage name?  Aren't there cases where A is renamed to B
   sometime in the history, and you have a patch that talks
   about the content change A->A but the tree you have has the
   contents already in B, and you would want to apply that
   patch?  It feels that this and your "ignore rename" could be
   handled much more cleanly and flexibly by preprocessing the
   patchfile.

 * By disabling the parsing of rename header lines, you are
   disabling the sanity checking of the input done in
   gitdiff_verify_name() called from gitdiff_oldname() and
   gitdiff_newname().  I think it is wrong for --no-rename
   option to affect the parsing of the input.  If we were to do
   this, perhaps write_out_results() or one of its callee would
   be a better place to do so.

^ permalink raw reply

* [PATCH 4/4] Move launch_editor() and stripspace() to new file editor.c.
From: Kristian Høgsberg @ 2007-09-27  4:50 UTC (permalink / raw)
  To: gitster; +Cc: git, Kristian Høgsberg
In-Reply-To: <1190868632-29287-3-git-send-email-krh@redhat.com>

Signed-off-by: Kristian Høgsberg <krh@redhat.com>
---
 Makefile             |    2 +-
 builtin-commit.c     |    1 +
 builtin-stripspace.c |   68 +------------------------------
 builtin-tag.c        |   40 +------------------
 editor.c             |  109 ++++++++++++++++++++++++++++++++++++++++++++++++++
 editor.h             |    9 ++++
 strbuf.h             |    3 -
 7 files changed, 122 insertions(+), 110 deletions(-)
 create mode 100644 editor.c
 create mode 100644 editor.h

diff --git a/Makefile b/Makefile
index 6172589..f6d991e 100644
--- a/Makefile
+++ b/Makefile
@@ -310,7 +310,7 @@ LIB_OBJS = \
 	alloc.o merge-file.o path-list.o help.o unpack-trees.o $(DIFF_OBJS) \
 	color.o wt-status.o archive-zip.o archive-tar.o shallow.o utf8.o \
 	convert.o attr.o decorate.o progress.o mailmap.o symlinks.o remote.o \
-	transport.o bundle.o parse-options.o
+	transport.o bundle.o parse-options.o editor.o
 
 BUILTIN_OBJS = \
 	builtin-add.o \
diff --git a/builtin-commit.c b/builtin-commit.c
index 69e8b19..7d87812 100644
--- a/builtin-commit.c
+++ b/builtin-commit.c
@@ -19,6 +19,7 @@
 #include "strbuf.h"
 #include "utf8.h"
 #include "parse-options.h"
+#include "editor.h"
 
 static const char builtin_commit_usage[] =
 	"[-a | --interactive] [-s] [-v] [--no-verify] [-m <message> | -F <logfile> | (-C|-c) <commit> | --amend] [-u] [-e] [--author <author>] [--template <file>] [[-i | -o] <path>...]";
diff --git a/builtin-stripspace.c b/builtin-stripspace.c
index c0b2130..4e5bb07 100644
--- a/builtin-stripspace.c
+++ b/builtin-stripspace.c
@@ -1,72 +1,6 @@
 #include "builtin.h"
 #include "cache.h"
-
-/*
- * Returns the length of a line, without trailing spaces.
- *
- * If the line ends with newline, it will be removed too.
- */
-static size_t cleanup(char *line, size_t len)
-{
-	while (len) {
-		unsigned char c = line[len - 1];
-		if (!isspace(c))
-			break;
-		len--;
-	}
-
-	return len;
-}
-
-/*
- * Remove empty lines from the beginning and end
- * and also trailing spaces from every line.
- *
- * Note that the buffer will not be NUL-terminated.
- *
- * Turn multiple consecutive empty lines between paragraphs
- * into just one empty line.
- *
- * If the input has only empty lines and spaces,
- * no output will be produced.
- *
- * If last line does not have a newline at the end, one is added.
- *
- * Enable skip_comments to skip every line starting with "#".
- */
-void stripspace(struct strbuf *sb, int skip_comments)
-{
-	int empties = 0;
-	size_t i, j, len, newlen;
-	char *eol;
-
-	/* We may have to add a newline. */
-	strbuf_grow(sb, 1);
-
-	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 && sb->buf[i] == '#') {
-			newlen = 0;
-			continue;
-		}
-		newlen = cleanup(sb->buf + i, len);
-
-		/* Not just an empty line? */
-		if (newlen) {
-			if (empties > 0 && j > 0)
-				sb->buf[j++] = '\n';
-			empties = 0;
-			memmove(sb->buf + j, sb->buf + i, newlen);
-			sb->buf[newlen + j++] = '\n';
-		} else {
-			empties++;
-		}
-	}
-
-	strbuf_setlen(sb, j);
-}
+#include "editor.h"
 
 int cmd_stripspace(int argc, const char **argv, const char *prefix)
 {
diff --git a/builtin-tag.c b/builtin-tag.c
index 0a36a5d..c9a0497 100644
--- a/builtin-tag.c
+++ b/builtin-tag.c
@@ -10,6 +10,7 @@
 #include "builtin.h"
 #include "refs.h"
 #include "tag.h"
+#include "editor.h"
 #include "run-command.h"
 
 static const char builtin_tag_usage[] =
@@ -17,45 +18,6 @@ static const char builtin_tag_usage[] =
 
 static char signingkey[1000];
 
-void launch_editor(const char *path, struct strbuf *buffer)
-{
-	const char *editor, *terminal;
-	struct child_process child;
-	const char *args[3];
-
-	editor = getenv("GIT_EDITOR");
-	if (!editor && editor_program)
-		editor = editor_program;
-	if (!editor)
-		editor = getenv("VISUAL");
-	if (!editor)
-		editor = getenv("EDITOR");
-
-	terminal = getenv("TERM");
-	if (!editor && (!terminal || !strcmp(terminal, "dumb"))) {
-		fprintf(stderr,
-		"Terminal is dumb but no VISUAL nor EDITOR defined.\n"
-		"Please supply the message using either -m or -F option.\n");
-		exit(1);
-	}
-
-	if (!editor)
-		editor = "vi";
-
-	memset(&child, 0, sizeof(child));
-	child.argv = args;
-	args[0] = editor;
-	args[1] = path;
-	args[2] = NULL;
-
-	if (run_command(&child))
-		die("There was a problem with the editor %s.", editor);
-
-	if (strbuf_read_file(buffer, path) < 0)
-		die("could not read message file '%s': %s",
-		    path, strerror(errno));
-}
-
 struct tag_filter {
 	const char *pattern;
 	int lines;
diff --git a/editor.c b/editor.c
new file mode 100644
index 0000000..6bc3033
--- /dev/null
+++ b/editor.c
@@ -0,0 +1,109 @@
+#include "git-compat-util.h"
+#include "editor.h"
+#include "run-command.h"
+
+void launch_editor(const char *path, struct strbuf *buffer)
+{
+	const char *editor, *terminal;
+	struct child_process child;
+	const char *args[3];
+
+	editor = getenv("GIT_EDITOR");
+	if (!editor && editor_program)
+		editor = editor_program;
+	if (!editor)
+		editor = getenv("VISUAL");
+	if (!editor)
+		editor = getenv("EDITOR");
+
+	terminal = getenv("TERM");
+	if (!editor && (!terminal || !strcmp(terminal, "dumb"))) {
+		fprintf(stderr,
+		"Terminal is dumb but no VISUAL nor EDITOR defined.\n"
+		"Please supply the message using either -m or -F option.\n");
+		exit(1);
+	}
+
+	if (!editor)
+		editor = "vi";
+
+	memset(&child, 0, sizeof(child));
+	child.argv = args;
+	args[0] = editor;
+	args[1] = path;
+	args[2] = NULL;
+
+	if (run_command(&child))
+		die("There was a problem with the editor %s.", editor);
+
+	if (strbuf_read_file(buffer, path) < 0)
+		die("could not read message file '%s': %s",
+		    path, strerror(errno));
+}
+
+/*
+ * Returns the length of a line, without trailing spaces.
+ *
+ * If the line ends with newline, it will be removed too.
+ */
+static size_t cleanup(char *line, size_t len)
+{
+	while (len) {
+		unsigned char c = line[len - 1];
+		if (!isspace(c))
+			break;
+		len--;
+	}
+
+	return len;
+}
+
+/*
+ * Remove empty lines from the beginning and end
+ * and also trailing spaces from every line.
+ *
+ * Note that the buffer will not be NUL-terminated.
+ *
+ * Turn multiple consecutive empty lines between paragraphs
+ * into just one empty line.
+ *
+ * If the input has only empty lines and spaces,
+ * no output will be produced.
+ *
+ * If last line does not have a newline at the end, one is added.
+ *
+ * Enable skip_comments to skip every line starting with "#".
+ */
+void stripspace(struct strbuf *sb, int skip_comments)
+{
+	int empties = 0;
+	size_t i, j, len, newlen;
+	char *eol;
+
+	/* We may have to add a newline. */
+	strbuf_grow(sb, 1);
+
+	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 && sb->buf[i] == '#') {
+			newlen = 0;
+			continue;
+		}
+		newlen = cleanup(sb->buf + i, len);
+
+		/* Not just an empty line? */
+		if (newlen) {
+			if (empties > 0 && j > 0)
+				sb->buf[j++] = '\n';
+			empties = 0;
+			memmove(sb->buf + j, sb->buf + i, newlen);
+			sb->buf[newlen + j++] = '\n';
+		} else {
+			empties++;
+		}
+	}
+
+	strbuf_setlen(sb, j);
+}
diff --git a/editor.h b/editor.h
new file mode 100644
index 0000000..9dc39f5
--- /dev/null
+++ b/editor.h
@@ -0,0 +1,9 @@
+#ifndef EDITOR_H
+#define EDITOR_H
+
+#include "cache.h"
+
+extern void launch_editor(const char *path, struct strbuf *buffer);
+extern void stripspace(struct strbuf *sb, int skip_comments);
+
+#endif
diff --git a/strbuf.h b/strbuf.h
index eef4e6d..d4d9e56 100644
--- a/strbuf.h
+++ b/strbuf.h
@@ -112,7 +112,4 @@ extern int strbuf_read_file(struct strbuf *sb, const char *path);
 
 extern int strbuf_getline(struct strbuf *, FILE *, int);
 
-extern void stripspace(struct strbuf *buf, int skip_comments);
-extern void launch_editor(const char *path, struct strbuf *buffer);
-
 #endif /* STRBUF_H */
-- 
1.5.2.GIT

^ permalink raw reply related

* [PATCH 3/4] Implement git commit as a builtin command.
From: Kristian Høgsberg @ 2007-09-27  4:50 UTC (permalink / raw)
  To: gitster; +Cc: git, Kristian Høgsberg
In-Reply-To: <1190868632-29287-2-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               |  611 +++++++++++++++++++++++++++++++++++++++
 builtin-tag.c                  |    3 +-
 builtin.h                      |    3 +-
 contrib/examples/git-commit.sh |  627 ++++++++++++++++++++++++++++++++++++++++
 git-commit.sh                  |  627 ----------------------------------------
 git.c                          |    3 +-
 strbuf.h                       |    1 +
 t/t3501-revert-cherry-pick.sh  |    4 +-
 t/t3901-i18n-patch.sh          |    8 +-
 t/test-lib.sh                  |    4 +-
 11 files changed, 1255 insertions(+), 645 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 d90e959..6172589 100644
--- a/Makefile
+++ b/Makefile
@@ -206,7 +206,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-ls-remote.sh \
 	git-merge-one-file.sh git-mergetool.sh git-parse-remote.sh \
 	git-pull.sh git-rebase.sh git-rebase--interactive.sh \
@@ -254,7 +254,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
@@ -324,6 +324,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 \
@@ -362,7 +363,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 \
@@ -822,9 +822,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..69e8b19
--- /dev/null
+++ b/builtin-commit.c
@@ -0,0 +1,611 @@
+/*
+ * 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 "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"
+#include "parse-options.h"
+
+static const char builtin_commit_usage[] =
+	"[-a | --interactive] [-s] [-v] [--no-verify] [-m <message> | -F <logfile> | (-C|-c) <commit> | --amend] [-u] [-e] [--author <author>] [--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;
+
+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_BOOLEAN, "all", 'a', &all },
+	{ OPTION_STRING, "author", 0, (void *) &force_author },
+	{ OPTION_BOOLEAN, "edit", 'e', &edit_flag },
+	{ OPTION_BOOLEAN, "include", 'i', &also },
+	{ OPTION_BOOLEAN, "interactive", 0, &interactive },
+	{ OPTION_BOOLEAN, "only", 'o', &only },
+	{ OPTION_STRING, "message", 'm', &message },
+	{ OPTION_BOOLEAN, "no-verify", 'n', &no_verify },
+	{ OPTION_BOOLEAN, "amend", 0, &amend },
+	{ OPTION_STRING, "reedit-message", 'c', &edit_message },
+	{ OPTION_STRING, "reuse-message", 'C', &use_message },
+	{ OPTION_BOOLEAN, "signoff", 's', &signoff },
+	{ OPTION_BOOLEAN, "quiet", 'q', &quiet },
+	{ OPTION_BOOLEAN, "verbose", 'v', &verbose },
+	{ OPTION_BOOLEAN, "untracked-files", 0, &untracked_files },
+	{ OPTION_STRING, "template", 't', &template_file },
+	{ OPTION_LAST },
+};
+
+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(0, prefix, files);
+		if (write_cache(fd, active_cache, active_nr) || close(fd))
+			die("unable to write new index file");
+		return lock_file.filename;
+	} else if (also) {
+		add_files_to_cache(fd, prefix, files);
+		if (write_cache(fd, active_cache, active_nr) || close(fd))
+			die("unable to write new index file");
+		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(0, prefix, files);
+	if (write_cache(fd, active_cache, active_nr) || close(fd))
+		die("unable to write new index file");
+
+	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(0, prefix, files);
+	if (write_cache(fd, active_cache, active_nr) || close(fd))
+		die("unable to write new index file");
+
+	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, 0);
+	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(&sb, 0, 0) < 0)
+			die("could not read log from standard input");
+	} else if (logfile) {
+		if (strbuf_read_file(&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_file(&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_file(&sb, git_path("SQUASH_MSG")) < 0)
+			die("could not read SQUASH_MSG: %s", strerror(errno));
+	} else if (!stat(template_file, &statbuf)) {
+		if (strbuf_read_file(&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_addch(&sb, '\0');
+		bol = strrchr(sb.buf + sb.len - 1, '\n');
+		if (!bol || prefixcmp(bol, sign_off_header))
+			fprintf(fp, "\n");
+		fprintf(fp, "%s%s\n", sign_off_header, 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)
+{
+	struct strbuf tmpl;
+	const char *nl;
+	int eol, i;
+
+	/* See if the template is just a prefix of the message. */
+	strbuf_init(&tmpl, 0);
+	if (template_file && strbuf_read_file(&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(sign_off_header) <= eol - i &&
+		    !prefixcmp(sb->buf + i, sign_off_header)) {
+			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_addf(sb, "author %s\n",
+			    fmt_ident(name, email,
+				      getenv("GIT_AUTHOR_DATE"), 1));
+		free(name);
+		free(email);
+	} else {
+		strbuf_addf(sb, "author %s\n", git_author_info(1));
+	}
+}
+
+static void parse_and_validate_options(const char ***argv)
+{
+	int f = 0;
+
+	(*argv)++;
+	while (parse_options(argv, commit_options, ARRAY_SIZE(commit_options),
+			     builtin_commit_usage))
+		;
+
+	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";
+		const char *out_enc;
+		char *enc, *end;
+		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) && !in_merge) {
+		run_status(stdout, index_file);
+		unlink(commit_editmsg);
+		return 1;
+	}
+
+	strbuf_init(&sb, 0);
+
+	/* 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_addf(&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_addf(&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_addf(&sb, "parent %s\n", sha1_to_hex(head_sha1));
+		strbuf_init(&m, 0);
+		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 (strbuf_getline(&m, fp, '\n') != EOF)
+			strbuf_addf(&sb, "parent %s\n", m.buf);
+		fclose(fp);
+		strbuf_release(&m);
+	} else {
+		reflog_msg = "commit";
+		strbuf_addf(&sb, "parent %s\n", sha1_to_hex(head_sha1));
+	}
+
+	determine_author_info(&sb);
+	strbuf_addf(&sb, "committer %s\n", git_committer_info(1));
+	if (!is_encoding_utf8(git_commit_encoding))
+		strbuf_addf(&sb, "encoding %s\n", git_commit_encoding);
+	strbuf_addch(&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_file(&sb, git_path(commit_editmsg)) < 0)
+		die("could not read commit message\n");
+	if (run_hook(index_file, "commit-msg", commit_editmsg))
+		exit(1);
+	stripspace(&sb, 1);
+	if (sb.len < header_len ||
+	    message_is_empty(&sb, header_len))
+		die("* no commit message?  aborting commit.");
+	strbuf_addch(&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_addf(&sb, "%s: %s\n", reflog_msg, header_line);
+	strbuf_addch(&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-tag.c b/builtin-tag.c
index a1a2cf0..0a36a5d 100644
--- a/builtin-tag.c
+++ b/builtin-tag.c
@@ -17,12 +17,11 @@ static const char builtin_tag_usage[] =
 
 static char signingkey[1000];
 
-static void launch_editor(const char *path, struct strbuf *buffer)
+void launch_editor(const char *path, struct strbuf *buffer)
 {
 	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/builtin.h b/builtin.h
index 65cc0fb..470a788 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);
@@ -67,10 +68,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..44ccc44
--- /dev/null
+++ b/contrib/examples/git-commit.sh
@@ -0,0 +1,627 @@
+#!/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
+
+	if test "$status_only" = "t" -o "$use_status_color" = "t"; then
+		color=
+	else
+		color=--nocolor
+	fi
+	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 test $# != 0
+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"
+		;;
+	-F*|-f*)
+		no_edit=t
+		log_given=t$log_given
+		logfile="${1#-[Ff]}"
+		;;
+	--F=*|--f=*|--fi=*|--fil=*|--file=*)
+		no_edit=t
+		log_given=t$log_given
+		logfile="${1#*=}"
+		;;
+	-a|--a|--al|--all)
+		all=t
+		;;
+	--au=*|--aut=*|--auth=*|--autho=*|--author=*)
+		force_author="${1#*=}"
+		;;
+	--au|--aut|--auth|--autho|--author)
+		case "$#" in 1) usage ;; esac
+		shift
+		force_author="$1"
+		;;
+	-e|--e|--ed|--edi|--edit)
+		edit_flag=t
+		;;
+	-i|--i|--in|--inc|--incl|--inclu|--includ|--include)
+		also=t
+		;;
+	--int|--inte|--inter|--intera|--interac|--interact|--interacti|\
+	--interactiv|--interactive)
+		interactive=t
+		;;
+	-o|--o|--on|--onl|--only)
+		only=t
+		;;
+	-m|--m|--me|--mes|--mess|--messa|--messag|--message)
+		case "$#" in 1) usage ;; esac
+		shift
+		log_given=m$log_given
+		log_message="${log_message:+${log_message}
+
+}$1"
+		no_edit=t
+		;;
+	-m*)
+		log_given=m$log_given
+		log_message="${log_message:+${log_message}
+
+}${1#-m}"
+		no_edit=t
+		;;
+	--m=*|--me=*|--mes=*|--mess=*|--messa=*|--messag=*|--message=*)
+		log_given=m$log_given
+		log_message="${log_message:+${log_message}
+
+}${1#*=}"
+		no_edit=t
+		;;
+	-n|--n|--no|--no-|--no-v|--no-ve|--no-ver|--no-veri|--no-verif|\
+	--no-verify)
+		verify=
+		;;
+	--a|--am|--ame|--amen|--amend)
+		amend=t
+		use_commit=HEAD
+		;;
+	-c)
+		case "$#" in 1) usage ;; esac
+		shift
+		log_given=t$log_given
+		use_commit="$1"
+		no_edit=
+		;;
+	--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="${1#*=}"
+		no_edit=
+		;;
+	--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=
+		;;
+	-C)
+		case "$#" in 1) usage ;; esac
+		shift
+		log_given=t$log_given
+		use_commit="$1"
+		no_edit=t
+		;;
+	--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="${1#*=}"
+		no_edit=t
+		;;
+	--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
+		;;
+	-s|--s|--si|--sig|--sign|--signo|--signof|--signoff)
+		signoff=t
+		;;
+	-t|--t|--te|--tem|--temp|--templ|--templa|--templat|--template)
+		case "$#" in 1) usage ;; esac
+		shift
+		templatefile="$1"
+		no_edit=
+		;;
+	-q|--q|--qu|--qui|--quie|--quiet)
+		quiet=t
+		;;
+	-v|--v|--ve|--ver|--verb|--verbo|--verbos|--verbose)
+		verbose=t
+		;;
+	-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
+		break
+		;;
+	-*)
+		usage
+		;;
+	*)
+		break
+		;;
+	esac
+	shift
+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$$"
+		W=
+		test -z "$initial_commit" && W=--with-tree=HEAD
+		commit_only=`git ls-files --error-unmatch $W -- "$@"` || 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 --add --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
+    GIT_INDEX_FILE="${TMP_INDEX:-${USE_INDEX}}" "$GIT_DIR"/hooks/pre-commit \
+    || 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" ]
+then
+	rm -f "$GIT_DIR/COMMIT_EDITMSG" "$GIT_DIR/SQUASH_MSG"
+	use_status_color=t
+	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
+	git gc --auto
+	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 44ccc44..0000000
--- a/git-commit.sh
+++ /dev/null
@@ -1,627 +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
-
-	if test "$status_only" = "t" -o "$use_status_color" = "t"; then
-		color=
-	else
-		color=--nocolor
-	fi
-	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 test $# != 0
-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"
-		;;
-	-F*|-f*)
-		no_edit=t
-		log_given=t$log_given
-		logfile="${1#-[Ff]}"
-		;;
-	--F=*|--f=*|--fi=*|--fil=*|--file=*)
-		no_edit=t
-		log_given=t$log_given
-		logfile="${1#*=}"
-		;;
-	-a|--a|--al|--all)
-		all=t
-		;;
-	--au=*|--aut=*|--auth=*|--autho=*|--author=*)
-		force_author="${1#*=}"
-		;;
-	--au|--aut|--auth|--autho|--author)
-		case "$#" in 1) usage ;; esac
-		shift
-		force_author="$1"
-		;;
-	-e|--e|--ed|--edi|--edit)
-		edit_flag=t
-		;;
-	-i|--i|--in|--inc|--incl|--inclu|--includ|--include)
-		also=t
-		;;
-	--int|--inte|--inter|--intera|--interac|--interact|--interacti|\
-	--interactiv|--interactive)
-		interactive=t
-		;;
-	-o|--o|--on|--onl|--only)
-		only=t
-		;;
-	-m|--m|--me|--mes|--mess|--messa|--messag|--message)
-		case "$#" in 1) usage ;; esac
-		shift
-		log_given=m$log_given
-		log_message="${log_message:+${log_message}
-
-}$1"
-		no_edit=t
-		;;
-	-m*)
-		log_given=m$log_given
-		log_message="${log_message:+${log_message}
-
-}${1#-m}"
-		no_edit=t
-		;;
-	--m=*|--me=*|--mes=*|--mess=*|--messa=*|--messag=*|--message=*)
-		log_given=m$log_given
-		log_message="${log_message:+${log_message}
-
-}${1#*=}"
-		no_edit=t
-		;;
-	-n|--n|--no|--no-|--no-v|--no-ve|--no-ver|--no-veri|--no-verif|\
-	--no-verify)
-		verify=
-		;;
-	--a|--am|--ame|--amen|--amend)
-		amend=t
-		use_commit=HEAD
-		;;
-	-c)
-		case "$#" in 1) usage ;; esac
-		shift
-		log_given=t$log_given
-		use_commit="$1"
-		no_edit=
-		;;
-	--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="${1#*=}"
-		no_edit=
-		;;
-	--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=
-		;;
-	-C)
-		case "$#" in 1) usage ;; esac
-		shift
-		log_given=t$log_given
-		use_commit="$1"
-		no_edit=t
-		;;
-	--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="${1#*=}"
-		no_edit=t
-		;;
-	--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
-		;;
-	-s|--s|--si|--sig|--sign|--signo|--signof|--signoff)
-		signoff=t
-		;;
-	-t|--t|--te|--tem|--temp|--templ|--templa|--templat|--template)
-		case "$#" in 1) usage ;; esac
-		shift
-		templatefile="$1"
-		no_edit=
-		;;
-	-q|--q|--qu|--qui|--quie|--quiet)
-		quiet=t
-		;;
-	-v|--v|--ve|--ver|--verb|--verbo|--verbos|--verbose)
-		verbose=t
-		;;
-	-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
-		break
-		;;
-	-*)
-		usage
-		;;
-	*)
-		break
-		;;
-	esac
-	shift
-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$$"
-		W=
-		test -z "$initial_commit" && W=--with-tree=HEAD
-		commit_only=`git ls-files --error-unmatch $W -- "$@"` || 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 --add --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
-    GIT_INDEX_FILE="${TMP_INDEX:-${USE_INDEX}}" "$GIT_DIR"/hooks/pre-commit \
-    || 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" ]
-then
-	rm -f "$GIT_DIR/COMMIT_EDITMSG" "$GIT_DIR/SQUASH_MSG"
-	use_status_color=t
-	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
-	git gc --auto
-	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 d7c6bca..9565555 100644
--- a/git.c
+++ b/git.c
@@ -320,6 +320,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 },
@@ -368,10 +369,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 },
diff --git a/strbuf.h b/strbuf.h
index 5657e3d..eef4e6d 100644
--- a/strbuf.h
+++ b/strbuf.h
@@ -113,5 +113,6 @@ extern int strbuf_read_file(struct strbuf *sb, const char *path);
 extern int strbuf_getline(struct strbuf *, FILE *, int);
 
 extern void stripspace(struct strbuf *buf, int skip_comments);
+extern void launch_editor(const char *path, struct strbuf *buffer);
 
 #endif /* STRBUF_H */
diff --git a/t/t3501-revert-cherry-pick.sh b/t/t3501-revert-cherry-pick.sh
index 552af1c..2dbe04f 100755
--- a/t/t3501-revert-cherry-pick.sh
+++ b/t/t3501-revert-cherry-pick.sh
@@ -44,7 +44,7 @@ test_expect_success setup '
 test_expect_success 'cherry-pick after renaming branch' '
 
 	git checkout rename2 &&
-	EDITOR=: VISUAL=: git cherry-pick added &&
+	git cherry-pick added &&
 	test -f opos &&
 	grep "Add extra line at the end" opos
 
@@ -53,7 +53,7 @@ test_expect_success 'cherry-pick after renaming branch' '
 test_expect_success 'revert after renaming branch' '
 
 	git checkout rename1 &&
-	EDITOR=: VISUAL=: git revert added &&
+	git revert added &&
 	test -f spoo &&
 	! grep "Add extra line at the end" spoo
 
diff --git a/t/t3901-i18n-patch.sh b/t/t3901-i18n-patch.sh
index 28e9e37..235f372 100755
--- a/t/t3901-i18n-patch.sh
+++ b/t/t3901-i18n-patch.sh
@@ -154,7 +154,7 @@ test_expect_success 'cherry-pick(U/U)' '
 	git reset --hard master &&
 	git cherry-pick side^ &&
 	git cherry-pick side &&
-	EDITOR=: VISUAL=: git revert HEAD &&
+	git revert HEAD &&
 
 	check_encoding 3
 '
@@ -169,7 +169,7 @@ test_expect_success 'cherry-pick(L/L)' '
 	git reset --hard master &&
 	git cherry-pick side^ &&
 	git cherry-pick side &&
-	EDITOR=: VISUAL=: git revert HEAD &&
+	git revert HEAD &&
 
 	check_encoding 3 8859
 '
@@ -184,7 +184,7 @@ test_expect_success 'cherry-pick(U/L)' '
 	git reset --hard master &&
 	git cherry-pick side^ &&
 	git cherry-pick side &&
-	EDITOR=: VISUAL=: git revert HEAD &&
+	git revert HEAD &&
 
 	check_encoding 3
 '
@@ -200,7 +200,7 @@ test_expect_success 'cherry-pick(L/U)' '
 	git reset --hard master &&
 	git cherry-pick side^ &&
 	git cherry-pick side &&
-	EDITOR=: VISUAL=: git revert HEAD &&
+	git revert HEAD &&
 
 	check_encoding 3 8859
 '
diff --git a/t/test-lib.sh b/t/test-lib.sh
index cc1253c..a232bd6 100644
--- a/t/test-lib.sh
+++ b/t/test-lib.sh
@@ -9,8 +9,8 @@ LC_ALL=C
 PAGER=cat
 TZ=UTC
 export LANG LC_ALL PAGER TZ
-EDITOR=:
-VISUAL=:
+EDITOR=/bin/true
+VISUAL=/bin/true
 unset GIT_EDITOR
 unset AUTHOR_DATE
 unset AUTHOR_EMAIL
-- 
1.5.2.GIT

^ permalink raw reply related

* [PATCH 2/4] This exports the update() function from builtin-add.c as
From: Kristian Høgsberg @ 2007-09-27  4:50 UTC (permalink / raw)
  To: gitster; +Cc: git, Kristian Høgsberg
In-Reply-To: <1190868632-29287-1-git-send-email-krh@redhat.com>

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

diff --git a/builtin-add.c b/builtin-add.c
index 5e30380..966e145 100644
--- a/builtin-add.c
+++ b/builtin-add.c
@@ -107,7 +107,7 @@ static void update_callback(struct diff_queue_struct *q,
 	}
 }
 
-static void update(int verbose, const char *prefix, const char **files)
+void add_files_to_cache(int verbose, const char *prefix, const char **files)
 {
 	struct rev_info rev;
 	init_revisions(&rev, prefix);
@@ -116,8 +116,6 @@ static void update(int verbose, const char *prefix, const char **files)
 	rev.diffopt.output_format = DIFF_FORMAT_CALLBACK;
 	rev.diffopt.format_callback = update_callback;
 	rev.diffopt.format_callback_data = &verbose;
-	if (read_cache() < 0)
-		die("index file corrupt");
 	run_diff_files(&rev, 0);
 }
 
@@ -218,7 +216,9 @@ int cmd_add(int argc, const char **argv, const char *prefix)
 	}
 
 	if (take_worktree_changes) {
-		update(verbose, prefix, argv + i);
+		if (read_cache() < 0)
+			die("index file corrupt");
+		add_files_to_cache(verbose, prefix, argv + i);
 		goto finish;
 	}
 
diff --git a/commit.h b/commit.h
index cc8d890..89caa12 100644
--- a/commit.h
+++ b/commit.h
@@ -130,6 +130,8 @@ extern struct commit_list *get_shallow_commits(struct object_array *heads,
 int in_merge_bases(struct commit *, struct commit **, int);
 
 extern int interactive_add(void);
+extern void add_files_to_cache(int verbose,
+			       const char *prefix, const char **files);
 extern int rerere(void);
 
 #endif /* COMMIT_H */
-- 
1.5.2.GIT

^ permalink raw reply related

* [PATCH 1/4] Add a simple option parser for use by builtin-commit.c.
From: Kristian Høgsberg @ 2007-09-27  4:50 UTC (permalink / raw)
  To: gitster; +Cc: git, Kristian Høgsberg

Signed-off-by: Kristian Høgsberg <krh@redhat.com>
---
 Makefile        |    2 +-
 parse-options.c |   74 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
 parse-options.h |   29 +++++++++++++++++++++
 3 files changed, 104 insertions(+), 1 deletions(-)
 create mode 100644 parse-options.c
 create mode 100644 parse-options.h

diff --git a/Makefile b/Makefile
index 62bdac6..d90e959 100644
--- a/Makefile
+++ b/Makefile
@@ -310,7 +310,7 @@ LIB_OBJS = \
 	alloc.o merge-file.o path-list.o help.o unpack-trees.o $(DIFF_OBJS) \
 	color.o wt-status.o archive-zip.o archive-tar.o shallow.o utf8.o \
 	convert.o attr.o decorate.o progress.o mailmap.o symlinks.o remote.o \
-	transport.o bundle.o
+	transport.o bundle.o parse-options.o
 
 BUILTIN_OBJS = \
 	builtin-add.o \
diff --git a/parse-options.c b/parse-options.c
new file mode 100644
index 0000000..2fb30cd
--- /dev/null
+++ b/parse-options.c
@@ -0,0 +1,74 @@
+#include "git-compat-util.h"
+#include "parse-options.h"
+
+int parse_options(const char ***argv,
+		  struct option *options, int count,
+		  const char *usage_string)
+{
+	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; i < count; i++) {
+		if ((**argv)[1] == '-') {
+			if (!prefixcmp(options[i].long_name, **argv + 2)) {
+				if (options[i].type != OPTION_BOOLEAN)
+					value = *++(*argv);
+				goto match;
+			}
+
+			eq = strchr(**argv + 2, '=');
+			if (eq && options[i].type != OPTION_BOOLEAN &&
+			    !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_BOOLEAN)
+					value = *++(*argv);
+				goto match;
+			}
+
+			if (options[i].type != OPTION_BOOLEAN) {
+				value = **argv + 2;
+				goto match;
+			}
+		}
+	}
+
+	usage(usage_string);
+
+ match:
+	switch (options[i].type) {
+	case OPTION_BOOLEAN:
+		*(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;
+}
diff --git a/parse-options.h b/parse-options.h
new file mode 100644
index 0000000..39399c3
--- /dev/null
+++ b/parse-options.h
@@ -0,0 +1,29 @@
+#ifndef PARSE_OPTIONS_H
+#define PARSE_OPTIONS_H
+
+enum option_type {
+    OPTION_BOOLEAN,
+    OPTION_STRING,
+    OPTION_INTEGER,
+    OPTION_LAST,
+};
+
+struct option {
+    enum option_type type;
+    const char *long_name;
+    char short_name;
+    void *value;
+};
+
+/* Parse the given options against the list of known options.  The
+ * order of the option structs matters, in that ambiguous
+ * abbreviations (eg, --in could be short for --include or
+ * --interactive) are matched by the first option that share the
+ * prefix.
+ */
+
+extern int parse_options(const char ***argv,
+			 struct option *options, int count,
+			 const char *usage_string);
+
+#endif
-- 
1.5.2.GIT

^ permalink raw reply related

* Re: What's cooking in git.git (topics)
From: Jeff King @ 2007-09-27  2:36 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git
In-Reply-To: <7vfy11yyxk.fsf@gitster.siamese.dyndns.org>

On Wed, Sep 26, 2007 at 01:05:59PM -0700, Junio C Hamano wrote:

> * jk/diff-rename (Tue Sep 25 15:29:42 2007 -0400) 1 commit
>  + diffcore-rename: cache file deltas
> 
> Parked in 'next' for now but is 'master' material.

My tests after this patch show that spanhash_find is responsible for
a large portion of the processing time in large renames, so I am going
to look into speeding that up.

> * lh/merge (Mon Sep 24 00:51:45 2007 +0200) 6 commits
>  + git-merge: add --ff and --no-ff options
>  + git-merge: add support for --commit and --no-squash
>  + git-merge: add support for branch.<name>.mergeoptions
>  + git-merge: refactor option parsing
>  + git-merge: fix faulty SQUASH_MSG
>  + Add test-script for git-merge porcelain
> 
> Comments?  I personally never felt need for --no-ff but the
> series is reasonably clean so I do not see strong objection
> against this series either.

I like it. I know that --no-ff is frowned upon, but I think previous
discussions have mentioned workflows where it might be used
intelligently. Since the patch is unlikely to break anything for
traditional workflows, I think it is a nice way to let people experiment
with alternative workflows that use --no-ff. Maybe something interesting
will come of it.

-Peff

^ permalink raw reply

* git-svn and branches
From: Steven Walter @ 2007-09-27  2:12 UTC (permalink / raw)
  To: git; +Cc: normalperson

I'm using git-svn to track a rather large subversion repository with a
non-standard layout.  In the past, I've only cared about trunk, but now
I need to occasionally use branches, too.  By adding a second git-svn
remote with the branch URL, I can fetch the branch, and git-svn is even
intelligent enough to notice that the branch was copied off of trunk.

However, git-svn also does a complete checkout for the first revision of
the branch.  By this, I mean it goes through shows "A    file" for every
file in the repository.  This takes quite a while, and seems rather
unnecessary given that git-svn already noticed that the branch shares a
history with trunk, which is already fetched.

Knowing just enough of what git-svn is doing to be dangerous, I whipped
up a short little patch.  This patch seems to work for the common case,
and avoids fetching every file from subversion.  It does break
sometimes, however, and I don't understand why.

Maybe someone with a better grasps of the code can see what I did wrong,
or suggest a better means to my end?

diff --git a/git-svn.perl b/git-svn.perl
index 484b057..1bc92b6 100755
--- a/git-svn.perl
+++ b/git-svn.perl
@@ -1848,9 +1848,10 @@ sub find_parent_branch {
                                              $self->full_url, $ed)
                          or die "SVN connection failed somewhere...\n";
                } else {
+                       $self->assert_index_clean($parent);
                        print STDERR "Following parent with do_update\n";
                        $ed = SVN::Git::Fetcher->new($self);
-                       $self->ra->gs_do_update($rev, $rev, $self, $ed)
+                       $self->ra->gs_do_update($rev, $r0, $self, $ed)
                          or die "SVN connection failed somewhere...\n";
                }
                print STDERR "Successfully followed parent\n";
-- 
-Steven Walter <stevenrwalter@gmail.com>
"A human being should be able to change a diaper, plan an invasion,
butcher a hog, conn a ship, design a building, write a sonnet, balance
accounts, build a wall, set a bone, comfort the dying, take orders,
give orders, cooperate, act alone, solve equations, analyze a new
problem, pitch manure, program a computer, cook a tasty meal, fight
efficiently, die gallantly. Specialization is for insects."
   -Robert Heinlein

^ permalink raw reply related

* [PATCH] git-bundle: fix commandline examples in the manpage
From: Miklos Vajna @ 2007-09-26 23:34 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git, Miklos Vajna

Multiple commands were displayed in one line, making the manpage hard to read.

Signed-off-by: Miklos Vajna <vmiklos@frugalware.org>
---
 Documentation/git-bundle.txt |    8 ++++++++
 1 files changed, 8 insertions(+), 0 deletions(-)

diff --git a/Documentation/git-bundle.txt b/Documentation/git-bundle.txt
index 5051e2b..0cc6511 100644
--- a/Documentation/git-bundle.txt
+++ b/Documentation/git-bundle.txt
@@ -103,14 +103,20 @@ We set a tag in R1 (lastR2bundle) after the previous such transport,
 and move it afterwards to help build the bundle.
 
 in R1 on A:
+
+------------
 $ git-bundle create mybundle master ^lastR2bundle
 $ git tag -f lastR2bundle master
+------------
 
 (move mybundle from A to B by some mechanism)
 
 in R2 on B:
+
+------------
 $ git-bundle verify mybundle
 $ git-fetch mybundle  refspec
+------------
 
 where refspec is refInBundle:localRef
 
@@ -124,9 +130,11 @@ Also, with something like this in your config:
 You can first sneakernet the bundle file to ~/tmp/file.bdl and
 then these commands:
 
+------------
 $ git ls-remote bundle
 $ git fetch bundle
 $ git pull bundle
+------------
 
 would treat it as if it is talking with a remote side over the
 network.
-- 
1.5.3.2.80.g077d6f-dirty

^ permalink raw reply related

* Re: [PATCH] Add --no-rename to git-apply
From: Robin Rosenberg @ 2007-09-26 22:31 UTC (permalink / raw)
  To: junkio; +Cc: git
In-Reply-To: <11908420041596-git-send-email-robin.rosenberg@dewire.com>

onsdag 26 september 2007 skrev Robin Rosenberg:
> With this option git-apply can apply a patch with a rename
> onto the original file(s).

The reason (off the record) is that I use it for exporting to another
more expen$ive SCM, that does rename tracking so I need to keep
the file with the original name until telling the other SCM to rename
the file. It may be useful for others doing similar things.

-- robin

^ permalink raw reply

* [EGIT] UTF-8 in author names in history views read as ISO-8859-1
From: José Fonseca @ 2007-09-26 22:04 UTC (permalink / raw)
  To: git

Hi,

First, thanks for the work on egit -- it is progressively becoming more 
and more useful.

I found that spite UTF-8 being the default encoding, egit is interpreting 
(or displaying) names as ISO-8859-1 in the history view.

BTW, something I really miss from egit is "Team > Compare With > HEAD 
revision" or something like that.

Keep up the good work!

Cheers,

José Fonseca

^ permalink raw reply

* Re: What's cooking in git.git (topics)
From: Tom Clarke @ 2007-09-26 21:53 UTC (permalink / raw)
  To: Johannes Schindelin; +Cc: Junio C Hamano, git
In-Reply-To: <Pine.LNX.4.64.0709262159430.28395@racer.site>

On 9/26/07, Johannes Schindelin <Johannes.Schindelin@gmx.de> wrote:
> Together with a resubmitted git-merge-rebase.sh (hint, hint), the
> mergeOptions would be quite useful for a workflow where you want to rebase
> on top of an upstream quite often.

I'll resubmit merge rebase tomorrow :-)

-Tom

^ permalink raw reply

* Re: What's cooking in git.git (topics)
From: Johannes Schindelin @ 2007-09-26 21:44 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git
In-Reply-To: <7vfy11yyxk.fsf@gitster.siamese.dyndns.org>

Hi,

On Wed, 26 Sep 2007, Junio C Hamano wrote:

> * mv/unknown (Tue Sep 25 16:38:46 2007 +0200) 1 commit
>  + Don't use "<unknown>" for placeholders and suppress printing of
>    empty user formats.
> 
> Parked in 'next'; I was already burned by it not passing one of the test 
> cases, and I am not absolutely certain what else this subtly breaks.  
> Hopefully minor.

I guess a few scripts could maybe rely on this behaviour.  We should 
advertise it as such.

> * lh/merge (Mon Sep 24 00:51:45 2007 +0200) 6 commits
>  + git-merge: add --ff and --no-ff options
>  + git-merge: add support for --commit and --no-squash
>  + git-merge: add support for branch.<name>.mergeoptions
>  + git-merge: refactor option parsing
>  + git-merge: fix faulty SQUASH_MSG
>  + Add test-script for git-merge porcelain
> 
> Comments?  I personally never felt need for --no-ff but the series is 
> reasonably clean so I do not see strong objection against this series 
> either.

Together with a resubmitted git-merge-rebase.sh (hint, hint), the 
mergeOptions would be quite useful for a workflow where you want to rebase 
on top of an upstream quite often.

> * js/rebase-i (Tue Sep 25 16:43:15 2007 +0100) 1 commit
>  + rebase -i: work on a detached HEAD
> 
> Waiting for autogc change as this textually interacts with it, and the 
> additional convenience can wait.

Sure.  I never used it anyway, but you specifically requested it ;-)  BTW 
thanks for merging the rest; especially the progress meter was a sore 
point for me since long.

> * jc/autogc (Mon Sep 17 00:55:13 2007 -0700) 10 commits
>  + git-gc --auto: run "repack -A -d -l" as necessary.
>  + git-gc --auto: restructure the way "repack" command line is built.
>  + git-gc --auto: protect ourselves from accumulated cruft
>  + git-gc --auto: add documentation.
>  + git-gc --auto: move threshold check to need_to_gc() function.
>  + repack -A -d: use --keep-unreachable when repacking
>  + pack-objects --keep-unreachable
>  + Export matches_pack_name() and fix its return value
>  + Invoke "git gc --auto" from commit, merge, am and rebase.
>  + Implement git gc --auto
> 
> I think the only remaining thing left with this thing is to prevent more 
> than one instances of it from running at the same time.  Any takers?

You mean, just creating a throw-away lock file?

> * ph/strbuf (Tue Sep 25 10:22:44 2007 +0200) 37 commits
>  + Small cache_tree_write refactor.
>  + Make builtin-rerere use of strbuf nicer and more efficient.
>  + Add strbuf_cmp.
>  + strbuf_setlen(): do not barf on setting length of an empty buffer
>    to 0
>  + sq_quote_argv and add_to_string rework with strbuf's.
>  + Full rework of quote_c_style and write_name_quoted.
>  + ...
> 
> I had to make a small fix-up to strbuf_setlen() last night to this 
> series; this should be ready for 'master'.
> 
> And it is better to push this out early, as the series touches 
> everywhere and conflicts with peoples' patches.

Hehe.  Indeed, I had to fix the notes series after rebasing it...

> * db/fetch-pack (Tue Sep 25 00:13:25 2007 -0400) 45 commits
>  + Prevent send-pack from segfaulting when a branch doesn't match
>  + Cleanup unnecessary break in remote.c
>  + Cleanup style nit of 'x == NULL' in remote.c
>  + Fix memory leaks when disconnecting transport instances
>  + Ensure builtin-fetch honors {fetch,transfer}.unpackLimit
>  + ...
> 
> Two issues known to me are:
> 
>  - "rsync" transport is not supported yet;

I promised to do this, and so I will today.

> * jc/stash-create (Mon Jul 9 00:51:23 2007 -0700) 2 commits
>  + rebase: allow starting from a dirty tree.
>  + stash: implement "stash create"
> 
> I think "stash create" is going in a good direction, but I do not think 
> rebase should unstash unconditionally on the resulting work tree.  A 
> good compromise might be not to unstash if the user asked to switch 
> branches first and to unstash if he didn't.

Sounds like a sensible change to me; maybe a little warning after the 
rebase?

I have no idea if I come around to do the same for rebase--interactive any 
time soon, though.

Ciao,
Dscho

^ permalink raw reply

* [PATCH] post-checkout hook, tests, and docs
From: Josh England @ 2007-09-26 21:31 UTC (permalink / raw)
  To: git; +Cc: Josh England

Updated post-checkout hook to take a flag specifying whether the checkout is
a branch checkout or a file checkout (from the index).

Signed-off-by: Josh England <jjengla@sandia.gov>
---
 Documentation/hooks.txt       |   14 ++++++++
 git-checkout.sh               |   12 +++++++
 t/t5403-post-checkout-hook.sh |   74 +++++++++++++++++++++++++++++++++++++++++
 3 files changed, 100 insertions(+), 0 deletions(-)
 create mode 100755 t/t5403-post-checkout-hook.sh

diff --git a/Documentation/hooks.txt b/Documentation/hooks.txt
index 58b9547..f110162 100644
--- a/Documentation/hooks.txt
+++ b/Documentation/hooks.txt
@@ -87,6 +87,20 @@ parameter, and is invoked after a commit is made.
 This hook is meant primarily for notification, and cannot affect
 the outcome of `git-commit`.
 
+post-checkout
+-----------
+
+This hook is invoked when a `git-checkout` is run after having updated the
+worktree.  The hook is given three parameters: the ref of the previous HEAD,
+the ref of the new HEAD (which may or may not have changed), and a flag
+indicating whether the checkout was a branch checkout (changing branches,
+flag=1) or a file checkout (retrieving a file from the index, flag=0).
+This hook cannot affect the outcome of `git-checkout`.
+
+This hook can be used to perform repository validity checks, auto-display
+differences from the previous HEAD if different, or set working dir metadata
+properties.
+
 post-merge
 -----------
 
diff --git a/git-checkout.sh b/git-checkout.sh
index 17f4392..8993920 100755
--- a/git-checkout.sh
+++ b/git-checkout.sh
@@ -137,6 +137,13 @@ Did you intend to checkout '$@' which can not be resolved as commit?"
 	git ls-files --error-unmatch -- "$@" >/dev/null || exit
 	git ls-files -- "$@" |
 	git checkout-index -f -u --stdin
+
+        # Run a post-checkout hook -- the HEAD does not change so the
+        # current HEAD is passed in for both args
+	if test -x "$GIT_DIR"/hooks/post-checkout; then
+	    "$GIT_DIR"/hooks/post-checkout $old $old 0
+	fi
+
 	exit $?
 else
 	# Make sure we did not fall back on $arg^{tree} codepath
@@ -284,3 +291,8 @@ if [ "$?" -eq 0 ]; then
 else
 	exit 1
 fi
+
+# Run a post-checkout hook
+if test -x "$GIT_DIR"/hooks/post-checkout; then
+        "$GIT_DIR"/hooks/post-checkout $old $new 1
+fi
diff --git a/t/t5403-post-checkout-hook.sh b/t/t5403-post-checkout-hook.sh
new file mode 100755
index 0000000..e656aa1
--- /dev/null
+++ b/t/t5403-post-checkout-hook.sh
@@ -0,0 +1,74 @@
+#!/bin/sh
+#
+# Copyright (c) 2006 Josh England
+#
+
+test_description='Test the post-checkout hook.'
+. ./test-lib.sh
+
+test_expect_success setup '
+	 echo Data for commit0. >a &&
+	 echo Data for commit0. >b &&
+	 git update-index --add a &&
+	 git update-index --add b &&
+	 tree0=$(git write-tree) &&
+	 commit0=$(echo setup | git commit-tree $tree0) &&
+        git update-ref refs/heads/master $commit0 &&
+	 git-clone ./. clone1 &&
+	 git-clone ./. clone2 &&
+        GIT_DIR=clone2/.git git branch -a new2 &&
+        echo Data for commit1. >clone2/b &&
+	 GIT_DIR=clone2/.git git add clone2/b &&
+	 GIT_DIR=clone2/.git git commit -m new2
+'
+
+for clone in 1 2; do
+    cat >clone${clone}/.git/hooks/post-checkout <<'EOF'
+#!/bin/sh
+echo $@ > $GIT_DIR/post-checkout.args
+EOF
+    chmod u+x clone${clone}/.git/hooks/post-checkout
+done
+
+test_expect_success 'post-checkout runs as expected ' '
+        GIT_DIR=clone1/.git git checkout master &&
+        test -e clone1/.git/post-checkout.args
+'
+
+test_expect_success 'post-checkout receives the right arguments with HEAD unchanged ' '
+        old=$(awk "{print \$1}" clone1/.git/post-checkout.args) &&
+        new=$(awk "{print \$2}" clone1/.git/post-checkout.args) &&
+        flag=$(awk "{print \$3}" clone1/.git/post-checkout.args) &&
+        test $old = $new -a $flag == 1
+'
+
+test_expect_success 'post-checkout runs as expected ' '
+        GIT_DIR=clone1/.git git checkout master &&
+        test -e clone1/.git/post-checkout.args
+'
+
+test_expect_success 'post-checkout args are correct with git checkout -b ' '
+        GIT_DIR=clone1/.git git checkout -b new1 &&
+        old=$(awk "{print \$1}" clone1/.git/post-checkout.args) &&
+        new=$(awk "{print \$2}" clone1/.git/post-checkout.args) &&
+        flag=$(awk "{print \$3}" clone1/.git/post-checkout.args) &&
+        test $old = $new -a $flag == 1
+'
+
+test_expect_success 'post-checkout receives the right args with HEAD changed ' '
+        GIT_DIR=clone2/.git git checkout new2 &&
+        old=$(awk "{print \$1}" clone2/.git/post-checkout.args) &&
+        new=$(awk "{print \$2}" clone2/.git/post-checkout.args) &&
+        flag=$(awk "{print \$3}" clone2/.git/post-checkout.args) &&
+        test $old != $new -a $flag == 1
+'
+
+test_expect_success 'post-checkout receives the right args when not switching branches ' '
+        GIT_DIR=clone2/.git git checkout master b &&
+        old=$(awk "{print \$1}" clone2/.git/post-checkout.args) &&
+        new=$(awk "{print \$2}" clone2/.git/post-checkout.args) &&
+        flag=$(awk "{print \$3}" clone2/.git/post-checkout.args) &&
+        test $old == $new -a $flag == 0
+'
+
+test_done
-- 
1.5.3.2.90.g6069e-dirty

^ permalink raw reply related

* [PATCH] Add --no-rename to git-apply
From: Robin Rosenberg @ 2007-09-26 21:26 UTC (permalink / raw)
  To: junkio; +Cc: git, Robin Rosenberg

With this option git-apply can apply a patch with a rename
onto the original file(s).

Signed-off-by: Robin Rosenberg <robin.rosenberg@dewire.com>
---
 Documentation/git-apply.txt       |    6 ++-
 builtin-apply.c                   |   17 +++++++-
 t/t4123-apply-renames-norename.sh |   85 +++++++++++++++++++++++++++++++++++++
 3 files changed, 106 insertions(+), 2 deletions(-)
 create mode 100755 t/t4123-apply-renames-norename.sh

diff --git a/Documentation/git-apply.txt b/Documentation/git-apply.txt
index 4c7e3a2..b691c55 100644
--- a/Documentation/git-apply.txt
+++ b/Documentation/git-apply.txt
@@ -10,7 +10,7 @@ SYNOPSIS
 --------
 [verse]
 'git-apply' [--stat] [--numstat] [--summary] [--check] [--index]
-	  [--apply] [--no-add] [--index-info] [-R | --reverse]
+	  [--apply] [--no-add] [--no-rename] [--index-info] [-R | --reverse]
 	  [--allow-binary-replacement | --binary] [--reject] [-z]
 	  [-pNUM] [-CNUM] [--inaccurate-eof] [--cached]
 	  [--whitespace=<nowarn|warn|error|error-all|strip>]
@@ -121,6 +121,10 @@ discouraged.
 	the result with this option, which would apply the
 	deletion part but not addition part.
 
+--no-rename::
+	When applying patches with renames, this patch applies
+	the patch to the original file.
+
 --allow-binary-replacement, --binary::
 	Historically we did not allow binary patch applied
 	without an explicit permission from the user, and this
diff --git a/builtin-apply.c b/builtin-apply.c
index 25b1447..73134e8 100644
--- a/builtin-apply.c
+++ b/builtin-apply.c
@@ -41,11 +41,12 @@ static int apply_in_reverse;
 static int apply_with_reject;
 static int apply_verbosely;
 static int no_add;
+static int no_rename;
 static int show_index_info;
 static int line_termination = '\n';
 static unsigned long p_context = ULONG_MAX;
 static const char apply_usage[] =
-"git-apply [--stat] [--numstat] [--summary] [--check] [--index] [--cached] [--apply] [--no-add] [--index-info] [--allow-binary-replacement] [--reverse] [--reject] [--verbose] [-z] [-pNUM] [-CNUM] [--whitespace=<nowarn|warn|error|error-all|strip>] <patch>...";
+"git-apply [--stat] [--numstat] [--summary] [--check] [--index] [--cached] [--apply] [--no-add] [--no-rename] [--index-info]   [--allow-binary-replacement] [--reverse [--reject] [--verbose] [-z] [-pNUM] [-CNUM] [--whitespace=<nowarn|warn|error|error-all|strip>]  <patch>...";
 
 static enum whitespace_eol {
 	nowarn_whitespace,
@@ -495,6 +496,8 @@ static int gitdiff_copydst(const char *line, struct patch *patch)
 
 static int gitdiff_renamesrc(const char *line, struct patch *patch)
 {
+	if (no_rename)
+		return 0;
 	patch->is_rename = 1;
 	patch->old_name = find_name(line, NULL, 0, 0);
 	return 0;
@@ -502,6 +505,8 @@ static int gitdiff_renamesrc(const char *line, struct patch *patch)
 
 static int gitdiff_renamedst(const char *line, struct patch *patch)
 {
+	if (no_rename)
+		return 0;
 	patch->is_rename = 1;
 	patch->new_name = find_name(line, NULL, 0, 0);
 	return 0;
@@ -2103,6 +2108,11 @@ static int check_patch(struct patch *patch, struct patch *prev_patch)
 	struct cache_entry *ce = NULL;
 	int ok_if_exists;
 
+	if (no_rename) {
+		new_name = old_name;
+		patch->new_name = old_name;
+	}
+
 	patch->rejected = 1; /* we will drop this after we succeed */
 
 	/*
@@ -2843,6 +2853,11 @@ int cmd_apply(int argc, const char **argv, const char *unused_prefix)
 			no_add = 1;
 			continue;
 		}
+		if (!strcmp(arg, "--no-rename")) {
+			no_rename = 1;
+			continue;
+		}
+
 		if (!strcmp(arg, "--stat")) {
 			apply = 0;
 			diffstat = 1;
diff --git a/t/t4123-apply-renames-norename.sh b/t/t4123-apply-renames-norename.sh
new file mode 100755
index 0000000..8c0d523
--- /dev/null
+++ b/t/t4123-apply-renames-norename.sh
@@ -0,0 +1,85 @@
+#!/bin/sh
+#
+# Copyright (c) 2007 Robin Rosenberg
+#
+
+test_description='git apply --no-rename.
+
+'
+
+. ./test-lib.sh
+
+# setup
+
+mkdir -p klibc/arch/x86_64/include/klibc
+
+cat >klibc/arch/x86_64/include/klibc/archsetjmp.h <<\EOF
+/*
+ * arch/x86_64/include/klibc/archsetjmp.h
+ */
+
+#ifndef _KLIBC_ARCHSETJMP_H
+#define _KLIBC_ARCHSETJMP_H
+
+struct __jmp_buf {
+  unsigned long __rbx;
+  unsigned long __rsp;
+  unsigned long __rbp;
+  unsigned long __r12;
+  unsigned long __r13;
+  unsigned long __r14;
+  unsigned long __r15;
+  unsigned long __rip;
+};
+
+typedef struct __jmp_buf jmp_buf[1];
+
+#endif /* _SETJMP_H */
+EOF
+
+cat >patch <<\EOF
+diff --git a/klibc/arch/x86_64/include/klibc/archsetjmp.h b/include/arch/m32r/klibc/archsetjmp.h
+similarity index 66%
+rename from klibc/arch/x86_64/include/klibc/archsetjmp.h
+rename to include/arch/m32r/klibc/archsetjmp.h
+--- a/klibc/arch/x86_64/include/klibc/archsetjmp.h
++++ b/include/arch/m32r/klibc/archsetjmp.h
+@@ -1,21 +1,21 @@
+ /*
+- * arch/x86_64/include/klibc/archsetjmp.h
++ * arch/m32r/include/klibc/archsetjmp.h
+  */
+
+ #ifndef _KLIBC_ARCHSETJMP_H
+ #define _KLIBC_ARCHSETJMP_H
+
+ struct __jmp_buf {
+-  unsigned long __rbx;
+-  unsigned long __rsp;
+-  unsigned long __rbp;
++  unsigned long __r8;
++  unsigned long __r9;
++  unsigned long __r10;
++  unsigned long __r11;
+   unsigned long __r12;
+   unsigned long __r13;
+   unsigned long __r14;
+   unsigned long __r15;
+-  unsigned long __rip;
+ };
+
+ typedef struct __jmp_buf jmp_buf[1];
+
+-#endif /* _SETJMP_H */
++#endif /* _KLIBC_ARCHSETJMP_H */
+EOF
+
+find klibc -type f -print | xargs git update-index --add --
+
+test_expect_success 'apply rename patch, without doing rename' \
+'git apply --no-rename patch &&
+git diff --numstat >stat.diff &&
+test "$(cat stat.diff)" = "6	6	klibc/arch/x86_64/include/klibc/archsetjmp.h"
+'
+
+test_done
-- 
1.5.3.1.g80926

^ permalink raw reply related

* Re: What's cooking in git.git (topics)
From: Carlos Rica @ 2007-09-26 21:07 UTC (permalink / raw)
  To: Johannes Schindelin; +Cc: Junio C Hamano, git
In-Reply-To: <Pine.LNX.4.64.0709150047030.28586@racer.site>

2007/9/15, Johannes Schindelin <Johannes.Schindelin@gmx.de>:
> On Fri, 14 Sep 2007, Junio C Hamano wrote:
>
> > * cr/reset (Fri Sep 14 01:19:30 2007 -0700) 5 commits
> >  + Simplify cache API
> >  + An additional test for "git-reset -- path"
> >  + Make "git reset" a builtin.
> >  + Move make_cache_entry() from merge-recursive.c into read-cache.c
> >  + Add tests for documented features of "git reset".
> >
> > I found "git reset commit paths..." had problem in this series,
> > which was why jc/cachetree is merged into this topic to fix it.
> > Hopefully we can put this in 'master' soon, after giving it
> > another and final round of eyeballing.
>
> I think this is my bug.  The initial reset-with-paths functionality in the
> builtin reset came out of my feather... Sorry!

I'm sorry for not being able to review that code, but now it was
a bit too hard for my current knowledge of the git's internals.
These days I'm very busy here and I cannot continue with that work,
but I will be soon in git to learn more. Please, be free to rework the
code of builtin-reset.c without waiting for me if anyone is interested
in get it done ASAP.

--
Carlos

^ permalink raw reply

* Re: grafts not appearing in manual pages
From: J. Bruce Fields @ 2007-09-26 21:01 UTC (permalink / raw)
  To: Mike Hommey; +Cc: git
In-Reply-To: <20070926202441.GA31848@glandium.org>

On Wed, Sep 26, 2007 at 10:24:41PM +0200, Mike Hommey wrote:
> Hi,
> 
> The only occurrence of grafts in the manual pages is in the
> git-filter-branch one. I somehow feel this is wrong not to see it
> described more "formally" in the manual pages.
> 
> I wouldn't mind writing a small something, except I have no idea what
> would be the most appropriate place to talk about it... Does anyone have
> such an idea ?

It could go in Documentation/user-manual.txt, but I don't know where.
Maybe at the end of the git-concepts chapter?  Patches welcomed.  I
guess what's needed (and currently missing) is an overview explaining
what they're actually for, plus some basic setup instructions.

And/or maybe a section 5 manual page documenting the format of the
"grafts" file?  Maybe that's overkill.  There's a paragraph in
Documentation/repository-layout.txt.  Maybe we need to make it easier to
find that file.

--b.

^ permalink raw reply


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