Merge branch 'nd/ita-empty-commit'
authorJunio C Hamano <gitster@pobox.com>
Thu, 27 Oct 2016 21:58:50 +0000 (14:58 -0700)
committerJunio C Hamano <gitster@pobox.com>
Thu, 27 Oct 2016 21:58:50 +0000 (14:58 -0700)
When new paths were added by "git add -N" to the index, it was
enough to circumvent the check by "git commit" to refrain from
making an empty commit without "--allow-empty". The same logic
prevented "git status" to show such a path as "new file" in the
"Changes not staged for commit" section.

* nd/ita-empty-commit:
commit: don't be fooled by ita entries when creating initial commit
commit: fix empty commit creation when there's no changes but ita entries
diff: add --ita-[in]visible-in-index
diff-lib: allow ita entries treated as "not yet exist in index"

Documentation/diff-options.txt
builtin/commit.c
diff-lib.c
diff.c
diff.h
sequencer.c
t/t2203-add-intent.sh
t/t7064-wtstatus-pv2.sh
wt-status.c
index 29630c238912ef9b70ee1a8e9f3906a4d1f63ddd..e6215c372c3c3c876125c8ac363a446fd840e112 100644 (file)
@@ -572,5 +572,13 @@ endif::git-format-patch[]
 --line-prefix=<prefix>::
        Prepend an additional prefix to every line of output.
 
+--ita-invisible-in-index::
+       By default entries added by "git add -N" appear as an existing
+       empty file in "git diff" and a new file in "git diff --cached".
+       This option makes the entry appear as a new file in "git diff"
+       and non-existent in "git diff --cached". This option could be
+       reverted with `--ita-visible-in-index`. Both options are
+       experimental and could be removed in future.
+
 For more detailed explanation on these common options, see also
 linkgit:gitdiffcore[7].
index 9fddb195c5588d651c8cd0c0b43ec11ab145f5ec..a2baa6ebd5b2dc1090b1bcc87c1e9cf95abd6398 100644 (file)
@@ -894,9 +894,14 @@ static int prepare_to_commit(const char *index_file, const char *prefix,
                if (amend)
                        parent = "HEAD^1";
 
-               if (get_sha1(parent, sha1))
-                       commitable = !!active_nr;
-               else {
+               if (get_sha1(parent, sha1)) {
+                       int i, ita_nr = 0;
+
+                       for (i = 0; i < active_nr; i++)
+                               if (ce_intent_to_add(active_cache[i]))
+                                       ita_nr++;
+                       commitable = active_nr - ita_nr > 0;
+               } else {
                        /*
                         * Unless the user did explicitly request a submodule
                         * ignore mode by passing a command line option we do
@@ -910,7 +915,7 @@ static int prepare_to_commit(const char *index_file, const char *prefix,
                        if (ignore_submodule_arg &&
                            !strcmp(ignore_submodule_arg, "all"))
                                diff_flags |= DIFF_OPT_IGNORE_SUBMODULES;
-                       commitable = index_differs_from(parent, diff_flags);
+                       commitable = index_differs_from(parent, diff_flags, 1);
                }
        }
        strbuf_release(&committer_ident);
index 3007c8524c9b8017cb3dc3f7460e020b0d1aafd7..52447466b5d16c4e8978795b0122e2182e83c5cb 100644 (file)
@@ -214,6 +214,12 @@ int run_diff_files(struct rev_info *revs, unsigned int option)
                                               !is_null_oid(&ce->oid),
                                               ce->name, 0);
                                continue;
+                       } else if (revs->diffopt.ita_invisible_in_index &&
+                                  ce_intent_to_add(ce)) {
+                               diff_addremove(&revs->diffopt, '+', ce->ce_mode,
+                                              EMPTY_BLOB_SHA1_BIN, 0,
+                                              ce->name, 0);
+                               continue;
                        }
 
                        changed = match_stat_with_submodule(&revs->diffopt, ce, &st,
@@ -379,6 +385,14 @@ static void do_oneway_diff(struct unpack_trees_options *o,
        struct rev_info *revs = o->unpack_data;
        int match_missing, cached;
 
+       /* i-t-a entries do not actually exist in the index */
+       if (revs->diffopt.ita_invisible_in_index &&
+           idx && ce_intent_to_add(idx)) {
+               idx = NULL;
+               if (!tree)
+                       return; /* nothing to diff.. */
+       }
+
        /* if the entry is not checked out, don't examine work tree */
        cached = o->index_only ||
                (idx && ((idx->ce_flags & CE_VALID) || ce_skip_worktree(idx)));
@@ -521,7 +535,8 @@ int do_diff_cache(const unsigned char *tree_sha1, struct diff_options *opt)
        return 0;
 }
 
-int index_differs_from(const char *def, int diff_flags)
+int index_differs_from(const char *def, int diff_flags,
+                      int ita_invisible_in_index)
 {
        struct rev_info rev;
        struct setup_revision_opt opt;
@@ -533,6 +548,7 @@ int index_differs_from(const char *def, int diff_flags)
        DIFF_OPT_SET(&rev.diffopt, QUICK);
        DIFF_OPT_SET(&rev.diffopt, EXIT_WITH_STATUS);
        rev.diffopt.flags |= diff_flags;
+       rev.diffopt.ita_invisible_in_index = ita_invisible_in_index;
        run_diff_index(&rev, 1);
        if (rev.pending.alloc)
                free(rev.pending.objects);
diff --git a/diff.c b/diff.c
index 4c09314cc7970788382a01c6ff839563d4563241..8981477c436dd11d97714f0ad7e1b9363e328798 100644 (file)
--- a/diff.c
+++ b/diff.c
@@ -3987,6 +3987,10 @@ int diff_opt_parse(struct diff_options *options,
                return parse_submodule_opt(options, arg);
        else if (skip_prefix(arg, "--ws-error-highlight=", &arg))
                return parse_ws_error_highlight_opt(options, arg);
+       else if (!strcmp(arg, "--ita-invisible-in-index"))
+               options->ita_invisible_in_index = 1;
+       else if (!strcmp(arg, "--ita-visible-in-index"))
+               options->ita_invisible_in_index = 0;
 
        /* misc options */
        else if (!strcmp(arg, "-z"))
diff --git a/diff.h b/diff.h
index 01afc70bd572448ed25cdc869b071994e948c546..e9ccb38c26c7f1b5b0d5932bb98c2875276cf828 100644 (file)
--- a/diff.h
+++ b/diff.h
@@ -146,6 +146,7 @@ struct diff_options {
        int dirstat_permille;
        int setup;
        int abbrev;
+       int ita_invisible_in_index;
 /* white-space error highlighting */
 #define WSEH_NEW 1
 #define WSEH_CONTEXT 2
@@ -360,7 +361,7 @@ extern int diff_result_code(struct diff_options *, int);
 
 extern void diff_no_index(struct rev_info *, int, const char **);
 
-extern int index_differs_from(const char *def, int diff_flags);
+extern int index_differs_from(const char *def, int diff_flags, int ita_invisible_in_index);
 
 /*
  * Fill the contents of the filespec "df", respecting any textconv defined by
index a61fe76f98f0a5805bb75e9b5c40c39b4b750498..5fd75f30dd4455bf71a12fd628b488fc2b17e192 100644 (file)
@@ -658,7 +658,7 @@ static int do_pick_commit(enum todo_command command, struct commit *commit,
                unborn = get_sha1("HEAD", head);
                if (unborn)
                        hashcpy(head, EMPTY_TREE_SHA1_BIN);
-               if (index_differs_from(unborn ? EMPTY_TREE_SHA1_HEX : "HEAD", 0))
+               if (index_differs_from(unborn ? EMPTY_TREE_SHA1_HEX : "HEAD", 0, 0))
                        return error_dirty_index(opts);
        }
        discard_cache();
@@ -1312,7 +1312,7 @@ int sequencer_continue(struct replay_opts *opts)
                if (res)
                        goto release_todo_list;
        }
-       if (index_differs_from("HEAD", 0)) {
+       if (index_differs_from("HEAD", 0, 0)) {
                res = error_dirty_index(opts);
                goto release_todo_list;
        }
index 8f22c43e245cb6fb68449a64ba45222e187f56a9..84a9028c433c02c72adcdf06222bcb409e2eb683 100755 (executable)
@@ -5,10 +5,24 @@ test_description='Intent to add'
 . ./test-lib.sh
 
 test_expect_success 'intent to add' '
+       test_commit 1 &&
+       git rm 1.t &&
+       echo hello >1.t &&
        echo hello >file &&
        echo hello >elif &&
        git add -N file &&
-       git add elif
+       git add elif &&
+       git add -N 1.t
+'
+
+test_expect_success 'git status' '
+       git status --porcelain | grep -v actual >actual &&
+       cat >expect <<-\EOF &&
+       DA 1.t
+       A  elif
+        A file
+       EOF
+       test_cmp expect actual
 '
 
 test_expect_success 'check result of "add -N"' '
@@ -43,7 +57,9 @@ test_expect_success 'i-t-a entry is simply ignored' '
        git add -N nitfol &&
        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 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_expect_success 'can commit with an unrelated i-t-a entry in index' '
@@ -113,5 +129,26 @@ test_expect_success 'cache-tree does skip dir that becomes empty' '
        )
 '
 
+test_expect_success 'commit: ita entries ignored in empty intial commit check' '
+       git init empty-intial-commit &&
+       (
+               cd empty-intial-commit &&
+               : >one &&
+               git add -N one &&
+               test_must_fail git commit -m nothing-new-here
+       )
+'
+
+test_expect_success 'commit: ita entries ignored in empty commit check' '
+       git init empty-subsequent-commit &&
+       (
+               cd empty-subsequent-commit &&
+               test_commit one &&
+               : >two &&
+               git add -N two &&
+               test_must_fail git commit -m nothing-new-here
+       )
+'
+
 test_done
 
index 3012a4d7c0aa5015add8ae3de2951981e28ee12a..e319fa2e8470791340a479989a81d934b79405da 100755 (executable)
@@ -246,8 +246,8 @@ test_expect_success 'verify --intent-to-add output' '
        git add --intent-to-add intent1.add intent2.add &&
 
        cat >expect <<-EOF &&
-       1 AM N... 000000 100644 100644 $_z40 $EMPTY_BLOB intent1.add
-       1 AM N... 000000 100644 100644 $_z40 $EMPTY_BLOB intent2.add
+       1 .A N... 000000 000000 100644 $_z40 $_z40 intent1.add
+       1 .A N... 000000 000000 100644 $_z40 $_z40 intent2.add
        EOF
 
        git status --porcelain=v2 >actual &&
index 0bd2781225a2a9fa3e7a553aaf49112745743244..a2e9d332d8332bb1dbeea26b683c5e3f032a822e 100644 (file)
@@ -438,7 +438,7 @@ static void wt_status_collect_changed_cb(struct diff_queue_struct *q,
 
                switch (p->status) {
                case DIFF_STATUS_ADDED:
-                       die("BUG: worktree status add???");
+                       d->mode_worktree = p->two->mode;
                        break;
 
                case DIFF_STATUS_DELETED:
@@ -548,6 +548,7 @@ static void wt_status_collect_changes_worktree(struct wt_status *s)
        setup_revisions(0, NULL, &rev, NULL);
        rev.diffopt.output_format |= DIFF_FORMAT_CALLBACK;
        DIFF_OPT_SET(&rev.diffopt, DIRTY_SUBMODULES);
+       rev.diffopt.ita_invisible_in_index = 1;
        if (!s->show_untracked_files)
                DIFF_OPT_SET(&rev.diffopt, IGNORE_UNTRACKED_IN_SUBMODULES);
        if (s->ignore_submodule_arg) {
@@ -571,6 +572,7 @@ static void wt_status_collect_changes_index(struct wt_status *s)
        setup_revisions(0, NULL, &rev, &opt);
 
        DIFF_OPT_SET(&rev.diffopt, OVERRIDE_SUBMODULE_CONFIG);
+       rev.diffopt.ita_invisible_in_index = 1;
        if (s->ignore_submodule_arg) {
                handle_ignore_submodules_arg(&rev.diffopt, s->ignore_submodule_arg);
        } else {
@@ -606,6 +608,8 @@ static void wt_status_collect_changes_initial(struct wt_status *s)
 
                if (!ce_path_match(ce, &s->pathspec, NULL))
                        continue;
+               if (ce_intent_to_add(ce))
+                       continue;
                it = string_list_insert(&s->change, ce->name);
                d = it->util;
                if (!d) {
@@ -912,6 +916,7 @@ static void wt_longstatus_print_verbose(struct wt_status *s)
 
        init_revisions(&rev, NULL);
        DIFF_OPT_SET(&rev.diffopt, ALLOW_TEXTCONV);
+       rev.diffopt.ita_invisible_in_index = 1;
 
        memset(&opt, 0, sizeof(opt));
        opt.def = s->is_initial ? EMPTY_TREE_SHA1_HEX : s->reference;