观察此脚本:
#!/bin/bash
# Initialize a git repo and make sure to clean old stuff up first.
rm -Rf repo1
mkdir repo1
cd repo1
git init &> /dev/null
# Create an initial commit so we're sure we're not observing behavior
# which only occurs in repos without commits.
touch README.md
git add README.md
git commit -m "Initial commit." &> /dev/null
# Get into a new folder to start messing around.
mkdir "some folder"
cd "some folder"
# Introduce and commit a file.
touch file1
git add file1
git commit -m "Add file1." &> /dev/null
# Remove the file just commited and introduce one with a different
# name.
rm file1
touch file2
# `git status` shows that 'file1' got deleted and 'file2' got added.
# Makes perfect sense, thus far.
git status
echo "##################################" # Just to separate outputs.
# Both the added and the deleted file match the pattern 'file*'.
# I therefore expect both the deletion of 'file1' and the introduction
# of 'file2' to be added to the index.
git add file*
# However, `git status` shows us that the deletion of 'file1' is not
# in the index.
git status
echo "##################################" # Just to separate outputs.
# Only if the pattern doesn't match any file existing in the working
# directory, does git add the deletion of 'file1' to the index.
git add *1
git status
echo "##################################" # Just to separate outputs.
在这里,我不知道git的行为是否有意义。这基本上是一个尽可能简单的实验室设置。在一个更复杂的情况下,今天让我感到很困惑。
git有这么好的理由吗?如果shell的路径名称扩展产生了文件,那么git就会以这种方式行事。"但是从用户的角度来论是一个理由。
如果没有这样的理由,说明这也是一个答案。
答案 0 :(得分:1)
这与在Git甚至有机会之前在shell中完成全局扩展(file*
)的事实有关。
在rm file1; touch file2
之后,shell会将file*
扩展为file2
并运行:
git add file2
因此没有添加file1
消失的事实。
如果你要从shell 保护 *
,那么 Git 会看到它:
git add 'file*'
然后 Git会自己进行扩展,并添加file1
消失的事实。但是这个glob星没有受到保护,所以shell会咀嚼它并吐出file2
作为单个匹配的文件名。
仅仅因为shell想要混淆,:-)后续git add *1
匹配 nothing ,而不是用任何东西替换*1
,shell保持原样。这会将*1
传递给Git,就像你引用它一样。 Git然后进行自己的扩展,现在添加了file1
已消失的事实。
并非所有shell都以这种方式运行。 Bash特别允许您控制结果:
bash$ echo 1 *asdf 2
1 *asdf 2
这是标准的默认行为:将失败的glob留作命令的参数。
bash$ shopt -s nullglob
bash$ echo 1 *asdf 2
1 2
这是明智的行为:*asdf
不匹配任何文件,因此它扩展为空。
bash$ shopt -u nullglob
bash$ shopt -s failglob
bash$ echo 1 *asdf 2
bash: no match: *asdf
这也是明智的行为:*asdf
不匹配任何文件,因此它会导致整个命令失败而根本不会运行。
其他一些shell也有类似的控件。例如,csh / tcsh默认为失败(明智),并允许您set nonomatch
在没有匹配时获得默认的POSIX-shell样式的传递行为。
作为所有这一点的一个相当消极的副作用--Git在之后做了自己的glob扩展这个事实已经完成了全局扩展 - 如果你有一个文件会发生奇怪的事情命名为 这种事情很少会引起问题,因为人们很少在文件名中使用shell glob字符 在类似但不那么棘手的情况下,Git对以冒号开头的pathspec进行了特殊处理,因此如果您有一个名为 因此,要编写文件名 对于我们正在使用的任何命令 所有都有效,但我敢说没有人会声称它很漂亮。 : - )*
:保护shell中的文件名将文本字符*
传递给Git,然后对其进行全局扩展,即使您不希望Git扩展它。 / p>
* [ ?
。但如果有人使用它们,一些命令会变得非常棘手。:x
的文件,则必须将其作为{{{G}传递给Git。 1}}或::x
。每当某个字符或序列具有特殊含义时,我们就会遇到这些语法与语义问题:例如,如果./:x
指令中的!
表示“不要忽略”,那么我们如何忽略名为.gitignore
的文件?编写良好的软件总是有一些逃生舱,但设计好的逃生舱可能很困难。 Git为glob字符设置的逃生舱口,为了避免将它们作为的 glob字符处理,是使用!gotcha!
为pathspec添加前缀。:(literal)
并确保既不 shell 也不 Git尝试进行全局扩展,我们可以使用:star*fish
git foo ':(literal)star*fish'
采用路径规范(例如foo
或git add
)。引号保护git rm
免受外壳攻击,然后*
保护:(literal)
免受Git攻击。