【问题标题】:Checkout to old commit, modify code, then push detached HEAD to origin/master签出旧提交,修改代码,然后将分离的 HEAD 推送到源/主
【发布时间】:2018-10-03 07:43:31
【问题描述】:

我已经在我的主分支上签出之前的提交(让我处于分离的 HEAD 状态)并修改了代码。现在我想推这个 版本到源/主,这样这个版本现在成为最新的提交。 从视觉上看,这就是我所做的

我就是这样开始的:

commits: A -> B -> C -> D (HEAD)

git checkoutC

commits: A -> B -> C (detached HEAD) -> D

修改C处的代码,导致C'

commits: A -> B -> C' (detached HEAD) -> D

然后我 added 和 commited 我的修改。现在我想在 origin/master 上做出C' 的最新提交,这样它就在D 之前提交:

commits: A -> B -> C'-> D -> C' (HEAD)

我并不特别关心D 前面的C' 是否恢复为C 或保持为C'

我不清楚我需要发出什么命令来实现这一点,有人知道吗?

更新:我也不担心保留D。因此,也许可以删除D,这会使C' 成为最新的?

更新:我最终以git rebase 提交C,从而删除D,然后重新添加修改以获取C',然后推送C'。我确信有更好的方法来做到这一点......

【问题讨论】:

  • 在您修改C 的代码,导致C' 的步骤中,您的图表不应该是:A -> B -> C -> C' (detached HEAD)
  • 是的,类似。然而,与那篇文章不同的是,我当然希望我的更改能够进入 master 分支。

标签: git


【解决方案1】:

好吧,您的提交图并不完全准确(可以稍微澄清一下)。这样做的原因是,当我们达到这一点时,它将有助于理解解决方案。所以:

我就是这样开始的:

commits: A -> B -> C -> D (HEAD)

现在,如果您要在提交之间绘制箭头,它们应该及时“向后”指向,因为 git 存储父指针而不是子指针。 (我只是画线而不是箭头。)

我假设master 分支(和origin/master 参考)位于D。此外,由于您没有显示分离的HEAD 状态,我假设您已签出master。我通常会画这样的东西

commits: A -- B -- C -- D <--(master)(origin/master)
                                 ^(HEAD)

这显示了 ref 指向的位置,并反映出 HEAD 是指向 master 的符号 ref。

我 git checkout 到 C:

所以现在我们有

commits: A -- B -- C -- D <--(master)(origin/master)
                   ^(HEAD)

(这显示了以这种方式绘制HEAD 的优势;只需移动它使其指向提交,我们就知道我们处于分离状态。)

修改C处的代码,导致C':

这就是上述图表变得不准确的地方。这个新的提交不会替换 C,而是C 的新子代。所以这更像是

commits: A -- B -- C -- D <--(master)(origin/master)
                    \
                     C'
                     ^(HEAD)

(这显示了绘制线条而不是箭头的优点;更容易描述不同的提交线)。但即使这样也不完全正确,因为按照惯例,C' 将意味着“一个新​​的/重写的提交,它将与C 应用于其父级相同的更改应用于Cs 父级”;所以我们应该叫它别的名字。

commits: A -- B -- C -- D <--(master)(origin/master)
                    \
                     E
                     ^(HEAD)

现在我们可以解决您正在尝试做的事情。

现在,虽然您在更新中说您不关心保留 D,但您可能没有考虑保留它的所有理由。如果你说“我真的想把 D 从历史中剔除,这就是为什么......”那是一回事,但如果只是“我不在乎一种或另一种”,那么你应该考虑保留它。原因如下:

删除D 是对历史的重写。推送分支后,对该分支进行历史重写可能会导致问题,尤其是在与其他用户共享存储库的情况下。请参阅“从上游变基中恢复”下的 git rebase 文档。

现在,如果您了解其中的问题 - 即,如果您知道您需要与拥有 ref 副本的任何其他人协调,如果不这样做可能会导致您的重写被意外撤消 - 而且仍然想丢弃D,那么你可以这样做:

重写方法

从您原来的问题停止的地方开始,您将master 分支移动到您新创建的提交。

git branch -f master
git checkout master

这会给你

commits: A -- B -- C -- D <--(origin/master)
                    \
                     E <--(master)
                              ^(HEAD)

(事实上,更容易的事情是从一开始就将master 重置为HEAD^,而不是检查到分离的HEAD 状态;假设,也就是说,你知道你要去在那一点上进行重写。)

然后您可以推送master 的重写,但您必须“强制”推送。这是您将导致上游变基的“危险信号”

git push --force-with-lease

如果其他人向origin/master 添加了更多提交,这将失败。这是因为完成重写可能会丢失他们的工作,并且至少应该采取额外的步骤来解决这个问题。如果您仍想覆盖该安全检查,您可以说

git push -f

请注意,无论是这个方法还是任何其他方法都不会真正删除提交D。它会从master 历史记录中删除D,这意味着它最终可能会被gc 删除。

commits: A -- B -- C -- D
                    \
                     E <--(master)(origin/master)
                              ^(HEAD)

无重写方法

另一方面,如果您认为重写带来的麻烦多于其价值,您会这样做:

再次从原始问题中断的地方开始,您可能希望保留所做的更改,这样您就不必重新开始。

git branch temp
git checkout master

现在恢复在D中所做的更改

git revert HEAD

屈服

                          ~D <--(master)
                         /         ^(HEAD)
commits: A -- B -- C -- D <--(origin/master)
                    \         
                     E <--(temp)

~D 的内容 (TREE) 将匹配 C 的内容,所以现在你可以说

git rebase master temp
git checkout master
git merge --ff-only temp
git branch -d temp

所以我们终于有了

                          ~D -- E' <--(master)
                         /               ^(HEAD)
commits: A -- B -- C -- D <--(origin/master)
                    \         
                     E

原来的E 提交不再感兴趣;对D(~D)的反转和对E(E')的改动添加在master上,可以正常推送。

【讨论】:

    【解决方案2】:

    如果你只是想用 C' 的修改代码重写你的 git 历史,你可以创建一个新分支:

    git checkout -b <name_of_new_branch>
    

    然后强制将你的新分支推送到远程:

    git push origin <your_branch_name> -f
    

    【讨论】:

      猜你喜欢
      • 2020-12-18
      • 1970-01-01
      • 1970-01-01
      • 2018-10-31
      • 2019-02-22
      • 2013-05-06
      • 1970-01-01
      • 2013-12-30
      • 1970-01-01
      相关资源
      最近更新 更多