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