Новости и статьи

Среда разработки встроенных систем или дистрибутивов на основе GNU/Linux. Здесь вы можете найти новости проекта, статьи и заметки, касающиеся разработки дистрибутива Radix cross Linux.

Git Subtree в деталях

7 ноября 2018 г.

Принимая решение об использовании того или иного средства в собственных проектах, инженеру приходится не только изучать сопроводительную документацию, но и проводить серию экспериментов для того, чтобы избежать потенциальных проблем в будущем. Если же речь идет о CM-политике, рассчитанной на длительную перспективу, цена ошибки выбора становится достаточно высока.

Целью настоящей работы является практическое изучение средства управления поддеревьями Git.

tags

Начиная с ревизии 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 subtree add --prefix=<subdir> <remote> <ref>

могут представлять собой, как имена веток, так и имена тегов удаленного репозитория.

Кроме того, если заранее добавить удаленный репозиторий в конфигурационный файл локального репозитория .git/config, с помошью команды:

bash-4.4$ git remote add build-system ../../remote/build-system.git

где 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), мы использовали простую структуру каталогов с тестовыми репозиториями для демонстрации работы функций на практике. Воспроизведем и теперь такое окружение:

bash-4.4$ vim _init.sh
#!/bin/sh

CWD=`pwd`

mkdir remote owner user

cd remote
git init --bare build-system.git
git init --bare platform.git

cd ../owner
git clone $CWD/remote/build-system.git
git clone $CWD/remote/platform.git

cd build-system
echo -e "\n[master] build-system 1.0.0\n" >README
git add README
git commit -m "init build-system master 1.0.0"
git push

cd ../platform
echo -e "\n[master] platform 1.0.0\n" >README
git add README
git commit -m "init platform master 1.0.0"
git push

cd ../../user
git clone $CWD/remote/build-system.git
git clone $CWD/remote/platform.git

cd $CWD

:wq

bash-4.4$ chmod a+x ./_init.sh
bash-4.4$ ./_init.sh
bash-4.4$

Здесь,

owner
рабочий каталог автора проектов;
remote
каталог представляющий сервер автора проектов, на котором располагаются upstream-репозитории основного проекта platform.git и подпроекта build-system.git;
user
рабочий каталог пользователя или участника команды разработчиков.

В качестве целей изучения возможностей git-subtree(1) мы будем рассматривать всё те же задачи, о которых мы говорили в статье Git Subrepo, но с учетом различий между этими двумя средствами.

Подключение поддерева

Запомним текущее состояние репозитория remote/platform.git:

bash-4.4$
bash-4.4$ cd owner/platform/
bash-4.4$ git log
commit 7fad4becbd13258216fb95cbe9d987dd33f0be6d (HEAD -> master, origin/master)
Author: user <___@_______>
Date:   Thu Nov 1 20:16:33 2018 +0300

    init platform master 1.0.0
bash-4.4$

и подключим master-ветку upstream-репозитория remote/build-system.git в каталог build-system:

bash-4.4$
bash-4.4$ git subtree add --prefix=build-system ../../remote/build-system.git/ master
git fetch ../../remote/build-system.git/ master
warning: no common commits
remote: Enumerating objects: 3, done.
remote: Counting objects: 100% (3/3), done.
remote: Total 3 (delta 0), reused 0 (delta 0)
Unpacking objects: 100% (3/3), done.
From ../../remote/build-system
 * branch            master     -> FETCH_HEAD
Added dir 'build-system'
bash-4.4$

Рассмотрим новое состояние локальной копии репозитория remote/platform.git:

bash-4.4$
bash-4.4$ git log --graph
*   commit 47905bcb80be6f7cb3030513986fad4df548f812 (HEAD -> master)
|\  Merge: 7fad4be 783c6d5
| | Author: user <___@_______>
| | Date:   Thu Nov 1 20:20:20 2018 +0300
| |
| |     Add 'build-system/' from commit '783c6d5af1100e9665f930c818c861ff011bed19'
| |
| |     git-subtree-dir: build-system
| |     git-subtree-mainline: 7fad4becbd13258216fb95cbe9d987dd33f0be6d
| |     git-subtree-split: 783c6d5af1100e9665f930c818c861ff011bed19
| |     git-subtree-repo: ../../remote/build-system.git/
| |     git-subtree-ref: master
| |
| * commit 783c6d5af1100e9665f930c818c861ff011bed19
|   Author: user <___@_______>
|   Date:   Thu Nov 1 20:16:33 2018 +0300
|
|       init build-system master 1.0.0
|
* commit 7fad4becbd13258216fb95cbe9d987dd33f0be6d (origin/master)
  Author: user <___@_______>
  Date:   Thu Nov 1 20:16:33 2018 +0300

      init platform master 1.0.0
bash-4.4$

Здесь следует обратить внимание на сообщение, которое оставила команда git-subtree(1) add. Фактически, с помощью данной команды, в репозиторий platform мы поставили разность:

bash-4.4$
bash-4.4$ git diff 7fad4becbd13258216fb95cbe9d987dd33f0be6d 47905bcb80be6f7cb3030513986fad4df548f812
diff --git a/build-system/README b/build-system/README
new file mode 100644
index 0000000..73a41c7
--- /dev/null
+++ b/build-system/README
@@ -0,0 +1,3 @@
+
+[master] build-system 1.0.0
+
bash-4.4$

Далее, когда история изменений уйдет несколько дальше, мы более подробно изучим детали подключения поддеревьев, а сейчас поставим наши изменения в upstream-репозиторий remote/platform.git:

bash-4.4$
bash-4.4$ git push
Enumerating objects: 6, done.
Counting objects: 100% (6/6), done.
Delta compression using up to 4 threads
Compressing objects: 100% (3/3), done.
Writing objects: 100% (5/5), 582 bytes | 582.00 KiB/s, done.
Total 5 (delta 0), reused 0 (delta 0)
To ../../remote/platform.git
   7fad4be..47905bc  master -> master
bash-4.4$

и еще раз посмотрим на сообщение команды git-subtree(1) add:

   Add 'build-system/' from commit '783c6d5af1100e9665f930c818c861ff011bed19'

   git-subtree-dir: build-system
   git-subtree-mainline: 7fad4becbd13258216fb95cbe9d987dd33f0be6d
   git-subtree-split: 783c6d5af1100e9665f930c818c861ff011bed19
   git-subtree-repo: ../../remote/build-system.git/
   git-subtree-ref: master

Эти сообщения полезны для того, чтобы, в случае необходимости, можно было найти историю подключения и текущее состояние поддерева. Оригинальная команда git-subtree(1) не добавляет последние две строки сообщения. Мы немного доработали эту утилиту для того, чтобы было легче искать информацию о том, когда и от какой именно ветки удаленного репозитория было создано то или иное поддерево нашего проекта.

Полный diff-файл наших изменений вы можете получить следующим образом:

bash-4.4$
bash-4.4$ git clone https://github.com/radix-platform/git.git
bash-4.4$ cd git
bash-4.4$ git checkout git-subtree-2.19.1
bash-4.4$ git diff v2.19.1 > ../git-subtree-2.19.1.patch
bash-4.4$

Для придания нашим примерам более реалистичного характера, сделаем изменения в upstream-репозитории remote/build-system.git:

bash-4.4$
bash-4.4$ cd owner/build-system/
bash-4.4$ vim README
bash-4.4$ cat README

[master] build-system 1.0.1

bash-4.4$

Сохраним эти изменения:

bash-4.4$
bash-4.4$ git add README
bash-4.4$ git commit -m "update build-system version to 1.0.1"
[master e5c5446] update build-system version to 1.0.1
 1 file changed, 1 insertion(+), 1 deletion(-)
bash-4.4$

и передадим их в upstream-репозиторий remote/build-system.git:

bash-4.4$
bash-4.4$ git push
Enumerating objects: 5, done.
Counting objects: 100% (5/5), done.
Writing objects: 100% (3/3), 274 bytes | 274.00 KiB/s, done.
Total 3 (delta 0), reused 0 (delta 0)
To ../../remote/build-system.git
   783c6d5..e5c5446  master -> master
bash-4.4$

Итак, ревизия репозитория build-system изменилась с 783c6d5 на e5c5446:

bash-4.4$
bash-4.4$ git log
commit e5c5446967599065dc02a269d8fcfc2c1d3c4f65 (HEAD -> master, origin/master)
Author: user <___@_______>
Date:   Thu Nov 1 20:26:52 2018 +0300

    update build-system version to 1.0.1

commit 783c6d5af1100e9665f930c818c861ff011bed19
Author: user <___@_______>
Date:   Thu Nov 1 20:16:33 2018 +0300

    init build-system master 1.0.0
bash-4.4$

Запомним это состояние и перейдем к работе с репозиторием-контейнером remote/platform.git.

Получение изменений из upstream-репозитория поддерева

Допустим, что мы еще не знаем об изменениях в upstream-репозитории поддерева и работаем над усовершенствованием кода platform:

bash-4.4$
bash-4.4$ cd owner/platform/
bash-4.4$ vim README
bash-4.4$ cat README

[master] platform 1.0.1

bash-4.4$
bash-4.4$ git add README
bash-4.4$ git commit -m "update platform version to 1.0.1"
[master 442c9e9] update platform version to 1.0.1
 1 file changed, 1 insertion(+), 1 deletion(-)
bash-4.4$
bash-4.4$ git push
Enumerating objects: 5, done.
Counting objects: 100% (5/5), done.
Delta compression using up to 4 threads
Compressing objects: 100% (2/2), done.
Writing objects: 100% (3/3), 306 bytes | 306.00 KiB/s, done.
Total 3 (delta 0), reused 0 (delta 0)
To ../../remote/platform.git
   47905bc..442c9e9  master -> master
bash-4.4$

В результате нашей работы мы получили следующее состояние репозитория remote/platform.git:

bash-4.4$
bash-4.4$ git log --graph
* commit 442c9e94c9890032fb2f3123661345d465e2849f (HEAD -> master, origin/master)
| Author: user <___@_______>
| Date:   Thu Nov 1 20:41:40 2018 +0300
|
|     update platform version to 1.0.1
|
*   commit 47905bcb80be6f7cb3030513986fad4df548f812
|\  Merge: 7fad4be 783c6d5
| | Author: user <___@_______>
| | Date:   Thu Nov 1 20:20:20 2018 +0300
| |
| |     Add 'build-system/' from commit '783c6d5af1100e9665f930c818c861ff011bed19'
| |
| |     git-subtree-dir: build-system
| |     git-subtree-mainline: 7fad4becbd13258216fb95cbe9d987dd33f0be6d
| |     git-subtree-split: 783c6d5af1100e9665f930c818c861ff011bed19
| |     git-subtree-repo: ../../remote/build-system.git/
| |     git-subtree-ref: master
| |
| * commit 783c6d5af1100e9665f930c818c861ff011bed19
|   Author: user <___@_______>
|   Date:   Thu Nov 1 20:16:33 2018 +0300
|
|       init build-system master 1.0.0
|
* commit 7fad4becbd13258216fb95cbe9d987dd33f0be6d
  Author: user <___@_______>
  Date:   Thu Nov 1 20:16:33 2018 +0300

      init platform master 1.0.0
bash-4.4$

Теперь, было бы не плохо узнать, что происходило в upstream-репозитории поддерева build-system в то время, пока мы занимались совершенствованием кода в основном репозитории нашего проекта. Пролистаем сначала поддеревья с помощью команды git subtree --list:

bash-4.4$
bash-4.4$ git subtree --list
build-system ../../remote/build-system.git/ branch master HEAD
bash-4.4$

Данная команда, в простом формате, предстваляет каталог-поддерева, URL upstream-репозитория поддерева, тип ссылки (ветка или тэг), название ссылки, а также ревизию указанной ветки или тэга репозитория, код которого мы поместили в наше поддерево. Однако мы помним, что разработка подпроекта build-system ушла вперед и указание на голову master-ветки уже не действительно. Скорее это сообщение о том, что бы мы хотели иметь в нашем репозитории, а не действительное положение дел.

Для того, чтобы узнать, насколько продвинулся код в upstream-репозитории поддерева, нам необходимо так же пролистать поддеревья, но с использованием опции -d:

bash-4.4$
bash-4.4$ git subtree -d --list
Looking for externals...

Commit: 47905bcb80be6f7cb3030513986fad4df548f812
build-system ../../remote/build-system.git/ branch master HEAD

The 'build-system' subtree seems not updated:
   original revision: 783c6d5af1100e9665f930c818c861ff011bed19
     remote revision: e5c5446967599065dc02a269d8fcfc2c1d3c4f65
You can update 'build-system' subtree by following command:

   git subtree pull --prefix=build-system ../../remote/build-system.git/ master

bash-4.4$

Теперь, полученный вывод говорит о том, что пока мы работали над кодом основного репозитория, master-ветка upstream-репозитория поддерева build-system ушла вперед. Кроме того, команда git subtree -d --list вывела подсказку о том, что мы можем получить изменения upstream-репозитория поддерева следующим образом:

bash-4.4$
bash-4.4$ git subtree pull --prefix=build-system ../../remote/build-system.git/ master
remote: Enumerating objects: 5, done.
remote: Counting objects: 100% (5/5), done.
remote: Total 3 (delta 0), reused 0 (delta 0)
Unpacking objects: 100% (3/3), done.
From ../../remote/build-system
 * branch            master     -> FETCH_HEAD
hint: Waiting for your editor to close the file...

данная команда, поскольку мы не задали -m "commit message", открывает редактор с текстом сообщения:

