代码之家  ›  专栏  ›  技术社区  ›  David Joyner

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

  •  1257
  • David Joyner  · 技术社区  · 16 年前

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

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

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

    什么是将选择性变更从一个开发分支合并到另一个开发分支的最佳方法,同时将其他所有的变更都留在后面?

    我考虑过的方法:

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

    2. 手动将常用文件复制到临时目录中,然后 git checkout 移动到另一个分支,然后手动将临时目录中的内容复制到工作树中。

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

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

    24 回复  |  直到 6 年前
        1
  •  415
  •   Inigo    7 年前

    你使用 cherry-pick 从一个分支获取单个提交的命令。

    如果所需的更改不在单个提交中,则使用此处显示的方法 split the commit into individual commits . 粗略地说,你用 git rebase -i 要获得要编辑的原始提交,则 git reset HEAD^ 有选择地还原更改,然后 git commit 将该位作为历史上的新提交提交。

    There is another nice method here 在红帽杂志上,他们使用 git add --patch 或者可能 git add --interactive 如果您想将不同的更改拆分到一个单独的文件中(在该页面中搜索“拆分”),则只需添加一个块的一部分即可。

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

        2
  •  882
  •   community wiki 11 revs, 9 users 33% Chris Steinbach    6 年前

    我和你上面提到的问题完全一样。但我发现 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"
      
        3
  •  275
  •   JamesThomasMoon    7 年前

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

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

    哪里 branchX 是要从中合并到当前分支的分支。

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

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

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

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

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

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

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

    git checkout HEAD file1
    

    这将检索的版本 file1 在当前分支中并覆盖 文件1 由Git自动合并。

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

    运行:

    git checkout branchX file1
    

    这将检索的版本 文件1 在里面 布兰克斯 重写 文件1 由Git自动合并。

    4)最后一种情况是,如果只想在 文件1 .

    在这种情况下,您可以编辑修改的 文件1 直接更新到你想要的版本 文件1 成为,然后承诺。

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



    为了进一步解释一个例子,假设您希望合并 布兰克斯 到当前分支:

    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
    #
    

    在哪里? 文件1 , file2 file3 文件是否已成功自动合并?

    这意味着 master 布兰克斯 因为这三个文件合并在一起没有任何冲突。

    您可以通过运行 git diff --cached ;

    git diff --cached file1
    git diff --cached file2
    git diff --cached file3
    

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

    1. 直接编辑文件
    2. 节约
    3. git commit

    如果你不想合并 文件1 希望在当前分支中保留版本

    Git签出头文件1
    

    如果你不想合并 文件2 只想要版本 布兰克斯

    git checkout branchX file2
    

    如果你想要 文件3 若要自动合并,请不要执行任何操作。

    现在Git已经合并了它。


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


    最后,别忘了 GIT提交 .

        4
  •  89
  •   Jean-François Corbett    12 年前

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

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

    以下是备选方案:

    分支“feature”中有一些更改,您希望以一种不马虎的方式将它们中的一些(但不是全部)提交给“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,将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
    
        5
  •  80
  •   Chronial    12 年前

    还有另一条路要走:

    git checkout -p
    

    它是介于 git checkout git 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.
    
        6
  •  49
  •   Eric Hu    13 年前

    虽然其中一些答案相当不错,但我觉得实际上没有一个回答了OP的原始约束:从特定的分支中选择特定的文件。这个解决方案可以做到这一点,但是如果有许多文件,可能会很麻烦。

    假设你有 master , exp1 exp2 树枝。您希望将每个实验分支中的一个文件合并到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
    

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

    编辑 :这将合并文件,但会进行智能合并。我不知道如何使用这个方法来获取文件中的差异信息(也许对于极端的差异,它仍然是如此)。如果不使用 -s recursive -X ignore-all-space 选择权

        7
  •  45
  •   Paweł Prażak    12 年前

    1800信息的答案完全正确。不过,作为一个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用hashes一个接一个地选择你的承诺。

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

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

        8
  •  39
  •   mahemoff    9 年前

    以下是您可以替换的方法 Myclass.java 文件在 master 分支机构 MyCase.java 在里面 feature1 分支机构。即使 MyCase.java 不存在于 主人 .

    git checkout master
    git checkout feature1 Myclass.java
    

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

        9
  •  25
  •   masukomi    9 年前

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

    第一步:区分分支

    git diff branch_b > my_patch_file.patch

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

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

    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的手册页了解更多选项。

    第三步:没有第三步

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

        10
  •  21
  •   community wiki 2 revs jejese    12 年前

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

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

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

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

        11
  •  14
  •   Felix    15 年前

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

    #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
    
        12
  •  14
  •   Stunner    11 年前

    我发现 this post 包含最简单的答案。只做:

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

    例子:

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

    更多信息请参见帖子。

        13
  •  13
  •   flyx    11 年前

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

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

    如果你跑

    git status
    

    您将看到文件已被暂存…

    然后运行

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

    简单。

        14
  •  10
  •   JimStar    10 年前

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

    您可以轻松做到:

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

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

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

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

    这会给你真正的选择 树路 合并其他分支中任何文件的机会。

        15
  •  7
  •   Susheel Javadi    13 年前

    我和你上面提到的问题完全一样。但我发现 this git blog 更清楚地解释答案。

    来自上述链接的命令:

    #You are in the branch you want to merge to
    git checkout <branch_you_want_to_merge_from> <file_paths...>
    
        16
  •  7
  •   Felipe    9 年前

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

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

    这是一些答案的混合。

        17
  •  6
  •   lumpidu    14 年前

    我会做一个

    git diff commit1..commit2文件模式git apply--索引和git commit

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

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

        18
  •  6
  •   Wade Santa    13 年前

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

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

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

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

    如上所述,它将您放入交互式编辑器中,在其中为C1和C2选择“Pick”行(如上所述)。保存并退出,然后它将继续执行REBASE,并为您提供分支“temp”,同时指向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
    
        19
  •  5
  •   Andy    14 年前

    我知道这个问题很老,还有很多其他的答案,但是我编写了自己的脚本“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
    
        20
  •  2
  •   Community CDub    8 年前

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

        21
  •  1
  •   raratiru    7 年前

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

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

        22
  •  1
  •   Dave C    7 年前

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

    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

        23
  •  0
  •   JBaczuk    6 年前

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

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

    2。重置为上次想要提交
    $ git reset --hard HEAD~n 在哪里 n 是您需要返回的提交数

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

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

        24
  •  0
  •   code4kix    6 年前

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

    下面的步骤假设您有两个分支 目标分支 source-branch 和目录 dir-to-merge 您要合并的在 源分支 . 还假设您有其他目录,如 dir-to-retain 在你不想改变和保留历史的目标中。另外,假设在 DIR合并 .

    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.
    
    推荐文章