merge-recursive.[ch]: thoroughly debug these
As a wise man once told me, "Deleted code is debugged code!" So, move the functions that are shared between merge-recursive and merge-ort from the former to the latter, and then debug the remainder of merge-recursive.[ch]. Joking aside, merge-ort was always intended to replace merge-recursive. It has numerous advantages over merge-recursive (operates much faster, can operate without a worktree or index, and fixes a number of known bugs and suboptimal merges). Since we have now replaced all callers of merge-recursive with equivalent functions from merge-ort, move the shared functions from the former to the latter, and delete the remainder of merge-recursive.[ch]. Signed-off-by: Elijah Newren <newren@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>main
parent
75cd9ae05f
commit
ad45b327c0
|
@ -109,23 +109,11 @@ subtree[=<path>];;
|
|||
two trees to match.
|
||||
|
||||
recursive::
|
||||
This can only resolve two heads using a 3-way merge
|
||||
algorithm. When there is more than one common
|
||||
ancestor that can be used for 3-way merge, it creates a
|
||||
merged tree of the common ancestors and uses that as
|
||||
the reference tree for the 3-way merge. This has been
|
||||
reported to result in fewer merge conflicts without
|
||||
causing mismerges by tests done on actual merge commits
|
||||
taken from Linux 2.6 kernel development history.
|
||||
Additionally this can detect and handle merges involving
|
||||
renames. It does not make use of detected copies. This was
|
||||
the default strategy for resolving two heads from Git v0.99.9k
|
||||
until v2.33.0.
|
||||
+
|
||||
For a path that is a submodule, the same caution as 'ort' applies to this
|
||||
strategy.
|
||||
+
|
||||
The 'recursive' strategy takes the same options as 'ort'.
|
||||
This is now a synonym for `ort`. It was an alternative
|
||||
implementation until v2.49.0, but was redirected to mean `ort`
|
||||
in v2.50.0. The previous recursive strategy was the default
|
||||
strategy for resolving two heads from Git v0.99.9k until
|
||||
v2.33.0.
|
||||
|
||||
resolve::
|
||||
This can only resolve two heads (i.e. the current branch
|
||||
|
@ -146,7 +134,7 @@ ours::
|
|||
ignoring all changes from all other branches. It is meant to
|
||||
be used to supersede old development history of side
|
||||
branches. Note that this is different from the -Xours option to
|
||||
the 'recursive' merge strategy.
|
||||
the 'ort' merge strategy.
|
||||
|
||||
subtree::
|
||||
This is a modified `ort` strategy. When merging trees A and
|
||||
|
|
|
@ -356,8 +356,6 @@ understanding these differences can be beneficial.
|
|||
The behavior for these commands somewhat depends upon the merge
|
||||
strategy being used:
|
||||
* `ort` behaves as described above
|
||||
* `recursive` tries to not vivify files unnecessarily, but does sometimes
|
||||
vivify files without conflicts.
|
||||
* `octopus` and `resolve` will always vivify any file changed in the merge
|
||||
relative to the first parent, which is rather suboptimal.
|
||||
|
||||
|
|
1
Makefile
1
Makefile
|
@ -1069,7 +1069,6 @@ LIB_OBJS += merge-blobs.o
|
|||
LIB_OBJS += merge-ll.o
|
||||
LIB_OBJS += merge-ort.o
|
||||
LIB_OBJS += merge-ort-wrappers.o
|
||||
LIB_OBJS += merge-recursive.o
|
||||
LIB_OBJS += merge.o
|
||||
LIB_OBJS += midx.o
|
||||
LIB_OBJS += midx-write.o
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
#ifndef MERGE_ORT_WRAPPERS_H
|
||||
#define MERGE_ORT_WRAPPERS_H
|
||||
|
||||
#include "merge-recursive.h"
|
||||
#include "merge-ort.h"
|
||||
|
||||
/*
|
||||
* rename-detecting three-way merge, no recursion.
|
||||
|
|
159
merge-ort.c
159
merge-ort.c
|
@ -26,6 +26,7 @@
|
|||
#include "cache-tree.h"
|
||||
#include "commit.h"
|
||||
#include "commit-reach.h"
|
||||
#include "config.h"
|
||||
#include "diff.h"
|
||||
#include "diffcore.h"
|
||||
#include "dir.h"
|
||||
|
@ -5322,3 +5323,161 @@ void merge_incore_recursive(struct merge_options *opt,
|
|||
merge_ort_internal(opt, merge_bases, side1, side2, result);
|
||||
trace2_region_leave("merge", "incore_recursive", opt->repo);
|
||||
}
|
||||
|
||||
static void merge_recursive_config(struct merge_options *opt, int ui)
|
||||
{
|
||||
char *value = NULL;
|
||||
int renormalize = 0;
|
||||
git_config_get_int("merge.verbosity", &opt->verbosity);
|
||||
git_config_get_int("diff.renamelimit", &opt->rename_limit);
|
||||
git_config_get_int("merge.renamelimit", &opt->rename_limit);
|
||||
git_config_get_bool("merge.renormalize", &renormalize);
|
||||
opt->renormalize = renormalize;
|
||||
if (!git_config_get_string("diff.renames", &value)) {
|
||||
opt->detect_renames = git_config_rename("diff.renames", value);
|
||||
free(value);
|
||||
}
|
||||
if (!git_config_get_string("merge.renames", &value)) {
|
||||
opt->detect_renames = git_config_rename("merge.renames", value);
|
||||
free(value);
|
||||
}
|
||||
if (!git_config_get_string("merge.directoryrenames", &value)) {
|
||||
int boolval = git_parse_maybe_bool(value);
|
||||
if (0 <= boolval) {
|
||||
opt->detect_directory_renames = boolval ?
|
||||
MERGE_DIRECTORY_RENAMES_TRUE :
|
||||
MERGE_DIRECTORY_RENAMES_NONE;
|
||||
} else if (!strcasecmp(value, "conflict")) {
|
||||
opt->detect_directory_renames =
|
||||
MERGE_DIRECTORY_RENAMES_CONFLICT;
|
||||
} /* avoid erroring on values from future versions of git */
|
||||
free(value);
|
||||
}
|
||||
if (ui) {
|
||||
if (!git_config_get_string("diff.algorithm", &value)) {
|
||||
long diff_algorithm = parse_algorithm_value(value);
|
||||
if (diff_algorithm < 0)
|
||||
die(_("unknown value for config '%s': %s"), "diff.algorithm", value);
|
||||
opt->xdl_opts = (opt->xdl_opts & ~XDF_DIFF_ALGORITHM_MASK) | diff_algorithm;
|
||||
free(value);
|
||||
}
|
||||
}
|
||||
git_config(git_xmerge_config, NULL);
|
||||
}
|
||||
|
||||
static void init_merge_options(struct merge_options *opt,
|
||||
struct repository *repo, int ui)
|
||||
{
|
||||
const char *merge_verbosity;
|
||||
memset(opt, 0, sizeof(struct merge_options));
|
||||
|
||||
opt->repo = repo;
|
||||
|
||||
opt->detect_renames = -1;
|
||||
opt->detect_directory_renames = MERGE_DIRECTORY_RENAMES_CONFLICT;
|
||||
opt->rename_limit = -1;
|
||||
|
||||
opt->verbosity = 2;
|
||||
opt->buffer_output = 1;
|
||||
strbuf_init(&opt->obuf, 0);
|
||||
|
||||
opt->renormalize = 0;
|
||||
|
||||
opt->conflict_style = -1;
|
||||
opt->xdl_opts = DIFF_WITH_ALG(opt, HISTOGRAM_DIFF);
|
||||
|
||||
merge_recursive_config(opt, ui);
|
||||
merge_verbosity = getenv("GIT_MERGE_VERBOSITY");
|
||||
if (merge_verbosity)
|
||||
opt->verbosity = strtol(merge_verbosity, NULL, 10);
|
||||
if (opt->verbosity >= 5)
|
||||
opt->buffer_output = 0;
|
||||
}
|
||||
|
||||
void init_ui_merge_options(struct merge_options *opt,
|
||||
struct repository *repo)
|
||||
{
|
||||
init_merge_options(opt, repo, 1);
|
||||
}
|
||||
|
||||
void init_basic_merge_options(struct merge_options *opt,
|
||||
struct repository *repo)
|
||||
{
|
||||
init_merge_options(opt, repo, 0);
|
||||
}
|
||||
|
||||
/*
|
||||
* For now, members of merge_options do not need deep copying, but
|
||||
* it may change in the future, in which case we would need to update
|
||||
* this, and also make a matching change to clear_merge_options() to
|
||||
* release the resources held by a copied instance.
|
||||
*/
|
||||
void copy_merge_options(struct merge_options *dst, struct merge_options *src)
|
||||
{
|
||||
*dst = *src;
|
||||
}
|
||||
|
||||
void clear_merge_options(struct merge_options *opt UNUSED)
|
||||
{
|
||||
; /* no-op as our copy is shallow right now */
|
||||
}
|
||||
|
||||
int parse_merge_opt(struct merge_options *opt, const char *s)
|
||||
{
|
||||
const char *arg;
|
||||
|
||||
if (!s || !*s)
|
||||
return -1;
|
||||
if (!strcmp(s, "ours"))
|
||||
opt->recursive_variant = MERGE_VARIANT_OURS;
|
||||
else if (!strcmp(s, "theirs"))
|
||||
opt->recursive_variant = MERGE_VARIANT_THEIRS;
|
||||
else if (!strcmp(s, "subtree"))
|
||||
opt->subtree_shift = "";
|
||||
else if (skip_prefix(s, "subtree=", &arg))
|
||||
opt->subtree_shift = arg;
|
||||
else if (!strcmp(s, "patience"))
|
||||
opt->xdl_opts = DIFF_WITH_ALG(opt, PATIENCE_DIFF);
|
||||
else if (!strcmp(s, "histogram"))
|
||||
opt->xdl_opts = DIFF_WITH_ALG(opt, HISTOGRAM_DIFF);
|
||||
else if (skip_prefix(s, "diff-algorithm=", &arg)) {
|
||||
long value = parse_algorithm_value(arg);
|
||||
if (value < 0)
|
||||
return -1;
|
||||
/* clear out previous settings */
|
||||
DIFF_XDL_CLR(opt, NEED_MINIMAL);
|
||||
opt->xdl_opts &= ~XDF_DIFF_ALGORITHM_MASK;
|
||||
opt->xdl_opts |= value;
|
||||
}
|
||||
else if (!strcmp(s, "ignore-space-change"))
|
||||
DIFF_XDL_SET(opt, IGNORE_WHITESPACE_CHANGE);
|
||||
else if (!strcmp(s, "ignore-all-space"))
|
||||
DIFF_XDL_SET(opt, IGNORE_WHITESPACE);
|
||||
else if (!strcmp(s, "ignore-space-at-eol"))
|
||||
DIFF_XDL_SET(opt, IGNORE_WHITESPACE_AT_EOL);
|
||||
else if (!strcmp(s, "ignore-cr-at-eol"))
|
||||
DIFF_XDL_SET(opt, IGNORE_CR_AT_EOL);
|
||||
else if (!strcmp(s, "renormalize"))
|
||||
opt->renormalize = 1;
|
||||
else if (!strcmp(s, "no-renormalize"))
|
||||
opt->renormalize = 0;
|
||||
else if (!strcmp(s, "no-renames"))
|
||||
opt->detect_renames = 0;
|
||||
else if (!strcmp(s, "find-renames")) {
|
||||
opt->detect_renames = 1;
|
||||
opt->rename_score = 0;
|
||||
}
|
||||
else if (skip_prefix(s, "find-renames=", &arg) ||
|
||||
skip_prefix(s, "rename-threshold=", &arg)) {
|
||||
if ((opt->rename_score = parse_rename_score(&arg)) == -1 || *arg != 0)
|
||||
return -1;
|
||||
opt->detect_renames = 1;
|
||||
}
|
||||
/*
|
||||
* Please update $__git_merge_strategy_options in
|
||||
* git-completion.bash when you add new options
|
||||
*/
|
||||
else
|
||||
return -1;
|
||||
return 0;
|
||||
}
|
||||
|
|
60
merge-ort.h
60
merge-ort.h
|
@ -1,10 +1,11 @@
|
|||
#ifndef MERGE_ORT_H
|
||||
#define MERGE_ORT_H
|
||||
|
||||
#include "merge-recursive.h"
|
||||
#include "hash.h"
|
||||
#include "strbuf.h"
|
||||
|
||||
struct commit;
|
||||
struct commit_list;
|
||||
struct tree;
|
||||
struct strmap;
|
||||
|
||||
|
@ -44,6 +45,51 @@ struct merge_result {
|
|||
unsigned _properly_initialized;
|
||||
};
|
||||
|
||||
struct merge_options_internal;
|
||||
struct merge_options {
|
||||
struct repository *repo;
|
||||
|
||||
/* ref names used in console messages and conflict markers */
|
||||
const char *ancestor;
|
||||
const char *branch1;
|
||||
const char *branch2;
|
||||
|
||||
/* rename related options */
|
||||
int detect_renames;
|
||||
enum {
|
||||
MERGE_DIRECTORY_RENAMES_NONE = 0,
|
||||
MERGE_DIRECTORY_RENAMES_CONFLICT = 1,
|
||||
MERGE_DIRECTORY_RENAMES_TRUE = 2
|
||||
} detect_directory_renames;
|
||||
int rename_limit;
|
||||
int rename_score;
|
||||
int show_rename_progress;
|
||||
|
||||
/* xdiff-related options (patience, ignore whitespace, ours/theirs) */
|
||||
long xdl_opts;
|
||||
int conflict_style;
|
||||
enum {
|
||||
MERGE_VARIANT_NORMAL = 0,
|
||||
MERGE_VARIANT_OURS,
|
||||
MERGE_VARIANT_THEIRS
|
||||
} recursive_variant;
|
||||
|
||||
/* console output related options */
|
||||
int verbosity;
|
||||
unsigned buffer_output; /* 1: output at end, 2: keep buffered */
|
||||
struct strbuf obuf; /* output buffer; if buffer_output == 2, caller
|
||||
* must handle and call strbuf_release */
|
||||
|
||||
/* miscellaneous control options */
|
||||
const char *subtree_shift;
|
||||
unsigned renormalize : 1;
|
||||
unsigned record_conflict_msgs_as_headers : 1;
|
||||
const char *msg_header_prefix;
|
||||
|
||||
/* internal fields used by the implementation */
|
||||
struct merge_options_internal *priv;
|
||||
};
|
||||
|
||||
/* Mostly internal function also used by merge-ort-wrappers.c */
|
||||
struct commit *make_virtual_commit(struct repository *repo,
|
||||
struct tree *tree,
|
||||
|
@ -119,4 +165,16 @@ void merge_get_conflicted_files(struct merge_result *result,
|
|||
void merge_finalize(struct merge_options *opt,
|
||||
struct merge_result *result);
|
||||
|
||||
|
||||
/* for use by porcelain commands */
|
||||
void init_ui_merge_options(struct merge_options *opt, struct repository *repo);
|
||||
/* for use by plumbing commands */
|
||||
void init_basic_merge_options(struct merge_options *opt, struct repository *repo);
|
||||
|
||||
void copy_merge_options(struct merge_options *dst, struct merge_options *src);
|
||||
void clear_merge_options(struct merge_options *opt);
|
||||
|
||||
/* parse the option in s and update the relevant field of opt */
|
||||
int parse_merge_opt(struct merge_options *opt, const char *s);
|
||||
|
||||
#endif
|
||||
|
|
4080
merge-recursive.c
4080
merge-recursive.c
File diff suppressed because it is too large
Load Diff
|
@ -1,132 +0,0 @@
|
|||
#ifndef MERGE_RECURSIVE_H
|
||||
#define MERGE_RECURSIVE_H
|
||||
|
||||
#include "strbuf.h"
|
||||
|
||||
struct commit;
|
||||
struct commit_list;
|
||||
struct object_id;
|
||||
struct repository;
|
||||
struct tree;
|
||||
|
||||
struct merge_options_internal;
|
||||
struct merge_options {
|
||||
struct repository *repo;
|
||||
|
||||
/* ref names used in console messages and conflict markers */
|
||||
const char *ancestor;
|
||||
const char *branch1;
|
||||
const char *branch2;
|
||||
|
||||
/* rename related options */
|
||||
int detect_renames;
|
||||
enum {
|
||||
MERGE_DIRECTORY_RENAMES_NONE = 0,
|
||||
MERGE_DIRECTORY_RENAMES_CONFLICT = 1,
|
||||
MERGE_DIRECTORY_RENAMES_TRUE = 2
|
||||
} detect_directory_renames;
|
||||
int rename_limit;
|
||||
int rename_score;
|
||||
int show_rename_progress;
|
||||
|
||||
/* xdiff-related options (patience, ignore whitespace, ours/theirs) */
|
||||
long xdl_opts;
|
||||
int conflict_style;
|
||||
enum {
|
||||
MERGE_VARIANT_NORMAL = 0,
|
||||
MERGE_VARIANT_OURS,
|
||||
MERGE_VARIANT_THEIRS
|
||||
} recursive_variant;
|
||||
|
||||
/* console output related options */
|
||||
int verbosity;
|
||||
unsigned buffer_output; /* 1: output at end, 2: keep buffered */
|
||||
struct strbuf obuf; /* output buffer; if buffer_output == 2, caller
|
||||
* must handle and call strbuf_release */
|
||||
|
||||
/* miscellaneous control options */
|
||||
const char *subtree_shift;
|
||||
unsigned renormalize : 1;
|
||||
unsigned record_conflict_msgs_as_headers : 1;
|
||||
const char *msg_header_prefix;
|
||||
|
||||
/* internal fields used by the implementation */
|
||||
struct merge_options_internal *priv;
|
||||
};
|
||||
|
||||
/* for use by porcelain commands */
|
||||
void init_ui_merge_options(struct merge_options *opt, struct repository *repo);
|
||||
/* for use by plumbing commands */
|
||||
void init_basic_merge_options(struct merge_options *opt, struct repository *repo);
|
||||
|
||||
void copy_merge_options(struct merge_options *dst, struct merge_options *src);
|
||||
void clear_merge_options(struct merge_options *opt);
|
||||
|
||||
/* parse the option in s and update the relevant field of opt */
|
||||
int parse_merge_opt(struct merge_options *opt, const char *s);
|
||||
|
||||
/*
|
||||
* RETURN VALUES: All the merge_* functions below return a value as follows:
|
||||
* > 0 Merge was clean
|
||||
* = 0 Merge had conflicts
|
||||
* < 0 Merge hit an unexpected and unrecoverable problem (e.g. disk
|
||||
* full) and aborted merge part-way through.
|
||||
*/
|
||||
|
||||
/*
|
||||
* rename-detecting three-way merge, no recursion.
|
||||
*
|
||||
* Outputs:
|
||||
* - See RETURN VALUES above
|
||||
* - opt->repo->index has the new index
|
||||
* - new index NOT written to disk
|
||||
* - The working tree is updated with results of the merge
|
||||
*/
|
||||
int merge_trees(struct merge_options *opt,
|
||||
struct tree *head,
|
||||
struct tree *merge,
|
||||
struct tree *merge_base);
|
||||
|
||||
/*
|
||||
* merge_recursive is like merge_trees() but with recursive ancestor
|
||||
* consolidation.
|
||||
*
|
||||
* NOTE: empirically, about a decade ago it was determined that with more
|
||||
* than two merge bases, optimal behavior was found when the
|
||||
* merge_bases were passed in the order of oldest commit to newest
|
||||
* commit. Also, merge_bases will be consumed (emptied) so make a
|
||||
* copy if you need it.
|
||||
*
|
||||
* Outputs:
|
||||
* - See RETURN VALUES above
|
||||
* - *result is treated as scratch space for temporary recursive merges
|
||||
* - opt->repo->index has the new index
|
||||
* - new index NOT written to disk
|
||||
* - The working tree is updated with results of the merge
|
||||
*/
|
||||
int merge_recursive(struct merge_options *opt,
|
||||
struct commit *h1,
|
||||
struct commit *h2,
|
||||
const struct commit_list *merge_bases,
|
||||
struct commit **result);
|
||||
|
||||
/*
|
||||
* merge_recursive_generic can operate on trees instead of commits, by
|
||||
* wrapping the trees into virtual commits, and calling merge_recursive().
|
||||
* It also writes out the in-memory index to disk if the merge is successful.
|
||||
*
|
||||
* Outputs:
|
||||
* - See RETURN VALUES above
|
||||
* - *result is treated as scratch space for temporary recursive merges
|
||||
* - opt->repo->index has the new index
|
||||
* - new index also written to $GIT_INDEX_FILE on disk
|
||||
* - The working tree is updated with results of the merge
|
||||
*/
|
||||
int merge_recursive_generic(struct merge_options *opt,
|
||||
const struct object_id *head,
|
||||
const struct object_id *merge,
|
||||
int num_merge_bases,
|
||||
const struct object_id *merge_bases,
|
||||
struct commit **result);
|
||||
|
||||
#endif
|
|
@ -338,7 +338,6 @@ libgit_sources = [
|
|||
'merge-ll.c',
|
||||
'merge-ort.c',
|
||||
'merge-ort-wrappers.c',
|
||||
'merge-recursive.c',
|
||||
'merge.c',
|
||||
'midx.c',
|
||||
'midx-write.c',
|
||||
|
|
Loading…
Reference in New Issue