Pull to refresh

Внешние зависимости в гите: submodule или subtree?

Git *
Давным-давно я усвоил, что зависимости должны храниться вместе с кодом проекта. Тогда, при возврате к старой версии кода, гораздо проще восстанавливать окружение.

У моего проекта несколько зависимостей. Бóльшая часть зависимостей живет в гитовых репозиториях. Сам проект тоже живёт в гите.

Одна из используемых нами библиотек часто обновляется. Мы сидим на девелоперской версии, и нередко сами контрибутим в неё код, который требуется нашему проекту. То есть требуется оперативно пропускать наши правки через основной репозиторий этой библиотеки — создавать и поддерживать свой форк по ряду причин совершенно не хочется.

Раньше я просто копировал зависимости в папку проекта, и добавлял к каждой файл VERSION.TXT с её версией. Но, если нужно работать с текущей версией стороннего кода, это неудобно. Да и копировать файлы руками когда есть гит как-то глупо. Хочется найти более современное решение.

Самая разрекламированная и модная фича гита для работы со сторонними репозиториями — git submodules («подмодули»). Естественно, в первую очередь я стал смотреть на неё.

До этого я уже пробовал работать с подмодулями в «домашних» проектах. Пока репозиторием аккуратно пользуется один человек, особых проблем нет. В любом случае — заметно удобнее чем копировать код руками.

Однако, как только я попытался перенести свой опыт на более серьёзный рабочий процесс, выяснилось, что не всё так просто.

Вот с чем мы столкнулись:
  • Каждый человек, работающий с основным репозиторием должен иметь доступ к репозиторию, из которого взят подмодуль.
  • В общем случае невозможно получить целостную рабочую копию одной командой. Теперь после git checkout нужно делать git submodule update --init.
  • То же относится и некоторым другим командам гита. Например, git archive игнорирует подмодули — больше нельзя одной командой запаковать весь проект в архив.
  • Из проекта верхнего уровня не видно изменений внутри подмодулей и наоборот. Чтобы узнать полного состояние рабочей копии проекта, необходимо запрашивать его для каждого подмодуля и для родительского проекта по отдельности. Без подмодулей достаточно сказать git status в любом месте внутри рабочей копии.
  • После замены корневой директории подмодуля на что-нибудь другое (например другой подмодуль), нужно вручную удалять старую версию во всех рабочих копиях.
  • Команда git submodule не понимает стандартных опций --git-dir и --work-tree. Её можно запускать только из корня рабочей копии. Это затрудняет автоматизацию.
В общем достаточно неприятно. Каждую из проблем можно так или иначе обойти, но проблем слишком много и постоянно появляются новые.

От подмодулей пришлось отказаться.

Но нет худа без добра. Знающие люди подсказали, что в гите с давних пор (ещё до 1.5.2) существует альтернативное решение — subtree merge strategy («поддеревья»).

Идея в том, чтобы взять историю коммитов из внешнего проекта, и перенаправить её в поддиректорию внутреннего. При этом используется стандартный гитовый механизм работы со внешними ветками.

Пример из документации: добавляем код из ветки master репозитория Bproject (лежит в /path/to/B) в наш проект в поддиректорию dir-B/.

$ git remote add -f Bproject /path/to/B
$ git merge -s ours --no-commit Bproject/master
$ git read-tree --prefix=dir-B/ -u Bproject/master
$ git commit -m "Merge B project as our subdirectory"

Нужно обратить внимание на ключ -f у git remote add. Он говорит гиту сразу сделать fetch этого remote-а.

В дальнейшем, изменения в Bproject подтягиваются командой git pull с явным указанием нужной ветки и стратегии объединения:

$ git pull -s subtree Bproject master

Если в рабочей копии не добавлен соответствующий remote-у поддерева, в истории основного репозитория не показывается имя ветки, из которой притянуты изменения.

Проблема чисто косметическая, и на работу не влияет. Лечится добавлением этого remote-а в рабочую копию:

$ git remote add -f Bproject /path/to/B

В дальнейшем, если появляются новые ветки, можно подтягивать изменения в remote:

$ git fetch Bproject

Есть ещё пара недостатков:
  1. Как и в случая с обычным объединением веток, в логах коммитов история поддерева перемешивается с историей основного проекта.
  2. Сабмитить изменения в проект поддерева значительно сложнее чем с подмодулями. Но это легко обойти, внося изменения в отдельный клон этого проекта.
Ни один из перечисленных недостатков не является критическим для нашего рабочего процесса.

Работать с поддеревьями значительно удобнее чем с подмодулями. Для неё не нужно переучивать пользователей, её проще автоматизировать. Поддеревья проще поддерживать. Рекомендую.

Кстати, на Гитхабе есть проект, нацеленный на развитие работы с поддеревьями: git-subtree.

Дополнительное чтение:
  1. Pro Git: Submodules
  2. Pro Git: Subtree Merging
Tags:
Hubs:
Total votes 29: ↑28 and ↓1 +27
Views 48K
Comments Comments 14