Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Git rebase recursive branches

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. normal state

It is all fine till I discover I've made a mistake in lesson1. So I go there and fix it. fix lesson 1

Now the problem occurs: I have to rebase each and every branch. So:

git checkout lesson2
git rebase lesson1

enter image description here

Afterwards the same for lesson3 and lesson4. enter image description here

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.

like image 455
Marcin Kunert Avatar asked Mar 24 '17 10:03

Marcin Kunert


People also ask

Should you rebase feature branches?

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.

When should you avoid rebasing a branch?

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.

What is the difference between a fast forward and recursive merge?

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.


2 Answers

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.)

like image 151
Mark Adelsberger Avatar answered Oct 27 '22 07:10

Mark Adelsberger


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.

like image 42
Gregg Avatar answered Oct 27 '22 05:10

Gregg