Skip to main content

git-orchard

pre-commit status Deploy Status Go Reference Arch User Repsoitory PyPI Go Report Card

A command-line utility for managing git-subtrees.

Install

AUR:

git-orchard is available from the Arch User Repository.

yay -S git-orchard

pip:

git-orchard is available as a pypi package.

pip install git-orchard

go:

go install github.com/jmelahman/git-orchard@latest

Usage

Subtrees are listed in a committed manifest at the repository root, .gitsubtrees or .config/git-orchard/subtrees (but not both), in the same syntax as .gitmodules:

[subtree "tools/foo"]
	remote = git@github.com:owner/foo.git
	branch = master

The same keys in git's own configuration (e.g. .git/config) override it for one clone. Pulls and adds are squashed unless the manifest sets orchard.squash = false.

git orchard init                        # list the subtrees already in git history
git orchard add git@github.com:owner/foo.git tools/foo
git orchard add git@github.com:owner/foo.git # prefix defaults to the repo name, foo
git orchard status                      # commits ahead/behind each upstream
git orchard pull [prefix...]            # merge upstream changes
git orchard push [prefix...]            # publish, fast-forward only
git orchard push --changed-since REV    # only subtrees changed since REV
git orchard push --tag tools/foo/v1.2.3 # publish as v1.2.3 upstream
git orchard release tools/foo          # tag the next version, e.g. tools/foo/v1.2.4, and push it to origin
git orchard release tools/foo v2.0.0   # or a version of your choosing
git orchard sync [prefix...]            # copy shared files into subtrees
git orchard mv tools/foo foo            # move a subtree and commit it, with anything staged
git orchard detach [worktree]           # turn a linked worktree into a standalone clone
git orchard ci [prefix...]              # GitHub Actions status of the monorepo and each subtree

Without a version, release picks one after the latest release, much as tag does: the patch version incremented (--minor and --major increment those instead), or a pre-release's stable release; --suffix rc picks the next release candidate, e.g. v1.2.4-rc, then v1.2.4-rc.1. Releases are the <prefix>/v* tags in the monorepo and on its remote, and the v* tags upstream, so releases from before the subtree count; --dry-run prints the pick. release requires the upstream branch to contain the release already, so the upstream tag lands on its history; --upstream pushes the branch and tag there directly instead of leaving it to the action. push, pull and release take --no-verify to skip git hooks. Pushes upstream run the monorepo's pre-push hook on what they change there, at monorepo paths, rather than on the split's commits, so a tool like pre-commit checks only those files instead of the whole repository; a tag the upstream branch already contains changes nothing. push --force overwrites upstream branches and tags, leased on their value when the push starts so a concurrent update still fails it; the action never forces. release --force moves an existing tag, unless the upstream already published it at another commit: the Go module proxy and release artifacts won't follow a moved release.

push splits each subtree out of the monorepo with git subtree split, which is deterministic, so the same history always gives the same commits and every push is a fast-forward. An upstream with commits the monorepo doesn't have rejects the push until they're pulled in.

mv moves a subtree's directory and manifest entry and commits the move, with the same git-subtree-dir and git-subtree-split trailers a squash has, so splits of the new prefix carry on from the upstream's history instead of starting over. Anything already staged goes into the same commit, so stage updates to whatever names the old prefix (a go.work, CI paths) first. Clones fetch the recorded split from the upstream, so mv refuses a subtree with unpushed commits; push them first.

detach gives a worktree made with git worktree add its own .git directory, with the repository's refs, config, hooks and excludes, and the worktree's HEAD and index, so uncommitted changes carry over; objects are hardlinked where possible, as for a local clone. It then drops the worktree from the original repository, and refuses one that is locked, has submodules, or is mid-merge, rebase, cherry-pick, revert or bisect.

Besides gh, for ci and release --watch, git-orchard only runs git, so credentials, SSH config and url.<base>.insteadOf rewrites apply as usual.

CI

ci shows the latest run of each workflow on each subtree's upstream branch, and, unless prefixes are given, on the monorepo's own default branch as the prefix .. Deleted and disabled workflows, and pull requests' runs, are left out. It asks the GitHub CLI, gh, so that must be signed in.

git orchard ci                          # every run, with the failed ones in red
git orchard ci --failed                 # only the failed runs
git orchard ci --watch                  # refresh every minute until interrupted
git orchard ci --json                   # for scripts
git orchard ci --failed --logs | claude -p "triage these CI failures"
git orchard ci --prune-stale            # delete stale workflows' failed runs, after asking

