Skip to content

Commit cb7083b

Browse files
authored
Merge pull request #97 from zero-ide/feature/git-conflict-file-guidance
refactor: include conflicted file names in pull guidance
2 parents cda8201 + 5075b65 commit cb7083b

File tree

2 files changed

+52
-0
lines changed

2 files changed

+52
-0
lines changed

Sources/Zero/Services/GitPanelService.swift

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,12 @@ class GitPanelService: ObservableObject {
128128
try gitService.pull(in: containerName)
129129
await refresh()
130130
} catch {
131+
if isPullConflictError(error) {
132+
let conflictedFiles = (try? gitService.conflictedFiles(in: containerName)) ?? []
133+
errorMessage = pullConflictGuidance(conflictedFiles: conflictedFiles)
134+
return
135+
}
136+
131137
errorMessage = mapRemoteActionError(error, action: .pull)
132138
}
133139
}
@@ -182,6 +188,27 @@ class GitPanelService: ObservableObject {
182188
return message
183189
}
184190

191+
private func isPullConflictError(_ error: Error) -> Bool {
192+
let normalized = error.localizedDescription.lowercased()
193+
return containsAny(normalized, patterns: ["automatic merge failed", "merge conflict", "conflict"])
194+
}
195+
196+
private func pullConflictGuidance(conflictedFiles: [String]) -> String {
197+
let files = conflictedFiles.map { $0.trimmingCharacters(in: .whitespacesAndNewlines) }
198+
.filter { !$0.isEmpty }
199+
200+
guard !files.isEmpty else {
201+
return "Pull hit merge conflicts. Resolve conflicted files, commit, then pull again."
202+
}
203+
204+
let maxPreview = 3
205+
let previewFiles = Array(files.prefix(maxPreview)).joined(separator: ", ")
206+
let remaining = files.count - maxPreview
207+
let suffix = remaining > 0 ? " (+\(remaining) more)" : ""
208+
209+
return "Pull hit merge conflicts in \(previewFiles)\(suffix). Resolve those files, commit, then pull again."
210+
}
211+
185212
private func containsAny(_ text: String, patterns: [String]) -> Bool {
186213
patterns.contains { text.contains($0) }
187214
}

Tests/ZeroTests/GitPanelServiceTests.swift

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,31 @@ final class GitPanelServiceTests: XCTestCase {
5050
func testPullMapsMergeConflictFailureToGuidance() async {
5151
// Given
5252
let runner = GitPanelMockContainerRunner()
53+
runner.nextShellOutput = """
54+
Sources/Zero/Views/EditorView.swift
55+
Sources/Zero/Services/GitPanelService.swift
56+
"""
57+
runner.shellErrorsByCommandSubstring["git pull"] = NSError(
58+
domain: "git",
59+
code: 1,
60+
userInfo: [NSLocalizedDescriptionKey: "Automatic merge failed; fix conflicts and then commit the result."]
61+
)
62+
let service = makeService(runner: runner)
63+
64+
// When
65+
await service.pull()
66+
67+
// Then
68+
XCTAssertEqual(
69+
service.errorMessage,
70+
"Pull hit merge conflicts in Sources/Zero/Views/EditorView.swift, Sources/Zero/Services/GitPanelService.swift. Resolve those files, commit, then pull again."
71+
)
72+
}
73+
74+
func testPullConflictFallsBackToGenericGuidanceWhenNoFilesDetected() async {
75+
// Given
76+
let runner = GitPanelMockContainerRunner()
77+
runner.nextShellOutput = ""
5378
runner.shellErrorsByCommandSubstring["git pull"] = NSError(
5479
domain: "git",
5580
code: 1,

0 commit comments

Comments
 (0)