Skip to content

Commit ad45b32

Browse files
newrengitster
authored andcommitted
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 <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent 75cd9ae commit ad45b32

File tree

9 files changed

+225
-4236
lines changed

9 files changed

+225
-4236
lines changed

Documentation/merge-strategies.adoc

Lines changed: 6 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -109,23 +109,11 @@ subtree[=<path>];;
109109
two trees to match.
110110

111111
recursive::
112-
This can only resolve two heads using a 3-way merge
113-
algorithm. When there is more than one common
114-
ancestor that can be used for 3-way merge, it creates a
115-
merged tree of the common ancestors and uses that as
116-
the reference tree for the 3-way merge. This has been
117-
reported to result in fewer merge conflicts without
118-
causing mismerges by tests done on actual merge commits
119-
taken from Linux 2.6 kernel development history.
120-
Additionally this can detect and handle merges involving
121-
renames. It does not make use of detected copies. This was
122-
the default strategy for resolving two heads from Git v0.99.9k
123-
until v2.33.0.
124-
+
125-
For a path that is a submodule, the same caution as 'ort' applies to this
126-
strategy.
127-
+
128-
The 'recursive' strategy takes the same options as 'ort'.
112+
This is now a synonym for `ort`. It was an alternative
113+
implementation until v2.49.0, but was redirected to mean `ort`
114+
in v2.50.0. The previous recursive strategy was the default
115+
strategy for resolving two heads from Git v0.99.9k until
116+
v2.33.0.
129117

