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.
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
The simplist way to perform a rebase is to follow this simple steps:
- Fetch the latest commits from the remote repo
- Run the rebase command
- Resolve conflicts (if any)
- 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.
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.
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)
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
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.
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.
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!