]> git.ipfire.org Git - thirdparty/git.git/commitdiff
describe: don't abort too early when searching tags
authorBenno Evers <benno@bmevers.de>
Wed, 26 Feb 2020 17:48:53 +0000 (18:48 +0100)
committerJunio C Hamano <gitster@pobox.com>
Wed, 26 Feb 2020 20:14:12 +0000 (12:14 -0800)
When searching the commit graph for tag candidates, `git-describe`
will stop as soon as there is only one active branch left and
it already found an annotated tag as a candidate.

This works well as long as all branches eventually connect back
to a common root, but if the tags are found across branches
with no common ancestor

                  B
                  o----.
                        \
          o-----o---o----x
          A

it can happen that the search on one branch terminates prematurely
because a tag was found on another, independent branch. This scenario
isn't quite as obscure as it sounds, since cloning with a limited
depth often introduces many independent "dead ends" into the commit
graph.

The help text of `git-describe` states pretty clearly that when
describing a commit D, the number appended to the emitted tag X should
correspond to the number of commits found by `git log X..D`.

Thus, this commit modifies the stopping condition to only abort
the search when only one branch is left to search *and* all current
best candidates are descendants from that branch.

For repositories with a single root, this condition is always
true: When the search is reduced to a single active branch, the
current commit must be an ancestor of *all* tag candidates. This
means that in the common case, this change will have no negative
performance impact since the same number of commits as before will
be traversed.

Signed-off-by: Benno Evers <benno@bmevers.de>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
builtin/describe.c
t/t6120-describe.sh

index b6df81d8d0e80e9665ae8f8afcf3cb03d1ae688c..420f4c6401b6204bb422325ed0c08d713b2dfcda 100644 (file)
@@ -376,11 +376,25 @@ static void describe_commit(struct object_id *oid, struct strbuf *dst)
                        if (!(c->object.flags & t->flag_within))
                                t->depth++;
                }
+               /* Stop if last remaining path already covered by best candidate(s) */
                if (annotated_cnt && !list) {
-                       if (debug)
-                               fprintf(stderr, _("finished search at %s\n"),
-                                       oid_to_hex(&c->object.oid));
-                       break;
+                       int best_depth = INT_MAX;
+                       unsigned best_within = 0;
+                       for (cur_match = 0; cur_match < match_cnt; cur_match++) {
+                               struct possible_tag *t = &all_matches[cur_match];
+                               if (t->depth < best_depth) {
+                                       best_depth = t->depth;
+                                       best_within = t->flag_within;
+                               } else if (t->depth == best_depth) {
+                                       best_within |= t->flag_within;
+                               }
+                       }
+                       if ((c->object.flags & best_within) == best_within) {
+                               if (debug)
+                                       fprintf(stderr, _("finished search at %s\n"),
+                                               oid_to_hex(&c->object.oid));
+                               break;
+                       }
                }
                while (parents) {
                        struct commit *p = parents->item;
index 09c50f3f0492acd4a44dcee8a67d4b3ee67fe581..34502e3a505443d02a57ccc241e178feeffd1ce5 100755 (executable)
@@ -479,4 +479,55 @@ test_expect_success 'name-rev covers all conditions while looking at parents' '
        )
 '
 
+#               B
+#               o
+#                \
+#  o-----o---o----x
+#        A
+#
+test_expect_success 'describe commits with disjoint bases' '
+       git init disjoint1 &&
+       (
+               cd disjoint1 &&
+
+               echo o >> file && git add file && git commit -m o &&
+               echo A >> file && git add file && git commit -m A &&
+               git tag A -a -m A &&
+               echo o >> file && git add file && git commit -m o &&
+
+               git checkout --orphan branch && rm file &&
+               echo B > file2 && git add file2 && git commit -m B &&
+               git tag B -a -m B &&
+               git merge --no-ff --allow-unrelated-histories master -m x &&
+
+               check_describe "A-3-*" HEAD
+       )
+'
+
+#           B
+#   o---o---o------------.
+#                         \
+#                  o---o---x
+#                  A
+#
+test_expect_success 'describe commits with disjoint bases 2' '
+       git init disjoint2 &&
+       (
+               cd disjoint2 &&
+
+               echo A >> file && git add file && GIT_COMMITTER_DATE="2020-01-01 18:00" git commit -m A &&
+               git tag A -a -m A &&
+               echo o >> file && git add file && GIT_COMMITTER_DATE="2020-01-01 18:01" git commit -m o &&
+
+               git checkout --orphan branch &&
+               echo o >> file2 && git add file2 && GIT_COMMITTER_DATE="2020-01-01 15:00" git commit -m o &&
+               echo o >> file2 && git add file2 && GIT_COMMITTER_DATE="2020-01-01 15:01" git commit -m o &&
+               echo B >> file2 && git add file2 && GIT_COMMITTER_DATE="2020-01-01 15:02" git commit -m B &&
+               git tag B -a -m B &&
+               git merge --no-ff --allow-unrelated-histories master -m x &&
+
+               check_describe "B-3-*" HEAD
+       )
+'
+
 test_done