首页 文章

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

提问于
浏览
1222

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

  • master :导入现有的代码库加上一些我一般都知道的mod

  • exp1 :实验分支#1

  • exp2 :实验分支#2

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

What is the best way to merge selective changes from one development branch to another while leaving behind everything else?

我考虑过的方法:

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

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

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

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

24 回答

  • 7

    我会做的

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

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

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

  • 20

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

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

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

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

  • 858

    我有与上面提到的完全相同的问题 . 但我发现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"
    
  • 2

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

    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
    

    这将在 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
    

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

    • 直接编辑文件

    • 保存

    • git commit

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

    git checkout HEAD file1
    

    如果您不想合并file2并且只想在branchX中使用该版本

    git checkout branchX file2
    

    如果您希望自动合并file3,请不要这样做任何东西 .

    Git已经在这一点上合并了它 .

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

    最后,别忘了 git commit .

  • 86

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

    还有另外一种方法:

    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.
    
  • 23

    虽然其中一些答案非常好,但我觉得没有人真正回答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 .

    EDIT :这将合并文件,但会进行智能合并 . 我无法弄清楚如何使用这种方法获取文件内差异信息(也许它仍然会出现极端差异 . 除非你使用 -s recursive -X ignore-all-space 选项,否则像空白一样的烦人的小东西会被合并回来)

  • 39

    1800 INFORMATION的答案完全正确 . 然而,作为一个git noob,“使用git cherry-pick”还不足以让我在互联网上更多地挖掘这一点,所以我想我会发布一个更详细的指南以防其他人在类似的船 .

    我的用例是想要有选择地将其他人的github分支中的更改转换为我自己的 . 如果您已经有一个包含更改的本地分支,则只需执行步骤2和5-7 .

    • 创建(如果未创建)包含您要引入的更改的本地分支 .

    $ git branch mybranch <base branch>

    • 切换到它 .

    $ git checkout mybranch

    • 从其他人的帐户中下拉您想要的更改 . 如果您还没有想要将它们添加为遥控器 .

    $ git remote add repos-w-changes <git url>

    • 从他们的分支中拉下所有东西 .

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

    • 查看提交日志以查看所需的更改:

    $ git log

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

    $ git checkout originalbranch

    • 樱桃用哈希一个接一个地挑选你的提交 .

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

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

  • 6

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

    git checkout master
    git checkout feature1 Myclass.java
    

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

  • 1

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

    第一步:扩散分支

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

    第三步:没有第三步

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

  • 5

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

    首先,你将采取不同寻常的步骤,事先声明你将要提交的是一个合并,没有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”分支中应该是多个不同的分支 .

  • 14

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

    #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
    
  • 13

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

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

    例:

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

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

  • 0

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

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

    如果你跑

    git status
    

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

    然后跑

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

    简单 .

  • 10

    奇怪的是,git仍然没有这么方便的工具"out of the box" . 当我通过当前版本分支中的 just some 错误修正更新一些旧版本分支(仍然有很多软件用户)时,我会大量使用它 . 在这种情况下,通常需要从trunk中的文件中快速获取代码行,忽略了许多其他更改(不应该进入旧版本)...当然需要 interactive three-way merge case, git checkout --patch <branch> <file path> 不适用于此选择性合并目的 .

    You can do it easily:

    只需将此行添加到全局 .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>
    

    这将为您提供其他分支中任何文件的真正选择性 tree-way 合并机会 .

  • 7

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

    来自以上链接的命令:

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

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

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

    这是一些答案的混合 .

  • 6

    我喜欢上面的'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并在主C1 C1处给你分支'temp'和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
    
  • 0

    我知道这个问题很老,还有很多其他答案,但是我编写了自己的脚本'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
    
  • 44

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

  • 48

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

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

  • 76

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

    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

  • 403

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

    1. Duplicate branch temporarily
    $ git checkout -b temp_branch

    2. Reset to last wanted commit
    $ git reset --hard HEAD~n ,其中 n 是您需要返回的提交数

    3. Checkout each file from original branch
    $ git checkout origin/original_branch filename.ext

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

  • 14

    如果您只需要合并特定目录并离开其他一切完好但保存历史,你可以尝试这个...在你实验之前创建一个新的 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.
    

相关问题