git 'rebase master' 和 'rebase --onto master' 之间的区别来自从 master 分支派生的分支

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/33942588/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-19 11:33:44  来源:igfitidea点击:

Difference between 'rebase master' and 'rebase --onto master' from a branch derived from a branch of master

gitgit-rebase

提问by punkrockbuddyholly

Given the following branch structure:

鉴于以下分支结构:

  *------*---*
Master        \
               *---*--*------*
               A       \
                        *-----*-----*
                        B         (HEAD)

If I want to merge my B changes (and onlymy B changes, no A changes) into master what is the difference between these two set of commands?

如果我想将我的 B 更改(只有我的 B 更改,没有 A 更改)合并到 master 中,这两组命令有什么区别?

>(B)      git rebase master
>(B)      git checkout master
>(master) git merge B


>(B)      git rebase --onto master A B
>(B)      git checkout master
>(master) git merge B

I'm mainly interested in learning if code from Branch A could make it into master if I use the first way.

我主要感兴趣的是学习如果我使用第一种方式,来自分支 A 的代码是否可以使其成为 master。

回答by torek

Bear with me for a while before I answer the question as asked. One the earlier answers is right but there are labeling and other relatively minor (but potentially confusing) issues, so I want to start with branch drawings and branch labels. Also, people coming from other systems, or maybe even just new to revision control and git, often think of branches as "lines of development" rather than "traces of history" (git implements them as the latter, rather than the former, so a commit is not necessarily on anyspecific "line of development").

在我回答问题之前,请耐心等待一段时间。一个较早的答案是正确的,但存在标签和其他相对较小(但可能令人困惑)的问题,所以我想从分支图和分支标签开始。此外,来自其他系统的人,或者甚至可能只是版本控制和 git 的新手,经常将分支视为“开发线”而不是“历史痕迹”(git 将它们实现为后者,而不是前者,所以提交不一定在任何特定的“开发路线”上)。

First, there is a minor problem with the way you drew your graph:

首先,您绘制图形的方式存在一个小问题:

  *------*---*
Master        \
               *---*--*------*
               A       \
                        *-----*-----*
                        B         (HEAD)

Here's the exact same graph, but with the labels drawn in differently and some more arrow-heads added (and I've numbered the commit nodes for use below):

这是完全相同的图形,但绘制的标签不同,并添加了更多箭头(我已经对提交节点进行了编号以供下面使用):

0 <- 1 <- 2         <-------------------- master
           \
            3 <- 4 <- 5 <- 6      <------ A
                       \
                        7 <- 8 <- 9   <-- HEAD=B

Why this matters is that git is quite loose about what it means for a commit to be "on" some branch—or perhaps a better phrase is to say that some commit is "contained in" some set of branches. Commits cannot be moved or changed, but branch labelscan and do move.

为什么这很重要是因为 git 对提交“在”某个分支上的含义非常松散——或者也许更好的说法是说某个提交“包含在”某些分支集合中。提交不能移动或更改,但分支标签可以并且确实移动。

