A clean Git history with Git Rebase and Conventional Commits
Tags: git, conventionalcommits, rebase
Published: 2023-03-12 19:11:46

_Other Git related articles on DEV from me:_
[Git signing and signoff like a champ](https://dev.to/janderssonse/git-signoff-and-signing-like-a-champ-41f3)
[The "linear-git-commit-history-with-signing-in-github-ui"-workaround](https://dev.to/janderssonse/the-linear-git-commit-history-with-signing-in-github-ui-workaround-2cej)

# Introduction

As developers we always strive to improve and simplify our processes. One important motivator is less time spent on future guesswork. We also would like that new developers in our projects will be able to quickly focus on their task instead of diving into a mess of lasagna and spaghetti code. 
And we all know that the "new developer" is in many cases you, a half year later when you are wondering what the hell you were thinking about many commits earlier.

One part of making life simpler for everyone is by keeping a clean Git history. Now, What is a clean Git History? Well, you can find different opinions on that, but for me (and quite a few projects) a nice Git history:

* tells the reader something in an expected understandable way
* makes a feature coherent. 
* makes it easy to revert or cherry pick a feature
* tells the reader that the former contributors cared about the project and new contributors

So, caring about your Git history is a well spent investment for your projects, and surely worth the small extra time it might take to learn.

This article will give you a short opinionated lightweight view  of how to achieve that.

## The basic rules:

- The commit that goes into the main (or develop etc) branch should aim to be atomic, aiming to contain only the feature you are working on
- While working in your feature branch, your commit messages can look in whatever way you wish: wip and so on. But, the important thing is that _when you are done with your feature and ready to go for a Pull Request you first rebase your feature branch and clean up your commits_.
- The feature commit should have a clear defined message - Don't re-invent here - There exists a fairly used and accepted convention called [Conventional Commits](https://www.conventionalcommits.org), so we are going to use that.

## Rebase and avoiding merge commits

Rebasing might sound scary if you are not familiar with it, (overwriting history, yikes?), but it is really not after you got the hang of where to, and how to. It puts your commits on top of whatever you rebase against.

Instead of when you do Pull Requests with merge:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3hmdmzfzh8zn0siqixle.png)  _"Merging main into the feature branch" by Atlassian is licensed under [CC BY 2.5 AU](https://creativecommons.org/licenses/by/2.5/au/)_


You will do this with rebase:


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gc775oygk6i2llbarvap.png)  _"Rebasing the feature branch onto main" by Atlassian is licensed under [CC BY 2.5 AU](https://creativecommons.org/licenses/by/2.5/au/)_

And in practice, the history of your commit graph will look like the one to the right, instead of the one to the left:
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yzwva7ioevtmr68f0fal.png)_"Messy left vs linear right" by Unknown is licensed under Unknown_

Much cleaner to follow and understand. And, just ignore the commit messages themselves for now - we will improve upon that soon.

Basically you will use rebase in a few different ways.
One is when you pull from the current branch. *Always* use pull with --rebase flag.

```console
git pull --rebase 
```

Another case is when you pull your changes from main/develop into your feature branch

```console

git rebase --interactive origin/main
  
```
(instead of doing git merge <the main or develop branch>)

Finally, the last case is when you squash all your features in your feature branch - before submitting your Pull/Merge-Request.

```console

git rebase --interactive <sha-with-the-commit-you-are-squashing-from>

```

This article wont go into every detail about learning you how to rebase, as there are many good articles about that already - I recommend that you read more about it in the following links: 
[Merge vs Rebase](https://www.atlassian.com/git/tutorials/merging-vs-rebasing) 
[Git Rebasing](https://git-scm.com/book/en/v2/Git-Branching-Rebasing)

And, then practice. Set up a git project and add a few commits, and a branch or two and start experimenting. If you don't try it for real, you will never feel comfortable. 

So, now we now how to get a linear, easy to follow Git graph. But if you look at the earlier comparison picture above, there is still much to improve upon regarding the commit messages themselves.

Enter..

## Conventional Commits

It is a simple convention that is used in quite a few Open Source projects. I think it originated in the Angular community (not sure here). 

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/j7ir9cb8kk4diamivja0.png)

The Conventional Commit format looks roughly like this:

> <type(optional scope): imperative of what it does

So a practical example message could then look like this:
> fix: add a validator for the fluxcapacitor class

But, for the real format read [more about Conventional Commits](https://www.conventionalcommits.org).

The great thing of not re-inventing things like commit message standards is that you 

* bypass endless organisation and team discussions about how to do things in certain way. Instead you can agree, avoid distractions and focus on the important work - making a good product
* make it easier for new people to dive in to the project
* can use tools written for that standard - in the case of Conventional Commits, autogenerate changelogs and more.
* write better commit messages, as you have to give them a bit of thought

## A rebase/conventional commit workflow example in practice

Lets walk through the life of fictional feature branch.

**Start**: You start your feature-branch from a branch, here main, (but might as well be develop or however you are working with branches).

 ```console
git checkout -b feature/my-branch
```
working....

taking a long break so

```console
git commit -m 'chore: a commit'

git push -u origin feature/my-branch
```

working ... 

```console
git commit -m 'wip gotta go'

git push
```
Ah, meanwhile, new features (commits) was added to main by collegue Knuth, so you update your feature branch:

```console

git rebase -i origin/main
```

back to working...

```console
git commit -m 'docs: last docs update'

git push
```

Finally, ready to proudly clean up our feature commit history, before submitting a Pull/Merge Request:

```console
git rebase -i <sha-to-latest-commit-before-the-feature-branch>
```
OR, if you read my [article about signoff and signing](https://dev.to/janderssonse/git-signoff-and-signing-like-a-champ-41f3) :) :)

```console

git rebase -i --signoff --gpg-sign <sha-hash-of-the-commit-before the-the-feature-branch>


```


Now you will enter Git's interactive rebasing mode, which shows a list of the commits (from the hash you choose), and you get a list of options too. 

Default 'pick' just takes the commit as is. Most often I do r (reword) for the top commit (oldest) and f (fixup) for the others. f means the commit contents will be squashed into the previous commit, and so we will end up with a single commit that we got the chance to reword. But see the example in next section.

So, With a lot of in work-in-progress commits on the feature-branch, you want to squash every commit with 'f' so there only will be one commit on the end. Except the last one that you want to reword with 'r'.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lkpsamxejjto5k3k7jh1.png)

After saving, you are given the dialouge to reword the commit you choose 'r' for. So just rewrite the commit header message as you please, here as a Conventional Commit: 

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pwq2t7cubn4aq2us9hxv.png)

Finally, after saving, and rebase signals done succesfully, you do a quick sanity check that everythings seems fine and then force push (to the feature branch).

```console

git push --force-with-lease

```

Done! Oh,,, then you remember - you want to improve your feature commit by adding a detailed description. So you rebase again (the now cleaned up, single commit), and add a description body.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2p3jxz7fbr7r93ig4gwy.png)

And, as you have rewritten your feature branch history again, you will also need to force push again.

```console
git push --force-with-lease
```
And cheers - open a Pull Request after verifying everything was fine. 

F.A.Q

_When should I not rebase?_

- Never rebase and force push to a public branch without telling everyone first. In other words, only force push to your feature branch. That is a golden rule, because if you do push to common branches, you might overwrite others commits, mess up their forks and, in general create confusion for other developers. However as long as you are in your feature branch, do as you please. There are advanced cases where you want to, or even need to force push to main/develop etc, but that is out scope for this article.

_Is rebasing widely used?_

- When submitting to Open Source projects it is not uncommon to get comments in the style of "_looks fine, but please rebase your feature branch into one feature_",

_What is the --force-with-lease good for?_
- If you force push, make a habit of using --force-with-lease option instead of --force, for a bit of extra safety - it will stop you from overwriting other peoples eventual added changes and stop the push.

_Should I always add a Git description?_
- You be the judge, but for big or hard to understand features, please take the care to add a longer description (body) besides the header message.

_Should I sign and signoff commits?_
- Yes, why not really? See [Sign and signoff your feature commits](https://dev.to/janderssonse/git-signoff-and-signing-like-a-champ-41f3)

_How can I enforce this in GitLab?_
- To enforce a linear history in GitLab UI:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9bicl8xzgl88hq3e7xn8.png)

_How can I enforce this in GitHub?_
- To enforce a linear history in GitHub UI:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hgygj7cddnsftnfmdgqi.png)

But, if you also sign your commits - Sadly Rebase with UI is broken in GitHub, and you will lose your signature. There is a simple CLI workaround, read more under the [F.A.Q in this article for a workaround hint](https://dev.to/janderssonse/git-signoff-and-signing-like-a-champ-41f3).

_Can I reuse this information?_
_All content in this article is licensed under the [CC0](https://creativecommons.org/share-your-work/public-domain/cc0/) if not stated otherwise. Which means, use as you please, no need for any attribution. Please add suggestions and corrections if I mixed up something, or that you feel something important is missing._