sha1_name.c: get_describe_name() by definition groks only commits
[gitweb.git] / sha1_name.c
index db8ac83a12693393f3d155f5cfef376740887272..caef6e5e62ace50b0d285d6cb4e0fd5196ca7f51 100644 (file)
@@ -9,11 +9,67 @@
 
 static int get_sha1_oneline(const char *, unsigned char *, struct commit_list *);
 
-static int find_short_object_filename(int len, const char *hex_pfx, unsigned char *sha1)
+typedef int (*disambiguate_hint_fn)(const unsigned char *, void *);
+
+struct disambiguate_state {
+       disambiguate_hint_fn fn;
+       void *cb_data;
+       unsigned char candidate[20];
+       unsigned candidate_exists:1;
+       unsigned candidate_checked:1;
+       unsigned candidate_ok:1;
+       unsigned disambiguate_fn_used:1;
+       unsigned ambiguous:1;
+};
+
+static void update_candidates(struct disambiguate_state *ds, const unsigned char *current)
+{
+       if (!ds->candidate_exists) {
+               /* this is the first candidate */
+               hashcpy(ds->candidate, current);
+               ds->candidate_exists = 1;
+               return;
+       } else if (!hashcmp(ds->candidate, current)) {
+               /* the same as what we already have seen */
+               return;
+       }
+
+       if (!ds->fn) {
+               /* cannot disambiguate between ds->candidate and current */
+               ds->ambiguous = 1;
+               return;
+       }
+
+       if (!ds->candidate_checked) {
+               ds->candidate_ok = ds->fn(ds->candidate, ds->cb_data);
+               ds->disambiguate_fn_used = 1;
+               ds->candidate_checked = 1;
+       }
+
+       if (!ds->candidate_ok) {
+               /* discard the candidate; we know it does not satisify fn */
+               hashcpy(ds->candidate, current);
+               ds->candidate_checked = 0;
+               return;
+       }
+
+       /* if we reach this point, we know ds->candidate satisfies fn */
+       if (ds->fn(current, ds->cb_data)) {
+               /*
+                * if both current and candidate satisfy fn, we cannot
+                * disambiguate.
+                */
+               ds->candidate_ok = 0;
+               ds->ambiguous = 1;
+       }
+
+       /* otherwise, current can be discarded and candidate is still good */
+}
+
+static void find_short_object_filename(int len, const char *hex_pfx, struct disambiguate_state *ds)
 {
        struct alternate_object_database *alt;
        char hex[40];
-       int found = 0;
        static struct alternate_object_database *fakeent;
 
        if (!fakeent) {
@@ -35,32 +91,27 @@ static int find_short_object_filename(int len, const char *hex_pfx, unsigned cha
        fakeent->next = alt_odb_list;
 
        sprintf(hex, "%.2s", hex_pfx);
-       for (alt = fakeent; alt && found < 2; alt = alt->next) {
+       for (alt = fakeent; alt && !ds->ambiguous; alt = alt->next) {
                struct dirent *de;
                DIR *dir;
                sprintf(alt->name, "%.2s/", hex_pfx);
                dir = opendir(alt->base);
                if (!dir)
                        continue;
-               while ((de = readdir(dir)) != NULL) {
+
+               while (!ds->ambiguous && (de = readdir(dir)) != NULL) {
+                       unsigned char sha1[20];
+
                        if (strlen(de->d_name) != 38)
                                continue;
                        if (memcmp(de->d_name, hex_pfx + 2, len - 2))
                                continue;
-                       if (!found) {
-                               memcpy(hex + 2, de->d_name, 38);
-                               found++;
-                       }
-                       else if (memcmp(hex + 2, de->d_name, 38)) {
-                               found = 2;
-                               break;
-                       }
+                       memcpy(hex + 2, de->d_name, 38);
+                       if (!get_sha1_hex(hex, sha1))
+                               update_candidates(ds, sha1);
                }
                closedir(dir);
        }
-       if (found == 1)
-               return get_sha1_hex(hex, sha1) == 0;
-       return found;
 }
 
 static int match_sha(unsigned len, const unsigned char *a, const unsigned char *b)
@@ -78,11 +129,10 @@ static int match_sha(unsigned len, const unsigned char *a, const unsigned char *
        return 1;
 }
 
-static int unique_in_pack(int len,
+static void unique_in_pack(int len,
                          const unsigned char *bin_pfx,
-                         struct packed_git *p,
-                         const unsigned char **found_sha1,
-                         int seen_so_far)
+                          struct packed_git *p,
+                          struct disambiguate_state *ds)
 {
        uint32_t num, last, i, first = 0;
        const unsigned char *current = NULL;
@@ -113,72 +163,75 @@ static int unique_in_pack(int len,
         * with an object name that could match "bin_pfx".  See if we have
         * 0, 1 or more objects that actually match(es).
         */
-       for (i = first; i < num; i++) {
-               current = nth_packed_object_sha1(p, first);
+       for (i = first; i < num && !ds->ambiguous; i++) {
+               current = nth_packed_object_sha1(p, i);
                if (!match_sha(len, bin_pfx, current))
                        break;
-
-               /* current matches */
-               if (!seen_so_far) {
-                       *found_sha1 = current;
-                       seen_so_far++;
-               } else if (seen_so_far) {
-                       /* is it the same as the one previously found elsewhere? */
-                       if (hashcmp(*found_sha1, current))
-                               return 2; /* definitely not unique */
-               }
+               update_candidates(ds, current);
        }
-       return seen_so_far;
 }
 
-static int find_short_packed_object(int len, const unsigned char *bin_pfx, unsigned char *sha1)
+static void find_short_packed_object(int len, const unsigned char *bin_pfx,
+                                    struct disambiguate_state *ds)
 {
        struct packed_git *p;
-       const unsigned char *found_sha1 = NULL;
-       int found = 0;
 
        prepare_packed_git();
-       for (p = packed_git; p && found < 2; p = p->next)
-               found = unique_in_pack(len, bin_pfx, p, &found_sha1, found);
-
-       if (found == 1)
-               hashcpy(sha1, found_sha1);
-       return found;
+       for (p = packed_git; p && !ds->ambiguous; p = p->next)
+               unique_in_pack(len, bin_pfx, p, ds);
 }
 
 #define SHORT_NAME_NOT_FOUND (-1)
 #define SHORT_NAME_AMBIGUOUS (-2)
 
-static int find_unique_short_object(int len, char *hex_pfx,
-                                   unsigned char *bin_pfx, unsigned char *sha1)
+static int finish_object_disambiguation(struct disambiguate_state *ds,
+                                       unsigned char *sha1)
 {
-       int has_unpacked, has_packed;
-       unsigned char unpacked_sha1[20], packed_sha1[20];
+       if (ds->ambiguous)
+               return SHORT_NAME_AMBIGUOUS;
 
-       prepare_alt_odb();
-       has_unpacked = find_short_object_filename(len, hex_pfx, unpacked_sha1);
-       has_packed = find_short_packed_object(len, bin_pfx, packed_sha1);
-       if (!has_unpacked && !has_packed)
+       if (!ds->candidate_exists)
                return SHORT_NAME_NOT_FOUND;
-       if (1 < has_unpacked || 1 < has_packed)
-               return SHORT_NAME_AMBIGUOUS;
-       if (has_unpacked != has_packed) {
-               hashcpy(sha1, (has_packed ? packed_sha1 : unpacked_sha1));
-               return 0;
-       }
-       /* Both have unique ones -- do they match? */
-       if (hashcmp(packed_sha1, unpacked_sha1))
+
+       if (!ds->candidate_checked)
+               /*
+                * If this is the only candidate, there is no point
+                * calling the disambiguation hint callback.
+                *
+                * On the other hand, if the current candidate
+                * replaced an earlier candidate that did _not_ pass
+                * the disambiguation hint callback, then we do have
+                * more than one objects that match the short name
+                * given, so we should make sure this one matches;
+                * otherwise, if we discovered this one and the one
+                * that we previously discarded in the reverse order,
+                * we would end up showing different results in the
+                * same repository!
+                */
+               ds->candidate_ok = (!ds->disambiguate_fn_used ||
+                                   ds->fn(ds->candidate, ds->cb_data));
+
+       if (!ds->candidate_ok)
                return SHORT_NAME_AMBIGUOUS;
-       hashcpy(sha1, packed_sha1);
+
+       hashcpy(sha1, ds->candidate);
        return 0;
 }
 
+static int disambiguate_commit_only(const unsigned char *sha1, void *cb_data_unused)
+{
+       int kind = sha1_object_info(sha1, NULL);
+       return kind == OBJ_COMMIT;
+}
+
 static int get_short_sha1(const char *name, int len, unsigned char *sha1,
-                         int quietly)
+                         unsigned flags)
 {
        int i, status;
        char hex_pfx[40];
        unsigned char bin_pfx[20];
+       struct disambiguate_state ds;
+       int quietly = !!(flags & GET_SHA1_QUIETLY);
 
        if (len < MINIMUM_ABBREV || len > 40)
                return -1;
@@ -203,7 +256,16 @@ static int get_short_sha1(const char *name, int len, unsigned char *sha1,
                bin_pfx[i >> 1] |= val;
        }
 
-       status = find_unique_short_object(i, hex_pfx, bin_pfx, sha1);
+       prepare_alt_odb();
+
+       memset(&ds, 0, sizeof(ds));
+       if (flags & GET_SHA1_COMMIT)
+               ds.fn = disambiguate_commit_only;
+
+       find_short_object_filename(len, hex_pfx, &ds);
+       find_short_packed_object(len, bin_pfx, &ds);
+       status = finish_object_disambiguation(&ds, sha1);
+
        if (!quietly && (status == SHORT_NAME_AMBIGUOUS))
                return error("short SHA1 %.*s is ambiguous.", len, hex_pfx);
        return status;
@@ -220,7 +282,7 @@ const char *find_unique_abbrev(const unsigned char *sha1, int len)
                return hex;
        while (len < 40) {
                unsigned char sha1_ret[20];
-               status = get_short_sha1(hex, len, sha1_ret, 1);
+               status = get_short_sha1(hex, len, sha1_ret, GET_SHA1_QUIETLY);
                if (exists
                    ? !status
                    : status == SHORT_NAME_NOT_FOUND) {
@@ -541,6 +603,7 @@ static int peel_onion(const char *name, int len, unsigned char *sha1)
 static int get_describe_name(const char *name, int len, unsigned char *sha1)
 {
        const char *cp;
+       unsigned flags = GET_SHA1_QUIETLY | GET_SHA1_COMMIT;
 
        for (cp = name + len - 1; name + 2 <= cp; cp--) {
                char ch = *cp;
@@ -551,7 +614,7 @@ static int get_describe_name(const char *name, int len, unsigned char *sha1)
                        if (ch == 'g' && cp[-1] == '-') {
                                cp++;
                                len -= cp - name;
-                               return get_short_sha1(cp, len, sha1, 1);
+                               return get_short_sha1(cp, len, sha1, flags);
                        }
                }
        }