common-repo

Manage repository configuration files as dependencies. Define inheritance in .common-repo.yaml, pull files from multiple Git repositories, and merge them with version pinning.

Why common-repo?

Modern software repositories require extensive configuration infrastructure: CI/CD pipelines, pre-commit hooks, linters, formatters, and countless dotfiles. Managing these across multiple projects typically means:

  • Manual copy-paste - Configuration files copied between projects lead to inconsistency and drift
  • No versioning - Unlike source dependencies, configs aren’t semantically versioned or tracked
  • Difficult updates - No automated way to propagate standards across repositories
  • No inheritance - Can’t easily extend standard configurations

common-repo treats repository configuration as software dependencies.

Configuration files become:

  • Semantically versioned - Track exactly which version you’re using
  • Automatically updateable - Detect outdated configs and upgrade deterministically
  • Composable - Pull from multiple upstream repos and merge intelligently
  • Inheritable - Build upon standards that themselves extend other standards
  • Self-consuming - Upstream repos can use self: to pull their own tooling without leaking it to consumers

Quick Start

Install common-repo:

curl -fsSL https://raw.githubusercontent.com/common-repo/common-repo/main/install.sh | sh

Create .common-repo.yaml in your repository:

- repo:
    url: https://github.com/your-org/shared-configs
    ref: v1.0.0
    with:
      - include: [".github/**", ".pre-commit-config.yaml"]

Apply configuration:

common-repo ls      # List files that would be created
common-repo diff    # Preview changes
common-repo apply   # Apply configuration

Next Steps

Getting Started with common-repo

This guide walks you through installing common-repo and applying your first configuration.

Installation

Install the latest release with a single command:

curl -fsSL https://raw.githubusercontent.com/common-repo/common-repo/main/install.sh | sh

This automatically detects your platform and installs the appropriate binary to ~/.local/bin. The installer also creates a short alias cr so you can use either common-repo or cr to run commands.

Installation options:

# Install a specific version
VERSION=v0.20.0 curl -fsSL https://raw.githubusercontent.com/common-repo/common-repo/main/install.sh | sh

# Install to a custom directory
INSTALL_DIR=/usr/local/bin curl -fsSL https://raw.githubusercontent.com/common-repo/common-repo/main/install.sh | sh

# Also install prek (fast pre-commit hooks)
INSTALL_PREK=1 curl -fsSL https://raw.githubusercontent.com/common-repo/common-repo/main/install.sh | sh

# Install with sudo (for system-wide installation)
curl -fsSL https://raw.githubusercontent.com/common-repo/common-repo/main/install.sh | sudo sh

# Skip creating the 'cr' alias
SKIP_ALIAS=1 curl -fsSL https://raw.githubusercontent.com/common-repo/common-repo/main/install.sh | sh

# Use GitHub token to avoid API rate limits (useful in CI)
GITHUB_TOKEN=ghp_xxx curl -fsSL https://raw.githubusercontent.com/common-repo/common-repo/main/install.sh | sh

From Pre-built Binaries

Download the latest release for your platform from GitHub Releases.

Available platforms:

  • Linux x86_64 (glibc): common-repo-vX.Y.Z-x86_64-unknown-linux-gnu.tar.gz
  • Linux x86_64 (musl): common-repo-vX.Y.Z-x86_64-unknown-linux-musl.tar.gz
  • Linux ARM64: common-repo-vX.Y.Z-aarch64-unknown-linux-gnu.tar.gz
  • macOS ARM64 (Apple Silicon): common-repo-vX.Y.Z-aarch64-apple-darwin.tar.gz
  • Windows x86_64: common-repo-vX.Y.Z-x86_64-pc-windows-msvc.zip

From Source

# Clone the repository
git clone https://github.com/common-repo/common-repo.git
cd common-repo

# Build and install
cargo install --path .

Or install directly from GitHub:

cargo install --git https://github.com/common-repo/common-repo

Verify Installation

common-repo --version
# Or use the short alias
cr --version

Your First Configuration

1. Initialize a Configuration File

Navigate to your project directory and run the interactive wizard:

cd your-project
common-repo init

The wizard will:

  • Prompt you to enter repository URLs (supports GitHub shorthand like org/repo)
  • Auto-detect the latest semver tag for each repository
  • Optionally set up pre-commit hooks
  • Generate a ready-to-use .common-repo.yaml

Alternative: Initialize from an existing repo

# Full URL
common-repo init https://github.com/your-org/shared-configs

# GitHub shorthand
common-repo init your-org/shared-configs

2. Define What to Inherit

Edit .common-repo.yaml to inherit from a repository. Here’s a simple example that pulls pre-commit configuration:

# .common-repo.yaml

# Inherit files from a remote repository
- repo:
    url: https://github.com/your-org/shared-configs
    ref: v1.0.0
    with:
      - include: ["**/*"]
      - exclude: [".git/**", "README.md"]

3. Preview Changes

Before applying, see what files would be created:

# List files that would be created
common-repo ls

# See a diff of changes
common-repo diff

4. Apply the Configuration

# Dry run first (recommended)
common-repo apply --dry-run

# Apply for real
common-repo apply

Understanding the Output

After running common-repo apply, you’ll see:

  • Files pulled from inherited repositories
  • Any merge operations applied (YAML, JSON, etc.)
  • Template variables substituted

The tool caches cloned repositories at ~/.common-repo/cache/ for faster subsequent runs.

Common Workflows

Check for Updates

# See if inherited repos have newer versions
common-repo check --updates

Update to Latest Compatible Versions

# Update refs to latest compatible versions (minor/patch only)
common-repo update

# Include breaking changes (major versions)
common-repo update --latest

View Inheritance Tree

# See how repos inherit from each other
common-repo tree

Example: Inheriting CI/CD Configuration

Here’s a practical example that inherits GitHub Actions workflows:

# .common-repo.yaml

# Pull CI workflows from your org's standard configs
- repo:
    url: https://github.com/your-org/ci-templates
    ref: v2.1.0
    with:
      - include: [".github/**"]

# Customize the project name in templates
- template-vars:
    project_name: my-awesome-project
    rust_version: "1.75"

# Mark workflow files as templates for variable substitution
- template:
    - ".github/workflows/*.yml"

Next Steps

Update Workflow

This guide explains how to check for updates and apply them to your inherited repositories.

Workflow Overview

The update workflow follows a check-review-apply pattern:

  1. Check for available updates
  2. Review changes before applying
  3. Apply with confidence

Checking for Updates

See if any inherited repositories have newer versions available:

common-repo check --updates

This compares your pinned refs against available tags in each repository. Output shows:

  • Current ref for each inherited repo
  • Available newer versions
  • Whether updates are compatible (minor/patch) or breaking (major)

Reviewing Changes

Before updating refs, preview what would change in your files:

# Update refs without applying
common-repo update --dry-run

# See file differences after updating
common-repo diff

The --dry-run flag shows which refs would change without modifying your config file.

Applying Updates

Compatible Updates (Safe)

Update to the latest compatible versions (same major version):

common-repo update

This updates minor and patch versions only. For example, v1.2.0 might update to v1.3.5 but not v2.0.0.

Latest Updates (Breaking)

Include breaking changes when updating:

common-repo update --latest

This updates to the newest available version regardless of major version changes.

Skip Confirmation

For scripting or CI, skip the confirmation prompt:

common-repo update --yes

Selective Updates

Update only specific upstream repos using glob patterns:

# Update repos matching a pattern
common-repo update --filter "github.com/org/*"

# Update multiple patterns (OR logic)
common-repo update --filter "*/*/ci-*" --filter "*/*/linter-*"

# Combine with other flags
common-repo update --filter "github.com/org/*" --latest --dry-run

Patterns match against the repository URL (with scheme stripped) combined with the optional path. For example:

ConfigMatch Target
url: https://github.com/org/repogithub.com/org/repo
url: https://github.com/org/monorepo, path: configs/eslintgithub.com/org/monorepo/configs/eslint

Pattern examples:

  • github.com/org/* - repos under a specific org
  • */*/ci-* - any repo with “ci-” prefix
  • github.com/** - all GitHub repos

Complete Example

A typical update session:

# Step 1: Check what's available
common-repo check --updates

# Step 2: Preview ref changes
common-repo update --dry-run

# Step 3: Apply ref updates to config
common-repo update

# Step 4: Preview file changes
common-repo diff

# Step 5: Apply file changes with dry-run first
common-repo apply --dry-run

# Step 6: Apply for real
common-repo apply

Semantic Versioning

common-repo understands semantic versioning when comparing refs:

Update TypeExample--compatible--latest
Patchv1.2.0 → v1.2.1YesYes
Minorv1.2.0 → v1.3.0YesYes
Majorv1.2.0 → v2.0.0NoYes

The --compatible flag (default) follows semver: patch and minor updates are safe, major updates may contain breaking changes.

Tips

Pin to specific versions for production stability:

- repo:
    url: https://github.com/your-org/configs
    ref: v2.1.0  # Pinned version

Use branches for development or always-latest behavior:

- repo:
    url: https://github.com/your-org/configs
    ref: main  # Tracks branch HEAD

Check updates in CI to catch drift:

# Fails if updates are available
common-repo check --updates || echo "Updates available"

File Filtering

This guide explains how to use include and exclude patterns to control which files are inherited from upstream repositories.

Basic Syntax

Use include and exclude in the with clause of a repo operation:

- repo:
    url: https://github.com/your-org/configs
    ref: v1.0.0
    with:
      - include: ["pattern1", "pattern2"]
      - exclude: ["pattern3"]

Or at the top level for local files:

- include: ["src/**", "Cargo.toml"]
- exclude: ["**/*.bak"]

Pattern Syntax

Patterns use glob syntax:

PatternMatches
*Any filename in current directory
**Any path (recursive)
*.rsAll .rs files in current directory
**/*.rsAll .rs files recursively
src/**Everything under src/
.*Hidden files at root
.*/**Everything in hidden directories

Order of Operations

  1. include runs first, adding files to the output
  2. exclude runs second, removing files from the output

This means you can include a broad pattern and then exclude specific files:

- repo:
    url: https://github.com/your-org/configs
    ref: v1.0.0
    with:
      - include: [".github/**"]
      - exclude: [".github/CODEOWNERS"]

