
Sync Submodules
- 2 installs
- 29 repo stars
- Updated March 27, 2026
- different-ai/openwork-hub
Safely syncs a superproject and updates git submodules, handling dirty submodules like a modified Cargo.lock via per-repo stash.
About
Walks through inspecting dirty state, stashing inside a dirty submodule, pulling the root, and updating submodules to pinned commits. A developer uses it when submodule update fails or local changes would be overwritten by checkout.
- Per-submodule stash before update (stashes are per-repo)
- Full sync sequence: pull, submodule sync, update --init --recursive
Sync Submodules by the numbers
- 2 all-time installs (skills.sh)
- Ranked #498 of 733 Git & Pull Requests skills by installs in the Skillselion catalog
- Data as of Aug 3, 2026 (Skillselion catalog sync)
npx skills add https://github.com/different-ai/openwork-hub --skill sync-submodulesAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 2 |
|---|---|
| repo stars | ★ 29 |
| Last updated | March 27, 2026 |
| Repository | different-ai/openwork-hub ↗ |
What it does
Safely syncs a superproject and updates git submodules, handling dirty submodules like a modified Cargo.lock via per-repo stash.
Files
Quick Usage (Already Configured)
1) See what is dirty (root + submodules)
date
git status --porcelain
git submodule foreach --recursive 'echo "--- $name ($path)"; git status --porcelain'2) If a submodule is dirty, stash inside that submodule
Example for _repos/openwork when Cargo.lock blocks an update:
git -C _repos/openwork status --porcelain
git -C _repos/openwork stash push -u -m "wip: allow submodule update"Notes:
- Stashes are per-repo. You must run
git stashin the submodule itself. - Use
-uif untracked files exist.
3) Pull root + update submodules to the commits pinned by the root repo
git pull
git submodule sync --recursive
git submodule update --init --recursive
date4) Re-apply the stash (optional)
Only do this if you actually want your local submodule changes back.
git -C _repos/openwork stash list
git -C _repos/openwork stash popCommon Gotchas
error: Your local changes ... would be overwritten by checkoutmeans the submodule has local changes andgit submodule updateis trying to move it to the commit pinned by the root repo.- Avoid
git submodule foreach --recursive 'git pull'for normal syncing. Submodules are typically pinned to specific commits; pulling inside them can move them off the pinned commit and make the root repo look dirty. - If you intend to bump submodules to newer upstream commits, that is a different operation (it changes the root repo):
git submodule update --remote --recursive
git statusSubmodule Pin Is Unreachable ("not our ref")
Symptom:
fatal: remote error: upload-pack: not our ref <sha>while fetching a submodule.
Meaning:
- The root repo points at a submodule commit SHA that the submodule remote no longer advertises (history rewrite, deleted branch/tag, or missing permissions).
Fix (safe, local): 1. Pull root without touching submodules:
git pull --recurse-submodules=no2. Move the submodule to a reachable ref (example uses origin/dev):
git -C _repos/<name> fetch origin --prune
git -C _repos/<name> checkout dev
git -C _repos/<name> pull --ff-only origin dev3. Stage the gitlink in the root repo:
git add _repos/<name>
git statusFix (proper, shared):
- Open a PR that repins the submodule to a reachable commit and avoid force-pushing branches/tags that are used as submodule pins.
One-Liner Helper (Conservative)
This prints dirty submodules without changing anything:
git submodule foreach --recursive 'test -z "$(git status --porcelain)" || echo "DIRTY: $name ($path)"'# No configuration required.
#
# Optional:
# - STASH_DIRTY=1 to auto-stash dirty submodules when running scripts/sync-submodules.sh
.env
#!/usr/bin/env bash
set -euo pipefail
# sync-submodules.sh
# Purpose: Safely update submodules to commits pinned by the root repo.
# Behavior: Refuses to proceed if any submodule is dirty unless STASH_DIRTY=1.
STASH_DIRTY="${STASH_DIRTY:-0}"
echo "[sync-submodules] start: $(date)"
echo "[sync-submodules] pulling root"
git pull
echo "[sync-submodules] syncing submodule URLs"
git submodule sync --recursive
echo "[sync-submodules] checking dirty submodules"
DIRTY=0
while IFS= read -r line; do
# lines look like: "Entering '<path>'"
if [[ "$line" =~ Entering\ \'(.*)\' ]]; then
SUB_PATH="${BASH_REMATCH[1]}"
STATUS="$(git -C "$SUB_PATH" status --porcelain || true)"
if [[ -n "$STATUS" ]]; then
echo "[sync-submodules] DIRTY: $SUB_PATH"
if [[ "$STASH_DIRTY" == "1" ]]; then
git -C "$SUB_PATH" stash push -u -m "wip: allow submodule update"
else
DIRTY=1
fi
fi
fi
done < <(git submodule foreach --recursive 'true')
if [[ "$DIRTY" == "1" ]]; then
echo "[sync-submodules] abort: dirty submodules detected"
echo "[sync-submodules] re-run with STASH_DIRTY=1 to auto-stash"
exit 2
fi
echo "[sync-submodules] updating submodules (pinned commits)"
git submodule update --init --recursive
echo "[sync-submodules] done: $(date)"