1627

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

  • master: 现有代码库的导入以及我通常确定的一些修改
  • exp1: 实验分支#1
  • exp2: 实验分支#2

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

将选择性更改从一个开发分支合并到另一个分支的最佳方法是什么?

我考虑过的方法:

  1. git merge --no-commit然后手动取消暂存大量我不想在分支之间通用的编辑。

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

  3. 上面的一个变种。暂时放弃exp分支并使用两个额外的本地存储库进行实验。这使得手动复制文件更加简单。

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

4

28 回答 28

1077

我遇到了与您上面提到的完全相同的问题。但我在解释答案时发现这一点更清楚。

概括:

  • 查看要合并的分支的路径,

     $ git checkout source_branch -- <paths>...
    
    Hint: It also works without `--` like seen in the linked post.
    
  • 或选择性地合并帅哥

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

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

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

     $ git commit -m "'Merge' these changes"
    
于 2009-08-31T05:53:03.230 回答
532

您可以使用cherry-pick命令从一个分支获取单个提交。

如果您想要的更改不在单个提交中,则使用此处显示的方法将提交拆分为单个提交。粗略地说,您用于git rebase -i获取原始提交以进行编辑,然后git reset HEAD^选择性地还原更改,然后git commit将该位作为历史中的新提交提交。

Red Hat Magazine 中还有另一种不错的方法,如果您想将不同的更改拆分到单个文件(在该页面中搜索“拆分”),他们使用git add --patch或可能允许您仅添加部分数据。git add --interactive

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

于 2009-01-16T06:01:26.250 回答
405

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

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

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

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

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

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

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

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

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

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

git checkout HEAD file1

这将检索file1当前分支中的版本并覆盖file1Git 自动合并的版本。

3)如果您想要 branchX 中的版本(而不是真正的合并)。

跑:

git checkout branchX file1

这将检索file1in的版本branchX并覆盖file1Git 自动合并的版本。

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
#

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

这意味着所有这三个文件的masterbranchX的更改已组合在一起而没有任何冲突。

您可以通过运行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 的失败合并。这意味着在同一行上发生的两个分支中都有变化。这是您需要手动解决冲突的地方。您可以通过直接编辑文件或为要file4成为的分支中的版本运行 checkout 命令来放弃合并完成。


最后,别忘了git commit

于 2011-09-03T08:48:40.487 回答
114

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

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

这是替代方案:

您在分支“功能”中有一些更改,并且您想以一种不马虎的方式将其中一些但不是全部带到“主控”(即您不想挑选并提交每个)

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 并将 feature 更改为 $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
于 2009-05-21T03:00:21.950 回答
106

还有另一种方法:

git checkout -p

它是和之间的混合git checkoutgit 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.
于 2012-08-28T18:47:17.393 回答
65

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

假设您有masterexp1exp2分支。您想将每个实验分支中的一个文件合并到 master 中。我会做这样的事情:

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

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

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

于 2011-08-25T01:31:22.263 回答
54

1800 INFORMATION的回答是完全正确的。但是,作为 Git 的新手,“使用 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

于 2009-05-07T11:38:51.973 回答
44

以下是如何将分支中的Myclass.java文件替换为master分支Myclass.java中的文件feature1。即使在Myclass.java上不存在它也会起作用master

git checkout master
git checkout feature1 Myclass.java

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

于 2013-07-29T08:37:12.800 回答
31

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

第一步:区分分支

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 补丁命令的保留,补丁文件的内容在每个文件名前面加上a/b/(或更多取决于补丁文件的生成方式),您需要删除它,以便它可以计算将实际文件输出到需要应用补丁的文件的路径。

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

第三步:没有第三步

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

于 2012-02-27T22:42:08.593 回答
25

以下是如何让历史记录跟踪来自另一个分支的几个文件,而无需大惊小怪,即使更“简单”的合并会带来更多您不想要的更改。

首先,您将采取不同寻常的步骤,提前声明您将要提交的是一个合并,而 Git 对您工作目录中的文件根本不做任何事情:

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

...其中“分支名称”是您声称要合并的任何内容。如果您要立即提交,它不会做任何更改,但它仍会显示来自另一个分支的祖先。如果需要,您也可以在命令行中添加更多分支、标签等。不过,此时没有要提交的更改,所以接下来从其他修订版中获取文件。

