Git: отслеживать ветвь в подмодуле, но совершать в другом подмодуле (возможно, вложенном)

Я ищу ситуацию, в которой у меня есть структура git с (возможно, вложенными подмодулями). Для каждого из этих подмодулей я хочу отдельно указать, следует ли отслеживать ветвь (см., например, Git подмодули: укажите ветку/тег)

Например, мой проект может выглядеть так:

main.tex
|- submod1 @ master
|    |-subsubmod1 @qsdf123
|- submod2 @ master
|    |-subsubmod2 @shasha12
|- submod3 @ qsdf321

Теперь я хочу, чтобы обновить мои подмодули.

git submodule update --recursive

обновит все подмодули до их последнего записанного ша (т.е. будет работать для subsubmod1, subsubmod2 и submod3, но для остальных будет делать неправильные вещи. С другой стороны,

git submodule update --recursive --remote

обновит все подмодули в связанной ветки (по умолчанию, мастер), т.е. будет работать для подмоду1 и подмод2, но для остальных делать не так.

Есть ли способ сделать это красиво?

В ответ на первый ответ я уточню, что я подразумеваю под "делать неправильные вещи".

Вот небольшой пример

[email protected] ~/Desktop/test $ git init
Initialized empty Git repository in /home/bartb/Desktop/test/.git/
[email protected] ~/Desktop/test $ git submodule add ../remote/ submod1
Cloning into 'submod1'...
done.
[email protected] ~/Desktop/test $ git submodule add ../remote/ submod2
Cloning into 'submod2'...
done.
[email protected] ~/Desktop/test $ cd submod1
[email protected] ~/Desktop/test/submod1 $ git log
commit 42d476962fc4e25c64ff2a807d2bf9b3e2ea31f8
Author: Bart Bogaerts <[email protected]>
Date:   Tue Jun 21 08:56:05 2016 +0300

    init commit

commit db1ba3bc4b02df4677f1197dc137ff36ddfeeb5f
Author: Bart Bogaerts <[email protected]>
Date:   Tue Jun 21 08:55:52 2016 +0300

    init commit
[email protected] ~/Desktop/test/submod1 $ git checkout db1ba3bc4b02df4677f1197dc137ff36ddfeeb5f
Note: checking out 'db1ba3bc4b02df4677f1197dc137ff36ddfeeb5f'.

You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by performing another checkout.

If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -b with the checkout command again. Example:

  git checkout -b <new-branch-name>

HEAD is now at db1ba3b... init commit
[email protected] ~/Desktop/test/submod1 $ cd ..
[email protected] ~/Desktop/test $ git config -f .gitmodules submodule.submod2.branch master
[email protected] ~/Desktop/test $ git commit -a -m "modules"
[master (root-commit) ea9e55f] modules
 3 files changed, 9 insertions(+)
 create mode 100644 .gitmodules
 create mode 160000 submod1
 create mode 160000 submod2
[email protected] ~/Desktop/test $ git status
On branch master
nothing to commit, working directory clean
[email protected] ~/Desktop/test $  git submodule update --recursive --remote
Submodule path 'submod1': checked out '42d476962fc4e25c64ff2a807d2bf9b3e2ea31f8'
[email protected] ~/Desktop/test $ git status
On branch master
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

    modified:   submod1 (new commits)

Как вы можете видеть, после того, как последний git submodule update --remote подмод1 выгрузился у мастера, хотя я никогда не настраивал для него основную ветку. Это то, что я подразумеваю под "делать неправильный материал"

То же самое происходит и для подподмодулей: все они проверяются у мастера, а не по их конкретному фиксации.

Эта "проблема" на самом деле является ожидаемой git submodule update --remote. Из документации git:

This option is only valid for the update command. Instead of using the superproject’s recorded SHA-1 to update the submodule, use the status of the submodule’s remote-tracking branch. The remote used is branch’s remote (branch.<name>.remote), defaulting to origin. 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).

https://git-scm.com/docs/git-submodule

В частности, часть:

The remote branch used defaults to master

Это то, чего я хочу избежать.

Изменить: дополнительный запрос: я не хочу вносить какие-либо изменения в подмодули или поднаборы (это совлокальные проекты).

Ответ 1

обновит все подмодули в связанной ветки (по умолчанию, мастер), т.е. будет работать для подмоду1 и подмод2, но для остальных делать не так.

Собственно, да, это сделает "неправильный материал для остальных".

Я проиллюстрирую эту ошибку с приведенным ниже примером (репо с именем parent с подмодулем 'sub', сам с подмодулем 'subsub'), используя git версию 2.9.0.windows. 1.

И я предложу простой способ обхода, позволяющий sub следовать master, убедившись, что subsub не проверен на свой собственный master.


Настройка

Сделайте repo subsub с двумя коммитами:

