简体   繁体   English

将提交历史隔离到特定分支

[英]Isolate commit history to specific branch

Say, I have an empty repository with a single initial commit: 说,我有一个空的存储库,只有一个初始提交:

* - Initial commit (master)

Then I create a develop branch locally and make a few commits on it. 然后,我在本地创建一个develop分支,并对其进行一些提交。 But before I push it to remote (without merging it with master yet) I update master with remote which brings a few commits as well to master branch and I get this: 但是在我将其推送到远程(尚未与master合并)之前,我使用remote更新master ,这也给master分支带来了一些提交,我得到了:

*   - Update something (master)
| * - Make some changes to the new feature (develop)
| |
* | - Make some changes (master)
| * - Add new feature (develop)
|/         
* - Initial commit (master)

So, my question is how can and should I, after I push develop branch to the remote, how I can merge develop with master branch as a single commit without copying any of the history of develop branch to master , but keep all the history on develop branch. 因此,我的问题是,在将develop分支推送到远程之后,我如何并且应该如何将developmaster分支合并为一个提交,而又没有将任何develop分支的历史复制到master ,而是将所有历史保留在develop分支。

The short answer is that you can't, quite—but you may be able to do what you want , and the main problem here is a bunch of ways Git does (and talks about) things that are weird and different, with fiddly technical definitions (which people get wrong too often, which just adds to the confusion). 简短的答案是,您不能做到,但是您可能能够做您想要的事情,而这里的主要问题是Git用怪异的技术来做(谈论)怪异的事情的一堆方法。定义(哪些人经常犯错,这只会增加混乱)。

Lots of background (sorry, it's kind of long) 很多背景(对不起,很长)

Let me redraw your commits horizontally (which works better for text articles on StackOverflow), and give them one-letter names: 让我水平重绘您的提交(这对于StackOverflow上的文本文章更好),并给它们一个字母名称:

A--C--E   <-- master
 \
  B--D    <-- develop

That is, A is the initial commit, C and E are the two additional commits that are currently "on" (findable from) branch master , and B and D are the two commits you made that are "on" (findable from) develop . 也就是说, A是初始提交, CE是当前在分支master上(可从中查找)的另外两个提交,而BD是您在develop master上(可从中查找)的两个提交。 。

Here's a trick question: which branch is commit A on? 这是一个技巧问题:提交A在哪个分支上? master , or develop ? master还是develop

It's a trick question because in Git, it is on both branches. 这是一个技巧问题,因为在Git中,它在两个分支上。

Branch names and reachability 分支名称和可达性

In fact, the names master and develop are nearly irrelevant. 实际上, masterdevelop这两个名字几乎是无关的。 What matters are the commits. 重要的是提交。 The names, which I've drawn over towards the right, merely point to one specific commit . 我已经向右绘制的名称仅指向一个特定的commit We call that one specific commit the tip of the branch, and then we—and Git—work backwards, using the parent information stored in each commit, to find earlier commits. 我们称一个特定的提交为分支的尖端 ,然后我们(和Git)使用存储在每个提交中的父信息向后工作,以查找较早的提交。

These parent links, E back to C back to A , or D to B to A , only go one way: from child, to parent. 这些父链接(从E返回CA或从DBA )只有一种方式:从子到父。 They're all backwards from the way we might expect at first, and they determine which commits are reachable from any given commit. 它们都比起初我们期望的要落后,并且它们确定从任何给定的提交中可以到达哪些提交。 Starting from C , we can reach C itself (of course) and also A ; C开始,我们可以到达C本身(当然),也可以到达A and that's all. 就这样。 Starting from E , we can reach E , C , and A —and that's the complete contents of the branch, master , since master points to commit E . E开始,我们可以到达ECA ,这就是分支master的完整内容,因为master指向提交E Starting from D , we can reach D , B , and A ; D开始,我们可以到达DBA ; and since develop points to D , that's the complete contents of the branch develop . 并且由于develop指向D ,因此分支的完整内容develop

But this means that commit A is on both branches . 但这意味着提交A两个分支上 That's just the way Git is: a commit is on any number of branches—even none, sometimes—and the set of branches is based on reachability , through the parent links in each commit. 这就是Git的方式:提交位于任意数量的分支上,有时甚至不包含任何分支,并且分支集基于可到达 (通过每个提交中的父链接)。 The names merely serve to get us started in the graph, and there are names other than branch names, such as tag names. 这些名称仅用于使我们从图中开始,还有分支名称以外的其他名称,例如标记名称。 Let's put in two new temporary commits, F and G , just for illustration: 让我们添加两个新的临时提交FG ,仅用于说明:

     tag: T
        |
        v
        F--G   <-- tempbranch
       /
A--C--E   <-- master
 \
  B--D    <-- develop

Now commit G is find-able through tempbranch ; 现在,可以通过tempbranch找到commit G commit F is find-able by its tag T and through tempbranch , and commits ACEFG are all on tempbranch . 可以通过其标记T 通过tempbranch来找到commit F ,而提交ACEFG都在tempbranch Let's now delete the name tempbranch entirely: 现在让我们完全删除名称 tempbranch

     tag: T
        |
        v
        F--G
       /
A--C--E   <-- master
 \
  B--D    <-- develop

Now we have an interesting case: commit G is no longer reachable at all. 现在我们有一个有趣的案例:提交G根本无法到达。 Commit F is still reachable, via the tag T . 通过标签T仍然可以到达提交F Git will eventually garbage-collect commit G , making it go away for real. Git最终将对提交G进行垃圾回收 ,使其真正消失。 Until then, if you have saved its hash ID somewhere, you can still view it, or even "resurrect" it by giving it a branch or tag name. 在此之前,如果您已将其哈希ID保存在某处,则仍可以查看它,甚至可以通过为其指定分支或标记名来“恢复”它。 (Git also keeps these IDs saved in things called reflogs . There's one for each reference—such as branch and tag names—and one for HEAD . These keep commits around for 30 days by default, even after removing their names.) (Git还将这些ID保存在称为reflogs的东西中。每个引用都有一个(例如分支和标记名称),而每个HEAD都有一个。默认情况下,即使删除了它们的名称,它们也会保留30天左右的提交时间。)

If we delete the tag T as well, commit F becomes eligible for garbage collection as well, and eventually we go back to the five commits we had before. 如果我们也删除标签T ,则提交F将有资格进行垃圾回收,最终我们返回到之前的五个提交。 Meanwhile, since F and G are not visible , you won't see them: Git will act as if they're not there, unless you dig up their hash IDs somehow. 同时,由于FG可见 ,因此您将看不到它们:Git的行为就像它们不存在一样,除非您以某种方式挖掘了它们的哈希ID。

Merge commits 合并提交

With all that out of the way, let's take a look at regular, ordinary merges. 顺便说一句,让我们看一下常规的普通合并。 A merge commit , in Git at least, is just like any other commit, with one exception: it has two or more parents, instead of just one. 至少在Git中, 合并提交与其他任何提交一样,但有一个例外:它有两个或多个父级,而不只是一个。 (Most merges just have two, and there's nothing particularly useful about three-or-more-parent merges.) (大多数合并只有两个,并且对于三个或三个以上的父合并没有什么特别有用的。)

Let's look at what happens with a regular merge that merges develop into master , in terms of the commit graph: 让我们来看看与合并规则的合并发生developmaster ,在提交图形方面:

A--C--E--F   <-- master
 \      /
  B--D-´   <-- develop

The name develop continues to point to commit D , but the name master now points to the new merge commit F . 名称develop继续指向提交D ,但是名称master现在指向新的合并提交 F Commit F points back to both commits E and D . 提交F点背到两个承诺E D

When Git does reachability computations, it follows all the parent links (simultaneously, as it were). 当Git进行可达性计算时,它会遵循所有父链接(同时保持原样)。 So at this point, every commit in the graph is on branch master . 因此,此时,图中的每个提交都在branch master Commits ABD are (still) on develop , and if you git checkout develop and write a new commit, the new commit will only be on develop , with the name develop automatically moving to point to the new commit: 提交ABD仍在develop ,如果您git checkout develop并编写了新的提交,则新的提交只会在develop名称 develop自动移至新提交:

A--C--E--F   <-- master
 \      /
  B--D-´--G   <-- develop

This is the normal way to handle these things. 这是处理这些事情的正常方法。 The tree (working-tree copy) that goes with commit F incorporates the changes from B and D into master : Git takes commit A vs commit E to find the changes from master , and A vs D to find the changes from develop , and combines them to make F . 提交F附带的 (工作树副本)将BD的更改合并到master :Git接受commit A与commit E来查找master的更改,并使用A vs D来找到来自develop的更改,并进行合并他们使F Then it records both E and D as F 's parents, so that the merge remembers which commit was merged. 然后它将E D都记录为F的父母,以便合并记住合并的提交。

Squash "merges" (end of background) 壁球“合并”(背景结束)

With all that out of the way, we can now talk about Git's so-called "squash merge". 综上所述,我们现在可以谈谈Git所谓的“壁球合并”。 You get these by running git merge --squash , and then doing a bit more. 您可以通过运行git merge --squash获得这些git merge --squash ,然后再执行一些操作。 Specifically, you have to also run git commit afterward. 具体来说,您之后还必须运行git commit

A squash merge is not actually a merge at all. 南瓜合并实际上根本不是合并。 If we draw the after-effect, we get this graph: 如果我们画出后效应,我们得到如下图:

A--C--E--F   <-- master
 \
  B--D   <-- develop

The contents of commit F are the same as what we would get with a real merge. 提交F内容与实际合并中的内容相同。 The new commit goes on master as usual, ie, master gets moved to point to the new commit. 新提交将像往常一样继续在master进行,即master移到指向新提交的位置。 The key difference is that the new commit does not point back to the merged-in commit, but only to the previous tip of master . 关键区别在于,新提交指向合并的提交,而指向master的上一个提示。

At first, this seems to be just what you want. 起初,这似乎只是您想要的。 It does, however, have one very big drawback. 但是,它确实有一个很大的缺点。 Let's add some new commits to develop : 让我们添加一些新的commit来进行develop

A--C--E--F   <-- master
 \
  B--D----G--H--I   <-- develop

Now, when we want to incorporate the changes from GHI into master , we need to merge again. 现在,当我们要将GHI的更改合并到master ,我们需要再次合并。 If we had a normal merge—so that F pointed back to D as well as to E —Git would know how to do this. 如果我们有一个正常的合并,以便F指向D以及E Git就会知道如何做到这一点。 Without a real merge, though, Git will wind up going all the way back to the common commit A again, and compare A -vs- F , and then A -vs- I , to try to combine the two changes. 但是,如果没有真正的合并,Git最终将再次回到通用提交A ,然后比较A -vs- F ,然后比较A -vs- I ,以尝试将这两个更改组合在一起。

But we already have the BD changes! 但是我们已经有了BD更改! They're in F . 他们在F If we had a real merge, Git would know that, and not have to figure this out. 如果我们进行了真正的合并,那么Git就会知道这一点,而不必弄清楚这一点。

If we're lucky—which we often are—Git figures this all out anyway. 如果我们很幸运(我们经常如此),Git还是会把这一切解决掉的。 The longer the chain of commits gets, though, with more and more changes, the less and less likely it becomes that Git will figure this out on its own. 但是,随着越来越多的更改,提交链越长,Git自行解决此问题的可能性就越小。

Hence, the usual rule for squash merges is that we should make them only if we're going to abandon the merged branch, eg, delete the name develop entirely. 因此,壁球合并的通常规则是, 当我们要放弃合并的分支时才应使它们合并,例如,完全删除名称develop Once we delete the name, the unreachable commits eventually get garbage-collected. 删除名称后,无法访问的提交最终将被垃圾回收。

What to do instead, maybe 该怎么做,也许

When you use a real merge, the drawback is that you see everything, because of the way Git follows both parents simultaneously. 当您使用真实合并时,缺点是您会看到所有内容,因为Git同时跟随父母双方。 That is, if you run: 也就是说,如果您运行:

git log master

you will see all of A , B , C , D , E , and F (intermixed and sorted by date, normally, though you can control this). 您将看到ABCDEF (尽管可以控制,但通常按日期进行混合和排序)。 However, there's a very important feature to avoid this. 但是,有一个非常重要的功能可以避免这种情况。 You can run: 您可以运行:

git log --first-parent master

instead, to view commits without following any of the "extra" parents from merges. 相反,查看提交时不遵循合并中的任何“额外”父项。 This --first-parent view will show you only commits ACEF , skipping BD . 这个--first-parent视图将显示您提交ACEF ,而跳过BD This is because --first-parent limits the graph traversal to skip the second parents of each merge commit. 这是因为--first-parent限制图形遍历以跳过每个合并提交的第二个父级。

Note that the proper workflow would be, since you have not pushed develop yet, to rebase develop on top of master after updating master . 需要注意的是正确的工作流程是,因为你还没有推动develop呢, 变基 develop之上master更新后master

git checkout master
git pull

git checkout develop
git rebase master

Now you can push, since develop content is based on (and validated with) the latest master content. 现在,您可以进行推送,因为develop内容基于最新的master内容(并经过验证)。
You resolve any conflict (from replaying develop on top of master ) locally first, then you push. 您首先在本地解决任何冲突(从在master上重放develop ),然后再推送。

You can create a new single commit on master containing all the changes introduced by develop using get merge --squash , and then committing the results. 您可以在master上创建一个新的单个提交,其中包含使用get merge --squash develop引入的所有更改,然后提交结果。

merge --sqauash will apply all the changes from develop to the index, and prepare a commit message containing all the concatenated commit messages from develop , but it will not create a commit. merge --sqauash将适用于所有从变化develop的指标,并编制包含所有级联提交从消息提交信息develop ,但它不会产生一个承诺。 It's up to you to then git commit and edit the prepared commit message. 然后由git commit并编辑准备的提交消息由您决定。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM