Merge with master.kernel.org:/pub/scm/git/git.git
authorPeter Anvin <hpa@tazenda.sc.orionmulti.com>
Mon, 3 Oct 2005 19:04:44 +0000 (12:04 -0700)
committerPeter Anvin <hpa@tazenda.sc.orionmulti.com>
Mon, 3 Oct 2005 19:04:44 +0000 (12:04 -0700)
44 files changed:
.gitignore
Documentation/git-clone.txt
Documentation/git-ls-files.txt
Makefile
apply.c
cache.h
debian/changelog
diff-files.c
diff.c
fsck-objects.c
git-archimport.perl
git-bisect.sh
git-branch.sh
git-checkout.sh
git-commit.sh
git-cvsimport.perl
git-fetch.sh
git-merge-recursive.py
git-merge.sh
git-sh-setup.sh
git-status.sh
git.sh
http-fetch.c
index.c
init-db.c
ls-files.c
mailsplit.c
read-cache.c
read-tree.c
refs.c
setup.c
sha1_file.c
sha1_name.c
show-branch.c
symbolic-ref.c [new file with mode: 0644]
t/Makefile
t/t3002-ls-files-dashpath.sh [new file with mode: 0755]
t/t5000-tar-tree.sh
t/t5400-send-pack.sh
t/t6002-rev-list-bisect.sh
tar-tree.c
update-index.c
update-ref.c
usage.c

index b99a37e..d190c0a 100644 (file)
@@ -82,6 +82,7 @@ git-ssh-push
 git-ssh-upload
 git-status
 git-stripspace
+git-symbolic-ref
 git-tag
 git-tar-tree
 git-unpack-file
index bd53ef4..7d713c7 100644 (file)
@@ -9,7 +9,7 @@ git-clone - Clones a repository.
 
 SYNOPSIS
 --------
-'git clone' [-l] [-u <upload-pack>] [-q] <repository> <directory>
+'git clone' [-l [-s]] [-q] [-n] [-u <upload-pack>] <repository> <directory>
 
 DESCRIPTION
 -----------
@@ -17,6 +17,7 @@ Clones a repository into a newly created directory.
 
 OPTIONS
 -------
+--local::
 -l::
        When the repository to clone from is on a local machine,
        this flag bypasses normal "git aware" transport
@@ -25,10 +26,22 @@ OPTIONS
        The files under .git/objects/ directory are hardlinked
        to save space when possible.
 
+--shared::
+-s::
+       When the repository to clone is on the local machine,
+       instead of using hard links automatically setup
+       .git/objects/info/alternatives to share the objects
+       with the source repository
+
+--quiet::
 -q::
        Operate quietly.  This flag is passed to "rsync" and
        "git-clone-pack" commands when given.
 
+-n::
+       No checkout of HEAD is performed after the clone is complete.
+
+--upload-pack <upload-pack>::
 -u <upload-pack>::
        When given, and the repository to clone from is handled
        by 'git-clone-pack', '--exec=<upload-pack>' is passed to
index 591f4ed..87cc362 100644 (file)
@@ -13,7 +13,7 @@ SYNOPSIS
                (-[c|d|o|i|s|u|k|m])\*
                [-x <pattern>|--exclude=<pattern>]
                [-X <file>|--exclude-from=<file>]
-               [--exclude-per-directory=<file>]
+               [--exclude-per-directory=<file>] [--] [<file>]\*
 
 DESCRIPTION
 -----------
@@ -77,6 +77,13 @@ OPTIONS
        K       to be killed
        ?       other
 
+--::
+       Do not interpret any more arguments as options.
+
+<file>::
+       Files to show. If no files are given all files which match the other
+       specified criteria are shown.
+
 Output
 ------
 show files just outputs the filename unless '--stage' is specified in
index 38330c2..4ea6d9a 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -50,7 +50,7 @@
 
 # DEFINES += -DUSE_STDEV
 
-GIT_VERSION = 0.99.7.GIT
+GIT_VERSION = 0.99.8.GIT
 
 CFLAGS = -g -O2 -Wall
 ALL_CFLAGS = $(CFLAGS) $(PLATFORM_DEFINES) $(DEFINES)
@@ -103,25 +103,29 @@ SIMPLE_PROGRAMS = \
 
 # ... and all the rest
 PROGRAMS = \
-       git-apply$X git-cat-file$X git-checkout-index$X         \
-       git-clone-pack$X git-commit-tree$X git-convert-objects$X        \
-       git-diff-files$X git-diff-index$X git-diff-stages$X     \
-       git-diff-tree$X git-fetch-pack$X git-fsck-objects$X     \
-       git-hash-object$X git-init-db$X git-local-fetch$X               \
-       git-ls-files$X git-ls-tree$X git-merge-base$X           \
-       git-merge-index$X git-mktag$X git-pack-objects$X                \
-       git-patch-id$X git-peek-remote$X git-prune-packed$X     \
-       git-read-tree$X git-receive-pack$X git-rev-list$X               \
-       git-rev-parse$X git-send-pack$X git-show-branch$X               \
-       git-show-index$X git-ssh-fetch$X git-ssh-upload$X               \
-       git-tar-tree$X git-unpack-file$X git-unpack-objects$X   \
-       git-update-index$X git-update-server-info$X                     \
-       git-upload-pack$X git-verify-pack$X git-write-tree$X    \
-       git-update-ref$X $(SIMPLE_PROGRAMS)
+       git-apply$X git-cat-file$X \
+       git-checkout-index$X git-clone-pack$X git-commit-tree$X \
+       git-convert-objects$X git-diff-files$X \
+       git-diff-index$X git-diff-stages$X \
+       git-diff-tree$X git-fetch-pack$X git-fsck-objects$X \
+       git-hash-object$X git-init-db$X \
+       git-local-fetch$X git-ls-files$X git-ls-tree$X git-merge-base$X \
+       git-merge-index$X git-mktag$X git-pack-objects$X git-patch-id$X \
+       git-peek-remote$X git-prune-packed$X git-read-tree$X \
+       git-receive-pack$X git-rev-list$X git-rev-parse$X \
+       git-send-pack$X git-show-branch$X \
+       git-show-index$X git-ssh-fetch$X \
+       git-ssh-upload$X git-tar-tree$X git-unpack-file$X \
+       git-unpack-objects$X git-update-index$X git-update-server-info$X \
+       git-upload-pack$X git-verify-pack$X git-write-tree$X \
+       git-update-ref$X git-symbolic-ref$X \
+       $(SIMPLE_PROGRAMS)
 
 # Backward compatibility -- to be removed after 1.0
 PROGRAMS += git-ssh-pull$X git-ssh-push$X
 
+GIT_LIST_TWEAK =
+
 PYMODULES = \
        gitMergeCommon.py
 
@@ -131,6 +135,8 @@ endif
 
 ifdef WITH_SEND_EMAIL
        SCRIPT_PERL += git-send-email.perl
+else
+       GIT_LIST_TWEAK += -e '/^send-email$$/d'
 endif
 
 LIB_FILE=libgit.a
@@ -181,6 +187,10 @@ endif
 ifneq (,$(findstring arm,$(shell uname -m)))
        ARM_SHA1 = YesPlease
 endif
+ifeq ($(shell uname -s),OpenBSD)
+       NEEDS_LIBICONV = YesPlease
+       PLATFORM_DEFINES += -I/usr/local/include -L/usr/local/lib
+endif
 
 ifndef NO_CURL
        ifdef CURLDIR
@@ -206,18 +216,32 @@ endif
 ifndef NO_OPENSSL
        LIB_OBJS += epoch.o
        OPENSSL_LIBSSL = -lssl
+       ifdef OPENSSLDIR
+               # Again this may be problematic -- gcc does not always want -R.
+               CFLAGS += -I$(OPENSSLDIR)/include
+               OPENSSL_LINK = -L$(OPENSSLDIR)/lib -R$(OPENSSLDIR)/lib
+       else
+               OPENSSL_LINK =
+       endif
 else
        DEFINES += '-DNO_OPENSSL'
        MOZILLA_SHA1 = 1
        OPENSSL_LIBSSL =
 endif
 ifdef NEEDS_SSL_WITH_CRYPTO
-       LIB_4_CRYPTO = -lcrypto -lssl
+       LIB_4_CRYPTO = $(OPENSSL_LINK) -lcrypto -lssl
 else
-       LIB_4_CRYPTO = -lcrypto
+       LIB_4_CRYPTO = $(OPENSSL_LINK) -lcrypto
 endif
 ifdef NEEDS_LIBICONV
-       LIB_4_ICONV = -liconv
+       ifdef ICONVDIR
+               # Again this may be problematic -- gcc does not always want -R.
+               CFLAGS += -I$(ICONVDIR)/include
+               ICONV_LINK = -L$(ICONVDIR)/lib -R$(ICONVDIR)/lib
+       else
+               ICONV_LINK =
+       endif
+       LIB_4_ICONV = $(ICONV_LINK) -liconv
 else
        LIB_4_ICONV =
 endif
@@ -273,8 +297,13 @@ all:
 git: git.sh Makefile
        rm -f $@+ $@
        sed -e '1s|#!.*/sh|#!$(SHELL_PATH)|' \
+<<<<<<< Makefile
            -e 's/@@GIT_VERSION@@/$(GIT_VERSION)/g' \
            -e 's/@@X@@/$(X)/g' <$@.sh >$@+
+=======
+           -e 's/@@GIT_VERSION@@/$(GIT_VERSION)/g' \
+           $(GIT_LIST_TWEAK) <$@.sh >$@+
+>>>>>>> .merge_file_3QHyD4
        chmod +x $@+
        mv $@+ $@
 
diff --git a/apply.c b/apply.c
index 964df2d..f886272 100644 (file)
--- a/apply.c
+++ b/apply.c
@@ -723,6 +723,16 @@ static int parse_single_patch(char *line, unsigned long size, struct patch *patc
        return offset;
 }
 
+static inline int metadata_changes(struct patch *patch)
+{
+       return  patch->is_rename > 0 ||
+               patch->is_copy > 0 ||
+               patch->is_new > 0 ||
+               patch->is_delete ||
+               (patch->old_mode && patch->new_mode &&
+                patch->old_mode != patch->new_mode);
+}
+
 static int parse_chunk(char *buffer, unsigned long size, struct patch *patch)
 {
        int hdrsize, patchsize;
@@ -733,6 +743,9 @@ static int parse_chunk(char *buffer, unsigned long size, struct patch *patch)
 
        patchsize = parse_single_patch(buffer + offset + hdrsize, size - offset - hdrsize, patch);
 
+       if (!patchsize && !metadata_changes(patch))
+               die("patch with only garbage at line %d", linenr);
+
        return offset + hdrsize + patchsize;
 }
 