Common Patterns

Include Only CI Files

Pull GitHub Actions workflows and nothing else:

- repo:
    url: https://github.com/your-org/ci-templates
    ref: v1.0.0
    with:
      - include: [".github/workflows/*.yml"]

Exclude Tests and Examples

Pull everything except test and example files:

- repo:
    url: https://github.com/your-org/library
    ref: v2.0.0
    with:
      - include: ["**/*"]
      - exclude: ["tests/**", "examples/**", "**/*_test.rs"]

Include Hidden Files

Dotfiles and hidden directories require explicit patterns:

- repo:
    url: https://github.com/your-org/dotfiles
    ref: v1.0.0
    with:
      - include:
          - ".*"        # .gitignore, .editorconfig, etc.
          - ".*/**"     # .github/*, .vscode/*, etc.

Exclude Generated Files

Skip files that shouldn’t be version-controlled:

- repo:
    url: https://github.com/your-org/project
    ref: v1.0.0
    with:
      - include: ["**/*"]
      - exclude:
          - ".git/**"
          - "target/**"
          - "node_modules/**"
          - "**/*.generated.*"

Multiple File Types

Include specific file types only:

- repo:
    url: https://github.com/your-org/configs
    ref: v1.0.0
    with:
      - include:
          - "**/*.yml"
          - "**/*.yaml"
          - "**/*.toml"
          - "**/*.json"

Combining with Other Operations

Filtering happens before other operations in the with clause:

- repo:
    url: https://github.com/your-org/templates
    ref: v1.0.0
    with:
      # Filter first
      - include: ["templates/**"]
      - exclude: ["templates/internal/**"]
      # Then rename
      - rename:
          - "^templates/(.*)": "$1"

Viewing Filtered Results

Check which files match your patterns:

# List all files that would be created
common-repo ls

# Filter the listing by pattern
common-repo ls --pattern "*.yml"

# Long format shows sizes
common-repo ls -l

Troubleshooting

Files not appearing? Check that your include pattern matches. Use common-repo ls to see what’s included.

Too many files? Add exclude patterns to filter out unwanted files.

Hidden files missing? Remember to explicitly include .* and .*/** patterns.

Configuration Reference

This document covers all operators and options available in .common-repo.yaml.

Configuration File

The .common-repo.yaml file is a list of operations executed in order. Each operation is a YAML map with a single key indicating the operator type.

Operator Quick Reference

OperatorDescription
repoInherit files from a remote Git repository
includeAdd files from the current repository
excludeRemove files from the in-memory filesystem
renameTransform file paths using regex patterns
templateMark files for variable substitution
template-varsDefine variables for template substitution
toolsValidate that required tools are installed
yamlMerge YAML configuration fragments
jsonMerge JSON configuration fragments
tomlMerge TOML configuration fragments
iniMerge INI configuration fragments
markdownMerge markdown document fragments
selfRun operations locally without exposing them to consumers

Example configuration:

# .common-repo.yaml
- repo: { ... }
- include: [ ... ]
- exclude: [ ... ]
- rename: [ ... ]
- template: [ ... ]
- template-vars: { ... }
- tools: [ ... ]
- yaml: { ... }
- json: { ... }
- toml: { ... }
- ini: { ... }
- markdown: { ... }
- self: [ ... ]

Core Operators

repo - Inherit from a Repository

Pull files from a remote Git repository.

- repo:
    url: https://github.com/owner/repo
    ref: v1.0.0

Options

OptionRequiredDescription
urlYesGit repository URL
refYesGit reference (tag, branch, or commit SHA)
pathNoSub-directory to use as root
withNoInline operations to apply

Examples

Basic inheritance:

- repo:
    url: https://github.com/common-repo/rust-cli
    ref: v1.2.0

Inherit a sub-directory:

# Only pull files from the 'templates/rust' directory
- repo:
    url: https://github.com/common-repo/templates
    ref: main
    path: templates/rust

Inline filtering with with:

- repo:
    url: https://github.com/common-repo/configs
    ref: v2.0.0
    with:
      - include: [".github/**", ".pre-commit-config.yaml"]
      - exclude: [".github/CODEOWNERS"]
      - rename:
          - ".github/workflows/ci-template.yml": ".github/workflows/ci.yml"

include - Add Files

Add files from the current repository to the output based on glob patterns.

- include:
    - "**/*"           # All files
    - ".*"             # Hidden files at root
    - ".*/**/*"        # All files in hidden directories

Patterns

  • **/* - All files recursively
  • *.rs - All Rust files in current directory
  • src/**/*.rs - All Rust files under src/
  • .* - Hidden files (dotfiles) at root
  • .*/**/* - All files in hidden directories

exclude - Remove Files

Remove files from the in-memory filesystem based on glob patterns.

- exclude:
    - ".git/**"
    - "target/**"
    - "**/*.bak"
    - "node_modules/**"

rename - Transform Paths

Transform file paths using regex patterns with capture group placeholders.

- rename:
    - "old-name/(.*)": "new-name/$1"
    - "^templates/(.*)": "$1"
    - "(.+)\\.template$": "$1"

Placeholders

  • $1 - First capture group
  • $2 - Second capture group
  • etc.

Examples

Strip a directory prefix:

- rename:
    - "^files/(.*)": "$1"

Result: files/config.yaml becomes config.yaml

Move files to a subdirectory:

- rename:
    - "^(.+\\.md)$": "docs/$1"

Result: README.md becomes docs/README.md

Rename file extensions:

- rename:
    - "(.+)\\.template$": "$1"

Result: config.yaml.template becomes config.yaml

template - Mark Template Files

Mark files for variable substitution. Files matching these glob patterns are scanned for the __COMMON_REPO__ prefix — if found, the file is flagged as a template for processing during composite construction.

- template:
    - "**/*.template"
    - ".github/workflows/*.yml"
    - "Cargo.toml"

Template files use the __COMMON_REPO__VARNAME__ sentinel pattern for variable placeholders:

# In a template file (e.g., Cargo.toml)
name = "__COMMON_REPO__PROJECT_NAME__"
version = "__COMMON_REPO__VERSION__"

Variable names must be valid identifiers ([A-Za-z_][A-Za-z0-9_]*) and must not contain double underscores (__).

template-vars - Define Variables

Define variables for template substitution. Values are literal strings.

- template-vars:
    project_name: my-project
    author: Jane Doe
    rust_version: "1.75"

Variable Cascading

Variables cascade through the inheritance tree. Child repos can override ancestor variables:

# In parent repo
- template-vars:
    log_level: info

# In child repo (overrides parent)
- template-vars:
    log_level: debug

tools - Validate Required Tools

Check that required tools are installed with correct versions.

- tools:
    - rustc: ">=1.70"
    - cargo: "*"
    - pre-commit: "^3.0"
    - node: "~18.0"

Version Constraints

SyntaxMeaning
*Any version
1.70Exactly 1.70
>=1.701.70 or higher
^1.70Compatible with 1.70 (>=1.70.0, <2.0.0)
~1.70Approximately 1.70 (>=1.70.0, <1.71.0)

This operator validates but does not install tools. Warnings are issued for missing or incompatible versions.

Structural Operators

self - Local-Only Operations

Run a sub-pipeline of operations that apply only to the local repository. Files produced by self: blocks are written to disk but never appear in the composite filesystem that downstream consumers see.

This is useful when a repository is both a source (providing shared configuration to consumers) and wants to consume from its own upstream repos.

- self:
    - repo:
        url: https://github.com/org/upstream-tooling
        ref: v1.0.0
    - exclude:
        - ".releaserc.yaml"

How It Works

  1. The orchestrator partitions self: blocks from the rest of the config
  2. The source pipeline runs first (producing the composite filesystem for consumers)
  3. Each self: block runs as an independent pipeline afterward
  4. Output from self: blocks is written to the working directory but excluded from what consumers inherit

Rules

  • self: blocks must contain at least one operation
  • self: blocks cannot be nested (no self: inside self:)
  • self: blocks are stripped when a repo is consumed as an upstream — consumers never see them
  • Any operation valid at the top level can appear inside self: (repo, include, exclude, rename, template, merge operators, etc.)
  • A self: block needs at least one repo: to populate its composite filesystem. Without a repo:, the composite is empty and filtering operators like include/exclude/rename have nothing to operate on.

Example: Source Repo That Consumes Upstream Tooling

# .common-repo.yaml for a shared-config repo

# Self operations — local only, consumers don't see these
- self:
    - repo:
        url: https://github.com/org/ci-tooling
        ref: v2.0.0
    - exclude:
        - ".releaserc.yaml"
        - "commitlint.config.cjs"

# Source API — what consumers get
- include:
    - "src/**"
    - "src/.*"
- template:
    - "src/.github/workflows/release.yaml"
- template-vars:
    GH_APP_ID_VAR: CHRISTMAS_ISLAND_APP_ID
- rename:
    - from: "^src/(.*)$"
      to: "$1"

In this example, the repo pulls CI tooling for its own use via self:, while consumers only see the files exposed by include, template, template-vars, and rename.

For a detailed guide on using self: when authoring upstream repositories, see Authoring Upstream Repositories.

Merge Operators

Merge operators intelligently combine configuration fragments into destination files.

Upstream-Declared Merge (defer/auto-merge)

All merge operators support two additional options for upstream-declared merge behavior:

OptionTypeDescription
auto-mergestringShorthand: sets source=dest to this value and implies defer=true
deferboolWhen true, this operation only runs when repo is used as an upstream

auto-merge is the preferred syntax when the source and destination filenames are the same:

# In upstream repo's .common-repo.yaml
- markdown:
    auto-merge: CLAUDE.md     # source=dest=CLAUDE.md, defer=true
    section: "## Rules"
    append: true

defer: true is used when source and destination paths differ:

- yaml:
    source: config/base.yaml
    dest: config.yaml
    path: settings
    defer: true               # Only applies when repo is inherited

See Upstream-Declared Merge Behavior for detailed usage.

Path Syntax

The path option in merge operators supports multiple notations for navigating nested structures:

SyntaxExampleDescription
Dot notationfoo.bar.bazAccess nested keys
Bracket notationfoo["bar"] or foo['bar']Access keys with special characters
Array indicesitems[0] or items[1].nameAccess array elements by index
Escaped dotsfoo\.barLiteral dot in key name
Mixedservers[0].config["special.key"]Combine notations

