You see, the word ‘forking’ sounds a little like ‘fucking’ so we laugh. Jokes. Ah. So. All this Git stuff is great and all, but after setting up one project to integrate really well with Git, adding submodules, branches, etc, you don’t really want to do it all over again for each project. Luckily, as with all problems in the first paragraph of these posts, Git is the answer. Bitbucket, Github, and other repo hosts offer a feature called ‘Forking’, which is really just copying a repo in a fancy way. It’s a fancy way because each copied repo (fork) maintains a connection to the original repo, so if you update the original you can sync the changes into each forked project (and vice versa). A little like a branch, although the repos themselves have no knowledge of the other forks.
Forks are intended to allow developers to work on projects they don’t have full access to. For example, a developer might want to add Mac support to some open source tool they find on Github. They’d fork the tool to their own repo, make the needed changes, and make a ‘pull request’ to the original developers, asking for their changes to be merged into the ‘official’ repo. And thus the ecosystem thrives.
Of course, none of this helps a game dev team where everyone can write to the project repo (unless you don’t trust your team). But you can still take the basic idea: a common code base that anyone can make changes to. By setting up a ‘Base Project’ with Git, and adding all the common tools you need, you just need to make a new fork each time you start a new game project.
You first need to set up the base project that any future projects will start as. Easy. Just start a new Unity project, and set it up for Git just like any other. Change project settings, add submodules and any assets (*hint* anything you’d include here might be well suited for a submodule) that you want in all your projects. Push everything to a remote repo host that provides forking , like Bitbucket or Github.
You won’t want to work with the base project repo directly, unless you want to update the base of all your projects (more on that later). Instead, you fork a new repo from the base project to get the start point of a new project. All this really does is copy the base project into a new repo, as well as maintain a link back to the base, so changes to the base can be pulled down into the new project, and vice-versa. After you’ve forked, you can clone the repo to be worked on locally (Examples show Bitbucket)
One last step, is to make a new branch in the forked project called ‘baseProject’ or something similar, and leave it alone for now (don’t commit to it). This will make it much easier to merge future changes back into the base project. Otherwise, it’s business as usual. Work on your project, commit and push as before. You’ve just managed to skip the boring setup stuff.
You’ll mostly want to leave the base project alone, but when you decide you want every new project to have orange sky by default (or something reasonable) then you can change it in the base and have it update. After committing and pushing changes to the base project, you’ll notice on each fork a dialogue showing that it’s “x changes behind baseProject” If you click the Sync option, bitbucket will attempt to merge your base changes into the fork.
If it works, congratulations, you’re done! It probably won’t though, since you’ve almost certainly modified some of the same files in your fork, and you’ll get a message like the following.
Not to worry, this just means that you need to manually go through the conflicts and resolve them. Unfortunately, you can’t do this online (at Bitbucket as of Dec ’13 anyway), so you’ll need to finish the merge locally. If you click on “View the conflicts” then click on Merge in the page that appears, you’ll get a customised instructions for how to do this via the command line. But if you’re using SourceTree, or you want to know how to do this without Bitbucket’s help, read on.
Resolve Sync Conflicts
Basically, we need to do the sync again, but this time locally. Luckily, a sync is just a merge by another name. First we need to add the base project’s repo to the fork’s local repo, if you haven’t done it already. In SourceTree, right click the sidebar of your fork’s repo, and click New Remote… In the window that pops up, click Add, and fill in the details of your base project’s remote. Make sure to name it something other than origin, and make sure Default remote isn’t checked, or it may cause issues with the actual forked remote.
After this, you just need to do a pull from the base project’s master into your own master. Make sure master is checked out, then click Pull at the top. Select your base project’s repo from the remote dropdown, and click Refresh to update the branches. Once it has, you can select master from the branch dropdown, and click OK.
SourceTree won’t commit the merge right away, since there are conflicts. Just resolve them as normal, commit and push, and you’ve successfully synced the fork. If you go back to your fork’s home page on Bitbucket, the sync notice should be gone.
Pushing Changes back To Base
Just like with submodules, you’ll often find that changes made in a fork should be added to the base project. You can always open up the base project, add the changes, and sync but, as always, there’s an easier way through the magic of Git. Did you take my advice about starting and ignoring a branch called baseProject in each fork? This is where it comes in handy. First, make sure any changes that you want to push back to base are in their own commit, if they aren’t, it’ll be easier to just re-add the changes in the base project directly. Otherwise, checkout baseProject and make sure it’s up to date with the base project’s master (pull from base-project/master). Then, we use a git feature called cherry pick to apply only the commit we want to push back to base onto baseProject. To do this, right-click on the commit with the changes and select cherry pick. After confirming, SourceTree will put any change from that on top of baseProject.
This might make it a little clearer. I had made changes in ‘OMG BUGFIX’ that I wanted to push back to base. After checking out baseProject, I cherry picked the ‘OMG BUGFIX’ commit. SourceTree applied it on top of baseProject, skipping the ‘fancy scene change’ commit in between.
Now you have a ‘clean’ branch, with no changes specific to the fork, ready to be applied back to the base project. To do the final sync back, you can either push baseProject to the base project’s remote master, or use Bitbucket’s pull request feature. If you do a pull request, make sure you’re requesting that the fork’s baseProject is merged into the base’s master (you don’t want to merge your fork’s master)
From there, you can sync the changes into other forks just like before. Also, just a note, Git cherry picking is technically breaking the Git branch structure (since the changes are copied and not linked), so you may run into issues later when you try to sync. Nothing should be too difficult to figure out though, and Git may in fact be able to cope automatically.
Go Forth, and Be Warned
And that about does it for this less than comprehensive guide to using Git with Unity. I hope this has helped make source control less intimidating, and even showed some uses beyond a backup solution. Messing with Git is the only way to get truly comfortable with it, so go ahead and try stuff out. My single biggest hurdle was learning when to trust Git (and when to not trust it). Once you’re comfortable with that, you’ll feel much better about experimenting with it. That said, the the goal of any source control is only ever to provide some quiet help to a project, and it should ideally be as invisible to as many people as possible. If you find yourself stressing about fixing some issue the ‘Git’ way, when it can be easily solved manually, it’s almost always better to just do the fix and moving on. Programmers are prone to over-optimising, and source control is no exception. If you’re spending more time fiddling with Git than it’s saving, you should rethink a couple things. Just make some awesome stuff!