在进行git合并时,是否有可能排除特定提交?

85
假设我想要从发布分支合并到主分支,但是在发布分支中有一些提交记录我不想包含在主分支中。是否有一种方法可以进行合并,以便其中一个或多个提交记录不会被合并?
目前我的策略是在主分支中执行以下操作:
git merge --no-commit release-branch
# Resolve conflicts and apply reverse patch of the commits that I don't want included
git commit # Edit commit message so that it lists the commits that have been reverse-patched

有没有更好的方法来做这件事?


可能是git-合并时跳过特定提交的重复问题 - 我知道那个问题比较年轻,但是它的被接受的答案在我看来更好。 - Tobias Kienzler
8个回答

121

我找到了一种适合我的解决方案Pro Git书中

假设您要排除文件config.php

在分支A上:

  1. 在相同的目录下创建一个名为.gitattributes的文件,并添加此行:config.php merge=ours。这告诉git在合并文件时使用什么策略。在这种情况下,它始终保留您的版本,即您要合并到的分支上的版本。

  2. 添加.gitattributes文件并提交。

在分支B上:重复1-2步骤

现在尝试合并。您的文件应该保持不变。


5
对于未来的读者而言,这对于明确地包括特定文件非常有效。(在我的情况下,我将不同的分支部署到不同的服务器上,并希望在每个分支上保持我的Capistrano部署脚本唯一。) - charliepark
17
由于某些原因,这对我不起作用。我创建了一个新分支,其中包含更改文件的提交,并在两个分支中添加了.gitattributes文件。当我合并回原始分支时,它似乎完全忽略了.gitattributes中的行,并且无论如何都会拉取更改后的文件。我是否缺少某些设置? - robbles
1
对我不起作用。我也按照VonC列出的问题说明进行了操作。我使用的是Windows系统。我正在尝试避免合并的文件是一个点文件(.core.config)。我需要用引号或其他方式来包含它的名称吗? - Sean
22
只有在这两个文件都被修改时,该操作才有效;否则,合并我们的文件将被忽略。 - Caumons
4
@robbles Caumons是正确的。基本上,只有在合并冲突时才会触发此属性。在您的情况下,您有一个快进式合并。Git会直接执行而不检查合并策略。 - Johnny Z
显示剩余2条评论

51

创建一个新的分支,交互式地变基并丢弃不要的提交记录,然后合并该分支。

如果要从分支中间删除更改,则需要进行变基操作。但当它在以后的合并中看到相同的更改(例如来自cherry-pick等操作)时,正确的事情将会发生。


4
当您尝试再次合并该分支时会发生什么? - Evgeny
4
需要使用哪些确切的命令来完成这个操作?我看不出如何“丢弃”不需要的提交。 - Henning
9
当你使用命令 git rebase -i other-branch 时,会出现一个带有多个提交记录的文本编辑器。删除你不想要的那些行即可。请注意,翻译过程中不改变原意,保持通俗易懂。 - Dustin

26
如果你有一个支持分支,用于修复错误和构建新版本。在主分支上,你也会频繁地构建新版本。每次构建新版本时,你都需要更改某个文件中的版本信息,提交该文件,创建标签并推送。现在从 support 分支合并到 master 分支将始终在包含版本信息的文件中发生冲突。
如果包含版本信息的文件包含版本信息,则可以使用fcurella的答案。但是,如果它确实可能包含可合并的信息(例如pom.xml、gradle.properties、MANIFEST.MF等),则必须执行一些额外的操作。
让我们使用以下示例。
      C---D*---E---F* support
     /
A---B---G---H*---I master

带星标的提交仅包含由于版本更改而应在合并期间忽略的更改。

为了在不因版本构建而产生合并冲突的情况下将 support 合并到 master,您可以执行以下任一操作:

多次合并提交

git checkout master
git merge C
git merge D -s ours
git merge E
git merge F -s ours

通过使用-s ours参数,我们告诉git仅记录合并而不更改工作区。这类似于svn的--record-only选项。
上述操作将导致以下布局。
      -------------C---D*---E---F* support
     /              \   \    \   \
A---B---G---H*---I---J---K----L---M master

使用cherry-pick合并一个提交

git checkout master
git merge support -s ours --no-commit
git cherry-pick C E --no-commit
git commit -m 'merged support into master'

首先,我们开始合并,但仅记录我们正在合并,而不更改工作区且不进行合并提交。然后,我们挑选要合并的提交,同样不进行提交。最后,我们提交合并结果。
上述操作将产生以下布局。
      C---D*---E---F* support
     /              \
A---B---G---H*---I---J master

甚至可以自动化挑选。

git checkout master
git merge support -s ours --no-commit
for id in `git log support --reverse --not HEAD --format="%H [%an] %s" |
  grep -v "bump version" |
  sed "s/\(\w*\)\s.*/\1/g"`
do
  git cherry-pick --no-commit $id
done
git commit -m 'merged support into master'

