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 release to the remote's current branch, if it isn't there, and then the branch and tag upstream 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 calling GitHub's API, 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 GitHub's API, with a token from GH_TOKEN, GITHUB_TOKEN or, where the GitHub CLI is signed in, gh auth token.

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. The token 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.6.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.6.0
File Actions
git_orchard-1.6.0-py3-none-win_arm64.whl Python 3 none Windows ARM64 Details for git_orchard-1.6.0-py3-none-win_arm64.whl
git_orchard-1.6.0-py3-none-win_amd64.whl Python 3 none Windows x86-64 Details for git_orchard-1.6.0-py3-none-win_amd64.whl
git_orchard-1.6.0-py3-none-manylinux_2_17_x86_64.whl Python 3 none Linux glibc 2.17+ x86-64 Details for git_orchard-1.6.0-py3-none-manylinux_2_17_x86_64.whl
git_orchard-1.6.0-py3-none-manylinux_2_17_aarch64.whl Python 3 none Linux glibc 2.17+ ARM64 Details for git_orchard-1.6.0-py3-none-manylinux_2_17_aarch64.whl
git_orchard-1.6.0-py3-none-macosx_11_0_x86_64.whl Python 3 none macOS 11.0+ x86-64 Details for git_orchard-1.6.0-py3-none-macosx_11_0_x86_64.whl
git_orchard-1.6.0-py3-none-macosx_11_0_arm64.whl Python 3 none macOS 11.0+ ARM64 Details for git_orchard-1.6.0-py3-none-macosx_11_0_arm64.whl
git_orchard-1.6.0-py3-none-any.whl Python 3 none any Details for git_orchard-1.6.0-py3-none-any.whl

Total release size: 30.1 MB

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

Download URL git_orchard-1.6.0-py3-none-win_arm64.whl
Size 4.1 MB
Tags Python 3 Windows ARM64
SHA-256 checksum
How to use checksums
776c9527d9c7d154618299b011e570a5abe550f8028f3ace0f0a112c4663409d
BLAKE2b-256 checksum
How to use checksums
7aa8a2c94e9ed760a273eaa38456b61aa92970807e4a5b05717078d98a938555
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.6.0-py3-none-win_amd64.whl

Download URL git_orchard-1.6.0-py3-none-win_amd64.whl
Size 4.6 MB
Tags Python 3 Windows x86-64
SHA-256 checksum
How to use checksums
964dbbf829eaeb85966e00d2951065625cb745c28d8ab6772afa68112077d913
BLAKE2b-256 checksum
How to use checksums
c952592e058f346892ae612917246e27688a314ec810b69b3af1807e285d979d
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.6.0-py3-none-manylinux_2_17_x86_64.whl

Download URL git_orchard-1.6.0-py3-none-manylinux_2_17_x86_64.whl
Size 4.5 MB
Tags Linux glibc 2.17+ x86-64 Python 3
SHA-256 checksum
How to use checksums
e9c83050178d744a0f42a5a76127adbad2e097a85c14f9c1cb395b57b7b8a199
BLAKE2b-256 checksum
How to use checksums
01d7ad7cbe8c250a9fe1a50d3b1146dff4367da7bc7d2fa54bb3a582e7f7a39d
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.6.0-py3-none-manylinux_2_17_aarch64.whl

Download URL git_orchard-1.6.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
c9f6873d18beab6fd7e82a60bc3a3a5595adacfe002fac67ad86f563c3332010
BLAKE2b-256 checksum
How to use checksums
fa276d3c4fb37d31fe65a98f2826ea04e15e24191bc0c47ea747c89e46b29a40
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.6.0-py3-none-macosx_11_0_x86_64.whl

Download URL git_orchard-1.6.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
8c1ff1dde19439b29c3f94762cdca7f605ab335885c984505192df99373e17a7
BLAKE2b-256 checksum
How to use checksums
9ecf7ed1c6fee5769fb8f38461d6427076f300aff23d6a0f671a7771bb7fd419
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.6.0-py3-none-macosx_11_0_arm64.whl

Download URL git_orchard-1.6.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
dd8c9e87bb866173e2e2ac4262ef6547e5fd8695aad58baca4e6e88f5b44a9ea
BLAKE2b-256 checksum
How to use checksums
62f247d7f36eccb1d6c1b94c175c8042397264550ab89810baaf15837045f899
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.6.0-py3-none-any.whl

Download URL git_orchard-1.6.0-py3-none-any.whl
Size 4.5 MB
Tags Python 3
SHA-256 checksum
How to use checksums
c4f1b90377f9386b00fbef2003375f7030fb1ca68890de87c6652caf23472050
BLAKE2b-256 checksum
How to use checksums
ec81e5833f4c9639a73a5b5ac131fbe59942b8a57571e1fee288e41a93abed1a
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.6.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