git - 您如何使用存储删除然后恢复非索引更改,而不会在 git 中发生 merge 冲突?

标签 git bash

我希望能够使用索引的当前状态为我的项目运行测试,忽略未提交的工作更改(我稍后计划将其添加到预提交 Hook )。但是,我无法弄清楚如何以一种永远不会导致 merge 冲突的方式删除然后恢复非索引更改。我需要这个,因为它是由脚本运行的,所以它在完成后不应该改变存储库状态。

git stash --include-untracked --keep-indexgit stash pop 很接近,但在很多情况下它会导致 merge 冲突,即使没有更改两个命令之间的位置。

例如:

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

从这里我想针对索引的当前状态运行测试,并恢复未提交的更改。预期结果是测试通过,因为重大更改未添加到索引中。

以下命令不起作用:

git commit --include-untracked --keep-index
./test.rb
git stash pop

问题出现在 git stash pop - 发生 merge 冲突。

我能想到的唯一其他解决方案是进行临时提交,然后存储剩余的更改,然后使用 git reset --soft HEAD~ 回滚提交,然后 pop 存储。然而,这既麻烦又麻烦,而且我不确定在预提交 Hook 中运行有多安全。

这个问题有更好的解决方案吗?

最佳答案

和你一样,我跑

git stash --keep-index --include-untracked

然后我可以运行测试等等。

下一部分很棘手。这些是我尝试过的一些东西:

  • git stash pop 可能因冲突而失败,这是 Not Acceptable 。
  • git stash pop --index 可能因冲突而失败,这是 Not Acceptable 。
  • git checkout stash -- . 应用所有跟踪的更改(良好),但也暂存它们( Not Acceptable ),并且不从存储中恢复未跟踪的文件( Not Acceptable )。存储仍然存在(很好——我可以 git stash drop)。
  • git merge --squash --strategy-option=theirs stash 可能因冲突而失败,这是 Not Acceptable ,即使没有冲突,它也不会从存储中恢复未跟踪的文件( Not Acceptable )。
  • git stash && git stash pop stash@{1} && git stash pop(尝试以相反的顺序应用变更集)可能因冲突而失败,这是 Not Acceptable 。

但是我找到了一组可以满足我们要求的命令:

# 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

为了减少输出,并压缩成一行:

git stash --quiet && git stash pop --quiet stash@{1} && git read-tree stash && git stash drop --quiet

据我所知,唯一不能恢复的是添加到索引中然后从工作树中删除的文件(它们最终会添加并存在)和重命名的文件索引,然后从工作树中删除(相同的结果)。出于这个原因,我们需要使用类似 git status -z | 的行来查找与这两种情况匹配的文件。 egrep -z '^[AR]D' |切-z -c 4- | tr '\0' '\n' 在初始存储之前,然后循环并在恢复后删除它们。

显然,如果工作树有任何未跟踪的文件或未暂存的更改,您应该只运行初始 git stash --keep-index --include-untracked。要检查你可以使用测试 git status --porcelain | egrep --silent '^(\?\?|.[DM])' 在你的脚本中。

我相信这比现有的答案更好——它不需要任何中间变量(除了树是否脏,以及恢复存储后需要删除哪些文件的记录),有更少的命令,并且不需要为了安全而关闭垃圾收集。有中间存储,但我认为这正是它们的用途。

这是我当前的预提交 Hook ,它执行所有提到的操作:

#!/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

欢迎任何改进。

关于git - 您如何使用存储删除然后恢复非索引更改,而不会在 git 中发生 merge 冲突?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/13889242/

相关文章:

linux - 脚本 bash chmod 绕一圈

Git 删除提交而不删除 merge 提交

Git gc 失败并显示 : Error pack-objects died of signal 9

bash - 通过 ssh 运行具有嵌套引号的 shell 命令

linux - 使用 awk 或 sed 从字符串中抓取文本的特定部分

linux - 定义引用其他别名的别名

git - 如何在 git 历史记录中搜索消失的行?

git 一次应用多个存储

git - fork 和重命名 GitHub 项目时的最佳工作流程

linux - Bash:将带有新行的字符串转换为带有逗号、内联的唯一值的字符串