Git:从上游开发分支进行rebase到开发分支

15

我有本地的masterdevelop分支。我在develop上进行所有工作,然后将其合并到master以发布版本。有一个远程分支upstream/master,其中包含我想要的更改,但我想要在其更改之上对develop中的更改(它们共享一个公共祖先)进行变基,并将其放回到develop中。我已经执行了git fetch upstream

Git分支重定基章节指出应该执行以下操作:

$ git checkout experiment
$ git rebase master

我(假设)在我的情况下意味着:

$ git checkout upstream/master
$ git rebase develop

但是这样我就会在upsteam/master分支上并处于脱离状态。然而,如果我从develop分支进行upstream/master的合并,那么更改将在develop上进行。

$ git checkout develop
$ git merge upstream/master

这种变基的方式对我来说似乎是倒退的。我想要将我在develop分支上所做的更改,在类似于合并的情况下,与upstream/master分支的更改一起变基到develop分支。那么我应该在upstream/master上进行变基,解决任何冲突,然后将其添加,存储并弹出到develop分支吗?

3个回答

18

重新定位实际上将是:

git checkout develop
git rebase upstream/master

(git rebase应该被理解为:"在目标分支上(这里是upstream/master),将我的当前分支(这里是develop)重新设置基础,并应用其提交记录。")

最终结果不会是分离头状态,而是新重写的develop分支。


15
最简单(对每个人都很明显)的方法是先更新您的master分支,然后在更新后的master上进行变基,此时masterorigin/master完全相同。
$ git fetch origin                    # Get updates from remote.
$ git checkout master                 # Now bring master into sync:
$ git merge --ff-only origin/master   # if this fails you have stuff
                                      # in your master that they don't
                                      # have in theirs, and you need
                                      # to decide what to do about it

此时,如果一切顺利,masterorigin/master应该是相同的(您可以使用图形查看器(如gitk)或git log --graph --oneline --decorate来查看),并且应该清楚git rebase master将如何工作。
但实际上您并不需要这样做。您只需在develop分支上执行git rebase origin/master即可。(这将使您的master未前进 - 可能在某个时候您会想要前进它 - 所以它并没有为您节省太多时间。但现在做起来更容易。)
长而乏味的“为什么”部分:git rebase在其长格式中需要三个点,文档将其描述为newbaseupstreambranch
git rebase ... [--onto <newbase>] [<upstream>] [<branch>]

如果您像这样指定精确的一个参数 git rebase master,那么会使用该参数作为upstream,同时计算newbasebranch。其中,branch是当前分支(即HEAD,不能是分离的)。如果省略--onto,则newbase将被视为upstream参数。因此,如果您现在在develop上,并运行git rebase X,则branchdevelop,而newbaseupstream都是X

重新设置基础库方法实际上是:

  1. 检出branch
  2. 保存对应于branch指向的提交的引用(ORIG_HEAD
  3. 重置它(类似于git reset --hard)到newbase
  4. 对于upstream..ORIG_HEAD中的每个提交(按从旧到新的顺序),git cherry-pick该提交以将其添加到刚刚重置的分支中。

因此,就像文档中所述:

   Assume the following history exists and the current branch is "topic":

                 A---B---C  HEAD=topic
                /
           D---E---F---G    master

当你执行git rebase master命令时,会得到以下结果:
                   A---B---C         ORIG_HEAD
                  /
                 /       A'--B'--C'  HEAD=topic
                /       /
           D---E---F---G             master

(我所做的只是在手册示例中添加了ORIG_HEAD标签和HEAD=,以显示原始提交“仍然存在”,并且HEAD是对主题的引用。)
那么,如果您有您的develop和master,并且它们有它们的master,其中有一些额外的更改会发生什么? 让我们画出来:
A -- B -- C                         master
          | \
          |   D                     origin/master
          |
          E -- F                    HEAD=develop

现在你需要执行以下命令:git rebase origin/master
A -- B -- C                         master
          | \
          |   D                     origin/master
          |     \
          |       E' -- F'          HEAD=develop
          |
          E -- F                    ORIG_HEAD

在某个时刻,您最终将自己的 master 移动到指向提交 D(并丢弃 ORIG_HEAD),结果如下:

A -- B -- C -- D                    master, origin/master
                 \
                   E' - F'          HEAD=develop

这其实就是一些标签移动了位置。

分支标签只是标签而已,每个标签指向一个(唯一的)提交。提交本身又会指向以前的提交,这就构建了提交树(或者说“提交有向无环图”)。


1Git 的 X..Y 语法隐藏了很多“深度魔法”。它的意思是“从标签 Y 可到达的所有提交,但不可从标签 X 到达的提交”,正是需要挑选樱桃的提交集合,因为这些提交集合在“变基”操作之前是在 branch 上的,而在 upstream 上不存在。初看起来“像是”基于时间顺序的序列,通常人们也会这么理解,但实际上它是基于提交图拓扑结构的。有时这会让人感到困惑:对于那些没有关系的提交树而言,A..B 表示“从 B 可到达的每个修订版本”。


1
关于rebase部分的更详细解释(比我的回答更详细):+1。 - VonC
谢谢,非常有帮助,清晰详细。非常感谢。 - ian
把分支合并到主分支上时,反过来重做这个过程是否是一个好建议?:git checkout master && git pull --rebase origin dev && git push -f - alper
@alper:rebase 的意思是“复制一些提交,然后放弃原始提交以支持新的副本”。如果你在运行 git rebase 时处于 master 上,那么你复制并放弃的提交就是之前存在于你的 master 上的提交。如果这些提交也存在于其他人的 master 上,他们可能会认为这种放弃方式是相当粗鲁的。因此,这真的取决于许多因素。对于所有情况,没有一个正确答案。 - torek
1
如果你准备好了(并非每个人都准备好了),一种可行的方法是完全删除你自己的master分支。这样你就不能错误地使用它。你总是要使用一个不同的名称。当你去创建一个新的分支,或者rebase,或者其他操作时,你将不得不使用名称origin/master来引用“他们”的最新的master提交(无论他们是谁):你运行git fetch origin来更新,之后origin/master就是他们的最新版本。 - torek
甚至不在本地拥有master,就解决了本地master落后的问题。也就是说,我们使用亚历山大大帝解开戈尔底斯结的办法:我们干脆将它完全切掉,根本不必在第一时间去解开它。 :-) - torek

3
您需要的命令是:

git checkout develop
git rebase upstream/master

然而,Git比那更智能,你可以将upstream/master配置为develop的上游跟踪分支:

git branch --set-upstream-to upstream/master develop

现在,当您执行git rebase时,它会自动使用'upstream/master'。但更好的是,这些命令:
git fetch origin
git rebase upstream/master

可以简化为只需执行以下操作:

git pull --rebase

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