Skip to content

Commit 3506dc9

Browse files
peffgitster
authored andcommitted
has_uncommitted_changes(): fall back to empty tree
If has_uncommitted_changes() can't resolve HEAD (e.g., because it's unborn or corrupt), then we end up calling run_diff_index() with an empty revs.pending array. This causes a segfault, as run_diff_index() blindly looks at the first pending item. Fixing this raises a question of fault: should run_diff_index() handle this case, or is the caller wrong to pass an empty pending list? Looking at the other callers of run_diff_index(), they handle this in one of three ways: - they resolve the object themselves, and avoid doing the diff if it's not valid - they resolve the object themselves, and fall back to the empty tree - they use setup_revisions(), which will die() if the object isn't valid Since this is the only broken caller, that argues that the fix should go there. Falling back to the empty tree makes sense here, as we'd claim uncommitted changes if and only if the index is non-empty. This may be a little funny in the case of corruption (the corrupt HEAD probably _isn't_ empty), but: - we don't actually know the reason here that HEAD didn't resolve (the much more likely case is that we have an unborn HEAD, in which case the empty tree comparison is the right thing) - this matches how other code, like "git diff", behaves While we're thinking about it, let's add an assertion to run_diff_index(). It should always be passed a single object, and as this bug shows, it's easy to get it wrong (and an assertion is easier to hunt down than a segfault, or a quietly ignored extra tree). Reported-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com> Signed-off-by: Jeff King <peff@peff.net> Signed-off-by: Junio C Hamano <gitster@pobox.com>
1 parent a42a58d commit 3506dc9

File tree

3 files changed

+25
-0
lines changed

3 files changed

+25
-0
lines changed

diff-lib.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -513,6 +513,9 @@ int run_diff_index(struct rev_info *revs, int cached)
513513
{
514514
struct object_array_entry *ent;
515515

516+
if (revs->pending.nr != 1)
517+
BUG("run_diff_index must be passed exactly one tree");
518+
516519
ent = revs->pending.objects;
517520
if (diff_cache(revs, &ent->item->oid, ent->name, cached))
518521
exit(128);

t/t5520-pull.sh

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -618,6 +618,18 @@ test_expect_success 'pull --rebase fails on unborn branch with staged changes' '
618618
)
619619
'
620620

621+
test_expect_success 'pull --rebase fails on corrupt HEAD' '
622+
test_when_finished "rm -rf corrupt" &&
623+
git init corrupt &&
624+
(
625+
cd corrupt &&
626+
test_commit one &&
627+
obj=$(git rev-parse --verify HEAD | sed "s#^..#&/#") &&
628+
rm -f .git/objects/$obj &&
629+
test_must_fail git pull --rebase
630+
)
631+
'
632+
621633
test_expect_success 'setup for detecting upstreamed changes' '
622634
mkdir src &&
623635
(cd src &&

wt-status.c

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2315,7 +2315,17 @@ int has_uncommitted_changes(int ignore_submodules)
23152315
if (ignore_submodules)
23162316
rev_info.diffopt.flags.ignore_submodules = 1;
23172317
rev_info.diffopt.flags.quick = 1;
2318+
23182319
add_head_to_pending(&rev_info);
2320+
if (!rev_info.pending.nr) {
2321+
/*
2322+
* We have no head (or it's corrupt); use the empty tree,
2323+
* which will complain if the index is non-empty.
2324+
*/
2325+
struct tree *tree = lookup_tree(the_hash_algo->empty_tree);
2326+
add_pending_object(&rev_info, &tree->object, "");
2327+
}
2328+
23192329
diff_setup_done(&rev_info.diffopt);
23202330
result = run_diff_index(&rev_info, 1);
23212331
return diff_result_code(&rev_info.diffopt, result);

0 commit comments

Comments
 (0)