代码之家  ›  专栏  ›  技术社区  ›  four-eyes

从特定提交开始创建新分支

  •  1
  • four-eyes  · 技术社区  · 7 年前

    我想从某个提交开始创建一个新分支。然而,我希望所有后来作出的承诺也成为该分支的一部分。

    假设这是 branch-D 所有的承诺。

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

    我想删除 commit-B 到一个新的 branch ,让我们命名它 branch-C ,只保留 commit-A 在…上 分支-D

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

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

    4 回复  |  直到 7 年前
        1
  •  4
  •   torek    7 年前

    Git的奇特之处在于它的分支 姓名 不要影响你的 提交历史记录 除了他们允许你和其他人,这是非常重要的 发现 提交。秘密在于,在Git中,提交可能是开启的 许多的 分支机构 同时 .

    然而,重要的是,一旦做出承诺,就永远无法实现 改变 . 它可能是 已复制 提交(新的,略有不同),但它不能更改。

    此外,当任何分支名称 可以 强制指向任何提交,分支名称运动有一个“正常方向”:通常,它们仅 进展 . 正如我们稍后将看到的,前进意味着无论他们过去指向什么提交,都仍然“在”那个分支上。所以,一旦你把你的承诺交给其他人,并要求他们称之为承诺 branch-D ,很难获得 另外 Git收回这一点。因此:

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

    是的,可能有一个非常简单的方法:“还没有推动任何事情”意味着你是 只有一个 谁有这些承诺,所以无论你如何找到它们,这就是为什么 每个人 找到他们,因为你是“所有人”。-)没有必要让其他人改变任何事情。

    只要它们已经按照您想要的方式进行了布局,您就只需要重新安排找到这些提交的方式。

    让我们用“Git方式”绘制现有的提交序列:

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

    在Git中,每个提交都由其散列ID唯一标识。这些东西又大又难看,显然是随机的(尽管它们实际上是完全确定的),所以我们很少使用它们,或者使用类似的缩写形式 d1c9d3a . 相反,让我们调用最后一个提交 D .

    内部提交 D ,还有另一个散列ID,标识 D 父提交 . 假设是 c033bae ,但让我们称之为“承诺” C 所以我们这么说 D 指向 C .

    同样地, C 点返回到 B ,它指向 A ,它指向。。。好吧,也许 master 你没有说,但现在让我们假设:

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

    这是一种更紧凑的绘制方式。总是,必然,点 向后 ,所以我们真的不需要内部箭头,我们知道它们总是倒退。树枝 姓名 然而,就像 主人 分支-D ... 好吧,我们可以说明这一点 在任何地方

    Git通过从分支名称指向的分支开始查找提交: D 对于 分支-D ,或最终 o 在第一行 主人 . 然后它查看当前提交的父级,以向后移动。然后它查看父对象的父对象,依此类推。因此,这两个 o 提交在两者上 主人 分支-D :我们可以从 主人 ,或者我们可以从 分支-D

    这意味着我们 希望 看起来,可能是这样:

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

    这里是上的提交 也在两个分支上,并提交 A. ,现在是 分支-D ,仍处于打开状态 branch-C 也只是不是 提示 属于 分支-C 再。


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

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

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


    如果第一张图片是对的,答案很简单:取一个新名字, 分支-C ,指向提交 D 分支-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 ,这是一个非常好的主意,以确保您没有任何修改,您要保存。虽然 提交 几乎是永久性的(你可以把它们取回,通常至少30天,即使你把它们从所有的分支名称中去掉),索引和工作树 git重置--硬 打击者是 .

    但是,如果你想要第二张图片,如果你想要提交的父对象 B 不是承诺 A. 你必须 复制 犯罪 B 对一个新的、不同的承诺,这就像 B 但是“。与原件之间的差异 B 副本将包括更改的父哈希ID。

    那个 ,您需要使用 git cherry-pick 或同等产品(例如 git rebase ,这基本上是一个大规模的cherry pick操作,它复制了许多提交)。为此,您可以:

    git checkout -b branch-C master
    

    给:

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

    然后运行三个 git樱桃镐 要复制的命令 , C D ; 或Simpler这使用 <commit>~<number> 符号:

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

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

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

    在这一点上,强制是安全的 分支-D 指向提交 A. :

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

    您也可以通过哈希ID来实现这一点,如前面的示例所示:

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

    我们所做的一切 branch-D~3 正在告诉Git: 倒数三个父步骤,一次一个父步骤 . 所以我们从 D ,倒数一步 ,倒数下一步 B ,然后倒数第三步 A. .

        2
  •  2
  •   Mark Adelsberger    7 年前

    使现代化 -我误读了你的图表,结果把分支名称弄错了。正如torek所指出的,对图表进行“更正确”的阅读还包括一些模糊之处。我将保留我的答案,因为我相信它传达了主要原则;如果你需要进一步编辑,我会尽量使用rebase branchC 历史但要获得更详细的答案,请参阅torek的回复。


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

    所以你现在有了

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

    我想你想以 只有 A 在…上 branchD ,并为 B , C D . 第一步是 (或 D )签出,创建新分支

    git branch branchC
    

    然后移动 branchD公司 分支返回到 A. .

    git reset --hard HEAD~3
    

    (I使用 HEAD~3 因为在这个例子中,这将是 A. . 这取决于从中执行的提交数 master 的历史。使用的提交ID(SHA)总是可以的 A. 代替 水头~3 .)

    完成这些步骤后,您就可以

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

    哪一个 看起来像 你所描述的,但我们没有达到描述所暗示的方式。

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

    问题中前后图的模糊性确实留下了一个可能性,即我误解了确切的内容 布兰奇 布兰奇 A. ,那么你必须重写 B , C D -你可以很容易地用 git rebase -i . 创建后 布兰奇 和移动 branchD公司

    git rebase -i branchD^ branchC
    

    在这里 branchD^ 是“之前提交”的可能名称 A. . 如果有这样的提交,它可能有其他名称-也许 主人 ,或者它的提交ID 这样的承诺,那么我想你可以说

    git rebase -i --root branchC
    

    (但在这种情况下,尝试删除 A. 布兰奇 历史可能毫无意义,所以我怀疑这就是这里发生的事情)。

    rebase命令将打开一个文本编辑器,其中包含一个待办事项列表,每个提交都有一个条目。您可以删除提交条目 A. 从列表中,然后保存并退出。这不会打扰 branchD公司 -它仍然指向 A. -但这将创造一个新的历史 布兰奇 通过复制 B , D . 那么你应该

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

    无论如何,做你想做的事意味着删除 B , C D 从历史上 . 您提到这些提交尚未完成 push ed,所以应该没有问题;但这是一个需要记住的重要区别。每当您想从分支的历史记录中删除提交时,如果远程上的分支还不知道有问题的提交,就更容易做到。

        3
  •  0
  •   Wiciaq123    7 年前

    您可以使用:

    git checkout hashOfCommitA
    

    然后

    git checkout -b NewBranchName
    

    现在,您有了带有提交A的新分支。然后可以返回到提交D

    git checkout nameOfFirstBranch
    

    然后从该分支恢复提交

    git revert hashOfCommitA
    

    现在您必须在其中一个上进行分支,在另一个上只有提交一段时间,即提交b、c和d。

        4
  •  -1
  •   ronjfoxiii    7 年前

    最直接的方法是在提交A之前在提交的基础上创建2个新分支。然后cherry pick提交A到1个分支,cherry pick提交B、C、D到第2个分支。