git-orchard
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.4.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Built distributions (wheels)
Total release size: 30.1 MB
Release files / git_orchard-1.4.0-py3-none-win_arm64.whl
| Download URL | git_orchard-1.4.0-py3-none-win_arm64.whl |
|---|---|
| Size | 4.1 MB |
| Tags | Python 3 Windows ARM64 |
|
SHA-256 checksum How to use checksums |
d61299963ecaebd97cb9e8e72af380dfd147c53c68c198b1d7d632ab989cc95e
|
|
BLAKE2b-256 checksum How to use checksums |
7e4c0827f578dc069f8ac34486e22d990162e5baa062324e25ed6f5506bd0186
|
| 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":"24.04","id":"noble","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.4.0-py3-none-win_amd64.whl
| Download URL | git_orchard-1.4.0-py3-none-win_amd64.whl |
|---|---|
| Size | 4.6 MB |
| Tags | Python 3 Windows x86-64 |
|
SHA-256 checksum How to use checksums |
911739557327584b6a050d552c8bf58ee9bf294000649619848bcf3775cece2e
|
|
BLAKE2b-256 checksum How to use checksums |
d1d6c6b0c070aeae88847eafd8db115ac0919384fbc9bc8b4e905ecfb0a432cb
|
| 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":"24.04","id":"noble","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.4.0-py3-none-manylinux_2_17_x86_64.whl
| Download URL | git_orchard-1.4.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 |
ac5d62b79c173ea75d7db71b818a4cafbfe3f80ab48df229f6f4ca5bd553bb7f
|
|
BLAKE2b-256 checksum How to use checksums |
590f024e1768a4923b75a0c8b700675e510dcc647f62700bd48bb4581ece675a
|
| 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":"24.04","id":"noble","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.4.0-py3-none-manylinux_2_17_aarch64.whl
| Download URL | git_orchard-1.4.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 |
c8b30afc60ca2aab94b1a63516f4fe2663d1e42adb57d36e295ddff8c9d6165c
|
|
BLAKE2b-256 checksum How to use checksums |
70ed036d71663724c1d4656d15e83eaa3764224c986bfec507111f29167462a2
|
| 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":"24.04","id":"noble","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.4.0-py3-none-macosx_11_0_x86_64.whl
| Download URL | git_orchard-1.4.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 |
5d2adb1e0ce5d52f96f413955a0e6d570c04d1d543a621e0c03bc31dae0d1831
|
|
BLAKE2b-256 checksum How to use checksums |
1f7e2423f713ea130ac57af2192db6fea547c96522239907c379639e0fd3e472
|
| 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":"24.04","id":"noble","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.4.0-py3-none-macosx_11_0_arm64.whl
| Download URL | git_orchard-1.4.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 |
c36b7aabcb3008f63a1a23f84b889f742bdfcedb72479339acd16d94d7c2adb7
|
|
BLAKE2b-256 checksum How to use checksums |
05c8c7fd4c1c266d0b893cf52767e0429d645c1a5b7a0f1cdd8207927e0a5ad1
|
| 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":"24.04","id":"noble","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.4.0-py3-none-any.whl
| Download URL | git_orchard-1.4.0-py3-none-any.whl |
|---|---|
| Size | 4.4 MB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
e76fb1b409536ee586eb8c1a7a37a4da3190d6f1d116192faff4e1f60325c78d
|
|
BLAKE2b-256 checksum How to use checksums |
ab405693c32b4ec3978b45123d9f942c62e6bdb7f4d2dc4c238ae1f8b60d5b2a
|
| 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":"24.04","id":"noble","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.4.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.