зеркало из https://github.com/microsoft/git.git
Merge branch 'en/merge-path-collision'
Updates for corner cases in merge-recursive. * en/merge-path-collision: t6036: avoid non-portable "cp -a" merge-recursive: combine error handling t6036, t6043: increase code coverage for file collision handling merge-recursive: improve rename/rename(1to2)/add[/add] handling merge-recursive: use handle_file_collision for add/add conflicts merge-recursive: improve handling for rename/rename(2to1) conflicts merge-recursive: fix rename/add conflict handling merge-recursive: new function for better colliding conflict resolutions merge-recursive: increase marker length with depth of recursion t6036, t6042: testcases for rename collision of already conflicting files t6042: add tests for consistency in file collision conflict handling
This commit is contained in:
Коммит
ac193e0e0a
|
@ -384,7 +384,9 @@ int ll_merge(mmbuffer_t *result_buf,
|
|||
if (opts->virtual_ancestor) {
|
||||
if (driver->recursive)
|
||||
driver = find_ll_merge_driver(driver->recursive);
|
||||
marker_size += 2;
|
||||
}
|
||||
if (opts->extra_marker_size) {
|
||||
marker_size += opts->extra_marker_size;
|
||||
}
|
||||
return driver->fn(driver, result_buf, path, ancestor, ancestor_label,
|
||||
ours, our_label, theirs, their_label,
|
||||
|
|
|
@ -13,6 +13,7 @@ struct ll_merge_options {
|
|||
unsigned virtual_ancestor : 1;
|
||||
unsigned variant : 2; /* favor ours, favor theirs, or union merge */
|
||||
unsigned renormalize : 1;
|
||||
unsigned extra_marker_size;
|
||||
long xdl_opts;
|
||||
};
|
||||
|
||||
|
|
|
@ -186,6 +186,7 @@ static int oid_eq(const struct object_id *a, const struct object_id *b)
|
|||
enum rename_type {
|
||||
RENAME_NORMAL = 0,
|
||||
RENAME_VIA_DIR,
|
||||
RENAME_ADD,
|
||||
RENAME_DELETE,
|
||||
RENAME_ONE_FILE_TO_ONE,
|
||||
RENAME_ONE_FILE_TO_TWO,
|
||||
|
@ -228,6 +229,7 @@ static inline void setup_rename_conflict_info(enum rename_type rename_type,
|
|||
struct stage_data *src_entry1,
|
||||
struct stage_data *src_entry2)
|
||||
{
|
||||
int ostage1 = 0, ostage2;
|
||||
struct rename_conflict_info *ci;
|
||||
|
||||
/*
|
||||
|
@ -264,18 +266,22 @@ static inline void setup_rename_conflict_info(enum rename_type rename_type,
|
|||
dst_entry2->rename_conflict_info = ci;
|
||||
}
|
||||
|
||||
if (rename_type == RENAME_TWO_FILES_TO_ONE) {
|
||||
/*
|
||||
* For each rename, there could have been
|
||||
* modifications on the side of history where that
|
||||
* file was not renamed.
|
||||
*/
|
||||
int ostage1 = o->branch1 == branch1 ? 3 : 2;
|
||||
int ostage2 = ostage1 ^ 1;
|
||||
if (rename_type == RENAME_ADD ||
|
||||
rename_type == RENAME_TWO_FILES_TO_ONE) {
|
||||
ostage1 = o->branch1 == branch1 ? 3 : 2;
|
||||
|
||||
ci->ren1_other.path = pair1->one->path;
|
||||
oidcpy(&ci->ren1_other.oid, &src_entry1->stages[ostage1].oid);
|
||||
ci->ren1_other.mode = src_entry1->stages[ostage1].mode;
|
||||
}
|
||||
|
||||
if (rename_type == RENAME_TWO_FILES_TO_ONE) {
|
||||
ostage2 = ostage1 ^ 1;
|
||||
|
||||
ci->ren2_other.path = pair2->one->path;
|
||||
oidcpy(&ci->ren2_other.oid, &src_entry2->stages[ostage2].oid);
|
||||
|
@ -690,27 +696,6 @@ static int update_stages(struct merge_options *opt, const char *path,
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int update_stages_for_stage_data(struct merge_options *opt,
|
||||
const char *path,
|
||||
const struct stage_data *stage_data)
|
||||
{
|
||||
struct diff_filespec o, a, b;
|
||||
|
||||
o.mode = stage_data->stages[1].mode;
|
||||
oidcpy(&o.oid, &stage_data->stages[1].oid);
|
||||
|
||||
a.mode = stage_data->stages[2].mode;
|
||||
oidcpy(&a.oid, &stage_data->stages[2].oid);
|
||||
|
||||
b.mode = stage_data->stages[3].mode;
|
||||
oidcpy(&b.oid, &stage_data->stages[3].oid);
|
||||
|
||||
return update_stages(opt, path,
|
||||
is_null_oid(&o.oid) ? NULL : &o,
|
||||
is_null_oid(&a.oid) ? NULL : &a,
|
||||
is_null_oid(&b.oid) ? NULL : &b);
|
||||
}
|
||||
|
||||
static void update_entry(struct stage_data *entry,
|
||||
struct diff_filespec *o,
|
||||
struct diff_filespec *a,
|
||||
|
@ -1058,7 +1043,8 @@ static int merge_3way(struct merge_options *o,
|
|||
const struct diff_filespec *a,
|
||||
const struct diff_filespec *b,
|
||||
const char *branch1,
|
||||
const char *branch2)
|
||||
const char *branch2,
|
||||
const int extra_marker_size)
|
||||
{
|
||||
mmfile_t orig, src1, src2;
|
||||
struct ll_merge_options ll_opts = {0};
|
||||
|
@ -1066,6 +1052,7 @@ static int merge_3way(struct merge_options *o,
|
|||
int merge_status;
|
||||
|
||||
ll_opts.renormalize = o->renormalize;
|
||||
ll_opts.extra_marker_size = extra_marker_size;
|
||||
ll_opts.xdl_opts = o->xdl_opts;
|
||||
|
||||
if (o->call_depth) {
|
||||
|
@ -1301,6 +1288,7 @@ static int merge_mode_and_contents(struct merge_options *o,
|
|||
const char *filename,
|
||||
const char *branch1,
|
||||
const char *branch2,
|
||||
const int extra_marker_size,
|
||||
struct merge_file_info *result)
|
||||
{
|
||||
if (o->branch1 != branch1) {
|
||||
|
@ -1311,7 +1299,8 @@ static int merge_mode_and_contents(struct merge_options *o,
|
|||
*/
|
||||
return merge_mode_and_contents(o, one, b, a,
|
||||
filename,
|
||||
branch2, branch1, result);
|
||||
branch2, branch1,
|
||||
extra_marker_size, result);
|
||||
}
|
||||
|
||||
result->merge = 0;
|
||||
|
@ -1352,7 +1341,8 @@ static int merge_mode_and_contents(struct merge_options *o,
|
|||
int ret = 0, merge_status;
|
||||
|
||||
merge_status = merge_3way(o, &result_buf, one, a, b,
|
||||
branch1, branch2);
|
||||
branch1, branch2,
|
||||
extra_marker_size);
|
||||
|
||||
if ((merge_status < 0) || !result_buf.ptr)
|
||||
ret = err(o, _("Failed to execute internal merge"));
|
||||
|
@ -1555,80 +1545,203 @@ static struct diff_filespec *filespec_from_entry(struct diff_filespec *target,
|
|||
return target;
|
||||
}
|
||||
|
||||
static int handle_file(struct merge_options *o,
|
||||
struct diff_filespec *rename,
|
||||
int stage,
|
||||
static int handle_file_collision(struct merge_options *o,
|
||||
const char *collide_path,
|
||||
const char *prev_path1,
|
||||
const char *prev_path2,
|
||||
const char *branch1, const char *branch2,
|
||||
const struct object_id *a_oid,
|
||||
unsigned int a_mode,
|
||||
const struct object_id *b_oid,
|
||||
unsigned int b_mode)
|
||||
{
|
||||
struct merge_file_info mfi;
|
||||
struct diff_filespec null, a, b;
|
||||
char *alt_path = NULL;
|
||||
const char *update_path = collide_path;
|
||||
|
||||
/*
|
||||
* It's easiest to get the correct things into stage 2 and 3, and
|
||||
* to make sure that the content merge puts HEAD before the other
|
||||
* branch if we just ensure that branch1 == o->branch1. So, simply
|
||||
* flip arguments around if we don't have that.
|
||||
*/
|
||||
if (branch1 != o->branch1) {
|
||||
return handle_file_collision(o, collide_path,
|
||||
prev_path2, prev_path1,
|
||||
branch2, branch1,
|
||||
b_oid, b_mode,
|
||||
a_oid, a_mode);
|
||||
}
|
||||
|
||||
/*
|
||||
* In the recursive case, we just opt to undo renames
|
||||
*/
|
||||
if (o->call_depth && (prev_path1 || prev_path2)) {
|
||||
/* Put first file (a_oid, a_mode) in its original spot */
|
||||
if (prev_path1) {
|
||||
if (update_file(o, 1, a_oid, a_mode, prev_path1))
|
||||
return -1;
|
||||
} else {
|
||||
if (update_file(o, 1, a_oid, a_mode, collide_path))
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Put second file (b_oid, b_mode) in its original spot */
|
||||
if (prev_path2) {
|
||||
if (update_file(o, 1, b_oid, b_mode, prev_path2))
|
||||
return -1;
|
||||
} else {
|
||||
if (update_file(o, 1, b_oid, b_mode, collide_path))
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Don't leave something at collision path if unrenaming both */
|
||||
if (prev_path1 && prev_path2)
|
||||
remove_file(o, 1, collide_path, 0);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Remove rename sources if rename/add or rename/rename(2to1) */
|
||||
if (prev_path1)
|
||||
remove_file(o, 1, prev_path1,
|
||||
o->call_depth || would_lose_untracked(prev_path1));
|
||||
if (prev_path2)
|
||||
remove_file(o, 1, prev_path2,
|
||||
o->call_depth || would_lose_untracked(prev_path2));
|
||||
|
||||
/*
|
||||
* Remove the collision path, if it wouldn't cause dirty contents
|
||||
* or an untracked file to get lost. We'll either overwrite with
|
||||
* merged contents, or just write out to differently named files.
|
||||
*/
|
||||
if (was_dirty(o, collide_path)) {
|
||||
output(o, 1, _("Refusing to lose dirty file at %s"),
|
||||
collide_path);
|
||||
update_path = alt_path = unique_path(o, collide_path, "merged");
|
||||
} else if (would_lose_untracked(collide_path)) {
|
||||
/*
|
||||
* Only way we get here is if both renames were from
|
||||
* a directory rename AND user had an untracked file
|
||||
* at the location where both files end up after the
|
||||
* two directory renames. See testcase 10d of t6043.
|
||||
*/
|
||||
output(o, 1, _("Refusing to lose untracked file at "
|
||||
"%s, even though it's in the way."),
|
||||
collide_path);
|
||||
update_path = alt_path = unique_path(o, collide_path, "merged");
|
||||
} else {
|
||||
/*
|
||||
* FIXME: It's possible that the two files are identical
|
||||
* and that the current working copy happens to match, in
|
||||
* which case we are unnecessarily touching the working
|
||||
* tree file. It's not a likely enough scenario that I
|
||||
* want to code up the checks for it and a better fix is
|
||||
* available if we restructure how unpack_trees() and
|
||||
* merge-recursive interoperate anyway, so punting for
|
||||
* now...
|
||||
*/
|
||||
remove_file(o, 0, collide_path, 0);
|
||||
}
|
||||
|
||||
/* Store things in diff_filespecs for functions that need it */
|
||||
memset(&a, 0, sizeof(struct diff_filespec));
|
||||
memset(&b, 0, sizeof(struct diff_filespec));
|
||||
null.path = a.path = b.path = (char *)collide_path;
|
||||
oidcpy(&null.oid, &null_oid);
|
||||
null.mode = 0;
|
||||
oidcpy(&a.oid, a_oid);
|
||||
a.mode = a_mode;
|
||||
a.oid_valid = 1;
|
||||
oidcpy(&b.oid, b_oid);
|
||||
b.mode = b_mode;
|
||||
b.oid_valid = 1;
|
||||
|
||||
if (merge_mode_and_contents(o, &null, &a, &b, collide_path,
|
||||
branch1, branch2, o->call_depth * 2, &mfi))
|
||||
return -1;
|
||||
mfi.clean &= !alt_path;
|
||||
if (update_file(o, mfi.clean, &mfi.oid, mfi.mode, update_path))
|
||||
return -1;
|
||||
if (!mfi.clean && !o->call_depth &&
|
||||
update_stages(o, collide_path, NULL, &a, &b))
|
||||
return -1;
|
||||
free(alt_path);
|
||||
/*
|
||||
* FIXME: If both a & b both started with conflicts (only possible
|
||||
* if they came from a rename/rename(2to1)), but had IDENTICAL
|
||||
* contents including those conflicts, then in the next line we claim
|
||||
* it was clean. If someone cares about this case, we should have the
|
||||
* caller notify us if we started with conflicts.
|
||||
*/
|
||||
return mfi.clean;
|
||||
}
|
||||
|
||||
static int handle_rename_add(struct merge_options *o,
|
||||
struct rename_conflict_info *ci)
|
||||
{
|
||||
char *dst_name = rename->path;
|
||||
struct stage_data *dst_entry;
|
||||
const char *cur_branch, *other_branch;
|
||||
struct diff_filespec other;
|
||||
struct diff_filespec *add;
|
||||
int ret;
|
||||
/* a was renamed to c, and a separate c was added. */
|
||||
struct diff_filespec *a = ci->pair1->one;
|
||||
struct diff_filespec *c = ci->pair1->two;
|
||||
char *path = c->path;
|
||||
char *prev_path_desc;
|
||||
struct merge_file_info mfi;
|
||||
|
||||
if (stage == 2) {
|
||||
dst_entry = ci->dst_entry1;
|
||||
cur_branch = ci->branch1;
|
||||
other_branch = ci->branch2;
|
||||
} else {
|
||||
dst_entry = ci->dst_entry2;
|
||||
cur_branch = ci->branch2;
|
||||
other_branch = ci->branch1;
|
||||
}
|
||||
int other_stage = (ci->branch1 == o->branch1 ? 3 : 2);
|
||||
|
||||
add = filespec_from_entry(&other, dst_entry, stage ^ 1);
|
||||
if (add) {
|
||||
int ren_src_was_dirty = was_dirty(o, rename->path);
|
||||
char *add_name = unique_path(o, rename->path, other_branch);
|
||||
if (update_file(o, 0, &add->oid, add->mode, add_name))
|
||||
output(o, 1, _("CONFLICT (rename/add): "
|
||||
"Rename %s->%s in %s. Added %s in %s"),
|
||||
a->path, c->path, ci->branch1,
|
||||
c->path, ci->branch2);
|
||||
|
||||
prev_path_desc = xstrfmt("version of %s from %s", path, a->path);
|
||||
if (merge_mode_and_contents(o, a, c, &ci->ren1_other, prev_path_desc,
|
||||
o->branch1, o->branch2,
|
||||
1 + o->call_depth * 2, &mfi))
|
||||
return -1;
|
||||
free(prev_path_desc);
|
||||
|
||||
if (ren_src_was_dirty) {
|
||||
output(o, 1, _("Refusing to lose dirty file at %s"),
|
||||
rename->path);
|
||||
return handle_file_collision(o,
|
||||
c->path, a->path, NULL,
|
||||
ci->branch1, ci->branch2,
|
||||
&mfi.oid, mfi.mode,
|
||||
&ci->dst_entry1->stages[other_stage].oid,
|
||||
ci->dst_entry1->stages[other_stage].mode);
|
||||
}
|
||||
/*
|
||||
* Because the double negatives somehow keep confusing me...
|
||||
* 1) update_wd iff !ren_src_was_dirty.
|
||||
* 2) no_wd iff !update_wd
|
||||
* 3) so, no_wd == !!ren_src_was_dirty == ren_src_was_dirty
|
||||
*/
|
||||
remove_file(o, 0, rename->path, ren_src_was_dirty);
|
||||
dst_name = unique_path(o, rename->path, cur_branch);
|
||||
} else {
|
||||
if (dir_in_way(rename->path, !o->call_depth, 0)) {
|
||||
dst_name = unique_path(o, rename->path, cur_branch);
|
||||
output(o, 1, _("%s is a directory in %s adding as %s instead"),
|
||||
rename->path, other_branch, dst_name);
|
||||
} else if (!o->call_depth &&
|
||||
would_lose_untracked(rename->path)) {
|
||||
dst_name = unique_path(o, rename->path, cur_branch);
|
||||
output(o, 1, _("Refusing to lose untracked file at %s; "
|
||||
"adding as %s instead"),
|
||||
rename->path, dst_name);
|
||||
}
|
||||
}
|
||||
if ((ret = update_file(o, 0, &rename->oid, rename->mode, dst_name)))
|
||||
; /* fall through, do allow dst_name to be released */
|
||||
else if (stage == 2)
|
||||
ret = update_stages(o, rename->path, NULL, rename, add);
|
||||
else
|
||||
ret = update_stages(o, rename->path, NULL, add, rename);
|
||||
|
||||
if (dst_name != rename->path)
|
||||
free(dst_name);
|
||||
static char *find_path_for_conflict(struct merge_options *o,
|
||||
const char *path,
|
||||
const char *branch1,
|
||||
const char *branch2)
|
||||
{
|
||||
char *new_path = NULL;
|
||||
if (dir_in_way(path, !o->call_depth, 0)) {
|
||||
new_path = unique_path(o, path, branch1);
|
||||
output(o, 1, _("%s is a directory in %s adding "
|
||||
"as %s instead"),
|
||||
path, branch2, new_path);
|
||||
} else if (would_lose_untracked(path)) {
|
||||
new_path = unique_path(o, path, branch1);
|
||||
output(o, 1, _("Refusing to lose untracked file"
|
||||
" at %s; adding as %s instead"),
|
||||
path, new_path);
|
||||
}
|
||||
|
||||
return ret;
|
||||
return new_path;
|
||||
}
|
||||
|
||||
static int handle_rename_rename_1to2(struct merge_options *o,
|
||||
struct rename_conflict_info *ci)
|
||||
{
|
||||
/* One file was renamed in both branches, but to different names. */
|
||||
struct merge_file_info mfi;
|
||||
struct diff_filespec other;
|
||||
struct diff_filespec *add;
|
||||
struct diff_filespec *one = ci->pair1->one;
|
||||
struct diff_filespec *a = ci->pair1->two;
|
||||
struct diff_filespec *b = ci->pair2->two;
|
||||
char *path_desc;
|
||||
|
||||
output(o, 1, _("CONFLICT (rename/rename): "
|
||||
"Rename \"%s\"->\"%s\" in branch \"%s\" "
|
||||
|
@ -1636,14 +1749,16 @@ static int handle_rename_rename_1to2(struct merge_options *o,
|
|||
one->path, a->path, ci->branch1,
|
||||
one->path, b->path, ci->branch2,
|
||||
o->call_depth ? _(" (left unresolved)") : "");
|
||||
if (o->call_depth) {
|
||||
struct merge_file_info mfi;
|
||||
struct diff_filespec other;
|
||||
struct diff_filespec *add;
|
||||
if (merge_mode_and_contents(o, one, a, b, one->path,
|
||||
ci->branch1, ci->branch2, &mfi))
|
||||
return -1;
|
||||
|
||||
path_desc = xstrfmt("%s and %s, both renamed from %s",
|
||||
a->path, b->path, one->path);
|
||||
if (merge_mode_and_contents(o, one, a, b, path_desc,
|
||||
ci->branch1, ci->branch2,
|
||||
o->call_depth * 2, &mfi))
|
||||
return -1;
|
||||
free(path_desc);
|
||||
|
||||
if (o->call_depth) {
|
||||
/*
|
||||
* FIXME: For rename/add-source conflicts (if we could detect
|
||||
* such), this is wrong. We should instead find a unique
|
||||
|
@ -1675,8 +1790,50 @@ static int handle_rename_rename_1to2(struct merge_options *o,
|
|||
}
|
||||
else
|
||||
remove_file_from_cache(b->path);
|
||||
} else if (handle_file(o, a, 2, ci) || handle_file(o, b, 3, ci))
|
||||
} else {
|
||||
/*
|
||||
* For each destination path, we need to see if there is a
|
||||
* rename/add collision. If not, we can write the file out
|
||||
* to the specified location.
|
||||
*/
|
||||
add = filespec_from_entry(&other, ci->dst_entry1, 2 ^ 1);
|
||||
if (add) {
|
||||
if (handle_file_collision(o, a->path,
|
||||
NULL, NULL,
|
||||
ci->branch1, ci->branch2,
|
||||
&mfi.oid, mfi.mode,
|
||||
&add->oid, add->mode) < 0)
|
||||
return -1;
|
||||
} else {
|
||||
char *new_path = find_path_for_conflict(o, a->path,
|
||||
ci->branch1,
|
||||
ci->branch2);
|
||||
if (update_file(o, 0, &mfi.oid, mfi.mode, new_path ? new_path : a->path))
|
||||
return -1;
|
||||
free(new_path);
|
||||
if (update_stages(o, a->path, NULL, a, NULL))
|
||||
return -1;
|
||||
}
|
||||
|
||||
add = filespec_from_entry(&other, ci->dst_entry2, 3 ^ 1);
|
||||
if (add) {
|
||||
if (handle_file_collision(o, b->path,
|
||||
NULL, NULL,
|
||||
ci->branch1, ci->branch2,
|
||||
&add->oid, add->mode,
|
||||
&mfi.oid, mfi.mode) < 0)
|
||||
return -1;
|
||||
} else {
|
||||
char *new_path = find_path_for_conflict(o, b->path,
|
||||
ci->branch2,
|
||||
ci->branch1);
|
||||
if (update_file(o, 0, &mfi.oid, mfi.mode, new_path ? new_path : b->path))
|
||||
return -1;
|
||||
free(new_path);
|
||||
if (update_stages(o, b->path, NULL, NULL, b))
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -1694,7 +1851,6 @@ static int handle_rename_rename_2to1(struct merge_options *o,
|
|||
char *path_side_2_desc;
|
||||
struct merge_file_info mfi_c1;
|
||||
struct merge_file_info mfi_c2;
|
||||
int ret;
|
||||
|
||||
output(o, 1, _("CONFLICT (rename/rename): "
|
||||
"Rename %s->%s in %s. "
|
||||
|
@ -1702,79 +1858,22 @@ static int handle_rename_rename_2to1(struct merge_options *o,
|
|||
a->path, c1->path, ci->branch1,
|
||||
b->path, c2->path, ci->branch2);
|
||||
|
||||
remove_file(o, 1, a->path, o->call_depth || would_lose_untracked(a->path));
|
||||
remove_file(o, 1, b->path, o->call_depth || would_lose_untracked(b->path));
|
||||
|
||||
path_side_1_desc = xstrfmt("version of %s from %s", path, a->path);
|
||||
path_side_2_desc = xstrfmt("version of %s from %s", path, b->path);
|
||||
if (merge_mode_and_contents(o, a, c1, &ci->ren1_other, path_side_1_desc,
|
||||
o->branch1, o->branch2, &mfi_c1) ||
|
||||
o->branch1, o->branch2,
|
||||
1 + o->call_depth * 2, &mfi_c1) ||
|
||||
merge_mode_and_contents(o, b, &ci->ren2_other, c2, path_side_2_desc,
|
||||
o->branch1, o->branch2, &mfi_c2))
|
||||
o->branch1, o->branch2,
|
||||
1 + o->call_depth * 2, &mfi_c2))
|
||||
return -1;
|
||||
free(path_side_1_desc);
|
||||
free(path_side_2_desc);
|
||||
|
||||
if (o->call_depth) {
|
||||
/*
|
||||
* If mfi_c1.clean && mfi_c2.clean, then it might make
|
||||
* sense to do a two-way merge of those results. But, I
|
||||
* think in all cases, it makes sense to have the virtual
|
||||
* merge base just undo the renames; they can be detected
|
||||
* again later for the non-recursive merge.
|
||||
*/
|
||||
remove_file(o, 0, path, 0);
|
||||
ret = update_file(o, 0, &mfi_c1.oid, mfi_c1.mode, a->path);
|
||||
if (!ret)
|
||||
ret = update_file(o, 0, &mfi_c2.oid, mfi_c2.mode,
|
||||
b->path);
|
||||
} else {
|
||||
char *new_path1 = unique_path(o, path, ci->branch1);
|
||||
char *new_path2 = unique_path(o, path, ci->branch2);
|
||||
output(o, 1, _("Renaming %s to %s and %s to %s instead"),
|
||||
a->path, new_path1, b->path, new_path2);
|
||||
if (was_dirty(o, path))
|
||||
output(o, 1, _("Refusing to lose dirty file at %s"),
|
||||
path);
|
||||
else if (would_lose_untracked(path))
|
||||
/*
|
||||
* Only way we get here is if both renames were from
|
||||
* a directory rename AND user had an untracked file
|
||||
* at the location where both files end up after the
|
||||
* two directory renames. See testcase 10d of t6043.
|
||||
*/
|
||||
output(o, 1, _("Refusing to lose untracked file at "
|
||||
"%s, even though it's in the way."),
|
||||
path);
|
||||
else
|
||||
remove_file(o, 0, path, 0);
|
||||
ret = update_file(o, 0, &mfi_c1.oid, mfi_c1.mode, new_path1);
|
||||
if (!ret)
|
||||
ret = update_file(o, 0, &mfi_c2.oid, mfi_c2.mode,
|
||||
new_path2);
|
||||
/*
|
||||
* unpack_trees() actually populates the index for us for
|
||||
* "normal" rename/rename(2to1) situtations so that the
|
||||
* correct entries are at the higher stages, which would
|
||||
* make the call below to update_stages_for_stage_data
|
||||
* unnecessary. However, if either of the renames came
|
||||
* from a directory rename, then unpack_trees() will not
|
||||
* have gotten the right data loaded into the index, so we
|
||||
* need to do so now. (While it'd be tempting to move this
|
||||
* call to update_stages_for_stage_data() to
|
||||
* apply_directory_rename_modifications(), that would break
|
||||
* our intermediate calls to would_lose_untracked() since
|
||||
* those rely on the current in-memory index. See also the
|
||||
* big "NOTE" in update_stages()).
|
||||
*/
|
||||
if (update_stages_for_stage_data(o, path, ci->dst_entry1))
|
||||
ret = -1;
|
||||
|
||||
free(new_path2);
|
||||
free(new_path1);
|
||||
}
|
||||
|
||||
return ret;
|
||||
return handle_file_collision(o, path, a->path, b->path,
|
||||
ci->branch1, ci->branch2,
|
||||
&mfi_c1.oid, mfi_c1.mode,
|
||||
&mfi_c2.oid, mfi_c2.mode);
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -2732,47 +2831,23 @@ static int process_renames(struct merge_options *o,
|
|||
0 /* update_wd */))
|
||||
clean_merge = -1;
|
||||
} else if (!oid_eq(&dst_other.oid, &null_oid)) {
|
||||
clean_merge = 0;
|
||||
try_merge = 1;
|
||||
output(o, 1, _("CONFLICT (rename/add): Rename %s->%s in %s. "
|
||||
"%s added in %s"),
|
||||
ren1_src, ren1_dst, branch1,
|
||||
ren1_dst, branch2);
|
||||
if (o->call_depth) {
|
||||
struct merge_file_info mfi;
|
||||
struct diff_filespec one, a, b;
|
||||
|
||||
oidcpy(&one.oid, &null_oid);
|
||||
one.mode = 0;
|
||||
one.path = ren1->pair->two->path;
|
||||
|
||||
oidcpy(&a.oid, &ren1->pair->two->oid);
|
||||
a.mode = ren1->pair->two->mode;
|
||||
a.path = one.path;
|
||||
|
||||
oidcpy(&b.oid, &dst_other.oid);
|
||||
b.mode = dst_other.mode;
|
||||
b.path = one.path;
|
||||
|
||||
if (merge_mode_and_contents(o, &one, &a, &b, ren1_dst,
|
||||
branch1, branch2,
|
||||
&mfi)) {
|
||||
clean_merge = -1;
|
||||
goto cleanup_and_return;
|
||||
}
|
||||
output(o, 1, _("Adding merged %s"), ren1_dst);
|
||||
if (update_file(o, 0, &mfi.oid,
|
||||
mfi.mode, ren1_dst))
|
||||
clean_merge = -1;
|
||||
try_merge = 0;
|
||||
} else {
|
||||
char *new_path = unique_path(o, ren1_dst, branch2);
|
||||
output(o, 1, _("Adding as %s instead"), new_path);
|
||||
if (update_file(o, 0, &dst_other.oid,
|
||||
dst_other.mode, new_path))
|
||||
clean_merge = -1;
|
||||
free(new_path);
|
||||
}
|
||||
/*
|
||||
* Probably not a clean merge, but it's
|
||||
* premature to set clean_merge to 0 here,
|
||||
* because if the rename merges cleanly and
|
||||
* the merge exactly matches the newly added
|
||||
* file, then the merge will be clean.
|
||||
*/
|
||||
setup_rename_conflict_info(RENAME_ADD,
|
||||
ren1->pair,
|
||||
NULL,
|
||||
branch1,
|
||||
branch2,
|
||||
ren1->dst_entry,
|
||||
NULL,
|
||||
o,
|
||||
ren1->src_entry,
|
||||
NULL);
|
||||
} else
|
||||
try_merge = 1;
|
||||
|
||||
|
@ -3053,7 +3128,8 @@ static int handle_content_merge(struct merge_options *o,
|
|||
df_conflict_remains = 1;
|
||||
}
|
||||
if (merge_mode_and_contents(o, &one, &a, &b, path,
|
||||
o->branch1, o->branch2, &mfi))
|
||||
o->branch1, o->branch2,
|
||||
o->call_depth * 2, &mfi))
|
||||
return -1;
|
||||
|
||||
/*
|
||||
|
@ -3183,6 +3259,15 @@ static int process_entry(struct merge_options *o,
|
|||
conflict_info->branch2))
|
||||
clean_merge = -1;
|
||||
break;
|
||||
case RENAME_ADD:
|
||||
/*
|
||||
* Probably unclean merge, but if the renamed file
|
||||
* merges cleanly and the result can then be
|
||||
* two-way merged cleanly with the added file, I
|
||||
* guess it's a clean merge?
|
||||
*/
|
||||
clean_merge = handle_rename_add(o, conflict_info);
|
||||
break;
|
||||
case RENAME_DELETE:
|
||||
clean_merge = 0;
|
||||
if (handle_rename_delete(o,
|
||||
|
@ -3197,9 +3282,14 @@ static int process_entry(struct merge_options *o,
|
|||
clean_merge = -1;
|
||||
break;
|
||||
case RENAME_TWO_FILES_TO_ONE:
|
||||
clean_merge = 0;
|
||||
if (handle_rename_rename_2to1(o, conflict_info))
|
||||
clean_merge = -1;
|
||||
/*
|
||||
* Probably unclean merge, but if the two renamed
|
||||
* files merge cleanly and the two resulting files
|
||||
* can then be two-way merged cleanly, I guess it's
|
||||
* a clean merge?
|
||||
*/
|
||||
clean_merge = handle_rename_rename_2to1(o,
|
||||
conflict_info);
|
||||
break;
|
||||
default:
|
||||
entry->processed = 0;
|
||||
|
@ -3267,14 +3357,27 @@ static int process_entry(struct merge_options *o,
|
|||
clean_merge = -1;
|
||||
}
|
||||
} else if (a_oid && b_oid) {
|
||||
/* Case C: Added in both (check for same permissions) and */
|
||||
if (!o_oid) {
|
||||
/* Case C: Added in both (check for same permissions) */
|
||||
output(o, 1,
|
||||
_("CONFLICT (add/add): Merge conflict in %s"),
|
||||
path);
|
||||
clean_merge = handle_file_collision(o,
|
||||
path, NULL, NULL,
|
||||
o->branch1,
|
||||
o->branch2,
|
||||
a_oid, a_mode,
|
||||
b_oid, b_mode);
|
||||
} else {
|
||||
/* case D: Modified in both, but differently. */
|
||||
int is_dirty = 0; /* unpack_trees would have bailed if dirty */
|
||||
clean_merge = handle_content_merge(o, path, is_dirty,
|
||||
clean_merge = handle_content_merge(o, path,
|
||||
is_dirty,
|
||||
o_oid, o_mode,
|
||||
a_oid, a_mode,
|
||||
b_oid, b_mode,
|
||||
NULL);
|
||||
}
|
||||
} else if (!o_oid && !a_oid && !b_oid) {
|
||||
/*
|
||||
* this entry was deleted altogether. a_mode == 0 means
|
||||
|
|
|
@ -64,15 +64,12 @@ test_expect_success 'merge simple rename+criss-cross with no modifications' '
|
|||
git ls-files -u >out &&
|
||||
test_line_count = 2 out &&
|
||||
git ls-files -o >out &&
|
||||
test_line_count = 3 out &&
|
||||
test_line_count = 1 out &&
|
||||
|
||||
git rev-parse >expect \
|
||||
L2:three R2:three \
|
||||
L2:three R2:three &&
|
||||
git rev-parse >actual \
|
||||
:2:three :3:three &&
|
||||
git hash-object >>actual \
|
||||
three~HEAD three~R2^0 &&
|
||||
test_cmp expect actual
|
||||
)
|
||||
'
|
||||
|
@ -140,15 +137,12 @@ test_expect_success 'merge criss-cross + rename merges with basic modification'
|
|||
git ls-files -u >out &&
|
||||
test_line_count = 2 out &&
|
||||
git ls-files -o >out &&
|
||||
test_line_count = 3 out &&
|
||||
test_line_count = 1 out &&
|
||||
|
||||
git rev-parse >expect \
|
||||
L2:three R2:three \
|
||||
L2:three R2:three &&
|
||||
git rev-parse >actual \
|
||||
:2:three :3:three &&
|
||||
git hash-object >>actual \
|
||||
three~HEAD three~R2^0 &&
|
||||
test_cmp expect actual
|
||||
)
|
||||
'
|
||||
|
@ -185,7 +179,7 @@ test_expect_success 'setup differently handled merges of rename/add conflict' '
|
|||
git branch B &&
|
||||
git checkout -b C &&
|
||||
echo 10 >>a &&
|
||||
echo "other content" >>new_a &&
|
||||
test_write_lines 0 1 2 3 4 5 6 7 foobar >new_a &&
|
||||
git add a new_a &&
|
||||
test_tick && git commit -m C &&
|
||||
|
||||
|
@ -195,14 +189,14 @@ test_expect_success 'setup differently handled merges of rename/add conflict' '
|
|||
|
||||
git checkout B^0 &&
|
||||
test_must_fail git merge C &&
|
||||
git clean -f &&
|
||||
git show :2:new_a >new_a &&
|
||||
git add new_a &&
|
||||
test_tick && git commit -m D &&
|
||||
git tag D &&
|
||||
|
||||
git checkout C^0 &&
|
||||
test_must_fail git merge B &&
|
||||
rm new_a~HEAD new_a &&
|
||||
printf "Incorrectly merged content" >>new_a &&
|
||||
test_write_lines 0 1 2 3 4 5 6 7 bad_merge >new_a &&
|
||||
git add -u &&
|
||||
test_tick && git commit -m E &&
|
||||
git tag E
|
||||
|
@ -225,21 +219,74 @@ test_expect_success 'git detects differently handled merges conflict' '
|
|||
test_line_count = 1 out &&
|
||||
|
||||
git rev-parse >expect \
|
||||
D:new_a E:new_a &&
|
||||
C:new_a D:new_a E:new_a &&
|
||||
git rev-parse >actual \
|
||||
:2:new_a :3:new_a &&
|
||||
:1:new_a :2:new_a :3:new_a &&
|
||||
test_cmp expect actual &&
|
||||
|
||||
git cat-file -p C:new_a >ours &&
|
||||
git cat-file -p B:new_a >theirs &&
|
||||
# Test that the two-way merge in new_a is as expected
|
||||
git cat-file -p D:new_a >ours &&
|
||||
git cat-file -p E:new_a >theirs &&
|
||||
>empty &&
|
||||
test_must_fail git merge-file \
|
||||
-L "Temporary merge branch 1" \
|
||||
-L "HEAD" \
|
||||
-L "" \
|
||||
-L "Temporary merge branch 2" \
|
||||
-L "E^0" \
|
||||
ours empty theirs &&
|
||||
sed -e "s/^\([<=>]\)/\1\1\1/" ours >expect &&
|
||||
git cat-file -p :1:new_a >actual &&
|
||||
git hash-object new_a >actual &&
|
||||
git hash-object ours >expect &&
|
||||
test_cmp expect actual
|
||||
)
|
||||
'
|
||||
|
||||
# Repeat the above testcase with precisely the same setup, other than with
|
||||
# the two merge bases having different orderings of commit timestamps so
|
||||
# that they are reversed in the order they are provided to merge-recursive,
|
||||
# so that we can improve code coverage.
|
||||
test_expect_success 'git detects differently handled merges conflict, swapped' '
|
||||
(
|
||||
cd rename-add &&
|
||||
|
||||
# Difference #1: Do cleanup from previous testrun
|
||||
git reset --hard &&
|
||||
git clean -fdqx &&
|
||||
|
||||
# Difference #2: Change commit timestamps
|
||||
btime=$(git log --no-walk --date=raw --format=%cd B | awk "{print \$1}") &&
|
||||
ctime=$(git log --no-walk --date=raw --format=%cd C | awk "{print \$1}") &&
|
||||
newctime=$(($btime+1)) &&
|
||||
git fast-export --no-data --all | sed -e s/$ctime/$newctime/ | git fast-import --force --quiet &&
|
||||
# End of differences; rest is copy-paste of last test
|
||||
|
||||
git checkout D^0 &&
|
||||
test_must_fail git merge -s recursive E^0 &&
|
||||
|
||||
git ls-files -s >out &&
|
||||
test_line_count = 3 out &&
|
||||
git ls-files -u >out &&
|
||||
test_line_count = 3 out &&
|
||||
git ls-files -o >out &&
|
||||
test_line_count = 1 out &&
|
||||
|
||||
git rev-parse >expect \
|
||||
C:new_a D:new_a E:new_a &&
|
||||
git rev-parse >actual \
|
||||
:1:new_a :2:new_a :3:new_a &&
|
||||
test_cmp expect actual &&
|
||||
|
||||
# Test that the two-way merge in new_a is as expected
|
||||
git cat-file -p D:new_a >ours &&
|
||||
git cat-file -p E:new_a >theirs &&
|
||||
>empty &&
|
||||
test_must_fail git merge-file \
|
||||
-L "HEAD" \
|
||||
-L "" \
|
||||
-L "E^0" \
|
||||
ours empty theirs &&
|
||||
sed -e "s/^\([<=>]\)/\1\1\1/" ours >expect &&
|
||||
git hash-object new_a >actual &&
|
||||
git hash-object ours >expect &&
|
||||
test_cmp expect actual
|
||||
)
|
||||
'
|
||||
|
@ -1402,4 +1449,349 @@ test_expect_failure 'check conflicting modes for regular file' '
|
|||
)
|
||||
'
|
||||
|
||||
# Setup:
|
||||
# L1---L2
|
||||
# / \ / \
|
||||
# master X ?
|
||||
# \ / \ /
|
||||
# R1---R2
|
||||
#
|
||||
# Where:
|
||||
# master has two files, named 'b' and 'a'
|
||||
# branches L1 and R1 both modify each of the two files in conflicting ways
|
||||
#
|
||||
# L2 is a merge of R1 into L1; more on it later.
|
||||
# R2 is a merge of L1 into R1; more on it later.
|
||||
#
|
||||
# X is an auto-generated merge-base used when merging L2 and R2.
|
||||
# since X is a merge of L1 and R1, it has conflicting versions of each file
|
||||
#
|
||||
# More about L2 and R2:
|
||||
# - both resolve the conflicts in 'b' and 'a' differently
|
||||
# - L2 renames 'b' to 'm'
|
||||
# - R2 renames 'a' to 'm'
|
||||
#
|
||||
# In the end, in file 'm' we have four different conflicting files (from
|
||||
# two versions of 'b' and two of 'a'). In addition, if
|
||||
# merge.conflictstyle is diff3, then the base version also has
|
||||
# conflict markers of its own, leading to a total of three levels of
|
||||
# conflict markers. This is a pretty weird corner case, but we just want
|
||||
# to ensure that we handle it as well as practical.
|
||||
|
||||
test_expect_success 'setup nested conflicts' '
|
||||
test_create_repo nested_conflicts &&
|
||||
(
|
||||
cd nested_conflicts &&
|
||||
|
||||
# Create some related files now
|
||||
for i in $(test_seq 1 10)
|
||||
do
|
||||
echo Random base content line $i
|
||||
done >initial &&
|
||||
|
||||
cp initial b_L1 &&
|
||||
cp initial b_R1 &&
|
||||
cp initial b_L2 &&
|
||||
cp initial b_R2 &&
|
||||
cp initial a_L1 &&
|
||||
cp initial a_R1 &&
|
||||
cp initial a_L2 &&
|
||||
cp initial a_R2 &&
|
||||
|
||||
test_write_lines b b_L1 >>b_L1 &&
|
||||
test_write_lines b b_R1 >>b_R1 &&
|
||||
test_write_lines b b_L2 >>b_L2 &&
|
||||
test_write_lines b b_R2 >>b_R2 &&
|
||||
test_write_lines a a_L1 >>a_L1 &&
|
||||
test_write_lines a a_R1 >>a_R1 &&
|
||||
test_write_lines a a_L2 >>a_L2 &&
|
||||
test_write_lines a a_R2 >>a_R2 &&
|
||||
|
||||
# Setup original commit (or merge-base), consisting of
|
||||
# files named "b" and "a"
|
||||
cp initial b &&
|
||||
cp initial a &&
|
||||
echo b >>b &&
|
||||
echo a >>a &&
|
||||
git add b a &&
|
||||
test_tick && git commit -m initial &&
|
||||
|
||||
git branch L &&
|
||||
git branch R &&
|
||||
|
||||
# Handle the left side
|
||||
git checkout L &&
|
||||
mv -f b_L1 b &&
|
||||
mv -f a_L1 a &&
|
||||
git add b a &&
|
||||
test_tick && git commit -m "version L1 of files" &&
|
||||
git tag L1 &&
|
||||
|
||||
# Handle the right side
|
||||
git checkout R &&
|
||||
mv -f b_R1 b &&
|
||||
mv -f a_R1 a &&
|
||||
git add b a &&
|
||||
test_tick && git commit -m "verson R1 of files" &&
|
||||
git tag R1 &&
|
||||
|
||||
# Create first merge on left side
|
||||
git checkout L &&
|
||||
test_must_fail git merge R1 &&
|
||||
mv -f b_L2 b &&
|
||||
mv -f a_L2 a &&
|
||||
git add b a &&
|
||||
git mv b m &&
|
||||
test_tick && git commit -m "left merge, rename b->m" &&
|
||||
git tag L2 &&
|
||||
|
||||
# Create first merge on right side
|
||||
git checkout R &&
|
||||
test_must_fail git merge L1 &&
|
||||
mv -f b_R2 b &&
|
||||
mv -f a_R2 a &&
|
||||
git add b a &&
|
||||
git mv a m &&
|
||||
test_tick && git commit -m "right merge, rename a->m" &&
|
||||
git tag R2
|
||||
)
|
||||
'
|
||||
|
||||
test_expect_success 'check nested conflicts' '
|
||||
(
|
||||
cd nested_conflicts &&
|
||||
|
||||
git clean -f &&
|
||||
git checkout L2^0 &&
|
||||
|
||||
# Merge must fail; there is a conflict
|
||||
test_must_fail git -c merge.conflictstyle=diff3 merge -s recursive R2^0 &&
|
||||
|
||||
# Make sure the index has the right number of entries
|
||||
git ls-files -s >out &&
|
||||
test_line_count = 2 out &&
|
||||
git ls-files -u >out &&
|
||||
test_line_count = 2 out &&
|
||||
# Ensure we have the correct number of untracked files
|
||||
git ls-files -o >out &&
|
||||
test_line_count = 1 out &&
|
||||
|
||||
# Create a and b from virtual merge base X
|
||||
git cat-file -p master:a >base &&
|
||||
git cat-file -p L1:a >ours &&
|
||||
git cat-file -p R1:a >theirs &&
|
||||
test_must_fail git merge-file --diff3 \
|
||||
-L "Temporary merge branch 1" \
|
||||
-L "merged common ancestors" \
|
||||
-L "Temporary merge branch 2" \
|
||||
ours \
|
||||
base \
|
||||
theirs &&
|
||||
sed -e "s/^\([<|=>]\)/\1\1/" ours >vmb_a &&
|
||||
|
||||
git cat-file -p master:b >base &&
|
||||
git cat-file -p L1:b >ours &&
|
||||
git cat-file -p R1:b >theirs &&
|
||||
test_must_fail git merge-file --diff3 \
|
||||
-L "Temporary merge branch 1" \
|
||||
-L "merged common ancestors" \
|
||||
-L "Temporary merge branch 2" \
|
||||
ours \
|
||||
base \
|
||||
theirs &&
|
||||
sed -e "s/^\([<|=>]\)/\1\1/" ours >vmb_b &&
|
||||
|
||||
# Compare :2:m to expected values
|
||||
git cat-file -p L2:m >ours &&
|
||||
git cat-file -p R2:b >theirs &&
|
||||
test_must_fail git merge-file --diff3 \
|
||||
-L "HEAD:m" \
|
||||
-L "merged common ancestors:b" \
|
||||
-L "R2^0:b" \
|
||||
ours \
|
||||
vmb_b \
|
||||
theirs &&
|
||||
sed -e "s/^\([<|=>]\)/\1\1/" ours >m_stage_2 &&
|
||||
git cat-file -p :2:m >actual &&
|
||||
test_cmp m_stage_2 actual &&
|
||||
|
||||
# Compare :3:m to expected values
|
||||
git cat-file -p L2:a >ours &&
|
||||
git cat-file -p R2:m >theirs &&
|
||||
test_must_fail git merge-file --diff3 \
|
||||
-L "HEAD:a" \
|
||||
-L "merged common ancestors:a" \
|
||||
-L "R2^0:m" \
|
||||
ours \
|
||||
vmb_a \
|
||||
theirs &&
|
||||
sed -e "s/^\([<|=>]\)/\1\1/" ours >m_stage_3 &&
|
||||
git cat-file -p :3:m >actual &&
|
||||
test_cmp m_stage_3 actual &&
|
||||
|
||||
# Compare m to expected contents
|
||||
>empty &&
|
||||
cp m_stage_2 expected_final_m &&
|
||||
test_must_fail git merge-file --diff3 \
|
||||
-L "HEAD" \
|
||||
-L "merged common ancestors" \
|
||||
-L "R2^0" \
|
||||
expected_final_m \
|
||||
empty \
|
||||
m_stage_3 &&
|
||||
test_cmp expected_final_m m
|
||||
)
|
||||
'
|
||||
|
||||
# Setup:
|
||||
# L1---L2---L3
|
||||
# / \ / \ / \
|
||||
# master X1 X2 ?
|
||||
# \ / \ / \ /
|
||||
# R1---R2---R3
|
||||
#
|
||||
# Where:
|
||||
# master has one file named 'content'
|
||||
# branches L1 and R1 both modify each of the two files in conflicting ways
|
||||
#
|
||||
# L<n> (n>1) is a merge of R<n-1> into L<n-1>
|
||||
# R<n> (n>1) is a merge of L<n-1> into R<n-1>
|
||||
# L<n> and R<n> resolve the conflicts differently.
|
||||
#
|
||||
# X<n> is an auto-generated merge-base used when merging L<n+1> and R<n+1>.
|
||||
# By construction, X1 has conflict markers due to conflicting versions.
|
||||
# X2, due to using merge.conflictstyle=3, has nested conflict markers.
|
||||
#
|
||||
# So, merging R3 into L3 using merge.conflictstyle=3 should show the
|
||||
# nested conflict markers from X2 in the base version -- that means we
|
||||
# have three levels of conflict markers. Can we distinguish all three?
|
||||
|
||||
test_expect_success 'setup virtual merge base with nested conflicts' '
|
||||
test_create_repo virtual_merge_base_has_nested_conflicts &&
|
||||
(
|
||||
cd virtual_merge_base_has_nested_conflicts &&
|
||||
|
||||
# Create some related files now
|
||||
for i in $(test_seq 1 10)
|
||||
do
|
||||
echo Random base content line $i
|
||||
done >content &&
|
||||
|
||||
# Setup original commit
|
||||
git add content &&
|
||||
test_tick && git commit -m initial &&
|
||||
|
||||
git branch L &&
|
||||
git branch R &&
|
||||
|
||||
# Create L1
|
||||
git checkout L &&
|
||||
echo left >>content &&
|
||||
git add content &&
|
||||
test_tick && git commit -m "version L1 of content" &&
|
||||
git tag L1 &&
|
||||
|
||||
# Create R1
|
||||
git checkout R &&
|
||||
echo right >>content &&
|
||||
git add content &&
|
||||
test_tick && git commit -m "verson R1 of content" &&
|
||||
git tag R1 &&
|
||||
|
||||
# Create L2
|
||||
git checkout L &&
|
||||
test_must_fail git -c merge.conflictstyle=diff3 merge R1 &&
|
||||
git checkout L1 content &&
|
||||
test_tick && git commit -m "version L2 of content" &&
|
||||
git tag L2 &&
|
||||
|
||||
# Create R2
|
||||
git checkout R &&
|
||||
test_must_fail git -c merge.conflictstyle=diff3 merge L1 &&
|
||||
git checkout R1 content &&
|
||||
test_tick && git commit -m "version R2 of content" &&
|
||||
git tag R2 &&
|
||||
|
||||
# Create L3
|
||||
git checkout L &&
|
||||
test_must_fail git -c merge.conflictstyle=diff3 merge R2 &&
|
||||
git checkout L1 content &&
|
||||
test_tick && git commit -m "version L3 of content" &&
|
||||
git tag L3 &&
|
||||
|
||||
# Create R3
|
||||
git checkout R &&
|
||||
test_must_fail git -c merge.conflictstyle=diff3 merge L2 &&
|
||||
git checkout R1 content &&
|
||||
test_tick && git commit -m "version R3 of content" &&
|
||||
git tag R3
|
||||
)
|
||||
'
|
||||
|
||||
test_expect_success 'check virtual merge base with nested conflicts' '
|
||||
(
|
||||
cd virtual_merge_base_has_nested_conflicts &&
|
||||
|
||||
git checkout L3^0 &&
|
||||
|
||||
# Merge must fail; there is a conflict
|
||||
test_must_fail git -c merge.conflictstyle=diff3 merge -s recursive R3^0 &&
|
||||
|
||||
# Make sure the index has the right number of entries
|
||||
git ls-files -s >out &&
|
||||
test_line_count = 3 out &&
|
||||
git ls-files -u >out &&
|
||||
test_line_count = 3 out &&
|
||||
# Ensure we have the correct number of untracked files
|
||||
git ls-files -o >out &&
|
||||
test_line_count = 1 out &&
|
||||
|
||||
# Compare :[23]:content to expected values
|
||||
git rev-parse L1:content R1:content >expect &&
|
||||
git rev-parse :2:content :3:content >actual &&
|
||||
test_cmp expect actual &&
|
||||
|
||||
# Imitate X1 merge base, except without long enough conflict
|
||||
# markers because a subsequent sed will modify them. Put
|
||||
# result into vmb.
|
||||
git cat-file -p master:content >base &&
|
||||
git cat-file -p L:content >left &&
|
||||
git cat-file -p R:content >right &&
|
||||
cp left merged-once &&
|
||||
test_must_fail git merge-file --diff3 \
|
||||
-L "Temporary merge branch 1" \
|
||||
-L "merged common ancestors" \
|
||||
-L "Temporary merge branch 2" \
|
||||
merged-once \
|
||||
base \
|
||||
right &&
|
||||
sed -e "s/^\([<|=>]\)/\1\1\1/" merged-once >vmb &&
|
||||
|
||||
# Imitate X2 merge base, overwriting vmb. Note that we
|
||||
# extend both sets of conflict markers to make them longer
|
||||
# with the sed command.
|
||||
cp left merged-twice &&
|
||||
test_must_fail git merge-file --diff3 \
|
||||
-L "Temporary merge branch 1" \
|
||||
-L "merged common ancestors" \
|
||||
-L "Temporary merge branch 2" \
|
||||
merged-twice \
|
||||
vmb \
|
||||
right &&
|
||||
sed -e "s/^\([<|=>]\)/\1\1\1/" merged-twice >vmb &&
|
||||
|
||||
# Compare :1:content to expected value
|
||||
git cat-file -p :1:content >actual &&
|
||||
test_cmp vmb actual &&
|
||||
|
||||
# Determine expected content in final outer merge, compare to
|
||||
# what the merge generated.
|
||||
cp -f left expect &&
|
||||
test_must_fail git merge-file --diff3 \
|
||||
-L "HEAD" -L "merged common ancestors" -L "R3^0" \
|
||||
expect vmb right &&
|
||||
test_cmp expect content
|
||||
)
|
||||
'
|
||||
|
||||
test_done
|
||||
|
|
|
@ -464,17 +464,28 @@ test_expect_success 'handle rename/rename (2to1) conflict correctly' '
|
|||
git ls-files -u c >out &&
|
||||
test_line_count = 2 out &&
|
||||
git ls-files -o >out &&
|
||||
test_line_count = 3 out &&
|
||||
test_line_count = 1 out &&
|
||||
|
||||
test_path_is_missing a &&
|
||||
test_path_is_missing b &&
|
||||
test_path_is_file c~HEAD &&
|
||||
test_path_is_file c~C^0 &&
|
||||
|
||||
git rev-parse >expect \
|
||||
C:a B:b &&
|
||||
git hash-object >actual \
|
||||
c~HEAD c~C^0 &&
|
||||
git rev-parse >actual \
|
||||
:2:c :3:c &&
|
||||
test_cmp expect actual &&
|
||||
|
||||
# Test that the two-way merge in new_a is as expected
|
||||
git cat-file -p :2:c >>ours &&
|
||||
git cat-file -p :3:c >>theirs &&
|
||||
>empty &&
|
||||
test_must_fail git merge-file \
|
||||
-L "HEAD" \
|
||||
-L "" \
|
||||
-L "C^0" \
|
||||
ours empty theirs &&
|
||||
git hash-object c >actual &&
|
||||
git hash-object ours >expect &&
|
||||
test_cmp expect actual
|
||||
)
|
||||
'
|
||||
|
@ -673,7 +684,7 @@ test_expect_success 'rename/rename/add-dest merge still knows about conflicting
|
|||
git ls-files -u c >out &&
|
||||
test_line_count = 2 out &&
|
||||
git ls-files -o >out &&
|
||||
test_line_count = 5 out &&
|
||||
test_line_count = 1 out &&
|
||||
|
||||
git rev-parse >expect \
|
||||
A:a C:b B:b C:c B:c &&
|
||||
|
@ -681,14 +692,27 @@ test_expect_success 'rename/rename/add-dest merge still knows about conflicting
|
|||
:1:a :2:b :3:b :2:c :3:c &&
|
||||
test_cmp expect actual &&
|
||||
|
||||
git rev-parse >expect \
|
||||
C:c B:c C:b B:b &&
|
||||
git hash-object >actual \
|
||||
c~HEAD c~B\^0 b~HEAD b~B\^0 &&
|
||||
test_cmp expect actual &&
|
||||
# Record some contents for re-doing merges
|
||||
git cat-file -p A:a >stuff &&
|
||||
git cat-file -p C:b >important_info &&
|
||||
git cat-file -p B:c >precious_data &&
|
||||
>empty &&
|
||||
|
||||
test_path_is_missing b &&
|
||||
test_path_is_missing c
|
||||
# Test the merge in b
|
||||
test_must_fail git merge-file \
|
||||
-L "HEAD" \
|
||||
-L "" \
|
||||
-L "B^0" \
|
||||
important_info empty stuff &&
|
||||
test_cmp important_info b &&
|
||||
|
||||
# Test the merge in c
|
||||
test_must_fail git merge-file \
|
||||
-L "HEAD" \
|
||||
-L "" \
|
||||
-L "B^0" \
|
||||
stuff empty precious_data &&
|
||||
test_cmp stuff c
|
||||
)
|
||||
'
|
||||
|
||||
|
@ -937,4 +961,283 @@ test_expect_failure 'mod6-check: chains of rename/rename(1to2) and rename/rename
|
|||
)
|
||||
'
|
||||
|
||||
test_conflicts_with_adds_and_renames() {
|
||||
sideL=$1
|
||||
sideR=$2
|
||||
|
||||
# Setup:
|
||||
# L
|
||||
# / \
|
||||
# master ?
|
||||
# \ /
|
||||
# R
|
||||
#
|
||||
# Where:
|
||||
# Both L and R have files named 'three' which collide. Each of
|
||||
# the colliding files could have been involved in a rename, in
|
||||
# which case there was a file named 'one' or 'two' that was
|
||||
# modified on the opposite side of history and renamed into the
|
||||
# collision on this side of history.
|
||||
#
|
||||
# Questions:
|
||||
# 1) The index should contain both a stage 2 and stage 3 entry
|
||||
# for the colliding file. Does it?
|
||||
# 2) When renames are involved, the content merges are clean, so
|
||||
# the index should reflect the content merges, not merely the
|
||||
# version of the colliding file from the prior commit. Does
|
||||
# it?
|
||||
# 3) There should be a file in the worktree named 'three'
|
||||
# containing the two-way merged contents of the content-merged
|
||||
# versions of 'three' from each of the two colliding
|
||||
# files. Is it present?
|
||||
# 4) There should not be any three~* files in the working
|
||||
# tree
|
||||
test_expect_success "setup simple $sideL/$sideR conflict" '
|
||||
test_create_repo simple_${sideL}_${sideR} &&
|
||||
(
|
||||
cd simple_${sideL}_${sideR} &&
|
||||
|
||||
# Create some related files now
|
||||
for i in $(test_seq 1 10)
|
||||
do
|
||||
echo Random base content line $i
|
||||
done >file_v1 &&
|
||||
cp file_v1 file_v2 &&
|
||||
echo modification >>file_v2 &&
|
||||
|
||||
cp file_v1 file_v3 &&
|
||||
echo more stuff >>file_v3 &&
|
||||
cp file_v3 file_v4 &&
|
||||
echo yet more stuff >>file_v4 &&
|
||||
|
||||
# Use a tag to record both these files for simple
|
||||
# access, and clean out these untracked files
|
||||
git tag file_v1 $(git hash-object -w file_v1) &&
|
||||
git tag file_v2 $(git hash-object -w file_v2) &&
|
||||
git tag file_v3 $(git hash-object -w file_v3) &&
|
||||
git tag file_v4 $(git hash-object -w file_v4) &&
|
||||
git clean -f &&
|
||||
|
||||
# Setup original commit (or merge-base), consisting of
|
||||
# files named "one" and "two" if renames were involved.
|
||||
touch irrelevant_file &&
|
||||
git add irrelevant_file &&
|
||||
if [ $sideL = "rename" ]
|
||||
then
|
||||
git show file_v1 >one &&
|
||||
git add one
|
||||
fi &&
|
||||
if [ $sideR = "rename" ]
|
||||
then
|
||||
git show file_v3 >two &&
|
||||
git add two
|
||||
fi &&
|
||||
test_tick && git commit -m initial &&
|
||||
|
||||
git branch L &&
|
||||
git branch R &&
|
||||
|
||||
# Handle the left side
|
||||
git checkout L &&
|
||||
if [ $sideL = "rename" ]
|
||||
then
|
||||
git mv one three
|
||||
else
|
||||
git show file_v2 >three &&
|
||||
git add three
|
||||
fi &&
|
||||
if [ $sideR = "rename" ]
|
||||
then
|
||||
git show file_v4 >two &&
|
||||
git add two
|
||||
fi &&
|
||||
test_tick && git commit -m L &&
|
||||
|
||||
# Handle the right side
|
||||
git checkout R &&
|
||||
if [ $sideL = "rename" ]
|
||||
then
|
||||
git show file_v2 >one &&
|
||||
git add one
|
||||
fi &&
|
||||
if [ $sideR = "rename" ]
|
||||
then
|
||||
git mv two three
|
||||
else
|
||||
git show file_v4 >three &&
|
||||
git add three
|
||||
fi &&
|
||||
test_tick && git commit -m R
|
||||
)
|
||||
'
|
||||
|
||||
test_expect_success "check simple $sideL/$sideR conflict" '
|
||||
(
|
||||
cd simple_${sideL}_${sideR} &&
|
||||
|
||||
git checkout L^0 &&
|
||||
|
||||
# Merge must fail; there is a conflict
|
||||
test_must_fail git merge -s recursive R^0 &&
|
||||
|
||||
# Make sure the index has the right number of entries
|
||||
git ls-files -s >out &&
|
||||
test_line_count = 3 out &&
|
||||
git ls-files -u >out &&
|
||||
test_line_count = 2 out &&
|
||||
# Ensure we have the correct number of untracked files
|
||||
git ls-files -o >out &&
|
||||
test_line_count = 1 out &&
|
||||
|
||||
# Nothing should have touched irrelevant_file
|
||||
git rev-parse >actual \
|
||||
:0:irrelevant_file \
|
||||
:2:three \
|
||||
:3:three &&
|
||||
git rev-parse >expected \
|
||||
master:irrelevant_file \
|
||||
file_v2 \
|
||||
file_v4 &&
|
||||
test_cmp expected actual &&
|
||||
|
||||
# Make sure we have the correct merged contents for
|
||||
# three
|
||||
git show file_v1 >expected &&
|
||||
cat <<-\EOF >>expected &&
|
||||
<<<<<<< HEAD
|
||||
modification
|
||||
=======
|
||||
more stuff
|
||||
yet more stuff
|
||||
>>>>>>> R^0
|
||||
EOF
|
||||
|
||||
test_cmp expected three
|
||||
)
|
||||
'
|
||||
}
|
||||
|
||||
test_conflicts_with_adds_and_renames rename rename
|
||||
test_conflicts_with_adds_and_renames rename add
|
||||
test_conflicts_with_adds_and_renames add rename
|
||||
test_conflicts_with_adds_and_renames add add
|
||||
|
||||
# Setup:
|
||||
# L
|
||||
# / \
|
||||
# master ?
|
||||
# \ /
|
||||
# R
|
||||
#
|
||||
# Where:
|
||||
# master has two files, named 'one' and 'two'.
|
||||
# branches L and R both modify 'one', in conflicting ways.
|
||||
# branches L and R both modify 'two', in conflicting ways.
|
||||
# branch L also renames 'one' to 'three'.
|
||||
# branch R also renames 'two' to 'three'.
|
||||
#
|
||||
# So, we have four different conflicting files that all end up at path
|
||||
# 'three'.
|
||||
test_expect_success 'setup nested conflicts from rename/rename(2to1)' '
|
||||
test_create_repo nested_conflicts_from_rename_rename &&
|
||||
(
|
||||
cd nested_conflicts_from_rename_rename &&
|
||||
|
||||
# Create some related files now
|
||||
for i in $(test_seq 1 10)
|
||||
do
|
||||
echo Random base content line $i
|
||||
done >file_v1 &&
|
||||
|
||||
cp file_v1 file_v2 &&
|
||||
cp file_v1 file_v3 &&
|
||||
cp file_v1 file_v4 &&
|
||||
cp file_v1 file_v5 &&
|
||||
cp file_v1 file_v6 &&
|
||||
|
||||
echo one >>file_v1 &&
|
||||
echo uno >>file_v2 &&
|
||||
echo eins >>file_v3 &&
|
||||
|
||||
echo two >>file_v4 &&
|
||||
echo dos >>file_v5 &&
|
||||
echo zwei >>file_v6 &&
|
||||
|
||||
# Setup original commit (or merge-base), consisting of
|
||||
# files named "one" and "two".
|
||||
mv file_v1 one &&
|
||||
mv file_v4 two &&
|
||||
git add one two &&
|
||||
test_tick && git commit -m english &&
|
||||
|
||||
git branch L &&
|
||||
git branch R &&
|
||||
|
||||
# Handle the left side
|
||||
git checkout L &&
|
||||
git mv one three &&
|
||||
mv -f file_v2 three &&
|
||||
mv -f file_v5 two &&
|
||||
git add two three &&
|
||||
test_tick && git commit -m spanish &&
|
||||
|
||||
# Handle the right side
|
||||
git checkout R &&
|
||||
git mv two three &&
|
||||
mv -f file_v3 one &&
|
||||
mv -f file_v6 three &&
|
||||
git add one three &&
|
||||
test_tick && git commit -m german
|
||||
)
|
||||
'
|
||||
|
||||
test_expect_success 'check nested conflicts from rename/rename(2to1)' '
|
||||
(
|
||||
cd nested_conflicts_from_rename_rename &&
|
||||
|
||||
git checkout L^0 &&
|
||||
|
||||
# Merge must fail; there is a conflict
|
||||
test_must_fail git merge -s recursive R^0 &&
|
||||
|
||||
# Make sure the index has the right number of entries
|
||||
git ls-files -s >out &&
|
||||
test_line_count = 2 out &&
|
||||
git ls-files -u >out &&
|
||||
test_line_count = 2 out &&
|
||||
# Ensure we have the correct number of untracked files
|
||||
git ls-files -o >out &&
|
||||
test_line_count = 1 out &&
|
||||
|
||||
# Compare :2:three to expected values
|
||||
git cat-file -p master:one >base &&
|
||||
git cat-file -p L:three >ours &&
|
||||
git cat-file -p R:one >theirs &&
|
||||
test_must_fail git merge-file \
|
||||
-L "HEAD:three" -L "" -L "R^0:one" \
|
||||
ours base theirs &&
|
||||
sed -e "s/^\([<=>]\)/\1\1/" ours >L-three &&
|
||||
git cat-file -p :2:three >expect &&
|
||||
test_cmp expect L-three &&
|
||||
|
||||
# Compare :2:three to expected values
|
||||
git cat-file -p master:two >base &&
|
||||
git cat-file -p L:two >ours &&
|
||||
git cat-file -p R:three >theirs &&
|
||||
test_must_fail git merge-file \
|
||||
-L "HEAD:two" -L "" -L "R^0:three" \
|
||||
ours base theirs &&
|
||||
sed -e "s/^\([<=>]\)/\1\1/" ours >R-three &&
|
||||
git cat-file -p :3:three >expect &&
|
||||
test_cmp expect R-three &&
|
||||
|
||||
# Compare three to expected contents
|
||||
>empty &&
|
||||
test_must_fail git merge-file \
|
||||
-L "HEAD" -L "" -L "R^0" \
|
||||
L-three empty R-three &&
|
||||
test_cmp three L-three
|
||||
)
|
||||
'
|
||||
|
||||
test_done
|
||||
|
|
|
@ -278,7 +278,7 @@ test_expect_success '1d-check: Directory renames cause a rename/rename(2to1) con
|
|||
git ls-files -u >out &&
|
||||
test_line_count = 2 out &&
|
||||
git ls-files -o >out &&
|
||||
test_line_count = 3 out &&
|
||||
test_line_count = 1 out &&
|
||||
|
||||
git rev-parse >actual \
|
||||
:0:x/b :0:x/c :0:x/d :0:x/e :0:x/m :0:x/n &&
|
||||
|
@ -293,15 +293,16 @@ test_expect_success '1d-check: Directory renames cause a rename/rename(2to1) con
|
|||
A:y/wham B:z/wham &&
|
||||
test_cmp expect actual &&
|
||||
|
||||
test_path_is_missing x/wham &&
|
||||
test_path_is_file x/wham~HEAD &&
|
||||
test_path_is_file x/wham~B^0 &&
|
||||
|
||||
git hash-object >actual \
|
||||
x/wham~HEAD x/wham~B^0 &&
|
||||
git rev-parse >expect \
|
||||
A:y/wham B:z/wham &&
|
||||
test_cmp expect actual
|
||||
# Test that the two-way merge in x/wham is as expected
|
||||
git cat-file -p :2:x/wham >expect &&
|
||||
git cat-file -p :3:x/wham >other &&
|
||||
>empty &&
|
||||
test_must_fail git merge-file \
|
||||
-L "HEAD" \
|
||||
-L "" \
|
||||
-L "B^0" \
|
||||
expect empty other &&
|
||||
test_cmp expect x/wham
|
||||
)
|
||||
'
|
||||
|
||||
|
@ -1077,7 +1078,7 @@ test_expect_success '5c-check: Transitive rename would cause rename/rename/renam
|
|||
git ls-files -u >out &&
|
||||
test_line_count = 6 out &&
|
||||
git ls-files -o >out &&
|
||||
test_line_count = 3 out &&
|
||||
test_line_count = 1 out &&
|
||||
|
||||
git rev-parse >actual \
|
||||
:0:y/b :0:y/c :0:y/e &&
|
||||
|
@ -1093,9 +1094,9 @@ test_expect_success '5c-check: Transitive rename would cause rename/rename/renam
|
|||
test_cmp expect actual &&
|
||||
|
||||
git hash-object >actual \
|
||||
w/d~HEAD w/d~B^0 z/d &&
|
||||
z/d &&
|
||||
git rev-parse >expect \
|
||||
O:x/d B:w/d O:x/d &&
|
||||
O:x/d &&
|
||||
test_cmp expect actual &&
|
||||
test_path_is_missing x/d &&
|
||||
test_path_is_file y/d &&
|
||||
|
@ -1670,7 +1671,7 @@ test_expect_success '7b-check: rename/rename(2to1), but only due to transitive r
|
|||
git ls-files -u >out &&
|
||||
test_line_count = 2 out &&
|
||||
git ls-files -o >out &&
|
||||
test_line_count = 3 out &&
|
||||
test_line_count = 1 out &&
|
||||
|
||||
git rev-parse >actual \
|
||||
:0:y/b :0:y/c :2:y/d :3:y/d &&
|
||||
|
@ -1678,15 +1679,16 @@ test_expect_success '7b-check: rename/rename(2to1), but only due to transitive r
|
|||
O:z/b O:z/c O:w/d O:x/d &&
|
||||
test_cmp expect actual &&
|
||||
|
||||
test_path_is_missing y/d &&
|
||||
test_path_is_file y/d~HEAD &&
|
||||
test_path_is_file y/d~B^0 &&
|
||||
|
||||
git hash-object >actual \
|
||||
y/d~HEAD y/d~B^0 &&
|
||||
git rev-parse >expect \
|
||||
O:w/d O:x/d &&
|
||||
test_cmp expect actual
|
||||
# Test that the two-way merge in y/d is as expected
|
||||
git cat-file -p :2:y/d >expect &&
|
||||
git cat-file -p :3:y/d >other &&
|
||||
>empty &&
|
||||
test_must_fail git merge-file \
|
||||
-L "HEAD" \
|
||||
-L "" \
|
||||
-L "B^0" \
|
||||
expect empty other &&
|
||||
test_cmp expect y/d
|
||||
)
|
||||
'
|
||||
|
||||
|
@ -3161,11 +3163,48 @@ test_expect_success '10c-check: Overwrite untracked with dir rename/rename(1to2)
|
|||
)
|
||||
'
|
||||
|
||||
test_expect_success '10c-check: Overwrite untracked with dir rename/rename(1to2), other direction' '
|
||||
(
|
||||
cd 10c &&
|
||||
|
||||
git reset --hard &&
|
||||
git clean -fdqx &&
|
||||
|
||||
git checkout B^0 &&
|
||||
mkdir y &&
|
||||
echo important >y/c &&
|
||||
|
||||
test_must_fail git merge -s recursive A^0 >out 2>err &&
|
||||
test_i18ngrep "CONFLICT (rename/rename)" out &&
|
||||
test_i18ngrep "Refusing to lose untracked file at y/c; adding as y/c~HEAD instead" out &&
|
||||
|
||||
git ls-files -s >out &&
|
||||
test_line_count = 6 out &&
|
||||
git ls-files -u >out &&
|
||||
test_line_count = 3 out &&
|
||||
git ls-files -o >out &&
|
||||
test_line_count = 3 out &&
|
||||
|
||||
git rev-parse >actual \
|
||||
:0:y/a :0:y/b :0:x/d :1:x/c :3:w/c :2:y/c &&
|
||||
git rev-parse >expect \
|
||||
O:z/a O:z/b O:x/d O:x/c O:x/c O:x/c &&
|
||||
test_cmp expect actual &&
|
||||
|
||||
git hash-object y/c~HEAD >actual &&
|
||||
git rev-parse O:x/c >expect &&
|
||||
test_cmp expect actual &&
|
||||
|
||||
echo important >expect &&
|
||||
test_cmp expect y/c
|
||||
)
|
||||
'
|
||||
|
||||
# Testcase 10d, Delete untracked w/ dir rename/rename(2to1)
|
||||
# Commit O: z/{a,b,c_1}, x/{d,e,f_2}
|
||||
# Commit A: y/{a,b}, x/{d,e,f_2,wham_1} + untracked y/wham
|
||||
# Commit B: z/{a,b,c_1,wham_2}, y/{d,e}
|
||||
# Expected: Failed Merge; y/{a,b,d,e} + untracked y/{wham,wham~B^0,wham~HEAD}+
|
||||
# Expected: Failed Merge; y/{a,b,d,e} + untracked y/{wham,wham~merged}+
|
||||
# CONFLICT(rename/rename) z/c_1 vs x/f_2 -> y/wham
|
||||
# ERROR_MSG(Refusing to lose untracked file at y/wham)
|
||||
|
||||
|
@ -3219,7 +3258,7 @@ test_expect_success '10d-check: Delete untracked with dir rename/rename(2to1)' '
|
|||
git ls-files -u >out &&
|
||||
test_line_count = 2 out &&
|
||||
git ls-files -o >out &&
|
||||
test_line_count = 4 out &&
|
||||
test_line_count = 3 out &&
|
||||
|
||||
git rev-parse >actual \
|
||||
:0:y/a :0:y/b :0:y/d :0:y/e :2:y/wham :3:y/wham &&
|
||||
|
@ -3232,11 +3271,16 @@ test_expect_success '10d-check: Delete untracked with dir rename/rename(2to1)' '
|
|||
echo important >expect &&
|
||||
test_cmp expect y/wham &&
|
||||
|
||||
git hash-object >actual \
|
||||
y/wham~B^0 y/wham~HEAD &&
|
||||
git rev-parse >expect \
|
||||
O:x/f O:z/c &&
|
||||
test_cmp expect actual
|
||||
# Test that the two-way merge in y/wham~merged is as expected
|
||||
git cat-file -p :2:y/wham >expect &&
|
||||
git cat-file -p :3:y/wham >other &&
|
||||
>empty &&
|
||||
test_must_fail git merge-file \
|
||||
-L "HEAD" \
|
||||
-L "" \
|
||||
-L "B^0" \
|
||||
expect empty other &&
|
||||
test_cmp expect y/wham~merged
|
||||
)
|
||||
'
|
||||
|
||||
|
@ -3665,7 +3709,7 @@ test_expect_success '11e-check: Avoid deleting not-uptodate with dir rename/rena
|
|||
git ls-files -u >out &&
|
||||
test_line_count = 4 out &&
|
||||
git ls-files -o >out &&
|
||||
test_line_count = 4 out &&
|
||||
test_line_count = 3 out &&
|
||||
|
||||
echo different >expected &&
|
||||
echo mods >>expected &&
|
||||
|
@ -3677,11 +3721,17 @@ test_expect_success '11e-check: Avoid deleting not-uptodate with dir rename/rena
|
|||
O:z/a O:z/b O:x/d O:x/c O:x/c A:y/c O:x/c &&
|
||||
test_cmp expect actual &&
|
||||
|
||||
git hash-object >actual \
|
||||
y/c~B^0 y/c~HEAD &&
|
||||
git rev-parse >expect \
|
||||
O:x/c A:y/c &&
|
||||
test_cmp expect actual
|
||||
# See if y/c~merged has expected contents; requires manually
|
||||
# doing the expected file merge
|
||||
git cat-file -p A:y/c >c1 &&
|
||||
git cat-file -p B:z/c >c2 &&
|
||||
>empty &&
|
||||
test_must_fail git merge-file \
|
||||
-L "HEAD" \
|
||||
-L "" \
|
||||
-L "B^0" \
|
||||
c1 empty c2 &&
|
||||
test_cmp c1 y/c~merged
|
||||
)
|
||||
'
|
||||
|
||||
|
@ -3689,7 +3739,7 @@ test_expect_success '11e-check: Avoid deleting not-uptodate with dir rename/rena
|
|||
# Commit O: z/{a,b}, x/{c_1,d_2}
|
||||
# Commit A: y/{a,b,wham_1}, x/d_2, except y/wham has uncommitted mods
|
||||
# Commit B: z/{a,b,wham_2}, x/c_1
|
||||
# Expected: Failed Merge; y/{a,b} + untracked y/{wham~B^0,wham~B^HEAD} +
|
||||
# Expected: Failed Merge; y/{a,b} + untracked y/{wham~merged} +
|
||||
# y/wham with dirty changes from before merge +
|
||||
# CONFLICT(rename/rename) x/c vs x/d -> y/wham
|
||||
# ERROR_MSG(Refusing to lose dirty file at y/wham)
|
||||
|
@ -3741,24 +3791,30 @@ test_expect_success '11f-check: Avoid deleting not-uptodate with dir rename/rena
|
|||
git ls-files -u >out &&
|
||||
test_line_count = 2 out &&
|
||||
git ls-files -o >out &&
|
||||
test_line_count = 4 out &&
|
||||
test_line_count = 3 out &&
|
||||
|
||||
test_seq 1 10 >expected &&
|
||||
echo important >>expected &&
|
||||
test_cmp expected y/wham &&
|
||||
|
||||
test_must_fail git rev-parse :1:y/wham &&
|
||||
git hash-object >actual \
|
||||
y/wham~B^0 y/wham~HEAD &&
|
||||
git rev-parse >expect \
|
||||
O:x/d O:x/c &&
|
||||
test_cmp expect actual &&
|
||||
|
||||
git rev-parse >actual \
|
||||
:0:y/a :0:y/b :2:y/wham :3:y/wham &&
|
||||
git rev-parse >expect \
|
||||
O:z/a O:z/b O:x/c O:x/d &&
|
||||
test_cmp expect actual
|
||||
test_cmp expect actual &&
|
||||
|
||||
# Test that the two-way merge in y/wham~merged is as expected
|
||||
git cat-file -p :2:y/wham >expect &&
|
||||
git cat-file -p :3:y/wham >other &&
|
||||
>empty &&
|
||||
test_must_fail git merge-file \
|
||||
-L "HEAD" \
|
||||
-L "" \
|
||||
-L "B^0" \
|
||||
expect empty other &&
|
||||
test_cmp expect y/wham~merged
|
||||
)
|
||||
'
|
||||
|
||||
|
|
Загрузка…
Ссылка в новой задаче