Merge branch 'jc/better-conflict-resolution'
* jc/better-conflict-resolution: Fix AsciiDoc errors in merge documentation git-merge documentation: describe how conflict is presented checkout --conflict=<style>: recreate merge in a non-default style checkout -m: recreate merge when checking out of unmerged index git-merge-recursive: learn to honor merge.conflictstyle merge.conflictstyle: choose between "merge" and "diff3 -m" styles rerere: understand "diff3 -m" style conflicts with the original rerere.c: use symbolic constants to keep track of parsing states xmerge.c: "diff3 -m" style clips merge reduction level to EAGER or less xmerge.c: minimum readability fixups xdiff-merge: optionally show conflicts in "diff3 -m" style xdl_fill_merge_buffer(): separate out a too deeply nested function checkout --ours/--theirs: allow checking out one side of a conflicting merge checkout -f: allow ignoring unmerged paths when checking out of the index Conflicts: Documentation/git-checkout.txt builtin-checkout.c builtin-merge-recursive.c t/t7201-co.sh
This commit is contained in:
@ -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>",
|
||||
@ -20,6 +23,18 @@ static const char * const checkout_usage[] = {
|
||||
NULL,
|
||||
};
|
||||
|
||||
struct checkout_opts {
|
||||
int quiet;
|
||||
int merge;
|
||||
int force;
|
||||
int writeout_stage;
|
||||
int writeout_error;
|
||||
|
||||
const char *new_branch;
|
||||
int new_branch_log;
|
||||
enum branch_track track;
|
||||
};
|
||||
|
||||
static int post_checkout_hook(struct commit *old, struct commit *new,
|
||||
int changed)
|
||||
{
|
||||
@ -84,8 +99,119 @@ static int skip_same_name(struct cache_entry *ce, int pos)
|
||||
return pos;
|
||||
}
|
||||
|
||||
static int check_stage(int stage, struct cache_entry *ce, int pos)
|
||||
{
|
||||
while (pos < active_nr &&
|
||||
!strcmp(active_cache[pos]->name, ce->name)) {
|
||||
if (ce_stage(active_cache[pos]) == stage)
|
||||
return 0;
|
||||
pos++;
|
||||
}
|
||||
return error("path '%s' does not have %s version",
|
||||
ce->name,
|
||||
(stage == 2) ? "our" : "their");
|
||||
}
|
||||
|
||||
static int checkout_paths(struct tree *source_tree, const char **pathspec)
|
||||
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)
|
||||
{
|
||||
while (pos < active_nr &&
|
||||
!strcmp(active_cache[pos]->name, ce->name)) {
|
||||
if (ce_stage(active_cache[pos]) == stage)
|
||||
return checkout_entry(active_cache[pos], state, NULL);
|
||||
pos++;
|
||||
}
|
||||
return error("path '%s' does not have %s version",
|
||||
ce->name,
|
||||
(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)
|
||||
{
|
||||
int pos;
|
||||
struct checkout state;
|
||||
@ -94,7 +220,8 @@ static int checkout_paths(struct tree *source_tree, const char **pathspec)
|
||||
int flag;
|
||||
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));
|
||||
|
||||
@ -122,8 +249,16 @@ static int checkout_paths(struct tree *source_tree, const char **pathspec)
|
||||
if (pathspec_match(pathspec, NULL, ce->name, 0)) {
|
||||
if (!ce_stage(ce))
|
||||
continue;
|
||||
errs = 1;
|
||||
error("path '%s' is unmerged", ce->name);
|
||||
if (opts->force) {
|
||||
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);
|
||||
}
|
||||
pos = skip_same_name(ce, pos) - 1;
|
||||
}
|
||||
}
|
||||
@ -141,6 +276,10 @@ static int checkout_paths(struct tree *source_tree, const char **pathspec)
|
||||
errs |= checkout_entry(ce, &state, NULL);
|
||||
continue;
|
||||
}
|
||||
if (stage)
|
||||
errs |= checkout_stage(stage, ce, pos, &state);
|
||||
else if (merge)
|
||||
errs |= checkout_merged(pos, &state);
|
||||
pos = skip_same_name(ce, pos) - 1;
|
||||
}
|
||||
}
|
||||
@ -178,17 +317,6 @@ static void describe_detached_head(char *msg, struct commit *commit)
|
||||
strbuf_release(&sb);
|
||||
}
|
||||
|
||||
struct checkout_opts {
|
||||
int quiet;
|
||||
int merge;
|
||||
int force;
|
||||
int writeout_error;
|
||||
|
||||
const char *new_branch;
|
||||
int new_branch_log;
|
||||
enum branch_track track;
|
||||
};
|
||||
|
||||
static int reset_tree(struct tree *tree, struct checkout_opts *o, int worktree)
|
||||
{
|
||||
struct unpack_trees_options opts;
|
||||
@ -445,6 +573,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;
|
||||
@ -452,14 +585,21 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
|
||||
const char *arg;
|
||||
struct branch_info new;
|
||||
struct tree *source_tree = NULL;
|
||||
char *conflict_style = NULL;
|
||||
struct option options[] = {
|
||||
OPT__QUIET(&opts.quiet),
|
||||
OPT_STRING('b', NULL, &opts.new_branch, "new branch", "branch"),
|
||||
OPT_BOOLEAN('l', NULL, &opts.new_branch_log, "log for new branch"),
|
||||
OPT_SET_INT('t', "track", &opts.track, "track",
|
||||
BRANCH_TRACK_EXPLICIT),
|
||||
OPT_SET_INT('2', "ours", &opts.writeout_stage, "stage",
|
||||
2),
|
||||
OPT_SET_INT('3', "theirs", &opts.writeout_stage, "stage",
|
||||
3),
|
||||
OPT_BOOLEAN('f', NULL, &opts.force, "force"),
|
||||
OPT_BOOLEAN('m', NULL, &opts.merge, "merge"),
|
||||
OPT_BOOLEAN('m', "merge", &opts.merge, "merge"),
|
||||
OPT_STRING(0, "conflict", &conflict_style, "style",
|
||||
"conflict style (merge or diff3)"),
|
||||
OPT_END(),
|
||||
};
|
||||
int has_dash_dash;
|
||||
@ -467,7 +607,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 = BRANCH_TRACK_UNSPECIFIED;
|
||||
|
||||
@ -491,6 +631,13 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
|
||||
|
||||
if (opts.track == BRANCH_TRACK_UNSPECIFIED)
|
||||
opts.track = git_branch_track;
|
||||
if (conflict_style) {
|
||||
opts.merge = 1; /* implied */
|
||||
git_xmerge_config("merge.conflictstyle", conflict_style, NULL);
|
||||
}
|
||||
|
||||
if (!opts.new_branch && (opts.track != git_branch_track))
|
||||
die("git checkout: --track and --no-track require -b");
|
||||
|
||||
if (opts.force && opts.merge)
|
||||
die("git checkout: -f and -m are incompatible");
|
||||
@ -574,15 +721,18 @@ no_reference:
|
||||
die("invalid path specification");
|
||||
|
||||
/* Checkout paths */
|
||||
if (opts.new_branch || opts.force || opts.merge) {
|
||||
if (opts.new_branch) {
|
||||
if (argc == 1) {
|
||||
die("git checkout: updating paths is incompatible with switching branches/forcing\nDid you intend to checkout '%s' which can not be resolved as commit?", argv[0]);
|
||||
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 {
|
||||
die("git checkout: updating paths is incompatible with switching branches/forcing");
|
||||
die("git checkout: updating paths is incompatible with switching branches.");
|
||||
}
|
||||
}
|
||||
|
||||
return checkout_paths(source_tree, pathspec);
|
||||
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);
|
||||
}
|
||||
|
||||
if (opts.new_branch) {
|
||||
@ -600,6 +750,8 @@ no_reference:
|
||||
if (new.name && !new.commit) {
|
||||
die("Cannot switch branch to a non-commit.");
|
||||
}
|
||||
if (opts.writeout_stage)
|
||||
die("--ours/--theirs is incompatible with switching branches.");
|
||||
|
||||
return switch_branches(&opts, &new);
|
||||
}
|
||||
|
Reference in New Issue
Block a user