About Rebasing¶
Rebasing is used to rearange commit history in a git repository.
We typically think of rebasing as the operation of moving the origin of a branch to a different commit, but it's extremely flexible.
One other usage of rebasing is to "clean up" or rearrange history along a single branch.
Using git rebase we can remove commits from our history, edit old commits, or insert new commits into our timeline.
However, there's some confusing information about what exactly is going on when we rebase.
It's common to see rebase described as re-writing history, but that's not really what's happening under the hood.
Rebasing actually leaves all the old git commits alone (remember that git commits are immutable), and creates a whole new history in parallel.
Time Travel¶
Git rebase works a bit like time travel in fiction (depending on the story)
Now, there's all kinds of ways time travel can work in fiction.
Unfortunately, it doesn't work like Bill and Ted's Excellent Adventure (wouldn't it be nice if bug fixes magically appeared as long as you promised to do them later),
but fortunately, it doesn't work like The Terminator series either (fixed a bug? Too bad, you will always have that bug because it's fate).
The story that demonstrates git rebase best is actually from Dragon Ball Z.
The Original Timeline¶
Trunks didn't know he was about to learn about directed acyclic graphs when he went back in time to save Goku.

Let's create a git repository with some sensible defaults and get going. We'll be following "Future" Trunks through history.
rm -vdrf !("Goku.ipynb"|"resources_raw"|"init.sh") > /dev/null
rm -rdf .git .gitignore > /dev/null
git init
git config --local user.name "Jason Ross"
git config --local user.email "jasonross1024@gmail.com"
git checkout -b "FutureTrunks"
echo ".ipynb_checkpoints/" >> .gitignore
echo "*.ipynb" >> .gitignore
git add .gitignore
Initialized empty Git repository in /home/jason/Projects/blog/content/GitTimeTravel/.git/ Switched to a new branch 'FutureTrunks'
We find our heros (minus Goku) assembled to confront an evil force that is approaching from space.


touch vegeta krillin gohan piccolo bulma
git add vegeta krillin gohan piccolo bulma
git commit --quiet -m \
"The Z fighters prepare for battle with the unknown evil force" \
--date 2000-01-01T09:00:00
Our heros wait with baited breath as a ramp descends from the alien spacecraft. It's Frieza! How can this be?!

touch frieza king-cold
git tag "Battle-Begins"
git add frieza king-cold
git commit --quiet -m "The evil arrives. IT'S FRIEZA (and king cold)" --date 2000-01-01T09:30
While Frieza could easily destroy the entire planet instantly, he cruelly decides to watch his minions fight our heros for his amusement instead.

touch minions
git add minions
git commit --quiet -m "Frieza's minions emerge and the battle begins" \
--date 2000-01-01T09:35
With a bang, a man appears from nothing. It's Goku! The earth is saved!

touch goku
git add goku
git commit --quiet -m "Goku arrives using instant transmission" \
--date 2000-01-01T09:40
Goku easily dispatches the enemy forces. Frieza and is father are no more.

git rm minions > /dev/null
git commit --quiet -m "Goku dispatches Frieza's minions with ease" \
--date 2000-01-01T09:45
git rm frieza king-cold > /dev/null
git commit --quiet -m "Goku defeats Frieza and his father once and for all" \
--date 2000-01-01T09:50
Bulma has a baby. It's the protagonist of our story, Trunks,

touch trunks
git add trunks
git commit --quiet -m "Trunks is born" --date 2002-01-01
But, to our shock and dismay, Goku falls ill with a deadly heart virus! He struggles for life, but ultimately succumbs. The savior of earth is no more.

touch heart-virus
git add heart-virus
git commit --quiet -m "Goku contracts a deadly heart virus" --date 2002-10-01
git rm goku heart-virus > /dev/null
git commit --quiet -m "Goku succumbs to the heart virus" --date 2002-11-01
Worse yet, our remaining heros find themselves facing new and terrifying foes. These androids are stronger than any of our heros and can fight tirelessly.

touch android17 android18
git add android17 android18
git commit --quiet -m "Androids 17 and 18 emerge from Dr. Gero's secret lab" \
--date 2003-01-01
Although our heros fight valiantly, the infinite energy reserves of the androids are too much for them and they are defeated one by one.

git rm piccolo krillin vegeta > /dev/null
git commit --quiet -m 'Defeat!
The Z fighters are defeated one by one, leaving only Trunks, Gohan, and Bulma' \
--date 2004-01-01
Trunks is too young to know these heros who tried to save the world from certain destruction, but they live on in the memories of his mother Bulma and Goku's son, Gohan. Trunks grows up in a world in chaos, fighting for survival at every turn and watching his friends be killed by these souless androids. Finally, even Gohan is defeated.

