- Add v1.1.0 and v1.2.0 changelog entries - Add exclude field to config reference and example config - Add ADRs documenting all major design decisions - Fix step numbering in reverse_sync() - Fix action.yml to copy VERSION file - Add dist/ and .env to .gitignore - Use refs/tags/ format for Nix flake tag refs Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2.2 KiB
ADR-009: Tree Comparison as Sync Skip Guard
Status: Accepted Date: 2026-02
Context
Both forward and reverse sync need to detect "nothing to do" quickly. The primary mechanism is SHA comparison against stored state (last-synced SHA). However, this misses cases where:
- State is reset or lost
- Reconciliation merges change SHAs without changing content
- Multiple sync runs overlap
Additionally, reverse sync originally relied on git log <base>..HEAD to find new commits. After a reconciliation merge, the .. range can leak old subrepo history through the merge's second parent, creating false positives.
Decision
Add tree-level comparison as an early skip guard in both forward and reverse sync. Compare the git tree objects (which represent directory content, not commit history) to determine if there's actually any content difference.
Forward sync
mono_tree=$(git rev-parse 'HEAD^{tree}')
subrepo_tree=$(git rev-parse "subrepo/${branch}^{tree}")
[ "$mono_tree" = "$subrepo_tree" ] && echo "skip"
Reverse sync
subrepo_tree=$(git rev-parse "HEAD^{tree}")
josh_tree=$(git rev-parse "mono-filtered/${branch}^{tree}")
[ "$subrepo_tree" = "$josh_tree" ] && echo "skip"
Tree comparison happens before commit log analysis. If trees are identical, there is definitionally nothing to sync, regardless of what the commit history looks like.
Combined with --ancestry-path
For reverse sync, even when trees differ, git log --ancestry-path restricts the commit range to the direct lineage between the two endpoints. This prevents old history from leaking through reconciliation merge parents.
Consequences
Positive:
- Eliminates false positives from reconciliation merges (trees are identical after reconciliation)
- Fast — tree SHA comparison is O(1), no content traversal
- Correct by definition — if trees match, content is identical
- Defense in depth — works even when state tracking has gaps
Negative:
- Tree comparison alone doesn't tell you which commits are new (still need
git logfor PR descriptions) - Adds an extra
git rev-parsecall per sync direction (negligible cost) - Cannot detect file-mode-only changes if josh normalizes modes (theoretical edge case)