Git合并会影响“合并”科? [英] Do Git merges affect the "merged" branch?

查看:168
本文介绍了Git合并会影响“合并”科?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我工作的公司一直在试用一个Git工作流程,包含三个主要分支(开发,测试版,主控)以及个别功能分支。这涉及到将特性分支合并为独立于彼此的'开发'和'测试版',然后定期将'测试版'合并到'主'中以用于版本。问题:当我们将一个特性分支合并到'develop'时,它似乎会影响特性分支的提交历史记录,所以当我们尝试将同一分支合并到'beta'时,它也将包含'develop'的所有提交,这是不是我们想要的。



有人知道这里发生了什么吗?是否有可能将一个分支合并到另外两个分支中而不包含任何不需要的提交?我们工作流程的哪些变化可以防止这种情况发生我知道这里没有任何代码示例或任何东西,但这不是真的那种类型的问题,我不知道如何提供更多有用的信息。



谢谢! / p>

解决方案

在一个基本的意义上,合并操作并不真正影响任何分支。 (它当然会做一个新的提交,它以通常的方式影响该分支)。Git的诀窍是将以下五个同时提出的想法保留在你的脑海中:


  1. Git中的重要内容是提交及其父链接。 Branch names 大多只是干扰注意事项(但请参阅第2点和第3点)。

  2. 只是特定提交的名称,我们称之为该提交的提交提交
  3. 在进行新提交时, Git使用当前提交作为它的父进程写新提交。 1 如果新提交有多个父进程(见下一点),则当前提交将成为其父 first 。无论如何,Git会更新分支名称以指向新的提交。这是分支增长的方式。


  4. merge commit 是提交两个(或更多)父提交。这是合并为名词,因为它是。
  5. 是合并的行为,我的意思是合并提交 - 涉及执行三向合并操作,然后像往常一样进行新提交,除了新提交具有两个(或更多)父项。 extra父母是合并提交。 2


合并行为 - 合并为动词 - 使用通过上述五点建立的历史记录。 Git发现了三个提交:


  • 当前提交,又名 HEAD 。 (这很简单。)

  • 要合并的提交:任何ID(s) git rev-parse 出现与你传递给 git merge 的参数一起使用。分支名称只是发现分支提交。

  • 合并基 。这就是提交历史记录的来源,这就是为什么你需要绘制图片段。


任何两个提交的合并基数松散地定义为图形一起回来的(第一个)点:

$ $ $ $ $ $ $ $ $ $ $ $ $ $ * - o - o - o< - branch1
\
o - o - o - o< - branch2

名称 branch1 指向第一行的提示(最右边)提交。名称 branch2 指向底线的提示提交。这两个提交的 merge base 标记为 * 3



为了执行合并操作,Git然后区分(如 git diff )合并基础提交 * branch1 )和他们(在 branch2 )在 README 中将单词 color 更改为 color ,Git只需要修改一次。



生成的存储在工作树中,成为合并提交的树。请注意,直到此时,我们是否将 branch2 合并到 branch1 branch1 转换为 branch2 :我们将得到相同的合并基础,并且具有相同的两个提交提交,因此得到相同的两个差异并且将这两个差异以相同的方式组合起来以得到相同的工作树。但是现在我们通过它的两个父母和现在来创建实际的合并 commit ,这关系到我们正在使用哪个分支。如果我们在 branch1 上,我们在 branch1 上创建新的提交,并提前 branch1

  ...-- o  -  o- -o  -  o  -  o --- M < -  branch1 
\ /
o - o - o - o < - branch2

新的合并提交有两个父项:一个是的旧提示branch1 另一个是 branch2 的提示。



因为我们现在有了一个新图,稍后 git merge 会找到一个新的 merge base 。假设我们在两个分支上进行了更多的提交:

  ...-- o  -  o  -  o-- o  -  o --- M  -  o  -  o<  -  branch1 
\ /
o - o - o - * - o - o - o < - branch2

如果我们现在要求合并两个分支,Git首先找到基地。这是 分支上的一个提交,最接近两个提示。我再次将此提交标识为 * ,并查看它的位置:它曾经是分支2 ,当我们进行合并时返回。



请注意,无论采用何种方式进行合并,情况仍然如此。



