Yes, there is a sane way to do this.
Preface
master
is now (if I understood correctly):
project
src
file.c
dev
is, since it did not contain commit A
:
src
file.c # with some changes
Your branches are like this:
master ( )----(A0)----(A)----(B)----(C)
\
\
dev (D1)----(D2)----(D3)----( )----(~ 30 additional commits...)
I also assume that A
differs from A0
in no way whatsoever, except project/
being inserted into all file paths ("moving your tree up" into the new project
root, as per your linked question).
Let's do it...
We do exactly what git rebase
would do internally, while fooling git
a bit, on every step, by doing our own "cherry-pick" step instead of what git
would do itself.
We use a second working directory. $WD1
is the absolute path to your original working directory, $WD2
is some other absolute path (outside of that).
First, create your helper directory as a clone of your original:
cd $WD2
git clone $WD1 dev .
Then, start a branch newdev
which will eventually be dev
rebased on A
.
cd $WD1
git checkout A
git checkout -b newdev
We get the D1
commit into newdev
without giving git
any chance to mess it up:
cd $WD2
git checkout D1
cd $WD1
rm -r project/src
cp -r $WD2/src project/
git add -A
git commit -m "`cd $WD2 ; git log -1 --format=%s`"
The situation is now:
newdev (D1')
/
/
master ( )----(A0)----(A)----(B)----(C)
\
\
dev (D1)----(D2)----(D3)----( )----(~ 30 additional commits...)
Now, repeat for D2
:
cd $WD2
git checkout D2
cd $WD1
rm -r project/src
cp -r $WD2/src project/
git add -A
git commit -m "`cd $WD2 ; git log -1 --format=%s`"
The situation is now:
newdev (D1')----(D2')
/
/
master ( )----(A0)----(A)----(B)----(C)
\
\
dev (D1)----(D2)----(D3)----( )----(~ 30 additional commits...)
Repeat until finished.
Of course, you will want to create a small shell script for those commands, which iterates through all commits D1 ... D30
.
Afterwards, a simple git rebase master
will do the rebase from A
to C
as usual. Then, get rid of newdev
by changing the name around:
git checkout newdev
git branch olddev dev # just in case...
git branch -D dev
git checkout -b dev
General notes
Why the second working directory?
Note that in this particular case there might be ways to avoid the secondary working directory, by using rm -r project/src ; git checkout D1 src ; mv src project/
directly. I prefer to do it as shown above instead, just to be 100% sure everything is very clean and "visible" at all times. This way the checkout operation is cleanly separated from the modification that we apply on our own.
There is literally nothing that could go wrong this way, and the approach works with all other kinds of changes as well. For a non-trivial change, assume somebody changed every whitespace in every single source file in A
. This approach makes it trivial to rebase other branches onto this (if you can put that whitespace change into a script as well).