merge-recursive: Fix D/F conflicts
The D/F conflicts that can be automatically resolved (file or directory unmodified on one side of history), have the nice property that process_entry() can correctly handle all subpaths of the D/F conflict. In the case of D->F conversions, it will correctly delete all non-conflicting files below the relevant directory and the directory itself (note that both untracked and conflicting files below the directory will prevent its removal). So if we handle D/F conflicts after all other conflicts, they become fairly simple to handle -- we just need to check for whether or not a path (file/directory) is in the way of creating the new content. We do this by having process_entry() defer handling such entries to a subsequent process_df_entry() step. Signed-off-by: Elijah Newren <newren@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>maint
							parent
							
								
									f433f70547
								
							
						
					
					
						commit
						37348937ff
					
				|  | @ -1072,6 +1072,7 @@ static int process_entry(struct merge_options *o, | |||
| 	unsigned char *a_sha = stage_sha(entry->stages[2].sha, a_mode); | ||||
| 	unsigned char *b_sha = stage_sha(entry->stages[3].sha, b_mode); | ||||
|  | ||||
| 	entry->processed = 1; | ||||
| 	if (o_sha && (!a_sha || !b_sha)) { | ||||
| 		/* Case A: Deleted in one */ | ||||
| 		if ((!a_sha && !b_sha) || | ||||
|  | @ -1104,33 +1105,28 @@ static int process_entry(struct merge_options *o, | |||
| 	} else if ((!o_sha && a_sha && !b_sha) || | ||||
| 		   (!o_sha && !a_sha && b_sha)) { | ||||
| 		/* Case B: Added in one. */ | ||||
| 		const char *add_branch; | ||||
| 		const char *other_branch; | ||||
| 		unsigned mode; | ||||
| 		const unsigned char *sha; | ||||
| 		const char *conf; | ||||
|  | ||||
| 		if (a_sha) { | ||||
| 			add_branch = o->branch1; | ||||
| 			other_branch = o->branch2; | ||||
| 			mode = a_mode; | ||||
| 			sha = a_sha; | ||||
| 			conf = "file/directory"; | ||||
| 		} else { | ||||
| 			add_branch = o->branch2; | ||||
| 			other_branch = o->branch1; | ||||
| 			mode = b_mode; | ||||
| 			sha = b_sha; | ||||
| 			conf = "directory/file"; | ||||
| 		} | ||||
| 		if (string_list_has_string(&o->current_directory_set, path)) { | ||||
| 			const char *new_path = unique_path(o, path, add_branch); | ||||
| 			clean_merge = 0; | ||||
| 			output(o, 1, "CONFLICT (%s): There is a directory with name %s in %s. " | ||||
| 			       "Adding %s as %s", | ||||
| 			       conf, path, other_branch, path, new_path); | ||||
| 			remove_file(o, 0, path, 0); | ||||
| 			update_file(o, 0, sha, mode, new_path); | ||||
| 			/* Handle D->F conflicts after all subfiles */ | ||||
| 			entry->processed = 0; | ||||
| 			/* But get any file out of the way now, so conflicted | ||||
| 			 * entries below the directory of the same name can | ||||
| 			 * be put in the working directory. | ||||
| 			 */ | ||||
| 			if (a_sha) | ||||
| 				output(o, 2, "Removing %s", path); | ||||
| 			/* do not touch working file if it did not exist */ | ||||
| 			remove_file(o, 0, path, !a_sha); | ||||
| 			return 1; /* Assume clean till processed */ | ||||
| 		} else { | ||||
| 			output(o, 2, "Adding %s", path); | ||||
| 			update_file(o, 1, sha, mode, path); | ||||
|  | @ -1178,6 +1174,64 @@ static int process_entry(struct merge_options *o, | |||
| 	return clean_merge; | ||||
| } | ||||
|  | ||||
| /* | ||||
|  * Per entry merge function for D/F conflicts, to be called only after | ||||
|  * all files below dir have been processed.  We do this because in the | ||||
|  * cases we can cleanly resolve D/F conflicts, process_entry() can clean | ||||
|  * out all the files below the directory for us. | ||||
|  */ | ||||
| static int process_df_entry(struct merge_options *o, | ||||
| 			 const char *path, struct stage_data *entry) | ||||
| { | ||||
| 	int clean_merge = 1; | ||||
| 	unsigned o_mode = entry->stages[1].mode; | ||||
| 	unsigned a_mode = entry->stages[2].mode; | ||||
| 	unsigned b_mode = entry->stages[3].mode; | ||||
| 	unsigned char *o_sha = stage_sha(entry->stages[1].sha, o_mode); | ||||
| 	unsigned char *a_sha = stage_sha(entry->stages[2].sha, a_mode); | ||||
| 	unsigned char *b_sha = stage_sha(entry->stages[3].sha, b_mode); | ||||
| 	const char *add_branch; | ||||
| 	const char *other_branch; | ||||
| 	unsigned mode; | ||||
| 	const unsigned char *sha; | ||||
| 	const char *conf; | ||||
| 	struct stat st; | ||||
|  | ||||
| 	/* We currently only handle D->F cases */ | ||||
| 	assert((!o_sha && a_sha && !b_sha) || | ||||
| 	       (!o_sha && !a_sha && b_sha)); | ||||
|  | ||||
| 	entry->processed = 1; | ||||
|  | ||||
| 	if (a_sha) { | ||||
| 		add_branch = o->branch1; | ||||
| 		other_branch = o->branch2; | ||||
| 		mode = a_mode; | ||||
| 		sha = a_sha; | ||||
| 		conf = "file/directory"; | ||||
| 	} else { | ||||
| 		add_branch = o->branch2; | ||||
| 		other_branch = o->branch1; | ||||
| 		mode = b_mode; | ||||
| 		sha = b_sha; | ||||
| 		conf = "directory/file"; | ||||
| 	} | ||||
| 	if (lstat(path, &st) == 0 && S_ISDIR(st.st_mode)) { | ||||
| 		const char *new_path = unique_path(o, path, add_branch); | ||||
| 		clean_merge = 0; | ||||
| 		output(o, 1, "CONFLICT (%s): There is a directory with name %s in %s. " | ||||
| 		       "Adding %s as %s", | ||||
| 		       conf, path, other_branch, path, new_path); | ||||
| 		remove_file(o, 0, path, 0); | ||||
| 		update_file(o, 0, sha, mode, new_path); | ||||
| 	} else { | ||||
| 		output(o, 2, "Adding %s", path); | ||||
| 		update_file(o, 1, sha, mode, path); | ||||
| 	} | ||||
|  | ||||
| 	return clean_merge; | ||||
| } | ||||
|  | ||||
| struct unpack_trees_error_msgs get_porcelain_error_msgs(void) | ||||
| { | ||||
| 	struct unpack_trees_error_msgs msgs = { | ||||
|  | @ -1249,6 +1303,13 @@ int merge_trees(struct merge_options *o, | |||
| 				&& !process_entry(o, path, e)) | ||||
| 				clean = 0; | ||||
| 		} | ||||
| 		for (i = 0; i < entries->nr; i++) { | ||||
| 			const char *path = entries->items[i].string; | ||||
| 			struct stage_data *e = entries->items[i].util; | ||||
| 			if (!e->processed | ||||
| 				&& !process_df_entry(o, path, e)) | ||||
| 				clean = 0; | ||||
| 		} | ||||
|  | ||||
| 		string_list_clear(re_merge, 0); | ||||
| 		string_list_clear(re_head, 0); | ||||
|  |  | |||
|  | @ -56,7 +56,7 @@ test_expect_success 'Handle D/F conflict, do not lose a/b-2/c/d in merge (resolv | |||
| 	test -f a/b-2/c/d | ||||
| ' | ||||
|  | ||||
| test_expect_failure 'Handle D/F conflict, do not lose a/b-2/c/d in merge (recursive)' ' | ||||
| test_expect_success 'Handle D/F conflict, do not lose a/b-2/c/d in merge (recursive)' ' | ||||
| 	git reset --hard && | ||||
| 	git checkout baseline^0 && | ||||
| 	git merge -s recursive master && | ||||
|  | @ -130,7 +130,7 @@ test_expect_success 'merge should not have D/F conflicts (resolve)' ' | |||
| 	test -f a/b/c/d | ||||
| ' | ||||
|  | ||||
| test_expect_failure 'merge should not have D/F conflicts (recursive)' ' | ||||
| test_expect_success 'merge should not have D/F conflicts (recursive)' ' | ||||
| 	git reset --hard && | ||||
| 	git checkout baseline^0 && | ||||
| 	git merge -s recursive test2 && | ||||
|  | @ -138,7 +138,7 @@ test_expect_failure 'merge should not have D/F conflicts (recursive)' ' | |||
| 	test -f a/b/c/d | ||||
| ' | ||||
|  | ||||
| test_expect_failure 'merge should not have F/D conflicts (recursive)' ' | ||||
| test_expect_success 'merge should not have F/D conflicts (recursive)' ' | ||||
| 	git reset --hard && | ||||
| 	git checkout -b foo test2 && | ||||
| 	git merge -s recursive baseline^0 && | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue
	
	 Elijah Newren
						Elijah Newren