An unopenable repository fails loudly — the standing row, the paused surface, the honest heal
Claude-Session: https://claude.ai/code/session_014PtZdPwqZuqEDLc6wZMtEy
This commit is contained in:
@@ -183,14 +183,41 @@ public final class HistoryStore {
|
||||
|
||||
private static let logger = Logger(subsystem: "dev.rzen.indie.Kanban", category: "git")
|
||||
|
||||
/// **The repository is there and cannot be opened** (06-history-undo.md ▸ Rules: "A `.git` that
|
||||
/// isn't a valid repository still reads as git mode — and fails loudly", ruled 2026-07-31).
|
||||
///
|
||||
/// Read off the committer's pause rather than stored beside it, deliberately: the detection-time
|
||||
/// probe *seeds* that pause (`init`), every later read of the repository refreshes it — the
|
||||
/// standing pause's 15 s re-read, the popover's `refreshPause`, any flush attempt — and a second
|
||||
/// stored copy could only ever be the stale one. `false` on every board with no repository to
|
||||
/// read, which is every mode but `.git`.
|
||||
///
|
||||
/// **Never a mode change.** Detection stays presence-shaped: the board is in git mode because a
|
||||
/// `.git` is at its root, whatever condition it is in, so add-git is never offered against it
|
||||
/// ("init into a repairable repo is exactly the never-mutate hazard").
|
||||
public var isRepositoryUnreadable: Bool { committer?.pause == .unreadable }
|
||||
|
||||
init(boardRoot: URL, mode: BoardGitMode, ledger: EchoLedger) {
|
||||
self.boardRoot = boardRoot
|
||||
self.mode = mode
|
||||
self.ledger = ledger
|
||||
if mode == .git {
|
||||
committer = GitAutoCommitter(boardRoot: boardRoot, ledger: ledger)
|
||||
let committer = GitAutoCommitter(boardRoot: boardRoot, ledger: ledger)
|
||||
self.committer = committer
|
||||
switcher = GitBranchSwitcher(boardRoot: boardRoot)
|
||||
housekeeper = GitHousekeeper(boardRoot: boardRoot)
|
||||
// **The detection-time probe** (06 ▸ Rules, the corrupt-`.git` loud failure): detection
|
||||
// answers presence, this answers readability, and the ruling wants the second answer at
|
||||
// the same moment as the first — "a standing breakage-class banner at detection …
|
||||
// never a silent placeholder discovered only in the popover".
|
||||
//
|
||||
// One `git_repository_open` per git-mode board open, which is the same call the branch
|
||||
// line makes a moment later and a handful of `stat`s in the ordinary case. That is the
|
||||
// budget 02's hang-avoidance doctrine leaves for an answer the open path cannot do
|
||||
// without: the alternative is a board that looks live until the first debounce fires.
|
||||
if !GitRepository.canOpen(at: boardRoot) {
|
||||
committer.noteRepositoryUnreadable()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -368,8 +395,15 @@ public final class HistoryStore {
|
||||
/// placeholder promises: an empty field means the derived default applies. The read afterwards is
|
||||
/// not ceremony — it is how the fields end up showing what the file says rather than what was
|
||||
/// typed at it, which is the only version that survives a foreign edit landing in between.
|
||||
///
|
||||
/// **Refused against an unreadable repository** (06 ▸ Rules, the corrupt-`.git` loud failure:
|
||||
/// "Lanework leaves the repository untouched"). This is the one identity call that *writes*, and
|
||||
/// `.git/config` is the file most likely to be what is wrong with a repository libgit2 will not
|
||||
/// open. Unreachable in practice — the sheet that hosts these fields resolves to nothing on such a
|
||||
/// board (`BoardSettingsSection.resolve`) — and gated anyway, because "never touched" is a
|
||||
/// promise about the repository rather than about which surfaces happen to be reachable.
|
||||
public func writeIdentity(name: String, email: String) async {
|
||||
guard mode == .git else { return }
|
||||
guard mode == .git, !isRepositoryUnreadable else { return }
|
||||
let root = boardRoot
|
||||
identityFailure = nil
|
||||
let outcome = await Task.detached(priority: .userInitiated) {
|
||||
|
||||
Reference in New Issue
Block a user