Skip to content

Commit 35ffe75

Browse files
committed
merge-tree: fix d/f conflicts
The previous commit documented two known breakages revolving around a case where one side flips a tree into a blob (or vice versa), where the original code simply gets confused and feeds a mixture of trees and blobs into either the recursive merge-tree (and recursing into the blob will fail) or three-way merge (and merging tree contents together with blobs will fail). Fix it by feeding trees (and only trees) into the recursive merge-tree machinery and blobs (and only blobs) into the three-way content level merge machinery separately; when this happens, the entire merge has to be marked as conflicting at the structure level. Signed-off-by: Junio C Hamano <[email protected]>
1 parent 8dd15c6 commit 35ffe75

File tree

2 files changed

+42
-34
lines changed

2 files changed

+42
-34
lines changed

builtin/merge-tree.c

Lines changed: 40 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ static void add_merge_entry(struct merge_list *entry)
2525
merge_result_end = &entry->next;
2626
}
2727

28-
static void merge_trees(struct tree_desc t[3], const char *base);
28+
static void merge_trees_recursive(struct tree_desc t[3], const char *base, int df_conflict);
2929

3030
static const char *explanation(struct merge_list *entry)
3131
{
@@ -190,41 +190,35 @@ static void resolve(const struct traverse_info *info, struct name_entry *ours, s
190190
add_merge_entry(final);
191191
}
192192

193-
static int unresolved_directory(const struct traverse_info *info, struct name_entry n[3])
193+
static void unresolved_directory(const struct traverse_info *info, struct name_entry n[3],
194+
int df_conflict)
194195
{
195196
char *newbase;
196197
struct name_entry *p;
197198
struct tree_desc t[3];
198199
void *buf0, *buf1, *buf2;
199200

200-
p = n;
201-
if (!p->mode) {
202-
p++;
203-
if (!p->mode)
204-
p++;
201+
for (p = n; p < n + 3; p++) {
202+
if (p->mode && S_ISDIR(p->mode))
203+
break;
205204
}
206-
if (!S_ISDIR(p->mode))
207-
return 0;
208-
/*
209-
* NEEDSWORK: this is broken. The path can originally be a file
210-
* and then one side may have turned it into a directory, in which
211-
* case we return and let the three-way merge as if the tree were
212-
* a regular file. If the path that was originally a tree is
213-
* now a file in either branch, fill_tree_descriptor() below will
214-
* die when fed a blob sha1.
215-
*/
205+
if (n + 3 <= p)
206+
return; /* there is no tree here */
216207

217208
newbase = traverse_path(info, p);
218-
buf0 = fill_tree_descriptor(t+0, n[0].sha1);
219-
buf1 = fill_tree_descriptor(t+1, n[1].sha1);
220-
buf2 = fill_tree_descriptor(t+2, n[2].sha1);
221-
merge_trees(t, newbase);
209+
210+
#define ENTRY_SHA1(e) (((e)->mode && S_ISDIR((e)->mode)) ? (e)->sha1 : NULL)
211+
buf0 = fill_tree_descriptor(t+0, ENTRY_SHA1(n + 0));
212+
buf1 = fill_tree_descriptor(t+1, ENTRY_SHA1(n + 1));
213+
buf2 = fill_tree_descriptor(t+2, ENTRY_SHA1(n + 2));
214+
#undef ENTRY_SHA1
215+
216+
merge_trees_recursive(t, newbase, df_conflict);
222217

223218
free(buf0);
224219
free(buf1);
225220
free(buf2);
226221
free(newbase);
227-
return 1;
228222
}
229223

230224

@@ -247,18 +241,26 @@ static struct merge_list *link_entry(unsigned stage, const struct traverse_info
247241
static void unresolved(const struct traverse_info *info, struct name_entry n[3])
248242
{
249243
struct merge_list *entry = NULL;
244+
int i;
245+
unsigned dirmask = 0, mask = 0;
246+
247+
for (i = 0; i < 3; i++) {
248+
mask |= (1 << 1);
249+
if (n[i].mode && S_ISDIR(n[i].mode))
250+
dirmask |= (1 << i);
251+
}
252+
253+
unresolved_directory(info, n, dirmask && (dirmask != mask));
250254

251-
if (unresolved_directory(info, n))
255+
if (dirmask == mask)
252256
return;
253257

254-
/*
255-
* Do them in reverse order so that the resulting link
256-
* list has the stages in order - link_entry adds new
257-
* links at the front.
258-
*/
259-
entry = link_entry(3, info, n + 2, entry);
260-
entry = link_entry(2, info, n + 1, entry);
261-
entry = link_entry(1, info, n + 0, entry);
258+
if (n[2].mode && !S_ISDIR(n[2].mode))
259+
entry = link_entry(3, info, n + 2, entry);
260+
if (n[1].mode && !S_ISDIR(n[1].mode))
261+
entry = link_entry(2, info, n + 1, entry);
262+
if (n[0].mode && !S_ISDIR(n[0].mode))
263+
entry = link_entry(1, info, n + 0, entry);
262264

263265
add_merge_entry(entry);
264266
}
@@ -329,15 +331,21 @@ static int threeway_callback(int n, unsigned long mask, unsigned long dirmask, s
329331
return mask;
330332
}
331333

332-
static void merge_trees(struct tree_desc t[3], const char *base)
334+
static void merge_trees_recursive(struct tree_desc t[3], const char *base, int df_conflict)
333335
{
334336
struct traverse_info info;
335337

336338
setup_traverse_info(&info, base);
339+
info.data = &df_conflict;
337340
info.fn = threeway_callback;
338341
traverse_trees(3, t, &info);
339342
}
340343

344+
static void merge_trees(struct tree_desc t[3], const char *base)
345+
{
346+
merge_trees_recursive(t, base, 0);
347+
}
348+
341349
static void *get_tree_descriptor(struct tree_desc *desc, const char *rev)
342350
{
343351
unsigned char sha1[20];

t/t4300-merge-tree.sh

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -254,7 +254,7 @@ EXPECTED
254254
test_cmp expected actual
255255
'
256256

257-
test_expect_failure 'turn file to tree' '
257+
test_expect_success 'turn file to tree' '
258258
git reset --hard initial &&
259259
rm initial-file &&
260260
mkdir initial-file &&
@@ -274,7 +274,7 @@ test_expect_failure 'turn file to tree' '
274274
test_cmp expect actual
275275
'
276276

277-
test_expect_failure 'turn tree to file' '
277+
test_expect_success 'turn tree to file' '
278278
git reset --hard initial &&
279279
mkdir dir &&
280280
test_commit "add-tree" "dir/path" "AAA" &&

0 commit comments

Comments
 (0)