git - 如何在预提交 Hook 中正确 git stash/pop 以获得干净的工作树进行测试?

标签 git githooks git-stash

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

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

# Run tests
...

# Restore changes
git stash pop -q

但这会导致问题。这是再现:
  • 添加 // Step 1a.java
  • git add .
  • 添加 // Step 2a.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 applygit 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/

    相关文章:

    linux - 为 SSH 远程命令全局配置 PATH

    git - 从提交中恢复已删除的文件

    android - Git错误403推送

    git - 使用 svn external 将 svn 迁移到 git

    git - 设置一个 git hook 以在远程服务器上自动 git pull

    Git 存储在本地或 git 存储库中

    node.js - 如何在 "npm install"上安装 git Hook ?

    git - 让 Git 在提交之前自动删除尾随空格

    git - 使用 git stash show 显示消息和差异

    git - 如何 git stash pop 在当前分支上创建的最后一个存储?