Re: [BUG] `git describe` doesn't traverse the graph in topological order
- From
Jeff King <peff@peff.net>
- Date
- Nov 20, 2025, 08:05 UTC
- Message-ID
- <20251120080525.GB1283645@coredump.intra.peff.net>
- In-Reply-To
- <aR6BlHflRVLN8_XO@rotor>
On Wed, Nov 19, 2025 at 09:48:52PM -0500, 'Ben Boeckel' wrote:
Show 6 quoted lines
> So I finally found some time to go back to this. The actual fix is
> actually rather easy (patch attached). However, as guessed at previously
> in the thread, the performance is in the tank without an up-to-date
> commit graph ("instant" with it versus "minutes" without). On the other
> hand, it is *accurate*. It does fix one expect-fail test case already in
> the test suite (also included in the patch).Minutes? Yikes. Let's look...
Show 15 quoted lines
> +/*
> + * Topological comparison: always return parents before children.
> + * This is reverse topological order: children before parents.
> + */
> +static int compare_commits_topo(const void *a_, const void *b_, void *_unused_ UNUSED)
> +{
> + struct commit *a = (struct commit *)a_;
> + struct commit *b = (struct commit *)b_;
> + if (repo_is_descendant_of(the_repository, a, &(struct commit_list){ b, NULL }))
> + return -1; // a is descendant, so comes before b
> + if (repo_is_descendant_of(the_repository, b, &(struct commit_list){ a, NULL }))
> + return 1; // b is descendant, so comes before a
> + // fallback: order by hash for determinism
> + return oidcmp(&a->object.oid, &b->object.oid);
> +}Ah. So you are doing two full traversals for each comparison. That is going to be expensive. You would do much better to walk all of history one time, marking the generation number (distance to root) of each commit, and then comparing generations here (if A has a lower generation than B, then you know that B cannot be an ancestor of A). Or if we have commit graphs, just use the generation numbers they already contain. ;)
We do all of this already for the "--topo-order" option of the revision traversal machinery. If we have commit graphs, it can output in topographical order in a streaming way (see init_topo_walk() in revision.c). If not, then we collect all of the commits up front and call sort_in_topological_order().
Sadly, git-describe does not seem to use the traversal machinery, so it is not as easy as just setting revs.topo_order. Either we have to adapt to using the regular traversal code, or those same concepts need to be applied to its custom traversal.
-Peff