Ask HN: Why are Git submodules so bad?
I have been a git user for a long time, but I've never used Subversion or any other VCS more than a little.
I also hardly use Git submodules, but when I do, I don't struggle.
Yet people talk about Git submodules as though they are really hard. I presume I'm just not using them as much as other people, or that my use case for them happens to be on their happy path.
So why are Git submodules so bad?
168 comments
[ 3.2 ms ] story [ 210 ms ] thread1. Git clone not cloning submodules. You need `git submodule update` or `git clone --recursive`, I think
2. Git submodules being out-of-sync because I forgot to pull them specifically. I'm pretty sure `git submodule update` doesn't always work with this but maybe only when 3)
3. Git diff returns something even after I commit, because the submodule has a change. I have to go into the submodule, and either commit / push that as well or revert it. Basically every operation I do on the git main I need to also do on the submodule if I modified files in both
4. Fixing merge conflicts and using git in one repo is already hard enough. The team I was working on kept having issues with using the wrong submodule commit, not having the same commit / push requirements on submodules, etc.
All of these can be fixed by tools and smart techniques like putting `git submodule update` in the makefile. Git submodules aren't "bad" and honestly they're an essential feature of git. But they are a struggle, and lots of people use monorepos instead (which have their own problems...).
In git parlance, the submodule porcelain is hard to use (but the plumbing is good)
You can have a 'pull, merge, push'-only system, but at that point we're re-inventing subversion. So making it more advanced would mean we also need to have the knowledge and skills to do other activities correctly and that means the tooling can't make as many choices for you because there simply isn't a default way that works all the time.
Most efforts at git-alternatives run in to the same problems and either they'll be just as advanced and have the samen benefits and downsides or they end up less advanced but now it's not equally useful and you can't really make it work right.
So? Maybe Subversion is all most developers need?
I used subversion for a long time and was resistant to moving ardour.org to use git instead. 24hours after we switched (we never use 3rd party git hosting as our canonical repo), I was already convinced it was not the right choice, but an excellent choice.
Being distributed, it solved the main gripes with SVN; it also added a better merging algorithm (https://foswiki.org/pub/Development/SVK/svk-visual-guide.pdf), solving another big gripe.
I was actually satisfied of it, and surprised that it never got attention, in particular, because there were no requirements in order to use it with existing SVN repositories. I'm actually baffled, because SVN is still active, so SVK would still be useful nowadays.
I think there is a reason why this XKCD was made: https://xkcd.com/1597/
It will work fine over a Windows share, Samba, NFS, and so on. It doesn’t need svn or http protocol to operate.
SVN doesn't work peer-to-peer or over email. And that's fine. It's just not ready to go with only local tools.
Of course, you can use GitHub with Subversion just fine, but that wasn't the point. The point was that Subversion alone is never enough if you want to collaborate.
That’s a bummer. It would help us point the discussion in the right direction.
> SVN doesn't work peer-to-peer or over email.
Why not? What is so different about people having their own subversion repositories over file protocol vs people having their git repositories?
Why would you not be able to send a subversion patch in an email?
As someone who uses git for 10 years, I understand it may not be as ergonomic as with git. But why not?
> The point was that Subversion alone is never enough if you want to collaborate.
Why not? Is git enough?
Sure, we use one canonical repository. All our “pull requests” are really merge requests, mostly to the main branch. So that’s pretty centralized, right? So why use a distributed VCS? Well, why use a local editor or IDE for code that is ultimately going to end up in the cloud somewhere? Sure, you might want to out of preference, but why should you be forced to? The fact is that wherever the code will end up is besides the point when it comes to how to develop it.
The truly important thing about distributed VCS is that it forces almost all of the operations on the repository to be usable locally. And why should it not be? What’s “git log”, “git blame”, or “git merge” got to do with whether there is one canonical repo or a hierarchy of upstreams?
I think that this idea that non-distributed VCS is somehow the default—as in the obvious, simple thing to implement—is just backwards. Of course the default assumption for any VCS operation—unless it has a name like “send-email”—should be that it operates on your own local copy.
Sure, we use a centralized repo structure. And the only call-central-command operation I use is “git push”. All the other fiddling and querying—and all the things that make version-control-as-history useful—is local.
Sure, CVS was limited but it was reliable and straightforward.
It was so reliable that people complained about SVN using a DB (Berkeley DB) as backend, as manual fixing of CVS files was a "normal" part of operation and people didn't believe that might not be needed ...
Like I’m gonna bother to set up a Subversion remote for every little repo that I create for myself.
This means you usually only have one svn repo, and you set it up the way you like. As an example, you may set things up so you can checkout:
server:/proj/small/hello - to get a single project
server:/proj/small- to get all small projects
server:/proj - to get all projects
If you already have one of those checked out, adding new project is as simple as "mkdir bar", "svn add bar", "svn commit". So juch easier than making new github repo. And multi-level hierarchical project nesting is still something impossible in git.
Were you paying attention to what I just wrote!? `git init`. What do I need a remote on the Internet for?
But yes, if all your work is on one machine, and you have backups of it, there is not much point in svn.
Git doesn't even technically have branches, just pointers to commits which can easily get mixed up, go headless and fail out in ways that just never happened in SVN.
And rebasing a branch with several commits can be a nightmare since you have to re-merge almost (but not exactly) the same code over and over again at whatever state it was in at some time in the past when some previous commit happened - unless you abort out and squash first. In SVN, you just merged the whole branch once and when both were in their final current state.
Of course, git's a lot more powerful, but with that comes complexity. SVN branching and merging was a snap comparatively.
* https://subversion.apache.org/docs/release-notes/1.10.html#c...
* https://subversion.apache.org/docs/release-notes/1.11.html#c...
I mean, if that's a reason to say git doesn't technically have branches, then neither does svn. It has subdirectories that you can make copies of at any level, and copy commits between them at any level, such that you can make an amazing repo-within-a-repo mess not possible in git.
Older systems like CVS are also still in use, but it appears that none of the old systems really lasted more broadly and aren't useful for the needs of today.
It's what enables three-way merges, and makes rebasing much more manageable.
The ability to traverse and jump to some other point in history is really missing here too.
Local commits imply traversing history, but even that is terrible with Git. You can't just do obvious things like "git previous" or "git next" or "git checkout <commit hash>".
Also, git checkout <commit hash> works?
What happens if you amend a commit after checking it out?
Hm. We must use git completely differently then. I can't really imagine what what I'd do without them.
> there's nothing they convey that commit messages don't express better.
Presumably you'd still want to maintain a list of HEADs, so you just want to alway refer to them by hash instead of a branch name? That's fine I guess -- not sure what it buys you.
> What happens if you amend a commit after checking it out?
Then it becomes a new commit? Not sure what you're getting at.
GitHub has come a long way. But I would guess the main reason it managed to become dominant is not because it had a better product. (At the time, it didn't.) It's because Git benefited from the celebrity of its author, Linus Torvalds.
(Nowadays mercurial can do rebase/amend just fine.. But it is too late)
I do like to squash and rebase before moving changes upstream. But, to me, that isn't really history quite yet. Or at least, it's not history that's worth recording. All those micro-commits from the work in progress are, in some ways, more akin to my editor's undo history. Which is also something I don't save.
It's also clear, in hindsight, that Mercurial's original position on this subject failed to anticipate AWS credentials accidentally being committed to source control.
But I have also seen (and done) some amount of history rewriting in long-lived branches that I don't think would have been necessary if Git had had some of Mercurial's ergonomics. Workflows for merging two different repositories while retaining the commit history from each, for example.
Although once stuff is pushed to the public repo you're probably going to want to change those keys regardless. And if it's on the local one, there's plenty of options for removing the commit.
It was also much slower than git. But I knew someone working on Google Code at the time who liked it better because it was "clean" and in Python.
Some other things it was slower, that's changed over time ofc.
Also, in terms of clean history, mercurial has best of both worlds with phases and hidden by default commits to keep track of such cleanup.
Yeah, it has more features now but at the time it didn’t. There was something called patch queue you could use for early stage work but that was all.
It's been long enough I don't remember the details about why I didn't like mercurial, but fame power had nothing to do with it, nor did website integration - I was only using it locally. How it worked just didn't fit with how I thought about version control.
Git has and always had a singularity bad UI amongst dvcs.
Not entirely true: checkout was split into switch and restore, which is something I guess.
And, yes: submodules are really useful, as well as a PIA.
git config --global submodule.recurse true
https://git-scm.com/book/en/v2/Git-Tools-Submodules search for "git config"
Though it’d be nice if `git commit’ supported it too and just did a `git submodule foreach git commit …`.
I've never thought of doing commits in submodules. We use them a lot at work but only in a "import a specific revision of this other repo into our repo" sense, if we needed to make changes we would do them in the repo that the submodule points to and then just update the ref that the submodule points to.
What's your use case for making and committing changes inside the submodule tree?
The use case being that you can work on and update some independent system/repo while getting real-time feedback on how the changes interact with the system as a whole.
When working on both, it's really annoying to have to commit to a dependency's repo, push, and pull down in the dependent repo. If you do the commit in the submodule, it's just immediately available...and you can still have whatever remote to push to. So it just cuts down on the number of checkouts you have to have.
For our purpose that's definitely worse; the submodule is supposed to be a pointer to a specific tree, with that tree being the same for all developers. If we want to change the tree that is pointed to, we should commit and push a change to the submodule ref.
doing a quick fix that changed an API and had to be done in backend and frontend and in the main.
though it's quite similar to todays gitops flow, but with submodules :)
This is git in a nutshell. Most defaults are very bad, and so using git from the command line is an exercise in learning which flags to set to achieve a sane workflow.
In this case, "git clone" clones a repo 99% of the time, because 99% of repos are shallow and simple. For "git clone" to only clone the top level when you have su modules instead of prompting to ask if you want a deep clone, or doing a deep clone by default because that's the expected behavior, is pretty poor design IMO.
If an important software tool is hard to use to the point that most people avoid it, then it's not fine. It's broken.
* some folks had to deal with a LOT of submodules back in the day; for instance, it wasn't uncommon to have a dozen+ in your "vendor/plugins" directory in a Rails 1.x app. More submodules, more problems
* sometimes submodules get used to "decompose a monolith" into a confusing tangle of interdependent repos. Actually changing stuff winds up requiring coordination across multiple repos (and depending on the org, multiple teams) and velocity drops to zero. Eventually somebody declares "submodules SUCK!!!!!one!!!" and fixes things by re-monolithing...
I think they're difficult to use, because it breaks my mental model of how I expect a repository to work. It creates a new level of abstraction for figuring out how everything is related, and what commands you need to be able to keep things in sync (as opposed to just a normal pull/branch/push flow). It creates a whole new layer to the way your VCS works the consumer needs to understand.
The two alternatives are
1. Have a bunch of repositories with an understanding of what an expected file structure is, ala ./projects/repo_1, ./projects/repo_2. You have a master repo with a readme instructing people on how to set it up. In theory, there's a disadvantage here in that it puts more work on the end user to manually set that up, but the advantage is there's a simpler understanding of how everything works together.
2. A mono repo. If you absolutely want all of the files to be linked together in a large repo, why not just put them in the same repo, rather than forking everything out across many repos. You lose a little flexibility in being able to mix and match branches, but nothing a good cherry-pick when needed can't fix.
Either of these strategies solve the same problem sub-modules are usually used to solve, without creating a more burdensome mental model, in my opinion. So the question becomes why use them and add more to understand, if there are simpler patterns to use instead.
What they're really for, is vendoring someone else's code into yours. They're still not great even at that, but sometimes they're the best option.
IE is that "a" usecase or "the" usecase? I've never seen submodules used for that, only for internal dep management, so if there's content about "what they're really for," I'd love to read more.
It'd be nice to use submodules for this but I gave up years ago.
The other big use is where you have your own libraries and you'd like to be able to share them across projects. My friend does game jams and has his own simple engine, he versions the engine and adds abilities and uses it across game projects.
Vendoring usually implies some kind of snapshot-copying of third-party code. A repo you depend on by value. That's actually solved by subtrees. If you buy that metaphor, then submodules, in contrast, express a dependency by reference.
tl;dr anyone vendoring with submodules is prolly doing it wrong
This is what I do. I have something like 17 code repos organized this way, plus lots of testing repos, plus an extra "hub" repo. (Credit to a friend for calling this repo "hub": short, to the point, requires no explanation.) The hub repo is a bunch of scripts and makefiles that configure everything and even clone the rest of the repos for me. It also has special grep and find scrips that will run on all of the repos as their target. The hub repo just needs one env var to tell it where the root of all the repos is. Note that in the file system the hub repo is under the root and a sibling of the code repos, not their parent in the file system.
Each code or test repo has an "externs" subdir populated only with softlinks to the other repos on which it depends. The scripts configure this by default, but it is also straightforward to configure by hand if you want to do something non-typical. For example, if you want to have multiple versions of a repo checked out on, say different branches/commits, you can do that and name each directory with a suffix of the branch/commit. Then the client repos can just point at the one they want. You can have all kinds of different configurations set up at any time. Doing this makes it straightforward to know what you depend on just by looking at the softlinks. There is no confusion at any time.
There are ways of configuring the system that do not even need all of the repos, so this is ideal. Using the hub repo makefile I can clone the whole system with one make target (after cloning hub), I can build the whole system with one target, I can test the whole system with one target. It is a testament to how well it works that I don't even know exactly how many repos I have. In short, it works great.
You end up with a lot of gotchas instead of them just working.
The mental model of juggling multiple repos in a non-atomic way also violates the rule of least astonishment. Working with read only submodules smooths this part out at least.
GUI support is slowly getting better at least.
Except practically nothing in git is unsafe, it's like Plan9's Venti in the sense that you can only add content to it. You never lose data, but you can easily lose your bearings.
I presume a major reason why there's not much effort put into UX guard rails preventing losing one's bearings in the day-to-day work is a product of the fundamental fact that the underlying committed data is always there. People just need to at least familiarize themselves with `git reflog` to help lose the anxiety.
That’s not always true, due to reflog expiration and GC.
This is an important difference to e.g. Subversion where you can delete branches/tags just to hide them, but the history always remains 100% there and nothing is ever really deleted.
https://nvd.nist.gov/vuln/detail/cve-2018-17456
rce via recursive submodule clone ftw
I've not seen many uses of submodules that weren't better served by adding the package from pypi/npm/crates/...
[1]: https://12factor.net/dependencies
If everyone used git submodules for deps, you'd end up with a (block) chain (if everyone's commits were signed) for deps.
I once worked in C++, and the lack of a (standard?) package manager was killing me for pretty much that reason. "This says it's using curl, but did they compile it with a weird flag, or was their version just old?", and stuff like that.
For the case you describe, a submodule is the next best thing to proper package management, and I'm glad those submodules are there to save you! But in themselves, the submodules don't guarantee build instructions, and the same guarantees of exactness can be had by locking dependencies.
I feel like your message mostly reinforces my belief in the NEED for package manager for every such language combo, having seen the immense value in other systems, and the extremes that we have to go to when they're missing.
I hear the Nix (and Guix) community has done great steps to manage these stray systems into reproducibility, using submodule chains of the sort you describe, but augmented with standardized build info to make it system-wide pkg management.
No, there are 3-4 suitable package managers [1] depending on what your bar for an acceptable package manager is. It's just C and C++ engineers have gone so long without one that they don't even think about it.
Honestly, we just need to start expecting more (and contributing more) with respect to unpackaged C and C++.
[1] vcpkg, Conan, nix, and spack off the top of my head
Just a few off the top of my head: identifying/resolving transitive dependencies and conflicts, separating development-only deps, optional dependencies for extra features, dependency ranges for pinning and exact locking, proxying/caching for offline-ish rebuild, auto-upgrades, license checking (GPL...), CVE flagging... All these are ~standardized per language.
If you've got (say) a Python script in git, and submodules seem like a good idea for specifying all your dependencies, instead of going towards a package manager, it feels to me like we're a long way away from sound, practical engineering.
For language agnostic, I'm sure the Nix/Guix folks can hook you up with a variant of this, even while using git (maybe even submodules!), but all the value I specified above is still there.
I guess I don't mind git, maybe even using submodules, but I hate the substitution of good package management hygiene over "just import the next folder down, which I submoduled now".
You can use release tags when using Git submodules, which makes it closer to a "normal dependency management system".
It's better than using the commit hash or branch, but still not that great without flexible semver dependencies...
I've enjoyed the middle ground that some package managers provide, where one can point to a git repo's tag/branch/commit hash instead of the upstream package repository of choice.
This means if you REALLY wanted to do some vendoring approach with custom code, you don't have to do the git submodule, just use the dependency as usual, it's just fetched from git, and built the normal way.
It's not for everybody (I still recommend to point to known packages first), but it's cool to acknowledge the usecase of "I need to get this from git repo" without being savages about it and throwing away our tools.
Using git submodules is no worse or better than say npm in that regard.
If you lack a dependency mgmt system, work mostly solo or in very small and tightly coordinated team and just need some githubbed project to make yours work, submodules may be the right tool for you.
There is nothing "bad" about submodules. They do what they were intended to do very well. They are often misused because people don't take the time to understand how things work (event at a conceptual level) before diving into it.
I've found submodules most useful when I'm tracking an external dependency that doesn't change often -and- I don't have a dependency management system to lean on.
For example, I have a repository of 100+ various bash utility scripts. One of the scripts depends on another repository with 500MB of data. I don't use it often so I don't need that large repo cloned alongside my bash scripts every time. In this case:
1) The dependency doesn't change often and I like being deliberate when using a new version of that dependency.
2) The dependency is optional and can simply be ignored when using Git's default clone behavior for my repo.
3) Since it's just a bunch of bash scripts, I don't have a package management system to lean on. I use these scripts on multiple *nix distributions.
Could I make it work without Git submodules? Yes. I would have added the dependency to .gitignore and wrote a script to `git clone` the dependency and set it to a specific revision. Submodules just makes that process a lot easier.
You were elaborating on the comment, weren't you? And that comment was quite vague too.
> There is nothing "bad" about submodules.
The difference between "what are uses and misuses" and "what is good and bad" is not enormous. I think you're nitpicking. Plus, there are things that are flat-out bad. It's a human-made program under constant development, after all.
What was vague about that comment?
> The difference between "what are uses and misuses" and "what is good and bad" is not enormous.
This is incorrect. The definition of "bad" is something of low quality. Misusing a tool and then calling it low quality is dumb.
> Plus, there are things that are flat-out bad. It's a human-made program under constant development, after all.
What about git submodules is "flat-out bad"?
It's ironic that you're criticizing others' comments on being vague and providing non-answers while you've contributed nothing to the discussion. Maybe educate yourself on technical things and you won't need to waste your time having meta discussions on internet forums. Looks to me like you want to contribute but have nothing of value to say.
Do you want me to grep the changelog for fixed mistakes?
Do you think they fixed all of them, just now, june 2022?
There are mistakes, which are flat-out bad. Trying to fix them is a continuous process that is not complete. And there are probably bad decisions that will be baked in forever, but that's not necessary to prove my point.
> It's ironic that you're criticizing others' comments on being vague and providing non-answers while you've contributed nothing to the discussion.
It's not ironic. One person asks for input. Another person gives a vague answer. I call the answer too vague to be useless. That doesn't make it my job to be specific now. I'm not answering OP. I'm giving feedback on a different answer.
Which is what you were originally doing too, so don't complain that it's not "of value".
And I'm not criticizing submodules here either.
I'll make my argument more clear. The idea that any software has "nothing bad" about it is ridiculous. That's a generic argument for all software. If you really want examples, look at the changelog. But I think it should be obvious. If you think it would even be useful for me to provide an example, then there's probably been a terrible miscommunication.
Every software out there has defects. Bugs, performance issues, UX issues, etc. To assume that anyone in this thread actually means that submodules have no defects whatsoever... well... that's just creating a straw man to tear down because you're grumpy or something.
The issue appears to be with your understanding of what "bad" means in this context. When the OP (and commenter I was responding to) says "bad", they're referring to the overall experience of using git submodules. That specifically does not include the minor fixes/improvements you'd find in a changelog.
By your definition, all software is "bad" because all software has defects in varying degrees. That of course makes the entire conversation pointless and pedantic.
Git submodules work just fine when they're used properly. I've been using them for years without issue and so have many other commenters here. Are there things we (the users of git submodules) would like to see improved? Of course! But that doesn't violate the statement that there is "nothing bad" about submodules in the context of this discussion.
I really hope this helps you navigate conversations with a little more nuance. If not, that's okay too. Have a nice weekend.
Ah, here's the part where things went wrong.
I said "what's good or bad", which is not a judgement of the overall experience. It's the same thing as "uses and misuses" from a different angle.
Talking about what software is better at and worse at is a good response to "why is it bad?". "No it isn't" is mostly a waste of time, especially when a lot of people have bad experiences.
And don't say that the miscommunication is in my mind when you glossed over the word "good" entirely.
> By your definition, all software is "bad"
By my definition, all software has bad elements and bad contexts. And good ones, too. Asking where those lie is not pedantic or pointless at all.
> that doesn't violate the statement that there is "nothing bad" about submodules in the context of this discussion.
If an "overall experience" being good enough means that there is "nothing bad", I'm not the one lacking nuance. You've just obliterated nuance.
I've always thought of them as a way to "vendor" a git repository, i.e. declare a dependency on a specific version of another project. I thought they made sense to use only when you're not actively developing the other project (at least within the same mental context). If you did want to develop the other project independently, I thought it best to clone it as a non-submodule somewhere else, push any commits, then pull them down into the submodule.
I'm glad to know that I'm not the only one to use them as such.
I have about ten different games, all using the same engine, but they were written over the course of many years and so are written against different commits within that engine repo, and git submodules capture that idea perfectly.
If I didn’t have something like that where I effectively had a long-lived library which I wanted to pull into multiple projects, I probably wouldn’t bother with the submodules. But it’s *so* much more convenient to have them in submodules than to copy the engine code separately into each project’s separate repos and then manage applying every change to the engine in all the different game repos individually.
Really, git submodules are exactly the same thing as subversion ‘externals’, but always pinned to a specific commit (which is an available-but-not-enabled-by-default option under subversion), and with a substantially easier interface so maybe folks who don’t need them are more likely to notice them and grumble about how they don’t solve an issue they have?
IMHO git submodules are a huge quality of life improvement over the same system from subversion (as was available in subversion back in the 1.8.x era; I haven’t really used svn in anger in a long time). I definitely wouuldn’t want to go back, or to not have them available.
So you have to understand the tradeoffs and make every decision at every step. It's the safe option.
Like, what happens if you remove a submodule between revisions? Git won't remove the files, you could have stuff in there. So it just dangles there, as a pile of changed files that you now have to separately, manually remove or commit, because it's no longer tracked as a submodule. And then repeat this same kind of "X could sometimes be risky, so don't do it" behavior for dozens of scenarios.
All of which is in some ways reasonable, and is very much "Git-like behavior". But it's annoying, and if you don't really understand it all it seems like it's just getting in your way all the time for no good reason. Git has been very very slowly improving this behavior in general, but it's still taking an extremely conservative stance on everything, so it'll probably never be streamlined or automagic - making one set of decisions implicitly would get in the way of someone who wants different behavior.
You know, the people that already carry everyone else through their job.
I once enabled it as submodule to test. It adds nothing and from that moment any change in the child creates a change in the parent, which for my use case is totally unnecessary (I want both of them to be independent, even if hierarchically one is inside the other).
Submodules are probably a good option to have libraries that you rarely touch, so you can update/modify them as with a maven/gradle project. For most other user cases submodules make more problems that advantages.
Annoying to setup and keep in sync "correctly" (for given project, EDIT: especially edits).
Sure, this depends a bit on the other tooling and for what reason you use.
This doesn't meant they are hard or complicated, but the moment the defaults do not do what most times is needed and you can't change the default (in the project, instead of user settings) i.e. need to do additional manual steps all the time some people will hate it many will dislike it.
[0]: https://github.com/git/git/blob/master/contrib/subtree/git-s...
If I ever have to untangle a messed-up repository containing a subtree again in my life I’m quitting development and moving to a cabin in the woods.
If we used containers, a Docker compose file would make things even simpler for us, but alas.
I'd like to jettison the entire model of "reference another repository that you may or may not have", along with the `.gitmodules` file as anything other than optional metadata, and instead have them be a fully integrated thing that necessarily comes along with any pull/push/clone.
One thing I haven't seen mentioned in this thread though is that they force an opinion of HTTPS vs SSH for the remote repository.
If a developer usually uses SSH, their credential manager might not be authenticated over HTTPS (if they even have one configured at all!) If they usually use HTTPS, they might not even have an SSH keypair associated with their identity. If they're on Windows setting up SSH is generally even higher friction than it is on Linux.
For someone just casually cloning a repository this is a lot of friction right out of the gate, and they haven't even had to start dealing with deciphering your build instructions yet!
-------
Personally I still use Git submodules despite their flaws because they mesh well with my workflow. (This is partially due to the fact that when I transitioned from Hg to Git it was at a job that used them heavily.)
The reality is every solution to bringing external code into your project (whether it's using submodules, subtrees, tools like Josh, scripts to clone separately, IDE features for multi-repo work, ensuring dependencies are always packages, or just plain ol' copy+pasting) all have different pros and cons. None of them are objectively bad, none are objectively best for all situations. You need to determine which workflow makes the most sense for you, your project, and your collaborators.