More specifically, a branch namelike master, A, or Bpoints to one specific commit. In this case, masterpoints to commit 2, Apoints to commit 6, and Bpoints to commit 9. The first few commits 0 through 2 are contained within all three branches; commits 3, 4, and 5 are contained within both Aand B; commit 6 is contained only within A; and commits 7 through 9 are contained only in B. (Incidentally, multiple names can point to the same commit, and that's normal when you make a new branch.)

更具体地说,分支masterAB指向一个具体的承诺。在这种情况下,master指向提交 2、A指向提交 6 和B指向提交 9。前几个提交 0 到 2 包含在所有三个分支中;提交 3、4 和 5 包含在A和 中B;提交 6 只包含在A; 并且提交 7 到 9 只包含在B. (顺便说一句,多个名称可以指向同一个提交,这在您创建新分支时很正常。)

Before we proceed, let me re-draw the graph yet one more way:

在我们继续之前,让我以另一种方式重新绘制图形:

0
 \
  1
   \
    2     <-- master
     \
      3 - 4 - 5
              |\
              | 6   <-- A
               \
                7
                 \
                  8
                   \
                    9   <-- HEAD=B       

This just emphasizes that it's not a horizontal lineof commits that matter, but rather the parent/child relationships. The branch labelpoints to a starting commit, and then (at least the way these graphs are drawn) we move left, maybe also going up or down as needed, to find parent commits.

这只是强调重要的不是提交的水平线,而是父/子关系。分支标签指向一个开始的提交,然后(至少是绘制这些图的方式)我们向左移动,也可能根据需要向上或向下移动,以找到父提交。



When you rebase commits, you're actually copyingthose commits.

当您重新提交提交时,您实际上是在复制这些提交。

Git can never change any commit

Git 永远不能改变任何提交

There's one "true name" for any commit (or indeed any object in a git repository), which is its SHA-1: that 40-hex-digit string like 9f317ce...that you see in git logfor instance. The SHA-1 is a cryptographic1checksum of the contents of the object. The contents are the author and committer (name and email), time stamps, a source tree, and the list of parent commits. The parent of commit #7 is always commit #5. If you make a mostly-exact copy of commit #7, but set its parent to commit #2 instead of commit #5, you get a different commit with a different ID. (I've run out of single digits at this point—normally I use single uppercase letters to represent commit IDs, but with branches named Aand BI thought that would be confusing. So I'll call a copy of #7, #7a, below.)

任何提交(或实际上是 git 存储库中的任何对象)都有一个“真实名称”,即它的 SHA-1:例如9f317ce...您在40 位十六进制数字中看到的字符串git log。SHA-1 是对象内容的加密1校验和。内容是作者和提交者(姓名和电子邮件)、时间戳、源代码树和父提交列表。提交 #7 的父项总是提交 #5。如果您制作提交 #7 的大部分精确副本,但将其父项设置为提交 #2 而不是提交 #5,您将获得具有不同 ID 的不同提交。(此时我已经用完了单个数字——通常我使用单个大写字母来表示提交 ID,但是命名了分支AB我认为这会令人困惑。所以我将调用 #7、#7a 的副本,

What git rebasedoes

有什么git rebase作用

When you ask git to rebase a chain of commits—such as commits #7-8-9 above—it has to copythem, at least if they're going to move anywhere (if they're not moving it can just leave the originals in place). It defaults to copying commits from the currently-checked-out branch, so git rebaseneeds just two extra pieces of information:

当您要求 git 对一系列提交进行 rebase 时——例如上面的提交 #7-8-9——它必须复制它们,至少如果它们要移动到任何地方(如果它们不移动,它可以只离开原件到位)。它默认从当前签出的分支复制提交,因此git rebase只需要两条额外的信息:

  • Which commits should it copy?
  • Where should the copies land? That is, what's the target parent-ID for the first-copied commit? (Additional commits simply point back to the first-copied, second-copied, and so on.)
  • 它应该复制哪些提交?
  • 副本应该在哪里?也就是说,第一次复制提交的目标父 ID 是什么?(额外的提交只是指向第一次复制,第二次复制,依此类推。)

When you run git rebase <upstream>, you let git figure out both parts from one single piece of information. When you use --onto, you get to tell git separately about the both parts: you still supply an upstreambut it doesn't compute the targetfrom <upstream>, it only computes the commits to copyfrom <upstream>. (Incidentally, I think <upstream>is not a good name, but it's what rebase uses and I don't have anything way better, so let's stick with it here. Rebase calls target<newbase>, but I think targetis a much better name.)

当您运行 时git rebase <upstream>,您让 git 从一条信息中找出两个部分。当您使用 时--onto,您可以分别告诉 git 关于这两个部分:您仍然提供 anupstream但它不计算目标from <upstream>,它只计算从复制提交<upstream>。(顺便说一句,我认为<upstream>这不是一个好名字,但它是 rebase 使用的,我没有更好的方法,所以让我们在这里坚持下去。Rebase 调用target<newbase>,但我认为target是一个更好的名字。)

Let's take a look at these two options first. Both assume that you're on branch Bin the first place:

我们先来看看这两个选项。两者都假设您B首先在分支上:

  1. git rebase master
  2. git rebase --onto master A
  1. git rebase master
  2. git rebase --onto master A

