Code

Merge branch 'ew/apply'
authorJunio C Hamano <junkio@cox.net>
Tue, 25 Jul 2006 19:50:23 +0000 (12:50 -0700)
committerJunio C Hamano <junkio@cox.net>
Tue, 25 Jul 2006 19:50:23 +0000 (12:50 -0700)
* 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)

builtin-apply.c
t/t4114-apply-typechange.sh [new file with mode: 0755]

index c903146bb65e6db4dcb527badf864d98db6aabb3..d924ac3d0ab663c2ceb05c2cb9943f6caa23c0ef 100644 (file)
@@ -1664,13 +1664,14 @@ static int apply_data(struct patch *patch, struct stat *st, struct cache_entry *
        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;
@@ -1728,13 +1729,33 @@ static int check_patch(struct patch *patch)
                                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) {
@@ -1762,10 +1783,13 @@ static int check_patch(struct patch *patch)
 
 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;
 }
 
@@ -2010,6 +2034,16 @@ static void create_one_file(char *path, unsigned mode, const char *buf, unsigned
                        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();
 
@@ -2044,32 +2078,42 @@ static void create_file(struct patch *patch)
        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;
+               }
        }
 }
 
diff --git a/t/t4114-apply-typechange.sh b/t/t4114-apply-typechange.sh
new file mode 100755 (executable)
index 0000000..ca81d72
--- /dev/null
@@ -0,0 +1,105 @@
+#!/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