DEV Community

Cover image for Solved: What in the world would you call this…?
Darian Vance
Darian Vance

Posted on • Originally published at wp.me

Solved: What in the world would you call this…?

🚀 Executive Summary

TL;DR: A nested .git folder within a Git subdirectory creates ‘phantom submodule’ behavior, preventing the parent repository from tracking individual files and leading to deployment issues. This problem can be resolved by either removing the nested .git folder, formalizing it as a proper Git submodule, or performing a ‘scorched earth’ reset for a guaranteed clean state.

🎯 Key Takeaways

  • A ‘phantom submodule’ or ‘Git Nesting Doll’ occurs when a subdirectory contains its own .git folder, causing the parent repository to track it as an empty pointer instead of its actual files.
  • Git status will show ‘modified: (new commits)’ for the problematic directory, but files within it cannot be added or committed directly.
  • Solutions range from the quick fix of removing the nested .git folder (destructive to inner history) to formalizing it as a proper submodule (preserving history) or a ‘scorched earth’ reset for stubborn cases.

Struggling with a Git subdirectory that won’t track files? Learn why a nested .git folder creates ‘phantom submodule’ behavior and discover three battle-tested methods to fix it, from the quick-and-dirty to the permanent solution.

What in the World Would You Call This? Taming Git’s Phantom Submodules

I’ll never forget it. 3 AM, a Thursday morning, and a ‘critical’ hotfix deployment to production. All the CI checks were green, tests passed, the pipeline glowed with success. We hit the big red button. Ten seconds later, alarms blare. The application on prod-app-01 is crash-looping. The logs scream FileNotFoundException: /etc/app/config/prod-secrets.json. I SSH in, heart pounding, and navigate to the directory. It’s empty. The entire prod-secrets/ directory, which should have been full of config files, was just… gone. After a frantic half-hour, we found the culprit. A junior dev, trying to be helpful, had run git init inside that directory by mistake. Our parent repo saw it, shrugged, and just committed an empty pointer to it instead of the actual files. We’ve all been there, and that phantom commit cost us an hour of downtime and a lot of sweat.

So, What’s Actually Happening Here?

When you see this in your terminal, it’s Git trying to be smart, but in a way that’s incredibly confusing at first glance:

$ git status
On branch main
Your branch is up to date with 'origin/main'.

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
        modified:   src/vendor/some-library (new commits)

no changes added to commit (use "git add" and/or "git commit -a")
Enter fullscreen mode Exit fullscreen mode

You see modified: src/vendor/some-library, but you can’t add it, you can’t commit it, and Git won’t show you the files inside. This happens because the some-library directory contains its own .git folder. The parent repository sees that .git folder and says, “Whoa, that’s another repository’s territory. I’m not going to track its individual files. I’ll just track which commit that repository is on.”

It’s treating it like a submodule, but without the proper setup in your .gitmodules file. I call it a “Phantom Submodule” or a “Git Nesting Doll”. It’s a repository within a repository, and it’s a common headache.

Three Ways to Fix This Mess

Depending on your goal and how much you value the history within that nested repo, here are the three paths I usually take, from the quick-and-dirty to the architecturally sound.

Solution 1: The Quick Fix (Just Nuke the .git Folder)

This is the most common solution and, honestly, the one you’ll use 90% of the time. The problem is the nested .git directory. The solution? Get rid of it.

When to use this: You downloaded a library, cloned a project into another, or accidentally ran git init, and you do not care about the git history of the inner folder. You just want its files to be part of your main project.

  1. Navigate to your project’s root directory.
  2. Simply remove the .git directory from the subdirectory. Be careful with rm -rf!
# The path here is the subdirectory that Git is ignoring
rm -rf ./src/vendor/some-library/.git
Enter fullscreen mode Exit fullscreen mode
  1. Now, run git status again. The “submodule” entry will be gone, and Git will suddenly see all the files in that directory as new, untracked files.
  2. Add them like you normally would.
git add src/vendor/some-library/
git commit -m "feat: Absorb some-library files into the main repo"
Enter fullscreen mode Exit fullscreen mode

Warning: This is a destructive action for the nested repository. You are permanently deleting its commit history. If you might need that history, do not use this method. Proceed to Solution 2.

Solution 2: The ‘Right’ Way (Embrace the Submodule)

Sometimes, you want to keep the two projects separate. Maybe some-library is an open-source tool you use, and you want to be able to pull updates from its own remote. In this case, you should formalize the relationship by properly adding it as a submodule.

When to use this: The subdirectory is a legitimate, separate project that you want to link to your main project while keeping its history and identity intact.

  1. First, remove the “phantom” entry from Git’s index. We need it to stop tracking that path before we can re-add it properly.
# Note the trailing slash is important here
git rm --cached src/vendor/some-library
Enter fullscreen mode Exit fullscreen mode
  1. Commit this removal to clean up the state.
git commit -m "chore: Remove incorrect submodule reference"
Enter fullscreen mode Exit fullscreen mode
  1. Now, properly add the directory as a submodule. You’ll need the URL of its remote repository.
# git submodule add [repository_url] [path]
git submodule add https://github.com/some-user/some-library.git src/vendor/some-library
Enter fullscreen mode Exit fullscreen mode

This creates a .gitmodules file and correctly registers the submodule. Now you can manage it properly, pulling updates and committing specific versions.

Solution 3: The ‘Scorched Earth’ Reset

I’ve seen situations where the Git index gets so confused that the above methods don’t work cleanly. This is my “when all else fails” approach. It’s brute force, but it’s clean and guaranteed to work.

When to use this: The other methods aren’t working, or you just want to be 100% certain you have a clean slate without any lingering Git weirdness.

  1. Move the problematic subdirectory completely out of your project.
mv src/vendor/some-library /tmp/some-library-backup
Enter fullscreen mode Exit fullscreen mode
  1. Commit the deletion. Your repository now officially has no knowledge of this folder.
git add src/vendor/
git commit -m "chore: Forcibly remove some-library to fix tracking"
Enter fullscreen mode Exit fullscreen mode
  1. Delete the .git folder from your backup copy.
rm -rf /tmp/some-library-backup/.git
Enter fullscreen mode Exit fullscreen mode
  1. Move the folder (now clean of its own Git history) back into your project.
mv /tmp/some-library-backup src/vendor/some-library
Enter fullscreen mode Exit fullscreen mode
  1. Add and commit the files. They will now be seen as brand new additions.
git add src/vendor/some-library/
git commit -m "feat: Re-add some-library files with correct tracking"
Enter fullscreen mode Exit fullscreen mode

Which One Should You Choose?

Here’s a quick breakdown to help you decide.

Method Speed Preserves History Best For…
1. Quick Fix Fastest No (destroys inner repo history) Accidental git init or when you just want the code, not the history.
2. The ‘Right’ Way Medium Yes (for both repos) Managing dependencies and linking separate but related projects correctly.
3. Scorched Earth Slowest No (destroys inner repo history) When things are truly broken and you need a guaranteed clean state.

At the end of the day, don’t feel bad when you run into this. It’s a rite of passage. It’s a quirk of how a powerful tool like Git works, and understanding why it happens is the key to not letting it derail your 3 AM deployment. Hopefully, this gives you a clear path out of the woods next time you find a phantom submodule lurking in your repository.


Darian Vance

👉 Read the original article on TechResolve.blog


Support my work

If this article helped you, you can buy me a coffee:

👉 https://buymeacoffee.com/darianvance

Top comments (0)