From 0570be79ead35e47e29ee2587e2c8ea47c091d49 Mon Sep 17 00:00:00 2001 From: Jerry Zhang Date: Mon, 24 Oct 2022 20:07:39 +0000 Subject: 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 Signed-off-by: Junio C Hamano --- diff.c | 58 +++++++++++++++++++++++++++++----------------------------- 1 file changed, 29 insertions(+), 29 deletions(-) (limited to 'diff.c') diff --git a/diff.c b/diff.c index e71cf75886..2aee15f2f1 100644 --- a/diff.c +++ b/diff.c @@ -6221,46 +6221,46 @@ 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); - 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"); - } else { - patch_id_add_string(&ctx, "---a/"); - the_hash_algo->update_fn(&ctx, p->one->path, len1); - patch_id_add_string(&ctx, "+++b/"); - 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) || + 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); - continue; - } - - xpp.flags = 0; - xecfg.ctxlen = 3; - xecfg.flags = XDL_EMIT_NO_HUNK_HDR; - if (xdi_diff_outf(&mf1, &mf2, NULL, - patch_id_consume, &data, &xpp, &xecfg)) - return error("unable to generate patch-id diff for %s", - p->one->path); + } 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, "---a/"); + the_hash_algo->update_fn(&ctx, p->one->path, len1); + patch_id_add_string(&ctx, "+++/dev/null"); + } else { + patch_id_add_string(&ctx, "---a/"); + the_hash_algo->update_fn(&ctx, p->one->path, len1); + patch_id_add_string(&ctx, "+++b/"); + the_hash_algo->update_fn(&ctx, p->two->path, len2); + } + 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"); + xpp.flags = 0; + xecfg.ctxlen = 3; + xecfg.flags = XDL_EMIT_NO_HUNK_HDR; + if (xdi_diff_outf(&mf1, &mf2, NULL, + 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); } -- cgit v1.2.3 From 51276c1832d64d3b8f4dfc06c3ef21bf74f1916e Mon Sep 17 00:00:00 2001 From: Jerry Zhang Date: Mon, 24 Oct 2022 20:07:40 +0000 Subject: patch-id: use stable patch-id for rebases Git doesn't persist patch-ids during the rebase process, so there is no need to specifically invoke the unstable variant. Use the stable logic for all internal patch-id calculations to minimize the number of code paths and improve test coverage. Signed-off-by: Jerry Zhang Signed-off-by: Junio C Hamano --- builtin/log.c | 2 +- diff.c | 12 ++++-------- diff.h | 2 +- patch-ids.c | 10 +++++----- patch-ids.h | 2 +- 5 files changed, 12 insertions(+), 16 deletions(-) (limited to 'diff.c') diff --git a/builtin/log.c b/builtin/log.c index 88a5e98875..306103f302 100644 --- a/builtin/log.c +++ b/builtin/log.c @@ -1694,7 +1694,7 @@ static void prepare_bases(struct base_tree_info *bases, struct object_id *patch_id; if (*commit_base_at(&commit_base, commit)) continue; - if (commit_patch_id(commit, &diffopt, &oid, 0, 1)) + if (commit_patch_id(commit, &diffopt, &oid, 0)) die(_("cannot get patch id")); ALLOC_GROW(bases->patch_id, bases->nr_patch_id + 1, bases->alloc_patch_id); patch_id = bases->patch_id + bases->nr_patch_id; diff --git a/diff.c b/diff.c index 2aee15f2f1..c86688794f 100644 --- a/diff.c +++ b/diff.c @@ -6174,7 +6174,7 @@ static void patch_id_add_mode(git_hash_ctx *ctx, unsigned mode) } /* returns 0 upon success, and writes result into oid */ -static int diff_get_patch_id(struct diff_options *options, struct object_id *oid, int diff_header_only, int stable) +static int diff_get_patch_id(struct diff_options *options, struct object_id *oid, int diff_header_only) { struct diff_queue_struct *q = &diff_queued_diff; int i; @@ -6261,21 +6261,17 @@ static int diff_get_patch_id(struct diff_options *options, struct object_id *oid return error("unable to generate patch-id diff for %s", p->one->path); } - if (stable) - flush_one_hunk(oid, &ctx); + flush_one_hunk(oid, &ctx); } - if (!stable) - the_hash_algo->final_oid_fn(oid, &ctx); - return 0; } -int diff_flush_patch_id(struct diff_options *options, struct object_id *oid, int diff_header_only, int stable) +int diff_flush_patch_id(struct diff_options *options, struct object_id *oid, int diff_header_only) { struct diff_queue_struct *q = &diff_queued_diff; int i; - int result = diff_get_patch_id(options, oid, diff_header_only, stable); + int result = diff_get_patch_id(options, oid, diff_header_only); for (i = 0; i < q->nr; i++) diff_free_filepair(q->queue[i]); diff --git a/diff.h b/diff.h index 8ae18e5ab1..fd33caeb25 100644 --- a/diff.h +++ b/diff.h @@ -634,7 +634,7 @@ int run_diff_files(struct rev_info *revs, unsigned int option); int run_diff_index(struct rev_info *revs, unsigned int option); int do_diff_cache(const struct object_id *, struct diff_options *); -int diff_flush_patch_id(struct diff_options *, struct object_id *, int, int); +int diff_flush_patch_id(struct diff_options *, struct object_id *, int); void flush_one_hunk(struct object_id *result, git_hash_ctx *ctx); int diff_result_code(struct diff_options *, int); diff --git a/patch-ids.c b/patch-ids.c index 8bf425555d..fefddc487e 100644 --- a/patch-ids.c +++ b/patch-ids.c @@ -11,7 +11,7 @@ static int patch_id_defined(struct commit *commit) } int commit_patch_id(struct commit *commit, struct diff_options *options, - struct object_id *oid, int diff_header_only, int stable) + struct object_id *oid, int diff_header_only) { if (!patch_id_defined(commit)) return -1; @@ -22,7 +22,7 @@ int commit_patch_id(struct commit *commit, struct diff_options *options, else diff_root_tree_oid(&commit->object.oid, "", options); diffcore_std(options); - return diff_flush_patch_id(options, oid, diff_header_only, stable); + return diff_flush_patch_id(options, oid, diff_header_only); } /* @@ -48,11 +48,11 @@ static int patch_id_neq(const void *cmpfn_data, b = container_of(entry_or_key, struct patch_id, ent); if (is_null_oid(&a->patch_id) && - commit_patch_id(a->commit, opt, &a->patch_id, 0, 0)) + commit_patch_id(a->commit, opt, &a->patch_id, 0)) return error("Could not get patch ID for %s", oid_to_hex(&a->commit->object.oid)); if (is_null_oid(&b->patch_id) && - commit_patch_id(b->commit, opt, &b->patch_id, 0, 0)) + commit_patch_id(b->commit, opt, &b->patch_id, 0)) return error("Could not get patch ID for %s", oid_to_hex(&b->commit->object.oid)); return !oideq(&a->patch_id, &b->patch_id); @@ -82,7 +82,7 @@ static int init_patch_id_entry(struct patch_id *patch, struct object_id header_only_patch_id; patch->commit = commit; - if (commit_patch_id(commit, &ids->diffopts, &header_only_patch_id, 1, 0)) + if (commit_patch_id(commit, &ids->diffopts, &header_only_patch_id, 1)) return -1; hashmap_entry_init(&patch->ent, oidhash(&header_only_patch_id)); diff --git a/patch-ids.h b/patch-ids.h index ab6c6a6804..490d739371 100644 --- a/patch-ids.h +++ b/patch-ids.h @@ -20,7 +20,7 @@ struct patch_ids { }; int commit_patch_id(struct commit *commit, struct diff_options *options, - struct object_id *oid, int, int); + struct object_id *oid, int); int init_patch_ids(struct repository *, struct patch_ids *); int free_patch_ids(struct patch_ids *); -- cgit v1.2.3 From 93105aba6c4c8608b10c8ebe14b2313b3d347124 Mon Sep 17 00:00:00 2001 From: Jerry Zhang Date: Mon, 24 Oct 2022 20:07:42 +0000 Subject: patch-id: fix patch-id for mode changes Currently patch-id as used in rebase and cherry-pick does not account for file modes if the file is modified. One consequence of this is that if you have a local patch that changes modes, but upstream has applied an outdated version of the patch that doesn't include that mode change, "git rebase" will drop your local version of the patch along with your mode changes. It also means that internal patch-id doesn't produce the same output as the builtin, which does account for mode changes due to them being part of diff output. Fix by adding mode to the patch-id if it has changed, in the same format that would be produced by diff, so that it is compatible with builtin patch-id. Signed-off-by: Jerry Zhang Signed-off-by: Junio C Hamano --- diff.c | 5 +++++ t/t3419-rebase-patch-id.sh | 31 ++++++++++++++++++++++++++++++- 2 files changed, 35 insertions(+), 1 deletion(-) (limited to 'diff.c') diff --git a/diff.c b/diff.c index c86688794f..20b121637d 100644 --- a/diff.c +++ b/diff.c @@ -6224,6 +6224,11 @@ static int diff_get_patch_id(struct diff_options *options, struct object_id *oid } else if (p->two->mode == 0) { patch_id_add_string(&ctx, "deletedfilemode"); patch_id_add_mode(&ctx, p->one->mode); + } else if (p->one->mode != p->two->mode) { + patch_id_add_string(&ctx, "oldmode"); + patch_id_add_mode(&ctx, p->one->mode); + patch_id_add_string(&ctx, "newmode"); + patch_id_add_mode(&ctx, p->two->mode); } if (diff_header_only) { diff --git a/t/t3419-rebase-patch-id.sh b/t/t3419-rebase-patch-id.sh index d24e55aac8..7181f176b8 100755 --- a/t/t3419-rebase-patch-id.sh +++ b/t/t3419-rebase-patch-id.sh @@ -48,7 +48,17 @@ test_expect_success 'setup: 500 lines' ' git branch -f squashed main && git checkout -q -f squashed && git reset -q --soft HEAD~2 && - git commit -q -m squashed + git commit -q -m squashed && + + git branch -f mode main && + git checkout -q -f mode && + test_chmod +x file && + git commit -q -a --amend && + + git branch -f modeother other && + git checkout -q -f modeother && + test_chmod +x file && + git commit -q -a --amend ' test_expect_success 'detect upstream patch' ' @@ -71,6 +81,13 @@ test_expect_success 'detect upstream patch binary' ' test_when_finished "rm .gitattributes" ' +test_expect_success 'detect upstream patch modechange' ' + git checkout -q modeother^{} && + git rebase mode && + git rev-list mode...HEAD~ >revs && + test_must_be_empty revs +' + test_expect_success 'do not drop patch' ' git checkout -q other^{} && test_must_fail git rebase squashed && @@ -85,4 +102,16 @@ test_expect_success 'do not drop patch binary' ' test_when_finished "rm .gitattributes" ' +test_expect_success 'do not drop patch modechange' ' + git checkout -q modeother^{} && + git rebase other && + cat >expected <<-\EOF && + diff --git a/file b/file + old mode 100644 + new mode 100755 + EOF + git diff HEAD~ >modediff && + test_cmp expected modediff +' + test_done -- cgit v1.2.3