Обновите подмодуль Git до последней фиксации на источнике


854

У меня есть проект с подмодулем Git. Он взят с URL-адреса ssh: // ... и находится в коммите A. Коммит B был перенесен на этот URL-адрес, и я хочу, чтобы субмодуль получил коммит и изменил его.

Теперь я понимаю, что git submodule updateдолжен это делать, но это не так. Он ничего не делает (нет вывода, код успешного завершения). Вот пример:

$ mkdir foo
$ cd foo
$ git init .
Initialized empty Git repository in /.../foo/.git/
$ git submodule add ssh://user@host/git/mod mod
Cloning into mod...
user@host's password: hunter2
remote: Counting objects: 131, done.
remote: Compressing objects: 100% (115/115), done.
remote: Total 131 (delta 54), reused 0 (delta 0)
Receiving objects: 100% (131/131), 16.16 KiB, done.
Resolving deltas: 100% (54/54), done.
$ git commit -m "Hello world."
[master (root-commit) 565b235] Hello world.
 2 files changed, 4 insertions(+), 0 deletions(-)
 create mode 100644 .gitmodules
 create mode 160000 mod
# At this point, ssh://user@host/git/mod changes; submodule needs to change too.
$ git submodule init
Submodule 'mod' (ssh://user@host/git/mod) registered for path 'mod'
$ git submodule update
$ git submodule sync
Synchronizing submodule url for 'mod'
$ git submodule update
$ man git-submodule 
$ git submodule update --rebase
$ git submodule update
$ echo $?
0
$ git status
# On branch master
nothing to commit (working directory clean)
$ git submodule update mod
$ ...

Я также попытался git fetch mod, который, кажется, делает выборку (но не может возможно, потому что это не запрашивает пароль!), Но git logи git showотрицает существование новых коммитов. До сих пор я только что занимался rmмодулем и заново его добавлял, но это в принципе неправильно и утомительно на практике.


5
Ответ Дэвида Z кажется лучшим способом сделать это - теперь, когда Git обладает необходимой вам функциональностью, встроенной через --remoteопцию, возможно, было бы полезно отметить это как принятый ответ, а не как «ручной» подход в ответе Джейсона?
Марк Эмери

1
Я полностью согласен с @MarkAmery. В то время как Джейсон дал рабочее решение, он не предназначен для этого, так как оставляет указатель коммита подмодуля на неверный идентификатор коммита. Новое --remoteна данный момент определенно является лучшим решением, и, поскольку этот вопрос был связан с Github Gist о подмодулях, я думаю, что для новых читателей было бы лучше увидеть новый ответ.
MutantOctopus

hunter2
Приятное

Ответы:


1458

Команда на git submodule updateсамом деле говорит Git, что вы хотите, чтобы ваши подмодули проверяли каждый коммит, уже указанный в индексе суперпроекта. Если вы хотите обновить свои подмодули до последнего коммита, доступного с их пульта, вам нужно будет сделать это прямо в подмодулях.

Итак, в заключение:

# Get the submodule initially
git submodule add ssh://bla submodule_dir
git submodule init

# Time passes, submodule upstream is updated
# and you now want to update

# Change to the submodule directory
cd submodule_dir

# Checkout desired branch
git checkout master

# Update
git pull

# Get back to your project root
cd ..

# Now the submodules are in the state you want, so
git commit -am "Pulled down update to submodule_dir"

Или, если вы занятой человек:

git submodule foreach git pull origin master

335
git submodule foreach git pull
Матиас Биненс

87
@ Никлас В этом случае используйте git submodule foreach git pull origin master.
Матиас Биненс

54
На данный момент, со всеми этими исправлениями к исправлениям, мне нужен кто-то, чтобы написать пояснительное сообщение в блоге и указать мне там. Пожалуйста.
Suz

25
незначительное улучшение подхода 'foreach' - вы можете добавить туда --recursive, если у вас есть подмодули внутри подмодулей. так: git submodule foreach --recursive git pull origin master.
Орион Элензил

4
@Abdull -aПереключатель для git commit«Скажите [s] команду автоматической установки файлов, которые были изменены и удалены, но новые файлы, о которых вы не сообщили Git, не затрагиваются».
Godfrzero

473

В Git 1.8.2 появилась новая опция --remote, которая включит именно это поведение. Бег

git submodule update --remote --merge

извлекает последние изменения из апстрима в каждом подмодуле, объединяет их и проверяет последнюю версию подмодуля. Как написано в документации :

--дистанционный пульт

Эта опция действительна только для команды обновления. Вместо того, чтобы использовать записанный SHA-1 суперпроекта для обновления подмодуля, используйте статус ветви удаленного отслеживания подмодуля.

Это эквивалентно запуску git pullв каждом подмодуле, что обычно именно то, что вы хотите.


4
"эквивалентно выполнению git pullв каждом подмодуле" Чтобы уточнить, нет разницы (с точки зрения пользователя) между вашим ответом и git submodule foreach git pull?
Деннис

3
@ Денис, по сути, делает то же самое, но я не уверен, что функциональность точно такая же. Могут быть некоторые незначительные различия, о которых я не знаю, например, в том, как две команды реагируют на некоторые настройки конфигурации.
Дэвид Z

5
Я бы хотел, чтобы я проголосовал за это 10000X. Почему это нигде не показано в документации git? Огромный недосмотр.
serraosays

4
Для меня они на самом деле отличались довольно значительно; foreach git pullтолько проверил их, но не обновил указатель основного репо, чтобы он указывал на более новую фиксацию подмодуля. Только с --remoteэтим он указал на последний коммит.
Ela782

5
почему опция --merge? Какая разница?
mFeinstein

127

В родительском каталоге вашего проекта запустите:

git submodule update --init

Или, если у вас есть рекурсивные подмодули, запустите:

git submodule update --init --recursive

Иногда это все еще не работает, потому что каким-то образом у вас есть локальные изменения в локальном каталоге подмодуля во время обновления подмодуля.

В большинстве случаев локальные изменения могут быть не теми, которые вы хотите зафиксировать. Это может произойти из-за удаления файла в вашем подмодуле и т. Д. Если это так, выполните сброс в локальном каталоге подмодуля и в родительском каталоге вашего проекта, запустите снова:

git submodule update --init --recursive

5
это верный ответ. Могу ли я отправить его в свой удаленный репозиторий как-нибудь?
MonsterMMORPG

Это работает для новых подмодулей! Я мог бы обновить все остальные, но папка новых подмодулей оставалась бы пустой, пока я не выполнил эту команду.
Алексис

1
Это не тянет изменения для существующих подмодулей
Сергей Григорьевич

73

Ваш основной проект указывает на конкретный коммит, в котором должен быть подмодуль. git submodule updateпытается проверить этот коммит в каждом подмодуле, который был инициализирован. Подмодуль на самом деле является независимым репозиторием - просто создайте новый коммит в подмодуле и нажмите, что недостаточно. Вам также необходимо явно добавить новую версию субмодуля в основной проект.

Итак, в вашем случае вы должны найти правильный коммит в подмодуле - давайте предположим, что это верхушка master:

cd mod
git checkout master
git pull origin master

Теперь вернитесь к основному проекту, создайте подмодуль и передайте следующее:

cd ..
git add mod
git commit -m "Updating the submodule 'mod' to the latest version"

Теперь нажмите вашу новую версию основного проекта:

git push origin master

С этого момента, если кто-то еще обновит свой основной проект, он git submodule updateобновит субмодуль, предполагая, что он инициализирован.


24

Кажется, что в этом обсуждении смешиваются два разных сценария:

Сценарий 1

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

Это, как указано, сделано с

git submodule foreach git pull origin BRANCH
git submodule update

Сценарий 2, на который, я думаю, нацелен OP

Новые вещи произошли в одном или нескольких подмодулях, и я хочу 1) вытащить эти изменения и 2) обновить родительский репозиторий, чтобы он указывал на HEAD (последний) коммит этого / этих подмодулей.

