来自 stash 的分支创建了两个新的父提交

Branch from stash creates two new parent commits

我像这样在一个分支上隐藏了提交

a -> b -> c    <- main
           \
            x  <- stash@{0}

我想使用隐藏的提交,所以我从中创建了一个分支

git branch tmp stash@{0}

我希望历史看起来像这样

a -> b -> c    <- main
           \
            x  <- tmp

但是,现在有两个新的父提交到分支,就像这样

a -> b -> c    <- main
           \
            y
             \
              x  <- tmp
             /
            z

其中 <y> 有提交消息 index on main: <c_hash> <c_message><z> 有提交消息 untracked files on main: <c_hash> <c_message>。在我的例子中,两个提交都是空的。

我怎样才能摆脱它们?


提供更多背景信息。隐藏的提交有一些有用的更改,我想将其用作 main 的一部分。我的实际历史更复杂,因为在此期间向 main 添加了更多提交(例如 def 等)。我知道我可以在 main 上弹出隐藏的提交,然后使用交互式变基将它从历史记录中移到它之前的位置。但是这次我从隐藏的提交创建了一个分支并重新设置 main 到它上面,这给我留下了这两个我现在不知道如何摆脱的不需要的父提交。

c 成为 x 的唯一父级的最简单方法是简单地在 main 之上重新创建它:

# Check out the 'tmp' branch
git switch tmp

# Move HEAD to the 'main' branch, but keep the changes
# introduced by 'x' in the index and in the working directoy
git reset --soft main

# Commit the changes again, this time on top of 'main'
git commit -m 'x'

生成的历史记录将如下所示:

a---b---c-----x 
        ^     ^
       main  tmp

谈论的是你可能应该做的事情,而不是你正在做的事情,但是让我们在这里谈谈你正在做的事情以及为什么会产生这样的结果weird-looking 结果。

git branch tmp stash@{0}

我们需要把它拆开一点,但首先:

a -> b -> c    <- main
           \
            x  <- stash@{0}

这张图在几个微妙但重要的方面是错误的。一个是在 Git 里面,所有的箭头都“向后走”,所以我们应该这样绘制我们的提交:

A <-B <-C   <-- main

(大写与小写是一个选择,我只使用大写,因为我认为在稍后的文本中引用它们时它看起来更好)。或者我们可以偷懒而不去理会内部箭头,因为它们是不可变的并且总是指向后方。但是,从 分支名称 出来的箭头确实会移动,因此最好继续绘制它:

A--B--C   <-- main (HEAD)

(我在此处添加了 HEAD 以表明我们在 main 上,因此 C 是当前提交。)

绘图中更重要的错误是 git stash 没有使 one 提交。它进行 两次 次提交,或者使用 -u-a-三次 次提交。 stash 进行的第一个和最后一个提交按顺序是索引 i 和 working-tree w 提交。提交 w 具有合并提交的 形式 。如果 git stash 总共进行了三个提交,则第二个提交是 untracked-files 提交 u。最后的 w 提交有两个 parents,当前提交和 i(按此顺序),或三个 parents:当前提交,i,和 u(按此顺序)。所以我们得到:

A--B--C   <-- main (HEAD)
      |\
      i-w   <-- stash

或:

A--B--C   <-- main (HEAD)
      |\
      i-w   <-- stash
       /
      u

stash ref (refs/stash) 不是分支名称,但 stash@{0} 是此 stash ref 的别名,因此它像一个一样工作,并且一次你创建了新的 tmp 分支然后你有:

A--B--C   <-- main (HEAD)
      |\
      i-w   <-- stash, tmp
       /
      u

此时您可能会 啊哈,就这样了,但以防万一您没有,我们现在注意到:

git branch tmp <commit-specifier>

for any valid commit-specifier 只是创建或尝试创建一个指向指定提交的新分支名称 tmp。提交已经存在,在本例中是 git stash 提交的 w (工作树)提交;该提交已经有三个 parents,即按顺序排列的 Ciu。您看到第三次提交的事实意味着您必须具有 运行 git stash -ugit stash -a,以便进行第三次提交。

My actual history is more complicated as there have been more commits added to main in the meantime ... But this time I created a branch from the stashed commit and rebased main onto it, which left me with these two undesired parent commits that I don't know how to get rid of now.

rebase 是个坏主意,您应该使用 reflog 撤消它。如果可能的话,将 main 恢复到以前的状态,并删除分支名称 tmp,保留隐藏提交。 (如果您 删除了存储 ,请保留分支名称 tmp 以便您可以轻松找到存储提交。)

一旦你回到这种情况:

A--B--C--D--E--F   <-- main (HEAD)
      |\
      i-w   <-- stash
       /
      u

您可以使用git stash branch创建一个新分支。此命令处理存储提交的时髦结构。

(注意:在使用 git stash branch 之前,请确保 git status 尽可能报告没有未跟踪的文件。您提到 u 提交似乎是空的,如果它是,这很好,因为如果 git stash 无法创建 u 提交。我认为这是 git stash 中的错误:我认为你应该能够告诉它 忽略 u 提交,即使它存在。但你不能,至少今天不能。)

git stash branch所做的是:

  • 创建一个指向 then-HEAD 提交的分支,即本例中的提交 C
  • 使用保存的索引提交 i 来恢复 Git 的索引;
  • 使用提交 w,保存的工作树,恢复你的工作树;和
  • 如果存在,使用提交u,保存的未跟踪文件,恢复未跟踪文件(包括untracked-but-ignored文件,如果存储是用-a)。

成功完成所有这些后,git stash branch 然后放下藏品。

git stash branch 命令可以创建存储的名称,例如 git stash branch stash@{3},并且可以使用分支或标记名称,只要该分支或标记名称指向存储提交。 (它会 尝试 使用该提交,即使它实际上不是隐藏提交:它只需要类似于一个,即合并提交。)

因此,在这种情况下,您可以使用:

git stash branch newbranch tmp

你将处于这种情况:

        D--E--F   <-- main
       /
A--B--C   <-- newbranch (HEAD)
      |\
      i-w   <-- tmp
       /
      u

Git 的索引和您的工作树现在已从存储中恢复,因此您现在可以提交(保存存储在 i 提交中的相同树)或 运行 git addgit commit 根据需要。如果提交 i 的已保存树与提交 C 已保存的树相匹配,则还没有任何内容“准备提交”(如 git status 所说)并且您只想 git add 事情并提交:

        G   <-- newbranch (HEAD)
       /
A--B--C--D--E--F   <-- main
      |\
      i-w   <-- tmp
       /
      u

你现在有一个 正常的 提交并且可以使用 Git 的所有正常机制,包括 cherry-pick 或变基,而不会出现所有的怪异隐藏强加。

结论

您应该从所有这些中得出的真正结论是 git stash 是一个非常糟糕的工具。尽量不要使用它。它并不 完全 糟糕,并且在某些小任务上还可以,但请谨慎使用。这对任何事情都没有好处long-term。使用 git stash branch 将存储 转换为 分支是处理存储时间过长的方法。