我正在尝试使用单元测试的裸运行来执行预提交 Hook ,并且我想确保我的工作目录是干净的。编译需要很长时间,所以我想尽可能利用重用已编译的二进制文件。我的脚本遵循我在网上看到的例子:
# Stash changes
git stash -q --keep-index
# Run tests
...
# Restore changes
git stash pop -q
但这会导致问题。这是再现:
// Step 1
至 a.java
git add .
// Step 2
至 a.java
git commit
git stash -q --keep-index
# 存储更改 git stash pop -q
# 恢复更改 在这一点上,我遇到了问题。
git stash pop -q
显然有冲突并且在 a.java
我有// Step 1
<<<<<<< Updated upstream
=======
// Step 2
>>>>>>> Stashed changes
有没有办法让它干净利落地 pop ?
最佳答案
有 - 但让我们以稍微迂回的方式到达那里。 (另外,请参阅下面的警告:存储代码中存在一个我认为非常罕见的错误,但显然有更多人遇到了。)git stash save
( git stash
的默认操作)进行至少有两个父项的提交(请参阅 this answer 以了解有关 stash 的更基本问题)。 stash
commit 是工作树状态,第二个父提交 stash^2
是存储时的索引状态。
存储完成后(假设没有 -p
选项),脚本— git stash
是一个 shell 脚本——使用 git reset --hard
清除更改。
当您使用 --keep-index
,脚本不会以任何方式更改保存的存储。相反,在 git reset --hard
之后操作,脚本使用了一个额外的 git read-tree --reset -u
清除工作目录更改,将它们替换为存储的“索引”部分。
换句话说,这几乎就像在做:
git reset --hard stash^2
除了
git reset
也会移动分支——根本不是你想要的,因此 read-tree
方法代替。这是您的代码回来的地方。您现在
# Run tests
关于索引提交的内容。假设一切顺利,我想您想让索引恢复到执行
git stash
时的状态。 ,并使工作树也恢复到其状态。与
git stash apply
或 git stash pop
,这样做的方法是使用 --index
(不是 --keep-index
,这只是为了存储创建时间,告诉存储脚本“重击工作目录”)。只是使用
--index
但是仍然会失败,因为 --keep-index
将索引更改重新应用到工作目录。因此,您必须首先摆脱所有这些更改……为此,您只需要(重新)运行 git reset --hard
,就像 stash 脚本本身之前所做的那样。 (可能你也想要 -q
。)所以,这是最后一个
# Restore changes
步:# Restore changes
git reset --hard -q
git stash pop --index -q
(我将它们分开为:
git stash apply --index -q && git stash drop -q
我自己,只是为了清楚起见,但
pop
会做同样的事情)。如以下评论中所述,最终
git stash pop --index -q
如果初始 git stash save
会有点提示(或者更糟的是,恢复旧的 stash ) step 没有找到要保存的更改。因此,您应该通过测试来保护“恢复”步骤,以查看“保存”步骤是否确实 stash 了任何内容。初始
git stash --keep-index -q
当它什么都不做时只是安静地退出(状态为 0),所以我们需要处理两种情况:在保存之前或之后都不存在存储;并且,在保存之前存在一些存储,并且保存没有执行任何操作,因此旧的现有存储仍然是存储堆栈的顶部。我认为最简单的方法是使用
git rev-parse
找出什么refs/stash
名字,如果有的话。所以我们应该让脚本更像这样:#! /bin/sh
# script to run tests on what is to be committed
# First, stash index and work dir, keeping only the
# to-be-committed changes in the working directory.
old_stash=$(git rev-parse -q --verify refs/stash)
git stash save -q --keep-index
new_stash=$(git rev-parse -q --verify refs/stash)
# If there were no changes (e.g., `--amend` or `--allow-empty`)
# then nothing was stashed, and we should skip everything,
# including the tests themselves. (Presumably the tests passed
# on the previous commit, so there is no need to re-run them.)
if [ "$old_stash" = "$new_stash" ]; then
echo "pre-commit script: no changes to test"
sleep 1 # XXX hack, editor may erase message
exit 0
fi
# Run tests
status=...
# Restore changes
git reset --hard -q && git stash apply --index -q && git stash drop -q
# Exit with status from test-run: nonzero prevents commit
exit $status
警告:git stash 中的小错误
方式有一个小错误
git stash
写其 "stash bag" .索引状态存储是正确的,但假设您执行以下操作:cp foo.txt /tmp/save # save original version
sed -i '' -e '1s/^/inserted/' foo.txt # insert a change
git add foo.txt # record it in the index
cp /tmp/save foo.txt # then undo the change
当您运行时
git stash save
在此之后,索引提交( refs/stash^2
)在 foo.txt
中插入了文本.工作树提交( refs/stash
)的版本应该是 foo.txt
没有额外插入的东西。但是,如果您查看它,您会发现它具有错误的(索引修改的)版本。上面的脚本使用
--keep-index
将工作树设置为索引,这一切都很好,并且为运行测试做了正确的事情。运行测试后,它使用 git reset --hard
返回 HEAD
提交状态(仍然很好)......然后它使用 git stash apply --index
恢复索引(有效)和工作目录。这就是出错的地方。从存储索引提交(正确)恢复索引,但从存储工作目录提交恢复工作目录。此工作目录提交的版本为
foo.txt
那在索引中。换句话说,最后一步—— cp /tmp/save foo.txt
——那取消了改变,已经取消了!(
stash
脚本中的错误发生是因为该脚本将工作树状态与 HEAD
提交进行比较,以便在将特殊工作目录提交部分之前计算要记录在特殊临时索引中的文件集存储包。由于 foo.txt
相对于 HEAD
没有变化,因此无法将 git add
放入特殊临时索引。然后使用索引提交的版本 foo.txt
进行特殊的工作树提交。 . 修复非常简单,但没有人将其放入官方 git [yet?]。我并不是想鼓励人们修改他们的 git 版本,而是 here's the fix .)
关于git - 如何在预提交 Hook 中正确 git stash/pop 以获得干净的工作树进行测试?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/20479794/