关于git:如何樱桃挑选一系列提交并合并到另一个分支?

How to cherry pick a range of commits and merge into another branch?

我有以下存储库布局:

  • 主分公司(生产)
  • 积分
  • 工作的

我想要实现的是从工作分支中挑选一系列提交并将其合并到集成分支中。 我是git的新手,我无法弄清楚如何正确地做到这一点(在一次操作中不提取合并的樱桃选择提交范围)而不会弄乱存储库。 关于这个的任何指针或想法? 谢谢!


当涉及到一系列提交时,采摘 是不切实际的。

正如Keith Kim在下面提到的,Git 1.7.2+引入了挑选一系列提交的能力(但你仍然需要知道未来合并的挑选的结果)

git cherry-pick" learned to pick a range of commits
(e.g."cherry-pick A..B" and"cherry-pick --stdin"), so did"git revert"; these do not support the nicer sequencing control"rebase [-i]" has, though.

达米安评论并警告我们:

In the"cherry-pick A..B" form, A should be older than B.
If they're the wrong order the command will silently fail.

如果要选择BD(包括)B^..D的范围。
请参阅"Git从先前提交的范围创建分支?"作为一个例子。

正如Jubobs在评论中提到的那样:

This assumes that B is not a root commit; you'll get an"unknown revision" error otherwise.

注意:从Git 2.9.x / 2.10(2016年第3季度)开始,您可以直接在孤儿分支(空头)上挑选一系列提交:请参阅"如何在git中使现有分支成为孤儿"。

原始答案(2010年1月)

一个rebase --onto会更好,你可以在集成分支上重放给定的提交范围,正如Charles Bailey在这里所描述的那样。
(另外,在git rebase手册页中查找"以下是如何将基于一个分支的主题分支移植到另一个分支",以查看git rebase --onto的实际示例)

如果您当前的分支是集成:

1
2
3
4
5
6
7
8
# Checkout a new temporary branch at the current location
git checkout -b tmp

# Move the integration branch to the head of the new patchset
git branch -f integration last_SHA-1_of_working_branch_range

# Rebase the patchset onto tmp, the old location of integration
git rebase --onto tmp first_SHA-1_of_working_branch_range~1 integration

这将重播之间的一切:

  • first_SHA-1_of_working_branch_range的父项之后(因此~1):您要重播的第一个提交
  • 最多"integration"(指向要从working分支重放的最后一次提交)

到"tmp"(指向integration之前指向的位置)

如果重播其中一个提交时有任何冲突:

  • 要么解决它并运行"git rebase --continue"。
  • 或者跳过此补丁,改为运行"git rebase --skip"
  • 或用"git rebase --abort"取消所有内容(并在tmp分支上放回integration分支)

rebase --onto之后,integration将返回到集成分支的最后一次提交(即"tmp"分支+所有重放的提交)

使用cherry-picking或rebase --onto时,请不要忘记它会对后续合并产生影响,如此处所述。

这里讨论一个纯粹的"cherry-pick"解决方案,它将涉及到以下内容:

If you want to use a patch approach then"git format-patch|git am" and"git cherry" are your options.
Currently, git cherry-pick accepts only a single commit, but if you want to pick the range B through D that would be B^..D in git lingo, so

1
2
3
4
git rev-list --reverse --topo-order B^..D | while read rev
do
  git cherry-pick $rev || break
done

但无论如何,当你需要"重放"一系列提交时,"重放"一词应该会促使你使用Git的"rebase"功能。


从git v1.7.2开始,cherry pick可以接受一系列提交:

git cherry-pick learned to pick a range of commits (e.g. cherry-pick A..B and cherry-pick --stdin), so did git revert; these do not support the nicer sequencing control rebase [-i] has, though.


你确定你不想真正合并分支吗?如果工作分支有一些你不想要的最近提交,你可以在你想要的位置创建一个带有HEAD的新分支。

现在,如果你真的想要挑选一系列提交,无论出于何种原因,一个优雅的方法是只需要一个补丁集并将其应用到你的新集成分支:

1
2
3
git format-patch A..B
git checkout integration
git am *.patch

这基本上就是git-rebase正在做的事情,但不需要玩游戏。如果需要合并,可以将--3way添加到git-am。如果您逐字按照说明操作,请确保在您执行此操作的目录中没有其他* .patch文件...


假设你有2个分支,

"branchA":包括要复制的提交(从"commitA"到"commitB")

"branchB":您希望从"branchA"传输提交的分支

1)

1
 git checkout <branchA>

2)获取"commitA"和"commitB"的ID

3)

1
git checkout <branchB>

4)

1
git cherry-pick <commitA>^..<commitB>

5)如果您有冲突,请解决并输入

1
git cherry-pick --continue

继续樱桃采摘过程。


我将VonC的代码包装成一个简短的bash脚本git-multi-cherry-pick,以便于运行:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#!/bin/bash

if [ -z $1 ]; then
    echo"Equivalent to running git-cherry-pick on each of the commits in the range specified.";
    echo"";
    echo"Usage:  $0 start^..end";
    echo"";
    exit 1;
fi

git rev-list --reverse --topo-order $1 | while read rev
do
  git cherry-pick $rev || break
done

我正在使用这个,因为我重建了一个项目的历史,该项目的第三方代码和自定义在同一个svn主干中混合在一起。我现在将核心第三方代码,第三方模块和自定义分离到他们自己的git分支上,以便更好地理解未来的自定义。 git-cherry-pick在这种情况下很有用,因为我在同一个存储库中有两棵树,但没有共享的祖先。


以上所有选项都将提示您解决合并冲突。如果要合并为团队提交的更改,则很难解决开发人员的合并冲突并继续进行。但是,"git merge"将一次性完成合并,但您无法通过一系列修订作为参数。我们必须使用"git diff"和"git apply"命令来进行转速的合并范围。我观察到如果补丁文件包含太多文件的差异,"git apply"将失败,因此我们必须为每个文件创建一个补丁然后应用。请注意,该脚本将无法删除源分支中删除的文件。这是一种罕见的情况,您可以从目标分支手动删除此类文件。如果无法应用补丁,"git apply"的退出状态不为零,但是如果使用-3way选项,它将回退到3路合并,您不必担心此失败。

下面是脚本。

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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
enter code here



  #!/bin/bash

    # This script will merge the diff between two git revisions to checked out branch
    # Make sure to cd to git source area and checkout the target branch
    # Make sure that checked out branch is clean run"git reset --hard HEAD"


    START=$1
    END=$2

    echo Start version: $START
    echo End version: $END

    mkdir -p ~/temp
    echo > /tmp/status
    #get files
    git --no-pager  diff  --name-only ${START}..${END} > ~/temp/files
    echo > ~/temp/error.log
    # merge every file
    for file in `cat  ~/temp/files`
    do
      git --no-pager diff --binary ${START}..${END} $file > ~/temp/git-diff
      if [ $? -ne 0 ]
      then
#      Diff usually fail if the file got deleted
        echo Skipping the merge: git diff command failed for $file >> ~/temp/error.log
        echo Skipping the merge: git diff command failed for $file
        echo"STATUS: FAILED $file">>  /tmp/status
        echo"STATUS: FAILED $file"
    # skip the merge for this file and continue the merge for others
        rm -f ~/temp/git-diff
        continue
      fi

      git apply  --ignore-space-change --ignore-whitespace  --3way --allow-binary-replacement ~/temp/git-diff

      if [ $? -ne 0 ]
       then
#  apply failed, but it will fall back to 3-way merge, you can ignore this failure
         echo"git apply command filed for $file"
       fi
       echo
       STATUS=`git status -s $file`


       if [ !"$STATUS" ]
       then
#   status is null if the merged diffs are already present in the target file
         echo"STATUS:NOT_MERGED $file"
         echo"STATUS: NOT_MERGED $file$"  >>  /tmp/status
       else
#     3 way merge is successful
         echo STATUS: $STATUS
         echo"STATUS: $STATUS"  >>  /tmp/status
       fi
    done

    echo GIT merge failed for below listed files

    cat ~/temp/error.log

    echo"Git merge status per file is available in /tmp/status"


另一种选择可能是将我们的策略与范围之前的提交合并,然后与该范围的最后一次提交(或最后一次提交时的分支)进行"正常"合并。因此假设只有2345和3456提交的master被合并到feature分支中:

1
2
3
4
5
master:
1234
2345
3456
4567

在功能分支中:

1
2
git merge -s ours 4567
git merge 2345