3 minute read

In a collaborative (or solo) git project I’m often in a position where I was working on a feature-branch, got busy, stopped working on it, then came back to it awhile later. In that time, there were changes on main (formerly master), that might affect my feature branch.

The question is: how do I merge the changes on main into my feature-branch, such that, when I merge the feature-branch into main down the road, there is a clean history?

We could merge --squash, but what we really want for a nice clean history is explained here.

Note, this blog post is a companion to this longer post

Change the editor

Before beginning, it’s nice to change the editor that Github uses away from VIM, which I find to be a pain. On my macbook, I ran:

$ git config --global core.editor "code --wait"`

to change the default editor to VS code. The --wait option freezes the command line until I close the file in the editor.

Getting main up to date

First, I pull main locally:

$ git checkout main
(main) $ git pull

Once everything is up to date there, I switch back to my branch:

(main) $ git checkout feature-branch
(feature-branch) $

Source vs. Target Branch

Quick note here. We are trying to merge like main --> feature-branch. The branch with added changes we are trying to gather is called the source branch (here main) while the branch you request to merge your changes into is called the target branch (here feature-branch). We want to merge main (source) into feature-branch (target).

Rebase into feature-branch

Now I want to rebase the main into feature-branch.

(feature-branch) $ git rebase main

I could have added the -i (or --interactive) flag, like git rebase main -i. This would allow me to craft a commit history by squashing all the commits except for one that I pick (the top-most in the editor). picking multiple is almost never worth it (or, alternatively, it means your branches are too long lived). We skip the -i flag at this stage because we don’t need to make the history clean, we just need to get all the changes from main. Later, when we merge the feature to main, we will use -i.

After the rebase, I need to pull to get all the changes:

(feature-branch) $ git pull

If an error comes up regarding divergent branches, then we need to specify how to reconcile them. We can pass one of the flags --rebase, --no-rebase, or --ff-only. We need to use --rebase because --ff-only will fail since the local and remote branches have diverged:

(feature-branch) $ git pull --rebase

Now I can push:

(feature-branch) $ git push

These two actions take all of the main commits that occurred since making the feature branch and slots them into the feature branch’s history

And if I check on the open PR for the feature branch, I will see all of the history from main, and I can continue working on (and making commits) the feature branch.

Merge to main

When I’m done with the feature-branch (meaning I’ve made all the commits I’m going to and pushed them all to Github) and ready to merge to main, I can

  • merge on Github using the open PR, including receiving code review and then clicking “Squash and merge”

OR if I want to merge without the PR / code review:

  • run these commands:
git checkout main
git pull
git checkout feature-branch
git rebase main -i

At this point, an editor will pop up. I can replace pick for all but the first commit (top-most in the editor) with s (for squash). Then I save and close the file.

Another text editor will open where you can craft a wonderful commit message to communicate the WHY of your changes (the WHAT is told by the diff).

Now, with the messy history of commits ironed out, we merge the feature to main:

git checkout main
git merge feature-branch
git push

And if we want to delete the remote feature-branch branch from Github:

git push origin :feature-branch

And locally we can delete the branch with:

git branch -d feature-branch