我希望能够使用索引的当前状态为我的项目运行测试,忽略未提交的工作更改(我后来计划将其添加到预提交挂钩).但是,我无法确定如何以永不导致合并冲突的方式删除然后恢复非索引更改.我需要这个,因为它是由脚本运行的,所以它不应该在完成后改变存储库状态.
git stash --include-untracked --keep-index
和git stash pop
接近,但在许多情况下,即使没有在两个命令之间所做的更改导致合并冲突.
例如:
mkdir project; cd project; git init .;
# setup the initial project with file.rb and test.rb
cat > file.rb <<EOF
def func()
return 42
end
EOF
cat > test.rb <<EOF
#!/usr/bin/env ruby
load './file.rb'
if (func() == 42)
puts "Tests passed"
exit 0
else
puts "Tests failed"
exit 1
end
EOF
chmod +x test.rb
git add .
git commit -m "Initial commit"
# now change file.rb and add the change
cat > file.rb <<EOF
def func()
return 10 + 32
end
EOF
git add file.rb
# now make a breaking change to func, and don't add the change
cat > file.rb <<EOF
def func()
return 20 + 32 # not 42 anymore...
end
EOF
Run Code Online (Sandbox Code Playgroud)
从这里开始,我想针对索引的当前状态运行测试,并恢复未提交的更改.预期的结果是测试通过,因为断裂变化没有添加到索引中.
以下命令不起作用:
git commit --include-untracked --keep-index
./test.rb
git stash pop
Run Code Online (Sandbox Code Playgroud)
问题发生在git stash pop
- 发生合并冲突.
我能想到的唯一其他解决方案是进行临时提交,然后隐藏剩余的更改,然后回滚提交git reset --soft HEAD~
,然后弹出存储.然而,这既麻烦,我不确定在预提交钩子中运行是多么安全.
这个问题有更好的解决方案吗?
和你一样,我跑
git stash --keep-index --include-untracked
Run Code Online (Sandbox Code Playgroud)
然后我可以运行测试等等.
下一部分很棘手.这些是我尝试的一些事情:
git stash pop
因冲突而失败,这是不可接受的.git stash pop --index
因冲突而失败,这是不可接受的.git checkout stash -- .
应用所有跟踪的更改(好),但也会对它们进行分级(不可接受),并且不会从存储中恢复未跟踪的文件(不可接受).藏匿物仍然存在(很好 - 我可以git stash drop
).git merge --squash --strategy-option=theirs stash
可以通过冲突失败,这是不可接受的,即使它没有冲突,它也不能从存储中恢复未跟踪的文件(不可接受).git stash && git stash pop stash@{1} && git stash pop
(尝试以相反的顺序应用变更集)可能会因冲突而失败,这是不可接受的.但是我发现了一组符合我们想要的命令:
# Stash what we actually want to commit
git stash
# Unstash the original dirty tree including any untracked files
git stash pop stash@{1}
# Replace the current index with that from the stash which contains only what we want to commit
git read-tree stash
# Drop the temporary stash of what we want to commit (we have it all in working tree now)
git stash drop
Run Code Online (Sandbox Code Playgroud)
为了减少输出,并浓缩成一行:
git stash --quiet && git stash pop --quiet stash@{1} && git read-tree stash && git stash drop --quiet
Run Code Online (Sandbox Code Playgroud)
据我所知,唯一没有恢复的是在索引中添加然后从工作树中删除的文件(它们最终会添加和存在)以及在索引中重命名的文件和然后从工作树中删除(相同的结果).出于这个原因,我们需要git status -z | egrep -z '^[AR]D' | cut -z -c 4- | tr '\0' '\n'
在初始存储之前查找与这两种情况匹配的文件,然后在恢复之后循环并删除它们.
显然,git stash --keep-index --include-untracked
如果工作树有任何未跟踪的文件或未分级的更改,那么您应该只运行初始化.要检查是否可以git status --porcelain | egrep --silent '^(\?\?|.[DM])'
在脚本中使用该测试.
我相信这比现有的答案更好 - 它不需要任何中间变量(除了树是否脏了,还有恢复存储后需要删除哪些文件的记录),命令和为了安全起见,不要求关闭垃圾收集.有中间藏匿处,但我认为这正是他们所要做的事情.
这是我当前的预提交钩子,它提供了所有提到的内容:
#!/bin/sh
# Do we need to tidy up the working tree before tests?
# A --quiet option here doesn't actually suppress the output, hence redirection.
git commit --dry-run >/dev/null
ret=$?
if [ $ret -ne 0 ]; then
# Nothing to commit, perhaps. Bail with success.
exit 0
elif git status --porcelain | egrep --silent '^(\?\?|.[DM])'; then
# There are unstaged changes or untracked files
dirty=true
# Remember files which were added or renamed and then deleted, since the
# stash and read-tree won't restore these
#
# We're using -z here to get around the difficulty of parsing
# - renames (-> appears in the string)
# - files with spaces or doublequotes (which are doublequoted, but not when
# untracked for unknown reasons)
# We're not trying to store the string with NULs in it in a variable,
# because you can't do that in a shell script.
todelete="$(git status -z | egrep -z '^[AR]D' | cut -z -c 4- | tr '\0' '\n')"
else
dirty=false
fi
if $dirty; then
# Tidy up the working tree
git stash --quiet --keep-index --include-untracked
ret=$?
# Abort if this failed
if [ $ret -ne 0 ]; then
exit $ret
fi
fi
# Run tests, remember outcome
make precommit
ret=$?
if $dirty; then
# Restore the working tree and index
git stash --quiet && git stash pop --quiet stash@{1} && git read-tree stash && git stash drop --quiet
restore_ret=$?
# Delete any files which had unstaged deletions
if [ -n "$todelete" ]; then
echo "$todelete" | while read file; do
rm "$file"
done
# Abort if this failed
if [ $restore_ret -ne 0 ]; then
exit $restore_ret
fi
fi
fi
# Exit with the exit status of the tests
exit $ret
Run Code Online (Sandbox Code Playgroud)
欢迎任何改进.
$ git config gc.auto 0 # safety play
$ INDEX=`git write-tree`
$ git add -f .
$ WORKTREE=`git write-tree`
$ git read-tree $INDEX
$ git checkout-index -af
$ git clean -dfx
$ # your tests here
$ git read-tree $WORKTREE
$ git checkout-index -af
$ git clean -dfx
$ git read-tree $INDEX
$ git config --unset gc.auto
$ # you're back.
Run Code Online (Sandbox Code Playgroud)
-x 的联机git clean
帮助页相当省略地建议了这个解决方案