fix(clone): clones that were empty, cut off, or still ending are handled safely by the next sync #175

Merged
james.maes merged 5 commits from fix/174-interrupted-clones into main 2026-11-11 05:23:31 +00:00
5 Commits
Author SHA1 Message Date
james.maesandClaude Fable 5.2 5124ed3a59 fix(clone): fourth review of #175: finishing a checkout removes nothing that was staged and writes through no link
The fourth review looked only at the changes of the third and found two ways,
both opened by those changes, in which finishing an unfinished checkout cost
the user something, and a few smaller things.

- A file somebody added to the index with git, and that the commit does not
  have, was removed by the forced checkout. The completion is now refused,
  names the file, and says how to go on ('git reset', or move the folder).
- Where the repository has a symbolic link, the folders on the way to it were
  no longer checked for links, so a link behind a linked folder could be
  replaced outside the clone. The way is checked again, and a link in the
  folder is the checkout's own only when it leads where the repository's does
  (also on a first checkout, where any link was taken before).
- Whether a marked folder holds its whole tree is asked of the files, not of
  the index: what the index says is whoever's git wrote it.
- A path recovery of an unfinished checkout whose branch had to move accepts
  the files of the commit it left (whole content), and takes the list of those
  commits off when it has written the tree.
- Tests for each, for a list that holds what is not a commit, for the list's
  cap and its entries being recorded once, for a file written before the
  attributes that change it, and for a link to a folder in a finished tree.
  The test of a rewritten history now writes the file it makes a statement
  about, and states what happens to it: it stays, untracked.
- README and threat model say what the code does, and name what is not
  covered: a marked folder whose index somebody's git wrote and whose last
  file was cut off is taken for finished; files of a commit the branch left
  stay in the folder.
- The tests that run the WSL clone script use the shell Git for Windows'
  launcher starts, not the launcher: it puts git's folders in front of the
  path, so the stand-in for git was never found when the tests were started
  from PowerShell (as CI starts them), and two of them failed there.

Refs #174

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
Signed-off-by: James Maes <james@kof22.com>
2026-11-10 23:22:43 -06:00
james.maesandClaude Fable 5.2 41f3712a8a fix(clone): third review of #175: the cases in which an unfinished clone was refused where it used to be completed
The third review confirmed, under real process kills, that a checkout that
was cut off leaves no index, and found no way left to replace or delete a
user's file. It found five narrow cases in which gclo refused, failed or said
done where it used to complete the tree.

- When the branch of an unfinished checkout must move before it is written
  (a tree Windows cannot write, or a history origin rewrote), the commit it
  leaves is recorded, and its files on disk are recognised by it as well.
  A history origin rewrote is followed again, as on main.
- A file is the unfinished checkout's own when it holds what the commit
  stores or what the checkout filters make of it (the 'ident' attribute).
- Where the repository has a symbolic link, a link in the folder is the
  checkout's own.
- A marked folder is taken for finished only when its index and its files
  hold the whole tree of its commit: an index somebody's git wrote over an
  unfinished tree does not make it whole.
- A link where the repository has a submodule is refused; a link is something
  with a target, not anything with a reparse point.
- Tests for each, for a file stored with carriage returns, for a completion
  that fails half-way, and for the order of what following origin's default
  branch writes. The message of an ordinary update that would replace files
  no longer says who changed them.

Refs #174

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
Signed-off-by: James Maes <james@kof22.com>
2026-11-10 22:30:40 -06:00
james.maesandClaude Fable 5.2 099c2b5ee8 fix(clone): second review of #175: an unfinished checkout is recognised by its missing index and completed where it stands
The second review showed that telling gclo's files from a user's by exact
content does not hold: with line endings being converted, the completion
refused files its own cut-off checkout had written, and a retry interrupted
after the branch had moved refused them for ever. It also found a user's file
deleted where a submodule's parent folder is needed.

- A marked folder is an unfinished checkout only while it has no index (git
  writes the index when a checkout has reached its end). A marked folder with
  an index is updated like any clone, without force.
- An unfinished checkout is completed at the commit its branch is on, before
  the branch moves; what origin has got since comes with the ordinary pull.
  When that commit or origin's cannot be written on Windows, the typed failure
  and the marker are kept as before.
- A file is the unfinished checkout's own when it holds the commit's content
  or the beginning of it, carriage returns aside.
- Refused as well: a file where a submodule's parent folder is needed, a link
  at a destination, a folder there that is not empty.
- Only a repository without branches of its own is an empty clone, and it
  follows origin's default branch only when its folder holds nothing but .git.
- An ordinary update that would replace files changed or added here says so.
- The refusals are Windows' rules and apply on Windows.
- WSL: 'mv -T'; the temporary folder's name is refused as an organization.
  The run tests find Git for Windows' shell beside git.exe and say when they
  had nothing to run.

Refs #174

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
Signed-off-by: James Maes <james@kof22.com>
2026-11-10 21:24:25 -06:00
james.maesandClaude Fable 5.2 9c53d985e2 fix(clone): review of #175: nothing that is not gclo's is written over, by the first checkout or by its retry
The first review reproduced two ways in which this change itself replaced a
user's files, and several holes in what it claimed.

- A repository that has local branches is left alone when its HEAD names a
  branch without commits: that is a branch somebody started, not an empty
  clone. Origin's default branch is followed only by a repository that has no
  branch of its own, and nothing changes before every check has passed.
- The first checkout and the completion of a cut-off checkout go through the
  refusals of the path recovery before anything changes: a link on the way, a
  file where a folder is needed, a folder with files where a file is needed,
  a file that is not what the unfinished checkout wrote, and anything but an
  empty folder where the repository has a submodule. The file a cut-off
  checkout was writing counts as gclo's and is finished.
- A branch's upstream is configured before the branch exists, and a marker
  without a branch starts the first checkout over instead of reporting an
  empty folder as done.
- The WSL clone uses a temporary folder of gclo's own, removes it on signals
  too, does not move into a destination that appeared meanwhile, accepts an
  empty folder at the destination, and removes old leftovers only from its own
  folder. The script is now also run for real by the tests.
- One operation per folder: the long-path spelling of a folder is the same
  folder, and the message fits every holder.

Refs #174

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
Signed-off-by: James Maes <james@kof22.com>
2026-11-10 20:40:44 -06:00
james.maesandClaude Fable 5.2 ddd93e36fe fix(clone): clones that were empty, cut off, or still ending are handled safely by the next sync
- The first checkout of a clone that was empty refuses, before anything
  changes, when something is already where the repository has a file or needs
  a folder: a forced checkout would have replaced it.
- The folder is marked unfinished before its branch is created, so a checkout
  that is cut off is completed by the next sync instead of being taken for up
  to date.
- An empty clone follows origin's default branch when origin does not have the
  branch the local HEAD names.
- One git operation per folder at a time in a process. A canceled sync stops
  waiting for its operations without stopping them; a second operation in the
  same folder is refused until the first has ended.
- A clone without .git/index says so and how to rebuild it, instead of git's
  count of conflicts.
- Clone in WSL clones under a temporary name and moves the folder into place
  when it is complete; leftovers of killed runs are removed once a day old.

Closes #174

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