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:

1. Dedicated Configuration Repository with Symlinks

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

2. Git Submodules

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

3. Package Manager Approach

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

4. Git Subtree

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

5. Template Repository with Update Script

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

Recommendation

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.

GPT-5:

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.

If symlinks aren’t an option: submodule + post-checkout copy

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/.


Alternative approaches (pick based on team/tooling)

  1. 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.
  1. 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.

  1. Git subtree
  • Vendor the shared-configs repo into vendor/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.

  1. 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.
  1. 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.

A tiny bootstrap you can drop into any repo

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.

⚠️ **GitHub.com Fallback** ⚠️