Reputation: 76887
I was trying out some sample instructions of git and came across this peculiar case that when we do a git rm *
, it doesn't delete the .*
files in the first attempt.
Why is it so?
mkdir -p test_repo1/folder && cd test_repo1
touch .testfile1 .testfile2 testfile3 folder/testfile4 folder/.testfile5
git init && git add . && git commit -m "test commit"
If now I do a git rm
as follows, I have to do it a second time to remove all the files
$ git rm -r *
rm 'folder/.testfile5'
rm 'folder/testfile4'
rm 'testfile3'
$ git rm -r *
rm '.testfile1'
rm '.testfile2'
Why doesn't git remove all files in the first attempt itself? Also, why is this happening for files withing the repo root only?
Interestingly, if all I have are those .testfiles
, then git removes them in the first attempt itself.
I am using git version 1.7.9.5
Upvotes: 13
Views: 38902
Reputation: 1324268
Note that the right syntax should be git rm -r .
('dot')
More worrying is git rm -r
(empty pathspec string) which does the same until Git 2.11!
See commit d426430 (22 Jun 2016) by Emily Xie (emilyxxie
).
(Merged by Junio C Hamano -- gitster
-- in commit 3b1e135, 26 Oct 2016)
pathspec
: warn on empty strings as pathspec
An empty string as a pathspec element matches all paths.
A buggy script, however, could accidentally assign an empty string to a variable that then gets passed to a Git command invocation, e.g.:
path=... compute a path to be removed in $path ...
git rm -r "$paht"
which would unintentionally remove all paths in the current directory.
The fix for this issue requires a two-step approach.
- As there may be existing scripts that knowingly use empty strings in this manner, the first step simply gives a warning that (1) tells that an empty string will become an invalid pathspec element and (2) asks the user to use "
.
" if they mean to match all.
- For step two, a follow-up patch several release cycles later will remove the warning and throw an error instead.
Update for Git 2.15.x/2.16 (Q1 2018):
The message "will be made invalid in upcoming releases" disappear, and becomes:
empty string is not a valid pathspec.
please use . instead if you meant to match all paths
See commit 9e4e8a6 (07 Jun 2017) by Emily Xie (emilyxxie
).
See commit 229a95a (23 Jun 2017) by Junio C Hamano (gitster
).
(Merged by Junio C Hamano -- gitster
-- in commit 728c573, 06 Nov 2017)
An empty string as a pathspec element matches all paths.
A buggy script, however, could accidentally assign an empty string to a variable that then gets passed to a Git command invocation, e.g.:
path=... compute a path to be removed in $path ...
git rm -r "$path"
which would unintentionally remove all paths in the current directory.
A git subcommand like "git add -p
"(man) spawns a separate git process while relaying its command line arguments.
A pathspec with only negative elements was mistakenly passed with an empty string, which has been corrected with Git 2.37 (Q3 2022).
See commit b02fdbc (29 May 2022) by Junio C Hamano (gitster
).
(Merged by Junio C Hamano -- gitster
-- in commit f008095, 07 Jun 2022)
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, Git v2.13.0-rc0 -- merge listed in batch #1) 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, Git v2.11.0-rc0 -- merge listed in batch #11) and 9e4e8a6 ("pathspec
: die on empty strings as pathspec", 2017-06-06, Git v2.16.0-rc0 -- merge listed in batch #1).
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.
Upvotes: 4
Reputation: 189387
The wildcard gets expanded by your shell, and the expansion does not include dot files, by default, in most shells.
So by the time git
executes, the first command has become
git rm -r folder testfile3
and the second probably a literal
git rm -r *
which git
then expands by itself.
As remarked by Keith, to remove everything in one go, prevent the shell from expanding the wildcard so that git
does the expansion the first time already. This can be done with double or single quotes, or with a backslash before the asterisk. I tend to prefer single quotes:
git rm -r '*'
Upvotes: 28
Reputation: 84343
When you run git rm *
you are actually using the shell's globbing rules to pass arguments to git-rm(1). Many shells don't include hidden files (e.g. files with leading dots) by default. In Bash, you can change the globbing rules with dotglob and GLOBIGNORE. For example:
Using the shopt builtin:
# set dotglob
shopt -s dotglob
git rm *
# unset dotglob
shopt -u dotglob
Using the GLOBIGNORE variable (which also sets dotglob):
# Run git within a modified environment that includes GLOBIGNORE.
GLOBIGNORE='.git' git rm *
Upvotes: 6
Reputation: 488183
As user1281385 noted in a comment, the shell expands *
the first time around.
The reason the second time removes the dot-files is that once there are no files to match, the shell leaves the literal asterisk as an argument (depending on your shell anyway, at least one variant errors out instead) and git then does its own matching.
Upvotes: 4