Skip to content

JohnsonArnek/Github-Family

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

3 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Github Family

Github Family is a tiny CLI for coordinating related Git repositories as one logical project.

It is built for projects that need separate visibility domains, such as:

  • public source
  • private core code
  • security fixes
  • experiments
  • client-specific work
  • private notes or docs

The pitch:

Work like it is one project. Store it as multiple Git repos. Preserve separate permissions. Detect incomplete cross-repo states.

The core abstraction is a small topology:

family -> domains -> repos

For example, private.security can be a child domain of private, and commands can target either repo roles or whole domains.

The portable unit is a small control repo:

my-project-family/
  .repo-family.toml
  .repo-family.lock.toml
  .gitignore
  public/      # child repo working copy, ignored
  private/     # child repo working copy, ignored
  security/    # child repo working copy, ignored

Commit the manifest and lockfile in the control repo. Recreate the child working copies with rf bootstrap.

What it is not

Repo Family does not make Git atomic across repositories.

It coordinates normal Git repositories and records bundle metadata so partial states can be detected, repaired, or reverted by normal Git operations.

No new VCS. No hosting platform. No magic distributed transaction layer.

Install

From this checkout:

py -m pip install -e .

Then:

rf --help

You can also run it without installing:

py -m repo_family --help

Quick start

Create a new family control repo:

rf new my-project --template open-core
rf new my-project --template public-private-security --github owner/my-project-family --push

Or create a manifest in the current directory:

rf init my-project
rf init my-project --template public-private-security

Available templates:

  • public-private
  • open-core
  • public-private-security

Edit .repo-family.toml:

[family]
name = "my-project"

[domains.public]
description = "Open source surface"

[domains.private]
description = "Closed source work"

[repos.public]
url = "git@github.com:you/my-project.git"
path = "public"
domain = "public"
visibility = "public"

[repos.private]
url = "git@github.com:you/my-project-private.git"
path = "private"
domain = "private"
visibility = "private"
optional = true

Clone the repos:

rf clone
rf bootstrap

See the whole family:

rf status
rf tree
rf paths

Create a lockfile snapshot:

rf snapshot

Check whether the workspace still matches the manifest and snapshot:

rf doctor
rf check
rf diff

Commit and push selected domains:

rf branch public private auth-refactor
rf commit public private --all -m "Update family bundle"
rf push public private --set-upstream

Create PRs with GitHub CLI:

rf pr public private --draft

Commands

rf new <name>

Creates a new repo-family control repo in one step:

rf new my-project --template open-core

This creates:

  • a target directory
  • .repo-family.toml
  • a managed .gitignore
  • a local Git repo on main
  • an initial commit

Add a GitHub remote:

rf new my-project --github owner/my-project-family

If the GitHub repo already exists and SSH auth is configured, push immediately:

rf new my-project --github owner/my-project-family --push

If GitHub CLI is installed and authenticated, Repo Family can create the GitHub repo too:

rf new my-project --github owner/my-project-family --create-github --push

Useful options:

--template public-private
--template open-core
--template public-private-security
--repo-visibility private
--repo-visibility public
--no-commit

rf init [name]

Creates .repo-family.toml in the current directory.

By default, it also adds child repo paths to a managed block in .gitignore, so the family directory can be committed as a control repo without committing child working copies. Use --no-gitignore to skip that.

Use --template for common layouts:

rf init my-project --template public-private
rf init my-project --template open-core
rf init my-project --template public-private-security

rf add <role> <url>

Adds a repo table to the manifest.

rf add security git@github.com:you/my-project-security.git \
  --domain private.security \
  --visibility security \
  --path security \
  --optional

By default, rf add updates the managed .gitignore block. Use --no-gitignore to skip that.

rf clone [selector...]

Clones each configured repo into its configured path. Optional repos may be unavailable, which supports degraded clone mode for users who only have access to part of the family.

Selectors can be repo roles or domains:

rf clone public
rf clone private.security

rf bootstrap [selector...]

Recreates a family workspace from the control repo.

git clone git@github.com:org/my-project-family.git
cd my-project-family
rf bootstrap

Bootstrap does four small things:

  • updates the managed .gitignore block for child repo paths
  • clones missing repos
  • fetches available repos with git fetch --all --prune
  • verifies lockfile SHAs are available locally when .repo-family.lock.toml exists

Useful options:

rf bootstrap --json
rf bootstrap --degraded
rf bootstrap --skip-fetch
rf bootstrap --no-gitignore

--degraded lets a workspace come up even when required repos are unavailable, which is useful for users who can only access part of the family.

rf ignore [selector...]

Updates the managed .gitignore block for selected child repo paths:

rf ignore
rf ignore private --json

The command only edits the block between:

# >>> repo-family child repos >>>
# <<< repo-family child repos <<<

rf status [selector...]

Shows path, visibility, branch, head SHA, dirty state, and upstream drift for each repo.

Use --json for scripts or CI:

rf status --json

rf tree [selector...]

Shows the family topology as domains and repos:

my-project
|-- public - Open source surface
|   `-- public [public] clean main abc123 path=public
`-- private - Closed source work
    `-- security
        `-- fixes [security] missing optional path=security

rf snapshot [selector...]

Writes .repo-family.lock.toml with the current commit for each repo.

By default, snapshot refuses dirty repos. Use --allow-dirty when you intentionally want to record that state.

rf check [selector...]

Checks for missing repos, invalid repo paths, dirty repos, and changes since the lockfile.

Use --strict if warnings should fail the command.

Use --json for scripts or CI:

rf check --json

rf doctor

Checks local setup:

  • git availability
  • gh availability and auth status
  • manifest validity
  • duplicate repo paths
  • lockfile parseability
  • workspace health
rf doctor
rf doctor --json

Missing or unauthenticated gh is a warning, not a hard failure, because only rf pr needs it.

rf diff [selector...]

Compares the current repo heads to .repo-family.lock.toml.

rf diff
rf diff private.security --json

rf diff exits nonzero when a selected repo has drifted from the lockfile.

rf paths [selector...]

Lists selected repo paths.

rf paths
rf paths private --json

This is useful when shell scripts need to map repo roles to local paths without parsing the manifest directly.

rf branch [selector...] <name>

Creates and switches to a branch in selected repos:

rf branch auth-refactor
rf branch public private auth-refactor

The branch name is the last argument. Any earlier positional arguments are selectors.

rf switch [selector...] <name>

Switches selected repos to an existing branch:

rf switch main
rf switch private.security embargo-fix

The branch name is the last argument. Any earlier positional arguments are selectors.

rf fetch [selector...]

Fetches selected repos:

rf fetch
rf fetch private --prune

rf pull [selector...]

Pulls selected repos:

rf pull
rf pull public private --ff-only
rf pull private --rebase

--ff-only and --rebase are mutually exclusive.

rf commit [selector...] -m <message>

Commits changes across selected repos.

By default, this only commits already staged changes. Use --all to run git add -A first:

rf commit private --all -m "Implement auth bundle"

Clean repos are skipped unless --allow-empty is passed.

rf push [selector...]

Pushes selected repos.

rf push private
rf push private --set-upstream

--set-upstream runs git push -u origin <branch> by default. Use --remote <name> to choose another remote.

rf repair [selector...]

Records the current fixed state in .repo-family.lock.toml and marks the bundle repaired.

rf repair
rf repair private.security

By default, repair refuses dirty repos. Use --allow-dirty only when you intentionally want to record that state.

rf revert [selector...]

Reverts the locked bundle commit in each selected repo, then records a new lockfile state marked reverted.

rf revert
rf revert private.security

This is coordinated Git revert, not atomic rollback. If one repo conflicts, normal Git conflict handling still applies.

rf pr [selector...]

Creates pull requests for selected repos using GitHub CLI:

rf pr public private --draft
rf pr public --title "Family bundle" --base main

The default PR title is deliberately boring:

Family bundle

That keeps public/private bundle names from leaking unless you explicitly pass --title.

rf exec [selector...] -- <command>

Runs a command in each selected repo:

rf exec private -- git status --short

rf bundle start <id>

Writes a named bundle snapshot:

rf bundle start auth-refactor

rf bundle status

Shows both the recorded bundle status and the status computed from the current workspace.

Bundle:          auth-refactor
Recorded status: complete
Computed status: complete

Use --json for scripts or CI:

rf bundle status --json

rf bundle mark <status>

Marks the current bundle status in the lockfile.

rf bundle mark repaired
rf bundle mark reverted

Supported statuses:

  • complete
  • incomplete
  • repaired
  • reverted

Manifest

The manifest lives at .repo-family.toml.

[family]
name = "example"

[domains.public]
description = "Open source surface"

[domains.private]
description = "Closed source work"

[domains.private.security]
description = "Embargoed security work"

[repos.public]
url = "git@github.com:org/example.git"
path = "public"
domain = "public"
visibility = "public"

[repos.private]
url = "git@github.com:org/example-private.git"
path = "private"
domain = "private"
visibility = "private"
optional = true

[repos.security]
url = "git@github.com:org/example-security.git"
path = "security"
domain = "private.security"
visibility = "security"
optional = true

Each repo needs:

  • url: Git clone URL
  • path: local path relative to the family root
  • domain: logical domain, such as public, private, or private.security
  • visibility: usually public, private, or security
  • optional: whether missing or inaccessible clones are allowed

If domain is omitted, Repo Family falls back to the repo's visibility so older manifests keep working.

Lockfile

The lockfile lives at .repo-family.lock.toml.

It records one bundle snapshot:

[snapshot]
id = "bundle-20260623-043000"
status = "complete"
family = "example"
created_at = "2026-06-23T04:30:00Z"

[repos.public]
path = "public"
domain = "public"
visibility = "public"
branch = "main"
head = "abc123..."
dirty = false

This makes it obvious when a repo family is complete, incomplete, or changed since the last recorded bundle.

Future shape

The deliberately small version is still only local coordination. Useful next features would be:

  • policy checks for public visibility leaks
  • dependency metadata between repo roles
  • CI wait/check aggregation
  • safer PR metadata templates per visibility domain

The core promise should stay boring:

Coordinate repo families. Do not pretend Git became atomic.

Tests

py -m unittest discover -s tests -v

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages