Git with Unity
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.
.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
-
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". ↩