Git development
 help / color / mirror / Atom feed
* Re: Merges without bases
From: Daniel Barkalow @ 2005-08-26 16:37 UTC (permalink / raw)
  To: Martin Langhoff; +Cc: Junio C Hamano, Darrin Thompson, git
In-Reply-To: <46a038f905082602176f9eef5d@mail.gmail.com>

On Fri, 26 Aug 2005, Martin Langhoff wrote:

> On 8/26/05, Junio C Hamano <junkio@cox.net> wrote:
> > their core GIT tools come from.  But how would _I_ pull from
> > that "My Project", if I did not want to pull unrelated stuff in?
>
> and then...
>
> > What I think _might_ deserve a bit more support would be a merge
> > of a foreign project as a subdirectory of a project.  Linus
>
> tla has an interesting implementation (and horrible name) for
> something like this. In Arch-speak, they are called 'configurations',
> a versioned control file that describes that in subdirectory foo we
> import from this other repo#branch.
>
> In cvs, you just do nested checkouts, and trust a `cvs update` done at
> the top will do the right thing;  and in fact recent cvs versions do.

The problem with both of these (and doing it in the build system) is that,
when a project includes another project, you generally don't want whatever
revision of the included project happens to be the latest; you want the
revision of the included project that the revision of the including
project you're looking at matches. That is, if App includes Lib, and
you're looking at an App commit, you want to have the version of Lib that
the commit was made with, not the latest version of Lib, which may not be
backwards compatible across non-release commits, or, in any case, won't
help in reconstructing a earlier state. I think a primary function of a
SCM is to be able to say, "It worked last Friday, and it's broken now.
What's different?" If the answer is, "On Saturday, we updated the
included Lib to their version from Thursday, which is broken", it'll be
really hard to track down without special tracking.

I think it's the lack of the special tracking, therefore, that makes this
not a good feature in most SCMs, and makes them not better than having the
build system do it (and potentially worse, if you've got your build system
checking out a version specified in a version-controlled file). But I
think that git can do better, including support for the required version
sometimes being a locally modified one and sometimes being the official
one when the local modifications have been accepted upstream.

	-Daniel
*This .sig left intentionally blank*

^ permalink raw reply

* Re: [PATCH] Fix pulling into the same branch.
From: Junio C Hamano @ 2005-08-26 18:03 UTC (permalink / raw)
  To: Johannes Schindelin; +Cc: git, Tony Luck, Linus Torvalds
In-Reply-To: <Pine.LNX.4.63.0508261501510.18279@wgmdd8.biozentrum.uni-wuerzburg.de>

Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:

> I like it. As Linus stated, the index originally had a different role from 
> what it has now, so it really should be an internal git thing, i.e. the 
> git user should not expect the index not to change when pulling.

Actually the issue and the way patch addresses it is not limited
to the index file.  The problem is that the relashionship
between your working tree and the $GIT_DIR/HEAD commit changes
unexpectedly (from the POV of the working tree) when the ref
being updated by the "push in reverse" machinery happens to be
the head of the branch you are currently on.  The working tree
is supposed to be based on $GIT_DIR/HEAD, but after the "push in
reverse".  This fix is about fast forwarding the working tree,
including the checked out files on the filesystem, to the
updated head when it happens.

I am tempted to move this logic to "git fetch" instead, because
it has the same issue.  Tony's "linus" branch example has been
updated to do a "git fetch" instead of "git pull" from the
earlier description in his howto, but if he happens to be on the
"linus" branch, he would still have this same problem.

Of course, doing this in "git fetch" needs to be done a bit more
carefully than in "git pull", because by definition "pull" is a
fetch followed by merging into the working tree, implying that
the repository has an associated working tree.  But "fetch" by
itself can be used for a naked repository [*1*], and we should
not unconditionally attempt to update the working tree that may
not exist.

I am planning to use the lack of index file for this detection,
but I've seen some people made an rsync copy of their private
repository as a whole to publish, so this heuristic may
probabaly break.  Maybe the lack of "$GIT_DIR/index" and
$GIT_DIR not ending with "/.git" and not being ".git"?  I am
undecided.

In the meantime, warning the user about the issue and suggesting
how to do the fast-forwarding of the working tree himself in the
warning message might be the safest and the most sensible thing
to do.


[Footnote]

*1* Do we want a term in the glossary to mean repositories like
linux-2.6.git and git.git that do not have associated working
trees?

^ permalink raw reply

* RE: [PATCH] Fix pulling into the same branch.
From: Luck, Tony @ 2005-08-26 18:13 UTC (permalink / raw)
  To: Junio C Hamano, Johannes Schindelin; +Cc: git, Linus Torvalds

>I am tempted to move this logic to "git fetch" instead, because
>it has the same issue.  Tony's "linus" branch example has been
>updated to do a "git fetch" instead of "git pull" from the
>earlier description in his howto, but if he happens to be on the
>"linus" branch, he would still have this same problem.

I don't spend much time on the linus branch (just to build an
updated cscope database, etc.).  I never want to check anything
in on that branch [sometime I should figure out how use the "hooks"
to keep me from shooting myself in the foot here].

>In the meantime, warning the user about the issue and suggesting
>how to do the fast-forwarding of the working tree himself in the
>warning message might be the safest and the most sensible thing
>to do.

Yes please ... a big fat warning with coloured flashing lights
and explosions from the sound card.

-Tony

^ permalink raw reply

* Template added to git repository?
From: Marco Costalba @ 2005-08-26 18:16 UTC (permalink / raw)
  To: junkio; +Cc: git

Hi,


$ git status
nothing to commit
$ make clean
rm -f *.o mozilla-sha1/*.o ppc/*.o git-update-cache git-diff-files git-init-db git-write-tree
git-read-tree git-commit-tree git-cat-file git-fsck-cache git-checkout-cache git-diff-tree
git-rev-tree git-ls-files git-ls-tree git-merge-base git-merge-cache git-unpack-file git-export

<stripped lines>


$ git status
#
# Changed but not updated:
#   (use git-update-cache to mark for commit)
#
#       deleted:  templates/blt/description
#       deleted:  templates/blt/hooks/applypatch-msg
#       deleted:  templates/blt/hooks/commit-msg
#       deleted:  templates/blt/hooks/post-commit
#       deleted:  templates/blt/hooks/post-update
#       deleted:  templates/blt/hooks/pre-applypatch
#       deleted:  templates/blt/hooks/pre-commit
#       deleted:  templates/blt/hooks/update
#       deleted:  templates/blt/info/exclude
#
nothing to commit
$ make
gcc -o update-cache.o -c -g -O2 -Wall '-DSHA1_HEADER=<openssl/sha.h>' update-cache.c
gcc -o read-cache.o -c -g -O2 -Wall '-DSHA1_HEADER=<openssl/sha.h>' read-cache.c
gcc -o sha1_file.o -c -g -O2 -Wall '-DSHA1_HEADER=<openssl/sha.h>' sha1_file.c

<stripped lines>

$ git status
nothing to commit
$

Perpahs generated template files should not be in git repository?


Marco


__________________________________________________
Do You Yahoo!?
Tired of spam?  Yahoo! Mail has the best spam protection around 
http://mail.yahoo.com 

^ permalink raw reply

* Fix: remove generated template files
From: Marco Costalba @ 2005-08-26 18:26 UTC (permalink / raw)
  To: junkio; +Cc: git

From: Marco Costalba <mcostalba@yahoo.it>
Date: 1125080490 +0200

This files are created by 'make' so remove from
repository

Signed-off-by: Marco Costalba <mcostalba@yahoo.it>

---

This time I send the patch ;-)


 templates/blt/description          |    1 -
 templates/blt/hooks/applypatch-msg |   14 --------
 templates/blt/hooks/commit-msg     |   14 --------
 templates/blt/hooks/post-commit    |    8 -----
 templates/blt/hooks/post-update    |    8 -----
 templates/blt/hooks/pre-applypatch |   14 --------
 templates/blt/hooks/pre-commit     |   61 ------------------------------------
 templates/blt/hooks/update         |   27 ----------------
 templates/blt/info/exclude         |    6 ----
 9 files changed, 0 insertions(+), 153 deletions(-)
 delete mode 100644 templates/blt/description
 delete mode 100644 templates/blt/hooks/applypatch-msg
 delete mode 100644 templates/blt/hooks/commit-msg
 delete mode 100644 templates/blt/hooks/post-commit
 delete mode 100644 templates/blt/hooks/post-update
 delete mode 100644 templates/blt/hooks/pre-applypatch
 delete mode 100644 templates/blt/hooks/pre-commit
 delete mode 100644 templates/blt/hooks/update
 delete mode 100644 templates/blt/info/exclude

01168c6044e7ebb570e851b4707b4fe0c7f40c15
diff --git a/templates/blt/description b/templates/blt/description
deleted file mode 100644
--- a/templates/blt/description
+++ /dev/null
@@ -1 +0,0 @@
-Unnamed repository; edit this file to name it for gitweb.
diff --git a/templates/blt/hooks/applypatch-msg b/templates/blt/hooks/applypatch-msg
deleted file mode 100644
--- a/templates/blt/hooks/applypatch-msg
+++ /dev/null
@@ -1,14 +0,0 @@
-#!/bin/sh
-#
-# An example hook script to check the commit log message taken by
-# applypatch from an e-mail message.
-#
-# The hook should exit with non-zero status after issuing an
-# appropriate message if it wants to stop the commit.  The hook is
-# allowed to edit the commit message file.
-#
-# To enable this hook, make this file executable.
-
-test -x "$GIT_DIR/hooks/commit-msg" &&
-	exec "$GIT_DIR/hooks/commit-msg" ${1+"$@"}
-:
diff --git a/templates/blt/hooks/commit-msg b/templates/blt/hooks/commit-msg
deleted file mode 100644
--- a/templates/blt/hooks/commit-msg
+++ /dev/null
@@ -1,14 +0,0 @@
-#!/bin/sh
-#
-# An example hook script to check the commit log message.
-# Called by git-commit-script with one argument, the name of the file
-# that has the commit message.  The hook should exit with non-zero
-# status after issuing an appropriate message if it wants to stop the
-# commit.  The hook is allowed to edit the commit message file.
-#
-# To enable this hook, make this file executable.
-
-# This example catches duplicate Signed-off-by lines.
-
-test "" = "$(grep '^Signed-off-by: ' "$1" |
-	 sort | uniq -c | sed -e '/^[ 	]*1 /d')"
diff --git a/templates/blt/hooks/post-commit b/templates/blt/hooks/post-commit
deleted file mode 100644
--- a/templates/blt/hooks/post-commit
+++ /dev/null
@@ -1,8 +0,0 @@
-#!/bin/sh
-#
-# An example hook script that is called after a successful
-# commit is made.
-#
-# To enable this hook, make this file executable.
-
-: Nothing
diff --git a/templates/blt/hooks/post-update b/templates/blt/hooks/post-update
deleted file mode 100644
--- a/templates/blt/hooks/post-update
+++ /dev/null
@@ -1,8 +0,0 @@
-#!/bin/sh
-#
-# An example hook script to prepare a packed repository for use over
-# dumb transports.
-#
-# To enable this hook, make this file executable by "chmod +x post-update".
-
-exec git-update-server-info
diff --git a/templates/blt/hooks/pre-applypatch b/templates/blt/hooks/pre-applypatch
deleted file mode 100644
--- a/templates/blt/hooks/pre-applypatch
+++ /dev/null
@@ -1,14 +0,0 @@
-#!/bin/sh
-#
-# An example hook script to verify what is about to be committed
-# by applypatch from an e-mail message.
-#
-# The hook should exit with non-zero status after issuing an
-# appropriate message if it wants to stop the commit.
-#
-# To enable this hook, make this file executable.
-
-test -x "$GIT_DIR/hooks/pre-commit" &&
-	exec "$GIT_DIR/hooks/pre-commit" ${1+"$@"}
-:
-
diff --git a/templates/blt/hooks/pre-commit b/templates/blt/hooks/pre-commit
deleted file mode 100644
--- a/templates/blt/hooks/pre-commit
+++ /dev/null
@@ -1,61 +0,0 @@
-#!/bin/sh
-#
-# An example hook script to verify what is about to be committed.
-# Called by git-commit-script with no arguments.  The hook should
-# exit with non-zero status after issuing an appropriate message if
-# it wants to stop the commit.
-#
-# To enable this hook, make this file executable.
-
-# This is slightly modified from Andrew Morton's Perfect Patch.
-# Lines you introduce should not have trailing whitespace.
-# Also check for an indentation that has SP before a TAB.
-perl -e '
-    my $fh;
-    my $found_bad = 0;
-    my $filename;
-    my $reported_filename = "";
-    my $lineno;
-    sub bad_line {
-	my ($why, $line) = @_;
-	if (!$found_bad) {
-	    print STDERR "*\n";
-	    print STDERR "* You have some suspicious patch lines:\n";
-	    print STDERR "*\n";
-	    $found_bad = 1;
-	}
-	if ($reported_filename ne $filename) {
-	    print STDERR "* In $filename\n";
-	    $reported_filename = $filename;
-	}
-	print STDERR "* $why (line $lineno)\n";
-	print STDERR "$filename:$lineno:$line\n";
-    }
-    open $fh, "-|", qw(git-diff-cache -p -M --cached HEAD);
-    while (<$fh>) {
-	if (m|^diff --git a/(.*) b/\1$|) {
-	    $filename = $1;
-	    next;
-	}
-	if (/^@@ -\S+ \+(\d+)/) {
-	    $lineno = $1 - 1;
-	    next;
-	}
-	if (/^ /) {
-	    $lineno++;
-	    next;
-	}
-	if (s/^\+//) {
-	    $lineno++;
-	    chomp;
-	    if (/\s$/) {
-		bad_line("trailing whitespace", $_);
-	    }
-	    if (/^\s* 	/) {
-		bad_line("indent SP followed by a TAB", $_);
-	    }
-	}
-    }
-    exit($found_bad);
-'
-
diff --git a/templates/blt/hooks/update b/templates/blt/hooks/update
deleted file mode 100644
--- a/templates/blt/hooks/update
+++ /dev/null
@@ -1,27 +0,0 @@
-#!/bin/sh
-#
-# An example hook script to mail out commit update information.
-# Called by git-receive-pack with arguments: refname sha1-old sha1-new
-#
-# To enable this hook:
-# (1) change the recipient e-mail address
-# (2) make this file executable by "chmod +x update".
-#
-
-recipient="commit-list@mydomain.xz"
-
-if expr "$2" : '0*$' >/dev/null
-then
-	echo "Created a new ref, with the following commits:"
-	git-rev-list --pretty "$3"
-else
-	$base=$(git-merge-base "$2" "$3")
-	if [ $base == "$2" ]; then
-		echo "New commits:"
-	else
-		echo "Rebased ref, commits from common ancestor:"
-fi
-git-rev-list --pretty "$3" "^$base"
-fi |
-mail -s "Changes to ref $1" "$recipient"
-exit 0
diff --git a/templates/blt/info/exclude b/templates/blt/info/exclude
deleted file mode 100644
--- a/templates/blt/info/exclude
+++ /dev/null
@@ -1,6 +0,0 @@
-# git-ls-files --others --exclude-from=.git/info/exclude
-# Lines that start with '#' are comments.
-# For a project mostly in C, the following would be a good set of
-# exclude patterns (uncomment them if you want to use them):
-# *.[oa]
-# *~



__________________________________________________
Do You Yahoo!?
Tired of spam?  Yahoo! Mail has the best spam protection around 
http://mail.yahoo.com 

^ permalink raw reply

* [RFC, PATCH] A new merge algorithm (EXPERIMENTAL)
From: Fredrik Kuivinen @ 2005-08-26 18:47 UTC (permalink / raw)
  To: git

Hi,

I have for some time now been playing with a new merge algorithm.  It
is designed to solve some of the content merges that comes up now and
then. For example the criss-cross merge case described by Bram Cohen
[1], the similar case described by Matthias Urlichs [3] and the merge
case Tony Luck found [2].

It does _not_ do anything smart with respect to tree changes. So with
respect to file renames, directory renames etc it wont be any better
than the current git-resolve-script.

The new code consists of a few files merge.py,
git-new-merge-one-file-script.py, gitMergeCommon.py and
gitMergeCore.py. Its all written in Python, at least version 2.4 is
required. It also need a small patch to read-tree.c. Right now its all
very much experimental. It seems to do the right thing on the test
cases I have given to it, but it probably contains lots of bugs and
unsolved corner cases.

The user interface consists of the program 'merge.py'. 
The syntax is: merge.py <branch> 

merge.py expects that the working directory is in sync with both the
cache and HEAD. It will merge HEAD with <branch>. The result of the
merge will be written to the working directory and the cache will be
updated to match the working directory. Those two steps are always
done, even if the merge was non-clean. merge.py will create the
appropriate .git/MERGE_HEAD.

I will try to describe how the algorithm works. The problem with the
usual 3-way merge algorithm is that we sometimes do not have a unique
common ancestor. In [1] B and C seems to be equally good. What this
algorithm does is to _merge_ the common ancestors, in this case B and
C, into a temporary tree lets call it T. It does then use this
temporary tree T as the common ancestor for D and E to produce the
final merge result. In the case described in [1] this will work out
fine and we get a clean merge with the expected result.

The common ancestors that are found to be "equally good" are named
"shared heads" in the code. In the criss-cross merge case there are
two shared heads, B and C.

In [2] the algorithm finds three shared heads,
f6fdd7d9c273bb2a20ab467cb57067494f932fa3,
3a931d4cca1b6dabe1085cc04e909575df9219ae and
c1ffb910f7a4e1e79d462bb359067d97ad1a8a25. Those three commits are then
merged to produce a new temporary tree T. T is then used as the common
ancestor to merge the original
a4cce10492358b33d33bb43f98284c80482037e8 and
7ffacc1a2527c219b834fe226a7a55dc67ca3637.

The result is the expected one, that is the diff between the merge
produced by 'merge.py' and 'git-resolve-script' is

diff --git a/arch/ia64/hp/sim/boot/bootloader.c b/arch/ia64/hp/sim/boot/bootloader.c
--- a/arch/ia64/hp/sim/boot/bootloader.c
+++ b/arch/ia64/hp/sim/boot/bootloader.c
@@ -30,10 +30,14 @@ struct disk_req {
        unsigned len;
 };
 
+/* SSC_WAIT_COMPLETION appears to want this large alignment.  gcc < 4
+ * seems to give it by default, however gcc > 4 is smarter and may
+ * not.
+ */
 struct disk_stat {
        int fd;
        unsigned count;
-};
+} __attribute__ ((aligned (16)));
 
 extern void jmp_to_kernel (unsigned long bp, unsigned long e_entry);
 extern struct ia64_boot_param *sys_fw_init (const char *args, int arglen);



In most merges there is only one shared head, in this case 'merge.py'
is supposed to produce similar results as we get with
'git-resolve-script'.

In real numbers it is as follows: In Linus' kernel tree there are
5996 commits. 400 of those have more than one parent. Of those 400
merge commits 4 have more than one shared head.

Currently the main problems and questions that I see with this patch
are:

* There is no way to easily detect if the merge was clean or not. A
  message about it is written to stdout but merge.py wont return a
  usable error code. This shouldn't be too hard to fix.
* Temporary trees and other objects may be written to the object
  database during the merge. They are currently not deleted.
* If this is going to be used for anything real it has to be cleaned
  up.
* Are there merge cases for which this algorithm clearly produces the 
  wrong result?
* Is it worth it? That is, is the added complexity in the merge logic
  worth the advantages of correctly handling some strange (but real
  life) merge cases?

- Fredrik

[1] http://www.gelato.unsw.edu.au/archives/git/0504/2279.html
[2] http://www.gelato.unsw.edu.au/archives/git/0508/8072.html, and the
    follow-up discussion.
[3] http://www.gelato.unsw.edu.au/archives/git/0507/6082.html

Signed-off-by: Fredrik Kuivinen <freku045@student.liu.se>

---

 Makefile                  |    3 
 git-new-merge-one-file.py |  153 ++++++++++++++++++++
 gitMergeCommon.py         |   68 +++++++++
 gitMergeCore.py           |  350 +++++++++++++++++++++++++++++++++++++++++++++
 merge.py                  |   39 +++++
 read-tree.c               |   13 +-
 6 files changed, 623 insertions(+), 3 deletions(-)

diff --git a/Makefile b/Makefile
--- a/Makefile
+++ b/Makefile
@@ -66,7 +66,8 @@ SCRIPTS=git git-apply-patch-script git-m
 	git-format-patch-script git-sh-setup-script git-push-script \
 	git-branch-script git-parse-remote-script git-verify-tag-script \
 	git-ls-remote-script git-clone-dumb-http git-rename-script \
-	git-request-pull-script git-bisect-script
+	git-request-pull-script git-bisect-script merge.py gitMergeCommon.py \
+	gitMergeCore.py git-new-merge-one-file.py
 
 SCRIPTS += git-count-objects-script
 # SCRIPTS += git-send-email-script