Trunks finds power within himself from his grief and anger, and becomes a legendary Super Sayian!

git rm gohan > /dev/null
git commit --quiet -m 'Gohans Sacrifice
Gohan sacrifices himself to save Trunks. Driven by anger and grief, Trunks
finally finds the strength to transform into a Super Sayian' \
--date 2014-01-01
Although Gohan is gone, Trunks' mother Bulma has an ace up her sleeve - a time machine! Can our hero can use it to change history and save Goku from the deadly heart virus? Will the future he returns to be one where the androids never decimated the earth?
Let's do a git rebase and find out!!!
The Modified Timeline¶
Now that we've created the original timeline, let's follow along from Trunks' perspective as he does a git rebase to rewrite history.
touch time-machine medicine
git add time-machine medicine
git commit --quiet -m 'The time machine
Bulma reveals that she has developed a time machine. Can Trunks use it to
change history, save Goku, and prevent this tragedy from ever happening?' \
--date 2017-01-01 > /dev/null
git tag "Jump-Back"
We start by doing a git rebase onto the tag "Battle-Begins". We need to do an interactive rebase to rework the history of the branch we're currently on.
I'm using a Jupyter Notebook to get terminal output and scripts to show in the same place. The side effect is that I can't use a text editor to do an interactve rebase normally. Instead, I just use
sedto mark all commits for edit and work my way through history manually. This is what thegit-rebase-todofile looks like.
# Tell git to use sed as the sequence editor just for this post
GIT_SEQUENCE_EDITOR="sed -i -re 's/^pick /e /'" git rebase -i Battle-Begins
Stopped at 711d206... The evil arrives. IT'S FRIEZA (and king cold) You can amend the commit now, with git commit --amend Once you are satisfied with your changes, run git rebase --continue
Let's just peek at the rebase todo list - you could normally edit this in a text editor
REBASEFILE=.git/rebase-merge/git-rebase-todo
cat $REBASEFILE | head -n 11
edit 29ea552e224f8bd1e1fa17e8dc29aa5eea050ef3 Frieza's minions emerge and the battle begins edit 70ad418efb57ec6d4ec874e1c80d716afec79ea0 Goku arrives using instant transmission edit 7efefaa2ac62b33cebe63646f3221b728202742b Goku dispatches Frieza's minions with ease edit ab589b3246668d251f3cd587f1f6878f5bfdee5d Goku defeats Frieza and his father once and for all edit b48006d32a99bbb82fdc495cd03b7f71d23f5e04 Trunks is born edit c2acaaa53ebece3371b99aa198db7d26156d32f1 Goku contracts a deadly heart virus edit 6c4849d5d198a2f7b8b8f27a949fcf34053e73c1 Goku succumbs to the heart virus edit ebd8fc80a3347cb5782d3f0b2e5e1886f50b2c2a Androids 17 and 18 emerge from Dr. Gero's secret lab edit 09bb56181be85c9918e09061ef8450a1a486078c Defeat! edit 3bf96c899d3474b9a8e769e0baefc33bf4b6ed2c Gohans Sacrifice edit 25640010f6982dc3599c2522e9a26d2f65714862 The time machine
git checkout Jump-Back -- trunks medicine time-machine
git mv trunks future-trunks
git add future-trunks medicine time-machine
git commit \
-m "Future Trunks arrives in a time machine to the astonishment of the Z fighters" \
--date 2000-01-01T09:33
[detached HEAD a91e3b5] Future Trunks arrives in a time machine to the astonishment of the Z fighters Date: Sat Jan 1 09:33:00 2000 -0800 3 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 future-trunks create mode 100644 medicine create mode 100644 time-machine
Trunks does a quick git log to see where he ended up. Uh-oh. Looks like there are some bad guys around.
git log --pretty=oneline --abbrev-commit
echo
echo
ls
a91e3b5 (HEAD) Future Trunks arrives in a time machine to the astonishment of the Z fighters 711d206 The evil arrives. IT'S FRIEZA (and king cold) c2a2078 (tag: Battle-Begins) The Z fighters prepare for battle with the unknown evil force bulma future-trunks king-cold medicine time-machine frieza gohan krillin piccolo vegeta
The story continues as it did before. Let's do a git rebase --continue to advance time a bit.
git rebase --continue
Stopped at 29ea552... Frieza's minions emerge and the battle begins You can amend the commit now, with git commit --amend Once you are satisfied with your changes, run git rebase --continue
Just as before, some minions emerge, but this time, Trunks deals with them. Then, to the astonishment of the Z fighters, the mysterious time traveler defeats the evil Frieza and King Cold.

