运行 仅测试暂存文件:`git stash -k -u` 和 `git stash pop` 将引发部分暂存文件的冲突

Run tests only for staged files: `git stash -k -u` and `git stash pop` will raise conflicts on partial staged files

tl;博士

我想 运行 在提交前仅使用暂存文件进行测试:

  1. git stash save -k -u 在测试前隐藏 unstaged/untracked files/changes
  2. 运行 使用暂存文件进行测试
  3. git stash pop 在第 1 步恢复 changes/files。

问题是使用 git stash pop 会在部分暂存的文件上引发冲突。解决冲突将导致丢失部分 staged/unstaged 更改(您需要选择部分行重新上演)。

Update: If you want to know the shell script to run this procedure, please jump to the last section for more information.

注意:只有相邻的行(或足够接近)部分暂存才会导致此问题。例如,如果文件中有 6 个新行更改:

1 +| a     (add to staged)
2 +| b     (add to staged)
3 +| c     (add to staged)
4  | d     (keep unstaged)
5  | e     (keep unstaged)
6  | f     (keep unstaged)

现在使用git stash -k -u然后git stash pop会引起冲突。

演示问题

Git 为提交前的更改提供了三个阶段:stagedunstageduntracked.

任何更改都将添加到 unstaged。在提交之前,您可以选择一些行或文件并将它们添加到 staged by git add.

现在,在向 staged 添加了一些代码后,我想 运行 仅使用暂存文件进行测试以确保它们适合提交,因此我需要隐藏 unstageduntracked 更改(新文件) git stash -k -u 并保持 staged 更改。

例如,我有 3 个文件更改:文件 A 已完全暂存,文件 B 已部分暂存(部分代码),文件 C 是未跟踪的新文件。

[staged]
  file A
  file B (only stage some of code)
[unstaged]
  file B
[untracked]
  file C (new file)

在 运行 宁 git stash -k -u 之后,所有 unstaged/untracked 更改都被隐藏。

[staged]
  file A
  file B (only stage some of code)
[unstaged/untracked]
  <none, clean>

问题来了。在 运行ning 测试和 git stash pop 之后,它会引发文件 B 的冲突,因为它是部分暂存的。我确定我在存储和测试时没有更改文件 B。

我想知道如何与 git stash pop 自动合并而没有任何冲突,就像我隐藏它们之前一样。

我的工作流程

我觉得这是一个很普通的工作流程

  development start
          |
[make changes (unstaged)] 
          |
(pick changes to staged for commit by `git add`)<---|
          |                                         |
          V                     (pick other changes to fulfill tests)
[partial staged/unstaged]                           |
          |                                         |
(stash unstaged changes by `git stash -k -u`)       |
          |                                         |
(run tests only with staged files for commit)       |
          |                                         | 
(restore stashed files by `git stash pop`)          |
          |                                         |
          |------------<if test failed>-------------| 
          |
    <if test success>
          |
[commit staged files by `git commit`]
          |
          V
keep development or next commit

我需要一种方法来 stash pop 而不会丢失所有更改的 staged/unstaged 状态。保留暂存文件对于通过添加其他更改来提交或完成测试非常重要。

更新解决方案:一个 shell 脚本到 运行 程序

根据@torek 的回答,我写了一个 shell 脚本来 运行 测试,只有暂存文件:

#!/bin/sh -e

# stash all unstaged changes
# (-k: unstaged files; -u: new added files; -q: quite)
echo '--------------------------------------------------------------'
echo '---- Stash all unstaged/untracked files (git stash -k -u) ----'
echo '--------------------------------------------------------------'
BEFORE_STASH_HASH=$(git rev-parse refs/stash)
git stash -k -u -q
AFTER_STASH_HASH=$(git rev-parse refs/stash)
if [ "$BEFORE_STASH_HASH" == "$AFTER_STASH_HASH" ]; then
  echo '\n\n---- Stash failed! Please check and retry. ----\n\n';
  exit 1;
fi;

# run test only with staged files
echo '-------------------'
echo '---- Run tests ----'
echo '-------------------'
<run your tests here> ||      #### <=== replace your test command here
(echo '\n\n---- Tests failed! Please fix it before commit. ----\n\n')

# restore all stashed changes
# 
echo '-----------------------------------------------------------'
echo '---- Restore all stashed files (git stash pop --index) ----'
echo '-----------------------------------------------------------'
git reset --hard -q &&
git clean -df -q &&
git stash pop --index -q ||
(echo '\n\n---- Restore failed! Please check and fix it. ----\n\n')

