Merge branch 'nd/diff-apply-ita'
authorJunio C Hamano <gitster@pobox.com>
Mon, 25 Jun 2018 20:22:36 +0000 (13:22 -0700)
committerJunio C Hamano <gitster@pobox.com>
Mon, 25 Jun 2018 20:22:36 +0000 (13:22 -0700)
"git diff" compares the index and the working tree. For paths
added with intent-to-add bit, the command shows the full contents
of them as added, but the paths themselves were not marked as new
files. They are now shown as new by default.

"git apply" learned the "--intent-to-add" option so that an
otherwise working-tree-only application of a patch will add new
paths to the index marked with the "intent-to-add" bit.

* nd/diff-apply-ita:
apply: add --intent-to-add
t2203: add a test about "diff HEAD" case
diff: turn --ita-invisible-in-index on by default
diff: ignore --ita-[in]visible-in-index when diffing worktree-to-tree

1  2 
Documentation/git-apply.txt
apply.c
builtin/diff.c
t/t2203-add-intent.sh
index 67228494c00e1df676723072d0884f7705e532f1,e3b966c65616b48c35e0e1dff53a5c2db6b67d36..b9aa39000fc8aa77d69751d9409a957139c2cff7
@@@ -9,7 -9,7 +9,7 @@@ git-apply - Apply a patch to files and/
  SYNOPSIS
  --------
  [verse]
- 'git apply' [--stat] [--numstat] [--summary] [--check] [--index] [--3way]
+ 'git apply' [--stat] [--numstat] [--summary] [--check] [--index | --intent-to-add] [--3way]
          [--apply] [--no-add] [--build-fake-ancestor=<file>] [-R | --reverse]
          [--allow-binary-replacement | --binary] [--reject] [-z]
          [-p<n>] [-C<n>] [--inaccurate-eof] [--recount] [--cached]
@@@ -74,6 -74,14 +74,14 @@@ OPTION
        cached data, apply the patch, and store the result in the index
        without using the working tree. This implies `--index`.
  
+ --intent-to-add::
+       When applying the patch only to the working tree, mark new
+       files to be added to the index later (see `--intent-to-add`
+       option in linkgit:git-add[1]). This option is ignored unless
+       running in a Git repository and `--index` is not specified.
+       Note that `--index` could be implied by other options such
+       as `--cached` or `--3way`.
  -3::
  --3way::
        When the patch does not apply cleanly, fall back on 3-way merge if
@@@ -113,10 -121,8 +121,10 @@@ explained for the configuration variabl
  linkgit:git-config[1]).
  
  -p<n>::
 -      Remove <n> leading slashes from traditional diff paths. The
 -      default is 1.
 +      Remove <n> leading path components (separated by slashes) from
 +      traditional diff paths. E.g., with `-p2`, a patch against
 +      `a/dir/file` will be applied directly to `file`. The default is
 +      1.
  
  -C<n>::
        Ensure at least <n> lines of surrounding context match before
@@@ -242,7 -248,7 +250,7 @@@ When `git apply` is used as a "better G
  the `--unsafe-paths` option to override this safety check.  This option
  has no effect when `--index` or `--cached` is in use.
  
 -Configuration
 +CONFIGURATION
  -------------
  
  apply.ignoreWhitespace::
@@@ -253,7 -259,7 +261,7 @@@ apply.whitespace:
        When no `--whitespace` flag is given from the command
        line, this configuration item is used as the default.
  
 -Submodules
 +SUBMODULES
  ----------
  If the patch contains any changes to submodules then 'git apply'
  treats these changes as follows.
diff --combined apply.c
index d79e61591b3814339fb8b2a39fb7d1e211d622c6,899c5cc0ac846f8ba9634b21028557ff3412f2f5..959c45791008214cbc971493afaeb5f0e761f486
+++ b/apply.c
@@@ -141,6 -141,8 +141,8 @@@ int check_apply_state(struct apply_stat
                        return error(_("--cached outside a repository"));
                state->check_index = 1;
        }
+       if (state->ita_only && (state->check_index || is_not_gitdir))
+               state->ita_only = 0;
        if (state->check_index)
                state->unsafe_paths = 0;
  
