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
This commit is contained in:
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
check and make the merge proceed anyway.
--merge-base=<commit>::
--merge-base=<tree-ish>::
Instead of finding the merge-bases for <branch1> and <branch2>,
specify a merge-base for the merge, and specifying multiple bases is
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

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,
info->commit ? &info->commit->object.oid : null_oid(),
NULL);
parse_tree(tree);
if (parse_tree(tree) < 0)
return 128;
init_tree_desc(&tree_desc, tree->buffer, tree->size);
switch (unpack_trees(1, &tree_desc, &opts)) {
case -2:
@ -783,9 +784,15 @@ static int merge_working_tree(const struct checkout_opts *opts,
if (new_branch_info->commit)
BUG("'switch --orphan' should never accept a commit as starting point");
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_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) {
ret = reset_tree(new_tree, opts, 1, writeout_error, new_branch_info);
if (ret)
@ -820,7 +827,8 @@ static int merge_working_tree(const struct checkout_opts *opts,
oid_to_hex(old_commit_oid));
init_tree_desc(&trees[0], tree->buffer, tree->size);
parse_tree(new_tree);
if (parse_tree(new_tree) < 0)
exit(128);
tree = new_tree;
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) {
/* not a commit */
*source_tree = parse_tree_indirect(rev);
if (!*source_tree)
die(_("unable to read tree (%s)"), oid_to_hex(rev));
} else {
parse_commit_or_die(new_branch_info->commit);
*source_tree = repo_get_commit_tree(the_repository,
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);
if (!tree)
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);
if (unpack_trees(1, &t, &opts) < 0)
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);
if (!tree)
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);
if (unpack_trees(1, &t, &opts))
exit(128); /* We've already reported the error, finish dying */

View File

@ -429,35 +429,49 @@ static int real_merge(struct merge_tree_options *o,
struct merge_options opt;
copy_merge_options(&opt, &o->merge_options);
parent1 = get_merge_parent(branch1);
if (!parent1)
help_unknown_ref(branch1, "merge-tree",
_("not something we can merge"));
parent2 = get_merge_parent(branch2);
if (!parent2)
help_unknown_ref(branch2, "merge-tree",
_("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);
/*
* 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;
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 {
parent1 = get_merge_parent(branch1);
if (!parent1)
help_unknown_ref(branch1, "merge-tree",
_("not something we can merge"));
parent2 = get_merge_parent(branch2);
if (!parent2)
help_unknown_ref(branch2, "merge-tree",
_("not something we can merge"));
/*
* Get the merge bases, in reverse order; see comment above
* 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);
for (i = 0; i < nr_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);
}
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) {
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);
}

View File

@ -778,8 +778,8 @@ static void prime_cache_tree_rec(struct repository *r,
struct cache_tree_sub *sub;
struct tree *subtree = lookup_tree(r, &entry.oid);
if (!subtree->object.parsed)
parse_tree(subtree);
if (parse_tree(subtree) < 0)
exit(128);
sub = cache_tree_sub(it, entry.path);
sub->cache_tree = cache_tree();

View File

@ -1658,9 +1658,10 @@ static int collect_merge_info(struct merge_options *opt,
info.data = opt;
info.show_all_errors = 1;
parse_tree(merge_base);
parse_tree(side1);
parse_tree(side2);
if (parse_tree(merge_base) < 0 ||
parse_tree(side1) < 0 ||
parse_tree(side2) < 0)
return -1;
init_tree_desc(t + 0, merge_base->buffer, merge_base->size);
init_tree_desc(t + 1, side1->buffer, side1->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.fn = twoway_merge;
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);
parse_tree(next);
if (parse_tree(next) < 0)
return -1;
init_tree_desc(&trees[1], next->buffer, next->size);
ret = unpack_trees(2, trees, &unpack_opts);
@ -4983,6 +4986,9 @@ redo:
if (result->clean >= 0) {
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 */
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)
{
parse_tree(tree);
if (parse_tree(tree) < 0)
exit(128);
init_tree_desc(desc, tree->buffer, tree->size);
}

View File

@ -77,7 +77,10 @@ int checkout_fast_forward(struct repository *r,
return -1;
}
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);
}

View File

@ -157,6 +157,11 @@ int reset_head(struct repository *r, const struct reset_head_opts *opts)
}
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);
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;
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);
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);
if (!tree)
return error(_("unable to read tree (%s)"), oid_to_hex(&oid));
prime_cache_tree(r, r->index, tree);
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_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

View File

@ -878,7 +878,7 @@ test_expect_success 'broken branch creation' '
echo "" > expected.ok
cat > expected.missing-tree.default <<EOF
fatal: unable to read tree $deleted
fatal: unable to read tree ($deleted)
EOF
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) {
buf = read_object_with_reference(r, oid, OBJ_TREE, &size, NULL);
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);
return buf;