Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Git: moving submodules recursively (nested submodules)

I have the following git structure

- git-repo a
-- subdirectory 2015
--- git-submodule b
-- git-submodule c
--- git-submodule d

I would like to move the git submodule c to the folder 2015. I know of "dirty ways" to do this (that involve modifying .git/config and changing gitdir in several of the files in the .git/modules files)

I recently read that git mv should be able to do this, i.e., running

git mv c 2015/

This works fine for repositories in which there is no nested submodule (d in my case). However, when I run this command on my directory, I'm getting errors like

fatal: Not a git repository: d/../../.git/modules/c/modules/d
fatal: 'git status --porcelain' failed in submodule 2015/c

(note, this error occurs on a git status after executing the above mentioned move)

Is anyone aware of a clean method to do this move (i.e., one that does not involve manually changing paths in .git/modules files)?

Edit: (6/10/2015)

My current best solution that does not involve modifications to any of the git config files is (first make sure that all changes to d have been committed and pushed somewhere)

rm c/d -rf
git mv c 2015
cd 2015/c
git submodule update

Edit: (8/10/2015)

An even less intrusive workaround

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

Edit: (21/9/2018)

Since git version 2.19. This has been fixed and git mv behaves as expected.

like image 418
BartBog Avatar asked Sep 25 '15 12:09

BartBog


2 Answers

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
like image 174
VonC Avatar answered Nov 03 '22 19:11

VonC


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

like image 4
BartBog Avatar answered Nov 03 '22 17:11

BartBog