A repository without an index was taken for one path recovery wrote
whenever gclo had cloned it. An ordinary clone whose index was deleted and
that has no path to rename then went through the hand-written apply on a
pull, was marked as managed for good, and had later edits overwritten.
The pull now takes that route only when the current commit holds a path
that cannot be created here. Anything else takes the ordinary pull, which
leaves local changes alone and marks nothing.
Refs #147
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
Signed-off-by: James Maes <james@kof22.com>
- A branch ahead of what was written counts as 'commits made here' only
when origin has nothing beyond what was written. A pull of the released
build that failed after it moved the branch leaves the branch at
origin's commit and the tree behind it; that tree is now written, and a
new invalid path is reported again instead of 'nothing to do'.
- A tree is taken for one gclo wrote without an index only in a
repository gclo cloned (it carries gclo's checkout marker). A clone made
by git without a checkout is not.
- Where nothing records that gclo wrote the tree (never checked out,
empty, or recognised only by its shape), a file already at a destination
is replaced only when it is, byte for byte, what the current commit has
there. A file a person put there or changed stops the apply.
- The pull recognises a tree written without an index, so a repository of
the released build whose recovery file was lost reports its paths again
and can be resolved from the app and with --sanitize-paths.
- A pull does not check out over a branch that has no commits yet when
files are staged in the folder ('checkout --orphan' in a checked-out
clone).
- What counts as origin's commit is what a clone, fetch, or pull recorded;
any other entry on the remote-tracking branch does not.
- The checkout marker is read from the repository's own configuration
only. Messages: a stale index.lock, a branch that tracks a local branch.
Refs #147
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
Signed-off-by: James Maes <james@kof22.com>
- An empty repository counts as never checked out only while its index is
empty and no file sits where the repository has one; a branch started
with 'checkout --orphan' in a checked-out clone is refused like any
ordinary clone.
- What counts as origin's commit: reflog entries a push wrote do not; a
branch that tracks another local branch has no record of origin.
- A pull that has nothing to write writes nothing: commits made on top
with origin unchanged leave every other file alone; a branch moved back
by hand is brought to origin's tip again.
- A repository written by the released build (commits, no index) whose
recovery file was lost can be resolved again, as the message advises.
- A clone that was never checked out follows origin by the same rule as
everything else: a commit made in it by hand is not dropped.
- Found before anything changes: another git process at work, a folder
with someone's files where a file is needed, a link on the way to a file
that is to be removed. A file of gclo's own is recognised whatever its
case on a file system that ignores case.
- A file that left the tree, where a folder of the user's now sits, leaves
the folder; a refused folder removal has touched nothing in it.
- The managed marker is read from the repository's own configuration only;
a refused apply leaves the configuration as it was. Threat model updated.
- The CLI keeps the list of invalid paths on a repository whose
sanitization failed.
Refs #147
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
Signed-off-by: James Maes <james@kof22.com>
Two independent reviews of this branch found that rewriting a clone someone
checked out cannot be made safe by checks: untracked files, files git was
told to ignore changes in, an apply interrupted and edited since, and git's
own view of a renamed tree each defeated one attempt, and the checks added
for managed repositories stopped repositories managed by the released build
from updating.
So the rule is the simple one:
- A clone that was checked out normally is never rewritten. Resolve and
--sanitize-paths refuse it with a message that says how to get a fresh
clone with the renames; nothing in it changes. This replaces the behaviour
this branch set out to fix (the tree of the commit already checked out
was rewritten with force, and later pulls moved the branch off local
commits).
- A repository gclo manages, or one that was never checked out, keeps the
semantics it has in the release: gclo writes its tree, and the
documentation says to treat it as a read-only mirror.
With these fixes for managed repositories, each with a regression test:
- The branch never moves off a commit made in the repository. It follows a
rewritten origin only when the current commit is one origin's branch
itself once pointed at, shown by the remote-tracking branch's reflog; a
local commit that was synced once is not mistaken for that. Local commits
on top of origin are left alone instead of being moved back.
- The branch moves after validation, so an uncovered invalid path leaves it
where it was.
- What would fail half-way (an unusable destination, a link on the way, a
file where a folder is needed) is found before anything changes.
- An interrupted apply is completed by the next pull, through a recovery
file saved without a materialized tip before the first destructive step.
The pending-checkout marker is not used for a repository that has a tree.
- A config flag, set once a recovery has been read or written, keeps a
repository known as managed when its recovery file is deleted as the
message for a corrupt one advises. An unreadable file in an ordinary
clone sets nothing.
- The index is written, attribute files are materialized first, the newer
choice wins when recoveries merge, and an empty clone whose origin's
first commit has invalid paths can be resolved.
Refs #147
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
Signed-off-by: James Maes <james@kof22.com>
The independent review of this branch found that converting an ordinary
clone into a recovery-managed one opened new ways to lose local work: an
untracked file where a file was about to be written was overwritten, and an
apply that failed part-way reused the pending-checkout marker, whose handler
treats the repository as never checked out and force-checks-out over it.
The apply now follows one set of rules for every repository:
- Which commit it writes is decided in one place. The branch only moves to
origin's tip when that drops no commit; a managed repository still at the
commit gclo last wrote follows a rewritten origin; local commits on top of
origin are left alone; diverged histories are refused. The move happens
after validation, so an uncovered invalid path leaves the branch where it
was.
- Before anything changes, an ordinary clone and a managed repository whose
last apply finished are checked for local work: uncommitted changes to
tracked files, and files gclo did not write and git does not track
sitting where a file is about to be written (an ignored file is replaced,
as git does). A managed repository gets this on every later pull too.
- The pending marker is no longer set on a repository that was checked out.
Before the first destructive step the recovery file is saved without a
materialized tip, listing everything that was and will be on disk; an
interrupted apply is then completed by the next pull through the same
code, and what left the tree is still cleaned up.
- A config flag records that a repository is recovery-managed, so deleting
a corrupt recovery file (as its message advises) no longer makes the
renamed tree look like uncommitted changes.
- An empty clone whose origin's first commit has invalid paths can be
resolved; resolving a clone that has nothing new on origin is refused
instead of converting it for nothing.
- A folder without files where a file belongs is replaced; one with files
is refused before anything changes.
Refs #147
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
Signed-off-by: James Maes <james@kof22.com>
"Resolve" and --sanitize-paths were built for a clone that was fetched but
never checked out. They are also offered when a pull into an ordinary,
checked-out clone finds Windows-invalid paths in the incoming commits, and
there the apply wrote the tree of the commit already checked out, with
force: uncommitted edits were overwritten, the incoming commits were not
delivered although the row said Done, and the next pull moved the branch to
origin's tip without checking that local commits were contained in it.
- Applying a recovery to an ordinary clone now goes to origin's tip, after
proving that loses nothing: uncommitted changes to tracked files, or
commits origin does not have, stop it with the advice the ordinary pull
gives, and nothing is changed. Files that left the tree are removed.
- A recovery-managed repository is only ever fast-forwarded.
- The pending-checkout marker is set for the whole apply, so an apply that
fails part-way is completed or re-reported by the next pull instead of
being called up to date.
- The index is written from the commit, so git reports only the renamed
and skipped paths as changed instead of every file as deleted.
- Attribute files are materialized first and apply to the whole tree.
- When recoveries are merged, the newer choice for a path wins whatever
kind it is: a rename replaces a stored skip, a skip a stored rename.
Closes#147
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
Signed-off-by: James Maes <james@kof22.com>