如何在预提交挂钩中正确 git stash/pop 以获得干净的工作树进行测试? [英] How do I properly git stash/pop in pre-commit hooks to get a clean working tree for tests?

查看:21
本文介绍了如何在预提交挂钩中正确 git stash/pop 以获得干净的工作树进行测试?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试使用单元测试的裸运行来执行预提交挂钩,并且我想确保我的工作目录是干净的.编译需要很长时间,所以我想尽可能利用重用已编译的二进制文件.我的脚本遵循我在网上看到的示例:

I'm trying to do a pre-commit hook with a bare run of unit tests and I want to make sure my working directory is clean. Compiling takes a long time so I want to take advantage of reusing compiled binaries whenever possible. My script follows examples I've seen online:

# Stash changes
git stash -q --keep-index

# Run tests
...

# Restore changes
git stash pop -q

但这会导致问题.这是重现:

This causes problems though. Here's the repro:

  1. //Step 1添加到a.java
  2. git add .
  3. //Step 2添加到a.java
  4. git commit
  1. git stash -q --keep-index # 存储更改
  2. 运行测试
  3. git stash pop -q # 恢复更改
  1. git stash -q --keep-index # Stash changes
  2. Run tests
  3. git stash pop -q # Restore changes

此时我遇到了问题.git stash pop -q 显然有冲突,在 a.java 我有

At this point I hit the problem. The git stash pop -q apparently has a conflict and in a.java I have

// Step 1
<<<<<<< Updated upstream
=======
// Step 2
>>>>>>> Stashed changes

有没有办法让它干净利落地弹出?

Is there a way to get this to pop cleanly?

推荐答案

有——但让我们以一种稍微迂回的方式到达那里.(另外,请参阅下面的警告:存储代码中存在一个我认为非常罕见的错误,但显然有更多人遇到了.)

There is—but let's get there in a slightly roundabout fashion. (Also, see warning below: there's a bug in the stash code which I thought was very rare, but apparently more people are running into.)

git stash save(git stash 的默认操作)进行至少有两个父级的提交(参见 这个答案 到一个关于 stash 的更基本的问题).stash 提交是工作树状态,第二个父提交 stash^2 是存储时的索引状态.

git stash save (the default action for git stash) makes a commit that has at least two parents (see this answer to a more basic question about stashes). The stash commit is the work-tree state, and the second parent commit stash^2 is the index-state at the time of the stash.

在进行 stash 之后(假设没有 -p 选项),脚本——git stash 是一个 shell 脚本——使用 git reset --hard 清除更改.

After the stash is made (and assuming no -p option), the script—git stash is a shell script—uses git reset --hard to clean out the changes.

当您使用 --keep-index 时,脚本不会以任何方式更改保存的存储.相反,在 git reset --hard 操作之后,脚本使用额外的 git read-tree --reset -u 来清除工作目录更改,替换它们与存储的索引"部分.

When you use --keep-index, the script does not change the saved stash in any way. Instead, after the git reset --hard operation, the script uses an extra git read-tree --reset -u to wipe out the work-directory changes, replacing them with the "index" part of the stash.

换句话说,这几乎就像在做:

In other words, it's almost like doing:

git reset --hard stash^2

除了 git reset 也会移动分支——根本不是你想要的,因此 read-tree 方法代替.

except that git reset would also move the branch—not at all what you want, hence the read-tree method instead.

这是您的代码回来的地方.您现在对索引提交的内容#Run tests.

This is where your code comes back in. You now # Run tests on the contents of the index commit.

假设一切顺利,我假设您希望将索引恢复到执行 git stash 时的状态,并将工作树恢复到其状态.

Assuming all goes well, I presume you want to get the index back into the state it had when you did the git stash, and get the work-tree back into its state as well.

