This article is meant to be a definitive guide on how to properly version control your Unity projects!
Version control is one of the most important tools in any developer’s arsenal. It allows you to easily roll back changes if you accidentally break something, compare older and newer versions of your code to see what’s different, and it allows teams to work on the same code without accidentally overwriting each other’s work. Until recently, only Unity Pro projects were able to be properly version controlled. However, since Unity 3.5, it is now possible to version control projects in the free version of Unity as well.
What is Version Control?
A version control system, or VCS, keeps track of changes you've made to files in folder, also known as your "working copy". These changes are stored alongside the folder in a database known as a "repository". Each time you change something in your working copy, you need to commit those changes to the repository. The VCS then compares what's already in the repository to the incoming changes and only stores the differences. This keeps the repository as small as possible.
For this project, we’ll be using Mercurial which is a distributed VCS. Unlike centralized VCS systems like Subversion (SVN), which typically rely on a remote repository stored on a server, a distributed VCS can host local repositories, sometimes called "local branches", directly on your computer. This means you can make changes, commit them to your local branch, test them and even discard them if something breaks, all without having to subject your team members to your changes until you know they're perfect. Once you're sure, you can "push" those changes to a remote repository for your team to "pull" into their own local branches.
Preparing a Unity Project for Version Control
A Unity project requires certain meta information to remember what components and connections are set on the various assets. Traditionally, this meta information was stored as a set of binary files in the Library folder of a Unity project. However, this "binary blob" can’t be properly merged, so changes made by two people could stomp all over each other, causing things to break in the editor, even if they were editing different assets.
The solution to this is to turn on Meta Files in the project settings.
- Click Edit > Project Settings > Editor
- Under the Version Control heading, change the Mode to Meta Files
- Click File > Save
This will cause Unity to create meta files beside each asset in the project Assets folder. Now, when an asset is modified in the editor, only the asset and it's associated meta file will report changes, instead of the entire Library folder.
Both Windows and OSX have Mercurial installers. Visit the Mercurial website and click the big download button. The site will automatically recognize what operating system you’re using and download the appropriate installer.
Unzip the installer and run it. Click through all the steps and it should install itself without any intervention necessary.
To make sure Mercurial installed correctly, open a command line. On Windows, press Start and type cmd into the search box. On OSX, open Spotlight and search for terminal.
On the command line, enter:
A short list of information should appear including what version of Mercurial you’re using, as well as a list of common commands. To get the complete list of commands, enter:
And to get help on a specific command, simply enter the name of the command after help:
hg help clone
NOTE: Mercurial commands always begin with hg, the element for mercury on the periodic table. Thankfully, this bit of clever naming was used because it’s easy to type.
Initializing the Repository
The first thing we need to do is give our project folder a repository.
On the command line enter:
That's it. Our folder now has a repository and is ready for version control. If you have hidden files turned on you'll notice a .hg folder has been created in the project folder. This is where the repository resides. If, for some reason, you ever want to remove version control, simply delete the .hg folder. Your working copy of the files will be unharmed.
Checking the Status
Now that our project folder has a repository, let’s check the status of the repository to see what’s changed. Mercurial will accept shortened command names, so any of the following will work:
hg status hg stat hg st
A status report should come up with a list of files, each with a single letter beside them. In this case, all the files will have question marks beside them. The question mark indicates the file isn't being version controlled in the repository yet.
|?||A file that is in the working copy but not being tracked by the repository.|
|!||A file that is being tracked by the repository but is missing in the working copy.|
|A||A file that has been added to the repository since the last commit.|
|M||A file that has been modified since the last commit.|
|R||A file that is slated for removal from the repository on the next commit.|
Adding Files to the Repository
We need to explicitly add files to our repository in order to let Mercurial know we want them to be version controlled.
Individual files can be added:
hg add myfile.txt
Entire folders can be added:
hg add scripts/
Let’s add every single un-versioned file (with a ? question mark in the status report) in one fell swoop by not specifying any filename at all:
Perform a status check.
Any files that were added should now have a letter A beside them, indicating they’ve been added to the repository and are now being tracked by Mercurial.
Now that we’ve told Mercurial which files we want to be version controlled, we need to commit them to the repository. Each commit is like a snapshot known as a revision, that keeps track of the differences between the previous revisions and the current one.
There are two parts to any commit, a message and your username. The message is where you get to describe what you did, but keep it short and sweet. The username is just an identifier to let anyone else who might use the repository know who made certain changes. The username is set with the -u flag and the message is set with the -m flag:
hg commit -u ian -m "initial"
To make sure the commit was successful, we need to check the log:
To be doubly sure, check the status to make sure no changes are left.
An empty status report means everything was properly committed.
NOTE: It is recommended that you commit often. Each commit should be as "atomic" as possible. That is, it should be easily described by a simple phrase like "increased player jump height". If you find yourself needing to use "and" or commas in your commit messages, then it’s probably time to make two separate commits. The reason for this is to make it easy to rollback specific changes you've made when you encounter issues.
There are two key ways to fix mistakes: a rollback or a revert. A rollback will undo the last command you entered which is ideal for fixing spelling mistakes in your commit messages or over zealous adding.
Perform a status check to make sure everything got rolled back correctly.
A revert is more powerful, allowing you to travel back in time through several revisions, in case you need to back out of several changes you’ve made. In order to find out what revision you’ll want to revert to you first need to check the log:
Either the number or hash can be used to refer to a specific revision while reverting. The number is specific to your repository since someone else’s branch might have more changes so their numbers would be different. The hash is universal, this means anyone could revert to a specific revision on a shared repository by using that hash string.
To revert to a specific revision, we need to specify the revision number using the -r flag. We also need to tell Mercurial to "revert all" using the -a flag. Finally, we need to tell Mercurial that we don’t want it to create any backup files using the --no-backup flag.
hg revert -r 0 -a --no-backup
Perform a status check to make sure everything reverted correctly.
NOTE: If you omit the --no-backup flag, Mercurial will create backup files for any files affected by the revert procedure. These backup files will all have a .orig extension. Be sure not to add .orig files.
Ignoring Unwanted Files
Now that we've undone our mistake, let's make sure it doesn't happen again. We want to permanently ignore the Library and Temp folders so that it can’t be added by accidentally the next time we get trigger happy with the add command.
Mercurial uses a special file called .hgignore to store the names of files and folders you want to permanently ignore. This file goes right inside your project folder and is version controlled just like everything else. This ensures everyone using the repo can't accidentally pollute it with unwanted files.
Using your favourite text editor to enter the following:
syntax: glob Library Temp *.pidb *.sln *.userprefs *.csprog *.orig
This tells Mercurial what we want to use shell-style syntax (known as "glob" syntax) to ignore the Library and Temp folders, ignore several temporary files that MonoDevelop creates, and to ignore .orig files just in case we accidentally create them when reverting.
Save the file in the root of your project folder as .hgignore (noting the dot at the beginning). Then perform another status check:
The .hgignore file should now be listed in the status report, but the Library folder, Temp folder and other ignored files should not. So we can safely use the add command without needing to use exact file names and then commit the results.
hg add hg commit -u ian -m "Initial"
Check the log to make sure the commit was successful.
NOTE: More information about hgignore files can be found here.
Pushing Changes to a Remote Repository
If you want to share your repository with other developers, the easiest way is to create a remote repository on a server and push your changes to that.
The first thing you’ll need to do is find a Mercurial host. Several exist including BitBucket and Kiln; Both have free accounts for small teams. In our case, we’ll use BitBucket, but both services work essentially the same. Once you have signed up for an account on either service, be sure to create a new repository using their web interface.
Once created, look for the "clone path". It should look something like this:
hg clone https://bitbucket.org/username/reponame
Normally, this command would be used to create a local copy of the repository. But we already have a local repository and we want to send changes from it to the remote repository instead. To do this, we can take the URL address at the end of the clone string and use it as the destination for the push command.
hg push https://bitbucket.org/username/reponame
Mercurial will compare the local repository to the remote repository to see how they differ. It will see that our local repository has newer changes that the remote repository is missing and will send them to the remote repository.
However, it will first prompt for a username and a password. These should correspond to the username/email and password you signed up to the host service with.
Mercurial should report that all changes were successfully pushed and that means anyone who depends on your repository can now "pull" from it to receive your changes. First, they'll have to clone the repository to make a local copy.
hg clone https://bitbucket.org/username/reponame
And then pull any changes that you or anyone else make as time goes on:
You'll then have to do an "update" to make sure that your working copy is updated:
But to save time, you can do the pull and update all at once using the -u flag:
hg pull -u
NOTE: Mercurial will prompt you whenever a binary file is updated or merged. Unless you are certain you’ve made changes to local binary files, always choose the "(O)other" option instead of the "(L)ocal" option.
Settings to Make Life Easier
There are several tedious aspects to issuing the same commands over and over, such as having to remember to supply a username when committing, having to type in a password every time you push, etc. Many of these settings can be stored in what’s known as an hgrc file. To create an hgrc file, use your favourite text editor and enter the following:
[paths] default = https://bitbucket.org/username/reponame
This tells Mercurial what path to use by default for push and pull commands. Be sure to replace this fake address with the real address to your remote repository. Next, enter the following:
[ui] username = Firstname Lastname
This tells Mercurial what username to use by default when committing. Once again, replace this with your correct credentials and be sure the email address matches the one you signed up with. Finally, enter:
[auth] host.prefix = bitbucket.org host.username = username host.password = password host.schemes = http https
This tells Mercurial what credentials to use when pushing to a secure remote repository so you won’t have to enter a username and password every time you push or pull. Be sure to replace the prefix with the shortest possible portion of your host’s address and don’t include the
http:// protocol in the prefix either. Next, replace the username and password with the one you signed up to your host with. The schema tell Mercurial which protocols to attempt to connect with.
Finally, save the file as hgrc (with no dot or extension on the end) inside the .hg folder in your repository. You should no longer need to manually give usernames, passwords or paths when issuing commands from now on.
Version control may seem a little daunting to the uninitiated, but the effort is worthwhile. It gives you peace of mind to know that at any point you can roll a broken project back to a point where it was working before. Likewise, using remote repositories means that code can not only be shared with team members, but recovering your project after something catastrophic happens (like a hard drive failure) is easy.
To learn more about distributed version control and Mercurial, I highly recommend visiting Hg Init. It has a series of articles explaining version control practices and Mercurial features in depth. It’s brief, highly informative, easy to read, easy to understand and even a little funny.