Commits

Anonymous committed a8861ea Merge

Merge branch 'ew/apply'

* ew/apply:
Fix t4114 on cygwin
apply: handle type-changing patch correctly.
apply: split out removal and creation into different phases.
apply: check D/F conflicts more carefully.
typechange tests for git apply (currently failing)

Comments (0)

Files changed (2)

 	return 0;
 }
 
-static int check_patch(struct patch *patch)
+static int check_patch(struct patch *patch, struct patch *prev_patch)
 {
 	struct stat st;
 	const char *old_name = patch->old_name;
 	const char *new_name = patch->new_name;
 	const char *name = old_name ? old_name : new_name;
 	struct cache_entry *ce = NULL;
+	int ok_if_exists;
 
 	if (old_name) {
 		int changed = 0;
 				old_name, st_mode, patch->old_mode);
 	}
 
+	if (new_name && prev_patch && prev_patch->is_delete &&
+	    !strcmp(prev_patch->old_name, new_name))
+		/* A type-change diff is always split into a patch to
+		 * delete old, immediately followed by a patch to
+		 * create new (see diff.c::run_diff()); in such a case
+		 * it is Ok that the entry to be deleted by the
+		 * previous patch is still in the working tree and in
+		 * the index.
+		 */
+		ok_if_exists = 1;
+	else
+		ok_if_exists = 0;
+
 	if (new_name && (patch->is_new | patch->is_rename | patch->is_copy)) {
-		if (check_index && cache_name_pos(new_name, strlen(new_name)) >= 0)
+		if (check_index &&
+		    cache_name_pos(new_name, strlen(new_name)) >= 0 &&
+		    !ok_if_exists)
 			return error("%s: already exists in index", new_name);
 		if (!cached) {
-			if (!lstat(new_name, &st))
-				return error("%s: already exists in working directory", new_name);
-			if (errno != ENOENT)
+			struct stat nst;
+			if (!lstat(new_name, &nst)) {
+				if (S_ISDIR(nst.st_mode) || ok_if_exists)
+					; /* ok */
+				else
+					return error("%s: already exists in working directory", new_name);
+			}
+			else if ((errno != ENOENT) && (errno != ENOTDIR))
 				return error("%s: %s", new_name, strerror(errno));
 		}
 		if (!patch->new_mode) {
 
 static int check_patch_list(struct patch *patch)
 {
+	struct patch *prev_patch = NULL;
 	int error = 0;
 
-	for (;patch ; patch = patch->next)
-		error |= check_patch(patch);
+	for (prev_patch = NULL; patch ; patch = patch->next) {
+		error |= check_patch(patch, prev_patch);
+		prev_patch = patch;
+	}
 	return error;
 }
 
 			return;
 	}
 
+	if (errno == EEXIST || errno == EACCES) {
+		/* We may be trying to create a file where a directory
+		 * used to be.
+		 */
+		struct stat st;
+		errno = 0;
+		if (!lstat(path, &st) && S_ISDIR(st.st_mode) && !rmdir(path))
+			errno = EEXIST;
+	}
+
 	if (errno == EEXIST) {
 		unsigned int nr = getpid();
 
 	cache_tree_invalidate_path(active_cache_tree, path);
 }
 
-static void write_out_one_result(struct patch *patch)
+/* phase zero is to remove, phase one is to create */
+static void write_out_one_result(struct patch *patch, int phase)
 {
 	if (patch->is_delete > 0) {
-		remove_file(patch);
+		if (phase == 0)
+			remove_file(patch);
 		return;
 	}
 	if (patch->is_new > 0 || patch->is_copy) {
-		create_file(patch);
+		if (phase == 1)
+			create_file(patch);
 		return;
 	}
 	/*
 	 * Rename or modification boils down to the same
 	 * thing: remove the old, write the new
 	 */
-	remove_file(patch);
+	if (phase == 0)
+		remove_file(patch);
+	if (phase == 1)
 	create_file(patch);
 }
 
 static void write_out_results(struct patch *list, int skipped_patch)
 {
+	int phase;
+
 	if (!list && !skipped_patch)
 		die("No changes");
 
-	while (list) {
-		write_out_one_result(list);
-		list = list->next;
+	for (phase = 0; phase < 2; phase++) {
+		struct patch *l = list;
+		while (l) {
+			write_out_one_result(l, phase);
+			l = l->next;
+		}
 	}
 }
 

t/t4114-apply-typechange.sh

+#!/bin/sh
+#
+# Copyright (c) 2006 Eric Wong
+#
+
+test_description='git-apply should not get confused with type changes.
+
+'
+
+. ./test-lib.sh
+
+test_expect_success 'setup repository and commits' '
+	echo "hello world" > foo &&
+	echo "hi planet" > bar &&
+	git update-index --add foo bar &&
+	git commit -m initial &&
+	git branch initial &&
+	rm -f foo &&
+	ln -s bar foo &&
+	git update-index foo &&
+	git commit -m "foo symlinked to bar" &&
+	git branch foo-symlinked-to-bar &&
+	rm -f foo &&
+	echo "how far is the sun?" > foo &&
+	git update-index foo &&
+	git commit -m "foo back to file" &&
+	git branch foo-back-to-file &&
+	rm -f foo &&
+	git update-index --remove foo &&
+	mkdir foo &&
+	echo "if only I knew" > foo/baz &&
+	git update-index --add foo/baz &&
+	git commit -m "foo becomes a directory" &&
+	git branch "foo-becomes-a-directory" &&
+	echo "hello world" > foo/baz &&
+	git update-index foo/baz &&
+	git commit -m "foo/baz is the original foo" &&
+	git branch foo-baz-renamed-from-foo
+	'
+
+test_expect_success 'file renamed from foo to foo/baz' '
+	git checkout -f initial &&
+	git diff-tree -M -p HEAD foo-baz-renamed-from-foo > patch &&
+	git apply --index < patch
+	'
+test_debug 'cat patch'
+
+
+test_expect_success 'file renamed from foo/baz to foo' '
+	git checkout -f foo-baz-renamed-from-foo &&
+	git diff-tree -M -p HEAD initial > patch &&
+	git apply --index < patch
+	'
+test_debug 'cat patch'
+
+
+test_expect_success 'directory becomes file' '
+	git checkout -f foo-becomes-a-directory &&
+	git diff-tree -p HEAD initial > patch &&
+	git apply --index < patch
+	'
+test_debug 'cat patch'
+
+
+test_expect_success 'file becomes directory' '
+	git checkout -f initial &&
+	git diff-tree -p HEAD foo-becomes-a-directory > patch &&
+	git apply --index < patch
+	'
+test_debug 'cat patch'
+
+
+test_expect_success 'file becomes symlink' '
+	git checkout -f initial &&
+	git diff-tree -p HEAD foo-symlinked-to-bar > patch &&
+	git apply --index < patch
+	'
+test_debug 'cat patch'
+
+
+test_expect_success 'symlink becomes file' '
+	git checkout -f foo-symlinked-to-bar &&
+	git diff-tree -p HEAD foo-back-to-file > patch &&
+	git apply --index < patch
+	'
+test_debug 'cat patch'
+
+
+test_expect_success 'symlink becomes directory' '
+	git checkout -f foo-symlinked-to-bar &&
+	git diff-tree -p HEAD foo-becomes-a-directory > patch &&
+	git apply --index < patch
+	'
+test_debug 'cat patch'
+
+
+test_expect_success 'directory becomes symlink' '
+	git checkout -f foo-becomes-a-directory &&
+	git diff-tree -p HEAD foo-symlinked-to-bar > patch &&
+	git apply --index < patch
+	'
+test_debug 'cat patch'
+
+
+test_done