Skip to content

Sync deletions, but only of files which came from the source. - #5

Merged
SgtSwagrid merged 1 commit into
mainfrom
sync-deletions
Sep 27, 2026
Merged

SgtSwagrid merged 1 commit into
mainfrom
sync-deletions

Conversation

@SgtSwagrid

Copy link
Copy Markdown
Owner

Sync deletions, but only of files which came from the source.

Files deleted from a source were never deleted downstream (e.g. claude-code-review.yml, which had to be removed from this repo by hand in b042a53).

Why it never worked

The sync workflow checks out the source with actions/checkout's default fetch-depth: 1. In a single-commit shallow clone, git log --diff-filter=D sees nothing, so the deletion step always found zero files. Had it found any, it would have deleted any target file with a matching name, whether or not it came from the source.

What's changed

  • Both repositories are checked out with full history.
  • A file deleted from the source is only deleted from the target if all of the following hold:
    1. It originated from the source. When it was last added to the target, it matched a version the source once had. This protects files the target had before a sync overwrote them.
    2. It wasn't re-added after the deletion. It was last added to the target before it was deleted from the source. This protects files restored downstream, even with identical content.
    3. It wasn't modified downstream. Its current content matches a version the source once had.
  • Each file kept for one of these reasons is logged with the reason, e.g. Kept: F (re-added after being deleted from the source).
  • Timestamps come from --first-parent history, so they reflect when a change landed on each branch rather than when it was committed on a feature branch.
  • Renames are treated as delete + add (--no-renames). Previously a rename showed up as R and was missed even with full history.
  • New syncDeletions option, on by default. Per-child or global, and an explicit false is respected (plain //= would have overwritten it).
  • If either checkout is shallow, deletions are skipped with a workflow warning, never guessed.

Testing

Scenarios: 46 checks across 21 synthetic source/target histories, including: plain deletion, an unrelated same-name file, identical and different re-adds, downstream modification, a target-original file overwritten by sync, a graph restructured through a new intermediate repo, ignored files, source.root/target.root mapping, renames, deletion via merge commit, sync via merge commit, delete/re-add cycles, stale versions, glob and unicode filenames, a directory at the path, and a shallow clone. The previous script fails 20 of them; dropping --first-parent fails 2.

Real graph (dry run, nothing pushed): replayed every edge reachable from github-config against current mains:

Edge Result
github-config → github-graph @ #4 deletes claude-code-review.yml (matches b042a53)
scala-library-config → 7 libraries, template, 3× Themis deletes linter.yml (deleted upstream in scala-config#38)
scala-website-config → template, fairmap, Themis, Kairos deletes linter.yml and the now-unreferenced deploy/Caddyfile
Themis → Iris deletes ModelTier.scala (moved out in Themis#138)
everything else no change

parse-config.sh wasn't executed locally (no jq available); the change there is a single if . == null default.

Note

Merging enables this for every caller of sync.yml@main. The next sync from each source above will open or refresh PRs containing the deletions listed.

🤖 Generated with Claude Code

Deletions were never propagated, as the sync workflow checked out the
source with a depth of 1, so `git log --diff-filter=D` could never see a
deletion. Had it done so, it would also have deleted any file in the
target that merely shared a name with one deleted from the source.

Both repositories are now checked out with full history, and a file is
only deleted from the target if:
- It originated from the source: when it was last added to the target,
  it matched a version which the source once had.
- It hasn't been re-added since: it was last added to the target before
  it was deleted from the source.
- It hasn't been modified downstream: it currently matches a version
  which the source once had.

Timestamps come from first-parent history, so they reflect when a change
landed on each branch rather than when it was first committed. Renames
are treated as a deletion plus an addition.

This is enabled by default, and can be disabled with `syncDeletions`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@SgtSwagrid
SgtSwagrid merged commit 93b88dc into main Sep 27, 2026
3 checks passed
@SgtSwagrid
SgtSwagrid deleted the sync-deletions branch September 27, 2026 19:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant