Merge branch 'js/merge-tree-3-trees'

"git merge-tree" has learned that the three trees involved in the
3-way merge only need to be trees, not necessarily commits.

* js/merge-tree-3-trees:
  fill_tree_descriptor(): mark error message for translation
  cache-tree: avoid an unnecessary check
  Always check `parse_tree*()`'s return value
  t4301: verify that merge-tree fails on missing blob objects
  merge-ort: do check `parse_tree()`'s return value
  merge-tree: fail with a non-zero exit code on missing tree objects
  merge-tree: accept 3 trees as arguments
maint
Junio C Hamano 2024-03-07 15:59:41 -08:00
commit ae46d5fb98
16 changed files with 124 additions and 35 deletions

View File

@ -64,10 +64,13 @@ OPTIONS
share no common history. This flag can be given to override that share no common history. This flag can be given to override that
check and make the merge proceed anyway. check and make the merge proceed anyway.


--merge-base=<commit>:: --merge-base=<tree-ish>::
Instead of finding the merge-bases for <branch1> and <branch2>, Instead of finding the merge-bases for <branch1> and <branch2>,
specify a merge-base for the merge, and specifying multiple bases is specify a merge-base for the merge, and specifying multiple bases is
currently not supported. This option is incompatible with `--stdin`. currently not supported. This option is incompatible with `--stdin`.
+
As the merge-base is provided directly, <branch1> and <branch2> do not need
to specify commits; trees are enough.


[[OUTPUT]] [[OUTPUT]]
OUTPUT OUTPUT

View File

@ -704,7 +704,8 @@ static int reset_tree(struct tree *tree, const struct checkout_opts *o,
init_checkout_metadata(&opts.meta, info->refname, init_checkout_metadata(&opts.meta, info->refname,
info->commit ? &info->commit->object.oid : null_oid(), info->commit ? &info->commit->object.oid : null_oid(),
NULL); NULL);
parse_tree(tree); if (parse_tree(tree) < 0)
return 128;
init_tree_desc(&tree_desc, tree->buffer, tree->size); init_tree_desc(&tree_desc, tree->buffer, tree->size);
switch (unpack_trees(1, &tree_desc, &opts)) { switch (unpack_trees(1, &tree_desc, &opts)) {
case -2: case -2:
@ -783,9 +784,15 @@ static int merge_working_tree(const struct checkout_opts *opts,
if (new_branch_info->commit) if (new_branch_info->commit)
BUG("'switch --orphan' should never accept a commit as starting point"); BUG("'switch --orphan' should never accept a commit as starting point");
new_tree = parse_tree_indirect(the_hash_algo->empty_tree); new_tree = parse_tree_indirect(the_hash_algo->empty_tree);
} else if (!new_tree)
BUG("unable to read empty tree");
} else {
new_tree = repo_get_commit_tree(the_repository, new_tree = repo_get_commit_tree(the_repository,
new_branch_info->commit); new_branch_info->commit);
if (!new_tree)
return error(_("unable to read tree (%s)"),
oid_to_hex(&new_branch_info->commit->object.oid));
}
if (opts->discard_changes) { if (opts->discard_changes) {
ret = reset_tree(new_tree, opts, 1, writeout_error, new_branch_info); ret = reset_tree(new_tree, opts, 1, writeout_error, new_branch_info);
if (ret) if (ret)
@ -820,7 +827,8 @@ static int merge_working_tree(const struct checkout_opts *opts,
oid_to_hex(old_commit_oid)); oid_to_hex(old_commit_oid));


init_tree_desc(&trees[0], tree->buffer, tree->size); init_tree_desc(&trees[0], tree->buffer, tree->size);
parse_tree(new_tree); if (parse_tree(new_tree) < 0)
exit(128);
tree = new_tree; tree = new_tree;
init_tree_desc(&trees[1], tree->buffer, tree->size); init_tree_desc(&trees[1], tree->buffer, tree->size);


@ -1240,10 +1248,15 @@ static void setup_new_branch_info_and_source_tree(
if (!new_branch_info->commit) { if (!new_branch_info->commit) {
/* not a commit */ /* not a commit */
*source_tree = parse_tree_indirect(rev); *source_tree = parse_tree_indirect(rev);
if (!*source_tree)
die(_("unable to read tree (%s)"), oid_to_hex(rev));
} else { } else {
parse_commit_or_die(new_branch_info->commit); parse_commit_or_die(new_branch_info->commit);
*source_tree = repo_get_commit_tree(the_repository, *source_tree = repo_get_commit_tree(the_repository,
new_branch_info->commit); new_branch_info->commit);
if (!*source_tree)
die(_("unable to read tree (%s)"),
oid_to_hex(&new_branch_info->commit->object.oid));
} }
} }



View File

@ -738,7 +738,8 @@ static int checkout(int submodule_progress, int filter_submodules)
tree = parse_tree_indirect(&oid); tree = parse_tree_indirect(&oid);
if (!tree) if (!tree)
die(_("unable to parse commit %s"), oid_to_hex(&oid)); die(_("unable to parse commit %s"), oid_to_hex(&oid));
parse_tree(tree); if (parse_tree(tree) < 0)
exit(128);
init_tree_desc(&t, tree->buffer, tree->size); init_tree_desc(&t, tree->buffer, tree->size);
if (unpack_trees(1, &t, &opts) < 0) if (unpack_trees(1, &t, &opts) < 0)
die(_("unable to checkout working tree")); die(_("unable to checkout working tree"));

View File

@ -331,7 +331,8 @@ static void create_base_index(const struct commit *current_head)
tree = parse_tree_indirect(&current_head->object.oid); tree = parse_tree_indirect(&current_head->object.oid);
if (!tree) if (!tree)
die(_("failed to unpack HEAD tree object")); die(_("failed to unpack HEAD tree object"));
parse_tree(tree); if (parse_tree(tree) < 0)
exit(128);
init_tree_desc(&t, tree->buffer, tree->size); init_tree_desc(&t, tree->buffer, tree->size);
if (unpack_trees(1, &t, &opts)) if (unpack_trees(1, &t, &opts))
exit(128); /* We've already reported the error, finish dying */ exit(128); /* We've already reported the error, finish dying */

View File

@ -429,6 +429,39 @@ static int real_merge(struct merge_tree_options *o,
struct merge_options opt; struct merge_options opt;


copy_merge_options(&opt, &o->merge_options); copy_merge_options(&opt, &o->merge_options);
opt.show_rename_progress = 0;

opt.branch1 = branch1;
opt.branch2 = branch2;

if (merge_base) {
struct tree *base_tree, *parent1_tree, *parent2_tree;

/*
* We actually only need the trees because we already
* have a merge base.
*/
struct object_id base_oid, head_oid, merge_oid;

if (repo_get_oid_treeish(the_repository, merge_base, &base_oid))
die(_("could not parse as tree '%s'"), merge_base);
base_tree = parse_tree_indirect(&base_oid);
if (!base_tree)
die(_("unable to read tree (%s)"), oid_to_hex(&base_oid));
if (repo_get_oid_treeish(the_repository, branch1, &head_oid))
die(_("could not parse as tree '%s'"), branch1);
parent1_tree = parse_tree_indirect(&head_oid);
if (!parent1_tree)
die(_("unable to read tree (%s)"), oid_to_hex(&head_oid));
if (repo_get_oid_treeish(the_repository, branch2, &merge_oid))
die(_("could not parse as tree '%s'"), branch2);
parent2_tree = parse_tree_indirect(&merge_oid);
if (!parent2_tree)
die(_("unable to read tree (%s)"), oid_to_hex(&merge_oid));

opt.ancestor = merge_base;
merge_incore_nonrecursive(&opt, base_tree, parent1_tree, parent2_tree, &result);
} else {
parent1 = get_merge_parent(branch1); parent1 = get_merge_parent(branch1);
if (!parent1) if (!parent1)
help_unknown_ref(branch1, "merge-tree", help_unknown_ref(branch1, "merge-tree",
@ -439,25 +472,6 @@ static int real_merge(struct merge_tree_options *o,
help_unknown_ref(branch2, "merge-tree", help_unknown_ref(branch2, "merge-tree",
_("not something we can merge")); _("not something we can merge"));


opt.show_rename_progress = 0;

opt.branch1 = branch1;
opt.branch2 = branch2;

if (merge_base) {
struct commit *base_commit;
struct tree *base_tree, *parent1_tree, *parent2_tree;

base_commit = lookup_commit_reference_by_name(merge_base);
if (!base_commit)
die(_("could not lookup commit '%s'"), merge_base);

opt.ancestor = merge_base;
base_tree = repo_get_commit_tree(the_repository, base_commit);
parent1_tree = repo_get_commit_tree(the_repository, parent1);
parent2_tree = repo_get_commit_tree(the_repository, parent2);
merge_incore_nonrecursive(&opt, base_tree, parent1_tree, parent2_tree, &result);
} else {
/* /*
* Get the merge bases, in reverse order; see comment above * Get the merge bases, in reverse order; see comment above
* merge_incore_recursive in merge-ort.h * merge_incore_recursive in merge-ort.h

View File

@ -261,7 +261,8 @@ int cmd_read_tree(int argc, const char **argv, const char *cmd_prefix)
cache_tree_free(&the_index.cache_tree); cache_tree_free(&the_index.cache_tree);
for (i = 0; i < nr_trees; i++) { for (i = 0; i < nr_trees; i++) {
struct tree *tree = trees[i]; struct tree *tree = trees[i];
parse_tree(tree); if (parse_tree(tree) < 0)
return 128;
init_tree_desc(t+i, tree->buffer, tree->size); init_tree_desc(t+i, tree->buffer, tree->size);
} }
if (unpack_trees(nr_trees, t, &opts)) if (unpack_trees(nr_trees, t, &opts))

View File

@ -116,6 +116,10 @@ static int reset_index(const char *ref, const struct object_id *oid, int reset_t


if (reset_type == MIXED || reset_type == HARD) { if (reset_type == MIXED || reset_type == HARD) {
tree = parse_tree_indirect(oid); tree = parse_tree_indirect(oid);
if (!tree) {
error(_("unable to read tree (%s)"), oid_to_hex(oid));
goto out;
}
prime_cache_tree(the_repository, the_repository->index, tree); prime_cache_tree(the_repository, the_repository->index, tree);
} }



View File

@ -778,8 +778,8 @@ static void prime_cache_tree_rec(struct repository *r,
struct cache_tree_sub *sub; struct cache_tree_sub *sub;
struct tree *subtree = lookup_tree(r, &entry.oid); struct tree *subtree = lookup_tree(r, &entry.oid);


if (!subtree->object.parsed) if (parse_tree(subtree) < 0)
parse_tree(subtree); exit(128);
sub = cache_tree_sub(it, entry.path); sub = cache_tree_sub(it, entry.path);
sub->cache_tree = cache_tree(); sub->cache_tree = cache_tree();



View File

@ -1658,9 +1658,10 @@ static int collect_merge_info(struct merge_options *opt,
info.data = opt; info.data = opt;
info.show_all_errors = 1; info.show_all_errors = 1;


parse_tree(merge_base); if (parse_tree(merge_base) < 0 ||
parse_tree(side1); parse_tree(side1) < 0 ||
parse_tree(side2); parse_tree(side2) < 0)
return -1;
init_tree_desc(t + 0, merge_base->buffer, merge_base->size); init_tree_desc(t + 0, merge_base->buffer, merge_base->size);
init_tree_desc(t + 1, side1->buffer, side1->size); init_tree_desc(t + 1, side1->buffer, side1->size);
init_tree_desc(t + 2, side2->buffer, side2->size); init_tree_desc(t + 2, side2->buffer, side2->size);
@ -4377,9 +4378,11 @@ static int checkout(struct merge_options *opt,
unpack_opts.verbose_update = (opt->verbosity > 2); unpack_opts.verbose_update = (opt->verbosity > 2);
unpack_opts.fn = twoway_merge; unpack_opts.fn = twoway_merge;
unpack_opts.preserve_ignored = 0; /* FIXME: !opts->overwrite_ignore */ unpack_opts.preserve_ignored = 0; /* FIXME: !opts->overwrite_ignore */
parse_tree(prev); if (parse_tree(prev) < 0)
return -1;
init_tree_desc(&trees[0], prev->buffer, prev->size); init_tree_desc(&trees[0], prev->buffer, prev->size);
parse_tree(next); if (parse_tree(next) < 0)
return -1;
init_tree_desc(&trees[1], next->buffer, next->size); init_tree_desc(&trees[1], next->buffer, next->size);


ret = unpack_trees(2, trees, &unpack_opts); ret = unpack_trees(2, trees, &unpack_opts);
@ -4983,6 +4986,9 @@ redo:


if (result->clean >= 0) { if (result->clean >= 0) {
result->tree = parse_tree_indirect(&working_tree_oid); result->tree = parse_tree_indirect(&working_tree_oid);
if (!result->tree)
die(_("unable to read tree (%s)"),
oid_to_hex(&working_tree_oid));
/* existence of conflicted entries implies unclean */ /* existence of conflicted entries implies unclean */
result->clean &= strmap_empty(&opt->priv->conflicted); result->clean &= strmap_empty(&opt->priv->conflicted);
} }

View File

@ -405,7 +405,8 @@ static inline int merge_detect_rename(struct merge_options *opt)


static void init_tree_desc_from_tree(struct tree_desc *desc, struct tree *tree) static void init_tree_desc_from_tree(struct tree_desc *desc, struct tree *tree)
{ {
parse_tree(tree); if (parse_tree(tree) < 0)
exit(128);
init_tree_desc(desc, tree->buffer, tree->size); init_tree_desc(desc, tree->buffer, tree->size);
} }



View File

@ -77,7 +77,10 @@ int checkout_fast_forward(struct repository *r,
return -1; return -1;
} }
for (i = 0; i < nr_trees; i++) { for (i = 0; i < nr_trees; i++) {
parse_tree(trees[i]); if (parse_tree(trees[i]) < 0) {
rollback_lock_file(&lock_file);
return -1;
}
init_tree_desc(t+i, trees[i]->buffer, trees[i]->size); init_tree_desc(t+i, trees[i]->buffer, trees[i]->size);
} }



View File

@ -157,6 +157,11 @@ int reset_head(struct repository *r, const struct reset_head_opts *opts)
} }


tree = parse_tree_indirect(oid); tree = parse_tree_indirect(oid);
if (!tree) {
ret = error(_("unable to read tree (%s)"), oid_to_hex(oid));
goto leave_reset_head;
}

prime_cache_tree(r, r->index, tree); prime_cache_tree(r, r->index, tree);


if (write_locked_index(r->index, &lock, COMMIT_LOCK) < 0) { if (write_locked_index(r->index, &lock, COMMIT_LOCK) < 0) {

View File

@ -707,6 +707,8 @@ static int do_recursive_merge(struct repository *r,
o.show_rename_progress = 1; o.show_rename_progress = 1;


head_tree = parse_tree_indirect(head); head_tree = parse_tree_indirect(head);
if (!head_tree)
return error(_("unable to read tree (%s)"), oid_to_hex(head));
next_tree = next ? repo_get_commit_tree(r, next) : empty_tree(r); next_tree = next ? repo_get_commit_tree(r, next) : empty_tree(r);
base_tree = base ? repo_get_commit_tree(r, base) : empty_tree(r); base_tree = base ? repo_get_commit_tree(r, base) : empty_tree(r);


@ -3882,6 +3884,8 @@ static int do_reset(struct repository *r,
} }


tree = parse_tree_indirect(&oid); tree = parse_tree_indirect(&oid);
if (!tree)
return error(_("unable to read tree (%s)"), oid_to_hex(&oid));
prime_cache_tree(r, r->index, tree); prime_cache_tree(r, r->index, tree);


if (write_locked_index(r->index, &lock, COMMIT_LOCK) < 0) if (write_locked_index(r->index, &lock, COMMIT_LOCK) < 0)

View File

@ -945,4 +945,37 @@ test_expect_success 'check the input format when --stdin is passed' '
test_cmp expect actual test_cmp expect actual
' '


test_expect_success '--merge-base with tree OIDs' '
git merge-tree --merge-base=side1^ side1 side3 >with-commits &&
git merge-tree --merge-base=side1^^{tree} side1^{tree} side3^{tree} >with-trees &&
test_cmp with-commits with-trees
'

test_expect_success 'error out on missing tree objects' '
git init --bare missing-tree.git &&
git rev-list side3 >list &&
git rev-parse side3^: >>list &&
git pack-objects missing-tree.git/objects/pack/side3-tree-is-missing <list &&
side3=$(git rev-parse side3) &&
test_must_fail git --git-dir=missing-tree.git merge-tree $side3^ $side3 >actual 2>err &&
test_grep "Could not read $(git rev-parse $side3:)" err &&
test_must_be_empty actual
'

test_expect_success 'error out on missing blob objects' '
echo 1 | git hash-object -w --stdin >blob1 &&
echo 2 | git hash-object -w --stdin >blob2 &&
echo 3 | git hash-object -w --stdin >blob3 &&
printf "100644 blob $(cat blob1)\tblob\n" | git mktree >tree1 &&
printf "100644 blob $(cat blob2)\tblob\n" | git mktree >tree2 &&
printf "100644 blob $(cat blob3)\tblob\n" | git mktree >tree3 &&
git init --bare missing-blob.git &&
cat blob1 blob3 tree1 tree2 tree3 |
git pack-objects missing-blob.git/objects/pack/side1-whatever-is-missing &&
test_must_fail git --git-dir=missing-blob.git >actual 2>err \
merge-tree --merge-base=$(cat tree1) $(cat tree2) $(cat tree3) &&
test_grep "unable to read blob object $(cat blob2)" err &&
test_must_be_empty actual
'

test_done test_done

View File

@ -878,7 +878,7 @@ test_expect_success 'broken branch creation' '


echo "" > expected.ok echo "" > expected.ok
cat > expected.missing-tree.default <<EOF cat > expected.missing-tree.default <<EOF
fatal: unable to read tree $deleted fatal: unable to read tree ($deleted)
EOF EOF


test_expect_success 'bisect fails if tree is broken on start commit' ' test_expect_success 'bisect fails if tree is broken on start commit' '

View File

@ -100,7 +100,7 @@ void *fill_tree_descriptor(struct repository *r,
if (oid) { if (oid) {
buf = read_object_with_reference(r, oid, OBJ_TREE, &size, NULL); buf = read_object_with_reference(r, oid, OBJ_TREE, &size, NULL);
if (!buf) if (!buf)
die("unable to read tree %s", oid_to_hex(oid)); die(_("unable to read tree (%s)"), oid_to_hex(oid));
} }
init_tree_desc(desc, buf, size); init_tree_desc(desc, buf, size);
return buf; return buf;