With the first command, the <upstream>argument to rebaseis master. With the second, it's A.

第一个命令的<upstream>参数rebasemaster。对于第二个,它是A.

Here's how git computes which commits to copy: it hands the current branch to git rev-list, and it also hands <upstream>to git rev-list, but using --not—or more precisely, with the equivalent of the two-dot exclude..includenotation. This means we need to know how git rev-listworks.

以下是 git 如何计算要复制的提交:它将当前分支传递给git rev-list,它也<upstream>传递给git rev-list,但使用--not- 或者更准确地说,与两点exclude..include表示法等效。这意味着我们需要知道它是如何git rev-list工作的。

While git rev-listis extremely complicated—most git commands end up using it; it's the engine for git log, git bisect, rebase, filter-branch, and so on—this particular case is not too hard: with the two-dot notation, rev-listlists every commit reachable from the right-hand side (including that commit itself), excluding every commit reachable from the left-hand side.

Whilegit rev-list极其复杂——大多数 git 命令最终都会使用它;它是git log, git bisect, rebase, filter-branch, 等等的引擎——这个特殊情况并不太难:用两点表示法,rev-list列出从右侧可以到达的每个提交(包括那个提交本身),排除从右侧可以到达的每个提交左手边。

In this case, git rev-list HEADfinds all commits reachable from HEAD—that is, almost all commits: commits 0-5 and 7-9—and git rev-list masterfinds all commits reachable from master, which is commit #s 0, 1, and 2. Subtracting 0-through-2 from 0-5,7-9 leaves 3-5,7-9. These are the candidate commits to copy, as listed by git rev-list master..HEAD.

在这种情况下,git rev-list HEAD查找所有可访问的提交HEAD——即几乎所有提交:提交 0-5 和 7-9——并git rev-list master查找所有可访问的提交master,即提交 #s 0、1 和 2。减去 0-through- 2 从 0-5,7-9 离开 3-5,7-9。这些是要复制的候选提交,由 列出git rev-list master..HEAD

For our second command, we have A..HEADinstead of master..HEAD, so the commits to subtract are 0-6. Commit #6 doesn't appear in the HEADset, but that's fine: subtracting away something that's not there, leaves it not there. The resulting candidates-to-copy is therefore 7-9.

对于我们的第二个命令,我们有A..HEAD代替master..HEAD,因此要减去的提交是 0-6。Commit #6 没有出现在HEAD集合中,但这很好:减去一些不存在的东西,让它不存在。因此,复制的候选结果为 7-9。

That still leaves us with figuring out the targetof the rebase, i.e., where should copied commits land? With the second command, the answer is "the commit identified by the --ontoargument". Since we said --onto master, that means the target is commit #2.

这仍然让我们弄清楚rebase的目标,即复制的提交应该在哪里?对于第二个命令,答案是“由--onto参数标识的提交”。既然我们说--onto master,这意味着目标是 commit #2。

rebase #1

变基 #1

git rebase master

git rebase master

With the first command, though, we didn't specify a target directly, so git uses the commit identified by <upstream>. The <upstream>we gave was master, which points to commit #2, so the target is commit #2.

但是,对于第一个命令,我们没有直接指定目标,因此 git 使用由<upstream>. 在<upstream>我们都给了master,犯这点#2,所以目标是提交#2。

The first command is therefore going to start by copying commit #3 with whatever minimal changes are needed so that its parent is commit #2. Its parent is alreadycommit #2. Nothing has to change, so nothing changes, and rebase just re-uses the existing commit #3. It must then copy #4 so that its parent is #3, but the parent is already #3, so it just re-uses #4. Likewise, #5 is already good. It completely ignores #6 (that's not in the set of commits to copy); it checks #s 7-9 but they're all good as well, so the whole rebase ends up just re-using all the original commits. You can force copies anyway with -f, but you didn't, so this whole rebase ends up doing nothing.

