繁体   English   中英

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

[英]How can I selectively merge or pick changes from another branch in Git?

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

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

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

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

我考虑过的方法:

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

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

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

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

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

总结:

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

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

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

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

Red Hat Magazine 中还有另一种不错的方法,他们使用git add --patchgit 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 非常适合挑选单个更改,但是如果您想引入所有更改,除了一些不好的更改,则很痛苦。 这是我的方法。

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

这是替代方案:

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

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

这将为您提供所需的每个文件的文件内差异。 没有更多了。 一点也不少。 在版本之间进行完全不同的文件更改非常有用——在我的情况下,将应用程序从Ruby on Rails 2 更改为 Ruby on Rails 3。

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

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

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

git checkout master
git checkout feature1 Myclass.java

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

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

第一步:区分分支

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

第三步:没有第三步

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

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

首先,您将采取不同寻常的步骤,提前声明您将要提交的是合并,而 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

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

但请注意,如果不清楚,这是一件一团糟的事情。 这不符合“分支”的精神,并且在此处选择樱桃是一种更诚实的方式来做您要做的事情。 如果您想对上次没有带来的同一分支上的其他文件进行另一次“合并”,它会以“已经是最新的”消息阻止您。 这是我们应该分支时不分支的一种症状,因为“来自”分支应该是多个不同的分支。

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

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

如果你跑

git status

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

然后运行

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

简单。

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

# 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

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

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

示例

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

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 博客在解释答案时更清楚。

来自上述链接的命令:

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

这不完全是您要查找的内容,但对我很有用:

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

这是一些答案的混合。

我会做一个

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

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

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

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

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

我喜欢之前的'git-interactive-merge' 答案,但还有一个更简单的。 让 Git 使用交互和 on 的 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' 以及 master + C1 + C2 的 HEAD:

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

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

# git branch -f master HEAD
# git branch -d temp

我编写了自己的名为“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

您可以使用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 中合并子目录?

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

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

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

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

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

如果您没有太多已更改的文件,则无需额外提交。

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

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

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

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

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

$ ls
a   b   f

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

$ 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 otherbranch )我们可以使用restore从其他otherbranch获取版本(现在, 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 -pa来启动交互式补丁过程,在这种情况下,我们一次只喂一个。 但在这种情况下,只有一个大块头,无论如何我都想编辑它,所以我说:

$ 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 )我们只调用git restore简单明了(同样,这是现代方式; restorereset更好,可以应用于单个文件):

$ git restore a

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

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

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

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

如果该差异中有任何二进制文件,则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

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

总之:

  1. 移动到要进行更改的分支。 (例如发展)
  2. 右键单击具有新更改的分支并选择“ cherrypick commit ”选项(例如 feature-ABC)。
  3. 最后接受并检查是否有冲突。

我找到了这个问题的解决方案,它非常漂亮且简单。 所以我只是把它留在这里供我自己和其他想要使用此解决方案的人使用。

回答
假设我在包含单个文件file1.txtbranch1
现在,我想将file1.txtbranch2中的另一个文件file2.txt合并。
所以命令应该是,

git checkout --patch branch2 <path_of_file2.txt_in_context_of_branch2>

这会提示控制台说您需要输入特定字符以反映当前分支branch1中的更改。 有时我用a来接受所有的帅哥。

我希望这个答案有帮助。
谢谢

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM