feat(git-sync): rebase local work onto origin on :gl divergence

Instead of refusing a divergence, :gl now replants the device's
local commit(s) onto origin's tip (rebase_local_onto: splice the
merge_base..HEAD paths from the card onto origin's tree,
last-writer-wins per note, no content merge) and ends LocalAhead
for :gp to publish. The branch ref moves last, after the merged
tree is applied to the card, so a power-pull mid-rebase leaves
HEAD at the old tip and the next :gl recomputes idempotently.

Replaces PullOutcome::Diverged with Rebased(oid); the UI gives it
the same stale-buffer refresh as Pulled and snackbars
"rebased <oid> - :gp to publish".
This commit is contained in:
Julien Calixte
2026-07-14 13:27:32 +02:00
parent b5c0c90dcc
commit 75f25433ec
2 changed files with 178 additions and 29 deletions

View File

@@ -340,17 +340,30 @@ fn main() -> anyhow::Result<()> {
PublishOutcome::Failed(reason) => reason,
}
}
GitOutcome::Pull(outcome) => match outcome {
// The working copy moved under us: stale resident
// buffers must re-read the disk. Clean parked buffers
// are dropped (they reload on the next switch), the
// clean active buffer is re-read now, and a RAM-dirty
// buffer is left alone — its edits win, last-writer-
// wins like the publish reconcile. The palette list is
// re-walked in the background for files the pull added
// or removed (it lands on `walk_rx` a few seconds
// later, instead of stalling the UI for the walk).
PullOutcome::Pulled(oid) => {
GitOutcome::Pull(outcome) => {
// Pulled and Rebased both move the working copy under us
// (Rebased applies origin's tree *and* replants our commit
// on top); LocalAhead / UpToDate leave the tree untouched.
let moved_working_copy = matches!(
outcome,
PullOutcome::Pulled(_) | PullOutcome::Rebased(_)
);
let notice = match outcome {
PullOutcome::Pulled(oid) => format!("pulled {oid}"),
PullOutcome::Rebased(oid) => format!("rebased {oid} - :gp to publish"),
PullOutcome::UpToDate => "up to date".to_string(),
PullOutcome::LocalAhead => "ahead - :gp to publish".to_string(),
PullOutcome::Failed(reason) => reason,
};
if moved_working_copy {
// Stale resident buffers must re-read the disk. Clean
// parked buffers are dropped (they reload on the next
// switch), the clean active buffer is re-read now, and
// a RAM-dirty buffer is left alone — its edits win,
// last-writer-wins like the publish reconcile. The
// palette list is re-walked in the background for files
// the pull added or removed (it lands on `walk_rx` a
// few seconds later, instead of stalling the UI).
ed.drop_clean_parked();
if ed.dirty() {
log::info!(
@@ -367,13 +380,9 @@ fn main() -> anyhow::Result<()> {
}
}
spawn_file_walk(walk_tx.clone());
format!("pulled {oid}")
}
PullOutcome::UpToDate => "up to date".to_string(),
PullOutcome::LocalAhead => "ahead - :gp to publish".to_string(),
PullOutcome::Diverged => "diverged - resolve on a computer".to_string(),
PullOutcome::Failed(reason) => reason,
},
notice
}
};
ed.set_notice(notice);
ed.draw_into(&mut back, true);