diff options
author | Junio C Hamano <gitster@pobox.com> | 2008-08-30 07:52:24 -0700 |
---|---|---|
committer | Junio C Hamano <gitster@pobox.com> | 2008-08-30 19:57:55 -0700 |
commit | 0cf8581e330e7140c9f5c94a53d441187c0f8ff9 (patch) | |
tree | a1e46a0198f62914d29e8e1429b8efce58ada4ee /builtin-checkout.c | |
parent | 29a1f99b4b81cd28163de1275265234c5ab804b4 (diff) | |
download | git-0cf8581e330e7140c9f5c94a53d441187c0f8ff9.tar.gz |
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>
Diffstat (limited to 'builtin-checkout.c')
-rw-r--r-- | builtin-checkout.c | 104 |
1 files changed, 102 insertions, 2 deletions
diff --git a/builtin-checkout.c b/builtin-checkout.c index 16bfbb6605..b957193155 100644 --- a/builtin-checkout.c +++ b/builtin-checkout.c @@ -13,6 +13,9 @@ #include "diff.h" #include "revision.h" #include "remote.h" +#include "blob.h" +#include "xdiff-interface.h" +#include "ll-merge.h" static const char * const checkout_usage[] = { "git checkout [options] <branch>", @@ -109,6 +112,19 @@ static int check_stage(int stage, struct cache_entry *ce, int pos) (stage == 2) ? "our" : "their"); } +static int check_all_stages(struct cache_entry *ce, int pos) +{ + if (ce_stage(ce) != 1 || + active_nr <= pos + 2 || + strcmp(active_cache[pos+1]->name, ce->name) || + ce_stage(active_cache[pos+1]) != 2 || + strcmp(active_cache[pos+2]->name, ce->name) || + ce_stage(active_cache[pos+2]) != 3) + return error("path '%s' does not have all three versions", + ce->name); + return 0; +} + static int checkout_stage(int stage, struct cache_entry *ce, int pos, struct checkout *state) { @@ -123,6 +139,77 @@ static int checkout_stage(int stage, struct cache_entry *ce, int pos, (stage == 2) ? "our" : "their"); } +/* NEEDSWORK: share with merge-recursive */ +static void fill_mm(const unsigned char *sha1, mmfile_t *mm) +{ + unsigned long size; + enum object_type type; + + if (!hashcmp(sha1, null_sha1)) { + mm->ptr = xstrdup(""); + mm->size = 0; + return; + } + + mm->ptr = read_sha1_file(sha1, &type, &size); + if (!mm->ptr || type != OBJ_BLOB) + die("unable to read blob object %s", sha1_to_hex(sha1)); + mm->size = size; +} + +static int checkout_merged(int pos, struct checkout *state) +{ + struct cache_entry *ce = active_cache[pos]; + const char *path = ce->name; + mmfile_t ancestor, ours, theirs; + int status; + unsigned char sha1[20]; + mmbuffer_t result_buf; + + if (ce_stage(ce) != 1 || + active_nr <= pos + 2 || + strcmp(active_cache[pos+1]->name, path) || + ce_stage(active_cache[pos+1]) != 2 || + strcmp(active_cache[pos+2]->name, path) || + ce_stage(active_cache[pos+2]) != 3) + return error("path '%s' does not have all 3 versions", path); + + fill_mm(active_cache[pos]->sha1, &ancestor); + fill_mm(active_cache[pos+1]->sha1, &ours); + fill_mm(active_cache[pos+2]->sha1, &theirs); + + status = ll_merge(&result_buf, path, &ancestor, + &ours, "ours", &theirs, "theirs", 1); + free(ancestor.ptr); + free(ours.ptr); + free(theirs.ptr); + if (status < 0 || !result_buf.ptr) { + free(result_buf.ptr); + return error("path '%s': cannot merge", path); + } + + /* + * NEEDSWORK: + * There is absolutely no reason to write this as a blob object + * and create a phoney cache entry just to leak. This hack is + * primarily to get to the write_entry() machinery that massages + * the contents to work-tree format and writes out which only + * allows it for a cache entry. The code in write_entry() needs + * to be refactored to allow us to feed a <buffer, size, mode> + * instead of a cache entry. Such a refactoring would help + * merge_recursive as well (it also writes the merge result to the + * object database even when it may contain conflicts). + */ + if (write_sha1_file(result_buf.ptr, result_buf.size, + blob_type, sha1)) + die("Unable to add merge result for '%s'", path); + ce = make_cache_entry(create_ce_mode(active_cache[pos+1]->ce_mode), + sha1, + path, 2, 0); + status = checkout_entry(ce, state, NULL); + return status; +} + static int checkout_paths(struct tree *source_tree, const char **pathspec, struct checkout_opts *opts) { @@ -134,6 +221,7 @@ static int checkout_paths(struct tree *source_tree, const char **pathspec, struct commit *head; int errs = 0; int stage = opts->writeout_stage; + int merge = opts->merge; int newfd; struct lock_file *lock_file = xcalloc(1, sizeof(struct lock_file)); @@ -165,6 +253,8 @@ static int checkout_paths(struct tree *source_tree, const char **pathspec, warning("path '%s' is unmerged", ce->name); } else if (stage) { errs |= check_stage(stage, ce, pos); + } else if (opts->merge) { + errs |= check_all_stages(ce, pos); } else { errs = 1; error("path '%s' is unmerged", ce->name); @@ -188,6 +278,8 @@ static int checkout_paths(struct tree *source_tree, const char **pathspec, } if (stage) errs |= checkout_stage(stage, ce, pos, &state); + else if (merge) + errs |= checkout_merged(pos, &state); pos = skip_same_name(ce, pos) - 1; } } @@ -476,6 +568,11 @@ static int switch_branches(struct checkout_opts *opts, struct branch_info *new) return ret || opts->writeout_error; } +static int git_checkout_config(const char *var, const char *value, void *cb) +{ + return git_xmerge_config(var, value, cb); +} + int cmd_checkout(int argc, const char **argv, const char *prefix) { struct checkout_opts opts; @@ -502,7 +599,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix) memset(&opts, 0, sizeof(opts)); memset(&new, 0, sizeof(new)); - git_config(git_default_config, NULL); + git_config(git_checkout_config, NULL); opts.track = git_branch_track; @@ -594,7 +691,7 @@ no_reference: die("invalid path specification"); /* Checkout paths */ - if (opts.new_branch || opts.merge) { + if (opts.new_branch) { if (argc == 1) { 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]); } else { @@ -602,6 +699,9 @@ no_reference: } } + if (1 < !!opts.writeout_stage + !!opts.force + !!opts.merge) + die("git checkout: --ours/--theirs, --force and --merge are incompatible when\nchecking out of the index."); + return checkout_paths(source_tree, pathspec, &opts); } |