Rebase¶
Reference: git-rebase(1)
The introduction is a condensed presentation of examples in git-rebase(1)
Rebase a topic¶
Suppose you have this situation:
A---B---C---D---E topic
/
D---E---A---F master
And you are on topic
.
Then the command:
git rebase master
will apply on master
all commit in topic
that where not yet
there, A will be skipped, and the result is:
B'--C'---D'---E' topic
/
D---E---A---F master
If you where not on topic
but on master
then you should have
used instead:
git rebase master topic
That will do the successive operations:
git checkout topic
git rebase master
If you want only to use the commits starting with D
you use:
git rebase --onto master C topic
to get:
D'---E' topic
/
D---E---A---F master
In all these example the rebase let you on topic
.
Interactive Rebase¶
Reference: git-rebase interactive mode
The --interactive
switch is used to reorder or merge patches.
$ git rebase --interactive HEAD~8
pick f08daa2 modified: moin/farmconfig.py configured for moinmoin notebook and marcwiki
pick 802071d moin/notebook.py: added config for notebook
pick 65802dc moin/farmconfig.py added mail_smarthost
pick ee35e7d changed fstab and hosts
pick 9913667 /etc/fstab: fixed cifs and nfs shares
pick 54055e3 fstab: crypt cannot be fscked at boot, disabled fsck
pick 1470a45 fstab: changed mountpoint
pick afbb0b8 passwd group mailcap state of etc/kernoel/master
# Rebase 15b369f..afbb0b8 onto 15b369f
#
# Commands:
# p, pick = use commit
# e, edit = use commit, but stop for amending
# s, squash = use commit, but meld into previous commit,
# and edit the sum of commit messages
# If you remove a line here THAT COMMIT WILL BE LOST.
The option are:
- delete:
- if you delete the commit line, it will be omitted from the rebase.
- reorder:
- You can change the orders of the commit pick lines, they will be processed in the new order.
- pick, or reword (shortcuts
c
andr
): - Include the commit silently, reword is similar to pick, but rebase will open the commit message in an editor to allow you to fix it.
- edit (shortcut
e
): - For each edit the commit is applied, then the rebase pause to
allow you to use
git commit --amend
to change the commit message, or change the commit, or split it in many smaller commits. - squash and fixup (shortcuts
s
andf
): - squash merge the commit in the previous one, then the rebase pause to let you edit the merged commits. If you instead use fixup, the second commit message is discarded and the first one is used.
- exec (shortcut
x
): - exec command launches the command in a shell spawn from the root
of the working tree. The rebase will continue if the shell exit
with a 0 status, and pause when the command fail, to let you fix
teh error and
git rebase --continue
orgit rebase --abort
.
reorder + squash + delete is a very powerful tool to clean a suite of patches.
For each edit, squash, failed exec or conflict rebase will stop until you edit or merge comments (in case of a squash), or fix the conflict, then you just need to:
$ git rebase --continue
or:
$ git rebase --abort
Interactive rebase example¶
You have made a small error in the file SmtplibExample.py, and corrected it, You don’t want to make a new commit for this tiny fix, but make it part of your previous commit.
You stash your current work
$ git stash --quiet
You look at the last commit for the file
$ git log -n 1 --pretty=oneline --abbrev-commit TD/Smtp/SmtplibExample.py
9c091e6 SmtplibExample.py: refactored to a function and a main.
You rebase from the previous commit:
$ git rebase --interactive 9c091e6^
You get the rebase list to edit:
pick 9c091e6 SmtplibExample.py: refactored to a function and a main.
pick 3d3f53e SmtplibExample2.py: 2to3, switched to argparse, minor fixes
pick 0c4f2cf Cours/SocketTcp.mdn: sockets lectures now in markdown
pick aa34250 index.mdn: added sockets
....
You change the first pick to edit valid it, then rebase pause at:
Stopped at 9c091e6... SmtplibExample.py: refactored to a function and a main.
You can amend the commit now, with
git commit --amend
Once you are satisfied with your changes, run
git rebase --continue
You checkout your amended file from the stash:
git checkout stash@{0} -- : TD/Smtp/SmtplibExample.py
You add it and amend the commit:
git add TD/Smtp/SmtplibExample.py
git commit --amend
You continue the rebase:
[detached HEAD eae8d29] SmtplibExample.py: refactored to a function and a main.
1 files changed, 22 insertions(+), 14 deletions(-)
Successfully rebased and updated refs/heads/master.
See also the Interactive rebase help at github
Checking your rebase and undoing it¶
The rebase can be a dangerous operation, sometime I lost a file by deleting a commit that add a file within an interactive rebase. The head before a rebase is stored in ORIG_HEAD. All dangerous operations like rebase, merge, pull, am modify this reference, so you can only use it to refer to the HEAD before the last dangerous operation (but a simple commit don’t change it).
To see what you have changed in the repository since last dangerous operation:
git diff ORIG_HEAD HEAD
If it was an interactive rebase to clean your history you expect that you preserved the global state of your repository, and to have an empty answer.
To see what commits are in HEAD and not in ORIG_HEAD:
git log ORIG_HEAD..HEAD
.. index:: gitk
You can also use visualization tools like tig ou gitk:
gitk ORIG_HEAD HEAD
gitk ORIG_HEAD --not --all
tig ORIG_HEAD..HEAD
Or:
tig ORIG_HEAD...HEAD
and you may want to toggle revision graph visualization with g key.
After an interactive rebase you may want to check the commits since the begining of the rebase in both branches. You will use:
git log --boundary --pretty=oneline --left-right ORIG_HEAD...HEAD
And if your rebase went wrong you restore the previous state with:
git reset --hard ORIG_HEAD
If you have lost your ORIG_HEAD after a rebase because you did an other operation that reset it, you can still find the previous head which is now a dangling ref, unless you have garbage collected it.
You need to inspect your reflog and find the first commit before the rebase, in an interactive rebase the process begin with a checkout of the commit on which you rebase, so the previous commit was the head before the rebase:
git reflog
....
95512de HEAD@{7}: rebase -i (pick): fixin typos
a1b9b5c HEAD@{8}: checkout: moving from master to a1b9b5c
c819a90 HEAD@{9}: commit: adding myfile.txt
In this example the previous head was the ninth older commit HEAD@{9} with an abbreviated commit c819a90.
dangling objects¶
The main section is the garbage collection section
After rebasing the old branch head is no longer in a branch and so it is dangling, it will be garbage collected when it will be no more referenced.
As explained in the previous section it is used in the reflog, so it will be garbage collected after expiring the reflog.
Sometime, when we are certain our rebase is correct and we will never want to come back to previous state, we want to clean these dangling objects. We use:
$ git prune
If we want to do the opposite, i.e. preventing this dangling commit to be lost some next garbage collection away we can point a new branch at it:
$ git branch <recovery-branch> <dangling-commit-sha>