GIT rebase 需要重新提交更改

GIT rebase requires to recommit changes

首先,我是 git 分支的新手。我不知道每个功能分支都应该从 master 分支出来,并且只使用与下一个功能分支有这种关系的先决条件功能分支。

我有三个分支。 masterfeature-1feature-2 都推送了一个 Bitbucket 存储库(启用了问题跟踪)。问题是提交 M4M5 是关键提交,所有分支都应在提交合并之前对其进行变基(git rebase 的任务)

M1 -- M2 -- M3 -- M4 -- M5   [master]
  \        /
   A1 --- A2                 [feature-1]
           \
            B1 -- B2 -- B3   [feature-2]

feature-2 的开发已经完成,现在需要合并到 master。这是我为 feature-2 变基 M4M5 提交到 feature-2.

的任务的优先级
  1. git push - feature-2
  2. 的发展
  3. git checkout feature-2
  4. git rebase master
  5. 解决冲突
  6. git pull

执行完这些步骤后,我在执行 git status 后注意到了。我必须再次推送所有提交(feature-2M4M5 和冲突提交)。好吧,我所要做的就是 git push 并发起拉取请求,我就完成了,对吧?但这会向问题跟踪器添加另一个 git 提交评论。

有没有办法将 feature-2 变基为 master 而无需再次推送 feature-2M4M5 的提交以及git log 应该包含冲突提交。

更新

Is there a way to rebase feature-2 to master without the need to push again the commits of feature-2

不是真的,考虑到 rebase 将在 master 之上重播 feature-2 的提交,给你这个:

M1 -- M2 -- M3 -- M4 -- M5   [master]
  \        /              \
   A1 --- A2 [feature-1]   A1' --- A2'
                                     \
                                      B1' -- B2' -- B3'   [feature-2]

' 符号所示,所有提交都已更改(它们的 SHA1 不同)。

因为 A1A2 已经合并到 master,更好的 rebase 应该是

git rebase --onto master A2 feature-2

那会得到:

M1 -- M2 -- M3 -- M4 -- M5   [master]
  \        /              \
   A1 --- A2 [feature-1]   B1' -- B2' -- B3'   [feature-2]

您应该 git push --force 新修订的 feature-2 分支以更新您的拉取请求。

强制推送应该更新 Bitbucket 中的拉取请求,因为它自 2012 年第 4 季度起支持此类推送 (issue 4913)。
强制推送不会产生任何其他不利影响,考虑到您正在将分支推送到 您的 分支,据推测,您是唯一推送更新的人。
由于拉取请求将使用 feature-2 的新历史自动更新自身,这不会损害所述拉取请求。

既然你已经推送了两个功能分支,你根本不应该变基。强烈建议不要对已发布的分支进行变基,因为它会破坏其他开发人员的存储库。原因是变基只是提交的 完全重写 。您要重新设置基准的提交会重新创建,内容会发生变化,而且——最重要的是——会使用不同的哈希值。这会导致新提交与旧提交不兼容,因此拥有旧提交的人最终会与替换它们的新提交发生冲突。

正确的解决方案是简单地合并更改。虽然这可能最终看起来不那么漂亮,但它是一个 non-destructive 操作,其中没有更改现有的提交。所发生的只是提交 添加 这将不会导致推或拉时出现问题。

也就是说,您 可以 变基并仍然发布更改的分支。但要做到这一点,您将需要 force-push 分支,而其他提取这些更改的开发人员将需要将他们的分支重置为新版本。


将我在下面的一些评论合并到答案中:重要的是要了解在 Git 中,分支只是指向提交的指针。整个历史——没有分支——是一个大的非循环图,其中提交只指向他们的 parent 提交。因此,以问题中的示例为例,这是历史,没有任何分支指针:

A -- B -- C -- D -- E
 \       /
  F --- G
         \
          H -- I -- J

每个字母代表一个提交,所有与左边相关的提交都是它的parent。因此,例如 F 的 parent 是 A,而 C 是 parent 的 B 和 [=22= 的合并提交].

现在,如果我们向该可视化添加分支,那么我们只需添加指向某些提交的指针。真的没什么别的(分支实际上只是一个包含提交哈希的文件):

                  master
                    ↓
A -- B -- C -- D -- E
 \       /
  F --- G  ← feature-1
         \
          H -- I -- J
                    ↑
                feature-2

现在,假设我们提交到 feature-2 分支。我们将该提交添加到树中......

         \
          H -- I -- J -- K
                    ↑
                feature-2

…然后我们将分支指针向前移动一位:

         \
          H -- I -- J -- K
                         ↑
                     feature-2

现在,要了解推送期间发生的情况,我们需要记住远程分支也只是分支,因此只是另一组指针。所以它实际上看起来像这样:

         \
          H -- I -- J ----------- K
                    ↑             ↑
           origin/feature-2    feature-2

我想您现在可以想象在推送期间会发生什么:我们告诉远程存储库更新其分支指针,使其指向 K。但是服务器只有 J,因此我们需要为服务器提供所有内容以构建可由 K 访问的树(因此中间的任何其他提交,以及这些提交的所有实际内容)。但当然我们不需要物理上推动 J,或 H,甚至 A(尽管这些都是 技术上 feature-2 分支,因为你可以 到达 他们); Git 足够聪明,可以找出实际上缺少哪些 objects(您可以在开始推送时看到 Git 计算)。

所以一旦我们将丢失的 objects 传输到远程存储库,我们就会告诉远程存储库更新它的 feature-1 指针,所以它也会指向 K。如果成功,我们还会更新我们的远程分支 (origin/feature-2) 以指向它(只是为了同步)。

现在,合并的情况确实是一样的。想象一下,我们将 master 合并到 feature-2(在 feature-2 上使用 git merge master):

                  master
                    ↓
A -- B -- C -- D -- E -----
 \       /                 \
  F --- G  ← feature-1      \
         \                   \
          H -- I -- J -- K -- L
                              ↑
                          feature-2

现在,如果我们想要推送 feature-2,我们需要再次为远程存储库提供它没有的所有 objects。由于我们现在正在进行合并提交,我们需要检查 all parents:因此,如果服务器没有 K,我们将需要推送K;而且,如果它没有 E,我们将不得不推送 E。当然,我们需要再次跟踪那些 parent,以确保所有 objects 都存在于遥控器上。一旦完成,我们只需再次告诉遥控器更新分支指针。

因此总结一下:分支 包含 所有提交,这些提交可以通过在非循环树中导航其提交的 parent 以某种方式访问​​。但即使这意味着分支通常非常“大”(在历史长度上),Git 只会将那些 objects 转移到它没有的远程存储库。因此,尽管合并可以向分支添加更多提交,但如果远程已经从另一个分支知道它们,则不一定必须传输这些提交。


最后,关于变基的一些最后的话:上面我们git merge mastermaster分支合并到feature-2中。如果我们改为 git rebase master,那么完整的树现在看起来像这样:

                  master               feature-2
                    ↓                      ↓
A -- B -- C -- D -- E -- H' -- I' -- J' -- K'
 \       /
  F --- G  ← feature-1
         \
          H -- I -- J -- K
                         ↑
                  origin/feature-2

如您所见,有新提交 H'I'J'K'。这些是重写的提交,因此它们从 E(其中 master 在变基时指向)而不是 G 开始。由于我们重新定位,因此没有合并提交 L。如上所述,原始提交仍然存在。只是没有指针指向它们;所以它们“丢失”了,最终会被垃圾收集。

那么现在推送有什么问题呢?远程分支仍然指向原来的 K,但我们希望它现在指向 K'。所以我们开始为远程存储库提供它需要的所有 objects ,就像以前一样。但是当我们告诉它设置更新分支指针时,它会拒绝这样做。这样做的原因是,通过将指针设置为 K',它必须“回到历史”并忽略提交 HK 的存在。它不知道我们已经对它们进行了 rebase,并且重写的和原始的之间也没有 link。所以为了防止数据意外丢失,远程会拒绝更新分支指针。

现在,您可以强制推送 分支。这将告诉远程存储库更新分支指针,即使这样做会丢弃那些原始提交。所以你这样做,情况将是这样的:

                                   origin/feature-2
                  master               feature-2
                    ↓                      ↓
A -- B -- C -- D -- E -- H' -- I' -- J' -- K'
 \       /
  F --- G  ← feature-1

到目前为止,一切都很好:您决定对分支进行变基,并告诉远程存储库接受它而不质疑它。但现在想象一下,我想 那个;我的分支仍然指向 I。所以 运行 pull 与反向 push 的作用相同:遥控器为我提供了完成历史记录所需的所有 objects,然后它告诉我在哪里设置分支指针。那时,我的本地 Git 拒绝这样做,原因与远程存储库之前这样做的原因相同。

通过之前的推送,我们知道我们想要替换原来的提交;但是我们没有那个,所以我们现在需要调查或四处询问,我们是否应该只更换我们的本地分支,或者远程是否确实存在一些故障。如果我们在本地自己完成了一些我们现在想合并的工作,情况会变得更糟。

这些问题发生在每个曾经获取过那些原始提交的人身上。一般来说,你想完全避免这种混乱,所以规则是永远不要 rebase 已经发布的东西。只要没有其他人获得这些原始提交,您就可以变基,但一旦情况不再如此,对所有相关人员来说都会一团糟。所以合并肯定是首选。

据我了解,您的代码库(团队)的规则要求您根据 master 重新设置您的功能分支。你可以通过说 git rebase --onto master A2 feature-2 来做到这一点,这意味着 "take commits of the feature-2 starting with A2 exclusive and place them on top of the master"。然后,您可以将更改直接推送到 master 并删除或保持 feature-2 分支不变,同样取决于工作流约定。

如果另一方面,不需要变基 - 您可以将 feature-2 简单合并到 master,将更改推送到 master,因为@poke推荐。