使用git stash applygit stash pop,这样做的方法是使用--index(不是--keep-index,这只是为了创建 stash 时间,告诉 stash 脚本重击工作目录").

With git stash apply or git stash pop, the way to do that is to use --index (not --keep-index, that's just for stash-creation time, to tell the stash script "whack on the work directory").

只是使用 --index 仍然会失败,因为 --keep-index 将索引更改重新应用到工作目录.因此,您必须首先摆脱所有这些更改……为此,您只需(重新)运行 git reset --hard,就像之前的 stash 脚本本身所做的那样.(可能你也想要 -q.)

Just using --index will still fail though, because --keep-index re-applied the index changes to the work directory. So you must first get rid of all of those changes ... and to do that, you simply need to (re)run git reset --hard, just like the stash script itself did earlier. (Probably you also want -q.)

所以,这是最后一个 #Restore changes 步骤:

So, this gives as the last # Restore changes step:

# Restore changes
git reset --hard -q
git stash pop --index -q

(我会将它们分开为:

git stash apply --index -q && git stash drop -q

我自己,只是为了清楚起见,但是 pop 会做同样的事情).

myself, just for clarity, but the pop will do the same thing).

正如下面的评论中所指出的,如果初始的 git stash pop --index -qgit stash save 步骤没有找到要保存的更改.因此,您应该通过测试来保护恢复"步骤,以查看保存"步骤是否确实隐藏了任何内容.

As noted in a comment below, the final git stash pop --index -q complains a bit (or, worse, restores an old stash) if the initial git stash save step finds no changes to save. You should therefore protect the "restore" step with a test to see if the "save" step actually stashed anything.

最初的 git stash --keep-index -q 在什么都不做的时候只是安静地退出(状态为 0),所以我们需要处理两种情况:在之前或之后不存在 stash节省;并且,在保存之前存在一些存储,而保存什么也没做,所以旧的现有存储仍然是存储堆栈的顶部.

The initial git stash --keep-index -q simply exits quietly (with status 0) when it does nothing, so we need to handle two cases: no stash exists either before or after the save; and, some stash existed before the save, and the save did nothing so the old existing stash is still the top of the stash stack.

我认为最简单的方法是使用 git rev-parse 找出 refs/stash 的名称,如果有的话.所以我们应该让脚本更像这样:

I think the simplest method is to use git rev-parse to find out what refs/stash names, if anything. So we should have the script read something more like this:

#! /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"的方式存在一个小错误.索引状态存储是正确的,但假设您执行以下操作:


warning: small bug in git stash

There's a minor bug in the way git stash writes its "stash bag". The index-state stash is correct, but suppose you do something like this:

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 的版本,没有额外插入的东西.但是,如果您查看它,您会发现它有错误的(索引修改的)版本.

When you run git stash save after this, the index-commit (refs/stash^2) has the inserted text in foo.txt. The work-tree commit (refs/stash) should have the version of foo.txt without the extra inserted stuff. If you look at it, though, you'll see it has the wrong (index-modified) version.

上面的脚本使用 --keep-index 将工作树设置为索引,这一切都很好,并且为运行测试做了正确的事情.运行测试后,它使用 git reset --hard 返回到 HEAD 提交状态(仍然很好)......然后它使用 git stash apply --index 恢复索引(有效)和工作目录.

The script above uses --keep-index to get the working tree set up as the index was, which is all perfectly fine and does the right thing for running the tests. After running the tests, it uses git reset --hard to go back to the HEAD commit state (which is still perfectly fine) ... and then it uses git stash apply --index to restore the index (which works) and the work directory.

这就是出错的地方.索引是(正确)从存储索引提交中恢复的,但工作目录是从存储工作目录提交中恢复的.此工作目录提交具有索引中的 foo.txt 版本.换句话说,最后一步——cp/tmp/save foo.txt——取消了更改,已经取消了!

This is where it goes wrong. The index is (correctly) restored from the stash index commit, but the work-directory is restored from the stash work-directory commit. This work-directory commit has the version of foo.txt that's in the index. In other words, that last step—cp /tmp/save foo.txt—that undid the change, has been un-un-done!

(stash 脚本中的错误发生是因为脚本将工作树状态与 HEAD 提交进行比较,以计算要记录在在使特殊的工作目录提交 stash-bag 的一部分之前的特殊临时索引.由于 foo.txt 相对于 HEAD 没有改变,它无法 git添加它到特殊的临时索引.然后用foo.txt的index-commit版本进行特殊的工作树提交.修复很简单,但没有人把它进入官方 git [还吗?].

(The bug in the stash script occurs because the script compares the work-tree state against the HEAD commit in order to compute the set of files to record in the special temporary index before making the special work-dir commit part of the stash-bag. Since foo.txt is unchanged with respect to HEAD, it fails to git add it to the special temporary index. The special work-tree commit is then made with the index-commit's version of foo.txt. The fix is very simple but no one has put it into official git [yet?].

不是我想鼓励人们修改他们的 git 版本,而是 这里是修复.)

Not that I want to encourage people to modify their versions of git, but here's the fix.)

这篇关于如何在预提交挂钩中正确 git stash/pop 以获得干净的工作树进行测试?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