head -n 5 $REBASEFILE
edit 70ad418efb57ec6d4ec874e1c80d716afec79ea0 Goku arrives using instant transmission edit 7efefaa2ac62b33cebe63646f3221b728202742b Goku dispatches Frieza's minions with ease edit ab589b3246668d251f3cd587f1f6878f5bfdee5d Goku defeats Frieza and his father once and for all edit b48006d32a99bbb82fdc495cd03b7f71d23f5e04 Trunks is born edit c2acaaa53ebece3371b99aa198db7d26156d32f1 Goku contracts a deadly heart virus
Let's think about how adjust our timeline. Trunks is about to defeat Frieza's minions, followed by Frieza and King Cold. In the old timeline, Goku defeats those enemies, so we will need to reword those commits. Goku shouldn't arrive until after the enemies are defeated, so we will need to reorder that commit. We can do this by editing our todo list. Again, I'm using a bunch of shell commands, but normally you'd just open it in a text editor...
{
head -n 2 $REBASEFILE | tail -n 1 \
| sed -re "s/^edit /reword /";
head -n 3 $REBASEFILE | tail -n 1 \
| sed -re "s/^edit /reword /";
head -n 1 $REBASEFILE;
tail -n +4 $REBASEFILE | head -n 2;
tail -n +6 $REBASEFILE | sed -re 's/^edit /drop /' | head -n 1;
tail -n +7 $REBASEFILE | head -n 1;
tail -n +8 $REBASEFILE | sed -re 's/^edit /drop /';
} > tmp
head -n 11 tmp
mv tmp $REBASEFILE
reword 7efefaa2ac62b33cebe63646f3221b728202742b Goku dispatches Frieza's minions with ease reword ab589b3246668d251f3cd587f1f6878f5bfdee5d Goku defeats Frieza and his father once and for all edit 70ad418efb57ec6d4ec874e1c80d716afec79ea0 Goku arrives using instant transmission edit b48006d32a99bbb82fdc495cd03b7f71d23f5e04 Trunks is born edit c2acaaa53ebece3371b99aa198db7d26156d32f1 Goku contracts a deadly heart virus drop 6c4849d5d198a2f7b8b8f27a949fcf34053e73c1 Goku succumbs to the heart virus edit ebd8fc80a3347cb5782d3f0b2e5e1886f50b2c2a Androids 17 and 18 emerge from Dr. Gero's secret lab drop 09bb56181be85c9918e09061ef8450a1a486078c Defeat! drop 3bf96c899d3474b9a8e769e0baefc33bf4b6ed2c Gohans Sacrifice drop 25640010f6982dc3599c2522e9a26d2f65714862 The time machine
After editing our todo list looks good. We'll once again have to do some fancy footwork with
sedto do the rewording, which you wouldn't normally do from a shell.
GIT_EDITOR="sed -i -re \
's/(.+minions.+)/Trunks defeats Friezas minions/; \
s/(.+father.+)/Trunks defeats Frieza and his father, dramatically/'" git rebase --continue
[detached HEAD afe18a6] Trunks defeats Friezas minions 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 minions [detached HEAD 50f58fe] Trunks defeats Frieza and his father, dramatically 2 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 frieza delete mode 100644 king-cold Stopped at 70ad418... Goku arrives using instant transmission You can amend the commit now, with git commit --amend Once you are satisfied with your changes, run git rebase --continue
Goku has arrived again, but this time, the enemies have been defeated before he arrives.
Goku and Trunks meet, and Trunks gives Goku the heart medicine.

echo "Met Goku" >> future-trunks
git add future-trunks
git commit -m "Future Trunks meets Goku" --date 2000-01-01T09:41:00
[detached HEAD f30a04c] Future Trunks meets Goku Date: Sat Jan 1 09:41:00 2000 -0800 1 file changed, 1 insertion(+)
Future Trunks is able to share the heart medicine with Goku and saves his life.

