Code

get_sha1_basic(): corner case ambiguity fix
authorJunio C Hamano <junkio@cox.net>
Thu, 15 Dec 2005 20:54:00 +0000 (12:54 -0800)
committerJunio C Hamano <junkio@cox.net>
Thu, 15 Dec 2005 20:54:00 +0000 (12:54 -0800)
When .git/refs/heads/frotz and .git/refs/tags/frotz existed, and
the object name stored in .git/refs/heads/frotz were corrupt, we
ended up picking tags/frotz without complaining.  Worse yet, if
the corrupt .git/refs/heads/frotz was more than 40 bytes and
began with hexadecimal characters, it silently overwritten the
initial part of the returned result.

This commit adds a couple of tests to demonstrate these cases,
with a fix.

Signed-off-by: Junio C Hamano <junkio@cox.net>
sha1_name.c
t/t0000-basic.sh
t/test-lib.sh

index faac158b16ca978914696ed5d2801770a122cce2..bf8f0f0e1feef55ba6be764f60815d15036efa4e 100644 (file)
@@ -203,11 +203,12 @@ const char *find_unique_abbrev(const unsigned char *sha1, int len)
        return NULL;
 }
 
-static int ambiguous_path(const char *path)
+static int ambiguous_path(const char *path, int len)
 {
        int slash = 1;
+       int cnt;
 
-       for (;;) {
+       for (cnt = 0; cnt < len; cnt++) {
                switch (*path++) {
                case '\0':
                        break;
@@ -224,6 +225,7 @@ static int ambiguous_path(const char *path)
                }
                return slash;
        }
+       return slash;
 }
 
 static int get_sha1_basic(const char *str, int len, unsigned char *sha1)
@@ -242,26 +244,41 @@ static int get_sha1_basic(const char *str, int len, unsigned char *sha1)
                return 0;
 
        /* Accept only unambiguous ref paths. */
-       if (ambiguous_path(str))
+       if (ambiguous_path(str, len))
                return -1;
 
        for (p = prefix; *p; p++) {
                char *pathname = git_path("%s/%.*s", *p, len, str);
+
                if (!read_ref(pathname, sha1)) {
                        /* Must be unique; i.e. when heads/foo and
                         * tags/foo are both present, reject "foo".
-                        * Note that read_ref() eventually calls
-                        * get_sha1_hex() which can smudge initial
-                        * part of the buffer even if what is read
-                        * is found to be invalid halfway.
                         */
                        if (1 < found++)
                                return -1;
                }
+
+               /* We want to allow .git/description file and
+                * "description" branch to exist at the same time.
+                * "git-rev-parse description" should silently skip
+                * .git/description file as a candidate for
+                * get_sha1().  However, having garbage file anywhere
+                * under refs/ is not OK, and we would not have caught
+                * ambiguous heads and tags with the above test.
+                */
+               else if (**p && !access(pathname, F_OK)) {
+                       /* Garbage exists under .git/refs */
+                       return error("garbage ref found '%s'", pathname);
+               }
        }
-       if (found == 1)
+       switch (found) {
+       case 0:
+               return -1;
+       case 1:
                return 0;
-       return -1;
+       default:
+               return error("ambiguous refname '%.*s'", len, str);
+       }
 }
 
 static int get_sha1_1(const char *name, int len, unsigned char *sha1);
index bc3e711a525f164f21132438d4d4e12e3dc10698..ffa723ea8bab577fa8443bf9c3b96a01df10434c 100755 (executable)
@@ -205,4 +205,52 @@ test_expect_success \
     'no diff after checkout and git-update-index --refresh.' \
     'git-diff-files >current && cmp -s current /dev/null'
 
+
+# extended sha1 parsing and ambiguity resolution
+
+GIT_AUTHOR_DATE='1995-01-29T16:00:00 -0800'
+GIT_AUTHOR_EMAIL=a.u.thor@example.com
+GIT_AUTHOR_NAME='A U Thor'
+GIT_COMMITTER_DATE='1995-01-29T16:00:00 -0800'
+GIT_COMMITTER_EMAIL=c.o.mmitter@example.com
+GIT_COMMITTER_NAME='C O Mmitter'
+export GIT_AUTHOR_DATE
+export GIT_AUTHOR_EMAIL
+export GIT_AUTHOR_NAME
+export GIT_COMMITTER_DATE
+export GIT_COMMITTER_EMAIL
+export GIT_COMMITTER_NAME
+
+test_expect_success \
+       'initial commit.' \
+       'commit=$(echo Initial commit | git-commit-tree $tree) &&
+        echo "$commit" >.git/refs/heads/master &&
+        git-ls-tree HEAD &&
+        test "$commit" = 51a092e9ef6cbbe66d258acd17599d3f80be6162'
+
+test_expect_success \
+       'Ambiguous' \
+       'echo "$commit" >.git/refs/heads/nasty &&
+        echo "$commit" >.git/refs/tags/nasty &&
+        if git-rev-parse --verify nasty
+        then
+               echo "should have barfed"
+               false
+        else
+               :
+        fi &&
+        # names directly underneath .git/ should not interfere
+        echo "$commit" >.git/refs/heads/description &&
+        git-rev-parse --verify description &&
+        # broken object name
+        echo fffffffffffffffffffffffffffffffffffffffg \
+               >.git/refs/heads/nasty &&
+        if git-rev-parse --verify nasty
+        then
+               echo "should have barfed"
+               false
+        else
+               :
+        fi'
+
 test_done
index 2819bef1c4a80954be121bde3c44fce9221fcdfa..a97d259e26bc269c8f370f1d3bfa82e7d6cb9831 100755 (executable)
@@ -18,6 +18,7 @@ unset GIT_ALTERNATE_OBJECT_DIRECTORIES
 unset GIT_AUTHOR_DATE
 unset GIT_AUTHOR_EMAIL
 unset GIT_AUTHOR_NAME
+unset GIT_COMMITTER_DATE
 unset GIT_COMMITTER_EMAIL
 unset GIT_COMMITTER_NAME
 unset GIT_DIFF_OPTS