根据我的经验,git commit -a
的行为与git commit .
相同,但是最近我创建了一个pre-commit钩子,该钩子会自动格式化我的源代码,现在git commit .
有一些意外的缺点-效果:在提交命令完成后,提交的文件最终在工作目录和索引中进行了修改。 git commit -a
不会发生这种情况。我试图了解运行git commit .
时幕后发生的情况,这是导致这种情况发生的原因,并查看是否有办法在我的预提交钩子脚本中正确处理它。
预提交挂钩:
git_toplevel=$(git rev-parse --show-toplevel)
git --no-pager diff -z --cached --name-only --diff-filter=ACMRT | $git_toplevel/meta/reformat.bash -s files
git --no-pager diff -z --name-only --diff-filter=ACMRT | xargs -0 --no-run-if-empty git add
当前使用的是git 1.8.3.1版,但在最新版本中却看到了相同的行为。
以下是在行首添加一个简单空格的命令序列:
[]$ git status
# On branch eroller/format-clean-filter
# Your branch is ahead of 'origin/eroller/format-clean-filter' by 1 commit.
# (use "git push" to publish your local commits)
#
# Changes not staged for commit:
# (use "git add <file>..." to update what will be committed)
# (use "git checkout -- <file>..." to discard changes in working directory)
#
# modified: src/host/cnv/denovo/denovo_cnv.cpp
#
no changes added to commit (use "git add" and/or "git commit -a")
-
[]$ git diff
diff --git a/src/host/cnv/denovo/denovo_cnv.cpp b/src/host/cnv/denovo/denovo_cnv.cpp
index 7cfb8dc..14058e3 100644
--- a/src/host/cnv/denovo/denovo_cnv.cpp
+++ b/src/host/cnv/denovo/denovo_cnv.cpp
@@ -28,7 +28,7 @@ using namespace std;
namespace cnv {
namespace denovo {
-SegmentsBySample LoadCallsForSamples(const vector<string>& callFiles, const ReferenceDictionary& reference)
+ SegmentsBySample LoadCallsForSamples(const vector<string>& callFiles, const ReferenceDictionary& reference)
{
function<SegmentsBySample::value_type(const string&)> loadCalls = [&](string callFile) {
return LoadCalls(callFile, reference);
-
[]$ git commit -m 'test' .
-
[]$ git status
# On branch eroller/format-clean-filter
# Your branch is ahead of 'origin/eroller/format-clean-filter' by 2 commits.
# (use "git push" to publish your local commits)
#
# Changes to be committed:
# (use "git reset HEAD <file>..." to unstage)
#
# modified: src/host/cnv/denovo/denovo_cnv.cpp
#
# Changes not staged for commit:
# (use "git add <file>..." to update what will be committed)
# (use "git checkout -- <file>..." to discard changes in working directory)
#
# modified: src/host/cnv/denovo/denovo_cnv.cpp
#
-
[]$ git diff
diff --git a/src/host/cnv/denovo/denovo_cnv.cpp b/src/host/cnv/denovo/denovo_cnv.cpp
index 14058e3..7cfb8dc 100644
--- a/src/host/cnv/denovo/denovo_cnv.cpp
+++ b/src/host/cnv/denovo/denovo_cnv.cpp
@@ -28,7 +28,7 @@ using namespace std;
namespace cnv {
namespace denovo {
- SegmentsBySample LoadCallsForSamples(const vector<string>& callFiles, const ReferenceDictionary& reference)
+SegmentsBySample LoadCallsForSamples(const vector<string>& callFiles, const ReferenceDictionary& reference)
{
function<SegmentsBySample::value_type(const string&)> loadCalls = [&](string callFile) {
return LoadCalls(callFile, reference);
-
[]$ git diff --cached
diff --git a/src/host/cnv/denovo/denovo_cnv.cpp b/src/host/cnv/denovo/denovo_cnv.cpp
index 7cfb8dc..14058e3 100644
--- a/src/host/cnv/denovo/denovo_cnv.cpp
+++ b/src/host/cnv/denovo/denovo_cnv.cpp
@@ -28,7 +28,7 @@ using namespace std;
namespace cnv {
namespace denovo {
-SegmentsBySample LoadCallsForSamples(const vector<string>& callFiles, const ReferenceDictionary& reference)
+ SegmentsBySample LoadCallsForSamples(const vector<string>& callFiles, const ReferenceDictionary& reference)
{
function<SegmentsBySample::value_type(const string&)> loadCalls = [&](string callFile) {
return LoadCalls(callFile, reference);
更新:使用@torek的非常详尽的答案(谢谢!),如果用户尝试使用git commit .
或git commit [--only] -- <files>
,我决定在预提交挂钩中给出一个错误。这是我的预提交脚本中的检查内容:
if [[ $GIT_INDEX_FILE != *"/index" ]] && [[ $GIT_INDEX_FILE != *"/index.lock" ]] ; then
echo "Error: pre-commit reformatting using unsupported index file ($GIT_INDEX_FILE)." >&2
echo " Are you using 'git commit [--only] -- <files>' to bypass staging?" >&2
echo " Use git commit -a or stage your files before committing using git add -- <files>" >&2
echo " Use '--no-verify' to bypass reformatting (not recommended)" >&2
exit 1
fi
答案 0 :(得分:4)
这里的根本问题是,Git不是从工作树而是从索引进行提交,这就是为什么首先需要git add
文件,而要在 the 索引进行提交的原因是一种白色谎言,因为与标准的谎言相比,可能有更多的索引文件。 (该索引也称为临时区域或缓存,具体取决于Git的哪个部分正在执行调用。)
索引(我的意思是一个标准索引)是.git
中名为index
的文件。如果检查您的.git
目录,则会找到这样的文件。过去,实际上只有一个文件。在现代Git(2.5或更高版本)中,由于添加了工作树,因此画面变得更加阴云密布:每个工作树实际上只有一个索引文件,因此.git/index
只是 the 索引 main 工作树。每个工作树都有一个辅助的 the 索引-但这并不是我要表达的意思,在这里,这只是一个例子,它显示了一个单一索引的假设已经在磨损边缘。诚然,您使用的是Git 1.8.3.1(确实很旧),但它也比简单的白色“ one index”设置更为复杂。
使用git commit -a
时,Git会创建一个新的额外索引。当您使用git commit .
时,您正在调用git commit --only .
(see the documentation for details),而Git会创建两个新的额外索引(索引?)。
Git的所有部分都能够重定向Git的 rest 以使用不同的非标准索引,并且git commit
的各种选项都使用了此功能。请注意,git commit -a
等效于git commit --include
,后跟需要添加的任何文件的名称。真正棘手的情况是您正在使用的git commit --only
。
一旦开始增加索引文件,事情就会变得混乱!
请记住,索引实质上是建议的下一次提交。如果只有一个索引(对于这个工作树,如果我们正在谈论Git 2.5或更高版本),那么只有一个提议的下一次提交。这不是太困难,我们只需要考虑每个文件有三个副本。让我们选择一个文件,例如README.md
:
HEAD:README.md
是README.md
的当前提交版本。您无法更改。 (您可以移动HEAD
本身,但是README.md
的已提交副本位于提交中,如提交的哈希ID所示,并且不会更改。)
名称HEAD:README.md
仅在Git内部有效。该名称访问该文件的冻结的,经过Git验证的,冻结干燥的副本;此副本将永远不会改变。例如,您可以在git show HEAD:README.md
上看到它。
:README.md
是索引中README.md
的副本。它原本与HEAD:README.md
相同,但是如果您运行git add README.md
,则现在可能会有所不同。
名称:README.md
也仅在Git内部有效。该名称将访问该可替换但已Git格式(冻结干燥格式)的文件副本,该副本存储在索引中。您可以随时用git add
替换。
最后,README.md
是普通的(非Git格式)文件。它不在Git中!它不在索引中!它位于您的工作树中,您可以在其中使用所有常规计算机工具进行查看和操作。 Git确实不使用该文件,它只是覆盖它或在您签出其他提交时将其删除。除了使用git status
进行检查之外,Git唯一要做的就是让您使用git add
将其复制到索引中,覆盖之前的内容(并在此过程中将其冷冻干燥)。
运行git status
会运行两个git diff
:
第一个将HEAD
提交与索引进行比较,即当前提交中的内容与建议的下一个提交中的内容。此处所有不同都被列为暂存提交。一切都一样,Git安静地什么也没说。
第二个git diff
将索引与工作树进行比较,即提议的提交中包含什么,以及可以复制到索引中的内容。这里所有不同都被列为未上演提交的。同样,再次,Git悄无声息。
(然后,最后一步是检查工作树中根本不在索引中的文件。Git会抱怨这些,说它们未被跟踪,除非您将它们列出在{ {1}}。在.gitignore
中列出并不会改变索引中是否存在文件的副本,而只会改变Git是否发牢骚。)
运行.gitignore
时,Git打包索引中的所有内容,并使用它来进行新的提交... 除非,否则您使用git commit
,{{ 1}}或--only
。
使用--include
,Git生成三个索引文件:
-a
。git commit --only
个文件.git/index
。有时它在--only
中。 也许它总是在这里!如果是这样,那将提供一种处理我在下面概述的情况的方法。但是没有文档可以保证这一点。git add
,然后.git/index.lock
将HEAD
文件提取到其中。如果您在运行git add
之前没有--only
{em> ,则第一个和第三个索引文件会匹配,因为将git add
文件添加到常规索引中与从git commit -a
创建新的临时索引并向其中添加--only
文件具有相同的效果。但是否则所有这三个文件可能都不同!
Git从 third 索引进行新的提交。如果新的提交成功,则Git用 second 索引替换常规索引(此替换通过HEAD
系统调用进行)。否则,Git返回正常索引。 (请注意,工作树什么都没有发生。)
如果您使用--only
或rename
,则Git仅使一个额外索引,因此您具有:
git commit --include
中的标准索引,以及到目前为止添加的所有内容;和然后Git启动提交过程。如果一切顺利,完成Git后,Git重命名临时索引,使其成为标准索引。如果情况不佳,Git会删除临时索引,而标准索引保持不变。同样,工作树没有任何反应。
Git在准备任何额外的索引文件后运行您的预提交钩子。特殊环境变量git commit -a
命名Git用于进行新提交的索引。因此,存在三种情况,其中两种情况还不错,而另一种则很糟糕:
.git/index
命名普通索引,一切正常。$GIT_INDEX_FILE
或GIT_INDEX_FILE
,并且git commit --include
命名第二个索引;没有第三索引;如果提交完成,Git将重命名第二个索引。git commit -a
和GIT_INDEX_FILE
命名第三个索引。没有简单的方法来查找第二个索引,如果提交成功,则该第二个索引将在提交后就位!如果您选择对索引中存储的文件进行更改,您的工作就是将它们更改为Git将用于提交的索引。为此,可以根据需要使用git commit --only
,因为这会将文件从工作树复制到GIT_INDEX_FILE
中命名的索引。
第一个问题是,一定不要查看工作树中的文件。他们无关紧要!它们可能包含与索引中完全不同的内容。在git add
期间尤其如此。
第二个更大的问题是,如果您更新了$GIT_INDEX_FILE
使用的第三索引,则还应该更新{ {1}}正在使用。这部分很棘手,因为除了假定它位于git commit --only
中之外,没有其他简单的方法可以找到它。尽管这可能行得通,但我在这里不建议这样做。
对此我真的没有任何建议-您发现的任何偷偷摸摸的方法都可能会破坏,因为处理该第三个索引的代码(当前的2.21-ish Git称为“假索引”)已在1.8到2000之间变化了很多。现代Git。通常的最佳实践建议是 not 根本不在Git挂钩中进行任何特殊格式化。相反,让Git钩子只检查是否文件的索引副本的格式正确:如果是,则继续提交,否则,放弃提交。其余的留给用户。
我已经看到和使用的一种替代方法是检查git commit --only
的实际设置。如果将其设置为git commit --only
,则用户正在使用.git/index.lock
,而没有任何特殊设置。 this same pre-commit hook(调用clang-format和autopep8)的另一个技巧是比较将被格式化的文件的索引和工作树,并在它们不匹配时拒绝运行。