echo "Has heart medicine" >> goku
git add goku
git commit -m "Future Trunks gives Goku the heart medicine so Goku will be able to save them from the androids." --date 2002-09-01
[detached HEAD 721c643] Future Trunks gives Goku the heart medicine so Goku will be able to save them from the androids. Date: Sun Sep 1 11:40:17 2002 -0700 1 file changed, 1 insertion(+)
After a while, Trunks (not Future Trunks) is born (just like before)
git rebase --continue
Stopped at b48006d... Trunks is born You can amend the commit now, with git commit --amend Once you are satisfied with your changes, run git rebase --continue
Goku still contracts the deadly heart virus, but this time he has medicine.
git rebase --continue
Stopped at c2acaaa... Goku contracts a deadly heart virus You can amend the commit now, with git commit --amend Once you are satisfied with your changes, run git rebase --continue
git rm medicine heart-virus
git commit -m "Goku takes the heart medicine and survives the deadly virus"
rm 'heart-virus' rm 'medicine' [detached HEAD f38bc83] Goku takes the heart medicine and survives the deadly virus 2 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 heart-virus delete mode 100644 medicine
Peeking at the rebase todo list, we see that we're set to drop the commit where Goku dies. It's safe to continue.
cat $REBASEFILE
drop 6c4849d5d198a2f7b8b8f27a949fcf34053e73c1 Goku succumbs to the heart virus edit ebd8fc80a3347cb5782d3f0b2e5e1886f50b2c2a Androids 17 and 18 emerge from Dr. Gero's secret lab drop 09bb56181be85c9918e09061ef8450a1a486078c Defeat! drop 3bf96c899d3474b9a8e769e0baefc33bf4b6ed2c Gohans Sacrifice drop 25640010f6982dc3599c2522e9a26d2f65714862 The time machine # Rebase c2a2078..2564001 onto c2a2078 (12 commands) # # Commands: # p, pick = use commit # r, reword = use commit, but edit the commit message # e, edit = use commit, but stop for amending # s, squash = use commit, but meld into previous commit # f, fixup = like "squash", but discard this commit's log message # x, exec = run command (the rest of the line) using shell # d, drop = remove commit # # These lines can be re-ordered; they are executed from top to bottom. # # If you remove a line here THAT COMMIT WILL BE LOST. # # However, if you remove everything, the rebase will be aborted. # # Note that empty commits are commented out
The androids emerge in this timeline, but our heros are prepared. With Goku and Trunks, how could they lose?

git rebase --continue
Stopped at ebd8fc8... Androids 17 and 18 emerge from Dr. Gero's secret lab You can amend the commit now, with git commit --amend Once you are satisfied with your changes, run git rebase --continue
One more
rebase --continueand we will be up to date.
git rebase --continue
Successfully rebased and updated refs/heads/FutureTrunks.
Trunks' changes to the past have caused a new threat to emerge - the evil Cell!

touch cell cell-time-machine
git add cell cell-time-machine
git commit -m "Cell arrives in a time machine and begins to wreak havoc" --date=2003-03-01
[FutureTrunks 936c4a8] Cell arrives in a time machine and begins to wreak havoc Date: Sat Mar 1 10:40:22 2003 -0800 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 cell create mode 100644 cell-time-machine
With their combined efforts, our heros are able to defeat this new threat.

git rm cell goku
git commit -m "Cell is defeated" --date=2003-12-01
rm 'cell' rm 'goku' [FutureTrunks e4b94b3] Cell is defeated Date: Mon Dec 1 10:40:23 2003 -0800 2 files changed, 1 deletion(-) delete mode 100644 cell delete mode 100644 goku
Trunks says goodbye to his new friends and returns to his own history. The rebase is done, but has he really changed history? What will his timeline look like with the androids defeated?

git rm trunks
git commit -m "Trunks bids his friends farewell and returns to his timeline" --date=2003-12-31
git tag "Trunks-Leaves"
git branch "MainTimeline"
rm 'trunks' [FutureTrunks 937bfba] Trunks bids his friends farewell and returns to his timeline Date: Wed Dec 31 10:40:24 2003 -0800 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 trunks
Like Trunks, we want to see what happened to our own timeline. Did any of the changes we made to the past actually change our history?
# do a hard-reset to simulate time travel
git reset --hard Jump-Back
# checkout future-trunks from the past to simulate trunks moving into the future
git checkout Trunks-Leaves -- future-trunks
mv future-trunks trunks
HEAD is now at 2564001 The time machine
Let's take a look at the log to see what our new timeline looks like!
git log --pretty=oneline --abbrev-commit
2564001 (HEAD -> FutureTrunks, tag: Jump-Back) The time machine 3bf96c8 Gohans Sacrifice 09bb561 Defeat! ebd8fc8 Androids 17 and 18 emerge from Dr. Gero's secret lab 6c4849d Goku succumbs to the heart virus c2acaaa Goku contracts a deadly heart virus b48006d Trunks is born ab589b3 Goku defeats Frieza and his father once and for all 7efefaa Goku dispatches Frieza's minions with ease 70ad418 Goku arrives using instant transmission 29ea552 Frieza's minions emerge and the battle begins 711d206 The evil arrives. IT'S FRIEZA (and king cold) c2a2078 (tag: Battle-Begins) The Z fighters prepare for battle with the unknown evil force
Looks like nothing changed in the original timeline! We didn't really change history at all.
git commit -m "Trunks arrives back in his own timeline. However, he discovers that the future is still as he left it." \
--date 2017-01-02
[FutureTrunks 3c2a95c] Trunks arrives back in his own timeline. However, he discovers that the future is still as he left it. Date: Mon Jan 2 10:40:28 2017 -0800 1 file changed, 1 insertion(+) create mode 100644 future-trunks
Although Trunks had hoped to have saved his own timeline, he finds things exactly as he left them. However, Trunks has grown emotionally and physically from his time and training with his father Vegeta and with Goku. Trunks is able to save his own timeline from the Androids.