diff --git a/git-new-merge-one-file.py b/git-new-merge-one-file.py
new file mode 100755
--- /dev/null
+++ b/git-new-merge-one-file.py
@@ -0,0 +1,153 @@
+#!/usr/bin/env python
+#
+# This script is based on 'git-merge-one-file-script' in the Git
+# distribution.
+#
+# This is a git per-file merge script, called with
+#
+#   $1 - original file SHA1 (or empty)
+#   $2 - file in branch1 SHA1 (or empty)
+#   $3 - file in branch2 SHA1 (or empty)
+#   $4 - pathname in repository
+#   $5 - orignal file mode (or empty)
+#   $6 - file in branch1 mode (or empty)
+#   $7 - file in branch2 mode (or empty)
+#
+# Handle some trivial cases.. The _really_ trivial cases have
+# been handled already by git-read-tree, but that one doesn't
+# do any merges that might change the tree layout.
+
+import sys, os
+from gitMergeCommon import *
+from sets import Set
+
+oSha = sys.argv[1]
+aSha = sys.argv[2]
+bSha = sys.argv[3]
+path = sys.argv[4]
+oMode = sys.argv[5]
+aMode = sys.argv[6]
+bMode = sys.argv[7]
+
+branch1 = os.environ['GIT_MERGE_BRANCH_1']
+branch2 = os.environ['GIT_MERGE_BRANCH_2']
+[files, dirs] = eval(open(os.environ['GIT_MERGE_DIRS']).read())
+
+# TODO
+# dir/file name conflicts
+
+# x <==> sha != '', - <==> sha == '' 
+#
+# Case  o a b
+# D     x x x
+# A     x x -
+# A     x - x
+# A     x - -
+# C     - x x
+# B     - x -
+# B     - - x
+#       - - - Doesn't happen
+
+if (oSha != '' and (aSha == '' or bSha == '')):
+#
+# Case A: Deleted in one
+#
+    if (aSha == ''   and bSha == '') or \
+       (aSha == oSha and bSha == '') or \
+       (aSha == ''   and bSha == oSha):
+# Deleted in both or deleted in one and unchanged in the other
+        print 'Removing ' + path
+        runProgram(['git-update-cache', '--force-remove', '--', path])
+    else:
+# Deleted in one and changed in the other
+        if aSha == '':
+            print 'CONFLICT (del/mod): "' + path + '" deleted in', \
+                  branch1, 'and modified in', branch2, '. Version ', branch2, ' of "' + path + '" left in tree'
+            mode = bMode
+            sha = bSha
+        else:
+            print 'CONFLICT (mod/del): "' + path + '" deleted in ' + branch2 + ' and modified in ' + branch1 + \
+                  '. Version ' + branch1 + ' of "' + path + '" left in tree'
+            mode = aMode
+            sha = aSha
+        runProgram(['git-update-cache', '--cacheinfo', mode, sha, path])
+
+elif (oSha == '' and aSha != '' and bSha == '') or \
+     (oSha == '' and aSha == '' and bSha != ''):
+#
+# Case B: Added in one.
+#
+    if aSha != '':
+        addBranch = branch1
+        otherBranch = branch2
+        conf = 'file/dir'
+    else:
+        addBranch = branch2
+        otherBranch = branch1
+        conf = 'dir/file'
+
+    if path in dirs:
+        newPath = path + '_' + addBranch
+        print 'CONFLICT (' + conf + '): There is a directory with name "' + path + '" in ' + otherBranch + \
+              '. Adding "' + path + '" as "' + newPath + '"'
+        runProgram(['git-update-cache', '--force-remove', '--', path])
+        path = newPath
+    else:
+        print 'Adding "' + path + '"'
+    runProgram(['git-update-cache', '--add', '--cacheinfo', aMode+bMode, aSha+bSha, path])
+
+elif oSha == '' and aSha != '' and bSha != '':
+#
+# Case C: Added in both (check for same permissions).
+#
+    if aSha == bSha:
+        if aMode != bMode:
+            print 'CONFLICT: File "' + path + '" added identically in both branches,'
+            print 'CONFLICT: but permissions conflict ' + aMode + '->' + bMode + '.'
+            print 'CONFLICT: adding with permission: ' + aMode
+            runProgram(['git-update-cache', '--add', '--cacheinfo', aMode, aSha, path])
+        else:
+            # This case is handled by git-read-tree
+            print 'Adding ' + path
+            runProgram(['git-update-cache', '--add', '--cacheinfo', aMode, aSha, path])
+    else:
+        newPath1 = path + '_' + branch1
+        newPath2 = path + '_' + branch2
+        print 'CONFLICT (add/add): File "' + path + '" added non-identically in both branches. ' + \
+              'Adding "' + newPath1 + '" and "' + newPath2 + '" instead.'
+        runProgram(['git-update-cache', '--force-remove', '--', path])
+        runProgram(['git-update-cache', '--add',
+                    '--cacheinfo', aMode, aSha, newPath1,
+                    '--cacheinfo', bMode, bSha, newPath2])
+
+elif oSha != '' and aSha != '' and bSha != '':
+#
+# case D: Modified in both, but differently.
+#
+    print 'Auto-merging ' + path + '.'
+    orig = runProgram(['git-unpack-file', oSha]).rstrip()
+    src1 = runProgram(['git-unpack-file', aSha]).rstrip()
+    src2 = runProgram(['git-unpack-file', bSha]).rstrip()
+    [out, ret] = runProgram(['merge',
+                             '-L', os.environ['GIT_MERGE_BRANCH_1'] + '/' + path,
+                             '-L', 'orig/' + path,
+                             '-L', os.environ['GIT_MERGE_BRANCH_2'] + '/' + path,
+                             src1, orig, src2], returnCode=True)
+
+    if ret != 0:
+        print 'CONFLICT (content): Merge conflict in "' + path + '".'
+
+    if aMode == oMode:
+        mode = bMode
+    else:
+        mode = aMode
+
+    sha = runProgram(['git-hash-object', '-t', 'blob', '-w', src1])
+    runProgram(['git-update-cache', '--cacheinfo', aMode, sha, path])
+    os.unlink(orig)
+    os.unlink(src1)
+    os.unlink(src2)
+else:
+    print 'ERROR: Fatal merge failure.'
+    print "ERROR: Shouldn't happen"
+
diff --git a/gitMergeCommon.py b/gitMergeCommon.py
new file mode 100644
--- /dev/null
+++ b/gitMergeCommon.py
@@ -0,0 +1,68 @@
+import sys
+
+if sys.version_info[0] < 2 or (sys.version_info[0] == 2 and sys.version_info[1] < 4):
+    print 'Python version 2.4 required, found', \
+    str(sys.version_info[0])+'.'+str(sys.version_info[1])+'.'+str(sys.version_info[2])
+    sys.exit(1)
+
+import subprocess, os
+
+DEBUG = 1
+
+def debug(str, level=1):
+    if level > DEBUG:
+        print str
+
+class ProgramError(Exception):
+    def __init__(self, progStr, error):
+        self.progStr = progStr
+        self.error = error
+
+def runProgram(prog, input=None, returnCode=False):
+    debug('runProgram prog: ' + str(prog) + " input: " + str(input))
+    if type(prog) is str:
+        progStr = prog
+    else:
+        progStr = ' '.join(prog)
+    
+    try:
+        pop = subprocess.Popen(prog,
+                               shell = type(prog) is str,
+                               stderr=subprocess.STDOUT,
+                               stdout=subprocess.PIPE,
+                               stdin=subprocess.PIPE)
+    except OSError, e:
+        debug("strerror: " + e.strerror, 2)
+        raise ProgramError(progStr, e.strerror)
+
+    if input != None:
+        pop.stdin.write(input)
+    pop.stdin.close()
+    
+    out = pop.stdout.read()
+    code = pop.wait()
+    if returnCode:
+        ret = [out, code]
+    else:
+        ret = out
+    if code != 0 and not returnCode:
+            print "error output: " + out
+            print 'prog: ' + str(prog)
+            raise ProgramError(progStr, out)
+    debug("output: " + out.replace('\0', '\n'))
+    return ret
+
+def setupEnvironment():
+    if 'GIT_DIR' not in os.environ:
+        os.environ['GIT_DIR'] = '.git'
+
+    if not os.environ.has_key('GIT_OBJECT_DIRECTORY'):
+        os.environ['GIT_OBJECT_DIRECTORY'] = os.environ['GIT_DIR'] + '/objects'
+
+def repoValid():
+    if not (os.path.exists(os.environ['GIT_DIR']) and
+            os.path.exists(os.environ['GIT_DIR'] + '/refs') and
+            os.path.exists(os.environ['GIT_OBJECT_DIRECTORY']) and
+            os.path.exists(os.environ['GIT_OBJECT_DIRECTORY'] + '/00')):
+        print "Not a Git archive"
+        sys.exit(1)
diff --git a/gitMergeCore.py b/gitMergeCore.py
new file mode 100644
--- /dev/null
+++ b/gitMergeCore.py
@@ -0,0 +1,350 @@
+import sys, math, random, os, re, signal, tempfile
+from gitMergeCommon import runProgram, debug, DEBUG
+from heapq import heappush, heappop
+from sets import Set
+
+class GitObject:
+    def __init__(self, sha):
+        self.sha = sha.rstrip()
+
+def loadCommit(sha):
+    c = Commit(sha)
+    c.getInfo()
+    return c
+
+class Commit(GitObject):
+    def __init__(self, sha, parents=None, tree=None, author=None,
+                 committer=None, msg=None):
+        self.parents = parents
+        self.author = author
+        self.committer = committer
+        self.msg = msg
+        self.children = []
+
+        if tree:
+            tree = tree.rstrip()
+        self._tree = tree
+
+        if not sha:
+            sha = self.writeObject(False)
+
+        GitObject.__init__(self, sha)
+
+    def writeObject(self, write=True):
+        fout = tempfile.NamedTemporaryFile('w')
+
+        fout.write('tree ' + self._tree + '\n')
+        for p in self.parents:
+            if isinstance(p, Commit):
+                p = p.sha
+            fout.write('parent ' + p + '\n')
+        fout.write('author Temporary merge author <temp@example.com> 0 +0000\n')
+        fout.write('committer Temporary merge committer <temp@example.com> 0 +0000\n\n')
+        fout.write(self.msg + '\n')
+        fout.flush()
+        if write:
+            runProgram(['git-hash-object', '-w', '-t', 'commit', fout.name])
+            ret = None
+        else:
+            ret = runProgram(['git-hash-object', '-t', 'commit', fout.name]).rstrip()
+        fout.close()
+        return ret
+        
+    def tree(self):
+        self.getInfo()
+        return self._tree
+
+    def shortInfo(self):
+        self.getInfo()
+        return self.sha.rstrip() + ' ' + self.msg.split('\n')[0]
+
+    def __str__(self):
+        return self.shortInfo()
+
+    def getInfo(self):
+        if self.msg != None:
+            return
+        else:
+            info = runProgram(['git-cat-file', 'commit', self.sha])
+            info = info.split('\n')
+            msg = False
+            self.msg = ''
+            newParents = []
+            for l in info:
+                if msg:
+                    self.msg += l + '\n'
+                else:
+                    if l.startswith('tree'):
+                        self._tree = l[5:].rstrip()
+                    elif l.startswith('author'):
+                        self.author = l
+                    elif l.startswith('committer'):
+                        self.committer = l
+                    elif l.startswith('parent'):
+                        newParents.append(l[7:].rstrip())
+                    elif l == '':
+                        msg = True
+            if not self.parents:
+                self.parents = newParents
+
+    # This is needed by the heap implementation in heapq. We want a
+    # max heap, but heapq provides us with a min heap. We therefore use
+    # >= instead of <= in this function.
+    def __le__(self, other):
+        return self.index >= other.index
+
+class Graph:
+    def __init__(self):
+        self.topoOrder = []
+        self.shaMap = {}
+
+    def addNode(self, node):
+        assert(isinstance(node, Commit) or isSha(node))
+        if isSha(node):
+            node = loadCommit(node)
+            self.fixParents(node)
+        self.shaMap[node.sha] = node
+        node.index = len(self.topoOrder)
+        self.topoOrder.append(node)
+        for p in node.parents:
+            p.children.append(node)
+        return node
+
+    def reachableNodes(self, n1, n2):
+        res = {}
+        def traverse(n):
+            res[n] = True
+            for p in n.parents:
+                traverse(p)
+
+        traverse(n1)
+        traverse(n2)
+        return res
+
+    def fixParents(self, node):
+        for x in range(0, len(node.parents)):
+            node.parents[x] = self.shaMap[node.parents[x]]
+
+def buildGraph(heads):
+    print 'buildGraph heads: ' + str(heads)
+    for h in heads:
+        assert(isSha(h))
+
+    g = Graph()
+
+    out = runProgram(['git-rev-list', '--parents', '--topo-order'] + heads)
+    print 'git-rev-list done.'
+    for l in out.split('\n'):
+        if l == '':
+            continue
+        shas = l.split(' ')
+
+        # This is a hack, we temporarily use the 'parents' attribute
+        # to contain a list of SHA1:s. They are later replaced by proper
+        # Commit objects.
+        c = Commit(shas[0], shas[1:])
+
+        g.topoOrder.append(c)
+        g.shaMap[c.sha] = c
+
+    for c in g.topoOrder:
+        g.fixParents(c)
+
+    g.topoOrder.reverse()
+
+#    print 'topoOrder:'
+#    for c in topoOrder:
+#        print str(c) + ' sha: ' + c.sha
+#        print 'parents: ' + str(c.parents)
+            
+    i = 0
+    for c in g.topoOrder:
+        c.index = i
+        i += 1
+        for p in c.parents:
+#            print 'p: ' + str(p) + ' c: ' + str(c)
+            p.children.append(c)
+    return g
+
+# Write the empty tree to the object database and return its SHA1
+def writeEmptyTree():
+    try:
+        os.unlink(os.environ['GIT_DIR'] + '/index')
+    except OSError:
+        pass
+    return runProgram(['git-write-tree']).rstrip()
+
+# Given a Graph of commit objects and two heads (which also are commit
+# objects), h1 and h2, this function computes the Minimal Most Common
+# Ancestor of h1 and h2.
+def mmca(graph, h1, h2):
+    h = []
+    heappush(h, h1)
+    heappush(h, h2)
+
+    roots = []
+    while(len(h) > 0):
+#        print 'heap: '
+#        for x in h:
+#            print str(x), x.index
+        if len(h) == 1 and len(roots) == 0:
+            break
+        
+        el = heappop(h)
+
+        if len(el.parents) == 0:
+            roots.append(el)
+
+        for p in el.parents:
+            if not p in h:
+                heappush(h, p)
+
+    if len(roots) > 0:
+        superRoot = Commit(sha=None, parents=[], tree=writeEmptyTree(), author='author',
+                           committer='committer', msg='empty tree commit')
+        graph.addNode(superRoot)
+        for r in roots:
+            r.parents = [superRoot]
+        superRoot.children = roots
+        return superRoot
+    else:
+        return h[0]
+
+def findShared(mmca, h1, h2):
+    def traverse(start, end, set):
+        stack = [start]
+        while len(stack) > 0:
+            el = stack.pop()
+            set.add(el)
+            if start != end:
+                for p in el.parents:
+                    if p not in set:
+                        stack.append(p)
+    h1Set = Set()
+    h2Set = Set()
+    traverse(h1, mmca, h1Set)
+    traverse(h2, mmca, h2Set)
+
+    return h1Set.intersection(h2Set)
+
+def sharedHeads(mmca, shared):
+    h = Set()
+    searched = Set()
+    stack = [mmca]
+    while len(stack) > 0:
+        el = stack.pop()
+        searched.add(el)
+        if len([c for c in el.children if c in shared]) == 0:
+            h.add(el)
+
+        for c in el.children:
+            if c in shared and c not in searched:
+                stack.append(c)
+
+    return list(h)
+
+getFilesRE = re.compile('([0-9]+) ([a-z0-9]+) ([0-9a-f]{40})\t(.*)')
+def getFilesAndDirs(tree1, tree2):
+    files = Set()
+    dirs = Set()
+    def addFilesDirs(tree):
+        out = runProgram(['git-ls-tree', '-r', '-z', tree])
+        for l in out.split('\0'):
+            m = getFilesRE.match(l)
+            if m:
+                if m.group(2) == 'tree':
+                    dirs.add(m.group(4))
+                elif m.group(2) == 'blob':
+                    files.add(m.group(4))
+
+    addFilesDirs(tree1)
+    addFilesDirs(tree2)
+    return [files, dirs]
+
+def merge(h1, h2, branch1Name, branch2Name, graph, indent=0, first=True):
+    print '\n' + '  '*indent, 'Merging:'
+    print '  '*indent, h1
+    print '  '*indent, h2
+    assert(isinstance(h1, Commit) and isinstance(h2, Commit))
+    assert(isinstance(graph, Graph))
+
+    a = mmca(graph, h1, h2)
+    print '  '*indent, 'mmca:', a
+    s = findShared(a, h1, h2)
+    print '  '*indent, 'found', len(s), 'shared commits.'
+    if len(s) < 10:
+        for x in s:
+            print '  '*indent, x
+    sys.stdout.flush()
+    s = sharedHeads(a, s)
+    print '  '*indent, 'found', len(s), 'shared head(s):'
+    for x in s :
+        print '  '*indent, x
+    Ms = s[0]
+
+    for h in s[1:]:
+        Ms = merge(Ms, h, branch1Name, branch2Name, graph, indent+1, False)
+
+    filesDirs = getFilesAndDirs(h1.tree(), h2.tree())
+    
+    print '  '*indent, 'running resolve on'
+    print '  '*indent, h1
+    print '  '*indent, h2
+
+    if first:
+        b1 = branch1Name
+        b2 = branch2Name
+    else:
+        b1 = 'Temporary shared merge branch 1'
+        b2 = 'Temporary shared merge branch 2'
+    shaRes = resolve(h1.tree(), h2.tree(), Ms.tree(), b1, b2, filesDirs)
+
+    res = Commit(None, [h1, h2], tree=shaRes, msg='Temporary merge commit')
+    graph.addNode(res)
+    res.writeObject()
+    print '  '*indent, 'merge returned: (tree)',  res.tree(), '(commit)', res.sha
+    return res
+
+shaRE = re.compile('^[0-9a-f]{40}$')
+def isSha(obj):
+    return type(obj) is str and bool(shaRE.match(obj))
+
+def resolve(head, merge, common, b1, b2, filesDirs):
+    assert(isSha(head) and isSha(merge) and isSha(common))
+    head = runProgram(['git-rev-parse', '--revs-only', head]).rstrip()
+    merge = runProgram(['git-rev-parse', '--revs-only', merge]).rstrip()
+
+    if common == merge:
+        return head
+
+    if common == head:
+        return merge
+
+    runProgram(['git-read-tree', head])
+    runProgram(['git-read-tree', '-i', '-m', common, head, merge])
+    os.environ['GIT_MERGE_BRANCH_1'] = b1
+    os.environ['GIT_MERGE_BRANCH_2'] = b2
+    fdirs = tempfile.NamedTemporaryFile('w')
+    fdirs.write(repr(filesDirs))
+    fdirs.flush()
+    os.environ['GIT_MERGE_DIRS'] = fdirs.name
+    try:
+        [tree, code] = runProgram('git-write-tree', returnCode=True)
+        tree = tree.rstrip()
+        if code != 0:
+            [out, code] = runProgram(['git-merge-cache', '-o',
+                                      'git-new-merge-one-file.py', '-a'],
+                                     returnCode=True)
+            
+            print out
+            if code != 0:
+                # Shouldn't happen...
+                print 'Fatal error: merge program failed.'
+                print out
+                sys.exit(1)
+            else:
+                return runProgram('git-write-tree').rstrip()
+        else:
+            return tree
+    finally:
+        fdirs.close()
diff --git a/merge.py b/merge.py
new file mode 100755
--- /dev/null
+++ b/merge.py
@@ -0,0 +1,39 @@
+#!/usr/bin/env python
+
+from gitMergeCommon import *
+from gitMergeCore import buildGraph, merge
+import sys
+
+if len(sys.argv) < 1:
+    print 'Usage:', sys.argv[0], '<branch>'
+    sys.exit(1)
+
+setupEnvironment()
+repoValid()
+
+h1 = firstBranch = 'HEAD'
+h2 = secondBranch = sys.argv[1]
+
+print 'h1: ' + h1 + ' h2: ' + h2
+h1 = runProgram(['git-rev-parse', '--revs-only', h1]).rstrip()
+h2 = runProgram(['git-rev-parse', '--revs-only', h2]).rstrip()
+print 'Resolved heads: h1: ' + h1 + ' h2: ' + h2
+
+print 'Building graph...'
+graph = buildGraph([h1, h2])
+print 'graph done.'
+
+res = merge(graph.shaMap[h1], graph.shaMap[h2], firstBranch, secondBranch, graph)
+print 'Merge result: (tree) ' + res.tree() + ' (commit) ' + res.sha
+
+# Checkout the merge results
+runProgram(['git-read-tree', 'HEAD'])
+runProgram(['git-update-cache', '--refresh'])
+runProgram(['git-read-tree', '-m', '-u', 'HEAD', res.tree()])
+
+try:
+    fout = open(os.environ['GIT_DIR'] + '/MERGE_HEAD', 'w+')
+    fout.write(h2 + '\n')
+    fout.close()
+except OSError, e:
+    print 'Failed to open', os.environ['GIT_DIR'] + '/MERGE_HEAD', '!'
diff --git a/read-tree.c b/read-tree.c
--- a/read-tree.c
+++ b/read-tree.c
@@ -7,6 +7,7 @@
 
 static int stage = 0;
 static int update = 0;
