Git Spaghetti

SpaGITi

A git repository with multiple users can become quite a hostile environment with merges all over the place making the commit history hard to navigate.

The branching strategy of GitHub flow is a nice way to keep things under control. Essentially, you keep a stable master and you make changes via feature branches which you merge into master via pull requests.

Messy Stuff

The above doesn’t prevent you from making a messy commit history or having interleaved merges. Here’s why a messy commit history is bad:

With several commits in a feature branch, you force the consumer of the repository to see every twist and turn of your journey on a feature branch before you got to the last commit – the one which represents the real change

Interleaved merges are bad for a couple of reasons:

  • Reverting a version which is based on interleaved merges is harder to reason about
  • The version on your branch before you merge to master doesn’t reflect what master will look like if you expect the merge to happen at the end – this makes your CI less valid as master’s head will not be the thing you tested
  • You can merge from master back to your branch before merging to master… but that’s even more difficult to understand.

A Better World

The rules:

  • Always work on a feature branch, never on master
  • Always squash your branch to a single commit before merging
  • If your branch is not in line with head of master, rebase it
  • While developing, if you want to sync with master, then squash and rebase
  • Always squash before rebasing

Why is this good?

  • With rebasing, you keep your feature branch in sync with the head of master without getting into a tangle
  • Squashing keeping rebasing simple
  • A single commit heading to master is easy to work with afterwards

What about upstream?

  • If working with an upstream fork, you never push to your own master from your own changes
  • Instead you rebase your master with upstream
  • Then continue with the above with your feature branches – using Pull Requests to get them to merge to upstream/master

Git Recipes

Making a new feature branch

# start on master
git checkout master

# then make sure you're up to date with the remote
git pull

# now create a new feature branch
git checkout -b MyFeatureBranch

Squashing a feature branch

For this we need to know where the branch started. If you know it’s, say, three commits, we can use the number-of-commits method. Otherwise do a git log to see either the git sha (hex number) of the start point, or, if you’re lucky, the name of the original branch on your local, from which you started, still sitting with its head at the place where you branch started.

# method one - rebasing

## option 1 - number of commits - say 3
git rebase -i HEAD~3

## option 2 - specific sha - say 1234abc
git rebase -i 1234abc

## option 3 - original local branch - say foo
git rebase -i foo

The above will open up your editor like this:

 pick 3162c24 The first commit from your list
pick fc5f2b4 The second
pick a4da55a The third

Modify the second picks to squashes:

 pick 3162c24 The first commit from your list
s fc5f2b4 The second
s a4da55a The third

Save the edit and then modify the commit message that comes up in the next editor window.

You can push this squashed version of your branch up to the remote. As it’s now changed the graph structure you’ll need a force push:

git push -f

The B method

You can also achieve this another way. If you have a lot of commits, then editing the list in an interactive rebase kind of sucks. You can do this:

# move the git pointer to the start of your changes - say
# three commits ago
git reset --hard HEAD~3

# merge that with where the git pointer was 1 position ago
git merge --squash HEAD@{1}

# you'll be prompted to edit the horrendous commit message this generates

# push that up to the server
git push -f

This way can be useful for zapping changes in one move.

From Squashing To Rebasing

This is the never-fail method of getting your feature branch in sync with a changing master.

# go to your feature branch
git checkout MyFeatureBranch

# sync from the remote
git fetch

# rebase your branch against the remote's master
git rebase origin/master

## here is where you may end up doing some resolution of conflicts, ending with a git rebase --continue

# make your changes go to your remote copy of your branch
# a force-push as the git tree changed during rebase
git push -f

Keeping in sync with the upstream

So your master is actually on a fork from an upstream. In that case, don’t merge to it yourself. Your PRs will go from your feature branches to the upstream. If you want your master to help you rebase your feature branches, or help you make new feature branches, then here’s how to sync your master:

# go to your master
git checkout master

# ensure your git has knowledge of upstream's changes
git fetch upstream

# make your local master advance to the head of
# upstream's master
git rebase upstream/master

# push this to your remote master on your fork
# this should not need a force as you shouldn't be polluting
# your master with any other changes:
git push

# now you're up to date

Conclusion

Git is a complex enough subject that it’s easy to get tied up in knots. The aim of this article – though clearly only showing a subset of what you could do with git is to show you a way to avoid a lot of the tangle and keep things clean.

Happy rebasing!

3 comments

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s