[英]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
分支推送到远程之后,我如何并且应该如何将develop
与master
分支合并为一个提交,而又没有将任何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用怪异的技术来做(谈论)怪异的事情的一堆方法。定义(哪些人经常犯错,这只会增加混乱)。
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
是初始提交, C
和E
是当前在分支master
上(可从中查找)的另外两个提交,而B
和D
是您在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中,它在两个分支上。
In fact, the names master
and develop
are nearly irrelevant. 实际上, master
和develop
这两个名字几乎是无关的。 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
返回C
到A
或从D
到B
到A
)只有一种方式:从子到父。 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
开始,我们可以到达E
, C
和A
,这就是分支master
的完整内容,因为master
指向提交E
Starting from D
, we can reach D
, B
, and A
; 从D
开始,我们可以到达D
, B
和A
; 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: 让我们添加两个新的临时提交F
和G
,仅用于说明:
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. 同时,由于F
和G
不可见 ,因此您将看不到它们:Git的行为就像它们不存在一样,除非您以某种方式挖掘了它们的哈希ID。
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: 让我们来看看与合并规则的合并发生develop
成master
,在提交图形方面:
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
附带的树 (工作树副本)将B
和D
的更改合并到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
的父母,以便合并记住合并的提交。
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. 删除名称后,无法访问的提交最终将被垃圾回收。
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). 您将看到A
, B
, C
, D
, E
和F
(尽管可以控制,但通常按日期进行混合和排序)。 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.