Git合并更新源分支未更改的文件

8

我很难理解以下行为在git中是一件好事。下面是我为了帮助说明我的问题而准备的一个示例。很多时候,我的团队和我自己会发现有变更/提交进入了我们不想让它们进入的分支。

> git init sandbox && cd sandbox
> echo "data a" > a.txt && echo "data b" > b.txt
> git add -A && git commit -a -m "initial population"
[master (root-commit) d7eb6af] initial population
 2 files changed, 2 insertions(+)
 create mode 100644 a.txt
 create mode 100644 b.txt
> git branch branch1
> echo "more data a" >> a.txt && git commit -a -m "changed a.txt on master"
[master 11eb82a] changed a.txt on master
 1 file changed, 1 insertion(+)
> git branch branch2 && git checkout branch2
Switched to branch 'branch2'
> echo "more data b" >> b.txt && git commit -a -m "changed b.txt on branch2"
[branch2 25b38db] changed b.txt on branch2
 1 file changed, 1 insertion(+)
> git checkout branch1
Switched to branch 'branch1'
> git merge branch2
Updating d7eb6af..25b38db
Fast-forward
 a.txt | 1 +
 b.txt | 1 +
 2 files changed, 2 insertions(+)

请注意,上述情况中,即使在branch2上未被修改/触及,a.txt也会在合并中更新。在上述情况下,我希望git能够智能地识别到a.txt在branch2上没有更改,因此在应用更新到branch1时,不进行这些更改。
我做错了什么吗?是的,我可以选择性地挑选,这对于我知道自己所做更改的简单示例是有效的,但在实际情况下,当更改更大且您不知道可能受到影响时,这是不现实的。
明确一点,我不想从git获得这种行为。
3个回答

3
'branch1'和'branch2'实际上是提交指针。它们是某个时刻提交历史记录的状态。因此,当将'branch2'合并到'branch1'时,git只需建立一个公共祖先,并尝试同时应用来自两棵树的更改即可。
以一个简单的图表为例:
 branch1       E <- branch2
    |         /
    v        /
A - B - C - D <- master

在上面的例子中,'branch1'指向提交B,而'branch2'指向提交E。这或多或少地描述了您在上面输入操作的顺序。如果您将'branch2'合并到'branch1'中,Git将在B中找到一个公共祖先,然后将存在于BE之间的所有历史记录应用于'branch1',具体包括提交CDE
然而,您想要的只是E。一种(不好的)解决方案是挑选樱桃,就像您已经识别出的那样。一种更好的解决方案是将'branch2'重新基于'branch1',从而重写'branch2'的历史记录,仅包括提交E通过'branch1':
git rebase --onto branch1 master branch2

这将产生您所需的结果,并读作“将最初基于主分支的branch2重新定位到branch1”。请注意,出于简单起见,我在此图中省略了“branch1”指针,并且E变为E',因为其提交哈希已更改(这是这些图表的常见约定):

       E' <- branch2
      /
     /
A - B - C - D <- master

您可以通过 git checkout branch2 && git rebase -i B 来获得类似的效果,然后在交互式 rebase 会话中删除提交 CD
在我的上一份工作中,我们经常遇到这种孤立的功能分支问题。它们从同一个生产分支在不同时间点剪切而来,如果没有进行 rebase 合并就会带来不必要的更改。作为集成经理,我通常会将它们的历史记录重写到过去的一个共同点(最后一个生产发布),从而允许整洁地合并。这是许多可能工作流程中的一种。最佳答案严重依赖于您的团队如何移动代码。例如,在 CI 环境中,CD 被随着像您描述的合并拉取有时并不那么重要。
最后,请注意,如果 E 依赖于 CD 中的任何代码,则当将 'branch1'(现在包含 E' 更改集)合并回 'master' 时,此解决方案将对您的历史记录造成严重影响。如果您的工作流程是增量的,并且 'branch1' 和 'branch2' 在类似的函数和文件中进行干涉,那么合并冲突将自然而然地出现。在这种情况下,可能需要更仔细地审视您团队的工作流程。

将“branch2”重新设置到“branch1”的方法让我感到担忧,因为这假设我想要保留分支1中的所有提交,而其中一些提交可能是被丢弃的。 - djschny
1
啊,但是一旦你愿意rebase 'branch2',那么git rebase -i branch1也不是什么大问题,可以丢弃不需要的内容。如果分支差异保持足够小并且正确分块,则重写(未推送)历史记录将成为Git工具中最强大的功能之一。然而,并非每个人或每个工作流程都适用。 - Christopher

3
如果上述命令完全正确地键入,那么git就是正确的。以下是您所做的事情: 1. 创建一个repo(默认为"master"分支) 2. 向“master”添加了一个改变集(2个新文件) 3. 创建了一个分支(“branch1”),但未更改到它 4. 在“master”上添加了一个修改集(更改了a.txt) 5. 创建了一个分支(“branch2”)并切换到它(此分支包括已修改的a.txt) 6. 在“branch2”上添加了一个更改集(更改了b.txt) 7. 切换到“branch1”(包含两个未更改的原始文件) 8. 与“branch2”合并(快进)(应用了a.txt和b.txt的两个更改)
这正是您所描述的,并且应该发生的事情。您可能的错误是认为您正在“branch1”上更改a.txt,而实际上是在创建“branch2”之前在“master”上更改了它,从而给人以“branch1”上的更改来自于“master”的错觉,当与“branch2”合并时,更改似乎是神奇地出现的,但实际上是来自于“branch2”。
如果您重复测试,但在第3步中切换到“branch1”(git checkout -b branch1)而不是将更改提交到“master”,则您会得到所期望的合并。

1
我的命令是正确的,完全是有意为之的。我试图理解如何使git仅应用在分支创建后我所做的更改,而不是之前的更改。 - djschny

2

即使在branch2分支上没有修改a.txt文件,但在合并时它也会被更新。

但实际上是修改了的。请运行除git merge branch2之外的所有命令,然后查看。

$ cat a.txt
data a

$ git checkout branch2
Switched to branch 'branch2'

$ cat a.txt
data a
more data a

我知道这是在branch2中的方式,但是我在分支之后没有修改/触摸它。

你将more data a提交到master。然后你从master创建了branch2。因此,branch2也将包含more data a


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接