目前,这是@Paul 提供的解决方法。我写在这里,但我仍在寻找更好的解决方案。

这些是步骤:

  1. 使用 git stash -k -u 存储 unstaged/untracked 文件,但保留暂存文件以供提交。
  2. 运行 使用暂存文件进行测试。 如果失败,转第3步;如果通过,转到步骤4。
  3. 如果失败,您需要恢复 unstaged/untracked 文件并将新代码添加到暂存状态。这是我的问题的解决方法:
    • 运行 git commit -m 'temp' 暂时将暂存文件存储在新提交中。
    • 运行 git stash pop 恢复 unstaged/untracked 个文件。它仍然会引发提交(先前暂存)和未暂存之间相邻行更改的冲突。
    • 运行 git checkout --theirs -- . && git reset 解决冲突,如果有的话,将unmerged状态重置为unstaged状态。因为之前暂存的文件现在已提交,git reset 不会影响它们。
    • 运行 git reset HEAD^ --soft 将最新提交的文件回滚到 staged 状态。
    • 现在您拥有与步骤 1 之前相同的文件状态 (staged/unstaged/untracked)。您可以添加从 unstagedstaged 的其他代码以满足测试要求。也许有一个 git 命令可以实现我正在寻找的上述步骤。
  4. 如果通过,只需git commit留言并开始下一次迭代开发。

另一种方法 是:(1) 先提交暂存文件,(2) 运行 测试最新的提交,(3) 修补提交,如果需要,通过 git commit --amend 直到通过测试。我不喜欢这种方法,因为任何对您工作的干扰都可能使您忘记继续修补提交以通过测试,并推送或发布错误的提交。

这不是一个完整的答案——更多信息请参见 How to recover from "git stash save --all"?——但是虽然这是一个有吸引力的过程,并且一旦 git stash 中的错误得到修复它就会起作用,但它有点危险今天

如果你已经修复了这个错误,或者不介意稍微危险地生活,:-) 你可以使用这个过程:

  1. 运行 git stash save -k -u 并确保它保存了一些东西(例如,比较 git rev-parse refs/stash 前后的结果)。
  2. 运行 你的测试。
  3. git reset --hard && git clean -df(可选,两者都包括 -q)。只有当测试修改提交的文件时才需要 git reset --hard,只有当测试创建未跟踪的文件时才需要 git clean
  4. 运行git stash pop --index。请注意,--index 在这里很关键。您也可以使用 -q

而不是 savepop --index,您可能想使用 createapply --index 并将您的储物袋存储在不同的参考下(您操纵 and/or 完成后删除,以任何你喜欢的方式)。当然,如果你打算走到这一步,你可能想要编写自己的修改后的 git stash 脚本,首先避免当前的错误。


运行 测试有一种完全不同且在我看来更简单的方法:

  1. 创建一个空的临时目录。
  2. 将当前索引变成一棵树,然后将该树读入临时目录。 (或使用 git checkout-index 将索引提取到临时目录中。无论哪种情况,请注意环境变量 GIT_WORK_TREEGIT_DIR,或者 --git-dir--work-tree前端 git 命令的参数。)
  3. 运行 临时目录中的测试。
  4. 放弃临时目录。

这避免了 git stash save 错误,并且对步骤 2 稍作修改,让您可以测试任何修订。有两个明显的缺点:你需要一个地方来存储临时树,临时树不在工作树所在的地方。这些问题的严重程度取决于您的存储库和测试。

这个流程非常适合我,包括部分暂存的文件。我没有在步骤 2 中修改文件。

# 1. Stash all changes, then discard all unstaged changes
$ git stash --include-untracked --keep-index

# 2. Run tests, run linter, check types, etc. Don't modify files. Commit if desired.

# 3a. If no commit was made, restore to initial state
$ git stash pop

# 3b. If a commit was made, restore unstaged changes
$ git checkout stash -- . ; git stash pop ; git reset

~/.gitconfig 中有用的别名:

[alias]
  stash-unstaged = stash --keep-index --include-untracked

  restore-unstaged = "!git checkout stash -- . ; git stash pop ; git reset"

  # Shortcuts
  stun = stash-unstaged
  restun = restore-unstaged

(请注意,别名 stash-unstaged 不太准确,因为存储还包括索引。但它很方便 shorthand。)

有了别名,命令就很简单了:

  1. git stash-unstaged

  2. 运行 测试、linting 等

  3. git stash popgit restore-unstaged,取决于是否进行了提交