Merge branch 'jc/apply'
authorJunio C Hamano <junkio@cox.net>
Mon, 28 Aug 2006 00:51:05 +0000 (17:51 -0700)
committerJunio C Hamano <junkio@cox.net>
Mon, 28 Aug 2006 00:51:05 +0000 (17:51 -0700)
* jc/apply:
git-apply --reject: finishing touches.
apply --reject: count hunks starting from 1, not 0
git-apply --verbose
git-apply --reject: send rejects to .rej files.
git-apply --reject
apply --reverse: tie it all together.
diff.c: make binary patch reversible.
builtin-apply --reverse: two bugfixes.

1  2 
Documentation/git-apply.txt
builtin-apply.c
diff.c
index 20e12ceda002d929ffc7ee13b77d0e23c7e91356,2e2acd72c7ebbe3d099456e27595b20d9d001d78..c76cfffdc6cd9982aa02e34b2b09fc22897e7402
@@@ -10,10 -10,10 +10,10 @@@ SYNOPSI
  --------
  [verse]
  'git-apply' [--stat] [--numstat] [--summary] [--check] [--index] [--apply]
 -        [--no-add] [--index-info] [--allow-binary-replacement]
 -        [--reverse] [--reject] [-z] [-pNUM]
 -        [-CNUM] [--whitespace=<nowarn|warn|error|error-all|strip>]
 -        [<patch>...]
 +        [--no-add] [--index-info] [--allow-binary-replacement | --binary]
 +        [-R | --reverse] [--reject] [-z] [-pNUM] [-CNUM] [--inaccurate-eof]
 +        [--whitespace=<nowarn|warn|error|error-all|strip>] [--exclude=PATH]
 +        [--cached] [--verbose] [<patch>...]
  
  DESCRIPTION
  -----------
@@@ -56,11 -56,6 +56,11 @@@ OPTION
        up-to-date, it is flagged as an error.  This flag also
        causes the index file to be updated.
  
 +--cached::
 +      Apply a patch without touching the working tree. Instead, take the
 +      cached data, apply the patch, and store the result in the index,
 +      without using the working tree. This implies '--index'.
 +
  --index-info::
        Newer git-diff output has embedded 'index information'
        for each blob to help identify the original version that
        the original version of the blob is available locally,
        outputs information about them to the standard output.
  
 ---reverse::
 +-R, --reverse::
        Apply the patch in reverse.
  
  --reject::
 -      For atomicity, `git apply` fails the whole patch and
 +      For atomicity, gitlink:git-apply[1] by default fails the whole patch and
        does not touch the working tree when some of the hunks
 -      do not apply by default.  This option makes it apply
 -      parts of the patch that are applicable, and leave the
 +      do not apply.  This option makes it apply
-       the parts of the patch that are applicable, and send the
-       rejected hunks to the standard output of the command.
++      the parts of the patch that are applicable, and leave the
+       rejected hunks in corresponding *.rej files.
  
  -z::
        When showing the index information, do not munge paths,
@@@ -96,8 -91,8 +96,8 @@@
        ever ignored.
  
  --apply::
 -      If you use any of the options marked ``Turns off
 -      "apply"'' above, git-apply reads and outputs the
 +      If you use any of the options marked "Turns off
 +      'apply'" above, gitlink:git-apply[1] reads and outputs the
        information you asked without actually applying the
        patch.  Give this flag after those flags to also apply
        the patch.
        the result with this option, which would apply the
        deletion part but not addition part.
  
 ---allow-binary-replacement::
 +--allow-binary-replacement, --binary::
        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
        result.  This allows binary files to be patched in a
        very limited way.
  
 +--exclude=<path-pattern>::
 +      Don't apply changes to files matching the given path pattern. This can
 +      be useful when importing patchsets, where you want to exclude certain
 +      files or directories.
 +
  --whitespace=<option>::
        When applying a patch, detect a new or modified line
        that ends with trailing whitespaces (this includes a
        line that solely consists of whitespaces).  By default,
        the command outputs warning messages and applies the
        patch.
 -      When `git-apply` is used for statistics and not applying a
 +      When gitlink:git-apply[1] is used for statistics and not applying a
        patch, it defaults to `nowarn`.
        You can use different `<option>` to control this
        behavior:
  * `strip` outputs warnings for a few such errors, strips out the
    trailing whitespaces and applies the patch.
  
 +--inacurate-eof::
 +      Under certain circumstances, some versions of diff do not correctly
 +      detect a missing new-line at the end of the file. As a result, patches
 +      created by such diff programs do not record incomplete lines
 +      correctly. This option adds support for applying such patches by
 +      working around this bug.
 +
 +--verbose::
 +      Report progress to stderr. By default, only a message about the
 +      current patch being applied will be printed. This option will cause
 +      additional information to be reported.
  
  Configuration
  -------------
