Git将不同分支中的旧提交推送到新分支

时间:2016-05-03 17:01:17

标签: git github pull-request

我现在已经和GIT挣扎了一段时间,因为它做了一些奇怪的事情。

我有一个带有初始代码的基本提交。

好的下一步我做了一个分支做我的编辑并提交并推送:

git checkout -b feature/feature1
git commit -m "added feature1"
git push origin feature/feature1

现在我对该功能提出拉取请求。

之后我改变分支并更新例如:

git checkout -b chore/update-framework
git commit -m "updated framework"
git push origin chore/update-framework

此时,当我在第二次推送时尝试拉取请求时,它包含第一次提交,其中包含所有功能内容。因此,该提交中的所有已更改文件都在新的pull请求中,而旧的pull请求仅包含要素文件。

我做错了什么?

2 个答案:

答案 0 :(得分:5)

当你这样做时:

git checkout -b chore/update-framework

您没有在主之前结帐

从您想要的提交开始一个新分支(以master为例):

git checkout -b chore/update-framework master

或:

git checkout master
git checkout -b chore/update-framework

或:

git branch chore/update-framework master
git checkout chore/update-framework

要纠正错误,您可以:

git rebase --onto master feature/feature1 chore/update-framework

Git将选择所有chore / update-framework的提交,这些提交未被feature / feature1指向,然后它将把它们放到master上,最后更新你的chore / update-framework分支。

答案 1 :(得分:2)

我认为你的问题归结为理解提交是什么意思(或者说包含在中)可能更好。这在Git中与许多传统版本控制系统不同。

在Git中,每个提交都在(或包含在)许多分支中,至少是潜在的。它可以在没有分支,1个分支,2个分支等等。这是因为,在Git中,分支名称只是指向一个特定提交的可移动指针。把它想象成一个黄色的粘滞便笺。创建新的分支名称意味着抓取空白便签并在其上写下名称feature/feature1

但附注的地方在哪里?好吧,这里绘制(部分)提交有很大帮助。

绘制(部分)提交图

在Git中,每次提交都指向"其前身(或父母承诺)。对于线性提交链,这基本上是一系列向后指向的箭头:

... <- o <- o <- o <- ... <- o

此处的每个o代表一个提交,每个提交都指向其(一个)父级。

合并提交指向两个父项,这实际上是使它成为合并提交的原因。当限制为StackOverflow上的纯文本时,使用箭头绘制起来要困难得多:

           o--o
          /    \
... --o--*      M--o
          \    /
           o--o

想象一下所有线上的箭头,以便它们都指向左边(上下左右,或者左右,如果需要的话)。 M这里是合并提交,有两个父母。我在这里标记了一个提交,提交*。它不是不是合并提交,但它确实有两个子项(两个提交指向它)。这有时会使它更有趣。

事实上,在我们进行合并提交之前,提交*特别有趣。让我们画一下(通过删除M和一些o):

           o
          /
... --o--*
          \
           o--o

将分支名称添加到图纸

这是分支机构真正得到适当关注的地方。让我们添加名称和更长的<--箭头:

           o   <-- bra1
          /
... --o--*
          \
           o--o   <-- bra2

分支bra1指向(或者,相当于粘贴到黄色粘滞便笺上)最高端o提交,bra2指向最低端({1}} {1}}。

我们称为o的提交在两个分支上都是 (就像它左边的每次提交一样)。

这个特殊难题还有两个部分。一个是名称*,另一个是与我们添加新提交和写新分支名称时发生的情况有关。

HEAD

Git中的名称HEAD告诉我们当前的提交和当前分支。 方式 Git这样做几乎是可笑的简单:HEAD通常只包含分支的名称。把它想象成另一个粘滞便笺(可能是亮粉色,或绿色或紫色,只是为了使它显然有点不同)。 可以直接指向提交 - &#34;分离的HEAD&#34;你毫无疑问看到的东西 - 但通常它只是写有一个分支名称:

HEAD

这意味着我们在分支 o <-- HEAD->bra1 / ... --o--* \ o--o <-- bra2

成长分支

让我们在bra1进行新的提交。

