0,0
рейтинг
21 ноября 2009 в 13:07

Разработка → Внешние зависимости в гите: 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
Александр Гладыш @agladysh
карма
91,0
рейтинг 0,0
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Реклама

Самое читаемое Разработка

Комментарии (14)

  • +1
    Спасибо, автор
    • +2
      Всегда пожалуйста!
  • +1
    Всем спасибо за карму, перенёс в тематический блог. :-)
  • –1
    0_0 Wow!
  • 0
    Большое спасибо! Про поддеревья не знал, а очень надо было ;)
    • +1
      Рад, что помог!
  • 0
    Как-то вы всё усложняете.

    Есть третий вариант, намного проще первых двух. держите отдельные репозитарии git в корневом каталоге проекта и в каждом из каталогов, содержащих подмодули.

    Если хотите скажем обновить все репозитарии начиная с корневого каталога одной командой, создайте alias gitpull='git pull && cd vendor/plugins/prawnto; git pull'

    Преимущества понятны?
    • –1
      Автор так жалко времени, потраченного на возню с git submodules и с subtree merge strategy, что он не готов принять лежащее на поверхности решение и минусует ;-(
      • +1
        Если я правильно понимаю ситуацию, предложенное Вами решение в действительности мало отличается от использования подмодулей. Кроме того, что подмодули на самом деле попытка «легитимизации» такого подхода.

        Список претензий к подмодулям есть в статье. Ваш подход хоть одну из них снимает?
        • –2
          Мой подход претензии эти игноритует. Поскольку я умею автоматизировать свою работу, то я предпочитаю простой и понятный способ использования git, а когда вижу длинный набор команд, то сокращаю его путём встроенных в ОС средств.

          Скажем, чтобы решить одну из ваших проблем, когда «из проекта верхнего уровня не видно изменений внутри подмодулей и наоборот», задать alias вида

          alias gitstatus='find. -type d -name '.git'|while read i; do cd "$i"/..;git status; done'

          для запуска из корня проекта.
          • 0
            Ну, я ведь не просто так претензии эти написал. Для меня они существенны, и вариант с альясами по ряду причин неприемлем. Если для Вас он проще — так это же хорошо! Я же никого не заставляю пользоваться поддеревьями. Каждый выбирает то, что ему лучше подходит.
  • 0
    Очень интересное решение. Спасибо!
    • +1
      Рад помочь!
  • +2
    Сабмитить изменения в проект поддерева значительно сложнее чем с подмодулями. Но это легко обойти, внося изменения в отдельный клон этого проекта.
    Кстати, на Гитхабе есть проект, нацеленный на развитие работы с поддеревьями: git-subtree.

    А тем временем на ГитХабе появился форк, позволяющий делать push поддерева :-) github.com/tickzoom/git-subtree

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