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

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

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

exp1exp2代表了两种截然不同的架构方法。在我进一步研究之前,我无法知道哪一种(如果有的话)可行。当我在一个分支中取得进展时,我有时会编辑一些对另一个分支有用的内容,并希望合并这些内容。

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

我考虑过的方法:

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

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

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

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

1000408 次浏览

您可以使用樱桃采摘命令从一个分支获取单个提交。

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

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

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

1800信息的回答是完全正确的。作为一个刚接触Git的人,虽然,“使用git chiry-选择”不足以让我在没有在互联网上更多挖掘的情况下弄清楚这一点,所以我想我会发布一个更详细的指南,以防其他人也在类似的船上。

我的用例是希望有选择地将更改从其他人的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(存档副本)

我不喜欢上面的方法。使用樱桃挑选是很好的选择一个单一的变化,但如果你想带来所有的变化,除了一些坏的,这是一个痛苦。这是我的方法。

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

以下是替代方案:

你在分支“功能”中有一些变化,你想以一种不马虎的方式将一些但不是全部带到“主”(即你不想选择并提交每一个)

git checkout featuregit checkout -b tempgit rebase -i master
# Above will drop you in an editor and pick the changes you want ala:pick 7266df7 First changepick 1b3f7df Another changepick 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 mastergit pull . tempgit branch -d temp

因此,只需将其包装在外壳脚本中,将master更改为$to并将功能更改为$from,您就可以开始了:

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

tl; dr

git checkout source_branch -- path/to/file# resolve conflicts if anygit commit -am '...'

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

总结:

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

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

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

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

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

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

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

# Make a new branch (this will be temporary)git checkout -b newbranch
# Grab the changesgit merge --no-commit  featurebranch
# Unstage those changesgit 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 keepgit commit

我会做一个

git diff的可提交文件git diff的可提交文件git diff的可提交文件git diff的可提交文件git diff的可提交文件git diff的可提交文件git diff的可提交文件git diff的可提交文件git diff的可提交文件git diff的可提交文件git diff的可提交文件git diff的可提交文件git diff的可提交文件

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

Re:如何从一个分支只拉取几个文件到另一个分支?被偷了

我编写了自己的脚本名为“p合并”来部分合并目录。这是一个半成品,我仍在学习Git和Bash脚本。

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

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

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

#!/bin/bash
E_BADARGS=65
if [ $# -ne 2 ]thenecho "Usage: `basename $0` branch path"exit $E_BADARGSfi
git merge $1 --no-commitIFS=$'\n'
# List of changes due to merge | replace nulls with newlines | strip lines to just filenames | ensure lines are uniquefor f in $(git status --porcelain -z -uno | tr '\000' '\n' | sed -e 's/^[[:graph:]][[:space:]]\{1,\}//' | uniq); do[[ $f == $2* ]] && continueif git reset $f >/dev/null 2>&1; then# Reset failed... file was previously unversionedecho Deleting $frm $felseecho Reverting $fgit checkout -- $f >/dev/null 2>&1fidoneunset IFS

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

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

git checkout mastergit checkout exp1 path/to/file_agit checkout exp2 path/to/file_b
# Save these files as a stashgit stash
# Merge stash with mastergit merge stash

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

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

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

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

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

--no-commit选项将暂存Git合并的文件而不实际提交它们。这将使您有机会随心所欲地修改合并的文件,然后自己提交。

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

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

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

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

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

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

git checkout HEAD file1

这将在当前分支中检索file1的版本并覆盖由Git自动擦除的file1

3)如果你想要在分支X中的版本(而不是真正的合并)。

运行:

git checkout branchX file1

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

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#

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

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

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

git diff --cached file1git diff --cached file2git diff --cached file3

如果你发现一些合并不可取,那么你可以

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

如果您不想合并file1并想在当前分支中保留版本

运行

git checkout HEAD file1

如果您不想合并file2并且只想要branchX中的版本

运行

git checkout branchX file2

如果您希望file3自动合并,请不要做任何事情。

Git此时已经合并了它。


上面的file4是Git失败的合并。这意味着两个分支中的更改发生在同一行。这是您需要手动解决冲突的地方。您可以通过直接编辑文件或在您希望file4成为的分支中运行版本的签出命令来丢弃合并完成。


最后,不要忘记git commit

我喜欢以前'git-inter这辆车-合并'的答案,但有一个更简单的答案。让Git使用交互式和到的rebase组合为您执行此操作:

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

因此,情况是您希望从“特征”分支(分支点“A”)中获得C1和C2,但目前没有其他分支。

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

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

      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

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

第一步:区分树枝

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

关于选项的有用说明

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

斜线不需要转义。

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