+static int ignore_working_dir = 0;
 
 static int unpack_tree(unsigned char *sha1)
 {
@@ -80,7 +81,10 @@ static void verify_uptodate(struct cache
 {
 	struct stat st;
 
-	if (!lstat(ce->name, &st)) {
+        if (ignore_working_dir)
+            return;
+
+        if (!lstat(ce->name, &st)) {
 		unsigned changed = ce_match_stat(ce, &st);
 		if (!changed)
 			return;
@@ -510,7 +514,7 @@ static int read_cache_unmerged(void)
 	return deleted;
 }
 
-static const char read_tree_usage[] = "git-read-tree (<sha> | -m [-u] <sha1> [<sha2> [<sha3>]])";
+static const char read_tree_usage[] = "git-read-tree (<sha> | -m [-u] [-i] <sha1> [<sha2> [<sha3>]])";
 
 static struct cache_file cache_file;
 
@@ -535,6 +539,11 @@ int main(int argc, char **argv)
 			continue;
 		}
 
+                if (!strcmp(arg, "-i")) {
+                        ignore_working_dir = 1;
+                        continue;
+                }
+
 		/* This differs from "-m" in that we'll silently ignore unmerged entries */
 		if (!strcmp(arg, "--reset")) {
 			if (stage || merge || emu23)

^ permalink raw reply

* Re: Fix: remove generated template files
From: Junio C Hamano @ 2005-08-26 18:52 UTC (permalink / raw)
  To: Marco Costalba; +Cc: git
In-Reply-To: <20050826182624.86421.qmail@web26306.mail.ukl.yahoo.com>

Marco Costalba <mcostalba@yahoo.it> writes:

> 01168c6044e7ebb570e851b4707b4fe0c7f40c15
> diff --git a/templates/blt/description b/templates/blt/description

Could I please see output of the following command in your
repository?

  $ git whatchanged 01168c6044e7ebb570e851b4707b4fe0c7f40c15 templates/

^ permalink raw reply

* Re: baffled again
From: Horst von Brand @ 2005-08-26 19:50 UTC (permalink / raw)
  To: Tony Luck; +Cc: Junio C Hamano, Linus Torvalds, tony.luck, git
In-Reply-To: <12c511ca05082422584e6b1bfb@mail.gmail.com>

Tony Luck <tony.luck@gmail.com> wrote:
> >  * Even if it does always choose the nicer choice of the two,
> >    Tony was lucky (no pun intended).  Rather, we were lucky that
> >    Tony was observant.  A careless merger may well have easily
> >    missed this mismerge (from the human point of view).

> Actually I can't take credit here. This was a case of the "many-eyes" of
> open source working at its finest ... someone e-mailed me and told me
> that I should have backed out the old patch before applying the new one.
> While typing the e-mail to say that I already had in the release branch,
> I found the problem that it had been "lost" in the merge into the test
> branch.

> But this is a good reminder that merging is not a precise science, and
> there is more than one plausible merge in many situations ... and while
> GIT will pick the one that you want far more often than not, there is
> the possibility that it will surprise you.  Maybe there should be a note
> to this effect in the tutorial.  Git is not magic, nor is it imbued with
> DWIM technology.

I have to disagree. If in some corner case it can do the wrong thing, no
amount of "I told you so in the docu!" will save the day. People /will/
overlook it, or be bitten when they have all but forgotten about it.
-- 
Dr. Horst H. von Brand                   User #22616 counter.li.org
Departamento de Informatica                     Fono: +56 32 654431
Universidad Tecnica Federico Santa Maria              +56 32 654239
Casilla 110-V, Valparaiso, Chile                Fax:  +56 32 797513

^ permalink raw reply

* Re: [RFC, PATCH] A new merge algorithm (EXPERIMENTAL)
From: Daniel Barkalow @ 2005-08-26 20:48 UTC (permalink / raw)
  To: Fredrik Kuivinen; +Cc: git
In-Reply-To: <20050826184731.GA13629@c165.ib.student.liu.se>

On Fri, 26 Aug 2005, Fredrik Kuivinen wrote:

> I will try to describe how the algorithm works. The problem with the
> usual 3-way merge algorithm is that we sometimes do not have a unique
> common ancestor. In [1] B and C seems to be equally good. What this
> algorithm does is to _merge_ the common ancestors, in this case B and
> C, into a temporary tree lets call it T. It does then use this
> temporary tree T as the common ancestor for D and E to produce the
> final merge result. In the case described in [1] this will work out
> fine and we get a clean merge with the expected result.

The only problem I can see with this is that it's likely to generate
conflicts between the shared heads, and the user is going to be confused
trying to resolve them, because the files with the conflicts will be
missing all of the more recent changes. Other than that, I think it should
give the right answer, although it will presumably involve a lot of
ancient history doing the internal merge. (Which would probably be really
painful if you've got two branches that cross-merge regularly and never
actually completely sync)

I'm getting pretty close to having a version of read-tree that does the
trivial merge portion based comparing the sides against all of the shared
heads. I think yours will be better for the cases we've identified, giving
the correct answer for Tony's case rather than reporting a conflict, but
it's clearly more complicated. I think my changes are worthwhile anyway,
since they make the merging logic more central, but obviously
insufficient.

I've been thinking that could be useful to have read-tree figure out the
history itself, instead of being passed ancestors, in which case it could
use your method, except more efficiently (and only look further at the
history when needed).

	-Daniel
*This .sig left intentionally blank*

^ permalink raw reply

* Re: [RFC, PATCH] A new merge algorithm (EXPERIMENTAL)
From: Fredrik Kuivinen @ 2005-08-26 21:39 UTC (permalink / raw)
  To: Daniel Barkalow; +Cc: git
In-Reply-To: <Pine.LNX.4.63.0508261558550.23242@iabervon.org>

On Fri, Aug 26, 2005 at 04:48:32PM -0400, Daniel Barkalow wrote:
> On Fri, 26 Aug 2005, Fredrik Kuivinen wrote:
> 
> > I will try to describe how the algorithm works. The problem with the
> > usual 3-way merge algorithm is that we sometimes do not have a unique
> > common ancestor. In [1] B and C seems to be equally good. What this
> > algorithm does is to _merge_ the common ancestors, in this case B and
> > C, into a temporary tree lets call it T. It does then use this
> > temporary tree T as the common ancestor for D and E to produce the
> > final merge result. In the case described in [1] this will work out
> > fine and we get a clean merge with the expected result.
> 
> The only problem I can see with this is that it's likely to generate
> conflicts between the shared heads, and the user is going to be confused
> trying to resolve them, because the files with the conflicts will be
> missing all of the more recent changes.

I don't actually think that conflicts between shared heads is a
problem. Given the criss-cross case (we want to merge A and B into M):

 M
 |\
 | \
 A  B
 |\/|
 |/\|
 C  D
 | /
 |/
 E

Lets assume there is a merge conflict if we try to merge C and D
(which are the two shared heads). Then both A and B must resolve this
conflict. If they have done it in the same way we wont get a merge
conflict at M, if they have resolved it differently we will get a
merge conflict. In the first case there is no merge conflict at M, in
the second case the user has to pick which one of the two different
resolutions she wants.

Note that the algorithm will happily write non-clean merge results to
the object database during the "merge shared heads" stage. Hence, when
we are merging C and D "internally" we will _not_ ask the user to
resolve any eventual merge conflicts.

> Other than that, I think it should
> give the right answer, although it will presumably involve a lot of
> ancient history doing the internal merge. (Which would probably be really
> painful if you've got two branches that cross-merge regularly and never
> actually completely sync)

The expensive part is the repeated merging. But as I wrote in my mail
multiple shared heads seems to be pretty uncommon. As far as I can
tell there is no reason for the number of shared heads to increase as
a repository grows larger. However, this do probably depend on usage
patterns.

But it is certainly possible to construct cases with an arbitrary
number of shared heads. In which case the algorithm will be a bit
slow, at least if there are real content merges going on which are
handled by merge(1).

> I'm getting pretty close to having a version of read-tree that does the
> trivial merge portion based comparing the sides against all of the shared
> heads. I think yours will be better for the cases we've identified, giving
> the correct answer for Tony's case rather than reporting a conflict, but
> it's clearly more complicated. I think my changes are worthwhile anyway,
> since they make the merging logic more central, but obviously
> insufficient.
> 
> I've been thinking that could be useful to have read-tree figure out the
> history itself, instead of being passed ancestors, in which case it could
> use your method, except more efficiently (and only look further at the
> history when needed).

It will be interesting to have a look at the code when you are done.
I find the Git architecture with respect to merging to be quite
nice. A core which handles the simple cases _fast_ and let the more
complicated cases be handled by someone else.

- Fredrik

^ permalink raw reply

* Re: [RFC, PATCH] A new merge algorithm (EXPERIMENTAL)
From: Daniel Barkalow @ 2005-08-26 22:45 UTC (permalink / raw)
  To: Fredrik Kuivinen; +Cc: git
In-Reply-To: <20050826213900.GA18880@c165.ib.student.liu.se>

On Fri, 26 Aug 2005, Fredrik Kuivinen wrote:

> On Fri, Aug 26, 2005 at 04:48:32PM -0400, Daniel Barkalow wrote:
> > On Fri, 26 Aug 2005, Fredrik Kuivinen wrote:
> >
> > > I will try to describe how the algorithm works. The problem with the
> > > usual 3-way merge algorithm is that we sometimes do not have a unique
> > > common ancestor. In [1] B and C seems to be equally good. What this
> > > algorithm does is to _merge_ the common ancestors, in this case B and
> > > C, into a temporary tree lets call it T. It does then use this
> > > temporary tree T as the common ancestor for D and E to produce the
> > > final merge result. In the case described in [1] this will work out
> > > fine and we get a clean merge with the expected result.
> >
> > The only problem I can see with this is that it's likely to generate
> > conflicts between the shared heads, and the user is going to be confused
> > trying to resolve them, because the files with the conflicts will be
> > missing all of the more recent changes.
>
> I don't actually think that conflicts between shared heads is a
> problem. Given the criss-cross case (we want to merge A and B into M):
>
>  M
>  |\
>  | \
>  A  B
>  |\/|
>  |/\|
>  C  D
>  | /
>  |/
>  E
>
> Lets assume there is a merge conflict if we try to merge C and D
> (which are the two shared heads). Then both A and B must resolve this
> conflict. If they have done it in the same way we wont get a merge
> conflict at M, if they have resolved it differently we will get a
> merge conflict. In the first case there is no merge conflict at M, in
> the second case the user has to pick which one of the two different
> resolutions she wants.
>
> Note that the algorithm will happily write non-clean merge results to
> the object database during the "merge shared heads" stage. Hence, when
> we are merging C and D "internally" we will _not_ ask the user to
> resolve any eventual merge conflicts.

Oh, okay, didn't see that part. So the merge for M sees that the old
conflict is replaced entire with the common resolution or with a conflict
between the different resolutions, but it doesn't report the old conflict
anyway, because that section's been replaced in both sides.

> > Other than that, I think it should
> > give the right answer, although it will presumably involve a lot of
> > ancient history doing the internal merge. (Which would probably be really
> > painful if you've got two branches that cross-merge regularly and never
> > actually completely sync)
>
> The expensive part is the repeated merging. But as I wrote in my mail
> multiple shared heads seems to be pretty uncommon. As far as I can
> tell there is no reason for the number of shared heads to increase as
> a repository grows larger. However, this do probably depend on usage
> patterns.

I'd guess that the number of shared heads will increase as the people's
usage gets more flexible. If people expected good results, I could see the
stable series being mostly done as patches to 2.6.X, which would then be
merged into various trees, and these would then be frequent common
ancestors in merges. I'd also not be surprised in Linus's tree were
abnormally straightforward, due to stuff getting serialized in -mm.

> > I'm getting pretty close to having a version of read-tree that does the
> > trivial merge portion based comparing the sides against all of the shared
> > heads. I think yours will be better for the cases we've identified, giving
> > the correct answer for Tony's case rather than reporting a conflict, but
> > it's clearly more complicated. I think my changes are worthwhile anyway,
> > since they make the merging logic more central, but obviously
> > insufficient.
> >
> > I've been thinking that could be useful to have read-tree figure out the
> > history itself, instead of being passed ancestors, in which case it could
> > use your method, except more efficiently (and only look further at the
> > history when needed).
>
> It will be interesting to have a look at the code when you are done.
> I find the Git architecture with respect to merging to be quite
> nice. A core which handles the simple cases _fast_ and let the more
> complicated cases be handled by someone else.

Right; I'm mostly just trying to get the fast path to not miss cases that
are more complicated than they look.

	-Daniel
*This .sig left intentionally blank*

^ permalink raw reply

* Re: [RFC, PATCH] A new merge algorithm (EXPERIMENTAL)
From: Junio C Hamano @ 2005-08-26 22:50 UTC (permalink / raw)
  To: Fredrik Kuivinen; +Cc: git, Daniel Barkalow
In-Reply-To: <20050826213900.GA18880@c165.ib.student.liu.se>

Fredrik Kuivinen <freku045@student.liu.se> writes:

> I find the Git architecture with respect to merging to be quite
> nice. A core which handles the simple cases _fast_ and let the more
> complicated cases be handled by someone else.

Exactly my feeling.  The three-way read-tree was made to do
"trivial" merges because the three-way merge operation is so
fundamental and more importantly because in what it does there
is no other plausible heuristics.  The cases that it leaves
stages unmerged in the index file are cases that a merge policy
might want to use heuristics to affect the outcome, and are left
for merge-cache to resolve.  And it is a good thing.

If the updated read-tree reads more than three trees at once and
makes decision internally which ones to choose, it would be very
desirable to see an implementation in which there is _no_
heuristics involved in the selection made by the built-in logic.

If there can be better selection made by more expensive logic,
and faster but less reliable heuristics by cheaper logic, it
would be very nice for the selection logic to call out a helper
"merge policy" program or dll module, just like the way
merge-cache calls merge-one-file-script, to make that decision.

^ permalink raw reply

* Re: Storing state in $GIT_DIR
From: Linus Torvalds @ 2005-08-27  0:23 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: Daniel Barkalow, Martin Langhoff, GIT
In-Reply-To: <7virxti6y5.fsf@assigned-by-dhcp.cox.net>



On Thu, 25 Aug 2005, Junio C Hamano wrote:
> 
> "v0.99.5^0" means "look at the named object v0.99.5, dereference
> it repeatedly until you get a non-tag, and take the result,
> which had better be a commit".  If a tag can contain more than
> one pointers, I do not know what it means.

Yeah, we'd have to either just say "I can't do that, Dave", or specify 
that it only looks at the first object in the list.

		Linus

^ permalink raw reply

* Re: [RFC, PATCH] A new merge algorithm (EXPERIMENTAL)
From: Linus Torvalds @ 2005-08-27  0:38 UTC (permalink / raw)
  To: Fredrik Kuivinen; +Cc: git
In-Reply-To: <20050826184731.GA13629@c165.ib.student.liu.se>



On Fri, 26 Aug 2005, Fredrik Kuivinen wrote:
>
> In real numbers it is as follows: In Linus' kernel tree there are
> 5996 commits. 400 of those have more than one parent. Of those 400
> merge commits 4 have more than one shared head.

Ok, that's already interesting in itself. I was wanting to re-run all the 
merges with the new "git-merge-base -a" to see which merges might have had 
different merge bases, and you've actually done that. Interesting to see 
the numbers.

> * Is it worth it? That is, is the added complexity in the merge logic
>   worth the advantages of correctly handling some strange (but real
>   life) merge cases?

I am of two minds on this. I hate the notion of a more complex merge. But
at the same time, it clearly is a very interesting case when we do have
multiple possible shared parents, and I think that at the very least we
should warn the user. And using a more complex merge algorithm when it
happens seems to be a very valid thing to do.

Also, it's possible that other developers see more of the criss-crossing
merges than I do - iow, they're probably more likely to happen in the
throw-away trees than in some of the main trees. Neither of the two cases
we've seen and had issues were merges I did, for example. Which means that
your "1% of all merges" number is probably low. Of course, it's quite
likely that in most cases, the "pick either one" approach will give the
exact same result as the more complex merge.

Using python, which people have less exposure to, sounds like an 
additional thorny issue..

		Linus

^ permalink raw reply

* Re: [RFC, PATCH] A new merge algorithm (EXPERIMENTAL)
From: Junio C Hamano @ 2005-08-27  1:08 UTC (permalink / raw)
  To: Linus Torvalds; +Cc: git
In-Reply-To: <Pine.LNX.4.58.0508261730480.3317@g5.osdl.org>

Linus Torvalds <torvalds@osdl.org> writes:

> On Fri, 26 Aug 2005, Fredrik Kuivinen wrote:
>>
>> In real numbers it is as follows: In Linus' kernel tree there are
>> 5996 commits. 400 of those have more than one parent. Of those 400
>> merge commits 4 have more than one shared head.
>
> Ok, that's already interesting in itself. I was wanting to re-run all the 
> merges with the new "git-merge-base -a" to see which merges might have had 
> different merge bases, and you've actually done that. Interesting to see 
> the numbers.

Fredrik, mind giving us the commit ID of those four for us to
take a look at them?

> I am of two minds on this. I hate the notion of a more complex merge. But
> at the same time, it clearly is a very interesting case when we do have
> multiple possible shared parents, and I think that at the very least we
> should warn the user. And using a more complex merge algorithm when it
> happens seems to be a very valid thing to do.

I agree.  GIT is lightening fast for trivial cases and we can
afford to spend more time to handle more complex ones carefully,
at the same time telling the user what we are doing is a good thing.

> Using python, which people have less exposure to, sounds like an 
> additional thorny issue..

Not too big a problem for me to follow the patch ;-).

^ permalink raw reply

* Subject: [PATCH] Add some documentation.
From: A Large Angry SCM @ 2005-08-27  1:18 UTC (permalink / raw)
  To: git, junkio

Add some documentation.

Text taken from the the commit messages and the command sources.

---

I made no attempt to use the proper terminology, as defined by the new
glossary, but instead used the text from the original commit messages and/or
the program source.

These are some of the easy ones. Some others (like git-diff-script) will
require real effort to adequately document.

Hopefully I followed my own instructions correctly and will avoid being hit
by the "shiny blue bat". :-)

 Documentation/git-add-script.txt           |   17 ++++++-----------
 Documentation/git-branch-script.txt        |   19 ++++++++++---------
 Documentation/git-cherry.txt               |   22 +++++++++++++---------
 Documentation/git-count-objects-script.txt |   18 ++++--------------
 Documentation/git-patch-id.txt             |   19 +++++++++----------
 Documentation/git-rebase-script.txt        |   17 +++++++----------
 Documentation/git-relink-script.txt        |   19 +++++++++----------
 Documentation/git-revert-script.txt        |   18 +++++++-----------
 Documentation/git-sh-setup-script.txt      |   18 +++++++-----------
 Documentation/git-verify-tag-script.txt    |   16 +++++-----------
 10 files changed, 77 insertions(+), 106 deletions(-)

a02a9ef2a347e4fac8088fb289dd24c7110c4ee4
diff --git a/Documentation/git-add-script.txt b/Documentation/git-add-script.txt
--- a/Documentation/git-add-script.txt
+++ b/Documentation/git-add-script.txt
@@ -3,26 +3,21 @@ git-add-script(1)

 NAME
 ----
-git-add-script - Some git command not yet documented.
-
+git-add-script - Add files to the cache.

 SYNOPSIS
 --------
-'git-add-script' [ --option ] <args>...
+'git-add-script' [<file>]\+

 DESCRIPTION
 -----------
-Does something not yet documented.
-
+A simple wrapper to git-update-cache to add files to the cache for people used
+to do "cvs add".

 OPTIONS
 -------
---option::
-	Some option not yet documented.
-
-<args>...::
-	Some argument not yet documented.
-
+<file>::
+	Files to add to the cache.

 Author
 ------
diff --git a/Documentation/git-branch-script.txt b/Documentation/git-branch-script.txt
--- a/Documentation/git-branch-script.txt
+++ b/Documentation/git-branch-script.txt
@@ -3,26 +3,27 @@ git-branch-script(1)

 NAME
 ----
-git-branch-script - Some git command not yet documented.
-
+git-branch-script - Create a new branch.

 SYNOPSIS
 --------
-'git-branch-script' [ --option ] <args>...
+'git-branch-script' [<branchname> [start-point]]

 DESCRIPTION
 -----------
-Does something not yet documented.
+If no argument is provided, show available branches and mark current
+branch with star. Otherwise, create a new branch of name <branchname>.

+If a starting point is also specified, that will be where the branch is
+created, otherwise it will be created at the current HEAD.

 OPTIONS
 -------
---option::
-	Some option not yet documented.
-
-<args>...::
-	Some argument not yet documented.
+<branchname>::
+	The name of the branch to create.

+start-point::
+	Where to make the branch; defaults to HEAD.

 Author
 ------
diff --git a/Documentation/git-cherry.txt b/Documentation/git-cherry.txt
--- a/Documentation/git-cherry.txt
+++ b/Documentation/git-cherry.txt
@@ -3,26 +3,30 @@ git-cherry(1)

 NAME
 ----
-git-cherry - Some git command not yet documented.
-
+git-cherry - Find commits not merged upstream.

 SYNOPSIS
 --------
-'git-cherry' [ --option ] <args>...
+'git-cherry' [-v] <upstream> [<head>]

 DESCRIPTION
 -----------
-Does something not yet documented.
-
+Each commit between the fork-point and <head> is examined, and compared against
+the change each commit between the fork-point and <upstream> introduces.
+Commits already included in upstream are prefixed with '-' (meaning "drop from
+my local pull"), while commits missing from upstream are prefixed with '+'
+(meaning "add to the updated upstream").

 OPTIONS
 -------
---option::
-	Some option not yet documented.
+-v::
+	Verbose.

-<args>...::
-	Some argument not yet documented.
+<upstream>::
+	Upstream branch to compare against.

+<head>::
+	Working branch; defaults to HEAD.

 Author
 ------
diff --git a/Documentation/git-count-objects-script.txt b/Documentation/git-count-objects-script.txt
--- a/Documentation/git-count-objects-script.txt
+++ b/Documentation/git-count-objects-script.txt
@@ -3,26 +3,16 @@ git-count-objects-script(1)

 NAME
 ----
-git-count-objects-script - Some git command not yet documented.
-
+git-count-objects-script - Reports on unpacked objects.

 SYNOPSIS
 --------
-'git-count-objects-script' [ --option ] <args>...
+'git-count-objects-script'

 DESCRIPTION
 -----------
-Does something not yet documented.
-
-
-OPTIONS
--------
---option::
-	Some option not yet documented.
-
-<args>...::
-	Some argument not yet documented.
-
+This counts the number of unpacked object files and disk space consumed by
+them, to help you decide when it is a good time to repack.

 Author
 ------
diff --git a/Documentation/git-patch-id.txt b/Documentation/git-patch-id.txt
--- a/Documentation/git-patch-id.txt
+++ b/Documentation/git-patch-id.txt
@@ -3,26 +3,25 @@ git-patch-id(1)

 NAME
 ----
-git-patch-id - Some git command not yet documented.
-
+git-patch-id - Generate a patch ID.

 SYNOPSIS
 --------
-'git-patch-id' [ --option ] <args>...
+'git-patch-id' < <patch>

 DESCRIPTION
 -----------
-Does something not yet documented.
+A "patch ID" is nothing but a SHA1 of the diff associated with a patch, with
+whitespace and line numbers ignored.  As such, it's "reasonably stable", but at
+the same time also reasonably unique, ie two patches that have the same "patch
+ID" are almost guaranteed to be the same thing.

+IOW, you can use this thing to look for likely duplicate commits.

 OPTIONS
 -------
---option::
-	Some option not yet documented.
-
-<args>...::
-	Some argument not yet documented.
-
+<patch>::
+	The diff to create the ID of.

 Author
 ------
diff --git a/Documentation/git-rebase-script.txt b/Documentation/git-rebase-script.txt
--- a/Documentation/git-rebase-script.txt
+++ b/Documentation/git-rebase-script.txt
@@ -3,26 +3,23 @@ git-rebase-script(1)

 NAME
 ----
-git-rebase-script - Some git command not yet documented.
-
+git-rebase-script - Rebase local commits to new upstream head.

 SYNOPSIS
 --------
-'git-rebase-script' [ --option ] <args>...
+'git-rebase-script' <upstream> [<head>]

 DESCRIPTION
 -----------
-Does something not yet documented.
-
+Rebases local commits to the new head of the upstream tree.'

 OPTIONS
 -------
---option::
-	Some option not yet documented.
-
-<args>...::
-	Some argument not yet documented.
+<upstream>::
+	Upstream branch to compare against.

+<head>::
+	Working branch; defaults to HEAD.

 Author
 ------
diff --git a/Documentation/git-relink-script.txt b/Documentation/git-relink-script.txt
--- a/Documentation/git-relink-script.txt
+++ b/Documentation/git-relink-script.txt
@@ -3,26 +3,25 @@ git-relink-script(1)

 NAME
 ----
-git-relink-script - Some git command not yet documented.
-
+git-relink-script - Hardlink common objects in local repositories.

 SYNOPSIS
 --------
-'git-relink-script' [ --option ] <args>...
+'git-relink-script' [--safe] <dir> <dir> [<dir>]\*

 DESCRIPTION
 -----------
-Does something not yet documented.
-
+This will scan 2 or more object repositories and look for common objects, check
+if they are hardlinked, and replace one with a hardlink to the other if not.

 OPTIONS
 -------
---option::
-	Some option not yet documented.
-
-<args>...::
-	Some argument not yet documented.
+--safe::
+	Stops if two objects with the same hash exist but have different sizes.
+	Default is to warn and continue.

+<dir>::
+	Directories containing a .git/objects/ subdirectory.

 Author
 ------
diff --git a/Documentation/git-revert-script.txt b/Documentation/git-revert-script.txt
--- a/Documentation/git-revert-script.txt
+++ b/Documentation/git-revert-script.txt
@@ -3,26 +3,22 @@ git-revert-script(1)

 NAME
 ----
-git-revert-script - Some git command not yet documented.
-
+git-revert-script - Revert an existing commit.

 SYNOPSIS
 --------
-'git-revert-script' [ --option ] <args>...
+'git-revert-script' <commit>

 DESCRIPTION
 -----------
-Does something not yet documented.
-
+Given one existing commit, revert the change the patch introduces, and record a
+new commit that records it.  This requires your working tree to be clean (no
+modifications from the HEAD commit).

 OPTIONS
 -------
---option::
-	Some option not yet documented.
-
-<args>...::
-	Some argument not yet documented.
-
+<commit>::
+	Commit to revert.

 Author
 ------
diff --git a/Documentation/git-sh-setup-script.txt b/Documentation/git-sh-setup-script.txt
--- a/Documentation/git-sh-setup-script.txt
+++ b/Documentation/git-sh-setup-script.txt
@@ -3,26 +3,22 @@ git-sh-setup-script(1)

 NAME
 ----
-git-sh-setup-script - Some git command not yet documented.
-
+git-sh-setup-script - Common git shell script setup code.

 SYNOPSIS
 --------
-'git-sh-setup-script' [ --option ] <args>...
+'git-sh-setup-script'

 DESCRIPTION
 -----------
-Does something not yet documented.
-

-OPTIONS
--------
---option::
-	Some option not yet documented.
+Sets up the normal git environment variables and a few helper functions
+(currently just "die()"), and returns ok if it all looks like a git archive.
+So use it something like

-<args>...::
-	Some argument not yet documented.
+	. git-sh-setup-script || die "Not a git archive"

+to make the rest of the git scripts more careful and readable.

 Author
 ------
diff --git a/Documentation/git-verify-tag-script.txt b/Documentation/git-verify-tag-script.txt
--- a/Documentation/git-verify-tag-script.txt
+++ b/Documentation/git-verify-tag-script.txt
@@ -3,26 +3,20 @@ git-verify-tag-script(1)

 NAME
 ----
-git-verify-tag-script - Some git command not yet documented.
-
+git-verify-tag-script - Check the GPG signature of tag.

 SYNOPSIS
 --------
-'git-verify-tag-script' [ --option ] <args>...
+'git-verify-tag-script' <tag>

 DESCRIPTION
 -----------
-Does something not yet documented.
-
+Validates the gpg signature created by git-tag-script.

 OPTIONS
 -------
---option::
-	Some option not yet documented.
-
-<args>...::
-	Some argument not yet documented.
-
+<tag>::
+	SHA1 identifier of a git tag object.

 Author
 ------

^ permalink raw reply

* [PATCH] Redo "revert" using three-way merge machinery.
From: Junio C Hamano @ 2005-08-27  1:18 UTC (permalink / raw)
  To: git

The reverse patch application using "git apply" sometimes is too
rigid.  Since the user would get used to resolving conflicting merges
by hand during the normal merge experience, using the same machinery
would be more helpful rather than just giving up.

Cherry-picking and reverting are essentially the same operation.
You pick one commit, and apply the difference that commit introduces
to its own commit ancestry chain to the current tree.  Revert applies
the diff in reverse while cherry-pick applies it forward.  They share
the same logic, just different messages and merge direction.

Signed-off-by: Junio C Hamano <junkio@cox.net>

---

 * I've reorganized some patches and merged a couple into one.
 * An earlier version of this patch was already sent to the
 * list, but this is a rework.

 Makefile            |    1 
 git-revert-script   |  185 +++++++++++++++++++++++++++++++++++++++++++--------
 git-sh-setup-script |   11 +++
 3 files changed, 167 insertions(+), 30 deletions(-)

0fa9fd414efbf73f1394b983d0f643b60d383ce0
diff --git a/Makefile b/Makefile
--- a/Makefile
+++ b/Makefile
@@ -215,6 +215,7 @@ check:
 install: $(PROG) $(SCRIPTS)
 	$(INSTALL) -m755 -d $(DESTDIR)$(bindir)
 	$(INSTALL) $(PROG) $(SCRIPTS) $(DESTDIR)$(bindir)
+	$(INSTALL) git-revert-script $(DESTDIR)$(bindir)/git-cherry-pick-script
 	$(MAKE) -C templates install
 	$(MAKE) -C tools install
 
diff --git a/git-revert-script b/git-revert-script
--- a/git-revert-script
+++ b/git-revert-script
@@ -1,37 +1,162 @@
 #!/bin/sh
+#
+# Copyright (c) 2005 Linus Torvalds
+# Copyright (c) 2005 Junio C Hamano
+#
 . git-sh-setup-script || die "Not a git archive"
 
-# We want a clean tree and clean index to be able to revert.
-status=$(git status)
-case "$status" in
-'nothing to commit') ;;
+case "$0" in
+*-revert-* )
+	me=revert ;;
+*-cherry-pick-* )
+	me=cherry-pick ;;
+esac
+
+usage () {
+	case "$me" in
+	cherry-pick)
+		die "usage git $me [-n] [-r] <commit-ish>"
+		;;
+	revert)
+		die "usage git $me [-n] <commit-ish>"
+		;;
+	esac
+}
+
+no_commit= replay=
+while case "$#" in 0) break ;; esac
+do
+	case "$1" in
+	-n|--n|--no|--no-|--no-c|--no-co|--no-com|--no-comm|\
+	    --no-commi|--no-commit)
+		no_commit=t
+		;;
+	-r|--r|--re|--rep|--repl|--repla|--replay)
+		replay=t
+		;;
+	-*)
+		usage
+		;;
+	*)
+		break
+		;;
+	esac
+	shift
+done
+
+test "$me,$replay" = "revert,t" && usage
+
+case "$no_commit" in
+t)
+	# We do not intend to commit immediately.  We just want to
+	# merge the differences in.
+	head=$(git-write-tree) ||
+		die "Your index file is unmerged."
+	;;
 *)
