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

我在一个新项目上使用 git,该项目有两个并行的但目前仍处于试验阶段的开发分支:

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

exp1exp2代表两种非常不同的体系结构方法。直到我步入正轨,我才知道哪一个(如果有的话)会工作。当我在一个分支中取得进展时,有时我会进行一些编辑,而这些编辑在另一分支中将非常有用,并且希望将这些合并。

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

我考虑过的方法:

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

  2. 手动将常用文件复制到 temp 目录中,然后git checkout移至另一个分支,然后从 temp 目录中进行更多手动复制到工作树中。

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

所有这三种方法似乎都是乏味且容易出错。我希望有更好的方法;类似于 filter path 参数,可以使git-merge更具选择性。

答案

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

摘要:

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

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

    提示:也可以在没有链接的情况下使用--就像在链接文章中看到的那样。

  • 或有选择地合并大块

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

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

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

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

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

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

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

拆分更改后,您现在可以选择所需的内容。

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

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

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

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

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

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

在这种情况下,您将接受合并文件,就像 Git 自动合并它们然后提交它们一样。

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

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

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

git checkout HEAD file1

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

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

跑:

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

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

还有另一种方法:

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.

尽管其中一些答案相当不错,但我觉得没有一个人能够真正回答 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

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

编辑 :这将合并文件,但会进行智能合并。我无法弄清楚如何使用此方法获取文件中的差异信息(也许仍然会存在极大差异。除非您使用-s recursive -X ignore-all-space选项)

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

下面是如何可以更换Myclass.java文件中master与分支Myclass.javafeature1支。即使Myclass.javamaster上不存在,它也将起作用。

git checkout master
git checkout feature1 Myclass.java

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

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

第一步:区分分支

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 以获得更多选项。

第三步:没有第三步

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

通过这种方法,即使更 “简单” 的合并会带来很多您不希望的更改,也可以使历史记录仅以最小的麻烦跟踪另一个分支中的几个文件。

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

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

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

git checkout branchname1 -- file1 file2 etc

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

git checkout branchname2 -- file3 file4 etc

现在,来自另一个分支的文件已包含在索引中,随时可以提交,并带有历史记录。

git commit

并且您将在该提交消息中进行很多说明。

但是请注意,以防万一,这是很麻烦的事情。这并不是出于 “分支” 的目的,而在这里摘樱桃是一种更诚实的方式来做您想做的事情。如果您想对上次未带过的同一分支上的其他文件进行另一次 “合并”,则会显示 “已经更新” 消息,从而阻止您。这是我们不应该分支时的一种症状,在 “from” 分支中应该有多个不同的分支。