[英]mercurial merging just the changes from a branch
I can use "graft" to copy the same changes I made in a particular commit from one branch to another. 我可以使用“graft”将我在特定提交中所做的相同更改从一个分支复制到另一个分支。 What I'd like to do is something similar, except I want to copy all the changes from a branch.
我想做的是类似的事情,除了我想要从分支复制所有更改。
That is, I start with a branch I'll call branch A. I make a new branch off branch A called feat1, which is adding a new feature. 也就是说,我从一个分支开始,我将调用分支A.我在分支A上创建了一个名为feat1的新分支,它正在添加一个新功能。 I make several commits in feat1, then merge it back into branch A.
我在feat1中做了几次提交,然后将它合并回到分支A.
I want to merge all the changes made in the feat1 branch into another branch I'll call branch B. But I don't want to merge everything from branch A into branch B, just the changes made over the course of the feat1 branch. 我想将在feat1分支中进行的所有更改合并到另一个分支中,我将其称为分支B.但我不想将从分支A到分支B的所有内容合并,只是在feat1分支的过程中进行的更改。 I think I can accomplish this using a series of grafts, one for each commit in my feat1 branch, but I think there should be a better way.
我想我可以使用一系列移植来实现这一点,一个用于我的feat1分支中的每个提交,但我认为应该有更好的方法。 Is there a standard way of accomplishing this?
有没有一种标准的方法来实现这一目标?
I'm currently using Mercurial, although I'm planning on transitioning to git at some point, so I'd like to know how to do it in either system. 我目前正在使用Mercurial,虽然我计划在某些时候转换到git,所以我想知道如何在任一系统中执行它。
(Note: this is cross-posted to git and mercurial , so we need an answer that covers both. I apologize for how long this is, too, but there are a lot of concepts to understand. If this is TL;DR, skip down to the last section, Avoiding all the copying . But note that you will then have to read upwards to see what I am talking about.) (注意:这是交叉发布到git和mercurial ,所以我们需要一个涵盖两者的答案。我为这多长时间道歉,但是有很多概念需要理解。如果这是TL; DR,跳过到最后一节, 避免所有的复制 。但请注意,你必须向上阅读,看看我在说什么。)
Both Git and Mercurial merge the same way. Git和Mercurial都以相同的方式合并。 1 In particular, branch names do not matter .
1特别是, 分支名称无关紧要 。 What matters is not the names;
重要的不是名字; what matters is the commit graph .
重要的是提交图 。
1 There are many tiny differences and even a few relatively big ones, but what I mean here is that the overall idea is the same. 1有许多微小的差异,甚至有一些相对较大的差异,但我的意思是整体思路是一样的。 Git also supports the concept of what Git calls a fast-forward merge , which is not a merge at all.
Git还支持Git称之为快进合并的概念,这根本不是合并。 This concept makes no sense in a Mercurial repository and simply cannot happen: Mercurial does not do fast-forwards.
这个概念在Mercurial存储库中毫无意义,根本不可能发生:Mercurial不会快速前进。 Running
hg merge <commit-specifier>
is roughly equivalent to running git merge --no-ff <commit-specifier>
. 运行
hg merge <commit-specifier>
大致相当于运行git merge --no-ff <commit-specifier>
。
In both Git and Mercurial, each commit records, in some manner, the ID of its parent commit, or—in the case of a merge commit—its parents (exactly two for Mercurial, two or more for Git). 在Git和Mercurial中,每个提交以某种方式记录其父提交的ID,或者 - 在合并提交的情况下 - 其父项(Mercurial恰好两个,Git两个或更多)。 Because each commit has a unique ID of its own, and each commit records its parent or parents, we can draw a graph—it starts out as a tree , which is simpler, and which I will illustrate here—of these commits:
因为每个提交都有自己的唯一ID,并且每个提交记录其父或父,我们可以绘制一个图形 - 它最初是一个树 ,这更简单,我将在这里说明这些提交:
A <--B <--C
Here we have a simple repository with only three commits in it. 这里我们有一个简单的存储库,其中只有三个提交。 Commit
C
is the latest. Commit
C
是最新的。 It has a single parent B
, so C
remembers the ID of B
. 它有一个父
B
,所以C
记住B
的ID。 B
, likewise, remembers the ID of A
. B
同样记得A
的ID。 (Note that A
is unaware of B
and B
is unaware of C
: the arrows go only backwards.) (注意
A
不知道B
和B
不知道C
:箭头只向后移动。)
Nothing about any commit can ever be changed (this is more true in Git, but pretty much true in Mercurial too). 任何提交都无法改变(在Git中更为正确,但在Mercurial中也是如此)。 So we don't need to draw the arrows as arrows;
所以我们不需要画箭头箭头; we can just use connecting lines, as long as we remember that the VCS has to follow them in a backwards direction.
我们可以只使用连接线,只要我们记住VCS必须沿向后方向跟随它们。 This is handy because we might now decide to add a new commit
D
whose parent is B
rather than C
: 这很方便,因为我们现在可能决定添加一个新的提交
D
,其父级为B
而不是C
:
A--B--C
\
D
This is the point where Mercurial and Git tend to diverge: in Mercurial, we often achieve this result (on purpose) by putting commit D
on a different branch. 这就是Mercurial和Git倾向于分歧的地方:在Mercurial中,我们经常通过将commit
D
放在不同的分支上来实现这个结果(故意)。 Mercurial records the actual name of the branch on which any commit was made, so from then on, Mercurial knows D
is on branch dev
or whatever. Mercurial记录了进行任何提交的分支的实际名称,因此从那时起,Mercurial知道
D
在分支dev
或者其他任何东西上。 Git uses a completely different scheme: Git does not know or care where a commit was made; Git使用完全不同的方案:Git不知道或关心提交的位置; Git finds commits by having names remember a last commit, and working backwards the way both systems can, following the internal arrows connecting commits.
Git通过让名字记住最后一次提交来找到提交,然后按照连接提交的内部箭头,以两种系统的方式向后工作。
So, in Mercurial, we might say: 所以,在Mercurial中,我们可能会说:
default: A--B--C
\
dev: D
Each commit is on the branch that goes with the line we put the commit on. 每个提交都在与我们提交的行相关的分支上。 But in Git, we might switch to drawing them like this:
但是在Git中,我们可能会改为绘制它们:
C <-- master
/
A--B
\
D <-- dev
That is, the name master
identifies commit C
, and the name dev
identifies commit D
. 也就是说,名称
master
标识提交C
,名称dev
标识提交D
Commits A
and B
are now on both branches. 提交
A
和B
现在都在两个分支上。
At this point, Mercurial users might declare that Git is just nuts, and many would agree with them. 此时,Mercurial用户可能会声明Git只是疯了,很多人会同意这些。 But Mercurial users are not off the hook here, because we can do this in Mercurial too: we can make commit
D
be on branch default
, and yet still have B
as its parent. 但Mercurial用户并不是在这里,因为我们也可以在Mercurial中执行此操作:我们可以将提交
D
设置为分支default
,但仍然将B
作为其父级。 That is: 那是:
A--B--C
default: \
D
is valid in Mercurial too! 在Mercurial也有效! And in modern Mercurial, we can set two bookmarks to remember commits
C
and D
, and we have the same situation as in Git. 在现代的Mercurial中,我们可以设置两个书签来记住提交
C
和D
,并且我们具有与Git相同的情况。
Hence, it's important in both systems to understand how the commit graph works. 因此,在两个系统中理解提交图如何工作是很重要的。 Because Mercurial's branch system is more rigid, you can often get away without this understanding—but eventually, you do need to know about it.
由于Mercurial的分支系统更加严格,您通常可以在没有这种理解的情况下逃脱 - 但最终,您确实需要了解它。
Now, let's look at your text description, and draw the graph, because it's the graph that matters. 现在,让我们看看你的文字描述,并绘制图形,因为它是重要的图形 。
I start with a branch I'll call branch A. I make a new branch off branch A called feat1, which is adding a new feature.
我从一个分支开始,我将调用分支A.我在分支A上创建了一个名为feat1的新分支,它正在添加一个新功能。 I make several commits in feat1, then merge it back into branch A.
我在feat1中做了几次提交,然后将它合并回到分支A.
I want to merge all the changes made in the feat1 branch into another branch I'll call branch B. But I don't want to merge everything from branch A into branch B, just the changes made over the course of the feat1 branch.
我想将在feat1分支中进行的所有更改合并到另一个分支中,我将其称为分支B.但我不想将从分支A到分支B的所有内容合并,只是在feat1分支的过程中进行的更改。 I think I can accomplish this using a series of grafts, one for each commit in my feat1 branch, but I think there should be a better way.
我想我可以使用一系列移植来实现这一点,一个用于我的feat1分支中的每个提交,但我认为应该有更好的方法。 Is there a standard way of accomplishing this?
有没有一种标准的方法来实现这一目标?
There's a piece missing here because in Mercurial, you almost certainly really started with default
, just as Git users start with master
. 有一块在这里失去了,因为水银,你几乎可以肯定是真的开始
default
,就如同用户的Git开始master
。 Let's draw at least one commit on default
, and then worry about branchA
and feat1
and branchB
. 让我们在
default
绘制至少一个提交,然后担心branchA
和feat1
和branchB
。
default: A
\
branchA: B--C-...--M
\ /
feat1: D--E
Commit M
is your merge, made by running hg checkout branchA; hg merge feat1
提交
M
是你的合并,通过运行hg checkout branchA; hg merge feat1
hg checkout branchA; hg merge feat1
. hg checkout branchA; hg merge feat1
。
The way merge works is that it looks, not at the branch names , but rather at the commit graph . 合并的方式是它看起来,而不是分支名称 ,而是在提交图上 。 Before
M
exists, the graph reads: 在
M
存在之前,图表显示:
...--C--...
\
D--E
I put in the ...
here because there might be some commit or commits after C
, such as F
or FGH
or whatever. 我把
...
放在这里因为在C
之后可能会有一些提交或提交,比如F
或FGH
或其他什么。 Let's assume that there are. 我们假设有。 The effect on Mercurial is less important than it is on Git, because in Git, this forces Git to do a real merge rather than a fast-forward non-merge, but it's useful here to help illustrate how merge works.
对Mercurial的影响不像Git那么重要,因为在Git中,这迫使 Git进行真正的合并而不是快速的非合并,但是这里有助于说明合并是如何工作的。 Let's use just one commit
F
: 我们只使用一个提交
F
:
...--C--F
\
D--E
To achieve a merge, both VCSes look at the graph at this point. 为了实现合并, 两个 VCS都会在此时查看图形。 They take the current commit—in this case,
F
—and the requested ("other" or --theirs
) commit, in this case E
—and search backwards through the graph for the best common ancestor commit. 他们采用当前的提交 - 在这种情况下,
F
和请求的(“其他”或 - --theirs
)提交,在这种情况下E
并向后搜索图表以获得最佳共同祖先提交。 That commit is obvious from the drawing: it's commit C
! 从图中可以明显看出这个提交:它是提交
C
!
So, at this point, both VCSes do the logical equivalent of: 因此,在这一点上,两个VCS都符合逻辑:
C
vs commit F
, to find out what we changed; C
vs commit F
,找出我们改变了什么; C
vs commit E
, to find out what they changed; C
vs commit E
,找出他们改变了什么; C
; C
的内容; if all goes well, make the new merge commit M
using F
as the first parent, and E
as the second parent: 如果一切顺利,使用
F
作为第一个父项,使用E
作为第二个父项,使新的合并提交M
:
...--C--F---M \\ / D--E
So, now that you have this, let's go on to the next part of your text: 所以,既然你已经拥有了这个,那么让我们继续你的文本的下一部分:
I want to merge all the changes made in the feat1 branch into another branch I'll call branch B.
我想将feat1分支中的所有更改合并到另一个分支中,我将其称为分支B.
This new branch, branchB
, does not spring up out of nowhere. 这个新的分支,
branchB
,不会冒出来。 You must create it. 你必须创建它。 The method here is a little bit different in Git and Mercurial, but it ends up working out the same ... well, mostly the same.
这里的方法在Git和Mercurial中略有不同,但它最终得到的结果相同......好吧, 大多数都是一样的。
In Git, you now pick one of the total set of commits that exist ( A
through F
plus the merge M
) and choose that to be the commit to which the name branchB
will point. 在Git中,您现在选择一个存在的总提交集(
A
到F
加上合并M
)并选择它作为名称branchB
将指向的提交。 Then run git branch branchB <commit-specifier>
, and the name branchB
now points to that commit. 然后运行
git branch branchB <commit-specifier>
,名称branchB
现在指向该提交。
In Mercurial, you now check out some existing commit: hg update -r <rev>
or similar. 在Mercurial中,您现在可以查看一些现有的提交:
hg update -r <rev>
或类似的。 Pick one of the commits, perhaps A
in which case we can just use hg update default
. 选择一个提交,也许是
A
在这种情况下我们可以使用hg update default
。 Then run hg branch branchB; hg commit
然后运行
hg branch branchB; hg commit
hg branch branchB; hg commit
to make a new commit, which creates branchB
. hg branch branchB; hg commit
进行新的提交,创建branchB
。
In both systems, a branch cannot exist if there are no commits on that branch; 在两个系统中,如果该分支上没有提交,则分支不能存在; but in Git, any commit can be on many branches, so we can make
branchB
exist by pointing it to commit A
. 但是在Git中,任何提交都可以在许多分支上,因此我们可以通过将其指向提交
A
来使branchB
存在。 In Mercurial, a commit is only on one branch, so we need to make a new commit to cause branchB
to come into existence. 在Mercurial中,提交仅在一个分支上,因此我们需要进行新的提交以使
branchB
成立。 So the pictures differ a bit. 所以图片有点不同。
Here is the Git picture: 这是Git图片:
A <-- master, branchB
\
B--C--F---M <-- branchA
\ /
D--E <-- feat1
and here is the one for Mercurial: 这是Mercurial的一个:
branchB: N
/
default: A
\
branchA: B--C--F---M
\ /
feat1: D--E
We can, at this point, make a gratuitous commit in Git, just to make the pictures match (except that since Git branch names move , we put them on the right with arrows pointing into the graph; Mercurial branch names are solidly fixed, so we can have them sit on the left and mark their commits forever, as long as our graph does not get very big anyway). 在这一点上,我们可以在Git中进行无偿提交,只是为了使图片匹配(除了自Git分支名称移动之后 ,我们将它们放在右边,箭头指向图形; Mercurial分支名称固定,因此我们可以让他们坐在左边并永久地标记他们的提交,只要我们的图表不会变得非常大)。 Git will write the new commit
N
, 2 and move the current branch name to point to that new commit. Git将编写新的提交
N
, 2并将当前分支名称移动到指向新提交。
But I don't want to merge everything from branch A into branch B, just the changes made over the course of the feat1 branch.
但我不想将从分支A到分支B的所有内容合并,只是在feat1分支的过程中进行的更改。 I think I can accomplish this using a series of grafts, one for each commit in my feat1 branch, but I think there should be a better way.
我想我可以使用一系列移植来实现这一点,一个用于我的feat1分支中的每个提交,但我认为应该有更好的方法。 Is there a standard way of accomplishing this?
有没有一种标准的方法来实现这一目标?
In both Git and Mercurial, you must in fact copy the commits. 在Git和Mercurial中,您实际上必须复制提交。 The graph is the commits;
图是提交; the commits
D
and E
that you made on feat1
are stuck where they are. 你在
feat1
上feat1
的提交D
和E
被卡在feat1
。 It does not matter whether the branch names move (Git) or are solidly fixed (Mercurial), because the commits themselves are immutable. 分支名称是移动(Git)还是固定(Mercurial)并不重要,因为提交本身是不可变的。
In Mercurial, you are correct that hg graft
copies commits. 在Mercurial中,你认为
hg graft
复制提交是正确的。 You can use a single command to copy all of the feat1
commits. 您可以使用单个命令复制所有
feat1
提交。 Because commits are specific to one particular branch, it's easy to copy all the feat1
commits. 因为提交特定于一个特定分支,所以很容易复制所有的
feat1
提交。
In Git, the git cherry-pick
command copies commits. 在Git中,
git cherry-pick
命令会复制提交。 You can use a single command to copy all of the commits you want here, too, but because commits DE
are on two branches—they're on both feat1
and branchA
now—you need to use a more complex specifier. 您也可以使用单个命令复制您想要的所有提交,但因为提交
DE
位于两个分支上 - 现在它们都在feat1
和 branchA
- 您需要使用更复杂的说明符。 Git's convenient specifiers—maybe not that convenient—use graph operations: feat1~2..feat1
would specify both commits D
and E
, in this case, as would branchA^..feat1
. Git方便的说明符 - 可能不是那种方便使用的图形操作:
feat1~2..feat1
将指定提交D
和E
,在这种情况下,就像branchA^..feat1
。 Mercurial can do this too, but you don't need this as often in Mercurial. 水银也可以这样做,但你并不需要这个经常在水银。
2 Note that we must git checkout branchB
and perhaps use git commit --allow-empty
. 2请注意,我们必须
git checkout branchB
并且可能使用git commit --allow-empty
。 The --allow-empty
flag tells Git that it should make the new commit even though the index —which is a Git-specific concept; --allow-empty
标志告诉Git它应该进行新的提交,即使索引 - 它是特定于Git的概念; Mercurial has no index—exactly matches the current commit. Mercurial没有索引 - 与当前提交完全匹配。 The
git checkout
step has the side effect of attaching the name HEAD
to the branch-name, so that Git knows which name is the current branch . git checkout
步骤具有将名称HEAD
附加到branch-name的副作用,以便Git知道哪个名称是当前分支 。 (Mercurial records the current branch name in a hidden data structure called the dirstate that, unlike Git's index, you do not need to know about.) (Mercurial将当前分支名称记录在一个名为dirstate的隐藏数据结构中,与Git的索引不同,您不需要了解它。)
Since your goal is to avoid using hg graft
or git cherry-pick
, let's consider how we could achieve this. 由于你的目标是避免使用
hg graft
或git cherry-pick
,让我们考虑如何实现这一点。 Note that it's not always possible, and even when it is possible, it sometimes requires a lot of foresight and planning. 请注意,并非总是可行,即使有可能,有时也需要大量的远见和规划。 But the key is this: Both Git and Mercurial use the merge base from the graph , to merge in the various features.
但关键是这样的:双方Git和Mercurial的使用合并基础从图中,在不同的功能合并。
Let's take the final Mercurial graph from above, where the two feat1
commits will have to be grafted in order to have them affect commit N
: 让我们从上面获取最终的Mercurial图,其中必须嫁接两个
feat1
提交以使它们影响提交N
:
branchB: N
/
default: A
\
branchA: B--C--F---M
\ /
feat1: D--E
Now, suppose that, when we went to create the branch feat1
, and then implement the feature, we knew in advance that we wanted to be able to just run hg update branchA; hg merge feat1; hg update branchB; hg merge feat1
现在,假设当我们创建分支
feat1
然后实现该功能时,我们事先知道我们希望能够运行hg update branchA; hg merge feat1; hg update branchB; hg merge feat1
hg update branchA; hg merge feat1; hg update branchB; hg merge feat1
hg update branchA; hg merge feat1; hg update branchB; hg merge feat1
. hg update branchA; hg merge feat1; hg update branchB; hg merge feat1
。
To make this work, we must have the first commit on feat1
come after an earlier commit in the graph . 为了使这项工作,我们必须在图表中的早期提交之后对
feat1
进行第一次提交。 Specifically, it must come after some commit that is an ancestor of the tips of both branchA
and branchB
. 具体来说,它必须在一些提交之后,它是
branchA
和 branchB
的提示的祖先。 Commit C
is too far down the path towards branchB
: it's not an ancestor of commit N
on branchA
. 提交
C
离branchB
的路径branchB
:它不是branchA
上提交N
的祖先。
The ideal commit is the one that is at the point where branchA
and branchB
first come back together. 理想的提交是在
branchA
和branchB
首先返回的位置。 That is, it's the commit whose hash ID Git will spell out by running git merge-base branchA branchB
: the most recent (in graph terms) commit that is an ancestor of the two tip commits. 也就是说,它是通过运行
git merge-base branchA branchB
其哈希ID Git的提交: 最近的 (以图形术语)提交是两个提示提交的祖先 。
Note that because we have not yet started on feat1
, what we must actually have at this point is just: 请注意,因为我们还没有开始使用
feat1
,所以我们此时必须具备的只是:
branchB: N
/
default: A
\
branchA: B--C
Mercurial does not have a particularly convenient tool for finding the desired hash ID, 3 but because Mercurial commits are firmly stapled to their branches, it's usually tremendously obvious anyway. 水银没有特别方便的工具,查找所需的散列ID,3,但由于水银提交牢固地钉在了分支机构,它通常是极其明显的反正。 In this case, that's commit
A
, which is the last commit on default
. 在这种情况下,这是提交
A
,这是default
的最后一次提交。 So we can: 所以我们可以:
hg update default
hg branch feat1
and then write and commit our code: 然后编写并提交我们的代码:
branchB: N
/
default: A
|\
feat1: | D--E
\
branchA: B--C
Meanwhile commit F
went into branchA
as before, so we hg update branchB
and find that we now have: 同时提交
F
像以前一样进入branchA
,所以我们hg update branchB
并发现我们现在有:
branchB: N
/
default: A
|\
feat1: | D--E
\
branchA: B--C--F
We run hg merge feat1
and get: 我们运行
hg merge feat1
并得到:
branchB: N
/
default: A
|\
feat1: | D------E
\ \
branchA: B--C--F--M
where the first parent of M
is F
and the second parent is E
, just as before. 其中
M
的第一个父亲是F
,第二个父亲是E
,就像以前一样。 But now we can hg update branchB; hg merge feat1
但是现在我们可以
hg update branchB; hg merge feat1
hg update branchB; hg merge feat1
. hg update branchB; hg merge feat1
。 The merge base of N
and E
is commit A
; N
和E
的合并基础是提交A
; Mercurial compares A
to N
to see what we did, and A
to E
to see what they did. Mercurial将
A
与N
进行比较,看看我们做了什么, A
到E
看看他们做了什么。 Mercurial builds the new merge commit O
and commits it: Mercurial构建新的合并提交
O
并提交它:
branchB: N----------O
/ /
default: A /
|\ /
feat1: | D------E
\ \
branchA: B--C--F--M
Aside from the fact that one uses different commands, and the branch labels themselves move about, the process is identical in Git. 除了使用不同的命令,并且分支标签本身移动的事实之外,该过程在Git中是相同的。
3 You can find the commit using -r
options and graph-oriented revision specifiers. 3您可以使用
-r
选项和面向图的修订说明符找到提交。 You want the last commit, numerically speaking, that satisfies ancestor(tip-of-branchA) & ancestor(tip-of-branchB)
. 从数字上讲,您希望最后一次提交满足
ancestor(tip-of-branchA) & ancestor(tip-of-branchB)
。 Update to that commit, and then create the feat1
commits. 更新到该提交,然后创建
feat1
提交。
In Git, to find the hash, simply run git merge-base branchA branchB
. 在Git中,要查找哈希,只需运行
git merge-base branchA branchB
。 Having found the hash, point a new branch name at that hash ID, check out that branch, and begin committing. 找到哈希后,在该哈希ID上指向一个新的分支名称,检查该分支,然后开始提交。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.