Skip to content

Commit b02fdbc

Browse files
committed
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, 859b7f1 (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", between d426430 (pathspec: warn on empty strings as pathspec, 2016-06-22) and 9e4e8a6 (pathspec: die on empty strings as pathspec, 2017-06-06). I think 9e4e8a6, which happened long after 859b7f1 happened, was not careful enough to turn the empty string 859b7f1 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 <[email protected]>
1 parent 17083c7 commit b02fdbc

File tree

2 files changed

+182
-1
lines changed

2 files changed

+182
-1
lines changed

pathspec.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -609,7 +609,7 @@ void parse_pathspec(struct pathspec *pathspec,
609609
*/
610610
if (nr_exclude == n) {
611611
int plen = (!(flags & PATHSPEC_PREFER_CWD)) ? 0 : prefixlen;
612-
init_pathspec_item(item + n, 0, prefix, plen, "");
612+
init_pathspec_item(item + n, 0, prefix, plen, ".");
613613
pathspec->nr++;
614614
}
615615

t/t6132-pathspec-exclude.sh

Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,7 @@ test_expect_success 'multiple exclusions' '
195195
'
196196

197197
test_expect_success 't_e_i() exclude case #8' '
198+
test_when_finished "rm -fr case8" &&
198199
git init case8 &&
199200
(
200201
cd case8 &&
@@ -244,4 +245,184 @@ test_expect_success 'grep --untracked PATTERN :(exclude)*FILE' '
244245
test_cmp expect-grep actual-grep
245246
'
246247

248+
# Depending on the command, all negative pathspec needs to subtract
249+
# either from the full tree, or from the current directory.
250+
#
251+
# The sample tree checked out at this point has:
252+
# file
253+
# sub/file
254+
# sub/file2
255+
# sub/sub/file
256+
# sub/sub/sub/file
257+
# sub2/file
258+
#
259+
# but there may also be some cruft that interferes with "git clean"
260+
# and "git add" tests.
261+
262+
test_expect_success 'archive with all negative' '
263+
git reset --hard &&
264+
git clean -f &&
265+
git -C sub archive --format=tar HEAD -- ":!sub/" >archive &&
266+
"$TAR" tf archive >actual &&
267+
cat >expect <<-\EOF &&
268+
file
269+
file2
270+
EOF
271+
test_cmp expect actual
272+
'
273+
274+
test_expect_success 'add with all negative' '
275+
H=$(git rev-parse HEAD) &&
276+
git reset --hard $H &&
277+
git clean -f &&
278+
test_when_finished "git reset --hard $H" &&
279+
for path in file sub/file sub/sub/file sub2/file
280+
do
281+
echo smudge >>"$path" || return 1
282+
done &&
283+
git -C sub add -- ":!sub/" &&
284+
git diff --name-only --no-renames --cached >actual &&
285+
cat >expect <<-\EOF &&
286+
file
287+
sub/file
288+
sub2/file
289+
EOF
290+
test_cmp expect actual &&
291+
git diff --name-only --no-renames >actual &&
292+
echo sub/sub/file >expect &&
293+
test_cmp expect actual
294+
'
295+
296+
test_expect_success 'add -p with all negative' '
297+
H=$(git rev-parse HEAD) &&
298+
git reset --hard $H &&
299+
git clean -f &&
300+
test_when_finished "git reset --hard $H" &&
301+
for path in file sub/file sub/sub/file sub2/file
302+
do
303+
echo smudge >>"$path" || return 1
304+
done &&
305+
yes | git -C sub add -p -- ":!sub/" &&
306+
git diff --name-only --no-renames --cached >actual &&
307+
cat >expect <<-\EOF &&
308+
file
309+
sub/file
310+
sub2/file
311+
EOF
312+
test_cmp expect actual &&
313+
git diff --name-only --no-renames >actual &&
314+
echo sub/sub/file >expect &&
315+
test_cmp expect actual
316+
'
317+
318+
test_expect_success 'clean with all negative' '
319+
H=$(git rev-parse HEAD) &&
320+
git reset --hard $H &&
321+
test_when_finished "git reset --hard $H && git clean -f" &&
322+
git clean -f &&
323+
for path in file9 sub/file9 sub/sub/file9 sub2/file9
324+
do
325+
echo cruft >"$path" || return 1
326+
done &&
327+
git -C sub clean -f -- ":!sub" &&
328+
test_path_is_file file9 &&
329+
test_path_is_missing sub/file9 &&
330+
test_path_is_file sub/sub/file9 &&
331+
test_path_is_file sub2/file9
332+
'
333+
334+
test_expect_success 'commit with all negative' '
335+
H=$(git rev-parse HEAD) &&
336+
git reset --hard $H &&
337+
test_when_finished "git reset --hard $H" &&
338+
for path in file sub/file sub/sub/file sub2/file
339+
do
340+
echo smudge >>"$path" || return 1
341+
done &&
342+
git -C sub commit -m sample -- ":!sub/" &&
343+
git diff --name-only --no-renames HEAD^ HEAD >actual &&
344+
cat >expect <<-\EOF &&
345+
file
346+
sub/file
347+
sub2/file
348+
EOF
349+
test_cmp expect actual &&
350+
git diff --name-only --no-renames HEAD >actual &&
351+
echo sub/sub/file >expect &&
352+
test_cmp expect actual
353+
'
354+
355+
test_expect_success 'reset with all negative' '
356+
H=$(git rev-parse HEAD) &&
357+
git reset --hard $H &&
358+
test_when_finished "git reset --hard $H" &&
359+
for path in file sub/file sub/sub/file sub2/file
360+
do
361+
echo smudge >>"$path" &&
362+
git add "$path" || return 1
363+
done &&
364+
git -C sub reset --quiet -- ":!sub/" &&
365+
git diff --name-only --no-renames --cached >actual &&
366+
echo sub/sub/file >expect &&
367+
test_cmp expect actual
368+
'
369+
370+
test_expect_success 'grep with all negative' '
371+
H=$(git rev-parse HEAD) &&
372+
git reset --hard $H &&
373+
test_when_finished "git reset --hard $H" &&
374+
for path in file sub/file sub/sub/file sub2/file
375+
do
376+
echo "needle $path" >>"$path" || return 1
377+
done &&
378+
git -C sub grep -h needle -- ":!sub/" >actual &&
379+
cat >expect <<-\EOF &&
380+
needle sub/file
381+
EOF
382+
test_cmp expect actual
383+
'
384+
385+
test_expect_success 'ls-files with all negative' '
386+
git reset --hard &&
387+
git -C sub ls-files -- ":!sub/" >actual &&
388+
cat >expect <<-\EOF &&
389+
file
390+
file2
391+
EOF
392+
test_cmp expect actual
393+
'
394+
395+
test_expect_success 'rm with all negative' '
396+
git reset --hard &&
397+
test_when_finished "git reset --hard" &&
398+
git -C sub rm -r --cached -- ":!sub/" >actual &&
399+
git diff --name-only --no-renames --diff-filter=D --cached >actual &&
400+
cat >expect <<-\EOF &&
401+
sub/file
402+
sub/file2
403+
EOF
404+
test_cmp expect actual
405+
'
406+
407+
test_expect_success 'stash with all negative' '
408+
H=$(git rev-parse HEAD) &&
409+
git reset --hard $H &&
410+
test_when_finished "git reset --hard $H" &&
411+
for path in file sub/file sub/sub/file sub2/file
412+
do
413+
echo smudge >>"$path" || return 1
414+
done &&
415+
git -C sub stash push -m sample -- ":!sub/" &&
416+
git diff --name-only --no-renames HEAD >actual &&
417+
echo sub/sub/file >expect &&
418+
test_cmp expect actual &&
419+
git stash show --name-only >actual &&
420+
cat >expect <<-\EOF &&
421+
file
422+
sub/file
423+
sub2/file
424+
EOF
425+
test_cmp expect actual
426+
'
427+
247428
test_done

0 commit comments

Comments
 (0)