然而,我们进行实际的合并提交至关重要。如果我们使用 git merge --squash ,那么不会进行合并提交,我们将不会获取此类的图。因为 git rebase 通过复制提交,并且 git merge,所以合并后两个分支都不会获得rebased 基于提交标识和父指针进行工作。任何副本都是不同的提交,所以任何旧的提交都不会指向新复制的提交。 (在这些图纸中,将合并点之后的提交绑定到右侧是可以的);不正确的是复制左侧的提交。)



如果你不禁止 git merge 做一个快进操作,也可能是 git merge 跳过进行合并提交,而只是移动分支标签。在这种情况下,两个分支标签 - 即您刚刚移动的标签,以及您要求合并的标签 - 都指向同一个提交。一旦发生这种情况,除了将标签移回标签之外,没有办法解开两个分支。为了防止 git merge 做这种快进而不是真正的合并,使用 - no-ff p>

下面是一个快进合并的例子(在引号中,因为没有实际的合并)。像往常一样,我们开始使用分支分支 - 但是当前分支没有任何提交, branch1 还没有 其他分支上, branch2

  ...  -  o  -  *<  -  branch1 
\
o - o - o< - branch2

如果坐在 branch1 上,我们运行 git merge branch2 -note缺少 - no-ff -Git注意到不需要实际的合并。相反,它执行标签快进操作,将名称 branch1 向前滑动,直到遇到分支2

  ...  -  o  -  o 
\
o - o- -o < - branch1,branch2

该图无处可记录任何分离性两个分支,所以我们不妨将理由纠正:

  ...-- o  -  o  -  o- -o  -  o<  -  branch1,branch2 

直到我们在 branch2

  ...-- o  -  o  -  o-- o  -  *<  -  branch1 
\
o< - branch2

这里没有什么错误,但请注意现在不可能告诉我们向上移动到第一行的三次提交合并了。






1 对于常规的 git commit 和对于 git merge ,但不适用于 git commit --amend 。提交的修改变体像往常一样进行新的提交,但不是将当前提交的提交设置为新提交的父提交,而是将当前提交的父父母设置为他们中的许多人,甚至可能根本就不是父母)作为新的承诺的父母。其效果是将当前提交推到一边,使其看起来好像提交已更改,实际上旧提交仍在存储库中。



2 多于两个父母的情况称为章鱼合并,我们可以在这里忽略它。 (对于重复的成对合并,它不会做任何事情)。
$ b

3 在复杂图形中,可能有多个这样的第一点 。在这种情况下,所有最低公共祖先节点都是合并基础,对于Git, -s 策略 合并策略论证决定如何处理这种情况。 (当然,也有 -s我们的策略,它忽略了所有其他的提交,并且完全绕过了三路合并代码,但我假设正常合并在这里。)


The company I work for has been trying out a Git workflow with three main branches (develop, beta, master) plus individual feature branches. This involves merging feature branches into 'develop' and 'beta' independently of one another, and then periodically merging 'beta' into 'master' for releases.

The problem: when we merge a feature branch to 'develop', it seems to affect the commit history for the feature branch, so when we try to merge that same branch into 'beta' it will also include all the commits from 'develop', which is not what we want.

Does anyone know what's going on here? Is it possible to merge a branch into two other branches without including any unwanted commits? What changes to our workflow could prevent this? I know there's no code sample or anything, but it's not really that type of question and I'm not sure how else to give more useful information.

Thank you!

解决方案

The merge operation doesn't really affect any branch, in one fundamental sense. (It does of course make a new commit, which affects that branch in the usual way.) The trick with Git is to keep the following five simultaneous ideas in your head:

  1. What matters in Git are commits, and their parent links. Branch names are mostly just distractions (but see points 2 and 3).

  2. A branch name is just the name for a particular commit, which we call the tip commit of that branch.

  3. When making a new commit, Git writes the new commit with the current commit as its parent.1 If the new commit has multiple parents (see next point), the current commit becomes its first parent. In any case Git then updates the branch-name to point to the new commit. This is how branches "grow".

  4. A merge commit is a commit with two (or more) parent commits. This is "merge as a noun", as it were.

  5. The act of making a merge—by which I mean a merge commit—involves doing the three-way-merge action, then making a new commit as usual, except that the new commit has two (or more) parents. The "extra" parents are the merged-in commit(s).2

The merge action—"merge as a verb"—uses the history built up through the five points above. Git finds three commits:

  • The current commit, aka HEAD. (This is easy.)
  • The commit(s) to be merged: whatever ID(s) git rev-parse comes up with for the argument(s) you pass to git merge. A branch name just finds the branch-tip commit.
  • The merge base. This is where the commit history comes in, and this is why you need to draw graph fragments.

The merge base of any two commits is loosely defined as "the (first) point where the graph comes back together":

...--o--*--o--o--o     <-- branch1
         \
          o--o--o--o   <-- branch2

The name branch1 points to the tip (rightmost) commit on the top line. The name branch2 points to the tip commit on the bottom line. The merge base of these two commits is the one marked *.3

To perform the merge action, Git then diffs (as in git diff) the merge base commit * against the two tips, giving two diffs. Git then combines the diffs, taking just one copy of each change: if both you (on branch1) and they (on branch2) changed the word color to colour in README, Git just takes the change once.

The resulting source, as stored in the work-tree, becomes the tree for the merge commit. Note that up until this point, it does not matter whether we are merging branch2 into branch1, or branch1 into branch2: we will get the same merge base, and have the same two tip commits, and hence get the same two diffs and combine those two diffs in the same way to arrive at the same work-tree. But now we make the actual merge commit, with its two parents, and now it matters which branch we're on. If we are on branch1, we make the new commit on branch1, and advance the name branch1:

...--o--o--o--o--o---M   <-- branch1
         \          /
          o--o--o--o     <-- branch2

The new merge commit has two parents: one is the old tip of branch1 and the other is the tip of branch2.

Because we now have a new graph, a later git merge will find a new merge base. Let's say that we make several more commits on both branches:

...--o--o--o--o--o---M--o--o     <-- branch1
         \          /
          o--o--o--*---o--o--o   <-- branch2

If we now ask to merge the two branches, Git first finds the base. That's a commit that's on both branches, nearest to the two tips. I've identified this commit as * again, and look where it is: it's the commit that used to be the tip of branch2, back when we did the merge.

Note that this is still the case regardless of which way we do the merge.

It is, however, critical that we make an actual merge commit. If we use git merge --squash, which does not make a merge commit, we will not get this kind of graph. It's also important that neither branch gets "rebased" after merging, since git rebase works by copying commits, and git merge works on the basis of commit identities and following parent pointers. Any copies are different commits, so any old commits will not point into the new copied commits. (It's OK to rebase commits after the merge point—to the right, in these drawings; what's not OK is copying commits that are to the left.)

If you do not prohibit git merge from doing a "fast forward" operation, it's also possible for git merge to skip making a merge commit, and instead just move the branch label. In this case the two branch labels—the one you just moved, and the one you asked to merge—wind up pointing to the same commit. Once this happens, there's no way to "untangle" the two branches except by moving the label back. To prevent git merge from doing this fast-forward instead of actually merging, use --no-ff.

Here is an example of a fast-forward "merge" (in quotes because there is no actual merge). We start, as usual, with diverged branches—but there are no commits on the current branch, branch1, that are not already also on the other branch, branch2:

...--o--*           <-- branch1
         \
          o--o--o   <-- branch2

If, while sitting on branch1, we run git merge branch2—note the lack of --no-ff—Git notices that no actual merging is required. Instead, it does a label "fast forward" operation, sliding the name branch1 forward until it meets the tip commit on branch2:

...--o--o
         \
          o--o--o   <-- branch1, branch2

This graph has nowhere to record any "separateness" between the two branches, so we might as well straighten out the kink:

...--o--o--o--o--o   <-- branch1, branch2

until we make new commits on branch2:

...--o--o--o--o--*     <-- branch1
                  \
                   o   <-- branch2

There's nothing wrong with this, but note how it is now impossible to tell that the three commits we "moved up" to the first row were merged.


1This is true for regular git commit and for git merge, but not for git commit --amend. The "amend" variant of a commit makes a new commit as usual, but instead of setting the current commit as the new commit's parent, it sets the current commit's parents (as many of them as there are, which may even be no parents at all) as the new commit's parents. The effect is to shove the current commit aside, making it seem as though the commit has changed, when in fact the old commit is still in the repository.

2The more-than-two-parents case is called an "octopus merge" and we can ignore it here. (It does nothing you cannot do with repeated pairwise merges.)

3In complex graphs there may be more than one such "first point". In this case, all lowest-common-ancestor nodes are merge bases, and for Git, the -s strategy merge strategy argument decides how to handle this case. (Of course, there is also the -s ours strategy, which ignores all the other commits, and simply bypasses the three-way merge code entirely. But I'm assuming normal merge here.)

这篇关于Git合并会影响“合并”科?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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