W3docs

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 SubtreeGit 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.

Практика

Практика
Каковы возможности и способы использования Git subtree?
Каковы возможности и способы использования Git subtree?
Was this page helpful?