@matheusbsbs / matheus-bagliano / commits / 35d2fffdb8
Fork 0

Provide 'git merge --abort' as a synonym to 'git reset --merge'

Teach 'git merge' the --abort option, which verifies the existence of MERGE_HEAD and then invokes 'git reset --merge' to abort the current in-progress merge and attempt to reconstruct the pre-merge state. The reason for adding this option is to provide a user interface for aborting an in-progress merge that is consistent with the interface for aborting a rebase ('git rebase --abort'), aborting the application of a patch series ('git am --abort'), and aborting an in-progress notes merge ('git notes merge --abort'). The patch includes documentation and testcases that explain and verify the various scenarios in which 'git merge --abort' can run. The testcases also document the cases in which 'git merge --abort' is unable to correctly restore the pre-merge state (look for the '###' comments towards the bottom of t/t7609-merge-abort.sh). This patch has been improved by the following contributions: - Jonathan Nieder: Move test documentation into test_description Thanks-to: Jonathan Nieder <jrnieder@gmail.com> Signed-off-by: Johan Herland <johan@herland.net> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Johan Herland committed Nov 9, 2010 at 22:49 UTC 35d2fffdb857836cc46f5a4ada525a246ddd5e11
3 files changed +351 -3
Documentation/git-merge.txt
+24 -3
@@ -13,6 +13,7 @@ SYNOPSIS
13 [-s <strategy>] [-X <strategy-option>]
14 [--[no-]rerere-autoupdate] [-m <msg>] <commit>...
15 'git merge' <msg> HEAD <commit>...
16 +'git merge' --abort
17
18 DESCRIPTION
19 -----------
@@ -47,6 +48,14 @@ The second syntax (<msg> `HEAD` <commit>...) is supported for
48 historical reasons. Do not use it from the command line or in
49 new scripts. It is the same as `git merge -m <msg> <commit>...`.
50
51 +The third syntax ("`git merge --abort`") can only be run after the
52 +merge has resulted in conflicts. 'git merge --abort' will abort the
53 +merge process and try to reconstruct the pre-merge state. However,
54 +if there were uncommitted changes when the merge started (and
55 +especially if those changes were further modified after the merge
56 +was started), 'git merge --abort' will in some cases be unable to
57 +reconstruct the original (pre-merge) changes. Therefore:
58 +
59 *Warning*: Running 'git merge' with uncommitted changes is
60 discouraged: while possible, it leaves you in a state that is hard to
61 back out of in the case of a conflict.
@@ -72,6 +81,18 @@ include::merge-options.txt[]
81 Allow the rerere mechanism to update the index with the
82 result of auto-conflict resolution if possible.
83
84 +--abort::
85 + Abort the current conflict resolution process, and
86 + try to reconstruct the pre-merge state.
87 ++
88 +If there were uncommitted worktree changes present when the merge
89 +started, 'git merge --abort' will in some cases be unable to
90 +reconstruct these changes. It is therefore recommended to always
91 +commit or stash your changes before running 'git merge'.
92 ++
93 +'git merge --abort' is equivalent to 'git reset --merge' when
94 +`MERGE_HEAD` is present.
95 +
96 <commit>...::
97 Commits, usually other branch heads, to merge into our branch.
98 You need at least one <commit>. Specifying more than one
@@ -142,7 +163,7 @@ happens:
163 i.e. matching `HEAD`.
164
165 If you tried a merge which resulted in complex conflicts and
145 -want to start over, you can recover with `git reset --merge`.
166 +want to start over, you can recover with `git merge --abort`.
167
168 HOW CONFLICTS ARE PRESENTED
169 ---------------------------
@@ -213,8 +234,8 @@ After seeing a conflict, you can do two things:
234
235 * Decide not to merge. The only clean-ups you need are to reset
236 the index file to the `HEAD` commit to reverse 2. and to clean
216 - up working tree changes made by 2. and 3.; `git-reset --hard` can
217 - be used for this.
237 + up working tree changes made by 2. and 3.; `git merge --abort`
238 + can be used for this.
239
240 * Resolve the conflicts. Git will mark the conflicts in
241 the working tree. Edit the files into shape and
builtin/merge.c
+14
@@ -56,6 +56,7 @@ static size_t xopts_nr, xopts_alloc;
56 static const char *branch;
57 static int verbosity;
58 static int allow_rerere_auto;
59 +static int abort_current_merge;
60
61 static struct strategy all_strategy[] = {
62 { "recursive", DEFAULT_TWOHEAD | NO_TRIVIAL },
@@ -194,6 +195,8 @@ static struct option builtin_merge_options[] = {
195 "message to be used for the merge commit (if any)",
196 option_parse_message),
197 OPT__VERBOSITY(&verbosity),
198 + OPT_BOOLEAN(0, "abort", &abort_current_merge,
199 + "abort the current in-progress merge"),
200 OPT_END()
201 };
202
@@ -914,6 +917,17 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
917 argc = parse_options(argc, argv, prefix, builtin_merge_options,
918 builtin_merge_usage, 0);
919
920 + if (abort_current_merge) {
921 + int nargc = 2;
922 + const char *nargv[] = {"reset", "--merge", NULL};
923 +
924 + if (!file_exists(git_path("MERGE_HEAD")))
925 + die("There is no merge to abort (MERGE_HEAD missing).");
926 +
927 + /* Invoke 'git reset --merge' */
928 + return cmd_reset(nargc, nargv, prefix);
929 + }
930 +
931 if (read_cache_unmerged())
932 die_resolve_conflict("merge");
933
t/t7609-merge-abort.sh new
+313
@@ -0,0 +1,313 @@
1 +#!/bin/sh
2 +
3 +test_description='test aborting in-progress merges
4 +
5 +Set up repo with conflicting and non-conflicting branches:
6 +
7 +There are three files foo/bar/baz, and the following graph illustrates the
8 +content of these files in each commit:
9 +
10 +# foo/bar/baz --- foo/bar/bazz <-- master
11 +# \
12 +# --- foo/barf/bazf <-- conflict_branch
13 +# \
14 +# --- foo/bart/baz <-- clean_branch
15 +
16 +Next, test git merge --abort with the following variables:
17 +- before/after successful merge (should fail when not in merge context)
18 +- with/without conflicts
19 +- clean/dirty index before merge
20 +- clean/dirty worktree before merge
21 +- dirty index before merge matches contents on remote branch
22 +- changed/unchanged worktree after merge
23 +- changed/unchanged index after merge
24 +'
25 +. ./test-lib.sh
26 +
27 +test_expect_success 'setup' '
28 + # Create the above repo
29 + echo foo > foo &&
30 + echo bar > bar &&
31 + echo baz > baz &&
32 + git add foo bar baz &&
33 + git commit -m initial &&
34 + echo bazz > baz &&
35 + git commit -a -m "second" &&
36 + git checkout -b conflict_branch HEAD^ &&
37 + echo barf > bar &&
38 + echo bazf > baz &&
39 + git commit -a -m "conflict" &&
40 + git checkout -b clean_branch HEAD^ &&
41 + echo bart > bar &&
42 + git commit -a -m "clean" &&
43 + git checkout master
44 +'
45 +
46 +pre_merge_head="$(git rev-parse HEAD)"
47 +
48 +test_expect_success 'fails without MERGE_HEAD (unstarted merge)' '
49 + test_must_fail git merge --abort 2>output &&
50 + grep -q MERGE_HEAD output &&
51 + test ! -f .git/MERGE_HEAD &&
52 + test "$pre_merge_head" = "$(git rev-parse HEAD)"
53 +'
54 +
55 +test_expect_success 'fails without MERGE_HEAD (completed merge)' '
56 + git merge clean_branch &&
57 + test ! -f .git/MERGE_HEAD &&
58 + # Merge successfully completed
59 + post_merge_head="$(git rev-parse HEAD)" &&
60 + test_must_fail git merge --abort 2>output &&
61 + grep -q MERGE_HEAD output &&
62 + test ! -f .git/MERGE_HEAD &&
63 + test "$post_merge_head" = "$(git rev-parse HEAD)"
64 +'
65 +
66 +test_expect_success 'Forget previous merge' '
67 + git reset --hard "$pre_merge_head"
68 +'
69 +
70 +test_expect_success 'Abort after --no-commit' '
71 + # Redo merge, but stop before creating merge commit
72 + git merge --no-commit clean_branch &&
73 + test -f .git/MERGE_HEAD &&
74 + # Abort non-conflicting merge
75 + git merge --abort &&
76 + test ! -f .git/MERGE_HEAD &&
77 + test "$pre_merge_head" = "$(git rev-parse HEAD)" &&
78 + test -z "$(git diff)" &&
79 + test -z "$(git diff --staged)"
80 +'
81 +
82 +test_expect_success 'Abort after conflicts' '
83 + # Create conflicting merge
84 + test_must_fail git merge conflict_branch &&
85 + test -f .git/MERGE_HEAD &&
86 + # Abort conflicting merge
87 + git merge --abort &&
88 + test ! -f .git/MERGE_HEAD &&
89 + test "$pre_merge_head" = "$(git rev-parse HEAD)" &&
90 + test -z "$(git diff)" &&
91 + test -z "$(git diff --staged)"
92 +'
93 +
94 +test_expect_success 'Clean merge with dirty index fails' '
95 + echo xyzzy >> foo &&
96 + git add foo &&
97 + git diff --staged > expect &&
98 + test_must_fail git merge clean_branch &&
99 + test ! -f .git/MERGE_HEAD &&
100 + test "$pre_merge_head" = "$(git rev-parse HEAD)" &&
101 + test -z "$(git diff)" &&
102 + git diff --staged > actual &&
103 + test_cmp expect actual
104 +'
105 +
106 +test_expect_success 'Conflicting merge with dirty index fails' '
107 + test_must_fail git merge conflict_branch &&
108 + test ! -f .git/MERGE_HEAD &&
109 + test "$pre_merge_head" = "$(git rev-parse HEAD)" &&
110 + test -z "$(git diff)" &&
111 + git diff --staged > actual &&
112 + test_cmp expect actual
113 +'
114 +
115 +test_expect_success 'Reset index (but preserve worktree changes)' '
116 + git reset "$pre_merge_head" &&
117 + git diff > actual &&
118 + test_cmp expect actual
119 +'
120 +
121 +test_expect_success 'Abort clean merge with non-conflicting dirty worktree' '
122 + git merge --no-commit clean_branch &&
123 + test -f .git/MERGE_HEAD &&
124 + # Abort merge
125 + git merge --abort &&
126 + test ! -f .git/MERGE_HEAD &&
127 + test "$pre_merge_head" = "$(git rev-parse HEAD)" &&
128 + test -z "$(git diff --staged)" &&
129 + git diff > actual &&
130 + test_cmp expect actual
131 +'
132 +
133 +test_expect_success 'Abort conflicting merge with non-conflicting dirty worktree' '
134 + test_must_fail git merge conflict_branch &&
135 + test -f .git/MERGE_HEAD &&
136 + # Abort merge
137 + git merge --abort &&
138 + test ! -f .git/MERGE_HEAD &&
139 + test "$pre_merge_head" = "$(git rev-parse HEAD)" &&
140 + test -z "$(git diff --staged)" &&
141 + git diff > actual &&
142 + test_cmp expect actual
143 +'
144 +
145 +test_expect_success 'Reset worktree changes' '
146 + git reset --hard "$pre_merge_head"
147 +'
148 +
149 +test_expect_success 'Fail clean merge with conflicting dirty worktree' '
150 + echo xyzzy >> bar &&
151 + git diff > expect &&
152 + test_must_fail git merge --no-commit clean_branch &&
153 + test ! -f .git/MERGE_HEAD &&
154 + test "$pre_merge_head" = "$(git rev-parse HEAD)" &&
155 + test -z "$(git diff --staged)" &&
156 + git diff > actual &&
157 + test_cmp expect actual
158 +'
159 +
160 +test_expect_success 'Fail conflicting merge with conflicting dirty worktree' '
161 + test_must_fail git merge conflict_branch &&
162 + test ! -f .git/MERGE_HEAD &&
163 + test "$pre_merge_head" = "$(git rev-parse HEAD)" &&
164 + test -z "$(git diff --staged)" &&
165 + git diff > actual &&
166 + test_cmp expect actual
167 +'
168 +
169 +test_expect_success 'Reset worktree changes' '
170 + git reset --hard "$pre_merge_head"
171 +'
172 +
173 +test_expect_success 'Fail clean merge with matching dirty worktree' '
174 + echo bart > bar &&
175 + git diff > expect &&
176 + test_must_fail git merge --no-commit clean_branch &&
177 + test ! -f .git/MERGE_HEAD &&
178 + test "$pre_merge_head" = "$(git rev-parse HEAD)" &&
179 + test -z "$(git diff --staged)" &&
180 + git diff > actual &&
181 + test_cmp expect actual
182 +'
183 +
184 +test_expect_success 'Abort clean merge with matching dirty index' '
185 + git add bar &&
186 + git diff --staged > expect &&
187 + git merge --no-commit clean_branch &&
188 + test -f .git/MERGE_HEAD &&
189 + ### When aborting the merge, git will discard all staged changes,
190 + ### including those that were staged pre-merge. In other words,
191 + ### --abort will LOSE any staged changes (the staged changes that
192 + ### are lost must match the merge result, or the merge would not
193 + ### have been allowed to start). Change expectations accordingly:
194 + rm expect &&
195 + touch expect &&
196 + # Abort merge
197 + git merge --abort &&
198 + test ! -f .git/MERGE_HEAD &&
199 + test "$pre_merge_head" = "$(git rev-parse HEAD)" &&
200 + git diff --staged > actual &&
201 + test_cmp expect actual &&
202 + test -z "$(git diff)"
203 +'
204 +
205 +test_expect_success 'Reset worktree changes' '
206 + git reset --hard "$pre_merge_head"
207 +'
208 +
209 +test_expect_success 'Fail conflicting merge with matching dirty worktree' '
210 + echo barf > bar &&
211 + git diff > expect &&
212 + test_must_fail git merge conflict_branch &&
213 + test ! -f .git/MERGE_HEAD &&
214 + test "$pre_merge_head" = "$(git rev-parse HEAD)" &&
215 + test -z "$(git diff --staged)" &&
216 + git diff > actual &&
217 + test_cmp expect actual
218 +'
219 +
220 +test_expect_success 'Abort conflicting merge with matching dirty index' '
221 + git add bar &&
222 + git diff --staged > expect &&
223 + test_must_fail git merge conflict_branch &&
224 + test -f .git/MERGE_HEAD &&
225 + ### When aborting the merge, git will discard all staged changes,
226 + ### including those that were staged pre-merge. In other words,
227 + ### --abort will LOSE any staged changes (the staged changes that
228 + ### are lost must match the merge result, or the merge would not
229 + ### have been allowed to start). Change expectations accordingly:
230 + rm expect &&
231 + touch expect &&
232 + # Abort merge
233 + git merge --abort &&
234 + test ! -f .git/MERGE_HEAD &&
235 + test "$pre_merge_head" = "$(git rev-parse HEAD)" &&
236 + git diff --staged > actual &&
237 + test_cmp expect actual &&
238 + test -z "$(git diff)"
239 +'
240 +
241 +test_expect_success 'Reset worktree changes' '
242 + git reset --hard "$pre_merge_head"
243 +'
244 +
245 +test_expect_success 'Abort merge with pre- and post-merge worktree changes' '
246 + # Pre-merge worktree changes
247 + echo xyzzy > foo &&
248 + echo barf > bar &&
249 + git add bar &&
250 + git diff > expect &&
251 + git diff --staged > expect-staged &&
252 + # Perform merge
253 + test_must_fail git merge conflict_branch &&
254 + test -f .git/MERGE_HEAD &&
255 + # Post-merge worktree changes
256 + echo yzxxz > foo &&
257 + echo blech > baz &&
258 + ### When aborting the merge, git will discard staged changes (bar)
259 + ### and unmerged changes (baz). Other changes that are neither
260 + ### staged nor marked as unmerged (foo), will be preserved. For
261 + ### these changed, git cannot tell pre-merge changes apart from
262 + ### post-merge changes, so the post-merge changes will be
263 + ### preserved. Change expectations accordingly:
264 + git diff -- foo > expect &&
265 + rm expect-staged &&
266 + touch expect-staged &&
267 + # Abort merge
268 + git merge --abort &&
269 + test ! -f .git/MERGE_HEAD &&
270 + test "$pre_merge_head" = "$(git rev-parse HEAD)" &&
271 + git diff > actual &&
272 + test_cmp expect actual &&
273 + git diff --staged > actual-staged &&
274 + test_cmp expect-staged actual-staged
275 +'
276 +
277 +test_expect_success 'Reset worktree changes' '
278 + git reset --hard "$pre_merge_head"
279 +'
280 +
281 +test_expect_success 'Abort merge with pre- and post-merge index changes' '
282 + # Pre-merge worktree changes
283 + echo xyzzy > foo &&
284 + echo barf > bar &&
285 + git add bar &&
286 + git diff > expect &&
287 + git diff --staged > expect-staged &&
288 + # Perform merge
289 + test_must_fail git merge conflict_branch &&
290 + test -f .git/MERGE_HEAD &&
291 + # Post-merge worktree changes
292 + echo yzxxz > foo &&
293 + echo blech > baz &&
294 + git add foo bar &&
295 + ### When aborting the merge, git will discard all staged changes
296 + ### (foo, bar and baz), and no changes will be preserved. Whether
297 + ### the changes were staged pre- or post-merge does not matter
298 + ### (except for not preventing starting the merge).
299 + ### Change expectations accordingly:
300 + rm expect expect-staged &&
301 + touch expect &&
302 + touch expect-staged &&
303 + # Abort merge
304 + git merge --abort &&
305 + test ! -f .git/MERGE_HEAD &&
306 + test "$pre_merge_head" = "$(git rev-parse HEAD)" &&
307 + git diff > actual &&
308 + test_cmp expect actual &&
309 + git diff --staged > actual-staged &&
310 + test_cmp expect-staged actual-staged
311 +'
312 +
313 +test_done