Merge branch 'jk/commit-graph-lazy-load-fallback'

The logic to lazy-load trees from the commit-graph has been made
more robust by falling back to reading the commit object when
the commit-graph is no longer available.

* jk/commit-graph-lazy-load-fallback:
  commit: fall back to full read when maybe_tree is NULL
main
Junio C Hamano 2026-05-31 10:00:38 +09:00
commit f6c8fe189b
2 changed files with 55 additions and 1 deletions

View File

@ -434,6 +434,27 @@ static inline void set_commit_tree(struct commit *c, struct tree *t)
c->maybe_tree = t;
}

static void load_tree_from_commit_contents(struct repository *r, struct commit *commit)
{
enum object_type type;
unsigned long size;
char *buf;
const char *p;
struct object_id tree_oid;

buf = odb_read_object(r->objects, &commit->object.oid, &type, &size);
if (!buf)
return;

if (type == OBJ_COMMIT &&
skip_prefix(buf, "tree ", &p) &&
!parse_oid_hex_algop(p, &tree_oid, &p, r->hash_algo) &&
*p == '\n')
set_commit_tree(commit, lookup_tree(r, &tree_oid));

free(buf);
}

struct tree *repo_get_commit_tree(struct repository *r,
const struct commit *commit)
{
@ -443,7 +464,17 @@ struct tree *repo_get_commit_tree(struct repository *r,
if (commit_graph_position(commit) != COMMIT_NOT_FROM_GRAPH)
return get_commit_tree_in_graph(r, commit);

return NULL;
/*
* This is either a corrupt commit, or one which we partially loaded
* from a graph file but then subsequently threw away the graph data.
*
* Optimistically assume it's the latter and try to reload from
* scratch. This gives a performance penalty if it really is a corrupt
* commit, but presumably that happens rarely (and only once per
* process).
*/
load_tree_from_commit_contents(r, (struct commit *)commit);
return commit->maybe_tree;
}

struct object_id *get_commit_tree_oid(const struct commit *commit)

View File

@ -360,4 +360,27 @@ test_expect_success SYMLINKS 'clone repo with symlinked objects directory' '
grep "is a symlink, refusing to clone with --local" err
'

test_expect_success 'dissociate from repo with commit graph' '
git init orig &&
# We are trying to make sure the dissociated repo can
# find the tree of the tip commit, so the test could still
# serve its purpose with an empty tree. But having actual
# content future-proofs us against any kind of internal
# empty-tree optimizations.
echo content >orig/file &&
git -C orig add . &&
git -C orig commit -m foo &&

# We will use graph.git as our "local" source to dissociate
# from.
git clone --bare orig graph.git &&
git -C graph.git commit-graph write --reachable &&

# And then finally clone orig, using graph.git to get our objects. This
# must be non-bare so that we perform the checkout step, which will
# need to access the tree of HEAD, which we will have originally loaded
# via the commit graph.
git clone --no-local --reference graph.git --dissociate orig clone
'

test_done