In the last post, I introduced the concept of Source Control and introduced several options available to you. In this post I want to focus on just one option and show some sample workflow of it’s use with Unity. We will create a new Unity project and configure it for versioning, initialize a local repository, make some commits, branches, and merges, as well as discuss some patterns of source control you should consider using.
Intro To Git
The source control option I prefer is called “Git” which is free (a large reason I like it) but also very powerful and popular among programmers. As long as you work on relatively small projects (like most mobile games) you shouldn’t run into too many problems even if you version your binary files alongside your code. To help, you could consider keeping your .psd’s separated from your project and only include final flattened .png’s or .jpg’s in the project.
Git doesn’t require a server to use- I have many projects which only exist on my local machine. However, there are plenty of hosts out there that are free to use (with limitations of course) and enable access to your project remotely as well as serving as a backup in the event of data loss through hardware failure (this has also happened to me).
Atlasssian provides a host of options to the Git user. Bitbucket is a website which will host your Git projects for free – even with unlimited repos. You only have to pay when you start needing to work in large teams. Atlasssian also provides a nice visual client for Git called SourceTree. Naturally these two services work very well together since they are developed by the same company. They also have a host of tutorials to introduce you to Git.
Initial project setup
For the purposes of this demo, I will assume that you have already installed SourceTree (I am using version Version 18.104.22.168 at the time of this writing). Create a new project in Unity, I called mine “SourceControlDemo”. From the file menu choose “Edit->Project Settings->Editor”. I like the Version Control Mode set to “Hidden Meta Files” and the Asset Serialization Mode set to “Force Text”. I use this because Text has more options for merging than binary. Save your project.
Using a Finder window (on mac) – or the equivalent file-browsing window of your OS, navigate so that you can see the root folder containing your project. Also, open SourceTree and you should see the “Bookmarks” window. Now drag and drop your Unity project folder from the finder window into SourceTree’s Bookmarks window. If the project had already been set up with Git it will simply add the bookmark. Otherwise, you will see a dialog window for creating a new repository in this folder. You can specify Git or Mercurial, but we will leave it as Git. It will also show the path, and a name to refer to your Bookmark which defaults to the name of the folder. All of the default settings are ok, so just click the Create button.
Double click your newly created bookmark to access the Source Control view of your project. Even though all we have done is create a new project, you will see that there are a numerous files listed as “Pending” or “Unstaged” – 68 in my case. This indicates that Git has detected changes / additions, etc which might need to be tracked. Everything in the Library folder can be recreated by Unity at any time. Likewise, Unity creates a Temp folder while your project is open, and this folder and its contents do not need to be version controlled or shared. Initially you only need to track the Asset folder and Project Settings folder and their contents.
Let’s tell SourceTree to “ignore” the folders we don’t need. From the file menu choose “Actions->Ignore…”. In the dialog that appears, select the radio option to “Ignore everything beneath:” and make sure that “Library” appears in the drop down. I will mark the ignore setting as “This repository only” and then click “OK”. Repeat the same steps to now ignore the “Temp” folder. If you don’t see that as an option, you can choose the option “Ignore custom pattern” and enter “Temp” in the text field above. It may take the program a minute to Refresh but in a moment you should see the number of untracked files reduce – down to 15 for me.
A hidden file called “.gitignore” will have been created and placed in the root of your project. It is just a normal text file that you can edit with TextEdit (on mac) or NotePad (on pc) for example. You can also open and edit this file through SourceTree’s UI. In the upper right of the project window is a button with a gear icon called “Settings” – click this button. In the dialog that appears choose the Advanced tab and at the top of the window will be a reference to the repository-specific ignore list. You can click the “Edit” button to view and or edit the contents of your ignore list.
You also have the option to specify ignore at a global level. For example, you may decide you ALWAYS want to ignore the Library and Temp folders. I choose not to because I may work on a non-Unity project someday that wants to use folders with those names. Either way, you can view and or edit your global ignore list as well. From the file menu choose “SourceTree->Preferences”. Select the “Git” tab from at the top of the dialog which appears. You should see a button called “Edit File” toward the top of this window and to the right of “Global Ignore List:”. Click that button and the “.gitignore_global” file should open in a text editor. You can add, modify, or remove entries from the ignore list directly here.
In the upper left of the SourceTree window you will see three buttons as a segmented button. On the left is a check mark which shows you the current status of pending changes in your project. The middle button looks like a clock and shows the Log of changes over time and the last button looks like a magnifying lens and allows you to search your commits. Make sure that the Check mark button (File Status) is selected.
Click the box at the top of your pending files area to select everything. You should see your files move from the Un-staged group to the Staged group – which means that those changes will be recorded in the next Commit. A text area is placed toward the bottom where you should type in a helpful message that indicates what this commit includes, removes and or modifies. Good commit messages are like good code comments – not “necessary” but certainly appreciated. My commit message is simple, “Initial project creation.” Finally click the “Commit” button at the bottom beneath your comment. If everything went smoothly, all of the files will disappear because there are no longer any “changes” detected by Git.
Note that at this point, all of your changes were only recorded locally. In order to share your changes on a remote repository you need to do a “Push” (you will see the button along the top of the window). Occasionally you won’t be allowed to push because your local copy is out of sync. In these cases you usually just need to click “Pull” first and then try again. Naturally, pushing changes to a remote server requires that you have actually created and configured one. See Bitbucket for more.
Click over to the Log mode (by clicking the button that looks like a clock) and you will see one entry in your commit history. Under the description you will see a tag which says “master” indicating which “branch” the commit was made on as well as the message you had typed for your commit. You will also see various other potentially helpful tidbits like a hash uniquely identifying the commit, the user who committed the changes and the date, etc. With that entry selected you can also see what files were added, removed, and or modified in the area beneath it.
Branches can be thought of as separate courses of history in your project. They make it easy to know which point in history held certain features, can keep risky or in-progress features separate from stable ones, and aid in organization. Therefore, smart users of Git will utilize more than just the “master” branch. I would recommend reading this article to understand a good workflow, but if I were to briefly summarize, you could utilize a setup according to the following pattern:
- master: this branch should only hold commits from the initial project creation and then actual “releases” of your project
- develop: a branch from master where you are actively developing features for the next release. The only commits this branch holds should be “stable” i.e. complete features which are able to compile etc.
- feature(named according to the feature): is a temporary branch from develop which holds features that are incomplete and might keep the project from compiling and or being playable. You will create a lot of these branches. They will be merged back into develop when completed.
- release: a branch off of develop where you believe a project is feature complete enough to be submitted to the store. You can simultaneously be adding “next version” features for your app on the develop branch without sacrificing the ability to do a QA pass on the project as it is at this point in time, including being able to address bugs specifically for this branch. When the QA pass is sufficient and the project is distributed to the store, you can merge the branch back into master as well as develop.
- hotfix: a branch off of master to specifically address a bug on a released store build. When completed you may make another release which should be merged to master as well as develop.
Let’s begin implementing this pattern. In the top of the SourceTree window you should see a button called “Branch”. Click that and you will see a new dialog appear. It will show the “Current Branch” label which is the base of the branch you will be extending from, and a “New Branch” text field where you should enter the name of your new branch. Enter “develop” and then click the “Create Branch” button. The branch will be created and automatically “checked-out” which means that it becomes the currently active branch. On the left side of the window under “Branches” you will see both “develop” and “master” listed, but the develop entry will be bold indicating it is current.
Click the “Branch” button again and we will create our first feature branch called “MainMenu”. Within this branch I will create a sample scene which could handle things like showing the title of the game and having buttons such as “Play”, “Options”, or “High Scores”.
Our first feature branch
Now that we have our MainMenu branch created and active, open or switch back to Unity. Create a new folder in the project pane called “Scenes”. Then create a new Scene and save it as “MainMenu” inside the Scenes folder.
Create a panel (from the file menu choose “GameObject->UI->Panel“) and add three buttons as its children (from the file menu choose “GameObject->UI->Button”). You can manually array them, or use a layout group component on the panel to automatically position them – it doesn’t matter for now. Save your scene and project then head back over to SourceTree.
Add all of the pending files to our commit as we did before, provide a nice commit message such as “Main Menu Scene created” and click the commit button. Switch over to the log area and you can see that the marker for the MainMenu branch is now above and separated from the master and develop branch markers, although the commit tree is perfectly straight because MainMenu has everything in its history that master and develop have.
A simultaneous feature branch
In source tree, double click the develop branch to make it the active branch. When you go back to Unity you will notice that your Scenes folder and its contents have been removed. This is because at that point in develop’s history, our project didn’t have it yet. Don’t worry, the changes aren’t lost, you can get them back just by switching branches again.
Create a new branch off of develop called “Game”. In Unity, recreate the “Scenes” folder and add a new scene called “Game”. For now just use your imagination and pretend we did a lot of cool stuff. Save your scene and project and head back to SourceTree. Stage and commit your changes as we have been doing, then switch to the Log view. Now we actually see a tree of commit history forming. The different “branches” feel a little less of an abstract idea because you can see them split. Work can continue independently on each branch, pull changes from other branches if necessary, and be merged back into the develop branch when they are done.
Let’s imagine that each of our features are considered “complete” and are ready to be merged back into the develop branch. You can do this by double clicking the develop branch (in the left side panel under “BRANCHES”) to make it active, and then choosing the Merge button at the top of the screen. In the dialog that appears, select the commit marked by the “Game” branch and then click “OK”. Click the Merge button a second time and choose the commit marked by the “MainMenu” branch and click “OK”.
At this point we get a “Merge Conflict” caused by the fact that we created a Scenes folder in each of the feature branches. The problem isn’t actually the Scenes folder itself, but a unique tracking file created by Unity called a “.meta” file within each branch. Swap back to the “File Status” mode (the check mark button) and you will see an Assets/Scenes.meta file with an exclamation mark next to it. Right click the file and choose Resolve->Using Mine. Commit your changes.
Note that we could have avoided this conflict by creating the folder structure initially and then having all feature branches created from that point. Other conflicts might appear for other reasons such as overlapping code modifications which need to be merged by hand.
At this point we may decide we no longer need the branches used to create the new features since they were completed. You can right click on the branches in the left hand side of the screen under “BRANCHES” and choose “Delete [Branch name]”. A confirmation dialog will appear – just hit OK. Go ahead and delete the “Game” and “MainMenu” branch.
In this post we ventured into Git with SourceTree, and showed how to make it work with a new Unity project. We made commits, created branches, merged branches (fixing a conflict along the way), and finally deleted branches. Still, this is yet another huge topic and we have barely scratched the surface.