Принимая решение об использовании того или иного средства в собственных проектах, инженеру приходится не только изучать сопроводительную документацию, но и проводить серию экспериментов для того, чтобы избежать потенциальных проблем в будущем. Если же речь идет о CM-политике, рассчитанной на длительную перспективу, цена ошибки выбора становится достаточно высока.
 Целью настоящей работы является практическое изучение средства управления поддеревьями Git.
 Начиная с ревизии 1.7.11 upstream-репозиторий Git, в каталоге contrib/subtree, содержит средство автоматизации работы с поддеревьями.
 Сервис git-subtree(1) фактически является полезной надстройкой, использующей функции git-read-tree(1) и git-write-tree(1). Поэтому ссылки  в командах git-subtree(1) add/pull/push:

 могут представлять собой, как имена веток, так и имена тегов удаленного репозитория.
 Кроме того, если заранее добавить удаленный репозиторий в конфигурационный файл локального репозитория .git/config, с помошью команды:
 где build-system является именем удаленного репозитория ../../remote/build-system.git, то в дальнейшем, при использовании команд git-subtree(1) add/pull/push, мы сможем ссылаться на upstream-репозиторий remote/build-system.git по имени.
 На данный момент git-subtree(1) практически не развивается, а лишь поддерживается в актуальном состоянии для текущей степени развития проекта Git.
 Однако git-subtree(1) является наиболее популярным и мощным средством работы с поддеревьями.
 В предыдущей статье, посвященной git-subrepo(1), мы использовали простую структуру каталогов с тестовыми репозиториями для демонстрации работы функций на практике. Воспроизведем и теперь такое окружение:
 Здесь,
 В качестве целей изучения возможностей git-subtree(1) мы будем рассматривать всё те же задачи, о которых мы говорили в статье Git Subrepo, но с учетом различий между этими двумя средствами.
 Запомним текущее состояние репозитория remote/platform.git:
 и подключим master-ветку upstream-репозитория remote/build-system.git в каталог build-system:
 Рассмотрим новое состояние локальной копии репозитория remote/platform.git:
 Здесь следует обратить внимание на сообщение, которое оставила команда git-subtree(1) add. Фактически, с помощью данной команды, в репозиторий platform мы поставили разность:
 Далее, когда история изменений уйдет несколько дальше, мы более подробно изучим детали подключения поддеревьев, а сейчас поставим наши изменения в upstream-репозиторий remote/platform.git:
 и еще раз посмотрим на сообщение команды git-subtree(1) add:
 Эти сообщения полезны для того, чтобы, в случае необходимости, можно было найти историю подключения и текущее состояние поддерева. Оригинальная команда git-subtree(1) не добавляет последние две строки сообщения. Мы немного доработали эту утилиту для того, чтобы было легче искать информацию о том, когда и от какой именно ветки удаленного репозитория было создано то или иное поддерево нашего проекта.
 Полный diff-файл наших изменений вы можете получить следующим образом:
 Для придания нашим примерам более реалистичного характера, сделаем изменения в upstream-репозитории remote/build-system.git:
 Сохраним эти изменения:
 и передадим их в upstream-репозиторий remote/build-system.git:
 Итак, ревизия репозитория build-system изменилась с 783c6d5 на e5c5446:
 Запомним это состояние и перейдем к работе с репозиторием-контейнером remote/platform.git.
 Допустим, что мы еще не знаем об изменениях в upstream-репозитории поддерева и работаем над усовершенствованием кода platform:
 В результате нашей работы мы получили следующее состояние репозитория remote/platform.git:
 Теперь, было бы не плохо узнать, что происходило в upstream-репозитории поддерева build-system в то время, пока мы занимались совершенствованием кода в основном репозитории нашего проекта. Пролистаем сначала поддеревья с помощью команды git subtree --list:
 Данная команда, в простом формате, предстваляет каталог-поддерева, URL upstream-репозитория поддерева, тип ссылки (ветка или тэг), название ссылки, а также ревизию указанной ветки или тэга репозитория, код которого мы поместили в наше поддерево. Однако мы помним, что разработка подпроекта build-system ушла вперед и указание на голову master-ветки уже не действительно. Скорее это сообщение о том, что бы мы хотели иметь в нашем репозитории, а не действительное положение дел.
 Для того, чтобы узнать, насколько продвинулся код в upstream-репозитории поддерева, нам необходимо так же пролистать поддеревья, но с использованием опции -d:
 Теперь, полученный вывод говорит о том, что пока мы работали над кодом основного репозитория, master-ветка upstream-репозитория поддерева build-system ушла вперед. Кроме того, команда git subtree -d --list вывела подсказку о том, что мы можем получить изменения upstream-репозитория поддерева следующим образом:
 данная команда, поскольку мы не задали -m «commit message», открывает редактор с текстом сообщения:
 Заменим этот текст на более информативный:
 После сохранения данного сообщения и закрытия редактора, мы получим следующий вывод:
 То есть, мы получили изменения upstream-репозитория remote/build-system.git и сохранили их в поддереве build-system локальной копии основного репозитория remote/platform.git.
 Проверим статус локального репозитория:
 Для того, чтобы остальные пользователи проекта смогли получить эти изменения, мы должны поставить их в upstream-репозиторий remote/platform.git:
 Посмотрим, что теперь содержит локальная копия репозитория platform, а также upstream-репозиторий remote/platform.git:
 Начальное состояние build-system (783c6d5af1100e9665f930c818c861ff011bed19) уже было в истории репозитория platform, когда он находился в точке 442c9e94c9890032fb2f3123661345d465e2849f. Следовательно, нам надо взять состояние platform в точке 442c9e94c9890032fb2f3123661345d465e2849f и состояние build-system в точке e5c5446967599065dc02a269d8fcfc2c1d3c4f65, вычислить разность между ними, и наложить полученную разность на master-ветку репозитория platform.
 Это стандартная операция слияния веток, суть которой можно выразить формулой
 где,
 Здесь p выступает в роли master-ветки, а b, — играет роль ветки, отделившейся ранее от master с целью создания новой функциональности.
 Рассмотрим еще раз сообщение, которое для нас приготовила утилита git-subtree(1) во время выполнения команды git-subtree-pull:
 В этом сообщении есть существенная для нас информация, а именно состояние
 в котором находилась master-ветка репозитория remote/build-system.git перед «заливкой» на master-ветку репозитория platform.
 Конечно, не очень удобно при выполнении команды git-subtree-pull вручную редактировать commit message, однако, другого способа передать нам состояние
 у утилиты git-subtree(1), в данном случае, просто не существует.
 Если бы мы воспользовались управлением -m
 то мы бы потеряли значение состояния b[n] и, наш коментарий оказался не информативным, и, стало быть, совершенно бесполезным.
 Следует заметить, что после того, как состояние master-ветки репозитория build-system сдвинулось с изначальной точки монтирования 47905bcb80be6f7cb3030513986fad4df548f812, мы всегда будем получать уведомление об изменениях в remote/build-system.git во время просмотра списка подключенных поддеревьев. Иными словами, использование опции -d в команде:
 будет всегда приводить к выводу сообщения, подобного следующему:
 Происходить это будет по тому, что вся информация о подключенном поддереве находится в сообщении, сопроводившим коммит 47905bcb80be6f7cb3030513986fad4df548f812:
 и нет никакого другого хранилища данной информации.
 Единственно, теперь после каждого обновления master-ветки репозитория remote/build-system.git, на стороне platform, при выполнении команды
 будет меняться лишь значение строки
 и нам самим будет необходимо принимать решения о том, нужны ли нам новые изменения поддерева build-system или, на данный момент, не нужны.
 Однако нам не составит труда выполнять команду pull так часто, как нам это будет необходимо, поскольку если репозиторий remote/build-system.git не имел актуальных изменений, команда:
 выдаст адекватное сообщение:
 Теперь все пользователи upstream-репозитория remote/platform.git могут получить полностью настроенное дерево исходного кода с помощью одной команды git-pull(1):
 Кроме того, пользователи смогут отслеживать историю развития не только основного репозитория проекта, но и всех его поддеревьев:
 Запомним состояние файлов, а также самого репозитория platform:
 Внесем изменения в поддерево build-system. Для этого отредактируем файл platform/build-system/README:
 Но не будем заносить эти изменения в origin/master репозитория platform. То есть не будем выполнять команду git-push(1), а выполним git-subtree-push напямую в origin репозиторий remote/build-system.git:
 Пролистаем поддеревья и убедимся в том, что HEAD master-ветки оригинального репозитория remote/build-system.git ушел вперед:
 Кроме того, посмотрим на состояние локальной копии репозитория platform, помня о том, что последний коммит еще не поставлен нами в upstream-репозиторий:
 Правильный путь поставки изменений в upstream-репозиторий основного проекта, состоит в том, чтобы все наши изменения поддерева не попадали непосредственно в основной репозиторий, а приходили из upstream-репозитория поддерева так, как будто мы их получали с помощью команды git-subtree-pull. Далее мы поясним смысл наших действий, а сейчас, для того, чтобы избежать последующих неприятностей, сделаем revert последнего коммита (abaa2c5edd49dd0cf395c99877b4711d0170af37) в локальную копию репозитория platform:
 чтобы затем «снять» этот же самый коммит обратно, но уже из оригинального upstream-репозитория remote/build-system.git. Но прежде убедимся в том, что мы удалили последний коммит abaa2c5edd49dd0cf395c99877b4711d0170af37:
 Да, мы действительно вернулись к нашему предыдущему состоянию ea52eabd5910159efabd80adcf522f22bf6a2af2 и теперь мы можем получить обратно те изменения, которые мы недавно отправили в оригинальный репозиторий remote/build-system.git. Для этого нам, как обычно, необходимо выполнить команду git-subtree-pull:
 Далее, нам будет предложено отредактировать сообщение о коммите:
 которое мы заменим на:
 и наконец, в качестве продолжения предыдущего вывода, получим:
 Теперь мы имеем нормальную историю и, что более важно, мы избежали нетривиального мержа изменений из remote/build-system.git на какой-либо из будущих стадий развития проекта. Произошло это по тому, что мы не оставляли изменений поддерева build-system в локальном репозитории platform, а получили их путем передачи через оригинальный репозиторий remote/build-system.git:
 На данном этапе, ни в коем случае нельзя забывать о том, что мы должны поставить произошедшие изменения из локальной копии в upstream-репозиторий remote/platform.git.
 В противном случае, когда пользователи самостоятельно выполнят команду git-subtree-pull, у них будут большие неприятности. Дело в том, что если автор не выполнит команду git-push(1), то другие участники проекта, при наличии соответствующих прав, смогут сделать это раньше автора.
 Действительно, поскольку upstream-репозиторий remote/platform.git так и не перешел в состояние 04a13bac91d1c445994ffc19db8b479d5e644e17, но репозиторий remote/build-system.git уже обновился, следовательно, после выполнения команды git-subtree-pull, любой участник проекта сможет поставить изменения в upstream-репозиторий remote/platform.git, а это приведет к рассогласованности upstream-репозитория remote/platform.git и его локальной копии на стороне автора (owner/platform).
 Если, в своей работе, мы используем git-subtre, основными нашими помощниками будут