因此,第一个命令将通过复制提交 #3 开始,并带有任何需要的最小更改,以便其父项是提交 #2。它的父级已经提交 #2。没有什么需要改变,所以什么都没有改变,rebase 只是重新使用现有的提交 #3。然后它必须复制 #4 以便它的父级是 #3,但父级已经是 #3,所以它只是重新使用 #4。同样,#5 已经很好了。它完全忽略了#6(这不在要复制的提交集中);它检查 #s 7-9 但它们也都很好,所以整个 rebase 最终只是重新使用所有原始提交。无论如何,您都可以使用 强制复制-f,但您没有这样做,因此整个 rebase 最终什么也没做。

rebase #2

变基 #2

git rebase --onto master A

git rebase --onto master A

The second rebase command used --ontoto select #2 as its target, but told git to copy just commits 7-9. Commit #7's parent is commit #5, so this copy really has to do something.2So git makes a new commit—let's call this #7a—that has commit #2 as its parent. The rebase moves on to commit #8: the copy now needs #7a as its parent. Finally, the rebase moves on to commit #9, which needs #8a as its parent. With all commits copied, the last thing rebase does is move the label(remember, labels move and change!). This gives a graph like this:

第二个 rebase 命令用于--onto选择 #2 作为其目标,但告诉 git 复制只提交 7-9。Commit #7 的父项是 commit #5,所以这个副本真的必须做些什么。2所以 git 进行了一个新的提交——我们称之为 #7a——它以提交 #2 作为其父项。变基继续提交#8:副本现在需要#7a 作为其父项。最后,rebase 继续提交 #9,它需要 #8a 作为其父项。复制所有提交后,rebase 所做的最后一件事就是移动标签(请记住,标签会移动和更改!)。这给出了这样的图表:

          7a - 8a - 9a       <-- HEAD=B
         /
0 - 1 - 2                    <-- master
         \
          3 - 4 - 5 - 6      <-- A
                    \
                     7 - 8 - 9   [abandoned]

OK, but what about git rebase --onto master A B?

好的,但是呢git rebase --onto master A B

This is almost the same as git rebase --onto master A. The difference is that extra Bat the end. Fortunately, this difference is verysimple: if you give git rebasethat one extra argument, it runs git checkouton that argument first.3

这几乎与git rebase --onto master A. 不同之B处在于最后是额外的。幸运的是,这个区别非常简单:如果你给git rebase那个额外的参数,它首先运行git checkout在那个参数上。3

Your original commands

你的原始命令

In your first set of commands, you ran git rebase masterwhile on branch B. As noted above, this is a big no-op: since nothing needs to move, git copies nothing at all (unless you use -f/ --force, which you didn't). You then checked out masterand used git merge B, which—if it it is told to4—creates a new commit with the merge. Therefore Dherik's answer, as of the time I saw it at least, is correct here: The merge commit has two parents, one of which is the tip of branch B, and that branch reaches back through three commits that are on branch Aand therefore some of what's on Awinds up being merged into master.

在您的第一组命令中,您git rebase master在 branch 上运行B。如上所述,这是一个很大的空操作:因为没有什么需要移动,所以 git 根本不复制任何内容(除非您使用-f/ --force,而您没有使用)。然后,您检出master并使用了git merge B,如果它被告知为4,它将创建一个带有合并的新提交。因此,Dherik 的回答,至少在我看到它的时候,在这里是正确的:合并提交有两个父级,其中一个是分支的尖端B,并且该分支通过分支上的三个提交返回A,因此一些正在发生的事情A最终会被合并到master.

With your second command sequence, you first checked out B(you were already on Bso this was redundant, but was part of the git rebase). You then had rebase copy three commits, producing the final graph above, with commits 7a, 8a, and 9a. You then checked out masterand made a merge commit with B(see footnote 4 again). Again Dherik's answer is correct: the only thing missing is that the original, abandoned commits are not drawn-in and it's not as obvious that the new merged-in commits are copies.

使用您的第二个命令序列,您首先检出B(您已经打开,B因此这是多余的,但它是 的一部分git rebase)。然后你有 rebase copy 三个提交,生成上面的最终图表,提交 7a、8a 和 9a。然后您签出master并进行了合并提交B(再次参见脚注 4)。再次 Dherik 的回答是正确的:唯一缺少的是原始的、放弃的提交没有被引入,并且新的合并提交是副本并不明显。



1This only matters in that it's extraordinarily difficult to target a particular checksum. That is, if someone youtrust tells you "I trust the commit with ID 1234567...", it's almost impossible for someone else—someone you may not trust so much—to come up with a commit that has that same ID, but has different contents. The chances of it happening by accident are 1 in 2160, which is much less likely than you having a heart attack while being struck by lightning while drowning in a tsunami while being abducted by space aliens. :-)

1这只重要的是,针对特定的校验和是非常困难的。也就是说,如果信任的某个人告诉您“我信任 ID 为 1234567 的提交...”,那么其他人(您可能不太信任的人)几乎不可能提出具有相同 ID 的提交,但是有不同的内容。意外发生的几率是 2 160 分之一,这比被外星人绑架时被闪电击中而在海啸中淹死而心脏病发作的可能性要小得多。:-)

2The actual copy is made using the equivalent of git cherry-pick: git compares the commit's tree with its parent's tree to get a diff, then applies the diff to the new parent's tree.

2实际副本是使用以下等价的git cherry-pick:git 将提交的树与其父树的树进行比较以获得差异,然后将差异应用于新的父树。

3This is actually, literally true at this time: git rebaseis a shell script that parses your options, then decides which kind of internal rebase to run: the non-interactive git-rebase--amor the interactive git-rebase--interactive. After it's figured out all the arguments, if there's the one left-over branch name argument, the script does git checkout <branch-name>before starting the internal rebase.

3 目前,这实际上是真的:git rebase是一个解析您的选项的 shell 脚本,然后决定运行哪种内部变基:非交互式git-rebase--am或交互式git-rebase--interactive. 在计算出所有参数后,如果还有一个剩余的分支名称参数,脚本会git checkout <branch-name>在开始内部变基之前执行。

4Since masterpoints to commit 2 and commit 2 is an ancestor of commit 9, this would normally not make a merge commit after all, but instead do what Git calls a fast-forwardoperation. You can instruct Git not to do these fast-forwards using git merge --no-ff. Some interfaces, such as GitHub's web interface and perhaps some GUIs, may separate the different kinds of operations, so that their "merge" forces a true merge like this.

4由于master指向提交 2 和提交 2 的点是提交 9 的祖先,因此这通常不会进行合并提交,而是执行 Git 所谓的快进操作。您可以使用 指示 Git 不要执行这些快进git merge --no-ff。某些界面,例如 GitHub 的 Web 界面和一些 GUI,可能会将不同类型的操作分开,因此它们的“合并”会强制进行真正的合并。

With a a fast-forward merge, the final graph for the first case is:

使用快进合并,第一种情况的最终图是:

0 <- 1 <- 2         [master used to be here]
           \
            3 <- 4 <- 5 <- 6      <------ A
                       \
                        7 <- 8 <- 9   <-- master, HEAD=B

In either case, commits 1 through 9 are now on bothbranches, masterandB. The difference, compared to the true merge is that, from the graph, you can see the history that includes the merge.

在任何一种情况下,提交 1 到 9 现在都在两个分支上,master并且B. 与真正的合并相比,不同之处在于,从图中,您可以看到包含合并的历史记录。

In other words, the advantage to a fast-forward merge is that it leaves no trace of what is otherwise a trivial operation. The disadvantage of a fast-forward merge is, well, that it leaves no trace. So the question of whether to allow the fast-forward is really a question of whether you wantto leave an explicit merge in the history formed by the commits.

换句话说,快进合并的优点是它不会留下任何微不足道的操作的痕迹。快进合并的缺点是它不留痕迹。所以是否允许快进的问题其实就是你是否在由提交形成的历史记录中留下显式合并的问题。

回答by Johannes Thorn

Before any of the given operations your repository looks like this

在任何给定操作之前,您的存储库看起来像这样

           o---o---o---o---o  master
                \
                 x---x---x---x---x  A
                                  \
                                   o---o---o  B

After a standard rebase (without --onto master) the structure will be:

在标准变基(没有--onto master)之后,结构将是:

           o---o---o---o---o  master
               |            \
               |             x'--x'--x'--x'--x'--o'--o'--o'  B
                \
                 x---x---x---x---x  A

