Вы правы: подмодуль является хранилищем само по себе. Это означает, что вы можете cd
или chdir
войти в подмодуль и начать использовать его так же, как и любой другой репозиторий Git, включая запуск git fetch
, git checkout
, git commit
и т. Д.
Что делает подмодуль подмодулем, так это тот факт, что какой-то другой Git, расположенный где-то над подмодулем, управляет подмодулем, обычно выполняя в нем git checkout <em>hash-id</em>
. Это переводит подмодуль в режим, который Git называет detached HEAD . (Управляющий Git - это суперпроект .)
Этот режим "отсоединенного HEAD" немного сложен. Вы можете делать новые коммиты в этом режиме, но когда вы это делаете, они не находят большинством обычных средств. Их легко найти на данный момент по специальному имени HEAD
, но это специальное имя HEAD
будет принудительно настроено суперпроектом, который chdir
войдет в подмодуль и запустит git checkout <em>hash-id</em>
опять же, теряя 1 совершенных вами коммитов.
Чтобы отправить в какой-либо другой репозиторий Git сделанные вами коммиты в вашем подмодуле Git, вы должен дать этим коммитам имя в другом Git хранилище. Обычно это означает, что им нужно дать название филиала. Нет жесткого требования использовать имя ветки в вашем хранилище, так как вы можете выполнить:
git push <remote-or-URL> HEAD:<name>
, чтобы отправить фиксацию, указанную в вашем хранилище подмодулей, через имя HEAD
, другому Git, и вежливо попросите его создать или обновить его имя name
, чтобы указать на этот коммит. Но большинству людей не нравится, например, , работающему в / с отсоединенным режимом HEAD.
Как правило, это означает, что для работы в репозитории Git, который действует как субмодуль для некоторого суперпроекта, вы должны использовать следующую последовательность: 2
Введите подмодуль. Начиная с шага 3, вы будете работать с ним как с обычным Git хранилищем.
Выйдите из режима отключенного HEAD , выбрав ветку имя от до git checkout
или путем создания имени ветви, указывающего на текущий коммит. Обратите внимание, что если вы выберете какое-нибудь существующее имя ветки, это может быть другой коммит от current commit. Или это может быть такой же коммит, что и текущий коммит.
Помните, что хранилище суперпроектов ранее сообщало Git: использовать этот необработанный коммит, его ха sh Идентификатор , чтобы войти в режим отключенного HEAD Теперь мы получаем из отдельного режима HEAD, который требует выбора или создания имени ветви. Если вы выберете какое-нибудь существующее имя ветви, вы застрянете с любым коммитом, который выберет это имя ветви. Но если вы разрабатываете новые коммиты в хранилище подмодулей, вы, вероятно, захотите, чтобы имя ветки запомнило их.
Теперь создайте новый коммит (ы) обычным способом. Используйте git push
обычным способом. Коммиты будут или не будут go в получающем репозитории обычным способом, который коммиты делают или не делают. Если они попадут в принимающее хранилище, то имя филиала этого хранилища будет создано или обновлено обычным способом.
Как только все будет сделано, выйдите из подмодуля. репозиторий, возвращающийся в репозиторий суперпроекта. Настало время сделать новый коммит в суперпроекте .
Я уже несколько раз упоминал, что суперпроект Git продолжает контролировать подмодуль Git , Он делает chdir
в подмодуле Git и запускает git checkout <em>hash-id</em>
. Ключ здесь состоит из двух частей:
- Когда суперпроект Git делает это с подмодулем?
- Где делает суперпроект Git получить необработанный га sh ID?
Ответ на первый вопрос сложен: git submodule update
делает это, но не всегда; 3 git checkout --recursive
делает это (всегда); различные другие операции могут иногда делать это, в зависимости от параметров и настроек. То есть, это не произойдет до тех пор, пока вы не попросите об этом, но не всегда очевидно, что вы просите, чтобы это произошло. Что мы собираемся сделать, это убедиться, что мы обращаемся к второй точке, прежде чем это произойдет снова.
Ответ на второй вопрос - где суперпроект Git получает необработанный га sh ID - то, что он получает его от , фиксирует в суперпроекте . Но вы сделали новый коммит в подмодуле и доставили его вверх по потоку в какой-нибудь другой Git репозиторий, так что теперь пришло время сделать новый коммит в суперпроекте , чтобы запишите right ha sh ID, то есть ha sh ID нового коммита, который вы сделали в подмодуле.
Как всегда, ни один коммит не может быть изменен; и, как всегда, Git делает новым коммитом из того, что находится в index (он же область подготовки ). Когда вы извлекаете какой-либо существующий коммит, Git считывает файлы из этого коммита в область index / staging. Если хранилище выступает в качестве суперпроекта для некоторого подмодуля, этот шаг также считывает желаемый га sh ID (в этом подмодуле) из коммита в индекс. Поскольку это уже не нужный идентификатор ha sh, теперь ваша задача - поместить новый ha sh ID в индекс.
Команда, которая делает это в суперпроекте git add
. Как всегда, git add
записывает данные в индекс. Для обычного файла он записывает в индекс копию этого файла. 4 Однако для подмодуля он:
- входит в подмодуль на мгновение;
- спрашивает , что Git: каков необработанный идентификатор ha sh для коммита, идентифицируемого вашим
HEAD
? (git rev-parse HEAD
); - вставляет полученный идентификатор ha sh, какой бы он ни был, в индекс суперпроекта Git, в слот для этого подмодуля.
Это работает, если подмодуль находится в отсоединенном HEAD
состояние или нет, потому что git rev-parse HEAD
всегда возвращает необработанный идентификатор ha sh текущего коммита.
Итак, после git add path/to/submodule
идентификатор ha sh идентификатора коммита, который выбранный вами (и фактически сделанный и выдвинутый) теперь записывается в index в суперпроекте. Ваш следующий коммит будет записывать , что raw ha sh ID.
Предполагая, что все остальное также готово, теперь вы можете запустить git commit
, чтобы сделать новый коммит в суперпроекте (который, по-видимому, находится и был все время в состоянии прикрепленного-HEAD, на каком-то имени ветви). Как только вы сделаете этот новый коммит суперпроекта, вы будете готовы git push
сделать его как обычно.
Обратите внимание на тщательный порядок шагов здесь:
- Введите подмодуль.
- Сделайте коммит (ы) в подмодуле, для простоты в ветвях, но как бы вы их ни делали, все в порядке.
- Отправьте эти коммиты туда, где люди клонируют субмодуль от, так что , что Git имеет их. Этот шаг требует установки имени ветви или тега в этом другом Git.
- Теперь, когда другие люди могут получить доступ к этой фиксации в репозитории
origin
субмодуля , make и pu sh коммит в суперпроекте, который ссылается на необработанный идентификатор коммита подмодуля sh ID.
Возможно - из-за распределенной природы Git - сделать совершить коммит в вашем подмодуле, но не pu sh где угодно, затем сделать коммит в вашем суперпроекте и pu sh it. Любой, кто получает этот новый коммит, получает коммит, который по необработанному номеру ha sh ID ссылается на коммит, который он не только не имеет, но даже не может получить . Поэтому шаг 3 («сделать коммит доступным для всех») должен произойти до push
на шаге 4. («Make commit» на шаге 4 может произойти раньше - просто будьте осторожны, чтобы не извлечь его sh и повторить его с любым обновленным коммитом ha sh, если фиксация подмодуля должна быть переделано по любой причине.)
1 Терять здесь означает «трудно найти». Сами коммиты сразу не исчезнут sh: у них тот же льготный период, что и у других потерянных коммитов, и вы можете использовать подмодуль Git HEAD
reflog, чтобы найти их, так же, как вы нашли потерянный фиксирует в любом репозитории, потому что подмодуль является , в конце концов, просто еще одним репозиторием.
2 Поскольку Git - это набор инструментов, а не предварительно упакованный Решение, есть много других способов достижения вашей цели. Это просто один очень гибкий способ.
3 В частности, git submodule update
имеет много обновлений режимов . С некоторыми аргументами вы можете указать git submodule update
проверить имя в подмодуле, что приведет к присоединению HEAD (а не отдельного) в первую очередь! Это часть того, на что ссылается сноска 2. Подробная работа git submodule update
довольно сложна, поэтому я стараюсь избегать этих вариантов в этом ответе.
4 Технически, он записывает объект blob с правильным содержимым файла - или повторно использует существующий объект BLOB-объекта без изменений, если это возможно, - а затем записывает идентификатор индекса * ha sh объекта индекса в индекс, а не фактическое содержимое файла. Но эффект такой, как если бы Git скопировал файл в индекс, если вы не опуститесь до уровня git ls-files --stage
и git update-index
.