This is a cheat sheet for learning how to manage branch development using rebase.
Developing software concurrently means that increasing development
velocity also increases the overhead of managing feature integration
in main. Most commits are "junk" -- small fixes for typos or other
minutiae that saturates the main branch log w/ noise. There are also
increasing compliance requirements that make tracking product changes
even more critical than ever.
This document is a guide for managing large repos at scale by keeping
features separate from main until they reach a subjective level of
acceptance. This guide helps keep Git commit logs simple and clean,
while eliminating most merge conflicts. This leads to more effecient
development and better communication about how and why code changes
over time.
Benefits:
- greatly simplifies branch merging
- greatly simplifies locating regressions via
git blame - can see one major change per line via
git log - can use the pull request ID to see the complete information
- one tool provides all related spec, bug, wireframe etc.
Start by creating a feature branch from main:
git checkout -b smart/my-featureMake a few commits to this README.md.
echo "\n\n$(date)" >> README.md
git add README.md
git commit -m "I made my edit to the README"
echo "\n\n$(date)" >> README.md
git add README.md
git commit -m "This is my second commit to README"
echo "\n\n$(date)" >> README.md
git add README.md
git commit -m "This is my third commit to README"Now it's a part of history:
git logcommit 82c1c0fbb3905dc7ff02304c2a0d2e0bc3e13ad5 (HEAD -> smart/my-feature)
Author: John Smart <thesmart@users.noreply.github.com>
Date: Wed Jun 2 13:58:14 2021 -0700
This is my third commit to README
commit 5a382de7c0eb3342732b6ea7717c9355d2852bab
Author: John Smart <thesmart@users.noreply.github.com>
Date: Wed Jun 2 13:58:14 2021 -0700
This is my second commit to README
commit e76901cd83cad74c19d674838c08d27b42ef77e8
Author: John Smart <thesmart@users.noreply.github.com>
Date: Wed Jun 2 13:58:14 2021 -0700
I made my edit to the README
commit 0caa69ddd46628913f171115d3b3cc6b60b19c44 (origin/main, origin/HEAD, main)
Author: John Smart <thesmart@users.noreply.github.com>
Date: Wed Jun 2 10:55:43 2021 -0700
Initial commit
We can edit history with interactive rebase:
git rebase -i HEAD~3
Interactive rebase will open a text editor. When closed, it takes the
whatever was saved and applies it to the history. Closing the unedited file
is effectively a noop. You can also get out of it by ctrl+c the paused
git rebase command.
Here, I squash the last three commits together to make one new commit that
replaces e76901c.
pick 0caa69d Making progress on the README file.
pick e76901c I made my edit to the README
squash 5a382de This is my second commit to README
squash 82c1c0f This is my third commit to README
A new editor opens to let me change the commit message. Here's the log after I'm finished:
commit a426582ca389b03a80a2dc3c41a625016fe0142c (HEAD -> smart/my-feature)
Author: John Smart <thesmart@users.noreply.github.com>
Date: Wed Jun 2 13:38:54 2021 -0700
Making progress on the README file.
commit 0caa69ddd46628913f171115d3b3cc6b60b19c44 (origin/main, origin/HEAD, main)
Author: John Smart <thesmart@users.noreply.github.com>
Date: Wed Jun 2 10:55:43 2021 -0700
Initial commit
Now let's suppose that our coworkers have been busy pushing features while we've been working on our branch. We can get pull their changes:
# you need a clean working dir, so if you have uncommited code, stash them
git stash
# rebase merge the remote changes
git pull --rebase origin main
# re-apply your prior uncommited changes
git stash pop # can also use `git stash apply`This will pull all of main's commits to our branch and THEN apply our changes on top. Squashed commits make it easier to resolve merge conflicts because you know that each pulled commit represents a cohensive, accepted body of working code.
Occasionally, it may be easier to fix merge conflicts by using interactive rebase pull, allowing you to fix merge conflicts one commit at a time:
git pull --rebase=interactive origin mainIf there is a conflict, resolve it and then resume the rebase process:
git rebase --continue
If you want to start over, you can abort the rebase and return to a pristine head:
git rebase --abort
This section deals with fixing commits that have not been pushed. In a later section we'll discuss how to fix remote commits.
Let's suppose I rebase and I messed something up. You can fix that too!
git refloga426582 (HEAD -> smart/my-feature) HEAD@{0}: rebase -i (finish): returning to refs/heads/smart/my-feature
a426582 HEAD@{1}: rebase -i (squash): Making progress on the README file.
b7c7998 HEAD@{2}: rebase -i (start): checkout HEAD~2
82c1c0f HEAD@{3}: commit: This is my third edit to README
1de605a HEAD@{4}: commit: This is my second edit to README
b7c7998 HEAD@{5}: commit: I made my edit to the README
0caa69d (origin/main, origin/HEAD, main) HEAD@{6}: checkout: moving from main to smart/my-feature
0caa69d (origin/main, origin/HEAD, main) HEAD@{7}: clone: from git@github.com:thesmart/git-rebase-cheat-sheet.git
You can go back in time like nothing happened:
# NOTE! This will destroy any local uncommitted changes!
git reset --hard 82c1c0fThe git reflog results of doing this:
82c1c0f (HEAD -> smart/my-feature) HEAD@{0}: reset: moving to 82c1c0f
a426582 HEAD@{1}: rebase -i (finish): returning to refs/heads/smart/my-feature
a426582 HEAD@{2}: rebase -i (squash): Making progress on the README file.
b7c7998 HEAD@{3}: rebase -i (start): checkout HEAD~2
82c1c0f HEAD@{4}: commit: This is my third edit to README
1de605a HEAD@{5}: commit: This is my second edit to README
b7c7998 HEAD@{6}: commit: I made my edit to the README
0caa69d (origin/main, origin/HEAD, main) HEAD@{7}: checkout: moving from main to smart/my-feature
0caa69d (origin/main, origin/HEAD, main) HEAD@{8}: clone: from git@github.com:thesmart/git-rebase-cheat-sheet.git
Note how reset just adds to the reflog such that you can even reset the reset.
When you're done tidying up your commit history, its time to open a pull request.
git push origin smart/my-featureEnumerating objects: 5, done.
Counting objects: 100% (5/5), done.
Delta compression using up to 12 threads
Compressing objects: 100% (2/2), done.
Writing objects: 100% (3/3), 862 bytes | 862.00 KiB/s, done.
Total 3 (delta 0), reused 0 (delta 0)
remote:
remote: Create a pull request for 'smart/my-feature' on GitHub by visiting:
remote: https://github.com/thesmart/git-rebase-cheat-sheet/pull/new/smart/my-feature
remote:
To github.com:thesmart/git-rebase-cheat-sheet.git
* [new branch] smart/my-feature -> smart/my-feature
Github remote provided a nice URL you can use. Here is an example of opening a pull request:

