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:
commit
9ba929ed65
@ -927,6 +927,14 @@ man.<tool>.path::
|
||||
Override the path for the given tool that may be used to
|
||||
display help in the 'man' format. See linkgit:git-help[1].
|
||||
|
||||
merge.conflictstyle::
|
||||
Specify the style in which conflicted hunks are written out to
|
||||
working tree files upon merge. The default is "merge", which
|
||||
shows `<<<<<<<` conflict marker, change made by one side,
|
||||
`=======` marker, change made by the other side, and then
|
||||
`>>>>>>>` marker. An alternate style, "diff3", adds `|||||||`
|
||||
marker and the original text before `=======` marker.
|
||||
|
||||
mergetool.<tool>.path::
|
||||
Override the path for the given tool. This is useful in case
|
||||
your tool is not in the PATH.
|
||||
|
@ -9,7 +9,7 @@ SYNOPSIS
|
||||
--------
|
||||
[verse]
|
||||
'git checkout' [-q] [-f] [--track | --no-track] [-b <new_branch> [-l]] [-m] [<branch>]
|
||||
'git checkout' [<tree-ish>] [--] <paths>...
|
||||
'git checkout' [-f|--ours|--theirs|-m|--conflict=<style>] [<tree-ish>] [--] <paths>...
|
||||
|
||||
DESCRIPTION
|
||||
-----------
|
||||
@ -27,14 +27,20 @@ the first namespace level.
|
||||
|
||||
When <paths> are given, this command does *not* switch
|
||||
branches. It updates the named paths in the working tree from
|
||||
the index file (i.e. it runs `git checkout-index -f -u`), or
|
||||
from a named commit. In
|
||||
this case, the `-f` and `-b` options are meaningless and giving
|
||||
the index file, or from a named commit. In
|
||||
this case, the `-b` options is meaningless and giving
|
||||
either of them results in an error. <tree-ish> argument can be
|
||||
used to specify a specific tree-ish (i.e. commit, tag or tree)
|
||||
to update the index for the given paths before updating the
|
||||
working tree.
|
||||
|
||||
The index may contain unmerged entries after a failed merge. By
|
||||
default, if you try to check out such an entry from the index, the
|
||||
checkout operation will fail and nothing will be checked out.
|
||||
Using -f will ignore these unmerged entries. The contents from a
|
||||
specific side of the merge can be checked out of the index by
|
||||
using --ours or --theirs. With -m, changes made to the working tree
|
||||
file can be discarded to recreate the original conflicted merge result.
|
||||
|
||||
OPTIONS
|
||||
-------
|
||||
@ -42,8 +48,17 @@ OPTIONS
|
||||
Quiet, suppress feedback messages.
|
||||
|
||||
-f::
|
||||
Proceed even if the index or the working tree differs
|
||||
from HEAD. This is used to throw away local changes.
|
||||
When switching branches, proceed even if the index or the
|
||||
working tree differs from HEAD. This is used to throw away
|
||||
local changes.
|
||||
+
|
||||
When checking out paths from the index, do not fail upon unmerged
|
||||
entries; instead, unmerged entries are ignored.
|
||||
|
||||
--ours::
|
||||
--theirs::
|
||||
When checking out paths from the index, check out stage #2
|
||||
('ours') or #3 ('theirs') for unmerged paths.
|
||||
|
||||
-b::
|
||||
Create a new branch named <new_branch> and start it at
|
||||
@ -84,7 +99,9 @@ exlicitly give a name with '-b' in such a case.
|
||||
based sha1 expressions such as "<branchname>@\{yesterday}".
|
||||
|
||||
-m::
|
||||
If you have local modifications to one or more files that
|
||||
--merge::
|
||||
When switching branches,
|
||||
if you have local modifications to one or more files that
|
||||
are different between the current branch and the branch to
|
||||
which you are switching, the command refuses to switch
|
||||
branches in order to preserve your modifications in context.
|
||||
@ -96,6 +113,16 @@ When a merge conflict happens, the index entries for conflicting
|
||||
paths are left unmerged, and you need to resolve the conflicts
|
||||
and mark the resolved paths with `git add` (or `git rm` if the merge
|
||||
should result in deletion of the path).
|
||||
+
|
||||
When checking out paths from the index, this option lets you recreate
|
||||
the conflicted merge in the specified paths.
|
||||
|
||||
--conflict=<style>::
|
||||
The same as --merge option above, but changes the way the
|
||||
conflicting hunks are presented, overriding the
|
||||
merge.conflictstyle configuration variable. Possible values are
|
||||
"merge" (default) and "diff3" (in addition to what is shown by
|
||||
"merge" style, shows the original contents).
|
||||
|
||||
<new_branch>::
|
||||
Name for the new branch.
|
||||
|
@ -119,6 +119,71 @@ When there are conflicts, these things happen:
|
||||
same and the index entries for them stay as they were,
|
||||
i.e. matching `HEAD`.
|
||||
|
||||
HOW CONFLICTS ARE PRESENTED
|
||||
---------------------------
|
||||
|
||||
During a merge, the working tree files are updated to reflect the result
|
||||
of the merge. Among the changes made to the common ancestor's version,
|
||||
non-overlapping ones (that is, you changed an area of the file while the
|
||||
other side left that area intact, or vice versa) are incorporated in the
|
||||
final result verbatim. When both sides made changes to the same area,
|
||||
however, git cannot randomly pick one side over the other, and asks you to
|
||||
resolve it by leaving what both sides did to that area.
|
||||
|
||||
By default, git uses the same style as that is used by "merge" program
|
||||
from the RCS suite to present such a conflicted hunk, like this:
|
||||
|
||||
------------
|
||||
Here are lines that are either unchanged from the common
|
||||
ancestor, or cleanly resolved because only one side changed.
|
||||
<<<<<<< yours:sample.txt
|
||||
Conflict resolution is hard;
|
||||
let's go shopping.
|
||||
=======
|
||||
Git makes conflict resolution easy.
|
||||
>>>>>>> theirs:sample.txt
|
||||
And here is another line that is cleanly resolved or unmodified.
|
||||
------------
|
||||
|
||||
The area a pair of conflicting changes happened is marked with markers
|
||||
"`<<<<<<<`", "`=======`", and "`>>>>>>>`". The part before the "`=======`"
|
||||
is typically your side, and the part after it is typically their side.
|
||||
|
||||
The default format does not show what the original said in the conflicted
|
||||
area. You cannot tell how many lines are deleted and replaced with the
|
||||
Barbie's remark by your side. The only thing you can tell is that your
|
||||
side wants to say it is hard and you'd prefer to go shopping, while the
|
||||
other side wants to claim it is easy.
|
||||
|
||||
An alternative style can be used by setting the "merge.conflictstyle"
|
||||
configuration variable to "diff3". In "diff3" style, the above conflict
|
||||
may look like this:
|
||||
|
||||
------------
|
||||
Here are lines that are either unchanged from the common
|
||||
ancestor, or cleanly resolved because only one side changed.
|
||||
<<<<<<< yours:sample.txt
|
||||
Conflict resolution is hard;
|
||||
let's go shopping.
|
||||
|||||||
|
||||
Conflict resolution is hard.
|
||||
=======
|
||||
Git makes conflict resolution easy.
|
||||
>>>>>>> theirs:sample.txt
|
||||
And here is another line that is cleanly resolved or unmodified.
|
||||
------------
|
||||
|
||||
In addition to the "`<<<<<<<`", "`=======`", and "`>>>>>>>`" markers, it uses
|
||||
another "`|||||||`" marker that is followed by the original text. You can
|
||||
tell that the original just stated a fact, and your side simply gave in to
|
||||
that statement and gave up, while the other side tried to have a more
|
||||
positive attitude. You can sometimes come up with a better resolution by
|
||||
viewing the original.
|
||||
|
||||
|
||||
HOW TO RESOLVE CONFLICTS
|
||||
------------------------
|
||||
|
||||
After seeing a conflict, you can do two things:
|
||||
|
||||
* Decide not to merge. The only clean-up you need are to reset
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -4,7 +4,7 @@
|
||||
#include "xdiff-interface.h"
|
||||
|
||||
static const char merge_file_usage[] =
|
||||
"git merge-file [-p | --stdout] [-q | --quiet] [-L name1 [-L orig [-L name2]]] file1 orig_file file2";
|
||||
"git merge-file [-p | --stdout] [--diff3] [-q | --quiet] [-L name1 [-L orig [-L name2]]] file1 orig_file file2";
|
||||
|
||||
int cmd_merge_file(int argc, const char **argv, const char *prefix)
|
||||
{
|
||||
@ -13,6 +13,17 @@ int cmd_merge_file(int argc, const char **argv, const char *prefix)
|
||||
mmbuffer_t result = {NULL, 0};
|
||||
xpparam_t xpp = {XDF_NEED_MINIMAL};
|
||||
int ret = 0, i = 0, to_stdout = 0;
|
||||
int merge_level = XDL_MERGE_ZEALOUS_ALNUM;
|
||||
int merge_style = 0;
|
||||
int nongit;
|
||||
|
||||
prefix = setup_git_directory_gently(&nongit);
|
||||
if (!nongit) {
|
||||
/* Read the configuration file */
|
||||
git_config(git_xmerge_config, NULL);
|
||||
if (0 <= git_xmerge_style)
|
||||
merge_style = git_xmerge_style;
|
||||
}
|
||||
|
||||
while (argc > 4) {
|
||||
if (!strcmp(argv[1], "-L") && i < 3) {
|
||||
@ -25,6 +36,8 @@ int cmd_merge_file(int argc, const char **argv, const char *prefix)
|
||||
else if (!strcmp(argv[1], "-q") ||
|
||||
!strcmp(argv[1], "--quiet"))
|
||||
freopen("/dev/null", "w", stderr);
|
||||
else if (!strcmp(argv[1], "--diff3"))
|
||||
merge_style = XDL_MERGE_DIFF3;
|
||||
else
|
||||
usage(merge_file_usage);
|
||||
argc--;
|
||||
@ -46,7 +59,7 @@ int cmd_merge_file(int argc, const char **argv, const char *prefix)
|
||||
}
|
||||
|
||||
ret = xdl_merge(mmfs + 1, mmfs + 0, names[0], mmfs + 2, names[2],
|
||||
&xpp, XDL_MERGE_ZEALOUS_ALNUM, &result);
|
||||
&xpp, merge_level | merge_style, &result);
|
||||
|
||||
for (i = 0; i < 3; i++)
|
||||
free(mmfs[i].ptr);
|
||||
|
16
ll-merge.c
16
ll-merge.c
@ -63,6 +63,7 @@ static int ll_xdl_merge(const struct ll_merge_driver *drv_unused,
|
||||
int virtual_ancestor)
|
||||
{
|
||||
xpparam_t xpp;
|
||||
int style = 0;
|
||||
|
||||
if (buffer_is_binary(orig->ptr, orig->size) ||
|
||||
buffer_is_binary(src1->ptr, src1->size) ||
|
||||
@ -77,10 +78,12 @@ static int ll_xdl_merge(const struct ll_merge_driver *drv_unused,
|
||||
}
|
||||
|
||||
memset(&xpp, 0, sizeof(xpp));
|
||||
if (git_xmerge_style >= 0)
|
||||
style = git_xmerge_style;
|
||||
return xdl_merge(orig,
|
||||
src1, name1,
|
||||
src2, name2,
|
||||
&xpp, XDL_MERGE_ZEALOUS,
|
||||
&xpp, XDL_MERGE_ZEALOUS | style,
|
||||
result);
|
||||
}
|
||||
|
||||
@ -95,10 +98,15 @@ static int ll_union_merge(const struct ll_merge_driver *drv_unused,
|
||||
char *src, *dst;
|
||||
long size;
|
||||
const int marker_size = 7;
|
||||
int status, saved_style;
|
||||
|
||||
int status = ll_xdl_merge(drv_unused, result, path_unused,
|
||||
orig, src1, NULL, src2, NULL,
|
||||
virtual_ancestor);
|
||||
/* We have to force the RCS "merge" style */
|
||||
saved_style = git_xmerge_style;
|
||||
git_xmerge_style = 0;
|
||||
status = ll_xdl_merge(drv_unused, result, path_unused,
|
||||
orig, src1, NULL, src2, NULL,
|
||||
virtual_ancestor);
|
||||
git_xmerge_style = saved_style;
|
||||
if (status <= 0)
|
||||
return status;
|
||||
size = result->size;
|
||||
|
29
rerere.c
29
rerere.c
@ -75,7 +75,10 @@ static int handle_file(const char *path,
|
||||
{
|
||||
SHA_CTX ctx;
|
||||
char buf[1024];
|
||||
int hunk = 0, hunk_no = 0;
|
||||
int hunk_no = 0;
|
||||
enum {
|
||||
RR_CONTEXT = 0, RR_SIDE_1, RR_SIDE_2, RR_ORIGINAL,
|
||||
} hunk = RR_CONTEXT;
|
||||
struct strbuf one, two;
|
||||
FILE *f = fopen(path, "r");
|
||||
FILE *out = NULL;
|
||||
@ -98,20 +101,24 @@ static int handle_file(const char *path,
|
||||
strbuf_init(&two, 0);
|
||||
while (fgets(buf, sizeof(buf), f)) {
|
||||
if (!prefixcmp(buf, "<<<<<<< ")) {
|
||||
if (hunk)
|
||||
if (hunk != RR_CONTEXT)
|
||||
goto bad;
|
||||
hunk = 1;
|
||||
hunk = RR_SIDE_1;
|
||||
} else if (!prefixcmp(buf, "|||||||") && isspace(buf[7])) {
|
||||
if (hunk != RR_SIDE_1)
|
||||
goto bad;
|
||||
hunk = RR_ORIGINAL;
|
||||
} else if (!prefixcmp(buf, "=======") && isspace(buf[7])) {
|
||||
if (hunk != 1)
|
||||
if (hunk != RR_SIDE_1 && hunk != RR_ORIGINAL)
|
||||
goto bad;
|
||||
hunk = 2;
|
||||
hunk = RR_SIDE_2;
|
||||
} else if (!prefixcmp(buf, ">>>>>>> ")) {
|
||||
if (hunk != 2)
|
||||
if (hunk != RR_SIDE_2)
|
||||
goto bad;
|
||||
if (strbuf_cmp(&one, &two) > 0)
|
||||
strbuf_swap(&one, &two);
|
||||
hunk_no++;
|
||||
hunk = 0;
|
||||
hunk = RR_CONTEXT;
|
||||
if (out) {
|
||||
fputs("<<<<<<<\n", out);
|
||||
fwrite(one.buf, one.len, 1, out);
|
||||
@ -127,9 +134,11 @@ static int handle_file(const char *path,
|
||||
}
|
||||
strbuf_reset(&one);
|
||||
strbuf_reset(&two);
|
||||
} else if (hunk == 1)
|
||||
} else if (hunk == RR_SIDE_1)
|
||||
strbuf_addstr(&one, buf);
|
||||
else if (hunk == 2)
|
||||
else if (hunk == RR_ORIGINAL)
|
||||
; /* discard */
|
||||
else if (hunk == RR_SIDE_2)
|
||||
strbuf_addstr(&two, buf);
|
||||
else if (out)
|
||||
fputs(buf, out);
|
||||
@ -146,7 +155,7 @@ static int handle_file(const char *path,
|
||||
fclose(out);
|
||||
if (sha1)
|
||||
SHA1_Final(sha1, &ctx);
|
||||
if (hunk) {
|
||||
if (hunk != RR_CONTEXT) {
|
||||
if (output)
|
||||
unlink(output);
|
||||
return error("Could not parse conflict hunks in %s", path);
|
||||
|
@ -161,4 +161,48 @@ test_expect_success 'ZEALOUS_ALNUM' '
|
||||
|
||||
'
|
||||
|
||||
cat >expect <<\EOF
|
||||
Dominus regit me,
|
||||
<<<<<<< new8.txt
|
||||
et nihil mihi deerit;
|
||||
|
||||
|
||||
|
||||
|
||||
In loco pascuae ibi me collocavit;
|
||||
super aquam refectionis educavit me.
|
||||
|||||||
|
||||
et nihil mihi deerit.
|
||||
In loco pascuae ibi me collocavit,
|
||||
super aquam refectionis educavit me;
|
||||
=======
|
||||
et nihil mihi deerit,
|
||||
|
||||
|
||||
|
||||
|
||||
In loco pascuae ibi me collocavit --
|
||||
super aquam refectionis educavit me,
|
||||
>>>>>>> new9.txt
|
||||
animam meam convertit,
|
||||
deduxit me super semitas jusitiae,
|
||||
propter nomen suum.
|
||||
Nam et si ambulavero in medio umbrae mortis,
|
||||
non timebo mala, quoniam TU mecum es:
|
||||
virga tua et baculus tuus ipsa me consolata sunt.
|
||||
EOF
|
||||
|
||||
test_expect_success '"diff3 -m" style output (1)' '
|
||||
test_must_fail git merge-file -p --diff3 \
|
||||
new8.txt new5.txt new9.txt >actual &&
|
||||
test_cmp expect actual
|
||||
'
|
||||
|
||||
test_expect_success '"diff3 -m" style output (2)' '
|
||||
git config merge.conflictstyle diff3 &&
|
||||
test_must_fail git merge-file -p \
|
||||
new8.txt new5.txt new9.txt >actual &&
|
||||
test_cmp expect actual
|
||||
'
|
||||
|
||||
test_done
|
||||
|
123
t/t7201-co.sh
123
t/t7201-co.sh
@ -369,7 +369,7 @@ test_expect_success \
|
||||
'checkout with --track, but without -b, fails with too short tracked name' '
|
||||
test_must_fail git checkout --track renamer'
|
||||
|
||||
test_expect_success 'checkout an unmerged path should fail' '
|
||||
setup_conflicting_index () {
|
||||
rm -f .git/index &&
|
||||
O=$(echo original | git hash-object -w --stdin) &&
|
||||
A=$(echo ourside | git hash-object -w --stdin) &&
|
||||
@ -380,7 +380,11 @@ test_expect_success 'checkout an unmerged path should fail' '
|
||||
echo "100644 $A 2 file" &&
|
||||
echo "100644 $B 3 file" &&
|
||||
echo "100644 $A 0 filf"
|
||||
) | git update-index --index-info &&
|
||||
) | git update-index --index-info
|
||||
}
|
||||
|
||||
test_expect_success 'checkout an unmerged path should fail' '
|
||||
setup_conflicting_index &&
|
||||
echo "none of the above" >sample &&
|
||||
cat sample >fild &&
|
||||
cat sample >file &&
|
||||
@ -391,6 +395,121 @@ test_expect_success 'checkout an unmerged path should fail' '
|
||||
test_cmp sample file
|
||||
'
|
||||
|
||||
test_expect_success 'checkout with an unmerged path can be ignored' '
|
||||
setup_conflicting_index &&
|
||||
echo "none of the above" >sample &&
|
||||
echo ourside >expect &&
|
||||
cat sample >fild &&
|
||||
cat sample >file &&
|
||||
cat sample >filf &&
|
||||
git checkout -f fild file filf &&
|
||||
test_cmp expect fild &&
|
||||
test_cmp expect filf &&
|
||||
test_cmp sample file
|
||||
'
|
||||
|
||||
test_expect_success 'checkout unmerged stage' '
|
||||
setup_conflicting_index &&
|
||||
echo "none of the above" >sample &&
|
||||
echo ourside >expect &&
|
||||
cat sample >fild &&
|
||||
cat sample >file &&
|
||||
cat sample >filf &&
|
||||
git checkout --ours . &&
|
||||
test_cmp expect fild &&
|
||||
test_cmp expect filf &&
|
||||
test_cmp expect file &&
|
||||
git checkout --theirs file &&
|
||||
test ztheirside = "z$(cat file)"
|
||||
'
|
||||
|
||||
test_expect_success 'checkout with --merge' '
|
||||
setup_conflicting_index &&
|
||||
echo "none of the above" >sample &&
|
||||
echo ourside >expect &&
|
||||
cat sample >fild &&
|
||||
cat sample >file &&
|
||||
cat sample >filf &&
|
||||
git checkout -m -- fild file filf &&
|
||||
(
|
||||
echo "<<<<<<< ours"
|
||||
echo ourside
|
||||
echo "======="
|
||||
echo theirside
|
||||
echo ">>>>>>> theirs"
|
||||
) >merged &&
|
||||
test_cmp expect fild &&
|
||||
test_cmp expect filf &&
|
||||
test_cmp merged file
|
||||
'
|
||||
|
||||
test_expect_success 'checkout with --merge, in diff3 -m style' '
|
||||
git config merge.conflictstyle diff3 &&
|
||||
setup_conflicting_index &&
|
||||
echo "none of the above" >sample &&
|
||||
echo ourside >expect &&
|
||||
cat sample >fild &&
|
||||
cat sample >file &&
|
||||
cat sample >filf &&
|
||||
git checkout -m -- fild file filf &&
|
||||
(
|
||||
echo "<<<<<<< ours"
|
||||
echo ourside
|
||||
echo "|||||||"
|
||||
echo original
|
||||
echo "======="
|
||||
echo theirside
|
||||
echo ">>>>>>> theirs"
|
||||
) >merged &&
|
||||
test_cmp expect fild &&
|
||||
test_cmp expect filf &&
|
||||
test_cmp merged file
|
||||
'
|
||||
|
||||
test_expect_success 'checkout --conflict=merge, overriding config' '
|
||||
git config merge.conflictstyle diff3 &&
|
||||
setup_conflicting_index &&
|
||||
echo "none of the above" >sample &&
|
||||
echo ourside >expect &&
|
||||
cat sample >fild &&
|
||||
cat sample >file &&
|
||||
cat sample >filf &&
|
||||
git checkout --conflict=merge -- fild file filf &&
|
||||
(
|
||||
echo "<<<<<<< ours"
|
||||
echo ourside
|
||||
echo "======="
|
||||
echo theirside
|
||||
echo ">>>>>>> theirs"
|
||||
) >merged &&
|
||||
test_cmp expect fild &&
|
||||
test_cmp expect filf &&
|
||||
test_cmp merged file
|
||||
'
|
||||
|
||||
test_expect_success 'checkout --conflict=diff3' '
|
||||
git config --unset merge.conflictstyle
|
||||
setup_conflicting_index &&
|
||||
echo "none of the above" >sample &&
|
||||
echo ourside >expect &&
|
||||
cat sample >fild &&
|
||||
cat sample >file &&
|
||||
cat sample >filf &&
|
||||
git checkout --conflict=diff3 -- fild file filf &&
|
||||
(
|
||||
echo "<<<<<<< ours"
|
||||
echo ourside
|
||||
echo "|||||||"
|
||||
echo original
|
||||
echo "======="
|
||||
echo theirside
|
||||
echo ">>>>>>> theirs"
|
||||
) >merged &&
|
||||
test_cmp expect fild &&
|
||||
test_cmp expect filf &&
|
||||
test_cmp merged file
|
||||
'
|
||||
|
||||
test_expect_success 'failing checkout -b should not break working tree' '
|
||||
git reset --hard master &&
|
||||
git symbolic-ref HEAD refs/heads/master &&
|
||||
|
@ -249,3 +249,23 @@ void xdiff_set_find_func(xdemitconf_t *xecfg, const char *value)
|
||||
value = ep + 1;
|
||||
}
|
||||
}
|
||||
|
||||
int git_xmerge_style = -1;
|
||||
|
||||
int git_xmerge_config(const char *var, const char *value, void *cb)
|
||||
{
|
||||
if (!strcasecmp(var, "merge.conflictstyle")) {
|
||||
if (!value)
|
||||
die("'%s' is not a boolean", var);
|
||||
if (!strcmp(value, "diff3"))
|
||||
git_xmerge_style = XDL_MERGE_DIFF3;
|
||||
else if (!strcmp(value, "merge"))
|
||||
git_xmerge_style = 0;
|
||||
else
|
||||
die("unknown style '%s' given for '%s'",
|
||||
value, var);
|
||||
return 0;
|
||||
}
|
||||
return git_default_config(var, value, cb);
|
||||
}
|
||||
|
||||
|
@ -17,5 +17,7 @@ int read_mmfile(mmfile_t *ptr, const char *filename);
|
||||
int buffer_is_binary(const char *ptr, unsigned long size);
|
||||
|
||||
extern void xdiff_set_find_func(xdemitconf_t *xecfg, const char *line);
|
||||
extern int git_xmerge_config(const char *var, const char *value, void *cb);
|
||||
extern int git_xmerge_style;
|
||||
|
||||
#endif
|
||||
|
@ -50,10 +50,16 @@ extern "C" {
|
||||
#define XDL_BDOP_CPY 2
|
||||
#define XDL_BDOP_INSB 3
|
||||
|
||||
/* merge simplification levels */
|
||||
#define XDL_MERGE_MINIMAL 0
|
||||
#define XDL_MERGE_EAGER 1
|
||||
#define XDL_MERGE_ZEALOUS 2
|
||||
#define XDL_MERGE_ZEALOUS_ALNUM 3
|
||||
#define XDL_MERGE_LEVEL_MASK 0x0f
|
||||
|
||||
/* merge output styles */
|
||||
#define XDL_MERGE_DIFF3 0x8000
|
||||
#define XDL_MERGE_STYLE_MASK 0x8000
|
||||
|
||||
typedef struct s_mmfile {
|
||||
char *ptr;
|
||||
|
235
xdiff/xmerge.c
235
xdiff/xmerge.c
@ -30,17 +30,32 @@ typedef struct s_xdmerge {
|
||||
* 2 = no conflict, take second.
|
||||
*/
|
||||
int mode;
|
||||
/*
|
||||
* These point at the respective postimages. E.g. <i1,chg1> is
|
||||
* how side #1 wants to change the common ancestor; if there is no
|
||||
* overlap, lines before i1 in the postimage of side #1 appear
|
||||
* in the merge result as a region touched by neither side.
|
||||
*/
|
||||
long i1, i2;
|
||||
long chg1, chg2;
|
||||
/*
|
||||
* These point at the preimage; of course there is just one
|
||||
* preimage, that is from the shared common ancestor.
|
||||
*/
|
||||
long i0;
|
||||
long chg0;
|
||||
} xdmerge_t;
|
||||
|
||||
static int xdl_append_merge(xdmerge_t **merge, int mode,
|
||||
long i1, long chg1, long i2, long chg2)
|
||||
long i0, long chg0,
|
||||
long i1, long chg1,
|
||||
long i2, long chg2)
|
||||
{
|
||||
xdmerge_t *m = *merge;
|
||||
if (m && (i1 <= m->i1 + m->chg1 || i2 <= m->i2 + m->chg2)) {
|
||||
if (mode != m->mode)
|
||||
m->mode = 0;
|
||||
m->chg0 = i0 + chg0 - m->i0;
|
||||
m->chg1 = i1 + chg1 - m->i1;
|
||||
m->chg2 = i2 + chg2 - m->i2;
|
||||
} else {
|
||||
@ -49,6 +64,8 @@ static int xdl_append_merge(xdmerge_t **merge, int mode,
|
||||
return -1;
|
||||
m->next = NULL;
|
||||
m->mode = mode;
|
||||
m->i0 = i0;
|
||||
m->chg0 = chg0;
|
||||
m->i1 = i1;
|
||||
m->chg1 = chg1;
|
||||
m->i2 = i2;
|
||||
@ -91,11 +108,13 @@ static int xdl_merge_cmp_lines(xdfenv_t *xe1, int i1, xdfenv_t *xe2, int i2,
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int xdl_recs_copy(xdfenv_t *xe, int i, int count, int add_nl, char *dest)
|
||||
static int xdl_recs_copy_0(int use_orig, xdfenv_t *xe, int i, int count, int add_nl, char *dest)
|
||||
{
|
||||
xrecord_t **recs = xe->xdf2.recs + i;
|
||||
xrecord_t **recs;
|
||||
int size = 0;
|
||||
|
||||
recs = (use_orig ? xe->xdf1.recs : xe->xdf2.recs) + i;
|
||||
|
||||
if (count < 1)
|
||||
return 0;
|
||||
|
||||
@ -113,65 +132,109 @@ static int xdl_recs_copy(xdfenv_t *xe, int i, int count, int add_nl, char *dest)
|
||||
return size;
|
||||
}
|
||||
|
||||
static int xdl_fill_merge_buffer(xdfenv_t *xe1, const char *name1,
|
||||
xdfenv_t *xe2, const char *name2, xdmerge_t *m, char *dest)
|
||||
static int xdl_recs_copy(xdfenv_t *xe, int i, int count, int add_nl, char *dest)
|
||||
{
|
||||
return xdl_recs_copy_0(0, xe, i, count, add_nl, dest);
|
||||
}
|
||||
|
||||
static int xdl_orig_copy(xdfenv_t *xe, int i, int count, int add_nl, char *dest)
|
||||
{
|
||||
return xdl_recs_copy_0(1, xe, i, count, add_nl, dest);
|
||||
}
|
||||
|
||||
static int fill_conflict_hunk(xdfenv_t *xe1, const char *name1,
|
||||
xdfenv_t *xe2, const char *name2,
|
||||
int size, int i, int style,
|
||||
xdmerge_t *m, char *dest)
|
||||
{
|
||||
const int marker_size = 7;
|
||||
int marker1_size = (name1 ? strlen(name1) + 1 : 0);
|
||||
int marker2_size = (name2 ? strlen(name2) + 1 : 0);
|
||||
int conflict_marker_size = 3 * (marker_size + 1)
|
||||
+ marker1_size + marker2_size;
|
||||
int size, i1, j;
|
||||
int j;
|
||||
|
||||
for (size = i1 = 0; m; m = m->next) {
|
||||
if (m->mode == 0) {
|
||||
size += xdl_recs_copy(xe1, i1, m->i1 - i1, 0,
|
||||
dest ? dest + size : NULL);
|
||||
if (dest) {
|
||||
for (j = 0; j < marker_size; j++)
|
||||
dest[size++] = '<';
|
||||
if (marker1_size) {
|
||||
dest[size] = ' ';
|
||||
memcpy(dest + size + 1, name1,
|
||||
marker1_size - 1);
|
||||
size += marker1_size;
|
||||
}
|
||||
dest[size++] = '\n';
|
||||
} else
|
||||
size += conflict_marker_size;
|
||||
size += xdl_recs_copy(xe1, m->i1, m->chg1, 1,
|
||||
dest ? dest + size : NULL);
|
||||
if (dest) {
|
||||
for (j = 0; j < marker_size; j++)
|
||||
dest[size++] = '=';
|
||||
dest[size++] = '\n';
|
||||
}
|
||||
size += xdl_recs_copy(xe2, m->i2, m->chg2, 1,
|
||||
dest ? dest + size : NULL);
|
||||
if (dest) {
|
||||
for (j = 0; j < marker_size; j++)
|
||||
dest[size++] = '>';
|
||||
if (marker2_size) {
|
||||
dest[size] = ' ';
|
||||
memcpy(dest + size + 1, name2,
|
||||
marker2_size - 1);
|
||||
size += marker2_size;
|
||||
}
|
||||
dest[size++] = '\n';
|
||||
}
|
||||
} else if (m->mode == 1)
|
||||
size += xdl_recs_copy(xe1, i1, m->i1 + m->chg1 - i1, 0,
|
||||
dest ? dest + size : NULL);
|
||||
/* Before conflicting part */
|
||||
size += xdl_recs_copy(xe1, i, m->i1 - i, 0,
|
||||
dest ? dest + size : NULL);
|
||||
|
||||
if (!dest) {
|
||||
size += marker_size + 1 + marker1_size;
|
||||
} else {
|
||||
for (j = 0; j < marker_size; j++)
|
||||
dest[size++] = '<';
|
||||
if (marker1_size) {
|
||||
dest[size] = ' ';
|
||||
memcpy(dest + size + 1, name1, marker1_size - 1);
|
||||
size += marker1_size;
|
||||
}
|
||||
dest[size++] = '\n';
|
||||
}
|
||||
|
||||
/* Postimage from side #1 */
|
||||
size += xdl_recs_copy(xe1, m->i1, m->chg1, 1,
|
||||
dest ? dest + size : NULL);
|
||||
|
||||
if (style == XDL_MERGE_DIFF3) {
|
||||
/* Shared preimage */
|
||||
if (!dest) {
|
||||
size += marker_size + 1;
|
||||
} else {
|
||||
for (j = 0; j < marker_size; j++)
|
||||
dest[size++] = '|';
|
||||
dest[size++] = '\n';
|
||||
}
|
||||
size += xdl_orig_copy(xe1, m->i0, m->chg0, 1,
|
||||
dest ? dest + size : NULL);
|
||||
}
|
||||
|
||||
if (!dest) {
|
||||
size += marker_size + 1;
|
||||
} else {
|
||||
for (j = 0; j < marker_size; j++)
|
||||
dest[size++] = '=';
|
||||
dest[size++] = '\n';
|
||||
}
|
||||
|
||||
/* Postimage from side #2 */
|
||||
size += xdl_recs_copy(xe2, m->i2, m->chg2, 1,
|
||||
dest ? dest + size : NULL);
|
||||
if (!dest) {
|
||||
size += marker_size + 1 + marker2_size;
|
||||
} else {
|
||||
for (j = 0; j < marker_size; j++)
|
||||
dest[size++] = '>';
|
||||
if (marker2_size) {
|
||||
dest[size] = ' ';
|
||||
memcpy(dest + size + 1, name2, marker2_size - 1);
|
||||
size += marker2_size;
|
||||
}
|
||||
dest[size++] = '\n';
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
static int xdl_fill_merge_buffer(xdfenv_t *xe1, const char *name1,
|
||||
xdfenv_t *xe2, const char *name2,
|
||||
xdmerge_t *m, char *dest, int style)
|
||||
{
|
||||
int size, i;
|
||||
|
||||
for (size = i = 0; m; m = m->next) {
|
||||
if (m->mode == 0)
|
||||
size = fill_conflict_hunk(xe1, name1, xe2, name2,
|
||||
size, i, style, m, dest);
|
||||
else if (m->mode == 1)
|
||||
size += xdl_recs_copy(xe1, i, m->i1 + m->chg1 - i, 0,
|
||||
dest ? dest + size : NULL);
|
||||
else if (m->mode == 2)
|
||||
size += xdl_recs_copy(xe2, m->i2 - m->i1 + i1,
|
||||
m->i1 + m->chg2 - i1, 0,
|
||||
dest ? dest + size : NULL);
|
||||
size += xdl_recs_copy(xe2, m->i2 - m->i1 + i,
|
||||
m->i1 + m->chg2 - i, 0,
|
||||
dest ? dest + size : NULL);
|
||||
else
|
||||
continue;
|
||||
i1 = m->i1 + m->chg1;
|
||||
i = m->i1 + m->chg1;
|
||||
}
|
||||
size += xdl_recs_copy(xe1, i1, xe1->xdf2.nrec - i1, 0,
|
||||
dest ? dest + size : NULL);
|
||||
size += xdl_recs_copy(xe1, i, xe1->xdf2.nrec - i, 0,
|
||||
dest ? dest + size : NULL);
|
||||
return size;
|
||||
}
|
||||
|
||||
@ -323,9 +386,20 @@ static int xdl_simplify_non_conflicts(xdfenv_t *xe1, xdmerge_t *m,
|
||||
*/
|
||||
static int xdl_do_merge(xdfenv_t *xe1, xdchange_t *xscr1, const char *name1,
|
||||
xdfenv_t *xe2, xdchange_t *xscr2, const char *name2,
|
||||
int level, xpparam_t const *xpp, mmbuffer_t *result) {
|
||||
int flags, xpparam_t const *xpp, mmbuffer_t *result) {
|
||||
xdmerge_t *changes, *c;
|
||||
int i1, i2, chg1, chg2;
|
||||
int i0, i1, i2, chg0, chg1, chg2;
|
||||
int level = flags & XDL_MERGE_LEVEL_MASK;
|
||||
int style = flags & XDL_MERGE_STYLE_MASK;
|
||||
|
||||
if (style == XDL_MERGE_DIFF3) {
|
||||
/*
|
||||
* "diff3 -m" output does not make sense for anything
|
||||
* more aggressive than XDL_MERGE_EAGER.
|
||||
*/
|
||||
if (XDL_MERGE_EAGER < level)
|
||||
level = XDL_MERGE_EAGER;
|
||||
}
|
||||
|
||||
c = changes = NULL;
|
||||
|
||||
@ -333,11 +407,14 @@ static int xdl_do_merge(xdfenv_t *xe1, xdchange_t *xscr1, const char *name1,
|
||||
if (!changes)
|
||||
changes = c;
|
||||
if (xscr1->i1 + xscr1->chg1 < xscr2->i1) {
|
||||
i0 = xscr1->i1;
|
||||
i1 = xscr1->i2;
|
||||
i2 = xscr2->i2 - xscr2->i1 + xscr1->i1;
|
||||
chg0 = xscr1->chg1;
|
||||
chg1 = xscr1->chg2;
|
||||
chg2 = xscr1->chg1;
|
||||
if (xdl_append_merge(&c, 1, i1, chg1, i2, chg2)) {
|
||||
if (xdl_append_merge(&c, 1,
|
||||
i0, chg0, i1, chg1, i2, chg2)) {
|
||||
xdl_cleanup_merge(changes);
|
||||
return -1;
|
||||
}
|
||||
@ -345,18 +422,21 @@ static int xdl_do_merge(xdfenv_t *xe1, xdchange_t *xscr1, const char *name1,
|
||||
continue;
|
||||
}
|
||||
if (xscr2->i1 + xscr2->chg1 < xscr1->i1) {
|
||||
i0 = xscr2->i1;
|
||||
i1 = xscr1->i2 - xscr1->i1 + xscr2->i1;
|
||||
i2 = xscr2->i2;
|
||||
chg0 = xscr2->chg1;
|
||||
chg1 = xscr2->chg1;
|
||||
chg2 = xscr2->chg2;
|
||||
if (xdl_append_merge(&c, 2, i1, chg1, i2, chg2)) {
|
||||
if (xdl_append_merge(&c, 2,
|
||||
i0, chg0, i1, chg1, i2, chg2)) {
|
||||
xdl_cleanup_merge(changes);
|
||||
return -1;
|
||||
}
|
||||
xscr2 = xscr2->next;
|
||||
continue;
|
||||
}
|
||||
if (level < 1 || xscr1->i1 != xscr2->i1 ||
|
||||
if (level == XDL_MERGE_MINIMAL || xscr1->i1 != xscr2->i1 ||
|
||||
xscr1->chg1 != xscr2->chg1 ||
|
||||
xscr1->chg2 != xscr2->chg2 ||
|
||||
xdl_merge_cmp_lines(xe1, xscr1->i2,
|
||||
@ -366,19 +446,25 @@ static int xdl_do_merge(xdfenv_t *xe1, xdchange_t *xscr1, const char *name1,
|
||||
int off = xscr1->i1 - xscr2->i1;
|
||||
int ffo = off + xscr1->chg1 - xscr2->chg1;
|
||||
|
||||
i0 = xscr1->i1;
|
||||
i1 = xscr1->i2;
|
||||
i2 = xscr2->i2;
|
||||
if (off > 0)
|
||||
if (off > 0) {
|
||||
i0 -= off;
|
||||
i1 -= off;
|
||||
}
|
||||
else
|
||||
i2 += off;
|
||||
chg0 = xscr1->i1 + xscr1->chg1 - i0;
|
||||
chg1 = xscr1->i2 + xscr1->chg2 - i1;
|
||||
chg2 = xscr2->i2 + xscr2->chg2 - i2;
|
||||
if (ffo > 0)
|
||||
chg2 += ffo;
|
||||
else
|
||||
if (ffo < 0) {
|
||||
chg0 -= ffo;
|
||||
chg1 -= ffo;
|
||||
if (xdl_append_merge(&c, 0, i1, chg1, i2, chg2)) {
|
||||
} else
|
||||
chg2 += ffo;
|
||||
if (xdl_append_merge(&c, 0,
|
||||
i0, chg0, i1, chg1, i2, chg2)) {
|
||||
xdl_cleanup_merge(changes);
|
||||
return -1;
|
||||
}
|
||||
@ -395,11 +481,14 @@ static int xdl_do_merge(xdfenv_t *xe1, xdchange_t *xscr1, const char *name1,
|
||||
while (xscr1) {
|
||||
if (!changes)
|
||||
changes = c;
|
||||
i0 = xscr1->i1;
|
||||
i1 = xscr1->i2;
|
||||
i2 = xscr1->i1 + xe2->xdf2.nrec - xe2->xdf1.nrec;
|
||||
chg0 = xscr1->chg1;
|
||||
chg1 = xscr1->chg2;
|
||||
chg2 = xscr1->chg1;
|
||||
if (xdl_append_merge(&c, 1, i1, chg1, i2, chg2)) {
|
||||
if (xdl_append_merge(&c, 1,
|
||||
i0, chg0, i1, chg1, i2, chg2)) {
|
||||
xdl_cleanup_merge(changes);
|
||||
return -1;
|
||||
}
|
||||
@ -408,11 +497,14 @@ static int xdl_do_merge(xdfenv_t *xe1, xdchange_t *xscr1, const char *name1,
|
||||
while (xscr2) {
|
||||
if (!changes)
|
||||
changes = c;
|
||||
i0 = xscr2->i1;
|
||||
i1 = xscr2->i1 + xe1->xdf2.nrec - xe1->xdf1.nrec;
|
||||
i2 = xscr2->i2;
|
||||
chg0 = xscr2->chg1;
|
||||
chg1 = xscr2->chg1;
|
||||
chg2 = xscr2->chg2;
|
||||
if (xdl_append_merge(&c, 2, i1, chg1, i2, chg2)) {
|
||||
if (xdl_append_merge(&c, 2,
|
||||
i0, chg0, i1, chg1, i2, chg2)) {
|
||||
xdl_cleanup_merge(changes);
|
||||
return -1;
|
||||
}
|
||||
@ -421,16 +513,17 @@ static int xdl_do_merge(xdfenv_t *xe1, xdchange_t *xscr1, const char *name1,
|
||||
if (!changes)
|
||||
changes = c;
|
||||
/* refine conflicts */
|
||||
if (level > 1 &&
|
||||
if (XDL_MERGE_ZEALOUS <= level &&
|
||||
(xdl_refine_conflicts(xe1, xe2, changes, xpp) < 0 ||
|
||||
xdl_simplify_non_conflicts(xe1, changes, level > 2) < 0)) {
|
||||
xdl_simplify_non_conflicts(xe1, changes,
|
||||
XDL_MERGE_ZEALOUS < level) < 0)) {
|
||||
xdl_cleanup_merge(changes);
|
||||
return -1;
|
||||
}
|
||||
/* output */
|
||||
if (result) {
|
||||
int size = xdl_fill_merge_buffer(xe1, name1, xe2, name2,
|
||||
changes, NULL);
|
||||
changes, NULL, style);
|
||||
result->ptr = xdl_malloc(size);
|
||||
if (!result->ptr) {
|
||||
xdl_cleanup_merge(changes);
|
||||
@ -438,14 +531,14 @@ static int xdl_do_merge(xdfenv_t *xe1, xdchange_t *xscr1, const char *name1,
|
||||
}
|
||||
result->size = size;
|
||||
xdl_fill_merge_buffer(xe1, name1, xe2, name2, changes,
|
||||
result->ptr);
|
||||
result->ptr, style);
|
||||
}
|
||||
return xdl_cleanup_merge(changes);
|
||||
}
|
||||
|
||||
int xdl_merge(mmfile_t *orig, mmfile_t *mf1, const char *name1,
|
||||
mmfile_t *mf2, const char *name2,
|
||||
xpparam_t const *xpp, int level, mmbuffer_t *result) {
|
||||
xpparam_t const *xpp, int flags, mmbuffer_t *result) {
|
||||
xdchange_t *xscr1, *xscr2;
|
||||
xdfenv_t xe1, xe2;
|
||||
int status;
|
||||
@ -482,7 +575,7 @@ int xdl_merge(mmfile_t *orig, mmfile_t *mf1, const char *name1,
|
||||
} else {
|
||||
status = xdl_do_merge(&xe1, xscr1, name1,
|
||||
&xe2, xscr2, name2,
|
||||
level, xpp, result);
|
||||
flags, xpp, result);
|
||||
}
|
||||
xdl_free_script(xscr1);
|
||||
xdl_free_script(xscr2);
|
||||
|
Loading…
Reference in New Issue
Block a user