...where the x'are commits from the Abranch. (Note how they're now duplicated at the base of branch B.)

...x'A分支提交的地方。(注意它们现在是如何在 branch 的基础上复制的B。)

Instead, a rebase with --onto masterwill create the following cleaner and simpler structure:

相反,rebase with--onto master将创建以下更清晰、更简单的结构:

           o---o---o---o---o  master
               |            \
               |             o'--o'--o'  B
                \
                 x---x---x---x---x  A

回答by Dherik

The differences:

区别:

First set

第一套

  • (B) git rebase master

    *---*---* [master]
             \
              *---*---*---* [A]
                       \
                        *---*---* [B](HEAD)
    
  • (乙) git rebase master

    *---*---* [master]
             \
              *---*---*---* [A]
                       \
                        *---*---* [B](HEAD)
    

Nothing happened. There are no new commits in masterbranch since the creation of Bbranch.

什么都没有发生。masterB分支创建以来,分支中没有新的提交。

  • (B) git checkout master

    *---*---* [master](HEAD)
             \
              *---*---*---* [A]
                       \
                        *---*---* [B]
    
  • (master) git merge B

    *---*---*-----------------------* [Master](HEAD)
             \                     /
              *---*---*---* [A]   /
                       \         /
                        *---*---* [B]
    
  • (乙) git checkout master

    *---*---* [master](HEAD)
             \
              *---*---*---* [A]
                       \
                        *---*---* [B]
    
  • (掌握) git merge B

    *---*---*-----------------------* [Master](HEAD)
             \                     /
              *---*---*---* [A]   /
                       \         /
                        *---*---* [B]
    

Second set

第二套

  • (B) git rebase --onto master A B

    *---*---*-- [master]
            |\
            | *---*---*---* [A]
            |
            *---*---* [B](HEAD)
    
  • (B) git checkout master

    *---*---*-- [master](HEAD)
            |\
            | *---*---*---* [A]
            |
            *---*---* [B]
    
  • (master) git merge B

    *---*---*----------------------* [master](HEAD)
            |\                    /
            | *---*---*---* [A]  /
            |                   /  
            *---*--------------* [B]
    
  • (乙) git rebase --onto master A B

    *---*---*-- [master]
            |\
            | *---*---*---* [A]
            |
            *---*---* [B](HEAD)
    
  • (乙) git checkout master

    *---*---*-- [master](HEAD)
            |\
            | *---*---*---* [A]
            |
            *---*---* [B]
    
  • (掌握) git merge B

    *---*---*----------------------* [master](HEAD)
            |\                    /
            | *---*---*---* [A]  /
            |                   /  
            *---*--------------* [B]
    

I want to merge my B changes (and only my B changes, no A changes) into master

我想将我的 B 更改(只有我的 B 更改,没有 A 更改)合并到 master

Be careful what you understand for "only my B changes".

请注意您对“仅我的 B 更改”的理解。

In the first set, the Bbranch is (before the final merge):

在第一组中,B分支是(在最终合并之前):

 *---*---*
          \
           *---*---*
                    \
                     *---*---* [B]

And in the second set your B branch is:

在第二组中,您的 B 分支是:

*---*---*
        |
        |
        |
        *---*---* [B]

If I understand correctly, what you want is only the B commits that are not in A branch. So, the second set is the right choice for you before the merge.

如果我理解正确,您想要的只是不在 A 分支中的 B 提交。因此,在合并之前,第二组是您的正确选择。

回答by sigjuice

git log --graph --decorate --oneline A B master(or an equivalent GUI tool) can be used after each git command to visualize the changes.

git log --graph --decorate --oneline A B master(或等效的 GUI 工具)可以在每个 git 命令之后使用以可视化更改。

This is the initial state of the repository, with Bas the current branch.

这是存储库的初始状态,B作为当前分支。

(B) git log --graph --oneline --decorate A B master
* 5a84c72 (A) C6
| * 9a90b7c (HEAD -> B) C9
| * 2968483 C8
| * 187c9c8 C7
|/  
* 769014a C5
* 6b8147c C4
* 9166c60 C3
* 0aaf90b (master) C2
* 8c46dcd C1
* 4d74b57 C0