The goal of feature branch squashing is to communicate well:
- Write a one-liner title in the active present tense. (e.g. "Adds foobar" instead of "Added foobar")
- Write a thorough description:
- provide context about the purpose of the change
- describe the tangible outcome of the change
- include links to specs, screenshots, animated gif demos, reproductions steps, bug ids, etc.
- Assign yourself as the assignee.
- Request review from others, and consider sending them a courtesy Slack message.
Suppose we made a mistake that we've already pushed to our feature branch. OH NOES!
No worries, you can force GitHub to accept your local commit history:
git push --force-with-lease origin main
This will force GitHub's history to change, but only if there are no commits
from other authors that would be lost. If you really want to destroy remote
changes, you can use the unsafe --force option instead.
NEVER do this on main!
If you need to fix something on main, open a branch and use git revert COMMIT_ID
to commit an inverse change. Then make your change, get a review, and
squash merge (this is discussed in the next section).
You should never merge a pull request that hasn't had a review. At least one other developer should look at and sign-off on your code.
You can find pull requests awaiting your review by setting this filter:

When you have had your code reviewed and feel good about it, merge your PR using "Squash and Merge".

This will turn your PR into a single merge to main, just copy over the PR title and description.
NOTE: Make sure to incude the PR id in the title: e.g. Adds foobar (#12345)

You should delete your feature branch now. If you need to make more changes, open a new branch.
git branch -D smart/my-feature
