Skip to content

Carry synced files onto an existing sync branch that holds another copy [patch] - #349

Open
matt-edmondson wants to merge 3 commits into
mainfrom
fix/203-sync-branch-diverged-file
Open

matt-edmondson wants to merge 3 commits into
mainfrom
fix/203-sync-branch-diverged-file

Conversation

@matt-edmondson

Copy link
Copy Markdown
Contributor

Fixes #203

What was wrong

sync copies the chosen file into each repository's working tree first, and only then switches to the sync branch. When --branch X names a branch that already exists and holds a different copy of that file, a plain git checkout X refuses with "Your local changes … would be overwritten by checkout", even if the new content matches the branch's copy. This happens on every re-run while a sync PR is still open. SwitchReposToBranchAsync caught the error and skipped the repo, which left the copied file modified on the user's original branch (usually main) with nothing committed.

Change

  • SyncGit.SwitchToSyncBranchAsync now takes the files sync wrote into that repository. If the plain checkout fails, it checks git status. When the only local changes are those files, it saves their bytes, runs checkout --force, and writes them back. The normal commit step then commits them on the sync branch.
  • If anything else is dirty, the original refusal stands and the repo is left exactly as it was. That covers the user's own edits to other files and untracked files the branch might track. A forced checkout would discard those, so it is never used then.
  • SwitchReposToBranchAsync passes each repository its own files.

I chose this over "switch before copying", which the triage suggested. That would have meant restructuring the interactive scan-and-copy flow in RunAsync, because copying happens per file long before commit selection. This change stays inside the branch switch.

Deviation from the acceptance criteria: when a switch still fails for an unrelated reason (the user's own edits block it), the copied file is kept on the original branch, as before, and is not discarded. Throwing it away there could destroy work, since #208 shows these paths can hold the user's own edits.

Tests

  • CommitFilesLeavesARepositoryUncommittedWhenItsCheckoutConflicts pinned the exact behaviour this issue reports as a bug. It is replaced by CommitFilesReusesASyncBranchThatHoldsADifferentCopyOfTheFile, with two cases: new content that differs from the branch's copy, and new content identical to it. Each case checks that the content lands on sync/shared, that the original branch tip doesn't move, and that the original branch is left with the file unmodified and git status clean.
  • New guard CommitFilesLeavesARepositoryUncommittedWhenTheUsersOwnEditsBlockTheCheckout: an unrelated user edit that collides with the branch still blocks the switch, and both the edit and the copied file survive.

With KtsuTools.Sync/ reverted, both reuse cases fail. The guard passes with or without the fix. Full suite with the fix: 387/387.

Builds were run with -p:WarningsNotAsErrors=NU1903, because main currently fails restore on the SixLabors.ImageSharp advisory (#270).


Generated by Claude Code

…py [patch]

sync copies files into each repository before switching to the sync
branch. When the branch already exists and holds a different copy of a
synced file, as on every re-run while a sync PR is open, a plain
checkout refuses ("would be overwritten by checkout"). The repo was
skipped, and the copied file stayed modified on the user's branch.

When the plain checkout fails and the only local changes are the files
sync wrote into that repo, save their content, force the checkout, and
write the content back so it is committed on the branch. Any other
local change, such as the user's own edits or untracked files, keeps the
refusal, so nothing of theirs can be discarded.

The test that pinned the old skip is replaced by one for the reuse case,
plus a guard that an unrelated user edit still blocks the switch.

Fixes #203

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017vfJW48r2AEHXkyMC1wF3i
Comment thread KtsuTools.Sync/SyncService.cs Fixed
…s repo once [patch]

main fails restore on NU1903 (GHSA-v76p-62qx-wwq2) against 3.1.12, so no
PR can build. This ports the bump from #270. SwitchReposToBranchAsync now
looks up each file's repository once and filters with Where.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017vfJW48r2AEHXkyMC1wF3i

matt-edmondson commented Nov 2, 2026 •

Copy link
Copy Markdown
Contributor Author

CI: all three Test on * jobs fail at restore with NU1903 (SixLabors.ImageSharp 3.1.12, GHSA-v76p-62qx-wwq2). This isn't caused by this change: main fails the same way.

No fix can be ported here yet. The 3.2.0 bump from #270 fails the build in CI without a Six Labors license key, and no patched 3.1.x exists, so I reverted it. Details and the three options for a maintainer are in #348 (comment). Once one of them lands on main, I'll merge it into this branch. Locally, with NU1903 not treated as an error, the full suite passes 387/387.


Generated by Claude Code

3.2.0 fails the build in CI without a Six Labors license key, so the bump
is not a usable fix for NU1903. That decision belongs to the maintainer.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017vfJW48r2AEHXkyMC1wF3i
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.

sync --branch skips a repo whose existing sync branch has a different copy of the file, leaving it uncommitted on the current branch

2 participants