git-sub-rebase
It's git rebase
except it also follows submodules, and rebases all of them onto the respective commit from the parent repo.
Usage
Link as git-sub-rebase
in your PATH, and then:
# Rebase current HEAD onto <ref>
git sub-rebase <ref>
# E.g.:
git sub-rebase origin/dev
You will see a whole bunch of debug text printed. This is intentional (easier to debug when something inevitably goes wrong).
What
Imagine you have two repos, structured like this, and you want to rebase HEAD
onto origin/master
Before rebase:
Parent Repo | Submodule
-------------------------|----------------------------
c <-- origin/master |
| |
| e <-- HEAD |
| | |
| d-------------------|-------->D <-- HEAD
| | | |
b--/--------------------|---->B / <-- origin/master
| / | | /
a-----------------------|---->A
Rebase the submodule and update pointers
Parent Repo | Submodule
-------------------------|----------------------------
c <-- origin/master |
| |
| e' <-- HEAD |
| | |
| d'------------------|---->D' <-- HEAD
| | | |
b--/--------------------|---->B <-- origin/master
| / | |
a-----------------------|---->A
Then rebase the main repo
Parent Repo | Submodule
-------------------------|----------------------------
e'' <-- HEAD |
| |
d''---------------------|---->D' <-- HEAD
| | |
c <-- origin/master | |
| | |
b-----------------------|---->B <-- origin/master
| | |
a-----------------------|---->A
You need to rebase D
onto B
in the submodule, then update d
and e
in the main repo to point to D'
, THEN rebase the updated d'
and e'
onto c
in the main repo. That way d''
points to D'
as d
pointed to D
, both repo histories are linear, and every commit in the parent repo points to a commit in the submodule that is in the branch from HEAD
.
Why
Look at how complicated this is, for just 3 commits and 1 submodule! I have to edit a repo that has many tracking submodules that all need to be rebased every time you want to rebase the parent repo, and I often have to rebase branches of 50+ commits against this. (This codebase was definitely not written during an extra long meeting while working on Binary Ninja, which has a notoriously submoduley repo structure...)
My options were:
- Manually handle rebase conflicts on every commit rebased, updating the various submodule pointers for each
- Discard submodule history and just rebase all the commits to the tip of the rebased submodule
- Write some rust code to do #1 for me :)
How
Easy N step process:
- Rebase all submodules according to these steps
- Record all old -> new commit hashes for the rebased submodule's commits
- Rebase parent repo's commits on new HEAD
a. For each commit in the parent repo that updates the submodule pointer, swap to the new submodule commit hash - Do some extra magic on the side for dealing with the fact that git submodules are a terrible system that break horribly when you try to do any git actions involving them
Contributing
This repo is a ceritified 👌 Spaghetti Masterpiece of recursion. If you wish to PR changes, you may do so at your own mental risk. The code is released under MIT License.