Merge branch 'js/apply-recount-allow-noop'
authorJunio C Hamano <gitster@pobox.com>
Sun, 18 Nov 2018 09:23:56 +0000 (18:23 +0900)
committerJunio C Hamano <gitster@pobox.com>
Sun, 18 Nov 2018 09:23:56 +0000 (18:23 +0900)
When editing a patch in a "git add -i" session, a hunk could be
made to no-op. The "git apply" program used to reject a patch with
such a no-op hunk to catch user mistakes, but it is now updated to
explicitly allow a no-op hunk in an edited patch.

* js/apply-recount-allow-noop:
apply --recount: allow "no-op hunks"

1  2 
apply.c
diff --combined apply.c
index 3746310cefae7ff8127cc033e0c11b3eb43eecd0,5fb8ecf22ce519e7ae3318b77d153027c6f0ec16..01793d612620246b14fa57f6ce3ed6c33df65d0f
+++ b/apply.c
@@@ -223,8 -223,8 +223,8 @@@ struct patch 
        struct fragment *fragments;
        char *result;
        size_t resultsize;
 -      char old_sha1_prefix[41];
 -      char new_sha1_prefix[41];
 +      char old_oid_prefix[GIT_MAX_HEXSZ + 1];
 +      char new_oid_prefix[GIT_MAX_HEXSZ + 1];
        struct patch *next;
  
        /* three-way fallback result */
@@@ -1093,14 -1093,13 +1093,14 @@@ static int gitdiff_index(struct apply_s
         */
        const char *ptr, *eol;
        int len;
 +      const unsigned hexsz = the_hash_algo->hexsz;
  
        ptr = strchr(line, '.');
 -      if (!ptr || ptr[1] != '.' || 40 < ptr - line)
 +      if (!ptr || ptr[1] != '.' || hexsz < ptr - line)
                return 0;
        len = ptr - line;
 -      memcpy(patch->old_sha1_prefix, line, len);
 -      patch->old_sha1_prefix[len] = 0;
 +      memcpy(patch->old_oid_prefix, line, len);
 +      patch->old_oid_prefix[len] = 0;
  
        line = ptr + 2;
        ptr = strchr(line, ' ');
                ptr = eol;
        len = ptr - line;
  
 -      if (40 < len)
 +      if (hexsz < len)
                return 0;
 -      memcpy(patch->new_sha1_prefix, line, len);
 -      patch->new_sha1_prefix[len] = 0;
 +      memcpy(patch->new_oid_prefix, line, len);
 +      patch->new_oid_prefix[len] = 0;
        if (*ptr == ' ')
                return gitdiff_oldmode(state, ptr + 1, patch);
        return 0;
@@@ -1748,7 -1747,7 +1748,7 @@@ static int parse_fragment(struct apply_
        }
        if (oldlines || newlines)
                return -1;
-       if (!deleted && !added)
+       if (!patch->recount && !deleted && !added)
                return -1;
  
        fragment->leading = leading;
@@@ -2132,12 -2131,10 +2132,12 @@@ static int parse_chunk(struct apply_sta
  
        if (!use_patch(state, patch))
                patch->ws_rule = 0;
 +      else if (patch->new_name)
 +              patch->ws_rule = whitespace_rule(state->repo->index,
 +                                               patch->new_name);
        else
 -              patch->ws_rule = whitespace_rule(patch->new_name
 -                                               ? patch->new_name
 -                                               : patch->old_name);
 +              patch->ws_rule = whitespace_rule(state->repo->index,
 +                                               patch->old_name);
  
        patchsize = parse_single_patch(state,
                                       buffer + offset + hdrsize,
@@@ -2207,7 -2204,7 +2207,7 @@@ static void reverse_patches(struct patc
                SWAP(p->new_mode, p->old_mode);
                SWAP(p->is_new, p->is_delete);
                SWAP(p->lines_added, p->lines_deleted);
 -              SWAP(p->old_sha1_prefix, p->new_sha1_prefix);
 +              SWAP(p->old_oid_prefix, p->new_oid_prefix);
  
                for (; frag; frag = frag->next) {
                        SWAP(frag->newpos, frag->oldpos);
@@@ -3145,16 -3142,15 +3145,16 @@@ static int apply_binary(struct apply_st
  {
        const char *name = patch->old_name ? patch->old_name : patch->new_name;
        struct object_id oid;
 +      const unsigned hexsz = the_hash_algo->hexsz;
  
        /*
         * For safety, we require patch index line to contain
 -       * full 40-byte textual SHA1 for old and new, at least for now.
 +       * full hex textual object ID for old and new, at least for now.
         */
 -      if (strlen(patch->old_sha1_prefix) != 40 ||
 -          strlen(patch->new_sha1_prefix) != 40 ||
 -          get_oid_hex(patch->old_sha1_prefix, &oid) ||
 -          get_oid_hex(patch->new_sha1_prefix, &oid))
 +      if (strlen(patch->old_oid_prefix) != hexsz ||
 +          strlen(patch->new_oid_prefix) != hexsz ||
 +          get_oid_hex(patch->old_oid_prefix, &oid) ||
 +          get_oid_hex(patch->new_oid_prefix, &oid))
                return error(_("cannot apply binary patch to '%s' "
                               "without full index line"), name);
  
                 * applies to.
                 */
                hash_object_file(img->buf, img->len, blob_type, &oid);
 -              if (strcmp(oid_to_hex(&oid), patch->old_sha1_prefix))
 +              if (strcmp(oid_to_hex(&oid), patch->old_oid_prefix))
                        return error(_("the patch applies to '%s' (%s), "
                                       "which does not match the "
                                       "current contents."),
                                       "'%s' but it is not empty"), name);
        }
  
 -      get_oid_hex(patch->new_sha1_prefix, &oid);
 +      get_oid_hex(patch->new_oid_prefix, &oid);
        if (is_null_oid(&oid)) {
                clear_image(img);
                return 0; /* deletion patch */
                if (!result)
                        return error(_("the necessary postimage %s for "
                                       "'%s' cannot be read"),
 -                                   patch->new_sha1_prefix, name);
 +                                   patch->new_oid_prefix, name);
                clear_image(img);
                img->buf = result;
                img->len = size;
  
                /* verify that the result matches */
                hash_object_file(img->buf, img->len, blob_type, &oid);
 -              if (strcmp(oid_to_hex(&oid), patch->new_sha1_prefix))
 +              if (strcmp(oid_to_hex(&oid), patch->new_oid_prefix))
                        return error(_("binary patch to '%s' creates incorrect result (expecting %s, got %s)"),
 -                              name, patch->new_sha1_prefix, oid_to_hex(&oid));
 +                              name, patch->new_oid_prefix, oid_to_hex(&oid));
        }
  
        return 0;
@@@ -3471,8 -3467,7 +3471,8 @@@ static int load_preimage(struct apply_s
        return 0;
  }
  
 -static int three_way_merge(struct image *image,
 +static int three_way_merge(struct apply_state *state,
 +                         struct image *image,
                           char *path,
                           const struct object_id *base,
                           const struct object_id *ours,
        status = ll_merge(&result, path,
                          &base_file, "base",
                          &our_file, "ours",
 -                        &their_file, "theirs", NULL);
 +                        &their_file, "theirs",
 +                        state->repo->index,
 +                        NULL);
        free(base_file.ptr);
        free(our_file.ptr);
        free(their_file.ptr);
@@@ -3570,7 -3563,7 +3570,7 @@@ static int try_threeway(struct apply_st
        /* Preimage the patch was prepared for */
        if (patch->is_new)
                write_object_file("", 0, blob_type, &pre_oid);
 -      else if (get_oid(patch->old_sha1_prefix, &pre_oid) ||
 +      else if (get_oid(patch->old_oid_prefix, &pre_oid) ||
                 read_blob_object(&buf, &pre_oid, patch->old_mode))
                return error(_("repository lacks the necessary blob to fall back on 3-way merge."));
  
        clear_image(&tmp_image);
  
        /* in-core three-way merge between post and our using pre as base */
 -      status = three_way_merge(image, patch->new_name,
 +      status = three_way_merge(state, image, patch->new_name,
                                 &pre_oid, &our_oid, &post_oid);
        if (status < 0) {
                if (state->apply_verbosity > verbosity_silent)
@@@ -4062,13 -4055,13 +4062,13 @@@ static int preimage_oid_in_gitlink_patc
            starts_with(++preimage, heading) &&
            /* does it record full SHA-1? */
            !get_oid_hex(preimage + sizeof(heading) - 1, oid) &&
 -          preimage[sizeof(heading) + GIT_SHA1_HEXSZ - 1] == '\n' &&
 +          preimage[sizeof(heading) + the_hash_algo->hexsz - 1] == '\n' &&
            /* does the abbreviated name on the index line agree with it? */
 -          starts_with(preimage + sizeof(heading) - 1, p->old_sha1_prefix))
 +          starts_with(preimage + sizeof(heading) - 1, p->old_oid_prefix))
                return 0; /* it all looks fine */
  
        /* we may have full object name on the index line */
 -      return get_oid_hex(p->old_sha1_prefix, oid);
 +      return get_oid_hex(p->old_oid_prefix, oid);
  }
  
  /* Build an index that contains just the files needed for a 3way merge */
@@@ -4097,7 -4090,7 +4097,7 @@@ static int build_fake_ancestor(struct a
                        else
                                return error(_("sha1 information is lacking or "
                                               "useless for submodule %s"), name);
 -              } else if (!get_oid_blob(patch->old_sha1_prefix, &oid)) {
 +              } else if (!get_oid_blob(patch->old_oid_prefix, &oid)) {
                        ; /* ok */
                } else if (!patch->lines_added && !patch->lines_deleted) {
                        /* mode-only change: update the current */
@@@ -4634,7 -4627,7 +4634,7 @@@ static int write_out_results(struct app
                }
                string_list_clear(&cpath, 0);
  
 -              rerere(0);
 +              repo_rerere(state->repo, 0);
        }
  
        return errs;
@@@ -4772,9 -4765,6 +4772,9 @@@ static int apply_option_parse_exclude(c
                                      const char *arg, int unset)
  {
        struct apply_state *state = opt->value;
 +
 +      BUG_ON_OPT_NEG(unset);
 +
        add_name_limit(state, arg, 1);
        return 0;
  }
@@@ -4783,9 -4773,6 +4783,9 @@@ static int apply_option_parse_include(c
                                      const char *arg, int unset)
  {
        struct apply_state *state = opt->value;
 +
 +      BUG_ON_OPT_NEG(unset);
 +
        add_name_limit(state, arg, 0);
        state->has_include = 1;
        return 0;
@@@ -4796,9 -4783,6 +4796,9 @@@ static int apply_option_parse_p(const s
                                int unset)
  {
        struct apply_state *state = opt->value;
 +
 +      BUG_ON_OPT_NEG(unset);
 +
        state->p_value = atoi(arg);
        state->p_value_known = 1;
        return 0;
@@@ -4808,9 -4792,6 +4808,9 @@@ static int apply_option_parse_space_cha
                                           const char *arg, int unset)
  {
        struct apply_state *state = opt->value;
 +
 +      BUG_ON_OPT_ARG(arg);
 +
        if (unset)
                state->ws_ignore_action = ignore_ws_none;
        else
@@@ -4822,12 -4803,9 +4822,12 @@@ static int apply_option_parse_whitespac
                                         const char *arg, int unset)
  {
        struct apply_state *state = opt->value;
 +
 +      BUG_ON_OPT_NEG(unset);
 +
        state->whitespace_option = arg;
        if (parse_whitespace_option(state, arg))
 -              exit(1);
 +              return -1;
        return 0;
  }
  
@@@ -4835,9 -4813,6 +4835,9 @@@ static int apply_option_parse_directory
                                        const char *arg, int unset)
  {
        struct apply_state *state = opt->value;
 +
 +      BUG_ON_OPT_NEG(unset);
 +
        strbuf_reset(&state->root);
        strbuf_addstr(&state->root, arg);
        strbuf_complete(&state->root, '/');
@@@ -4957,10 -4932,10 +4957,10 @@@ int apply_parse_options(int argc, cons
        struct option builtin_apply_options[] = {
                { OPTION_CALLBACK, 0, "exclude", state, N_("path"),
                        N_("don't apply changes matching the given path"),
 -                      0, apply_option_parse_exclude },
 +                      PARSE_OPT_NONEG, apply_option_parse_exclude },
                { OPTION_CALLBACK, 0, "include", state, N_("path"),
                        N_("apply changes matching the given path"),
 -                      0, apply_option_parse_include },
 +                      PARSE_OPT_NONEG, apply_option_parse_include },
                { OPTION_CALLBACK, 'p', NULL, state, N_("num"),
                        N_("remove <num> leading slashes from traditional diff paths"),
                        0, apply_option_parse_p },