Ever since I started working with development, I have both created and consumed libraries, frameworks, utilities and snippets to simplify and streamline my code. The methods to include this code have varied a lot; from simply copy-pasting code to linked files to private NuGet servers with build automation, full CI/CD etc.
Lately I have started working with Git Submodules, and I struggled a lot with changes being reverted, project dependencies locked in Catch 22 scenarios, and Git command line parameters that I never really understood…
So I finally took the time to really understand how Visual Studio can help me and which processes to follow to stay away from the pitfalls and get the most out of this feature.
The scenario I have is not really relevant to how you can work with Git Submodules, the methods are likely the same for most other types of platforms, projects and languages. Anyway, I have a simple (stupid) little plugin for Microsoft Dynamics 365 / Common Data Service. It just reads the name of the user that initiated the call to the plugin, and writes the name to the Plugin Trace Log.
To make this code simpler and not have to do the always repeated instantiation of TracingService, PluginContext and OrganizationService, I have a little helper library with the
JonasPluginBase class. The code in this library is also available through a shared project, so I don’t have to add a dependency to a separate assembly from my plugin.
2. Adding a submodule
Adding a submodule is still not available in the Visual Studio UI, so to add the helper repository to my solution I have to execute Git command:
git submodule add <path to helper repository>
This adds the referenced repository to a subfolder in my plugin repository. It will create a
.gitmodules file defining name, path and source of the added module.
3. Including helper code and committing
Now that the helper repository has been added to a subfolder under my local repository, I can easily add the shared project from the submodule to my existing solution.
When the changes are committed and pushed, the remote repository shows a representation of the submodule. It looks like a folderwith a somewhat different icon, and it clearly states which version (commit id) of the referenced repository that is included.
Opening that folder does not take you to a subfolder in the current repository, but navigates into the referenced repository. It is a “fake” folder, pointing you somewhere else.
4. Consuming the base class
Now that the
JonasPluginBase helper code is available in my solution I can consume this library to simplify my code.
One of the advantages of including the helper code as a submodule compared to consuming a NuGet package or similar is that we have the code in our solution to help debugging and seeing what is actually going on inside that library.
5. Updating the base class
Another great advantage of including submodules is that you can update the code of the referenced project from within “your own” solution. This speeds up development and encourages continuous improvement of the helper libraries.
So I will improve the JonasPluginBase by adding my good old Canary Tracer.
In the image above I have added the
CanaryTracer that contains an extension to
ITracingService to dump everything in the
PluginContext to the trace log – essentially being your canary in the coal mine. The Gist is available here and you may also want to read my article A Canary in CRM.
6. Committing the submodule
After making changes to the submodule code, it is important not to try to push the changes while still in “your” repository. This is one of the mistakes I made and confusions I had about submodules.
Instead you need to switch to the repository of the submodule, and find the changes to push there. The video below goes through how to easily go through that process.
7. Updating reference – wrong way
When the submodule code is updated, the consuming repository detects this as a pending change to the submodule.
This is one of the core things I had a hard time wrapping my head around. I can see that the submodule is updated, bu tmy instinct says that I do not want to “push the submodule” from my own repository, I just want to make sure I’m using the latest version available of it. So that gut feeling told me to right click the pending change to see what I could do with it. And there is an option for “Submodule Update” – sounds perfect, please do update my submodule.
But what this option does is to update the files in my local submodule folder with the files from the remote repository – and from the commit that my repository currently points at. End result being that any local changes I had in my submodule folder will now be reverted to the commit to which my repo points to.
8. Referencing latest version of submodule repo
When you are in a situation like the one I put myself in the above section, or just want to make sure you are now referencing the latest version or the remote submodule repository, you can always do a
git checkout master (or whatever branch you want) from within the submodule repository folder.
After you have the latest files locally, you can update your repository with the new referenced commit in the submodule.
9. Tracking changes on GitHub
When you look at the submodule on GitHub you can see the specific commit of the submodule that is referenced. This is as far as I know still not possible to see from within Visual Studio.
Looking at the commit to your repository you can even see the included changes from the submodule repository, not only that the reference was updated in your repository.
References and links
Sample project: https://github.com/rappen/CoolRappSolution
Helper library: https://github.com/rappen/JonasPluginBase
Full video describing this article: https://jonasr.app/git-vs-subm-video