Skip to content

Instantly share code, notes, and snippets.

@thedeemon
Created February 18, 2026 16:20
Show Gist options
  • Select an option

  • Save thedeemon/4c0396b64c3c7f7d6aabd18d3cb4651a to your computer and use it in GitHub Desktop.

Select an option

Save thedeemon/4c0396b64c3c7f7d6aabd18d3cb4651a to your computer and use it in GitHub Desktop.
// Formats only the changed lines mentioned in "git diff".
// Removes trailing whitespace and in leading whitespace converts tabs to spaces.
// Made with Codex.
import Foundation
struct ParsedDiff {
let files: [FileChange]
}
struct FileChange {
let filePath: String
var lineNumbers: [Int]
}
enum DiffParseError: Error, CustomStringConvertible {
case invalidHunkHeader(String)
var description: String {
switch self {
case .invalidHunkHeader(let header):
return "Invalid hunk header: \(header)"
}
}
}
func parseFilePath(fromDiffGitLine line: String) -> String? {
// Example: diff --git a/path/to/file b/path/to/file
let parts = line.split(separator: " ")
guard parts.count >= 4 else { return nil }
let bPath = String(parts[3])
if bPath == "b/dev/null" {
// Deleted file: fall back to original path.
let aPath = String(parts[2])
return aPath.hasPrefix("a/") ? String(aPath.dropFirst(2)) : aPath
}
return bPath.hasPrefix("b/") ? String(bPath.dropFirst(2)) : bPath
}
func parseNewStartLine(fromHunkHeader line: String) throws -> Int {
// Example: @@ -13,7 +13,7 @@ optional trailing text
guard let plusRange = line.range(of: " +") else {
throw DiffParseError.invalidHunkHeader(line)
}
let afterPlus = line[plusRange.upperBound...]
guard let spaceAfterNewRange = afterPlus.firstIndex(of: " ") else {
throw DiffParseError.invalidHunkHeader(line)
}
let newRangeChunk = afterPlus[..<spaceAfterNewRange] // e.g. 13,7 or 42
let startString = newRangeChunk.split(separator: ",").first.map(String.init) ?? ""
guard let start = Int(startString) else {
throw DiffParseError.invalidHunkHeader(line)
}
return start
}
func parseGitDiff(_ text: String) throws -> ParsedDiff {
let lines = text.split(whereSeparator: \ .isNewline).map(String.init)
var files: [FileChange] = []
var currentFileIndex: Int?
var currentNewLineNumber: Int?
for line in lines {
if line.hasPrefix("diff --git ") {
let currentFile = parseFilePath(fromDiffGitLine: line)
currentNewLineNumber = nil
if let file = currentFile {
if let existingIndex = files.firstIndex(where: { $0.filePath == file }) {
currentFileIndex = existingIndex
} else {
files.append(FileChange(filePath: file, lineNumbers: []))
currentFileIndex = files.count - 1
}
} else {
currentFileIndex = nil
}
continue
}
if line.hasPrefix("@@ ") {
currentNewLineNumber = try parseNewStartLine(fromHunkHeader: line)
continue
}
guard let fileIndex = currentFileIndex, let newLine = currentNewLineNumber else {
continue
}
if line.hasPrefix("+") && !line.hasPrefix("+++") {
files[fileIndex].lineNumbers.append(newLine)
currentNewLineNumber = newLine + 1
} else if line.hasPrefix("-") && !line.hasPrefix("---") {
// Removed line from old file; does not advance new-file line number.
continue
} else if line.hasPrefix(" ") {
currentNewLineNumber = newLine + 1
} else if line.hasPrefix("\\ No newline at end of file") {
continue
} else {
// Unknown line in hunk context; safest behavior is to ignore it.
continue
}
}
return ParsedDiff(files: files)
}
func printUsage() {
let program = CommandLine.arguments.first ?? "diff-parser"
print("Usage: \(program) [path-to-diff.txt]")
print("If no path is provided, the program runs `git diff` and parses its output.")
print("Then it rewrites only added lines in changed files:")
print("- leading indentation tabs are replaced with 4 spaces")
print("- trailing whitespace is removed")
}
func readDiffText(fromFilePath filePath: String?) throws -> String {
if let filePath {
return try String(contentsOfFile: filePath, encoding: .utf8)
}
let process = Process()
process.executableURL = URL(fileURLWithPath: "/usr/bin/env")
process.arguments = ["git", "diff"]
let stdoutPipe = Pipe()
let stderrPipe = Pipe()
process.standardOutput = stdoutPipe
process.standardError = stderrPipe
try process.run()
process.waitUntilExit()
let stdoutData = stdoutPipe.fileHandleForReading.readDataToEndOfFile()
let stderrData = stderrPipe.fileHandleForReading.readDataToEndOfFile()
guard process.terminationStatus == 0 else {
let stderrText = String(data: stderrData, encoding: .utf8) ?? "unknown error"
let conciseError = stderrText
.split(whereSeparator: \.isNewline)
.map(String.init)
.first(where: { !$0.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty }) ?? "unknown error"
throw NSError(
domain: "DiffParser",
code: Int(process.terminationStatus),
userInfo: [NSLocalizedDescriptionKey: "git diff failed: \(conciseError)"]
)
}
return String(data: stdoutData, encoding: .utf8) ?? ""
}
func transformedTargetedLine(_ line: String) -> String {
var current = line.startIndex
while current < line.endIndex {
let ch = line[current]
if ch == " " || ch == "\t" {
current = line.index(after: current)
} else {
break
}
}
let leading = String(line[..<current]).replacingOccurrences(of: "\t", with: " ")
let remainder = String(line[current...])
let combined = leading + remainder
var end = combined.endIndex
while end > combined.startIndex {
let prev = combined.index(before: end)
let ch = combined[prev]
if ch == " " || ch == "\t" {
end = prev
} else {
break
}
}
return String(combined[..<end])
}
func applyLineTransformations(filePath: String, lineNumbers: [Int]) throws -> Bool {
if lineNumbers.isEmpty {
return false
}
let original = try String(contentsOfFile: filePath, encoding: .utf8)
let hadTrailingNewline = original.hasSuffix("\n")
var lines = original.components(separatedBy: "\n")
if hadTrailingNewline && !lines.isEmpty {
lines.removeLast()
}
var changed = false
for lineNumber in lineNumbers {
guard lineNumber > 0 && lineNumber <= lines.count else {
continue
}
let index = lineNumber - 1
let updated = transformedTargetedLine(lines[index])
if updated != lines[index] {
lines[index] = updated
changed = true
}
}
if !changed {
return false
}
var rewritten = lines.joined(separator: "\n")
if hadTrailingNewline {
rewritten += "\n"
}
try rewritten.write(toFile: filePath, atomically: true, encoding: .utf8)
return true
}
func main() {
if CommandLine.arguments.contains("--help") || CommandLine.arguments.contains("-h") {
printUsage()
return
}
let diffPath = CommandLine.arguments.dropFirst().first
do {
let text = try readDiffText(fromFilePath: diffPath)
let parsed = try parseGitDiff(text)
var updatedFiles = 0
for fileChange in parsed.files {
if try applyLineTransformations(filePath: fileChange.filePath, lineNumbers: fileChange.lineNumbers) {
updatedFiles += 1
print("Updated \(fileChange.filePath)")
}
}
print("Done. Updated \(updatedFiles) file(s).")
} catch {
fputs("Error: \(error)\n", stderr)
exit(1)
}
}
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment