Working in a sub-module is like working in any other git repository. Any git command that you perform inside a sub-module directory are executed in the context of that sub-repository. Sub modules too can have different branches, different log history, etc separated from the parent repository.
Editing the sub-module’s content and updating its repository
Lets say you want to change a tiny bit in a Sub-module. CD to the sub-module directory. Edit a file in the sub-module and change the working directory back to the main repository. Run git status. When you run ‘git status’ notice that it shows there are un-committed changes in the sub-module:
modified: styles/module (modified content)
When a module contains some uncommitted changes, so it is considered dirty. You must make sure to always keep a clean state in your sub-modules.
Now, change the working directory back to the sub-module. Add your changes to the staging, commit them and push to the remote:
cd path/to/submodule git commit -am "colour change" git push origin BranchName
Whenever you try to commit any changes in a sub-module, you should make sure you currently have a branch checked out in the Sub-module before you commit your changes. Thats because if you are in a detached HEAD situation, your commit will easily get lost; When you are in a ‘detached HEAD’ state it means your work is not attached to any local branch and will be gone as soon as you checkout anything else. Normally in Git, you always have a certain branch checked out. However when working with sub-modules, it is the normal state to have a certain commit (and not a branch) checked out. This is what we call a ‘Detached HEAD’ situation. This Here is a good article to learn more about ‘Detached HEAD’: https://www.git-tower.com/learn/git/faq/detached-head-when-checkout-commit
Having committed and pushed the changes of sub-module to its remote repository, change the working directory back to the parent repository. Run ‘git status’. Now you should see still there are ‘un-committed changes’ in the sub-module, but now it is pointing to the new commit id you’ve just made in the sub-module.
modified: styles/module (new commits)
Think of the commit ID as a version. When we commit and push to a sub-module, the parent repository now points to sub-module’s latest commit id. At that point we must commit the sub-module update to the parent project’s repository:
// in main repository git commit -am "styles/module sub-module update"
-a flag will make sure all the modifications are included in the commit in case if those are not already added to the staging
After making a change and committing that in the sub-module, why we need to do another commit in the parent repository?
The relevant state for the sub-modules are defined by the main repository. If you commit in your main repository, the state of the sub-module is also defined by this commit. The ‘git submodule update’ command sets the Git repository of the sub-module to that particular commit specified by the main repository. The sub-module repository tracks its own content which is nested into the main repository. The main repository refers to a commit of the nested sub-module repository. This means that if you pull in or make new change into the sub-modules, you need to create a new commit in your main repository in order to track the updates of the nested sub-modules.
Pulling remote changes
I’ve identified a couple of ways that you can bring sub-module updates to your local repository
Updating a sub-modules and committing changes to the parent repository
There are two ways to get this done.
Method 1: Change the working directory to the sub-module directory and run ‘git fetch’. This will download any new data from the remote repository if available. Fetch is great for getting a fresh view on all the things that happened in a remote repository. It won’t integrate any of the new data into your working files. Due to it’s ‘harmless’ nature, you can rest assured: Fetch will never manipulate, destroy, or screw up anything.
cd /path/to/sub-module git fetch
Then git pull can be used to bring/integrate the changes to the local sub-module repository just as usual. It will move the sub-module pointer to a different revision than the one you’ve initially checked-out. When you are in a ‘detached HEAD’ state it means your work is not attached to any local branch. If you need to pull some changes from this situation, you need to tell git on which branch you want to integrate the pulled down changes. That means you cannot use the shorthand ‘git pull’ syntax but instead need to specify the remote and branch, too:
git pull origin master
Now if you execute ‘git status’, you’d notice that we are still on that same detached HEAD commit as before. The currently checked out commit was not moved like when we are on a branch. If we want to use the new sub-module code in our main project, we have to explicitly move the HEAD pointer by checking out the branch with latest changes. See below:
git checkout master
An alternative route would be, simply cd to the sub-module directory. ‘git fetch’ and checkout the branch you want to bring the changes into. Then execute ‘git pull’ to integrate remote changes to the currently checked-out branch. This will move the HEAD pointer to the latest commit pulled in from the remote.
Now if you go back to the parent repository and run ‘git submodule status’, you’ll see a commit hash preceded by a plus(+) sign that indicates the sub-module pointer has moved to a different revision. Now we can commit this change to the parent repository to make it official. However, in case if we want to reset the sub-module to the original commit recorded in the parent repository, we can simply do that by running below command:
git submodule update OR git submodule update /path/to/submodule
After that if you go back to the sub-module and run ‘git status’ you’ll see that the HEAD has again gone back to the ‘Detached HEAD’ state.
Method 2: By using git submodule update command
Change the working directory to the parent repository and run below command:
git submodule update --remote
This will update the branch registered in the .gitmodule and by default, you will end up with a detached HEAD.
To update a particular sub-module, execute below command:
git submodule update --remote styles/module
Execute below command to update all sub-modules recursively along with their tracking branches:
git submodule update --remote --recursive
There are three update strategies available. 1. checkout 2. merge 3. rebase. Unless if you specify a different updating strategy, the updating will be performed as ‘checkout’ and which will leave your sub-modules in a ‘Detached HEAD’ state. After you’ve executed the above command a ‘git status’ will show you there are new commits made to the sub-modules. Then you can simply commit those changes to the parent repository. This command eliminates the need to navigate into each sub-module and fetch updates manually.
Above methods are best suited for situations where we only want to update particular sub-modules but not the parent repository.
Updating both parent and sub-module(s) branches with their remote changes
Change the working directory to the parent repository dir, and run ‘git pull’. This will pull all the updates made to the parent repository along with any sub-module commits made to it. In this way your local cache is up-to-date with the sub-module’s remote, but the sub-moodule’s working directory won’t be updated. It will still be stuck to its former contents. Now if you run ‘git status’ (from the parent repository), it will show ‘new commits’ message pointing to the relevant sub-module. Then you can manually update the local sub-module repository by issuing the below command:
git submodule udpate
If you don’t take the above step, your next container commit will regress the sub-module.
This is good for situations where someone in our team has updated the parent repository along with sub-module changes and pushed to the remote and later we when we want to take those updates. Here there is no need to commit anything because the commit is already there.