-	echo "$status"
-	die "Your working tree is dirty; cannot revert a previous patch." ;;
+	check_clean_tree || die "Cannot run $me from a dirty tree."
+	head=$(git-rev-parse --verify HEAD) ||
+		die "You do not have a valid HEAD"
+	;;
 esac
 
 rev=$(git-rev-parse --verify "$@") &&
-commit=$(git-rev-parse --verify "$rev^0") || exit
-if git-diff-tree -R -M -p $commit | git-apply --index &&
-   msg=$(git-rev-list --pretty=oneline --max-count=1 $commit)
-then
-        {
-                echo "$msg" | sed -e '
-			s/^[^ ]* /Revert "/
-			s/$/"/'
-                echo
-                echo "This reverts $commit commit."
-                test "$rev" = "$commit" ||
-                echo "(original 'git revert' arguments: $@)"
-        } | git commit -F -
-else
-        # Now why did it fail?
-        parents=`git-cat-file commit "$commit" 2>/dev/null |
-                sed -ne '/^$/q;/^parent /p' |
-                wc -l`
-        case $parents in
-        0) die "Cannot revert the root commit nor non commit-ish." ;;
-        1) die "The patch does not apply." ;;
-        *) die "Cannot revert a merge commit." ;;
-        esac
-fi
+commit=$(git-rev-parse --verify "$rev^0") ||
+	die "Not a single commit $@"
+prev=$(git-rev-parse --verify "$commit^1" 2>/dev/null) ||
+	die "Cannot run $me a root commit"
+git-rev-parse --verify "$commit^2" >/dev/null 2>&1 &&
+	die "Cannot run $me a multi-parent commit."
+
+# "commit" is an existing commit.  We would want to apply
+# the difference it introduces since its first parent "prev"
+# on top of the current HEAD if we are cherry-pick.  Or the
+# reverse of it if we are revert.
+
+case "$me" in
+revert)
+	git-rev-list --pretty=oneline --max-count=1 $commit |
+	sed -e '
+		s/^[^ ]* /Revert "/
+		s/$/"/'
+	echo
+	echo "This reverts $commit commit."
+	test "$rev" = "$commit" ||
+	echo "(original 'git revert' arguments: $@)"
+	base=$commit next=$prev
+	;;
+
+cherry-pick)
+	pick_author_script='
+	/^author /{
+		h
+		s/^author \([^<]*\) <[^>]*> .*$/\1/
+		s/'\''/'\''\'\'\''/g
+		s/.*/GIT_AUTHOR_NAME='\''&'\''/p
+
+		g
+		s/^author [^<]* <\([^>]*\)> .*$/\1/
+		s/'\''/'\''\'\'\''/g
+		s/.*/GIT_AUTHOR_EMAIL='\''&'\''/p
+
+		g
+		s/^author [^<]* <[^>]*> \(.*\)$/\1/
+		s/'\''/'\''\'\'\''/g
+		s/.*/GIT_AUTHOR_DATE='\''&'\''/p
+
+		q
+	}'
+	set_author_env=`git-cat-file commit "$commit" |
+	sed -ne "$pick_author_script"`
+	eval "$set_author_env"
+	export GIT_AUTHOR_NAME
+	export GIT_AUTHOR_EMAIL
+	export GIT_AUTHOR_DATE
+
+	git-cat-file commit $commit | sed -e '1,/^$/d'
+	case "$replay" in
+	'')
+		echo "(cherry picked from $commit commit)"
+		test "$rev" = "$commit" ||
+		echo "(original 'git cherry-pick' arguments: $@)"
+		;;
+	esac
+	base=$prev next=$commit
+	;;
+
+esac >.msg
+
+# This three way merge is an interesting one.  We are at
+# $head, and would want to apply the change between $commit
+# and $prev on top of us (when reverting), or the change between
+# $prev and $commit on top of us (when cherry-picking or replaying).
+
+git-read-tree -m -u $base $head $next &&
+result=$(git-write-tree 2>/dev/null) || {
+    echo >&2 "Simple $me fails; trying Automatic $me."
+    git-merge-cache -o git-merge-one-file-script -a || {
+	    echo >&2 "Automatic $me failed.  After fixing it up,"
+	    echo >&2 "you can use \"git commit -F .msg\""
+	    case "$me" in
+	    cherry-pick)
+		echo >&2 "You may choose to use the following when making"
+		echo >&2 "the commit:"
+		echo >&2 "$set_author_env"
+	    esac
+	    exit 1
+    }
+    result=$(git-write-tree) || exit
+}
+
+# If we are cherry-pick, and if the merge did not result in
+# hand-editing, we will hit this commit and inherit the original
+# author date and name.
+# If we are revert, or if our cherry-pick results in a hand merge,
+# we had better say that the current user is responsible for that.
+
+case "$no_commit" in
+'')
+	git commit -F .msg
+	rm -f .msg
+	;;
+esac
diff --git a/git-sh-setup-script b/git-sh-setup-script
--- a/git-sh-setup-script
+++ b/git-sh-setup-script
@@ -11,6 +11,17 @@ die() {
 	exit 1
 }
 
