Shattered Vales Studio Inc.

Working with Submodules & Shared Packages

Some code is used by more than one project: a shared Unity toolkit used by several games, or a common library used by several SaaS services. This guide explains the ways to share code between repositories, which one to choose, and how to work day to day with Git submodules and Unity Git packages.

Step 1: Choose the Right Way to Share Code

Never copy and paste shared code between repositories. Copies drift apart, and every bug has to be fixed several times. Use one of these approaches instead:

ApproachHow it worksBest for
Versioned packageThe shared code is published as a package (UPM, NuGet, or npm) with version numbers. Projects depend on a specific version.The default. Stable libraries used by several projects.
Git submoduleOne repository is embedded inside another at a fixed commit.Code you need to edit alongside the project, or that can't be packaged.
MonorepoRelated projects live in one repository.Tightly coupled projects released together. A lead's decision.

The studio preference is packages first: they have clear versions, release notes, and a simple upgrade path. Submodules are powerful but easy to get wrong, so use them only where a lead has set them up. Whichever a project uses is documented in its README.

Step 2: Understand How Submodules Work

A submodule is a full Git repository living in a folder of another repository (the parent). The parent doesn't store the submodule's files. It stores two things:

# .gitmodules
[submodule "Packages/com.shatteredvales.core"]
    path = Packages/com.shatteredvales.core
    url = git@github.com:Shattered-Vales-Studio/unity-core.git

Because a specific commit is checked out, the submodule folder is normally in "detached HEAD" state. That is expected; see Git Fundamentals.

Step 3: Clone and Sync Projects with Submodules

A normal clone leaves submodule folders empty. Clone with submodules from the start:

git clone --recurse-submodules git@github.com:Shattered-Vales-Studio/<repo>.git

Already cloned without them, or someone updated a submodule pointer? Bring every submodule to the commit the parent expects:

git submodule update --init --recursive

To have pull, switch, and similar commands update submodules automatically, turn on this setting once:

git config --global submodule.recurse true

Step 4: Update a Submodule to a Newer Version

Updating a submodule is a change to the parent repository, so it goes on a branch and through a pull request like any other change:

git switch -c chore/issue-77-update-core Dev
cd Packages/com.shatteredvales.core
git fetch --tags
git checkout v1.5.0                              # a release tag, not an unreleased commit
cd ../..
git status                                       # shows: modified: Packages/com.shatteredvales.core (new commits)
git add Packages/com.shatteredvales.core
git commit -m "chore(deps): update core toolkit to v1.5.0"

Read the shared library's release notes before updating, and test the project afterwards. Mention any breaking changes in the pull request.

Step 5: Change Shared Code

Shared code has its own repository, branch protection, and reviewers. Changes go there first, then the parent is updated to use them:

  1. Inside the submodule folder, switch to its development branch and create a feature branch (the shared repository has its own issue numbers).
  2. Commit, push, and open a pull request in the shared repository.
  3. Once it's merged (and released, if the library uses tags), update the parent's pointer as in Step 4.
cd Packages/com.shatteredvales.core
git switch Dev && git pull
git switch -c feature/issue-12-tooltip-delay
# ...edit, commit...
git push -u origin feature/issue-12-tooltip-delay
gh pr create --base Dev

Never commit inside a submodule while it's in detached HEAD state. Those commits aren't on any branch and are easily lost. Switch to a branch first.

Step 6: Use Unity Packages from Git

Unity's Package Manager (UPM) can install a package straight from a Git repository, which is the studio's preferred way to share Unity code. Add it to Packages/manifest.json, pinned to a release tag after the #:

{
  "dependencies": {
    "com.shatteredvales.core": "git@github.com:Shattered-Vales-Studio/unity-core.git#v1.5.0",
    "com.unity.inputsystem": "1.11.2"
  }
}

Step 7: Fix Common Submodule Problems

SymptomFix
Submodule folder is emptygit submodule update --init --recursive
git status shows "modified: <submodule> (new commits)" and you didn't mean to update itReset the submodule to the commit the parent expects: git submodule update. Don't stage it.
"modified content" inside a submoduleYou have uncommitted edits in the submodule. cd into it and commit them on a branch, or discard them with git restore .
Permission denied (publickey) or "repository not found" for the submoduleYou don't have access to the shared repository, or your SSH key isn't set up. Ask your lead for access.
Merge conflict on a submodule pathDecide which commit the parent should use, cd into the submodule and check it out, then git add <path> in the parent.
UPM error resolving a Git packageCheck you can clone the package URL from the terminal and that the tag exists: git ls-remote --tags <url>