Skip to content

Commit 0d990b2

Browse files
yyvchMaxDesiatov
andauthored
Show human-readable git errors (#8152)
Show human-readable git errors ### Motivation: I noticed that when swiftpm cache is empty and there's a network issue, then `swift build` throws an error which is not helpful, something like this: ``` error: GitShellError(result: <AsyncProcessResult: exit: terminated(code: 128), output: >) ``` where it does say "output" but doesn't actually include output or show any details about what dependency caused the error. ### Modifications: - `GitShellError` now conforms to CustomStringConvertible protocol, so that when it is thrown then it will print what was the git error - `GitShelError` has now package access to allow catching it explicitly. ### Result: Errors now tell details of the failure: ``` error: Git command 'git -C /Users/yy/Library/Caches/org.swift.swiftpm/repositories/postgres-kit-e4358808 config --get remote.origin.url' failed: fatal: cannot change to '/Users/yy/Library/Caches/org.swift.swiftpm/repositories/postgres-kit-e4358808': No such file or directory ``` --------- Co-authored-by: Max Desiatov <m_desiatov@apple.com>
1 parent 4073657 commit 0d990b2

File tree

2 files changed

+87
-3
lines changed

2 files changed

+87
-3
lines changed

Sources/SourceControl/GitRepository.swift

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
//
33
// This source file is part of the Swift open source project
44
//
5-
// Copyright (c) 2014-2020 Apple Inc. and the Swift project authors
5+
// Copyright (c) 2014-2024 Apple Inc. and the Swift project authors
66
// Licensed under Apache License v2.0 with Runtime Library Exception
77
//
88
// See http://swift.org/LICENSE.txt for license information
@@ -1161,8 +1161,16 @@ extension GitFileSystemView: @unchecked Sendable {}
11611161

11621162
// MARK: - Errors
11631163

1164-
private struct GitShellError: Error {
1164+
package struct GitShellError: Error, CustomStringConvertible {
11651165
let result: AsyncProcessResult
1166+
1167+
public var description: String {
1168+
let stdout = (try? self.result.utf8Output()) ?? ""
1169+
let stderr = (try? self.result.utf8stderrOutput()) ?? ""
1170+
let output = (stdout + stderr).spm_chomp()
1171+
let command = self.result.arguments.joined(separator: " ")
1172+
return "Git command '\(command)' failed: \(output)"
1173+
}
11661174
}
11671175

11681176
private enum GitInterfaceError: Swift.Error {

Tests/SourceControlTests/GitRepositoryProviderTests.swift

Lines changed: 77 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
//
33
// This source file is part of the Swift open source project
44
//
5-
// Copyright (c) 2022 Apple Inc. and the Swift project authors
5+
// Copyright (c) 2022-2024 Apple Inc. and the Swift project authors
66
// Licensed under Apache License v2.0 with Runtime Library Exception
77
//
88
// See http://swift.org/LICENSE.txt for license information
@@ -40,4 +40,80 @@ class GitRepositoryProviderTests: XCTestCase {
4040
XCTAssertThrowsError(try provider.isValidDirectory(notGitChildPath))
4141
}
4242
}
43+
44+
func testIsValidDirectoryThrowsPrintableError() throws {
45+
try testWithTemporaryDirectory { temp in
46+
let provider = GitRepositoryProvider()
47+
let expectedErrorMessage = "not a git repository"
48+
XCTAssertThrowsError(try provider.isValidDirectory(temp)) { error in
49+
let errorString = String(describing: error)
50+
XCTAssertTrue(
51+
errorString.contains(expectedErrorMessage),
52+
"Error string '\(errorString)' should contain '\(expectedErrorMessage)'"
53+
)
54+
}
55+
}
56+
}
57+
58+
func testGitShellErrorIsPrintable() throws {
59+
let stdOut = "An error from Git - stdout"
60+
let stdErr = "An error from Git - stderr"
61+
let arguments = ["git", "error"]
62+
let command = "git error"
63+
let result = AsyncProcessResult(
64+
arguments: arguments,
65+
environment: [:],
66+
exitStatus: .terminated(code: 1),
67+
output: .success(Array(stdOut.utf8)),
68+
stderrOutput: .success(Array(stdErr.utf8))
69+
)
70+
let error = GitShellError(result: result)
71+
let errorString = "\(error)"
72+
XCTAssertTrue(
73+
errorString.contains(stdOut),
74+
"Error string '\(errorString)' should contain '\(stdOut)'"
75+
)
76+
XCTAssertTrue(
77+
errorString.contains(stdErr),
78+
"Error string '\(errorString)' should contain '\(stdErr)'"
79+
)
80+
XCTAssertTrue(
81+
errorString.contains(command),
82+
"Error string '\(errorString)' should contain '\(command)'"
83+
)
84+
}
85+
86+
func testGitShellErrorEmptyStdOut() throws {
87+
let stdErr = "An error from Git - stderr"
88+
let result = AsyncProcessResult(
89+
arguments: ["git", "error"],
90+
environment: [:],
91+
exitStatus: .terminated(code: 1),
92+
output: .success([]),
93+
stderrOutput: .success(Array(stdErr.utf8))
94+
)
95+
let error = GitShellError(result: result)
96+
let errorString = "\(error)"
97+
XCTAssertTrue(
98+
errorString.contains(stdErr),
99+
"Error string '\(errorString)' should contain '\(stdErr)'"
100+
)
101+
}
102+
103+
func testGitShellErrorEmptyStdErr() throws {
104+
let stdOut = "An error from Git - stdout"
105+
let result = AsyncProcessResult(
106+
arguments: ["git", "error"],
107+
environment: [:],
108+
exitStatus: .terminated(code: 1),
109+
output: .success(Array(stdOut.utf8)),
110+
stderrOutput: .success([])
111+
)
112+
let error = GitShellError(result: result)
113+
let errorString = "\(error)"
114+
XCTAssertTrue(
115+
errorString.contains(stdOut),
116+
"Error string '\(errorString)' should contain '\(stdOut)'"
117+
)
118+
}
43119
}

0 commit comments

Comments
 (0)