Это будет сделано

git submodule foreach git pull origin BRANCH
git add module_1_name
git add module_2_name
......
git add module_n_name
git push origin BRANCH

Не очень практично, поскольку вам придется жестко задавать n путей ко всем n подмодулям, например, в скрипте, чтобы обновить указатели фиксации родительского репозитория.

Было бы здорово иметь автоматическую итерацию для каждого подмодуля, обновляя указатель родительского репозитория (используя git add), чтобы он указывал на заголовок подмодуля (ов).

Для этого я сделал небольшой скрипт Bash:

git-update-submodules.sh

#!/bin/bash

APP_PATH=$1
shift

if [ -z $APP_PATH ]; then
  echo "Missing 1st argument: should be path to folder of a git repo";
  exit 1;
fi

BRANCH=$1
shift

if [ -z $BRANCH ]; then
  echo "Missing 2nd argument (branch name)";
  exit 1;
fi

echo "Working in: $APP_PATH"
cd $APP_PATH

git checkout $BRANCH && git pull --ff origin $BRANCH

git submodule sync
git submodule init
git submodule update
git submodule foreach "(git checkout $BRANCH && git pull --ff origin $BRANCH && git push origin $BRANCH) || true"

for i in $(git submodule foreach --quiet 'echo $path')
do
  echo "Adding $i to root repo"
  git add "$i"