Examples:

# Navigate to nested object
- yaml:
    source: labels.yml
    dest: config.yml
    path: metadata.labels

# Access array element
- json:
    source: script.json
    dest: package.json
    path: scripts[0]

# Key with special characters
- toml:
    source: deps.toml
    dest: Cargo.toml
    path: dependencies["my-crate"]

# Escaped dot in key name
- yaml:
    source: fragment.yml
    dest: config.yml
    path: config\.v2.settings

yaml - Merge YAML Files

- yaml:
    source: fragment.yml
    dest: config.yml

Options

OptionRequiredDefaultDescription
sourceYes*-Source fragment file
destYes*-Destination file
auto-mergeNo-Shorthand: sets source=dest, implies defer=true
deferNofalseOnly apply when repo is used as an upstream
pathNorootPath to merge at (see Path Syntax)
array_modeNoreplaceArray handling: replace, append, or append_unique
appendNofalseDeprecated: use array_mode: append instead

*Either source+dest or auto-merge is required

Array Merge Modes

ModeDescription
replaceReplace destination array with source array (default)
appendAppend source items to the end of destination array
append_uniqueAppend only items not already in destination array

Examples

Merge at root:

- yaml:
    source: extra-config.yml
    dest: config.yml

Merge at a specific path:

# Merge labels into metadata.labels
- yaml:
    source: labels.yml
    dest: kubernetes.yml
    path: metadata.labels

Append to a list:

# Add items to an existing list
- yaml:
    source: extra-items.yml
    dest: config.yml
    path: items
    append: true

Add jobs to CI workflow:

# Merge shared CI jobs into existing workflow
- yaml:
    source: ci-jobs.yml
    dest: .github/workflows/ci.yml
    path: jobs
    append: true

json - Merge JSON Files

- json:
    source: fragment.json
    dest: package.json
    path: dependencies

Options

OptionRequiredDefaultDescription
sourceYes*-Source fragment file
destYes*-Destination file
auto-mergeNo-Shorthand: sets source=dest, implies defer=true
deferNofalseOnly apply when repo is used as an upstream
pathNorootDot-notation path to merge at
appendNofalseAppend to arrays instead of replace
positionNo-Where to append: start or end (only used when append: true)

*Either source+dest or auto-merge is required

Examples

Add dependencies to package.json:

- json:
    source: extra-deps.json
    dest: package.json
    path: dependencies

Append scripts:

- json:
    source: scripts.json
    dest: package.json
    path: scripts
    append: true
    position: start

Merge TypeScript compiler options:

- json:
    source: strict-options.json
    dest: tsconfig.json
    path: compilerOptions

toml - Merge TOML Files

- toml:
    source: fragment.toml
    dest: Cargo.toml
    path: dependencies

Options

OptionRequiredDefaultDescription
sourceYes*-Source fragment file
destYes*-Destination file
auto-mergeNo-Shorthand: sets source=dest, implies defer=true
deferNofalseOnly apply when repo is used as an upstream
pathNorootPath to merge at (see Path Syntax)
array_modeNoreplaceArray handling: replace, append, or append_unique
appendNofalseDeprecated: use array_mode: append instead
preserve-commentsNofalseKeep comments in output

*Either source+dest or auto-merge is required

See Array Merge Modes for details on array handling options.

Examples

Add Cargo dependencies:

- toml:
    source: common-deps.toml
    dest: Cargo.toml
    path: dependencies

Add dev-dependencies with comments preserved:

- toml:
    source: test-deps.toml
    dest: Cargo.toml
    path: dev-dependencies
    preserve-comments: true

Merge pyproject.toml settings:

- toml:
    source: lint-settings.toml
    dest: pyproject.toml
    path: tool.ruff

ini - Merge INI Files

- ini:
    source: fragment.ini
    dest: config.ini
    section: database

Options

OptionRequiredDefaultDescription
sourceYes*-Source fragment file
destYes*-Destination file
auto-mergeNo-Shorthand: sets source=dest, implies defer=true
deferNofalseOnly apply when repo is used as an upstream
sectionNo-INI section to merge into
appendNofalseAppend values instead of replace
allow-duplicatesNofalseAllow duplicate keys

*Either source+dest or auto-merge is required

Examples

Merge database settings:

- ini:
    source: db-settings.ini
    dest: config.ini
    section: database

Add editor configuration rules:

# Merge settings into the [*] section for all files
- ini:
    source: editor-rules.ini
    dest: .editorconfig
    section: "*"

Configure git settings:

- ini:
    source: git-aliases.ini
    dest: .gitconfig
    section: alias

markdown - Merge Markdown Files

- markdown:
    source: installation.md
    dest: README.md
    section: "## Installation"

Options

OptionRequiredDefaultDescription
sourceYes*-Source fragment file
destYes*-Destination file
auto-mergeNo-Shorthand: sets source=dest, implies defer=true
deferNofalseOnly apply when repo is used as an upstream
sectionNo-Heading to merge under
levelNo2Heading level (1-6)
appendNofalseAppend to section
positionNo-Where to insert: start or end (only used when append: true)
create-sectionNofalseCreate section if missing

*Either source+dest or auto-merge is required

Examples

Add installation instructions:

- markdown:
    source: install-instructions.md
    dest: README.md
    section: "## Installation"
    append: true
    position: end
    create-section: true

Add contributing section:

# Insert a standard Contributing section in the README
- markdown:
    source: contributing-section.md
    dest: README.md
    section: "## Contributing"
    create-section: true

Merge CLAUDE.md rules (upstream-declared):

# In upstream repo - will only apply when inherited
- markdown:
    auto-merge: CLAUDE.md
    section: "## Rules"
    append: true

Operation Order

Operations execute in the order they appear in the configuration file. For inheritance:

  1. Ancestor repos are processed before parent repos
  2. Parent repos are processed before the local repo
  3. Siblings are processed in declaration order

This means later operations can override earlier ones, and child repos can customize what they inherit from ancestors.

self: blocks execute after the source pipeline completes. Each self: block runs as an independent pipeline invocation with its own composite filesystem. Like the source pipeline, composite (upstream-derived) files take precedence over local files for shared paths. Because each self: block starts with an empty composite, it needs a repo: operator to pull in upstream content — otherwise filtering operators have nothing to work with.

Example Order

# local .common-repo.yaml
- repo: {url: A, ref: v1}  # A is processed first (including A's ancestors)
- repo: {url: B, ref: v2}  # B is processed second (including B's ancestors)
- include: ["local/**"]    # Local operations are processed last

If A inherits from C, and B inherits from D:

Processing order: C -> A -> D -> B -> local

Complete Example

Here’s a complete configuration showing multiple operators:

# .common-repo.yaml

# Inherit base Rust CLI configuration
- repo:
    url: https://github.com/common-repo/rust-cli
    ref: v2.0.0
    with:
      - include: ["**/*"]
      - exclude: [".git/**", "target/**"]

# Inherit pre-commit configuration
- repo:
    url: https://github.com/common-repo/pre-commit-rust
    ref: v1.5.0
    with:
      - include: [".pre-commit-config.yaml"]

# Consume shared tooling for this repo only (not exposed to consumers)
- self:
    - repo:
        url: https://github.com/common-repo/shared-tooling
        ref: v1.0.0

# Include local files
- include:
    - src/**
    - Cargo.toml
    - README.md

# Exclude generated files
- exclude:
    - "**/*.generated.rs"

# Rename template files
- rename:
    - "(.+)\\.template$": "$1"

# Define template variables
- template-vars:
    project_name: my-project
    author: Your Name
    rust_edition: "2021"

# Mark files as templates
- template:
    - Cargo.toml
    - README.md

# Require tools
- tools:
    - rustc: ">=1.70"
    - cargo: "*"
    - pre-commit: ">=3.0"

# Merge additional dependencies into Cargo.toml
- toml:
    source: extra-deps.toml
    dest: Cargo.toml
    path: dependencies

# Add CI workflow
- yaml:
    source: ci-jobs.yml
    dest: .github/workflows/ci.yml
    path: jobs
    append: true

CLI Reference

Complete reference for all common-repo commands.

Global Options

These options are available for all commands:

OptionDescription
--color <WHEN>Colorize output: always, never, auto (default: auto)
--log-level <LEVEL>Set log level: error, warn, info, debug, trace (default: info)
--verboseIncrease output verbosity (can be repeated for more detail)
--quietSuppress output except errors
-h, --helpPrint help information
-V, --versionPrint version

Verbosity Control

The --verbose and --quiet flags provide convenient shortcuts for controlling output verbosity:

  • --quiet: Shows only error messages. Useful for scripting.
  • --verbose: Shows debug-level messages. Can be repeated for more detail.
  • --verbose --verbose: Shows trace-level messages (maximum verbosity).

These flags override --log-level when specified. Note that --verbose and --quiet cannot be used together.

Examples:

# Minimal output for scripts
common-repo apply --quiet

# Debug output to troubleshoot issues
common-repo apply --verbose

# Maximum verbosity for detailed debugging
common-repo check --verbose --verbose

Commands

add - Add Repository

Add a repository to an existing .common-repo.yaml configuration file. Automatically detects the latest semver tag.

common-repo add [OPTIONS] <URI>

Arguments

