Grant Schmick
Grant Schmick

Reputation: 23

How do I "git add" a modified submodule into the main module?

I modified, added, committed and pushed a change to a submodule, then went up to the main module directory and ran:

# git status
...
Changes not staged for commit:
...
modified:   deps/gr-d13 (modified content, untracked content)
...

# git add deps/gr-d13
(no errors)
# git status
...
Changes not staged for commit:
...
modified:   deps/gr-d13 (modified content, untracked content)
...

Exactly the same as before. My submodule was not staged. Why not?

Both main and submodule are on the same branch.

Upvotes: 1

Views: 1435

Answers (1)

torek
torek

Reputation: 488183

Let's start by diving in at the deep end of the pool. This:

modified:   deps/gr-d13 (modified content, untracked content)

indicates that your superproject Git did a:

(cd deps/gr-d13; git status)

to find out the status of the submodule repository, and when it did, its (the submodule's) work-tree has files that are modified and files that are untracked.

If you want those files to be committed, you'll need to enter the submodule repository yourself, and work with it just as you would with any Git repository. Just remember that it's currently in detached HEAD mode, with a specific commit checked out. This commit may or may not have a branch name associated with it. If not, remember what you need to do.

Once you have a new commit in the submodule, you can have the superproject refer to that new commit. To do that, return to the superproject, and run git add deps/gr-d13. This will tell the superproject Git to do (cd deps/gr-d13; git rev-parse HEAD) to find the correct commit hash ID, and then record that hash ID in the superproject's index.

Background

Remember that a submodule is nothing more than a second Git repository. Your (top level) Git repository is now a superproject. In your superproject, each commit you make lists two things:

  • the path-name of the submodule repository, in this case, deps/gr-d13
  • the hash ID that your superproject Git should use when it does:

    (cd $submodule; git checkout $hash)
    

    so as to force the submodule Git to be in detached HEAD mode at the named commit.

(The superproject should also have a .gitmodules file, which contains the information that a new clone of the superproject needs in order to run the git clone command that creates the submodule repository. This file should be in each commit as well. Once the submodule repository exists, Git no longer needs the .gitmodules file for this information, though.)

The files that you see in your work-tree are not the committed files. (The committed files are stored in a special, compressed, read-only, Git-only format, as part of the commit's snapshot.) As in any Git repository, when you check out a commit, its files go into the index for that repository, and from there, they get copied out, and expanded into useful form, to the work-tree, so that you can see and work on / with them.

Your superproject doesn't have any of the submodule commits inside it, it just has the hash IDs of submodule commits. Your work-tree for your superproject repository does, however, have a directory (or folder, if you prefer that term) that contains the .git1 and work-tree of the submodule. This means the submodule's work-tree is a subdirectory of the superproject's work-tree.

You can, at any time, use:

cd deps/gr-d13

to enter the submodule repository yourself. If you do that, you can work with it just like you work with any repository. It has commits, a HEAD, an index, and a work-tree, just like any repository. The only thing special about this submodule repository is that there's an outer repository that will, occasionally (when told), do a cd into the submodule and run git checkout <hash> to force it into detached HEAD mode again, at the specified commit.


1In old versions of Git, this .git will be an actual directory, containing the submodule's repository database (i.e., everything that's not the work-tree). In modern Git, this .git will be a plain file. Git now "absorbs" the submodule repository database into the superproject, in a directory underneath the .git holding the superproject data.


Working with submodules and push/fetch

Working with a regular Git repository is often a bit complicated, because Git is a distributed version control system. This means you not only have to manage your repository, you have to worry about some other repository, such as one on GitHub, that mostly contains the same commits, but that has its own branch names.

In other words, to work with one Git repository, you're actually working with two Git repositories: yours, and another one over at origin. Your work-load is doubled (or worse since they're only loosely coordinated).

When you add a submodule into the mix, your workload doubles again (or worse), because the submodule is a Git repository, and as such, it has an origin too. So now you're working with four Git repositories. Yours and the one at origin are however-coordinated they are; and you have to coordinate yours with your submodule repository. But your submodule repository is also however-coordinated with its origin.

Because your superproject repository keeps detaching the HEAD in the submodule, actually doing any work in the submodule requires entering it, re-attaching its HEAD to one of its branch names (or creating a new branch name if appropriate), and only then doing work and making new commits. Once you have made these new commits, you may want to test them with your superproject, perhaps by making commits in the superproject. If they don't work out, you may need to undo all of these commits.

(You can, if you like, work with uncommitted changes in both the superproject and submodule repositories. Depending on your tasks and requirements, this may be easier.)

In any case, once the submodule commits exist and are correct, you'll want to use git push from the submodule to send those commits to its origin. You can do this before or after you update your superproject to use those commits. Just remember that if you do update your superproject first, you should be sure to git push from the submodule before you git push from the superproject. The reason is that anyone else who wants your new superproject commit is going to need your new submodule commit too.

Upvotes: 4

Related Questions