fix(recovery): keep every write and delete inside the working tree #142

Merged
james.maes merged 2 commits from fix/141-recovery-working-tree into main 2026-11-10 19:33:12 +00:00
2 Commits
Author SHA1 Message Date
james.maesandClaude Fable 5.2 a900c5d9dd fix(recovery): names Windows opens differently, real links only, safe cleanup
Follow-up to the independent review of this branch.

- A folder whose name ends in a space was checked under one name and opened
  under another, so a link behind it could still carry a delete out of the
  working directory. WorkingTree.Resolve now refuses any part that Windows
  does not open literally (empty, trailing dot or space, a colon), and the
  recovery file's stored paths must also pass the path validator.
- A link is recognized by having a target, not by the reparse-point
  attribute: folders under a cloud-sync root carry that attribute without
  being links, and recovery refused to write into them.
- Cleanup of a failed clone moved to WorkingTree.DeleteDirectory, which
  removes a link inside the folder as a link and never enters it; it is
  tested, where the previous change to that cleanup was not.
- The destination's old content is removed whatever it is: a file, a hard
  link, a file link, a folder link. Only the name in the working tree goes.
- The git-folder rule is the one git has: .git and GIT~1 (with trailing
  dots, spaces or a stream suffix). git~2 and up are ordinary names again.
  The rule now runs before the trailing-dot rule, whose suggestion for
  ".git." was ".git" itself; every suggestion re-validates clean.
- Shared parent folders are checked for links once per materialization.
- A bare repository with a recovery file gets a clear message.
- Tests remove the junctions they create, and a hard-link test replaces
  reliance on the symlink test that needs a privilege.

Refs #141

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
Signed-off-by: James Maes <james@kof22.com>
2026-11-10 13:32:32 -06:00
james.maesandClaude Fable 5.2 c4f2e7258a fix(recovery): keep every write and delete inside the working tree
Path recovery materializes the working tree itself instead of using a git
checkout, and it was less careful than one: the list of previously written
files in .git\gclo-recovery.json decided what to delete without being
checked, a path segment named .git was valid, writes followed links, and
cleanup of a failed clone followed directory links.

- New internal WorkingTree is the only way the recovery writes or deletes
  a file. The target must resolve inside the working directory, must not
  be in a .git folder (in any spelling Windows opens as that folder: letter
  case, the GIT~1 short name, trailing dots and spaces, a stream suffix),
  and must not be reached through a link. An existing file is removed and
  created anew, never written through.
- The recovery file's stored paths are validated when it is loaded. A file
  that lists a path outside the working tree fails the row with a message
  that says how to rebuild it; nothing is deleted.
- WindowsPathValidator treats .git and its aliases as an invalid segment,
  with a suggested replacement, so a tree entry or a rename can no longer
  land in git's own folder.
- Cleanup of a failed clone skips links when clearing attributes.

Threat model: T3 and T4 updated.

Closes #141

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
Signed-off-by: James Maes <james@kof22.com>
2026-11-10 13:02:54 -06:00