1

I have a git repository that has multiple submodules, which also have submodules. In my graph of dependencies, I have such subgraphs:

enter image description here

I would like that all repositories share the common parent folder, i.e. if my repositories are named A, B, C, etc..., then A is in C:/dev/folderA, B is in C:/dev/folderB, C is in C:/dev/folderC, etc...

Is such a setup possible? If so, how to create it.

Community
  • 1
  • 1
Brainless
  • 1,522
  • 1
  • 16
  • 30

1 Answers1

1

I don't think you can get what you want from Git, but I'm not really sure what you want (nor why you want it).

Here is how submodules actually work in Git.

Suppose R is some repository acting as a superproject. This means R has at least one submodule S. The submodule S within R consists of:

  • instructions to Git about where to git clone from. These are stored in the .gitmodules file that appears in in each commit in R, and are copied from there to R's .git/config when you ask Git to do so. The .gitmodules entry for submodule "foo" has, at a minimum, these two sub-fields:

    [submodule "foo"]
        path = path/to/foo
        url = https://github.com/owner/foo.git
    

    (The submodule name would be "path/to/foo" normally, but I've deliberately cheated a bit here to show how they separate.)

  • gitlink entries in each commit, under the given path.

Each gitlink consists of a "file" (pseudo-file, really) whose mode is 160000, and whose stored hash ID is the hash ID of a commit within the submodule repository. The pathname of that file appears in Git's index after you git checkout a commit. In the example above, submodule "foo" would appear in the index under the name path/to/foo.1 The hash ID here will be the hash ID that git submodule update will git checkout in S, by executing:

(cd path/to/foo; git checkout $hash)

where $hash is this hash ID.

Now, submodule S is itself a Git repository. In the old days, before git submodule absorbgitdirs, path/to/foo would wind up containing a directory named .git, which is the actual repository itself. The rest of path/to/foo—a directory—would simply contain the work-tree for this repository.

With the changes from git submodule absorbgitdirs, the repository for S now resides underneath R's repository, i.e., in .git/ itself. Here you will find a modules/ sub-directory: .git/modules/foo will contain the repository for S. The directory path/to/foo will contain a file named .git that contains the path to this repository, plus all the files for the currently-checked-out-commit, i.e., the work-tree for repository S.

Now suppose S itself is acting as a superproject, with submodule T. This means that, in the work-tree for S (which resides in path/to/foo), there is a work-tree .gitmodules; commits in S have a committed, frozen copy of this .gitmodules; this .gitmodules lists the name of submodule T and a path relative to path/to/foo. If that submodule is named bar and lives in path = bar, the relative path from the work-tree for R is path/to/foo/bar.

This relative path within R contains the work-tree for T. The repository for T lives in one of two places depending on your Git vintage:

  • pre-absorb-Git-dirs: path/to/foo/bar/.git; or
  • post-absorb: .git/modules/foo/modules/bar.

Note that sibling submodules may be adjacent: if R has submodules S1 and S2 whose paths are path/to/s1 and path/to/s2, the repositories proper and the two work-trees are adjacent within the work-tree for R. Submodules of submodules, however, have work-trees that are necessarily deeper within the submodule's work-tree.

Note as well that if R has S and T as submodules, and S has T as a submodule, you get two copies of the repository T, one in .git/modules/T and one in .git/modules/S/modules/T (assuming the new absorbed layout). You also get two separate work-trees, but this is clearly necessary, since the two might have two different commits checked out. It would be nice if the absorb code were clever and discovered the duplication of repository T, but it isn't and doesn't.


1Remember that in the index, each committed file is represented by a tuple with four entries:

  • name: the file's path name, including slashes, such as path/to/foo or path/file.ext.
  • mode: usually 100644 (regular file) or 100755 (executable file). You can also have a symbolic link, on systems that support it, stored as mode 120000, and of course the gitlinks, mode 160000.
  • staging slot number: usually zero; nonzero numbers indicate that you are in a conflicted merge.
  • hash ID: the hash ID of a Git blob object, in all cases except for gitlink, where this is the hash ID of the commit in the submodule.
torek
  • 448,244
  • 59
  • 642
  • 775