done

git commit -m "Updated $BRANCH branch of deployment repo to point to latest head of submodules"
git push origin $BRANCH

Чтобы запустить его, выполните

git-update-submodules.sh /path/to/base/repo BRANCH_NAME

разработка

Прежде всего, я предполагаю, что ветка с именем $ BRANCH (второй аргумент) существует во всех репозиториях. Не стесняйтесь сделать это еще сложнее.

Первая пара разделов посвящена проверке наличия аргументов. Затем я извлекаю последние материалы из родительского репозитория (я предпочитаю использовать --ff (ускоренная перемотка вперед) всякий раз, когда я просто выполняю вытягивание. У меня отключен ребаз, кстати).

git checkout $BRANCH && git pull --ff origin $BRANCH

Затем может потребоваться некоторая инициализация субмодуля, если новые субмодули были добавлены или еще не инициализированы:

git submodule sync
git submodule init
git submodule update

Затем я обновляю / тяну все подмодули:

git submodule foreach "(git checkout $BRANCH && git pull --ff origin $BRANCH && git push origin $BRANCH) || true"

Обратите внимание на несколько вещей: во-первых, я объединяю в цепочку некоторые команды Git, то &&есть предыдущая команда должна выполняться без ошибок.

После возможного успешного извлечения (если новый материал был найден на удаленном компьютере) я делаю пуш, чтобы убедиться, что возможный коммит-слияние не остался на клиенте. Опять же, это происходит только в том случае, если тяга принесла новые вещи.

Наконец, в завершение необходимо || trueубедиться, что сценарий продолжает работу с ошибками. Чтобы это работало, все в итерации должно быть заключено в двойные кавычки, а команды Git заключены в круглые скобки (приоритет оператора).

Моя любимая часть:

for i in $(git submodule foreach --quiet 'echo $path')
do
  echo "Adding $i to root repo"
  git add "$i"
done

--quietВыполните итерацию всех подмодулей - с , который удаляет вывод 'Entering MODULE_PATH'. Используя 'echo $path'(должно быть в одинарных кавычках), путь к подмодулю записывается в вывод.

Этот список относительных путей подмодулей записан в array ( $(...)) - наконец, выполните итерации и выполните git add $iобновление родительского репозитория.

Наконец, коммит с некоторым сообщением, объясняющим, что родительский репозиторий был обновлен. Этот коммит будет проигнорирован по умолчанию, если ничего не было сделано. Подтолкните это к источнику, и все готово.

У меня есть сценарий, выполняющий это в задании Jenkins, который впоследствии связывается с запланированным автоматическим развертыванием, и он работает как шарм.

Я надеюсь, что это кому-то поможет.


2
! @ # $% ТАК Мы используем сценарии, схожие с вашими; примечание: вместо `` `git submodule foreach --quiet 'echo $ path'` `` мы используем `` `git submodule foreach --recursive --quiet pwd` `` внутри циклов for. Команда pwdвыводит правильный «абсолютный путь» для каждого присутствующего подмодуля; --recursiveобеспечивает посещение всех подмодулей, включая подмодули внутри подмодулей -..., которые могут присутствовать в большом проекте. Оба метода вызывают проблемы с каталогами, которые содержат пробелы, например, /c/Users/Ger/Project\ Files/...поэтому политика никогда не должна использовать пробелы в наших проектах.
Гер Хоббельт

