How can I “splice” two (or more) completely unrela

2020-07-18 03:35发布

I'm trying to combine two branches each with different root commit into a new, empty branch. This is not the usual merge thing as i don't want to have the branches combined only in the last commit (in that case, the new branch would have a merge commit and 'below' still two seperate histories. The new branch would still have two root commits.).

One important fact in my scenario is that both branches are completely unrelated. None commit of either branch affects pathes of the other branches' commits => There will be no conflicts.

Let's assume the following branches:

A---B-----C-D
  X---Y-Z

This is what i am looking for:

A-X-B-Y-Z-C-D

The main point is that i want to have the commits in chronological order. How to achieve?

标签: git merge branch
2条回答
迷人小祖宗
2楼-- · 2020-07-18 04:00

Quick simple solution to merge is,

suppose A---B-----C-D (is branch 1) and X---Y-Z (is branch 2). Take all the patches from branch-2 (git format-patch -n) then apply all the patches on top of branch 1 (git am *.patch), and then shuffle where ever you want the sequence to be using git rebase -i HEAD~n

Hope that helps!

查看更多
Lonely孤独者°
3楼-- · 2020-07-18 04:05

You write:

One important fact in my scenario is that both branches are completely unrelated. No commit of either branch affects patches of the other branches' commits => There will be no conflicts.

Of course, the "splicing" operation you suggest is probably a bad idea if you anticipate that many conflicts will arise. Let's assume that, indeed, nothing bad will happen.

If, at the beginning, your repo looks like this

A---B------C--D [branch1]

  X---Y--Z [branch2]

you can follow the procedure outlined below to automatically "splice" commits from both branches into a single branch, while maintaining chronological order.

"Splicing" two unrelated branches

  1. Make sure you're in a clean working state; then check out branch1 and merge branch2 into it:

    git checkout branch1
    git merge branch2
    

    That will yield

    A---B------C--D---E [HEAD=branch1]
                     /
      X---Y--Z------- [branch2]
    

    Now, I know that's not what you want, but bear with me for a second. We will use merge commit E to have access to "the ancestry on both sides" at once.

  2. Check out branch2 and reset it to commit A.

    git checkout branch2
    git reset --hard A
    

    You'll be in the following situation:

    A [HEAD=branch2]
     \
      ---B------C--D---E [branch1]
                      /
       X---Y--Z------- 
    
  3. Generate a list (in chronological order) of all the non-merge commits reachable from branch1 but not from branch2:

    git rev-list --no-merges --reverse branch2..branch1
    

    This should yield the following list of commits: X, B, Y, Z, C, D; commit E, which was created in Step 1 will not be in that list, because we used the --no-merges flag.

  4. Cherry-pick those commits on top of branch2 (A).

    git cherry-pick `git rev-list --no-merges --reverse branch2..branch1`
    

    Your repo will then look as follows:

    A--X'--B'--Y'--Z'--C'--D' [HEAD=branch2]
     \
      ---B-----C-D---E [branch1]
                    /
       X---Y-Z------ 
    
  5. Delete branch1:

    git branch -D branch1
    

    Edit: As you correctly remarked, because branch1 is not fully merged into the current branch (branch2), using just -d won't do, here; you need to use the -D flag instead.

    Your repo will then simply be

    A--X'--B'--Y'--Z'--C'--D' [HEAD=branch2]
    
  6. (Optionally) Rename branch2:

    git branch -m branch2 <more_meaningful_name>
    

Generalization to more than two branches

Let's assume you have n completely unrelated branches: branch1, branch2, ..., branchn, where branch1 corresponds to the branch whose root commit is the oldest commit in the entire repository; Let's call that commit A.

A ----- o ---- o [branch1]

   o ----- o ---- o -- o [branch2]

...

 o ----- o - o [branchn]

If you don't know which commit is A, you can identify it by running

git rev-list --reverse --max-parents=0 --all

The commit ID of A will be the first listed in the output of that command. And you can identify which branch is branch1 by running:

git branch -r --contains `git rev-list --reverse --max-parents=0 --all | head -1`

Then the procedure outlines in the two-branch case becomes:

  1. Create a commit that has access to the ancestry of all branches, by merging all branches other than branch1 into branch1.

  2. (same as in two-branch case)

  3. (same as in two-branch case)
  4. (same as in two-branch case)
  5. Delete all branches other than branch2.
  6. (same as in two-branch case)
查看更多
登录 后发表回答