diff --git a/cache.h b/cache.h
index 52a45f9..ec2a161 100644 (file)
--- a/cache.h
+++ b/cache.h
@@ -189,6 +189,7 @@ extern char *git_path(const char *fmt, ...) __attribute__((format (printf, 1, 2)
 extern char *sha1_file_name(const unsigned char *sha1);
 extern char *sha1_pack_name(const unsigned char *sha1);
 extern char *sha1_pack_index_name(const unsigned char *sha1);
+extern const unsigned char null_sha1[20];
 
 int git_mkstemp(char *path, size_t n, const char *template);
 
@@ -228,6 +229,10 @@ extern int has_pack_index(const unsigned char *sha1);
 extern int get_sha1(const char *str, unsigned char *sha1);
 extern int get_sha1_hex(const char *hex, unsigned char *sha1);
 extern char *sha1_to_hex(const unsigned char *sha1);   /* static buffer result! */
+extern int read_ref(const char *filename, unsigned char *sha1);
+extern const char *resolve_ref(const char *path, unsigned char *sha1, int);
+extern int create_symref(const char *git_HEAD, const char *refs_heads_master);
+extern int validate_symref(const char *git_HEAD);
 
 /* General helper functions */
 extern void usage(const char *err) NORETURN;
index 128513a..bebc191 100644 (file)
@@ -1,3 +1,9 @@
+git-core (0.99.8-0) unstable; urgency=low
+
+  * GIT 0.99.8
+
+ -- Junio C Hamano <junkio@cox.net>  Sun,  2 Oct 2005 12:54:26 -0700
+
 git-core (0.99.7-0) unstable; urgency=low
 
   * GIT 0.99.7
index e8db3d2..5e59832 100644 (file)
@@ -34,7 +34,6 @@ static void show_modified(int oldmode, int mode,
 
 int main(int argc, const char **argv)
 {
-       static const unsigned char null_sha1[20] = { 0, };
        const char **pathspec;
        const char *prefix = setup_git_directory();
        int entries, i;
diff --git a/diff.c b/diff.c
index 9bded28..7d06b03 100644 (file)
--- a/diff.c
+++ b/diff.c
@@ -10,7 +10,6 @@
 #include "diffcore.h"
 
 static const char *diff_opts = "-pu";
-static unsigned char null_sha1[20] = { 0, };
 
 static int use_size_cache;
 
@@ -414,7 +413,7 @@ void diff_free_filespec_data(struct diff_filespec *s)
 static void prep_temp_blob(struct diff_tempfile *temp,
                           void *blob,
                           unsigned long size,
-                          unsigned char *sha1,
+                          const unsigned char *sha1,
                           int mode)
 {
        int fd;
index 247edf0..65cec7d 100644 (file)
@@ -402,25 +402,17 @@ static void fsck_object_dir(const char *path)
 
 static int fsck_head_link(void)
 {
-       int fd, count;
-       char hex[40];
        unsigned char sha1[20];
-       static char path[PATH_MAX], link[PATH_MAX];
-       const char *git_dir = get_git_dir();
-
-       snprintf(path, sizeof(path), "%s/HEAD", git_dir);
-       if (readlink(path, link, sizeof(link)) < 0)
-               return error("HEAD is not a symlink");
-       if (strncmp("refs/heads/", link, 11))
-               return error("HEAD points to something strange (%s)", link);
-       fd = open(path, O_RDONLY);
-       if (fd < 0)
-               return error("HEAD: %s", strerror(errno));
-       count = read(fd, hex, sizeof(hex));
-       close(fd);
-       if (count < 0)
-               return error("HEAD: %s", strerror(errno));
-       if (count < 40 || get_sha1_hex(hex, sha1))
+       const char *git_HEAD = strdup(git_path("HEAD"));
+       const char *git_refs_heads_master = resolve_ref(git_HEAD, sha1, 1);
+       int pfxlen = strlen(git_HEAD) - 4; /* strip .../.git/ part */
+
+       if (!git_refs_heads_master)
+               return error("HEAD is not a symbolic ref");
+       if (strncmp(git_refs_heads_master + pfxlen, "refs/heads/", 11))
+               return error("HEAD points to something strange (%s)",
+                            git_refs_heads_master + pfxlen);
+       if (!memcmp(null_sha1, sha1, 20))
                return error("HEAD: not a valid git pointer");
        return 0;
 }
index 3749b8b..980e827 100755 (executable)
@@ -228,10 +228,12 @@ foreach my $ps (@psets) {
     # skip commits already in repo
     #
     if (ptag($ps->{id})) {
-      $opt_v && print "Skipping already imported: $ps->{id}\n";
+      $opt_v && print " * Skipping already imported: $ps->{id}\n";
       next;
     }
 
+    print " * Starting to work on $ps->{id}\n";
+
     # 
     # create the branch if needed
     #
@@ -675,6 +677,10 @@ sub find_parents {
     # that branch.
     #
     foreach my $branch (keys %branches) {
+
+       # check that we actually know about the branch
+       next unless -e "$git_dir/refs/heads/$branch";
+
        my $mergebase = `git-merge-base $branch $ps->{branch}`;
        die "Cannot find merge base for $branch and $ps->{branch}" if $?;
        chomp $mergebase;
index 8dc77c9..1ab2f18 100755 (executable)
@@ -38,7 +38,8 @@ bisect_start() {
        # Verify HEAD. If we were bisecting before this, reset to the
        # top-of-line master first!
        #
-       head=$(readlink $GIT_DIR/HEAD) || die "Bad HEAD - I need a symlink"
+       head=$(GIT_DIR="$GIT_DIR" git-symbolic-ref HEAD) ||
+       die "Bad HEAD - I need a symbolic ref"
        case "$head" in
        refs/heads/bisect*)
                git checkout master || exit
@@ -46,7 +47,7 @@ bisect_start() {
        refs/heads/*)
                ;;
        *)
-               die "Bad HEAD - strange symlink"
+               die "Bad HEAD - strange symbolic ref"
                ;;
        esac
 
@@ -135,7 +136,7 @@ bisect_next() {
        echo "$rev" > "$GIT_DIR/refs/heads/new-bisect"
        git checkout new-bisect || exit
        mv "$GIT_DIR/refs/heads/new-bisect" "$GIT_DIR/refs/heads/bisect" &&
-       ln -sf refs/heads/bisect "$GIT_DIR/HEAD"
+       GIT_DIR="$GIT_DIR" git-symbolic-ref HEAD refs/heads/bisect
        git-show-branch "$rev"
 }
 
index dcec2a9..074229c 100755 (executable)
@@ -14,7 +14,8 @@ If two arguments, create a new branch <branchname> based off of <start-point>.
 
 delete_branch () {
     option="$1" branch_name="$2"
-    headref=$(readlink "$GIT_DIR/HEAD" | sed -e 's|^refs/heads/||')
+    headref=$(GIT_DIR="$GIT_DIR" git-symbolic-ref HEAD |
+              sed -e 's|^refs/heads/||')
     case ",$headref," in
     ",$branch_name,")
        die "Cannot delete the branch you are on." ;;
@@ -67,7 +68,8 @@ done
 
 case "$#" in
 0)
-       headref=$(readlink "$GIT_DIR/HEAD" | sed -e 's|^refs/heads/||')
+       headref=$(GIT_DIR="$GIT_DIR" git-symbolic-ref HEAD |
+                 sed -e 's|^refs/heads/||')
        git-rev-parse --symbolic --all |
        sed -ne 's|^refs/heads/||p' |
        sort |
index 37afcdd..c382590 100755 (executable)
@@ -71,7 +71,8 @@ if [ "$?" -eq 0 ]; then
                echo $new > "$GIT_DIR/refs/heads/$newbranch"
                branch="$newbranch"
        fi
-       [ "$branch" ] && ln -sf "refs/heads/$branch" "$GIT_DIR/HEAD"
+       [ "$branch" ] &&
+       GIT_DIR="$GIT_DIR" git-symbolic-ref HEAD "refs/heads/$branch"
        rm -f "$GIT_DIR/MERGE_HEAD"
 else
        exit 1
index 18b259c..1206c20 100755 (executable)
@@ -153,15 +153,8 @@ if [ -f "$GIT_DIR/MERGE_HEAD" ]; then
 fi >>.editmsg
 
 PARENTS="-p HEAD"
-if [ ! -r "$GIT_DIR/HEAD" ]; then
-       if [ -z "$(git-ls-files)" ]; then
-               echo Nothing to commit 1>&2
-               exit 1
-       fi
-       PARENTS=""
-       current=
-else
-       current=$(git-rev-parse --verify HEAD)
+if GIT_DIR="$GIT_DIR" git-rev-parse --verify HEAD >/dev/null 2>&1
+then
        if [ -f "$GIT_DIR/MERGE_HEAD" ]; then
                PARENTS="-p HEAD "`sed -e 's/^/-p /' "$GIT_DIR/MERGE_HEAD"`
        fi
@@ -194,6 +187,12 @@ else
                export GIT_AUTHOR_EMAIL
                export GIT_AUTHOR_DATE
        fi
+else
+       if [ -z "$(git-ls-files)" ]; then
+               echo Nothing to commit 1>&2
+               exit 1
+       fi
+       PARENTS=""
 fi
 git-status >>.editmsg
 if [ "$?" != "0" -a ! -f $GIT_DIR/MERGE_HEAD ]
index 565f4f1..f35c0d0 100755 (executable)
@@ -510,7 +510,7 @@ unless($pid) {
 
 my $state = 0;
 
-my($patchset,$date,$author,$branch,$ancestor,$tag,$logmsg);
+my($patchset,$date,$author_name,$author_email,$branch,$ancestor,$tag,$logmsg);
 my(@old,@new);
 my $commit = sub {
        my $pid;
@@ -591,11 +591,11 @@ my $commit = sub {
                }
 
                exec("env",
-                       "GIT_AUTHOR_NAME=$author",
-                       "GIT_AUTHOR_EMAIL=$author",
+                       "GIT_AUTHOR_NAME=$author_name",
+                       "GIT_AUTHOR_EMAIL=$author_email",
                        "GIT_AUTHOR_DATE=".strftime("+0000 %Y-%m-%d %H:%M:%S",gmtime($date)),
-                       "GIT_COMMITTER_NAME=$author",
-                       "GIT_COMMITTER_EMAIL=$author",
+                       "GIT_COMMITTER_NAME=$author_name",
+                       "GIT_COMMITTER_EMAIL=$author_email",
                        "GIT_COMMITTER_DATE=".strftime("+0000 %Y-%m-%d %H:%M:%S",gmtime($date)),
                        "git-commit-tree", $tree,@par);
                die "Cannot exec git-commit-tree: $!\n";
@@ -638,7 +638,7 @@ my $commit = sub {
                print $out "object $cid\n".
                    "type commit\n".
                    "tag $xtag\n".
-                   "tagger $author <$author>\n"
+                   "tagger $author_name <$author_email>\n"
                    or die "Cannot create tag object $xtag: $!\n";
                close($out)
                    or die "Cannot create tag object $xtag: $!\n";
@@ -683,7 +683,11 @@ while(<CVS>) {
                $state=3;
        } elsif($state == 3 and s/^Author:\s+//) {
                s/\s+$//;
-               $author = $_;
+               if (/^(.*?)\s+<(.*)>/) {
+                   ($author_name, $author_email) = ($1, $2);
+               } else {
+                   $author_name = $author_email = $_;
+               }
                $state = 4;
        } elsif($state == 4 and s/^Branch:\s+//) {
                s/\s+$//;
index 27407c1..61da6a9 100755 (executable)
@@ -5,6 +5,7 @@
 _x40='[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]'
 _x40="$_x40$_x40$_x40$_x40$_x40$_x40$_x40$_x40"
 
+tags=
 append=
 force=
 update_head_ok=
@@ -17,6 +18,9 @@ do
        -f|--f|--fo|--for|--forc|--force)
                force=t
                ;;
+       -t|--t|--ta|--tag|--tags)
+               tags=t
+               ;;
        -u|--u|--up|--upd|--upda|--updat|--update|--update-|--update-h|\
        --update-he|--update-hea|--update-head|--update-head-|\
        --update-head-o|--update-head-ok)
@@ -158,7 +162,26 @@ case "$update_head_ok" in
        ;;
 esac
 
-for ref in $(get_remote_refs_for_fetch "$@")
+# If --tags (and later --heads or --all) is specified, then we are
+# not talking about defaults stored in Pull: line of remotes or
+# branches file, and just fetch those and refspecs explicitly given.
+# Otherwise we do what we always did.
+
+reflist=$(get_remote_refs_for_fetch "$@")
+if test "$tags"
+then
+       taglist=$(git-ls-remote --tags "$remote" | awk '{ print "."$2":"$2 }')
+       if test "$#" -gt 1
+       then
+               # remote URL plus explicit refspecs; we need to merge them.
+               reflist="$reflist $taglist"
+       else
+               # No explicit refspecs; fetch tags only.
+               reflist=$taglist
+       fi
+fi
+
+for ref in $reflist
 do
     refs="$refs $ref"
 
index 689f914..b80a860 100755 (executable)
@@ -7,9 +7,6 @@ from sets import Set
 sys.path.append('@@GIT_PYTHON_PATH@@')
 from gitMergeCommon import *
 
-# The actual merge code
-# ---------------------
-
 originalIndexFile = os.environ.get('GIT_INDEX_FILE',
                                    os.environ.get('GIT_DIR', '.git') + '/index')
 temporaryIndexFile = os.environ.get('GIT_DIR', '.git') + \
@@ -21,11 +18,23 @@ def setupIndex(temporary):
         pass
     if temporary:
         newIndex = temporaryIndexFile
-        os.environ
     else:
         newIndex = originalIndexFile
     os.environ['GIT_INDEX_FILE'] = newIndex
 
+# This is a global variable which is used in a number of places but
+# only written to in the 'merge' function.
+
+# cacheOnly == True  => Don't leave any non-stage 0 entries in the cache and
+#                       don't update the working directory.
+#              False => Leave unmerged entries in the cache and update
+#                       the working directory.
+
+cacheOnly = False
+
+# The entry point to the merge code
+# ---------------------------------
+
 def merge(h1, h2, branch1Name, branch2Name, graph, callDepth=0):
     '''Merge the commits h1 and h2, return the resulting virtual
     commit object and a flag indicating the cleaness of the merge.'''
@@ -35,6 +44,7 @@ def merge(h1, h2, branch1Name, branch2Name, graph, callDepth=0):
     def infoMsg(*args):
         sys.stdout.write('  '*callDepth)
         printList(args)
+
     infoMsg('Merging:')
     infoMsg(h1)
     infoMsg(h2)
@@ -46,27 +56,27 @@ def merge(h1, h2, branch1Name, branch2Name, graph, callDepth=0):
         infoMsg(x)
     sys.stdout.flush()
 
-    Ms = ca[0]
+    mergedCA = ca[0]
     for h in ca[1:]:
-        [Ms, ignore] = merge(Ms, h,
-                             'Temporary shared merge branch 1',
-                             'Temporary shared merge branch 2',
-                             graph, callDepth+1)
-        assert(isinstance(Ms, Commit))
+        [mergedCA, dummy] = merge(mergedCA, h,
+                                  'Temporary shared merge branch 1',
+                                  'Temporary shared merge branch 2',
+                                  graph, callDepth+1)
+        assert(isinstance(mergedCA, Commit))
 
+    global cacheOnly
     if callDepth == 0:
         setupIndex(False)
-        cleanCache = False
+        cacheOnly = False
     else:
         setupIndex(True)
         runProgram(['git-read-tree', h1.tree()])
-        cleanCache = True
+        cacheOnly = True
 
-    [shaRes, clean] = mergeTrees(h1.tree(), h2.tree(), Ms.tree(),
-                                 branch1Name, branch2Name,
-                                 cleanCache)
+    [shaRes, clean] = mergeTrees(h1.tree(), h2.tree(), mergedCA.tree(),
+                                 branch1Name, branch2Name)
 
-    if clean or cleanCache:
+    if clean or cacheOnly:
         res = Commit(None, [h1, h2], tree=shaRes)
         graph.addNode(res)
     else:
@@ -89,16 +99,255 @@ def getFilesAndDirs(tree):
 
     return [files, dirs]
 
+# Those two global variables are used in a number of places but only
+# written to in 'mergeTrees' and 'uniquePath'. They keep track of
+# every file and directory in the two branches that are about to be
+# merged.
+currentFileSet = None
+currentDirectorySet = None
+
+def mergeTrees(head, merge, common, branch1Name, branch2Name):
+    '''Merge the trees 'head' and 'merge' with the common ancestor
+    'common'. The name of the head branch is 'branch1Name' and the name of
+    the merge branch is 'branch2Name'. Return a tuple (tree, cleanMerge)
+    where tree is the resulting tree and cleanMerge is True iff the
+    merge was clean.'''
+    
+    assert(isSha(head) and isSha(merge) and isSha(common))
+
+    if common == merge:
+        print 'Already uptodate!'
+        return [head, True]
+
+    if cacheOnly:
+        updateArg = '-i'
+    else:
+        updateArg = '-u'
+
+    [out, code] = runProgram(['git-read-tree', updateArg, '-m',
+                                common, head, merge], returnCode = True)
+    if code != 0:
+        die('git-read-tree:', out)
+
+    [tree, code] = runProgram('git-write-tree', returnCode=True)
+    tree = tree.rstrip()
+    if code != 0:
+        global currentFileSet, currentDirectorySet
+        [currentFileSet, currentDirectorySet] = getFilesAndDirs(head)
+        [filesM, dirsM] = getFilesAndDirs(merge)
+        currentFileSet.union_update(filesM)
+        currentDirectorySet.union_update(dirsM)
+
+        entries = unmergedCacheEntries()
+        renamesHead =  getRenames(head, common, head, merge, entries)
+        renamesMerge = getRenames(merge, common, head, merge, entries)
+
+        cleanMerge = processRenames(renamesHead, renamesMerge,
+                                    branch1Name, branch2Name)
+        for entry in entries:
+            if entry.processed:
+                continue
+            if not processEntry(entry, branch1Name, branch2Name):
+                cleanMerge = False
+                
+        if cleanMerge or cacheOnly:
+            tree = runProgram('git-write-tree').rstrip()
+        else:
+            tree = None
+    else:
+        cleanMerge = True
+
+    return [tree, cleanMerge]
+
+# Low level file merging, update and removal
+# ------------------------------------------
+
+def mergeFile(oPath, oSha, oMode, aPath, aSha, aMode, bPath, bSha, bMode,
+              branch1Name, branch2Name):
+
+    merge = False
+    clean = True
+
+    if stat.S_IFMT(aMode) != stat.S_IFMT(bMode):
+        clean = False
+        if stat.S_ISREG(aMode):
+            mode = aMode
+            sha = aSha
+        else:
+            mode = bMode
+            sha = bSha
+    else:
+        if aSha != oSha and bSha != oSha:
+            merge = True
+
+        if aMode == oMode:
+            mode = bMode
+        else:
+            mode = aMode
+
+        if aSha == oSha:
+            sha = bSha
+        elif bSha == oSha:
+            sha = aSha
+        elif stat.S_ISREG(aMode):
+            assert(stat.S_ISREG(bMode))
+
+            orig = runProgram(['git-unpack-file', oSha]).rstrip()
+            src1 = runProgram(['git-unpack-file', aSha]).rstrip()
+            src2 = runProgram(['git-unpack-file', bSha]).rstrip()
+            [out, code] = runProgram(['merge',
+                                      '-L', branch1Name + '/' + aPath,
+                                      '-L', 'orig/' + oPath,
+                                      '-L', branch2Name + '/' + bPath,
+                                      src1, orig, src2], returnCode=True)
+
+            sha = runProgram(['git-hash-object', '-t', 'blob', '-w',
+                              src1]).rstrip()
+
+            os.unlink(orig)
+            os.unlink(src1)
+            os.unlink(src2)
+            
+            clean = (code == 0)
+        else:
+            assert(stat.S_ISLNK(aMode) and stat.S_ISLNK(bMode))
+            sha = aSha
+
+            if aSha != bSha:
+                clean = False
+
+    return [sha, mode, clean, merge]
+
+def updateFile(clean, sha, mode, path):
+    updateCache = cacheOnly or clean
+    updateWd = not cacheOnly
+
+    return updateFileExt(sha, mode, path, updateCache, updateWd)
+
+def updateFileExt(sha, mode, path, updateCache, updateWd):
+    if cacheOnly:
+        updateWd = False
+
+    if updateWd:
+        pathComponents = path.split('/')
+        for x in xrange(1, len(pathComponents)):
+            p = '/'.join(pathComponents[0:x])
+
+            try:
+                createDir = not stat.S_ISDIR(os.lstat(p).st_mode)
+            except: 
+                createDir = True
+            
+            if createDir:
+                try:
+                    os.mkdir(p)
+                except OSError, e:
+                    die("Couldn't create directory", p, e.strerror)
+
+        prog = ['git-cat-file', 'blob', sha]
+        if stat.S_ISREG(mode):
+            try:
+                os.unlink(path)
+            except OSError:
+                pass
+            if mode & 0100:
+                mode = 0777
+            else:
+                mode = 0666
+            fd = os.open(path, os.O_WRONLY | os.O_TRUNC | os.O_CREAT, mode)
+            proc = subprocess.Popen(prog, stdout=fd)
+            proc.wait()
+            os.close(fd)
+        elif stat.S_ISLNK(mode):
+            linkTarget = runProgram(prog)
+            os.symlink(linkTarget, path)
+        else:
+            assert(False)
+
+    if updateWd and updateCache:
+        runProgram(['git-update-index', '--add', '--', path])
+    elif updateCache:
+        runProgram(['git-update-index', '--add', '--cacheinfo',
+                    '0%o' % mode, sha, path])
+
+def removeFile(clean, path):
+    updateCache = cacheOnly or clean
+    updateWd = not cacheOnly
+
+    if updateCache:
+        runProgram(['git-update-index', '--force-remove', '--', path])
+
+    if updateWd:
+        try:
+            os.unlink(path)
+        except OSError, e:
+            if e.errno != errno.ENOENT and e.errno != errno.EISDIR:
+                raise
+
+def uniquePath(path, branch):
+    def fileExists(path):
+        try:
+            os.lstat(path)
+            return True
+        except OSError, e:
+            if e.errno == errno.ENOENT:
+                return False
+            else:
+                raise
+
+    newPath = path + '_' + branch
+    suffix = 0
+    while newPath in currentFileSet or \
+          newPath in currentDirectorySet  or \
+          fileExists(newPath):
+        suffix += 1
+        newPath = path + '_' + branch + '_' + str(suffix)
+    currentFileSet.add(newPath)
+    return newPath
+
+# Cache entry management
+# ----------------------
+
 class CacheEntry:
     def __init__(self, path):
         class Stage:
             def __init__(self):
                 self.sha1 = None
                 self.mode = None
+
+            # Used for debugging only
+            def __str__(self):
+                if self.mode != None:
+                    m = '0%o' % self.mode
+                else:
+                    m = 'None'
+
+                if self.sha1:
+                    sha1 = self.sha1
+                else:
+                    sha1 = 'None'
+                return 'sha1: ' + sha1 + ' mode: ' + m
         
-        self.stages = [Stage(), Stage(), Stage()]
+        self.stages = [Stage(), Stage(), Stage(), Stage()]
         self.path = path
+        self.processed = False
+
+    def __str__(self):
+        return 'path: ' + self.path + ' stages: ' + repr([str(x) for x in self.stages])
 
+class CacheEntryContainer:
+    def __init__(self):
+        self.entries = {}
+
+    def add(self, entry):
+        self.entries[entry.path] = entry
+
+    def get(self, path):
+        return self.entries.get(path)
+
+    def __iter__(self):
+        return self.entries.itervalues()
+    
 unmergedRE = re.compile(r'^([0-7]+) ([0-9a-f]{40}) ([1-3])\t(.*)$', re.S)
 def unmergedCacheEntries():
     '''Create a dictionary mapping file names to CacheEntry
@@ -108,155 +357,340 @@ def unmergedCacheEntries():
     lines = runProgram(['git-ls-files', '-z', '--unmerged']).split('\0')
     lines.pop()
 
-    res = {}
+    res = CacheEntryContainer()
     for l in lines:
         m = unmergedRE.match(l)
         if m:
             mode = int(m.group(1), 8)
             sha1 = m.group(2)
-            stage = int(m.group(3)) - 1
+            stage = int(m.group(3))
             path = m.group(4)
 
-            if res.has_key(path):
-                e = res[path]
-            else:
+            e = res.get(path)
+            if not e:
                 e = CacheEntry(path)
-                res[path] = e
-                
+                res.add(e)
+
             e.stages[stage].mode = mode
             e.stages[stage].sha1 = sha1
         else:
-            die('Error: Merge program failed: Unexpected output from', \
+            die('Error: Merge program failed: Unexpected output from',
                 'git-ls-files:', l)
     return res
 
-def mergeTrees(head, merge, common, branch1Name, branch2Name,
-               cleanCache):
-    '''Merge the trees 'head' and 'merge' with the common ancestor
-    'common'. The name of the head branch is 'branch1Name' and the name of
-    the merge branch is 'branch2Name'. Return a tuple (tree, cleanMerge)
-    where tree is the resulting tree and cleanMerge is True iff the
-    merge was clean.'''
+lsTreeRE = re.compile(r'^([0-7]+) (\S+) ([0-9a-f]{40})\t(.*)\n$', re.S)
+def getCacheEntry(path, origTree, aTree, bTree):
+    '''Returns a CacheEntry object which doesn't have to correspond to
+    a real cache entry in Git's index.'''
     
-    assert(isSha(head) and isSha(merge) and isSha(common))
+    def parse(out):
+        if out == '':
+            return [None, None]
+        else:
+            m = lsTreeRE.match(out)
+            if not m:
+                die('Unexpected output from git-ls-tree:', out)
+            elif m.group(2) == 'blob':
+                return [m.group(3), int(m.group(1), 8)]
+            else:
+                return [None, None]
 
-    if common == merge:
-        print 'Already uptodate!'
-        return [head, True]
+    res = CacheEntry(path)
 
-    if cleanCache:
-        updateArg = '-i'
-    else:
-        updateArg = '-u'
+    [oSha, oMode] = parse(runProgram(['git-ls-tree', origTree, '--', path]))
+    [aSha, aMode] = parse(runProgram(['git-ls-tree', aTree, '--', path]))
+    [bSha, bMode] = parse(runProgram(['git-ls-tree', bTree, '--', path]))
 
-    [out, code] = runProgram(['git-read-tree', updateArg, '-m', common, head, merge], returnCode = True)
-    if code != 0:
-        die('git-read-tree:', out)
+    res.stages[1].sha1 = oSha
+    res.stages[1].mode = oMode
+    res.stages[2].sha1 = aSha
+    res.stages[2].mode = aMode
+    res.stages[3].sha1 = bSha
+    res.stages[3].mode = bMode
 
-    cleanMerge = True
+    return res
 
-    [tree, code] = runProgram('git-write-tree', returnCode=True)
-    tree = tree.rstrip()
-    if code != 0:
-        [files, dirs] = getFilesAndDirs(head)
-        [filesM, dirsM] = getFilesAndDirs(merge)
-        files.union_update(filesM)
-        dirs.union_update(dirsM)
-        
-        cleanMerge = True
-        entries = unmergedCacheEntries()
-        for name in entries:
-            if not processEntry(entries[name], branch1Name, branch2Name,
-                                files, dirs, cleanCache):
-                cleanMerge = False
-                
-        if cleanMerge or cleanCache:
-            tree = runProgram('git-write-tree').rstrip()
+# Rename detection and handling
+# -----------------------------
+
+class RenameEntry:
+    def __init__(self,
+                 src, srcSha, srcMode, srcCacheEntry,
+                 dst, dstSha, dstMode, dstCacheEntry,
+                 score):
+        self.srcName = src
+        self.srcSha = srcSha
+        self.srcMode = srcMode
+        self.srcCacheEntry = srcCacheEntry
+        self.dstName = dst
+        self.dstSha = dstSha
+        self.dstMode = dstMode
+        self.dstCacheEntry = dstCacheEntry
+        self.score = score
+
+        self.processed = False
+
+class RenameEntryContainer:
+    def __init__(self):
+        self.entriesSrc = {}
+        self.entriesDst = {}
+
+    def add(self, entry):
+        self.entriesSrc[entry.srcName] = entry
+        self.entriesDst[entry.dstName] = entry
+
+    def getSrc(self, path):
+        return self.entriesSrc.get(path)
+
+    def getDst(self, path):
+        return self.entriesDst.get(path)
+
+    def __iter__(self):
+        return self.entriesSrc.itervalues()
+
+parseDiffRenamesRE = re.compile('^:([0-7]+) ([0-7]+) ([0-9a-f]{40}) ([0-9a-f]{40}) R([0-9]*)$')
+def getRenames(tree, oTree, aTree, bTree, cacheEntries):
+    '''Get information of all renames which occured between 'oTree' and
+    'tree'. We need the three trees in the merge ('oTree', 'aTree' and
+    'bTree') to be able to associate the correct cache entries with
+    the rename information. 'tree' is always equal to either aTree or bTree.'''
+
+    assert(tree == aTree or tree == bTree)
+    inp = runProgram(['git-diff-tree', '-M', '--diff-filter=R', '-r',
+                      '-z', oTree, tree])
+
+    ret = RenameEntryContainer()
+    try:
+        recs = inp.split("\0")
+        recs.pop() # remove last entry (which is '')
+        it = recs.__iter__()
+        while True:
+            rec = it.next()
+            m = parseDiffRenamesRE.match(rec)
+
+            if not m:
+                die('Unexpected output from git-diff-tree:', rec)
+
+            srcMode = int(m.group(1), 8)
+            dstMode = int(m.group(2), 8)
+            srcSha = m.group(3)
+            dstSha = m.group(4)
+            score = m.group(5)
+            src = it.next()
+            dst = it.next()
+
+            srcCacheEntry = cacheEntries.get(src)
+            if not srcCacheEntry:
+                srcCacheEntry = getCacheEntry(src, oTree, aTree, bTree)
+                cacheEntries.add(srcCacheEntry)
+
+            dstCacheEntry = cacheEntries.get(dst)
+            if not dstCacheEntry:
+                dstCacheEntry = getCacheEntry(dst, oTree, aTree, bTree)
+                cacheEntries.add(dstCacheEntry)
+
+            ret.add(RenameEntry(src, srcSha, srcMode, srcCacheEntry,
+                                dst, dstSha, dstMode, dstCacheEntry,
+                                score))
+    except StopIteration:
+        pass
+    return ret
+
+def fmtRename(src, dst):
+    srcPath = src.split('/')
+    dstPath = dst.split('/')
+    path = []
+    endIndex = min(len(srcPath), len(dstPath)) - 1
+    for x in range(0, endIndex):
+        if srcPath[x] == dstPath[x]:
+            path.append(srcPath[x])
         else:
-            tree = None
+            endIndex = x
+            break
+
+    if len(path) > 0:
+        return '/'.join(path) + \
+               '/{' + '/'.join(srcPath[endIndex:]) + ' => ' + \
+               '/'.join(dstPath[endIndex:]) + '}'
     else:
-        cleanMerge = True
+        return src + ' => ' + dst
 
-    return [tree, cleanMerge]
+def processRenames(renamesA, renamesB, branchNameA, branchNameB):
+    srcNames = Set()
+    for x in renamesA:
+        srcNames.add(x.srcName)
+    for x in renamesB:
+        srcNames.add(x.srcName)
 
-def processEntry(entry, branch1Name, branch2Name, files, dirs, cleanCache):
-    '''Merge one cache entry. 'files' is a Set with the files in both of
-    the heads that we are going to merge. 'dirs' contains the
-    corresponding data for directories. If 'cleanCache' is True no
-    non-zero stages will be left in the cache for the path
-    corresponding to the entry 'entry'.'''
+    cleanMerge = True
+    for path in srcNames:
+        if renamesA.getSrc(path):
+            renames1 = renamesA
+            renames2 = renamesB
+            branchName1 = branchNameA
+            branchName2 = branchNameB
+        else:
+            renames1 = renamesB
+            renames2 = renamesA
+            branchName1 = branchNameB
+            branchName2 = branchNameA
+        
+        ren1 = renames1.getSrc(path)
+        ren2 = renames2.getSrc(path)
+
+        ren1.dstCacheEntry.processed = True
+        ren1.srcCacheEntry.processed = True
+
+        if ren1.processed:
+            continue
+
+        ren1.processed = True
+        removeFile(True, ren1.srcName)
+        if ren2:
+            # Renamed in 1 and renamed in 2
+            assert(ren1.srcName == ren2.srcName)
+            ren2.dstCacheEntry.processed = True
+            ren2.processed = True
+
+            if ren1.dstName != ren2.dstName:
+                print 'CONFLICT (rename/rename): Rename', \
+                      fmtRename(path, ren1.dstName), 'in branch', branchName1, \
+                      'rename', fmtRename(path, ren2.dstName), 'in', branchName2
+                cleanMerge = False
 
-# cleanCache == True  => Don't leave any non-stage 0 entries in the cache and
-#                        don't update the working directory
-#               False => Leave unmerged entries and update the working directory
+                if ren1.dstName in currentDirectorySet:
+                    dstName1 = uniquePath(ren1.dstName, branchName1)
+                    print ren1.dstName, 'is a directory in', branchName2, \
+                          'adding as', dstName1, 'instead.'
+                    removeFile(False, ren1.dstName)
+                else:
+                    dstName1 = ren1.dstName
 
-# clean     == True  => non-conflict case
-#              False => conflict case
+                if ren2.dstName in currentDirectorySet:
+                    dstName2 = uniquePath(ren2.dstName, branchName2)
+                    print ren2.dstName, 'is a directory in', branchName1, \
+                          'adding as', dstName2, 'instead.'
+                    removeFile(False, ren2.dstName)
+                else:
+                    dstName2 = ren1.dstName
 
-# If cleanCache == False then the cache shouldn't be updated if clean == False
+                updateFile(False, ren1.dstSha, ren1.dstMode, dstName1)
+                updateFile(False, ren2.dstSha, ren2.dstMode, dstName2)
+            else:
+                print 'Renaming', fmtRename(path, ren1.dstName)
+                [resSha, resMode, clean, merge] = \
+                         mergeFile(ren1.srcName, ren1.srcSha, ren1.srcMode,
+                                   ren1.dstName, ren1.dstSha, ren1.dstMode,
+                                   ren2.dstName, ren2.dstSha, ren2.dstMode,
+                                   branchName1, branchName2)
+
+                if merge:
+                    print 'Auto-merging', ren1.dstName
+
+                if not clean:
+                    print 'CONFLICT (content): merge conflict in', ren1.dstName
+                    cleanMerge = False
+
+                    if not cacheOnly:
+                        updateFileExt(ren1.dstSha, ren1.dstMode, ren1.dstName,
+                                      updateCache=True, updateWd=False)
+                updateFile(clean, resSha, resMode, ren1.dstName)
+        else:
+            # Renamed in 1, maybe changed in 2
+            if renamesA == renames1:
+                stage = 3
+            else:
+                stage = 2
+                
+            srcShaOtherBranch  = ren1.srcCacheEntry.stages[stage].sha1
+            srcModeOtherBranch = ren1.srcCacheEntry.stages[stage].mode
+
+            dstShaOtherBranch  = ren1.dstCacheEntry.stages[stage].sha1
+            dstModeOtherBranch = ren1.dstCacheEntry.stages[stage].mode
+
+            tryMerge = False
+            
+            if ren1.dstName in currentDirectorySet:
+                newPath = uniquePath(ren1.dstName, branchName1)
+                print 'CONFLICT (rename/directory): Rename', \
+                      fmtRename(ren1.srcName, ren1.dstName), 'in', branchName1,\
+                      'directory', ren1.dstName, 'added in', branchName2
+                print 'Renaming', ren1.srcName, 'to', newPath, 'instead'
+                cleanMerge = False
+                removeFile(False, ren1.dstName)
+                updateFile(False, ren1.dstSha, ren1.dstMode, newPath)
+            elif srcShaOtherBranch == None:
+                print 'CONFLICT (rename/delete): Rename', \
+                      fmtRename(ren1.srcName, ren1.dstName), 'in', \
+                      branchName1, 'and deleted in', branchName2
+                cleanMerge = False
+                updateFile(False, ren1.dstSha, ren1.dstMode, ren1.dstName)
+            elif dstShaOtherBranch:
+                newPath = uniquePath(ren1.dstName, branchName2)
+                print 'CONFLICT (rename/add): Rename', \
+                      fmtRename(ren1.srcName, ren1.dstName), 'in', \
+                      branchName1 + '.', ren1.dstName, 'added in', branchName2
+                print 'Adding as', newPath, 'instead'
+                updateFile(False, dstShaOtherBranch, dstModeOtherBranch, newPath)
+                cleanMerge = False
+                tryMerge = True
+            elif renames2.getDst(ren1.dstName):
+                dst2 = renames2.getDst(ren1.dstName)
+                newPath1 = uniquePath(ren1.dstName, branchName1)
+                newPath2 = uniquePath(dst2.dstName, branchName2)
+                print 'CONFLICT (rename/rename): Rename', \
+                      fmtRename(ren1.srcName, ren1.dstName), 'in', \
+                      branchName1+'. Rename', \
+                      fmtRename(dst2.srcName, dst2.dstName), 'in', branchName2
+                print 'Renaming', ren1.srcName, 'to', newPath1, 'and', \
+                      dst2.srcName, 'to', newPath2, 'instead'
+                removeFile(False, ren1.dstName)
+                updateFile(False, ren1.dstSha, ren1.dstMode, newPath1)
+                updateFile(False, dst2.dstSha, dst2.dstMode, newPath2)
+                dst2.processed = True
+                cleanMerge = False
+            else:
+                tryMerge = True
 
-    def updateFile(clean, sha, mode, path, onlyWd=False):
-        updateCache = not onlyWd and (cleanCache or (not cleanCache and clean))
-        updateWd = onlyWd or (not cleanCache and clean)
+            if tryMerge:
+                print 'Renaming', fmtRename(ren1.srcName, ren1.dstName)
+                [resSha, resMode, clean, merge] = \
+                         mergeFile(ren1.srcName, ren1.srcSha, ren1.srcMode,
+                                   ren1.dstName, ren1.dstSha, ren1.dstMode,
+                                   ren1.srcName, srcShaOtherBranch, srcModeOtherBranch,
+                                   branchName1, branchName2)
 
-        if updateWd:
-            prog = ['git-cat-file', 'blob', sha]
-            if stat.S_ISREG(mode):
-                try:
-                    os.unlink(path)
-                except OSError:
-                    pass
-                if mode & 0100:
-                    mode = 0777
-                else:
-                    mode = 0666
-                fd = os.open(path, os.O_WRONLY | os.O_TRUNC | os.O_CREAT, mode)
-                proc = subprocess.Popen(prog, stdout=fd)
-                proc.wait()
-                os.close(fd)
-            elif stat.S_ISLNK(mode):
-                linkTarget = runProgram(prog)
-                os.symlink(linkTarget, path)
-            else:
-                assert(False)
+                if merge:
+                    print 'Auto-merging', ren1.dstName
 
-        if updateWd and updateCache:
-            runProgram(['git-update-index', '--add', '--', path])
-        elif updateCache:
-            runProgram(['git-update-index', '--add', '--cacheinfo',
-                        '0%o' % mode, sha, path])
+                if not clean:
+                    print 'CONFLICT (rename/modify): Merge conflict in', ren1.dstName
+                    cleanMerge = False
 
-    def removeFile(clean, path):
-        if cleanCache or (not cleanCache and clean):
-            runProgram(['git-update-index', '--force-remove', '--', path])
+                    if not cacheOnly:
+                        updateFileExt(ren1.dstSha, ren1.dstMode, ren1.dstName,
+                                      updateCache=True, updateWd=False)
+                updateFile(clean, resSha, resMode, ren1.dstName)
 
-        if not cleanCache and clean:
-            try:
-                os.unlink(path)
-            except OSError, e:
-                if e.errno != errno.ENOENT and e.errno != errno.EISDIR:
-                    raise
+    return cleanMerge
 
-    def uniquePath(path, branch):
-        newPath = path + '_' + branch
-        suffix = 0
-        while newPath in files or newPath in dirs:
-            suffix += 1
-            newPath = path + '_' + branch + '_' + str(suffix)
-        files.add(newPath)
-        return newPath
+# Per entry merge function
+# ------------------------
 
-    debug('processing', entry.path, 'clean cache:', cleanCache)
+def processEntry(entry, branch1Name, branch2Name):
+    '''Merge one cache entry.'''
+
+    debug('processing', entry.path, 'clean cache:', cacheOnly)
 
     cleanMerge = True
 
     path = entry.path
-    oSha = entry.stages[0].sha1
-    oMode = entry.stages[0].mode
-    aSha = entry.stages[1].sha1
-    aMode = entry.stages[1].mode
-    bSha = entry.stages[2].sha1
-    bMode = entry.stages[2].mode
+    oSha = entry.stages[1].sha1
+    oMode = entry.stages[1].mode
+    aSha = entry.stages[2].sha1
+    aMode = entry.stages[2].mode
+    bSha = entry.stages[3].sha1
+    bMode = entry.stages[3].mode
 
     assert(oSha == None or isSha(oSha))
     assert(aSha == None or isSha(aSha))
@@ -275,28 +709,26 @@ def processEntry(entry, branch1Name, branch2Name, files, dirs, cleanCache):
            (not aSha     and bSha == oSha):
     # Deleted in both or deleted in one and unchanged in the other
             if aSha:
-                print 'Removing ' + path
+                print 'Removing', path
             removeFile(True, path)
         else:
     # Deleted in one and changed in the other
             cleanMerge = False
             if not aSha:
-                print 'CONFLICT (del/mod): "' + path + '" deleted in', \
-                      branch1Name, 'and modified in', branch2Name, \
-                      '. Version', branch2Name, ' of "' + path + \
-                      '" left in tree'
+                print 'CONFLICT (delete/modify):', path, 'deleted in', \
+                      branch1Name, 'and modified in', branch2Name + '.', \
+                      'Version', branch2Name, 'of', path, 'left in tree.'
                 mode = bMode
                 sha = bSha
             else:
-                print 'CONFLICT (mod/del): "' + path + '" deleted in', \
-                      branch2Name, 'and modified in', branch1Name + \
-                      '. Version', branch1Name, 'of "' + path + \
-                      '" left in tree'
+                print 'CONFLICT (modify/delete):', path, 'deleted in', \
+                      branch2Name, 'and modified in', branch1Name + '.', \
+                      'Version', branch1Name, 'of', path, 'left in tree.'
                 mode = aMode
                 sha = aSha
 
             updateFile(False, sha, mode, path)
-    
+
     elif (not oSha and aSha     and not bSha) or \
          (not oSha and not aSha and bSha):
     #
@@ -307,27 +739,26 @@ def processEntry(entry, branch1Name, branch2Name, files, dirs, cleanCache):
             otherBranch = branch2Name
             mode = aMode
             sha = aSha
-            conf = 'file/dir'
+            conf = 'file/directory'
         else:
             addBranch = branch2Name
             otherBranch = branch1Name
             mode = bMode
             sha = bSha
-            conf = 'dir/file'
+            conf = 'directory/file'
     
-        if path in dirs:
+        if path in currentDirectorySet:
             cleanMerge = False
             newPath = uniquePath(path, addBranch)
-            print 'CONFLICT (' + conf + \
-                  '): There is a directory with name "' + path + '" in', \
-                  otherBranch + '. Adding "' + path + '" as "' + newPath + '"'
+            print 'CONFLICT (' + conf + '):', \
+                  'There is a directory with name', path, 'in', \
+                  otherBranch + '. Adding', path, 'as', newPath
 
             removeFile(False, path)
-            path = newPath
+            updateFile(False, sha, mode, newPath)
         else:
-            print 'Adding "' + path + '"'
-
-        updateFile(True, sha, mode, path)
+            print 'Adding', path
+            updateFile(True, sha, mode, path)
     
     elif not oSha and aSha and bSha:
     #
@@ -336,10 +767,9 @@ def processEntry(entry, branch1Name, branch2Name, files, dirs, cleanCache):
         if aSha == bSha:
             if aMode != bMode:
                 cleanMerge = False
-                print 'CONFLICT: File "' + path + \
-                      '" added identically in both branches,', \
-                      'but permissions conflict', '0%o' % aMode, '->', \
-                      '0%o' % bMode
+                print 'CONFLICT: File', path, \
+                      'added identically in both branches, but permissions', \
+                      'conflict', '0%o' % aMode, '->', '0%o' % bMode
                 print 'CONFLICT: adding with permission:', '0%o' % aMode
 
                 updateFile(False, aSha, aMode, path)
@@ -350,8 +780,9 @@ def processEntry(entry, branch1Name, branch2Name, files, dirs, cleanCache):
             cleanMerge = False
             newPath1 = uniquePath(path, branch1Name)
             newPath2 = uniquePath(path, branch2Name)
-            print 'CONFLICT (add/add): File "' + path + \
-                  '" added non-identically in both branches.'
+            print 'CONFLICT (add/add): File', path, \
+                  'added non-identically in both branches. Adding as', \
+                  newPath1, 'and', newPath2, 'instead.'
             removeFile(False, path)
             updateFile(False, aSha, aMode, newPath1)
             updateFile(False, bSha, bMode, newPath2)
@@ -360,39 +791,24 @@ def processEntry(entry, branch1Name, branch2Name, files, dirs, cleanCache):
     #
     # 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', branch1Name + '/' + path,
-                                 '-L', 'orig/' + path,
-                                 '-L', branch2Name + '/' + path,
-                                 src1, orig, src2], returnCode=True)
-
-        if aMode == oMode:
-            mode = bMode
+        print 'Auto-merging', path
+        [sha, mode, clean, dummy] = \
+              mergeFile(path, oSha, oMode,
+                        path, aSha, aMode,
+                        path, bSha, bMode,
+                        branch1Name, branch2Name)
+        if clean:
+            updateFile(True, sha, mode, path)
         else:
-            mode = aMode
-
-        sha = runProgram(['git-hash-object', '-t', 'blob', '-w',
-                          src1]).rstrip()
-
-        if ret != 0:
             cleanMerge = False
-            print 'CONFLICT (content): Merge conflict in "' + path + '".'
+            print 'CONFLICT (content): Merge conflict in', path
 
-            if cleanCache:
+            if cacheOnly:
                 updateFile(False, sha, mode, path)
             else:
-                updateFile(True, aSha, aMode, path)
-                updateFile(False, sha, mode, path, True)
-        else:
-            updateFile(True, sha, mode, path)
-
-        os.unlink(orig)
-        os.unlink(src1)
-        os.unlink(src2)
+                updateFileExt(aSha, aMode, path,
+                              updateCache=True, updateWd=False)
+                updateFileExt(sha, mode, path, updateCache=False, updateWd=True)
     else:
         die("ERROR: Fatal merge failure, shouldn't happen.")
 
@@ -416,7 +832,7 @@ for nextArg in xrange(1, len(sys.argv)):
         try:
             h1 = firstBranch = sys.argv[nextArg + 1]
             h2 = secondBranch = sys.argv[nextArg + 2]
-       except IndexError:
+        except IndexError:
             usage()
         break
 
@@ -428,8 +844,8 @@ try:
 
     graph = buildGraph([h1, h2])
 
-    [res, clean] = merge(graph.shaMap[h1], graph.shaMap[h2],
-                         firstBranch, secondBranch, graph)
+    [dummy, clean] = merge(graph.shaMap[h1], graph.shaMap[h2],
+                           firstBranch, secondBranch, graph)
 
     print ''
 except:
index 29e86c6..d12a2a9 100755 (executable)
@@ -123,10 +123,30 @@ case "$#,$common" in
        dropsave
        exit 0
        ;;
-1,*)
+1,?*"$LF"?*)
        # We are not doing octopus and not fast forward.  Need a
        # real merge.
        ;;
+1,*)
+       # We are not doing octopus, not fast forward, and have only
+       # one common.  See if it is really trivial.
+       echo "Trying really trivial in-index merge..."
+       git-update-index --refresh 2>/dev/null
+       if git-read-tree --trivial -m -u $common $head "$1" &&
+          result_tree=$(git-write-tree)
+       then
+           echo "Wonderful."
+           result_commit=$(
+               echo "$merge_msg" |
+               git-commit-tree $result_tree -p HEAD -p "$1"
+           ) || exit
+           git-update-ref HEAD $result_commit $head
+           summary $result_commit
+           dropsave
+           exit 0
+       fi
+       echo "Nope."
+       ;;
 *)
        # An octopus.  If we can reach all the remote we are up to date.
        up_to_date=t
index 55db795..a017268 100755 (executable)
 unset CDPATH
 
 die() {
-       echo "$@" >&2
+       echo >&2 "$@"
        exit 1
 }
 
-[ -h "$GIT_DIR/HEAD" ] &&
+case "$(GIT_DIR="$GIT_DIR" git-symbolic-ref HEAD 2>/dev/null)" in
+refs/*)        : ;;
+*)     false ;;
+esac &&
 [ -d "$GIT_DIR/refs" ] &&
 [ -d "$GIT_OBJECT_DIRECTORY/00" ]
index 621fa49..44398d7 100755 (executable)
@@ -31,15 +31,15 @@ report () {
   [ "$header" ]
 }
 
-branch=`readlink "$GIT_DIR/HEAD"`
+branch=$(GIT_DIR="$GIT_DIR" git-symbolic-ref HEAD)
 case "$branch" in
 refs/heads/master) ;;
 *)     echo "# On branch $branch" ;;
 esac
 
-git-update-index --refresh >/dev/null 2>&1
+git-update-index -q --unmerged --refresh || exit
 
-if test -f "$GIT_DIR/HEAD"
+if GIT_DIR="$GIT_DIR" git-rev-parse --verify HEAD >/dev/null 2>&1
 then
        git-diff-index -M --cached HEAD |
        sed 's/^://' |
diff --git a/git.sh b/git.sh
index 2986f08..7400c16 100755 (executable)
--- a/git.sh
+++ b/git.sh
@@ -26,17 +26,50 @@ esac
 
 echo "Usage: git COMMAND [OPTIONS] [TARGET]"
 if [ -n "$cmd" ]; then
-    echo " git command '$cmd' not found: commands are:"
-else
-    echo " git commands are:"
+    echo "git command '$cmd' not found."
 fi
+echo "git commands are:"
 
-cat <<\EOF
-    add apply archimport bisect branch checkout cherry clone
-    commit count-objects cvsimport diff fetch format-patch
-    fsck-cache get-tar-commit-id init-db log ls-remote octopus
-    pack-objects parse-remote patch-id prune pull push rebase
-    relink rename repack request-pull reset resolve revert
-    send-email shortlog show-branch status tag verify-tag
-    whatchanged
+fmt <<\EOF | sed -e 's/^/    /'
+add
+apply
+archimport
+bisect
+branch
+checkout
+cherry
+clone
+commit
+count-objects
+cvsimport
+diff
+fetch
+format-patch
+fsck-objects
+get-tar-commit-id
+init-db
+log
+ls-remote
+octopus
+pack-objects
+parse-remote
+patch-id
+prune
+pull
+push
+rebase
+relink
+rename
+repack
+request-pull
+reset
+resolve
+revert
+send-email
+shortlog
+show-branch
+status
+tag
+verify-tag
+whatchanged
 EOF
index 0566a91..71a8c60 100644 (file)
@@ -1,6 +1,6 @@
 #include "cache.h"
 #include "commit.h"
-
+#include "pack.h"
 #include "fetch.h"
 
 #include <curl/curl.h>
 #define curl_global_init(a) do { /* nothing */ } while(0)
 #endif
 
+#define PREV_BUF_SIZE 4096
+#define RANGE_HEADER_SIZE 30
+
 static CURL *curl;
 static struct curl_slist *no_pragma_header;
+static struct curl_slist *no_range_header;
 static char curl_errorstr[CURL_ERROR_SIZE];
 
 static char *initial_base;
@@ -87,12 +91,37 @@ void prefetch(unsigned char *sha1)
 {
 }
 
+int relink_or_rename(char *old, char *new) {
+       int ret;
+
+       ret = link(old, new);
+       if (ret < 0) {
+               /* Same Coda hack as in write_sha1_file(sha1_file.c) */
+               ret = errno;
+               if (ret == EXDEV && !rename(old, new))
+                       return 0;
+       }
+       unlink(old);
+       if (ret) {
+               if (ret != EEXIST)
+                       return ret;
+       }
+
+       return 0;
+}
+
 static int got_alternates = 0;
 
 static int fetch_index(struct alt_base *repo, unsigned char *sha1)
 {
        char *filename;
        char *url;
+       char tmpfile[PATH_MAX];
+       int ret;
+       long prev_posn = 0;
+       char range[RANGE_HEADER_SIZE];
+       struct curl_slist *range_header = NULL;
+       CURLcode curl_result;
 
        FILE *indexfile;
 
@@ -108,7 +137,8 @@ static int fetch_index(struct alt_base *repo, unsigned char *sha1)
                repo->base, sha1_to_hex(sha1));
        
        filename = sha1_pack_index_name(sha1);
-       indexfile = fopen(filename, "w");
+       snprintf(tmpfile, sizeof(tmpfile), "%s.temp", filename);
+       indexfile = fopen(tmpfile, "a");
        if (!indexfile)
                return error("Unable to open local file %s for pack index",
                             filename);
@@ -119,13 +149,36 @@ static int fetch_index(struct alt_base *repo, unsigned char *sha1)
        curl_easy_setopt(curl, CURLOPT_HTTPHEADER, no_pragma_header);
        curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, curl_errorstr);
        
-       if (curl_easy_perform(curl)) {
+       /* If there is data present from a previous transfer attempt,
+          resume where it left off */
+       prev_posn = ftell(indexfile);
+       if (prev_posn>0) {
+               if (get_verbosely)
+                       fprintf(stderr,
+                               "Resuming fetch of index for pack %s at byte %ld\n",
+                               sha1_to_hex(sha1), prev_posn);
+               sprintf(range, "Range: bytes=%ld-", prev_posn);
+               range_header = curl_slist_append(range_header, range);
+               curl_easy_setopt(curl, CURLOPT_HTTPHEADER, range_header);
+       }
+
+       /* Clear out the Range: header after performing the request, so
+          other curl requests don't inherit inappropriate header data */
+       curl_result = curl_easy_perform(curl);
+       curl_easy_setopt(curl, CURLOPT_HTTPHEADER, no_range_header);
+       if (curl_result != 0) {
                fclose(indexfile);
                return error("Unable to get pack index %s\n%s", url,
                             curl_errorstr);
        }
 
        fclose(indexfile);
+
+       ret = relink_or_rename(tmpfile, filename);
+       if (ret)
+               return error("unable to write index filename %s: %s",
+                            filename, strerror(ret));
+
        return 0;
 }
 
@@ -306,6 +359,12 @@ static int fetch_pack(struct alt_base *repo, unsigned char *sha1)
        struct packed_git **lst;
        FILE *packfile;
        char *filename;
+       char tmpfile[PATH_MAX];
+       int ret;
+       long prev_posn = 0;
+       char range[RANGE_HEADER_SIZE];
+       struct curl_slist *range_header = NULL;
+       CURLcode curl_result;
 
        if (fetch_indices(repo))
                return -1;
@@ -325,7 +384,8 @@ static int fetch_pack(struct alt_base *repo, unsigned char *sha1)
                repo->base, sha1_to_hex(target->sha1));
 
        filename = sha1_pack_name(target->sha1);
-       packfile = fopen(filename, "w");
+       snprintf(tmpfile, sizeof(tmpfile), "%s.temp", filename);
+       packfile = fopen(tmpfile, "a");
        if (!packfile)
                return error("Unable to open local file %s for pack",
                             filename);
@@ -336,7 +396,24 @@ static int fetch_pack(struct alt_base *repo, unsigned char *sha1)
        curl_easy_setopt(curl, CURLOPT_HTTPHEADER, no_pragma_header);
        curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, curl_errorstr);
 
-       if (curl_easy_perform(curl)) {
+       /* If there is data present from a previous transfer attempt,
+          resume where it left off */
+       prev_posn = ftell(packfile);
+       if (prev_posn>0) {
+               if (get_verbosely)
+                       fprintf(stderr,
+                               "Resuming fetch of pack %s at byte %ld\n",
+                               sha1_to_hex(target->sha1), prev_posn);
+               sprintf(range, "Range: bytes=%ld-", prev_posn);
+               range_header = curl_slist_append(range_header, range);
+               curl_easy_setopt(curl, CURLOPT_HTTPHEADER, range_header);
+       }
+
+       /* Clear out the Range: header after performing the request, so
+          other curl requests don't inherit inappropriate header data */
+       curl_result = curl_easy_perform(curl);
+       curl_easy_setopt(curl, CURLOPT_HTTPHEADER, no_range_header);
+       if (curl_result != 0) {
                fclose(packfile);
                return error("Unable to get pack file %s\n%s", url,
                             curl_errorstr);
@@ -344,11 +421,18 @@ static int fetch_pack(struct alt_base *repo, unsigned char *sha1)
 
        fclose(packfile);
 
+       ret = relink_or_rename(tmpfile, filename);
+       if (ret)
+               return error("unable to write pack filename %s: %s",
+                            filename, strerror(ret));
+
        lst = &repo->packs;
        while (*lst != target)
                lst = &((*lst)->next);
        *lst = (*lst)->next;
 
+       if (verify_pack(target, 0))
+               return -1;
        install_packed_git(target);
 
        return 0;
@@ -360,14 +444,33 @@ static int fetch_object(struct alt_base *repo, unsigned char *sha1)
        char *filename = sha1_file_name(sha1);
        unsigned char real_sha1[20];
        char tmpfile[PATH_MAX];
+       char prevfile[PATH_MAX];
        int ret;
        char *url;
        char *posn;
+       int prevlocal;
+       unsigned char prev_buf[PREV_BUF_SIZE];
+       ssize_t prev_read = 0;
+       long prev_posn = 0;
+       char range[RANGE_HEADER_SIZE];
+       struct curl_slist *range_header = NULL;
+       CURLcode curl_result;
+
+       snprintf(tmpfile, sizeof(tmpfile), "%s.temp", filename);
+       snprintf(prevfile, sizeof(prevfile), "%s.prev", filename);
+
+       if (unlink(prevfile) && (errno != ENOENT))
+               return error("Failed to unlink %s (%s)",
+                            prevfile, strerror(errno));
+       if (rename(tmpfile, prevfile) && (errno != ENOENT))
+               return error("Failed to rename %s to %s (%s)",
+                            tmpfile, prevfile, strerror(errno));
 
-       snprintf(tmpfile, sizeof(tmpfile), "%s/obj_XXXXXX",
-                get_object_directory());
+       local = open(tmpfile, O_WRONLY | O_CREAT | O_EXCL, 0666);
+
+       /* Note: if another instance starts now, it will turn our new
+          tmpfile into its prevfile. */
 
-       local = mkstemp(tmpfile);
        if (local < 0)
                return error("Couldn't create temporary file %s for %s: %s\n",
                             tmpfile, filename, strerror(errno));
@@ -396,8 +499,57 @@ static int fetch_object(struct alt_base *repo, unsigned char *sha1)
 
        curl_easy_setopt(curl, CURLOPT_URL, url);
 
-       if (curl_easy_perform(curl)) {
-               unlink(filename);
+       /* If a previous temp file is present, process what was already
+          fetched. */
+       prevlocal = open(prevfile, O_RDONLY);
+       if (prevlocal != -1) {
+               do {
+                       prev_read = read(prevlocal, prev_buf, PREV_BUF_SIZE);
+                       if (prev_read>0) {
+                               if (fwrite_sha1_file(prev_buf,
+                                                    1,
+                                                    prev_read,
+                                                    NULL) == prev_read) {
+                                       prev_posn += prev_read;
+                               } else {
+                                       prev_read = -1;
+                               }
+                       }
+               } while (prev_read > 0);
+               close(prevlocal);
+       }
+       unlink(prevfile);
+
+       /* Reset inflate/SHA1 if there was an error reading the previous temp
+          file; also rewind to the beginning of the local file. */
+       if (prev_read == -1) {
+               memset(&stream, 0, sizeof(stream));
+               inflateInit(&stream);
+               SHA1_Init(&c);
+               if (prev_posn>0) {
+                       prev_posn = 0;
+                       lseek(local, SEEK_SET, 0);
+                       ftruncate(local, 0);
+               }
+       }
+
+       /* If we have successfully processed data from a previous fetch
+          attempt, only fetch the data we don't already have. */
+       if (prev_posn>0) {
+               if (get_verbosely)
+                       fprintf(stderr,
+                               "Resuming fetch of object %s at byte %ld\n",
+                               hex, prev_posn);
+               sprintf(range, "Range: bytes=%ld-", prev_posn);
+               range_header = curl_slist_append(range_header, range);
+               curl_easy_setopt(curl, CURLOPT_HTTPHEADER, range_header);
+       }
+
+       /* Clear out the Range: header after performing the request, so
+          other curl requests don't inherit inappropriate header data */
+       curl_result = curl_easy_perform(curl);
+       curl_easy_setopt(curl, CURLOPT_HTTPHEADER, no_range_header);
+       if (curl_result != 0) {
                return error("%s", curl_errorstr);
        }
 
@@ -413,20 +565,11 @@ static int fetch_object(struct alt_base *repo, unsigned char *sha1)
                unlink(tmpfile);
                return error("File %s has bad hash\n", hex);
        }
-       ret = link(tmpfile, filename);
-       if (ret < 0) {
-               /* Same Coda hack as in write_sha1_file(sha1_file.c) */
-               ret = errno;
-               if (ret == EXDEV && !rename(tmpfile, filename))
-                       goto out;
-       }
-       unlink(tmpfile);
-       if (ret) {
-               if (ret != EEXIST)
-                       return error("unable to write sha1 filename %s: %s",
-                                    filename, strerror(ret));
-       }
- out:
+       ret = relink_or_rename(tmpfile, filename);
+       if (ret)
+               return error("unable to write sha1 filename %s: %s",
+                            filename, strerror(ret));
+
        pull_say("got %s\n", hex);
        return 0;
 }
@@ -519,6 +662,7 @@ int main(int argc, char **argv)
 
        curl = curl_easy_init();
        no_pragma_header = curl_slist_append(no_pragma_header, "Pragma:");
+       no_range_header = curl_slist_append(no_range_header, "Range:");
 
        curl_ssl_verify = getenv("GIT_SSL_NO_VERIFY") ? 0 : 1;
        curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, curl_ssl_verify);
diff --git a/index.c b/index.c
index 87fc7b0..bdde65f 100644 (file)
--- a/index.c
+++ b/index.c
@@ -29,7 +29,7 @@ int hold_index_file_for_update(struct cache_file *cf, const char *path)
                signal(SIGINT, remove_lock_file_on_signal);
                atexit(remove_lock_file);
        }
-       return open(cf->lockfile, O_RDWR | O_CREAT | O_EXCL, 0600);
+       return open(cf->lockfile, O_RDWR | O_CREAT | O_EXCL, 0666);
 }
 
 int commit_index_file(struct cache_file *cf)
index da2bc8f..aabc09f 100644 (file)
--- a/init-db.c
+++ b/init-db.c
@@ -166,6 +166,7 @@ static void create_default_files(const char *git_dir,
 {
        unsigned len = strlen(git_dir);
        static char path[PATH_MAX];
+       unsigned char sha1[20];
 
        if (len > sizeof(path)-50)
                die("insane git directory %s", git_dir);
@@ -186,15 +187,14 @@ static void create_default_files(const char *git_dir,
 
        /*
         * Create the default symlink from ".git/HEAD" to the "master"
-        * branch
+        * branch, if it does not exist yet.
         */
        strcpy(path + len, "HEAD");
-       if (symlink("refs/heads/master", path) < 0) {
-               if (errno != EEXIST) {
-                       perror(path);
+       if (read_ref(path, sha1) < 0) {
+               if (create_symref(path, "refs/heads/master") < 0)
                        exit(1);
-               }
        }
+       path[len] = 0;
        copy_templates(path, len, template_path);
 }
 
index 956be09..f47114a 100644 (file)
@@ -530,7 +530,7 @@ static void verify_pathspec(void)
 static const char ls_files_usage[] =
        "git-ls-files [-z] [-t] (--[cached|deleted|others|stage|unmerged|killed|modified])* "
        "[ --ignored ] [--exclude=<pattern>] [--exclude-from=<file>] "
-       "[ --exclude-per-directory=<filename> ]";
+       "[ --exclude-per-directory=<filename> ] [--] [<file>]*";
 
 int main(int argc, const char **argv)
 {
@@ -544,6 +544,10 @@ int main(int argc, const char **argv)
        for (i = 1; i < argc; i++) {
                const char *arg = argv[i];
 
+               if (!strcmp(arg, "--")) {
+                       i++;
+                       break;
+               }
                if (!strcmp(arg, "-z")) {
                        line_terminator = 0;
                        continue;
index a3238c2..7afea1a 100644 (file)
@@ -128,7 +128,7 @@ int main(int argc, char **argv)
                unsigned long len = parse_email(map, size);
                assert(len <= size);
                sprintf(name, "%04d", ++nr);
-               fd = open(name, O_WRONLY | O_CREAT | O_EXCL, 0600);
+               fd = open(name, O_WRONLY | O_CREAT | O_EXCL, 0666);
                if (fd < 0) {
                        perror(name);
                        exit(1);
index 0e345bd..d2aebdd 100644 (file)
@@ -464,11 +464,15 @@ int read_cache(void)
 
        errno = EBUSY;
        if (active_cache)
-               return error("more than one cachefile");
+               return active_nr;
+
        errno = ENOENT;
        fd = open(get_index_file(), O_RDONLY);
-       if (fd < 0)
-               return (errno == ENOENT) ? 0 : error("open failed");
+       if (fd < 0) {
+               if (errno == ENOENT)
+                       return 0;
+               die("index file open failed (%s)", strerror(errno));
+       }
 
        size = 0; // avoid gcc warning
        map = MAP_FAILED;
@@ -480,7 +484,7 @@ int read_cache(void)
        }
        close(fd);
        if (map == MAP_FAILED)
-               return error("mmap failed");
+               die("index file mmap failed (%s)", strerror(errno));
 
        hdr = map;
        if (verify_hdr(hdr, size) < 0)
@@ -501,7 +505,7 @@ int read_cache(void)
 unmap:
        munmap(map, size);
        errno = EINVAL;
-       return error("verify header failed");
+       die("index file corrupt");
 }
 
 #define WRITE_BUFFER_SIZE 8192
index ca80873..5fdf58d 100644 (file)
@@ -13,6 +13,8 @@
 static int merge = 0;
 static int update = 0;
 static int index_only = 0;
+static int nontrivial_merge = 0;
+static int trivial_merges_only = 0;
 
 static int head_idx = -1;
 static int merge_size = 0;
@@ -275,6 +277,9 @@ static int unpack_trees(merge_fn_t fn)
        if (unpack_trees_rec(posns, len, "", fn, &indpos))
                return -1;
 
+       if (trivial_merges_only && nontrivial_merge)
+               die("Merge requires file-level merging");
+
        check_updates(active_cache, active_nr);
        return 0;
 }
@@ -460,6 +465,8 @@ static int threeway_merge(struct cache_entry **stages)
                verify_uptodate(index);
        }
 
+       nontrivial_merge = 1;
+
        /* #2, #3, #4, #6, #7, #9, #11. */
        count = 0;
        if (!head_match || !remote_match) {
@@ -629,9 +636,9 @@ int main(int argc, char **argv)
                        continue;
                }
 
-               if (!strcmp(arg, "--head")) {
-                       head_idx = stage - 1;
-                       fn = threeway_merge;
+               if (!strcmp(arg, "--trivial")) {
+                       trivial_merges_only = 1;
+                       continue;
                }
 
                /* "-m" stands for "merge", meaning we start in stage 1 */
@@ -657,7 +664,8 @@ int main(int argc, char **argv)
        }
        if ((update||index_only) && !merge)
                usage(read_tree_usage);
-       if (merge && !fn) {
+
+       if (merge) {
                if (stage < 2)
                        die("just how do you expect me to merge %d trees?", stage-1);
                switch (stage - 1) {
@@ -674,9 +682,7 @@ int main(int argc, char **argv)
                        fn = threeway_merge;
                        break;
                }
-       }
 
-       if (head_idx < 0) {
                if (stage - 1 >= 3)
                        head_idx = stage - 2;
                else
diff --git a/refs.c b/refs.c
index 1610180..2aac90c 100644 (file)
--- a/refs.c
+++ b/refs.c
 #include "cache.h"
 
 #include <errno.h>
+#include <ctype.h>
 
-static int read_ref(const char *refname, unsigned char *sha1)
+/* We allow "recursive" symbolic refs. Only within reason, though */
+#define MAXDEPTH 5
+
+#ifndef USE_SYMLINK_HEAD
+#define USE_SYMLINK_HEAD 1
+#endif
+
+int validate_symref(const char *path)
 {
-       int ret = -1;
-       int fd = open(git_path("%s", refname), O_RDONLY);
+       struct stat st;
+       char *buf, buffer[256];
+       int len, fd;
+
+       if (lstat(path, &st) < 0)
+               return -1;
+
+       /* Make sure it is a "refs/.." symlink */
+       if (S_ISLNK(st.st_mode)) {
+               len = readlink(path, buffer, sizeof(buffer)-1);
+               if (len >= 5 && !memcmp("refs/", buffer, 5))
+                       return 0;
+               return -1;
+       }
+
+       /*
+        * Anything else, just open it and try to see if it is a symbolic ref.
+        */
+       fd = open(path, O_RDONLY);
+       if (fd < 0)
+               return -1;
+       len = read(fd, buffer, sizeof(buffer)-1);
+       close(fd);
+
+       /*
+        * Is it a symbolic ref?
+        */
+       if (len < 4 || memcmp("ref:", buffer, 4))
+               return -1;
+       buf = buffer + 4;
+       len -= 4;
+       while (len && isspace(*buf))
+               buf++, len--;
+       if (len >= 5 && !memcmp("refs/", buffer, 5))
+               return 0;
+       return -1;
+}
 
-       if (fd >= 0) {
-               char buffer[60];
-               if (read(fd, buffer, sizeof(buffer)) >= 40)
-                       ret = get_sha1_hex(buffer, sha1);
+const char *resolve_ref(const char *path, unsigned char *sha1, int reading)
+{
+       int depth = MAXDEPTH, len;
+       char buffer[256];
+
+       for (;;) {
+               struct stat st;
+               char *buf;
+               int fd;
+
+               if (--depth < 0)
+                       return NULL;
+
+               /* Special case: non-existing file.
+                * Not having the refs/heads/new-branch is OK
+                * if we are writing into it, so is .git/HEAD
+                * that points at refs/heads/master still to be
+                * born.  It is NOT OK if we are resolving for
+                * reading.
+                */
+               if (lstat(path, &st) < 0) {
+                       if (reading || errno != ENOENT)
+                               return NULL;
+                       memset(sha1, 0, 20);
+                       return path;
+               }
+
+               /* Follow "normalized" - ie "refs/.." symlinks by hand */
+               if (S_ISLNK(st.st_mode)) {
+                       len = readlink(path, buffer, sizeof(buffer)-1);
+                       if (len >= 5 && !memcmp("refs/", buffer, 5)) {
+                               path = git_path("%.*s", len, buffer);
+                               continue;
+                       }
+               }
+
+               /*
+                * Anything else, just open it and try to use it as
+                * a ref
+                */
+               fd = open(path, O_RDONLY);
+               if (fd < 0)
+                       return NULL;
+               len = read(fd, buffer, sizeof(buffer)-1);
                close(fd);
+
+               /*
+                * Is it a symbolic ref?
+                */
+               if (len < 4 || memcmp("ref:", buffer, 4))
+                       break;
+               buf = buffer + 4;
+               len -= 4;
+               while (len && isspace(*buf))
+                       buf++, len--;
+               while (len && isspace(buf[len-1]))
+                       buf[--len] = 0;
+               path = git_path("%.*s", len, buf);
        }
-       return ret;
+       if (len < 40 || get_sha1_hex(buffer, sha1))
+               return NULL;
+       return path;
+}
+
+int create_symref(const char *git_HEAD, const char *refs_heads_master)
+{
+#if USE_SYMLINK_HEAD
+       unlink(git_HEAD);
+       return symlink(refs_heads_master, git_HEAD);
+#else
+       const char *lockpath;
+       char ref[1000];
+       int fd, len, written;
+
+       len = snprintf(ref, sizeof(ref), "ref: %s\n", refs_heads_master);
+       if (sizeof(ref) <= len) {
+               error("refname too long: %s", refs_heads_master);
+               return -1;
+       }
+       lockpath = mkpath("%s.lock", git_HEAD);
+       fd = open(lockpath, O_CREAT | O_EXCL | O_WRONLY, 0666); 
+       written = write(fd, ref, len);
+       close(fd);
+       if (written != len) {
+               unlink(lockpath);
+               error("Unable to write to %s", lockpath);
+               return -2;
+       }
+       if (rename(lockpath, git_HEAD) < 0) {
+               unlink(lockpath);
+               error("Unable to create %s", git_HEAD);
+               return -3;
+       }
+       return 0;
+#endif
+}
+
+int read_ref(const char *filename, unsigned char *sha1)
+{
+       if (resolve_ref(filename, sha1, 1))
+               return 0;
+       return -1;
 }
 
 static int do_for_each_ref(const char *base, int (*fn)(const char *path, const unsigned char *sha1))
@@ -54,7 +192,7 @@ static int do_for_each_ref(const char *base, int (*fn)(const char *path, const u
                                        break;
                                continue;
                        }
-                       if (read_ref(path, sha1) < 0)
+                       if (read_ref(git_path("%s", path), sha1) < 0)
                                continue;
                        if (!has_sha1_file(sha1))
                                continue;
@@ -71,7 +209,7 @@ static int do_for_each_ref(const char *base, int (*fn)(const char *path, const u
 int head_ref(int (*fn)(const char *path, const unsigned char *sha1))
 {
        unsigned char sha1[20];
-       if (!read_ref("HEAD", sha1))
+       if (!read_ref(git_path("HEAD"), sha1))
                return fn("HEAD", sha1);
        return 0;
 }
@@ -101,33 +239,14 @@ static char *ref_lock_file_name(const char *ref)
        return ret;
 }
 
-static int read_ref_file(const char *filename, unsigned char *sha1) {
-       int fd = open(filename, O_RDONLY);
-       char hex[41];
-       if (fd < 0) {
-               return error("Couldn't open %s\n", filename);
-       }
-       if ((read(fd, hex, 41) < 41) ||
-           (hex[40] != '\n') ||
-           get_sha1_hex(hex, sha1)) {
-               error("Couldn't read a hash from %s\n", filename);
-               close(fd);
-               return -1;
-       }
-       close(fd);
-       return 0;
-}
-
 int get_ref_sha1(const char *ref, unsigned char *sha1)
 {
-       char *filename;
-       int retval;
+       const char *filename;
+
        if (check_ref_format(ref))
                return -1;
-       filename = ref_file_name(ref);
-       retval = read_ref_file(filename, sha1);
-       free(filename);
-       return retval;
+       filename = git_path("refs/%s", ref);
+       return read_ref(filename, sha1);
 }
 
 static int lock_ref_file(const char *filename, const char *lock_filename,
@@ -140,7 +259,7 @@ static int lock_ref_file(const char *filename, const char *lock_filename,
                return error("Couldn't open lock file for %s: %s",
                             filename, strerror(errno));
        }
-       retval = read_ref_file(filename, current_sha1);
+       retval = read_ref(filename, current_sha1);
        if (old_sha1) {
                if (retval) {
                        close(fd);
diff --git a/setup.c b/setup.c
index 9e20160..c487d7e 100644 (file)
--- a/setup.c
+++ b/setup.c
@@ -76,18 +76,20 @@ const char **get_pathspec(const char *prefix, const char **pathspec)
  * Test it it looks like we're at the top
  * level git directory. We want to see a
  *
- *  - a HEAD symlink and a refs/ directory under ".git"
  *  - either a .git/objects/ directory _or_ the proper
  *    GIT_OBJECT_DIRECTORY environment variable
+ *  - a refs/ directory under ".git"
+ *  - either a HEAD symlink or a HEAD file that is formatted as
+ *    a proper "ref:".
  */
 static int is_toplevel_directory(void)
 {
-       struct stat st;
-
-       return  !lstat(".git/HEAD", &st) &&
-               S_ISLNK(st.st_mode) &&
-               !access(".git/refs/", X_OK) &&
-               (getenv(DB_ENVIRONMENT) || !access(".git/objects/", X_OK));
+       if (access(".git/refs/", X_OK) ||
+           access(getenv(DB_ENVIRONMENT) ?
+                  getenv(DB_ENVIRONMENT) : ".git/objects/", X_OK) ||
+           validate_symref(".git/HEAD"))
+               return 0;
+       return 1;
 }
 
 const char *setup_git_directory(void)
index 1e847a8..895c1fa 100644 (file)
@@ -20,6 +20,8 @@
 #endif
 #endif
 
+const unsigned char null_sha1[20] = { 0, };
+
 static unsigned int sha1_file_open_flag = O_NOATIME;
 
 static unsigned hexval(char c)
index b4fed92..57e6cd3 100644 (file)
@@ -119,21 +119,6 @@ static int get_short_sha1(const char *name, int len, unsigned char *sha1)
        return -1;
 }
 
-static int get_sha1_file(const char *path, unsigned char *result)
-{
-       char buffer[60];
-       int fd = open(path, O_RDONLY);
-       int len;
-
-       if (fd < 0)
-               return -1;
-       len = read(fd, buffer, sizeof(buffer));
-       close(fd);
-       if (len < 40)
-               return -1;
-       return get_sha1_hex(buffer, result);
-}
-
 static int get_sha1_basic(const char *str, int len, unsigned char *sha1)
 {
        static const char *prefix[] = {
@@ -150,7 +135,7 @@ static int get_sha1_basic(const char *str, int len, unsigned char *sha1)
 
        for (p = prefix; *p; p++) {
                char *pathname = git_path("%s/%.*s", *p, len, str);
-               if (!get_sha1_file(pathname, sha1))
+               if (!read_ref(pathname, sha1))
                        return 0;
        }
 
index 5778a59..8429c17 100644 (file)
@@ -349,6 +349,7 @@ int main(int ac, char **av)
        int all_heads = 0, all_tags = 0;
        int all_mask, all_revs, shown_merge_point;
        char head_path[128];
+       const char *head_path_p;
        int head_path_len;
        unsigned char head_sha1[20];
        int merge_base = 0;
@@ -430,11 +431,15 @@ int main(int ac, char **av)
        if (0 <= extra)
                join_revs(&list, &seen, num_rev, extra);
 
-       head_path_len = readlink(".git/HEAD", head_path, sizeof(head_path)-1);
-       if ((head_path_len < 0) || get_sha1("HEAD", head_sha1))
+       head_path_p = resolve_ref(git_path("HEAD"), head_sha1, 1);
+       if (head_path_p) {
+               head_path_len = strlen(head_path_p);
+               memcpy(head_path, head_path_p, head_path_len + 1);
+       }
+       else {
+               head_path_len = 0;
                head_path[0] = 0;
-       else
-               head_path[head_path_len] = 0;
+       }
 
        if (merge_base)
                return show_merge_base(seen, num_rev);
diff --git a/symbolic-ref.c b/symbolic-ref.c
new file mode 100644 (file)
index 0000000..af087d2
--- /dev/null
@@ -0,0 +1,34 @@
+#include "cache.h"
+
+static const char git_symbolic_ref_usage[] =
+"git-symbolic-ref name [ref]";
+
+static int check_symref(const char *HEAD)
+{
+       unsigned char sha1[20];
+       const char *git_HEAD = strdup(git_path("%s", HEAD));
+       const char *git_refs_heads_master = resolve_ref(git_HEAD, sha1, 0);
+       if (git_refs_heads_master) {
+               /* we want to strip the .git/ part */
+               int pfxlen = strlen(git_HEAD) - strlen(HEAD);
+               puts(git_refs_heads_master + pfxlen);
+       }
+       else
+               die("No such ref: %s", HEAD);
+}
+
+int main(int argc, const char **argv)
+{
+       setup_git_directory();
+       switch (argc) {
+       case 2:
+               check_symref(argv[1]);
+               break;
+       case 3:
+               create_symref(strdup(git_path("%s", argv[1])), argv[2]);
+               break;
+       default:
+               usage(git_symbolic_ref_usage);
+       }
+       return 0;
+}
index a6b8088..e71da77 100644 (file)
@@ -5,6 +5,7 @@
 
 #GIT_TEST_OPTS=--verbose --debug
 SHELL_PATH ?= $(SHELL)
+TAR ?= $(TAR)
 
 T = $(wildcard t[0-9][0-9][0-9][0-9]-*.sh)
 
diff --git a/t/t3002-ls-files-dashpath.sh b/t/t3002-ls-files-dashpath.sh
new file mode 100755 (executable)
index 0000000..b42f138
--- /dev/null
@@ -0,0 +1,69 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Junio C Hamano
+#
+
+test_description='git-ls-files test (-- to terminate the path list).
+
+This test runs git-ls-files --others with the following on the
+filesystem.
+
+    path0       - a file
+    -foo       - a file with a funny name.
+    --         - another file with a funny name.
+'
+. ./test-lib.sh
+
+test_expect_success \
+       setup \
+       'echo frotz >path0 &&
+       echo frotz >./-foo &&
+       echo frotz >./--'
+
+test_expect_success \
+    'git-ls-files without path restriction.' \
+    'git-ls-files --others >output &&
+     diff -u output - <<EOF
+--
+-foo
+output
+path0
+EOF
+'
+
+test_expect_success \
+    'git-ls-files with path restriction.' \
+    'git-ls-files --others path0 >output &&
+       diff -u output - <<EOF
+path0
+EOF
+'
+
+test_expect_success \
+    'git-ls-files with path restriction with --.' \
+    'git-ls-files --others -- path0 >output &&
+       diff -u output - <<EOF
+path0
+EOF
+'
+
+test_expect_success \
+    'git-ls-files with path restriction with -- --.' \
+    'git-ls-files --others -- -- >output &&
+       diff -u output - <<EOF
+--
+EOF
+'
+
+test_expect_success \
+    'git-ls-files with no path restriction.' \
+    'git-ls-files --others -- >output &&
+       diff -u output - <<EOF
+--
+-foo
+output
+path0
+EOF
+'
+
+test_done
index 6bf3406..5dffb8e 100755 (executable)
@@ -50,7 +50,7 @@ test_expect_success \
 
 test_expect_success \
     'validate file modification time' \
-    'TZ=GMT tar tvf b.tar a/a |
+    'TZ=GMT $TAR tvf b.tar a/a |
      awk \{print\ \$4,\ \(length\(\$5\)\<7\)\ ?\ \$5\":00\"\ :\ \$5\} \
      >b.mtime &&
      echo "2005-05-27 22:00:00" >expected.mtime &&
@@ -63,7 +63,7 @@ test_expect_success \
 
 test_expect_success \
     'extract tar archive' \
-    '(cd b && tar xf -) <b.tar'
+    '(cd b && $TAR xf -) <b.tar'
 
 test_expect_success \
     'validate filenames' \
@@ -80,7 +80,7 @@ test_expect_success \
 
 test_expect_success \
     'extract tar archive with prefix' \
-    '(cd c && tar xf -) <c.tar'
+    '(cd c && $TAR xf -) <c.tar'
 
 test_expect_success \
     'validate filenames with prefix' \
index 59ce77b..1a4d2f2 100755 (executable)
@@ -20,12 +20,12 @@ test_expect_success setup '
            commit=$(echo "Commit #$i" | git-commit-tree $tree -p $parent) &&
            parent=$commit || return 1
        done &&
-       echo "$commit" >.git/HEAD &&
+       git-update-ref HEAD "$commit" &&
        git-clone -l ./. victim &&
        cd victim &&
        git-log &&
        cd .. &&
-       echo $zero >.git/HEAD &&
+       git-update-ref HEAD "$zero" &&
        parent=$zero &&
        for i in $cnt
        do
@@ -33,7 +33,7 @@ test_expect_success setup '
            commit=$(echo "Rebase #$i" | git-commit-tree $tree -p $parent) &&
            parent=$commit || return 1
        done &&
-       echo "$commit" >.git/HEAD &&
+       git-update-ref HEAD "$commit" &&
        echo Rebase &&
        git-log'
 
index d0a4ff2..42fcbc6 100755 (executable)
@@ -7,20 +7,6 @@ test_description='Tests git-rev-list --bisect functionality'
 . ./test-lib.sh
 . ../t6000lib.sh # t6xxx specific functions
 
-bc_expr()
-{
-bc <<EOF
-scale=1
-define abs(x) {
-       if (x>=0) { return (x); } else { return (-x); }
-}
-define floor(x) {
-       save=scale; scale=0; result=x/1; scale=save; return (result);
-}
-$*
-EOF
-}
-
 # usage: test_bisection max-diff bisect-option head ^prune...
 #
 # e.g. test_bisection 1 --bisect l1 ^l0
@@ -35,8 +21,19 @@ test_bisection_diff()
         _head=$1
        shift 1
        _bisection_size=$(git-rev-list $_bisection "$@" | wc -l)
-       [ -n "$_list_size" -a -n "$_bisection_size" ] || error "test_bisection_diff failed"
-       test_expect_success "bisection diff $_bisect_option $_head $* <= $_max_diff" "[ $(bc_expr "floor(abs($_list_size/2)-$_bisection_size)") -le $_max_diff ]"
+       [ -n "$_list_size" -a -n "$_bisection_size" ] ||
+       error "test_bisection_diff failed"
+
+       # Test if bisection size is close to half of list size within
+       # tolerance.
+       # 
+       _bisect_err=`expr $_list_size - $_bisection_size \* 2`
+       test "$_bisect_err" -lt 0 && _bisect_err=`expr 0 - $_bisect_err`
+       _bisect_err=`expr $_bisect_err / 2` ; # floor
+
+       test_expect_success \
+       "bisection diff $_bisect_option $_head $* <= $_max_diff" \
+       'test $_bisect_err -le $_max_diff'
 }
 
 date >path0
index 2716ae3..970c4bb 100644 (file)
@@ -353,6 +353,8 @@ static void traverse_tree(void *buffer, unsigned long size,
 
                if (size < namelen + 20 || sscanf(buffer, "%o", &mode) != 1)
                        die("corrupt 'tree' file");
+               if (S_ISDIR(mode) || S_ISREG(mode))
+                       mode |= (mode & 0100) ? 0777 : 0666;
                buffer = sha1 + 20;
                size -= namelen + 20;
 
index 01eaa1a..b825a11 100644 (file)
@@ -13,7 +13,7 @@
  * like "git-update-index *" and suddenly having all the object
  * files be revision controlled.
  */
-static int allow_add = 0, allow_remove = 0, allow_replace = 0, not_new = 0, quiet = 0, info_only = 0;
+static int allow_add = 0, allow_remove = 0, allow_replace = 0, allow_unmerged = 0, not_new = 0, quiet = 0, info_only = 0;
 static int force_remove;
 
 /* Three functions to allow overloaded pointer return; see linux/err.h */
@@ -135,7 +135,7 @@ static struct cache_entry *refresh_entry(struct cache_entry *ce)
 
        changed = ce_match_stat(ce, &st);
        if (!changed)
-               return ce;
+               return NULL;
 
        if (ce_modified(ce, &st))
                return ERR_PTR(-EINVAL);
@@ -156,16 +156,20 @@ static int refresh_cache(void)
                struct cache_entry *ce, *new;
                ce = active_cache[i];
                if (ce_stage(ce)) {
-                       printf("%s: needs merge\n", ce->name);
-                       has_errors = 1;
                        while ((i < active_nr) &&
                               ! strcmp(active_cache[i]->name, ce->name))
                                i++;
                        i--;
+                       if (allow_unmerged)
+                               continue;
+                       printf("%s: needs merge\n", ce->name);
+                       has_errors = 1;
                        continue;
                }
 
                new = refresh_entry(ce);
+               if (!new)
+                       continue;
                if (IS_ERR(new)) {
                        if (not_new && PTR_ERR(new) == -ENOENT)
                                continue;
@@ -335,6 +339,10 @@ int main(int argc, const char **argv)
                                allow_remove = 1;
                                continue;
                        }
+                       if (!strcmp(path, "--unmerged")) {
+                               allow_unmerged = 1;
+                               continue;
+                       }
                        if (!strcmp(path, "--refresh")) {
                                has_errors |= refresh_cache();
                                continue;
@@ -383,9 +391,11 @@ int main(int argc, const char **argv)
                        update_one(buf.buf, prefix, prefix_length);
                }
        }
-       if (write_cache(newfd, active_cache, active_nr) ||
-           commit_index_file(&cache_file))
-               die("Unable to write new cachefile");
+       if (active_cache_changed) {
+               if (write_cache(newfd, active_cache, active_nr) ||
+                   commit_index_file(&cache_file))
+                       die("Unable to write new cachefile");
+       }
 
        return has_errors ? 1 : 0;
 }
index 1863b82..4a1704c 100644 (file)
@@ -4,53 +4,6 @@
 
 static const char git_update_ref_usage[] = "git-update-ref <refname> <value> [<oldval>]";
 
-#define MAXDEPTH 5
-
-static const char *resolve_ref(const char *path, unsigned char *sha1)
-{
-       int depth = MAXDEPTH, len;
-       char buffer[256];
-
-       for (;;) {
-               struct stat st;
-               int fd;
-
-               if (--depth < 0)
-                       return NULL;
-
-               /* Special case: non-existing file */
-               if (lstat(path, &st) < 0) {
-                       if (errno != ENOENT)
-                               return NULL;
-                       memset(sha1, 0, 20);
-                       return path;
-               }
-
-               /* Follow "normalized" - ie "refs/.." symlinks by hand */
-               if (S_ISLNK(st.st_mode)) {
-                       len = readlink(path, buffer, sizeof(buffer)-1);
-                       if (len >= 5 && !memcmp("refs/", buffer, 5)) {
-                               path = git_path("%.*s", len, buffer);
-                               continue;
-                       }
-               }
-
-               /*
-                * Anything else, just open it and try to use it as
-                * a ref
-                */
-               fd = open(path, O_RDONLY);
-               if (fd < 0)
-                       return NULL;
-               len = read(fd, buffer, sizeof(buffer)-1);
-               close(fd);
-               break;
-       }
-       if (len < 40 || get_sha1_hex(buffer, sha1))
-               return NULL;
-       return path;
-}
-
 static int re_verify(const char *path, unsigned char *oldsha1, unsigned char *currsha1)
 {
        char buf[40];
@@ -84,7 +37,7 @@ int main(int argc, char **argv)
        if (oldval && get_sha1(oldval, oldsha1) < 0)
                die("%s: not a valid old SHA1", oldval);
 
-       path = resolve_ref(git_path("%s", refname), currsha1);
+       path = resolve_ref(git_path("%s", refname), currsha1, !!oldval);
        if (!path)
                die("No such ref: %s", refname);
 
diff --git a/usage.c b/usage.c
index 86211c9..dfa87fe 100644 (file)
--- a/usage.c
+++ b/usage.c
@@ -15,7 +15,7 @@ static void report(const char *prefix, const char *err, va_list params)
 void usage(const char *err)
 {
        fprintf(stderr, "usage: %s\n", err);
-       exit(1);
+       exit(129);
 }
 
 void die(const char *err, ...)
@@ -25,7 +25,7 @@ void die(const char *err, ...)
        va_start(params, err);
        report("fatal: ", err, params);
        va_end(params);
-       exit(1);
+       exit(128);
 }
 
 int error(const char *err, ...)