有没有自动化多个合并提交的示例? - Santos
使用cherry pick生成单个合并提交的解决方案非常棒!谢谢!这正是我所需要的。我们有两个从同一源代码分支出来的项目。我们需要保留它们之间的一些差异,但总体上我们需要将大部分更改合并来回传递。非常好的文章! - Brent K.
在创建新分支时,通常需要更新该分支的某些配置。我们想提交这些更改,但不想将它们合并回去。使用SVN,我们可以记录并合并它们,然后忘记它们。非cherry-pick解决方案似乎做了类似的事情 - 但我是否可以在合并C或E之前以单个步骤合并D和F? - Simone
在类似于我之前评论的情况下,我们能否“挑选”某些提交?我真正需要的是在将来在该分支上进行工作时忘记这些提交的能力。换句话说,我不想真正选择有意义的更改,我更愿意将非有意义的更改标记为“不要合并”。我们可以在Git中做到这一点吗? - Simone

4
这不能直接完成的原因在于每个提交都包含指向父提交的链接(通常只有一个,但合并时可能有多个)。这样,如果您有一个提交(通过其SHA1总和),整个历史记录也会固定下来,因为父提交也包含对其父提交的链接,以此类推。因此,略去历史记录中的补丁的唯一方法是编写新的提交。在新创建的分支上使用git rebase -i可能是最简单的方法。

3
主要问题是:你想如何表示你想跳过的提交?
1. 暗中隐藏它们(不是我最喜欢的) 2. 明确地跳过它们 3. 明确地撤消它们
不幸的是,第2种方法无法在历史图表中表示。
第1种方法可能可行,但我绝不会这样做:合并提交可以包含更改。 - 通常,合并提交指向两个或多个分支的合并结果:那些分支中开发的所有内容应该在合并后的代码中(即在合并提交所指向的代码中)。除此之外,此提交中不应该包含其他任何内容。
但是,令人惊讶的是,您可以更改整个代码库并将其表示为合并。合并的效果有两个方面:它合并历史树和合并两个代码库。它肯定会完成前者(否则没有人称其为合并),但对于后者,它可能会失败,例如当出现合并冲突并且解决错误时(然后代码库未正确合并)。
其他答案中建议使用隐藏方法。我建议采用明确的方式:合并加还原提交。

这是一个适用于短期分支的绝佳策略。但是,这种策略存在一个问题,当你将正确合并的分支(带有还原操作的分支)合并回工作分支时,工作分支也会接收到还原操作,因为它是一个常规的真实提交。如果你的情况是工作分支真的不应该有这个提交,那么你就必须还原还原操作。只要特定的工作分支在使用中,这个过程就会一直持续下去。 - Ron Wertlen
@RonWertlen 您的说法是正确的,即主分支不能再次合并到“工作分支”/发布分支中,而不引入还原的影响。- 但这不仅适用于我在答案中建议的第3种策略,也适用于策略1。- 这也是我个人不会追求OP问题中的工作流程的原因之一。 - Robert Siemer
我也同意你的看法。1和2是反工作流程的。如果你有长期存在的分支,并且想永久地将提交保留在其中一个分支中而不在另一个分支中,那么现在是时候开始考虑重新组织你的项目,并使用子模块(在node.js中,你还可以使用node_modules作为实现相同目的的并行机制)。 - Ron Wertlen
此外,第二点在历史图中无法表达,这个陈述并不是100%清晰的。实际上,通过使用“--force”选项重置或重新设置远程仓库的索引,可以“欺骗”自己的历史记录。当然,这是一个非常糟糕的想法。 - Ron Wertlen
@RonWertlen 你刚刚提醒了我一种合并内容但跳过某些提交的方法。- 但这与远程无关。 - Robert Siemer

3

也可以修改.git/info/attributes文件并将其保留在.git文件夹中,而不是添加到各个地方的.gitattribute文件,这将最终需要将它们添加到源代码控制中。


1
为了避免与@fcurella的答案遇到覆盖问题,首先定义一个合并驱动程序,始终优先使用我们当前版本的文件,利用现有的true命令。我们将称这个驱动程序为ours,以保持与类似的合并策略一致: git config --global merge.ours.driver true 之后,我们可以实现@fcurella的解决方案:

Let's say you want to exclude the file .env

On branch A (e.g. Master):

  1. Create a file named .gitattributes in the same dir, with this line:

    .env merge=ours 
    

    This tells git what strategy to use when merging the file. In this case it always keep your version, ie. the version on the branch you are merging into.

  2. Add the .gitattributes file and commit

On branch B (e.g. Develop): repeat steps 1-2

Try merging now. Your file should be left untouched.

阅读this以获取更多解释。

我喜欢这个,它解决了包括无合并冲突覆盖问题在内的问题。我的用例是生产/阶段环境之间的CI/CD管理。然而,我不喜欢依赖全局git配置,容易忘记并且没有在其他机器上配置。 - Merlin

1

如果你只想排除一些在结尾的提交,你可以直接提交到特定的提交编号:

git checkout partlyMergedFrom
git whatchanged
--> find the commit hash up to where you want to merge
git checkout partlyMergedInto
git merge e40a0e384f58409fe3c864c655a8d252b6422bfc
git whatchanged
--> check that you really got all the changes you want to have

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