Conversation
A sync that has to merge cannot finish: it stages a merge of the remote
target with the local revision, commits it, and the commit is refused for
the very divergence the merge resolves. The message tells the user to sync
while they are inside a sync:
Synchronizing with local changes failed to merge with remote revision:
Branch has been advanced by another instance, sync and re-stage to commit
The merge itself is fine — computed, staged, correct. Only landing it is
blocked, and the only way out is `lore commit --force` by hand.
The check is written as a pointer comparison:
if !globals.force() && !branch_latest.is_zero()
&& branch_latest != current_revision
{
return Err(BranchAdvanced.into());
}
but the rule it stands for is "do not drop what another instance added".
An anchor equal to the latest is the cheapest way to satisfy that, not the
only one: a staged state that already has the latest in its ancestry
publishes that work rather than replacing it. Git draws the same line in
its push path — a merge commit containing the remote tip is accepted,
because reachability, not pointer equality, is the safety property.
So when the cheap comparison fails, ask the real question before refusing.
The two parents answer it outright; otherwise walk first parents back from
the staged state's own parent, stopping once revision numbers fall below
the latest's. The shape this exists for — sync staging a merge of a target
that descends from the latest — resolves in one or two steps, and a plain
commit on a stale anchor aborts immediately. Nothing changes for a commit
whose anchor is the latest: the walk is reached only where the code
previously returned an error.
It fails closed. An unreadable state, a walk past its bound, a history
this clone only partly holds — all answer "no" and the commit is refused.
A gap costs a refusal the user resolves by syncing; the opposite mistake
costs somebody's work.
Two tests define the boundary, with identical pointers and only the staged
state differing: one whose ancestry reaches the latest two steps back (the
sync-merge shape) now commits, one built on the stale anchor is still
refused. Reverting the check turns the first red and leaves the second
green.
Verified end to end on a repository that was actually stuck this way: the
sync now completes on its own, anchor and branch latest meet on the new
merge revision, nothing staged is left behind, and both sides' files are
present — without `--force`.
cargo test -p lore-revision: 1061 passed. Smoke suite against release
binaries: 1009 passed, 0 failed. clippy and nightly fmt clean.
Signed-off-by: Jochen Hunz <j.hunz@anchorpoint.app>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
A sync that has to merge cannot finish. It stages a merge of the remote target with the local revision, commits it — and the commit is refused for the very divergence the merge resolves:
It tells the user to sync, inside a sync. The merge itself is computed, staged and correct; only landing it is blocked, and the only way out is
lore commit --forceby hand.The rule the check stands for
That is a pointer comparison, but the rule it exists for is "do not drop what another instance added". An anchor equal to the latest is the cheapest way to satisfy it, not the only one — a staged state that already has the latest in its ancestry publishes that work rather than replacing it.
Git draws the same line in its push path: a non-fast-forward is rejected, but a merge commit that contains the remote tip is accepted, because reachability is the safety property, not pointer equality.
What this changes
When the cheap comparison fails, ask the real question before refusing. The two parents answer it outright; otherwise walk first parents back from the staged state's own parent, stopping once revision numbers fall below the latest's.
It fails closed. An unreadable state, a walk past its bound, a history this clone only partly holds — all answer "no" and the commit is refused. A gap costs a refusal the user resolves by syncing; the opposite mistake costs somebody's work. The first-parent walk is part of that: a latest reachable only through a second parent is not found, and refusing is the right answer to not knowing.
Tests
Two tests in
lore-revision/tests/commit.rsdefine the boundary, with identical pointers and only the staged state differing:parent_selftwo steps above the latest,parent_otherthe anchor — the sync-merge shapeReverting the check turns the first red and leaves the second green, so the pair measures this change and not the fixture.
They set the anchor and the latest directly rather than racing two clones: the check only ever reads those pointers, and how they came to differ — a server-side fast-forward merge, a forced commit — is not something it can see.
Verification
Beyond the unit tests, on a repository that was genuinely stuck this way:
The sync completes on its own, with no
--forceanywhere.cargo test -p lore-revision— 1061 passed, 0 failed-m smoke -n 4, release binaries) — 1009 passed, 0 failedcargo clippy --all-targets -- -D warningsandcargo +nightly fmt --all --checkcleanNot covered
parent_otherwill refuse rather than allow. Safe, but it means the check is stricter than the rule it states.Need help on this PR? Tag
@codesmith-botwith what you need. Autofix is disabled.