Have you or a loved one ever wanted to:
- delete a commit
- edit the content of a commit
- remove a specific file that was accidentally added to a commit
- include missed files in a commit
- fix a typo in an old commit message
- combine a bunch of commits together
If you’ve worked with git, you’ve likely needed to do at least one of these at some point. And if you’re like me, you’ve probably read over the --help
documentation of git to figure out how to do some of these things, and oh boy the docs can be cagey about telling you what exactly to do. This article is a reference on just a few things you can do with git rebases, and how to do it. Here’s the equivalent article directly from the git documentation if you’re interested in further reading.
Rewriting history is controversial
Rebasing involves rewriting history.
Rewriting history involves force pushing to overwrite the remote state of a repository.
Force pushing has the potential to delete others work (if you rebase recklessly).
Rewriting history is controversial. Some say it’s okay to modify history because it’s sometimes necessary and can enhance git history. Others say it’s not okay because git is supposed to be a historical representation of all changes, and you should prefer to roll your fixes forward. In my opinion if your historical modifications don’t impact others, it should be acceptable to rewrite history. It’s important when managing git history to ensure that you aren’t impacting other people’s work. You should follow these general practices:
- Always use
--force-with-lease
when pushing out rebases. - Avoid rewriting history if you’re collaborating with others on the same branch.
- If you have to rebase changes while working with others,
git pull
often to always make sure you’re working with the latest content. - Don’t edit other people’s commits unless they know about and consent to the edits.
So what is a git rebase?
A git rebase is the act of disassembling and reassembling a range of commits. The function’s name comes from its original purpose of changing the forked location of your branch to a different commit or branch. There are two general types of rebases. Ones that require human interaction to complete, and ones that do not. A human interactive rebase would involve doing things like adding, editing, deleting, combining, and splitting commits. A noninteractive rebase would be attaching your base to a different branch.
Interactive rebases are executed by running git rebase --interactive $GITREVISION
Non-interactive rebases are executed by running git rebase $GITREVISION
How do I git rebase?
Determine what commits to rebase
You can use most valid forms of gitrevision syntax, but git log --oneline
and git reflog
are the most common options for discovering historical edits. git log --oneline
will only show you commits. git reflog
will show you commits in addition to actions you take like checking out branches, other rebases you’ve done, git resets, commit amends, and other historical modifications made to git. The only time I use referential syntax is if I’m recovering a previously deleted commit since git will locally log the commit even after deleting it.
Here’s a table of some gitrevision syntaxes you can use in a rebase, and what they roughly translate to.
syntax | meaning |
---|---|
016a428 | rebase all commits between HEAD and 016a428 |
HEAD@{5} | rebase all commits contained in my last 5 git actions |
HEAD~5 | rebase the last 5 commits |
HEAD@{5hr} | rebase all commits made in the last 5 hours |
':/^Foo' | rebase all commits made between HEAD and the first commit found with a commit message that matches the regular expression ^Foo |
Edit your git-rebase-todo
A git-rebase-todo
file is a manifest containing all the commits in your rebase that you can edit to instruct git on what changes you want to make. It includes the action you want to do, the commit hash, and the commit message for easy identification. An example looks like this:
|
|
The only commands in that list I haven’t used are exec, break, label, reset, and merge.
Start the rebase
As soon as you save the git-rebase-todo
file and close it, the rebase will start. The first step git does is it saves all your commits into a staging area for safekeeping if you need to perform any reversions. Then it goes through each commit oldest to newest. Actions like pick, squash, fixup, and drop will be executed automatically. Actions like reword and edit will pause for you to make your requested changes. If it’s a git message change, it will open the commit message file and allow you to make your changes.
Edit steps are a bit more complex. Git has no idea what modifications you intend to make on the edit step, so you need to tell it when you’re finished. You can make any changes you need to the commit, and once you’re finished you run git commit --amend
to add your content to the commit. Then run git rebase --continue
to tell git you’re done with the current commit.
Push up your rebase
If you’re editing any commits that aren’t strictly local to your machine, you’ll have to instruct your remote git website that yes, you did mean to delete that commit. To do this, run git push --force-with-lease
. If you don’t, git will reject your changes when you try and push because it thinks it has content you’re missing that you need to pull down.
--force-with-lease
is the same as --force
except it has a failsafe to abort if there are remote changes your local git client doesn’t know about. Always use it when force pushing changes.
Deleting commits
- Determine what commits you want to delete by running
git log --oneline
and find the hash of the commit you intend to delete
|
|
- Go one past that commit and copy its hash
- Run
git rebase --interactive 76d462d
- Modify the
git-rebase-todo
to delete the commit
|
|
- Save and close the
git-rebase-todo
- Run
git push --force-with-lease
if working with remote changes to overwrite history
If everything worked, the commit should be gone. You can run git log --oneline
to validate its deletion.
|
|
Editing commit content
- Determine what commit you want to edit by running
git log --oneline
and find the SHA of the commit you intend to modify
|
|
- Go one past that commit and copy its commit SHA
- Run
git rebase --interactive 6dd4f4e
- Modify the
git-rebase-todo
to include your request to edit the commit
|
|
- Save and close the
git-rebase-todo
- Git will pause on the commit you want to edit
- Make your changes and run
git commit --amend
to include the edits - Run
git rebase --continue
to tell git you’re done making your changes - Run
git push --force-with-lease
if working with remote changes to overwrite history
|
|
If everything worked, the commit should be edited with a new SHA. You can run git log --oneline
to validate its modification.
Combining commits by squashing
- Determine what commits you want to squash by running
git log --oneline
and find the hash of the oldest commit you want to squash
|
|
- go one past that commit and copy its hash
- run
git rebase --interactive 64378f8
- modify the
git-rebase-todo
to include your squashes
|
|
- save and close the
git-rebase-todo
- git will pause on the newly squashed commit and give you an opportunity to modify the commit message if desired
- save and close the
COMMIT_EDITMSG
file to finish the rebase - run
git push --force-with-lease
if working with remote changes to overwrite history
|
|
If everything worked, the commit should be edited with a new SHA. You can run git log --oneline
to validate its modification.
If you enjoyed this article, have any questions, noticed something inaccurate, or you just want to say hi feel free to drop a comment below or send an email to me@norling.io