Git with Unity

March 22, 2018

This is a follow-up to my post earlier this week introducing the correct .gitattributes settings for line endings; it dives a little bit more deeply into some of the configuration that you might be interested in if you're getting started building games in Unity.

Around the VSTS team, we've got a lot of gamers. A couple of people on the team used to develop commercial games, or even Xbox itself. Some people build games in their spare time, as hobby projects. And of course some don't want to develop games, but still love to play them.

And me? I'm actually none of those. I get excited every time there's a new Mario platformer, but otherwise, I don't really play games. But I wanted to try my hand at hacking on them, so I decided to grab Unity and give it a spin.

So far I haven't actually built anything useful, but I did start to understand how Unity fits in with Git.

.gitignore

The .gitignore file is a metadata file that controls how Git operates on your repository. Files listed in .gitignore will be — like the name implies — ignored. They won't show up in git status and they won't be added to the repository.

It's important to make sure that you .gitignore your build output directories, any cache data and temporary files or directories that your tools make. And Unity actually has a lot of these, including Build directories and a cache directory, Library.

Thankfully, you don't have to know what all these directories are. You can instead go to the gitignore repository, which contains crowd-sourced best practices that you can just drop into your repository. There's a .gitignore file customized for each type of project you might encounter. So you can just grab the Unity .gitignore, drop it into your repository, and go.

Easier still: if you use a Git hosting provider like GitHub or Visual Studio Team Services, you can select one of these .gitignore files when you create the repository. We have the same crowd-sourced .gitignore files ready to go, so that when you create a repository they're there for you to get started with.

Unity .gitignore in VSTS

.gitattributes

The other metadata file that Git uses is called .gitattributes. You might be familiar with .gitattributes because you use it to control how Git handles line endings for your file.

You do use .gitattributes to configure your line endings, don't you?

But .gitattributes is more than just line endings - you can also configure how files are merged when two people change the same file in two different branches.

This means that you can set up Unity's "Smart Merge" functionality. By default, Git is totally unaware of the type of content that you're checking in. If a file is changed in two different branches, it will try to merge the file just by looking at the lines, without understanding them.

But Unity includes a semantic merge tool that understands the actual contents of the scene files, so it can help deal with merging them. You just need to configure .gitattributes to use it.

You can add these lines to your .gitattributes:

*.anim merge=unityyamlmerge eol=lf
*.asset merge=unityyamlmerge eol=lf
*.controller merge=unityyamlmerge eol=lf
*.mat merge=unityyamlmerge eol=lf
*.meta merge=unityyamlmerge eol=lf
*.physicsMaterial merge=unityyamlmerge eol=lf
*.physicsMaterial2D merge=unityyamlmerge eol=lf
*.prefab merge=unityyamlmerge eol=lf
*.unity merge=unityyamlmerge eol=lf

Git LFS

One of the great things about Git is that it's a distributed version control system. That means that you get an entire copy of the repository from the server. That means not just all of the files in the current version of the branch that you're interested in, but all the branches, and all the history that you've ever checked in.

This means that you can work completely disconnected from your server: you can run git log or git blame to analyze the changes that have been made, even if you're on an airplane1.

But it's problematic when you're checking in large files. If you have assets like images, audio or movies, Git starts to choke. And it's not even the size of the assets themselves as much as the history that's problematic.

If you have a 100K PNG, then that's not so bad. The problem is that you've changed that 100K ping a dozen times. Now you've got 1.2 MB in history that you have to download every time you run git clone. And that's just one file. So it adds up very quicky.

Git LFS helps here: it's the Large File Storage extension to Git.

Instead of storing every copy of these assets in the repository directly, Git LFS stores this data in a separate location, the large file storage area. In the repository, it just checks in a little stub file, the "git-lfs pointer file", that lets Git LFS know where it can get the data when it needs it.

So when you clone the repository, you don't download all those assets, just the tiny (128 byte) git-lfs pointer files. When git needs the files, to write them to your working directory, Git LFS will download them from the server and put them on disk. It's a nice hybrid system between a totally distributed version control system, and a centralized system.

You can download git-lfs - or, if you use Git for Windows, it's already included. It's easy to set up, you just add some more lines to your .gitattributes file to make sure that your textures and artwork are handled by LFS:

*.jpg filter=lfs diff=lfs merge=lfs -text
*.gif filter=lfs diff=lfs merge=lfs -text
*.png filter=lfs diff=lfs merge=lfs -text

*.wav filter=lfs diff=lfs merge=lfs -text
*.ogg filter=lfs diff=lfs merge=lfs -text
*.mp3 filter=lfs diff=lfs merge=lfs -text

*.mp4 filter=lfs diff=lfs merge=lfs -text
*.mov filter=lfs diff=lfs merge=lfs -text

*.fbx filter=lfs diff=lfs merge=lfs -text
*.blend filter=lfs diff=lfs merge=lfs -text
*.obj filter=lfs diff=lfs merge=lfs -text

Locking

Git LFS 2.0 introduces the ability to put advisory locks on files. This is critical if you're working with multiple artists. Otherwise, two people might start working on the same image. When they go to merge their branches, they'll realize that they've both done this work, and they have a merge conflict.

Unfortunately, there's no "smart merge" for images. They'll have to figure out how to resolve this manually - probably losing one of the other's work.

The new locking functionality does require additional support on the server. Both GitHub and Visual Studio Team Services offer locking, so if you're using one of those services, you can just run:

git lfs lock file.png

to lock a file. When you've finished editing, and want to unlock it, you can run:

git lfs unlock file.png

I'm excited to continue playing with Unity for building games. But to be completely honest, I'm even more excited to use a totally new tool with Git. There's a lot of new functionality here, and I'm looking forward to learning how the Git community can help make Unity developers even more productive with version control.

Footnotes

  1. I'm old enough to remember when airplanes didn't have Wifi. Back in those bad old days, the version control nerds used to talk about "working on an airplane" meaning "working without being able to talk to your version control server".