apply: allow-binary-replacement.
authorJunio C Hamano <junkio@cox.net>
Tue, 15 Nov 2005 01:37:05 +0000 (17:37 -0800)
committerJunio C Hamano <junkio@cox.net>
Thu, 17 Nov 2005 00:20:40 +0000 (16:20 -0800)
A new option, --allow-binary-replacement, is introduced.

When you feed a diff that records full SHA1 name of pre- and
post-image blob on its index line to git-apply with this option,
the post-image blob replaces the path if what you have in the
working tree matches the pre-image _and_ post-image blob is
already available in the object directory.

Later we _might_ want to enhance the diff output to also include
the full binary data of the post-image, to make this more
useful, but this is good enough for local rebasing application.

Signed-off-by: Junio C Hamano <junkio@cox.net>
Documentation/git-apply.txt
apply.c
t/t4103-apply-binary.sh

index 6702a18..626e281 100644 (file)
@@ -8,7 +8,7 @@ git-apply - Apply patch on a git index file and a work tree
 
 SYNOPSIS
 --------
 
 SYNOPSIS
 --------
-'git-apply' [--stat] [--numstat] [--summary] [--check] [--index] [--apply] [--no-add] [--index-info] [-z] [<patch>...]
+'git-apply' [--stat] [--numstat] [--summary] [--check] [--index] [--apply] [--no-add] [--index-info] [--allow-binary-replacement] [-z] [<patch>...]
 
 DESCRIPTION
 -----------
 
 DESCRIPTION
 -----------
@@ -79,6 +79,17 @@ OPTIONS
        the result with this option, which would apply the
        deletion part but not addition part.
 
        the result with this option, which would apply the
        deletion part but not addition part.
 
+--allow-binary-replacement::
+       When applying a patch, which is a git-enhanced patch
+       that was prepared to record the pre- and post-image object
+       name in full, and the path being patched exactly matches
+       the object the patch applies to (i.e. "index" line's
+       pre-image object name is what is in the working tree),
+       and the post-image object is available in the object
+       database, use the post-image object as the patch
+       result.  This allows binary files to be patched in a
+       very limited way.
+
 Author
 ------
 Written by Linus Torvalds <torvalds@osdl.org>
 Author
 ------
 Written by Linus Torvalds <torvalds@osdl.org>
diff --git a/apply.c b/apply.c
index a002e15..129edb1 100644 (file)
--- a/apply.c
+++ b/apply.c
@@ -16,6 +16,7 @@
 //  --numstat does numeric diffstat, and doesn't actually apply
 //  --index-info shows the old and new index info for paths if available.
 //
 //  --numstat does numeric diffstat, and doesn't actually apply
 //  --index-info shows the old and new index info for paths if available.
 //
+static int allow_binary_replacement = 0;
 static int check_index = 0;
 static int write_index = 0;
 static int diffstat = 0;
 static int check_index = 0;
 static int write_index = 0;
 static int diffstat = 0;
@@ -27,7 +28,7 @@ static int no_add = 0;
 static int show_index_info = 0;
 static int line_termination = '\n';
 static const char apply_usage[] =
 static int show_index_info = 0;
 static int line_termination = '\n';
 static const char apply_usage[] =
-"git-apply [--stat] [--numstat] [--summary] [--check] [--index] [--apply] [--no-add] [--index-info] [-z] <patch>...";
+"git-apply [--stat] [--numstat] [--summary] [--check] [--index] [--apply] [--no-add] [--index-info] [--allow-binary-replacement] [-z] <patch>...";
 
 /*
  * For "diff-stat" like behaviour, we keep track of the biggest change
 
 /*
  * For "diff-stat" like behaviour, we keep track of the biggest change
@@ -900,11 +901,13 @@ static int parse_chunk(char *buffer, unsigned long size, struct patch *patch)
                        patch->is_binary = 1;
 
                /* Empty patch cannot be applied if:
                        patch->is_binary = 1;
 
                /* Empty patch cannot be applied if:
-                * - it is a binary patch or
-                * - metadata does not change and is not a binary patch.
+                * - it is a binary patch and we do not do binary_replace, or
+                * - text patch without metadata change
                 */
                if ((apply || check) &&
                 */
                if ((apply || check) &&
-                   (patch->is_binary || !metadata_changes(patch)))
+                   (patch->is_binary
+                    ? !allow_binary_replacement
+                    : !metadata_changes(patch)))
                        die("patch with only garbage at line %d", linenr);
        }
 
                        die("patch with only garbage at line %d", linenr);
        }
 
