我可以安全地将一个分支重新划分为另一个分支然后再掌握吗? [英] Can I safely rebase one branch into other and then to master?

查看:61
本文介绍了我可以安全地将一个分支重新划分为另一个分支然后再掌握吗?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我必须开发分支,然后根据分支A中的代码找到了分支B.

我想将A改组为B,所以我可以继续开发B.

很快A将合并到master(在B之前),但不是现在.然后,当我合并B时,它会否破坏基于A的引用呢?

我可以在master上重新建立B的基准,一切都会好吗,还是我需要做一些特殊的步骤?

解决方案

请注意,git(以及几乎所有其他版本控制系统)在 on 而不是 in 中调用此基础em>到.

您确实可以进行这种变基.但是,重要的是要知道,当您重新构建某些提交时,git实际上将它们复制 new 提交.这会影响共享代码的每个人,如果他们已经具有 old 提交,则 .

图片

让我们假设您现在所拥有的可以这样绘制:

...--N--O--P--Q--R   <-- master
      \     \
       \     J--K    <-- A
        \
         E--F--G     <-- B

此处分支A指向提交K,分支B指向提交G,分支master指向提交R(每个提交现在都具有这些单字母代码,因此我们可以更容易地识别它;实际上,它们的名称是SHA-1散列,如bfcd84f529b...).因此,提交NRmaster上(所有提交都隐藏在N的左侧);提交NOPJK(和以前一样N剩余的所有提交)在分支A上;并提交NEFG(以及N剩余的所有提交一如既往)在分支B上.

合并基地

提交N是分支Bmaster合并基础,而提交P是分支Amaster的合并基础.合并的基础只是 1 分支之后分支的提交,如果像我们刚才那样绘制图形,就很容易看出来.这对您的git rebase很重要,因为git使用的默认方法是查找此合并基础提交,以及在该点之后 copy 提交. (而且,这意味着在进行基础调整之前,绘制图形可能是一个好主意,以便您可以看到要执行的操作.)

复制提交

要自己复制一个提交,请使用命令git cherry-pick.您现在不需要执行此操作-rebase会为您完成操作-但了解其工作原理是一个好主意,因此让我们简要介绍一下.

在git中,每个提交都是一个完全独立的快照.例如,考虑在分支B上提交F.提交F时,包含与项目相关联的完整源代码树.对于提交EG,也是如此.如果我们比较提交E与提交F,我们将看到在提交E的树"和提交 >".同样,如果比较FG,我们会发现从FG的变化.

将提交转换为一组更改(称为 changeset ),使我们可以复制提交.例如,假设我们看到了从NE的情况.然后进一步假设我们签出commit K并进行相同的更改,然后将其提交到新的临时分支上:

...--N--O--P--Q--R   <-- master
      \     \
       \     J--K    <-- A
        \        \
         \        E'   <-- temp branch
          \
           E--F--G   <-- B

我将新提交称为E',而不是给它一个新的字母,因为它是提交E副本.当然,它与提交E并不完全相同.一方面,它具有不同的父级(它以commit K作为其父级);而且它可能与E有很多其他区别:具体来说,在提交OPJK中发生的任何事情,因为我们现在将从N的更改应用于E转到"K中的内容".

Rebase只是重复的副本

git rebase的创作者:

  1. 确定要复制的提交;
  2. 使用临时分支复制它们;和
  3. 重新指向原始分支.

如果先git checkout B然后git rebase A,则在步骤1中标识的提交是在BA之间的合并基数之后,直到分支B尖端的提交. AB在这里是因为您当前的分支是B,而您说的是git rebase A.

锻炼:查看图表并确定BA的合并基础.可能很难发现,但是如果我们像这样重新绘制图形怎么办?

             Q--R    <-- master
            /
...--N--O--P--J--K   <-- A
      \
       E--F--G       <-- B

(从拓扑结构上讲,该图与我们绘制的原始图完全相同,但现在很明显,提交N是合并基础.)

简而言之,它们是提交EFG.因此,变基继续执行第2步并复制这三个提交,将副本放置在分支A的尖端之后(上")(因为您说过git rebase A).

因此,在重新设置基准点的这一点上,我们现在有了:

             Q--R            <-- master
            /
...--N--O--P--J--K           <-- A
      \           \
       \           E'-F'-G'  <-- temp
        \
         E--F--G             <-- B

现在,rebase要做的最后一件事是将当前分支B指向刚刚复制的最后一个提交.这很简单:

             Q--R            <-- master
            /
...--N--O--P--J--K           <-- A
      \           \
       \           E'-F'-G'  <-- B
        \
         E--F--G             [abandoned]

最初的提交,从EG,大部分时候都消失了(它们仍然通过 reflog 保留在分支B中,但是除非您专门查看,否则,不会再看到它们).新副本E'G'已添加到分支A的尖端附近.

这一切为什么重要

所有这些复制和改组都是在您的存储库中的 中进行的,而在其他任何存储库副本中的 not 中进行.这意味着任何同事或集中式服务器都没有这些副本.如果将副本git push复制到集中式服务器,则服务器将获取副本-但是如果服务器具有原来的,现在已废弃的副本,则提交,并且如果服务器将其称为分支B",则必须强制执行-推送到服务器.

执行此强制推送将使服务器丢弃其原始提交副本,并将复制的提交置于适当位置,并将其称为分支B".即使服务器上在提交G之后有一个提交H,分支B指向提交H,也是如此. 换句话说,这可能会丢失"来自服务器的提交.这是第一个难题:您必须与使用服务器的其他所有人进行协调,以免丢失提交.

此外,在进行强制推送之后,和/或如果您的同事共享您的存储库(通过从中提取/拉取),您的同事可能需要调整他们的存储库以使其负责复制提交和移动分支标签.对他们来说,这是一个上游基础,并且他们必须从中恢复.这是第二个难题:您必须与使用分支的其他所有人进行协调,以便他们知道要从上游基础恢复.

简而言之,请确保您所有的共同开发人员对此都表示满意.

如果您有其他人与您一起工作,并且他们正在共享您将要改组的分支机构,只需确保他们都知道这一点即可.如果您将要重新定位的这些提交发布-如果它们完全是您自己的存储库专用的文件-那么这种协调完全不需要任何努力,因为没有其他人可以通知,因此有没有人可以反对.

正在合并,并可能再次重新部署

很快A将合并到master(在B之前),但不是现在.然后,当我合并B时,它会否破坏基于A的引用呢?

同样,回答这些问题的方法是从绘制提交图开始.让我们使用新的,基于基础的图形,然后添加将B合并到master中的合并提交.由于我们要显示合并,因此我们需要再次重新绘制图形,以使连接行更加容易:

...--N--O--P-Q-R             <-- master
            \
             J--K            <-- A
                 \
                  E'-F'-G'   <-- B

现在,我们将运行git checkout mastergit merge A进行新的合并提交M:

...--N--O--P-Q-R--M          <-- master
            \    /
             J--K            <-- A
                 \
                  E'-F'-G'   <-- B

我们在这里不必更改B中的任何内容,我们可以照常继续进行开发.但是,如果我们想要,并且如果我们所有的共同开发人员都同意,那么我们现在可以再次将B重新建立基础,这次是master.

如果我们git checkout B; git rebase master,则rebase将照常从查找Bmaster之间的合并基数开始.合并基础是什么?查看该图,找到masterB结合的点.绝对不是新的合并提交M,但是也不再是N.

分支B上的提交是NOPJKE'F'G'(以及我们提交的所有提交)看不到N的左侧).这部分很简单.棘手的部分是分支master上的提交.它们是NOPQRM,但是也是 JK.这是因为可以通过遵循提交M第二父级来实现这两个提交.

因此,合并基础(定义为两个分支上的最近"提交)实际上是提交K.这正是我们想要的!为了将B重建为master,我们想要 git复制E'F'G'.新副本E''F''G''将出现在M之后,我们将得到此副本:

                    E''-F''-G''   <-- B
                   /
...--N--O--P-Q-R--M               <-- master
            \    /
             J--K                 <-- A
                 \
                  E'-F'-G'        [abandoned]

这里真正的教训是绘制图形,因为它可以帮助您弄清楚发生了什么.


1 至少,只有一个这样的点很简单.在更复杂的图中,可以有多个合并基础.不过,用复杂的图形进行重新计算需要的工作比我在此特殊的SO发布中所能写的要多得多.

I have to development branches and I found branch B depending on code from branch A.

I want to rebase A into B so I can keep on developing B.

Soon A will be merged to master (before B), but not now. Then, when I merge B, will it break the reference from having A rebased on it?

Can I just then rebase B on master and all will be fine or do I need to do any special step?

解决方案

Note that git (and pretty much all other version control systems) calls this rebasing onto, not into.

You may indeed do this kind of rebase. However, it's important to know that when you rebase some commit(s), git actually copies them to new commits. This affects everyone who shares the code, if they already have the old commits.

The picture

Let's suppose that what you have right now can be drawn this way:

...--N--O--P--Q--R   <-- master
      \     \
       \     J--K    <-- A
        \
         E--F--G     <-- B

Here branch A points to commit K, branch B points to commit G, and branch master points to commit R (each commit now has these one-letter codes so that we can identify it more easily; in reality, their names are SHA-1 hashes like bfcd84f529b...). Commits N through R are therefore on master (along with all commits hidden off to the left of N); commits N, O, P, J, and K (and all commits left of N as before) are on branch A; and commits N, E, F, and G (and all commits left of N as always) are on branch B.

Merge bases

Commit N is the merge base of branches B and master, and commit P is the merge base of branches A and master. The merge base is simply1 the commit after which the branches diverge, which becomes easy to see if you draw the graph as we just did. This matters for your git rebase because the default method git uses is to find this merge base commit, and copy commits that are after that point. (And, this means that before you do a rebase, it may be a good idea for you to draw the graph, so that you can see what you will be doing.)

Copying commits

To copy a single commit yourself, use the command git cherry-pick. You don't need to do this now—rebase will do it for you—but it's a good idea to know how it works, so let's cover that briefly.

In git, each commit is a fully self-contained snapshot. For instance, consider commit F on branch B. Commit F contains the complete source tree associated with your project, as of the time you made commit F. This is also true of commits E and G. If we compare commit E vs commit F, we will see what changed between "the tree for commit E" and "the tree for commit F". Similarly, if we compare F vs G, we will find out what changed in going from F to G.

Converting a commit to a set of changes—called a changeset—allows us to copy a commit. Suppose, for instance, we see what happened from N to E. Then suppose further that we check out commit K and make the same change, and then commit it on a new, temporary branch:

...--N--O--P--Q--R   <-- master
      \     \
       \     J--K    <-- A
        \        \
         \        E'   <-- temp branch
          \
           E--F--G   <-- B

I called the new commit E', rather than giving it a new single letter, because it's a copy of commit E. It's not exactly the same as commit E of course. For one thing, it has a different parent (it has commit K as its parent); and it probably has a bunch of other differences from E: specifically whatever happened in commits O, P, J, and K, since we've now applied "the changes from N to E onto "what was in K".

Rebase is just repeated copies

git rebase works by:

  1. Identifying the commits to copy;
  2. Copying them using a temporary branch; and
  3. Re-pointing the original branch.

If you git checkout B and then git rebase A, the commits identified in step 1 are those after the merge base between B and A, up to the tip of branch B. The A and B here are because your current branch is B and you said git rebase A.

Exercise: look at the graph and identify the merge-base of B and A. It might be a bit hard to spot, but what if we re-draw the graph like this?

             Q--R    <-- master
            /
...--N--O--P--J--K   <-- A
      \
       E--F--G       <-- B

(This drawing is, topologically speaking, exactly the same as the original graph we drew, but now it's super-obvious that commit N is the merge base.)

In short, these are commits E, F, and G. So rebase goes on to step 2 and copies those three commits, placing the copies after ("onto") the tip of branch A (because you said git rebase A).

Thus, at this point in the rebase process, we now have:

             Q--R            <-- master
            /
...--N--O--P--J--K           <-- A
      \           \
       \           E'-F'-G'  <-- temp
        \
         E--F--G             <-- B

There's just one last thing for rebase to do now, which is to re-point the current branch B, to the last commit just copied. That's pretty easy:

             Q--R            <-- master
            /
...--N--O--P--J--K           <-- A
      \           \
       \           E'-F'-G'  <-- B
        \
         E--F--G             [abandoned]

The original commits, E through G, are mostly gone at this point (they're still retained through the reflog for branch B but unless you specifically look there, you won't see them any more). The new copies, E' through G', have been added on just past the tip of branch A.

Why all this matters

All of this copying and shuffling-about takes place in your repository, and not in anyone else's copy of your repository. This means that any co-worker or centralized server does not have these copies yet. If you git push the copies to a centralized server, the server will get the copies—but if the server had the original, now abandoned, commits, and if the server called that "branch B", you will have to force-push to the server.

Doing this force-push will make the server discard its copies of the original commits, and put in place the copied commits and call those "branch B". This is true even if, on the server, there is a commit H subsequent to commit G, with branch B pointing to commit H. In other words, this can "lose" commits from the server. That's the first wrinkle: you must coordinate with everyone else using the server, so that you do not lose commits.

Further, after doing a force push, and/or if your co-workers are sharing your repository (by fetching/pulling from it), your co-workers may need to adjust their repositories to account for the copy-commits-and-move-branch-label. To them, this is an upstream rebase and they must recover from it. This is the second wrinkle: you must coordinate with everyone else using the branch, so that they know to recover from the upstream rebase.

In short, make sure all your co-developers are OK with this.

If you have others working with you, and they are sharing the branches you will rebase, just make sure they all know about it. If these commits that you will rebase are not published—if they're purely private to your own repository—then this coordination requires no effort at all, as there is no one else to inform, so there is no one who can object to it.

Merging, and maybe rebasing again

Soon A will be merged to master (before B), but not now. Then, when I merge B, will it break the reference from having A rebased on it?

Again, the way to answer these questions is to start by drawing the commit graph. Let's use the new, rebased drawing, then add the merge commit that merges B into master. Since we want to show the merge, we need to re-draw the graph a bit yet again, to make it easier to connect the rows:

...--N--O--P-Q-R             <-- master
            \
             J--K            <-- A
                 \
                  E'-F'-G'   <-- B

Now we'll run git checkout master and git merge A to make new merge commit M:

...--N--O--P-Q-R--M          <-- master
            \    /
             J--K            <-- A
                 \
                  E'-F'-G'   <-- B

We did not have to change anything in B here, and we can continue developing on it as usual. But, if we want to—and if all our co-developers agree—we can now rebase B again, this time onto master.

If we git checkout B; git rebase master, the rebase will, as usual, start by finding the merge base between B and master. What is the merge base? Look at the graph and find the point where master and B join up. It's definitely not the new merge commit M, but it's not N any more either.

The commits that are on branch B are N, O, P, J, K, E', F', and G' (and any commits we can't see that are to the left of N). This part is straightforward enough. The tricky part is the commits that are on branch master. These are N, O, P, Q, R, and M, but also J and K. This is because those two commits can be reached by following commit M's second parent.

Therefore, the merge base—which is defined as the "closest" commit that's on both branches—is actually commit K. This is exactly what we want! To rebase B onto master, we want git to copy E', F', and G'. The new copies E'', F'', and G'' will go after M, and we will get this:

                    E''-F''-G''   <-- B
                   /
...--N--O--P-Q-R--M               <-- master
            \    /
             J--K                 <-- A
                 \
                  E'-F'-G'        [abandoned]

The real lesson here is draw the graph as it will help you figure out what is going on.


1At least, it's simple when there is a single such point. In more complex graphs, there can be more than one merge base. Rebasing with complex graphs requires a lot more than I can write in this particular SO posting, though.

这篇关于我可以安全地将一个分支重新划分为另一个分支然后再掌握吗?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