git checkout branchname1 -- file1 file2 etc.

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

git checkout branchname2 -- file3 file4 etc.

现在来自另一个分支的文件在索引中,准备好提交,并带有历史记录。

git commit

您将在该提交消息中进行大量解释。

但请注意,如果不清楚,这是一件搞砸的事情。它不符合“分支”的精神,而cherry-pick是一种更诚实的方式来做你想做的事情,在这里。如果您想对上次未带入的同一分支上的其他文件进行另一次“合并”,它将以“已经是最新的”消息阻止您。这是我们应该有的时候没有分支的症状,因为“来自”分支应该不止一个不同的分支。

于 2012-09-27T02:55:45.240 回答
18

这是我合并选择性文件的工作流程。

# 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
于 2010-02-18T04:28:19.650 回答
17

最简单的方法是将存储库设置为要合并的分支,然后运行

git checkout [branch with file] [path to file you would like to merge]

如果你跑

git status

你会看到文件已经暂存...

然后运行

git commit -m "Merge changes on '[branch]' to [file]"

简单的。

于 2013-10-29T20:54:31.263 回答
16

奇怪的是,git 还没有“开箱即用”这么方便的工具。当仅通过当前版本分支中的一些错误修复来更新一些旧版本分支(仍然有很多软件用户)时,我会大量使用它。在这种情况下,通常需要从主干文件中快速获取一些代码行,忽略许多其他更改(不应该进入旧版本)......当然还有交互式三向合并在这种情况下需要,git checkout --patch <branch> <file path>不可用于此选择性合并目的。

你可以轻松做到:

只需将此行添加到[alias]全局.gitconfig或本地.git/config文件中的部分:

[alias]
    mergetool-file = "!sh -c 'git show $1:$2 > $2.theirs; git show $(git merge-base $1 $(git rev-parse HEAD)):$2 > $2.base; /C/BCompare3/BCompare.exe $2.theirs $2 $2.base $2; rm -f $2.theirs; rm -f $2.base;' -"

这意味着您使用 Beyond Compare。如果需要,只需更改为您选择的软件。或者,如果您不需要交互式选择性合并,您可以将其更改为三向自动合并:

[alias]
    mergetool-file = "!sh -c 'git show $1:$2 > $2.theirs; git show $(git merge-base $1 $(git rev-parse HEAD)):$2 > $2.base; git merge-file $2 $2.base $2.theirs; rm -f $2.theirs; rm -f $2.base;' -"

然后像这样使用:

git mergetool-file <source branch> <file path>

这将为您提供其他分支中任何文件的真正选择性树方式合并机会。

于 2014-07-03T04:29:55.433 回答
14

我发现这篇文章包含最简单的答案。只需这样做:

git checkout <branch from which you want files> <file paths>

例子

将 .gitignore 文件从 branchB 拉到当前分支:

git checkout branchB .gitignore

有关更多信息,请参阅帖子。

于 2013-11-04T10:51:34.490 回答
9

我遇到了与您上面提到的完全相同的问题。但我发现这个 Git 博客在解释答案时更加清晰。

来自上述链接的命令:

# You are in the branch you want to merge to
git checkout <branch_you_want_to_merge_from> <file_paths...>
于 2012-06-19T10:15:42.790 回答
9

它不完全是您想要的,但它对我很有用:

git checkout -p <branch> -- <paths> ...

这是一些答案的混合。

于 2016-02-09T01:02:52.680 回答
8

我会做一个

git diff commit1..commit2 文件模式 | git-apply --index && git commit

通过这种方式,您可以限制来自分支的文件模式的提交范围。

它是从Re: 如何只将几个文件从一个分支拉到另一个分支的?

于 2011-01-26T10:50:17.233 回答
7

我喜欢之前的“git-interactive-merge”答案,但有一个更简单的。让 Git 使用交互式和到的 rebase 组合为您执行此操作:

      A---C1---o---C2---o---o feature
     /
----o---o---o---o master

所以情况是你想要'feature'分支(分支点'A')中的C1和C2,但现在没有其他的。

# git branch temp feature
# git checkout master
# git rebase -i --onto HEAD A temp

与前面的答案一样,它会将您带到交互式编辑器中,您可以在其中为 C1 和 C2 选择“选择”行(如上所述)。保存并退出,然后它将继续进行 rebase 并在 master + C1 + C2 处为您提供分支“temp”和 HEAD:

      A---C1---o---C2---o---o feature
     /
----o---o---o---o-master--C1---C2 [HEAD, temp]

然后您可以将 master 更新为 HEAD 并删除 temp 分支,您就可以开始了:

# git branch -f master HEAD
# git branch -d temp
于 2011-12-09T19:46:52.703 回答
7

对我来说,git reset --soft branch这是从另一个分支中选择性地选择更改的最简单方法,因为此命令将所有差异更改放入我的工作树中,我可以轻松地选择或恢复我需要的那个。

通过这种方式,我可以完全控制提交的文件。

于 2019-04-01T22:08:02.457 回答
6

我编写了自己的脚本“pmerge”来部分合并目录。这是一项正在进行的工作,我仍在学习 Git 和 Bash 脚本。

此命令使用git merge --no-commit然后取消应用与提供的路径不匹配的更改。

用法:git pmerge branch path
示例:git merge develop src/

我没有对它进行广泛的测试。工作目录应该没有任何未提交的更改和未跟踪的文件。

#!/bin/bash

E_BADARGS=65

if [ $# -ne 2 ]
then
    echo "Usage: `basename $0` branch path"
    exit $E_BADARGS
fi

git merge $1 --no-commit
IFS=$'\n'

# List of changes due to merge | replace nulls with newlines | strip lines to just filenames | ensure lines are unique
for f in $(git status --porcelain -z -uno | tr '\000' '\n' | sed -e 's/^[[:graph:]][[:space:]]\{1,\}//' | uniq); do
    [[ $f == $2* ]] && continue
    if git reset $f >/dev/null 2>&1; then
        # Reset failed... file was previously unversioned
        echo Deleting $f
        rm $f
    else
        echo Reverting $f
        git checkout -- $f >/dev/null 2>&1
    fi
done
unset IFS
于 2011-05-27T01:33:15.090 回答
6

您可以使用read-tree将给定的远程树读取或合并到当前索引中,例如:

git remote add foo git@example.com/foo.git
git fetch foo
git read-tree --prefix=my-folder/ -u foo/master:trunk/their-folder

要执行合并,请-m改用。

另请参阅:如何在 Git 中合并子目录?

于 2016-02-25T18:25:39.020 回答
4

按文件选择性合并/提交的简单方法:

git checkout dstBranch
git merge srcBranch

// Make changes, including resolving conflicts to single files
git add singleFile1 singleFile2
git commit -m "message specific to a few files"
git reset --hard # Blow away uncommitted changes
于 2017-11-22T17:25:26.540 回答
3

当两个分支的当前提交之间只有几个文件发生更改时,我通过浏览不同的文件手动合并更改。

git difftool <branch-1>..<branch-2>

另请参阅 https://sites.google.com/site/icusite/setup/git-difftool

于 2017-10-07T19:49:36.253 回答
3

如果您没有太多已更改的文件,这将使您没有额外的提交。

1. 临时复制分支
$ git checkout -b temp_branch

2. Reset to last Wanted commit
$ git reset --hard HEAD~n ,n你需要返回的提交数在哪里

3.从原始分支签出每个文件
$ git checkout origin/original_branch filename.ext

现在,如果需要,您可以提交并强制推送(覆盖远程)。

于 2018-10-16T18:15:48.053 回答
3

如果您只需要合并一个特定的目录并保留其他所有内容并保留历史记录,您可以尝试这个...在实验之前创建一个新target-branch目录master

下面的步骤假设你有两个分支target-branch和,你要合并source-branch的目录在. 还假设您在目标中有其他目录,您不想更改和保留历史记录。此外,假设.dir-to-mergesource-branchdir-to-retaindir-to-merge

git checkout target-branch
git merge --no-ff --no-commit -X theirs source-branch
# the option "-X theirs", will pick theirs when there is a conflict. 
# the options "--no--ff --no-commit" prevent a commit after a merge, and give you an opportunity to fix other directories you want to retain, before you commit this merge.

# the above, would have messed up the other directories that you want to retain.
# so you need to reset them for every directory that you want to retain.
git reset HEAD dir-to-retain
# verify everything and commit.
于 2018-11-06T16:35:35.917 回答
1

我将专注于我感兴趣的这个问题的子集:我有两个分支,我想将一个文件从一个文件伪合并到另一个文件中。

(我说“伪合并”是因为我不需要或不想要合并提交;我只想以我认为合适的方式组合文件的两个版本的贡献。)

我的方法基于https://stackoverflow.com/a/39916536/341994中采用的方法。不幸的是,该问题已作为重复项关闭(错误地,在我看来:它不是该问题的重复项,并且回答者在那里所做的回答和关闭重复项是错误的)。但是这个答案有一些问题,所以我对方法进行了现代化和清理。而不是checkoutand reset,我使用restore, 并且我不会费心去做任何我不需要做的事情。

好的,想象一下我有三个文件:

$ ls
a   b   f

但我只想伪合并其中一个,a,来自otherbranch。让我们看看他们,看看情况会是什么样子。这是我的版本:

$ cat a
line one
line two
line three
line four
line five

这是otherbranch的版本:

$ git show otherbranch:a
line one
line two edited
line three
line four
line five
line six

现在这里的技巧是我们将使用索引作为便签本(毕竟,这就是它的用途)。因此,我们从确保将我们的版本复制到索引中开始(步骤 1):

$ git add a

现在(第 2 步)我们可以使用restore从以下位置获取版本otherbranch(现在,这restorecheckout它让我们说得更清楚):

$ git restore --source otherbranch a

乍一看,这看起来很糟糕。我们现在已经完全用来自的版本覆盖了我们的 a otherbranch,如您所见:

$ cat a
line one
line two edited
line three
line four
line five
line six

但不用担心!之前版本的 a 仍在索引中,如您所见:

$ git diff a
diff --git a/a b/a
index abf51fa..333614b 100644
--- a/a
+++ b/a
@@ -1,6 +1,7 @@
 line one
-line two
+line two edited
 line three
 line four
 line five
+line six

很好,现在我们已经为关键动作做好了准备(步骤 3)。我们对从工作树到索引的文件进行交互式补丁。 add

我们可以说git add -p a开始交互式补丁过程,在这种情况下,我们一次只吃一大块。但在这种情况下,只有一个大块,我还是想编辑它,所以我说:

$ git add --e a

结果是我们在编辑器中打开了一个差异补丁文件!它看起来像这样:

 line one
-line two
+line two edited
 line three
 line four
 line five
+line six

通过仔细编辑,我们现在可以决定我们想要接受哪些部分以及我们不接受哪些部分。让我们接受“第六行”而不是“第二行已编辑”。所以我们编辑成这样:

 line one
 line two
 line three
 line four
 line five
+line six

我们关闭编辑器并将补丁应用于 a 的索引版本。但我们还没有完成!a的otherbranch版本仍在工作树中:

$ cat a
line one
line two edited
line three
line four
line five
line six

我们喜欢的版本在索引中,记得吗?为了得到它,(第 4 步)我们只需调用git restore简单明了的方法(同样,这是现代方式;比单个文件restore更好reset并且可以应用于单个文件):

$ git restore a

现在我们的 a 是正确的,我们都完成了:

$ cat a
line one
line two
line three
line four
line five
line six

此时我们可以提交,但我们不必这样做;我们已经完成了我们打算完成的事情。

于 2021-05-02T21:30:39.460 回答
0

我想要什么:交互式地从一个分支(有几个混乱的提交)中挑选大块到一个新分支中的一个干净的提交中。

git diff如果该差异中有任何二进制文件, +git apply将不起作用。

我的做法:

# New branch from a clean starting point, e.g. master
git checkout new-clean-branch origin/master

# Get all changes from the messy branch
# (quote the star so your shell doesn't expand it)
git checkout messy-branch -- '*'

# Unstage ("un-add") everything
git restore --staged .

# Interactively add hunks to be staged for commit
git add -p
于 2021-10-05T13:25:53.860 回答
0

如果你是Gitkraken用户,这里有一个小指南

总之:

  1. 移动到要进行更改的分支。(例如开发)
  2. 右键单击有新更改的分支并选择“ cherrypick commit ”选项(例如,功能-ABC)。
  3. 最后接受并检查是否存在冲突。
于 2021-11-20T16:40:17.383 回答