仅对暂存文件运行测试:`git stash -k -u`和`git stash pop`会在部分暂存文件上引发冲突。

7

tl;dr

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

  1. git stash save -k -u 用于在测试之前存储未暂存/未跟踪的文件/更改
  2. 使用暂存的文件运行测试
  3. git stash pop 以还原第一步中的更改/文件。

问题在于使用 git stash pop 将导致部分暂存的文件出现冲突。解决冲突将导致丢失部分暂存/未暂存的更改(您需要重新选择部分行以再次进行暂存)。

更新:如果您想了解运行此过程的shell脚本,请跳转到最后一节获取更多信息。

注意:仅对部分暂存的相邻行(或足够接近的行)会导致此问题。 例如,如果您在某个文件中有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。在提交之前,您可以选择一些行或文件,并通过git add将它们添加到staged中。

现在,在将一些代码添加到staged之后,我想只使用暂存的文件运行测试,以确保它们适合提交,因此我需要通过git stash -k -u隐藏unstageduntracked更改(新文件),并保留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命令后,所有未提交/未跟踪的更改都会被隐藏。
[staged]
  file A
  file B (only stage some of code)
[unstaged/untracked]
  <none, clean>

问题来了。在运行测试后,再运行 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状态。保留staged文件对于提交或通过添加其他更改来完成测试非常重要。

解决方案更新:运行该程序的shell脚本

根据@torek的回答,我编写了一个shell脚本来仅使用staged文件运行测试:
#!/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
# https://dev59.com/a53ha4cB1Zd3GeqPXJ6F
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')

元信息:这是一个非常精心组织的问题——感谢您花时间使用精确的语言,并制作工作流程图以使其他人更容易回答! - coreyward
您可以在运行测试之前提交已暂存的更改,如果更改未通过测试,则可以撤消提交。 - Paul
@Paul,如果失败了,如何恢复已暂存/未暂存的更改? - Xaree Lee
似乎 git stash pop 无论如何都会触发自动合并,但是你不能传递合并标志,例如 -s theirs,这将解决此问题。我想指出的是,如果同一文件中的暂存和未暂存更改不足以引起冲突,自动合并将正常解决 - 我认为这个问题的术语“部分暂存”并不清楚,并且我认为在早期的 git 版本中也存在此问题。 - André Sassi
1
这里涉及到几个交织在一起的问题,与 --keep-index--include-untracked 有关。请注意,使用 git stash 在此情况下会产生 三个 提交:一个用于索引状态,一个用于工作树状态,另一个用于保存未跟踪的文件。恢复存储状态需要重新设置并清理。详情请参见 https://dev59.com/VWIj5IYBdhLWcg3wIx_W。 - torek
显示剩余3条评论
3个回答

2
这不是完整的答案-有关更多详细信息,请参见如何从“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,并将您的stash-bag存储在不同的引用下(您可以以任何方式操纵和/或删除它)。当然,如果您要走这么远,您可能希望编写自己修改过的git stash脚本,以避免当前版本中的错误。
有一种完全不同且我认为更简单的运行测试的方法:
  1. 创建一个空的临时目录。
  2. 将当前索引转换为树,然后将该树读入临时目录。(或使用git checkout-index将索引提取到临时目录。在任一情况下,请注意环境变量GIT_WORK_TREEGIT_DIR,或前端git命令的--git-dir--work-tree参数。)
  3. 在临时目录中运行测试。
  4. 丢弃临时目录。
这避免了git stash save的错误,并且通过对步骤2进行轻微修改,可以让您测试任何修订版。有两个明显的缺点:您需要一个存储临时树的地方,而且临时树不在工作树中。这些问题的严重程度取决于您的存储库和测试。

太棒了。我不知道使用 git stash -k -u 是否仍然会存储已暂存(索引)的文件,而且我可以恢复它们。这种方法比使用临时的 git commit 保留已暂存的文件更好。 - Xaree Lee

0

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

以下是步骤:

使用git stash -k -u将未暂存/未跟踪的文件储藏,但保留已暂存的文件以进行提交。 使用已暂存的文件运行测试。如果失败,请转到步骤3;如果通过,请转到步骤4。 如果失败,则需要将未暂存/未跟踪的文件恢复并将新代码添加到已暂存状态。这是我的问题的解决方法:
  • 运行git commit -m 'temp'将已暂存的文件暂时存储在一个新的提交中。
  • 运行git stash pop来恢复未暂存/未跟踪的文件。它仍然会引发提交(先前已暂存)和未暂存之间相邻行更改的冲突。
  • 运行git checkout --theirs -- . && git reset来解决任何冲突,并将unmerged状态重置为unstaged状态。因为先前已暂存的文件现在已经提交,所以git reset不会影响它们。
  • 运行git reset HEAD^ --soft将最新提交中的文件回滚到staged状态。
  • 现在您拥有与步骤1之前相同的文件状态(已暂存/未暂存/未跟踪)。您可以从unstaged中添加其他代码到staged以满足测试要求。也许有一个git命令可以实现上述这些步骤,我正在寻找。
如果通过,请使用消息git commit并开始下一轮开发迭代。
另一种方法是:(1)首先只提交已暂存的文件,(2)在最新的提交上运行测试,(3)如果需要,通过使用git commit --amend来修补提交,直到测试通过。我不太喜欢这种方法,因为任何对你工作的中断可能会导致你忘记继续修补提交以通过测试,并且推送或发布错误的提交。

0

这个流程对我来说非常完美,包括部分暂存的文件。我不会在第二步修改文件。

# 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并不完全准确,因为存储还包括索引。但这是一个方便的简写。)

使用别名,命令非常简单:

  1. git stash-unstaged

  2. 运行测试、linting等。

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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接