@@@ -2375,7 -2377,7 +2377,7 @@@ static void update_pre_post_images(stru
        if (postlen
            ? postlen < new_buf - postimage->buf
            : postimage->len < new_buf - postimage->buf)
 -              die("BUG: caller miscounted postlen: asked %d, orig = %d, used = %d",
 +              BUG("caller miscounted postlen: asked %d, orig = %d, used = %d",
                    (int)postlen, (int) postimage->len, (int)(new_buf - postimage->buf));
  
        /* Fix the length of the whole thing */
@@@ -3509,7 -3511,7 +3511,7 @@@ static int load_current(struct apply_st
        unsigned mode = patch->new_mode;
  
        if (!patch->is_new)
 -              die("BUG: patch to %s is not a creation", patch->old_name);
 +              BUG("patch to %s is not a creation", patch->old_name);
  
        pos = cache_name_pos(name, strlen(name));
        if (pos < 0)
@@@ -3860,9 -3862,9 +3862,9 @@@ static int check_unsafe_path(struct pat
        if (!patch->is_delete)
                new_name = patch->new_name;
  
 -      if (old_name && !verify_path(old_name))
 +      if (old_name && !verify_path(old_name, patch->old_mode))
                return error(_("invalid path '%s'"), old_name);
 -      if (new_name && !verify_path(new_name))
 +      if (new_name && !verify_path(new_name, patch->new_mode))
                return error(_("invalid path '%s'"), new_name);
        return 0;
  }
@@@ -4058,7 -4060,7 +4060,7 @@@ static int build_fake_ancestor(struct a
  {
        struct patch *patch;
        struct index_state result = { NULL };
 -      static struct lock_file lock;
 +      struct lock_file lock = LOCK_INIT;
        int res;
  
        /* Once we start supporting the reverse patch, it may be
@@@ -4242,7 -4244,7 +4244,7 @@@ static void patch_stats(struct apply_st
  
  static int remove_file(struct apply_state *state, struct patch *patch, int rmdir_empty)
  {
-       if (state->update_index) {
+       if (state->update_index && !state->ita_only) {
                if (remove_file_from_cache(patch->old_name) < 0)
                        return error(_("unable to remove %s from index"), patch->old_name);
        }
@@@ -4265,15 -4267,15 +4267,15 @@@ static int add_index_file(struct apply_
        int namelen = strlen(path);
        unsigned ce_size = cache_entry_size(namelen);
  
-       if (!state->update_index)
-               return 0;
        ce = xcalloc(1, ce_size);
        memcpy(ce->name, path, namelen);
        ce->ce_mode = create_ce_mode(mode);
        ce->ce_flags = create_ce_flags(0);
        ce->ce_namelen = namelen;
-       if (S_ISGITLINK(mode)) {
+       if (state->ita_only) {
+               ce->ce_flags |= CE_INTENT_TO_ADD;
+               set_object_name_for_intent_to_add_entry(ce);
+       } else if (S_ISGITLINK(mode)) {
                const char *s;
  
                if (!skip_prefix(buf, "Subproject commit ", &s) ||
@@@ -4465,8 -4467,9 +4467,9 @@@ static int create_file(struct apply_sta
  
        if (patch->conflicted_threeway)
                return add_conflicted_stages_file(state, patch);
-       else
+       else if (state->update_index)
                return add_index_file(state, path, mode, buf, size);
+       return 0;
  }
  
  /* phase zero is to remove, phase one is to create */
@@@ -4686,7 -4689,7 +4689,7 @@@ static int apply_patch(struct apply_sta
        if (state->whitespace_error && (state->ws_error_action == die_on_ws_error))
                state->apply = 0;
  
-       state->update_index = state->check_index && state->apply;
+       state->update_index = (state->check_index || state->ita_only) && state->apply;
        if (state->update_index && !is_lock_file_locked(&state->lock_file)) {
                if (state->index_file)
                        hold_lock_file_for_update(&state->lock_file,
@@@ -4941,6 -4944,8 +4944,8 @@@ int apply_parse_options(int argc, cons
                        N_("instead of applying the patch, see if the patch is applicable")),
                OPT_BOOL(0, "index", &state->check_index,
                        N_("make sure the patch is applicable to the current index")),
+               OPT_BOOL('N', "intent-to-add", &state->ita_only,
+                       N_("mark new files with `git add --intent-to-add`")),
                OPT_BOOL(0, "cached", &state->cached,
                        N_("apply a patch without touching the working tree")),
                OPT_BOOL_F(0, "unsafe-paths", &state->unsafe_paths,
diff --combined builtin/diff.c
index bfefff3a84896a79fbed42eec1121286edcc86dd,00424c296d19e3812115659cdc3dc82a68359e85..b709b6e9842c68597b5bb7149db4054ef980fc53
@@@ -352,6 -352,13 +352,13 @@@ int cmd_diff(int argc, const char **arg
        rev.diffopt.flags.allow_external = 1;
        rev.diffopt.flags.allow_textconv = 1;
  
+       /*
+        * Default to intent-to-add entries invisible in the
+        * index. This makes them show up as new files in diff-files
+        * and not at all in diff-cached.
+        */
+       rev.diffopt.ita_invisible_in_index = 1;
        if (nongit)
                die(_("Not a git repository"));
        argc = setup_revisions(argc, argv, &rev, NULL);
                if (!obj)
                        die(_("invalid object '%s' given."), name);
                if (obj->type == OBJ_COMMIT)
 -                      obj = &((struct commit *)obj)->tree->object;
 +                      obj = &get_commit_tree(((struct commit *)obj))->object;
  
                if (obj->type == OBJ_TREE) {
                        obj->flags |= flags;
diff --combined t/t2203-add-intent.sh
index 04d840a5448567bf438688c4aebd7e94d63da4ec,0891827863740844ee59d32ae6d40bb3629ca151..e7a400b4c77a822a49365e4049fbe68b059d23ee
@@@ -27,12 -27,12 +27,12 @@@ test_expect_success 'git status' 
  
  test_expect_success 'git status with porcelain v2' '
        git status --porcelain=v2 | grep -v "^?" >actual &&
 -      nam1=d00491fd7e5bb6fa28c517a0bb32b8b506539d4d &&
 -      nam2=ce013625030ba8dba906f756967f9e9ca394464a &&
 +      nam1=$(echo 1 | git hash-object --stdin) &&
 +      nam2=$(git hash-object elif) &&
        cat >expect <<-EOF &&
 -      1 DA N... 100644 000000 100644 $nam1 $_z40 1.t
 -      1 A. N... 000000 100644 100644 $_z40 $nam2 elif
 -      1 .A N... 000000 000000 100644 $_z40 $_z40 file
 +      1 DA N... 100644 000000 100644 $nam1 $ZERO_OID 1.t
 +      1 A. N... 000000 100644 100644 $ZERO_OID $nam2 elif
 +      1 .A N... 000000 000000 100644 $ZERO_OID $ZERO_OID file
        EOF
        test_cmp expect actual
  '
@@@ -70,8 -70,7 +70,7 @@@ test_expect_success 'i-t-a entry is sim
        git commit -m second &&
        test $(git ls-tree HEAD -- nitfol | wc -l) = 0 &&
        test $(git diff --name-only HEAD -- nitfol | wc -l) = 1 &&
-       test $(git diff --name-only --ita-invisible-in-index HEAD -- nitfol | wc -l) = 0 &&
-       test $(git diff --name-only --ita-invisible-in-index -- nitfol | wc -l) = 1
+       test $(git diff --name-only -- nitfol | wc -l) = 1
  '
  
  test_expect_success 'can commit with an unrelated i-t-a entry in index' '
@@@ -99,13 -98,13 +98,13 @@@ test_expect_success 'cache-tree invalid
  
        : >dir/bar &&
        git add -N dir/bar &&
-       git diff --cached --name-only >actual &&
+       git diff --name-only >actual &&
        echo dir/bar >expect &&
        test_cmp expect actual &&
  
        git write-tree >/dev/null &&
  
-       git diff --cached --name-only >actual &&
+       git diff --name-only >actual &&
        echo dir/bar >expect &&
        test_cmp expect actual
  '
@@@ -181,12 -180,24 +180,24 @@@ test_expect_success 'rename detection f
                EOF
                test_cmp expected.2 actual.2 &&
  
 -              hash=12f00e90b6ef79117ce6e650416b8cf517099b78 &&
 +              hash=$(git hash-object third) &&
                git status --porcelain=v2 | grep -v "^?" >actual.3 &&
                cat >expected.3 <<-EOF &&
                2 .R N... 100644 100644 100644 $hash $hash R100 third   first
                EOF
-               test_cmp expected.3 actual.3
+               test_cmp expected.3 actual.3 &&
+               git diff --stat >actual.4 &&
+               cat >expected.4 <<-EOF &&
+                first => third | 0
+                1 file changed, 0 insertions(+), 0 deletions(-)
+               EOF
+               test_cmp expected.4 actual.4 &&
+               git diff --cached --stat >actual.5 &&
+               : >expected.5 &&
+               test_cmp expected.5 actual.5
        )
  '
  
@@@ -212,7 -223,7 +223,7 @@@ test_expect_success 'double rename dete
                EOF
                test_cmp expected.2 actual.2 &&
  
 -              hash=12f00e90b6ef79117ce6e650416b8cf517099b78 &&
 +              hash=$(git hash-object third) &&
                git status --porcelain=v2 | grep -v "^?" >actual.3 &&
                cat >expected.3 <<-EOF &&
                2 R. N... 100644 100644 100644 $hash $hash R100 second  first
        )
  '
  
- test_done
+ test_expect_success 'diff-files/diff-cached shows ita as new/not-new files' '
+       git reset --hard &&
+       echo new >new-ita &&
+       git add -N new-ita &&
+       git diff --summary >actual &&
+       echo " create mode 100644 new-ita" >expected &&
+       test_cmp expected actual &&
+       git diff --cached --summary >actual2 &&
+       : >expected2 &&
+       test_cmp expected2 actual2
+ '
  
+ test_expect_success '"diff HEAD" includes ita as new files' '
+       git reset --hard &&
+       echo new >new-ita &&
+       git add -N new-ita &&
+       git diff HEAD >actual &&
+       cat >expected <<-\EOF &&
+       diff --git a/new-ita b/new-ita
+       new file mode 100644
+       index 0000000..3e75765
+       --- /dev/null
+       +++ b/new-ita
+       @@ -0,0 +1 @@
+       +new
+       EOF
+       test_cmp expected actual
+ '
+ test_expect_success 'apply --intent-to-add' '
+       git reset --hard &&
+       echo new >new-ita &&
+       git add -N new-ita &&
+       git diff >expected &&
+       grep "new file" expected &&
+       git reset --hard &&
+       git apply --intent-to-add expected &&
+       git diff >actual &&
+       test_cmp expected actual
+ '
+ test_done