在进行新提交时,Git:

  1. 阅读bra1(它说HEAD)。
  2. 从分支branch bra1中读取提交ID(这是一些丑陋的SHA-1 bra1或其他)。
  3. 使用您的日志消息指向该父ID,使用您a1c397f...编辑的任何内容进行新提交。新提交获得一个新的唯一SHA-1(例如git add)。
  4. 将此新号码写入当前分支0bc3112...
  5. 第4步导致bra1指向新提交,现在我们有:

    bra1

    添加合并提交

    为了完整起见,让我们看一下合并提交。

    进行合并本身的过程可能很麻烦,但实际上合并 commit 很容易:它只是一个有两个父母的提交。请注意,我们仍然在 o--o <-- HEAD->bra1 / ... --o--* \ o--o <-- bra2 。我们运行bra1。 Git启动合​​并机制来完成工作树中的工作。如果存在冲突,则会造成混乱,我们必须手动修复,但如果没有,它会自动启动新的提交。

    新提交就像以前一样发生,只做了一个小改动。在步骤3中,不是编写一个父ID(来自现有git merge bra2的父ID),而是写入两个父ID:第一个是通常的ID ,第二个是从阅读bra1获得的ID。

    第4步正常工作,将新提交的ID写入bra2(因为bra1仍然说&#34;使用分支HEAD&#34;):

    bra1

    因为 o--o / \ ... --o--* M <-- HEAD->bra1 \ / o--o <-- bra2 是一个合并(有两个父母),这意味着以前所有提交的M提交现在都在bra2上了!

    提交bra1曾经在两个分支上,但仍然是。而且,从*开始并向左工作,我们仍可以提交这两个*次提交。

    我们只允许向左移动(ish),因此从bra2开始,我们允许移至bra2,这意味着我们无法到达顶部的提交。我们只能从那里开始,或从M开始。但是,我们不仅被允许,而且实际上必需,以跟随所有合并的父母M。所以从M开始,我们得到bra1,然后我们得到分支结构的两边一直回到提交M,并从那里继续向左。

    如果计算节点,您将看到*中包含的三个提交未包含在bra1中...但bra2中包含的所有提交都包含在内在bra2

    好的,那么创建 new 分支呢?

    此处bra1重新发挥作用。

    您可以使用HEAD 创建分支:

    git branch

    默认情况下,这会显示$ git branch bra3 以确定我们现在的位置。 HEAD表示HEADbra1中包含bra1个ID。因此,这会使M指向bra3

    M

    请注意 o--o / \ ... --o--* M <-- HEAD->bra1, bra3 \ / o--o <-- bra2 仍然指向以前的位置,也没有其他分支受到干扰,我们只添加了一个新的可移动标签HEAD。如果我们现在移动bra3bra1,则bra2继续指向bra3

    由于M仍然指向HEAD,新的提交会使bra1更改。

    如果我们使用bra1创建新分支,我们会改为:

    git checkout -b

    这看起来几乎完全一样。不同之处在于,除了添加新的分支名称外,我们更改了 o--o / \ ... --o--* M <-- bra1, HEAD->bra3 \ / o--o <-- bra2 。现在它指向HEAD

    创建一个指向当前提交以外的其他内容的新分支

    让我们回到bra3,它只是创建分支而不会更改git branchHEAD命令还有一个可选参数:

    git branch

    而不是让$ git branch bra4 bra2 指向与bra4相同的提交,而是说#34; make HEAD指向与bra4相同的提交&#34; 。所以现在我们有:

    bra2

    现在让 o--o / \ ... --o--* M <-- bra1, HEAD->bra3 \ / o--o <-- bra2, bra4 ,只是为了看看它做了什么(就图表而言 - 它当然还会检出文件):

    git checkout bra4

    同样,任何分支标签本身都没有发生任何变化。在图表中,我们只需更改$ git checkout bra4 o--o / \ ... --o--* M <-- bra1, bra3 \ / o--o <-- bra2, HEAD->bra4 点的位置。

    (我们本来可以使用合并后的表单 - 我们现在已经有HEAD了,所以现在已经太晚了,但是这可能是一个命令而不是两个 - 通过bra4。 )

    新分支指针的底线

    这意味着每当我们创建新分支时,我们都可以选择提交新分支指向默认是&#34;只要git checkout -b bra4 bra2指向&#34;,这通常意味着读取另一个分支名称。我们可以先将HEAD指向某个有用的地方,或者我们可以将它添加到分支创建者中并使它们一起发生。

    这留下了一个问题,它并不总是有一个正确的答案:我们应该在哪里指出新的分支?

    我们可能希望将其指向HEADorigin/master。有时候选择其他起点更有意义,甚至可能没有标签指向它。

    等等,origin/feature1是标签吗?

    是的,这些东西都是分支标签 - 也是提交的可移动指针。它们不是移动的标签。当你运行origin/master(或拉动,但拉动只是获取,然后是另一个步骤)时,让它们移动。换句话说,他们跟踪遥控器&#34; (如果需要,您git fetchgit fetch origin移动);所以他们是远程跟踪分支

    当我们绘制上面的图表时,我们可能应该包含这些标签......可能是这样的:

    origin/*

    如果您想制作一个从 o <-- HEAD->feature1 / ... --o--* <-- origin/feature1 \ o--o <-- feature2 长出的新feature3*也会这样做。然后,提交git checkout -b feature3 origin/feature1将包含在四个分支中:*feature1origin/feature1feature2