git rm android17 android18
git commit -m 'Trunks defeats the androids and saves his future'\
--date=2017-01-03
rm 'android17' rm 'android18' [FutureTrunks 6180b99] Trunks defeats the androids and saves his future--date=2017-01-03 2 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 android17 delete mode 100644 android18
What Happened?¶
When Trunks got in the time machine, he thought he could change history. However, history in Dragon Ball Z, like git commit objects, is immutable. When he arrived in the past, he created a fork in the timeline. Changes made to the fork didn't affect his own timeline. In the same way, when we did a git rebase, we didn't actually change the timeline we came from; we just created a new one. The other timeline still exists, and we can go back using the reflog (at least until the old timeline gets garbage collected).
The real danger of "rewriting history" with git if you push a commit to a remote with incompatible history. Suppose we had pushed Goku's death to a remote repository - we'd be "locked in" at that point and would have to find some other way to save the future. But as long as we haven't pushed a commit, we're OK to rebase and amend and rebase to our heart's content (as long as we don't leave detached branches around too long - check your gc.pruneExpire setting).
Let's inspect the reflog and take a look at what really happened.
git log --graph --reflog --pretty=oneline --author-date-order --decorate --abbrev-commit
* 6180b99 (HEAD -> FutureTrunks) Trunks defeats the androids and saves his future--date=2017-01-03 * 3c2a95c Trunks arrives back in his own timeline. However, he discovers that the future is still as he left it. * 2564001 (tag: Jump-Back) The time machine * 3bf96c8 Gohans Sacrifice * 09bb561 Defeat! | * 937bfba (tag: Trunks-Leaves, MainTimeline) Trunks bids his friends farewell and returns to his timeline | * e4b94b3 Cell is defeated | * 936c4a8 Cell arrives in a time machine and begins to wreak havoc * | ebd8fc8 Androids 17 and 18 emerge from Dr. Gero's secret lab | * 59d1361 Androids 17 and 18 emerge from Dr. Gero's secret lab | * f38bc83 Goku takes the heart medicine and survives the deadly virus * | 6c4849d Goku succumbs to the heart virus | * 3bef15d Goku contracts a deadly heart virus * | c2acaaa Goku contracts a deadly heart virus | * cf2d94d Trunks is born | * 721c643 Future Trunks gives Goku the heart medicine so Goku will be able to save them from the androids. * | b48006d Trunks is born * | ab589b3 Goku defeats Frieza and his father once and for all * | 7efefaa Goku dispatches Frieza's minions with ease | * f30a04c Future Trunks meets Goku * | 70ad418 Goku arrives using instant transmission | * 9a6ec8d Goku arrives using instant transmission | * 50f58fe Trunks defeats Frieza and his father, dramatically | * afe18a6 Trunks defeats Friezas minions * | 29ea552 Frieza's minions emerge and the battle begins | * ad25791 Frieza's minions emerge and the battle begins | * a91e3b5 Future Trunks arrives in a time machine to the astonishment of the Z fighters |/ * 711d206 The evil arrives. IT'S FRIEZA (and king cold) * c2a2078 (tag: Battle-Begins) The Z fighters prepare for battle with the unknown evil force
We can see that there are now two branches in history.
I used some tags to make it easier to jump back to the time machine, but if I hadn't done so, I still could have gotten the reference to jump back to using the reflog.
Note that in a normal scenario, we'd just leave the old timeline behind and proceed as before, but suppose we made a mistake in the middle of a rebase and didn't realize it until after?
We'd just do a git reset back to our original starting point and do the rebase all over again.
Nothing would be lost unless we waited long enough for the garbage collector to prune our dangling branch.