Skip to content

Commit a03b555

Browse files
Denton-Lgitster
authored andcommitted
merge: teach --autostash option
In rebase, one can pass the `--autostash` option to cause the worktree to be automatically stashed before continuing with the rebase. This option is missing in merge, however. Implement the `--autostash` option and corresponding `merge.autoStash` option in merge which stashes before merging and then pops after. This option is useful when a developer has some local changes on a topic branch but they realize that their work depends on another branch. Previously, they had to run something like git fetch ... git stash push git merge FETCH_HEAD git stash pop but now, that is reduced to git fetch ... git merge --autostash FETCH_HEAD When an autostash is generated, it is automatically reapplied to the worktree only in three explicit situations: 1. An incomplete merge is commit using `git commit`. 2. A merge completes successfully. 3. A merge is aborted using `git merge --abort`. In all other situations where the merge state is removed using remove_merge_branch_state() such as aborting a merge via `git reset --hard`, the autostash is saved into the stash reflog instead keeping the worktree clean. Helped-by: Phillip Wood <[email protected]> Suggested-by: Alban Gruin <[email protected]> Signed-off-by: Denton Liu <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent 804fe31 commit a03b555

File tree

12 files changed

+214
-6
lines changed

12 files changed

+214
-6
lines changed

Documentation/config/merge.txt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,16 @@ merge.stat::
7070
Whether to print the diffstat between ORIG_HEAD and the merge result
7171
at the end of the merge. True by default.
7272

73+
merge.autoStash::
74+
When set to true, automatically create a temporary stash entry
75+
before the operation begins, and apply it after the operation
76+
ends. This means that you can run merge on a dirty worktree.
77+
However, use with care: the final stash application after a
78+
successful merge might result in non-trivial conflicts.
79+
This option can be overridden by the `--no-autostash` and
80+
`--autostash` options of linkgit:git-merge[1].
81+
Defaults to false.
82+
7383
merge.tool::
7484
Controls which merge tool is used by linkgit:git-mergetool[1].
7585
The list below shows the valid built-in values.

Documentation/git-merge.txt

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -94,19 +94,24 @@ will be appended to the specified message.
9494

9595
--abort::
9696
Abort the current conflict resolution process, and
97-
try to reconstruct the pre-merge state.
97+
try to reconstruct the pre-merge state. If an autostash entry is
98+
present, apply it to the worktree.
9899
+
99100
If there were uncommitted worktree changes present when the merge
100101
started, 'git merge --abort' will in some cases be unable to
101102
reconstruct these changes. It is therefore recommended to always
102103
commit or stash your changes before running 'git merge'.
103104
+
104105
'git merge --abort' is equivalent to 'git reset --merge' when
105-
`MERGE_HEAD` is present.
106+
`MERGE_HEAD` is present unless `MERGE_AUTOSTASH` is also present in
107+
which case 'git merge --abort' applies the stash entry to the worktree
108+
whereas 'git reset --merge' will save the stashed changes in the stash
109+
reflog.
106110

107111
--quit::
108112
Forget about the current merge in progress. Leave the index
109-
and the working tree as-is.
113+
and the working tree as-is. If `MERGE_AUTOSTASH` is present, the
114+
stash entry will be saved to the stash reflog.
110115

111116
--continue::
112117
After a 'git merge' stops due to conflicts you can conclude the

Documentation/merge-options.txt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,14 @@ ifndef::git-pull[]
155155
Note that not all merge strategies may support progress
156156
reporting.
157157

158+
--autostash::
159+
--no-autostash::
160+
Automatically create a temporary stash entry before the operation
161+
begins, and apply it after the operation ends. This means
162+
that you can run the operation on a dirty worktree. However, use
163+
with care: the final stash application after a successful
164+
merge might result in non-trivial conflicts.
165+
158166
endif::git-pull[]
159167

160168
--allow-unrelated-histories::

branch.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -344,6 +344,7 @@ void remove_merge_branch_state(struct repository *r)
344344
unlink(git_path_merge_rr(r));
345345
unlink(git_path_merge_msg(r));
346346
unlink(git_path_merge_mode(r));
347+
save_autostash(git_path_merge_autostash(r));
347348
}
348349

349350
void remove_branch_state(struct repository *r, int verbose)

builtin/commit.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1713,6 +1713,8 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
17131713
&oid, flags);
17141714
}
17151715

1716+
apply_autostash(git_path_merge_autostash(the_repository));
1717+
17161718
UNLEAK(err);
17171719
UNLEAK(sb);
17181720
return 0;

builtin/merge.c

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ static int show_progress = -1;
8282
static int default_to_upstream = 1;
8383
static int signoff;
8484
static const char *sign_commit;
85+
static int autostash;
8586
static int no_verify;
8687

8788
static struct strategy all_strategy[] = {
@@ -286,6 +287,7 @@ static struct option builtin_merge_options[] = {
286287
OPT_SET_INT(0, "progress", &show_progress, N_("force progress reporting"), 1),
287288
{ OPTION_STRING, 'S', "gpg-sign", &sign_commit, N_("key-id"),
288289
N_("GPG sign commit"), PARSE_OPT_OPTARG, NULL, (intptr_t) "" },
290+
OPT_AUTOSTASH(&autostash),
289291
OPT_BOOL(0, "overwrite-ignore", &overwrite_ignore, N_("update ignored files (default)")),
290292
OPT_BOOL(0, "signoff", &signoff, N_("add Signed-off-by:")),
291293
OPT_BOOL(0, "no-verify", &no_verify, N_("bypass pre-merge-commit and commit-msg hooks")),
@@ -475,6 +477,7 @@ static void finish(struct commit *head_commit,
475477
/* Run a post-merge hook */
476478
run_hook_le(NULL, "post-merge", squash ? "1" : "0", NULL);
477479

480+
apply_autostash(git_path_merge_autostash(the_repository));
478481
strbuf_release(&reflog_message);
479482
}
480483

@@ -634,6 +637,9 @@ static int git_merge_config(const char *k, const char *v, void *cb)
634637
return 0;
635638
} else if (!strcmp(k, "gpg.mintrustlevel")) {
636639
check_trust_level = 0;
640+
} else if (!strcmp(k, "merge.autostash")) {
641+
autostash = git_config_bool(k, v);
642+
return 0;
637643
}
638644

639645
status = fmt_merge_msg_config(k, v, cb);
@@ -1281,6 +1287,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
12811287
if (abort_current_merge) {
12821288
int nargc = 2;
12831289
const char *nargv[] = {"reset", "--merge", NULL};
1290+
struct strbuf stash_oid = STRBUF_INIT;
12841291

12851292
if (orig_argc != 2)
12861293
usage_msg_opt(_("--abort expects no arguments"),
@@ -1289,8 +1296,17 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
12891296
if (!file_exists(git_path_merge_head(the_repository)))
12901297
die(_("There is no merge to abort (MERGE_HEAD missing)."));
12911298

1299+
if (read_oneliner(&stash_oid, git_path_merge_autostash(the_repository),
1300+
READ_ONELINER_SKIP_IF_EMPTY))
1301+
unlink(git_path_merge_autostash(the_repository));
1302+
12921303
/* Invoke 'git reset --merge' */
12931304
ret = cmd_reset(nargc, nargv, prefix);
1305+
1306+
if (stash_oid.len)
1307+
apply_autostash_oid(stash_oid.buf);
1308+
1309+
strbuf_release(&stash_oid);
12941310
goto done;
12951311
}
12961312

@@ -1513,6 +1529,10 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
15131529
goto done;
15141530
}
15151531

1532+
if (autostash)
1533+
create_autostash(the_repository,
1534+
git_path_merge_autostash(the_repository),
1535+
"merge");
15161536
if (checkout_fast_forward(the_repository,
15171537
&head_commit->object.oid,
15181538
&commit->object.oid,
@@ -1579,6 +1599,11 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
15791599
if (fast_forward == FF_ONLY)
15801600
die(_("Not possible to fast-forward, aborting."));
15811601

1602+
if (autostash)
1603+
create_autostash(the_repository,
1604+
git_path_merge_autostash(the_repository),
1605+
"merge");
1606+
15821607
/* We are going to make a new commit. */
15831608
git_committer_info(IDENT_STRICT);
15841609

builtin/rebase.c

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1376,8 +1376,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
13761376
{ OPTION_STRING, 'S', "gpg-sign", &gpg_sign, N_("key-id"),
13771377
N_("GPG-sign commits"),
13781378
PARSE_OPT_OPTARG, NULL, (intptr_t) "" },
1379-
OPT_BOOL(0, "autostash", &options.autostash,
1380-
N_("automatically stash/stash pop before and after")),
1379+
OPT_AUTOSTASH(&options.autostash),
13811380
OPT_STRING_LIST('x', "exec", &exec, N_("exec"),
13821381
N_("add exec lines after each commit of the "
13831382
"editable list")),

parse-options.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -336,5 +336,6 @@ int parse_opt_passthru_argv(const struct option *, const char *, int);
336336
#define OPT_CLEANUP(v) OPT_STRING(0, "cleanup", v, N_("mode"), N_("how to strip spaces and #comments from message"))
337337
#define OPT_PATHSPEC_FROM_FILE(v) OPT_FILENAME(0, "pathspec-from-file", v, N_("read pathspec from file"))
338338
#define OPT_PATHSPEC_FILE_NUL(v) OPT_BOOL(0, "pathspec-file-nul", v, N_("with --pathspec-from-file, pathspec elements are separated with NUL character"))
339+
#define OPT_AUTOSTASH(v) OPT_BOOL(0, "autostash", v, N_("automatically stash/stash pop before and after"))
339340

340341
#endif

path.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1535,5 +1535,6 @@ REPO_GIT_PATH_FUNC(merge_msg, "MERGE_MSG")
15351535
REPO_GIT_PATH_FUNC(merge_rr, "MERGE_RR")
15361536
REPO_GIT_PATH_FUNC(merge_mode, "MERGE_MODE")
15371537
REPO_GIT_PATH_FUNC(merge_head, "MERGE_HEAD")
1538+
REPO_GIT_PATH_FUNC(merge_autostash, "MERGE_AUTOSTASH")
15381539
REPO_GIT_PATH_FUNC(fetch_head, "FETCH_HEAD")
15391540
REPO_GIT_PATH_FUNC(shallow, "shallow")

path.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -177,11 +177,12 @@ struct path_cache {
177177
const char *merge_rr;
178178
const char *merge_mode;
179179
const char *merge_head;
180+
const char *merge_autostash;
180181
const char *fetch_head;
181182
const char *shallow;
182183
};
183184

184-
#define PATH_CACHE_INIT { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL }
185+
#define PATH_CACHE_INIT { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL }
185186

186187
const char *git_path_cherry_pick_head(struct repository *r);
187188
const char *git_path_revert_head(struct repository *r);
@@ -190,6 +191,7 @@ const char *git_path_merge_msg(struct repository *r);
190191
const char *git_path_merge_rr(struct repository *r);
191192
const char *git_path_merge_mode(struct repository *r);
192193
const char *git_path_merge_head(struct repository *r);
194+
const char *git_path_merge_autostash(struct repository *r);
193195
const char *git_path_fetch_head(struct repository *r);
194196
const char *git_path_shallow(struct repository *r);
195197

0 commit comments

Comments
 (0)