diff --git a/Documentation/RelNotes/2.53.0.adoc b/Documentation/RelNotes/2.53.0.adoc index 9e8384a4c101ac..32b6966c9e4dc4 100644 --- a/Documentation/RelNotes/2.53.0.adoc +++ b/Documentation/RelNotes/2.53.0.adoc @@ -228,6 +228,16 @@ Fixes since v2.52 handling stateful ISO/IEC 2022 encoded strings. (merge cee341e9dd rs/macos-iconv-workaround later to maint). + * Running "git diff" with "--name-only" and other options that allows + us not to look at the blob contents, while objects that are lazily + fetched from a promisor remote, caused use-after-free, which has + been corrected. + + * The ort merge machinery hit an assertion failure in a history with + criss-cross merges renamed a directory and a non-directory, which + has been corrected. + (merge 979ee83e8a en/ort-recursive-d-f-conflict-fix later to maint). + * Other code cleanup, docfix, build fix, etc. (merge 46207a54cc qj/doc-http-bad-want-response later to maint). (merge df90eccd93 kh/doc-commit-extra-references later to maint). @@ -252,3 +262,4 @@ Fixes since v2.52 (merge 93f894c001 bc/checkout-error-message-fix later to maint). (merge abf05d856f rs/show-branch-prio-queue later to maint). (merge 06188ea5f3 rs/parse-config-expiry-simplify later to maint). + (merge 861dbb1586 dd/t5403-modernise later to maint). diff --git a/builtin/describe.c b/builtin/describe.c index 443546aaac96f0..989a78d715d525 100644 --- a/builtin/describe.c +++ b/builtin/describe.c @@ -112,13 +112,13 @@ static int replace_name(struct commit_name *e, if (!e->tag) { t = lookup_tag(the_repository, &e->oid); - if (!t || parse_tag(t)) + if (!t || parse_tag(the_repository, t)) return 1; e->tag = t; } t = lookup_tag(the_repository, oid); - if (!t || parse_tag(t)) + if (!t || parse_tag(the_repository, t)) return 0; *tag = t; @@ -335,7 +335,7 @@ static void append_name(struct commit_name *n, struct strbuf *dst) { if (n->prio == 2 && !n->tag) { n->tag = lookup_tag(the_repository, &n->oid); - if (!n->tag || parse_tag(n->tag)) + if (!n->tag || parse_tag(the_repository, n->tag)) die(_("annotated tag %s not available"), n->path); } if (n->tag && !n->name_checked) { diff --git a/builtin/pack-objects.c b/builtin/pack-objects.c index 1ce8d6ee215326..ca44b7894fc064 100644 --- a/builtin/pack-objects.c +++ b/builtin/pack-objects.c @@ -3293,7 +3293,7 @@ static void add_tag_chain(const struct object_id *oid) tag = lookup_tag(the_repository, oid); while (1) { - if (!tag || parse_tag(tag) || !tag->tagged) + if (!tag || parse_tag(the_repository, tag) || !tag->tagged) die(_("unable to pack objects reachable from tag %s"), oid_to_hex(oid)); diff --git a/builtin/tag.c b/builtin/tag.c index 01eba90c5c7bb2..aeb04c487fe95a 100644 --- a/builtin/tag.c +++ b/builtin/tag.c @@ -149,7 +149,7 @@ static int verify_tag(const char *name, const char *ref UNUSED, if (format->format) flags = GPG_VERIFY_OMIT_STATUS; - if (gpg_verify_tag(oid, name, flags)) + if (gpg_verify_tag(the_repository, oid, name, flags)) return -1; if (format->format) diff --git a/builtin/verify-tag.c b/builtin/verify-tag.c index 558121eaa1688e..4a261b2369729f 100644 --- a/builtin/verify-tag.c +++ b/builtin/verify-tag.c @@ -61,7 +61,7 @@ int cmd_verify_tag(int argc, continue; } - if (gpg_verify_tag(&oid, name, flags)) { + if (gpg_verify_tag(repo, &oid, name, flags)) { had_error = 1; continue; } diff --git a/diff.c b/diff.c index 436da250eb150d..a68ddd2168ba1c 100644 --- a/diff.c +++ b/diff.c @@ -7098,6 +7098,7 @@ static void diffcore_skip_stat_unmatch(struct diff_options *diffopt) if (!diffopt->flags.no_index) diffopt->skip_stat_unmatch++; diff_free_filepair(p); + q->queue[i] = NULL; } } free(q->queue); @@ -7141,6 +7142,10 @@ void diff_queued_diff_prefetch(void *repository) for (i = 0; i < q->nr; i++) { struct diff_filepair *p = q->queue[i]; + + if (!p) + continue; + diff_add_if_missing(repo, &to_fetch, p->one); diff_add_if_missing(repo, &to_fetch, p->two); } diff --git a/fsck.c b/fsck.c index 138fffded935c4..fae18d8561e067 100644 --- a/fsck.c +++ b/fsck.c @@ -474,7 +474,7 @@ static int fsck_walk_tag(struct tag *tag, void *data, struct fsck_options *optio { const char *name = fsck_get_object_name(options, &tag->object.oid); - if (parse_tag(tag)) + if (parse_tag(the_repository, tag)) return -1; if (name) fsck_put_object_name(options, &tag->tagged->oid, "%s", name); diff --git a/merge-ort.c b/merge-ort.c index 9e85a5e60ae69f..2b837a58c3a6f8 100644 --- a/merge-ort.c +++ b/merge-ort.c @@ -1502,11 +1502,44 @@ static void resolve_trivial_directory_merge(struct conflict_info *ci, int side) VERIFY_CI(ci); assert((side == 1 && ci->match_mask == 5) || (side == 2 && ci->match_mask == 3)); + + /* + * Since ci->stages[0] matches ci->stages[3-side], resolve merge in + * favor of ci->stages[side]. + */ oidcpy(&ci->merged.result.oid, &ci->stages[side].oid); ci->merged.result.mode = ci->stages[side].mode; ci->merged.is_null = is_null_oid(&ci->stages[side].oid); + + /* + * Because we resolved in favor of "side", we are no longer + * considering the paths which matched (i.e. had the same hash) any + * more. Strip the matching paths from both dirmask & filemask. + * Another consequence of merging in favor of side is that we can no + * longer have a directory/file conflict either..but there's a slight + * nuance we consider before clearing it. + * + * In most cases, resolving in favor of the other side means there's + * no conflict at all, but if we had a directory/file conflict to + * start, and the directory is resolved away, the remaining file could + * still be part of a rename. If the remaining file is part of a + * rename, then it may also be part of a rename conflict (e.g. + * rename/delete or rename/rename(1to2)), so we can't + * mark it as a clean merge if we started with a directory/file + * conflict and still have a file left. + * + * In contrast, if we started with a directory/file conflict and + * still have a directory left, no file under that directory can be + * part of a rename, otherwise we would have had to recurse into the + * directory and would have never ended up within + * resolve_trivial_directory_merge() for that directory. + */ + ci->dirmask &= (~ci->match_mask); + ci->filemask &= (~ci->match_mask); + assert(!ci->filemask || !ci->dirmask); ci->match_mask = 0; - ci->merged.clean = 1; /* (ci->filemask == 0); */ + ci->merged.clean = !ci->df_conflict || ci->dirmask; + ci->df_conflict = 0; } static int handle_deferred_entries(struct merge_options *opt, diff --git a/object-name.c b/object-name.c index fed5de51531fde..8b862c124e05a9 100644 --- a/object-name.c +++ b/object-name.c @@ -449,7 +449,7 @@ static int show_ambiguous_object(const struct object_id *oid, void *data) } else if (type == OBJ_TAG) { struct tag *tag = lookup_tag(ds->repo, oid); - if (!parse_tag(tag) && tag->tag) { + if (!parse_tag(ds->repo, tag) && tag->tag) { /* * TRANSLATORS: This is a line of ambiguous * tag object output. E.g.: diff --git a/ref-filter.c b/ref-filter.c index d7454269e87cd3..c318f9ca0ec8dd 100644 --- a/ref-filter.c +++ b/ref-filter.c @@ -2866,7 +2866,7 @@ static int match_points_at(struct oid_array *points_at, while (obj && obj->type == OBJ_TAG) { struct tag *tag = (struct tag *)obj; - if (parse_tag(tag) < 0) { + if (parse_tag(the_repository, tag) < 0) { obj = NULL; break; } diff --git a/t/t4067-diff-partial-clone.sh b/t/t4067-diff-partial-clone.sh index 581250dd2d227a..72f25de44950ae 100755 --- a/t/t4067-diff-partial-clone.sh +++ b/t/t4067-diff-partial-clone.sh @@ -132,6 +132,41 @@ test_expect_success 'diff with rename detection batches blobs' ' test_line_count = 1 done_lines ' +test_expect_success 'diff succeeds even if entries are removed from queue' ' + test_when_finished "rm -rf server client trace" && + + test_create_repo server && + for l in a c e g i p + do + echo $l >server/$l && + git -C server add $l || return 1 + done && + git -C server commit -m x && + + for l in a e i + do + git -C server rm $l || return 1 + done && + + for l in b d f i + do + echo $l$l >server/$l && + git -C server add $l || return 1 + done && + git -C server commit -a -m x && + + test_config -C server uploadpack.allowfilter 1 && + test_config -C server uploadpack.allowanysha1inwant 1 && + git clone --filter=blob:limit=0 "file://$(pwd)/server" client && + + for file in $(ls client) + do + cat client/$file >$file && + mv $file client/$file || return 1 + done && + git -C client diff --name-only --relative HEAD^ +' + test_expect_success 'diff does not fetch anything if inexact rename detection is not needed' ' test_when_finished "rm -rf server client trace" && diff --git a/t/t5403-post-checkout-hook.sh b/t/t5403-post-checkout-hook.sh index 978f240cdaceb4..1462e3365b95c2 100755 --- a/t/t5403-post-checkout-hook.sh +++ b/t/t5403-post-checkout-hook.sh @@ -109,7 +109,7 @@ test_expect_success 'post-checkout hook is triggered by clone' ' echo "$@" >"$GIT_DIR/post-checkout.args" EOF git clone --template=templates . clone3 && - test -f clone3/.git/post-checkout.args + test_path_is_file clone3/.git/post-checkout.args ' test_done diff --git a/t/t6422-merge-rename-corner-cases.sh b/t/t6422-merge-rename-corner-cases.sh index f14c0fb30e1bf2..e18d5a227d54f7 100755 --- a/t/t6422-merge-rename-corner-cases.sh +++ b/t/t6422-merge-rename-corner-cases.sh @@ -1439,4 +1439,90 @@ test_expect_success 'rename/rename(1to2) with a binary file' ' ) ' +# Testcase preliminary submodule/directory conflict and submodule rename +# Commit O: +# Commit A1: introduce "folder" (as a tree) +# Commit B1: introduce "folder" (as a submodule) +# Commit A2: merge B1 into A1, but keep folder as a tree +# Commit B2: merge A1 into B1, but keep folder as a submodule +# Merge A2 & B2 +test_setup_submodule_directory_preliminary_conflict () { + git init submodule_directory_preliminary_conflict && + ( + cd submodule_directory_preliminary_conflict && + + # Trying to do the A2 and B2 merges above is slightly more + # challenging with a local submodule (because checking out + # another commit has the submodule in the way). Instead, + # first create the commits with the wrong parents but right + # trees, in the order A1, A2, B1, B2... + # + # Then go back and create new A2 & B2 with the correct + # parents and the same trees. + + git commit --allow-empty -m orig && + + git branch A && + git branch B && + + git checkout B && + mkdir folder && + echo A>folder/A && + echo B>folder/B && + echo C>folder/C && + echo D>folder/D && + echo E>folder/E && + git add folder && + git commit -m B1 && + + git commit --allow-empty -m B2 && + + git checkout A && + git init folder && + ( + cd folder && + >Z && + >Y && + git add Z Y && + git commit -m "original submodule commit" + ) && + git add folder && + git commit -m A1 && + + git commit --allow-empty -m A2 && + + NewA2=$(git commit-tree -p A^ -p B^ -m "Merge B into A" A^{tree}) && + NewB2=$(git commit-tree -p B^ -p A^ -m "Merge A into B" B^{tree}) && + git update-ref refs/heads/A $NewA2 && + git update-ref refs/heads/B $NewB2 + ) +} + +test_expect_success 'submodule/directory preliminary conflict' ' + test_setup_submodule_directory_preliminary_conflict && + ( + cd submodule_directory_preliminary_conflict && + + git checkout A^0 && + + test_expect_code 1 git merge B^0 && + + # Make sure the index has the right number of entries + git ls-files -s >actual && + test_line_count = 2 actual && + + # The "folder" as directory should have been resolved away + # as part of the merge. The "folder" as submodule got + # renamed to "folder~Temporary merge branch 2" in the + # virtual merge base, resulting in a + # "folder~Temporary merge branch 2" -> "folder" + # rename in the outermerge for the submodule, which then + # becomes part of a rename/delete conflict (because "folder" + # as a submodule was deleted in A2). + submod=$(git rev-parse A:folder) && + printf "160000 $submod 1\tfolder\n160000 $submod 2\tfolder\n" >expect && + test_cmp expect actual + ) +' + test_done diff --git a/tag.c b/tag.c index f5c232d2f1f36c..2f12e51024ec0b 100644 --- a/tag.c +++ b/tag.c @@ -1,4 +1,3 @@ -#define USE_THE_REPOSITORY_VARIABLE #define DISABLE_SIGN_COMPARE_WARNINGS #include "git-compat-util.h" @@ -13,6 +12,7 @@ #include "gpg-interface.h" #include "hex.h" #include "packfile.h" +#include "repository.h" const char *tag_type = "tag"; @@ -44,28 +44,28 @@ static int run_gpg_verify(const char *buf, unsigned long size, unsigned flags) return ret; } -int gpg_verify_tag(const struct object_id *oid, const char *name_to_report, - unsigned flags) +int gpg_verify_tag(struct repository *r, const struct object_id *oid, + const char *name_to_report, unsigned flags) { enum object_type type; char *buf; unsigned long size; int ret; - type = odb_read_object_info(the_repository->objects, oid, NULL); + type = odb_read_object_info(r->objects, oid, NULL); if (type != OBJ_TAG) return error("%s: cannot verify a non-tag object of type %s.", name_to_report ? name_to_report : - repo_find_unique_abbrev(the_repository, oid, DEFAULT_ABBREV), + oid_to_hex(oid), type_name(type)); - buf = odb_read_object(the_repository->objects, oid, &type, &size); + buf = odb_read_object(r->objects, oid, &type, &size); if (!buf) return error("%s: unable to read file.", name_to_report ? name_to_report : - repo_find_unique_abbrev(the_repository, oid, DEFAULT_ABBREV)); + oid_to_hex(oid)); ret = run_gpg_verify(buf, size, flags); @@ -148,9 +148,11 @@ int parse_tag_buffer(struct repository *r, struct tag *item, const void *data, u FREE_AND_NULL(item->tag); } - if (size < the_hash_algo->hexsz + 24) + if (size < r->hash_algo->hexsz + 24) return -1; - if (memcmp("object ", bufptr, 7) || parse_oid_hex(bufptr + 7, &oid, &bufptr) || *bufptr++ != '\n') + if (memcmp("object ", bufptr, 7) || + parse_oid_hex_algop(bufptr + 7, &oid, &bufptr, r->hash_algo) || + *bufptr++ != '\n') return -1; if (!starts_with(bufptr, "type ")) @@ -201,7 +203,7 @@ int parse_tag_buffer(struct repository *r, struct tag *item, const void *data, u return 0; } -int parse_tag(struct tag *item) +int parse_tag(struct repository *r, struct tag *item) { enum object_type type; void *data; @@ -210,8 +212,7 @@ int parse_tag(struct tag *item) if (item->object.parsed) return 0; - data = odb_read_object(the_repository->objects, &item->object.oid, - &type, &size); + data = odb_read_object(r->objects, &item->object.oid, &type, &size); if (!data) return error("Could not read %s", oid_to_hex(&item->object.oid)); @@ -220,7 +221,7 @@ int parse_tag(struct tag *item) return error("Object %s not a tag", oid_to_hex(&item->object.oid)); } - ret = parse_tag_buffer(the_repository, item, data, size); + ret = parse_tag_buffer(r, item, data, size); free(data); return ret; } diff --git a/tag.h b/tag.h index ef12a610372063..534687c4caeca4 100644 --- a/tag.h +++ b/tag.h @@ -13,10 +13,10 @@ struct tag { }; struct tag *lookup_tag(struct repository *r, const struct object_id *oid); int parse_tag_buffer(struct repository *r, struct tag *item, const void *data, unsigned long size); -int parse_tag(struct tag *item); +int parse_tag(struct repository *r, struct tag *item); void release_tag_memory(struct tag *t); struct object *deref_tag(struct repository *r, struct object *, const char *, int); -int gpg_verify_tag(const struct object_id *oid, +int gpg_verify_tag(struct repository *r, const struct object_id *oid, const char *name_to_report, unsigned flags); struct object_id *get_tagged_oid(struct tag *tag); diff --git a/walker.c b/walker.c index 409b646578a3d4..2891563b03620b 100644 --- a/walker.c +++ b/walker.c @@ -115,7 +115,7 @@ static int process_commit(struct walker *walker, struct commit *commit) static int process_tag(struct walker *walker, struct tag *tag) { - if (parse_tag(tag)) + if (parse_tag(the_repository, tag)) return -1; return process(walker, tag->tagged); }