--logs adds each failed run's logs as Markdown, cut down to the step that failed in each failed job (at most --lines of its last lines, 150 by default), without the setup and verbose logging GitHub's log viewer collapses. A failed run of a workflow that won't run again is stale: it shows dimmed, --failed hides it, and it doesn't count as a failure. So far that's Dependabot Updates in a repository with no .github/dependabot.yml and security updates off, as in a subtree whose dependencies the monorepo updates. --prune-stale deletes every failed run of such workflows, not just the latest, since the one before would take its place; it lists them and asks first, unless --yes. gh must be able to read the repository's security updates setting, which takes admin access; a repository it can't tell about is taken to be live. ci exits 1 if any run failed, even if it couldn't check some repository; 2 if it couldn't check some repository and nothing failed; and 130 if interrupted. So a script can ask only when something failed:

logs=$(git orchard ci --failed --logs); [ $? = 1 ] && claude -p "triage these CI failures" <<<"$logs"

Shared files

Subtrees that are published on their own each need their own copy of config like .pre-commit-config.yaml or .github/dependabot.yml. git orchard sync keeps those copies in step with one source. A subtree lists the profiles it shares, and each profile is a directory under orchard.sharedDir (.config/git-orchard/shared by default):

[subtree "tools/foo"]
	remote = git@github.com:owner/foo.git
	shared = base
	shared = go
.config/git-orchard/shared/
  base/.github/dependabot.yml   # → tools/foo/.github/dependabot.yml
  base/.pre-commit-config.yaml
  go/.pre-commit-config.yaml

A file in a profile lands at the same path in the subtree, replacing it whole. When the subtree's file marks a block for the profile, only the lines between the markers are replaced, and the rest of the file stays the subtree's own:

repos:
  # BEGIN orchard:base
  # END orchard:base
  # BEGIN orchard:go
  # END orchard:go
  - repo: local # not shared
    hooks: [...]

Markers work in any comment syntax, since git-orchard only looks for BEGIN orchard:<profile> and END orchard:<profile> in the line. Two profiles can share a file only through blocks.

sync exits 1 when it changes a file, like a formatter, and --check prints the differences without writing them. To run it on every commit, add the hook to the monorepo's root pre-commit config (not to the subtrees', since their mirrors have no manifest):

repos:
  - repo: https://github.com/jmelahman/git-orchard
    rev: v1.2.3
    hooks:
      - id: git-orchard-sync

GitHub Action

This repository is also an action that mirrors a monorepo's subtrees on every push: changed subtrees are pushed to their upstreams, and a <prefix>/<name> tag is published to that prefix's upstream as <name>.

on:
  push:
    branches: [master]
    tags: ["**/v*"] # `*` doesn't match `/`

jobs:
  mirror:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6
        with:
          fetch-depth: 0 # splits need the full history
          persist-credentials: false
      - uses: jmelahman/git-orchard@v1
        with:
          app-client-id: ${{ vars.ORCHARD_APP_CLIENT_ID }}
          app-private-key: ${{ secrets.ORCHARD_APP_PRIVATE_KEY }}

The action pushes as a GitHub App, which git orchard github-app creates:

git orchard github-app  # add --org ORG for an organization's repositories

It opens a browser to create a private App under your account from a manifest (contents and workflows write, no webhook), then to install it: pick the upstream repositories there. With the GitHub CLI installed, it stores the App's client ID and private key on the monorepo (origin, or --repo) as the ORCHARD_APP_CLIENT_ID variable and ORCHARD_APP_PRIVATE_KEY secret; otherwise it writes the key to a file and prints the gh commands to store it. The App and its key are yours; git-orchard runs no service.

The action mints a short-lived token from the App's installation for owner (default: the monorepo's owner). Alternatively, pass token, e.g. a fine-grained token with "Contents" and "Workflows" read and write on the upstreams; GitHub refuses pushes that change .github/workflows without the latter. Either way, GitHub remotes in the manifest are rewritten to use the token. changed-since defaults to the start of the pushed range; set it empty to push every subtree. The action builds git-orchard from its own source, so the CLI is always the version the action is pinned to.

Metadata

Release files for git-orchard 1.5.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Built distributions (wheels)

Table of built distributions (wheels) for git-orchard 1.5.0
File Actions
git_orchard-1.5.0-py3-none-win_arm64.whl Python 3 none Windows ARM64 Details for git_orchard-1.5.0-py3-none-win_arm64.whl
git_orchard-1.5.0-py3-none-win_amd64.whl Python 3 none Windows x86-64 Details for git_orchard-1.5.0-py3-none-win_amd64.whl
git_orchard-1.5.0-py3-none-manylinux_2_17_x86_64.whl Python 3 none Linux glibc 2.17+ x86-64 Details for git_orchard-1.5.0-py3-none-manylinux_2_17_x86_64.whl
git_orchard-1.5.0-py3-none-manylinux_2_17_aarch64.whl Python 3 none Linux glibc 2.17+ ARM64 Details for git_orchard-1.5.0-py3-none-manylinux_2_17_aarch64.whl
git_orchard-1.5.0-py3-none-macosx_11_0_x86_64.whl Python 3 none macOS 11.0+ x86-64 Details for git_orchard-1.5.0-py3-none-macosx_11_0_x86_64.whl
git_orchard-1.5.0-py3-none-macosx_11_0_arm64.whl Python 3 none macOS 11.0+ ARM64 Details for git_orchard-1.5.0-py3-none-macosx_11_0_arm64.whl
git_orchard-1.5.0-py3-none-any.whl Python 3 none any Details for git_orchard-1.5.0-py3-none-any.whl

