Skip to content

Instantly share code, notes, and snippets.

@ccadden
Last active September 21, 2022 00:59
Show Gist options
  • Save ccadden/be217cacfa4801cce77c4b2c2ba9801c to your computer and use it in GitHub Desktop.
Save ccadden/be217cacfa4801cce77c4b2c2ba9801c to your computer and use it in GitHub Desktop.
Rebase/Fixup Guide

Rebasing, Fixup Commits, and Autosquashing

Rebasing

Rebasing is the act of taking a git branch which was taken from a given commit in the history and moving it to be branched off a different commit. This is done in short by taking all commits after the branch event and playing them on top of other commits. Depending on what files changed in the diff there may or may not be collisions which require manual resolution.

A Graphical Example

Say a developer branches off from main to do work. While that work is in progress, other branches are merged back into main so that the repo visualization looks something like this:

* bbfe18e (rebase-branch) Even more branch commits
* e83a812 Branch commit for README
| * 3a1ec67 (HEAD -> main) Another merge from another branch
| * f53c9c0 Commits from a different branch
|/
* eb4af0b Flesh out README
* 1a9a0b0 Initial commit

The point of the rebase would be to reconfigure the commit history to look something more like this:

 * bbfe18e (rebase-branch) Even more branch commits
 * e83a812 Branch commit for README
/  
* 3a1ec67 (HEAD -> main) Another merge from another branch
* f53c9c0 Commits from a different branch
* eb4af0b Flesh out README
* 1a9a0b0 Initial commit

Performing a Rebase

The simplist way to perform a rebase is to follow this simple steps:

  1. Fetch the latest commits from the remote repo
  2. Run the rebase command
  3. Resolve conflicts (if any)
  4. Force push changes to the remote

To fetch the lastest changes simply run:

git fetch

This will get all the latest branch information from the remote and ensure that a rebase is not being performed on an out of date branch.

To rebase run the following command. Remember that one is able to rebase onto any branch, simply change origin/main below to the target branch name.

git rebase -i origin/main

This will run the rebase in interactive mode which will give an idea of what commits are staged to be rebased. Using the same example git branches from the above section, the rebase should look something like this:

1 pick e83a812 Branch commit for README
2 pick bbfe18e Even more branch commits
3
4 # Rebase 3a1ec67..bbfe18e onto 3a1ec67 (2 commands)

Write and quit the file. If there are conflicts an error message will appear:

CONFLICT (content): Merge conflict in README.md
error: could not apply e83a812... Branch commit for README
Resolve all conflicts manually, mark them as resolved with
"git add/rm <conflicted_files>", then run "git rebase --continue".
You can instead skip this commit: run "git rebase --skip".
To abort and get back to the state before "git rebase", run "git rebase --abort".
Could not apply e83a812... Branch commit for README

Running git status will show any conflicts that exist with a both modified tag:

$ git status
interactive rebase in progress; onto 3a1ec67
Last command done (1 command done):
   pick e83a812 Branch commit for README
Next command to do (1 remaining command):
   pick bbfe18e Even more branch commits
  (use "git rebase --edit-todo" to view and edit)
You are currently rebasing branch 'rebase-branch' on '3a1ec67'.
  (fix conflicts and then run "git rebase --continue")
  (use "git rebase --skip" to skip this patch)
  (use "git rebase --abort" to check out the original branch)

Unmerged paths:
  (use "git reset HEAD <file>..." to unstage)
  (use "git add <file>..." to mark resolution)

	both modified:   README.md

no changes added to commit (use "git add" and/or "git commit -a")

Edit the files one by one looking for merge conflicts. These can be found easily by searching for HEAD in the file. Pick and choose which lines of code to retain and make sure to delete the special characters surrounding the blocks of code marked as merge conflicts.

  1 # A WHOLE NEW README                                                            
  2 WOW LOOK AT ALL THESE CHANGES!!!                                                
  3                                                                                 
  4 <<<<<<< HEAD                                                                    
  5 ## I WAS COMMITED IN A DIFFERENT BRANCH                                         
  6 Lorem Ipsum                                                                     
  7                                                                                 
  8 ## AN EVEN DIFFERENT COMMIT FROM A DIFFERENT BRANCH                             
  9 Heyo                                                                            
 10 =======                                                                         
 11 Fleshing out README                                                             
 12                                                                                 
 13 Branch commit for README                                                        
 14 >>>>>>> e83a812... Branch commit for README  

Once conflics are handled, save the file and add it to the staging area. Do this for any other files with conflicts, then continue the rebase:

$ git add README.md
$ git rebase --continue
Successfully rebased and updated refs/heads/rebase-branch.

Aborting a Rebase

If unsure whether a rebase is going well or not, do not be afraid to bail out. This can be achieved by running git rebase --abort during any merge conflict resolution step.

Pushing Rebased Code

Attepmting to push rebased code to a remote will trigger an error message (due to the fact that there are conflicts between the local and remote branches). To get around this double check that the code is ready to be pushed. Then go ahead and run git push origin --force-with-lease on the branch that has been rebased. This will overwrite the commits on the remote and destroy them, so be very careful.

$ git push origin --force-with-lease
Enumerating objects: 16, done.
Counting objects: 100% (16/16), done.
Delta compression using up to 12 threads
Compressing objects: 100% (12/12), done.
Writing objects: 100% (12/12), 6.01 KiB | 6.01 MiB/s, done.
Total 12 (delta 6), reused 0 (delta 0)
remote: Resolving deltas: 100% (6/6), completed with 3 local objects.
To github.com:Project/rebase.git
 + 735cd70...6a06398 rebase-branch -> rebase-branch (forced update)

Rebasing Onto a Rebased Branch

There may be times where it is neccessary to rebase a branch back onto a branch that itself has been rebased. Due to the way git works this means that commits which existed in the base branch no longer have the same commit hash. Git will assume that these are different commits and try to apply them again.

Using the same example from earlier, running a rebase on a branch of a branch may yield something like:

1 pick e83a812 Branch commit for README # Duplicate commit
2 pick bbfe18e Even more branch commits # Duplicate commit
3 pick 2f1bf5b Additional changes to a branch off a branch 

If this occurs simply drop the duplicate commits on the branch that needs rebasing by changing pick to drop.

1 drop e83a812 Branch commit for README # Duplicate commit
2 drop bbfe18e Even more branch commits # Duplicate commit
3 pick 2f1bf5b Additional changes to a branch off a branch 

Fixup Commits and Autosquashing

Say a branch is reviewed by a fellow developer and changes are requested. What is the best way to implement these changes without disturbing the amazing Atomic Commits already in place, or redoing the entire commit history? Enter fixup commits.

Performing a Fixup Commit

To create a fixup commit, just add code as usual to the stagging area. Once there find the commit hash to fixup the commit onto. It does not matter how far back in the commit history the commit is if it is on the current branch. Copy the commit hash to fixup onto and run git commit --fixup <COMMIT_HASH>.

For example:

$ git add -p # Add the code
diff --git a/README.md b/README.md
index 4ad1a7f..3ea4102 100644
--- a/README.md
+++ b/README.md
@@ -2,6 +2,6 @@

 Fleshing out README

-Branch commit for README
+Branch commit for README with feedback from a review

 Event more branch commits
Stage this hunk [y,n,q,a,d,e,?]? y
$ git log # Find the commit
commit bbfe18e187a1927d9d38daaef63af6682205b078 (HEAD -> rebase-branch)
Author: Rebaser <rebase@blah.blah>
Date:   Fri Jan 10 13:09:51 2020 -0500

    Even more branch commits

commit e83a8125d1f60e056e09cd4ab923d3b60c0a9f94 # <---- The commit we want to fixup onto
Author: Rebaser <rebase@blah.blah>
Date:   Fri Jan 10 12:57:11 2020 -0500

    Branch commit for README
$ git commit --fixup e83a8125d1f60e056e09cd4ab923d3b60c0a9f94 # Do the fixup
[rebase-branch 2b0b37e] fixup! Branch commit for README
 1 file changed, 1 insertion(+), 1 deletion(-)

This can be done any number of times for any number of commits, in any order! This is all sorted out when autosquashing.

Cleaning Up Fixup Commits With Autosquash

One the code has been fixup'd to the appropriate level it is time to clean up the commit history using rebasing. Rebase as described above, but pass the --autosquash flag. This will automatically order fixup commits to be folded into the proper commit.

The output will look something like this:

1 pick e83a812 Branch commit for README                                           
2 fixup 2b0b37e fixup! Branch commit for README                                   
3 fixup 31f00a2 fixup! Branch commit for README                                   
4 pick bbfe18e Even more branch commits                                           
5 fixup 9a6b90e fixup! Even more branch commits                                   
6                                                                                 
7 # Rebase 3a1ec67..31f00a2 onto 3a1ec67 (5 commands)

Notice that the commits are staged to be fixup'd into the commit selected in the preceeding section! This will ditch their commit messages and include them in the first pick commit above them.

Continue with the rebase and all the fixup commits will be folded into the correct commit. The commit history is now nice and clean and ready to be pushed back up!

Additional Reading

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment