当工作目录中的两个文件只与索引匹配时,git的模式匹配

时间:2017-08-07 00:30:16

标签: git

观察此脚本:

#!/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就会以这种方式行事。"但是从用户的角度来论是一个理由。

如果没有这样的理由,说明这也是一个答案。

1 个答案:

答案 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样式的传递行为。

Shell加Git等于错误

作为所有这一点的一个相当消极的副作用--Git在之后做了自己的glob扩展这个事实已经完成了全局扩展 - 如果你有一个文件会发生奇怪的事情命名为*:保护shell中的文件名将文本字符*传递给Git,然后对其进行全局扩展,即使您不希望Git扩展它。 / p>

这种事情很少会引起问题,因为人们很少在文件名中使用shell glob字符* [ ?。但如果有人使用它们,一些命令会变得非常棘手。

在类似但不那么棘手的情况下,Git对以冒号开头的pathspec进行了特殊处理,因此如果您有一个名为:x的文件,则必须将其作为{{{G}传递给Git。 1}}或::x。每当某个字符或序列具有特殊含义时,我们就会遇到这些语法与语义问题:例如,如果./:x指令中的!表示“不要忽略”,那么我们如何忽略名为.gitignore的文件?编写良好的软件总是有一些逃生舱,但设计好的逃生舱可能很困难。 Git为glob字符设置的逃生舱口,为了避免将它们作为 glob字符处理,是使用!gotcha!为pathspec添加前缀。

因此,要编写文件名:(literal)并确保既不 shell 也不 Git尝试进行全局扩展,我们可以使用:

star*fish

对于我们正在使用的任何命令git foo ':(literal)star*fish' 采用路径规范(例如foogit add)。引号保护git rm免受外壳攻击,然后*保护:(literal)免受Git攻击。

所有都有效,但我敢说没有人会声称它很漂亮。 : - )