【问题标题】:Create new branch from specific commit onwards从特定提交开始创建新分支
【发布时间】:2018-05-03 19:55:01
【问题描述】:

我想从某个提交开始创建一个新分支。但是,我希望之后提交的所有提交也成为该分支的一部分。

让我们假设这是branch-D,包含所有提交。

> branch-D
commit-A ---- commit-B ---- commit-C ---- commit-D

我想删除从 commit-B 到新的 branch 的所有内容,我们将其命名为 branch-C,并且只在 branch-D 上保留 commit-A

> branch-D
commit-A

> branch-C
commit-B ---- commit-C ---- commit-D

我还没有将任何东西推送到远程存储库。 有没有办法做到这一点?

【问题讨论】:

  • 哪个提交是初始提交?您是要基于commit-A 的父级创建branch-C 还是将branch-C 作为孤儿分支?

标签: git version-control branch commit git-commit


【解决方案1】:

最直接的方法是在提交 A 之前从提交中创建 2 个新分支。然后将cherry-pick commit-A 提交到1 个分支,并将cherry-pick 提交B、C、D 到第2 个分支。

【讨论】:

  • 这不是最直接的方法。移动裁判实际上要容易得多。 Cherry-picking 很容易成为 Stack Overflow 上最被过度推荐的 git 操作;它适用于特定的用例(我很高兴在自己的工作中从未遇到过),但每个人都想急于使用其他命令更容易完成(并且副作用更少)的事情。
【解决方案2】:

更新 - 我误读了您的图表,结果混淆了分支名称。同样正如 torek 指出的那样,对图表的“更正确”阅读包括一些歧义。我将大部分保持原样(但理顺分支名称),因为我相信它传达了主要原则;如果您需要进一步编辑 branchC 历史记录,我会稍微介绍一下如何使用 rebase;但更详细的答案,请参阅 torek 的回复。


首先让我谈谈我认为您正在寻找的解决方案;但我建议阅读更多内容,因为这个问题存在概念上的混淆,值得澄清。

所以你现在有了

A -- B -- C -- D <--(branchD)

我认为您希望在branchD使用A,以及BCD 的新分支。所以第一步是,签出branchD(或D),创建新分支

git branch branchC

然后将branchD 分支移回A

git reset --hard HEAD~3

(我使用了HEAD~3,因为在此示例中,这将是A 的一个名称。这取决于从master 的历史记录中取出的提交数量。使用A 的提交 ID (SHA) 代替 HEAD~3。)

在这些步骤之后,你有

A <--(branchD)
 \
  B -- C -- D <--(branchC)

哪个看起来像您所描述的,但我们没有按照描述所暗示的方式到达那里。

我没有移动提交;我移动了树枝。这在 git 中要简单得多,它反映了提交不像在某些系统中那样“属于”git 中的分支。因此,“在前一次提交中分支但在该分支上包含以下提交”的语句在 git 中真的没有意义。

问题中前后图表中的歧义确实让我有可能误解了branchC 应该在其历史中包含的内容。如果branchC意味着包含A,那么您必须重写BCD - 您可以使用git rebase -i 最轻松地做到这一点。在创建 branchC 并移动 branchD 之后,您可以说

git rebase -i branchD^ branchC

这里branchD^ 是“A 之前的提交”的可能名称。如果有这样的提交,它可能有其他名称——也许是master,或者肯定是它的提交 ID。如果有 没有这样的提交,那么我想你可以说

git rebase -i --root branchC

(但在这种情况下,尝试从 branchC 历史记录中删除 A 可能没有意义,所以我怀疑这就是这里发生的事情)。

rebase 命令将打开一个文本编辑器,其中包含一个 TODO 列表,其中每个提交都有一个条目。您可以从列表中删除提交A 的条目,然后保存并退出。这不会干扰branchD - 它仍将指向A - 但它会通过复制BCDbranchC 创建新的历史记录。那么你会有

x -- B' -- C' -- D' <--(branchC)
  \
   A <--(branchD)

无论如何,做你想做的意味着从branchD的历史中删除BCD。你提到这些提交没有pushed,所以应该没问题;但这是一个需要牢记的重要区别。任何时候你想从一个分支的历史中删除一个提交,如果远程分支还不知道有问题的提交,这样做会更容易。

【讨论】:

    【解决方案3】:

    Git 的特殊之处在于分支名称 不会影响您的提交历史 中的内容,除了它们允许您和其他任何人,这非常重要—找到提交。秘密在于,在 Git 中,一次提交可能会在许多个分支上同时

    不过,重要的是,一旦提交,就不能更改。它可以复制到(新的,略有不同的)提交,但不能更改。

    此外,虽然任何分支名称​​可以被强制指向任何提交,但分支名称运动有一个“正常方向”:通常,它们只是提前。正如我们稍后会看到的那样,推进意味着他们过去指向的任何提交仍然“在”那个分支上。因此,一旦您将提交提交给其他人(其他 Git)并要求他们将这些提交称为 branch-D,就很难让 other Git 收回它。因此:

    我还没有将任何东西推送到远程存储库。有没有办法做到这一点?

    是的,并且可能有一个非常简单的方法:“还没有推送任何东西”意味着你是唯一拥有这些提交的人,所以无论你找到它们,那就是每个人如何找到他们,因为你是“每个人”。 :-) 无需让其他人更改任何内容。

    只要它们已经按照您想要的方式进行布局,您只需重新排列查找这些提交的方式即可。

    让我们用“Git Way”画出你现有的一系列提交:

    ... <--A <--B <--C <--D   <-- branch-D
    

    在 Git 中,每个提交都由其哈希 ID 唯一标识。这些东西又大又丑,而且显然是随机的(尽管它们实际上是完全确定的),所以我们几乎从不使用它们,或者使用像 d1c9d3a 这样的缩写形式。取而代之的是,让我们将最后一次提交称为D

    在提交D 内部,还有另一个哈希ID,标识D父提交。假设它是c033bae,但让我们称之为“提交C”。所以我们说D 指向 C

    同样,C 指向 BA,又指向 ...就目前而言:

    ...--o--o   <-- master
             \
              A--B--C--D   <-- branch-D
    

    这是一种更紧凑的绘制方式。提交总是,必然,指向向后,所以我们并不真正需要内部箭头——我们知道它们总是向后移动。但是,分支名称,例如masterbranch-D ...好吧,我们可以在任何地方说明这些观点。我们要做的是让它们指向一个分支的“tip commit”。

    Git 通过从分支名称指向的那个开始查找提交:D,对于branch-D,或者在第一行的最后一个o 对于master。然后它查看当前提交的父级,以向后移动。然后它查看父母的父母,依此类推。因此两个o 提交同时在master branch-D 上:我们可以从master 开始找到它们,或者我们可以从branch-D 开始并向后工作来找到它们四个步骤。

    这意味着我们想要的图片看起来可能是这样的:

    ...--o--o   <-- master
             \
              A   <-- branch-D
               \
                B--C--D   <-- branch-C
    

    这里,master 上的提交也在两个分支上,提交A,现在是branch-D 的尖端,仍然在branch-C 上。这不再是branch-C提示了。


    另一方面,也许我们想要的图片是这样的:

              ?--?--?   <-- branch-C
             /
    ...--o--o   <-- master
             \
              A   <-- branch-D
               \
                B--C--D   <-- ???
    

    也就是说,我们需要回答一个问题。名称 branch-C 将指向某个特定的提交。当我们后退三步时,我们应该到达提交A 吗?或者我们应该到达master 的最后一次提交?


    如果第一张图是对的,答案很简单:取新名字branch-C,指向commit D;然后强制现有名称branch-D 移回提交A。为此:

    git branch branch-C branch-D  # copy the hash ID from branch-D to new branch-C
    

    然后,根据现在签出的分支,选择:

    git reset --hard <hash-of-A> # move current branch; re-set index and work-tree
    

    或:

    git branch -f branch-D <hash-of-A> # force branch-D to point to A
    

    请注意,在使用git reset --hard 之前,最好确保您没有任何要保存的修改。虽然 commits 几乎是永久性的(您可以将它们取回,通常至少 30 天,即使您将它们从所有分支名称中踢掉),git reset --hard clobbers 的索引和工作树是不是

    如果你想要第二张图片——如果你想让提交 B 的父级不是提交 A——你必须复制提交 B 到一个新的、不同的提交,即“像B,但是......”。原始B 和副本之间的差异将包括更改的父哈希 ID。

    对于,您将需要使用git cherry-pick 或等效项(例如git rebase,这基本上是一个复制大量提交的整体樱桃挑选操作)。为此,您可以:

    git checkout -b branch-C master
    

    给予:

    ...--o--o   <-- branch-C (HEAD), master
             \
              A--B--C--D   <-- branch-D
    

    然后运行三个git cherry-pick命令复制BCD;或更简单——这使用&lt;commit&gt;~&lt;number&gt; 表示法:

     git cherry-pick branch-D~3..branch-D
    

    一次复制所有三个。这会产生:

              B'-C'-D'   <-- branch-C (HEAD)
             /
    ...--o--o   <-- master
             \
              A--B--C--D   <-- branch-D
    

    此时,强制branch-D 指向提交A 是安全的:

    git branch -f branch-D branch-D~3
    

    您也可以像前面的示例一样通过哈希 ID 来做到这一点:

    git branch -f branch-D <hash-of-A>
    

    我们对branch-D~3 所做的只是告诉 Git:倒数三个父步骤,一次一个父步骤。所以我们从D开始,倒数到C,再倒数到B,再倒数到A

    【讨论】:

      【解决方案4】:

      你可以使用:

      git checkout hashOfCommitA
      

      然后

      git checkout -b NewBranchName
      

      现在你有了新的提交 A 的分支。然后你可以返回提交 D

      git checkout nameOfFirstBranch
      

      然后从这个分支恢复提交 A

      git revert hashOfCommitA
      

      现在你必须在其中一个上进行分支,只有提交 A,而另一个是提交 b、c 和 d。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2017-12-02
        • 2021-10-15
        • 2016-12-20
        • 1970-01-01
        • 2011-01-28
        • 2011-11-10
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多