Skip to content

Commit db7f9c4

Browse files
committed
Bug: Fixed issue where the GitShell would crash when no data is present
1 parent 9277639 commit db7f9c4

File tree

2 files changed

+110
-76
lines changed

2 files changed

+110
-76
lines changed

Sources/Version-Control/Base/Core/GitShell.swift

Lines changed: 81 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -42,122 +42,127 @@ public struct GitShell {
4242
var stderr = ""
4343

4444
let process = Process()
45-
process.launchPath = "/usr/bin/env"
45+
process.executableURL = URL(fileURLWithPath: "/usr/bin/env")
4646
process.arguments = ["git"] + args
47-
process.currentDirectoryPath = path.relativePath
47+
process.currentDirectoryURL = path
4848

4949
let stdoutPipe = Pipe()
5050
let stderrPipe = Pipe()
5151
process.standardOutput = stdoutPipe
5252
process.standardError = stderrPipe
5353

54-
let environment = ProcessInfo.processInfo.environment
55-
56-
// Set TERM to 'dumb' in the environment
57-
var gitEnvironment = environment
54+
var gitEnvironment = ProcessInfo.processInfo.environment
5855
gitEnvironment["TERM"] = "dumb"
5956

6057
if let environment = options?.env {
61-
process.environment = environment
58+
gitEnvironment.merge(environment) { (_, new) in new }
6259
}
6360

6461
process.environment = gitEnvironment
6562

66-
process.launch()
63+
do {
64+
try process.run()
65+
} catch {
66+
throw ProcessError.launchFailed(error.localizedDescription)
67+
}
68+
69+
let stdoutHandle = stdoutPipe.fileHandleForReading
70+
let stderrHandle = stderrPipe.fileHandleForReading
71+
72+
stdoutHandle.readabilityHandler = { handle in
73+
let data = handle.availableData
74+
if let output = String(data: data, encoding: .utf8) {
75+
stdout += output
76+
}
77+
}
78+
79+
stderrHandle.readabilityHandler = { handle in
80+
let data = handle.availableData
81+
if let output = String(data: data, encoding: .utf8) {
82+
stderr += output
83+
}
84+
}
6785

6886
let commandLineURL = "/usr/bin/env git " + args.joined(separator: " ")
6987
print("Command Line URL: \(commandLineURL)")
7088

71-
let stdoutData = stdoutPipe.fileHandleForReading.readDataToEndOfFile()
72-
if let output = String(data: stdoutData, encoding: .utf8) {
73-
stdout = output
89+
let timeout: TimeInterval = 30 // Adjust this value as needed
90+
let timeoutDate = Date(timeIntervalSinceNow: timeout)
91+
92+
while process.isRunning && Date() < timeoutDate {
93+
Thread.sleep(forTimeInterval: 0.1)
7494
}
7595

76-
let stderrData = stderrPipe.fileHandleForReading.readDataToEndOfFile()
77-
if let errorOutput = String(data: stderrData, encoding: .utf8) {
78-
stderr = errorOutput
96+
if process.isRunning {
97+
process.terminate()
98+
throw ProcessError.timeout
7999
}
80100

81-
process.waitUntilExit()
101+
stdoutHandle.readabilityHandler = nil
102+
stderrHandle.readabilityHandler = nil
82103

83-
let combinedOuput = stdout + stderr
104+
let exitCode = Int(process.terminationStatus)
84105

85-
print("Function: \(name), Output: \(stdout)")
106+
let combinedOutput = stdout + stderr
86107

87108
let result = IGitResult(stdout: stdout,
88109
stderr: stderr,
89-
exitCode: Int(process.terminationStatus),
110+
exitCode: exitCode,
90111
gitError: nil,
91112
gitErrorDescription: nil,
92-
combinedOutput: stdout + stderr,
113+
combinedOutput: combinedOutput,
93114
path: path.relativePath.escapedWhiteSpaces())
94115

95-
let exitCode = result.exitCode
96-
var gitError: GitError?
97-
let acceptableExitCode = options?.successExitCodes != nil
98-
? options?.successExitCodes?.contains(exitCode)
99-
: false
100-
101-
if !acceptableExitCode! {
102-
gitError = parseError(stderr: result.stderr)
103-
if gitError == nil {
104-
gitError = parseError(stderr: result.stdout)
105-
}
106-
}
116+
let acceptableExitCode = options?.successExitCodes?.contains(exitCode) ?? (exitCode == 0)
107117

108-
let gitErrorDescription = gitError != nil ? getDescriptionError(gitError!) : nil
118+
if !acceptableExitCode {
119+
let gitError = parseError(stderr: result.stderr) ?? parseError(stderr: result.stdout)
120+
let gitErrorDescription = gitError.map { getDescriptionError($0) }
109121

110-
var gitResult = IGitResult(stdout: stdout,
111-
stderr: stderr,
112-
exitCode: exitCode,
113-
gitError: gitError,
114-
gitErrorDescription: gitErrorDescription,
115-
combinedOutput: combinedOuput,
116-
path: path.relativePath.escapedWhiteSpaces())
122+
var gitResult = IGitResult(stdout: stdout,
123+
stderr: stderr,
124+
exitCode: exitCode,
125+
gitError: gitError,
126+
gitErrorDescription: gitErrorDescription ?? "Unknown error",
127+
combinedOutput: combinedOutput,
128+
path: path.relativePath.escapedWhiteSpaces())
117129

118-
var acceptableError = true
130+
let acceptableError = gitError.map { options?.expectedErrors?.contains($0) ?? false } ?? false
119131

120-
if gitError != nil && options?.expectedErrors != nil {
121-
acceptableError = ((options?.expectedErrors?.contains(gitError!)) != nil)
122-
}
132+
if acceptableError {
133+
return gitResult
134+
}
123135

124-
if (gitError != nil && acceptableError) || acceptableError {
125-
return gitResult
126-
}
136+
var errorMessage = [String]()
137+
errorMessage.append("`git \(args.joined(separator: " "))` exited with an unexpected code: \(exitCode)")
127138

128-
var errorMessage = [String]()
139+
if !result.stdout.isEmpty {
140+
errorMessage.append("stdout:")
141+
errorMessage.append(result.stdout)
142+
}
129143

130-
errorMessage.append("`git \(args.joined(separator: " "))` exited with an unexpected code: \(exitCode)")
144+
if !result.stderr.isEmpty {
145+
errorMessage.append("stderr:")
146+
errorMessage.append(result.stderr)
147+
}
131148

132-
if !result.stdout.isEmpty {
133-
errorMessage.append("stdout:")
134-
errorMessage.append(result.stdout)
135-
}
149+
if let gitError = gitError {
150+
errorMessage.append("(The error was parsed as \(gitError): \(gitErrorDescription ?? ""))")
151+
}
136152

137-
if !result.stderr.isEmpty {
138-
errorMessage.append("stderr:")
139-
errorMessage.append(result.stderr)
140-
}
153+
if gitError == .PushWithFileSizeExceedingLimit {
154+
if let files = getFileFromExceedsError(error: errorMessage.joined(separator: "\n")) {
155+
gitResult.gitErrorDescription? += "\n\nFile causing error:\n\n" + files.joined(separator: "\n")
156+
}
157+
}
141158

142-
if let gitError = gitError {
143-
errorMessage.append("(The error was parsed as \(gitError): \(gitErrorDescription ?? ""))")
159+
throw ProcessError.unexpectedExitCode(exitCode)
144160
}
145161

146-
let errorString = errorMessage.joined(separator: "\n")
147-
148-
print(errorMessage.joined(separator: "\n"))
149-
150-
if gitError == GitError.PushWithFileSizeExceedingLimit {
151-
let result = getFileFromExceedsError(error: errorMessage.joined())
152-
let files = result.joined(separator: "\n")
153-
154-
if !files.isEmpty {
155-
gitResult.gitErrorDescription! += "\n\nFile causing error:\n\n" + files
156-
}
157-
}
162+
try? stdoutHandle.close()
163+
try? stderrHandle.close()
158164

159-
throw GitErrorParser(result: gitResult,
160-
args: args)
165+
return result
161166
}
162167

163168
/**
@@ -291,7 +296,7 @@ public struct GitShell {
291296
}
292297
}
293298

294-
func getFileFromExceedsError(error: String) -> [String] {
299+
func getFileFromExceedsError(error: String) -> [String]? {
295300
do {
296301
let beginRegex = try NSRegularExpression(
297302
pattern: "(^remote:\\serror:\\sFile\\s)",
@@ -314,7 +319,7 @@ public struct GitShell {
314319
)
315320

316321
if beginMatches.count != endMatches.count {
317-
return []
322+
return nil // Changed from [] to nil
318323
}
319324

320325
var files: [String] = []
@@ -328,9 +333,9 @@ public struct GitShell {
328333
files.append(file)
329334
}
330335

331-
return files
336+
return files.isEmpty ? nil : files // Return nil if no files found
332337
} catch {
333-
return []
338+
return nil // Changed from [] to nil
334339
}
335340
}
336341
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
//
2+
// ProcessError.swift
3+
//
4+
//
5+
// Created by Tihan-Nico Paxton on 2024/07/02.
6+
//
7+
8+
// Process Specific Errors
9+
public enum ProcessError: Error {
10+
case launchFailed(String)
11+
case timeout
12+
case unexpectedExitCode(Int)
13+
case outputParsingFailed
14+
}
15+
16+
extension ProcessError {
17+
var errorDescription: String? {
18+
switch self {
19+
case .launchFailed(let reason):
20+
return "Failed to launch process: \(reason)"
21+
case .timeout:
22+
return "Process execution timed out"
23+
case .unexpectedExitCode(let code):
24+
return "Process exited with unexpected code: \(code)"
25+
case .outputParsingFailed:
26+
return "Failed to parse process output"
27+
}
28+
}
29+
}

0 commit comments

Comments
 (0)