builtin-apply: keep information about files to be deleted
authorMichał Kiedrowicz <michal.kiedrowicz@gmail.com>
Sat, 11 Apr 2009 19:31:00 +0000 (21:31 +0200)
committerJunio C Hamano <gitster@pobox.com>
Thu, 16 Apr 2009 00:47:55 +0000 (17:47 -0700)
Example correct diff generated by `diff -M -B' might look like this:

diff --git a/file1 b/file2
similarity index 100%
rename from file1
rename to file2
diff --git a/file2 b/file1
similarity index 100%
rename from file2
rename to file1

Information about removing `file2' comes after information about creation
of new `file2' (renamed from `file1'). Existing implementation isn't able to
apply such patch, because it has to know in advance which files will be
removed.

This patch populates fn_table with information about removal of files
before calling check_patch() for each patch to be applied.

Signed-off-by: Michał Kiedrowicz <michal.kiedrowicz@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
builtin-apply.c
t/t4130-apply-criss-cross-rename.sh

index 1926cd8..7b404ef 100644 (file)
@@ -2271,6 +2271,25 @@ static struct patch *in_fn_table(const char *name)
        return NULL;
 }
 
+/*
+ * item->util in the filename table records the status of the path.
+ * Usually it points at a patch (whose result records the contents
+ * of it after applying it), but it could be PATH_WAS_DELETED for a
+ * path that a previously applied patch has already removed.
+ */
+ #define PATH_TO_BE_DELETED ((struct patch *) -2)
+#define PATH_WAS_DELETED ((struct patch *) -1)
+
+static int to_be_deleted(struct patch *patch)
+{
+       return patch == PATH_TO_BE_DELETED;
+}
+
+static int was_deleted(struct patch *patch)
+{
+       return patch == PATH_WAS_DELETED;
+}
+
 static void add_to_fn_table(struct patch *patch)
 {
        struct string_list_item *item;
@@ -2291,7 +2310,22 @@ static void add_to_fn_table(struct patch *patch)
         */
        if ((patch->new_name == NULL) || (patch->is_rename)) {
                item = string_list_insert(patch->old_name, &fn_table);
-               item->util = (struct patch *) -1;
+               item->util = PATH_WAS_DELETED;
+       }
+}
+
+static void prepare_fn_table(struct patch *patch)
+{
+       /*
+        * store information about incoming file deletion
+        */
+       while (patch) {
+               if ((patch->new_name == NULL) || (patch->is_rename)) {
+                       struct string_list_item *item;
+                       item = string_list_insert(patch->old_name, &fn_table);
+                       item->util = PATH_TO_BE_DELETED;
+               }
+               patch = patch->next;
        }
 }
 
@@ -2304,8 +2338,8 @@ static int apply_data(struct patch *patch, struct stat *st, struct cache_entry *
        struct patch *tpatch;
 
        if (!(patch->is_copy || patch->is_rename) &&
-           ((tpatch = in_fn_table(patch->old_name)) != NULL)) {
-               if (tpatch == (struct patch *) -1) {
+           (tpatch = in_fn_table(patch->old_name)) != NULL && !to_be_deleted(tpatch)) {
+               if (was_deleted(tpatch)) {
                        return error("patch %s has been renamed/deleted",
                                patch->old_name);
                }
@@ -2399,10 +2433,9 @@ static int check_preimage(struct patch *patch, struct cache_entry **ce, struct s
        assert(patch->is_new <= 0);
 
        if (!(patch->is_copy || patch->is_rename) &&
-           (tpatch = in_fn_table(old_name)) != NULL) {
-               if (tpatch == (struct patch *) -1) {
+           (tpatch = in_fn_table(old_name)) != NULL && !to_be_deleted(tpatch)) {
+               if (was_deleted(tpatch))
                        return error("%s: has been deleted/renamed", old_name);
-               }
                st_mode = tpatch->new_mode;
        } else if (!cached) {
                stat_ret = lstat(old_name, st);
@@ -2410,6 +2443,9 @@ static int check_preimage(struct patch *patch, struct cache_entry **ce, struct s
                        return error("%s: %s", old_name, strerror(errno));
        }
 
+       if (to_be_deleted(tpatch))
+               tpatch = NULL;
+
        if (check_index && !tpatch) {
                int pos = cache_name_pos(old_name, strlen(old_name));
                if (pos < 0) {
@@ -2471,6 +2507,7 @@ static int check_patch(struct patch *patch)
        const char *new_name = patch->new_name;
        const char *name = old_name ? old_name : new_name;
        struct cache_entry *ce = NULL;
+       struct patch *tpatch;
        int ok_if_exists;
        int status;
 
@@ -2481,7 +2518,8 @@ static int check_patch(struct patch *patch)
                return status;
        old_name = patch->old_name;
 
-       if (in_fn_table(new_name) == (struct patch *) -1)
+       if ((tpatch = in_fn_table(new_name)) &&
+                       (was_deleted(tpatch) || to_be_deleted(tpatch)))
                /*
                 * A type-change diff is always split into a patch to
                 * delete old, immediately followed by a patch to
@@ -2533,6 +2571,7 @@ static int check_patch_list(struct patch *patch)
 {
        int err = 0;
 
+       prepare_fn_table(patch);
        while (patch) {
                if (apply_verbosely)
                        say_patch_name(stderr,
index 08c5f38..8623dbe 100755 (executable)
@@ -31,7 +31,7 @@ test_expect_success 'diff -M -B' '
 
 '
 
-test_expect_failure 'apply' '
+test_expect_success 'apply' '
        git apply diff
 '