Git subtree в деталях

    Принимая решение об использовании того или иного средства в собственных проектах, инженеру приходится не только изучать сопроводительную документацию, но и проводить серию экспериментов для того, чтобы избежать потенциальных проблем в будущем. Если же речь идет о 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 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.



    ЛИТЕРАТУРА:


    Share post
    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More
    Ads

    Comments 0

    Only users with full accounts can post comments. Log in, please.