
Your commits represent and encapsulate your added value to the project you’re working on. They are meant to forever live in the history of your repo. They are your legacy to all the programmers who will work on the project after you.
It’s almost magical when you think about it. For example, you can still find the very first commit from Chris Lattner in the Swift repo. If you have (a lot of) time, you can go commit by commit from this one and re-experience the whole history of the Swift project.
A commit history doesn’t need to tell the true story of the development process with all its hiccups and try-and-errors. I actually think it shouldn’t tell the real story.
A good git history tells an idealized version of the story where we knew exactly what we were doing. Let me show you how to achieve this.
Tools
Some people find interacting with git
directly from the terminal intimidating. For the purpose of this article, I’ll be using my git
GUI of choice, Fork, for all git
operations. However, everything I’m doing here can be achieved from the terminal, too.
My Git Workflow
This is my typical git workflow when working on a new pull request:
- Create a new branch for the task.
- Work on the task. Commit more/less randomly with commit messages like
"WIP"
,"experiment"
,"keep this??"
, etc. - Push to the server regularly, at least once a day.
- When done with the task, reset all commits and create new ones representing the idealized version of the story. This usually takes just a couple of minutes. The most difficult part is to come up with meaningful commit messages.
- Force-push the branch. ⚠️ This can be done safely only when I’m the only person working on the PR.
- Open a PR, ask for a review.
- Incorporate the feedback from the review using
fixup!
commits. - Apply fixup commits, force-push one more time, merge the branch ?
Resetting the History
Find the root commit of your branch and select Reset 'branch name' to Here
.

Select Soft
or Mixed
for the reset type. This doesn’t discard your current changes, it only resets the commits. Don’t select Hard
⚠️ if you don’t want to discard all your changes!

You can now see all your changes at once in Unstaged changes
.

Recreating the Commits
Now, the fun part: Go through the changes and stage only the ones that make a nice small meaningful commit.
For example, let’s put all changes related to updating Pods to a separate commit:


You don’t have to always stage all changes in a file. Simply select the lines you want to stage and press Stage
:


If you find a change that shouldn’t be there, feel free to discard it. No more accidentally committed changes!

Repeat these steps until you have all the changes committed.
Voila! We now have nice and meaningful commits:

We can now push the new commits to the server and open a PR. We need to use “force” push because we want to rewrite the existing commits on the remote branch.

⚠️ There’s nothing wrong with using force-push extensively in your git workflow. However, it can be dangerous in situations when you’re not the only person working on the given branch (or just yourself working from multiple computers).
Always be careful when doing this operation. I strongly recommend learning more about how to use
push --force
andpush --force-with-lease
safely before you start using this workflow.
Incorporating PR Feedback
OK, the PR has been opened for a while and we gathered some valuable feedback from our teammates. How to fix all the issues they found while keeping the commit history nice and readable? Meet fixup!
commits. They are meant to exist only temporarily and their only purpose is to fix another, already existing commit.
After making the required changes, we need to identify to which commit(s) the new changes relate.
We will commit the changes with the name fixup! <the name of the commit we fix>
. We repeat this process with all the new changes.

If you want your teammates to review the fixups you’ve made, you can push the fixup commits to the server and ask them for another review.
Applying All fixup!
Commits
Once the whole team is happy with the PR, there’s one final step. Of course, we don’t want to have the fixup!
commits on master
!
We select the root commit of our branch and choose Rebase 'branch name' to Here Interactively
.

Fork opens a new modal dialog and it automatically offers to squash the fixup!
commits with their related commits.

The only remaining step is to force-push the updated commits and merge the PR ?
Tips & Ideas
- The interactive rebase functionality also allows you to change the order of the commits, reword, or even remove certain commits completely.
- When your work touches
xcodeproj
file a lot, it can be sometimes difficult to correctly identify and split all the changes in this file into separate commits. I always try to commit changes like adding a target or a file immediately. - If a part of your task is renaming of certain elements, it’s easier to commit the related changes immediately, rather than recreating the “Rename xxx” commit later.