diff --git a/commit.c b/commit.c index e3e7352e69..fd8723502e 100644 --- a/commit.c +++ b/commit.c @@ -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) diff --git a/t/t5604-clone-reference.sh b/t/t5604-clone-reference.sh index 470bfb610c..c232ab8c15 100755 --- a/t/t5604-clone-reference.sh +++ b/t/t5604-clone-reference.sh @@ -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