2
Это хорошо, и вы правы, что в некоторых ответах есть неправильное понимание того, что это за вопрос, но, как указывает отличный ответ Дэвида З, ваш сценарий не нужен, так как функциональность была встроена в Git с середины 2013 года, когда они добавили --remoteопцию. git submodule update --remoteведет себя примерно так же, как ваш скрипт.
Марк Эмери

@GerHobbelt Спасибо. Вы правы, у нас есть только 1 уровень подмодулей, поэтому я никогда не думал, что это будет рекурсивно. Я не буду обновлять сценарий, пока у меня не будет возможности проверить, работает ли он должным образом, но определенно мой сценарий будет включать субмодули. Что касается пробелов в папках, это определенно звучит как то, чего следует избегать! : S
Фредерик Струк-Шёнинг

@MarkAmery Спасибо за ваш отзыв. Однако я вижу одну проблему: не в качестве аргумента можно указать ветку для подмодулей. Из руководства по git: The remote branch used defaults to master, but the branch name may be overridden by setting the submodule.<name>.branch option in either .gitmodules or .git/config (with .git/config taking precedence).я не хочу редактировать ни .gitmodules, ни .git / config каждый раз, когда я хочу сделать это в другой ветке, кроме master. Но, может быть, я что-то пропустил? Кроме того, кажется, что этот метод обеспечивает рекурсивное слияние (таким образом, отсутствует возможность ускоренной перемотки вперед).
Фредерик Штрук-Шёнинг

И последнее: я попробовал метод @ DavidZ, и он, похоже, не выполняет точную вещь, которую я намеревался сделать (и о которой спрашивал op): добавление коммитов HEAD подмодулей в parent (то есть «обновление указателя») ). Однако он, похоже, отлично выполняет (и быстрее) единственную работу по извлечению и объединению последних изменений во всех подмодулях. Увы, по умолчанию только из главной ветки (если только вы не редактируете файл .gitmodules (см. Выше)).
Фредерик Штрук-Шёнинг

19

Просто и понятно, чтобы получить подмодули:

git submodule update --init --recursive

А теперь перейдите к обновлению до последней ветки master (например):

git submodule foreach git pull origin master

13

Обратите внимание, что современная форма обновления коммитов подмодулей будет выглядеть так:

git submodule update --recursive --remote --merge --force

Старая форма была:

git submodule foreach --quiet git pull --quiet origin

За исключением ... эта вторая форма не очень "тихо".

См. Коммит а282f5a (12 апреля 2019 г.) Нгуен Тхай Нгук Дуй ( pclouds) .
(Объединено Junio ​​C Hamano - gitster- в коммите f1c9f6c , 25 апреля 2019 г.)

submodule foreach: fix " <command> --quiet" не соблюдается

Робин сообщил, что

git submodule foreach --quiet git pull --quiet origin

уже не совсем тихо.
Перед fc1b924 должно быть тихо ( submodule: submoduleподкоманда port ' foreach' из оболочки в C, 2018-05-10, Git v2.19.0-rc0), потому что тогда parseoptнельзя случайно съесть параметры.

« git pull» ведет себя так, как будто --quietне дано.

Это происходит потому, что parseoptin submodule--helperпопытается проанализировать оба --quietпараметра, как если бы они были параметрами foreach, а не git-pulls.
Проанализированные параметры удаляются из командной строки. Поэтому, когда мы делаем тянуть позже, мы выполняем только это

git pull origin

При вызове вспомогательного модуля субмодуля добавление " --" перед " git pull" остановит parseoptпараметры синтаксического анализа, которые на самом деле не принадлежат submodule--helper foreach.

PARSE_OPT_KEEP_UNKNOWNудаляется в качестве меры безопасности. parseoptникогда не должен видеть неизвестные варианты, или что-то пошло не так. Есть также обновление строки использования пары, пока я смотрю на них.

