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

时间:2009-01-16 04:55:09

标签: git git-merge git-cherry-pick git-patch

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

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

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

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

我考虑过的方法:

  1. git merge --no-commit然后手动取消大量编辑,我不想在分支机构之间进行通用。

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

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

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

26 个答案:

答案 0 :(得分:910)

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

要点:

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

    $ 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"
    

答案 1 :(得分:430)

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

如果您想要的更改不在单独提交中,请使用此处显示的方法split the commit into individual commits。粗略地说,您使用git rebase -i来获取原始提交进行编辑,然后git reset HEAD^选择性地还原更改,然后git commit将该位提交为历史记录中的新提交。

红帽杂志中的

There is another nice method here,如果你想将不同的更改分成单个文件,他们使用git add --patch或可能git add --interactive只允许你添加一部分大块(在该页面中搜索“拆分”)。

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

答案 2 :(得分:283)

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

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

其中branchX是您要合并到当前分支的分支。

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

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

1)您想要真正的合并。

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

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

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

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

git checkout HEAD file1

这将检索当前分支中file1的版本并覆盖Git的file1 automerged。

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

执行命令

git checkout branchX file1

这将检索file1branchX的版本并覆盖由Git自动合并的file1

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

在这种情况下,您可以直接编辑修改后的file1,将其更新为您想要的file1版本,然后再提交。

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



为了进一步解释一个例子,假设您要将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
#

file1file2file3是git已成功自动合并的文件。

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

您可以通过运行git diff --cached;

来检查合并的完成方式
git diff --cached file1
git diff --cached file2
git diff --cached file3

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

  1. 直接编辑文件
  2. 保存
  3. git commit
  4. 如果您不想合并file1并希望保留当前分支中的版本

    运行

    git checkout HEAD file1
    

    如果您不想合并file2并且只想要branchX

    中的版本

    运行

    git checkout branchX file2
    

    如果您希望file3自动合并,请不要执行任何操作。

    Git此时已将其合并。


    上面的

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


    最后,不要忘记git commit

答案 3 :(得分:96)

我不喜欢上述方法。使用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

答案 4 :(得分:84)

还有另一种方法:

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.

答案 5 :(得分:50)

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

假设您拥有masterexp1exp2分支机构。您希望将每个实验分支中的一个文件合并为主文件。我会做这样的事情:

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选项,否则像空白一样的烦人的小东西会被合并回来)< / p>

答案 6 :(得分:46)

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

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

答案 7 :(得分:40)

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

git checkout master
git checkout feature1 Myclass.java

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

答案 8 :(得分:27)

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

第一步:扩散分支

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的手册页以获取更多选项。

第三步:没有第三步

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

答案 9 :(得分:22)

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

首先,您将采取不同寻常的步骤,提前声明您要提交的内容是合并,而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”分支中应该是多个不同的分支。

答案 10 :(得分:15)

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

#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

答案 11 :(得分:15)

我发现this post包含最简单的答案。仅仅这样做:

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

示例:

$ #pulling .gitignore file from branchB into current branch
$ git checkout branchB .gitignore

有关详细信息,请参阅帖子。

答案 12 :(得分:14)

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

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

如果你跑

git status

你会看到文件已经上演......

然后运行

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

简单。

答案 13 :(得分:12)

奇怪的是,git仍然没有“开箱即用”的方便工具。当我通过当前版本分支中的一些错误修正更新一些旧版本分支(仍有很多软件用户)时,我会大量使用它。在这种情况下,通常需要从trunk中的文件中快速获取一些代码行,忽略许多其他更改(不应该进入旧版本)......当然,在这种情况下需要交互式三向合并,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>

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

答案 14 :(得分:9)

这不是你想要的,但它对我有用:

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

这是一些答案的混合。

答案 15 :(得分:8)

我遇到了与上面提到的完全相同的问题。但我发现this git blog更清楚地解释了答案。

来自上述链接的命令:

#You are in the branch you want to merge to
git checkout <branch_you_want_to_merge_from> <file_paths...>

答案 16 :(得分:7)

我喜欢上面的'git-interactive-merge'答案,但有一个更简单。让git使用交互式的rebase组合并执行此操作:

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

所以情况是你想要来自'feature'分支的C1和C2(分支点'A'),但现在不需要其余的。

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

如上所述,您将进入交互式编辑器,在该编辑器中为C1和C2选择“拾取”行(如上所述)。保存并退出,然后它将继续使用rebase并为你提供分支'temp'和主人+ C1 + C2的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

答案 17 :(得分:7)

我会做一个

  

git diff commit1..commit2 filepattern | git-apply --index&amp;&amp; git commit

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

被盗:http://www.gelato.unsw.edu.au/archives/git/0701/37964.html

答案 18 :(得分: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 w 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

答案 19 :(得分:4)

您可以使用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

另请参阅:How do I merge a sub directory in git?

答案 20 :(得分:3)

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

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

答案 21 :(得分:3)

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

1。暂时复制分支
$ git checkout -b temp_branch

2。重设为上次想要的提交
$ git reset --hard HEAD~n,其中n是您需要返回的提交次数

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

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

答案 22 :(得分:3)

git reset --soft branch呢?我很惊讶没有人提及它。

对我来说,这是从另一分支中选择性地选择更改的最简单方法,因为此命令将所有diff更改放入我的工作树中,因此我可以轻松地选择或还原所需的内容。这样,我可以完全控制已提交的文件。

答案 23 :(得分:2)

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

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

答案 24 :(得分:2)

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

下面的步骤假定您有两个分支target-branchsource-branch,并且要合并的目录dir-to-merge位于source-branch中。还要假设您不想更改和保留历史记录的目标中还有其他目录,例如dir-to-retain。另外,假设dir-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.

答案 25 :(得分:0)

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

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

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

好的,假设我有三个文件:

$ ls
a   b   f

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

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

这是其他分支的版本:

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

现在的诀窍是我们将使用索引作为便笺簿(毕竟,这就是它的用途)。所以我们开始(第 1 步)确保我们的版本被复制到索引中:

$ git add a

现在(第 2 步)我们可以使用 restoreotherbranch 获取版本(现在,restorecheckout 更好,因为它让我们更清楚地说话) :

$ git restore --source otherbranch a

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

$ 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

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

 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(同样,这是现代方式;restorereset 更好并且可以应用于单个文件):< /p>

$ git restore a

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

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

此时我们可以提交,但我们不必;我们已经完成了我们计划完成的任务。