команды:
 которые позволяют видеть изменения, либо основного репозитория, либо поддерева соответственно.
 А вот утилитой git-format-patch следует пользоваться осторожно.
 Так, например, если мы попытаемся сформировать серию patch-файлов от состояния ea52eabd5910159efabd80adcf522f22bf6a2af2, с помощью утилиты git-format-patch:
 мы увидим, что разность состояний файла build-system/README рассматривается относительно каталога build-system/, а не от корня дерева исходного кода:
 В это же время, рассчитав разность между состояниями того же файла с помощью команды
git-diff:
 мы получим правильный абсолютный путь в patch-файле:
 Параметры команды git-diff(1), в предылущем примере, мы определили исходя из списка изменений, сделанных в основном репозитории platform.
 Наличие подпроектов накладывает дополнительные требования к CM-политике и требует введения некоторых правил, соблюдение которых всеми участниками проекта позволит избежать конфликтов и непредвиденных затрат времени. Самыми простыми из этих правил могут быть, например, следующие:
 Для того, чтобы рассмотреть подобные ограничения нам необходимо несколько доработать содержимое наших тестовых репозиториев.
 Зафиксируем, для начала, текуще состояние upstream-репозитория remote/build-system.git:
 Допустим теперь, что автор upstream-репозитория remote/build-system.git решил внести в проект изменения, которые, согласно его версионной политике, должны повысить minor компонент текущей версии 1.0.2. Для этого ему необходимо создать новую ветку проекта с именем build-system-1.1.x и далее, по мере разработки, фиксировать состояния тегами: 1.1.0, 1.1.1, 1.1.2, и так далее.
 Создадим новую ветку:
 Повысим версию, записанную в файле README до версии 1.1.0:
 и внесем изменения в upstream-репозиторий remote/build-system.git:
 Теперь допустим, что, по мере разработки, мы продвинулись до версии 1.1.1:
 и зафиксировали данное состояние тэгом:
 После этого в upstream-репозитории remote/build-system.git мы можем наблюдать следующие ссылки:
 Тем временем, разработчики проекта platform получили задачу стабилизировать сборку с использованием новой версии build-system. Естественно, для выполнения поставленной задачи, они создали новую ветку, например, platform-1.0.2:
 И подключили к ней, в качестве поддерева, уже не мастер-ветку репозитория remote/build-system.git, а тэг 1.1.1:
 Итак мы имеем новое состояние подпроекта build-system и должны внести эти изменения в upstream-репозиторий remote/platform.git:
 Проверим историю:
 Мы действительно подключили нужный нам тэг и, теперь задача CM-инженера состоит в том, чтобы разработчики проекта platform, работающие на ветке platform-1.0.2 даже не пытались править build-system. Это было бы не логично и, более того, так как поддерево build-system ссылается на тэг, совершенно недопустимо.
 Для того, чтобы запретить изменения в upstream-репозитории со стороны поддерева build-system проекта platform, необходимо разработать pre-receive хук и поместить его в файл
 Займемся созданием простого скрипта, запрещающего коммиты в том случае, если совершается попытка изменить состояние тэга:
 Данный скрипт требует пояснений. Наверное странно видеть, что сначала мы проверяем, не является ли ссылка веткой, а затем снова пытаемся проверить, не представляет ли она, фактически, тэг. Все дело в том, что функция, реализующая команду:
 не анализирует параметр <ref> и не сверяется с историей сообщений, сохраненных на этапе подключения поддерева в каталог <subdir> и, поэтому ссылки, заданные не полностью (refs/tags/1.1.1), а кратко (1.1.1) поступают на вход upstream-репозитория как полные имена веток (в нашем случае: refs/heads/1.1.1).
 Существующее положение дел можно поправить, если доработать функциональность git-subtree(1). Однако сейчас мы не будем этого делать и продолжим наши рассуждения.
 Итак, для проверки скрипта pre-receive, внесем изменения в файл user/platform/build-system/README:
 и попробуем доставить их в upstream-репозиторий remote/build-system.git.
 Ссылка на ревизию upstream-репозитория является обязательным параметром команды
 Поэтому, для того чтобы вспомнить, каким образом мы подключали поддерево, нам будет необходимо уточнить имя сылки с помощью команды:
 Итак, вспомнив имя тэга, мы наконец можем попробовать доставить наши изменения в upstream-репозиторий remote/build-system.git:
 Поскольку, созданный заранее, хук pre-receive отработал правильно, нам не удалось доставить наши изменения. Вместо этого мы получили сообщение об ошибке:
 и нам, так или иначе, придется откатить, сделанные нами, изменения:
 вернув, тем самым, предыдущее состояние ветки platform-1.0.2:
 В отличие от git-subrepo, где история основного репозитория остается линейной, а подпроекты подключаются как squashed-коммиты, git-subtree(1) представляется более мощным средством, позволяющим реализовать практически любые сценарии работы.
 С точки зрения доработки существующих средств под нужды собственного проекта или просто с целью их усовершенствования, необходимо учитывать следующее. Проект git-subrepo развивается и команда разработчиков открыта для предложений. С другой стороны, git-subtree более прост в реализации и любые его изменения не составят большого труда, однако, команда разработчиков Git с трудом принимает предложения, поскольку ее практически не интересует совершенствование данного инструмента и, с наибольшей вероятностью, вам придется накладывать собственные изменения каждый раз при переходе на новую версию Git.