pathspec: correct an empty string used as a pathspec element
Pathspecs with only negative elements did not work with some commands that pass the pathspec along to a subprocess. For instance, $ git add -p -- ':!*.txt' should add everything except for paths ending in ".txt", but it gets complaint from underlying "diff-index" and aborts. We used to error out when a pathspec with only negative elements in it, like the one in the above example. Later,859b7f1d
(pathspec: don't error out on all-exclusionary pathspec patterns, 2017-02-07) updated the logic to add an empty string as an extra element. The intention was to let the extra element to match everything and let the negative ones given by the user to subtract from it. At around the same time, we were migrating from "an empty string is a valid pathspec element that matches everything" to "either a dot or ":/" is used to match all, and an empty string is rejected", betweend426430e
(pathspec: warn on empty strings as pathspec, 2016-06-22) and9e4e8a64
(pathspec: die on empty strings as pathspec, 2017-06-06). I think9e4e8a64
, which happened long after859b7f1d
happened, was not careful enough to turn the empty string859b7f1d
added to either a dot or ":/". A care should be taken as the definition of "everything" depends on subcommand. For the purpose of "add -p", adding a "." to add everything in the current directory is the right thing to do. But for some other commands, ":/" (i.e. really really everything, even things outside the current subdirectory) is the right choice. We would break commands in a big way if we get this wrong, so add a handful of test pieces to make sure the resulting code still excludes the paths that are expected and includes "everything" else. Signed-off-by: Junio C Hamano <gitster@pobox.com>
This commit is contained in:
parent
17083c79ae
commit
b02fdbc80a
@ -609,7 +609,7 @@ void parse_pathspec(struct pathspec *pathspec,
|
|||||||
*/
|
*/
|
||||||
if (nr_exclude == n) {
|
if (nr_exclude == n) {
|
||||||
int plen = (!(flags & PATHSPEC_PREFER_CWD)) ? 0 : prefixlen;
|
int plen = (!(flags & PATHSPEC_PREFER_CWD)) ? 0 : prefixlen;
|
||||||
init_pathspec_item(item + n, 0, prefix, plen, "");
|
init_pathspec_item(item + n, 0, prefix, plen, ".");
|
||||||
pathspec->nr++;
|
pathspec->nr++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -195,6 +195,7 @@ test_expect_success 'multiple exclusions' '
|
|||||||
'
|
'
|
||||||
|
|
||||||
test_expect_success 't_e_i() exclude case #8' '
|
test_expect_success 't_e_i() exclude case #8' '
|
||||||
|
test_when_finished "rm -fr case8" &&
|
||||||
git init case8 &&
|
git init case8 &&
|
||||||
(
|
(
|
||||||
cd case8 &&
|
cd case8 &&
|
||||||
@ -244,4 +245,184 @@ test_expect_success 'grep --untracked PATTERN :(exclude)*FILE' '
|
|||||||
test_cmp expect-grep actual-grep
|
test_cmp expect-grep actual-grep
|
||||||
'
|
'
|
||||||
|
|
||||||
|
# Depending on the command, all negative pathspec needs to subtract
|
||||||
|
# either from the full tree, or from the current directory.
|
||||||
|
#
|
||||||
|
# The sample tree checked out at this point has:
|
||||||
|
# file
|
||||||
|
# sub/file
|
||||||
|
# sub/file2
|
||||||
|
# sub/sub/file
|
||||||
|
# sub/sub/sub/file
|
||||||
|
# sub2/file
|
||||||
|
#
|
||||||
|
# but there may also be some cruft that interferes with "git clean"
|
||||||
|
# and "git add" tests.
|
||||||
|
|
||||||
|
test_expect_success 'archive with all negative' '
|
||||||
|
git reset --hard &&
|
||||||
|
git clean -f &&
|
||||||
|
git -C sub archive --format=tar HEAD -- ":!sub/" >archive &&
|
||||||
|
"$TAR" tf archive >actual &&
|
||||||
|
cat >expect <<-\EOF &&
|
||||||
|
file
|
||||||
|
file2
|
||||||
|
EOF
|
||||||
|
test_cmp expect actual
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'add with all negative' '
|
||||||
|
H=$(git rev-parse HEAD) &&
|
||||||
|
git reset --hard $H &&
|
||||||
|
git clean -f &&
|
||||||
|
test_when_finished "git reset --hard $H" &&
|
||||||
|
for path in file sub/file sub/sub/file sub2/file
|
||||||
|
do
|
||||||
|
echo smudge >>"$path" || return 1
|
||||||
|
done &&
|
||||||
|
git -C sub add -- ":!sub/" &&
|
||||||
|
git diff --name-only --no-renames --cached >actual &&
|
||||||
|
cat >expect <<-\EOF &&
|
||||||
|
file
|
||||||
|
sub/file
|
||||||
|
sub2/file
|
||||||
|
EOF
|
||||||
|
test_cmp expect actual &&
|
||||||
|
git diff --name-only --no-renames >actual &&
|
||||||
|
echo sub/sub/file >expect &&
|
||||||
|
test_cmp expect actual
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'add -p with all negative' '
|
||||||
|
H=$(git rev-parse HEAD) &&
|
||||||
|
git reset --hard $H &&
|
||||||
|
git clean -f &&
|
||||||
|
test_when_finished "git reset --hard $H" &&
|
||||||
|
for path in file sub/file sub/sub/file sub2/file
|
||||||
|
do
|
||||||
|
echo smudge >>"$path" || return 1
|
||||||
|
done &&
|
||||||
|
yes | git -C sub add -p -- ":!sub/" &&
|
||||||
|
git diff --name-only --no-renames --cached >actual &&
|
||||||
|
cat >expect <<-\EOF &&
|
||||||
|
file
|
||||||
|
sub/file
|
||||||
|
sub2/file
|
||||||
|
EOF
|
||||||
|
test_cmp expect actual &&
|
||||||
|
git diff --name-only --no-renames >actual &&
|
||||||
|
echo sub/sub/file >expect &&
|
||||||
|
test_cmp expect actual
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'clean with all negative' '
|
||||||
|
H=$(git rev-parse HEAD) &&
|
||||||
|
git reset --hard $H &&
|
||||||
|
test_when_finished "git reset --hard $H && git clean -f" &&
|
||||||
|
git clean -f &&
|
||||||
|
for path in file9 sub/file9 sub/sub/file9 sub2/file9
|
||||||
|
do
|
||||||
|
echo cruft >"$path" || return 1
|
||||||
|
done &&
|
||||||
|
git -C sub clean -f -- ":!sub" &&
|
||||||
|
test_path_is_file file9 &&
|
||||||
|
test_path_is_missing sub/file9 &&
|
||||||
|
test_path_is_file sub/sub/file9 &&
|
||||||
|
test_path_is_file sub2/file9
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'commit with all negative' '
|
||||||
|
H=$(git rev-parse HEAD) &&
|
||||||
|
git reset --hard $H &&
|
||||||
|
test_when_finished "git reset --hard $H" &&
|
||||||
|
for path in file sub/file sub/sub/file sub2/file
|
||||||
|
do
|
||||||
|
echo smudge >>"$path" || return 1
|
||||||
|
done &&
|
||||||
|
git -C sub commit -m sample -- ":!sub/" &&
|
||||||
|
git diff --name-only --no-renames HEAD^ HEAD >actual &&
|
||||||
|
cat >expect <<-\EOF &&
|
||||||
|
file
|
||||||
|
sub/file
|
||||||
|
sub2/file
|
||||||
|
EOF
|
||||||
|
test_cmp expect actual &&
|
||||||
|
git diff --name-only --no-renames HEAD >actual &&
|
||||||
|
echo sub/sub/file >expect &&
|
||||||
|
test_cmp expect actual
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'reset with all negative' '
|
||||||
|
H=$(git rev-parse HEAD) &&
|
||||||
|
git reset --hard $H &&
|
||||||
|
test_when_finished "git reset --hard $H" &&
|
||||||
|
for path in file sub/file sub/sub/file sub2/file
|
||||||
|
do
|
||||||
|
echo smudge >>"$path" &&
|
||||||
|
git add "$path" || return 1
|
||||||
|
done &&
|
||||||
|
git -C sub reset --quiet -- ":!sub/" &&
|
||||||
|
git diff --name-only --no-renames --cached >actual &&
|
||||||
|
echo sub/sub/file >expect &&
|
||||||
|
test_cmp expect actual
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'grep with all negative' '
|
||||||
|
H=$(git rev-parse HEAD) &&
|
||||||
|
git reset --hard $H &&
|
||||||
|
test_when_finished "git reset --hard $H" &&
|
||||||
|
for path in file sub/file sub/sub/file sub2/file
|
||||||
|
do
|
||||||
|
echo "needle $path" >>"$path" || return 1
|
||||||
|
done &&
|
||||||
|
git -C sub grep -h needle -- ":!sub/" >actual &&
|
||||||
|
cat >expect <<-\EOF &&
|
||||||
|
needle sub/file
|
||||||
|
EOF
|
||||||
|
test_cmp expect actual
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'ls-files with all negative' '
|
||||||
|
git reset --hard &&
|
||||||
|
git -C sub ls-files -- ":!sub/" >actual &&
|
||||||
|
cat >expect <<-\EOF &&
|
||||||
|
file
|
||||||
|
file2
|
||||||
|
EOF
|
||||||
|
test_cmp expect actual
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'rm with all negative' '
|
||||||
|
git reset --hard &&
|
||||||
|
test_when_finished "git reset --hard" &&
|
||||||
|
git -C sub rm -r --cached -- ":!sub/" >actual &&
|
||||||
|
git diff --name-only --no-renames --diff-filter=D --cached >actual &&
|
||||||
|
cat >expect <<-\EOF &&
|
||||||
|
sub/file
|
||||||
|
sub/file2
|
||||||
|
EOF
|
||||||
|
test_cmp expect actual
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'stash with all negative' '
|
||||||
|
H=$(git rev-parse HEAD) &&
|
||||||
|
git reset --hard $H &&
|
||||||
|
test_when_finished "git reset --hard $H" &&
|
||||||
|
for path in file sub/file sub/sub/file sub2/file
|
||||||
|
do
|
||||||
|
echo smudge >>"$path" || return 1
|
||||||
|
done &&
|
||||||
|
git -C sub stash push -m sample -- ":!sub/" &&
|
||||||
|
git diff --name-only --no-renames HEAD >actual &&
|
||||||
|
echo sub/sub/file >expect &&
|
||||||
|
test_cmp expect actual &&
|
||||||
|
git stash show --name-only >actual &&
|
||||||
|
cat >expect <<-\EOF &&
|
||||||
|
file
|
||||||
|
sub/file
|
||||||
|
sub2/file
|
||||||
|
EOF
|
||||||
|
test_cmp expect actual
|
||||||
|
'
|
||||||
|
|
||||||
test_done
|
test_done
|
||||||
|
Loading…
Reference in New Issue
Block a user