flowchart LR
T[tree] --> Q{can this path land?}
Q -->|yes| P[commit + push]
Q -->|conflict / too big / submodule / hook reject| H[hold back]
P --> R[keep going]
H --> R
R --> F{anything still blocked?}
F -->|held some| D[remote has the rest]
F -->|nothing landed| K[park bak/pending-merge]
-
one conflicted, oversized, or submodule path parks the whole checkpoint
-
the rest of the tree sits unbacked on that machine
-
drip: push the clean subset; hold the rest; never stall the backup
-
laptop + desktop both edit
-
N local commits replay against remote -> same conflict N times
-
other machine's work never left that machine
-
agents touch many repos per session
-
add+commit+pushper repo burns time -
one blocker used to abort the whole run
-
pre-commit rejects one path (context-pollution, tilde, policy)
-
rest of the staged tree was fine
-
drip: eliminate the offender, land the rest, hold with a specific reason
-
two hosts append different
.ndjson/.jsonllines -
git sees one slot, two lines -> conflict
-
nothing was edited; both lines should keep
Separate bins. Same package. Plain gitty != gittysnap.
# scoped package; same bin names
npm i -g @vd7/gittyOptional resolvers (missing file -> empty allowlist -> drip/park, no auto-resolve):
per_host:
paths:
- registry/system-resource/manifest.json
globs:
- snapshots/$(hostname)/**
semantic_union:
globs:
- "**/*.ndjson"
- "**/*.jsonl"
key_field: ts| path | do this | get this |
|---|---|---|
| A | gitty |
drip + holdback checkpoint on remote |
| B | gittysnap |
snapshot-first solo sync |
| C | gittyunion install |
NDJSON union merge driver |
| D | gitty-dispatch |
config-driven Git operation routing |
# land whatever can land; hold blockers; park only if nothing can
gitty "checkpoint" "$HOME/Documents/a/gitty"
gitty -vGITTY_PARTIAL=1 default. Park-only: GITTY_PARTIAL=0. Bulldoze: GITTY_FORCE=1.
Integration preserves configured Git clean/smudge filters. If Git's internal merge snapshot fails on a filtered worktree, gitty computes the clean merge tree from Git objects, runs merge hooks against that tree, and restores only incoming paths after proving they do not overlap local worktree changes.
Holdback reasons:
-
optional
.gitty/holdback-reasons.jsonorGITTY_HOLDBACK_CATALOG -
maps hook stderr → stable
[CODE] reason -
missing catalog → offender stderr (never mute constant)
Stale remote tip: autoheal fetch+rebase unless GITTY_NO_STALE_BASE_HEAL=1.
# both hosts on remote first; rc=2 on conflict; worktree clean
gittysnap# union + dedupe + stable-sort for .ndjson / .jsonl
gittyunion installgitty-dispatch --config /absolute/path/dispatch.json --require-match -- push origin mainConfiguration supplies policy; the binary supplies exact dispatch:
{
"dispatch": {"version": 1, "real_git": "/usr/bin/git"},
"guarded_repos": [{
"match": {"remote_urls": ["https://example.invalid/team/repo.git"]},
"dispatch": {
"mode": "redirect",
"intercept_ops": ["push"],
"passthrough_ops": ["status", "log", "diff"],
"driver_argv": ["/absolute/path/sync-driver", "{repo_root}", "{operation}"],
"authorized_ancestor": "/absolute/path/sync-driver"
}
}]
}The driver runs when an intercepted operation has no exact canonical driver in
its process ancestry. Descendant Git calls pass through. Environment markers,
substrings, basenames, and shell evaluation never authorize a request. Rules,
driver paths, and intercepted operations remain consumer-owned configuration.
Driver paths may be absolute or start with {config_dir}/; both are
canonicalized before comparison. Use the standalone {git_args} element when a
driver needs the original Git argument vector. Git, Zsh, and jq are required.
Linux supports whitespace in driver paths; macOS ancestry verification fails
closed for such paths because ps does not expose exact argument boundaries.
Matched repositories are default-deny: every literal Git operation belongs in
either intercept_ops or passthrough_ops. Configuration and configured
executables are trusted inputs; process ancestry prevents accidental or agentic
route-around, not attacks by another process already running as the same user.
| signal | meaning |
|---|---|
| partial integrate | resolved subset committed; held paths stay local |
| holdback | oversized / submodule / push-reject / hook-reject left unstaged |
[CODE] reason |
catalog match for pre-commit reject (else offender stderr line) |
| elimination scan | hook blocked commit; drip retries without each staged path |
| park | nothing could land; bak/pending-merge-* + remote-snapshot-* |
| merge error | original Git diagnostic; exit 2; no conflict parking |
Local branch stays put on park. Nothing lost.
Other merge or merge-commit failures stop with exit 2 rather than masquerading as parked conflicts. Any in-progress merge is left in place for manual recovery.
| problem | fix | stability | why |
|---|---|---|---|
| one unresolvable file parks whole merge | drip (GITTY_PARTIAL=1): commit resolved subset |
stable | backup as much as possible |
| oversized path blocks push | leave GITTY_PARTIAL=1 |
stable | hold back; push the rest |
| pre-commit rejects one staged path | leave GITTY_PARTIAL=1; elimination drip holds that path |
stable | clean subset still lands |
mute pre-commit hook rejection reason |
.gitty/holdback-reasons.json or GITTY_HOLDBACK_CATALOG |
stable | stable [CODE] + fix hint |
| remote ahead / stale-base guard | leave autoheal on; GITTY_NO_STALE_BASE_HEAL=1 to skip |
stable | fetch+rebase then retry |
| N-commit rebase replays same conflict N times | merge --ff-only then --no-ff once |
stable | one merge, one conflict set |
| per-host artifact conflicts every sync | .gitty/additive-resolvers.yaml per_host |
stable | ours on that host; --no-ff keeps both parents |
.ndjson append conflict |
semantic_union or gittyunion install |
stable | union + dedupe + stable-sort |
| fresh clone drops union driver | re-run gittyunion install |
stable | attrs alone insufficient |
| nothing can land | park bak/pending-merge-* |
stable | first-class ref; nothing lost; non-fatal |
foreign-owned .git (old sudo git) |
sudo chown -R "$(whoami)" .git |
stable | fail-fast; GITTY_SKIP_PERM_CHECK=1 opt out |
npm run test:integrate (also included in npm test) requires Git, zsh, and
git-crypt. It exercises integration using disposable local repositories and
local remotes only.
| Doc | Topic |
|---|---|
docs/README |
index |
docs/commands |
all bins |
docs/gittysnap |
snapshot-first sync |
docs/gittyunion |
NDJSON union driver |
docs/partial-commit |
holdback + drip |
docs/environment |
env |
docs/hooks |
repo hooks |
npm tarball = bin/ + README only. docs/ is GitHub.
