Структура репоризитория и проекта

Все C/C++ проекты храняться в едином репозитории, работа которого опирается на систему контроля версий git. Каждый проект представляет собой git-репозиторий (как правило bare).

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

Каждый C/С++ проект, как правило, включает в себя

  1. Исходники (.c, .cpp, .h, .hpp, ...)

    Директория src

  2. Инструкции по сборке (файлы Makefile, CMakeLists.txt)

    Файлы CMakeLists.txt, Makefile, ... в корне проекта, возможно дополнительные диреткории (например cmake со скриптами, расширяющими функциональность cmake))

  3. Публичные заголовочные файлы (для проектов-библиотек)

    Директория include

  4. Документацию

    Исходники и скрипты для генерации документации располагаются в директории doc.

  5. Ресурсы

    Директория res

  6. Тесты

    Директория test

Подпроекты

Существует два способа организации подпроектов средствами git. Первый - с помощью подмодулей. Второй - с помощью ветвлений и удаленных репозиториев. В первом случае информация о подпроектах инкапсулирована в отдельных репозиториях (по 1 на каждый проект), а файлы подпроектов располагаются в специальных поддерикториях суперпроекта. Во втором случае файлы подпроекта являются неотъемлемой частью дерева суперпроекта, а манипуляция подпроектами осуществляется с помощью ветвлений.

Основные различия встают во весь рост при клонировании суперпроекта. В случае с ветвлениями, пользователь, клонирующий суперпроект, может и не подозревать о наличии подпроектов в нём. Выделение подпроекта опционально. При использовании подмодулей клонирующий должен четко понимать, что проект имеет зависимости и аккуратно к ним относится. В качестве "печеньки" клонирующий получает менее нагруженный репозиторий.

Рассмотрим оба способа на примере.

Допустим мы работали над библиотекой core и на определённом этапе работы получили следующее дерево проекта:

.
├── cmake
│   └── nx_lib.cmake
├── doc
│   ├── pages
│   │   ├── index.hpp
│   │   └── readme.md
│   └── doxygen
├── include
│   └── core
│       ├── base.hpp
│       ├── core.hpp
│       └── data.hpp
├── src
│   ├── base.cpp
│   ├── _base_impl.cpp
│   ├── _base_impl.hpp
│   ├── core.cpp
│   └── data.cpp
├── test
│   ├── tests
│   │   ├── tbase.hpp
│   │   ├── tcore.hpp
│   │   └── tdata.hpp
│   └── test.cpp
└── CMakeLists.txt

Далее мы создали проект a, использующий core как библиотеку, причём мы бы хотели включить core в a как подпроект.

Ветвление и удалённые репозитории

Добавляем в проект a новую ветку, содержимое которой - клон проекта core.

git remote add core /repo/core.git
git fetch core
git checkout -b core@rmt core/master

Мы использовали суффикс @rmt, чтобы пометить ветку-подпроект и отличать её от обычных веток. В нашей текущей дериктории находится проект core. Теперь в проекте a, в директорию core допишем файлы проекта core:

git checkout master
git read-tree --prefix=core -u core@rmt

Вот собственно и все. Теперь в суперпроекте a есть директория с подпроектом core. Причем фалы этой директории находятся под версионным контролем проекта а. Если проект core изменился, мы можем влить изменения следующим образом:

git checkout core@rmt
git pull
git checkout master
git merge --squash -s subtree --no-commit core@rmt

Допустим вы ушли в отпуск, а по возвращении обнаружили, что коллега работал над проектом a, не подозревая, что есть подпроект core. В итоге в директории core проекта a появились некоторые изменения, которые вы бы хотели слить в подпроект core. Все что нужно - слить изменения в ветку core@rmt и отправить изменения. Существует одна загвоздка - не существует коммита, который бы указывал на директорию core. Его придется создать низкоуровневой командой commit-tree. Этот "оторванный" коммит в последующем примкнет к истории в ветке core@rmt:

$>echo "core-changes" | git commit-tree HEAD:core
485a320dae49bc6b08c4b5d363d0674b1369b2a6
$>git checkout core@rmt
$>git merge 485a32
$>git push core core@rmt:master

Еще одна частая ситуация - разделяемые подпроекты. Допустим теперь мы работаем над проектом super, который зависит от подпроекта core. Мы уже знаем как действовать...

git remote add core /repo/core.git
git fetch core
git checkout -b core@rmt core/master
git checkout master
git read-tree --prefix=core -u core@rmt

Теперь мы хотели бы добавить подпроект a, но так, чтобы зависимость a от core не дублировалась, но подпроекты a и core сохраняли возможность подтягивать изменения и отправлять. Сначала добавим подпроект a:

git remote add core /repo/core.git
git fetch core
git checkout -b core@rmt core/master
git checkout master
git read-tree --prefix=core -u core@rmt

Пользуясь тем, что в отличии от подмодулей, вся информация о подпроектах инкапсулирована в подветках, а не в файлах, заменяем папку core подпроекта a на симлинк и сообщаем git, чтобы он игнорил данный путь.

git reset a/core/*
git rm -r a/core
ln -s ../core a/core
git add a/core
echo "a/core" >> .gitignore
git add .gitignore
git commit -m"add a as subproject"

Очень важно в ветке a@rmt также заменить директорию core на символическую ссылку. Иначе когда-нибудь случайно поменяете какой-нибудь файл в папке core и не сможете сменить ветку на master, так как имеются изменения, которые нельзя закоммитить так как они игнорятся.

git checkout a@rmt
rm -r core
ln -s ../core core

И напоследок следует упомянуть маленькую, но очень важную деталь. При таком подходе подпроекты не обязаны располагаться в отдельной папке, что позволяет подпроектам быть совсем маленькими. Пусть к примеру имеется очередная реализация строки в C++:

$>ls -la string
.git string.cpp  string.hpp
$>cd proj/src
git remote add string /repo/string
git fetch string
git checkout -b string@rmt string/master
git checkout master
git read-tree --prefix= -u string@rmt

Потрясающе! Мы добавили файлы в дерево проекта, но при этом можем управлять ими как подпроектом с помощью ветки string@rmt!

И напоследок вернёмся к нашему super. Все подпроекты в нём - это ветки с суффиксом @rmt:

$>git branch | grep @rmt
  a@rmt
  core@rmt

Изменения в подпроекте core можно получить так:

git diff-tree -p HEAD:core core@rmt

Изменения в удаленном подпроекте core можно получить так:

git diff-tree -p HEAD:core core/master

Подмодули

TODO:


Comments

comments powered by Disqus