+check_clean_tree() {
+    dirty1_=`git-update-cache -q --refresh` && {
+    dirty2_=`git-diff-cache --name-only --cached HEAD`
+    case "$dirty2_" in '') : ;; *) (exit 1) ;; esac
+    } || {
+	echo >&2 "$dirty1_"
+	echo "$dirty2_" | sed >&2 -e 's/^/modified: /'
+	(exit 1)
+    }
+}
+
 [ -d "$GIT_DIR" ] &&
 [ -d "$GIT_DIR/refs" ] &&
 [ -d "$GIT_OBJECT_DIRECTORY" ] &&

^ permalink raw reply

* [PATCH] Use "git cherry-pick" in "git rebase"
From: Junio C Hamano @ 2005-08-27  1:19 UTC (permalink / raw)
  To: git

Rewrite "git rebase" using "git cherry-pick".

Signed-off-by: Junio C Hamano <junkio@cox.net>

---

 git-rebase-script |   16 ++++++++++------
 1 files changed, 10 insertions(+), 6 deletions(-)

913404ae6371857c21c696cc3d8b0a04421fdeb2
diff --git a/git-rebase-script b/git-rebase-script
--- a/git-rebase-script
+++ b/git-rebase-script
@@ -37,7 +37,7 @@ git-rev-parse --verify "$upstream^0" >"$
 
 tmp=.rebase-tmp$$
 fail=$tmp-fail
-trap "rm -rf $tmp-*" 0 1 2 3 15
+trap "rm -rf $tmp-*" 1 2 3 15
 
 >$fail
 
@@ -48,14 +48,18 @@ do
 	-) continue ;;
 	esac
 	S=`cat "$GIT_DIR/HEAD"` &&
-        GIT_EXTERNAL_DIFF=git-apply-patch-script git-diff-tree -p $commit &&
-	git-commit-script -C "$commit" || {
+	git-cherry-pick-script --replay $commit || {
+		echo >&2 "* Not applying the patch and continuing."
 		echo $commit >>$fail
-		git-read-tree --reset -u $S
+		git-reset-script --hard $S
 	}
 done
 if test -s $fail
 then
-	echo Some commits could not be rebased, check by hand:
-	cat $fail
+	echo >&2 Some commits could not be rebased, check by hand:
+	cat >&2 $fail
+	echo >&2 "(the same list of commits are found in $tmp)"
+	exit 1
+else
+	rm -f $fail
 fi

^ permalink raw reply

* [PATCH] Teach git-status-script about git-ls-files --others
From: Junio C Hamano @ 2005-08-27  1:18 UTC (permalink / raw)
  To: git

When there is non-empty $GIT_DIR/info/exclude file, use it along
with .gitignore per-directory exclude pattern files (which was
a convention agreed on the list while ago and is compatible with
Cogito) to generate a list of ignored files as well.

Signed-off-by: Junio C Hamano <junkio@cox.net>

---

 git-status-script |   32 ++++++++++++++++++++++++++++++--
 1 files changed, 30 insertions(+), 2 deletions(-)

5444043b5f5a2464938acd6da9408ec55ca40249
diff --git a/git-status-script b/git-status-script
--- a/git-status-script
+++ b/git-status-script
@@ -1,4 +1,7 @@
 #!/bin/sh
+#
+# Copyright (c) 2005 Linus Torvalds
+#
 . git-sh-setup-script || die "Not a git archive"
 
 report () {
@@ -33,10 +36,35 @@ case "$branch" in
 refs/heads/master) ;;
 *)	echo "# On branch $branch" ;;
 esac
+
 git-update-cache --refresh >/dev/null 2>&1
-git-diff-cache -M --cached HEAD | sed 's/^://' | report "Updated but not checked in" "will commit"
+
+git-diff-cache -M --cached HEAD |
+sed 's/^://' |
+report "Updated but not checked in" "will commit"
+
 committable="$?"
-git-diff-files | sed 's/^://' | report "Changed but not updated" "use git-update-cache to mark for commit"
+
+git-diff-files |
+sed 's/^://' |
+report "Changed but not updated" "use git-update-cache to mark for commit"
+
+if grep -v '^#' "$GIT_DIR/info/exclude" >/dev/null 2>&1
+then
+	git-ls-files --others \
+	    --exclude-from="$GIT_DIR/info/exclude" \
+	    --exclude-per-directory=.gitignore |
+	sed -e '
+	1i\
+#\
+# Ignored files:\
+#   (use "git add" to add to commit)\
+#
+	s/^/#	/
+	$a\
+#'
+fi
+
 if [ "$committable" == "0" ]
 then
 	echo "nothing to commit"

^ permalink raw reply

* [HOWTO] Using post-update hook
From: Junio C Hamano @ 2005-08-27  1:19 UTC (permalink / raw)
  To: git

The pages under http://www.kernel.org/pub/software/scm/git/docs/
are built from Documentation/ directory of the git.git project
and needed to be kept up-to-date.  The www.kernel.org/ servers
are mirrored and I was told that the origin of the mirror is on
the machine master.kernel.org, on which I was given an account
when I took over git maintainership from Linus.

The directories relevant to this how-to are these two:

    /pub/scm/git/git.git/	The public git repository.
    /pub/software/scm/git/docs/	The HTML documentation page.

So I made a repository to generate the documentation under my
home directory over there.

    $ cd
    $ mkdir doc-git && cd doc-git
    $ git clone /pub/scm/git/git.git/ docgen

What needs to happen is to update the $HOME/doc-git/docgen/
working tree, build HTML docs there and install the result in
/pub/software/scm/git/docs/ directory.  So I wrote a little
script:

    $ cat >dododoc.sh <<\EOF
    #!/bin/sh
    cd $HOME/doc-git/docgen || exit

    unset GIT_DIR

    git pull /pub/scm/git/git.git/ master &&
    cd Documentation &&
    make install-webdoc
    EOF

Initially I used to run this by hand whenever I push into the
public git repository.  Then I did a cron job that ran twice a
day.  The current round uses the post-update hook mechanism,
like this:

    $ cat >/pub/scm/git/git.git/hooks/post-update <<\EOF
    #!/bin/sh
    #
    # An example hook script to prepare a packed repository for use over
    # dumb transports.
    #
    # To enable this hook, make this file executable by "chmod +x post-update".

    case " $* " in
    *' refs/heads/master '*)
            echo $HOME/doc-git/dododoc.sh | at now
            ;;
    esac
    exec git-update-server-info
    EOF
    $ chmod +x /pub/scm/git/git.git/hooks/post-update

There are three things worth mentioning:

 - The update-hook is run after the repository accepts a "git
   push", under my user privilege.  It is given the full names
   of refs that have been updated as arguments.  My post-update
   runs the dododoc.sh script only when the master head is
   updated.

 - When update-hook is run, GIT_DIR is set to '.' by the calling
   receive-pack.  This is inherited by the dododoc.sh run via
   the "at" command, and needs to be unset; otherwise, "git
   pull" it does into $HOME/doc-git/docgen/ repository would not
   work correctly.

 - This is still crude and does not protect against simultaneous
   make invocations stomping on each other.  I would need to add
   some locking mechanism for this.

^ permalink raw reply

* [PATCH] Remove git-apply-patch-script.
From: Junio C Hamano @ 2005-08-27  1:19 UTC (permalink / raw)
  To: git

Now the rebase is rewritten to use git cherry-pick, there is no user
for that ancient script.  I've checked Cogito and StGIT to make sure
they do not use it.

Signed-off-by: Junio C Hamano <junkio@cox.net>

---

 Documentation/git-apply-patch-script.txt           |   32 ----
 Documentation/git.txt                              |    3 
 .../howto/rebase-from-internal-branch.txt          |    2 
 Makefile                                           |    2 
 git-apply-patch-script                             |  144 --------------------
 git-cherry                                         |   14 --
 6 files changed, 4 insertions(+), 193 deletions(-)
 delete mode 100644 Documentation/git-apply-patch-script.txt
 delete mode 100755 git-apply-patch-script

ad3f5eb30999095f276f8d4664f3e7884296c797
diff --git a/Documentation/git-apply-patch-script.txt b/Documentation/git-apply-patch-script.txt
deleted file mode 100644
--- a/Documentation/git-apply-patch-script.txt
+++ /dev/null
@@ -1,32 +0,0 @@
-git-apply-patch-script(1)
-=========================
-v0.99.4, May 2005
-
-NAME
-----
-git-apply-patch-script - Sample script to apply the diffs from git-diff-*
-
-
-SYNOPSIS
---------
-'git-apply-patch-script'
-
-DESCRIPTION
------------
-This is a sample script to be used via the 'GIT_EXTERNAL_DIFF'
-environment variable to apply the differences that the "git-diff-*"
-family of commands report to the current work tree.
-
-
-Author
-------
-Written by Junio C Hamano <junkio@cox.net>
-
-Documentation
---------------
-Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel.org>.
-
-GIT
----
-Part of the link:git.html[git] suite
-
diff --git a/Documentation/git.txt b/Documentation/git.txt
--- a/Documentation/git.txt
+++ b/Documentation/git.txt
@@ -242,9 +242,6 @@ Ancillary Commands
 ------------------
 Manipulators:
 