Here is a script to create a repository in this state.

这是在此状态下创建存储库的脚本。

#!/bin/bash

commit () {
    for i in $(seq  ); do
        echo article $i > $i
        git add $i
        git commit -m C$i
    done
}

git init
commit 0 2

git checkout -b A
commit 3 6

git checkout -b B HEAD~
commit 7 9

The first rebase command does nothing.

第一个 rebase 命令什么都不做。

(B) git rebase master
Current branch B is up to date.

Checking out masterand merging Bsimply points masterat the same commit as B, (i.e. 9a90b7c). No new commits are created.

签出master和合并B只是指向masterB, (即9a90b7c)相同的提交。不会创建新的提交。

(B) git checkout master
Switched to branch 'master'

(master) git merge B
Updating 0aaf90b..9a90b7c
Fast-forward
<... snipped diffstat ...>

(master) git log --graph --oneline --decorate A B master
* 5a84c72 (A) C6
| * 9a90b7c (HEAD -> master, B) C9
| * 2968483 C8
| * 187c9c8 C7
|/  
* 769014a C5
* 6b8147c C4
* 9166c60 C3
* 0aaf90b C2
* 8c46dcd C1
* 4d74b57 C0

The second rebase command copies the commits in the range A..Band points them at master. The three commits in this range are 9a90b7c C9, 2968483 C8, and 187c9c8 C7. The copies are new commits with their own commit IDs; 7c0e241, 40b105d, and 5b0bda1. The branches masterand Aare unchanged.

第二个 rebase 命令复制范围内的提交A..B并将它们指向master. 此范围内的三个提交是9a90b7c C9, 2968483 C8, and 187c9c8 C7. 副本是具有自己提交 ID 的新提交;7c0e24140b105d、 和5b0bda1。分支masterA不变。

(B) git rebase --onto master A B
First, rewinding head to replay your work on top of it...
Applying: C7
Applying: C8
Applying: C9

(B) log --graph --oneline --decorate A B master
* 7c0e241 (HEAD -> B) C9
* 40b105d C8
* 5b0bda1 C7
| * 5a84c72 (A) C6
| * 769014a C5
| * 6b8147c C4
| * 9166c60 C3
|/  
* 0aaf90b (master) C2
* 8c46dcd C1
* 4d74b57 C0

As before, checking out masterand merging Bsimply points masterat the same commit as B, (i.e. 7c0e241). No new commits are created.

和以前一样,检出master和合并B只是指向masterB, (即7c0e241)相同的提交。不会创建新的提交。

The original chain of commits that Bwas pointing at still exists.

B指向的原始提交链仍然存在。

git log --graph --oneline --decorate A B master 9a90b7c
* 7c0e241 (HEAD -> master, B) C9
* 40b105d C8
* 5b0bda1 C7
| * 5a84c72 (A) C6
| | * 9a90b7c C9    <- NOTE: This is what B used to be
| | * 2968483 C8
| | * 187c9c8 C7
| |/  
| * 769014a C5
| * 6b8147c C4
| * 9166c60 C3
|/  
* 0aaf90b C2
* 8c46dcd C1
* 4d74b57 C0

回答by choroba

You can try it yourself and see. You can create a local git repository to play with:

你可以自己试试看。您可以创建一个本地 git 存储库来使用:

#! /bin/bash
set -e
mkdir repo
cd repo

git init
touch file
git add file
git commit -m 'init'

echo a > file0
git add file0
git commit -m 'added a to file'

git checkout -b A
echo b >> fileA
git add fileA
git commit -m 'b added to file'
echo c >> fileA
git add fileA
git commit -m 'c added to file'

git checkout -b B
echo x >> fileB
git add fileB
git commit -m 'x added to file'
echo y >> fileB
git add fileB
git commit -m 'y added to file'
cd ..

git clone repo rebase
cd rebase
git checkout master
git checkout A
git checkout B
git rebase master
cd ..

git clone repo onto
cd onto
git checkout master
git checkout A
git checkout B
git rebase --onto master A B
cd ..

diff <(cd rebase; git log --graph --all) <(cd onto; git log --graph --all)