ArgumentDescription
<URI>Repository URL to add (e.g., https://github.com/org/repo or org/repo GitHub shorthand)

Options

OptionDescription
-y, --yesNon-interactive mode: create minimal config without prompting if none exists

Examples

# Add a repo to existing config
common-repo add your-org/shared-configs

# Add using full URL
common-repo add https://github.com/your-org/shared-configs

# Create new config with --yes (non-interactive)
common-repo add --yes your-org/shared-configs

Behavior

  • If .common-repo.yaml exists: appends the new repository before the include section
  • If no config exists: prompts for confirmation to create a minimal config (use --yes to skip prompt)
  • Automatically fetches and uses the latest semver tag, or falls back to main if no tags found
  • Warns when adding repositories with only 0.x.x versions (unstable API)

apply - Apply Configuration

Apply the .common-repo.yaml configuration to your repository. This runs the full 6-phase pipeline: discover repos, clone, process, merge, and write files.

common-repo apply [OPTIONS]

Options

OptionDescription
-c, --config <PATH>Path to config file (default: .common-repo.yaml)
-o, --output <PATH>Output directory (default: current directory)
--cache-root <PATH>Cache directory (default: ~/.cache/common-repo on Linux, ~/Library/Caches/common-repo on macOS)
-n, --dry-runShow what would be done without making changes
-f, --forceOverwrite existing files without prompting
--no-cacheBypass cache and fetch fresh clones

Use global --verbose or --quiet flags for verbosity control.

Examples

# Apply configuration
common-repo apply

# Preview changes without applying
common-repo apply --dry-run

# Apply with verbose output
common-repo apply --verbose

# Apply to a different directory
common-repo apply --output ./output

# Force fresh clones (ignore cache)
common-repo apply --no-cache

# Use a different config file
common-repo apply --config my-config.yaml

check - Validate and Check Updates

Check configuration validity and optionally check for repository updates.

common-repo check [OPTIONS]

Options

OptionDescription
-c, --config <FILE>Path to config file (default: .common-repo.yaml)
--cache-root <DIR>Cache directory
--updatesCheck for newer versions of inherited repositories

Examples

# Validate configuration
common-repo check

# Check for available updates
common-repo check --updates

Output

When checking for updates, you’ll see:

  • Current ref for each inherited repo
  • Available newer versions (if any)
  • Whether updates are compatible (minor/patch) or breaking (major)

completions - Generate Shell Completions

Generate shell completion scripts for tab-completion support.

common-repo completions <SHELL>

Arguments

ArgumentDescription
<SHELL>Shell to generate completions for: bash, zsh, fish, powershell, elvish

Examples

# Generate bash completions
common-repo completions bash > ~/.local/share/bash-completion/completions/common-repo

# Generate zsh completions
common-repo completions zsh > ~/.zfunc/_common-repo

# Generate fish completions
common-repo completions fish > ~/.config/fish/completions/common-repo.fish

# Generate PowerShell completions
common-repo completions powershell >> $PROFILE

Installation

Bash

# Option 1: User-level installation
mkdir -p ~/.local/share/bash-completion/completions
common-repo completions bash > ~/.local/share/bash-completion/completions/common-repo

# Option 2: Source directly in .bashrc
echo 'eval "$(common-repo completions bash)"' >> ~/.bashrc

Zsh

# Add to fpath (recommended)
mkdir -p ~/.zfunc
common-repo completions zsh > ~/.zfunc/_common-repo

# Add to .zshrc (before compinit)
echo 'fpath=(~/.zfunc $fpath)' >> ~/.zshrc
echo 'autoload -Uz compinit && compinit' >> ~/.zshrc

Fish

common-repo completions fish > ~/.config/fish/completions/common-repo.fish

PowerShell

# Add to your PowerShell profile
common-repo completions powershell >> $PROFILE

Elvish

common-repo completions elvish >> ~/.elvish/rc.elv

Note on cr alias: If you installed via the shell installer, the cr alias is available. Completions generated for common-repo will work when you type common-repo. For the cr alias, you can create a symlink or alias in your shell configuration, or generate completions separately using cr completions <shell>.

diff - Preview Changes

Show differences between current files and what the configuration would produce.

common-repo diff [OPTIONS]

Options

OptionDescription
-c, --config <FILE>Path to config file (default: .common-repo.yaml)
--cache-root <DIR>Cache directory
--working-dir <DIR>Directory to compare against (default: current)
--summaryShow only a summary, not individual files

Examples

# Show full diff
common-repo diff

# Show summary only
common-repo diff --summary

# Compare against a different directory
common-repo diff --working-dir ./other-project

init - Initialize Configuration

Create a new .common-repo.yaml configuration file. By default, launches an interactive wizard that guides you through adding repositories with automatic version detection.

common-repo init [OPTIONS] [URI]

Arguments

ArgumentDescription
[URI]Repository URL to initialize from (e.g., https://github.com/org/repo or org/repo GitHub shorthand)

Options

OptionDescription
-i, --interactiveInteractive setup wizard (default when no URI provided)
-f, --forceOverwrite existing configuration

Examples

# Launch interactive wizard (default)
common-repo init

# Initialize from a specific repository
common-repo init https://github.com/your-org/shared-configs

# Use GitHub shorthand
common-repo init your-org/shared-configs

# Overwrite existing config
common-repo init --force

# Explicitly use interactive mode
common-repo init --interactive

Interactive Wizard

When run without arguments, init launches an interactive wizard that:

  1. Prompts for repository URLs (supports GitHub shorthand like org/repo)
  2. Auto-detects the latest semver tag for each repository
  3. Falls back to main branch if no semver tags are found
  4. Optionally sets up pre-commit hooks (detects prek or pre-commit CLI)
  5. Generates a ready-to-use .common-repo.yaml

update - Update Repository Refs

Update repository refs in your configuration to newer versions.

common-repo update [OPTIONS]

Options

OptionDescription
-c, --config <FILE>Path to config file (default: .common-repo.yaml)
--cache-root <DIR>Cache directory
--compatibleUpdate to latest compatible versions only (default)
--latestUpdate to latest versions, including breaking changes
--yesDon’t ask for confirmation
--dry-runShow what would be updated without changing files

Examples

# Update to latest compatible versions (minor/patch only)
common-repo update

# Preview updates without applying
common-repo update --dry-run

# Update to latest versions including breaking changes
common-repo update --latest

# Update without confirmation
common-repo update --yes

info - Show Configuration Info

Display information about the current configuration.

common-repo info [OPTIONS]

Options

OptionDescription
-c, --config <FILE>Path to config file (default: .common-repo.yaml)
--cache-root <DIR>Cache directory

Examples

# Show configuration overview
common-repo info

Output

Shows:

  • Inherited repositories and their refs
  • Operations defined
  • Template variables
  • Required tools

ls - List Files

List files that would be created or modified by the configuration.

common-repo ls [OPTIONS]

Options

OptionDescription
-c, --config <FILE>Path to config file (default: .common-repo.yaml)
--cache-root <DIR>Cache directory
--working-dir <DIR>Working directory for local file operations
-p, --pattern <PATTERN>Filter by glob pattern (e.g., *.rs, src/**)
-l, --longLong format with size and permissions
-s, --sort <SORT>Sort by: name, size, path (default: name)
--countShow only total file count
-r, --reverseReverse sort order

Examples

# List all files
common-repo ls

# Long format
common-repo ls -l

# Filter by pattern
common-repo ls --pattern "*.yml"
common-repo ls -p "src/**/*.rs"

# Count files
common-repo ls --count

# Sort by size, largest first
common-repo ls -l --sort size --reverse

validate - Validate Configuration

Validate a .common-repo.yaml configuration file for syntax and semantic errors.

common-repo validate [OPTIONS]

Options

OptionDescription
-c, --config <FILE>Path to config file (default: .common-repo.yaml)
--cache-root <DIR>Cache directory
--check-reposAlso verify that referenced repositories are accessible
--strictFail on warnings (not just errors)

Examples

# Validate syntax
common-repo validate

# Also check that repos are accessible
common-repo validate --check-repos

# Strict mode (fail on warnings)
common-repo validate --strict

cache - Manage Cache

Manage the repository cache.

common-repo cache <SUBCOMMAND> [OPTIONS]

Global Cache Options

OptionDescription
--cache-root <DIR>Cache directory (default: ~/.cache/common-repo)

Subcommands

list - List cached repositories

common-repo cache list [OPTIONS]
OptionDescription
--detailedShow detailed info (last modified time, file count)
--jsonOutput in JSON format for scripting

clean - Clean cached repositories

common-repo cache clean [OPTIONS]

At least one filter must be specified:

OptionDescription
--allDelete all cached repositories
--unusedDelete entries older than 30 days
--older-than <DURATION>Delete entries older than specified duration
--dry-runShow what would be deleted without deleting
--yesSkip confirmation prompt

Duration format: Number followed by unit: s (seconds), m (minutes), h (hours), d (days), w (weeks). Examples: 30d, 7d, 1h, 2w, 30days, 1week

Examples

# List all cached repos
common-repo cache list

# List with detailed info
common-repo cache list --detailed

# Output as JSON for scripting
common-repo cache list --json

# Preview what would be cleaned (dry run)
common-repo cache clean --unused --dry-run

# Delete entries older than 30 days
common-repo cache clean --unused

# Delete entries older than 7 days
common-repo cache clean --older-than 7d

# Delete all cached repos without prompting
common-repo cache clean --all --yes

JSON Output Schema

When using cache list --json, the output format is:

[
  {
    "hash": "a1b2c3d4",
    "ref": "v1.0.0",
    "path": "subdir or null",
    "size": 12345,
    "file_count": 42,
    "last_modified": 1704067200
  }
]

tree - Show Inheritance Tree

Display the repository inheritance tree.

common-repo tree [OPTIONS]

Options

OptionDescription
-c, --config <FILE>Path to config file (default: .common-repo.yaml)
--cache-root <DIR>Cache directory
--depth <NUM>Maximum depth to display (omit for full tree)

Examples

# Show full inheritance tree
common-repo tree

# Show only first two levels
common-repo tree --depth 2

Output

my-project
├── github.com/common-repo/rust-cli@v2.0.0
│   └── github.com/common-repo/base@v1.0.0
└── github.com/common-repo/pre-commit@v1.5.0

Environment Variables

VariableDescription
COMMON_REPO_CONFIGDefault config file path
COMMON_REPO_CACHEDefault cache directory

Exit Codes

CodeMeaning
0Success
1General error (configuration errors, network failures, I/O errors)
2Invalid command-line usage (unknown flags, missing required arguments)

Special case for diff command:

  • Exit code 0: No changes detected (files match configuration)
  • Exit code 1: Changes detected (files differ from configuration)

This follows the convention established by diff(1) and git diff.

Scripting examples:

# Check if config is valid
common-repo validate && echo "Config is valid"

# Check if changes are needed
common-repo diff && echo "Up to date" || echo "Changes detected"

# Handle usage errors separately from runtime errors
common-repo apply
case $? in
  0) echo "Success" ;;
  1) echo "Error during execution" ;;
  2) echo "Invalid arguments" ;;
