题 如何有选择地合并或从Git中的另一个分支选择更改?


我在一个新项目中使用git,该项目有两个并行 - 但目前是实验性的 - 开发分支:

  • master:导入现有的代码库加上一些我一般都知道的mod
  • exp1:实验分支#1
  • exp2:实验分支#2

exp1 和 exp2 代表两种截然不同的建筑方法。直到我走得更远,我无法知道哪一个(如果有的话)会起作用。当我在一个分支中取得进展时,我有时会在另一个分支中进行编辑,并且只想合并那些。

将选择性更改从一个开发分支合并到另一个开发分支而将其他所有内容合并的最佳方法是什么?

我考虑过的方法:

  1. git merge --no-commit 然后手动取消大量编辑,我不想在分支之间做出共同点。

  2. 手动将公共文件复制到临时目录中,然后执行 git checkout 移动到另一个分支,然后更多手动从temp目录复制到工作树中。

  3. 以上的变化。放弃了 exp 现在分支,并使用另外两个本地存储库进行实验。这使得手动复制文件更加简单。

所有这三种方法看起来都很乏味且容易出错。我希望有更好的方法;类似于过滤器路径参数的东西 git-merge 更有选择性。


1159
2018-01-16 04:55


起源


如果实验分支的变化在单独的提交中组织良好,那么最好考虑选择性合并 提交 而不是选择性文件。下面的大多数答案都假设是这种情况。 - akaihola
不会是一个组合 git merge -s ours --no-commit其次是一些 git read-tree 对此有好的解决方案吗?看到 stackoverflow.com/questions/1214906/... - VonC
最近的一个问题有一个单行的,写得很好的答案: stackoverflow.com/questions/10784523/... - brahn


答案:


你用的是 摘樱桃 命令从一个分支获取单个提交。

如果您想要的更改不在单独提交中,请使用此处显示的方法 将提交拆分为单独的提交。粗略地说,你使用 git rebase -i 然后获得原始提交进行编辑 git reset HEAD^ 那么,有选择地还原变化 git commit 将该位提交为历史记录中的新提交。

这里有另一种不错的方法 在红帽杂志,他们使用 git add --patch 或者可能 git add --interactive 如果要将不同的更改拆分为单个文件(在该页面中搜索“拆分”),则允许您仅添加部分块。

拆分更改后,您现在可以选择您想要的更改。


390
2018-01-16 06:01



根据我的理解,这比投票得多的答案更令人费解。 - Alexander Bird
这在技术上是正确的答案,正确的答案确实显得“复杂”。 ---更高的投票答案只是一个快速而肮脏的“诀窍”答案,对于大多数人来说,这就是他们的全部内容(: - Jacob
@akaihola:HEAD ^是正确的。请参阅man git-rev-parse:修订参数的后缀^表示该提交对象的第一个父级。前缀^表示法用于排除从提交可到达的提交。 - Tyler Rick
我只是想分享另一种方法,这种方法看起来最干净,而且不那么复杂: jasonrudolph.com/blog/2009/02/25/... 完全简洁和令人敬畏 - superuseroi
关于哪种方法“正确”的辩论感到困惑?考虑 文件和提交之间的区别(见底部备注)。 OP希望合并FILES并且不提及COMMITS。较高投票的答案特定于文件;接受的答案使用cherry-pick,这是特定于提交。 Cherry-pick可能是有选择地合并提交的关键,但是将文件从一个分支移动到另一个分支可能会非常痛苦。虽然提交是git强项的核心,但不要忘记文件仍然有作用! - Kay V


我有与上面提到的完全相同的问题。但我发现了 这个 更清楚地解释答案。

概要:

  • 签出要合并的分支的路径,

    $ git checkout source_branch -- <paths>...
    
  • 或有选择地合并帅哥

    $ git checkout -p source_branch -- <paths>...
    

    或者,使用重置,然后使用选项添加 -p

    $ git reset <paths>...
    $ git add -p <paths>...
    
  • 最后提交

    $ git commit -m "'Merge' these changes"
    

820
2017-09-03 08:48



Bart J的链接文章是最好的方法。清晰,简单,一个命令。这是我即将使用的那个。 :) - Pistos
这不是真正的合并。您按文件而不是按提交选择更改,您将丢失任何现有的提交信息(作者,消息)。当然,如果您想要合并某些文件中的所有更改,这很好,您必须重新执行所有提交。但是,如果文件包含要合并的更改和其他要丢弃的更改,则其他答案中提供的方法之一将更好地为您服务。 - akaihola
@mykhal和其他人:这会自动暂存索引中的文件,所以如果你签出了 foo.c 做 git reset HEAD foo.c 要取消暂存该文件,然后你可以将其区分开来。我在尝试之后发现了这一点并回到这里寻找答案 - michiakig
要查看您还可以使用的更改: git diff --cached - OderWat
根据这个 回答  git checkout -p <revision> -- <path> 将发出您描述的前三个命令:) - 7hi4g0


要有选择地将文件从一个分支合并到另一个分支,请运行

git merge --no-ff --no-commit branchX

哪里 branchX 是要合并到当前分支的分支。

--no-commit 选项将暂存已由Git合并而不实际提交它们的文件。这将使您有机会根据需要修改合并的文件,然后自己提交。

根据您要合并文件的方式,有四种情况:

1)你想要一个真正的合并。

在这种情况下,您接受合并文件的方式是Git自动合并它们然后提交它们。

2)有些文件你不想合并。

例如,您希望保留当前分支中的版本并忽略要合并的分支中的版本。

要选择当前分支中的版本,请运行:

git checkout HEAD file1

这将检索版本 file1 在当前分支中覆盖 file1 由Git自动充电。

3)如果你想在branchX中使用该版本(而不是真正的合并)。

跑:

git checkout branchX file1

这将检索版本 file1 在 branchX 并覆盖 file1 由Git自动合并。

4)最后一种情况是,如果您只想选择特定的合并 file1

在这种情况下,您可以编辑修改后的内容 file1 直接,将其更新为您想要的版本 file1 成为,然后承诺。

如果Git无法自动合并文件,它会将文件报告为“未合并“并生成一份副本,您需要手动解决冲突。



为了进一步解释一个例子,假设您要合并 branchX 进入当前分支:

git merge --no-ff --no-commit branchX

然后你运行 git status 用于查看已修改文件状态的命令。

例如:

git status

# On branch master
# Changes to be committed:
#
#       modified:   file1
#       modified:   file2
#       modified:   file3
# Unmerged paths:
#   (use "git add/rm <file>..." as appropriate to mark resolution)
#
#       both modified:      file4
#

哪里 file1file2,和 file3 是git成功自动合并的文件。

这意味着改变了 master 和 branchX 因为所有这三个文件已经组合在一起而没有任何冲突。

您可以通过运行来检查合并的完成方式 git diff --cached;

git diff --cached file1
git diff --cached file2
git diff --cached file3

如果您发现某些合并不受欢迎,那么您可以

  1. 直接编辑文件
  2. 保存
  3. git commit

如果你不想合并 file1 并希望保留当前分支中的版本

git checkout HEAD file1

如果你不想合并 file2 并且只想要版本 branchX

git checkout branchX file2

如果你想 file3 要自动合并,不要做任何事情。

Git已经在这一点上合并了它。


file4 上面是Git失败的合并。这意味着在同一行上发生的两个分支都发生了变化。您需要手动解决冲突。您可以通过直接编辑文件或对所需分支中的版本运行checkout命令来放弃合并完成 file4 成为。


最后,别忘了 git commit


241
2018-05-21 03:00



