
Система контроля версий 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/
