@@ -89,6 +89,11 @@ merge.autoStash::
`--autostash` options of linkgit:git-merge[1].
Defaults to false.
+merge.overwritesamecontent::
+ When set to true, it will modify the behavior of git merge
+ to overwrite untracked files that have the same name and
+ content than files in the merged commit.
+
merge.tool::
Controls which merge tool is used by linkgit:git-mergetool[1].
The list below shows the valid built-in values.
@@ -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
@@ -760,6 +760,7 @@ static int merge_working_tree(const struct checkout_opts *opts,
&new_branch_info->commit->object.oid :
&new_branch_info->oid, NULL);
topts.preserve_ignored = !opts->overwrite_ignore;
+ topts.overwrite_same_content = 0;/* FIXME: opts->overwrite_same_content */
tree = parse_tree_indirect(old_branch_info->commit ?
&old_branch_info->commit->object.oid :
the_hash_algo->empty_tree);
@@ -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()
};
@@ -656,6 +658,8 @@ static int git_merge_config(const char *k, const char *v, void *cb)
} else if (!strcmp(k, "merge.autostash")) {
autostash = git_config_bool(k, v);
return 0;
+ } else if (!strcmp(k,"merge.overwritesamecontent")) {
+ overwrite_same_content = git_config_bool(k, v);
}
status = fmt_merge_msg_config(k, v, cb);
@@ -684,6 +688,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 +751,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 +1579,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)));
@@ -204,4 +204,38 @@ test_expect_success 'will not clobber WT/index when merging into unborn' '
grep bar untracked-file
'
+test_expect_success 'create branch A' '
+ git reset --hard c0 &&
+ git checkout -b A
+'
+
+test_expect_success 'fastforward will not overwrite untracked file with the same content' '
+ test_when_finished "git branch -D B && git reset --hard c0 && git clean --force" &&
+ git checkout -b B &&
+ test_commit --no-tag "tracked" file "content" &&
+ git checkout A &&
+ echo content >file &&
+ test_must_fail git merge B
+'
+
+test_expect_success 'will not overwrite untracked file with the same content' '
+ test_when_finished "git branch -D B && git reset --hard c0 && git clean --force" &&
+ git checkout -b B &&
+ test_commit --no-tag "tracked" file "content" fileB "content" &&
+ git checkout A &&
+ test_commit --no-tag "exA" fileA "content" &&
+ echo content >file &&
+ test_must_fail git merge B
+'
+
+test_expect_success 'will not overwrite unstaged file with the same content' '
+ test_when_finished "git branch -D B && git reset --hard c0 && git clean --force" &&
+ test_commit --no-tag "unstaged" file "other" &&
+ git checkout -b B &&
+ test_commit --no-tag "staged" file "content" &&
+ git checkout A &&
+ echo content >file &&
+ test_must_fail git merge B
+'
+
test_done
@@ -1,6 +1,6 @@
#!/bin/sh
-test_description='test when merge with untracked file'
+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
@@ -18,7 +18,7 @@ test_expect_success 'fastforward overwrite untracked file that has the same cont
test_commit --no-tag "tracked" file "content" &&
git checkout A &&
echo content >file &&
- git merge B
+ git merge --overwrite-same-content B
'
test_expect_success 'fastforward fail when untracked file has different content' '
@@ -27,7 +27,7 @@ test_expect_success 'fastforward fail when untracked file has different content'
test_commit --no-tag "tracked" file "content" &&
git switch A &&
echo other >file &&
- test_must_fail git merge B
+ test_must_fail git merge --overwrite-same-content B
'
test_expect_success 'normal merge overwrite untracked file that has the same content' '
@@ -37,7 +37,7 @@ test_expect_success 'normal merge overwrite untracked file that has the same con
git switch A &&
test_commit --no-tag "exA" fileA "content" &&
echo content >file &&
- git merge B
+ git merge --overwrite-same-content B
'
test_expect_success 'normal merge fail when untracked file has different content' '
@@ -47,7 +47,7 @@ test_expect_success 'normal merge fail when untracked file has different content
git switch A &&
test_commit --no-tag "exA" fileA "content" &&
echo dif >file &&
- test_must_fail git merge B
+ test_must_fail git merge --overwrite-same-content B
'
test_expect_success 'merge fail when tracked file modification is unstaged' '
@@ -57,7 +57,28 @@ test_expect_success 'merge fail when tracked file modification is unstaged' '
test_commit --no-tag "staged" file "content" &&
git switch A &&
echo content >file &&
- test_must_fail git merge B
+ test_must_fail git merge --overwrite-same-content B
+'
+
+test_expect_success 'fastforward overwrite untracked file that has the same content with the configuration variable' '
+ test_when_finished "git branch -D B && git reset --hard init && git clean --force" &&
+ test_config merge.overwritesamecontent true &&
+ git checkout -b B &&
+ test_commit --no-tag "tracked" file "content" &&
+ git checkout A &&
+ echo content >file &&
+ git merge B
+'
+
+test_expect_success 'normal merge overwrite untracked file that has the same content with the configuration variable' '
+ test_when_finished "git branch -D B && git reset --hard init && git clean --force" &&
+ test_config merge.overwritesamecontent true &&
+ 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 B
'
test_done
@@ -158,17 +158,17 @@ void setup_unpack_trees_porcelain(struct unpack_trees_options *opts,
if (!strcmp(cmd, "checkout"))
msg = advice_enabled(ADVICE_COMMIT_BEFORE_MERGE)
? _("The following untracked working tree files would be overwritten by checkout:\n%%s"
- "Please move or remove them before you switch branches.")
+ "Please move or remove them before you switch branches.%%s")
: _("The following untracked working tree files would be overwritten by checkout:\n%%s");
else if (!strcmp(cmd, "merge"))
msg = advice_enabled(ADVICE_COMMIT_BEFORE_MERGE)
? _("The following untracked working tree files would be overwritten by merge:\n%%s"
- "Please move or remove them before you merge.")
+ "Please move or remove them before you merge.%%s")
: _("The following untracked working tree files would be overwritten by merge:\n%%s");
else
msg = advice_enabled(ADVICE_COMMIT_BEFORE_MERGE)
? _("The following untracked working tree files would be overwritten by %s:\n%%s"
- "Please move or remove them before you %s.")
+ "Please move or remove them before you %s.%%s")
: _("The following untracked working tree files would be overwritten by %s:\n%%s");
msgs[ERROR_WOULD_LOSE_UNTRACKED_OVERWRITTEN] =
strvec_pushf(&opts->msgs_to_free, msg, cmd, cmd);
@@ -251,6 +251,14 @@ static void display_error_msgs(struct unpack_trees_options *o)
{
int e;
unsigned error_displayed = 0;
+ const char *can_overwrite_msg;
+
+ if (o->can_overwrite) {
+ can_overwrite_msg = _("\nYou can also rerun the command with --overwrite-same-content to overwrite files with same content.");
+ } else {
+ can_overwrite_msg = "";
+ }
+
for (e = 0; e < NB_UNPACK_TREES_ERROR_TYPES; e++) {
struct string_list *rejects = &o->unpack_rejects[e];
@@ -261,7 +269,8 @@ static void display_error_msgs(struct unpack_trees_options *o)
error_displayed = 1;
for (i = 0; i < rejects->nr; i++)
strbuf_addf(&path, "\t%s\n", rejects->items[i].string);
- error(ERRORMSG(o, e), super_prefixed(path.buf));
+
+ error(ERRORMSG(o, e), super_prefixed(path.buf), can_overwrite_msg);
strbuf_release(&path);
}
string_list_clear(rejects, 0);
@@ -1711,6 +1720,7 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options
struct pattern_list pl;
int free_pattern_list = 0;
struct dir_struct dir = DIR_INIT;
+ o->can_overwrite = 1;
if (o->reset == UNPACK_RESET_INVALID)
BUG("o->reset had a value of 1; should be UNPACK_TREES_*_UNTRACKED");
@@ -2257,8 +2267,12 @@ 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 (!ie_modified(&o->result, ce, st, 0)) {
- return 0;
+ } else if (ce && !ie_modified(o->src_index, ce, st, 0)) {
+ if(o->overwrite_same_content) {
+ return 0;
+ }
+ } else {
+ o->can_overwrite = 0;
}
return add_rejected_path(o, error_type, name);
@@ -2266,8 +2280,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 and unless it has the same
- * content than the merged file.
+ * 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,8 @@ struct unpack_trees_options {
quiet,
exiting_early,
show_all_errors,
+ overwrite_same_content,
+ can_overwrite,
dry_run;
enum unpack_trees_reset_type reset;
const char *prefix;