Skip to content

Commit 8cfd4ac

Browse files
peffgitster
authored andcommitted
describe: handle blob traversal with no commits
When describing a blob, we traverse from HEAD, remembering each commit we saw, and then checking each blob to report the containing commit. But if we haven't seen any commits at all, we'll segfault (we store the "current" commit as an oid initialized to the null oid, causing lookup_commit_reference() to return NULL). This shouldn't be able to happen normally. We always start our traversal at HEAD, which must be a commit (a property which is enforced by the refs code). But you can trigger the segfault like this: blob=$(echo foo | git hash-object -w --stdin) echo $blob >.git/HEAD git describe $blob We can instead catch this case and return an empty result, which hits the usual "we didn't find $blob while traversing HEAD" error. This is a minor lie in that we did "find" the blob. And this even hints at a bigger problem in this code: what if the traversal pointed to the blob as _not_ part of a commit at all, but we had previously filled in the recorded "current commit"? One could imagine this happening due to a tag pointing directly to the blob in question. But that can't happen, because we only traverse from HEAD, never from any other refs. And the intent of the blob-describing code is to find blobs within commits. So I think this matches the original intent as closely as we can (and again, this segfault cannot be triggered without corrupting your repository!). The test here does not use the formula above, which works only for the files backend (and not reftables). Instead we use another loophole to create the bogus state using only Git commands. See the comment in the test for details. Signed-off-by: Jeff King <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent c647871 commit 8cfd4ac

File tree

2 files changed

+20
-2
lines changed

2 files changed

+20
-2
lines changed

builtin/describe.c

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -507,8 +507,10 @@ static void process_object(struct object *obj, const char *path, void *data)
507507

508508
if (oideq(pcd->looking_for, &obj->oid) && !pcd->dst->len) {
509509
reset_revision_walk();
510-
describe_commit(&pcd->current_commit, pcd->dst);
511-
strbuf_addf(pcd->dst, ":%s", path);
510+
if (!is_null_oid(&pcd->current_commit)) {
511+
describe_commit(&pcd->current_commit, pcd->dst);
512+
strbuf_addf(pcd->dst, ":%s", path);
513+
}
512514
free_commit_list(pcd->revs->commits);
513515
pcd->revs->commits = NULL;
514516
}

t/t6120-describe.sh

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -423,6 +423,22 @@ test_expect_success 'describe blob on an unborn branch' '
423423
test_grep "cannot search .* on an unborn branch" actual
424424
'
425425

426+
# This test creates a repository state that we generally try to disallow: HEAD
427+
# is pointing to an object that is not a commit. The ref update code forbids
428+
# non-commit writes directly to HEAD or to any branch in refs/heads/. But we
429+
# can use the loophole of pointing HEAD to another non-branch ref (something we
430+
# should forbid, but don't for historical reasons).
431+
#
432+
# Do not take this test as an endorsement of the loophole! If we ever tighten
433+
# it, it is reasonable to just drop this test entirely.
434+
test_expect_success 'describe blob on a non-commit HEAD' '
435+
oldbranch=$(git symbolic-ref HEAD) &&
436+
test_when_finished "git symbolic-ref HEAD $oldbranch" &&
437+
git symbolic-ref HEAD refs/tags/test-blob &&
438+
test_must_fail git describe test-blob 2>actual &&
439+
test_grep "blob .* not reachable from HEAD" actual
440+
'
441+
426442
test_expect_success ULIMIT_STACK_SIZE 'name-rev works in a deep repo' '
427443
i=1 &&
428444
while test $i -lt 8000

0 commit comments

Comments
 (0)