Skip to content

Instantly share code, notes, and snippets.

@loilo
Last active April 3, 2024 07:24
Show Gist options
  • Star 89 You must be signed in to star a gist
  • Fork 12 You must be signed in to fork a gist
  • Save loilo/930f141d9acf89e9e734ffa042acd750 to your computer and use it in GitHub Desktop.
Save loilo/930f141d9acf89e9e734ffa042acd750 to your computer and use it in GitHub Desktop.
Split a large pull request into two

This is about how to split a GitHub pull request, following a real in-the-wild example.

So it happened that I once wanted to fix a bug (#832) on the shelljs repository.

I forked the repository, cloned it and created a new branch called ln-directory-dest. I fixed the bug, created a pull request, and implemented initial review feedback.

At this point, I had added the commits A, B, C, D, E and F (renamed here for simplicity). It made the repository look like this:

X -- [master]
 `- A -- B -- C -- D -- E -- F -- [ln-directory-dest]

However, it happened that the project maintainer encouraged me to take on some similar issues (namely, #829). I jumped on and fixed that one — and pushed the commit (G) to the ln-directory-dest branch.

X -- [master]
 `- A - B - C - D - E - F - G -- [ln-directory-dest]

I hoped this was okay, but the maintainer (understandibly) preferred having two separate pull requests for the fixes.

So, what was desired was this:

X -- [master]
 `- A - B - C - D - E - F -- [ln-directory-dest]
 `- G -- [ln-handle-dead-links]

So that left me wondering how to take G from the ln-directory-dest branch and put it onto ln-handle-dead-links.

After reading some articles (most about a similar git constellation, but not the same), I sucessfully trial-and-error'ed my way to success.

The steps are the following:

  1. (Being on the ln-directory-dest branch,) create the new branch ln-handle-dead-links (but not checking it out):

    git branch ln-handle-dead-links

    Result:

    X -- [master]
     `- A - B - C - D - E - F - G -- [ln-directory-dest], [ln-handle-dead-links]
    
  2. Set the HEAD of branch ln-directory-dest back by one commit (to F).

    git reset --hard HEAD~1

    Result:

    X -- [master]
     `- A - B - C - D - E - F -- [ln-directory-dest]
                            `- G -- [ln-handle-dead-links]
    
  3. Rebase all commits that happened between branch ln-directory-dest and branch ln-handle-dead-links (which is only commit G) onto master.

    git rebase --onto master ln-directory-dest ln-handle-dead-links

    Result:

    X -- [master]
     `- A - B - C - D - E - F -- [ln-directory-dest]
     `- G -- [ln-handle-dead-links]
    

And we're done!

@ComFreek
Copy link

ComFreek commented Jan 13, 2020

In my scenario where commits weren't in a clear order, using git rebase -i turned out to be easier.

Scenario: I've got the following commits on master such that I want A -- C -- E on one branch and B -- D on another.

X - A -- B -- C -- D -- E [master]
  1. git checkout -b ace-branch

    X - A -- B -- C -- D -- E [master]
                             ` [ace-branch]
    
  2. git rebase -i HEAD~5 because we want to modify the last 5 commits

  3. An editor will pop up asking you configure how you want the last commits to survive:

    pick 1234567 A
    pick 789abcd B
    pick 7654321 C
    pick dcba987 D
    pick 5674321 E
    
  4. Replace pick by drop in the lines for commits B and D. (If VIM is used, press insert, then do replacement.)

  5. Close the editor. (If VIM is used, press escape, then :wq and enter.)

  6. Now the current branch, which is ace-branch, will only contain commits A, C and E.

    X - A -- B -- C -- D -- E [master]
      `- A -- C -- E          [ace-branch]
    
  7. Repeat the same steps in order to create a bd-branch. Start by git checkout master, git checkout -b bd-branch.

    Note that we need to check out master first since otherwise bd-branch would start with the history of ace-branch, which we just modified to no longer contain commits B and D.

    X - A -- B -- C -- D -- E   [master]
     \                          [bd-branch]
      `- A -- C -- E            [ace-branch]
    

After you have done all steps, it should look as follows:

X - A -- B -- C -- D -- E           [master]
 \_
  | \- A -- C -- E                  [ace-branch]
  \_
    \- B -- D                       [bd-branch]

Finally, you can also just drop all commits on master by git checkout master && git reset --hard HEAD~5. Then it looks like as if you made followed very clean approach from the very beginning 😄

X                                   [master]
 \_
  | \- A -- C -- E                  [ace-branch]
  \_
    \- B -- D                       [bd-branch]

@ashanz93
Copy link

ashanz93 commented Apr 3, 2020

Worked perfectly @loilo. This is what I desired to do

@areee
Copy link

areee commented Jul 10, 2020

Thanks for this hint, @ComFreek! It worked perfectly!

@itai-huddly
Copy link

Thanks for this guide, it's quite useful!

@jvortmann
Copy link

Alternatively switch (with -c to create a branch) can be used and it allows a starting-point as a param:

X - A -- B -- C -- D -- E [master]

git switch -c ace-branch X will result in (and move to the branch):

X - A -- B -- C -- D -- E [master]
 \
  `- [ace-branch] *

and then git cherry-pick A C E will cherry pick those commits in that order and apply to the branch:

X - A -- B -- C -- D -- E [master]
 \
  `- A -- C -- E [ace-branch] *

To create the other branch it is the same two commands and you can even change the order if needed:
git switch -c bd-branch X && git cherry-pick B D or
git switch -c db-branch X && git cherry-pick D B

@TheGreatRambler
Copy link

git cherry-pick worked nicely for me, thank you

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment