@matheusbsbs / matheus-bagliano / commits / 0cf8581e33
Fork 0

checkout -m: recreate merge when checking out of unmerged index

This teaches git-checkout to recreate a merge out of unmerged index entries while resolving conflicts. With this patch, checking out an unmerged path from the index now have the following possibilities: * Without any option, an attempt to checkout an unmerged path will atomically fail (i.e. no other cleanly-merged paths are checked out either); * With "-f", other cleanly-merged paths are checked out, and unmerged paths are ignored; * With "--ours" or "--theirs, the contents from the specified stage is checked out; * With "-m" (we should add "--merge" as synonym), the 3-way merge is recreated from the staged object names and checked out. Signed-off-by: Junio C Hamano <gitster@pobox.com>

Junio C Hamano committed Aug 30, 2008 at 07:52 UTC 0cf8581e330e7140c9f5c94a53d441187c0f8ff9
3 files changed +173 -5
Documentation/git-checkout.txt
+8 -3
@@ -9,7 +9,7 @@ SYNOPSIS
9 --------
10 [verse]
11 'git checkout' [-q] [-f] [[--track | --no-track] -b <new_branch> [-l]] [-m] [<branch>]
12 -'git checkout' [-f|--ours|--theirs] [<tree-ish>] [--] <paths>...
12 +'git checkout' [-f|--ours|--theirs|-m] [<tree-ish>] [--] <paths>...
13
14 DESCRIPTION
15 -----------
@@ -35,7 +35,8 @@ default, if you try to check out such an entry from the index, the
35 checkout operation will fail and nothing will be checked out.
36 Using -f will ignore these unmerged entries. The contents from a
37 specific side of the merge can be checked out of the index by
38 -using --ours or --theirs.
38 +using --ours or --theirs. With -m, changes made to the working tree
39 +file can be discarded to recreate the original conflicted merge result.
40
41 OPTIONS
42 -------
@@ -83,7 +84,8 @@ entries; instead, unmerged entries are ignored.
84 based sha1 expressions such as "<branchname>@\{yesterday}".
85
86 -m::
86 - If you have local modifications to one or more files that
87 + When switching branches,
88 + if you have local modifications to one or more files that
89 are different between the current branch and the branch to
90 which you are switching, the command refuses to switch
91 branches in order to preserve your modifications in context.
@@ -95,6 +97,9 @@ When a merge conflict happens, the index entries for conflicting
97 paths are left unmerged, and you need to resolve the conflicts
98 and mark the resolved paths with `git add` (or `git rm` if the merge
99 should result in deletion of the path).
100 ++
101 +When checking out paths from the index, this option lets you recreate
102 +the conflicted merge in the specified paths.
103
104 <new_branch>::
105 Name for the new branch.
builtin-checkout.c
+102 -2
@@ -13,6 +13,9 @@
13 #include "diff.h"
14 #include "revision.h"
15 #include "remote.h"
16 +#include "blob.h"
17 +#include "xdiff-interface.h"
18 +#include "ll-merge.h"
19
20 static const char * const checkout_usage[] = {
21 "git checkout [options] <branch>",
@@ -109,6 +112,19 @@ static int check_stage(int stage, struct cache_entry *ce, int pos)
112 (stage == 2) ? "our" : "their");
113 }
114
115 +static int check_all_stages(struct cache_entry *ce, int pos)
116 +{
117 + if (ce_stage(ce) != 1 ||
118 + active_nr <= pos + 2 ||
119 + strcmp(active_cache[pos+1]->name, ce->name) ||
120 + ce_stage(active_cache[pos+1]) != 2 ||
121 + strcmp(active_cache[pos+2]->name, ce->name) ||
122 + ce_stage(active_cache[pos+2]) != 3)
123 + return error("path '%s' does not have all three versions",
124 + ce->name);
125 + return 0;
126 +}
127 +
128 static int checkout_stage(int stage, struct cache_entry *ce, int pos,
129 struct checkout *state)
130 {
@@ -123,6 +139,77 @@ static int checkout_stage(int stage, struct cache_entry *ce, int pos,
139 (stage == 2) ? "our" : "their");
140 }
141
142 +/* NEEDSWORK: share with merge-recursive */
143 +static void fill_mm(const unsigned char *sha1, mmfile_t *mm)
144 +{
145 + unsigned long size;
146 + enum object_type type;
147 +
148 + if (!hashcmp(sha1, null_sha1)) {
149 + mm->ptr = xstrdup("");
150 + mm->size = 0;
151 + return;
152 + }
153 +
154 + mm->ptr = read_sha1_file(sha1, &type, &size);
155 + if (!mm->ptr || type != OBJ_BLOB)
156 + die("unable to read blob object %s", sha1_to_hex(sha1));
157 + mm->size = size;
158 +}
159 +
160 +static int checkout_merged(int pos, struct checkout *state)
161 +{
162 + struct cache_entry *ce = active_cache[pos];
163 + const char *path = ce->name;
164 + mmfile_t ancestor, ours, theirs;
165 + int status;
166 + unsigned char sha1[20];
167 + mmbuffer_t result_buf;
168 +
169 + if (ce_stage(ce) != 1 ||
170 + active_nr <= pos + 2 ||
171 + strcmp(active_cache[pos+1]->name, path) ||
172 + ce_stage(active_cache[pos+1]) != 2 ||
173 + strcmp(active_cache[pos+2]->name, path) ||
174 + ce_stage(active_cache[pos+2]) != 3)
175 + return error("path '%s' does not have all 3 versions", path);
176 +
177 + fill_mm(active_cache[pos]->sha1, &ancestor);
178 + fill_mm(active_cache[pos+1]->sha1, &ours);
179 + fill_mm(active_cache[pos+2]->sha1, &theirs);
180 +
181 + status = ll_merge(&result_buf, path, &ancestor,
182 + &ours, "ours", &theirs, "theirs", 1);
183 + free(ancestor.ptr);
184 + free(ours.ptr);
185 + free(theirs.ptr);
186 + if (status < 0 || !result_buf.ptr) {
187 + free(result_buf.ptr);
188 + return error("path '%s': cannot merge", path);
189 + }
190 +
191 + /*
192 + * NEEDSWORK:
193 + * There is absolutely no reason to write this as a blob object
194 + * and create a phoney cache entry just to leak. This hack is
195 + * primarily to get to the write_entry() machinery that massages
196 + * the contents to work-tree format and writes out which only
197 + * allows it for a cache entry. The code in write_entry() needs
198 + * to be refactored to allow us to feed a <buffer, size, mode>
199 + * instead of a cache entry. Such a refactoring would help
200 + * merge_recursive as well (it also writes the merge result to the
201 + * object database even when it may contain conflicts).
202 + */
203 + if (write_sha1_file(result_buf.ptr, result_buf.size,
204 + blob_type, sha1))
205 + die("Unable to add merge result for '%s'", path);
206 + ce = make_cache_entry(create_ce_mode(active_cache[pos+1]->ce_mode),
207 + sha1,
208 + path, 2, 0);
209 + status = checkout_entry(ce, state, NULL);
210 + return status;
211 +}
212 +
213 static int checkout_paths(struct tree *source_tree, const char **pathspec,
214 struct checkout_opts *opts)
215 {
@@ -134,6 +221,7 @@ static int checkout_paths(struct tree *source_tree, const char **pathspec,
221 struct commit *head;
222 int errs = 0;
223 int stage = opts->writeout_stage;
224 + int merge = opts->merge;
225 int newfd;
226 struct lock_file *lock_file = xcalloc(1, sizeof(struct lock_file));
227
@@ -165,6 +253,8 @@ static int checkout_paths(struct tree *source_tree, const char **pathspec,
253 warning("path '%s' is unmerged", ce->name);
254 } else if (stage) {
255 errs |= check_stage(stage, ce, pos);
256 + } else if (opts->merge) {
257 + errs |= check_all_stages(ce, pos);
258 } else {
259 errs = 1;
260 error("path '%s' is unmerged", ce->name);
@@ -188,6 +278,8 @@ static int checkout_paths(struct tree *source_tree, const char **pathspec,
278 }
279 if (stage)
280 errs |= checkout_stage(stage, ce, pos, &state);
281 + else if (merge)
282 + errs |= checkout_merged(pos, &state);
283 pos = skip_same_name(ce, pos) - 1;
284 }
285 }
@@ -476,6 +568,11 @@ static int switch_branches(struct checkout_opts *opts, struct branch_info *new)
568 return ret || opts->writeout_error;
569 }
570
571 +static int git_checkout_config(const char *var, const char *value, void *cb)
572 +{
573 + return git_xmerge_config(var, value, cb);
574 +}
575 +
576 int cmd_checkout(int argc, const char **argv, const char *prefix)
577 {
578 struct checkout_opts opts;
@@ -502,7 +599,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
599 memset(&opts, 0, sizeof(opts));
600 memset(&new, 0, sizeof(new));
601
505 - git_config(git_default_config, NULL);
602 + git_config(git_checkout_config, NULL);
603
604 opts.track = git_branch_track;
605
@@ -594,7 +691,7 @@ no_reference:
691 die("invalid path specification");
692
693 /* Checkout paths */
597 - if (opts.new_branch || opts.merge) {
694 + if (opts.new_branch) {
695 if (argc == 1) {
696 die("git checkout: updating paths is incompatible with switching branches.\nDid you intend to checkout '%s' which can not be resolved as commit?", argv[0]);
697 } else {
@@ -602,6 +699,9 @@ no_reference:
699 }
700 }
701
702 + if (1 < !!opts.writeout_stage + !!opts.force + !!opts.merge)
703 + die("git checkout: --ours/--theirs, --force and --merge are incompatible when\nchecking out of the index.");
704 +
705 return checkout_paths(source_tree, pathspec, &opts);
706 }
707
t/t7201-co.sh
+63
@@ -407,4 +407,67 @@ test_expect_success 'checkout unmerged stage' '
407 test ztheirside = "z$(cat file)"
408 '
409
410 +test_expect_success 'checkout with --merge' '
411 + rm -f .git/index &&
412 + O=$(echo original | git hash-object -w --stdin) &&
413 + A=$(echo ourside | git hash-object -w --stdin) &&
414 + B=$(echo theirside | git hash-object -w --stdin) &&
415 + (
416 + echo "100644 $A 0 fild" &&
417 + echo "100644 $O 1 file" &&
418 + echo "100644 $A 2 file" &&
419 + echo "100644 $B 3 file" &&
420 + echo "100644 $A 0 filf"
421 + ) | git update-index --index-info &&
422 + echo "none of the above" >sample &&
423 + echo ourside >expect &&
424 + cat sample >fild &&
425 + cat sample >file &&
426 + cat sample >filf &&
427 + git checkout -m -- fild file filf &&
428 + (
429 + echo "<<<<<<< ours"
430 + echo ourside
431 + echo "======="
432 + echo theirside
433 + echo ">>>>>>> theirs"
434 + ) >merged &&
435 + test_cmp expect fild &&
436 + test_cmp expect filf &&
437 + test_cmp merged file
438 +'
439 +
440 +test_expect_success 'checkout with --merge, in diff3 -m style' '
441 + git config merge.conflictstyle diff3 &&
442 + rm -f .git/index &&
443 + O=$(echo original | git hash-object -w --stdin) &&
444 + A=$(echo ourside | git hash-object -w --stdin) &&
445 + B=$(echo theirside | git hash-object -w --stdin) &&
446 + (
447 + echo "100644 $A 0 fild" &&
448 + echo "100644 $O 1 file" &&
449 + echo "100644 $A 2 file" &&
450 + echo "100644 $B 3 file" &&
451 + echo "100644 $A 0 filf"
452 + ) | git update-index --index-info &&
453 + echo "none of the above" >sample &&
454 + echo ourside >expect &&
455 + cat sample >fild &&
456 + cat sample >file &&
457 + cat sample >filf &&
458 + git checkout -m -- fild file filf &&
459 + (
460 + echo "<<<<<<< ours"
461 + echo ourside
462 + echo "|||||||"
463 + echo original
464 + echo "======="
465 + echo theirside
466 + echo ">>>>>>> theirs"
467 + ) >merged &&
468 + test_cmp expect fild &&
469 + test_cmp expect filf &&
470 + test_cmp merged file
471 +'
472 +
473 test_done