-link:git-apply-patch-script.html[git-apply-patch-script]::
-	Sample script to apply the diffs from git-diff-*
-
 link:git-convert-cache.html[git-convert-cache]::
 	Converts old-style GIT repository
 
diff --git a/Documentation/howto/rebase-from-internal-branch.txt b/Documentation/howto/rebase-from-internal-branch.txt
--- a/Documentation/howto/rebase-from-internal-branch.txt
+++ b/Documentation/howto/rebase-from-internal-branch.txt
@@ -38,7 +38,7 @@ ancestry graph looked like this:
 So I started from master, made a bunch of edits, and committed:
 
     $ git checkout master
-    $ cd Documentation; ed git.txt git-apply-patch-script.txt ...
+    $ cd Documentation; ed git.txt ...
     $ cd ..; git add Documentation/*.txt
     $ git commit -s -v
 
diff --git a/Makefile b/Makefile
--- a/Makefile
+++ b/Makefile
@@ -57,7 +57,7 @@ SPARSE_FLAGS = -D__BIG_ENDIAN__ -D__powe
 
 
 
-SCRIPTS=git git-apply-patch-script git-merge-one-file-script git-prune-script \
+SCRIPTS=git git-merge-one-file-script git-prune-script \
 	git-pull-script git-tag-script git-resolve-script git-whatchanged \
 	git-fetch-script git-status-script git-commit-script \
 	git-log-script git-shortlog git-cvsimport-script git-diff-script \
diff --git a/git-apply-patch-script b/git-apply-patch-script
deleted file mode 100755
--- a/git-apply-patch-script
+++ /dev/null
@@ -1,144 +0,0 @@
-#!/bin/sh
-# Copyright (C) 2005 Junio C Hamano
-#
-# Applying diff between two trees to the work tree can be
-# done with the following single command:
-#
-# GIT_EXTERNAL_DIFF=git-apply-patch-script git-diff-tree -p $tree1 $tree2
-#
-
-case "$#" in
-1)
-    echo >&2 "cannot handle unmerged diff on path $1."
-    exit 1 ;;
-8 | 9)
-    echo >&2 "cannot handle rename diff between $1 and $8 yet."
-    exit 1 ;;
-esac
-name="$1" tmp1="$2" hex1="$3" mode1="$4" tmp2="$5" hex2="$6" mode2="$7"
-
-type1=f
-case "$mode1" in
-*120???) type1=l  ;;
-*1007??) mode1=+x ;;
-*1006??) mode1=-x ;;
-.)       type1=-  ;; 
-esac
-
-type2=f
-case "$mode2" in
-*120???) type2=l  ;;
-*1007??) mode2=+x ;;
-*1006??) mode2=-x ;;
-.)       type2=-  ;; 
-esac
-
-case "$type1,$type2" in
-
--,?)
-    dir=$(dirname "$name")
-    case "$dir" in '' | .) ;; *) mkdir -p "$dir" ;; esac || {
-	echo >&2 "cannot create leading path for $name."
-	exit 1
-    }
-    if test -e "$name"
-    then
-	echo >&2 "path $name to be created already exists."
-	exit 1
-    fi
-    case "$type2" in
-    f)
-        # creating a regular file
-	cat "$tmp2" >"$name" || {
-	    echo >&2 "cannot create a regular file $name."
-	    exit 1
-	} 
-	case "$mode2" in
-	+x)
-	    echo >&2 "created a regular file $name with mode +x."
-	    chmod "$mode2" "$name"
-	    ;;
-	-x)
-	    echo >&2 "created a regular file $name."
-            ;;
-        esac
-	;;
-    l)
-        # creating a symlink
-        ln -s "$(cat "$tmp2")" "$name" || {
-	    echo >&2 "cannot create a symbolic link $name."
-	    exit 1
-	}
-	echo >&2 "created a symbolic link $name."
-        ;;
-    *)
-        echo >&2 "do not know how to create $name of type $type2."
-	exit 1
-    esac
-    git-update-cache --add -- "$name" ;;
-
-?,-)
-    rm -f "$name" || {
-	echo >&2 "cannot remove $name"
-	exit 1
-    }
-    echo >&2 "deleted $name."
-    git-update-cache --remove -- "$name" ;;
-
-l,f|f,l)
-    echo >&2 "cannot change a regular file $name and a symbolic link $name."
-    exit 1 ;;
-
-l,l)
-    # symlink to symlink
-    current=$(readlink "$name") || {
-	echo >&2 "cannot read the target of the symbolic link $name."
-	exit 1
-    }
-    original=$(cat "$tmp1")
-    next=$(cat "$tmp2")
-    test "$original" != "$current" || {
-	echo >&2 "cannot apply symbolic link target change ($original->$next) to $name which points to $current."
-	exit 1
-    }
-    if test "$next" != "$current"
-    then
-	rm -f "$name" && ln -s "$next" "$name" || {
-	    echo >&2 "cannot create symbolic link $name."
-	    exit 1
-	}
-	echo >&2 "changed symbolic target of $name."
-        git-update-cache -- "$name"
-    fi ;;
-
-f,f)
-    # changed
-    test -e "$name" || {
-	echo >&2 "regular file $name to be patched does not exist."
-	exit 1
-    }
-    dir=$(dirname "$name")
-    case "$dir" in '' | .) ;; *) mkdir -p "$dir";; esac || {
-	echo >&2 "cannot create leading path for $name."
-	exit 1
-    }
-    tmp=.git-apply-patch-$$
-    trap "rm -f $tmp-*" 0 1 2 3 15
-
-    # Be careful, in case "$tmp2" is borrowed path from the work tree
-    # we are looking at...
-    diff -u -L "a/$name" -L "b/$name" "$tmp1" "$tmp2" >$tmp-patch
-
-    # This will say "patching ..." so we do not say anything outselves.
-    patch -p1 <$tmp-patch || exit
-    rm -f $tmp-patch
-    case "$mode1,$mode2" in
-    "$mode2,$mode1") ;;
-    *)
-	chmod "$mode2" "$name"
-	echo >&2 "changed mode from $mode1 to $mode2."
-	;;
-    esac
-    git-update-cache -- "$name"
-
-esac
diff --git a/git-cherry b/git-cherry
--- a/git-cherry
+++ b/git-cherry
@@ -15,18 +15,8 @@ usage="usage: $0 "'[-v] <upstream> [<hea
 Each commit between the fork-point and <head> is examined, and
 compared against the change each commit between the fork-point and
 <upstream> introduces.  If the change does not seem to be in the
-upstream, it is shown on the standard output.
-
-The output is intended to be used as:
-
-    OLD_HEAD=$(git-rev-parse HEAD)
-    git-rev-parse upstream >${GIT_DIR-.}/HEAD
-    git-cherry upstream $OLD_HEAD |
-    while read commit
-    do
-        GIT_EXTERNAL_DIFF=git-apply-patch-script git-diff-tree -p "$commit" &&
-	git-commit-script -C "$commit"
-    done
+upstream, it is shown on the standard output with prefix "+".  Otherwise
+it is shown with prefix "-".
 '
 
 case "$1" in -v) verbose=t; shift ;; esac 

^ permalink raw reply

* Status of Mac OS/X ports of git and cogito?
From: John Ellson @ 2005-08-27  1:30 UTC (permalink / raw)
  To: git

Do git and cogito build easily on Mac OS/X now?   Are there binaries available 
anywhere?


John

^ permalink raw reply

* Re: [RFC, PATCH] A new merge algorithm (EXPERIMENTAL)
From: Fredrik Kuivinen @ 2005-08-27  1:40 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: Linus Torvalds, git
In-Reply-To: <7vk6i89ofi.fsf@assigned-by-dhcp.cox.net>

On Fri, Aug 26, 2005 at 06:08:33PM -0700, Junio C Hamano wrote:
> Linus Torvalds <torvalds@osdl.org> writes:
> 
> > On Fri, 26 Aug 2005, Fredrik Kuivinen wrote:
> >>
> >> In real numbers it is as follows: In Linus' kernel tree there are
> >> 5996 commits. 400 of those have more than one parent. Of those 400
> >> merge commits 4 have more than one shared head.
> >
> > Ok, that's already interesting in itself. I was wanting to re-run all the 
> > merges with the new "git-merge-base -a" to see which merges might have had 
> > different merge bases, and you've actually done that. Interesting to see 
> > the numbers.
> 
> Fredrik, mind giving us the commit ID of those four for us to
> take a look at them?

Sure, they are:
467ca22d3371f132ee225a5591a1ed0cd518cb3d which has two shared heads
7e2987503dda95a5f80290bb8c06279009c2419e and
eff910a91ac04ab1d9e210d4f721484af3b39c8d

0e396ee43e445cb7c215a98da4e76d0ce354d9d7 with the heads
462cee296476278acaa54c41925b3273e0e4dd40 and
8be3de3fd8469154a2b3e18a4712032dac5b4a53

3190186362466658f01b2e354e639378ce07e1a9 with
38d84c3bd6dd22bdb1f797c87006931133d71aea and
46906c4415f88cebfad530917bada0835d651824

and finally

da28c12089dfcfb8695b6b555cdb8e03dda2b690 with
9e566d8bd61f939b7f5d7d969f5b178571471cf9 and
18190cc08d70a6ec8ef69f0f6ede021f7cb3f9b8

(The script which finds those commits also prints out the commit id of
any octopus commits. There is one commit with more than two parents in
the kernel repository,
13e652800d1644dfedcd0d59ac95ef0beb7f3165. However, it only looks like
that commit has three parents, two of them are actually identical.)

> > I am of two minds on this. I hate the notion of a more complex merge. But
> > at the same time, it clearly is a very interesting case when we do have
> > multiple possible shared parents, and I think that at the very least we
> > should warn the user. And using a more complex merge algorithm when it
> > happens seems to be a very valid thing to do.
> 
> I agree.  GIT is lightening fast for trivial cases and we can
> afford to spend more time to handle more complex ones carefully,
> at the same time telling the user what we are doing is a good thing.
> 
> > Using python, which people have less exposure to, sounds like an 
> > additional thorny issue..
> 
> Not too big a problem for me to follow the patch ;-).
> 

Good to know :)

I mainly wanted to try the idea with the new algorithm, so some kind
of high level language seemed like a good choice.

- Fredrik

^ permalink raw reply

* Re: [PATCH] Fix pulling into the same branch.
From: Junio C Hamano @ 2005-08-27  3:31 UTC (permalink / raw)
  To: Luck, Tony; +Cc: Junio C Hamano, Johannes Schindelin, git, Linus Torvalds
In-Reply-To: <B8E391BBE9FE384DAA4C5C003888BE6F043BA469@scsmsx401.amr.corp.intel.com>

"Luck, Tony" <tony.luck@intel.com> writes:

>>In the meantime, warning the user about the issue and suggesting
>>how to do the fast-forwarding of the working tree himself in the
>>warning message might be the safest and the most sensible thing
>>to do.
>
> Yes please ... a big fat warning with coloured flashing lights
> and explosions from the sound card.

Yeah, but what to do afterwards?  I can see this as an
compromise:

 * "git fetch", just like the updated "git pull", reads
   $GIT_DIR/HEAD before it starts its work, and compares it with
   $GIT_DIR/HEAD after it is done.  If the --update-head-ok flag
   is not given on the command line, and if the HEAD changed,
   then barf and exit with non-zero status after reverting
   $GIT_DIR/HEAD to its original value.  If --update-head-ok is
   given, none of the above check and revert happens.

 * "git pull" calls "git fetch" with the extra flag, and does
   its thing the current way.

So if you are calling from the command line, "git fetch linus",
when you were still on "linus" branch (which you do not normally
do but just to prevent mistakes), it would trigger the check and
your $GIT_DIR/HEAD would stay intact.  If you stayed on your own
branch, "git fetch linus" would continue to just fast forward
"linus" head without touching the working tree.

Come to think of it, it may be cleaner to simply forbid
fast-forward fetching into the current repository (whether it is
"git fetch" or "git pull").  At least in your workflow you do
not do that ever anyway.

Johannes, what do you think, as the original advocate of "push in
reverse"?

^ permalink raw reply

* Re: Status of Mac OS/X ports of git and cogito?
From: Junio C Hamano @ 2005-08-27  3:50 UTC (permalink / raw)
  To: John Ellson; +Cc: git
In-Reply-To: <deofnh$jl0$1@sea.gmane.org>

John Ellson <ellson@research.att.com> writes:

> Do git and cogito build easily on Mac OS/X now?  Are there
> binaries available anywhere?

Not that I know of.  We used to get portability patches from
Darwin folks, but I haven't seen any lately.

I am somewhat interested in what portability glitches we still
have, but not having an access to a machine, it is more of a
curiosity rather than a necessity for me.

Are Darwin folks on the list happy with the current codebase, or
have you given up because it is too GNU/Linux specific?

^ 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