Package Exports
- git-auto-remote
- git-auto-remote/dist/cli.js
This package does not declare an exports field, so the exports above have been automatically detected and optimized by JSPM instead. If any package subpath is missing, it is recommended to post an issue to the original package (git-auto-remote) to support the "exports" field. If that is not possible, create a JSPM override to customize the exports field for this package.
Readme
git-auto-remote
Git tooling for repositories that track multiple upstreams with disjoint histories — for example a public/private fork split, or a monorepo that mirrors a subset of itself to an open-source repo.
Two features in one CLI, both invoked via git hooks installed by git-auto-remote setup:
- Auto-routing — on branch checkout, detect which remote's root commit(s) the branch descends from and set
branch.<name>.pushRemoteaccordingly. Pre-push hook refuses cross-history pushes. - Mirror sync — on
git pull, cherry-pick new commits from a configured mirror remote (restricted to an allowlist of paths). Empty commits and round-tripped changes drop automatically viagit am --empty=drop.
Install
bunx git-auto-remote setupHooks are idempotent and chainable: if another tool has already installed a hook, git-auto-remote appends its block marked with # >>> git-auto-remote <hook> >>> sentinels. Other tools can detect our presence by grepping for the string git-auto-remote <hook>.
Auto-routing (always on after setup)
Given a repo with multiple remotes that have disjoint root commits (e.g. public and private), checking out a new branch sets the appropriate pushRemote:
git switch public
git checkout -b feat/open-x # -> pushRemote = public
git switch private
git checkout -b feat/closed-y # -> pushRemote = privatePre-push verifies the push belongs to the target remote's history; cross-fork pushes are rejected (bypass with --no-verify if deliberate).
Decision rules:
| Situation | Action |
|---|---|
| No remotes configured | No-op |
| All remotes share the same root set | Inherit parent branch's pushRemote |
| Remotes have disjoint roots, branch matches exactly one | Route to that remote |
| Branch matches zero remotes | No-op (silent) |
| Branch matches two or more remotes | Refuse to auto-configure, warn |
Any manually configured branch.<name>.pushRemote is always respected.
Mirror sync
Opt-in per remote via git config. A remote becomes a "mirror" by setting syncPaths:
# In your private clone, treat the 'public' remote as a mirror of packages/:
git config fork-remote.public.syncPaths "packages"
git config fork-remote.public.syncTargetBranch "private"
git-auto-remote mirror bootstrap public <sha-whose-tree-matches-current-packages/>| Config key | Meaning | Default |
|---|---|---|
fork-remote.<name>.syncPaths |
Space-separated pathspecs to include when cherry-picking. Presence (either here or via syncPathsFile) makes the remote a mirror. |
required |
fork-remote.<name>.syncPathsFile |
Repo-relative path to a newline-separated file of sync paths. Supports # comments. Contents union with syncPaths. |
(none) |
fork-remote.<name>.excludePaths / .excludePathsFile |
Pathspecs that are never synced, even if under syncPaths. Useful for repo-local-only files that live in a shared directory. |
(none) |
fork-remote.<name>.reviewPaths / .reviewPathsFile |
Pathspecs (subset of syncPaths) whose changes always trigger a partial review pause, even when the rest of the commit is clean. |
(none) |
fork-remote.<name>.syncBranch |
Remote branch to pull from. | <remote>/HEAD, else main |
fork-remote.<name>.syncTargetBranch |
Local branch that receives replayed commits. | <remote> |
fork-remote.<name>.partialHandler |
Path to a script that resolves "partial" commits. | (none) |
fork-remote.<name>.pushSyncRef |
Push the tracking ref to the remote after each advance (for CI durability). | true |
Per-commit classification
Each mirror commit is classified by comparing its changed paths against the path config:
- Paths matching any
excludePathare dropped entirely — they do not count as included or excluded. - Remaining paths matching
syncPathsare included. - Remaining paths are excluded (outside the sync surface).
- Any included path matching a
reviewPathflags the commit as review-required.
| Situation | Classification | Action |
|---|---|---|
| 0 included paths | Out-of-scope | empty patch, dropped |
| all included, 0 excluded, 0 review-required | Clean | included in a batched git am run |
| otherwise (any excluded OR any review-required) | Partial | breaks the batch; applied alone; paused for review |
A batched run of clean + out-of-scope commits is applied via a single git format-patch ... | git am --empty=drop --3way.
Partial commit review (interactive)
When a partial is encountered, its in-scope changes are applied as a commit and the tool pauses:
[mirror public] Partial: feat: shared lib + private glue (abc1234)
Excluded paths: package.json, privpkgs/foo.ts
Review-required paths: tooling/workspace.gitconfig
Review: git show HEAD
Amend: git commit --amend (optionally include excluded content)
Continue: git-auto-remote mirror continue public
Skip: git-auto-remote mirror skip publicEither list is printed only if non-empty.
mirror continue <remote>— resume with whatever amendments you mademirror skip <remote>— drop the partial (resets HEAD~1) and resume past it
Non-interactive mode (CI)
mirror pull --non-interactive never pauses for a human:
| Situation | Behavior | Exit code |
|---|---|---|
| Up to date | no-op | 0 |
| Only clean commits | all applied | 0 |
| Partial encountered, no handler | nothing applied, ref unchanged (so CI surfaces it again) | 2 |
| Conflict in a clean commit | git am --abort, exit |
2 |
Scripted partial resolution (--on-partial)
Provide a command that decides what to do with partials. Useful for LLM-in-CI setups:
git-auto-remote mirror pull --non-interactive --on-partial ./ci/llm-amend.sh publicOr configure it permanently:
git config fork-remote.public.partialHandler /path/to/handler.shThe handler is invoked with the partial's subset already applied to HEAD. It may amend HEAD, leave it as-is, or signal skip/punt via exit code:
| Exit code | Meaning | Tool response |
|---|---|---|
| 0 | Resolved; HEAD is the answer | Continue sync |
| 2 | Skip this commit | git reset --hard HEAD~1; ref advances past; continue |
| any other | Punt (can't decide) | Interactive: pause for human. Non-interactive: rewind HEAD + ref, exit 2 |
Handler receives the following env vars:
MIRROR_REMOTE public
MIRROR_SOURCE_SHA abc1234...
MIRROR_SOURCE_SUBJECT feat: shared lib + private glue
MIRROR_INCLUDED_PATHS newline-separated
MIRROR_EXCLUDED_PATHS newline-separatedAnd positional args: <remote> <source-sha>. Full source diff available via git show $MIRROR_SOURCE_SHA.
Tracking-ref durability
The last-synced position per mirror is stored under refs/git-auto-remote/mirror/<remote>. With pushSyncRef=true (default), the ref is pushed to the remote after each advance — so CI clones (which start with a fresh .git/) pick up the state automatically. setup also adds a fetch refspec (+refs/git-auto-remote/mirror/*:...) to each mirror's remote config.
Commands
git-auto-remote setup [--quiet] Install hooks
git-auto-remote status Auto-routing status
git-auto-remote detect [ref] Ancestry analysis
git-auto-remote uninstall Remove hook blocks
git-auto-remote mirror list Show configured mirrors
git-auto-remote mirror status [<remote>] Show sync state
git-auto-remote mirror bootstrap <remote> <sha> [--force]
git-auto-remote mirror pull [<remote>] [--non-interactive] [--on-partial <cmd>]
git-auto-remote mirror continue [<remote>]
git-auto-remote mirror skip [<remote>]Bypassing auto-routing on push
If you really need to push across histories, bypass the pre-push safety net with git push --no-verify.
Requirements
Git ≥ 2.34 (for git am --empty=drop). setup checks the version and refuses if too old.