diff --combined builtin-apply.c
index f8f5eebd2f9b6ebbad46e8d86efde535f30a194f,0b00a98aa1b283796e0c67fb42e0c9a35d74ff5c..b47ccacc2e0618476ca4816e111a97b2c46f0f4f
@@@ -38,12 -38,14 +38,14 @@@ static int summary
  static int check;
  static int apply = 1;
  static int apply_in_reverse;
+ static int apply_with_reject;
+ static int apply_verbosely;
  static int no_add;
  static int show_index_info;
  static int line_termination = '\n';
  static unsigned long p_context = -1;
  static const char apply_usage[] =
- "git-apply [--stat] [--numstat] [--summary] [--check] [--index] [--cached] [--apply] [--no-add] [--index-info] [--allow-binary-replacement] [-z] [-pNUM] [-CNUM] [--whitespace=<nowarn|warn|error|error-all|strip>] <patch>...";
+ "git-apply [--stat] [--numstat] [--summary] [--check] [--index] [--cached] [--apply] [--no-add] [--index-info] [--allow-binary-replacement] [--reverse] [--reject] [--verbose] [-z] [-pNUM] [-CNUM] [--whitespace=<nowarn|warn|error|error-all|strip>] <patch>...";
  
  static enum whitespace_eol {
        nowarn_whitespace,
@@@ -122,6 -124,7 +124,7 @@@ struct fragment 
        unsigned long newpos, newlines;
        const char *patch;
        int size;
+       int rejected;
        struct fragment *next;
  };
  
@@@ -138,6 -141,7 +141,7 @@@ struct patch 
        char *new_name, *old_name, *def_name;
        unsigned int old_mode, new_mode;
        int is_rename, is_copy, is_new, is_delete, is_binary;
+       int rejected;
        unsigned long deflate_origlen;
        int lines_added, lines_deleted;
        int score;
        struct patch *next;
  };
  
+ static void say_patch_name(FILE *output, const char *pre, struct patch *patch, const char *post)
+ {
+       fputs(pre, output);
+       if (patch->old_name && patch->new_name &&
+           strcmp(patch->old_name, patch->new_name)) {
+               write_name_quoted(NULL, 0, patch->old_name, 1, output);
+               fputs(" => ", output);
+               write_name_quoted(NULL, 0, patch->new_name, 1, output);
+       }
+       else {
+               const char *n = patch->new_name;
+               if (!n)
+                       n = patch->old_name;
+               write_name_quoted(NULL, 0, n, 1, output);
+       }
+       fputs(post, output);
+ }
  #define CHUNKSIZE (8192)
  #define SLOP (16)
  
@@@ -606,7 -628,9 +628,7 @@@ static char *git_header_name(char *line
         * form.
         */
        for (len = 0 ; ; len++) {
 -              char c = name[len];
 -
 -              switch (c) {
 +              switch (name[len]) {
                default:
                        continue;
                case '\n':
@@@ -1061,8 -1085,12 +1083,12 @@@ static struct fragment *parse_binary_hu
                llen = linelen(buffer, size);
                used += llen;
                linenr++;
-               if (llen == 1)
+               if (llen == 1) {
+                       /* consume the blank line */
+                       buffer++;
+                       size--;
                        break;
+               }
                /* Minimum line is "A00000\n" which is 7-byte long,
                 * and the line length must be multiple of 5 plus 2.
                 */
@@@ -1542,7 -1570,8 +1568,8 @@@ static int apply_one_fragment(struct bu
        lines = 0;
        pos = frag->newpos;
        for (;;) {
-               offset = find_offset(buf, desc->size, oldlines, oldsize, pos, &lines);
+               offset = find_offset(buf, desc->size,
+                                    oldlines, oldsize, pos, &lines);
                if (match_end && offset + oldsize != desc->size)
                        offset = -1;
                if (match_beginning && offset)
                        /* Warn if it was necessary to reduce the number
                         * of context lines.
                         */
-                       if ((leading != frag->leading) || (trailing != frag->trailing))
-                               fprintf(stderr, "Context reduced to (%ld/%ld) to apply fragment at %d\n",
+                       if ((leading != frag->leading) ||
+                           (trailing != frag->trailing))
+                               fprintf(stderr, "Context reduced to (%ld/%ld)"
+                                       " to apply fragment at %d\n",
                                        leading, trailing, pos + lines);
  
                        if (size > alloc) {
                                desc->buffer = buf;
                        }
                        desc->size = size;
-                       memmove(buf + offset + newsize, buf + offset + oldsize, size - offset - newsize);
+                       memmove(buf + offset + newsize,
+                               buf + offset + oldsize,
+                               size - offset - newsize);
                        memcpy(buf + offset, newlines, newsize);
                        offset = 0;
  
@@@ -1616,7 -1649,7 +1647,7 @@@ static int apply_binary_fragment(struc
                                     "without the reverse hunk to '%s'",
                                     patch->new_name
                                     ? patch->new_name : patch->old_name);
-               fragment = fragment;
+               fragment = fragment->next;
        }
        data = (void*) fragment->patch;
        switch (fragment->binary_patch_method) {
@@@ -1715,7 -1748,7 +1746,7 @@@ static int apply_binary(struct buffer_d
                write_sha1_file_prepare(desc->buffer, desc->size, blob_type,
                                        sha1, hdr, &hdrlen);
                if (strcmp(sha1_to_hex(sha1), patch->new_sha1_prefix))
-                       return error("binary patch to '%s' creates incorrect result", name);
+                       return error("binary patch to '%s' creates incorrect result (expecting %s, got %s)", name, patch->new_sha1_prefix, sha1_to_hex(sha1));
        }
  
        return 0;
@@@ -1730,9 -1763,12 +1761,12 @@@ static int apply_fragments(struct buffe
                return apply_binary(desc, patch);
  
        while (frag) {
-               if (apply_one_fragment(desc, frag, patch->inaccurate_eof) < 0)
-                       return error("patch failed: %s:%ld",
-                                    name, frag->oldpos);
+               if (apply_one_fragment(desc, frag, patch->inaccurate_eof)) {
+                       error("patch failed: %s:%ld", name, frag->oldpos);
+                       if (!apply_with_reject)
+                               return -1;
+                       frag->rejected = 1;
+               }
                frag = frag->next;
        }
        return 0;
@@@ -1768,8 -1804,9 +1802,9 @@@ static int apply_data(struct patch *pat
        desc.size = size;
        desc.alloc = alloc;
        desc.buffer = buf;
        if (apply_fragments(&desc, patch) < 0)
-               return -1;
+               return -1; /* note with --reject this succeeds. */
  
        /* NUL terminate the result */
        if (desc.alloc <= desc.size)
@@@ -1794,6 -1831,7 +1829,7 @@@ static int check_patch(struct patch *pa
        struct cache_entry *ce = NULL;
        int ok_if_exists;
  
+       patch->rejected = 1; /* we will drop this after we succeed */
        if (old_name) {
                int changed = 0;
                int stat_ret = 0;
  
        if (apply_data(patch, &st, ce) < 0)
                return error("%s: patch does not apply", name);
+       patch->rejected = 0;
        return 0;
  }
  
  static int check_patch_list(struct patch *patch)
  {
        struct patch *prev_patch = NULL;
 -      int error = 0;
 +      int err = 0;
  
        for (prev_patch = NULL; patch ; patch = patch->next) {
 -              error |= check_patch(patch, prev_patch);
+               if (apply_verbosely)
+                       say_patch_name(stderr,
+                                      "Checking patch ", patch, "...\n");
 +              err |= check_patch(patch, prev_patch);
                prev_patch = patch;
        }
 -      return error;
 +      return err;
  }
  
  static void show_index_list(struct patch *list)
@@@ -2217,23 -2259,99 +2257,99 @@@ static void write_out_one_result(struc
        if (phase == 0)
                remove_file(patch);
        if (phase == 1)
-       create_file(patch);
+               create_file(patch);
+ }
+ static int write_out_one_reject(struct patch *patch)
+ {
+       FILE *rej;
+       char namebuf[PATH_MAX];
+       struct fragment *frag;
+       int cnt = 0;
+       for (cnt = 0, frag = patch->fragments; frag; frag = frag->next) {
+               if (!frag->rejected)
+                       continue;
+               cnt++;
+       }
+       if (!cnt) {
+               if (apply_verbosely)
+                       say_patch_name(stderr,
+                                      "Applied patch ", patch, " cleanly.\n");
+               return 0;
+       }
+       /* This should not happen, because a removal patch that leaves
+        * contents are marked "rejected" at the patch level.
+        */
+       if (!patch->new_name)
+               die("internal error");
+       /* Say this even without --verbose */
+       say_patch_name(stderr, "Applying patch ", patch, " with");
+       fprintf(stderr, " %d rejects...\n", cnt);
+       cnt = strlen(patch->new_name);
+       if (ARRAY_SIZE(namebuf) <= cnt + 5) {
+               cnt = ARRAY_SIZE(namebuf) - 5;
+               fprintf(stderr,
+                       "warning: truncating .rej filename to %.*s.rej",
+                       cnt - 1, patch->new_name);
+       }
+       memcpy(namebuf, patch->new_name, cnt);
+       memcpy(namebuf + cnt, ".rej", 5);
+       rej = fopen(namebuf, "w");
+       if (!rej)
+               return error("cannot open %s: %s", namebuf, strerror(errno));
+       /* Normal git tools never deal with .rej, so do not pretend
+        * this is a git patch by saying --git nor give extended
+        * headers.  While at it, maybe please "kompare" that wants
+        * the trailing TAB and some garbage at the end of line ;-).
+        */
+       fprintf(rej, "diff a/%s b/%s\t(rejected hunks)\n",
+               patch->new_name, patch->new_name);
+       for (cnt = 1, frag = patch->fragments;
+            frag;
+            cnt++, frag = frag->next) {
+               if (!frag->rejected) {
+                       fprintf(stderr, "Hunk #%d applied cleanly.\n", cnt);
+                       continue;
+               }
+               fprintf(stderr, "Rejected hunk #%d.\n", cnt);
+               fprintf(rej, "%.*s", frag->size, frag->patch);
+               if (frag->patch[frag->size-1] != '\n')
+                       fputc('\n', rej);
+       }
+       fclose(rej);
+       return -1;
  }
  
- static void write_out_results(struct patch *list, int skipped_patch)
+ static int write_out_results(struct patch *list, int skipped_patch)
  {
        int phase;
+       int errs = 0;
+       struct patch *l;
  
        if (!list && !skipped_patch)
-               die("No changes");
+               return error("No changes");
  
        for (phase = 0; phase < 2; phase++) {
-               struct patch *l = list;
+               l = list;
                while (l) {
-                       write_out_one_result(l, phase);
+                       if (l->rejected)
+                               errs = 1;
+                       else {
+                               write_out_one_result(l, phase);
+                               if (phase == 1 && write_out_one_reject(l))
+                                       errs = 1;
+                       }
                        l = l->next;
                }
        }
+       return errs;
  }
  
  static struct lock_file lock_file;
@@@ -2308,11 -2426,13 +2424,13 @@@ static int apply_patch(int fd, const ch
                        die("unable to read index file");
        }
  
-       if ((check || apply) && check_patch_list(list) < 0)
+       if ((check || apply) &&
+           check_patch_list(list) < 0 &&
+           !apply_with_reject)
                exit(1);
  
-       if (apply)
-               write_out_results(list, skipped_patch);
+       if (apply && write_out_results(list, skipped_patch))
+               exit(1);
  
        if (show_index_info)
                show_index_list(list);
@@@ -2345,6 -2465,7 +2463,7 @@@ int cmd_apply(int argc, const char **ar
        int i;
        int read_stdin = 1;
        int inaccurate_eof = 0;
+       int errs = 0;
  
        const char *whitespace_option = NULL;
  
                int fd;
  
                if (!strcmp(arg, "-")) {
-                       apply_patch(0, "<stdin>", inaccurate_eof);
+                       errs |= apply_patch(0, "<stdin>", inaccurate_eof);
                        read_stdin = 0;
                        continue;
                }
                        apply_in_reverse = 1;
                        continue;
                }
+               if (!strcmp(arg, "--reject")) {
+                       apply = apply_with_reject = apply_verbosely = 1;
+                       continue;
+               }
+               if (!strcmp(arg, "--verbose")) {
+                       apply_verbosely = 1;
+                       continue;
+               }
                if (!strcmp(arg, "--inaccurate-eof")) {
                        inaccurate_eof = 1;
                        continue;
                        usage(apply_usage);
                read_stdin = 0;
                set_default_whitespace_mode(whitespace_option);
-               apply_patch(fd, arg, inaccurate_eof);
+               errs |= apply_patch(fd, arg, inaccurate_eof);
                close(fd);
        }
        set_default_whitespace_mode(whitespace_option);
        if (read_stdin)
-               apply_patch(0, "<stdin>", inaccurate_eof);
+               errs |= apply_patch(0, "<stdin>", inaccurate_eof);
        if (whitespace_error) {
                if (squelch_whitespace_errors &&
                    squelch_whitespace_errors < whitespace_error) {
                        int squelched =
                                whitespace_error - squelch_whitespace_errors;
-                       fprintf(stderr, "warning: squelched %d whitespace error%s\n",
+                       fprintf(stderr, "warning: squelched %d "
+                               "whitespace error%s\n",
                                squelched,
                                squelched == 1 ? "" : "s");
                }
                        die("Unable to write new index file");
        }
  
-       return 0;
+       return !!errs;
  }
diff --combined diff.c
index ca171e8e69680f8f873529729b129858a8201e08,b8161960e6dbee0b8ad13bd1f34bfc843c69c8f5..70699fd8c48754215934a8ed45db336b9db68918
--- 1/diff.c
--- 2/diff.c
+++ b/diff.c
@@@ -838,7 -838,7 +838,7 @@@ static unsigned char *deflate_it(char *
        return deflated;
  }
  
- static void emit_binary_diff(mmfile_t *one, mmfile_t *two)
+ static void emit_binary_diff_body(mmfile_t *one, mmfile_t *two)
  {
        void *cp;
        void *delta;
        unsigned long deflate_size;
        unsigned long data_size;
  
-       printf("GIT binary patch\n");
        /* We could do deflated delta, or we could do just deflated two,
         * whichever is smaller.
         */
        free(data);
  }
  
+ static void emit_binary_diff(mmfile_t *one, mmfile_t *two)
+ {
+       printf("GIT binary patch\n");
+       emit_binary_diff_body(one, two);
+       emit_binary_diff_body(two, one);
+ }
  #define FIRST_FEW_BYTES 8000
  static int mmfile_is_binary(mmfile_t *mf)
  {
@@@ -1101,7 -1107,7 +1107,7 @@@ void fill_filespec(struct diff_filespe
  {
        if (mode) {
                spec->mode = canon_mode(mode);
 -              memcpy(spec->sha1, sha1, 20);
 +              hashcpy(spec->sha1, sha1);
                spec->sha1_valid = !is_null_sha1(sha1);
        }
  }
@@@ -1140,7 -1146,7 +1146,7 @@@ static int work_tree_matches(const cha
        if ((lstat(name, &st) < 0) ||
            !S_ISREG(st.st_mode) || /* careful! */
            ce_match_stat(ce, &st, 0) ||
 -          memcmp(sha1, ce->sha1, 20))
 +          hashcmp(sha1, ce->sha1))
                return 0;
        /* we return 1 only when we can stat, it is a regular file,
         * stat information matches, and sha1 recorded in the cache
@@@ -1168,7 -1174,7 +1174,7 @@@ static struct sha1_size_cache *locate_s
        while (last > first) {
                int cmp, next = (last + first) >> 1;
                e = sha1_size_cache[next];
 -              cmp = memcmp(e->sha1, sha1, 20);
 +              cmp = hashcmp(e->sha1, sha1);
                if (!cmp)
                        return e;
                if (cmp < 0) {
                        sizeof(*sha1_size_cache));
        e = xmalloc(sizeof(struct sha1_size_cache));
        sha1_size_cache[first] = e;
 -      memcpy(e->sha1, sha1, 20);
 +      hashcpy(e->sha1, sha1);
        e->size = size;
        return e;
  }
@@@ -1516,7 -1522,7 +1522,7 @@@ static void diff_fill_sha1_info(struct 
                }
        }
        else
 -              memset(one->sha1, 0, 20);
 +              hashclr(one->sha1);
  }
  
  static void run_diff(struct diff_filepair *p, struct diff_options *o)
                ;
        }
  
 -      if (memcmp(one->sha1, two->sha1, 20)) {
 +      if (hashcmp(one->sha1, two->sha1)) {
                int abbrev = o->full_index ? 40 : DEFAULT_ABBREV;
  
                len += snprintf(msg + len, sizeof(msg) - len,
@@@ -2098,7 -2104,7 +2104,7 @@@ int diff_unmodified_pair(struct diff_fi
         * dealing with a change.
         */
        if (one->sha1_valid && two->sha1_valid &&
 -          !memcmp(one->sha1, two->sha1, sizeof(one->sha1)))
 +          !hashcmp(one->sha1, two->sha1))
                return 1; /* no change */
        if (!one->sha1_valid && !two->sha1_valid)
                return 1; /* both look at the same file on the filesystem. */
@@@ -2237,7 -2243,7 +2243,7 @@@ static void diff_resolve_rename_copy(vo
                        if (!p->status)
                                p->status = DIFF_STATUS_RENAMED;
                }
 -              else if (memcmp(p->one->sha1, p->two->sha1, 20) ||
 +              else if (hashcmp(p->one->sha1, p->two->sha1) ||
                         p->one->mode != p->two->mode)
                        p->status = DIFF_STATUS_MODIFIED;
                else {