Git Subtree
Краткое введение в Git subtree: преимущества, недостатки и отличия от submodule.
Как упоминалось на предыдущей странице, Git Submodule полезен в конкретных случаях. Для отслеживания программных зависимостей внутри единого репозитория многие разработчики предпочитают Git Subtree.
На этой странице объясняется, что такое Git subtree, когда его использовать вместо submodule, а также как добавлять, обновлять и передавать изменения обратно в subtree. Здесь также рассматривается перебазирование репозитория, содержащего subtree, и наиболее часто используемые параметры команды.
Что такое Git Subtree
Git Subtree — это альтернатива Git Submodule. Он позволяет вложить один репозиторий в другой в виде подкаталога, сохраняя при этом историю встроенного («дочернего») проекта. Это один из способов отслеживания истории программных зависимостей.
Ключевое отличие от submodule состоит в том, что subtree — это просто каталог. В отличие от submodule, subtree не требует файла .gitmodules или специальных gitlink-ссылок в вашем репозитории. Файлы хранятся непосредственно в рабочем дереве, фиксируются вместе с остальными данными и автоматически переносятся при каждом clone, branch и merge. Любой, кто клонирует ваш репозиторий, получает код зависимости без выполнения каких-либо дополнительных команд.
Под капотом git subtree — это фарфоровая команда (обёртка) над стандартными сантехническими командами, такими как git merge, git read-tree и git filter-branch/split. Вам не нужно изучать новый формат хранения — только несколько новых команд.
При использовании submodule клонирование даёт вам пустой каталог до запуска git submodule update --init. При использовании subtree файлы уже присутствуют. Именно это единственное отличие определяет большинство преимуществ и недостатков, описанных ниже.
Subtree и Submodule
Оба подхода позволяют встроить один репозиторий в другой, но представляют собой противоположные компромиссы. Выбор зависит от того, кто использует репозиторий и как часто код передаётся обратно в исходный проект.
| Аспект | Git Subtree | Git Submodule |
|---|---|---|
Файлы после clone | Уже присутствуют | Пустые до submodule update --init |
| Дополнительные метаданные | Отсутствуют | .gitmodules + gitlink-ссылки |
| Фиксация точного коммита в источнике | Нет (история сливается) | Да (записывается SHA) |
| Передача изменений в исходный проект | Вручную (git subtree split + push) | Естественный (коммит внутри submodule) |
| Размер репозитория | Больше (история подпроекта копируется) | Меньше (только указатель) |
| Порог вхождения для потребителей | Отсутствует | Необходимо изучить команды submodule |
Простое правило: предпочитайте subtree, когда вы преимущественно используете зависимость и хотите, чтобы каждый клон работал сразу; предпочитайте submodule, когда подпроект активно разрабатывается совместно и вам нужно фиксировать или отправлять изменения на точный коммит в исходном репозитории.
Зачем использовать Git Subtree
Преимущества
- Поддерживается в Git 1.7.10 и более поздних версиях (команда
subtreeвходит в состав самого Git). - Простой рабочий процесс для тех, кто клонирует ваш репозиторий — никаких дополнительных команд для изучения.
- Код подпроекта присутствует сразу после клонирования основного проекта.
- Не добавляет новых файлов метаданных (например,
.gitmodules). - Позволяет изменять зависимость на месте без отдельной рабочей копии репозитория.
Недостатки
- Требует изучения новой стратегии слияния и нескольких команд, специфичных для
subtree. - Передача кода в исходный проект — многоэтапный процесс (
git subtree split, затем push). - Код основного проекта и подпроекта смешиваются в одном репозитории, что увеличивает его размер и может загромождать историю.
Как добавить Subtree
Предположим, существует внешний проект, и вы хотите добавить его в свой репозиторий в определённый каталог.
Например, чтобы добавить расширение Vim в репозиторий, хранящий вашу конфигурацию Vim, выполните:
git subtree add --prefix .vim/bundle/example https://github.com/Example/vim-example.git master --squashСоставные части этой команды:
--prefix .vim/bundle/example— каталог, в котором будет находиться подпроект. Этот параметр является обязательным для каждой командыsubtree.https://github.com/Example/vim-example.git— исходный репозиторий (URL или, в дальнейшем, именованный remote).master— ветка (или коммит/тег), из которой производится извлечение.--squash— объединяет полную историю подпроекта в один коммит, чтобы она не засоряла ваш журнал. Опустите этот параметр, если хотите, чтобы полная история исходного проекта была влита.
С параметром --squash Git записывает SHA-1 ветки master на тот момент для дальнейшего использования и создаёт два коммита — сжатый импорт и слияние:
commit 6d7054b3acea64e2e31f4d6fb2e3be12e5865e87
Merge: 87fa91e ef86deb
Author: Ann Smith<[email protected]m>
Date: Tue Jun 10 13:37:03 2016 +0200
Merge commit 'fe67ddf158faccff4082d78a25c45d8cd93e8ba8' as '.vim/bundle/example'
commit fe67ddf158faccff4082d78a25c45d8cd93e8ba8
Author: Ann Smith<[email protected]m>
Date: Tue May 12 13:37:03 2015 +0200
Squashed '.vim/bundle/example/' content from commit b999b09
git-subtree-dir: .vim/bundle/example
git-subtree-split: b999b09cd9d69f359fa5668e81b09dcfde455ccaОбновление Subtree
Чтобы обновить подпапку до последней версии дочернего репозитория, выполните subtree pull с тем же префиксом и источником:
git subtree pull --prefix .vim/bundle/example https://github.com/Example/vim-example.git master --squashЭто извлекает исходную ветку и сливает её в каталог вашего subtree, создавая новый коммит слияния. Всегда используйте тот же --prefix и тот же выбор --squash/без---squash, что и при subtree add, иначе Git не сможет правильно согласовать истории.
Обратите внимание, что git subtree хранит идентификаторы коммитов подпроекта в метаданных сообщения коммита, а не в символических ссылках. Чтобы найти имя ветки или тега, связанное с сохранённым коммитом, выполните запрос к remote:
git ls-remote https://github.com/Example/vim-example.git | grep <commit-sha>Замените <commit-sha> фактическим хэшем коммита из строки git-subtree-split: вашего коммита импорта.
Перебазирование после Git Subtree
Чтобы перебазировать репозиторий, содержащий subtree, используйте интерактивный режим git rebase:
git rebase --interactive HEAD~5В редакторе можно удалить или сжать коммиты слияния subtree, затем сохранить и выполнить:
git rebase --continueПоскольку перебазирование перезаписывает историю, коммиты слияния subtree могут быть удалены или изменены. После такой перезаписи обычно необходимо заново установить subtree, повторно выполнив git subtree add или git subtree pull. Имейте в виду, что изменённая структура коммитов может также вызвать конфликты слияния во время перебазирования, поэтому предпочтительно перебазировать ветки, которые не были отправлены и опубликованы.
Распространённые параметры
| Параметр | Описание |
|---|---|
-q, --quiet | Подавляет ненужные сообщения о результатах на stderr. |
-d, --debug | Выводит дополнительные отладочные сообщения на stderr. |
-P <prefix>, --prefix=<prefix> | Определяет путь в репозитории к subtree, которым вы хотите управлять. Обязателен для всех команд. |
-m <message>, --message=<message> | Задаёт <message> в качестве сообщения коммита для коммита слияния. Действителен для add, merge и pull. |
--squash | Импортирует подпроект в виде одного коммита вместо слияния его полной истории. |
Использование Git Subtree без отслеживания Remote
Добавьте git subtree в указанную папку с префиксом. Используйте флаг --squash для сохранения всей истории подпроекта в вашем основном репозитории:
git subtree add --prefix .vim/bundle/vim-double-upon https://hostname.org/example/vim-plugins.git master --squashКоманда выполняет извлечение и сжимает историю. Вывод обычно показывает прогресс загрузки, за которым следует подтверждение добавления:
git fetch https://hostname.org/example/vim-plugins.git master
warning: no common commits
remote: Counting objects: 325, done.
remote: Compressing objects: 100% (145/145), done.
remote: Total 325 (delta 101), reused 313 (delta 89)
Receiving objects: 100% (325/325), 61.47 KiB, done.
Resolving deltas: 100% (110/110), done.
From https://hostname.org/vim-plugins.git
* branch master -> FETCH_HEAD
Added dir '.vim/bundle/vim-double-upon'Это создаёт коммит слияния, сжимая всю историю подпроекта в один коммит:
3bca0ad [4 minutes ago] (HEAD, stree) Merge commit 'fa2f5dc4f1b94356bca8a440c786a94f75dc0a45' as '.vim/bundle/vim-double-upon' [John Brown]
fa2f5dc [4 minutes ago] Squashed '.vim/bundle/vim-double-upon/' content from commit 13189ec [John Brown]Для обновления кода плагина из исходного репозитория выполните git subtree pull:
git subtree pull --prefix .vim/bundle/vim-double-upon https://hostname.org/example/vim-plugins.git master --squashПередача изменений обратно в исходный проект
Поскольку код подпроекта смешан с вашим репозиторием, вы не можете просто отправить его обратно. Сначала необходимо извлечь изменения, затронувшие каталог subtree, в отдельную ветку с помощью git subtree split:
git subtree split --prefix .vim/bundle/vim-double-upon -b split-branchЭто перезаписывает только коммиты, затрагивающие .vim/bundle/vim-double-upon, в новую ветку (split-branch), пути в которой относятся к корню подпроекта. Затем можно отправить эту ветку в исходный репозиторий:
git push https://hostname.org/example/vim-plugins.git split-branch:masterЭто однонаправленное извлечение объясняет, почему «передача изменений в исходный проект» указана выше как недостаток — встроенной двунаправленной синхронизации нет.
Чтобы сократить ежедневные команды add/pull/push, добавьте подпроект как remote.
Добавление подпроекта как Remote
Регистрация источника как именованного remote сокращает каждую последующую команду — вы используете vim-double-upon вместо полного URL. Флаг -f немедленно выполняет загрузку:
git remote add -f vim-double-upon https://hostname.org/example/vim-plugins.gitДобавьте subtree:
git subtree add --prefix .vim/bundle/vim-double-upon vim-double-upon master --squashОбновите подпроект следующим образом:
git fetch vim-double-upon master
git subtree pull --prefix .vim/bundle/vim-double-upon vim-double-upon master --squashРезюме
Git Subtree — это альтернатива Git Submodule. В то время как submodule хранит подпроект как отдельный указатель, который необходимо инициализировать, subtree хранит подпроект как обычный каталог, присутствующий в каждом клоне. Используйте git subtree add для импорта зависимости, git subtree pull для её обновления и git subtree split + push для отправки изменений в исходный проект — встроенной двунаправленной синхронизации нет. Чтобы узнать больше, изучите Git Branch и Git Merge, которые лежат в основе команд subtree.