130118
resolve::
131119
This can only resolve two heads (i.e. the current branch
@@ -146,7 +134,7 @@ ours::
146134
ignoring all changes from all other branches. It is meant to
147135
be used to supersede old development history of side
148136
branches. Note that this is different from the -Xours option to
149-
the 'recursive' merge strategy.
137+
the 'ort' merge strategy.
150138

151139
subtree::
152140
This is a modified `ort` strategy. When merging trees A and

Documentation/technical/sparse-checkout.adoc

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -356,8 +356,6 @@ understanding these differences can be beneficial.
356356
The behavior for these commands somewhat depends upon the merge
357357
strategy being used:
358358
* `ort` behaves as described above
359-
* `recursive` tries to not vivify files unnecessarily, but does sometimes
360-
vivify files without conflicts.
361359
* `octopus` and `resolve` will always vivify any file changed in the merge
362360
relative to the first parent, which is rather suboptimal.
363361

Makefile

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1069,7 +1069,6 @@ LIB_OBJS += merge-blobs.o
10691069
LIB_OBJS += merge-ll.o
10701070
LIB_OBJS += merge-ort.o
10711071
LIB_OBJS += merge-ort-wrappers.o
1072-
LIB_OBJS += merge-recursive.o
10731072
LIB_OBJS += merge.o
10741073
LIB_OBJS += midx.o
10751074
LIB_OBJS += midx-write.o

merge-ort-wrappers.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
#ifndef MERGE_ORT_WRAPPERS_H
22
#define MERGE_ORT_WRAPPERS_H
33

4-
#include "merge-recursive.h"
4+
#include "merge-ort.h"
55

66
/*
77
* rename-detecting three-way merge, no recursion.

merge-ort.c

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
#include "cache-tree.h"
2727
#include "commit.h"
2828
#include "commit-reach.h"
29+
#include "config.h"
2930
#include "diff.h"
3031
#include "diffcore.h"
3132
#include "dir.h"
@@ -5322,3 +5323,161 @@ void merge_incore_recursive(struct merge_options *opt,
53225323
merge_ort_internal(opt, merge_bases, side1, side2, result);
53235324
trace2_region_leave("merge", "incore_recursive", opt->repo);
53245325
}
5326+
5327+
static void merge_recursive_config(struct merge_options *opt, int ui)
5328+
{
5329+
char *value = NULL;
5330+
int renormalize = 0;
5331+
git_config_get_int("merge.verbosity", &opt->verbosity);
5332+
git_config_get_int("diff.renamelimit", &opt->rename_limit);
5333+
git_config_get_int("merge.renamelimit", &opt->rename_limit);
5334+
git_config_get_bool("merge.renormalize", &renormalize);
5335+
opt->renormalize = renormalize;
5336+
if (!git_config_get_string("diff.renames", &value)) {
5337+
opt->detect_renames = git_config_rename("diff.renames", value);
5338+
free(value);
5339+
}
5340+
if (!git_config_get_string("merge.renames", &value)) {
5341+
opt->detect_renames = git_config_rename("merge.renames", value);
5342+
free(value);
5343+
}
5344+
if (!git_config_get_string("merge.directoryrenames", &value)) {
5345+
int boolval = git_parse_maybe_bool(value);
5346+
if (0 <= boolval) {
5347+
opt->detect_directory_renames = boolval ?
5348+
MERGE_DIRECTORY_RENAMES_TRUE :
5349+
MERGE_DIRECTORY_RENAMES_NONE;
5350+
} else if (!strcasecmp(value, "conflict")) {
5351+
opt->detect_directory_renames =
5352+
MERGE_DIRECTORY_RENAMES_CONFLICT;
5353+
} /* avoid erroring on values from future versions of git */
5354+
free(value);
5355+
}
5356+
if (ui) {
5357+
if (!git_config_get_string("diff.algorithm", &value)) {
5358+
long diff_algorithm = parse_algorithm_value(value);
5359+
if (diff_algorithm < 0)
5360+
die(_("unknown value for config '%s': %s"), "diff.algorithm", value);
5361+
opt->xdl_opts = (opt->xdl_opts & ~XDF_DIFF_ALGORITHM_MASK) | diff_algorithm;
5362+
free(value);
5363+
}
5364+
}
5365+
git_config(git_xmerge_config, NULL);
5366+
}
5367+
5368+
static void init_merge_options(struct merge_options *opt,
5369+
struct repository *repo, int ui)
5370+
{
5371+
const char *merge_verbosity;
5372+
memset(opt, 0, sizeof(struct merge_options));
5373+
5374+
opt->repo = repo;
5375+
5376+
opt->detect_renames = -1;
5377+
opt->detect_directory_renames = MERGE_DIRECTORY_RENAMES_CONFLICT;
5378+
opt->rename_limit = -1;
5379+
5380+
opt->verbosity = 2;
5381+
opt->buffer_output = 1;
5382+
strbuf_init(&opt->obuf, 0);
5383+
5384+
opt->renormalize = 0;
5385+
5386+
opt->conflict_style = -1;
5387+
opt->xdl_opts = DIFF_WITH_ALG(opt, HISTOGRAM_DIFF);
5388+
5389+
merge_recursive_config(opt, ui);
5390+
merge_verbosity = getenv("GIT_MERGE_VERBOSITY");
5391+
if (merge_verbosity)
5392+
opt->verbosity = strtol(merge_verbosity, NULL, 10);
5393+
if (opt->verbosity >= 5)
5394+
opt->buffer_output = 0;
5395+
}
5396+
5397+
void init_ui_merge_options(struct merge_options *opt,
5398+
struct repository *repo)
5399+
{
5400+
init_merge_options(opt, repo, 1);
5401+
}
5402+
5403+
void init_basic_merge_options(struct merge_options *opt,
5404+
struct repository *repo)
5405+
{
5406+
init_merge_options(opt, repo, 0);
5407+
}
5408+
5409+
/*
5410+
* For now, members of merge_options do not need deep copying, but
5411+
* it may change in the future, in which case we would need to update
5412+
* this, and also make a matching change to clear_merge_options() to
5413+
* release the resources held by a copied instance.
5414+
*/
5415+
void copy_merge_options(struct merge_options *dst, struct merge_options *src)
5416+
{
5417+
*dst = *src;
5418+
}
5419+
5420+
void clear_merge_options(struct merge_options *opt UNUSED)
5421+
{
5422+
; /* no-op as our copy is shallow right now */
5423+
}
5424+
5425+
int parse_merge_opt(struct merge_options *opt, const char *s)
5426+
{
5427+
const char *arg;
5428+
5429+
if (!s || !*s)
5430+
return -1;
5431+
if (!strcmp(s, "ours"))
5432+
opt->recursive_variant = MERGE_VARIANT_OURS;
5433+
else if (!strcmp(s, "theirs"))
5434+
opt->recursive_variant = MERGE_VARIANT_THEIRS;
5435+
else if (!strcmp(s, "subtree"))
5436+
opt->subtree_shift = "";
5437+
else if (skip_prefix(s, "subtree=", &arg))
5438+
opt->subtree_shift = arg;
5439+
else if (!strcmp(s, "patience"))
5440+
opt->xdl_opts = DIFF_WITH_ALG(opt, PATIENCE_DIFF);
5441+
else if (!strcmp(s, "histogram"))
5442+
opt->xdl_opts = DIFF_WITH_ALG(opt, HISTOGRAM_DIFF);
5443+
else if (skip_prefix(s, "diff-algorithm=", &arg)) {
5444+
long value = parse_algorithm_value(arg);
5445+
if (value < 0)
5446+
return -1;
5447+
/* clear out previous settings */
5448+
DIFF_XDL_CLR(opt, NEED_MINIMAL);
5449+
opt->xdl_opts &= ~XDF_DIFF_ALGORITHM_MASK;
5450+
opt->xdl_opts |= value;
5451+
}
5452+
else if (!strcmp(s, "ignore-space-change"))
5453+
DIFF_XDL_SET(opt, IGNORE_WHITESPACE_CHANGE);
5454+
else if (!strcmp(s, "ignore-all-space"))
5455+
DIFF_XDL_SET(opt, IGNORE_WHITESPACE);
5456+
else if (!strcmp(s, "ignore-space-at-eol"))
5457+
DIFF_XDL_SET(opt, IGNORE_WHITESPACE_AT_EOL);
5458+
else if (!strcmp(s, "ignore-cr-at-eol"))
5459+
DIFF_XDL_SET(opt, IGNORE_CR_AT_EOL);
5460+
else if (!strcmp(s, "renormalize"))
5461+
opt->renormalize = 1;
5462+
else if (!strcmp(s, "no-renormalize"))
5463+
opt->renormalize = 0;
5464+
else if (!strcmp(s, "no-renames"))
5465+
opt->detect_renames = 0;
5466+
else if (!strcmp(s, "find-renames")) {
5467+
opt->detect_renames = 1;
5468+
opt->rename_score = 0;
5469+
}
5470+
else if (skip_prefix(s, "find-renames=", &arg) ||
5471+
skip_prefix(s, "rename-threshold=", &arg)) {
5472+
if ((opt->rename_score = parse_rename_score(&arg)) == -1 || *arg != 0)
5473+
return -1;
5474+
opt->detect_renames = 1;
5475+
}
5476+
/*
5477+
* Please update $__git_merge_strategy_options in
5478+
* git-completion.bash when you add new options
5479+
*/
5480+
else
5481+
return -1;
5482+
return 0;
5483+
}