esac

Common Workflows

First-Time Setup

# Initialize config interactively
common-repo init

# Or add repos one at a time
common-repo add your-org/shared-configs
common-repo add your-org/ci-templates

# Preview what would be created
common-repo ls
common-repo diff

# Apply
common-repo apply

Regular Maintenance

# Check for updates
common-repo check --updates

# Review and apply updates
common-repo update --dry-run
common-repo update

Shell Completions

Enable tab-completion for faster command entry:

# Bash
common-repo completions bash > ~/.local/share/bash-completion/completions/common-repo

# Zsh
common-repo completions zsh > ~/.zfunc/_common-repo

# Fish
common-repo completions fish > ~/.config/fish/completions/common-repo.fish

See completions for detailed installation instructions.

Debugging

# Debug output
common-repo apply --verbose

# Maximum verbosity (trace level)
common-repo apply --verbose --verbose

# Validate configuration
common-repo validate --check-repos --strict

# View inheritance
common-repo tree
common-repo info

GitHub Actions

common-repo provides two GitHub Actions:

  • setup-common-repo – installs the common-repo binary
  • common-repo – checks for upstream updates and creates PRs

Setup Action

Installs the common-repo binary and adds it to PATH. Use this in CI workflows that need the binary directly.

steps:
  - uses: common-repo/setup-common-repo@v1
  - run: common-repo validate

Inputs

InputDescriptionDefault
versionVersion to install (e.g., v0.28.1)latest

Outputs

OutputDescription
versionThe installed version of common-repo
pathPath to the installed binary

Pin to a specific version

- uses: common-repo/setup-common-repo@v1
  with:
    version: v0.28.1

The cr alias is also available after setup:

- uses: common-repo/setup-common-repo@v1
- run: cr validate

Sync Action

Checks for upstream updates and creates pull requests with the changes. This is the primary action for keeping inherited configuration files up to date.

Quick Start

Add this workflow to your repository:

# .github/workflows/upstream-sync.yml
name: Sync Upstream Configuration

on:
  schedule:
    - cron: '0 9 * * 1'  # Weekly on Monday at 9am UTC
  workflow_dispatch:     # Allow manual trigger

permissions:
  contents: write
  pull-requests: write

jobs:
  sync:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: common-repo/common-repo@v1

Inputs

InputDescriptionDefault
tokenGitHub token for creating PRsgithub.token
config-pathPath to .common-repo.yaml.common-repo.yaml
versionPin common-repo version (e.g., v0.27.0)latest
update-strategycompatible (minor/patch) or latest (all)compatible
force-syncRun apply even without version updatesfalse
pr-titlePull request titlechore(deps): update common-repo inherited files
pr-branchBranch name for the PRchore/upstream-sync
commit-messageCommit messagechore(deps): update common-repo files
dry-runCheck without creating PRfalse

Outputs

OutputDescription
has-updatestrue if version updates were available
has-changestrue if any file changes were made
pr-urlURL of created/updated PR
pr-numberPR number
files-changedJSON array of changed files

Examples

Update to latest versions (including breaking changes)

- uses: common-repo/common-repo@v1
  with:
    update-strategy: latest

Pin common-repo version for reproducibility

- uses: common-repo/common-repo@v1
  with:
    version: v0.27.0

Force re-sync (even without version updates)

Useful if someone manually edited a managed file:

- uses: common-repo/common-repo@v1
  with:
    force-sync: true

Dry-run to check for updates without creating PR

- uses: common-repo/common-repo@v1
  id: check
  with:
    dry-run: true

- run: echo "Updates available: ${{ steps.check.outputs.has-updates }}"

Custom PR settings

- uses: common-repo/common-repo@v1
  with:
    pr-title: 'chore: sync shared configs'
    pr-branch: 'automation/config-sync'
    commit-message: 'chore: update shared configuration files'

Requirements

Permissions

The workflow needs these permissions:

permissions:
  contents: write       # Push to PR branch
  pull-requests: write  # Create/update PRs

Private inherited repos

If your .common-repo.yaml references private repositories, you need a Personal Access Token (PAT) with repo scope:

- uses: common-repo/common-repo@v1
  with:
    token: ${{ secrets.PAT_TOKEN }}

Self-hosted runners

The action requires curl, git, gh (GitHub CLI), and jq. These are pre-installed on GitHub-hosted runners but may need to be installed on self-hosted runners.

How It Works

  1. Installs the common-repo binary (via setup-common-repo internally)
  2. Runs common-repo check --updates to detect available updates
  3. If updates exist (or force-sync: true):
    • Runs common-repo update to bump refs in .common-repo.yaml
    • Runs common-repo apply to regenerate files
  4. Creates or updates a PR with all changes

The action adds the dependencies label to PRs if that label exists in your repository.

Recipes and Patterns

Practical examples for common use cases.

Standard Project Setups

Rust CLI Project

A complete Rust CLI project with CI, pre-commit hooks, and conventional commits.

# .common-repo.yaml

# Inherit base Rust CLI configuration
- repo:
    url: https://github.com/common-repo/rust-cli
    ref: v2.0.0
    with:
      - include: ["**/*"]
      - exclude: [".git/**", "target/**", "src/**"]

# Add pre-commit hooks for Rust
- repo:
    url: https://github.com/common-repo/pre-commit-rust
    ref: v1.0.0
    with:
      - include: [".pre-commit-config.yaml"]

# Project-specific variables
- template-vars:
    project_name: my-cli
    author: Your Name
    description: A command-line tool

# Mark config files as templates
- template:
    - Cargo.toml
    - README.md
    - .github/workflows/*.yml

# Require essential tools
- tools:
    - rustc: ">=1.70"
    - cargo: "*"
    - pre-commit: ">=3.0"

Python Project with UV

Modern Python project using UV for dependency management.

# .common-repo.yaml

# Base Python project structure
- repo:
    url: https://github.com/common-repo/python-uv
    ref: v1.0.0
    with:
      - include: ["**/*"]
      - exclude: [".git/**", "__pycache__/**", ".venv/**"]

# Pre-commit hooks for Python
- repo:
    url: https://github.com/common-repo/pre-commit-python
    ref: v1.5.0
    with:
      - include: [".pre-commit-config.yaml"]

# Variables
- template-vars:
    project_name: my-python-project
    python_version: "3.11"
    author: Your Name

- template:
    - pyproject.toml
    - README.md

- tools:
    - python: ">=3.11"
    - uv: "*"

Node.js/TypeScript Project

TypeScript project with ESLint, Prettier, and GitHub Actions.

# .common-repo.yaml

# TypeScript base configuration
- repo:
    url: https://github.com/common-repo/typescript
    ref: v3.0.0
    with:
      - include:
          - tsconfig.json
          - .eslintrc.json
          - .prettierrc
          - .github/**

# Add package.json dependencies
- json:
    source: dev-deps.json
    dest: package.json
    path: devDependencies

- template-vars:
    project_name: my-project
    node_version: "20"

- tools:
    - node: ">=20"
    - npm: ">=10"

Upstream Repo Patterns

Upstream Repo That Consumes Its Own Tooling

An upstream repo that provides shared configuration to consumers, while also pulling CI and release tooling for its own use. The self: block keeps local consumption separate from the source API.

# .common-repo.yaml in an upstream repo

# Pull tooling for this repo only — consumers never see this
- self:
    - repo:
        url: https://github.com/org/release-tooling
        ref: v3.0.0
    - repo:
        url: https://github.com/org/ci-base
        ref: v1.2.0
    - exclude:
        - ".releaserc.yaml"
        - "commitlint.config.cjs"

# Source API — what consumers inherit
- include:
    - "configs/**"
    - ".github/**"
- template:
    - ".github/workflows/ci.yml"
- template-vars:
    GH_APP_ID_VAR: ORG_APP_ID
- rename:
    - from: "^configs/(.*)$"
      to: "$1"

Without self:, the release tooling and CI base files would leak into every consumer that inherits from this repo. See Authoring Upstream Repositories for more details.

CI/CD Patterns

GitHub Actions Workflow Inheritance

Inherit and customize GitHub Actions workflows.

# .common-repo.yaml

# Base CI workflows
- repo:
    url: https://github.com/your-org/ci-templates
    ref: v2.0.0
    with:
      - include: [".github/**"]

# Add project-specific jobs to CI
- yaml:
    source: local-ci-jobs.yml
    dest: .github/workflows/ci.yml
    path: jobs
    append: true

# Customize workflow variables
- template-vars:
    default_branch: main
    node_version: "20"
    deploy_environment: production

- template:
    - ".github/workflows/*.yml"

local-ci-jobs.yml:

integration-tests:
  runs-on: ubuntu-latest
  steps:
    - uses: actions/checkout@v4
    - name: Run integration tests
      run: ./scripts/integration-test.sh

Multi-Stage Deployment

Different configurations for different environments.

# .common-repo.yaml

# Base deployment configuration
- repo:
    url: https://github.com/your-org/deploy-configs
    ref: v1.0.0
    path: kubernetes  # Only kubernetes configs

# Environment-specific overrides
- yaml:
    source: env/production.yml
    dest: kubernetes/deployment.yml
    path: spec.template.spec

- template-vars:
    environment: staging
    replicas: "3"
    image_tag: latest

- template:
    - "kubernetes/*.yml"

Organization Standards

Company-Wide Defaults

Create a base configuration that all projects inherit.

In your org’s base repo (github.com/your-org/base-config):

# .common-repo.yaml (this repo exports these files)

- include:
    - .editorconfig
    - .gitignore
    - .github/CODEOWNERS
    - .github/PULL_REQUEST_TEMPLATE.md
    - .github/ISSUE_TEMPLATE/**
    - LICENSE

- template-vars:
    org_name: Your Organization
    support_email: support@your-org.com

In each project:

# .common-repo.yaml

# Inherit org-wide defaults
- repo:
    url: https://github.com/your-org/base-config
    ref: v1.0.0

# Add language-specific configuration
- repo:
    url: https://github.com/your-org/rust-standards
    ref: v2.0.0

# Project-specific customization
- template-vars:
    project_name: my-service
    team: platform

Security Configurations

Standardize security tooling across projects.

# .common-repo.yaml

# Security scanning configuration
- repo:
    url: https://github.com/your-org/security-configs
    ref: v1.2.0
    with:
      - include:
          - .github/workflows/security.yml
          - .snyk
          - .trivyignore

# Merge security checks into existing CI
- yaml:
    source: security-jobs.yml
    dest: .github/workflows/ci.yml
    path: jobs
    append: true

Monorepo Patterns

Shared Configuration Across Packages

Use path filtering to share configs in a monorepo.

# packages/web/.common-repo.yaml

# Inherit from monorepo root configs
- repo:
    url: https://github.com/your-org/your-monorepo
    ref: main
    path: shared/web-configs
    with:
      - include: ["**/*"]

