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
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.
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.
git-orchard only runs git, so credentials, SSH config and url.<base>.insteadOf rewrites apply as usual.
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.2.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Built distributions (wheels)
| File | Reset | |||
|---|---|---|---|---|
| git_orchard-1.2.0-py3-none-win_arm64.whl | Python 3 | none | Windows ARM64 | Details |
| git_orchard-1.2.0-py3-none-win_amd64.whl | Python 3 | none | Windows x86-64 | Details |
| git_orchard-1.2.0-py3-none-manylinux_2_17_x86_64.whl | Python 3 | none | Linux glibc 2.17+ x86-64 | Details |
| git_orchard-1.2.0-py3-none-manylinux_2_17_aarch64.whl | Python 3 | none | Linux glibc 2.17+ ARM64 | Details |
| git_orchard-1.2.0-py3-none-macosx_11_0_x86_64.whl | Python 3 | none | macOS 11.0+ x86-64 | Details |
| git_orchard-1.2.0-py3-none-macosx_11_0_arm64.whl | Python 3 | none | macOS 11.0+ ARM64 | Details |
| git_orchard-1.2.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 29.4 MB
Release files / git_orchard-1.2.0-py3-none-win_arm64.whl
| Download URL | git_orchard-1.2.0-py3-none-win_arm64.whl |
|---|---|
| Size | 4.0 MB |
| Tags | Python 3 Windows ARM64 |
|
SHA-256 checksum How to use checksums |
1b561bb672fbf6ec2c58ab74b19539111d284764053e8b6da0b5a0e07a9766cf
|
|
BLAKE2b-256 checksum How to use checksums |
cf0cd2009e638bb912b51992b03650e723d1bbcd54e7581ba69d2d97f295fd70
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.12.19 {"installer":{"name":"uv","version":"0.12.19","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.2.0-py3-none-win_amd64.whl
| Download URL | git_orchard-1.2.0-py3-none-win_amd64.whl |
|---|---|
| Size | 4.4 MB |
| Tags | Python 3 Windows x86-64 |
|
SHA-256 checksum How to use checksums |
79be8851ded35d90bc73c1a12f3f589ac0b0a539e063e6eb20259c14872c1fdf
|
|
BLAKE2b-256 checksum How to use checksums |
9c5d2cdaea6214b392e316998e4d199cc162c32091263cdbc7524731bcfbbf06
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.12.19 {"installer":{"name":"uv","version":"0.12.19","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.2.0-py3-none-manylinux_2_17_x86_64.whl
| Download URL | git_orchard-1.2.0-py3-none-manylinux_2_17_x86_64.whl |
|---|---|
| Size | 4.3 MB |
| Tags | Linux glibc 2.17+ x86-64 Python 3 |
|
SHA-256 checksum How to use checksums |
1efaf13ce938616a95489d35d8a539ebdf551706396bca039e2fafa01babc885
|
|
BLAKE2b-256 checksum How to use checksums |
dc60d722d209d1ee5ba10bfaeb4671fef6d6ae6c4738e62294d76c89e285c586
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.12.19 {"installer":{"name":"uv","version":"0.12.19","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.2.0-py3-none-manylinux_2_17_aarch64.whl
| Download URL | git_orchard-1.2.0-py3-none-manylinux_2_17_aarch64.whl |
|---|---|
| Size | 3.9 MB |
| Tags | Linux glibc 2.17+ ARM64 Python 3 |
|
SHA-256 checksum How to use checksums |
2489533e9a4e3b6f31489e91924cebc7d7723852224de0f5f18dd1b6e868eb7a
|
|
BLAKE2b-256 checksum How to use checksums |
97154ec9bae9265257b268c26b655f93baba0dfbdd31b356296a7617ed7dc3bb
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.12.19 {"installer":{"name":"uv","version":"0.12.19","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.2.0-py3-none-macosx_11_0_x86_64.whl
| Download URL | git_orchard-1.2.0-py3-none-macosx_11_0_x86_64.whl |
|---|---|
| Size | 4.4 MB |
| Tags | Python 3 macOS 11.0+ x86-64 |
|
SHA-256 checksum How to use checksums |
8d1a46f678aadf0315c9e1d91cd0f4911630b1e8b172eeaa16255b9fb44c8001
|
|
BLAKE2b-256 checksum How to use checksums |
a15d95cc311e05ddf1dc6a79dc5d9ba3ab8d25a29edc36bd57f92da51c807f77
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.12.19 {"installer":{"name":"uv","version":"0.12.19","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.2.0-py3-none-macosx_11_0_arm64.whl
| Download URL | git_orchard-1.2.0-py3-none-macosx_11_0_arm64.whl |
|---|---|
| Size | 4.0 MB |
| Tags | Python 3 macOS 11.0+ ARM64 |
|
SHA-256 checksum How to use checksums |
767392be10acc021c1aeedc2a9260d1111f6740564422f6423a633ecbcab4909
|
|
BLAKE2b-256 checksum How to use checksums |
5aefdae0a12893bb25f851d48b476721205ca6b915e1e0618ec02317da0a738f
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.12.19 {"installer":{"name":"uv","version":"0.12.19","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.2.0-py3-none-any.whl
| Download URL | git_orchard-1.2.0-py3-none-any.whl |
|---|---|
| Size | 4.3 MB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
a7eb4e2b60655c04439835cb9b085f38f5c509d4f5019d2a4c14831eab7062cb
|
|
BLAKE2b-256 checksum How to use checksums |
25a076dc13dced260e7c1bf3b39abcaba12b6daa2fc956fbbdc81a3a5e818f03
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.12.19 {"installer":{"name":"uv","version":"0.12.19","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}
|