-p1选项是*Unix补丁命令的保留,并且补丁文件的内容在每个文件名前面加上a/b/(或更多,取决于补丁文件的生成方式),您需要剥离这些文件名,以便它可以找出真正的文件到补丁需要应用的文件的路径。

查看git的手册页-申请更多选项。

第三步:没有第三步

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

我遇到了你上面提到的完全相同的问题。但是我发现在解释答案时这个git博客更清楚。

来自上面链接的命令:

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

还有另一条路要走:

git checkout -p

它是git checkoutgit add -p之间的混合,可能正是您正在寻找的:

   -p, --patchInteractively select hunks in the difference between the <tree-ish>(or the index, if unspecified) and the working tree. The chosenhunks 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 discardedits from your current working tree. See the “Interactive Mode”section of git-add(1) to learn how to operate the --patch mode.

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

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

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

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

git checkout branchname1 -- file1 file2 etc.

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

git checkout branchname2 -- file3 file4 etc.

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

git commit

在提交消息中,您将有很多解释要做。

但请注意,如果不清楚,这是一个混乱的事情。这不符合“分支”的精神,在这里,选择是一种更诚实的方式来做你正在做的事情。如果你想对同一分支上的其他文件进行另一次“合并”,而你上次没有带来,它会让你停止“已经更新”消息。这是我们应该分支时没有分支的症状,因为“来自”分支应该不止一个不同的分支。

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

git checkout mastergit checkout feature1 Myclass.java

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

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

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

如果你逃跑

git status

您将看到文件已经暂存…

那就跑

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

简单。

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

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

示例

将. gitignore文件从分支B拉入当前分支:

git checkout branchB .gitignore

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

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

你可以很容易地做到:

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

[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>

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

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

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

这是一些答案的混合。

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

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

要执行合并,请改用-m

另见:如何在Git中合并子目录?

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

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

另见https://sites.google.com/site/icusite/setup/git-difftool

通过文件进行选择性合并/提交的简单方法:

git checkout dstBranchgit merge srcBranch
// Make changes, including resolving conflicts to single filesgit add singleFile1 singleFile2git commit -m "message specific to a few files"git reset --hard # Blow away uncommitted changes

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

1.暂时重复分支
$ git checkout -b temp_branch

2.重置为上次通缉
$ git reset --hard HEAD~n,其中n是需要返回的提交数

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

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

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

下面的步骤假设您有两个分支target-branchsource-branch,并且要合并的目录dir-to-merge位于source-branch中。还假设您在目标中有其他目录,例如dir-to-retain,您不想更改和保留历史记录。此外,假设dir-to-merge中存在合并冲突。

git checkout target-branchgit 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.

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

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

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

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

我的方法是基于https://stackoverflow.com/a/39916536/341994中采取的方法。不幸的是,这个问题被关闭为重复(错误的是,在我看来:这不是这个问题的重复,而且回答者在那里所做的回答是错误的)。但是那个答案有一些问题,所以我改进了这个方法。我用restore代替了checkoutreset,而且我懒得提交任何我不需要提交的东西。

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

$ lsa   b   f

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

$ cat aline oneline twoline threeline fourline five

这是另一个分支的版本:

$ git show otherbranch:aline oneline two editedline threeline fourline fiveline six

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

$ git add a

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

$ git restore --source otherbranch a

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

$ cat aline oneline two editedline threeline fourline fiveline six

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

$ git diff adiff --git a/a b/aindex abf51fa..333614b 100644--- a/a+++ b/a@@ -1,6 +1,7 @@line one-line two+line two editedline threeline fourline five+line six

很好,现在我们准备好进行键移动(第3步)。我们从工作树到索引执行文件的交互式补丁add

我们可以说git add -p a来启动交互式补丁过程,在这种情况下,我们一次被喂一个大块。但在这种情况下,只有一个大块,无论如何我都想编辑它,所以我说:

$ git add --e a

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

 line one-line two+line two editedline threeline fourline five+line six

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

 line oneline twoline threeline fourline five+line six

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

$ cat aline oneline two editedline threeline fourline fiveline six

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

$ git restore a

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

$ cat aline oneline twoline threeline fourline fiveline six

我们可以在这一点上承诺,但我们不必这样做;我们已经完成了我们打算完成的事情。

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

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

我的方法:

# New branch from a clean starting point, e.g. mastergit 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") everythinggit restore --staged .
# Interactively add hunks to be staged for commitgit add -p

如果您是吉特克拉肯用户这里有一个小指南

总结:

  1. 移动到您想要带来更改的分支。(例如开发)
  2. 右键单击具有新更改的分支并选择“樱桃采摘提交”选项(例如功能-ABC)。
  3. 最后接受并检查是否有冲突。