Git rebase 基础知识

90

最近开始使用 git rebase,但不确定是否正确。为了这个问题,origin中有两个分支:masternextnext是从master分支创建的。

自从两者上次同步以来,master进行了2次提交,next进行了6次提交:

$ git log --oneline origin/next..origin/master
59b5552 master commit #2
485a811 master commit #1

$ git log --oneline origin/master..origin/next
4ebf401 next commit #6
e9b6586 next commit #5
197ada0 next commit #4
4a2c3c6 next commit #3
040a055 next commit #2
84537bf next commit #1

当我检出 next 分支并执行 git rebase -i origin/master 命令时,会得到以下结果:

$ git status
# On branch next
# Your branch and 'origin/next' have diverged,
# and have 8 and 6 different commits each, respectively.

最后,在执行git pull --rebase之后,来自master的两个提交已经被合并到next中:

$ git log --oneline origin/next..next 
8741d09 master commit #2
485a811 master commit #1

问题:

  1. 这是正确的方法吗?
  2. 为什么在运行pull --rebase之前有8和6个不同的提交?
  3. 是否可能简化此流程?

非常感谢 :)

3个回答

98

让我们从头开始。这是您原始状态的图表:

A-B-C  (主分支, origin/master)
 \
  D-E-F-G-H-I  (次分支, origin/next)

当您检出 next 并将其变基到 origin/master 上时,它在已经存在于 origin/master 上的两个提交之后创建了 6 个新提交。这些新提交以 "主要提交 #2"(我的图表中的 C)作为祖先,而不是它们最初的祖先 A(我的图表中标记为 origin/masterorigin/next 分离的位置),所以它们的哈希值会不同。我相信这就是为什么您会看到 nextorigin/next 有 8 个不同提交的原因:其中包括来自 origin/master 的 2 个提交和 "重新哈希" 的 6 个提交,这些提交最初在 origin/next 上。

在执行 git checkout next ; git rebase -i origin/master 后,您应该会看到以下结果:

A-B-C  (主分支, origin/master)
 \   \
  \   D'-E'-F'-G'-H'-I' (次分支, next)
   \
    D-E-F-G-H-I  (origin/next)

您可以看到 next 确实有 8 个提交不在 origin/next 上,而 origin/next 则有 6 个提交不在 next 上。当然,这只是根据提交的 SHA-1 哈希值来判断的。如果您执行 git diff origin/next next,实际内容应该非常接近 -- 差异应该只显示来自图表中标记为BC的更改。

当你在next分支上执行git pull --rebase命令时,它会从源(远程仓库的origin/next)获取更改,并将当前分支(next)重新应用到该远程分支上。这会导致next分支中存在但不在origin/next分支中的更改出现在新的next分支的origin/next之后,图示如下:
A-B-C  (master, origin/master)
 \
  D-E-F-G-H-I  (origin/next)
             \
              B'-C' (next)
如果这正是您想要的历史记录图形,那么您已经成功了。
然而,我怀疑您真正想要的是中间的图表,特别是如果next是一个功能分支,您正在其中开发项目的下一部分,而master则用于稳定的代码和小错误修复。如果是这样,那么您应该使用git push而不是git pull --rebase来使远程仓库反映您的版本历史,而不是相反。

我认为 git rebase --pull 很像 git pull --rebase。它会先进行一次 fetch,然后执行 git rebase @{u}。虽然这是个谎言,但这是一个简单的思考方式。重点是你的本地分支被重置到了 @{u},然后在重置之前旧分支上的所有本地提交都会被重新应用到上游的内容之上。这允许轻松地将变更推送到上游。 - Seth Robertson
1
在执行 git pull --rebase 命令后,你会得到 A-D-E-F-G-H-I-B'-C'。该命令将强制使你的本地分支(next)包含 @{u}(origin/next)上的所有提交,然后会将 next 分支中独有的内容进行回放(可能是 cherry-pick)到其之上。感谢 Git 的智能处理,它不会尝试创建 D"-E"-F"-G"-H"-I"。 - Seth Robertson
非常抱歉打错了,应该是 git pull --rebase。感谢您的解释,我现在明白发生了什么。我正在执行的流程正确吗?先执行 git rebase 然后再执行 git pull --rebase,还是有其他方法? - David Kuridža
1
@DavidKuridža:你没有必要执行任何一个命令。如果next在ADEFGHI处,而你的目标是ADEFGHIB'C',那么你需要运行的唯一命令是git checkout next; git cherry-pick ...master - Seth Robertson
大卫,我认为在rebase之后跟随pull --rebase是自我否定的,因为它在一个方向上更改历史记录,然后返回到另一方向。在运行任何命令(除了git fetch以更新origin/*)之前要回答的真正问题是:完成时我想让历史记录看起来像什么?一旦您知道了这一点,那么您采取的行动应该朝着那个目标前进。 - Mike Seplowitz
显示剩余2条评论

3

从最简单的步骤开始,将您的分支与主分支合并; 名称;

git-rebase

简介;
git rebase [-i | --interactive] [options] [--exec <cmd>] [--onto <newbase>]
        [<upstream>] [<branch>]
git rebase [-i | --interactive] [options] [--exec <cmd>] [--onto <newbase>]
        --root [<branch>]
git rebase --continue | --skip | --abort | --edit-todo

描述: 假设存在以下历史记录,当前分支为“sample”:

 A---B---C sample
         /
    D---E---F---G master

从此处开始,以下任意一条命令都将产生相同的结果:
git rebase master
git rebase master sample

would be:

A'--B'--C' sample
                 /
    D---E---F---G master

注意: 后一种形式只是 git checkout sample 后跟着的 git rebase master 的简写。当 rebase 完成后,sample 将保留为当前分支。

如果上游分支已经包含了您所做的更改(例如,因为您发送的补丁已被上游应用),则该提交将被跳过。例如,在以下历史记录上运行 'git rebase master'(其中 A' 和 A 引入相同的更改集,但具有不同的提交人信息):

A---B---C sample
         /
    D---E---A'---F master

将会导致:
 B'---C' sample
              /
D---E---A'---F master

这些都是关于rebase过程的图解理解。一旦你在输入git rebase master后解决了发现的冲突,就要解决冲突并输入git add -u将更改的代码添加到存储库中。之后执行命令git rebase --continue并继续解决冲突和重复该命令。

git add -u 

并且。
git rebase --continue 

直到没有冲突被发现。 最后的命令将是:

git push --force origin sample(your branch name)

1
外部参考请访问 https://www.kernel.org/pub/software/scm/git/docs/git-rebase.html - Sam
3
请重新调整您的图表,使其更加清晰易懂。 - chill appreciator
@Sam,你的回答中有git命令,即git rebase [-i | --interactive] [options]Synopsis部分下。你能告诉我理解这些参数的规则吗?例如,|代表什么?[...]又代表什么?这些规则在你提到的在线文档中在哪里可以找到? - Istiaque Ahmed

1

我并不总是使用git rebase,但当我使用时,我会使用这个 "hammer":

o---o---o---o---o  A
     \
      o---o---o---o---o  B
                       \
                        o---o---o  C

git rebase -r B C --onto A    # C should be branch, not hash

o---o---o---o---o  A
    |            \
    |             o'--o'--o'  C
     \
      o---o---o---o---o  B

不再为每种情况下最佳的git rebase方法而感到紧张(“我应该使用rebase -i吗?我应该使用cherry-pick吗?”),并花费时间查找它们。

当然,这并不适用于所有人,例如,在我们想要拆分提交的情况下,有些人不喜欢{{link1:“在新分支上做一堆奇怪的工作”}}。但是有些人,比如我,{{link2:“对交互式方法有恐惧症”}}。

不要害怕做1000种rebase的人,而是害怕做1000次rebase的人。 ~不是李小龙


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