Merge commit 'e5c5446967599065dc02a269d8fcfc2c1d3c4f65'

# Please enter a commit message to explain why this merge is necessary,
# especially if it merges an updated upstream into a topic branch.
#
# Lines starting with '#' will be ignored, and an empty message aborts
# the commit.
  

Заменим этот текст на более информативный:

Pull changes from master of upstream build-system.git repository:

Merge commit 'e5c5446967599065dc02a269d8fcfc2c1d3c4f65'
  

После сохранения данного сообщения и закрытия редактора, мы получим следующий вывод:

Merge made by the 'recursive' strategy.
 build-system/README | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
bash-4.4$

То есть, мы получили изменения upstream-репозитория remote/build-system.git и сохранили их в поддереве build-system локальной копии основного репозитория remote/platform.git.

Проверим статус локального репозитория:

bash-4.4$
bash-4.4$ git status
On branch master
Your branch is ahead of 'origin/master' by 2 commits.
  (use "git push" to publish your local commits)

nothing to commit, working tree clean
bash-4.4$

Для того, чтобы остальные пользователи проекта смогли получить эти изменения, мы должны поставить их в upstream-репозиторий remote/platform.git:

bash-4.4$
bash-4.4$ git push
Enumerating objects: 9, done.
Counting objects: 100% (8/8), done.
Delta compression using up to 4 threads
Compressing objects: 100% (3/3), done.
Writing objects: 100% (5/5), 583 bytes | 583.00 KiB/s, done.
Total 5 (delta 0), reused 0 (delta 0)
To ../../remote/platform.git
   442c9e9..ea52eab  master -> master
bash-4.4$

Посмотрим, что теперь содержит локальная копия репозитория platform, а также upstream-репозиторий remote/platform.git:

bash-4.4$
bash-4.4$ git log --graph
*   commit ea52eabd5910159efabd80adcf522f22bf6a2af2 (HEAD -> master, origin/master)
|\  Merge: 442c9e9 e5c5446
| | Author: user <___@_______>
| | Date:   Thu Nov 1 20:48:05 2018 +0300
| |
| |     Pull changes from master of upstream build-system.git repository.
| |
| |     Merge commit 'e5c5446967599065dc02a269d8fcfc2c1d3c4f65'
| |
| * commit e5c5446967599065dc02a269d8fcfc2c1d3c4f65
| | Author: user <___@_______>
| | Date:   Thu Nov 1 20:26:52 2018 +0300
| |
| |     update build-system version to 1.0.1
| |
* | commit 442c9e94c9890032fb2f3123661345d465e2849f
| | Author: user <___@_______>
| | Date:   Thu Nov 1 20:41:40 2018 +0300
| |
| |     update platform version to 1.0.1
| |
* |   commit 47905bcb80be6f7cb3030513986fad4df548f812
|\ \  Merge: 7fad4be 783c6d5
| |/  Author: user <___@_______>
| |   Date:   Thu Nov 1 20:20:20 2018 +0300
| |
| |       Add 'build-system/' from commit '783c6d5af1100e9665f930c818c861ff011bed19'
| |
| |       git-subtree-dir: build-system
| |       git-subtree-mainline: 7fad4becbd13258216fb95cbe9d987dd33f0be6d
| |       git-subtree-split: 783c6d5af1100e9665f930c818c861ff011bed19
| |       git-subtree-repo: ../../remote/build-system.git/
| |       git-subtree-ref: master
| |
| * commit 783c6d5af1100e9665f930c818c861ff011bed19
|   Author: user <___@_______>
|   Date:   Thu Nov 1 20:16:33 2018 +0300
|
|       init build-system master 1.0.0
|
* commit 7fad4becbd13258216fb95cbe9d987dd33f0be6d
  Author: user <___@_______>
  Date:   Thu Nov 1 20:16:33 2018 +0300

      init platform master 1.0.0
bash-4.4$

Мы видим, что начальное состояние репозитория remote/build-system.git, в момент подключения его в качестве поддерева платформы, было равно 783c6d5af1100e9665f930c818c861ff011bed19. Однако пока мы работали над кодом в репозитории platform, состояние репозитория изменилось и стало равным e5c5446967599065dc02a269d8fcfc2c1d3c4f65.

Начальное состояние build-system (783c6d5af1100e9665f930c818c861ff011bed19) уже было в истории репозитория platform, когда он находился в точке 442c9e94c9890032fb2f3123661345d465e2849f. Следовательно, нам надо взять состояние platform в точке 442c9e94c9890032fb2f3123661345d465e2849f и состояние build-system в точке e5c5446967599065dc02a269d8fcfc2c1d3c4f65, вычислить разность между ними, и наложить полученную разность на master-ветку репозитория platform.

Это стандартная операция слияния веток, суть которой можно выразить формулой

  p[n] = p[n-1] + diff(p[n-1], b[n])

где,

  p[n]   = ea52eabd5910159efabd80adcf522f22bf6a2af2,
  p[n-1] = 442c9e94c9890032fb2f3123661345d465e2849f,
  b[n]   = e5c5446967599065dc02a269d8fcfc2c1d3c4f65.

Здесь p выступает в роли master-ветки, а b, – играет роль ветки, отделившейся ранее от master с целью создания новой функциональности.

Рассмотрим еще раз сообщение, которое для нас приготовила утилита git-subtree(1) во время выполнения команды git-subtree-pull:

  git subtree pull --prefix=build-system ../../remote/build-system.git/ master
Merge commit 'e5c5446967599065dc02a269d8fcfc2c1d3c4f65'

# Please enter a commit message to explain why this merge is necessary,
# especially if it merges an updated upstream into a topic branch.
#
# Lines starting with '#' will be ignored, and an empty message aborts
# the commit.
  

В этом сообщении есть существенная для нас информация, а именно состояние

  b[n] = e5c5446967599065dc02a269d8fcfc2c1d3c4f65

в котором находилась master-ветка репозитория remote/build-system.git перед "заливкой" на master-ветку репозитория platform.

Конечно, не очень удобно при выполнении команды git-subtree-pull вручную редактировать commit message, однако, другого способа передать нам состояние

  b[n] = e5c5446967599065dc02a269d8fcfc2c1d3c4f65

у утилиты git-subtree(1), в данном случае, просто не существует.

Если бы мы воспользовались управлением -m

  git subtree pill -m "Our own message" ...

то мы бы потеряли значение состояния b[n] и, наш коментарий оказался не информативным, и, стало быть, совершенно бесполезным.

Следует заметить, что после того, как состояние master-ветки репозитория build-system сдвинулось с изначальной точки монтирования 47905bcb80be6f7cb3030513986fad4df548f812, мы всегда будем получать уведомление об изменениях в remote/build-system.git во время просмотра списка подключенных поддеревьев. Иными словами, использование опции -d в команде:

  git subtree -d --list

будет всегда приводить к выводу сообщения, подобного следующему:

bash-4.4$
bash-4.4$ git subtree -d --list
Looking for externals...

Commit: 47905bcb80be6f7cb3030513986fad4df548f812
build-system ../../remote/build-system.git/ branch master HEAD

The 'build-system' subtree seems not updated:
   original revision: 783c6d5af1100e9665f930c818c861ff011bed19
     remote revision: e5c5446967599065dc02a269d8fcfc2c1d3c4f65
You can update 'build-system' subtree by following command:

   git subtree pull --prefix=build-system ../../remote/build-system.git/ master

bash-4.4$

Происходить это будет по тому, что вся информация о подключенном поддереве находится в сообщении, сопроводившим коммит 47905bcb80be6f7cb3030513986fad4df548f812:

bash-4.4$
bash-4.4$ git show 47905bcb80be6f7cb3030513986fad4df548f812
commit 47905bcb80be6f7cb3030513986fad4df548f812
Merge: 7fad4be 783c6d5
Author: user <___@_______>
Date:   Thu Nov 1 20:20:20 2018 +0300

    Add 'build-system/' from commit '783c6d5af1100e9665f930c818c861ff011bed19'

    git-subtree-dir: build-system
    git-subtree-mainline: 7fad4becbd13258216fb95cbe9d987dd33f0be6d
    git-subtree-split: 783c6d5af1100e9665f930c818c861ff011bed19
    git-subtree-repo: ../../remote/build-system.git/
    git-subtree-ref: master

diff --cc build-system/README
index 0000000,0000000..73a41c7
new file mode 100644
--- /dev/null
+++ b/build-system/README
@@@ -1,0 -1,0 +1,3 @@@
++
++[master] build-system 1.0.0
++
bash-4.4$

и нет никакого другого хранилища данной информации.

Единственно, теперь после каждого обновления master-ветки репозитория remote/build-system.git, на стороне platform, при выполнении команды

  git subtree -d --list

будет меняться лишь значение строки

     remote revision: e5c5446967599065dc02a269d8fcfc2c1d3c4f65

и нам самим будет необходимо принимать решения о том, нужны ли нам новые изменения поддерева build-system или, на данный момент, не нужны.

Однако нам не составит труда выполнять команду pull так часто, как нам это будет необходимо, поскольку если репозиторий remote/build-system.git не имел актуальных изменений, команда:

  git subtree pull --prefix=build-system ../../remote/build-system.git/ master

выдаст адекватное сообщение:

bash-4.4$
bash-4.4$ git subtree pull --prefix=build-system ../../remote/build-system.git/ master
From ../../remote/build-system
 * branch            master     -> FETCH_HEAD
Already up to date.
bash-4.4$

Получение кода пользователями

Теперь все пользователи upstream-репозитория remote/platform.git могут получить полностью настроенное дерево исходного кода с помощью одной команды git-pull(1):

bash-4.4$
bash-4.4$ cd user/platform/
bash-4.4$
bash-4.4$ git pull
remote: Enumerating objects: 15, done.
remote: Counting objects: 100% (15/15), done.
remote: Compressing objects: 100% (8/8), done.
remote: Total 13 (delta 0), reused 0 (delta 0)
Unpacking objects: 100% (13/13), done.
From ../../remote/platform
   7fad4be..ea52eab  master     -> origin/master
Updating 7fad4be..ea52eab
Fast-forward
 README              | 2 +-
 build-system/README | 3 +++
 2 files changed, 4 insertions(+), 1 deletion(-)
 create mode 100644 build-system/README

bash-4.4$

Кроме того, пользователи смогут отслеживать историю развития не только основного репозитория проекта, но и всех его поддеревьев:

bash-4.4$
bash-4.4$ git log -3 --graph
*   commit ea52eabd5910159efabd80adcf522f22bf6a2af2 (HEAD -> master, origin/master, origin/HEAD)
|\  Merge: 442c9e9 e5c5446
| | Author: user <___@_______>
| | Date:   Thu Nov 1 20:48:05 2018 +0300
| |
| |     Pull changes from master of upstream build-system.git repository.
| |
| |     Merge commit 'e5c5446967599065dc02a269d8fcfc2c1d3c4f65'
| |
| * commit e5c5446967599065dc02a269d8fcfc2c1d3c4f65
| | Author: user <___@_______>
| | Date:   Thu Nov 1 20:26:52 2018 +0300
| |
| |     update build-system version to 1.0.1
| |
* | commit 442c9e94c9890032fb2f3123661345d465e2849f
| | Author: user <___@_______>
| | Date:   Thu Nov 1 20:41:40 2018 +0300
| |
| |     update platform version to 1.0.1
bash-4.4$

Поставка изменений поддерева в upstream-репозиторий

Запомним состояние файлов, а также самого репозитория platform:

bash-4.4$
bash-4.4$ git log -3 --graph
*   commit ea52eabd5910159efabd80adcf522f22bf6a2af2 (HEAD -> master, origin/master, origin/HEAD)
|\  Merge: 442c9e9 e5c5446
| | Author: user <___@_______>
| | Date:   Thu Nov 1 20:48:05 2018 +0300
| |
| |     Pull changes from master of upstream build-system.git repository.
| |
| |     Merge commit 'e5c5446967599065dc02a269d8fcfc2c1d3c4f65'
| |
| * commit e5c5446967599065dc02a269d8fcfc2c1d3c4f65
| | Author: user <___@_______>
| | Date:   Thu Nov 1 20:26:52 2018 +0300
| |
| |     update build-system version to 1.0.1
| |
* | commit 442c9e94c9890032fb2f3123661345d465e2849f
| | Author: user <___@_______>
| | Date:   Thu Nov 1 20:41:40 2018 +0300
| |
| |     update platform version to 1.0.1
bash-4.4$

Внесем изменения в поддерево build-system. Для этого отредактируем файл platform/build-system/README:

bash-4.4$
bash-4.4$ cd owner/platform/
bash-4.4$
bash-4.4$ vim build-system/README
bash-4.4$ cat build-system/README

[master] build-system 1.0.2

bash-4.4$
bash-4.4$ git add build-system/README
bash-4.4$ git commit -m "build-system is updated to version 1.0.2 from platform side"
[master abaa2c5] build-system is updated to version 1.0.2 from platform side
 1 file changed, 1 insertion(+), 1 deletion(-)
bash-4.4$

Но не будем заносить эти изменения в origin/master репозитория platform. То есть не будем выполнять команду git-push(1), а выполним git-subtree-push напямую в origin репозиторий remote/build-system.git:

bash-4.4$
bash-4.4$ git subtree push --prefix=build-system ../../remote/build-system.git/ master
git push using:  ../../remote/build-system.git/ master
Enumerating objects: 5, done.
Counting objects: 100% (5/5), done.
Writing objects: 100% (3/3), 290 bytes | 290.00 KiB/s, done.
Total 3 (delta 0), reused 0 (delta 0)
To ../../remote/build-system.git/
   e5c5446..0673142  0673142942ccf53514a276e855a98514952bb713 -> master
bash-4.4$

Пролистаем поддеревья и убедимся в том, что HEAD master-ветки оригинального репозитория remote/build-system.git ушел вперед:

bash-4.4$
bash-4.4$ git subtree -d --list
Looking for externals...

Commit: 47905bcb80be6f7cb3030513986fad4df548f812
build-system ../../remote/build-system.git/ branch master HEAD

The 'build-system' subtree seems not updated:
   original revision: 783c6d5af1100e9665f930c818c861ff011bed19
     remote revision: 0673142942ccf53514a276e855a98514952bb713
You can update 'build-system' subtree by following command:

   git subtree pull --prefix=build-system ../../remote/build-system.git/ master

bash-4.4$

Кроме того, посмотрим на состояние локальной копии репозитория platform, помня о том, что последний коммит еще не поставлен нами в upstream-репозиторий:

bash-4.4$
bash-4.4$ git log -4 --graph
* commit abaa2c5edd49dd0cf395c99877b4711d0170af37 (HEAD -> master)
| Author: user <___@_______>
| Date:   Thu Nov 1 21:48:40 2018 +0300
|
|     build-system is updated to version 1.0.2 from platform side
|
*   commit ea52eabd5910159efabd80adcf522f22bf6a2af2 (origin/master)
|\  Merge: 442c9e9 e5c5446
| | Author: user <___@_______>
| | Date:   Thu Nov 1 20:48:05 2018 +0300
| |
| |     Pull changes from master of upstream build-system.git repository.
| |
| |     Merge commit 'e5c5446967599065dc02a269d8fcfc2c1d3c4f65'
| |
| * commit e5c5446967599065dc02a269d8fcfc2c1d3c4f65
| | Author: user <___@_______>
| | Date:   Thu Nov 1 20:26:52 2018 +0300
| |
| |     update build-system version to 1.0.1
| |
* | commit 442c9e94c9890032fb2f3123661345d465e2849f
| | Author: user <___@_______>
| | Date:   Thu Nov 1 20:41:40 2018 +0300
| |
| |     update platform version to 1.0.1
bash-4.4$

Правильный путь поставки изменений в upstream-репозиторий основного проекта, состоит в том, чтобы все наши изменения поддерева не попадали непосредственно в основной репозиторий, а приходили из upstream-репозитория поддерева так, как будто мы их получали с помощью команды git-subtree-pull. Далее мы поясним смысл наших действий, а сейчас, для того, чтобы избежать последующих неприятностей, сделаем revert последнего коммита (abaa2c5edd49dd0cf395c99877b4711d0170af37) в локальную копию репозитория platform:

bash-4.4$
bash-4.4$ git reset --hard HEAD^
HEAD is now at ea52eab Pull changes from master of upstream build-system.git repository.
bash-4.4$

чтобы затем "снять" этот же самый коммит обратно, но уже из оригинального upstream-репозитория remote/build-system.git. Но прежде убедимся в том, что мы удалили последний коммит abaa2c5edd49dd0cf395c99877b4711d0170af37:

bash-4.4$ 
bash-4.4$ git log -3 --graph
*   commit ea52eabd5910159efabd80adcf522f22bf6a2af2 (HEAD -> master, origin/master)
|\  Merge: 442c9e9 e5c5446
| | Author: user <___@_______>
| | Date:   Thu Nov 1 20:48:05 2018 +0300
| |
| |     Pull changes from master of upstream build-system.git repository.
| |
| |     Merge commit 'e5c5446967599065dc02a269d8fcfc2c1d3c4f65'
| |
| * commit e5c5446967599065dc02a269d8fcfc2c1d3c4f65
| | Author: user <___@_______>
| | Date:   Thu Nov 1 20:26:52 2018 +0300
| |
| |     update build-system version to 1.0.1
| |
* | commit 442c9e94c9890032fb2f3123661345d465e2849f
| | Author: user <___@_______>
| | Date:   Thu Nov 1 20:41:40 2018 +0300
| |
| |     update platform version to 1.0.1
bash-4.4$

Да, мы действительно вернулись к нашему предыдущему состоянию ea52eabd5910159efabd80adcf522f22bf6a2af2 и теперь мы можем получить обратно те изменения, которые мы недавно отправили в оригинальный репозиторий remote/build-system.git. Для этого нам, как обычно, необходимо выполнить команду git-subtree-pull:

bash-4.4$
bash-4.4$ git subtree pull --prefix=build-system ../../remote/build-system.git/ master
From ../../remote/build-system
 * branch            master     -> FETCH_HEAD
hint: Waiting for your editor to close the file...

Далее, нам будет предложено отредактировать сообщение о коммите:

Merge commit '0673142942ccf53514a276e855a98514952bb713'

# Please enter a commit message to explain why this merge is necessary,
# especially if it merges an updated upstream into a topic branch.
#
# Lines starting with '#' will be ignored, and an empty message aborts
# the commit.
  

которое мы заменим на:

Pull changes from master of origin remote/build-system repository.

Merge commit '0673142942ccf53514a276e855a98514952bb713'
  

и наконец, в качестве продолжения предыдущего вывода, получим:

Merge made by the 'recursive' strategy.
 build-system/README | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
bash-4.4$

Теперь мы имеем нормальную историю и, что более важно, мы избежали нетривиального мержа изменений из remote/build-system.git на какой-либо из будущих стадий развития проекта. Произошло это по тому, что мы не оставляли изменений поддерева build-system в локальном репозитории platform, а получили их путем передачи через оригинальный репозиторий remote/build-system.git:

bash-4.4$
bash-4.4$ git log --graph
*   commit 04a13bac91d1c445994ffc19db8b479d5e644e17 (HEAD -> master)
|\  Merge: ea52eab 0673142
| | Author: user <___@_______>
| | Date:   Thu Nov 1 21:59:45 2018 +0300
| |
| |     Pull changes from master of origin remote/build-system repository.
| |
| |     Merge commit '0673142942ccf53514a276e855a98514952bb713'
| |
| * commit 0673142942ccf53514a276e855a98514952bb713
| | Author: user <___@_______>
| | Date:   Thu Nov 1 21:48:40 2018 +0300
| |
| |     build-system is updated to version 1.0.2 from platform side
| |
* |   commit ea52eabd5910159efabd80adcf522f22bf6a2af2 (origin/master)
|\ \  Merge: 442c9e9 e5c5446
| |/  Author: user <___@_______>
| |   Date:   Thu Nov 1 20:48:05 2018 +0300
| |
| |       Pull changes from master of upstream build-system.git repository.
| |
| |       Merge commit 'e5c5446967599065dc02a269d8fcfc2c1d3c4f65'
| |
| * commit e5c5446967599065dc02a269d8fcfc2c1d3c4f65
| | Author: user <___@_______>
| | Date:   Thu Nov 1 20:26:52 2018 +0300
| |
| |     update build-system version to 1.0.1
| |
* | commit 442c9e94c9890032fb2f3123661345d465e2849f
| | Author: user <___@_______>
| | Date:   Thu Nov 1 20:41:40 2018 +0300
| |
| |     update platform version to 1.0.1
| |
* |   commit 47905bcb80be6f7cb3030513986fad4df548f812
|\ \  Merge: 7fad4be 783c6d5
| |/  Author: user <___@_______>
| |   Date:   Thu Nov 1 20:20:20 2018 +0300
| |
| |       Add 'build-system/' from commit '783c6d5af1100e9665f930c818c861ff011bed19'
| |
| |       git-subtree-dir: build-system
| |       git-subtree-mainline: 7fad4becbd13258216fb95cbe9d987dd33f0be6d
| |       git-subtree-split: 783c6d5af1100e9665f930c818c861ff011bed19
| |       git-subtree-repo: ../../remote/build-system.git/
| |       git-subtree-ref: master
| |
| * commit 783c6d5af1100e9665f930c818c861ff011bed19
|   Author: user <___@_______>
|   Date:   Thu Nov 1 20:16:33 2018 +0300
|
|       init build-system master 1.0.0
|
* commit 7fad4becbd13258216fb95cbe9d987dd33f0be6d
  Author: user <___@_______>
  Date:   Thu Nov 1 20:16:33 2018 +0300

      init platform master 1.0.0
bash-4.4$

На данном этапе, ни в коем случае нельзя забывать о том, что мы должны поставить произошедшие изменения из локальной копии в upstream-репозиторий remote/platform.git.

bash-4.4$
bash-4.4$ git status
On branch master
Your branch is ahead of 'origin/master' by 2 commits.
  (use "git push" to publish your local commits)

nothing to commit, working tree clean
bash-4.4$
bash-4.4$ git push
Enumerating objects: 9, done.
Counting objects: 100% (8/8), done.
Delta compression using up to 4 threads
Compressing objects: 100% (3/3), done.
Writing objects: 100% (5/5), 600 bytes | 600.00 KiB/s, done.
Total 5 (delta 0), reused 0 (delta 0)
To ../../remote/platform.git
   ea52eab..04a13ba  master -> master
bash-4.4$

В противном случае, когда пользователи самостоятельно выполнят команду 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-format-patch

Если, в своей работе, мы используем git-subtre, основными нашими помощниками будут команды:

  git log -- . ":(exclude)build-system"
  git log -- build-system

которые позволяют видеть изменения, либо основного репозитория, либо поддерева соответственно.

А вот утилитой git-format-patch следует пользоваться осторожно.

Так, например, если мы попытаемся сформировать серию patch-файлов от состояния ea52eabd5910159efabd80adcf522f22bf6a2af2, с помощью утилиты git-format-patch:

bash-4.4$
bash-4.4$ git format-patch ea52eabd5910159efabd80adcf522f22bf6a2af2 --stdout
From 0673142942ccf53514a276e855a98514952bb713 Mon Sep 17 00:00:00 2001
From: user <___@_______>
Date: Thu, 1 Nov 2018 21:48:40 +0300
Subject: [PATCH] build-system is updated to version 1.0.2 from platform side

---
 README | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/README b/README
index 629b3f4..4fbbbaf 100644
--- a/README
+++ b/README
@@ -1,3 +1,3 @@

-[master] build-system 1.0.1
+[master] build-system 1.0.2

--
2.19.1

bash-4.4$

мы увидим, что разность состояний файла build-system/README рассматривается относительно каталога build-system/, а не от корня дерева исходного кода:

--- a/README
+++ b/README

В это же время, рассчитав разность между состояниями того же файла с помощью команды git-diff:

bash-4.4$
bash-4.4$ git diff ea52eabd5910159efabd80adcf522f22bf6a2af2 04a13bac91d1c445994ffc19db8b479d5e644e17
diff --git a/build-system/README b/build-system/README
index 629b3f4..4fbbbaf 100644
--- a/build-system/README
+++ b/build-system/README
@@ -1,3 +1,3 @@

-[master] build-system 1.0.1
+[master] build-system 1.0.2

bash-4.4$

мы получим правильный абсолютный путь в patch-файле:

--- a/build-system/README
+++ b/build-system/README

Параметры команды git-diff(1), в предылущем примере, мы определили исходя из списка изменений, сделанных в основном репозитории platform.

Политики

Наличие подпроектов накладывает дополнительные требования к CM-политике и требует введения некоторых правил, соблюдение которых всеми участниками проекта позволит избежать конфликтов и непредвиденных затрат времени. Самыми простыми из этих правил могут быть, например, следующие:

  • создавать новые ветки в upstream-репозиториях подпроектов имеет ограниченный круг разработчиков;
  • никто не должен иметь возможности изменять код upstream-репозитория, если он подключен к основному проекту по тэгу.

Для того, чтобы рассмотреть подобные ограничения нам необходимо несколько доработать содержимое наших тестовых репозиториев.

Зафиксируем, для начала, текуще состояние upstream-репозитория remote/build-system.git:

bash-4.4$
bash-4.4$ cd owner/build-system/
bash-4.4$ cat README

[master] build-system 1.0.2

bash-4.4$

Допустим теперь, что автор upstream-репозитория remote/build-system.git решил внести в проект изменения, которые, согласно его версионной политике, должны повысить minor компонент текущей версии 1.0.2. Для этого ему необходимо создать новую ветку проекта с именем build-system-1.1.x и далее, по мере разработки, фиксировать состояния тегами: 1.1.0, 1.1.1, 1.1.2, и так далее.

Создадим новую ветку:

bash-4.4$
bash-4.4$ git checkout -b build-system-1.1.x
Switched to a new branch 'build-system-1.1.x'
bash-4.4$ git branch
* build-system-1.1.x
  master
bash-4.4$

Повысим версию, записанную в файле README до версии 1.1.0:

bash-4.4$
bash-4.4$ vim README
bash-4.4$ cat README

[master] build-system 1.1.0

bash-4.4$
bash-4.4$ git commit -a -m "Move on to developing 1.1.x functionality"
[build-system-1.1.x f6d79c1] Move on to developing 1.1.x functionality
 1 file changed, 1 insertion(+), 1 deletion(-)
bash-4.4$

и внесем изменения в upstream-репозиторий remote/build-system.git:

bash-4.4$
bash-4.4$ git push --set-upstream origin build-system-1.1.x
Enumerating objects: 5, done.
Counting objects: 100% (5/5), done.
Writing objects: 100% (3/3), 280 bytes | 280.00 KiB/s, done.
Total 3 (delta 0), reused 0 (delta 0)
To ../../remote/build-system.git
 * [new branch]      build-system-1.1.x -> build-system-1.1.x
