History Repeating Itself

At my last job, I wanted to take some private company CocoaPods and merge them into the main company codebase. That way, I could make changes to interrelated classes with a single commit.

But the pods and the main codebase were all in different GitHub repositories.

The naive way to do this would just be to take all the pod doors files and copy them over to the main repository, and check them in as a new commit. But that would lose all the history of those files, which I didn’t want.

Instead, I decided to copy the GitHub history of the pod repositories over. Yup, you can combine completely unrelated GitHub repositories and retain all their histories, together, without mucking about in git internals. Thanks to Jens Ayton for telling me about the necessary steps.

I’ve created an extremely simple set of three GitHub repositories to show how it works.

WhiteProject and BlueProject are the stand-ins for the CocoaPods projects. They have but a single file in them, White.swift and Blue.swift, respectively.

RainbowProject is the stand-in for the main codebase. It’s a regular sample Xcode project, in this case a macOS command-line app.

You can see that color projects each have a commit history, for the creation of their Swift files and for the addition of some comments.

First thing I did was clone all three repositories locally, in the same parent directory.

Then, I created a branch in RainbowProject called add-white-project, so I could make a pull request of it later.

After that, I added a remote reference to the WhiteProject repository to RainbowProject, like this:

git remote add WhiteProject ../WhiteProject/

I make the connection via the two local copies of the repositories. I don’t know if there’s a way to accomplish this without using local copies.

Here’s what it looks like to have that remote reference, in SourceTree:

Table with header Remotes and two rows, first row WhiteProject and second row origin

Next, I went ahead and merged the remote repository into the local repository with this command:

git merge --allow-unrelated-histories -m 'Merge history from WhiteProject' WhiteProject/master

Note the following:

  • The --allow-unrelated-histories argument is needed by git 2.9 and higher according to this Stack Overflow answer and my own experience. I’ve got git 2.10 installed on my machine. Is that from an Xcode install or my own separate install? What version of git does come with Xcode? I can’t answer these questions, so your mileage may vary.
  • You need to specify both the remote repository and the branch in the remote repository, or it won’t work.

Here’s what it looks like in SourceTree after that merge:

Tree with root add-white-project and two branches, first branch from the RainbowProject repository with one commit, and second branch from the WhiteProject repository with two commits

Note the separate WhiteProject repository history is all there (all two commits, in our extremely simple example), and it’s hanging off of that merge commit we just made, all without obliterating the previous RainbowProject history, either. That’s what we want.

From here, I made a pull request, as you would do for a Real Project at Work. Here’s what that looks like on the GitHub website:

Screenshot of GitHub pull request user interface including PR text and list of commits.

I merged that, and then removed the remote reference, which was no longer needed:

git remote remove WhiteProject

At that point, I was done with the WhiteProject merge, and ready to perform the same steps for the BlueProject.

Now, the steps I followed for the merge at work were much more complicated than this simple example. In particular, I had to take what used to be separate static libraries whose files were managed by CocoaPods, and add them to my main Xcode project directly.

From that more complex scenario, I have a bunch of tips:

  • Make sure the files you’re merging in are all in different locations than the existing files, otherwise there’ll be conflicts.
  • Image and other resource files that couldn’t be in asset catalogs as long as they were in a pod can now be put into the asset catalog of the main app (and should be).
  • Your pod source code file might use [NSBundle bundleForClass:[MyPodClass class]] to get the bundle to load a resource from. You should change that to the main bundle, [NSBundle mainBundle], where you can’t just replace it with nil, like in [UIStoryboard storyboardWithName:bundle:].
  • Check whether you’re loading the pod bundle explicitly for anything and change your code to use other mechanisms.

That’s it! Let me know if you have any questions.