Merge branch 'nd/ita-empty-commit'

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"
maint
Junio C Hamano 2016-10-27 14:58:50 -07:00
commit 650360210a
9 changed files with 89 additions and 13 deletions

View File

@ -572,5 +572,13 @@ endif::git-format-patch[]
--line-prefix=<prefix>:: --line-prefix=<prefix>::
Prepend an additional prefix to every line of output. 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 For more detailed explanation on these common options, see also
linkgit:gitdiffcore[7]. linkgit:gitdiffcore[7].

View File

@ -894,9 +894,14 @@ static int prepare_to_commit(const char *index_file, const char *prefix,
if (amend) if (amend)
parent = "HEAD^1"; parent = "HEAD^1";


if (get_sha1(parent, sha1)) if (get_sha1(parent, sha1)) {
commitable = !!active_nr; int i, ita_nr = 0;
else {
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 * Unless the user did explicitly request a submodule
* ignore mode by passing a command line option we do * 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 && if (ignore_submodule_arg &&
!strcmp(ignore_submodule_arg, "all")) !strcmp(ignore_submodule_arg, "all"))
diff_flags |= DIFF_OPT_IGNORE_SUBMODULES; 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); strbuf_release(&committer_ident);

View File

@ -214,6 +214,12 @@ int run_diff_files(struct rev_info *revs, unsigned int option)
!is_null_oid(&ce->oid), !is_null_oid(&ce->oid),
ce->name, 0); ce->name, 0);
continue; 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, 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; struct rev_info *revs = o->unpack_data;
int match_missing, cached; 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 */ /* if the entry is not checked out, don't examine work tree */
cached = o->index_only || cached = o->index_only ||
(idx && ((idx->ce_flags & CE_VALID) || ce_skip_worktree(idx))); (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; 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 rev_info rev;
struct setup_revision_opt opt; 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, QUICK);
DIFF_OPT_SET(&rev.diffopt, EXIT_WITH_STATUS); DIFF_OPT_SET(&rev.diffopt, EXIT_WITH_STATUS);
rev.diffopt.flags |= diff_flags; rev.diffopt.flags |= diff_flags;
rev.diffopt.ita_invisible_in_index = ita_invisible_in_index;
run_diff_index(&rev, 1); run_diff_index(&rev, 1);
if (rev.pending.alloc) if (rev.pending.alloc)
free(rev.pending.objects); free(rev.pending.objects);

4
diff.c
View File

@ -3987,6 +3987,10 @@ int diff_opt_parse(struct diff_options *options,
return parse_submodule_opt(options, arg); return parse_submodule_opt(options, arg);
else if (skip_prefix(arg, "--ws-error-highlight=", &arg)) else if (skip_prefix(arg, "--ws-error-highlight=", &arg))
return parse_ws_error_highlight_opt(options, 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 */ /* misc options */
else if (!strcmp(arg, "-z")) else if (!strcmp(arg, "-z"))

3
diff.h
View File

@ -146,6 +146,7 @@ struct diff_options {
int dirstat_permille; int dirstat_permille;
int setup; int setup;
int abbrev; int abbrev;
int ita_invisible_in_index;
/* white-space error highlighting */ /* white-space error highlighting */
#define WSEH_NEW 1 #define WSEH_NEW 1
#define WSEH_CONTEXT 2 #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 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 * Fill the contents of the filespec "df", respecting any textconv defined by

View File

@ -658,7 +658,7 @@ static int do_pick_commit(enum todo_command command, struct commit *commit,
unborn = get_sha1("HEAD", head); unborn = get_sha1("HEAD", head);
if (unborn) if (unborn)
hashcpy(head, EMPTY_TREE_SHA1_BIN); 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); return error_dirty_index(opts);
} }
discard_cache(); discard_cache();
@ -1312,7 +1312,7 @@ int sequencer_continue(struct replay_opts *opts)
if (res) if (res)
goto release_todo_list; goto release_todo_list;
} }
if (index_differs_from("HEAD", 0)) { if (index_differs_from("HEAD", 0, 0)) {
res = error_dirty_index(opts); res = error_dirty_index(opts);
goto release_todo_list; goto release_todo_list;
} }

View File

@ -5,10 +5,24 @@ test_description='Intent to add'
. ./test-lib.sh . ./test-lib.sh


test_expect_success 'intent to add' ' test_expect_success 'intent to add' '
test_commit 1 &&
git rm 1.t &&
echo hello >1.t &&
echo hello >file && echo hello >file &&
echo hello >elif && echo hello >elif &&
git add -N file && 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"' ' 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 add -N nitfol &&
git commit -m second && git commit -m second &&
test $(git ls-tree HEAD -- nitfol | wc -l) = 0 && 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' ' 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 test_done



View File

@ -246,8 +246,8 @@ test_expect_success 'verify --intent-to-add output' '
git add --intent-to-add intent1.add intent2.add && git add --intent-to-add intent1.add intent2.add &&


cat >expect <<-EOF && cat >expect <<-EOF &&
1 AM N... 000000 100644 100644 $_z40 $EMPTY_BLOB intent1.add 1 .A N... 000000 000000 100644 $_z40 $_z40 intent1.add
1 AM N... 000000 100644 100644 $_z40 $EMPTY_BLOB intent2.add 1 .A N... 000000 000000 100644 $_z40 $_z40 intent2.add
EOF EOF


git status --porcelain=v2 >actual && git status --porcelain=v2 >actual &&

View File

@ -438,7 +438,7 @@ static void wt_status_collect_changed_cb(struct diff_queue_struct *q,


switch (p->status) { switch (p->status) {
case DIFF_STATUS_ADDED: case DIFF_STATUS_ADDED:
die("BUG: worktree status add???"); d->mode_worktree = p->two->mode;
break; break;


case DIFF_STATUS_DELETED: 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); setup_revisions(0, NULL, &rev, NULL);
rev.diffopt.output_format |= DIFF_FORMAT_CALLBACK; rev.diffopt.output_format |= DIFF_FORMAT_CALLBACK;
DIFF_OPT_SET(&rev.diffopt, DIRTY_SUBMODULES); DIFF_OPT_SET(&rev.diffopt, DIRTY_SUBMODULES);
rev.diffopt.ita_invisible_in_index = 1;
if (!s->show_untracked_files) if (!s->show_untracked_files)
DIFF_OPT_SET(&rev.diffopt, IGNORE_UNTRACKED_IN_SUBMODULES); DIFF_OPT_SET(&rev.diffopt, IGNORE_UNTRACKED_IN_SUBMODULES);
if (s->ignore_submodule_arg) { 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); setup_revisions(0, NULL, &rev, &opt);


DIFF_OPT_SET(&rev.diffopt, OVERRIDE_SUBMODULE_CONFIG); DIFF_OPT_SET(&rev.diffopt, OVERRIDE_SUBMODULE_CONFIG);
rev.diffopt.ita_invisible_in_index = 1;
if (s->ignore_submodule_arg) { if (s->ignore_submodule_arg) {
handle_ignore_submodules_arg(&rev.diffopt, s->ignore_submodule_arg); handle_ignore_submodules_arg(&rev.diffopt, s->ignore_submodule_arg);
} else { } else {
@ -606,6 +608,8 @@ static void wt_status_collect_changes_initial(struct wt_status *s)


if (!ce_path_match(ce, &s->pathspec, NULL)) if (!ce_path_match(ce, &s->pathspec, NULL))
continue; continue;
if (ce_intent_to_add(ce))
continue;
it = string_list_insert(&s->change, ce->name); it = string_list_insert(&s->change, ce->name);
d = it->util; d = it->util;
if (!d) { if (!d) {
@ -912,6 +916,7 @@ static void wt_longstatus_print_verbose(struct wt_status *s)


init_revisions(&rev, NULL); init_revisions(&rev, NULL);
DIFF_OPT_SET(&rev.diffopt, ALLOW_TEXTCONV); DIFF_OPT_SET(&rev.diffopt, ALLOW_TEXTCONV);
rev.diffopt.ita_invisible_in_index = 1;


memset(&opt, 0, sizeof(opt)); memset(&opt, 0, sizeof(opt));
opt.def = s->is_initial ? EMPTY_TREE_SHA1_HEX : s->reference; opt.def = s->is_initial ? EMPTY_TREE_SHA1_HEX : s->reference;