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 handlingmaint
						commit
						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; | ||||
| 	/* | ||||
| 	 * For each rename, there could have been | ||||
| 	 * modifications on the side of history where that | ||||
| 	 * file was not renamed. | ||||
| 	 */ | ||||
| 	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, | ||||
| 			struct rename_conflict_info *ci) | ||||
| 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) | ||||
| { | ||||
| 	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; | ||||
| 	struct merge_file_info mfi; | ||||
| 	struct diff_filespec null, a, b; | ||||
| 	char *alt_path = NULL; | ||||
| 	const char *update_path = collide_path; | ||||
|  | ||||
| 	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; | ||||
| 	/* | ||||
| 	 * 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); | ||||
| 	} | ||||
|  | ||||
| 	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)) | ||||
| 			return -1; | ||||
|  | ||||
| 		if (ren_src_was_dirty) { | ||||
| 			output(o, 1, _("Refusing to lose dirty file at %s"), | ||||
| 			       rename->path); | ||||
| 	/* | ||||
| 	 * 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)) { | ||||
| 		/* | ||||
| 		 * 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 | ||||
| 		 * 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. | ||||
| 		 */ | ||||
| 		remove_file(o, 0, rename->path, ren_src_was_dirty); | ||||
| 		dst_name = unique_path(o, rename->path, cur_branch); | ||||
| 		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 { | ||||
| 		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); | ||||
| 		} | ||||
| 		/* | ||||
| 		 * 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); | ||||
| 	} | ||||
| 	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); | ||||
| 	/* 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; | ||||
|  | ||||
| 	return ret; | ||||
| 	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) | ||||
| { | ||||
| 	/* 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; | ||||
|  | ||||
| 	int other_stage = (ci->branch1 == o->branch1 ? 3 : 2); | ||||
|  | ||||
| 	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); | ||||
|  | ||||
| 	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); | ||||
| } | ||||
|  | ||||
| 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 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)) | ||||
| 		return -1; | ||||
| 	} 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 */ | ||||
| 		/* 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, | ||||
| 						   o_oid, o_mode, | ||||
| 						   a_oid, a_mode, | ||||
| 						   b_oid, b_mode, | ||||
| 						   NULL); | ||||
| 		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, | ||||
| 							   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 >expect  \ | ||||
| 			C:a     B:b    && | ||||
| 		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 | ||||
| 	) | ||||
| ' | ||||
|  | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue
	
	 Junio C Hamano
						Junio C Hamano