
Система контроля версий git уже давно стала стандартом де-факто в мире разработки, но для большинства разработчиков на Unity не секрет, что существует ряд трудностей связанных с особенностями Unity, которые мешают эффективно использовать ее совместно с git.
Вот список типичных проблем:
- в репозиторий попадают ненужные файлы или наоборот не попадают нужные
- множество больших файлов раздувает размер репозитория
- проблема с мерджем yaml файлов Unity
- в репозиторий добавлен только сам файл или только meta
- в проекте присутствуют пустые папки
- сложность автоматической нумерации версий и билдов
- неудобство использования кода между несколькими проектами
О решение этих проблем, связанных с совместным использованием git и Unity, вы можете прочитать в моем цикле статей.
В этой статье будет описано решение первых трех проблем
Попробуем по шагам расписать методы решения каждой из проблем
Первое, что нам нужно будет сделать после ��оздания репозитория для вашего проекта, это настроить исключения, не буду очень подробно на этом останавливаться, вот хороший пример.
Единственное замечание, что скорее всего в конец стоит дописать пару исключений
!*.dll — так как, если вы будете использовать плагины или сторонние ассеты, то dll вам придется хранить в репозитории а в Windows git игнорирует dll по умолчанию;
!*.obj — если используете модели в этом формате, опять же в windows obj файлы могут по умолчанию игнорироваться
моя версия .gitignore
# This .gitignore file should be placed at the root of your Unity project directory
#
# Get latest from https://github.com/github/gitignore/blob/master/Unity.gitignore
#
/[Ll]ibrary/
/[Tt]emp/
/[Oo]bj/
/[Bb]uild/
/[Bb]uilds/
/[Ll]ogs/
/[Uu]ser[Ss]ettings/
# MemoryCaptures can get excessive in size.
# They also could contain extremely sensitive data
/[Mm]emoryCaptures/
# Asset meta data should only be ignored when the corresponding asset is also ignored
!/[Aa]ssets/**/*.meta
# Uncomment this line if you wish to ignore the asset store tools plugin
# /[Aa]ssets/AssetStoreTools*
# Autogenerated Jetbrains Rider plugin
/[Aa]ssets/Plugins/Editor/JetBrains*
# Visual Studio cache directory
.vs/
# Gradle cache directory
.gradle/
# Autogenerated VS/MD/Consulo solution and project files
ExportedObj/
.consulo/
*.csproj
*.unityproj
*.sln
*.suo
*.tmp
*.user
*.userprefs
*.pidb
*.booproj
*.svd
*.pdb
*.mdb
*.opendb
*.VC.db
# Unity3D generated meta files
*.pidb.meta
*.pdb.meta
*.mdb.meta
# Unity3D generated file on crash reports
sysinfo.txt
# Builds
*.apk
*.unitypackage
# Crashlytics generated file
crashlytics-build.properties
# Packed Addressables
/[Aa]ssets/[Aa]ddressable[Aa]ssets[Dd]ata/*/*.bin*
# Temporary auto-generated Android Assets
/[Aa]ssets/[Ss]treamingAssets/aa.meta
/[Aa]ssets/[Ss]treamingAssets/aa/*
# Exceptions
!*.dll
!*.obj
Вторым шагом мы попробуем решить проблему роста репозитория от больших файлов. Этим решением является LFS
подробнее про LFS
LFS — это надстройка над git, которая сохраняет в git репозиторий вместо бинарного файла его идентификатор, а сам файл кладет в специальное key-value хранилище.
Таким образом, в самом git репозитории хранится только LFS заглушка файла а сам файл после checkout скачивается из хранилища.
Неплохая статья правда на английском:
www.atlassian.com/git/tutorials/git-lfs
Таким образом, в самом git репозитории хранится только LFS заглушка файла а сам файл после checkout скачивается из хранилища.
Неплохая статья правда на английском:
www.atlassian.com/git/tutorials/git-lfs
Чтобы настроить lfs типы файлов для нашего репозитори добавим в файл
.gitattributes в корне проекта несколько строчек (возможно вам придется его создать, причем Windows может не дать создать файл с таким имеем в Explorer)## git-lfs ##
#Image
*.jpg filter=lfs diff=lfs merge=lfs -text
*.jpeg filter=lfs diff=lfs merge=lfs -text
*.png filter=lfs diff=lfs merge=lfs -text
*.gif filter=lfs diff=lfs merge=lfs -text
*.psd filter=lfs diff=lfs merge=lfs -text
*.ai filter=lfs diff=lfs merge=lfs -text
*.tif filter=lfs diff=lfs merge=lfs -text
*.tga filter=lfs diff=lfs merge=lfs -text
*.cubemap filter=lfs diff=lfs merge=lfs -text
*.svg filter=lfs diff=lfs merge=lfs -text
#Audio
*.mp3 filter=lfs diff=lfs merge=lfs -text
*.wav filter=lfs diff=lfs merge=lfs -text
*.ogg filter=lfs diff=lfs merge=lfs -text
#Video
*.mp4 filter=lfs diff=lfs merge=lfs -text
*.mov filter=lfs diff=lfs merge=lfs -text
*.webm filter=lfs diff=lfs merge=lfs -text
#3D Object
*.FBX 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
#ETC
*.a filter=lfs diff=lfs merge=lfs -text
*.exr filter=lfs diff=lfs merge=lfs -text
*.pdf filter=lfs diff=lfs merge=lfs -text
*.zip filter=lfs diff=lfs merge=lfs -text
*.dll filter=lfs diff=lfs merge=lfs -text
*.unitypackage filter=lfs diff=lfs merge=lfs -text
*.aif filter=lfs diff=lfs merge=lfs -text
*.ttf filter=lfs diff=lfs merge=lfs -text
*.rns filter=lfs diff=lfs merge=lfs -text
*.reason filter=lfs diff=lfs merge=lfs -text
*.lxo filter=lfs diff=lfs merge=lfs -text
здесь перечислены большинство файлов который являются бинарными и могут быть достаточно большого размера. немного поясню:
строки, начинающиеся с # — это комментарии;
filter=lfs diff=lfs merge=lfs — это волшебные слова, заставляющие git использовать lfs для этих типов файлов; -text означает что файл бинарный и мерджить его не надо.Если в вашем проекте используются еще какие-то большие бинарные файлы, допишите их сюда в дальнейшем поменять тип хранения файла (перенести в lfs или вынести оттуда) будет довольно сложно.
Следующим шагом попробуем немного немного улучшить ситуацию со сложными мерджами.
В составе Unity есть утилита UnityYAMLMerge, которая позволяет эффективно мерджить yaml файлы. Добавим в файл
.gitattributes еще несколько строчек:*.cs diff=csharp text
*.cginc text
*.shader text
*.mat merge=unityyamlmerge
*.anim merge=unityyamlmerge
*.unity merge=unityyamlmerge
*.prefab merge=unityyamlmerge
*.physicsMaterial2D merge=unityyamlmerge
*.physicsMaterial merge=unityyamlmerge
*.asset merge=unityyamlmerge
*.meta merge=unityyamlmerge
*.controller merge=unityyamlmerge
Поясню, что мы сделали:
для .cs файлов подсказали, что там будет текст являющийся C# кодом;
для файлов cginc и shader тоже выбрали текстовое представление
для большинства Unity yaml файлов выбрали кастомный драйвер слияния (custom merge driver) unityyamlmerge
Также необходимо его настроить: добавим следующий код в любой .gitconfig, проще всего в локальный, находящийся по пути .git/config от корня репозитория:
[merge "unityyamlmerge"]
name = Unity SmartMerge (UnityYamlMerge)
driver = \"{путь к папке с Unity}/Editor/Data/Tools/UnityYAMLMerge.exe\" merge -h -p --force --fallback none %O %B %A %A
recursive = binary
Флаг -p заставляет UnityYamlMerge менять содержимое файлов даже если полностью конфликт разрешить не удалось, и сильно упрощает его дальнейшее решение руками. Например при слияние двух веток где было изменена одна и та же сцена, при использовании стандартного механизма слияния git, мы увидим множество изменений. При использование кастомного драйвера даже если было изменено одно и тоже поле одного и того же компонента, в конфликте будет ровно 1 строчка.
Для удобства я создал небольшой скрипт, который позволит провести установку unityyamlmerge автоматически при первом открытие проекта версией Unity. Его можно положить в любое место внутри папки Assets (требует чтобы git был установлен в системе и был прописан в переменной PATH т.е. был доступен по команде git);
этот класс
Принцип работы: каждый раз, когда Unity запускается или перекомпилирует скрипты, мы проверяем совпадения ключа в EditorPrefs с нашим «актуальным» ключом, если нет(либо поменялась версия нашего скрипта, либо версия Unity, либо это первый запуск) мы через команды git дописываем в локальный gitconfig настройки драйвера.
#if UNITY_EDITOR
using UnityEngine;
using UnityEditor;
using System;
namespace GitIntegration
{
[InitializeOnLoad]
public class SmartMergeRegistrator
{
const string SmartMergeRegistratorEditorPrefsKey = "smart_merge_installed";
const int Version = 1;
static string VersionKey = $"{Version}_{Application.unityVersion}";
public static string ExecuteGitWithParams(string param)
{
var processInfo = new System.Diagnostics.ProcessStartInfo("git");
processInfo.UseShellExecute = false;
processInfo.WorkingDirectory = Environment.CurrentDirectory;
processInfo.RedirectStandardOutput = true;
processInfo.RedirectStandardError = true;
processInfo.CreateNoWindow = true;
var process = new System.Diagnostics.Process();
process.StartInfo = processInfo;
process.StartInfo.FileName = "git";
process.StartInfo.Arguments = param;
process.Start();
process.WaitForExit();
if (process.ExitCode != 0)
throw new Exception(process.StandardError.ReadLine());
return process.StandardOutput.ReadLine();
}
[MenuItem("Tools/Git/SmartMerge registration")]
static void SmartMergeRegister()
{
try
{
var UnityYAMLMergePath = EditorApplication.applicationContentsPath + "/Tools" + "/UnityYAMLMerge.exe";
ExecuteGitWithParams("config merge.unityyamlmerge.name \"Unity SmartMerge (UnityYamlMerge)\"");
ExecuteGitWithParams($"config merge.unityyamlmerge.driver \"\\\"{UnityYAMLMergePath}\\\" merge -h -p --force --fallback none %O %B %A %A\"");
ExecuteGitWithParams("config merge.unityyamlmerge.recursive binary");
EditorPrefs.SetString(SmartMergeRegistratorEditorPrefsKey, VersionKey);
Debug.Log($"Succesfuly registered UnityYAMLMerge with path {UnityYAMLMergePath}");
}
catch (Exception e)
{
Debug.Log($"Fail to register UnityYAMLMerge with error: {e}");
}
}
//Unity calls the static constructor when the engine opens
static SmartMergeRegistrator()
{
var instaledVersionKey = EditorPrefs.GetString(SmartMergeRegistratorEditorPrefsKey);
if (instaledVersionKey != VersionKey)
SmartMergeRegister();
}
}
}
#endif
Принцип работы: каждый раз, когда Unity запускается или перекомпилирует скрипты, мы проверяем совпадения ключа в EditorPrefs с нашим «актуальным» ключом, если нет(либо поменялась версия нашего скрипта, либо версия Unity, либо это первый запуск) мы через команды git дописываем в локальный gitconfig настройки драйвера.
финальная версия .gitattributes
## Unity ##
*.cs diff=csharp text
*.cginc text
*.shader text
*.mat merge=unityyamlmerge
*.anim merge=unityyamlmerge
*.unity merge=unityyamlmerge
*.prefab merge=unityyamlmerge
*.physicsMaterial2D merge=unityyamlmerge
*.physicsMaterial merge=unityyamlmerge
*.asset merge=unityyamlmerge
*.meta merge=unityyamlmerge
*.controller merge=unityyamlmerge
## git-lfs ##
#Image
*.jpg filter=lfs diff=lfs merge=lfs -text
*.jpeg filter=lfs diff=lfs merge=lfs -text
*.png filter=lfs diff=lfs merge=lfs -text
*.gif filter=lfs diff=lfs merge=lfs -text
*.psd filter=lfs diff=lfs merge=lfs -text
*.ai filter=lfs diff=lfs merge=lfs -text
*.tif filter=lfs diff=lfs merge=lfs -text
*.tga filter=lfs diff=lfs merge=lfs -text
*.cubemap filter=lfs diff=lfs merge=lfs -text
*.svg filter=lfs diff=lfs merge=lfs -text
#Audio
*.mp3 filter=lfs diff=lfs merge=lfs -text
*.wav filter=lfs diff=lfs merge=lfs -text
*.ogg filter=lfs diff=lfs merge=lfs -text
#Video
*.mp4 filter=lfs diff=lfs merge=lfs -text
*.mov filter=lfs diff=lfs merge=lfs -text
*.webm filter=lfs diff=lfs merge=lfs -text
#3D Object
*.FBX 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
#ETC
*.a filter=lfs diff=lfs merge=lfs -text
*.exr filter=lfs diff=lfs merge=lfs -text
*.pdf filter=lfs diff=lfs merge=lfs -text
*.zip filter=lfs diff=lfs merge=lfs -text
*.dll filter=lfs diff=lfs merge=lfs -text
*.unitypackage filter=lfs diff=lfs merge=lfs -text
*.aif filter=lfs diff=lfs merge=lfs -text
*.ttf filter=lfs diff=lfs merge=lfs -text
*.rns filter=lfs diff=lfs merge=lfs -text
*.reason filter=lfs diff=lfs merge=lfs -text
*.lxo filter=lfs diff=lfs merge=lfs -text
После выполнения этих шагов настоятельно рекомендую закомитить текущее состояние репозитория.
Готовый проект https://github.com/newnon/UnityGitHabr1
еще раз напомню для корректной работы, git должен быть уставлен в системе и доступен по команде git.
Если хотите поэкспериментировать
В репозитории на github есть бренчи с именами test1 test2 test3
Без установленного кастомного merge драйвера в test1 без конфликта не удастся вмерджить ни test2 ни test3
При установленном драйвере test2 вливается в test1 без конфликта а test3 с конфликтом в одну строчку в измененном m_LocalPosition
Eстановить удалить драйвер можно в любой момент через меню Unity Tools/Git/
Без установленного кастомного merge драйвера в test1 без конфликта не удастся вмерджить ни test2 ни test3
При установленном драйвере test2 вливается в test1 без конфликта а test3 с конфликтом в одну строчку в измененном m_LocalPosition
Eстановить удалить драйвер можно в любой момент через меню Unity Tools/Git/
