互联网上充斥着对这个问题的错误和不理想的答案。这是不幸的,因为你会认为这是你想做的常见事情。
问题:当pre-commit
挂钩运行时,存储库可能不干净。因此,如果你天真地进行测试,他们就不会反对你所做的事情,而是你的工作树中发生的任何污垢。
显而易见的事情是git stash --keep-index --include-untracked
开头的pre-commit
和退出时的git pop
。这样你就可以测试(纯)索引,这就是我们想要的。
不幸的是,如果你使用git add --patch
,这会产生合并冲突标记,(特别是如果你编辑了很多),因为stash@{0}
的内容在提交后可能与工作树不匹配。
另一种常见的解决方案是克隆存储库并在新的临时存储库中运行测试。这有两个问题:一个是我们还没有提交,所以我们不能轻易地获得我们即将提交的状态的存储库副本(我确信有一种方法可以做到这一点) ,但我不感兴趣,因为:)。其次,我的测试可能对当前工作目录的位置敏感。例如,由于本地环境配置。
那么:我如何将我的工作树恢复到git stash --keep-index --include-untracked
之前的状态,而不引入合并冲突标记,而不修改提交后HEAD
?
答案 0 :(得分:3)
git write-tree在pre-commit
挂钩中非常有用。它将一个树写入索引的repo(如果提交最终确定,则将重用此树。)
将树写入仓库后,您可以使用git archive | tar -x
将树写入临时目录。
例如:
#!/bin/bash
TMPDIR=$(mktemp -d)
TREE=$(git write-tree)
git archive $TREE | tar -x -C $TMPDIR
# Run tests in $TMPDIR
RESULT=$?
rm -rf "$TMPDIR"
exit $RESULT
答案 1 :(得分:2)
如果克隆整个仓库太贵了,也许您只需要一份工作目录。制作副本比尝试处理冲突更简单。例如:
#!/bin/sh -e
trap 'rm -rf $TMPD' 0
mkdir ${TMPD=$PWD/.tmpdir}
git ls-tree -r HEAD | while read mod type sha name; do
if test "$type" = blob; then
mkdir -p $TMPD/$( dirname "$name" )
git show $sha > $TMPD/"$name";
chmod $mod $TMPD/"$name"
fi
done
cd $TMPD
git diff --cached HEAD | patch
# Run tests here
这将转储树在$ TMPD中提交后的状态,因此您可以在那里运行测试。你应该以比这里更安全的方式获得一个临时目录,但是为了使最终的diff工作(或者为了简化脚本和cd),它必须是工作目录的子代。
答案 2 :(得分:2)
如果你能负担得起使用临时目录(即制作当前结账的完整副本),你可以像这样使用临时目录:
tmpdir=$(mktemp -d) # Or put it wherever you like
git archive HEAD | tar -xf - -C "$tmpdir"
git diff --staged | patch -p1 -d "$tmpdir"
cd "$tmpdir"
...
这基本上是William Pursell的解决方案,但利用git archive
使代码更简单,我希望会更快。
或者,首先通过cd:
cd somewhere
git -C path/to/repo archive HEAD | tar -xf -
git -C path/to/repo diff --staged | patch -p1
...
git -C
需要Git 1.8.5。
答案 3 :(得分:-1)
我终于找到了我想要的解决方案。仅检查提交前索引的状态,并使索引和工作树完全与提交之前一样。
如果您发现任何问题或更好的方法,请回复,作为评论或您自己的答案。
这假设在运行时没有其他任何东西会尝试存储或以其他方式修改git存储库或工作树。这没有保修,可能是错误的,并将您的代码抛到风中。小心使用。
# pre-commit.sh
REPO_PATH=$PWD
git stash save -q --keep-index --include-untracked # (stash@{1})
git stash save -q # (stash@{0})
# Our state at this point:
# * clean worktree
# * stash@{0} contains what is to be committed
# * stash@{1} contains everything, including dirt
# Now reintroduce the changes to be committed so that they can be tested
git stash apply stash@{0} -q
git_unstash() {
G="git --work-tree \"$REPO_PATH\" --git-dir \"$REPO_PATH/.git\""
eval "$G" reset -q --hard # Clean worktree again
eval "$G" stash pop -q stash@{1} # Put worktree to original dirty state
eval "$G" reset -q stash@{0} . # Restore index, ready for commit
eval "$G" stash drop -q stash@{0} # Clean up final remaining stash
}
trap git_unstash EXIT
... tests against what is being committed go here ...