The below's been moved to a dedicated gist which is also updated and a bit ahead of the instructions below.
This repo is designated for experiments with rebasing.
The only document changed is this README.md.
At this stage it is not so important to follow
the history of commits, therefore early commit messages
are used to easily identify commits (e.g. A
, B
etc.)
and their rebased variations (e.g. A'
, B'
, B''
etc.)
Decorated one-line logs of this repo are used to illustrate the effect of rebase operations.
Legend
A
,B
,B1
,B2
etc. - commitsA'
,A''
,A1'
etc. - rebased commits- dev - branch name
There is some story of project development and master represents
released product, with latest commits A
and B
. At B
new development
branch dev has started.
Other changes (e.g. documentation) were introduced to master (D
).
We are on dev.
* e9378e5 C (HEAD -> dev)
| * e58c3c0 D (origin/master, master)
|/
* bdef04a B
* 9fa8598 A
* 6b2487d Init
We've done some changes F
that we want to push, but it appears that
meanwhile someone has already updated remote with E
.
git push
will require to merge in remote changes before any push.
git pull
will most likely cause a merge conflict (which you may
want to abort with git merge --abort
).
The proper way would be git pull --rebase
or git fetch && git rebase origin/dev
.
git push
to update the remote immediately.
Anyway if you have forgotten to rebase on pull, do the following
git merge --abort
(tree on the left)git rebase origin/dev
(tree on the right)
* 65dfd56 F (HEAD -> dev) * b7200ef F'(HEAD -> dev)
| * 0b160f5 E (origin/dev) * 0b160f5 E (origin/dev)
|/ |
| * e58c3c0 D (origin/master, master) | * e58c3c0 D (origin/master, master)
* | 801f263 C * | 801f263 C
|/ |/
* bdef04a B * bdef04a B
* 9fa8598 A * 9fa8598 A
* 6b2487d Init * 6b2487d Init
(actual git log
output above and further down is slightly amended for the sake of readability)
Should you anyway run into merger conflict:
- Resolve conflicts
git add <changed-files>
git rebase --continue
to complete
Note that F'
is assigned a different SHA.
You work on a feature on its own branch fea01. Every addition and amendment has its own commit. Finally you feel code is good enough (tested, etc) to be merged.
You find out that commits G1
..G4
refer to class Golf,
H1
..H3
refer to class Hockey, and I1
..I4
refer to
some core function.
* c051b13 I4 (HEAD -> fea01)
* 0324a66 H3
* b23329b I3
* b91896e G4
* 0b2fd60 I2
* f9f844d H2
* fbff5ee I1
* 5f382e6 G3
* 01664cf G2
* 13ee55b H1
* ede1d37 G1
* acc1c91 F' (origin/dev, dev)
* 0b160f5 E
| * e58c3c0 D (origin/master, master)
* | 801f263 C
|/
* bdef04a B
* 9fa8598 A
* 6b2487d Init
However commits related to an entity are scattered across the branch. It is sensible to group commits per entity and to probably even squash commits related to the same entity for it doesn't make sense to keep track for every tiny change anymore (especially if some part of code has evolved and its early versions are obsolete and useless).
Bring some order, squash commits. Have a back up to start over.
Rebasing commits on a branch has a side-effect:
...-G-H-I-J-K
^ feature-branch
git rebase -i HEAD~4 # (J-K-I-H)
...-G-J'-K'-I'-H'
^ feature-branch
Where are H, I, J, K? They are gone (almost), there is no reference (branch or tag) to track them.
Just create a branch pointer at current position to be able to undo things easily.
git checkout fea01
# Create a back-up pointer
git branch fea01-backup
# Rebase last 11 commits;
# alternatively refer to a particular SHA, e.g. acc1c91 (refers to F')
git rebase -i HEAD~11
# here is what git offers as a command list
pick becaf4d G1
pick b74e607 H1
pick 44db105 G2
pick 6a5edee G3
pick 2ec4aa5 I1
pick 91c528f H2
pick 427403a I2
pick 25c5f5a G4
pick 09def4c I3
pick 6406b2e H3
pick 9d4219f I4
# you see commits listed in chronological order
# bring some order
pick becaf4d G1
pick 44db105 G2
pick 6a5edee G3
pick 25c5f5a G4
pick b74e607 H1
pick 91c528f H2
pick 6406b2e H3
pick 2ec4aa5 I1
pick 427403a I2
pick 09def4c I3
pick 9d4219f I4
# squash (retaining only first commit in a group message)
pick becaf4d G1
fixup 44db105 G2
fixup 6a5edee G3
fixup 25c5f5a G4
pick b74e607 H1
fixup 91c528f H2
fixup 6406b2e H3
pick 2ec4aa5 I1
fixup 427403a I2
fixup 09def4c I3
fixup 9d4219f I4
# Save and quit the editor
# Resolve conflicts if any
# You may run into commit --allow-empty warning if you were undoing
# changes by means of committing deletions of what was added before
git commit --allow-empty -m "abc..."
# Or you may be required to resolve conflicts manually.
# When done stage files
git add README.md
# Whenever git rebase prompts for an additional action
# when done do:
git rebase --continue
# Redo the above until success reported
# At any stage until success reported you may revert to the initial state
git rebase --abort
Notes:
pick
: keep commit as isreword
: make a pause, edit messageedit
: make a pause, make any changessquash
: commit squahed onto upper, message added upfixup
: commit squashed onto upper, message ignoreddrop
: do not add this commit
Merger conflicts appear often when you are trying to reorder changes to the same file. Good reason to keep classes in their own files.
Now your tree looks like
* d034fee I' (HEAD -> fea01)
* 20f43d4 H'
* 35fcd3b G'
| * c051b13 I4 (HEAD -> fea01-backup)
| * 0324a66 H3
| * b23329b I3
| * b91896e G4
| * 0b2fd60 I2
| * f9f844d H2
| * fbff5ee I1
| * 5f382e6 G3
| * 01664cf G2
| * 13ee55b H1
| * ede1d37 G1
|/
* acc1c91 F' (origin/dev, dev)
* 0b160f5 E
| * e58c3c0 D (origin/master, master)
* | 801f263 C
|/
* bdef04a B
* 9fa8598 A
* 6b2487d Init
You've got 3 squashed and ordered commits G'
, H'
, and I'
.
fea01-backup points at rebase source commits.
If anything goes wrong just get rid of rebased commits:
git checkout fea01-backup
- switch to backup branchgit branch -f fea01
- forcely re-assign fea01 to the "old" location
Now your're totally back. Try again :)
If you're happy with the rebase result (e.g. tests run OK) just get rid of fea01-backup.
# forcely remove branch
git branch -D fea01-backup
# results in
* d034fee I' (HEAD -> fea01)
* 20f43d4 H'
* 35fcd3b G'
* acc1c91 F' (origin/dev, dev)
* 0b160f5 E
| * e58c3c0 D (origin/master, master)
* | 801f263 C
|/
* bdef04a B
* 9fa8598 A
* 6b2487d Init
You may keep developing on fea01 further, rebasing onto fea0-release and further onto dev from time to time whenever you have your changes consistent and wish to test it against codebase of a higher level.
So general workflow recurring feature development is:
- Branch off feaXX from dev
- Commit to feaXX
- Make a backup branch pointer feaXX-backup
- Rebase and test until you're happy
- Kill feaXX-backup
* 1b9180 Fix query further
| ...
* d4f67a Fix filtered query
1b9180
is a catch up of d4f67a
where we've forgotten to add a semi-colon (e.g.)
Normally, you would squash it during rebase.
Just tell git to suggest squashing at interactive rebase when committing:
* 1b9180 fixup! Fix filtered query
| ...
* d4f67a Fix filtered query
At git rebase -i
1b9180
will be suggested right after d4f67a
with a fixup command.
Also git rebase -i --autosquash <targetBase>
will try its best.