При этом я также добавляю " --" к другим подкомандам, которые передают " $@" submodule--helper. « $@» в этих случаях являются путями и менее вероятны --something-like-this.
Но точка зрения остается в силе, git-submoduleпроанализировала и классифицировала, какие есть варианты, какие есть пути.
submodule--helperникогда не следует рассматривать пути, проходящие мимо, git-submoduleкак варианты, даже если они выглядят как один.


А в Git 2.23 (Q3 2019) исправлена ​​другая проблема: « git submodule foreach» не защищал параметры командной строки, передаваемые команде для правильного запуска в каждом подмодуле, когда использовалась --recursiveопция « ».

См. Коммит 30db18b (24 июня 2019 г.) от Morian Sonnet ( momoson) .
(Слиты Junio C Hamano - gitster- в фиксации 968eecb , 9 июля 2019)

submodule foreach: исправить рекурсию опций

Вызов:

git submodule foreach --recursive <subcommand> --<option>

приводит к ошибке, утверждающей, что опция --<option>неизвестна submodule--helper.
Это, конечно, только тогда, когда <option>недопустимый вариант для git submodule foreach.

Причина этого заключается в том, что вышеуказанный вызов внутренне преобразуется в вызов субмодуля - помощника:

git submodule--helper foreach --recursive \
    -- <subcommand> --<option>

Этот вызов начинается с выполнения подкоманды с ее параметром внутри подмодуля первого уровня и продолжается вызовом следующей итерации submodule foreachвызова.

git --super-prefix <submodulepath> submodule--helper \
   foreach --recursive <subcommand> --<option>

внутри подмодуля первого уровня. Обратите внимание, что двойная черта перед подкомандой отсутствует.

Эта проблема начинает возникать только недавно, так как PARSE_OPT_KEEP_UNKNOWNфлаг для разбора аргумента git submodule foreachбыл удален в коммите a282f5a .
Следовательно, неизвестная опция теперь жаловалась, так как разбор аргументов не заканчивается должным образом двойной чертой.

Эта фиксация устраняет проблему, добавляя двойную черту перед подкомандой во время рекурсии.


7
git pull --recurse-submodules

Это потянет все последние коммиты.


4

В моем случае я хотел gitобновить до последней версии и в то же время повторно заполнить все отсутствующие файлы.

Следующее восстановило отсутствующие файлы (спасибо, --forceчто, кажется, здесь не упоминалось), но не получило никаких новых коммитов:

git submodule update --init --recursive --force

Это сделал:

git submodule update --recursive --remote --merge --force


3

@ Джейсон правильно, но не совсем.

Обновить

Обновите зарегистрированные подмодули, то есть клонируйте отсутствующие подмодули и извлеките фиксацию, указанную в индексе репозитория. Это приведет к отсоединению подмодулей HEAD, если не указаны --rebase или --merge или ключевой подмодуль. $ Name.update не установлен для перебазирования или слияния.

Итак, git submodule updateделает извлечение, но это для фиксации в индексе содержащего репозитория. Он еще не знает о новом коммите вверх по течению. Перейдите в свой подмодуль, получите нужный коммит и зафиксируйте обновленное состояние подмодуля в основном репозитории, а затем выполните git submodule update.


1
Похоже, что если я переместлю субмодуль в другой коммит, а затем запустлю git submodule update, обновление переместит субмодуль в коммит, который указан в текущем HEAD суперпроекта. (что бы ни говорилось в самом последнем коммите в суперпроекте - подпроект должен быть при этом - такое поведение после объяснения в посте Джейсона кажется мне логичным) Оно также кажется извлекаемым, но только в том случае, если подпроект находится на неправильном коммите , что добавляло к моему замешательству.
Танатос

2

Вот потрясающий однострочник, чтобы обновить все до последней версии на мастере:

git submodule foreach 'git fetch origin --tags; git checkout master; git pull' && git pull && git submodule update --init --recursive

Благодаря Марку Жакиту


2

Если вы не знаете ветку хоста, сделайте так:

git submodule foreach git pull origin $(git rev-parse --abbrev-ref HEAD)

Он получит ветку основного репозитория Git, а затем для каждого подмодуля сделает тягу из той же ветки.


0

Если вы ищете masterветку оформления для каждого подмодуля - вы можете использовать следующую команду для этой цели:

git submodule foreach git checkout master
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.