Branch 'build-system-1.1.x' set up to track remote branch 'build-system-1.1.x' from 'origin'.
bash-4.4$

Теперь допустим, что, по мере разработки, мы продвинулись до версии 1.1.1:

bash-4.4$
bash-4.4$ vim README
bash-4.4$ cat README

[master] build-system 1.1.1

bash-4.4$
bash-4.4$ git commit -a -m "Update build-system version to 1.1.1"
[build-system-1.1.x f9544a4] Update build-system version to 1.1.1
 1 file changed, 1 insertion(+), 1 deletion(-)
bash-4.4$
bash-4.4$ git push
Enumerating objects: 5, done.
Counting objects: 100% (5/5), done.
Writing objects: 100% (3/3), 276 bytes | 276.00 KiB/s, done.
Total 3 (delta 0), reused 0 (delta 0)
To ../../remote/build-system.git
   f6d79c1..f9544a4  build-system-1.1.x -> build-system-1.1.x
bash-4.4$

и зафиксировали данное состояние тэгом:

bash-4.4$
bash-4.4$ git tag -a 1.1.1 -m "Created tag for release (version 1.1.1)"
bash-4.4$ git push origin 1.1.1
Enumerating objects: 1, done.
Counting objects: 100% (1/1), done.
Writing objects: 100% (1/1), 170 bytes | 170.00 KiB/s, done.
Total 1 (delta 0), reused 0 (delta 0)
To ../../remote/build-system.git
 * [new tag]         1.1.1 -> 1.1.1
bash-4.4$

После этого в upstream-репозитории remote/build-system.git мы можем наблюдать следующие ссылки:

bash-4.4$
bash-4.4$ cd remote/build-system.git/
bash-4.4$ tree refs
refs
├── heads
│   ├── build-system-1.1.x
│   └── master
└── tags
    └── 1.1.1

2 directories, 3 files

bash-4.4$

Тем временем, разработчики проекта platform получили задачу стабилизировать сборку с использованием новой версии build-system. Естественно, для выполнения поставленной задачи, они создали новую ветку, например, platform-1.0.2:

bash-4.4$
bash-4.4$ cd user/platform/
bash-4.4$ git branch
* master
bash-4.4$ git pull
Already up to date.
bash-4.4$
bash-4.4$ git checkout -b platform-1.0.2
Switched to a new branch 'platform-1.0.2'
bash-4.4$ vim README
bash-4.4$ cat README

[master] platform 1.0.2

bash-4.4$ git commit -a -m "Сreated platform-1.0.2 branch for the transition to the system 1.1.1"
[platform-1.0.2 00a1250] Сreated platform-1.0.2 branch for the transition to the system 1.1.1
 1 file changed, 1 insertion(+), 1 deletion(-)
bash-4.4$

И подключили к ней, в качестве поддерева, уже не мастер-ветку репозитория remote/build-system.git, а тэг 1.1.1:

bash-4.4$
bash-4.4$ git rm -rf build-system/
rm 'build-system/README'
bash-4.4$ git commit -a -m "Removed subtre based on build-system/master"
[platform-1.0.2 7db0f54] Removed subtre based on build-system/master
 1 file changed, 3 deletions(-)
 delete mode 100644 build-system/README
bash-4.4$
bash-4.4$
bash-4.4$ git push --set-upstream origin platform-1.0.2
Enumerating objects: 7, done.
Counting objects: 100% (7/7), done.
Delta compression using up to 4 threads
Compressing objects: 100% (3/3), done.
Writing objects: 100% (5/5), 550 bytes | 550.00 KiB/s, done.
Total 5 (delta 0), reused 0 (delta 0)
To ../../remote/platform.git/
 * [new branch]      platform-1.0.2 -> platform-1.0.2
Branch 'platform-1.0.2' set up to track remote branch 'platform-1.0.2' from 'origin'.
bash-4.4$
bash-4.4$
bash-4.4$ git subtree add --prefix=build-system ../../remote/build-system.git/ 1.1.1
git fetch ../../remote/build-system.git/ 1.1.1
remote: Enumerating objects: 9, done.
remote: Counting objects: 100% (9/9), done.
remote: Compressing objects: 100% (3/3), done.
remote: Total 7 (delta 0), reused 0 (delta 0)
Unpacking objects: 100% (7/7), done.
From ../../remote/build-system
 * tag               1.1.1      -> FETCH_HEAD
Added dir 'build-system'
bash-4.4$

Итак мы имеем новое состояние подпроекта build-system и должны внести эти изменения в upstream-репозиторий remote/platform.git:

bash-4.4$
bash-4.4$ git push
Enumerating objects: 12, done.
Counting objects: 100% (12/12), done.
Delta compression using up to 4 threads
Compressing objects: 100% (4/4), done.
Writing objects: 100% (8/8), 889 bytes | 889.00 KiB/s, done.
Total 8 (delta 0), reused 0 (delta 0)
To ../../remote/platform.git/
   7db0f54..6f1a50e  platform-1.0.2 -> platform-1.0.2
bash-4.4$

Проверим историю:

bash-4.4$
bash-4.4$ git log -3 --graph
*   commit 6f1a50e249e01f69c54f343b65747d28abc6456d (HEAD -> platform-1.0.2, origin/platform-1.0.2)
|\  Merge: 7db0f54 f9544a4
| | Author: user <___@_______>
| | Date:   Fri Nov 2 18:24:54 2018 +0300
| |
| |     Add 'build-system/' from commit 'f045926542e9f685034545a45317093383fddf99'
| |
| |     git-subtree-dir: build-system
| |     git-subtree-mainline: 7db0f5452e67086dc4e381a0ccb14f25d48ecf0b
| |     git-subtree-split: f045926542e9f685034545a45317093383fddf99
| |     git-subtree-repo: ../../remote/build-system.git/
| |     git-subtree-ref: 1.1.1
| |
| * commit f9544a4cc2650a83b96f400fdfc95ba64a38ec6e
| | Author: user <___@_______>
| | Date:   Fri Nov 2 17:59:43 2018 +0300
| |
| |     Update build-system version to 1.1.1
| |
| * commit f6d79c12ada29438454739fe6f6db9592d413be2
| | Author: user <___@_______>
| | Date:   Fri Nov 2 17:54:35 2018 +0300
| |
| |     Move on to developing 1.1.x functionality
bash-4.4$

Мы действительно подключили нужный нам тэг и, теперь задача CM-инженера состоит в том, чтобы разработчики проекта platform, работающие на ветке platform-1.0.2 даже не пытались править build-system. Это было бы не логично и, более того, так как поддерево build-system ссылается на тэг, совершенно недопустимо.

Для того, чтобы запретить изменения в upstream-репозитории со стороны поддерева build-system проекта platform, необходимо разработать pre-receive хук и поместить его в файл

  remote/build-system.git/hooks/pre-receive

Займемся созданием простого скрипта, запрещающего коммиты в том случае, если совершается попытка изменить состояние тэга:

#!/bin/sh

zero_commit="0000000000000000000000000000000000000000"

LC_COLLATE='C'

allowed_users=(habr habrahabr)

while read oldrev newrev refname; do

  #
  # git-subtree(1) push имеет в своем распоряжении только имя ссылки,
  # которую вы пытаетесь изменить.  Поэтому нам надо проверить два
  # факта:
  #
  #   1) не пытаетесь ли вы создать новую ветку, а если пытаетесь, то
  #   2) не яляется ли это имя фактической ссылкой на tag, но так как
  #      имя задано вами без указания полного пути, то есть, например,
  #      не 'refs/tags/1.1.1', а '1.1.1', утилита git-subtree(1)
  #      могла ошибиться и передать его как 'refs/heads/1.1.1',
  #      то есть как имя ветки.
  # :
  if [[ $oldrev == $zero_commit && $refname =~ ^refs/heads/ ]]; then
    refbase=$(basename $refname)
    refpath=$(git show-ref $refbase | cut -f2 -d' ')

    if [[ $refpath =~ ^refs/tags/.*$ ]]; then
      echo ""
      echo "ERROR: Trying to change TAG named as '$refpath'."
      echo ""
      exit 1
    fi

    if [[ ! ${allowed_users[*]} =~ $USER ]]; then
      echo ""
      echo "ERROR: Trying to create NEW BRANCH with name '$refname'."
      echo ""
      exit 1
    fi
  fi
done

exit 0

Данный скрипт требует пояснений. Наверное странно видеть, что сначала мы проверяем, не является ли ссылка веткой, а затем снова пытаемся проверить, не представляет ли она, фактически, тэг. Все дело в том, что функция, реализующая команду:

  git subtree push --prefix=<subdir> <remote> <ref>

не анализирует параметр <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:

bash-4.4$
bash-4.4$ cd user/platform/
bash-4.4$ vim build-system/README 
bash-4.4$ cat build-system/README

[master] build-system 1.1.1

Try to change.

bash-4.4$
bash-4.4$ git commit -a -m "Try to change the tag of build-system"
[platform-1.0.2 34e7970] Try to change the tag of build-system
 1 file changed, 2 insertions(+)
bash-4.4$

и попробуем доставить их в upstream-репозиторий remote/build-system.git.

К сожалению ...

Ссылка на ревизию upstream-репозитория является обязательным параметром команды

  git subtree push --prefix=<subdir> <remote> <ref>

Поэтому, для того чтобы вспомнить, каким образом мы подключали поддерево, нам будет необходимо уточнить имя сылки с помощью команды:

bash-4.4$ git subtree -d --list
Looking for externals...

Commit: 6f1a50e249e01f69c54f343b65747d28abc6456d
build-system ../../remote/build-system.git/ tag 1.1.1 f045926542e9f685034545a45317093383fddf99
bash-4.4$

Итак, вспомнив имя тэга, мы наконец можем попробовать доставить наши изменения в upstream-репозиторий remote/build-system.git:

bash-4.4$
bash-4.4$ git subtree push --prefix=build-system ../../remote/build-system.git/ 1.1.1
git push using:  ../../remote/build-system.git/ 1.1.1
Enumerating objects: 5, done.
Counting objects: 100% (5/5), done.
Writing objects: 100% (3/3), 293 bytes | 293.00 KiB/s, done.
Total 3 (delta 0), reused 0 (delta 0)
remote:
remote: ERROR: Trying to change TAG named as 'refs/tags/1.1.1'.
remote:
To ../../remote/build-system.git/
 ! [remote rejected] c3a7333aaa818a7d7a0d501d4b69db1c6a01d40f -> 1.1.1 (pre-receive hook declined)
error: failed to push some refs to '../../remote/build-system.git/'
bash-4.4$

Поскольку, созданный заранее, хук pre-receive отработал правильно, нам не удалось доставить наши изменения. Вместо этого мы получили сообщение об ошибке:

remote:
remote: ERROR: Trying to change TAG named as 'refs/tags/1.1.1'.
remote:

и нам, так или иначе, придется откатить, сделанные нами, изменения:

bash-4.4$
bash-4.4$ git reset --hard HEAD^
HEAD is now at 6f1a50e Add 'build-system/' from commit 'f045926542e9f685034545a45317093383fddf99'
bash-4.4$

вернув, тем самым, предыдущее состояние ветки platform-1.0.2:

bash-4.4$
bash-4.4$ git log -3 --graph
*   commit 6f1a50e249e01f69c54f343b65747d28abc6456d (HEAD -> platform-1.0.2, origin/platform-1.0.2)
|\  Merge: 7db0f54 f9544a4
| | Author: user <___@_______>
| | Date:   Fri Nov 2 18:24:54 2018 +0300
| |
| |     Add 'build-system/' from commit 'f045926542e9f685034545a45317093383fddf99'
| |
| |     git-subtree-dir: build-system
| |     git-subtree-mainline: 7db0f5452e67086dc4e381a0ccb14f25d48ecf0b
| |     git-subtree-split: f045926542e9f685034545a45317093383fddf99
| |     git-subtree-repo: ../../remote/build-system.git/
| |     git-subtree-ref: 1.1.1
| |
| * commit f9544a4cc2650a83b96f400fdfc95ba64a38ec6e
| | Author: user <___@_______>
| | Date:   Fri Nov 2 17:59:43 2018 +0300
| |
| |     Update build-system version to 1.1.1
| |
| * commit f6d79c12ada29438454739fe6f6db9592d413be2
| | Author: user <___@_______>
| | Date:   Fri Nov 2 17:54:35 2018 +0300
| |
| |     Move on to developing 1.1.x functionality
bash-4.4$

Выводы

В отличие от git-subrepo, где история основного репозитория остается линейной, а подпроекты подключаются как squashed-коммиты, git-subtree(1) представляется более мощным средством, позволяющим реализовать практически любые сценарии работы.

С точки зрения доработки существующих средств под нужды собственного проекта или просто с целью их усовершенствования, необходимо учитывать следующее. Проект git-subrepo развивается и команда разработчиков открыта для предложений. С другой стороны, git-subtree более прост в реализации и любые его изменения не составят большого труда, однако, команда разработчиков Git с трудом принимает предложения, поскольку ее практически не интересует совершенствование данного инструмента и, с наибольшей вероятностью, вам придется накладывать собственные изменения каждый раз при переходе на новую версию Git.

ЛИТЕРАТУРА:

  1. Git – Subtree Merging
  2. Git Subtree
  3. githooks(5) Manual Page
  4. git-receive-pack(1) Manual Page
  5. Git – githooks Documentation
  6. Examples for Pre-receive hooks
  7. Git Subrepo sources
  8. Mastering Git subtrees
  9. Mastering Git submodules