EDIT: this is an old post and a lof the information in this document is outdated.
Git is a popular free and open source distributed version control system.
I am new to Unity, but as a long time git user, I wanted to use git for my Unity projects. The bottom line is... it doesn't work nearly as well as I would like it to.
This page contains all the information I could gather on the subject.
http://docs.unity3d.com/Manual/SmartMerge.html
Unity incorporates a tool called UnityYAMLMerge that can merge scene and prefab files in a semantically correct way. The tool can be accessed from the command line and is also available to third party version control software.
Unity 5 comes with a tool called UnityYAMLMerge that can automatically merge scenes and prefabs stored in YAML format. If there is a conflict, it will open a standard merge tool (such as KDiff3 or Meld) for a 3-way merge where BASE, LOCAL and REMOTE will all have been preprocessed.
Our objective is to configure git and Unity to take advantage of this tool.
You can configure UnityYAMLMerge to use a merge tool
of your choice. Edit the file mergespecfile.txt
(located in C:\Program Files\Unity\Editor\Data\Tools
) accordingly. For instance, to
enable KDiff3, change the following two lines...
unity use "%programs%/KDiff3/kdiff3.exe" "%b" "%l" "%r" -o "%d"
prefab use "%programs%/KDiff3/kdiff3.exe" "%b" "%l" "%r" -o "%d"
...and add the following line at the begining of the section 'Default fallbacks for unknown files'.
* use "%programs%/KDiff3/kdiff3.exe" "%b" "%l" "%r" -o "%d"
Edit the paths to match those on your environment.
http://docs.unity3d.com/Manual/class-EditorManager.html
To assist with version control merges, Unity can store scene files in a text-based format (see the text scene format pages for further details). If scene merges will not be performed then Unity can store scenes in a more space efficient binary format or allow both text and binary scene files to exist at the same time.
In order for the UnityYAMLMerge tool to work, assets must be stored in
YAML format. Go into Edit > Project Settings > Editor, set the Asset Serialization
option to Force Text
.
If you do this on a large existing codebase, it can take some time (and even crash).
Now that everything is setup on the Unity side, we need to tell git to use the proper merge tool.
Add the following to your .git/config:
[merge]
tool = unityyamlmerge
[mergetool "unityyamlmerge"]
trustExitCode = false
keepTemporaries = true
keepBackup = false
path = 'C:\\Program Files\\Unity\\Editor\\Data\\Tools\\UnityYAMLMerge.exe'
cmd = 'C:\\Program Files\\Unity\\Editor\\Data\\Tools\\UnityYAMLMerge.exe' merge -p "$BASE" "$REMOTE" "$LOCAL" "$MERGED"
Edit the paths to match those on your environment.
Next, we want to prevent git from trying to merge some types of file on its own by declaring them as binary files.
To do this, put the following in your .gitattributes:
*.unity binary
*.prefab binary
*.asset binary
The good news is that UnityYAMLMerge will do all the merging for us. The bad news if that the file will be treated as on opaque blob as if it was still in binary format.
We can use a simple textconv script to retain basic, human-readable diffs.
Add this to your .git/config:
[diff "unity"]
textconv='C:\\Users\\petit_v\\unityYamlTextConv.py'
Edit the paths to match those on your environment.
Then, edit your .gitattributes to read (note the added diff=unity
):
*.unity binary diff=unity
*.prefab binary diff=unity
*.asset binary diff=unity
An example of a textconv script, unityYamlTextConv.py
, could be:
#!/usr/bin/env python
import sys
import yaml
import pprint
if len(sys.argv) < 2:
sys.exit(-1)
def tag_unity3d_com_ctor(loader, tag_suffix, node):
values = loader.construct_mapping(node, deep=True)
if 'Prefab' in values:
if 'm_Modification' in values['Prefab']:
del values['Prefab']['m_Modification']
return values
class UnityParser(yaml.parser.Parser):
DEFAULT_TAGS = {u'!u!': u'tag:unity3d.com,2011'}
DEFAULT_TAGS.update(yaml.parser.Parser.DEFAULT_TAGS)
class UnityLoader(yaml.reader.Reader, yaml.scanner.Scanner, UnityParser, yaml.composer.Composer, yaml.constructor.Constructor, yaml.resolver.Resolver):
def __init__(self, stream):
yaml.reader.Reader.__init__(self, stream)
yaml.scanner.Scanner.__init__(self)
UnityParser.__init__(self)
yaml.composer.Composer.__init__(self)
yaml.constructor.Constructor.__init__(self)
yaml.resolver.Resolver.__init__(self)
UnityLoader.add_multi_constructor('tag:unity3d.com', tag_unity3d_com_ctor)
with open(sys.argv[1], 'r') as stream:
docs = yaml.load_all(stream, Loader=UnityLoader)
for doc in docs:
pprint.pprint(doc, width=120, indent=1, depth=6)
Now it's possible to see what changed inside of a prefab, asset or scene:
There are a lot of things that could be done to improve the output of the script to make it more meaningfull to us humans.
[rant]
You may have noticed that the script above will not display the m_Modification
field inside of a Prefab. Apparently, someone at Unity thought it was
a good idea to store a complete list of all the modifications to a prefab
inside of said prefab. Why?... I use a version constrol system whose sole
purpose is to keep track of file changes, I don't need or want any of this.
If you make a change to a Prefab in the Editor and then Ctrl-Z, you can get yourself into a situation like this one:
$ git status
On branch master
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git checkout -- <file>..." to discard changes in working directory)
modified: Assets/prefabs/Asteriod.prefab
no changes added to commit (use "git add" and/or "git commit -a")
$ git diff
$
This is because m_Modification
has been altered, even though the prefab
is still the same in every way.
At this point, you should git chekout
the file back, unless you want to
introduce needless non fast-forward merges in your history. In fact,
the UnityYAMLMerge tool seems to be ignoring this section entirely.
EDIT: It apprears this is a bug that will be fixed in Unity 5.4.
This is complete nonsense, this is madness, and it makes me sad.
[/rant]
Unfortunatly, PyYAML seems to choke on some Unity scenes. It's not celar to me weather PyYAML has a bug or if Unity is not following the spec.
yaml.scanner.ScannerError: mapping values are not allowed here
See:
- http://answers.unity3d.com/questions/1071634/error-unable-to-parse-yaml-file.html
- http://forum.unity3d.com/threads/scene-files-invalid-yaml.355653/
You may use the following .gitignore file. Up-to-date definitions can be found on github.
Thanks to all the contributors for putting these files together.
#
# == Windows ==
#
# Windows image file caches
Thumbs.db
ehthumbs.db
# Folder config file
Desktop.ini
# Recycle Bin used on file shares
$RECYCLE.BIN/
# Windows Installer files
*.cab
*.msi
*.msm
*.msp
# Windows shortcuts
*.lnk
#
# == Unity ==
#
/[Ll]ibrary/
/[Tt]emp/
/[Oo]bj/
/[Bb]uild/
/[Bb]uilds/
/Assets/AssetStoreTools*
# Autogenerated VS/MD solution and project files
ExportedObj/
*.csproj
*.unityproj
*.sln
*.suo
*.tmp
*.user
*.userprefs
*.pidb
*.booproj
*.svd
# Unity3D generated meta files
*.pidb.meta
# Unity3D Generated File On Crash Reports
sysinfo.txt
# Builds
*.apk
*.unitypackage
I can't believe how hard it is to diff and merge some types of files in Unity, even after doing all that. How do Unity developers review their changes before committing them? How do they handle even trivial merge conflicts?
Git + Unity is full of spiders. And snakes. And they're on fire.
Sources:
On Windows 10, I got this to work (with Meld):
mergespecfile.txt
.gitattributes
.git/config