Migrating to a Mobile Monorepo for React Native
Over the past few months, we've been adding some React Native to our existing iOS and Android apps. We started out by just creating a
react-native repository and adding it as a submodule of our respective
android git repositories, but we quickly found that there was a fair amount of friction in coordinating between the three. We've now moved all of our mobile-related repositories (including a
mobile-scripts and a
shared-webview repository) into a single
Why did we do it?
We'd also get into a state where the
master branch of
react-native contained some changes that had only been coordinated with e.g. the
ios repo, and the
react-native submodule would be several commits behind. Then someone working on the Android side would update the submodule, and they'd have to track down all the breakages.
In short, we started running into a lot of synchronization issues that wouldn't happen if all of the code was in the same repository. With a monorepo, pull requests could be combined, reviews would be more coherent, and it would be easier to verify correctness between codebases.
- as a result, they would be less likely to get out of sync
- not having to mess with submodules 🎉
- we might lose Git history (this didn't turn out to be the case)
- we'd have to change all of our Jenkins build scripts
- moving all our developers to a new repository requires coordination
- we'd lose any in-flight branches and open PRs to the old repositories (we actually found a solution for this too)
What were the steps?
Make a fresh monorepo:
mkdir mobile; cd mobile; git init .
Have all the repos that you want to combine cloned and fully up to date
$ ls . mobile android ios react-native
Preparing the repos for merging
Clone each repo into
android as the example) and then move all files into a subfolder (except for
.git, of course).
git clone android m_android cd m_android mkdir android mv * .* android mv android/.git .
Then commit the result:
git add . && git commit -m'move to subfolder'
With the code for each respective codebase moved into a subdirectory, we're then able to move them all into a single repository without having them clash with each other. To illustrate, here's what the rough directory structure looks like:
android/ build.gradle, etc. ios/ AppDelegate.m, etc. react-native/ index.ios.js, etc. m_android/ android/ build.gradle, etc. m_ios/ ios/ AppDelegate.m, etc. m_react-native/ react-native/ index.ios.js, etc.
The monorepo will then have the following structure:
mobile/ android/ build.gradle, etc. ios/ AppDelegate.m, etc. react-native/ index.ios.js, etc.
m_reponame into the monorepo
Turns out git has super powers, and can totally merge in multiple unrelated repositories and preserve all the relevant git history. Who knew?
cd mobile git fetch ../m_android git merge FETCH_HEAD --no-ff --allow-unrelated-histories \ -m 'merging in android repo'
Again, do this for each repository that you need to merge in.
One thing I'm glossing over (that you'll have to figure out manually) is various dotfiles that you want to be shared.
.gitignore is fine being in the respective subdirectories, but in our case we use Phabricator, and so we needed to make a top-level
.arcconfig file that merged the
.arcconfigs from the previous three repositories.
I also had to manually bring over submodules, by re-cloning them in the new monorepo and checking them out at the commit where they were pinned in the pre-monorepo repositories.
And of course the
react-native folder was in a different place now that it wasn't a submodule, but a peer, to the iOS and Android codebases, so we had to update various relative paths and build scripts.
Bringing in new changes from the old repos
After creating the monorepo, our team had a flex week where they could still operate on the old repositories, so that they could land any outstanding code changes that they had inflight and move over at their own pace.
If you do the same, here's how to bring in new changes to the old repositories:
(cd android && git pull) cd m_android git pull ../android --no-ff \ -m 'merging in latest android changes'
At this point check for new files. Files that were added in the
android repo after the monorepo move will not get automatically moved into the
m_android/android subdirectory. It will be pretty obvious, because the only directory in the
m_android repo should be
android. If there's anything else there,
git mv the_new_thing android && git commit -am "moving new files
into subdirectory" before continuing.
cd ../mobile git fetch ../m_android git merge FETCH_HEAD --no-ff \ -m 'merging in latest android changes'
This will even work if there have been changes committed to the monorepo, although you may have to resolve merge conflicts.
Following Git history across the monorepo divide
One of the things that surprised me the most was that Git was totally able to track the changes, even though they came from three different repositories. When doing
git log for a specific file, you just need to add the
--follow option (which tells Git to follow the file across renames), and everything works!
And that's it!
We've been working with the monorepo for over a month now, and it's been well worth it. If you're integrating React Native into two existing apps that are currently in separate repositories, you might benefit from the shift as well!