Forking Go Modules (Or Any Lib) Using Subtree
The two common patterns to forking a module are (1) forking via a separate repo
or (2) vendoring, which forks all deps into the current module (or app). I
recommend a third approach to forking using git subtree
and go mod edit -replace
. It’s very similar to repo forking without the repo
Let’s say you’ve made modifications to github.com/tj/go-spin
. Once preserving changes
in a repo is needed, use git subtree
to move the fork into your monorepo, or app repo
1. Make the changes to the original git repo (no push)
Make the needed changes, like adding routines or types, and commit those to the original repo on disk as usual.
You can commit to master
or your own feature/add-api
branch.
2. subtree
the repo into your monorepo
If you have a monorepo, subtree the lib there , e.g.
monorepo/go/forks/go-spin
. If your app is more self-contained, subtree into
app/forks/go-spin
. Both cases will behave very similarly.
Add the “remote” to the original repo
# in your own repo add the remote to the original
# mkdir -p forks/go-spin
git remote add subtree/go-spin ../../go-spin
# this pulls in all commits, including upstream and your fork's changes
git subtree add -P forks/go-spin subtree/go-spin feature/add-api
Now, all commits you made are in your fork. You can proceed with future work in your fork. If you accidentally commit in the wrong place, subtree makes it easy to pull or push commits to where they are needed.
3. Replace the module path in your own go module.
In the calling module or app, replace the module path
go mod edit -replace=github.com/tj/gospin=../forks/go-spin
Be sure that CI has access to the ../forks dir, or place the lib in your current app.
The benefit of this approach, is that no code changes will be needed. Go will
point references to go-spin
to your own fork
Wrap Up
The major benefit over time is reducing the number of fork-repos you need, while minimizing any “vendoring”. Vendoring is great for stability, but heavy handed with updating deps.
The subtree
approach is more surgical in allowing only specific forks to be “vendored”,
and reduces any extra bookkeeping or dependency management.
You might ask “why not just copy”? Subtree is smarter about merges in both direcions. subtree saves a lot of pain if you accidentally make changes to the wrong copy of the fork