[email protected] D:\git\tests\subm
> git init subsub1
Initialized empty Git repository in D:/git/tests/subm/subsub1/.git/
> cd subsub1
> git commit --allow-empty -m "subsub c1"
[master (root-commit) f3087a9] subsub c1
> git commit --allow-empty -m "subsub c2"
[master 03d08cc] subsub c2

Давайте сделаем этот репо subsub подмодулем другого репо 'sub':

[email protected] D:\git\tests\subm
> git init sub
Initialized empty Git repository in D:/git/tests/subm/sub/.git/

> cd sub
> git submodule add -- ../subsub
Cloning into 'D:/git/tests/subm/sub/subsub'...
done.

По умолчанию этот субмодуль "subsub" выводится на свой собственный master HEAD ( gl является псевдонимом для git log с симпатичным форматом):

[email protected] D:\git\tests\subm\sub\subsub
> gl
* 03d08cc  - (HEAD -> master, origin/master, origin/HEAD) subsub c2 (4 minutes ago) VonC
* f3087a9  - subsub c1 (4 minutes ago) VonC

Удостоверьтесь, что sub имеет subsub в c1 (который не является master HEAD C2):

[email protected] D:\git\tests\subm\sub\subsub
> git checkout @~
Note: checking out '@~'.

You are in 'detached HEAD' state.     
HEAD is now at f3087a9... subsub c1
> git br -avv
* (HEAD detached at f3087a9) f3087a9 subsub c1
  master                03d08cc [origin/master] subsub c2
  remotes/origin/HEAD   -> origin/master
  remotes/origin/master 03d08cc subsub c2

Пусть добавляет и фиксирует этот подмодуль "subsub" (выдается в master~1 c1) в своем родительском репо 'sub':

[email protected] D:\git\tests\subm\sub\subsub
> cd ..
[email protected] D:\git\tests\subm\sub
> git add .
> git commit -m "subsub at HEAD-1"
[master (root-commit) 1b8144b] subsub at HEAD-1
 2 files changed, 4 insertions(+)
 create mode 100644 .gitmodules
 create mode 160000 subsub

Добавьте несколько коммитов в этот репо 'sub':

[email protected] D:\git\tests\subm\sub
> git commit --allow-empty -m "sub c1"
[master b7d1c40] sub c1
> git commit --allow-empty -m "sub c2"
[master c77f4b2] sub c2

[email protected] D:\git\tests\subm\sub
> gl
* c77f4b2  - (HEAD -> master) sub c2 (2 seconds ago) VonC
* b7d1c40  - sub c1 (3 seconds ago) VonC
* 1b8144b  - subsub at HEAD-1 (77 seconds ago) VonC

Последняя фиксация sub связывает свой подмодуль "subsub" с правой фиксацией (подпункт c1 один, а не c2)

[email protected] D:\git\tests\subm\sub
> git ls-tree @
100644 blob 25a0feba7e1c1795be3b8e7869aaa5dba29d33e8    .gitmodules
160000 commit f3087a9bc9b743625e0799f57c017c82c50e35d6  subsub
              ^^^
              That is subsub master~1 commit c1

Наконец, позвольте сделать основной родительский репо 'parent' и добавить 'sub' в качестве подмодуля:

[email protected] D:\git\tests\subm
> git init parent
Initialized empty Git repository in D:/git/tests/subm/parent/.git/
> cd parent

[email protected] D:\git\tests\subm\parent
> git submodule add -- ../sub
Cloning into 'D:/git/tests/subm/parent/sub'...
done.

Убедитесь, что sub не проверен на master HEAD (как и раньше для subsub)

[email protected] D:\git\tests\subm\parent
> cd sub

[email protected] D:\git\tests\subm\parent\sub
> gl
* c77f4b2  - (HEAD -> master, origin/master, origin/HEAD) sub c2 (2 minutes ago) VonC
* b7d1c40  - sub c1 (2 minutes ago) VonC
* 1b8144b  - subsub at HEAD-1 (3 minutes ago) VonC

[email protected] D:\git\tests\subm\parent\sub
> git checkout @~1
Note: checking out '@~1'.

You are in 'detached HEAD' state.
HEAD is now at b7d1c40... sub c1

Теперь добавим sub (проверили на своем c1 commit, а не на его c2 master HEAD), на parent repo:

[email protected] D:\git\tests\subm\parent
> git add .
> git st
On branch master

Initial commit

Changes to be committed:
  (use "git rm --cached <file>..." to unstage)

        new file:   .gitmodules
        new file:   sub


[email protected] D:\git\tests\subm\parent
> git commit -m "sub at c1"
[master (root-commit) 27374ec] sub at c1
 2 files changed, 4 insertions(+)
 create mode 100644 .gitmodules
 create mode 160000 sub

Пусть make sub следует master как подмодуль в repo parent:

[email protected] D:\git\tests\subm\parent
> git config -f .gitmodules submodule.sub.branch master
> git diff
diff --git a/.gitmodules b/.gitmodules
index 8688a8c..97974c1 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -1,3 +1,4 @@
 [submodule "sub"]
        path = sub
        url = ../sub
