Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 19 additions & 11 deletions Sources/Zero/Services/GitPanelService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ class GitPanelService: ObservableObject {
branches = try gitService.branches(in: containerName)
errorMessage = nil
} catch {
errorMessage = error.localizedDescription
reportFailure(action: "refresh", userMessage: error.localizedDescription, error: error)
}
}

Expand All @@ -51,7 +51,7 @@ class GitPanelService: ObservableObject {
try gitService.add(files: files, in: containerName)
await refresh()
} catch {
errorMessage = error.localizedDescription
reportFailure(action: "stage", userMessage: error.localizedDescription, error: error)
}
}

Expand All @@ -62,7 +62,7 @@ class GitPanelService: ObservableObject {
try gitService.addAll(in: containerName)
await refresh()
} catch {
errorMessage = error.localizedDescription
reportFailure(action: "stageAll", userMessage: error.localizedDescription, error: error)
}
}

Expand All @@ -73,7 +73,7 @@ class GitPanelService: ObservableObject {
try gitService.commit(message: message, in: containerName)
await refresh()
} catch {
errorMessage = error.localizedDescription
reportFailure(action: "commit", userMessage: error.localizedDescription, error: error)
}
}

Expand All @@ -84,7 +84,7 @@ class GitPanelService: ObservableObject {
try gitService.commitAll(message: message, in: containerName)
await refresh()
} catch {
errorMessage = error.localizedDescription
reportFailure(action: "commitAll", userMessage: error.localizedDescription, error: error)
}
}

Expand All @@ -95,7 +95,7 @@ class GitPanelService: ObservableObject {
try gitService.createAndCheckoutBranch(name: name, in: containerName)
await refresh()
} catch {
errorMessage = error.localizedDescription
reportFailure(action: "createBranch", userMessage: error.localizedDescription, error: error)
}
}

Expand All @@ -106,7 +106,7 @@ class GitPanelService: ObservableObject {
try gitService.checkout(branch: branch, in: containerName)
await refresh()
} catch {
errorMessage = error.localizedDescription
reportFailure(action: "checkout", userMessage: error.localizedDescription, error: error)
}
}

Expand All @@ -117,7 +117,8 @@ class GitPanelService: ObservableObject {
try gitService.push(in: containerName)
await refresh()
} catch {
errorMessage = mapRemoteActionError(error, action: .push)
let message = mapRemoteActionError(error, action: .push)
reportFailure(action: "push", userMessage: message, error: error)
}
}

Expand All @@ -130,11 +131,13 @@ class GitPanelService: ObservableObject {
} catch {
if isPullConflictError(error) {
let conflictedFiles = (try? gitService.conflictedFiles(in: containerName)) ?? []
errorMessage = pullConflictGuidance(conflictedFiles: conflictedFiles)
let message = pullConflictGuidance(conflictedFiles: conflictedFiles)
reportFailure(action: "pull", userMessage: message, error: error)
return
}

errorMessage = mapRemoteActionError(error, action: .pull)
let message = mapRemoteActionError(error, action: .pull)
reportFailure(action: "pull", userMessage: message, error: error)
}
}

Expand All @@ -155,10 +158,15 @@ class GitPanelService: ObservableObject {
} catch {
selectedDiffTitle = "Diff · \(path)"
selectedDiff = "Failed to load diff: \(error.localizedDescription)"
errorMessage = error.localizedDescription
reportFailure(action: "loadDiff", userMessage: error.localizedDescription, error: error)
}
}

private func reportFailure(action: String, userMessage: String, error: Error) {
errorMessage = userMessage
AppLogStore.shared.append("GitPanel \(action) failed: \(userMessage) [raw=\(error.localizedDescription)]")
}

func showUntrackedDiffPlaceholder(for path: String) {
selectedDiffTitle = "Untracked · \(path)"
selectedDiff = "Untracked files have no git diff until staged."
Expand Down
51 changes: 51 additions & 0 deletions Tests/ZeroTests/GitPanelServiceTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,16 @@ private final class GitPanelMockContainerRunner: ContainerRunning {

@MainActor
final class GitPanelServiceTests: XCTestCase {
override func setUp() {
super.setUp()
AppLogStore.shared.clear()
}

override func tearDown() {
AppLogStore.shared.clear()
super.tearDown()
}

func testPushMapsNonFastForwardFailureToGuidance() async {
// Given
let runner = GitPanelMockContainerRunner()
Expand Down Expand Up @@ -129,6 +139,47 @@ final class GitPanelServiceTests: XCTestCase {
XCTAssertEqual(service.errorMessage, "fatal: unexpected socket close")
}

func testPushFailureAppendsErrorToAppLogStore() async {
// Given
let runner = GitPanelMockContainerRunner()
runner.shellErrorsByCommandSubstring["git push"] = NSError(
domain: "git",
code: 1,
userInfo: [NSLocalizedDescriptionKey: "fatal: unexpected socket close"]
)
let service = makeService(runner: runner)

// When
await service.push()

// Then
let logEntries = AppLogStore.shared.recentEntries()
XCTAssertTrue(logEntries.contains { entry in
entry.contains("GitPanel push failed") && entry.contains("fatal: unexpected socket close")
})
}

func testPullConflictAppendsGuidanceToAppLogStore() async {
// Given
let runner = GitPanelMockContainerRunner()
runner.nextShellOutput = "Sources/Zero/Views/EditorView.swift"
runner.shellErrorsByCommandSubstring["git pull"] = NSError(
domain: "git",
code: 1,
userInfo: [NSLocalizedDescriptionKey: "Automatic merge failed; fix conflicts and then commit the result."]
)
let service = makeService(runner: runner)

// When
await service.pull()

// Then
let logEntries = AppLogStore.shared.recentEntries()
XCTAssertTrue(logEntries.contains { entry in
entry.contains("GitPanel pull failed") && entry.contains("Pull hit merge conflicts")
})
}

private func makeService(runner: GitPanelMockContainerRunner) -> GitPanelService {
let service = GitPanelService()
service.setup(gitService: GitService(runner: runner), containerName: "zero-dev-container")
Expand Down