Проект git-subrepo существует достаточно давно, однако упоминаний о нем незаслуженно мало. Автором git-subrepo является Ingy dot Net.
Если посмотреть на историю комитов master-ветки проекта, то может показаться, что проект остановился в развитии 2 года назад. Однако работы над проектом ведутся и хочется надеяться, что скоро будет выпущена версия 0.4.0.
Важным свойством данного средства является то, что на стороне пользователя нет необходимости устанавливать git-subrepo до тех пор, пока пользователь не решит делать комиты в upstream-репозитории подпроектов. Кроме того, пользователь получает полностью готовое и настроенное дерево исходного кода в момент копирования основного репозитория посредством стандартной команды git-clone(1).
Выбирая средства поддержки подмодулей/поддеревьев/подпроектов основного репозитория-контейнера, разработчик прежде всего определяет спектр возможностей, которое предоставляет тот или иной механизм и дает ответы на следующие вопросы:
- необходимо ли хранить полную историю подпроекта в основном репозитории или достаточно squashed коммитов;
- нужна ли возможность поставки изменений из подпроекта в upstream-репозиторий поддерева;
- существует ли необходимость подключать фиксированные теги upstream-репозитория подпроекта или достаточно возможности подключения веток;
- будет ли необходимо вдальнейшем удалять как сами подпроекты так и, ставшую не нужной, часть истории этих подпроектов;
- должен ли будет пользователь предпринимать какие-либо действия для ручной настройки подпроектов после клонирования репозитория основного проекта;
- насколько трудоемким окажется вопрос анализа истории подключения подпроектов и конкретных ревизий, от которых берет начало подпроект;
- как повлияет то или иное средство на политику управления конфигурациями (Source Configuration Management) и, на сколько данное средство усложнит каждодневный труд инженеров.
Разумеется, данный перечень вопросов не может отразить всю полноту входных параметров, необходимых для правильного выбора, но для предварительного рассмотрения существующих средств, он вполне достаточен и, мы, говоря о проекте git-subrepo, призываем читателя рассматривать данный проект именно с этих позиций.
Инсталляция git-subrepo
Пакет git-subrepo, на стороне разработчика, может быть установлен как локально, в своем домашнем каталоге, так и на системном уровне.
В первом случае, достаточно клонировать репозиторий git-subrepo в нужный каталог, например, ~/bin:
bash-4.4$ cd ~/bin
bash-4.4$ git clone https://github.com/ingydotnet/git-subrepo.git
и настроить переменные окружения
bash-4.4$ vim subrepo-env.sh
#!/bin/sh
export GIT_SUBREPO_ROOT="/home/username/bin/git-subrepo"
export PATH="/home/username/bin/git-subrepo/lib:$PATH"
export MANPATH="/home/username/bin/git-subrepo/man:$MANPATH"
:wq
bash-4.4$ source ./subrepo-env.sh
Если посмотреть переменные, определенные в Make-файле git-subrepo:
# Install variables:
PREFIX ?= /usr/local
INSTALL_LIB ?= $(DESTDIR)$(shell git --exec-path)
INSTALL_EXT ?= $(INSTALL_LIB)/$(NAME).d
INSTALL_MAN1 ?= $(DESTDIR)$(PREFIX)/share/man/man1
то легко выяснить, что на системном уровне git-subrepo устанавливается в каталог, где располагается Git:
bash-4.4$
bash-4.4$ git --exec-path
/usr/libexec/git-core
bash-4.4$
Таким образом команда для инсталляции git-subrepo может выглядеть, например, следующим образом:
bash-4.4$ make PREFIX=/usr install
Наличие переменной DESTDIR позволяет без дополнительных усилий (разумеется, если мы находимся не в cross-окружении) сделать пакет для любого дистрибутива Linux, что может быть полезно для DevOps инженеров.
Инсталлируем git-subrepo от имени суперпользователя:
bash-4.4$
bash-4.4$ cd git-subrepo/
bash-4.4$ make PREFIX=/usr install
install -C -d -m 0755 /usr/libexec/git-core/
install -C -m 0755 lib/git-subrepo /usr/libexec/git-core/
install -C -d -m 0755 /usr/libexec/git-core/git-subrepo.d/
install -C -m 0755 lib/git-subrepo.d/help-functions.bash lib/git-subrepo.d/bash+.bash /usr/libexec/git-core/git-subrepo.d/
install -C -d -m 0755 /usr/share/man/man1/
install -C -m 0644 man/man1/git-subrepo.1 /usr/share/man/man1/
bash-4.4$
Для анализа возможностей git-subrepo нам понадобится простое тестовое окружение, где мы сможем воспроизвести стандартные сценарии работы.
Тестовое окружение
Создадим три каталога owner, remote, user, в которых разместим модели upstream- и локальных репозиториев разработчика и пользователя.
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$
bash-4.4$ ./_init.sh
bash-4.4$
Здесь,
— | рабочий каталог автора проектов ; | |
— | каталог представляющий сервер автора проектов, на котором располагаются upstream-репозитории основного проекта platform.git и подпроекта build-system.git ; | |
— | рабочий каталог пользователя или участника команды разработчиков. |
Автор проекта и пользователи имеют собственные копии upstream-репозиториев на своих машинах, представленные в нашем примере в каталогах owner и user соответствено.
Задача автора состоит в том, чтобы включив подпроект builld-system в основное дерево platform обеспечить пользователям и участникам проекта следующие возможности:
- клонировать основной репозиторий с включенным в его состав подпроектом build-system и при этом не заботиться о настройке версиий или ревизий. То есть каждой ветке репозитория platform должна соответствовать определенная ревизия определенной ветки репозитория build-system и пользователь должен получать настроенное дерево исходников за одну операцию git-clone(1), без каких-либо дополнительных действий.
- поставлять свои изменения в upstream репозитории проекта, как в основной, так и во вспомогательный.
- получать изменения, сделанные другими участниками проекта или пользователями, разумеется если они имеют соответствующие права.
Рассмотрим действия автора, которые он должен осуществить для реализации данных требований.
Подключение субпроекта
Для подключения нового поддерева следует воспользоваться командой git subrepo clone, которая по своему назначению похожа на команду git-clone(1). Обязательным параметром команды служит URL удаленного репозитория. Также можно указать каталог в котором будет располагаться подпроект и ветку удаленного репозитория. Мы будем работать с master-ветками, поэтому, в нашем примере, мы опускаем ненужные параметры комманд.
Итак, автор проекта, на своей рабочей машине, может подключить подпроект build-system с помощью команды git subrepo clone ../../remote/build-system.git/ build-system:
bash-4.4$
bash-4.4$ cd owner/platform/
bash-4.4$ git subrepo clone ../../remote/build-system.git/ build-system
Subrepo '../../remote/build-system.git' (master) cloned into 'build-system'.
bash-4.4$
Рассмотрим, какие изменения произошли в локальном репозитории platform:
bash-4.4$
bash-4.4$ git status
On branch master
Your branch is ahead of 'origin/master' by 1 commit.
(use "git push" to publish your local commits)
nothing to commit, working tree clean
bash-4.4$
bash-4.4$
bash-4.4$ git subrepo status
1 subrepo:
Git subrepo 'build-system':
Remote URL: ../../remote/build-system.git
Upstream Ref: b2f5079
Tracking Branch: master
Pulled Commit: b2f5079
Pull Parent: b5e76a7
bash-4.4$
История подпроекта build-system не поставляется в основное дерево, мы имеем лишь один squashed-комит, который сопровождается справочной информацией. Данная информация поступает под версионный контроль в файле ./build-system/.gitrepo/config:
[subrepo]
remote = ../../remote/build-system.git
branch = master
commit = b2f507918f2821cb7dd90c33223ed5cc3c9922a2
parent = b5e76a713f895565b06ee3ccfa29f19131ba06dd
method = merge
cmdver = 0.4.1
Информацию о подпроектах можно получать с помощью команды git subrepo config, например узнать ревизию upstream-проекта remote/build-system.git, которая только что пришла в основной репозиторий, можно в помощью команды:
bash-4.4$
bash-4.4$ git subrepo config build-system commit
Subrepo 'build-system' option 'commit' has value 'b2f507918f2821cb7dd90c33223ed5cc3c9922a2'.
bash-4.4$
Следует упомянуть о том, что оригинальный пакет git-subrepo сохраняет информацию о подпроектах не в файле .gitrepo/config, а в файле .gitrepo. Если вы предпочитаете хранить информацию в скрытом подкаталоге, то можете выбрать ветку git-subrepo-0.3.2 или git-subrepo-0.4.1 дочернего репозитория git-subrepo.
Итак, мы получили последнюю версию master-ветки upstream-репозитория remote/build-system.git и поместили ее в подкаталог build-system основного проекта platform.
Для поставки этих изменений в upstream-репозиторий remote/platform.git, автору необходимо выполнить команду git push:
bash-4.4$
bash-4.4$ git push
Enumerating objects: 7, done.
Counting objects: 100% (7/7), done.
Delta compression using up to 4 threads
Compressing objects: 100% (4/4), done.
Writing objects: 100% (6/6), 849 bytes | 849.00 KiB/s, done.
Total 6 (delta 0), reused 0 (delta 0)
To /home/prog/0.4.1/remote/platform.git
<font color="#8b0000">b5e76a7..6b831e4</font> master -> master
bash-4.4$
Более подробную информацию о командах git subrepo можно получить из файла ReadMe.pod или в командной строке
bash-4.4$ git subrepo help <command>
например:
bash-4.4$ git subrepo help clone
Рассмотрим теперь все происходящее со стороны пользователя.
Получение кода пользователями
На данный момент, когда пользователь еще не получил обновления upstream-репозитория platform.git, его копия содержит один файл README
bash-4.4$
bash-4.4$ cd user/platform/
bash-4.4$ ls
README
bash-4.4$
содержащий одну строку:
bash-4.4$
bash-4.4$ cat README
[master] platform 1.0.0
bash-4.4$
После снятия изменений upstream-репозитория
bash-4.4$
bash-4.4$ git pull
remote: Enumerating objects: 7, done.
remote: Counting objects: 100% (7/7), done.
remote: Compressing objects: 100% (4/4), done.
remote: Total 6 (delta 0), reused 0 (delta 0)
Unpacking objects: 100% (6/6), done.
From /home/prog/0.4.1/remote/platform
b5e76a7..6b831e4 master -> origin/master
Updating <font color="#8b0000">b5e76a7..6b831e4</font>
Fast-forward
build-system/.gitrepo/config | 12 ++++++++++++
build-system/README | 3 +++
2 files changed, 15 insertions(+)
create mode 100644 build-system/.gitrepo/config
create mode 100644 build-system/README
bash-4.4$
пользователь будет иметь в своем распоряжении код подпроекта build-system именно той ревизии, которую определил автор проекта. Пользователь может в любой момент уточнить текущую ревизию с помощью команды config:
bash-4.4$
bash-4.4$ git subrepo config build-system/ commit
Subrepo 'build-system' option 'commit' has value 'b2f507918f2821cb7dd90c33223ed5cc3c9922a2'.
bash-4.4$
Примечательно то, что у пользователя нет необходимости осуществлять дополнительные настройки и он может положиться на то, что автор проекта поставил ему именно ту ревизию build-system, которая необходима для работы текущей версии platform.
Именно этого добивался автор проекта.
Поставка изменений в upstream-проект
Допустим теперь, что наш пользователь является участником проекта и ему разрешено поставлять изменения не только в upstream-репозиторий remote/platform.git, но еще и в upstream-репозиторий подпроекта remote/build-system.git.
Тогда, если пользователь сделает изменения:
bash-4.4$
bash-4.4$ cd build-system/
bash-4.4$ vim README
bash-4.4$ cat README
[master] build-system 1.0.1
bash-4.4$
bash-4.4$ git commit -a -m "update BS version to 1.0.1"
[master d30b001] update BS version to 1.0.1
1 file changed, 1 insertion(+), 1 deletion(-)
bash-4.4$
bash-4.4$ cd ..
bash-4.4$ git log
commit d30b001286b08708f5c30c1f5346a90e4339f969 (HEAD -> master)
Author: user <___@_____>
Date: Tue Oct 30 10:49:32 2018 +0300
update BS version to 1.0.1
. . .
bash-4.4$
он сможет поставить их в upstream-репозитории следующим образом:
bash-4.4$
bash-4.4$ git subrepo push build-system/
Subrepo 'build-system' pushed to '../../remote/build-system.git' (master).
bash-4.4$
Поскольку файлы конфигурации подпроектов .gitrepo/config хранятся под версионным контролем, пользователю необходимо отослать изменения статуса подпроекта в upstream-репозиторий основного проекта remote/platform.git.
То есть пользователь не должен забывать о проверке статуса локального репозитория и вовремя выполнять команду git-push(1).
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: 14, done.
Counting objects: 100% (14/14), done.
Delta compression using up to 4 threads
Compressing objects: 100% (7/7), done.
Writing objects: 100% (9/9), 992 bytes | 992.00 KiB/s, done.
Total 9 (delta 1), reused 0 (delta 0)
To /home/prog/0.4.1/remote/platform.git
d00be9f..deccb66 master -> master
bash-4.4$
В противном случае, при последующем снятии изменений upstream-репозитория, он получит merge-конфликт.
Разумеется, здесь нет ни чего необычного, однако, после выполнения команды git subrepo push ..., легко забыть о состоянии локальной копии основного репозитория.
Непосредственная работа с upstream-репозиторием
Рассмотрим теперь, что произошло в upstream-репозитории remote/build-system.git
bash-4.4$
bash-4.4$ cd owner/build-system/
bash-4.4$
bash-4.4$ git pull
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 /home/prog/0.4.1/remote/build-system
b2f5079..d229920 master -> origin/master
Updating b2f5079..d229920
Fast-forward
README | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
bash-4.4$
bash-4.4$ git log
commit d229920c7de34405bc7b8d47f36d420987687908 (HEAD -> master, origin/master)
Author: user <___@_____>
Date: Tue Oct 30 10:49:32 2018 +0300
update BS version to 1.0.1
commit b2f507918f2821cb7dd90c33223ed5cc3c9922a2
Author: user <___@_____>
Date: Tue Oct 30 10:05:30 2018 +0300
init build-system master 1.0.0
bash-4.4$
То есть автор проекта получил изменения, внесенные участником проекта.
Разумеется, автор может вносить изменения непосредственно в upstream-репозиторий проекта build-system:
bash-4.4$
bash-4.4$ cd owner/build-system/
bash-4.4$
bash-4.4$ vim README
bash-4.4$ cat README
[master] build-system 1.0.2
bash-4.4$ git commit -a -m "update build-system version to 1.0.2"
[master 8255f59] update build-system version to 1.0.2
1 file changed, 1 insertion(+), 1 deletion(-)
bash-4.4$ git push
Enumerating objects: 5, done.
Counting objects: 100% (5/5), done.
Writing objects: 100% (3/3), 281 bytes | 281.00 KiB/s, done.
Total 3 (delta 0), reused 0 (delta 0)
To /home/prog/0.4.1/remote/build-system.git
d229920..8255f59 master -> master
bash-4.4$
И все участники, а также пользователи основного проекта смогут получать эти изменения с помощью команды git subrepo pull
bash-4.4$
bash-4.4$ cd owner/platform/
bash-4.4$
bash-4.4$ git subrepo pull build-system/
Subrepo 'build-system' pulled from '../../remote/build-system.git' (master).
bash-4.4$ git status
On branch master
Your branch is ahead of 'origin/master' by 1 commit.
(use "git push" to publish your local commits)
nothing to commit, working tree clean
bash-4.4$ git push
Enumerating objects: 11, done.
Counting objects: 100% (11/11), done.
Delta compression using up to 4 threads
Compressing objects: 100% (4/4), done.
Writing objects: 100% (6/6), 670 bytes | 670.00 KiB/s, done.
Total 6 (delta 1), reused 0 (delta 0)
To /home/prog/0.4.1/remote/platform.git
6b831e4..b6f4a7b master -> master
bash-4.4$
Выводы
Если у разработчика нет необходимости хранить истории подпроектов в основном репозитории и, при поставке кода он оперирует ветками, а не фиксированными тегами, то git-subrepo вполне подходит для организации повседневной работы.
Условно, к числу недостатков git-subrepo можно отнести то обстоятельство, что операция git subrepo clone возможна только по отношению к веткам подпроекта. Иными словами, пользователь не может подключить подпроект ссылаясь на его фиксированный тэг или определенную ревизию, то есть команды типа
bash-4.4$ git subrepo clone ../../remote/build-system.git build-system -t 1.0.1
bash-4.4$ git subrepo clone ../../remote/build-system.git build-system 7f5d1113eb0bc6
не допустимы.
ЛИТЕРАТУРА:
Комментарии (25)
gecube
01.11.2018 19:34+1Я сейчас исследую возможности git'а для построения сложных проектов.
Пока что осилил submodules. Они достаточно удобны, когда не нужно напрямую менять содержимое субмодулей. А ещё они нативной поддерживаются средствами сборки кода — gitlab-ci & co.
Интересно узнать альтернативные подходы, но чтобы удовлетворить это требование + чтобы было этими подходами удобноalex1t
02.11.2018 01:05+1Пользуемся сабмодулями достаточно активно для общих подпроектов сервисов. Всё можно менять и из основного репозитория. Точнее для работы с гитом сабмодуля — входишь в него и делаешь всё как и везде. Даже VSCode поддерживает работу с сабмодулями
gdt
02.11.2018 08:22В чём преимущество перед сабмодулями?
rcl Автор
02.11.2018 08:45Дело не в преимуществе, а в целесообразности. Главное отличие состоит в том, что при использовании submodules, пользователь снимает репозиторий-контейнер практически не настроенным и ему надо проделывать дополнительные мероприятия по согласованию ревизий подмодулей и репозитория-контейнера (так или иначе, это именно так, и автор проекта должен обеспечить пользователя соответствующими инструкциями, будь то простая инструкция по клонированию `git clone --recurse-submodules ...' или другое напоминание). Если же мы говорим о git-subrepo и git-subtree, то здесь, копируя репозиторий-контейнер, пользователь получает полностью настроенное дерево за одну операцию git-clone(1). Все для него уже сделано автором. А в остальном, все зависит от задач, которые решает автор и от его политики SCM (Source Configuration Management).
gecube
02.11.2018 10:08+1Т.е. правильно ли я понимаю, что раз тут все проделывается за одну команду git-clone, то интеграция с системами сборки получается прозрачная и автоматическая?
rcl Автор
02.11.2018 11:15Так точно. Автор должен все сделать так, чтобы для пользователя репозиторй выглядел как монолитная структура, не требующая дополнительных настроек.
rcl Автор
02.11.2018 09:15Представим, например, следующую задачу. Ваш проект состоит из нескольких подпроектов/модулей и, допустим, один модуль представляет исходный код межмодульных интерфейсов. Набор интерфейсов, разрабатываемый отдельной командой, должен быть фиксирован для каждой ветки вашего проекта. Более того, никто из других команд разработчиков не должен иметь права изменять код интерфейсов, а наоборот, совершенствовать свой модуль в строгой согласованности с другими.
В этом случае CM инженер решит поставлять в каждую ветку проекта конкретные, фиксированыые ревизии интерфейсного модуля и постарается запретить любые комиты со стороны разработчиков. Только при переходе на новую версию интерфейсного модуля, все остальные команды создадут новые ветки собственных модулей, подключат к ним новую версию интерфейсного модуля и начнут работать над собственным кодом с целью приведения его в соответствие новой версии интерфейсов.
Согласитесь, что при данном подходе интерфейсный модуль лучше всего подключать не как submodule, обвязывая его хуками, а как subtree (а вот нужна ли при этом история интерфейсного модуля в общем индексе, это уже другой вопрос).
calg0n
02.11.2018 10:39+1Ну фиг знает. Всё таки надёжнее каждый модуль проекта хранить в отдельной репе с полноценной историей. В основном проекте создаём папочку subreps, добавляем её в gitignore, в subreps клоним полноценные репы.
Andy_U
02.11.2018 11:21Вот только что делать при таком подходе, если у вас несколько веток в проекте и к каждой отдельной ветке нужно подцеплять «свою» ветку из другого проекта. git subtree эту проблему решает на раз. Единственное неудобство — merge между своими ветками нужно делать осторожно, чтобы подпроекты не смержились.
calg0n
02.11.2018 11:24+1А какая разница сколько веток и какой проект? Это ж полноценные репы, цепляйте что хотите.
gdt
02.11.2018 12:06В проекте, над которым я работаю, мы используем submodule и каждая ветка основного проекта связана с конкретной веткой сабмодуля. Меняю ветку — сабмодуль тоже меняется, что я делаю не так?
calg0n
02.11.2018 13:46Это если один проект. А если несколько проектов используют общий функционал? Если у каждого проекта своя репа и этот общий функционал нужно использовать в них. Сабмодули тут не подойдут.
gdt
02.11.2018 14:46Есть как минимум ещё один проект, где используется тот же самый сабмодуль.
calg0n
02.11.2018 15:05А как вы разруливаете конфликты когда несколько разрабов работают над общей репой?
gdt
02.11.2018 15:08Как обычно, не вижу разницы, если честно. Фичи делаются в отдельных ветках, перед merge делаем rebase чтобы свести конфликты к минимуму, если какие-то ломающие изменения — то да, всем придётся править код, но только после принудительного обновления сабмодуля до мастера (т. е. ничего не ломается само по себе).
calg0n
02.11.2018 15:21Насколько я знаю подмодули не работают без корневого репозитория, ветки наследуются от родителя и сабмодули не хранят историю изменений файлов, только хеш-сумму этих изменений. Из-за этого есть проблемы с мержем подмодулей в рамках проекта.
Возможно что-то изменилось с того момента, но насколько я вижу — нет.gdt
02.11.2018 15:39Ну как, у нас сабмодуль — это полноценный репозиторий. В том плане, что если изменения в проекте требуют изменений в сабмодуле — делается отдельная ветка и там и там, два пулл реквеста и т. д. За счёт линейной истории конфликты сведены к минимуму. Грубо говоря, для проекта сабмодуль — это внешний компонент, и конфликт сводится к тому, что есть хэш, в котором реализована фича А, и есть хэш, в котором она не реализована, ничего сложного.
Проблемы с мержем сабмодулей, про которые вы говорите, за полтора года работы над этим проектом встречались раза два.calg0n
02.11.2018 15:53Мы постоянно сталкиваемся с конфликтами в своей общей репе :) Тут зависит от проекта конечно же, но с т.з. полноценного подрепозитория сабмодули нам не подошли.
rcl Автор
02.11.2018 11:22git-subrepo и git-subtree не отменяет возможность ведения upstream-репозиториев как самостоятельных продуктов, а в основном дереве держать либо squashed-комиты, либо полноценные истории.
А на счет папочек в .gitignore все зависит от CM инженера. Либо он настраивает права пользователей и хуки, либо он отдает все на откуп инженерам команды разработчиков.
Товарищ Сухов сказал: «Лучше конечно помучиться.»
Andy_U
А не могли бы Вы сравнить возможности данного расширения с git subtree? Заранее спасибо.
rcl Автор
Обязательно. Я сейчас работаю над описанием внутренностей git-subtree и скоро выдам раезультат.