merge-ort.h

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
#ifndef MERGE_ORT_H
22
#define MERGE_ORT_H
33

4-
#include "merge-recursive.h"
54
#include "hash.h"
5+
#include "strbuf.h"
66

77
struct commit;
8+
struct commit_list;
89
struct tree;
910
struct strmap;
1011

@@ -44,6 +45,51 @@ struct merge_result {
4445
unsigned _properly_initialized;
4546
};
4647

48+
struct merge_options_internal;
49+
struct merge_options {
50+
struct repository *repo;
51+
52+
/* ref names used in console messages and conflict markers */
53+
const char *ancestor;
54+
const char *branch1;
55+
const char *branch2;
56+
57+
/* rename related options */
58+
int detect_renames;
59+
enum {
60+
MERGE_DIRECTORY_RENAMES_NONE = 0,
61+
MERGE_DIRECTORY_RENAMES_CONFLICT = 1,
62+
MERGE_DIRECTORY_RENAMES_TRUE = 2
63+
} detect_directory_renames;
64+
int rename_limit;
65+
int rename_score;
66+
int show_rename_progress;
67+
68+
/* xdiff-related options (patience, ignore whitespace, ours/theirs) */
69+
long xdl_opts;
70+
int conflict_style;
71+
enum {
72+
MERGE_VARIANT_NORMAL = 0,
73+
MERGE_VARIANT_OURS,
74+
MERGE_VARIANT_THEIRS
75+
} recursive_variant;
76+
77+
/* console output related options */
78+
int verbosity;
79+
unsigned buffer_output; /* 1: output at end, 2: keep buffered */
80+
struct strbuf obuf; /* output buffer; if buffer_output == 2, caller
81+
* must handle and call strbuf_release */
82+
83+
/* miscellaneous control options */
84+
const char *subtree_shift;
85+
unsigned renormalize : 1;
86+
unsigned record_conflict_msgs_as_headers : 1;
87+
const char *msg_header_prefix;
88+
89+
/* internal fields used by the implementation */
90+
struct merge_options_internal *priv;
91+
};
92+
4793
/* Mostly internal function also used by merge-ort-wrappers.c */
4894
struct commit *make_virtual_commit(struct repository *repo,
4995
struct tree *tree,
@@ -119,4 +165,16 @@ void merge_get_conflicted_files(struct merge_result *result,
119165
void merge_finalize(struct merge_options *opt,
120166
struct merge_result *result);
121167

168+
169+
/* for use by porcelain commands */
170+
void init_ui_merge_options(struct merge_options *opt, struct repository *repo);
171+
/* for use by plumbing commands */
172+
void init_basic_merge_options(struct merge_options *opt, struct repository *repo);
173+
174+
void copy_merge_options(struct merge_options *dst, struct merge_options *src);
175+
void clear_merge_options(struct merge_options *opt);
176+
177+
/* parse the option in s and update the relevant field of opt */
178+
int parse_merge_opt(struct merge_options *opt, const char *s);
179+
122180
#endif

0 commit comments

Comments
 (0)