Total release size: 30.1 MB

Release files / git_orchard-1.5.0-py3-none-win_arm64.whl

Download URL git_orchard-1.5.0-py3-none-win_arm64.whl
Size 4.1 MB
Tags Python 3 Windows ARM64
SHA-256 checksum
How to use checksums
00af6c459c461f16e187e3cfb60e2b156f1a48b7932ab8888a64306a05a44dd4
BLAKE2b-256 checksum
How to use checksums
f0be9d8ce847aaeaffd2a2661eede55e23d7a6ed8ea47d354d52400d9271921a
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.13.6 {"installer":{"name":"uv","version":"0.13.6","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"26.04","id":"resolute","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release files / git_orchard-1.5.0-py3-none-win_amd64.whl

Download URL git_orchard-1.5.0-py3-none-win_amd64.whl
Size 4.6 MB
Tags Python 3 Windows x86-64
SHA-256 checksum
How to use checksums
2acda0b6e9036bcd5ba79913a819916760813aa802cc33eddfab852f5c3ae660
BLAKE2b-256 checksum
How to use checksums
594b8bed77ae24ee384343f7db2f2800a162c7ed5ed801fe36918c8b373629b6
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.13.6 {"installer":{"name":"uv","version":"0.13.6","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"26.04","id":"resolute","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release files / git_orchard-1.5.0-py3-none-manylinux_2_17_x86_64.whl

Download URL git_orchard-1.5.0-py3-none-manylinux_2_17_x86_64.whl
Size 4.4 MB
Tags Linux glibc 2.17+ x86-64 Python 3
SHA-256 checksum
How to use checksums
0e5131f011e9dc151277dc8c69f2bf6fae924a482100b6a490a6153f1279baf5
BLAKE2b-256 checksum
How to use checksums
b19fee985103c7ea5fa45fd59b868f965b3eb1c584471604b2396753b764a68e
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.13.6 {"installer":{"name":"uv","version":"0.13.6","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"26.04","id":"resolute","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release files / git_orchard-1.5.0-py3-none-manylinux_2_17_aarch64.whl

Download URL git_orchard-1.5.0-py3-none-manylinux_2_17_aarch64.whl
Size 4.0 MB
Tags Linux glibc 2.17+ ARM64 Python 3
SHA-256 checksum
How to use checksums
8979031f078978129e335452741f22f03314be99d47c230871bd6b92bab12c76
BLAKE2b-256 checksum
How to use checksums
882a8c3960445911d4df55035589a05a5b8758aaa41740da0c51893d3160b2cd
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.13.6 {"installer":{"name":"uv","version":"0.13.6","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"26.04","id":"resolute","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release files / git_orchard-1.5.0-py3-none-macosx_11_0_x86_64.whl

Download URL git_orchard-1.5.0-py3-none-macosx_11_0_x86_64.whl
Size 4.5 MB
Tags Python 3 macOS 11.0+ x86-64
SHA-256 checksum
How to use checksums
9762f21f31eb7de67adc297b929501f1c0e2d9527cd09706efad22dc97f774d9
BLAKE2b-256 checksum
How to use checksums
58dad6f75d9f627069f1b941d4979177810b5e55a2bb203e1cd12aba0ed84664
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.13.6 {"installer":{"name":"uv","version":"0.13.6","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"26.04","id":"resolute","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release files / git_orchard-1.5.0-py3-none-macosx_11_0_arm64.whl

Download URL git_orchard-1.5.0-py3-none-macosx_11_0_arm64.whl
Size 4.1 MB
Tags Python 3 macOS 11.0+ ARM64
SHA-256 checksum
How to use checksums
b5dabfb6099a98a74bbaa68d99bb6d4123c73d255b576c1609612bc4614b4230
BLAKE2b-256 checksum
How to use checksums
d6bc358201a9cd02ec77964df795256ede9bc03d6c1f35ff2eb167453c22f4b9
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.13.6 {"installer":{"name":"uv","version":"0.13.6","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"26.04","id":"resolute","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release files / git_orchard-1.5.0-py3-none-any.whl

Download URL git_orchard-1.5.0-py3-none-any.whl
Size 4.4 MB
Tags Python 3
SHA-256 checksum
How to use checksums
253e28aa6a3ccf4daed2545bbfed9163614f2f8026b8b299da0dbf1f1659e778
BLAKE2b-256 checksum
How to use checksums
dccb968da1b2c3903fd02aa4e8fdf3206b5ece224f0326fe810ba00c77dd56b6
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.13.6 {"installer":{"name":"uv","version":"0.13.6","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"26.04","id":"resolute","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Security for git-orchard 1.5.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page