patch-id: fix stable patch id for binary / header-only

Patch-ids for binary patches are found by hashing the object
ids of the before and after objects in succession. However in
the --stable case, there is a bug where hunks are not flushed
for binary and header-only patch ids, which would always result
in a patch-id of 0000. The --unstable case is currently correct.

Reorder the logic to branch into 3 cases for populating the
patch body: header-only which populates nothing, binary which
populates the object ids, and normal which populates the text
diff. All branches will end up flushing the hunk.

Don't populate the ---a/ and +++b/ lines for binary diffs, to correspond
to those lines not being present in the "git diff" text output.
This is necessary because we advertise that the patch-id calculated
internally and used in format-patch is the same that what the
builtin "git patch-id" would produce when piped from a diff.

Update the test to run on both binary and normal files.

Signed-off-by: Jerry Zhang <jerry@skydio.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
This commit is contained in:
Jerry Zhang 2022-10-24 20:07:39 +00:00 committed by Junio C Hamano
parent a0feb8611d
commit 0570be79ea
2 changed files with 53 additions and 39 deletions

32
diff.c
View File

@ -6221,12 +6221,25 @@ static int diff_get_patch_id(struct diff_options *options, struct object_id *oid
if (p->one->mode == 0) {
patch_id_add_string(&ctx, "newfilemode");
patch_id_add_mode(&ctx, p->two->mode);
} else if (p->two->mode == 0) {
patch_id_add_string(&ctx, "deletedfilemode");
patch_id_add_mode(&ctx, p->one->mode);
}
if (diff_header_only) {
/* don't do anything since we're only populating header info */
} else if (diff_filespec_is_binary(options->repo, p->one) ||
diff_filespec_is_binary(options->repo, p->two)) {
the_hash_algo->update_fn(&ctx, oid_to_hex(&p->one->oid),
the_hash_algo->hexsz);
the_hash_algo->update_fn(&ctx, oid_to_hex(&p->two->oid),
the_hash_algo->hexsz);
} else {
if (p->one->mode == 0) {
patch_id_add_string(&ctx, "---/dev/null");
patch_id_add_string(&ctx, "+++b/");
the_hash_algo->update_fn(&ctx, p->two->path, len2);
} else if (p->two->mode == 0) {
patch_id_add_string(&ctx, "deletedfilemode");
patch_id_add_mode(&ctx, p->one->mode);
patch_id_add_string(&ctx, "---a/");
the_hash_algo->update_fn(&ctx, p->one->path, len1);
patch_id_add_string(&ctx, "+++/dev/null");
@ -6237,22 +6250,9 @@ static int diff_get_patch_id(struct diff_options *options, struct object_id *oid
the_hash_algo->update_fn(&ctx, p->two->path, len2);
}
if (diff_header_only)
continue;
if (fill_mmfile(options->repo, &mf1, p->one) < 0 ||
fill_mmfile(options->repo, &mf2, p->two) < 0)
return error("unable to read files to diff");
if (diff_filespec_is_binary(options->repo, p->one) ||
diff_filespec_is_binary(options->repo, p->two)) {
the_hash_algo->update_fn(&ctx, oid_to_hex(&p->one->oid),
the_hash_algo->hexsz);
the_hash_algo->update_fn(&ctx, oid_to_hex(&p->two->oid),
the_hash_algo->hexsz);
continue;
}
xpp.flags = 0;
xecfg.ctxlen = 3;
xecfg.flags = XDL_EMIT_NO_HUNK_HDR;
@ -6260,7 +6260,7 @@ static int diff_get_patch_id(struct diff_options *options, struct object_id *oid
patch_id_consume, &data, &xpp, &xecfg))
return error("unable to generate patch-id diff for %s",
p->one->path);
}
if (stable)
flush_one_hunk(oid, &ctx);
}

View File

@ -43,15 +43,16 @@ test_expect_success 'setup: 500 lines' '
git add newfile &&
git commit -q -m "add small file" &&
git cherry-pick main >/dev/null 2>&1
'
git cherry-pick main >/dev/null 2>&1 &&
test_expect_success 'setup attributes' '
echo "file binary" >.gitattributes
git branch -f squashed main &&
git checkout -q -f squashed &&
git reset -q --soft HEAD~2 &&
git commit -q -m squashed
'
test_expect_success 'detect upstream patch' '
git checkout -q main &&
git checkout -q main^{} &&
scramble file &&
git add file &&
git commit -q -m "change big file again" &&
@ -61,14 +62,27 @@ test_expect_success 'detect upstream patch' '
test_must_be_empty revs
'
test_expect_success 'detect upstream patch binary' '
echo "file binary" >.gitattributes &&
git checkout -q other^{} &&
git rebase main &&
git rev-list main...HEAD~ >revs &&
test_must_be_empty revs &&
test_when_finished "rm .gitattributes"
'
test_expect_success 'do not drop patch' '
git branch -f squashed main &&
git checkout -q -f squashed &&
git reset -q --soft HEAD~2 &&
git commit -q -m squashed &&
git checkout -q other^{} &&
test_must_fail git rebase squashed &&
git rebase --quit
test_when_finished "git rebase --abort"
'
test_expect_success 'do not drop patch binary' '
echo "file binary" >.gitattributes &&
git checkout -q other^{} &&
test_must_fail git rebase squashed &&
test_when_finished "git rebase --abort" &&
test_when_finished "rm .gitattributes"
'
test_done