# Package-specific settings
- template-vars:
    package_name: web
    port: "3000"

Multiple Config Repositories

Compose from multiple specialized config repos.

# .common-repo.yaml

# Linting standards
- repo:
    url: https://github.com/your-org/lint-configs
    ref: v2.0.0
    with:
      - include: [".eslintrc.json", ".prettierrc"]

# Testing configuration
- repo:
    url: https://github.com/your-org/test-configs
    ref: v1.5.0
    with:
      - include: ["jest.config.js", "vitest.config.ts"]

# CI/CD templates
- repo:
    url: https://github.com/your-org/ci-configs
    ref: v3.0.0
    with:
      - include: [".github/**"]

Configuration Merging

Extending package.json

Add dependencies without overwriting the whole file.

# .common-repo.yaml

# Base project
- repo:
    url: https://github.com/your-org/node-base
    ref: v1.0.0

# Add shared dev dependencies
- json:
    source: shared-dev-deps.json
    dest: package.json
    path: devDependencies

# Add shared scripts
- json:
    source: shared-scripts.json
    dest: package.json
    path: scripts
    append: true

shared-dev-deps.json:

{
  "eslint": "^8.0.0",
  "prettier": "^3.0.0",
  "typescript": "^5.0.0"
}

Merging Cargo.toml Dependencies

Add common Rust dependencies.

# .common-repo.yaml

- repo:
    url: https://github.com/your-org/rust-base
    ref: v1.0.0

# Add logging dependencies
- toml:
    source: logging-deps.toml
    dest: Cargo.toml
    path: dependencies

# Add dev dependencies
- toml:
    source: dev-deps.toml
    dest: Cargo.toml
    path: dev-dependencies

logging-deps.toml:

tracing = "0.1"
tracing-subscriber = "0.3"

Extending Pre-commit Hooks

Add hooks to an existing pre-commit configuration.

# .common-repo.yaml

# Base pre-commit config
- repo:
    url: https://github.com/your-org/pre-commit-base
    ref: v1.0.0
    with:
      - include: [".pre-commit-config.yaml"]

# Add additional hooks
- yaml:
    source: extra-hooks.yml
    dest: .pre-commit-config.yaml
    path: repos
    append: true

extra-hooks.yml:

- repo: local
  hooks:
    - id: custom-check
      name: Custom Check
      entry: ./scripts/check.sh
      language: script

Advanced Patterns

Conditional Configuration via Environment

Use environment variables for conditional behavior.

# .common-repo.yaml

- repo:
    url: https://github.com/your-org/base-config
    ref: v1.0.0

- template-vars:
    # Default to development settings
    log_level: debug
    enable_metrics: "false"

    # CI-specific overrides
    ci_mode: "false"

