Git: moving submodules recursively (nested submodules)

生来就可爱ヽ(ⅴ<●) 提交于 2019-11-30 08:36:59

Update Q2 2018 and Git 2.18:

Moving a submodule that itself has submodule in it with "git mv" forgot to make necessary adjustment to the nested sub-submodules;
now the codepath learned to recurse into the submodules.

See commit 6856077 (28 Mar 2018) by Jonathan Tan (jhowtan).
See commit da62f78, commit 0c89fdd, commit 3b8fb39, commit f793b89, commit 61aad92 (28 Mar 2018) by Stefan Beller (stefanbeller).
(Merged by Junio C Hamano -- gitster -- in commit 0c7ecb7, 08 May 2018)

submodule: fixup nested submodules after moving the submodule

As submodules can have nested submodules themselves, we'd also want to fix the nested submodules when asked to. Add an option to recurse into the nested submodules and connect them as well.

As submodules are identified by their name (which determines their git directory in relation to their superproject's git directory) internally and by their path in the working tree of the superproject, we need to make sure that the mapping of name <-> path is kept intact. We can do that in the git-mv command by writing out the .gitmodules file first and then forcing a reload of the submodule config machinery.


Update Q4 2017:

The latest Git 2.14.x/2.15 (Q4 2017) documents the bug

See commit c514167 (15 Sep 2017) by Heiko Voigt (hvoigt).
(Merged by Junio C Hamano -- gitster -- in commit 450b908, 25 Sep 2017)

When using git-mv with a submodule it will detect that and update the paths for its configurations (.gitmodules, worktree and gitfile).
This does not work for recursive submodules where a user renames the root submodule.


Original answer 2015

I just tested it with git 2.6.0 (on Windows), and moving submodules with nested submodules of their own seems to be problematic:

C:\Users\vonc\prog\git\tests\submove>git clone --recursive a a1
Cloning into 'a1'...
done.
Submodule '2015/b' (C:/Users/vonc/prog/git/tests/submove/b) registered for path '2015/b'
Submodule 'c' (C:/Users/vonc/prog/git/tests/submove/c) registered for path 'c'
Cloning into '2015/b'...
done.
Submodule path '2015/b': checked out 'dc18955ec7b9ad0c04245968e2474646e4d593b2'
Cloning into 'c'...
done.
Submodule path 'c': checked out 'fb4722eaca17ac171b7a2c8c5a1ac1e697f0ee85'
Submodule 'd' (C:/Users/vonc/prog/git/tests/submove/d) registered for path 'd'
Cloning into 'd'...
done.
Submodule path 'c/d': checked out '73cd7b8ff82519720b2fcca18df5ed00dd618b71'

I have:

a1
  c
    d
  2015
    b

If I try and move submodule c in the 2015 subfolder:

C:\Users\vonc\prog\git\tests\submove\a1>git mv c 2015/c

C:\Users\vonc\prog\git\tests\submove\a1>git status
fatal: Not a git repository: d/../../.git/modules/c/modules/d
fatal: 'git status --porcelain' failed in submodule 2015/c

I found that I need to modify 2 things in the nested module d:

1/ modify manually .git/modules/c/modules/d/config to inject the right path:

git config -f .git/modules/c/modules/d/config core.worktree ../../../../../2015/c/d
                                                                           ^^^^

2/ modify d worktree:

git config -f .git/modules/c/modules/d/config core.worktree ../../../../../2015/c/d

From there, a git status works, but I like (to be sure) to add a git submodule sync --recursive:

C:\Users\vonc\prog\git\tests\submove\a1>git submodule sync --recursive
Synchronizing submodule url for '2015/b'
Synchronizing submodule url for '2015/c'
Synchronizing submodule url for '2015/c/d'

The git status does show the expected result:

C:\Users\vonc\prog\git\tests\submove\a1>git status
On branch master
Your branch is up-to-date with 'origin/master'.
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

        modified:   .gitmodules
        renamed:    c -> 2015/c

I add and commit that change:

C:\Users\vonc\prog\git\tests\submove\a1>git add .

C:\Users\vonc\prog\git\tests\submove\a1>git commit -m "move sub c in 2015/c"
[master 8289632] move sub c in 2015/c
 2 files changed, 1 insertion(+), 1 deletion(-)
 rename c => 2015/c (100%)

C:\Users\vonc\prog\git\tests\submove\a1>gl
* 8289632 - (HEAD -> master) move sub c in 2015/c (3 seconds ago) <VonC>
* 7ebb8e0 - (origin/master, origin/HEAD) a with sub c (38 minutes ago) <VonC>

I now clone that repo to check the move was indeed properly registered:

C:\Users\vonc\prog\git\tests\submove>git clone --recursive a1 a2
Cloning into 'a2'...
done.
Submodule '2015/b' (C:/Users/vonc/prog/git/tests/submove/b) registered for path '2015/b'
Submodule 'c' (C:/Users/vonc/prog/git/tests/submove/c) registered for path '2015/c'
Cloning into '2015/b'...
done.
Submodule path '2015/b': checked out 'dc18955ec7b9ad0c04245968e2474646e4d593b2'
Cloning into '2015/c'...
done.
Submodule path '2015/c': checked out 'fb4722eaca17ac171b7a2c8c5a1ac1e697f0ee85'
Submodule 'd' (C:/Users/vonc/prog/git/tests/submove/d) registered for path 'd'
Cloning into 'd'...
done.
Submodule path '2015/c/d': checked out '73cd7b8ff82519720b2fcca18df5ed00dd618b71'

As you can see, c and c/d are in the expected 2015/ subfolder:

C:\Users\vonc\prog\git\tests\submove>cd a2

C:\Users\vonc\prog\git\tests\submove\a2>dir 2015\c

 Directory of C:\Users\vonc\prog\git\tests\submove\a2\2015\c

03/10/2015  18:10    <DIR>          .
03/10/2015  18:10    <DIR>          ..
03/10/2015  18:10                29 .git
03/10/2015  18:10                40 .gitmodules
03/10/2015  18:10    <DIR>          d
               2 File(s)             69 bytes
               3 Dir(s)  23 656 910 848 bytes free

C:\Users\vonc\prog\git\tests\submove\a2>dir 2015\c\d

 Directory of C:\Users\vonc\prog\git\tests\submove\a2\2015\c\d

03/10/2015  18:10    <DIR>          .
03/10/2015  18:10    <DIR>          ..
03/10/2015  18:10                42 .git
03/10/2015  18:10                 3 d.txt
               2 File(s)             45 bytes
               2 Dir(s)  23 656 910 848 bytes free
BartBog

As confirmed by @VonC in https://stackoverflow.com/a/32924692/2274140, this is a bug in git mv.

There are several possible workarounds. The easiest one requires no complex modifications of .git files (I have been using this one ever since asking the question). It works as follows:

git mv c 2015
rm 2015/c/d/.git
cd 2015/c
git submodule update

It temporarily removes the .git file in the d subsubmodule. The git submodule update then fixes this .git file again.

For other workarounds that avoid the temporary remove of the gitdir, see this answer: https://stackoverflow.com/a/32924692/2274140

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!