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

How to selectively merge or pick changes from another branch in Git?

我在一个新项目中使用了Git,这个项目有两个并行的(但目前是实验性的)开发分支:

  • master:导入现有的代码库,加上一些我一般确信的模块
  • exp1:实验分支1
  • exp2:实验分支2

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

将选择性变更从一个开发分支合并到另一个开发分支的最佳方法是什么,而将所有其他内容都抛在脑后?

我考虑过的方法:

  • git merge --no-commit后面是大量编辑的手动取消排序,我不想在分支之间进行公共编辑。

  • 手动将通用文件复制到一个临时目录中,然后执行git checkout以移动到另一个分支,然后再手动将临时目录中的文件复制到工作树中。

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

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


    我和你上面提到的问题完全一样。但我在解释答案时发现这一点更清楚。

    总结:

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

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

      提示:它也可以在没有--的情况下工作,就像在链接的帖子中看到的那样。

    • 或者选择性地合并大块

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

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

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

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


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

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

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

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


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

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

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

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

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

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

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

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

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

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

    1
    git checkout HEAD file1

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

    3)如果需要BranchX中的版本(而不是真正的合并)。

    运行:

    1
    git checkout branchX file1

    这将检索branchX中的file1版本,并覆盖git自动合并的file1版本。

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

    在这种情况下,您可以直接编辑修改后的file1,将其更新为您希望file1版本成为的任何内容,然后提交。

    如果Git不能自动合并文件,它将报告文件为"未合并",并在需要手动解决冲突的地方生成一个副本。

    < BR>

    为了进一步解释一个例子,假设您希望将branchX合并到当前分支中:

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

    然后运行git status命令查看修改文件的状态。

    例如:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    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来检查合并是如何完成的;

    1
    2
    3
    git diff --cached file1
    git diff --cached file2
    git diff --cached file3

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

  • 直接编辑文件
  • 节约
  • git commit
  • 如果您不想合并file1并想在当前分支中保留该版本

    1
    git checkout HEAD file1

    如果不想合并file2,只想合并branchX中的版本

    1
    git checkout branchX file2

    如果你想让file3自动合并,不要做任何事情。

    现在Git已经合并了它。

    < BR>

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

    < BR>

    最后,别忘了给git commit


    我不喜欢上面的方法。使用cherry-pick非常适合选择单个更改,但是如果你想引入除某些坏更改之外的所有更改,这是一种痛苦。这是我的方法。

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

    以下是备选方案:

    在分支"feature"中有一些更改,您希望以一种不马虎的方式将它们中的一些(但不是全部)提交给"master"(即,您不希望对每一个进行挑选和提交)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    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,就可以开始了:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    #!/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


    还有另一条路要走:

    1
    git checkout -p

    它是git checkoutgit add -p的混合体,可能正是您想要的:

    1
    2
    3
    4
    5
    6
    7
    8
    9
       -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中。我会这样做:

    1
    2
    3
    4
    5
    6
    7
    8
    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

    这将为您所需的每个文件提供文件内差异。再也没有了。没什么。在不同版本之间,文件的更改是完全不同的,这很有用——在我的例子中,将应用程序从Rails2更改为Rails3。

    编辑:这将合并文件,但进行智能合并。我不知道如何使用这个方法来获取文件中的差异信息(也许对于极端的差异,它仍然是如此)。如果不使用-s recursive -X ignore-all-space选项,诸如whitespace之类恼人的小东西就会重新合并进来。


    1800信息的答案是完全正确的。不过,作为一个Git Noob,"使用Git Cherry Pick"还不足以让我在互联网上多挖一点就明白这一点,所以我想我会发布一个更详细的指南,以防其他人在类似的船上。

    我的用例希望有选择地将其他人的Github分支中的更改拉到我自己的分支中。如果已经有一个本地分支进行了更改,则只需执行步骤2和5-7。

  • 使用要引入的更改创建(如果未创建)本地分支。

    $ git branch mybranch

  • 切换到它。

    $ git checkout mybranch

  • 从另一个人的帐户中下拉所需的更改。如果您还没有,您将希望将它们添加为远程。

    $ git remote add repos-w-changes

  • 把树枝上的东西都拉下来。

    $ git pull repos-w-changes branch-i-want

  • 查看提交日志以查看您想要的更改:

    $ git log

  • 切换回要将更改拉入的分支。

    $ git checkout originalbranch

  • Cherry用hashes一个接一个地选择你的承诺。

    $ git cherry-pick -x hash-of-commit

  • 帽子提示:http://www.sourcemage.org/git_指南


    下面介绍如何用feature1分支中的Myclass.java文件替换master分支中的Myclass.java文件。即使Myclass.javamaster上不存在,它也会起作用。

    1
    2
    git checkout master
    git checkout feature1 Myclass.java

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


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

    第一步:区分分支

    git diff branch_b > my_patch_file.patch

    创建当前分支和分支之间的差异的修补文件

    第二步:对匹配模式的文件应用修补程序

    git apply -p1 --include=pattern/matching/the/path/to/file/or/folder my_patch_file.patch

    关于选项的有用注释

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

    斜杠不需要逃跑。

    另外,您可以使用--exclude来代替它,并将它应用于除匹配模式的文件之外的所有内容,或者使用-r反转修补程序。

    -p1选项是*unix patch命令的一个保留项,事实上,补丁文件的内容会在每个文件名前面加上a/b/(或者更多,取决于补丁文件的生成方式),您需要删除这些文件,以便它能够找出补丁需要应用到的文件路径的真实文件。

    查看Git Apply的手册页了解更多选项。

    第三步:没有第三步

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


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

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

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

    . …其中"branchname"是您声称要合并的内容。如果你马上承诺,它不会做任何改变,但它仍然会显示出来自另一个分支的祖先。如果需要,也可以向命令行添加更多的分支/标记等。不过,此时没有要提交的更改,所以接下来从其他修订版获取文件。

    1
    git checkout branchname1 -- file1 file2 etc

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

    1
    git checkout branchname2 -- file3 file4 etc

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

    1
    git commit

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

    不过,请注意,如果不清楚,这是一个混乱的事情要做。这不是一个"分支"的目的,而樱桃采摘是一个更诚实的方式来做你将要做的,在这里。如果你想对上次没有带来的同一个分支上的其他文件执行另一个"合并",它将用一条"已经更新"消息阻止你。当我们应该有分支时,这是一个不分支的症状,在"从"分支中应该有多个不同的分支。


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

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

    例子:

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

    更多信息请参见帖子。


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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    #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


    最简单的方法是将repo设置为要与之合并的分支,然后运行,

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

    如果你跑

    1
    git status

    您将看到文件已被暂存…

    然后运行

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

    简单。


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

    您可以轻松做到:

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

    1
    2
    [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;' -"

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

    1
    2
    [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;' -"

    然后这样使用:

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

    这将为您提供一个真正的选择性树方法,使您可以合并其他分支中的任何文件。


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

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

    这是一些答案的混合。


    我和你上面提到的问题完全一样。但我发现这个博客在解释答案时更清晰。

    来自上述链接的命令:

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


    我喜欢上面的"Git Interactive Merge"答案,但有一个更简单。让Git为您使用Interactive和On的Rebase组合:

    1
    2
    3
          A---C1---o---C2---o---o feature
         /
    ----o---o---o---o master

    因此,您需要"feature"分支(分支点"a")中的c1和c2,但目前没有其他分支。

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

    如上所述,它将您放到交互式编辑器中,在其中为C1和C2选择"pick"行(如上所述)。保存并退出,然后它将继续执行REBASE,并为您提供分支"temp",同时指向master+c1+c2:

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

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

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

    我会做一个

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

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

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


    我知道这个问题很老,还有很多其他的答案,但是我编写了自己的脚本"pmerge"来部分合并目录。这是一项正在进行的工作,我仍然在学习Git和Bash脚本。

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

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

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    #!/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=$'
    '
    # 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' '
    ' | 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将给定的远程树读取或合并到当前索引中,例如:

    1
    2
    3
    git remote add foo [email protected]/foo.git
    git fetch foo
    git read-tree --prefix=my-folder/ -u foo/master:trunk/their-folder

    要执行合并,请使用-m

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


    那么git reset --soft branch呢?我很惊讶还没有人提到这件事。

    对我来说,这是从另一个分支中有选择地选择更改的最简单方法,因为这个命令将所有的diff更改放在我的工作树中,我可以轻松地选择或恢复我需要的更改。这样,我就可以完全控制提交的文件。


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


    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


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

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

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

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

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


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

    git difftoll ..


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

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    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.