GIT rebase需要重新提交更改

4

首先,我对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任务已经完成,现在需要将其合并到主分支。以下是我为了将M4M5提交记录重新定位到feature-2所做的任务先后顺序。
  1. git push - 开发feature-2
  2. git checkout feature-2
  3. git rebase master
  4. 解决冲突
  5. git pull
在执行完这些步骤后,我发现执行git status之后,必须再次推送所有提交记录(包括feature-2M4M5和冲突提交记录)。那么,我只需要执行git push并发起拉取请求,就行了,对吗?但是这会向问题跟踪器添加另一个git提交注释。
有没有一种方法可以在不需要再次推送feature-2M4M5的提交记录的情况下将feature-2重新定位到master,而且git log应该包含冲突提交记录。 更新
  • 为了更好的澄清,更改了问题详情

是的。但应该是GIT分支而不是GIT-Flow - David B
您可以随时编辑您的问题以进行澄清。 - jub0bs
1
“需要我重新提交更改”是什么意思?为什么需要这样做?还有哪些更改? - Sascha Wolf
更新了我的问题。 - David B
只需从feature-2创建一个新的本地分支,并将其与主分支进行rebase。 - slobobaby
显示剩余2条评论
3个回答

12

由于您已经推送了两个特性分支,因此不应再进行变基。强烈不建议对已发布的分支进行变基,因为它会破坏其他开发人员的存储库。这是因为变基只是提交的一个完全重写。正在变基的提交将被重新创建,内容已更改,并且最重要的是具有不同的哈希。这导致新提交与旧提交不兼容,因此谁拥有旧提交的人最终会与替换它们的新提交产生冲突。

正确的解决方案是简单地合并更改。虽然这可能看起来不太美观,但这是一种非破坏性的操作,在这种操作中没有更改现有的提交。所有发生的事情就是添加提交,这样在推送或拉取时就不会出现任何问题。

话虽如此,您仍然可以进行变基并发布更改的分支。但是,为此,您需要强制推送分支,而拉取这些更改的其他开发人员将需要将其分支重置为新版本。


将我下面的一些评论纳入答案:重要的是要理解,在Git中,分支只是指向提交的指针。整个历史记录——没有分支——是一个大的无环图形,其中提交只指向其父提交。因此,以您在问题中的示例为例,这是没有任何分支指针的历史记录:

A -- B -- C -- D -- E
 \       /
  F --- G
         \
          H -- I -- J
每个字母代表一个提交,所有与其相连且在其左侧的提交称为其父提交。例如,F的父提交是AC是一个合并提交,其父提交是BG
现在,如果我们在此可视化中添加分支,则只需添加指向某些提交的指针即可。这实际上什么都不是(分支就是一个文件,其中包含提交哈希):
                  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

我想你现在可以想象在push期间会发生什么:我们告诉远程仓库更新它的分支指针,使其指向K。但是服务器只有J,所以我们需要给服务器构建可访问K的树中缺失的所有内容(也就是在它们之间的所有提交和实际内容)。但当然我们不需要物理地推送JH或甚至A(虽然它们都是feature-2分支上的技术上的对象,因为你可以到达它们);Git聪明到足以确定哪些对象实际上是缺失的(你可以在开始推送时看到Git正在计算这个过程)。

因此,一旦我们将缺失的对象传输到远程仓库,我们就会告诉远程仓库更新它的feature-1指针,使其也指向K。如果成功了,我们还会更新我们的远程分支(origin/feature-2),使其也指向K(只是为了保持同步)。

现在,合并的情况也是相同的。想象一下我们把master合并到feature-2中(使用git merge master命令处于feature-2分支):

                  master
                    ↓
A -- B -- C -- D -- E -----
 \       /                 \
  F --- G  ← feature-1      \
         \                   \
          H -- I -- J -- K -- L
                              ↑
                          feature-2
现在,如果我们想要推送feature-2,我们需要给远程仓库所有缺少的对象。由于我们现在处于合并提交状态,所以我们需要检查所有的父级:所以如果服务器没有K,我们需要推送K; 但是,如果它没有E,我们也需要推送E。当然,我们还需要再次遵循这些父级,以确保所有对象都存在于远程。一旦完成了这个过程,我们只需再次告诉远程更新分支指针。

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


