Давным-давно я усвоил, что зависимости должны храниться вместе с кодом проекта. Тогда, при возврате к старой версии кода, гораздо проще восстанавливать окружение.
У моего проекта несколько зависимостей. Бóльшая часть зависимостей живет в гитовых репозиториях. Сам проект тоже живёт в гите.
Одна из используемых нами библиотек часто обновляется. Мы сидим на девелоперской версии, и нередко сами контрибутим в неё код, который требуется нашему проекту. То есть требуется оперативно пропускать наши правки через основной репозиторий этой библиотеки — создавать и поддерживать свой форк по ряду причин совершенно не хочется.
Раньше я просто копировал зависимости в папку проекта, и добавлял к каждой файл VERSION.TXT с её версией. Но, если нужно работать с текущей версией стороннего кода, это неудобно. Да и копировать файлы руками когда есть гит как-то глупо. Хочется найти более современное решение.
Самая разрекламированная и модная фича гита для работы со сторонними репозиториями — git submodules («подмодули»). Естественно, в первую очередь я стал смотреть на неё.
До этого я уже пробовал работать с подмодулями в «домашних» проектах. Пока репозиторием аккуратно пользуется один человек, особых проблем нет. В любом случае — заметно удобнее чем копировать код руками.
Однако, как только я попытался перенести свой опыт на более серьёзный рабочий процесс, выяснилось, что не всё так просто.
Вот с чем мы столкнулись:
От подмодулей пришлось отказаться.
Но нет худа без добра. Знающие люди подсказали, что в гите с давних пор (ещё до 1.5.2) существует альтернативное решение — subtree merge strategy («поддеревья»).
Идея в том, чтобы взять историю коммитов из внешнего проекта, и перенаправить её в поддиректорию внутреннего. При этом используется стандартный гитовый механизм работы со внешними ветками.
Пример из документации: добавляем код из ветки master репозитория Bproject (лежит в /path/to/B) в наш проект в поддиректорию dir-B/.
Нужно обратить внимание на ключ -f у git remote add. Он говорит гиту сразу сделать fetch этого remote-а.
В дальнейшем, изменения в Bproject подтягиваются командой git pull с явным указанием нужной ветки и стратегии объединения:
Если в рабочей копии не добавлен соответствующий remote-у поддерева, в истории основного репозитория не показывается имя ветки, из которой притянуты изменения.
Проблема чисто косметическая, и на работу не влияет. Лечится добавлением этого remote-а в рабочую копию:
В дальнейшем, если появляются новые ветки, можно подтягивать изменения в remote:
Есть ещё пара недостатков:
Работать с поддеревьями значительно удобнее чем с подмодулями. Для неё не нужно переучивать пользователей, её проще автоматизировать. Поддеревья проще поддерживать. Рекомендую.
Кстати, на Гитхабе есть проект, нацеленный на развитие работы с поддеревьями: git-subtree.
Дополнительное чтение:
У моего проекта несколько зависимостей. Бóльшая часть зависимостей живет в гитовых репозиториях. Сам проект тоже живёт в гите.
Одна из используемых нами библиотек часто обновляется. Мы сидим на девелоперской версии, и нередко сами контрибутим в неё код, который требуется нашему проекту. То есть требуется оперативно пропускать наши правки через основной репозиторий этой библиотеки — создавать и поддерживать свой форк по ряду причин совершенно не хочется.
Раньше я просто копировал зависимости в папку проекта, и добавлял к каждой файл 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
Есть ещё пара недостатков:
- Как и в случая с обычным объединением веток, в логах коммитов история поддерева перемешивается с историей основного проекта.
- Сабмитить изменения в проект поддерева значительно сложнее чем с подмодулями. Но это легко обойти, внося изменения в отдельный клон этого проекта.
Работать с поддеревьями значительно удобнее чем с подмодулями. Для неё не нужно переучивать пользователей, её проще автоматизировать. Поддеревья проще поддерживать. Рекомендую.
Кстати, на Гитхабе есть проект, нацеленный на развитие работы с поддеревьями: git-subtree.
Дополнительное чтение: