I'm writing a programming course with in which I want to show how to write a program step-by-step. I thought I might use git for this purpose. The idea is to keep each lesson as a separate branch and create new branches as the course goes on.
It is all fine till I discover I've made a mistake in lesson1
. So I go there and fix it.
Now the problem occurs: I have to rebase each and every branch. So:
git checkout lesson2
git rebase lesson1
Afterwards the same for lesson3
and lesson4
.
I have about 20 lessons per course so every mistake is very painful. Is there a way to automate it or at least make it easier for me?
btw. The tool I've used to create the images is available here.
If the feature branch you are getting changes from is shared with other developers, rebasing is not recommended, because the rebasing process will create inconsistent repositories. For individuals, rebasing makes a lot of sense. If you want to see the history completely same as it happened, you should use merge.
If you use pull requests as part of your code review process, you need to avoid using git rebase after creating the pull request. As soon as you make the pull request, other developers will be looking at your commits, which means that it's a public branch.
Note: There is nothing right or wrong of either one of the strategies but with fast forward merge you have a straight line of history and with the recursive merge, it is of multiple lines.
So had to go back to the drawing board...
I previously suggested a simple filter-branch
command, but this has a significant flaw. (tl;dr - I no longer suggest this as a use case for filter-branch --parent-filter
; unless you care about why, you can jump to the next paragraph.) When you re-parent with git filter-branch
it doesn't re-apply changes for an effective merge, but rather keeps the tree in the re-parented commit as it was (creating new diffs, essentially). A filter-branch
is still possible, but it requires either a tree-filter
or an index-filter
and this would start to get rather complex. (If you can automate the fix in a script, then using that script as a tree-filter
should work - possibly with a little finesse in the rev-list arguments - but let's assume for the general case that this wouldn't be so easy. I thought about scripting a way to merge the changes from the "fix" commit into each commit in the graft, but that could result in a conflict at every turn and also isn't so easy...)
So what to do instead? Well, a scripted approach like Libin Varghese suggests is ok if there are no conflicts, and assuming that you can iterate through the ref names in a sensible way. But supposing there might be conflicts, there is another way...
So if you have
Bfix <--(lesson1)
/
A --- B --- C --- D --- E <--(lesson3)(HEAD)
|
(lesson2)
what you're trying to do essentially is
1) re-apply C
, D
, and E
over Bfix
as C'
, D'
, and E'
(a single rebase operation)
2) move all refs from a replaced commit (X
) to its replacement (X'
)
Using a single rebase minimizes the amount of conflict resolution. If you just rebase lesson3
then you'll have
(lesson1)
|
Bfix --- C' --- D' --- E' <--(lesson3)(HEAD)
/
A --- B --- C <--(lesson2)
and then you just need rewrite the refs for branches other than the 1st and last lessons. That means you need a mapping from "old commit X
" to "replacement commit X'
".
Just such a mapping is passed on stdin to .git/hooks/post-rewrite (if it exists) at the conclusion of a rebase. So you could write a script that uses git show-ref
to map ref (branch) names to "old" SHA1 values, then use the mapping on stdin to find the corresponding "new" SHA1 value, and call git update-ref
.
(I am planning to provide an example script, but I'm having some trouble with hooks in my test repo; so if I have a little time later, I'll come back to this. But if you're comfortable with scripting and hooks, the above outlines what needs to be done.)
Here is my attempt at solving the problem. You will have to fix my syntax errors, and complete the automation issues but this might be a start.
the single line
git rebase lesson1 lesson2
has the same effect as
git checkout lesson2
git rebase lesson1
you should rebase the last lesson so all the intermediate commits are transferred to the new branch at the same time. You will have to fix any conflicts that occur.
git rebase lesson1 lesson4
then transfer the branches to new commits (if the lessons are contiguous) with commands that look something like.
git branch lesson2a lesson4^2
git branch lesson3a lesson4^1
if the branches are contiguous. 'git help revisions' shows how to find a commit using it's commit message from the given branch.
git branch lesson2a lesson4^"{/Partial lesson2 commit message}"
git branch lesson3a lesson4^"{/Partial Lesson3 commit message}"
once this looks right remove the old commits
git branch -f lesson2 lesson2a
git branch -D lesson2a
see 'git help rebase' for the rebase syntax
and 'git help revisions' for different ways to specify commits.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With