@@ -100,6 +100,10 @@ will be appended to the specified message.
Silently overwrite ignored files from the merge result. This
is the default behavior. Use `--no-overwrite-ignore` to abort.
+--overwrite-same-content::
+ Silently overwrite untracked files that have the same content
+ and name than files in the merged commit from the merge result.
+
--abort::
Abort the current conflict resolution process, and
try to reconstruct the pre-merge state. If an autostash entry is
@@ -68,6 +68,7 @@ static int option_edit = -1;
static int allow_trivial = 1, have_message, verify_signatures;
static int check_trust_level = 1;
static int overwrite_ignore = 1;
+static int overwrite_same_content;
static struct strbuf merge_msg = STRBUF_INIT;
static struct strategy **use_strategies;
static size_t use_strategies_nr, use_strategies_alloc;
@@ -305,6 +306,7 @@ static struct option builtin_merge_options[] = {
OPT_BOOL(0, "overwrite-ignore", &overwrite_ignore, N_("update ignored files (default)")),
OPT_BOOL(0, "signoff", &signoff, N_("add a Signed-off-by trailer")),
OPT_BOOL(0, "no-verify", &no_verify, N_("bypass pre-merge-commit and commit-msg hooks")),
+ OPT_BOOL(0, "overwrite-same-content", &overwrite_same_content, N_("overwrite untracked file with the same content and name")),
OPT_END()
};
@@ -684,6 +686,7 @@ static int read_tree_trivial(struct object_id *common, struct object_id *head,
opts.trivial_merges_only = 1;
opts.merge = 1;
opts.preserve_ignored = 0; /* FIXME: !overwrite_ignore */
+ opts.overwrite_same_content = overwrite_same_content;
trees[nr_trees] = parse_tree_indirect(common);
if (!trees[nr_trees++])
return -1;
@@ -746,6 +749,7 @@ static int try_merge_strategy(const char *strategy, struct commit_list *common,
o.branch1 = head_arg;
o.branch2 = merge_remote_util(remoteheads->item)->name;
+ o.overwrite_same_content = overwrite_same_content;
for (j = common; j; j = j->next)
commit_list_insert(j->item, &reversed);
@@ -1573,7 +1577,8 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
if (checkout_fast_forward(the_repository,
&head_commit->object.oid,
&commit->object.oid,
- overwrite_ignore)) {
+ overwrite_ignore,
+ overwrite_same_content)) {
apply_autostash(git_path_merge_autostash(the_repository));
ret = 1;
goto done;
@@ -92,6 +92,7 @@ static struct strvec opt_strategies = STRVEC_INIT;
static struct strvec opt_strategy_opts = STRVEC_INIT;
static char *opt_gpg_sign;
static int opt_allow_unrelated_histories;
+static int opt_overwrite_same_content;
/* Options passed to git-fetch */
static char *opt_all;
@@ -182,6 +183,7 @@ static struct option pull_options[] = {
OPT_SET_INT(0, "allow-unrelated-histories",
&opt_allow_unrelated_histories,
N_("allow merging unrelated histories"), 1),
+ OPT_BOOL(0, "overwrite-same-content", &opt_overwrite_same_content, N_("overwrite untracked file with the same content and name")),
/* Options passed to git-fetch */
OPT_GROUP(N_("Options related to fetching")),
@@ -612,7 +614,7 @@ static int pull_into_void(const struct object_id *merge_head,
*/
if (checkout_fast_forward(the_repository,
the_hash_algo->empty_tree,
- merge_head, 0))
+ merge_head, 0, opt_overwrite_same_content))
return 1;
if (update_ref("initial pull", "HEAD", merge_head, curr_head, 0, UPDATE_REFS_DIE_ON_ERR))
@@ -679,6 +681,8 @@ static int run_merge(void)
strvec_pushf(&args, "--cleanup=%s", cleanup_arg);
if (opt_ff)
strvec_push(&args, opt_ff);
+ if (opt_overwrite_same_content)
+ strvec_push(&args, "--overwrite-same-content");
if (opt_verify)
strvec_push(&args, opt_verify);
if (opt_verify_signatures)
@@ -1078,7 +1082,7 @@ int cmd_pull(int argc, const char **argv, const char *prefix)
"commit %s."), oid_to_hex(&orig_head));
if (checkout_fast_forward(the_repository, &orig_head,
- &curr_head, 0))
+ &curr_head, 0, opt_overwrite_same_content))
die(_("Cannot fast-forward your working tree.\n"
"After making sure that you saved anything precious from\n"
"$ git diff %s\n"
@@ -1858,7 +1858,8 @@ int try_merge_command(struct repository *r,
int checkout_fast_forward(struct repository *r,
const struct object_id *from,
const struct object_id *to,
- int overwrite_ignore);
+ int overwrite_ignore,
+ int overwrite_same_content);
int sane_execvp(const char *file, char *const argv[]);
@@ -4066,6 +4066,7 @@ 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 */
+ unpack_opts.overwrite_same_content = opt->overwrite_same_content;
parse_tree(prev);
init_tree_desc(&trees[0], prev->buffer, prev->size);
parse_tree(next);
@@ -46,6 +46,7 @@ struct merge_options {
/* miscellaneous control options */
const char *subtree_shift;
unsigned renormalize : 1;
+ int overwrite_same_content;
/* internal fields used by the implementation */
struct merge_options_internal *priv;
@@ -47,7 +47,8 @@ int try_merge_command(struct repository *r,
int checkout_fast_forward(struct repository *r,
const struct object_id *head,
const struct object_id *remote,
- int overwrite_ignore)
+ int overwrite_ignore,
+ int overwrite_same_content)
{
struct tree *trees[MAX_UNPACK_TREES];
struct unpack_trees_options opts;
@@ -80,6 +81,7 @@ int checkout_fast_forward(struct repository *r,
memset(&opts, 0, sizeof(opts));
opts.preserve_ignored = !overwrite_ignore;
+ opts.overwrite_same_content = overwrite_same_content;
opts.head_idx = 1;
opts.src_index = r->index;
@@ -529,7 +529,7 @@ static int fast_forward_to(struct repository *r,
struct strbuf err = STRBUF_INIT;
repo_read_index(r);
- if (checkout_fast_forward(r, from, to, 1))
+ if (checkout_fast_forward(r, from, to, 1, 0))
return -1; /* the callee should have complained already */
strbuf_addf(&sb, _("%s: fast-forward"), _(action_name(opts)));
new file mode 100755
@@ -0,0 +1,63 @@
+#!/bin/sh
+
+test_description='test when merge with untracked files and the option --overwrite-same-content'
+
+GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
+export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
+
+. ./test-lib.sh
+
+test_expect_success 'setup' '
+ test_commit "init" README.md "content" &&
+ git checkout -b A
+'
+
+test_expect_success 'fastforward overwrite untracked file that has the same content' '
+ test_when_finished "git branch -D B && git reset --hard init && git clean --force" &&
+ git checkout -b B &&
+ test_commit --no-tag "tracked" file "content" &&
+ git checkout A &&
+ echo content >file &&
+ git merge --overwrite-same-content B
+'
+
+test_expect_success 'fastforward fail when untracked file has different content' '
+ test_when_finished "git branch -D B && git reset --hard init && git clean --force" &&
+ git checkout -b B &&
+ test_commit --no-tag "tracked" file "content" &&
+ git switch A &&
+ echo other >file &&
+ test_must_fail git merge --overwrite-same-content B
+'
+
+test_expect_success 'normal merge overwrite untracked file that has the same content' '
+ test_when_finished "git branch -D B && git reset --hard init && git clean --force" &&
+ git checkout -b B &&
+ test_commit --no-tag "tracked" file "content" fileB "content" &&
+ git switch A &&
+ test_commit --no-tag "exA" fileA "content" &&
+ echo content >file &&
+ git merge --overwrite-same-content B
+'
+
+test_expect_success 'normal merge fail when untracked file has different content' '
+ test_when_finished "git branch -D B && git reset --hard init && git clean --force" &&
+ git checkout -b B &&
+ test_commit --no-tag "tracked" file "content" fileB "content" &&
+ git switch A &&
+ test_commit --no-tag "exA" fileA "content" &&
+ echo dif >file &&
+ test_must_fail git merge --overwrite-same-content B
+'
+
+test_expect_success 'merge fail when tracked file modification is unstaged' '
+ test_when_finished "git branch -D B && git reset --hard init && git clean --force" &&
+ test_commit --no-tag "unstaged" file "other" &&
+ git checkout -b B &&
+ test_commit --no-tag "staged" file "content" &&
+ git switch A &&
+ echo content >file &&
+ test_must_fail git merge --overwrite-same-content B
+'
+
+test_done
@@ -2257,6 +2257,10 @@ static int check_ok_to_remove(const char *name, int len, int dtype,
if (result) {
if (result->ce_flags & CE_REMOVE)
return 0;
+ } else if (ce && !ie_modified(o->src_index, ce, st, 0)) {
+ if(o->overwrite_same_content) {
+ return 0;
+ }
}
return add_rejected_path(o, error_type, name);
@@ -2264,7 +2268,8 @@ static int check_ok_to_remove(const char *name, int len, int dtype,
/*
* We do not want to remove or overwrite a working tree file that
- * is not tracked, unless it is ignored.
+ * is not tracked, unless it is ignored or it has the same content
+ * than the merged file with the option --overwrite_same_content.
*/
static int verify_absent_1(const struct cache_entry *ce,
enum unpack_trees_error_types error_type,
@@ -71,6 +71,7 @@ struct unpack_trees_options {
quiet,
exiting_early,
show_all_errors,
+ overwrite_same_content,
dry_run;
enum unpack_trees_reset_type reset;
const char *prefix;