Implement undo and redo as forward commits
GitHistoryProvider is the second HistoryProviding implementation: its stack IS HEAD's first-parent ancestry, reseeded on load (redo empty), re-synced to HEAD before every crossing so agents' self-commits become the top and ⌘Z steps back exactly one commit; any arriving commit clears redo (a heal-only window deliberately does not). Restores are forward commits through the ordinary signature path — GitRestoreOperation materializes only the current-vs-target diff as working-tree writes and resolves no reset/checkout symbol at all; heal commits are transparent in-session (pointer passes over, restores exclude heal-owned paths, identity carried on landed windows via PlannedCommit.kind → GitLandedCommit). Subjects "Undo:/Redo: <crossed subject>"; menu labels never nest in-session; the root commit is not a step (crossing it would restore the empty tree). Provider binding flips: makeHistoryProvider(store, tier, git) — free binds native everywhere, Pro binds the git provider on git boards and NOTHING on mode-none/repo-nested (the pair disables through existing validation); add-git mid-session live-binds via HistoryStore.didAddGit → bindHistoryProvider (the flip only ever adds). SessionSettleGate is the reusable Save All / Discard / Cancel step: restores whose diff touches an open Edit session or raw-source buffer gate on it (Save All applies with validation — a refused buffer cancels the whole restore focused on the offender; Discard reverts via CardBodyEditSession.discardBuffer and reconciles against the working tree, deliberately skipping the second flush); untouched sessions ride through undisturbed. Built for the branch-switch card to reuse. BoardStore gains the async performWholesale sibling. CardHistorySection fills the m6 EmptyView slot: read-only, newest first, follows the card across lane moves by folder-component match (the UUID is the identity — no rename detection), absent off git mode and off Pro. 2332 tests / 403 suites green; InertGitTests untouched. Claude-Session: https://claude.ai/code/session_01SR4XGjmBE16ZUYWpfFHXwY
This commit is contained in:
@@ -110,6 +110,38 @@ public struct GitChangedPath: Sendable, Equatable, Hashable {
|
||||
|
||||
// MARK: - A planned commit
|
||||
|
||||
/// **Which of 06's classes a planned commit belongs to** — carried through the libgit2 work so a
|
||||
/// landed commit can be recognized by the class that planned it.
|
||||
///
|
||||
/// It exists for one consumer: the undo provider's **heal transparency** (06-history-undo.md ▸ Rules
|
||||
/// ▸ Heal commits are transparent to undo, in-session: "heal-class commits — their paths known by the
|
||||
/// Writer's heal-marked receipts — never become undo steps"). Receipts live on the main actor and are
|
||||
/// cleared the moment a window commits, so the only way the stack can ever learn *which commit* was
|
||||
/// the heal is to be told at the moment it lands.
|
||||
///
|
||||
/// A tag rather than a re-derivation, deliberately: a plan whose staging produced HEAD's tree is
|
||||
/// skipped and lands no commit at all, so the oids that come back are not positionally alignable with
|
||||
/// the plans that were submitted.
|
||||
public enum PlannedCommitKind: String, Sendable, Equatable, CaseIterable {
|
||||
/// A repository's first commit — "Initial board state", never split (06 ▸ Rules ▸ Abnormal repo
|
||||
/// states).
|
||||
case root
|
||||
case foreign
|
||||
case heal
|
||||
case user
|
||||
}
|
||||
|
||||
/// One commit that actually landed: its oid, and the class of the plan that made it.
|
||||
public struct GitLandedCommit: Sendable, Equatable {
|
||||
public let oid: String
|
||||
public let kind: PlannedCommitKind
|
||||
|
||||
public init(oid: String, kind: PlannedCommitKind) {
|
||||
self.oid = oid
|
||||
self.kind = kind
|
||||
}
|
||||
}
|
||||
|
||||
/// One commit a flush intends to make: which paths it stages, what it says, and who it is by.
|
||||
///
|
||||
/// A value rather than a call, because the flush's whole decision — the three-way split, the
|
||||
@@ -138,19 +170,33 @@ public struct PlannedCommit: Sendable, Equatable {
|
||||
/// itself.
|
||||
public let committer: GitIdentity
|
||||
|
||||
public init(paths: [String], message: String, author: GitIdentity, committer: GitIdentity) {
|
||||
/// Which of 06's classes planned this — carried so the landed commit can be recognized by it.
|
||||
/// See `PlannedCommitKind`; defaulted so a caller with only one class to make (add-git's root
|
||||
/// commit, the undo provider's restore) says nothing about a split it is not part of.
|
||||
public let kind: PlannedCommitKind
|
||||
|
||||
public init(
|
||||
paths: [String],
|
||||
message: String,
|
||||
author: GitIdentity,
|
||||
committer: GitIdentity,
|
||||
kind: PlannedCommitKind = .user
|
||||
) {
|
||||
self.paths = paths
|
||||
self.message = message
|
||||
self.author = author
|
||||
self.committer = committer
|
||||
self.kind = kind
|
||||
}
|
||||
}
|
||||
|
||||
/// How a flush ended — the four outcomes 06 gives the committer, and no fifth.
|
||||
public enum GitCommitOutcome: Sendable, Equatable {
|
||||
|
||||
/// One commit per planned commit that had anything in it, oldest first.
|
||||
case committed([String])
|
||||
/// One commit per planned commit that had anything in it, oldest first — each carrying the class
|
||||
/// of the plan that made it (`PlannedCommitKind`), which is how heal transparency reaches the
|
||||
/// undo stack.
|
||||
case committed([GitLandedCommit])
|
||||
|
||||
/// **The happy path, not a malfunction** (06 ▸ Interaction with external writers): the tree had
|
||||
/// nothing to commit — an agent already committed its own work, or the window held only paths
|
||||
@@ -452,11 +498,11 @@ enum GitCommitOperation {
|
||||
// own terms.
|
||||
resetIndexToHead(index, in: repository)
|
||||
|
||||
var landed: [String] = []
|
||||
var landed: [GitLandedCommit] = []
|
||||
for plan in commits {
|
||||
switch commit(plan, in: repository, index: index) {
|
||||
case let .landed(oid):
|
||||
landed.append(oid)
|
||||
landed.append(GitLandedCommit(oid: oid, kind: plan.kind))
|
||||
case .skipped:
|
||||
continue
|
||||
case let .stopped(outcome):
|
||||
|
||||
Reference in New Issue
Block a user