- template:
    - config/*.yml

Version Pinning Strategy

Pin specific versions while allowing updates.

# .common-repo.yaml

# Pin to specific major version for stability
- repo:
    url: https://github.com/your-org/stable-configs
    ref: v2.0.0  # Update manually for major versions

# Use branch for frequently updated configs
- repo:
    url: https://github.com/your-org/evolving-configs
    ref: main  # Always get latest (use with caution)

# Use commit SHA for maximum reproducibility
- repo:
    url: https://github.com/your-org/critical-configs
    ref: abc123def456  # Exact commit

Template File Patterns

Use template files for customization.

In config repo:

templates/
  Cargo.toml.template
  README.md.template

In .common-repo.yaml:

- repo:
    url: https://github.com/your-org/rust-templates
    ref: v1.0.0
    with:
      - include: ["templates/**"]
      - rename:
          - "templates/(.+)\\.template$": "$1"

- template-vars:
    project_name: my-project
    description: A great project
    license: MIT

- template:
    - Cargo.toml
    - README.md

Debugging Tips

Verbose Output

# See detailed processing (debug level)
common-repo apply --verbose

# Maximum verbosity (trace level)
common-repo apply --verbose --verbose

Inspect Before Applying

# List what would be created
common-repo ls -l

# See the diff
common-repo diff

# Dry run
common-repo apply --dry-run

Check Inheritance

# View inheritance tree
common-repo tree

# Get configuration overview
common-repo info

Validate Configuration

# Check syntax
common-repo validate

# Also check repo accessibility
common-repo validate --check-repos

Troubleshooting

This guide covers common issues and their solutions when using common-repo.

Git Authentication Errors

Problem

Git clone error for https://github.com/org/private-repo@main: Authentication failed
  hint: Check SSH keys, git credentials, or personal access token

or

Git command failed for https://github.com/org/repo: ls-remote - Permission denied

Causes

  • Accessing a private repository without credentials
  • Expired or invalid Git credentials
  • SSH key not configured for the repository host

Solutions

For HTTPS URLs:

  1. Ensure you have access to the repository
  2. Configure Git credential helper:
    git config --global credential.helper store
    
  3. Or use a personal access token in the URL:
    - repo:
        url: https://${GITHUB_TOKEN}@github.com/org/private-repo
        ref: main
    

For SSH URLs:

  1. Ensure your SSH key is added to the Git host
  2. Test SSH connectivity:
    ssh -T git@github.com
    
  3. Use SSH URL format in configuration:
    - repo:
        url: git@github.com:org/repo.git
        ref: main
    

For CI/CD environments:

  • GitHub Actions: Use ${{ secrets.GITHUB_TOKEN }} or a deploy key
  • GitLab CI: Use CI_JOB_TOKEN or deploy tokens

YAML Configuration Syntax Errors

Problem

Configuration parsing error: Missing url field
  hint: Add 'url: https://github.com/...' to the repo block

or

YAML parsing error: expected ',' or ']' at line X column Y

Causes

  • Incorrect indentation (YAML requires consistent spacing)
  • Missing or extra colons, brackets, or quotes
  • Tabs instead of spaces

Solutions

  1. Validate your YAML before running:

    common-repo validate
    
  2. Check indentation - YAML uses 2-space indentation by convention:

    # Correct
    - repo:
        url: https://github.com/org/repo
        ref: main
    
    # Incorrect (inconsistent indentation)
    - repo:
      url: https://github.com/org/repo
        ref: main
    
  3. Quote special characters in strings:

    # Correct
    - include: ["*.yml", "**/*.yaml"]
    
    # May cause issues
    - include: [*.yml, **/*.yaml]
    
  4. Use a YAML linter in your editor (e.g., YAML extension for VS Code)


Circular Dependency Detected

Problem

Cycle detected in repository dependencies: repo-a -> repo-b -> repo-a

Cause

Two or more repositories reference each other, creating an infinite loop.

Solution

  1. Review your dependency chain - Draw out which repos inherit from which
  2. Break the cycle by removing one of the circular references
  3. Use composition instead of inheritance - Extract shared config into a third repository that both can inherit from without referencing each other

Example fix:

Before: A -> B -> A (cycle)
After:  A -> C, B -> C (shared base, no cycle)

Network Connectivity Issues

Problem

Network operation error: https://github.com/org/repo - Connection timeout

or

Git clone error: Could not resolve host

Causes

  • No internet connection
  • Firewall blocking Git traffic
  • GitHub/GitLab outage
  • Proxy misconfiguration

Solutions

  1. Check connectivity:

    ping github.com
    git ls-remote https://github.com/common-repo/common-repo
    
  2. Configure proxy if behind a corporate firewall:

    git config --global http.proxy http://proxy.example.com:8080
    
  3. Check service status:

    • GitHub: https://www.githubstatus.com/
    • GitLab: https://status.gitlab.com/
  4. Retry - Transient network issues often resolve themselves


Cache Problems

Problem

Cache operation error: Failed to read cached repository

or stale/corrupted cached data causing unexpected behavior.

Cause

  • Corrupted cache files
  • Disk space issues
  • Interrupted previous operation

Solutions

  1. Clear the cache:

    common-repo cache clear
    
  2. View cache status:

    common-repo cache list
    
  3. Force fresh clone by clearing cache before apply:

    common-repo cache clear && common-repo apply
    

The cache is stored in your system’s cache directory (typically ~/.cache/common-repo on Linux/macOS).


Merge Conflicts

Problem

Merge conflict warning: source.txt -> dest.txt: File already exists

Cause

A file from an inherited repository conflicts with an existing file in your repository or another inherited repository.

Solutions

  1. Use exclude to skip conflicting files:

    - repo:
        url: https://github.com/org/configs
        ref: main
        with:
          - exclude: ["conflicting-file.yml"]
    
  2. Use rename to place the file elsewhere:

    - repo:
        url: https://github.com/org/configs
        ref: main
        with:
          - rename:
              - "ci.yml": ".github/workflows/inherited-ci.yml"
    
  3. Check operation order - Files from later operations overwrite earlier ones. Reorder your configuration if needed.


Invalid Glob Patterns

Problem

Glob pattern error: invalid pattern syntax

Cause

Malformed glob pattern in include, exclude, or other operators.

Solutions

  1. Use valid glob syntax:

    # Correct patterns
    - include:
        - "**/*.rs"      # All .rs files recursively
        - "src/**/*"     # All files under src/
        - "*.md"         # .md files in root only
        - ".*"           # Hidden files in root
    
    # Invalid patterns
    - include:
        - "**[.rs"       # Unclosed bracket
        - "src/***"      # Invalid triple asterisk
    
  2. Test patterns with common-repo ls before applying:

    common-repo ls
    

Git Reference Not Found

Problem

Git clone error for https://github.com/org/repo@v2.0.0: reference not found
  hint: Verify the repository URL and ref (branch/tag) are correct

Cause

The specified ref (branch, tag, or commit) does not exist in the repository.

Solutions

  1. Verify the ref exists:

    git ls-remote https://github.com/org/repo
    
  2. Check for typos in branch/tag names

  3. Use the correct ref format:

    # Tag
    ref: v1.0.0
    
    # Branch
    ref: main
    
    # Commit SHA (full or abbreviated)
    ref: abc1234
    
  4. Check for updates - the tag may have been deleted or renamed:

    common-repo check --updates
    

Permission Denied Writing Files

Problem

I/O error: Permission denied

Cause

  • Running in a read-only directory
  • File is owned by another user
  • File is locked by another process

Solutions

  1. Check directory permissions:

    ls -la .
    
  2. Ensure you own the files or have write access

  3. Close editors that may have files open

  4. On Windows, check if files are marked read-only


Getting Help

If your issue isn’t covered here:

  1. Run with verbose output for more details:

    common-repo apply --verbose
    # Or for maximum verbosity (trace level)
    common-repo apply --verbose --verbose
    
  2. Check existing issues: https://github.com/common-repo/common-repo/issues

  3. Open a new issue with:

    • Your .common-repo.yaml (sanitized of secrets)
    • Full error message
    • Output of common-repo --version
    • Your operating system

Authoring Upstream Repositories

This guide explains how to create and maintain upstream repositories that other projects can inherit from using common-repo.

What is an Upstream Repository?

An upstream repository contains configuration files, templates, and standards that other repositories (consumers) can inherit—essentially a library of reusable project configurations.

Upstream repository vs consumer repository:

AspectUpstream RepositoryConsumer Repository
PurposeProvides files for others to inheritUses files from upstream repos
AudienceMaintainers of shared standardsIndividual projects
Config fileOptional .common-repo.yamlRequired .common-repo.yaml
VersioningSemantic versioning with Git tagsReferences upstream repo versions
Can also consumeYes, via self: blocks (local-only)Yes, directly via repo:

Common use cases for upstream repositories:

  • Organization-wide coding standards (linting, formatting rules)
  • CI/CD workflow templates (GitHub Actions, GitLab CI)
  • Project scaffolding and boilerplate
  • Security policies and configurations
  • Documentation templates

Getting Started

Minimal Upstream Repository Structure

A upstream repository can be as simple as a directory with files to share:

my-upstream-repo/
├── .github/
│   └── workflows/
│       └── ci.yml
├── .pre-commit-config.yaml
└── README.md

No special configuration is required. Consumers reference your repository directly:

# Consumer's .common-repo.yaml
- repo:
    url: https://github.com/your-org/my-upstream-repo
    ref: v1.0.0

Optional: Configuration in Upstream Repos

Upstream repositories can include their own .common-repo.yaml to inherit from other upstreams, creating an inheritance chain:

org-base/          # Base standards
  └── rust-base/   # Rust-specific (inherits org-base)
      └── my-app/  # Consumer (inherits rust-base)

Using self: for Local Consumption

An upstream repo often needs to consume tooling from its own upstreams — CI config, pre-commit hooks, release automation — without leaking those files to the repos that consume it. The self: operator solves this.

Operations inside a self: block run in an isolated pipeline. Their output is written to the local working directory but never enters the composite filesystem that consumers see. This lets a single .common-repo.yaml define both what the repo provides (its source API) and what it consumes locally.

# .common-repo.yaml for an upstream repo

# Local consumption — pull tooling for this repo's own use.
# Consumers never see these operations.
- self:
    - repo:
        url: https://github.com/org/ci-tooling
        ref: v2.0.0
    - exclude:
        - ".releaserc.yaml"

# Source API — what consumers inherit
- include:
    - "src/**"
    - "src/.*"
- rename:
    - from: "^src/(.*)$"
      to: "$1"

Without self:, this repo would need a separate mechanism to pull its own tooling, or its consumers would inherit the CI tooling files unintentionally.

Key points:

  • self: blocks are stripped when a consumer inherits from this repo — consumers never see them
  • Any operator can appear inside self: (repo, include, exclude, rename, merge operators, etc.)
  • A self: block needs at least one repo: to populate its composite filesystem — without one, filtering operators like include/exclude/rename have nothing to operate on
  • Multiple self: blocks are allowed; each runs as an independent pipeline
  • self: blocks cannot be nested
  • The source pipeline runs first, then each self: block runs afterward as an independent pipeline invocation

See the Configuration Reference for the full operator specification.

Upstream-Declared File Filtering

Upstream repositories can define their “public API” by specifying which files are exposed to consumers. This is useful when a repository contains internal files that should not be inherited.

Filtering Operations in Upstream Repos

Upstream repos can use these operations to control which files consumers receive:

OperationPurpose
includeAllowlist of files to expose (all others excluded)
excludeBlocklist of files to hide (all others included)
renameTransform file paths before consumers see them

Example: Exposing Only Public Files

An upstream repo with internal test fixtures that should not be inherited:

# Upstream repo: .common-repo.yaml
- include:
    patterns:
      - "templates/**"
      - "configs/**"
      - ".github/**"
# Internal test fixtures, scripts, and docs are NOT exposed

Consumers automatically receive only the declared public files.

Example: Hiding Internal Files

An upstream repo that exposes everything except internal directories:

# Upstream repo: .common-repo.yaml
- exclude:
    patterns:
      - "internal/**"
      - "scripts/dev-*.sh"
      - ".internal-*"

Example: Renaming Template Files

An upstream repo that provides templates with a different naming convention:

# Upstream repo: .common-repo.yaml
- rename:
    - from: "templates/(.*)\\.template"
      to: "$1"

This transforms templates/config.yaml.template to config.yaml in consumers.

Operation Order

Operations are applied in this order:

  1. Upstream filtering (include/exclude/rename from upstream’s config)
  2. Upstream merge declarations (deferred merge operations)
  3. Consumer’s with: clause (further filtering/transforms by consumer)

This ensures upstream repos control their exposed files, while consumers can further filter (but not expand) what they receive.

Config File Auto-Exclusion

Upstream repository config files (.common-repo.yaml and .commonrepo.yaml) are automatically excluded and never copied to consumers. This prevents upstream configs from overwriting consumer configs.

Upstream-Declared Merge Behavior

By default, files from upstream repositories overwrite files in consumer repositories. However, upstream authors often know best how their files should integrate. The defer mechanism allows upstream repos to declare merge behavior that automatically applies when consumers inherit from them.

When to Use Upstream-Declared Merges

Use upstream-declared merges when:

  • Your upstream provides partial content meant to augment consumer files (e.g., additional CLAUDE.md rules)
  • Files need intelligent merging rather than overwriting (e.g., shared dependencies in Cargo.toml)
  • You want to reduce boilerplate in consumer configurations

Two Syntax Forms

1. auto-merge - When source and destination have the same filename (most common):

# Upstream repo: .common-repo.yaml
- markdown:
    auto-merge: CLAUDE.md
    section: "## Inherited Rules"
    append: true

This is shorthand for: source=CLAUDE.md, dest=CLAUDE.md, defer=true.

2. defer: true - When source and destination paths differ:

# Upstream repo: .common-repo.yaml
- yaml:
    source: config/labels.yaml
    dest: kubernetes.yaml
    path: metadata.labels
    defer: true

Example: Sharing CLAUDE.md Rules

An organization wants all repos to inherit coding guidelines:

# Upstream repo: org-standards/.common-repo.yaml
- markdown:
    auto-merge: CLAUDE.md
    section: "## Organization Standards"
    append: true
    create-section: true

Consumer repos automatically get merged CLAUDE.md content:

# Consumer repo: .common-repo.yaml
- repo:
    url: https://github.com/org/org-standards
    ref: v1.0.0
# No 'with:' clause needed - CLAUDE.md merges automatically

Example: Sharing Dependencies

A base Rust configuration shares common dependencies:

# Upstream repo: rust-base/.common-repo.yaml
- toml:
    auto-merge: Cargo.toml
    path: dependencies

Consumers inherit the base dependencies merged into their Cargo.toml.

Consumer Override

Consumers can always override upstream-declared behavior using the with: clause:

# Consumer .common-repo.yaml
- repo:
    url: https://github.com/org/org-standards
    ref: v1.0.0
    with:
      # Override: copy instead of merge
      - include: ["CLAUDE.md"]
      # This replaces the upstream-declared merge with a simple copy

When a consumer specifies merge operations for the same destination file, the consumer’s merge operations run after deferred (upstream-declared) merge operations, so consumer parameters take effect last.

Supported Merge Operators

All merge operators support defer and auto-merge:

OperatorExample Use Case
markdownShared CLAUDE.md rules, README sections
yamlKubernetes labels, GitHub Actions workflow steps
jsonpackage.json dependencies, tsconfig settings
tomlCargo.toml dependencies, pyproject.toml settings
iniGit config defaults, editor settings

Publishing Your First Version

  1. Commit your configuration files to the repository
  2. Create a Git tag following semantic versioning:
    git tag v1.0.0
    git push origin v1.0.0
    
  3. Consumers can now reference it:
    - repo:
        url: https://github.com/your-org/my-upstream-repo
        ref: v1.0.0
    

File Organization

Repository Root vs Subdirectory

By default, consumers inherit from the repository root. Use the path option to expose a subdirectory:

my-upstream-repo/
├── templates/
│   ├── rust/          # Rust project templates
│   │   ├── .github/
│   │   └── Cargo.toml
│   └── python/        # Python project templates
│       ├── .github/
│       └── pyproject.toml
└── README.md

Consumers select which template to use:

# Consumer's .common-repo.yaml
- repo:
    url: https://github.com/your-org/my-upstream-repo
    ref: v1.0.0
    path: templates/rust  # Only inherit from this subdirectory

Organizing Files by Concern

Group related files together to make selective inheritance easier:

upstream-repo/
├── ci/                    # CI/CD configurations
│   ├── .github/
│   └── .gitlab-ci.yml
├── quality/               # Code quality tools
│   ├── .pre-commit-config.yaml
│   ├── .editorconfig
│   └── rustfmt.toml
├── security/              # Security configurations
│   ├── .github/dependabot.yml
│   └── SECURITY.md
└── README.md

Consumers can pick specific concerns:

- repo:
    url: https://github.com/your-org/upstream-repo
    ref: v1.0.0
    with:
      - include: ["ci/**", "quality/**"]

Dotfiles and Hidden Files

Dotfiles (files starting with .) are included by default. Structure them naturally:

upstream-repo/
├── .github/
│   ├── workflows/
│   │   └── ci.yml
│   └── dependabot.yml
├── .pre-commit-config.yaml
├── .editorconfig
└── .gitignore

Files you typically should not include in upstream repos:

  • .git/ directory (automatically excluded)
  • .env files with secrets
  • Local IDE configurations (.vscode/, .idea/)

Template Variables

Template variables let consumers customize inherited files.

Naming Conventions

Use descriptive, lowercase names with underscores:

# Good variable names
project_name: my-app
rust_version: "1.75"
org_name: acme-corp
enable_coverage: true

# Avoid
PROJECT: my-app      # Uppercase
proj: my-app         # Abbreviation
projectName: my-app  # CamelCase

Required vs Optional Variables

Document which variables consumers must provide. In your template files, use sensible defaults where possible:

# .github/workflows/ci.yml (in upstream repo)
name: CI

env:
  RUST_VERSION: ${{ vars.rust_version || '1.75' }}  # Optional with default
  PROJECT_NAME: ${{ vars.project_name }}            # Required

Documenting Variables

Include a README section or separate file listing available variables:

## Template Variables

| Variable | Required | Default | Description |
|----------|----------|---------|-------------|
| `project_name` | Yes | - | Name of the project |
| `rust_version` | No | 1.75 | Rust toolchain version |
| `enable_coverage` | No | false | Enable code coverage |

Variable Overrides

Child repos can override variables defined by their parents:

# Consumer's .common-repo.yaml
- template-vars:
    project_name: my-project
    ci_timeout: "30"

Versioning and Releases

Semantic Versioning

Follow semantic versioning for upstream repositories:

  • MAJOR (v2.0.0): Breaking changes that require consumer updates
  • MINOR (v1.1.0): New files or features, backward compatible
  • PATCH (v1.0.1): Bug fixes, documentation updates

When to Bump Versions

Change TypeVersion BumpExample
Removed file that consumers depend onMajorDeleting .pre-commit-config.yaml
Renamed template variableMajorproject_namename
Changed file structure consumers referenceMajorMoving ci.yml to new path
Added new optional filesMinorNew workflow file
Added new template variable with defaultMinorNew enable_feature variable
Fixed bug in configurationPatchCorrected YAML syntax
Updated documentationPatchClarified usage instructions

Git Tagging Guidelines

# Create an annotated tag with message
git tag -a v1.2.0 -m "Add Python support and coverage workflows"

# Push the tag
git push origin v1.2.0

# List existing tags
git tag -l "v*"

Annotated tags (-a) are preferred as they include author and date information.

Changelog Maintenance

Maintain a CHANGELOG.md to document changes:

# Changelog

## [1.2.0] - 2024-01-15
### Added
- Python project template in `templates/python/`
- Coverage workflow for all language templates

### Changed
- Updated Rust version default to 1.75

## [1.1.0] - 2024-01-01
### Added
- Security policy template

Testing Your Upstream Repository

Testing Locally

Test your upstream repo against a local consumer before publishing:

# In consumer repository
cd my-consumer-project

# Reference local upstream repo instead of remote
# Edit .common-repo.yaml temporarily:
# - repo:
#     url: /path/to/local/upstream-repo
#     ref: HEAD

# Or use a file:// URL
# - repo:
#     url: file:///absolute/path/to/upstream-repo
#     ref: HEAD

common-repo ls      # Verify expected files
common-repo diff    # Check for issues
common-repo apply --dry-run

Creating a Test Consumer

Maintain a test consumer repository or directory:

upstream-repo/
├── .github/
├── templates/
├── tests/
│   └── test-consumer/    # Test consumer project
│       ├── .common-repo.yaml
│       └── validate.sh
└── README.md

The test consumer validates that your upstream repo works correctly:

# tests/test-consumer/.common-repo.yaml
- repo:
    url: ../..  # Relative path to upstream repo root
    ref: HEAD
    with:
      - include: ["templates/rust/**"]

CI Testing Strategies

Add CI workflows that validate your upstream repository:

# .github/workflows/test.yml
name: Test Upstream Repo

on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Install common-repo
        run: cargo install common-repo

      - name: Validate against test consumer
        run: |
          cd tests/test-consumer
          common-repo validate
          common-repo apply --dry-run

Composability

Designing for Multiple Upstream Inheritance

Consumers often inherit from multiple upstream repositories. Design yours to work alongside others:

# Consumer inheriting from multiple upstream repos
- repo:
    url: https://github.com/org/base-standards
    ref: v1.0.0

- repo:
    url: https://github.com/org/rust-config
    ref: v2.0.0

- repo:
    url: https://github.com/org/security-policies
    ref: v1.5.0

Avoiding File Conflicts

When multiple upstream repos provide similar files, conflicts can occur. Strategies to avoid this:

Use specific subdirectories:

# Instead of:
upstream-repo/
└── ci.yml

# Use:
upstream-repo/
└── .github/workflows/
    └── source-name-ci.yml   # Prefixed to avoid conflicts

Document file ownership:

## Files Provided

This upstream repository provides:
- `.github/workflows/lint.yml` - Linting workflow
- `.github/workflows/test.yml` - Test workflow

If you inherit from other upstream repos, ensure they don't provide the same files,
or use `exclude` to skip conflicting files.

Namespace Considerations

Consider prefixing files with your upstream repo’s purpose:

org-security/
└── .github/
    └── workflows/
        └── security-scan.yml   # Clear ownership

org-quality/
└── .github/
    └── workflows/
        └── quality-lint.yml    # No conflict with security-scan.yml

What to Include and Exclude

Good Candidates for Upstream Repos

File TypeExamplesWhy Share
CI/CD workflows.github/workflows/, .gitlab-ci.ymlStandardize build/test/deploy
Code quality.pre-commit-config.yaml, rustfmt.tomlConsistent formatting
Editor configs.editorconfig, .vscode/settings.jsonShared development experience
SecuritySECURITY.md, dependabot.ymlOrg-wide security policies
Documentation templatesCONTRIBUTING.md, issue templatesConsistent contributor experience
Build configurationsCargo.toml fragments, tsconfig.jsonShared build settings

What to Avoid

Do not include:

  • Secrets or credentials: API keys, tokens, passwords
  • Environment-specific paths: /Users/yourname/...
  • Large binary files: Images, compiled artifacts
  • Generated files: target/, node_modules/, dist/
  • Personal IDE settings: User-specific configurations
  • Repository-specific data: Git history, issues, PRs

Handling Sensitive Data

If your templates reference secrets, use placeholders:

# Good: Reference secrets by name
env:
  API_KEY: ${{ secrets.API_KEY }}

# Bad: Never include actual secrets
env:
  API_KEY: sk-1234567890abcdef

Document required secrets in your README:

## Required Secrets

Consumers must configure these repository secrets:

| Secret | Description |
|--------|-------------|
| `API_KEY` | API key for external service |
| `DEPLOY_TOKEN` | Token for deployment |

Next Steps

Schema Reference

This page documents the configuration schemas for common-repo.

v1 Schema (Legacy)

The original v1 schema from commonrepo. This has been superseded by the v2 schema with operators.

######################
# CommonRepo v1 schema
#
# This is the original schema definition for the v1 of commonrepo. This has been
# superceded by the v2 common-repo.
#
# This schema has the problem where the operations like "include", "rename" and
# "exclude" are applied in a fixed order, and only once, which is inflexible and
# actually more difficult to implement than operators. It also makes it more
# difficult to implement new operators in the form of plugins.
#
# This v1 schema is from the codebase at https://github.com/shakefu/commonrepo.

#########
# Upstreams
#
# Upstream repositories define which of their files should be imported into the
# child repository.
#
# This should be defined in the `.commonrepo.yml` file.
#
# The bare minimum configuration is a single `include: ["**/*"]` entry.

# include opts-in files and folders based on globbing patterns
include:
  # Everything
  - "**/*"
  # Every hidden file
  - .*
  # Every hidden directory
  - .*/**/*
  # Everything in the files/ directory
  - files/**/*
  # Individual file
  - .gitignore

# after the full list of included files is built, the exclusion filters are
# applied to remove any unwanted files from the working list
exclude:
  # Template related workflows
  - .github/workflows/template_*
  # Markdown files
  - "**/*.md"

# before renames are applied, files that are templates store the vars: context
template:
  - "templates/**"

# after the filtered list is created, destination file names are generated by
# passing the working list through the rename transforms, in order
rename:
  # Rename a path
  - "badname/(.*)": "goodname/$1"
  # Strip one directory from the path
  - "^files/(.*)": "$1"
  # Strip multiple directories from the path
  - "some/parent/dir/(.*)": "$1"
  # Recompose directories
  - "parent/([^/]+)/dir/(.*)": "$1/$2"
  # Add a prefix to the path
  - "(.*\\.md)": "docs/$1"
  # Move templates to repo root
  - "templates/(.*)": "$1"

# Install specs use SemVer constraints
install:
  # List of maps, where the key name matches the tool filename/path, the version
  # describes the desired version.
  - golang: 1.17.x
  - jq: ^1

# Optional override for path if you don't want to use the default
install-from: ./.commonrepo/install/

# Optional override for preferred install manager order
install-with: [apt-get, brew, platform]

---

###########
# Consumers
#
# Consumer repositories can define which upstream repos they want to consume from, as
# well as apply additional filters to the ones defined in the upstream repos.

# define a list of upstream repositories, which mimic the consumer repos keys
upstream:
  - url: https://github.com/shakefu/commonrepo
    ref: v1.1.0
    overwrite: false  # TBD if this should be implemented
    include: [.*]
    exclude: [.gitignore]
    rename: [{".*\\.md": "docs/$1"}]

# Template context for all upstreams...
template-vars:
  project: myprojectname

v2 Schema (Current)

The v2 schema uses operators for flexible, composable configuration. All operators are documented in the Configuration Reference.

Available operators: repo, include, exclude, rename, template, template-vars, tools, self, yaml, json, toml, ini, markdown.