小心但是:如果 git merge --no-commit branchX 只是一个快进,指针将被更新,因此--no-commit被默默地忽略 - cfi
@cfi如何添加 --no-ff 防止这种行为? - Eduardo Costa
我绝对建议用Eduardo的“--no-ff”选项更新这个答案。我阅读了整个事情(其他方面很棒)只是为了让我的合并得到快速转发。 - Funktr0n
该解决方案可提供最佳结果和灵活性。 - Thiago Macedo
与得票最多的答案不同,这个解决方案保留了我的合并历史记录,这对我很重要,因为我在分支机构之间来回编织部分提交。我没有尝试所有其他建议的解决方案,所以也许他们中的一些也这样做。 - ws_e_c421


我不喜欢上述方法。使用cherry-pick非常适合选择单个更改,但如果您想要引入除了一些不良更改之外的所有更改,那将是一种痛苦。这是我的方法。

没有 --interactive 您可以传递给git merge的参数。

这是替代方案:

你在分支'功能'中有一些变化,你想要以一种不邋way的方式将一些但不是全部的变为'master'(即你不想挑选并提交每一个)

git checkout feature
git checkout -b temp
git rebase -i master

# Above will drop you in an editor and pick the changes you want ala:
pick 7266df7 First change
pick 1b3f7df Another change
pick 5bbf56f Last change

# Rebase b44c147..5bbf56f onto b44c147
#
# Commands:
# pick = use commit
# edit = use commit, but stop for amending
# squash = use commit, but meld into previous commit
#
# If you remove a line here THAT COMMIT WILL BE LOST.
# However, if you remove everything, the rebase will be aborted.
#

git checkout master
git pull . temp
git branch -d temp

所以只需将它包装在shell脚本中,将master更改为$ to并将功能更改为$ from,您就可以了:

#! /bin/bash
# git-interactive-merge
from=$1
to=$2
git checkout $from
git checkout -b ${from}_tmp
git rebase -i $to
# Above will drop you in an editor and pick the changes you want
git checkout $to
git pull . ${from}_tmp
git branch -d ${from}_tmp

85
2017-08-28 18:47



请编辑您的答案,选择您的代码段并点击“101 010”图标(工具栏第二组中的第三个图标)将其缩进四个空格。这将修复格式化。 - akaihola
我修改了格式化 - 如果你想做一些提交,这是一个非常好的方法 - 1800 INFORMATION
我现在正在使用这种技术,它似乎运作得非常好。 - dylanfm
你可能想要改变 git rebase -i $to 至 git rebase -i $to || $SHELL,以便用户可以打电话 git --skip 如果rebase失败,必要时。也值得链接线 && 而不是换行符。 - sircolinton
不幸的是,似乎答案中的链接已经死了。 - ThomasW


还有另外一种方法:

git checkout -p

它是一种混合 git checkout 和 git add -p 并且可能正是您正在寻找的:

   -p, --patch
       Interactively select hunks in the difference between the <tree-ish>
       (or the index, if unspecified) and the working tree. The chosen
       hunks are then applied in reverse to the working tree (and if a
       <tree-ish> was specified, the index).

       This means that you can use git checkout -p to selectively discard
       edits from your current working tree. See the “Interactive Mode”
       section of git-add(1) to learn how to operate the --patch mode.

73
2017-08-25 01:31



这是迄今为止最简单,最简单的方法,只要您只需要可管理的更改数量即可合并。我希望更多人会注意到这个答案并对其进行投票。示例:git checkout --patch exp1 file_to_merge - Tyler Rick
类似的答案贴在这个问题上: stackoverflow.com/a/11593308/47185 - Tyler Rick
哦,我不知道结账有补丁!我确实检查了/重置/添加-p。 - Daniel C. Sobral
真的是最简单的方法。 git checkout -p featurebranch filename。最好的是当命令运行时,它会给你一个y / n / e /?/ ...等。用于决定如何合并文件的选项。我尝试使用e,我甚至可以在应用之前编辑补丁...它有多酷。用于合并来自其他分支的选择性文件的真正的一行。 - infoclogged


虽然其中一些答案非常好,但我觉得没有人真正回答OP的原始约束:从特定分支中选择特定文件。这个解决方案可以做到这一点,但如果有很多文件可能会很乏味。

让我们说你有 masterexp1,和 exp2 分支机构。您希望将每个实验分支中的一个文件合并为主文件。我会做这样的事情:

git checkout master
git checkout exp1 path/to/file_a
git checkout exp2 path/to/file_b

# save these files as a stash
git stash
# merge stash with master
git merge stash

这将为您提供所需的每个文件的文件内差异。而已。没什么。在版本之间进行完全不同的文件更改很有用 - 在我的例子中,将应用程序从Rails 2更改为Rails 3。

编辑:这将合并文件,但进行智能合并。我无法弄清楚如何使用这种方法来获取文件内的差异信息(也许它仍然会出现极端的差异。除非你使用,否则像空白这样烦人的小东西会被合并回来 -s recursive -X ignore-all-space 选项)


48
2018-05-07 11:38



另请注意:您可以从内联的给定分支执行多个文件,例如 git checkout exp1 path/to/file_a path/to/file_x - EMiller
这很漂亮。我做到了 git checkout feature <path>/* 获取文件组。 - isherwood
这工作正常,但添加了两个额外的提交对象。不是很大,但有点凌乱 - MightyPork
@MightyPork,你是对的。不幸的是,自从我很久以前写这篇文章以来,我不再确定为什么“git stash”和“git merge stash”步骤在那里而不是“git commit”。 - Eric Hu
我想,那很清楚。这样它就会合并一个文件,不一定会覆盖目标分支上的先前更改。 - MightyPork


1800 INFORMATION的答案完全正确。然而,作为一个git noob,“使用git cherry-pick”还不足以让我在互联网上更多地挖掘这一点,所以我想我会发布一个更详细的指南以防其他人在类似的船。

我的用例是想有选择地将其他人的github分支中的更改转换为我自己的。如果您已经有一个包含更改的本地分支,则只需执行步骤2和5-7。

  1. 创建(如果未创建)包含您要引入的更改的本地分支。

    $ git branch mybranch <base branch>

  2. 切换到它。

    $ git checkout mybranch

  3. 从其他人的帐户中下拉您想要的更改。如果您还没有想要将它们添加为遥控器。

    $ git remote add repos-w-changes <git url>

  4. 从他们的分支拉下一切。

    $ git pull repos-w-changes branch-i-want

  5. 查看提交日志以查看所需的更改:

    $ git log 

  6. 切换回要将更改拉入的分支。

    $ git checkout originalbranch

  7. Cherry用哈希一个接一个地挑选你的提交。

    $ git cherry-pick -x hash-of-commit

帽子提示: http://www.sourcemage.org/Git_Guide


42
2017-07-29 08:37



提示:首先使用 git cherry 命令(请参见手册)以识别尚未合并的提交。 - akaihola
这工作.. 1.创建一个新的分支2.创建一些文件/做了一些更改3.提交4. checkout主分支5.运行git cherry-pick -x hash-of-commit并解决merge合并冲突你是好的去。 - user2601010
您的链接不再有效。你能更新一下吗? - creep3007


以下是您可以替换的方法 Myclass.java 档案 master 分支机构 Myclass.java 在 feature1 科。即使这样也会奏效 Myclass.java 不存在 master

git checkout master
git checkout feature1 Myclass.java

请注意,这将覆盖 - 而不是合并 - 而忽略主分支中的本地更改。


37
2018-02-27 22:42



这不会合并。它只会使用feature1分支的更改覆盖master上的更改。 - Skunkwaffle
完美的是,我正在寻找这种合并的地方 theirs 覆盖 ours => +1干杯;) - olibre
有时你想要做的就是替换整个文件,所以这就是我想要的,但你需要确保你想要丢失你对这个文件所做的所有更改。 - MagicLAMP
最干净的解决方案,因为OP特别希望用另一个分支上的等效文件替换整个文件: 2. Manual copying of common files into a temp directory followed by ...copying out of the temp directory into the working tree. - Brent Faust


实际上,简单的方法 合并 来自两个分支的特定文件,不仅仅用来自另一个分支的文件替换特定文件。

第一步:扩散分支

git diff branch_b > my_patch_file.patch

创建当前分支和branch_b之间差异的补丁文件

第二步:在匹配模式的文件上应用补丁

git apply -p1 --include=pattern/matching/the/path/to/file/or/folder my_patch_file.patch

关于选项的有用说明

您可以使用 * 作为包含模式中的通配符。

斜杠不需要转义。

此外,您可以使用--exclude将其应用于除匹配模式的文件之外的所有内容,或者使用-R反转补丁

-p1选项是* unix patch命令的保留,以及补丁文件的内容在每个文件名前加上 a/要么 b/ (或者更多取决于补丁文件的生成方式),您需要将其剥离,以便它可以将实际文件计算到补丁需要应用到的文件的路径。

查看git-apply的手册页以获取更多选项。

第三步:没有第三步

显然你想要提交你的更改,但是谁说你在提交之前没有其他相关的调整。


22
2018-02-18 04:28



这在current_branch有许多需要保留的“附加”更改时非常有用。得到了branch_b带来的变化的差异为:git diff HEAD ... branch_b(是 - 三个句点做魔术)。 - Saad Malik
@masukomi,在第2步,您不应该将步骤1中创建的补丁文件添加为参数吗? - Spiralis
@Spiralis是的。添加它。最后的参数。谢谢。 - masukomi


以下是如何让历史记录只关注来自另一个分支的几个文件,即使更“简单”的合并会带来更多您不想要的更改。

首先,您将采取不寻常的步骤,提前声明您要提交的内容是合并,而不是git对工作目录中的文件执行任何操作:

git merge --no-ff --no-commit -s ours branchname1

。 。 。其中“branchname”是你声称要合并的东西。如果你马上提交,它将不做任何改变,但它仍然显示来自另一个分支的祖先。您可以添加更多分支/标签/等。如果需要,也可以到命令行。此时,提交没有任何更改,因此请从其他修订中获取文件。

git checkout branchname1 -- file1 file2 etc

如果您要从多个其他分支合并,请根据需要重复。

git checkout branchname2 -- file3 file4 etc

现在,来自其他分支的文件位于索引中,准备提交,具有历史记录。

git commit

并且你将在该提交消息中做很多解释。

但是请注意,如果不清楚,这是搞乱的事情要做。它不符合“分支”的精神,而樱桃选择是一种更诚实的方式来做你正在做的事情,在这里。如果你想为你上次没有带来的同一个分支上的其他文件做另一个“合并”,它会阻止你发送一条“已经是最新的”消息。这是我们应该拥有的不分支的症状,在“from”分支中应该是多个不同的分支。


20
2017-11-04 10:51



你的第一个命令(git merge --no-ff --no-commit -s outs branchname1)正是我所寻找的!谢谢! - RobM
有了多个分支,需要历史记录,需要合并单个文件,并且必须在推送之前更改文件的内容,这似乎是一个不错的选择。例如dev => master,但是您想在推送到master之前更改主机定义或类似内容。 - timss


我知道我有点晚了,但这是我合并选择性文件的工作流程。

#make a new branch ( this will be temporary)
git checkout -b newbranch
# grab the changes 
git merge --no-commit  featurebranch
# unstage those changes
git reset HEAD
(you can now see the files from the merge are unstaged)
# now you can chose which files are to be merged.
git add -p
# remember to "git add" any new files you wish to keep
git commit

14
2017-10-29 20:54



我对此略有不同。而不是合并我樱桃采摘。它完成了这项工作。这种方法的唯一缺点是你失去了对原始提交哈希的引用。 - Matt Florence