Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Rebase branch after GitHub "Squash and merge" onto master

Let's say I've developed a feature on branch1 and sent it out for code review using a GitHub Pull Request. While it's being reviewed, I do some follow-on work on branch2.

 branch2                   -> D --> E --> F
                          /    
 branch1  -> A --> B --> C
         /
 master M

My reviewer loves my work! No changes are required. I merge the pull request for branch1 using GitHub's Squash and merge feature.

After running git pull on master and deleting branch1, I'm left with this situation:

 branch2  -> A --> B --> C -> D --> E --> F
         /
 master M --> S

To send out a clean-looking PR for branch2, I'd like to get my commit tree looking like this:

 branch2        -> D' --> E' --> F'
               /
 master M --> S

The code at S (the commit generated by "Squash and merge" for branch1) is identical to C, since it's just the squashed version of A --> B --> C.

One way to achieve this would be to run a sequence like this on branch2:

git reset --hard S
git cherry-pick D E F

But listing all the commits out this way gets tedious, and this really feels like a rebase. git rebase master won't work, of course, since commits A, B and C need to disappear.

What's the best way to rebase a branch off a squashed version of one of its ancestor commits?

like image 962
danvk Avatar asked Jan 25 '17 18:01

danvk


People also ask

Should I rebase before merging to master?

Any changes from other developers need to be incorporated with git merge instead of git rebase . For this reason, it's usually a good idea to clean up your code with an interactive rebase before submitting your pull request.

Should you squash before rebasing?

Before rebasing such branches, you may want to squash your commits together, and then rebase that single commit, so you can handle all conflicts at once. Here's how to do that. Imagine you've been working on the feature branch show_birthday , and you want to squash and rebase it onto main .

Should I squash and merge or rebase and merge?

When should I rebase and when should I squash? It does not matter which you use but I recommend rebase. Rebase changes the parent node of the feature branch but merge does not and I recommend it because it keeps the commit structure simpler but as a git user, it makes not different.

How do you rebase squashed commits?

In case you are using the Tower Git client, using Interactive Rebase to squash some commits is very simple: just select the commits you want to combine, right-click any of them, and select the "Squash Revisions..." option from the contextual menu.


1 Answers

Use git rebase with --onto. This is still a bit tricky, so to make it easy you will want to do one thing different, earlier.

I think it's better, by the way, to draw these graphs with the branch names at the right side, pointing to one specific commit. This is because in Git, commits are on multiple branches, and branch names really do just point to one specific commit. It's also worth reversing the internal arrows (because Git really stores them that way) or just using connecting lines so as not to imply the wrong direction.

Hence:

          D--E--F   <-- branch2
         /    
  A--B--C       <-- branch1
 /
M          <-- master

Commits A through C really are on both branch1 and branch2, while commits D through F are only on branch2. (Commits M and earlier are on all three branches.)

What git rebase upstream does is select all1 commits reachable from the current branch, but not reachable from the upstream argument, then copy them (with git cherry-pick or equivalent) so that they come right after the upstream commit.

After the squash-"merge" (not really a merge), if you run git fetch and then fast-forward your master, you have the same thing you drew, but I leave branch1 in and put the labels on the left and add origin/master here:

          D--E--F   <-- branch2
         /    
  A--B--C       <-- branch1
 /
M--S       <-- master, origin/master

(Or, if you don't fast-forward your master yet, only origin/master points to commit S).

You now want to tell Git to copy D-E-F with cherry-pick, then move the label branch2 to point to the last commit copied. You don't want to copy A-B-C as they're incorporated in S. You want the copies to go after S, to which origin/master now points—whether or not you've updated master. Hence:

git checkout branch2
git rebase --onto origin/master branch1

The upstream is now branch1 instead of master, but the --onto tells Git where to place the copies: branch1 is only serving to delimit what not to copy. So now Git copies D-E-F and changes branch2 to point there:

          D--E--F   [abandoned]
         /    
  A--B--C       <-- branch1
 /
M--S       <-- master?, origin/master
    \
     D'-E'-F'   <-- branch2

and now you can delete the name branch1. (And now you can fast-forward master if you didn't yet—it does not really matter when you do it, and in fact you don't need your own master at all.)


1More precisely, rebase selects commits that are (a) not merge commits and (b) do not have the same git patch-id as some commit in the excluded set, using a symmetric difference. That is, rather than upstream..HEAD, Git actually runs git rev-list on upstream...HEAD, with --cherry-mark or similar, to pick out commits. The implementations vary slightly depending on the particular kind of rebase.

like image 109
torek Avatar answered Sep 26 '22 00:09

torek