16

How can I do the reverse of git submodule absorbgitdirs? I.e. move a submodule's .git information out of superproject/.git/modules/<module> and back to the superproject/path/to/<module>/.git directory?

I still want <module> to be a submodule of superproject, I just don't want <module>'s .git directory info in superproject's .git/modules directory.

genpfault
  • 51,148
  • 11
  • 85
  • 139

2 Answers2

9

Note that this would make superproject/path/to/<module>/ a nested Git repo, whose SHA1 would still be recorded by the parent project.

To keep the exact same state, you can copy superproject/.git/modules/<module> and rename to superproject/path/to/<module>, and rename <module>/<module> to <module>/.git.

Then you can use the git submodule deinit to remove the submodule:

mv asubmodule asubmodule_tmp
git submodule deinit -f -- a/submodule    
rm -rf .git/modules/a/submodule

# if you want to leave it in your working tree
git rm --cached asubmodule
mv asubmodule_tmp asubmodule

I still want to be a submodule of superprojec

Then its .git folder would be in superproject/.git/modules/<module>

submodule absorbgitdirs does not leave any choice:

If a git directory of a submodule is inside the submodule, move the git directory of the submodule into its superprojects $GIT_DIR/modules path and then connect the git directory and its working directory by setting the core.worktree and adding a .git file pointing to the git directory embedded in the superprojects git directory.

I don't see in git config any configuration that might move $GIT_DI R/modules.

absorbgitdirs was introduced in commit f6f8586 for Git 2.12 (Q4 2016)
Its tests shows it expects to use GIT_DIR/modules.

Older Git (before Git 1.7.8, Oct. 2011) had a .git directly inside the submodule folder.


As noted by Jeremiah Rose in the comments:

Another use case is: if you are using a Docker container to use git commands inside a submodule, where the container can't see the superproject and errors out. :(

VonC
  • 1,262,500
  • 529
  • 4,410
  • 5,250
  • I could have sworn at some point you could have a submodule and *not* have to have it's `.git` info living in `superproject/.git/modules/`. – genpfault Jun 15 '17 at 05:49
  • 2
    @genpfault Yes: older versions of Git had the .git directly in the submodule folder. – VonC Jun 15 '17 at 05:51
  • Experimenting a bit, looks like cloning the sumodule's repo, then `git submodule add ./path/to/` leaves the submodule's `.git` directory in-place. But a direct `git submodule add ` will put the `.git` info in `superproject/.git/modules`. So file move dance you suggested should work, given a final `git submodule add ./path/to/` to re-submodule. Thanks! – genpfault Jun 15 '17 at 05:55
  • 1
    @genpfault I have updated the answer to indicate when a submodule `.git` moved to `GIT_DIR/modules`, and when `absorbgitdirs` was added to Git. – VonC Jun 15 '17 at 06:03
  • 1
    @genpfault: Out of interest, is there a particular reason that you want to do this? There is a good reason that this changed in Git. It is much safer to use the modern layout. – CB Bailey Jun 15 '17 at 06:08
  • 1
    @CharlesBailey: Working around a [broken script](https://github.com/mate-desktop/debian-packages/issues/100) that expected a "real" `.git` directory instead of the submodule stub `.git` file. Definitely looks like fixing the script is the way to go. – genpfault Jun 15 '17 at 06:13
  • 4
    @CBBailey: Another such case happens when a build script queries Git for versioning tags (eg. `git describe`), but they're unavailable because the parent gitdir is unavailable (eg. building in a separate Docker context). – alecov Jul 02 '20 at 21:02
  • 1
    Another use case is if you are using a Docker container to use git commands inside a submodule, where the container can't see the superproject and errors out. :( – Jeremiah Rose Jul 16 '21 at 08:12
  • @JeremiahRose Good point. I have included your comment in the answer for more visibility. – VonC Jul 16 '21 at 09:57
8

I wrote a script to do this. Add this to your ~/.gitconfig:

[alias]
    extract-submodules = "!gitextractsubmodules() { set -e && { if [ 0 -lt \"$#\" ]; then printf \"%s\\n\" \"$@\"; else git ls-files --stage | sed -n \"s/^160000 [a-fA-F0-9]\\+ [0-9]\\+\\s*//p\"; fi; } | { local path && while read -r path; do if [ -f \"${path}/.git\" ]; then local git_dir && git_dir=\"$(git -C \"${path}\" rev-parse --absolute-git-dir)\" && if [ -d \"${git_dir}\" ]; then printf \"%s\t%s\n\" \"${git_dir}\" \"${path}/.git\" && mv --no-target-directory --backup=simple -- \"${git_dir}\" \"${path}/.git\" && git --work-tree=\"${path}\" --git-dir=\"${path}/.git\" config --local --path --unset core.worktree && rm -f -- \"${path}/.git~\" && if 1>&- command -v attrib.exe; then MSYS2_ARG_CONV_EXCL=\"*\" attrib.exe \"+H\" \"/D\" \"${path}/.git\"; fi; fi; fi; done; }; } && gitextractsubmodules"

and then run:

git extract-submodules [<path>...]

If you run it without specifying any submodules, it will extract all of them.


If you want to see what the code is doing, just change

&& gitextractsubmodules

at the end to

&& type gitextractsubmodules

and then run the command without arguments to make Bash pretty-print the function body.

user541686
  • 205,094
  • 128
  • 528
  • 886
  • Doing what you suggest in order to see what the code is doing does not pretty-print the function body. It seems, instead, that it just tells you the type: (i.e. `gitextractsubmodules is a shell function`. – Rik Jun 20 '22 at 14:49
  • @Rik: Interesting, thanks for the feedback. Try `declare -f` instead. What shell are you using? – user541686 Jun 21 '22 at 04:04
  • I'm using terminal on Debian - though I've tried Terminology too. The default interactive shell is `/bin/bash`, the default non-interactive is **dash**. I think it's getting run through the non-interactive and is as such *ash* and thus bash commands are not available. I've tried `type -f` with the error `-f not found`, I've tried `typeset -f`, `declare -f`, `whence`, and all of the latter commands are not found. Seems there isn't a solution without using `git bash`. – Rik Jun 22 '22 at 01:17
  • @Rik: In that case I think what you want to do is to [turn this into a `.sh script file` with `#!/bin/bash` and call that](https://stackoverflow.com/a/39445884/541686). – user541686 Jun 22 '22 at 07:05
  • 2
    I've cleaned up this one-liner to a human readable posix script and added support for nested submodules. https://gist.github.com/Jamesits/64f9daf3f9467a8aee9a1a5c6e970ea3 – Jamesits Feb 24 '23 at 05:22
  • As a warning to anyone using this: If you have a submodule that contains submodules, this will break those child submodules. You will need to go to each broken submodule and deinit/init those submodules. – Roman Alexander Mar 16 '23 at 15:03