Using ReactiveCocoa as a subtree22 Feb 2014
ReactiveCocoa (RAC) is a Cocoa framework that simplifies processing streams of values using the Functional Reactive Programming (FRP) paradigm. It was developed by GitHub and is a loose port of .NET Reactive Extensions. Since discovering FRP I've been using it whenever I can. I think it is revolutionary, and I'm very glad RAC allows me to use FRP concepts in Cocoa.
The official way of using RAC in a project is to clone its GitHub repository, including submodules. RAC uses several external frameworks: Expecta for matching, Specta for TDD/BDD, and xcconfigs for common Xcode configurations.
Submodules vs. subtrees
Git submodules are quite cumbersome to use. My biggest problems with submodules are:
- when you commit to a submodule, you also need to commit a corresponding submodule change to the main repository;
- if you lose a submodule change, you won't be able to clone the main repository;
- to make even a small change to an upstream project you have to fork it, othewise you won't be able to commit your changes (the changes will exist as a local commit which the main repository refers to, but you won't be able to commit them to the upstream repository if it is read-only, and therefore your main repository will become un-cloneable);
- my biggest problem yet: main repository depends on other repositories, which means that if one of the repositories used as a submodule disappears, you're screwed.
Luckily, the new Git subtree feature is now available. Subtrees are submodules done right. They have several advantages over submodules:
- all project files are in one place: the main repository has everything necessary, as subtree files are copied to it;
- it is easy to update a subtree project, as Git can filter commits made to the subtree and isolate them for pushing upstream;
- no need to clone a third-party repository in order to make small modifications to it so that it works with your project;
- no metadata is added to the project (as is the case with
Using RAC as a subtree
Assuming your repository is
foo, add RAC subtree:
$ cd foo $ git subtree add --squash --prefix vendor/rac git://github.com/ReactiveCocoa/ReactiveCocoa.git master
--prefix vendor/racspecifies project subdirectory where RAC will be stored;
masterspecifies which branch of the upstream repository to clone as subtree;
--squashtells Git to squash all upstream commits into a single commit so that your repository's history is not polluted with upstream history.
After cloning RAC you would normally initialise its submodules to install the externals. This time, however, we will not do that. Instead, we'll clone dependencies as subtrees of our project and link to target directories in RAC directory tree. This way if any other project needs them, we can point it to the existing subtrees.
$ git subtree add --squash --prefix vendor/expecta git://github.com/github/expecta.git master $ git subtree add --squash --prefix vendor/specta git://github.com/github/specta.git master $ git subtree add --squash --prefix vendor/xcconfigs git://github.com/jspahrsummers/xcconfigs.git master
Let's replace the empty submodule directories in the cloned RAC subtree with links to our new subtrees:
$ cd vendor/rac/external $ rm -r specta $ ln -s ../../specta $ rm -r expecta $ ln -s ../../expecta $ rm -r xcconfigs $ ln -s ../../xcconfigs
Now RAC thinks everything is in place, and you don't have to use submodules anymore.
To get the latest commits from RAC repository into your subtree, execute the following command:
$ git subtree pull --squash --prefix vendor/expecta git://github.com/github/expecta.git master $ git subtree pull --squash --prefix vendor/specta git://github.com/github/specta.git master $ git subtree pull --squash --prefix vendor/xcconfigs git://github.com/jspahrsummers/xcconfigs.git master $ git subtree pull --squash --prefix vendor/rac git://github.com/ReactiveCocoa/ReactiveCocoa.git master