+       branch = master

[email protected] D:\git\tests\subm\parent
> git add .
> git commit -m "sub follows master"
[master 2310a02] sub follows master
 1 file changed, 1 insertion(+)

[email protected] D:\git\tests\subm\parent
> gl
* 2310a02  - (HEAD -> master) sub follows master (1 second ago) VonC
* 27374ec  - sub at c1 (2 minutes ago) VonC

Ошибка:

Если я клонирую repo parent, а затем попрошу какой-либо из его подмодуля проверить его после удаленной ветки, sub и subsub проверит их ветку master (пока только sub должен проверить master, subsub должен оставаться на c1)

Сначала клон:

[email protected] D:\git\tests\subm
> git clone --recursive parent p1
Cloning into 'p1'...
done.
Submodule 'sub' (D:/git/tests/subm/sub) registered for path 'sub'
Cloning into 'D:/git/tests/subm/p1/sub'...
done.
Submodule path 'sub': checked out 'b7d1c403edaddf6a4c00bbbaa8e2dfa6ffbd112f'
Submodule 'subsub' (D:/git/tests/subm/subsub) registered for path 'sub/subsub'
Cloning into 'D:/git/tests/subm/p1/sub/subsub'...
done.
Submodule path 'sub/subsub': checked out 'f3087a9bc9b743625e0799f57c017c82c50e35d6'

До сих пор так хорошо: sub и subsub выставляются в c1, а не c2 (то есть: не их master HEAD C2)

Но:

[email protected] D:\git\tests\subm\p1
> git submodule update --recursive --remote
Submodule path 'sub': checked out 'c77f4b2590794e030ec68a8cea23ae566701d2de'
Submodule path 'sub/subsub': checked out '03d08cc81e3b9c0734b8f53fad03ea7b1f0373df'

Теперь, из клона p1, оба подмодуля и подсуммодуля находятся в их master HEAD C2.

И даже несмотря на то, что sub (вычисленный на master как ожидалось) все еще имеет subsub в c2:

[email protected] D:\git\tests\subm\p1\sub
> git ls-tree @
100644 blob 25a0feba7e1c1795be3b8e7869aaa5dba29d33e8    .gitmodules
160000 commit f3087a9bc9b743625e0799f57c017c82c50e35d6  subsub

Обход проблемы:

Не изменяя ничего внутри sub и subsub, вот как убедиться, что subsub остается в ожидаемом c1 commit вместо следующего master (например, sub)

Вызвать git submodule update --recursive из подмодуля, который сам вложил подмодули (поэтому здесь нет --remote)

[email protected] D:\git\tests\subm\p1\sub
> git submodule update --recursive
Submodule path 'subsub': checked out 'f3087a9bc9b743625e0799f57c017c82c50e35d6'

Теперь имеем:

  • sub, оставшееся при master (из-за директивы parent .gitmodules branch и ее начальной git submodule update --recursive --remote)
  • subsub возвращается к записанному sha1 (c1, not master c2)

Заключение

  • Это выглядит как плохой дизайн: --recursive применяет --remote ко всем вложенным подмодулям, по умолчанию используется мастер, когда не найдено submodule.<path>.<branch>.
  • Вы можете script выйти из этого, чтобы:

    • update --remote что вы хотите
    • сброс любого подмодуля, который не имеет ветки, указанной в файле верхнего родительского репо .gitmodules, на их правильный SHA1.

Просто создайте где-нибудь в %PATH% git-subupd script (a bash script, который будет работать даже в обычном сеансе Windows CMD, потому что он будет интерпретироваться git bash)

git-subupd:

#!/bin/bash
git submodule update --recursive --remote
export top=$(pwd)
git submodule foreach --recursive 'b=$(git config -f ${top}/.gitmodules submodule.${path}.branch); case "${b}" in "") git checkout ${sha1};; esac'

"комбинации команд git" сводятся к одному вызову git:

cd /path/to/parent/repo
git subupd

Вот и все. (Любой script, называемый git-xxx, может быть вызван git с git xxx)

[email protected] D:\git\tests\subm\p1
> git subupd
Submodule path 'sub/subsub': checked out '03d08cc81e3b9c0734b8f53fad03ea7b1f0373df'
Entering 'sub'
Entering 'sub/subsub'
Previous HEAD position was 03d08cc... subsub c2
HEAD is now at f3087a9... subsub c1

sub остается установленным в master (commit c2, без изменений), а subsub - от reset до c1 (вместо master c2).

OP BartBog объявляет в комментариях с небольшим изменением этого script с:

export top=$(pwd)
git submodule foreach --recursive \
  'b=$(git config -f ${top}/.gitmodules submodule.${path}.branch); \
   case "${b}" in \
     "") git checkout ${sha1};; \
      *) git checkout ${b}; git pull origin ${b};; \
   esac' 

чтобы избежать вызова submodule update --remote И чтобы убедиться, что мои подмодули не находятся в состоянии отсоединенной головки (