最后,关于rebase的一些注意事项:上面我们使用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 开始(在 rebase 时 master 指向的位置)而不是 G。由于我们进行了 rebase,因此没有合并提交 L。正如上面明确说明的那样,原始提交仍然存在。只是没有指针指向它们;因此它们是“丢失的”,最终将被垃圾回收。

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

现在,您可以进行 强制推送 分支。这将告诉远程仓库即使这样做会丢弃那些原始提交,也要更新分支指针。因此,您这样做后,情况将如下所示:

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

现在为止一切都很好:你决定将分支 rebasing,并告诉远程仓库接受它,没有质疑。但是现在想象一下我想要 pull;而我的分支仍然指向 I。所以运行 pull 相当于 push 的反向操作:远程给我所有完成历史所需的对象,然后告诉我在哪里设置分支指针。此时,我的本地 Git 拒绝这样做,原因与远程仓库之前拒绝相同。

在 push 之前,我们知道要替换原始提交;但是对于 pull,我们没有这个信息,因此现在需要调查或询问是否应该仅替换本地分支,或者是否实际上在远程存在某些错误。如果我们现在在本地进行了一些工作需要合并,则情况会变得更糟。

这些问题发生在每个获取那些原始提交的人身上。通常,您希望完全避免这种混乱,因此规则是永远不要 rebase 已经发布的内容。只要没有其他人获取到这些原始提交,就可以进行 rebase,但是一旦不再是这种情况,将会对所有涉及的人造成麻烦。因此,强烈建议使用 merge。


通过强制推送,能否向受影响的问题添加另一个提交注释? - David B
如果你使用强制推送,无论你要推送什么内容,在服务器上都会被覆盖,没有任何问题。因此,当存在其他提交时,它们将被简单地删除。这也是为什么你应该避免使用它的原因。 - poke
为什么应该避免这样做,因为这些提交的内容与特性分支相同,只是添加了合并冲突(如果有)? - David B
很好的帖子,poke。我想要一些澄清。假设我在一个新分支上进行了几次提交,并将我的更新分支推送到原始版本;没有其他人会在这个分支上工作;但他们可能已经获取了它。我想彻底删除其中的一个提交(因为它删除了一个我不想从其他机器中删除的文件)。听起来像是其他人可能已经获取了的新提交将在源和其他克隆版本中变得“丢失”,而且不应该有任何问题。对吗? - Patrick Cummins
@PatrickCummins 其他人已经获取了它,这正是问题所在,他们已经知道旧版本,因此如果您用其他东西替换分支,则他们的副本将不兼容。对于您是唯一作者且不会成为他人工作基础的分支,这不会是一个大问题。随后的获取将只更新远程跟踪分支。因此,您可以重新设置此类分支,但仍应避免这样做。这完全取决于在团队中正确传达这一点。如果清楚地说明了使用哪些分支,通常就不会有问题。 - poke
显示剩余9条评论

2
据我了解,你的代码库(团队)的规则要求你将功能分支与master进行rebase。你可以通过运行git rebase --onto master A2 feature-2实现这一点,意思是“从A2(不包括)开始获取feature-2的提交,并将它们放在master的顶部”。然后,你可以直接将更改推送到master,并根据工作流程约定删除或保留feature-2分支。
另一方面,如果不要求rebase,你可以将feature-2简单合并到master中,按照@poke的建议将更改推送到master

1

有没有一种方法可以将feature-2重新基于master,而无需再次推送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 中,更好的变基将是:

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 命令来更新你的 pull request,以便更新新修订的 feature-2 分支。
自2012年第四季度(问题4913)起,Bitbucket 支持强制推送,因此强制推送应该可以更新 pull request。
考虑到你正在将分支推送到你的 fork 中,在那里你很可能是唯一一个推送更新的人,因此强制推送不会产生其他负面影响。
由于 pull request 将自动使用 feature-2 的新历史记录更新自身,因此不会妨碍该 pull-request。

2
不,通常不应该使用 push --force。那是一种不好的事情,通常应该避免,如果建议使用,也应该有警告。当然,如果我们只谈论私人分支仅向用户显示,push --force 可能是可以的,但无论如何都应该提到免责声明。 - eis
在拉取请求方面,强制推送是标准做法。我已经编辑了答案来解释原因。 - VonC
@VonC。这样做会在与“feature-2”相关的受影响问题上创建另一个提交评论吗? - David B
不,对于拉取请求来说,它将用新的历史记录替换旧的历史记录。 - VonC

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接