@@ -1158,10 +1161,77 @@ static int apply_one_fragment(struct buffer_desc *desc, struct fragment *frag)
 static int apply_fragments(struct buffer_desc *desc, struct patch *patch)
 {
        struct fragment *frag = patch->fragments;
 static int apply_fragments(struct buffer_desc *desc, struct patch *patch)
 {
        struct fragment *frag = patch->fragments;
+       const char *name = patch->old_name ? patch->old_name : patch->new_name;
+
+       if (patch->is_binary) {
+               unsigned char sha1[20];
+
+               if (!allow_binary_replacement)
+                       return error("cannot apply binary patch to '%s' "
+                                    "without --allow-binary-replacement",
+                                    name);
+
+               /* For safety, we require patch index line to contain
+                * full 40-byte textual SHA1 for old and new, at least for now.
+                */
+               if (strlen(patch->old_sha1_prefix) != 40 ||
+                   strlen(patch->new_sha1_prefix) != 40 ||
+                   get_sha1_hex(patch->old_sha1_prefix, sha1) ||
+                   get_sha1_hex(patch->new_sha1_prefix, sha1))
+                       return error("cannot apply binary patch to '%s' "
+                                    "without full index line", name);
+
+               if (patch->old_name) {
+                       unsigned char hdr[50];
+                       int hdrlen;
+
+                       /* See if the old one matches what the patch
+                        * applies to.
+                        */
+                       write_sha1_file_prepare(desc->buffer, desc->size,
+                                               "blob", sha1, hdr, &hdrlen);
+                       if (strcmp(sha1_to_hex(sha1), patch->old_sha1_prefix))
+                               return error("the patch applies to '%s' (%s), "
+                                            "which does not match the "
+                                            "current contents.",
+                                            name, sha1_to_hex(sha1));
+               }
+               else {
+                       /* Otherwise, the old one must be empty. */
+                       if (desc->size)
+                               return error("the patch applies to an empty "
+                                            "'%s' but it is not empty", name);
+               }
+
+               /* For now, we do not record post-image data in the patch,
+                * and require the object already present in the recipient's
+                * object database.
+                */
+               if (desc->buffer) {
+                       free(desc->buffer);
+                       desc->alloc = desc->size = 0;
+               }
+               get_sha1_hex(patch->new_sha1_prefix, sha1);
+
+               if (memcmp(sha1, null_sha1, 20)) {
+                       char type[10];
+                       unsigned long size;
+
+                       desc->buffer = read_sha1_file(sha1, type, &size);
+                       if (!desc->buffer)
+                               return error("the necessary postimage %s for "
+                                            "'%s' does not exist",
+                                            patch->new_sha1_prefix, name);
+                       desc->alloc = desc->size = size;
+               }
+
+               return 0;
+       }
 
        while (frag) {
                if (apply_one_fragment(desc, frag) < 0)
 
        while (frag) {
                if (apply_one_fragment(desc, frag) < 0)
-                       return error("patch failed: %s:%ld", patch->old_name, frag->oldpos);
+                       return error("patch failed: %s:%ld",
+                                    name, frag->oldpos);
                frag = frag->next;
        }
        return 0;
                frag = frag->next;
        }
        return 0;
@@ -1203,6 +1273,7 @@ static int check_patch(struct patch *patch)
        struct stat st;
        const char *old_name = patch->old_name;
        const char *new_name = patch->new_name;
        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;
 
        if (old_name) {
                int changed;
 
        if (old_name) {
                int changed;
@@ -1277,7 +1348,7 @@ static int check_patch(struct patch *patch)
        }       
 
        if (apply_data(patch, &st) < 0)
        }       
 
        if (apply_data(patch, &st) < 0)
-               return error("%s: patch does not apply", old_name);
+               return error("%s: patch does not apply", name);
        return 0;
 }
 
        return 0;
 }
 
@@ -1726,6 +1797,10 @@ int main(int argc, char **argv)
                        diffstat = 1;
                        continue;
                }
                        diffstat = 1;
                        continue;
                }
+               if (!strcmp(arg, "--allow-binary-replacement")) {
+                       allow_binary_replacement = 1;
+                       continue;
+               }
                if (!strcmp(arg, "--numstat")) {
                        apply = 0;
                        numstat = 1;
                if (!strcmp(arg, "--numstat")) {
                        apply = 0;
                        numstat = 1;
index 948d5b5..9057f6d 100644 (file)
@@ -51,6 +51,14 @@ test_expect_failure 'check binary diff (copy) -- should fail.' \
        'git-checkout master
         git-apply --check C.diff'
 
        'git-checkout master
         git-apply --check C.diff'
 
+test_expect_failure 'check incomplete binary diff with replacement -- should fail.' \
+       'git-checkout master
+        git-apply --check --allow-binary-replacement B.diff'
+
+test_expect_failure 'check incomplete binary diff with replacement (copy) -- should fail.' \
+       'git-checkout master
+        git-apply --check --allow-binary-replacement C.diff'
+
 # Now we start applying them.
 
 test_expect_failure 'apply binary diff -- should fail.' \
 # Now we start applying them.
 
 test_expect_failure 'apply binary diff -- should fail.' \