Managing shared files across different git repos - chunhualiao/public-docs GitHub Wiki
Problem: I often have some configuration files such as .clinerules I would like to reuse across different git repos. How to manage these configuration files efficiently? Having separated copies in different git repos is a pain.
Totally hear you—copy-pasting the same config into every repo gets old fast. Here are solid, battle-tested ways to keep a single source of truth and reuse it everywhere.
There are several effective strategies for managing shared configuration files across multiple git repositories. Here are the most practical approaches:
Create a central repository for all your shared configs, then symlink to them from your projects:
# Create a central config repo
~/dotfiles/
├── .clinerules
├── .eslintrc
└── other-configs/
# In each project, create symlinks
cd ~/my-project
ln -s ~/dotfiles/.clinerules .clinerules
Pros: Simple, changes immediately reflected everywhere Cons: Symlinks can be fragile, team members need the same setup
Add your config repository as a submodule in each project:
# Add config repo as submodule
git submodule add https://github.com/you/shared-configs.git configs
ln -s configs/.clinerules .clinerules
# Update submodule when configs change
git submodule update --remote
Pros: Version controlled, explicit dependency Cons: Submodules can be complex, requires manual updates
Publish configs as a package (npm, pip, etc.) depending on your ecosystem:
// package.json for npm example
{
"devDependencies": {
"@myorg/shared-configs": "^1.0.0"
}
}
Then copy or reference configs during setup/build.
Pros: Versioned, standard dependency management Cons: Requires publish step, language-specific
More user-friendly alternative to submodules:
# Add configs as subtree
git subtree add --prefix=configs https://github.com/you/shared-configs.git main --squash
# Pull updates
git subtree pull --prefix=configs https://github.com/you/shared-configs.git main --squash
Pros: No special commands for other users, configs included in repo Cons: Can bloat history, less clear separation
Create a template repo with your configs and a script to sync them:
#!/bin/bash
# sync-configs.sh
curl -O https://raw.githubusercontent.com/you/configs/main/.clinerules
curl -O https://raw.githubusercontent.com/you/configs/main/.eslintrc
Pros: Simple, explicit control Cons: Manual sync required, no version tracking
For individual use, I'd recommend option 1 (symlinks with a dotfiles repo) - it's simple and changes are instant. For team environments, option 2 (submodules) or option 3 (package manager) work better as they're more explicit and portable.
You might also consider using tools like:
- chezmoi - specifically designed for dotfile management
- stow - GNU tool for managing symlinks
- yadm - Yet Another Dotfiles Manager
These tools add convenience features like templating, encryption for sensitive configs, and better cross-platform support.
Recommended: submodule + in-repo symlink (portable & versioned)
Keep your configs in one repo, add it as a submodule in each project, then symlink files from that submodule to where the tool expects them (e.g., repo root).
1) Create a shared config repo (once):
# e.g., github.com/your-org/shared-configs
mkdir shared-configs && cd $_
git init -b main
# add .clinerules, .editorconfig, etc.
git add . && git commit -m "initial shared configs"
git remote add origin [email protected]:your-org/shared-configs.git
git push -u origin main
2) In each project:
# add submodule
git submodule add -b main [email protected]:your-org/shared-configs.git vendor/shared-configs
# symlink the file(s) to their expected locations
ln -s vendor/shared-configs/.clinerules .clinerules # macOS/Linux
# Windows (PowerShell, Developer Mode or admin):
# New-Item -ItemType SymbolicLink -Path .clinerules -Target vendor\shared-configs\.clinerules
git add .gitmodules vendor/shared-configs .clinerules
git commit -m "Use shared .clinerules via submodule"
3) Cloning/using the repo:
git clone --recurse-submodules <repo>
# or after cloning:
git submodule update --init --recursive
4) Updating configs across repos:
-
Update
shared-configs
once. -
In each project:
git submodule update --remote -- vendor/shared-configs git commit -m "Bump shared configs"
-
(Optional) tag releases in
shared-configs
and pin submodules to tagged commits for stability.
Why this is nice
- Single source of truth, but each repo pins an exact version (reproducible).
- Symlink target lives inside the repo (the submodule path), so collaborators’ clones resolve it cleanly on any OS that supports symlinks.
Gotchas
- On Windows, ensure symlinks are enabled (Developer Mode) or fall back to the “copy on checkout” hook below.
Keep the submodule, but copy files into place automatically.
.git/hooks/post-checkout
(make it executable):
#!/usr/bin/env bash
cp -f vendor/shared-configs/.clinerules .clinerules
You can mirror this in post-merge
and post-rewrite
hooks so changes propagate after pulls and rebase. Commit a sample hook (e.g., in tools/hooks/
) and have a small bootstrap script that installs it into .git/hooks/
.
- Template repo (for new repos only)
- Mark a repo as a Template on GitHub; “Use this template” seeds new projects with your configs.
- Pro: zero submodule friction.
- Con: changes don’t propagate automatically to existing repos.
- Curl/sync script (simple, no git plumbing)
-
Keep canonical files in
shared-configs
. -
In each repo, add:
make update-configs
with:
update-configs: curl -fsSLo .clinerules https://raw.githubusercontent.com/your-org/shared-configs/main/.clinerules
-
Pair with a CI check that compares the local file to the canonical one and fails if drifted.
- Git subtree
-
Vendor the
shared-configs
repo intovendor/shared-configs
:git subtree add --prefix vendor/shared-configs [email protected]:your-org/shared-configs.git main --squash
-
Update with
git subtree pull ...
. -
You’ll still need a symlink or copy step if the tool requires the file at repo root.
- Dotfiles manager (great for personal use)
- Tools like chezmoi, yadm, or GNU stow can place symlinks into your working trees locally.
- Pro: very convenient for your own machines.
- Con: collaborators won’t get the files automatically.
- Tool-level includes (best if supported)
- Some tools allow “include/extends” from a shared path (e.g.,
$XDG_CONFIG_HOME
). - If
.clinerules
(or your tool) supports includes, point each repo’s tiny stub to the central file and version that stub.
bash -c '
set -euo pipefail
TARGET=vendor/shared-configs
[email protected]:your-org/shared-configs.git
git submodule add -b main "$REPO" "$TARGET" 2>/dev/null || true
rm -f .clinerules
if command -v ln >/dev/null; then
ln -s "$TARGET/.clinerules" .clinerules
else
# Windows fallback: copy if symlink is unavailable
cp -f "$TARGET/.clinerules" .clinerules
fi
git add .gitmodules "$TARGET" .clinerules
git commit -m "wire up shared .clinerules" || true
'
If you tell me your OS mix (macOS/Linux/Windows) and whether teammates are allergic to submodules, I can tailor this down to a single command and (optionally) a tiny CI check to prevent drift.