Build branch switching and the popover git surface
GitBranchSwitcher holds 06's sequence as one object: settle editors
explicitly (SessionSettleGate — Save All applies raw buffers with
validation and a refused buffer cancels the whole switch; Discard
reverts buffers AND reconciles the session folders against HEAD;
never silent), flush the pending auto-commit, stamp intent in the
per-board registry, bracketed safe checkout (git_checkout_tree
GIT_CHECKOUT_SAFE + set_head — no path passes FORCE, abort
included), one reload via the async wholesale bracket (failed final
reload engages the existing read-only lock), reseed undo/redo from
the new HEAD with redo empty, clear the stamp. Create-and-switch
keeps the full sequence — the tree-cannot-change proof fails under
concurrent writers. Lock contention shows the 02 in-progress row's
waiting state ("waiting for another writer's git lock"), bounded at
30s then failing cleanly naming the lock path.
GitOperationStamp + GitOperationRecovery: the own-leftovers rule as
a pure conjunction — pause state AND matching stamp = the app's own
interrupted operation, aborted to the pre-operation state with a
banner, stamp cleared on success only; either alone defers to the
pause-and-defer stance. Checked where the committer starts.
BoardGitControls replaces the read-only branch line: branch picker,
inline create-and-switch, the abnormal-state pause note in 06's own
words with controls dimmed, and commit-identity fields that read and
write repo-local .git/config (derived default as placeholder, never
value; unfocused resync, focused keystrokes kept; 2s poll while
visible — .git is watcher-filtered by design).
Also fixes a shipped bug from the undo card: plan(reconciling:)
matched card ids as path prefixes, so the reconcile branch was inert
on every board (<lane>/<card> never matches a bare id) — a session
file the restore diff couldn't name (attachment, comment, draft)
survived Discard and landed in the next flush's commit. One shared
component-exact folder-name resolver now serves both Discard paths;
noteDiscarded takes cardFolderName; regression test verified failing
against the pre-fix code.
41 branch tests + the regression; 2374 tests / 409 suites green;
InertGitTests untouched.
Claude-Session: https://claude.ai/code/session_01SR4XGjmBE16ZUYWpfFHXwY
This commit is contained in:
@@ -629,6 +629,55 @@ enum GitCommitOperation {
|
||||
)
|
||||
}
|
||||
|
||||
/// **What repo-local config actually says** — the two values behind the popover's identity fields,
|
||||
/// each `nil` when the file does not name it (06 ▸ Interaction with external writers).
|
||||
///
|
||||
/// Deliberately *not* `userIdentity(at:)`: that answers "who will this commit be by", derived
|
||||
/// default included, and a field pre-filled with a derived value would turn a placeholder into a
|
||||
/// value the moment the user typed anywhere else in the popover. The fields show what the file
|
||||
/// says and nothing more; the derived default is their placeholder.
|
||||
nonisolated static func repoLocalIdentity(at boardRoot: URL) -> (name: String?, email: String?) {
|
||||
_ = startUp
|
||||
guard let repository = open(boardRoot) else { return (nil, nil) }
|
||||
defer { git_repository_free(repository) }
|
||||
return GitConfigFile.identity(inGitDirectory: gitDirectory(of: repository))
|
||||
}
|
||||
|
||||
/// **Writes the popover's identity fields into repo-local config** — the one write of those keys
|
||||
/// in the app (`GitConfigFile.writeIdentity`, where the file-format rules live).
|
||||
///
|
||||
/// The `.git` directory comes from libgit2 rather than from `boardRoot/.git`, for
|
||||
/// `userIdentity(at:)`'s reason: a board whose `.git` is a *file* (a linked worktree) has its real
|
||||
/// config somewhere else, and writing beside the pointer would be writing to nothing.
|
||||
nonisolated static func writeRepoLocalIdentity(
|
||||
name: String?,
|
||||
email: String?,
|
||||
at boardRoot: URL
|
||||
) -> Result<Void, GitOperationFailure> {
|
||||
_ = startUp
|
||||
let operation = "Saving this board's commit identity"
|
||||
guard let repository = open(boardRoot) else {
|
||||
return .failure(GitOperationFailure(
|
||||
operation: operation,
|
||||
message: "this board's repository could not be opened"
|
||||
))
|
||||
}
|
||||
defer { git_repository_free(repository) }
|
||||
do {
|
||||
try GitConfigFile.writeIdentity(
|
||||
name: name,
|
||||
email: email,
|
||||
inGitDirectory: gitDirectory(of: repository)
|
||||
)
|
||||
return .success(())
|
||||
} catch {
|
||||
return .failure(GitOperationFailure(
|
||||
operation: operation,
|
||||
message: (error as NSError).localizedDescription
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
private static func signature(_ identity: GitIdentity) -> UnsafeMutablePointer<git_signature>? {
|
||||
var signature: UnsafeMutablePointer<git_signature>?
|
||||
let now = Date()
|
||||
|
||||
Reference in New Issue
Block a user