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
25 changes: 22 additions & 3 deletions CodeEdit/Features/SourceControl/Client/GitClient+Status.swift
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,25 @@ extension GitClient {
/// - Throws: Can throw ``GitClient/GitClientError`` errors if it finds unexpected output.
func getStatus() async throws -> Status {
let output = try await run("status -z --porcelain=2 -u")
return try parseStatusString(output)
}

/// Parses a status string from ``getStatus()`` and returns a ``Status`` object if possible.
/// - Parameter output: The git output from running `status`. Expects a porcelain v2 string.
/// - Returns: A status object if parseable.
func parseStatusString(_ output: borrowing String) throws -> Status {
let endsInNull = output.last == Character(UnicodeScalar(0))
let endIndex: String.Index
if endsInNull && output.count > 1 {
endIndex = output.index(before: output.endIndex)
} else {
endIndex = output.endIndex
}

var status = Status(changedFiles: [], unmergedChanges: [], untrackedFiles: [])

var index = output.startIndex
while index < output.endIndex {
while index < endIndex {
let typeIndex = index

// Move ahead no matter what.
Expand Down Expand Up @@ -100,7 +114,11 @@ extension GitClient {
}
index = newIndex
}
index = output.index(after: index)
defer {
if index < output.index(before: output.endIndex) {
index = output.index(after: index)
}
}
return output[startIndex..<index]
}

Expand Down Expand Up @@ -147,7 +165,8 @@ extension GitClient {
try moveToNextSpace(from: &index, output: output)
}
try moveOneChar(from: &index, output: output)
let filename = String(try substringToNextNull(from: &index, output: output))
let substring = try substringToNextNull(from: &index, output: output)
let filename = String(substring)
return GitChangedFile(
status: status,
stagedStatus: stagedStatus,
Expand Down
27 changes: 27 additions & 0 deletions CodeEditTests/Features/SourceControl/GitClientTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
//
// GitClientTests.swift
// CodeEditTests
//
// Created by Khan Winter on 9/11/25.
//

import Testing
@testable import CodeEdit

@Suite
struct GitClientTests {
@Test
func statusParseNullAtEnd() throws {
try withTempDir { dirURL in
// swiftlint:disable:next line_length
let string = "1 .M N... 100644 100644 100644 eaef31cfa2a22418c00d7477da0b7151d122681e eaef31cfa2a22418c00d7477da0b7151d122681e CodeEdit/Features/SourceControl/Client/GitClient+Status.swift\01 AM N... 000000 100644 100644 0000000000000000000000000000000000000000 e0f5ce250b32cf6610a284b7a33ac114079f5159 CodeEditTests/Features/SourceControl/GitClientTests.swift\0"
let client = GitClient(directoryURL: dirURL, shellClient: .live())
let status = try client.parseStatusString(string)

#expect(status.changedFiles.count == 2)
// No null string at the end
#expect(status.changedFiles[0].fileURL.lastPathComponent == "GitClient+Status.swift")
#expect(status.changedFiles[1].fileURL.lastPathComponent == "GitClientTests.swift")
}
}
}
Loading