native macOS codings agent orchestrator prowl.onev.cat
Something went wrong. Try again.
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580import Foundation
struct GitClient { private struct WorktreeSortEntry { let worktree: Worktree let createdAt: Date let index: Int }
private let shell: ShellClient
private let resolveGit: @Sendable (Bool) async throws -> GitExecutable
nonisolated init( shell: ShellClient = .live, resolveGit: @escaping @Sendable (Bool) async throws -> GitExecutable = { try await GitExecutableResolver.shared.resolve(revalidate: $0) } ) { self.shell = shell self.resolveGit = resolveGit }
nonisolated func repoRoot(for path: URL) async throws -> URL { let normalizedPath = Self.directoryURL(for: path) let git = try await resolveGit(true) do { _ = try await git.run(["rev-parse", "--git-dir"], in: normalizedPath, shell: shell) } catch { if isConfirmedNonRepository(error, at: normalizedPath) { throw GitClientError.notRepository } throw wrapShellError(error, operation: .repoRoot, command: "git rev-parse --git-dir") } let wtURL = try wtScriptURL() let output = try await runBundledWtProcess( operation: .repoRoot, executableURL: wtURL, arguments: ["root"], currentDirectoryURL: normalizedPath ) let trimmed = output.trimmingCharacters(in: .whitespacesAndNewlines) if trimmed.isEmpty { let command = "\(wtURL.lastPathComponent) root" throw GitClientError.commandFailed(command: command, message: "Empty output") } return URL(fileURLWithPath: trimmed).standardizedFileURL }
nonisolated func worktrees(for repoRoot: URL) async throws -> [Worktree] { let repositoryRootURL = repoRoot.standardizedFileURL let output = try await runWtList(repoRoot: repoRoot) let trimmed = output.trimmingCharacters(in: .whitespacesAndNewlines) if trimmed.isEmpty { return [] } let data = Data(trimmed.utf8) let entries = try JSONDecoder().decode([GitWtWorktreeEntry].self, from: data) .filter { !$0.isBare } var seenWorktreeIDs = Set<Worktree.ID>() let worktreeEntries: [WorktreeSortEntry] = entries.enumerated().compactMap { index, entry -> WorktreeSortEntry? in let worktreeURL = URL(fileURLWithPath: entry.path).standardizedFileURL let name = entry.branch.isEmpty ? worktreeURL.lastPathComponent : entry.branch let detail = Self.relativePath(from: repositoryRootURL, to: worktreeURL) let id = worktreeURL.path(percentEncoded: false) guard seenWorktreeIDs.insert(id).inserted else { return nil } let resourceValues = try? worktreeURL.resourceValues(forKeys: [ .creationDateKey, .contentModificationDateKey, ]) let createdAt = resourceValues?.creationDate ?? resourceValues?.contentModificationDate let sortDate = createdAt ?? .distantPast return WorktreeSortEntry( worktree: Worktree( id: id, name: name, detail: detail, workingDirectory: worktreeURL, repositoryRootURL: repositoryRootURL, createdAt: createdAt ), createdAt: sortDate, index: index ) } return worktreeEntries .sorted { lhs, rhs in if lhs.createdAt != rhs.createdAt { return lhs.createdAt > rhs.createdAt } return lhs.index < rhs.index } .map(\.worktree) }
nonisolated func pruneWorktrees(for repoRoot: URL) async throws { let path = repoRoot.path(percentEncoded: false) _ = try await runGit( operation: .worktreePrune, arguments: ["-C", path, "worktree", "prune"] ) }
nonisolated func localBranchNames(for repoRoot: URL) async throws -> Set<String> { let path = repoRoot.path(percentEncoded: false) let output = try await runGit( operation: .branchNames, arguments: [ "-C", path, "for-each-ref", "--format=%(refname:short)", "refs/heads", ] ) let names = output .split(whereSeparator: \.isNewline) .map { String($0).trimmingCharacters(in: .whitespacesAndNewlines).lowercased() } .filter { !$0.isEmpty } return Set(names) }
nonisolated func isValidBranchName(_ branchName: String, for repoRoot: URL) async -> Bool { let path = repoRoot.path(percentEncoded: false) do { _ = try await runGit( operation: .branchNameValidation, arguments: ["-C", path, "check-ref-format", "--branch", branchName] ) return true } catch { return false } }
nonisolated func isBareRepository(for repoRoot: URL) async throws -> Bool { let path = repoRoot.path(percentEncoded: false) let output = try await runGit( operation: .repoIsBare, arguments: ["-C", path, "rev-parse", "--is-bare-repository"] ) return output.trimmingCharacters(in: .whitespacesAndNewlines) == "true" }
nonisolated func branchRefs(for repoRoot: URL) async throws -> [String] { try await branchRefOptions(for: repoRoot).map(\.ref) }
nonisolated func branchRefOptions(for repoRoot: URL) async throws -> [GitBranchRefOption] { let path = repoRoot.path(percentEncoded: false) let localOutput = try await runGit( operation: .branchRefs, arguments: [ "-C", path, "for-each-ref", "--format=%(refname)", "refs/heads", ] ) let remoteOutput = try await runGit( operation: .branchRefs, arguments: [ "-C", path, "for-each-ref", "--format=%(refname)", "refs/remotes", ] ) let localRefs = parseRefLines(localOutput, prefix: "refs/heads/") .sorted { $0.localizedStandardCompare($1) == .orderedAscending } let remoteRefs = parseRefLines(remoteOutput, prefix: "refs/remotes/") // Drop `<remote>/HEAD` symbolic pointers: they resolve to a branch that is // already listed, and selecting one would derive an invalid `HEAD` branch // name in the remote-tracking checkout path. .filter { !$0.hasSuffix("/HEAD") } .sorted { $0.localizedStandardCompare($1) == .orderedAscending } return deduplicatedOptions( localRefs.map { GitBranchRefOption(ref: $0, kind: .local) } + remoteRefs.map { GitBranchRefOption(ref: $0, kind: .remoteTracking) } ) }
nonisolated func remoteBranchRefs(for remoteURL: String) async throws -> GitRemoteBranchRefs { let output = try await runGit( operation: .remoteBranchRefs, arguments: ["ls-remote", "--symref", "--end-of-options", remoteURL, "HEAD", "refs/heads/*"] ) let parsed = parseRemoteBranchRefs(output) let options = parsed.refs .sorted { $0.localizedStandardCompare($1) == .orderedAscending } .map { GitBranchRefOption(ref: "origin/\($0)", kind: .fetchedRemote) } let defaultBaseRef = parsed.defaultBranch.map { "origin/\($0)" } ?? options.first?.ref return GitRemoteBranchRefs(options: deduplicatedOptions(options), defaultBaseRef: defaultBaseRef) }
nonisolated func defaultRemoteBranchRef(for repoRoot: URL) async throws -> String? { let path = repoRoot.path(percentEncoded: false) do { let output = try await runGit( operation: .defaultRemoteBranchRef, arguments: ["-C", path, "symbolic-ref", "-q", "refs/remotes/origin/HEAD"] ) let trimmed = output.trimmingCharacters(in: .whitespacesAndNewlines) if let resolved = normalizeRemoteRef(trimmed), await refExists(resolved, repoRoot: repoRoot) { return resolved } } catch { let rootPath = repoRoot.path(percentEncoded: false) gitLogger.warning( "Default remote branch ref failed for \(rootPath): \(error.localizedDescription)" ) } let fallback = "origin/main" if await refExists(fallback, repoRoot: repoRoot) { return fallback } return nil }
nonisolated func automaticWorktreeBaseRef(for repoRoot: URL) async -> String? { let resolved = try? await defaultRemoteBranchRef(for: repoRoot) if let resolved { return Self.preferredBaseRef(remote: resolved, localHead: nil) } let localHead = try? await localHeadBranchRef(for: repoRoot) let resolvedLocalHead = await resolveLocalHead(localHead, repoRoot: repoRoot) return Self.preferredBaseRef(remote: nil, localHead: resolvedLocalHead) }
nonisolated func ignoredFileCount(for repoRoot: URL) async throws -> Int { let path = repoRoot.path(percentEncoded: false) let output = try await runGit( operation: .ignoredFileCount, arguments: ["-C", path, "ls-files", "--others", "-i", "--exclude-standard"] ) return parseFileListCount(output) }
nonisolated func untrackedFileCount(for repoRoot: URL) async throws -> Int { let path = repoRoot.path(percentEncoded: false) let output = try await runGit( operation: .untrackedFileCount, arguments: ["-C", path, "ls-files", "--others", "--exclude-standard"] ) return parseFileListCount(output) }
nonisolated func createWorktree( named name: String, in repoRoot: URL, baseDirectory: URL, copyFiles: (ignored: Bool, untracked: Bool), baseRef: String ) async throws -> Worktree { var createdWorktree: Worktree? for try await event in createWorktreeStream( GitWorktreeCreateRequest( name: name, repoRoot: repoRoot, baseDirectory: baseDirectory, copyFiles: GitWorktreeCreateRequest.CopyFiles(ignored: copyFiles.ignored, untracked: copyFiles.untracked), baseRef: baseRef ) ) { if case .finished(let worktree) = event { createdWorktree = worktree } } guard let createdWorktree else { let wtURL = try wtScriptURL() let command = ([wtURL.lastPathComponent] + createWorktreeArguments( baseDirectory: baseDirectory, name: name, copyIgnored: copyFiles.ignored, copyUntracked: copyFiles.untracked, baseRef: baseRef )).joined(separator: " ") throw GitClientError.commandFailed(command: command, message: "Empty output") } return createdWorktree }
nonisolated func createWorktreeStream( _ request: GitWorktreeCreateRequest ) -> AsyncThrowingStream<GitWorktreeCreateEvent, Error> { AsyncThrowingStream { continuation in Task { let repositoryRootURL = request.repoRoot.standardizedFileURL do { let wtURL = try wtScriptURL() let arguments = createWorktreeArguments( baseDirectory: request.baseDirectory, name: request.name, copyIgnored: request.copyFiles.ignored, copyUntracked: request.copyFiles.untracked, baseRef: request.baseRef, directoryOverride: request.directoryOverride ) let envURL = URL(fileURLWithPath: "/usr/bin/env") let git = try await resolveGit(false) let localeArguments = git.environmentArguments + ["LC_MESSAGES=C"] let invocationArguments = localeArguments + [wtURL.path(percentEncoded: false)] + arguments let command = ([envURL.path(percentEncoded: false)] + invocationArguments).joined(separator: " ") var pathLine: String? do { for try await streamEvent in shell.runLoginStream( envURL, invocationArguments, request.repoRoot ) { switch streamEvent { case .line(let line): continuation.yield(.outputLine(line)) if line.source == .stdout { let trimmed = line.text.trimmingCharacters(in: .whitespacesAndNewlines) if !trimmed.isEmpty { pathLine = trimmed } } case .finished(let output): if pathLine == nil { pathLine = lastNonEmptyLine(in: output.stdout) } guard let pathLine else { throw GitClientError.commandFailed(command: command, message: "Empty output") } let worktreeURL = URL(fileURLWithPath: pathLine).standardizedFileURL let detail = Self.relativePath(from: repositoryRootURL, to: worktreeURL) let id = worktreeURL.path(percentEncoded: false) let resourceValues = try? worktreeURL.resourceValues(forKeys: [ .creationDateKey, .contentModificationDateKey, ]) let createdAt = resourceValues?.creationDate ?? resourceValues?.contentModificationDate let worktree = Worktree( id: id, name: request.name, detail: detail, workingDirectory: worktreeURL, repositoryRootURL: repositoryRootURL, createdAt: createdAt ) continuation.yield(.finished(worktree)) continuation.finish() return } } continuation.finish(throwing: GitClientError.commandFailed(command: command, message: "Empty output")) } catch { if let gitError = error as? GitClientError { continuation.finish(throwing: gitError) } else { continuation.finish( throwing: wrapShellError(error, operation: .worktreeCreate, command: command) ) } } } catch { continuation.finish(throwing: error) } } } }
nonisolated private func createWorktreeArguments( baseDirectory: URL, name: String, copyIgnored: Bool, copyUntracked: Bool, baseRef: String, directoryOverride: URL? = nil ) -> [String] { var arguments = ["--base-dir", baseDirectory.path(percentEncoded: false), "sw"] if copyIgnored { arguments.append("--copy-ignored") } if copyUntracked { arguments.append("--copy-untracked") } if !baseRef.isEmpty { arguments.append("--from") arguments.append(baseRef) } if let directoryOverride { arguments.append("--path") arguments.append(directoryOverride.path(percentEncoded: false)) } if copyIgnored || copyUntracked { arguments.append("--verbose") } arguments.append(name) return arguments }
nonisolated func renameBranch(in worktreeURL: URL, to branchName: String) async throws { let path = worktreeURL.path(percentEncoded: false) _ = try await runGit( operation: .branchRename, arguments: ["-C", path, "branch", "-m", branchName] ) }
nonisolated func branchName(for worktreeURL: URL) async -> String? { let headURL = await MainActor.run { GitWorktreeHeadResolver.headURL( for: worktreeURL, fileManager: .default ) } guard let headURL else { return nil } guard let line = try? String(contentsOf: headURL, encoding: .utf8) .split(whereSeparator: \.isNewline) .first else { return nil } let trimmed = line.trimmingCharacters(in: .whitespacesAndNewlines) let refPrefix = "ref:" if trimmed.hasPrefix(refPrefix) { let ref = trimmed.dropFirst(refPrefix.count).trimmingCharacters(in: .whitespaces) let headsPrefix = "refs/heads/" if ref.hasPrefix(headsPrefix) { return String(ref.dropFirst(headsPrefix.count)) } return String(ref) } return "HEAD" }
nonisolated func lineChanges(at worktreeURL: URL) async -> GitLineChanges? { if await isWorktreeIndexLocked(worktreeURL) { return nil } let path = worktreeURL.path(percentEncoded: false) do { async let diffOutput = runGit( operation: .lineChanges, arguments: ["-C", path, "diff", "HEAD", "--shortstat"] ) async let untrackedOutput = runGit( operation: .untrackedFilePaths, arguments: ["-C", path, "-c", "core.quotePath=false", "ls-files", "--others", "--exclude-standard", "-z"] ) let tracked = parseShortstat(try await diffOutput) let untrackedPaths = parseNULFileList(try await untrackedOutput) let untracked = Self.countLinesInFiles(untrackedPaths, relativeTo: worktreeURL) return GitLineChanges( added: tracked.added + untracked.lines, removed: tracked.removed, skippedUntrackedFileCount: untracked.skippedFileCount ) } catch { return nil } }
nonisolated static func indexEntryCount(at worktreeURL: URL) -> Int? { let gitDir = resolveGitDirectory(for: worktreeURL) guard let gitDir else { return nil } let indexURL = gitDir.appending(path: "index") guard let handle = try? FileHandle(forReadingFrom: indexURL) else { return nil } defer { try? handle.close() } guard let header = try? handle.read(upToCount: 12), header.count == 12 else { return nil } guard header.prefix(4).elementsEqual("DIRC".utf8) else { return nil } let version = Self.bigEndianUInt32(from: header, offset: 4) guard (2...4).contains(version) else { return nil } return Int(Self.bigEndianUInt32(from: header, offset: 8)) }
nonisolated static let untrackedLineCountByteBudget = 32 * 1_024 * 1_024 nonisolated static let untrackedLineCountCacheUpdateBatchSize = 256
nonisolated static func countLinesInFiles( _ relativePaths: [String], relativeTo base: URL, cache: UntrackedLineCountCache = .shared, byteBudget: Int = untrackedLineCountByteBudget ) -> UntrackedLineCountResult { struct FileToCount { let relativePath: String let url: URL let fingerprint: UntrackedLineFileFingerprint }
let resourceKeys: Set<URLResourceKey> = [ .contentModificationDateKey, .fileResourceIdentifierKey, .fileSizeKey, ] var skippedFileCount = 0 let files = relativePaths.compactMap { relativePath -> FileToCount? in let fileURL = base.appending(path: relativePath) guard let values = try? fileURL.resourceValues(forKeys: resourceKeys), let byteCount = values.fileSize, let modificationDate = values.contentModificationDate else { if FileManager.default.fileExists(atPath: fileURL.path(percentEncoded: false)) { skippedFileCount += 1 } return nil } return FileToCount( relativePath: relativePath, url: fileURL, fingerprint: UntrackedLineFileFingerprint( byteCount: byteCount, modificationDate: modificationDate, resourceIdentifier: values.fileResourceIdentifier.map { String(describing: $0) } ) ) } let worktreeKey = base.standardizedFileURL.path(percentEncoded: false) let cacheFiles = files.map { UntrackedLineCacheFile(relativePath: $0.relativePath, fingerprint: $0.fingerprint) } let cachedValues = cache.cachedValues(for: cacheFiles, worktreeKey: worktreeKey)
var total = 0 var misses: [FileToCount] = [] for file in files { switch cachedValues[file.relativePath] { case .text(let lines): total += lines case .binary: break case nil: misses.append(file) } }
var remainingByteBudget = max(0, byteBudget) var cacheUpdates: [UntrackedLineCacheUpdate] = [] cacheUpdates.reserveCapacity(untrackedLineCountCacheUpdateBatchSize) for file in misses.sorted(by: { lhs, rhs in if lhs.fingerprint.byteCount == rhs.fingerprint.byteCount { return lhs.relativePath < rhs.relativePath } return lhs.fingerprint.byteCount < rhs.fingerprint.byteCount }) { guard file.fingerprint.byteCount <= remainingByteBudget else { skippedFileCount += 1 continue } let cacheUpdate: UntrackedLineCacheUpdate? switch Self.countLines(in: file.url, maximumByteCount: remainingByteBudget) { case .text(let lines, let bytesRead): total += lines remainingByteBudget -= bytesRead cacheUpdate = UntrackedLineCacheUpdate( relativePath: file.relativePath, fingerprint: file.fingerprint, value: .text(lines) ) case .binary(let bytesRead): remainingByteBudget -= bytesRead cacheUpdate = UntrackedLineCacheUpdate( relativePath: file.relativePath, fingerprint: file.fingerprint, value: .binary ) case .budgetExceeded: remainingByteBudget = 0 skippedFileCount += 1 cacheUpdate = nil case .unavailable(let bytesRead): remainingByteBudget -= bytesRead skippedFileCount += 1 cacheUpdate = nil } if let cacheUpdate { cacheUpdates.append(cacheUpdate) if cacheUpdates.count == untrackedLineCountCacheUpdateBatchSize { cache.store(cacheUpdates, worktreeKey: worktreeKey) cacheUpdates.removeAll(keepingCapacity: true) } } } cache.store(cacheUpdates, worktreeKey: worktreeKey) return UntrackedLineCountResult(lines: total, skippedFileCount: skippedFileCount) }
nonisolated private static func bigEndianUInt32(from data: Data, offset: Int) -> UInt32 { data[offset..<(offset + 4)].reduce(UInt32(0)) { ($0 << 8) | UInt32($1) } }
nonisolated private enum FileLineCountResult { case text(lines: Int, bytesRead: Int) case binary(bytesRead: Int) case budgetExceeded case unavailable(bytesRead: Int) }
nonisolated private static func countLines( in fileURL: URL, maximumByteCount: Int ) -> FileLineCountResult { guard let handle = try? FileHandle(forReadingFrom: fileURL) else { return .unavailable(bytesRead: 0) } defer { try? handle.close() }
let binaryProbeByteCount = 8_192 let chunkByteCount = 64 * 1_024 var bytesRead = 0 var probedByteCount = 0 var lineCount = 0 var isEmpty = true var lastByte: UInt8?
while true { let chunk: Data do { // The extra byte distinguishes a file exactly at the budget from one // that grew after its metadata fingerprint was collected. let allowedReadCount = min(chunkByteCount, maximumByteCount - bytesRead + 1) guard allowedReadCount > 0 else { return .budgetExceeded } guard let readChunk = try handle.read(upToCount: allowedReadCount) else { break } chunk = readChunk } catch { return .unavailable(bytesRead: bytesRead) } guard !chunk.isEmpty else { break } bytesRead += chunk.count guard bytesRead <= maximumByteCount else { return .budgetExceeded }
isEmpty = false if probedByteCount < binaryProbeByteCount { let remainingProbeCount = binaryProbeByteCount - probedByteCount let probe = chunk.prefix(remainingProbeCount) if probe.containsByte(0x00) { return .binary(bytesRead: bytesRead) } probedByteCount += probe.count } lineCount += chunk.countOccurrences(of: 0x0A) lastByte = chunk.last }
if !isEmpty, lastByte != 0x0A { lineCount += 1 } return .text(lines: lineCount, bytesRead: bytesRead) }
nonisolated private static func resolveGitDirectory(for worktreeURL: URL) -> URL? { let gitURL = worktreeURL.appending(path: ".git") var isDirectory = ObjCBool(false) guard FileManager.default.fileExists( atPath: gitURL.path(percentEncoded: false), isDirectory: &isDirectory ) else { return nil } if isDirectory.boolValue { return gitURL } guard let contents = try? String(contentsOf: gitURL, encoding: .utf8), let line = contents.split(whereSeparator: \.isNewline).first else { return nil } let trimmed = line.trimmingCharacters(in: .whitespacesAndNewlines) guard trimmed.hasPrefix("gitdir:") else { return nil } let pathPart = trimmed.dropFirst("gitdir:".count).trimmingCharacters(in: .whitespacesAndNewlines) guard !pathPart.isEmpty else { return nil } return URL(fileURLWithPath: String(pathPart), relativeTo: worktreeURL).standardizedFileURL }
nonisolated private func isWorktreeIndexLocked(_ worktreeURL: URL) async -> Bool { let headURL = await MainActor.run { GitWorktreeHeadResolver.headURL( for: worktreeURL, fileManager: .default ) } guard let headURL else { return false } let gitDirectory = headURL.deletingLastPathComponent() let lockURL = gitDirectory.appending(path: "index.lock") return FileManager.default.fileExists(atPath: lockURL.path(percentEncoded: false)) }
nonisolated func diffNameStatus(at worktreeURL: URL) async -> String { let path = worktreeURL.path(percentEncoded: false) do { return try await runGit( operation: .diffNameStatus, arguments: ["-C", path, "-c", "core.quotePath=false", "diff", "HEAD", "--name-status"] ) } catch { return "" } }
/// Resolves the base an outgoing comparison should diff against. The ladder /// advances only when a source is absent; a source that is present but fails /// to resolve throws its own `OutgoingBaseResolutionError` instead of /// cascading, so explicit intent (a pull request, a configured base) is /// never silently replaced by a guess. nonisolated func outgoingBaseResolution( pullRequest: GitPullRequestBase?, configuredBaseRef: String?, in worktreeURL: URL ) async throws -> OutgoingBaseResolution { if let pullRequest { return try await pullRequestBaseResolution(pullRequest, in: worktreeURL) } let configured = configuredBaseRef?.trimmingCharacters(in: .whitespacesAndNewlines) ?? "" if !configured.isEmpty { guard let qualified = await qualifiedBaseRef(named: configured, repoRoot: worktreeURL) else { throw OutgoingBaseResolutionError.unresolvedRepositorySettingBase(configured) } return OutgoingBaseResolution( ref: qualified.ref, displayName: qualified.displayName, source: .repositorySetting ) } if let automatic = await automaticWorktreeBaseRef(for: worktreeURL), let qualified = await qualifiedBaseRef(named: automatic, repoRoot: worktreeURL) { return OutgoingBaseResolution( ref: qualified.ref, displayName: qualified.displayName, source: .automatic ) } throw OutgoingBaseResolutionError.noResolvableBase }
nonisolated private func pullRequestBaseResolution( _ pullRequest: GitPullRequestBase, in worktreeURL: URL ) async throws -> OutgoingBaseResolution { let branch = pullRequest.baseRefName.trimmingCharacters(in: .whitespacesAndNewlines) guard !branch.isEmpty else { throw OutgoingBaseResolutionError.incompletePullRequest } guard let pullRequestRepository = Self.pullRequestRepositoryWebInfo(pullRequest.url) else { throw OutgoingBaseResolutionError.invalidPullRequestURL(pullRequest.url) } let matchingRemotes = await remoteWebCandidates(for: worktreeURL) .filter { Self.matches($0.info, pullRequestRepository) } .map(\.name) guard !matchingRemotes.isEmpty else { throw OutgoingBaseResolutionError.noMatchingRemote( host: pullRequestRepository.host, repositoryPath: pullRequestRepository.repositoryPath ) } guard matchingRemotes.count == 1, let remote = matchingRemotes.first else { throw OutgoingBaseResolutionError.multipleMatchingRemotes(matchingRemotes.sorted()) } let qualifiedRef = "refs/remotes/\(remote)/\(branch)" guard await refExists(qualifiedRef, repoRoot: worktreeURL) else { throw OutgoingBaseResolutionError.unresolvedPullRequestBase(remote: remote, branch: branch) } return OutgoingBaseResolution( ref: qualifiedRef, displayName: "\(remote)/\(branch)", source: .pullRequest ) }
/// Qualifies a user- or heuristic-provided base name (`origin/main`, `main`) /// into a full ref, preferring the remote-tracking namespace. Names that are /// already fully qualified are verified as-is. nonisolated private func qualifiedBaseRef( named name: String, repoRoot: URL ) async -> (ref: String, displayName: String)? { if name.hasPrefix("refs/") { guard await refExists(name, repoRoot: repoRoot) else { return nil } let displayName = name .replacing(/^refs\/(remotes|heads)\//, with: "", maxReplacements: 1) return (name, displayName) } let remoteRef = "refs/remotes/\(name)" if await refExists(remoteRef, repoRoot: repoRoot) { return (remoteRef, name) } let headRef = "refs/heads/\(name)" if await refExists(headRef, repoRoot: repoRoot) { return (headRef, name) } return nil }
/// Captures the immutable Git revisions used by a three-dot pull request /// comparison. The merge base is the left side; the current HEAD is the /// right side. Capturing both keeps the file list and all file documents /// internally consistent even if refs move while the window is loading. nonisolated func outgoingChangesComparison( base: OutgoingBaseResolution, at worktreeURL: URL ) async throws -> GitOutgoingChangesComparison { let path = worktreeURL.path(percentEncoded: false) let head = try await runGit( operation: .outgoingChangesComparison, arguments: ["-C", path, "rev-parse", "--verify", "HEAD"] ).trimmingCharacters(in: .whitespacesAndNewlines) guard !head.isEmpty else { throw GitClientError.commandFailed(command: "git rev-parse --verify HEAD", message: "Empty output") } let mergeBase = try await runGit( operation: .outgoingChangesComparison, arguments: ["-C", path, "merge-base", base.ref, head] ).trimmingCharacters(in: .whitespacesAndNewlines) guard !mergeBase.isEmpty else { throw GitClientError.commandFailed(command: "git merge-base \(base.ref) \(head)", message: "Empty output") } return GitOutgoingChangesComparison(base: base, mergeBase: mergeBase, head: head) }
/// Lists committed files in a captured pull-request comparison. Unlike /// `diffNameStatus(at:)`, errors deliberately propagate so callers never /// mistake a missing base for an empty outgoing diff. nonisolated func outgoingDiffNameStatus( for comparison: GitOutgoingChangesComparison, at worktreeURL: URL ) async throws -> String { let path = worktreeURL.path(percentEncoded: false) return try await runGit( operation: .outgoingDiffNameStatus, arguments: [ "-C", path, "-c", "core.quotePath=false", "diff", "--name-status", comparison.mergeBase, comparison.head, ] ) }
nonisolated func untrackedFilePaths(at worktreeURL: URL) async -> [String] { let path = worktreeURL.path(percentEncoded: false) do { let output = try await runGit( operation: .untrackedFilePaths, arguments: ["-C", path, "-c", "core.quotePath=false", "ls-files", "--others", "--exclude-standard"] ) return output .split(whereSeparator: \.isNewline) .map { String($0).trimmingCharacters(in: .whitespacesAndNewlines) } .filter { !$0.isEmpty } } catch { return [] } }
nonisolated func showFileAtHEAD(_ relativePath: String, in worktreeURL: URL) async -> String? { await showFile(relativePath, at: "HEAD", in: worktreeURL) }
nonisolated func showFile(_ relativePath: String, at revision: String, in worktreeURL: URL) async -> String? { let path = worktreeURL.path(percentEncoded: false) do { return try await runGit( operation: .showFile, arguments: ["-C", path, "show", "\(revision):\(relativePath)"] ) } catch { return nil } }
nonisolated func repositoryWebURL(for repositoryRoot: URL) async -> URL? { await remoteWebInfo(for: repositoryRoot)?.repositoryURL }
nonisolated func githubRemoteInfos(for repositoryRoot: URL) async -> [GithubRemoteInfo] { let candidates = await remoteWebCandidates(for: repositoryRoot).compactMap { candidate -> ( name: String, info: GithubRemoteInfo )? in guard let info = Self.parseGithubRemoteInfo(candidate.info) else { return nil } return (name: candidate.name, info: info) } return Self.prioritizedGithubRemoteInfos(candidates) }
nonisolated func remoteInfo(for repositoryRoot: URL) async -> GithubRemoteInfo? { guard let remoteWebInfo = await remoteWebInfo(for: repositoryRoot) else { return nil } return Self.parseGithubRemoteInfo(remoteWebInfo) }
nonisolated private func remoteWebInfo(for repositoryRoot: URL) async -> GitRemoteWebInfo? { let candidates = await remoteWebCandidates(for: repositoryRoot) return Self.originFirstRemoteWebCandidates(candidates).first?.info }
nonisolated private func remoteWebCandidates( for repositoryRoot: URL ) async -> [(name: String, info: GitRemoteWebInfo)] { let path = repositoryRoot.path(percentEncoded: false) guard let remotesOutput = try? await runGit( operation: .remoteInfo, arguments: ["-C", path, "remote"] ) else { return [] } let remotes = remotesOutput .split(whereSeparator: \.isNewline) .map { String($0).trimmingCharacters(in: .whitespacesAndNewlines) } .filter { !$0.isEmpty } var candidates: [(name: String, info: GitRemoteWebInfo)] = [] for remote in remotes { guard let remoteURL = try? await runGit( operation: .remoteInfo, arguments: ["-C", path, "remote", "get-url", remote] ) else { continue } if let info = Self.parseRepositoryWebInfo(remoteURL) { candidates.append((name: remote, info: info)) } } return candidates }
nonisolated private static func originFirstRemoteWebCandidates( _ candidates: [(name: String, info: GitRemoteWebInfo)] ) -> [(name: String, info: GitRemoteWebInfo)] { guard candidates.contains(where: { $0.name == "origin" }) else { return candidates } return candidates.filter { $0.name == "origin" } + candidates.filter { $0.name != "origin" } }
nonisolated private static func pullRequestRepositoryWebInfo(_ pullRequestURL: String) -> GitRemoteWebInfo? { guard let pullRequestInfo = parseRepositoryWebInfo(pullRequestURL) else { return nil } let components = pullRequestInfo.repositoryPath.split(separator: "/", omittingEmptySubsequences: true) guard components.count >= 4, components[2].caseInsensitiveCompare("pull") == .orderedSame else { return nil } return GitRemoteWebInfo( host: pullRequestInfo.host, repositoryPath: "\(components[0])/\(components[1])", port: pullRequestInfo.port ) }
nonisolated private static func matches(_ lhs: GitRemoteWebInfo, _ rhs: GitRemoteWebInfo) -> Bool { // Pull request URLs use the web transport while remotes commonly use SSH; // their ports do not identify different repositories. lhs.host.caseInsensitiveCompare(rhs.host) == .orderedSame && lhs.repositoryPath.caseInsensitiveCompare(rhs.repositoryPath) == .orderedSame }
nonisolated static func prioritizedGithubRemoteInfos( _ candidates: [(name: String, info: GithubRemoteInfo)] ) -> [GithubRemoteInfo] { var seen = Set<String>() return candidates .enumerated() .sorted { lhs, rhs in let lhsPriority = githubPullRequestRemotePriority(lhs.element.name) let rhsPriority = githubPullRequestRemotePriority(rhs.element.name) if lhsPriority != rhsPriority { return lhsPriority < rhsPriority } let nameComparison = lhs.element.name.localizedStandardCompare(rhs.element.name) if nameComparison != .orderedSame { return nameComparison == .orderedAscending } return lhs.offset < rhs.offset } .compactMap { entry in let info = entry.element.info let key = [ info.host.lowercased(), info.owner.lowercased(), info.repo.lowercased(), ].joined(separator: "/") guard seen.insert(key).inserted else { return nil } return info } }
nonisolated private static func githubPullRequestRemotePriority(_ name: String) -> Int { switch name.lowercased() { case "origin": 0 case "upstream": 1 default: 2 } }
nonisolated func remoteNames(for repoRoot: URL) async throws -> [String] { let path = repoRoot.path(percentEncoded: false) let output = try await runGit( operation: .remoteList, arguments: ["-C", path, "remote"] ) return output .split(whereSeparator: \.isNewline) .map { String($0).trimmingCharacters(in: .whitespacesAndNewlines) } .filter { !$0.isEmpty } }
nonisolated func fetchRemote(_ remote: String, for repoRoot: URL) async throws { let path = repoRoot.path(percentEncoded: false) _ = try await runGit( operation: .fetchRemote, arguments: ["-C", path, "fetch", remote] ) }
nonisolated func removeWorktree(_ worktree: Worktree, deleteBranch: Bool) async throws -> URL { let rootPath = worktree.repositoryRootURL.path(percentEncoded: false) let worktreeURL = worktree.workingDirectory.standardizedFileURL let worktreePath = Self.canonicalWorktreePath(worktreeURL.path(percentEncoded: false)) let initialWorktreePaths = try await registeredWorktreePaths(rootPath: rootPath) guard initialWorktreePaths.contains(worktreePath) else { throw GitClientError.worktreeNotRegistered( path: worktree.workingDirectory.path(percentEncoded: false) ) } let relocatedURL = Self.worktreeDirectoryHasGitMetadata(worktreeURL) ? Self.relocateWorktreeDirectory(worktreeURL) : nil if let relocatedURL { do { _ = try? await runGit( operation: .worktreePrune, arguments: ["-C", rootPath, "worktree", "prune", "--expire=now"] ) let remainingWorktreePaths = try await registeredWorktreePaths(rootPath: rootPath) if remainingWorktreePaths.contains(worktreePath) { try await runGitWorktreeRemove(rootPath: rootPath, worktreePath: worktreePath) } } catch { let removalError = error do { try FileManager.default.moveItem(at: relocatedURL, to: worktreeURL) } catch { throw GitClientError.worktreeRecoveryFailed( path: worktreeURL.path(percentEncoded: false), removalError: removalError.localizedDescription, recoveryError: error.localizedDescription ) } throw removalError } if deleteBranch { _ = try? await deleteLocalBranch( named: worktree.name, for: worktree.repositoryRootURL, force: false ) } Task.detached { try? FileManager.default.removeItem(at: relocatedURL) } return worktree.workingDirectory } try await runGitWorktreeRemove(rootPath: rootPath, worktreePath: worktreePath) if deleteBranch { _ = try? await deleteLocalBranch( named: worktree.name, for: worktree.repositoryRootURL, force: false ) } return worktree.workingDirectory }
nonisolated private func registeredWorktreePaths(rootPath: String) async throws -> Set<String> { let output = try await runGit( operation: .worktreeList, arguments: ["-C", rootPath, "worktree", "list", "--porcelain"] ) // `git worktree list --porcelain` reports the raw on-disk path (e.g. `/private/tmp/foo`), // while `worktrees(for:)` stores `standardizedFileURL` paths (which resolve `/private` // symlinks to `/tmp`). Canonicalize both sides identically so the removal guard matches // externally-created worktrees living under symlinked roots like /tmp or /var. return Set(Self.parseGitWorktreePorcelainPaths(output).map(Self.canonicalWorktreePath)) }
nonisolated func deleteLocalBranch( named branchName: String, for repoRoot: URL, force: Bool ) async throws -> LocalBranchDeletionOutcome { guard !branchName.isEmpty else { return .notRequested } let rootPath = repoRoot.path(percentEncoded: false) let normalizedName = branchName.lowercased() let names = try await localBranchNames(for: repoRoot) guard names.contains(normalizedName) else { return .notFound } let protectedNames = await protectedLocalBranchNames(for: repoRoot) guard !protectedNames.contains(normalizedName) else { return .protected } _ = try await runGit( operation: .branchDelete, arguments: ["-C", rootPath, "branch", force ? "-D" : "-d", branchName] ) return .deleted }
nonisolated private func protectedLocalBranchNames(for repoRoot: URL) async -> Set<String> { var names: Set<String> = ["main", "master"] if let defaultRef = try? await defaultRemoteBranchRef(for: repoRoot), let defaultBranchName = Self.localBranchName(fromRef: defaultRef) { names.insert(defaultBranchName.lowercased()) } return names }
nonisolated private static func localBranchName(fromRef ref: String) -> String? { let trimmed = ref.trimmingCharacters(in: .whitespacesAndNewlines) guard !trimmed.isEmpty else { return nil } guard let slashIndex = trimmed.firstIndex(of: "/") else { return trimmed } let name = trimmed[trimmed.index(after: slashIndex)...] return name.isEmpty ? nil : String(name) }
nonisolated private func parseShortstat(_ output: String) -> (added: Int, removed: Int) { let trimmed = output.trimmingCharacters(in: .whitespacesAndNewlines) guard !trimmed.isEmpty else { return (0, 0) } var added = 0 var removed = 0 if let match = trimmed.firstMatch(of: /(\d+)\s+insertions?\(\+\)/) { added = Int(match.1) ?? 0 } if let match = trimmed.firstMatch(of: /(\d+)\s+deletions?\(-\)/) { removed = Int(match.1) ?? 0 } return (added, removed) }
nonisolated private func parseFileList(_ output: String) -> [String] { output .split(whereSeparator: \.isNewline) .map { String($0).trimmingCharacters(in: .whitespacesAndNewlines) } .filter { !$0.isEmpty } }
nonisolated private func parseNULFileList(_ output: String) -> [String] { output .split(separator: "\0", omittingEmptySubsequences: true) .map(String.init) }
nonisolated private func parseFileListCount(_ output: String) -> Int { parseFileList(output).count }
nonisolated private func lastNonEmptyLine(in output: String) -> String? { output .split(whereSeparator: \.isNewline) .map { String($0).trimmingCharacters(in: .whitespacesAndNewlines) } .last { !$0.isEmpty } }
nonisolated private func parseRefLines(_ output: String, prefix: String) -> [String] { output .split(whereSeparator: \.isNewline) .compactMap { line -> String? in let ref = String(line).trimmingCharacters(in: .whitespacesAndNewlines) guard !ref.isEmpty else { return nil } return ref.hasPrefix(prefix) ? String(ref.dropFirst(prefix.count)) : ref } }
nonisolated private func parseRemoteBranchRefs(_ output: String) -> (refs: [String], defaultBranch: String?) { var refs: [String] = [] var defaultBranch: String? for rawLine in output.split(whereSeparator: \.isNewline) { let line = String(rawLine).trimmingCharacters(in: .whitespacesAndNewlines) if line.hasPrefix("ref:") { let parts = line.split(separator: "\t", omittingEmptySubsequences: false) guard parts.count >= 2, parts[1] == "HEAD" else { continue } let target = parts[0] .dropFirst("ref:".count) .trimmingCharacters(in: .whitespacesAndNewlines) if let branch = normalizeHeadRef(target) { defaultBranch = branch } continue } let parts = line.split(separator: "\t", omittingEmptySubsequences: false) guard parts.count >= 2, let branch = normalizeHeadRef(String(parts[1])) else { continue } refs.append(branch) } return (deduplicated(refs), defaultBranch) }
nonisolated private func normalizeHeadRef(_ value: String) -> String? { let trimmed = value.trimmingCharacters(in: .whitespacesAndNewlines) let prefix = "refs/heads/" guard trimmed.hasPrefix(prefix) else { return nil } let branch = String(trimmed.dropFirst(prefix.count)) return branch.isEmpty ? nil : branch }
nonisolated private func deduplicated(_ values: [String]) -> [String] { var seen = Set<String>() return values.filter { seen.insert($0).inserted } }
nonisolated private func deduplicatedOptions(_ options: [GitBranchRefOption]) -> [GitBranchRefOption] { var seen = Set<String>() return options.filter { seen.insert($0.id).inserted } }
nonisolated private func normalizeRemoteRef(_ value: String) -> String? { let trimmed = value.trimmingCharacters(in: .whitespacesAndNewlines) guard !trimmed.isEmpty else { return nil } let prefix = "refs/remotes/" if trimmed.hasPrefix(prefix) { return String(trimmed.dropFirst(prefix.count)) } return trimmed }
nonisolated private func localHeadBranchRef(for repoRoot: URL) async throws -> String? { let path = repoRoot.path(percentEncoded: false) let output = try await runGit( operation: .localHeadRef, arguments: ["-C", path, "symbolic-ref", "--short", "HEAD"] ) let trimmed = output.trimmingCharacters(in: .whitespacesAndNewlines) return trimmed.isEmpty ? nil : trimmed }
nonisolated private func resolveLocalHead(_ localHead: String?, repoRoot: URL) async -> String? { guard let localHead else { return nil } if await refExists(localHead, repoRoot: repoRoot) { return localHead } return nil }
nonisolated static func preferredBaseRef(remote: String?, localHead: String?) -> String? { remote ?? localHead }
nonisolated private func refExists(_ ref: String, repoRoot: URL) async -> Bool { let path = repoRoot.path(percentEncoded: false) do { _ = try await runGit( operation: .defaultRemoteBranchRef, arguments: ["-C", path, "rev-parse", "--verify", "--quiet", ref] ) return true } catch { return false } }
nonisolated private func runGit( operation: GitOperation, arguments: [String] ) async throws -> String { let git = try await resolveGit(false) let command = ([git.url.path(percentEncoded: false)] + arguments).joined(separator: " ") do { return try await git.run(arguments, shell: shell).stdout } catch { throw wrapShellError(error, operation: operation, command: command) } }
nonisolated private func runWtList(repoRoot: URL) async throws -> String { let wtURL = try wtScriptURL() let arguments = ["ls", "--json"] return try await runBundledWtProcess( operation: .worktreeList, executableURL: wtURL, arguments: arguments, currentDirectoryURL: repoRoot ) }
nonisolated private func wtScriptURL() throws -> URL { guard let url = Bundle.main.url(forResource: "wt", withExtension: nil, subdirectory: "git-wt") else { fatalError("Bundled wt script not found") } return url }
nonisolated private func runBundledWtProcess( operation: GitOperation, executableURL: URL, arguments: [String], currentDirectoryURL: URL? ) async throws -> String { let git = try await resolveGit(false) let command = ([executableURL.path(percentEncoded: false)] + arguments).joined(separator: " ") do { return try await shell.run( URL(fileURLWithPath: "/usr/bin/env"), git.environmentArguments + [executableURL.path(percentEncoded: false)] + arguments, currentDirectoryURL ).stdout } catch { throw wrapShellError(error, operation: operation, command: command) } }
nonisolated private static func relativePath(from base: URL, to target: URL) -> String { let baseComponents = base.standardizedFileURL.pathComponents let targetComponents = target.standardizedFileURL.pathComponents var index = 0 while index < min(baseComponents.count, targetComponents.count), baseComponents[index] == targetComponents[index] { index += 1 } var result: [String] = [] if index < baseComponents.count { result.append(contentsOf: Array(repeating: "..", count: baseComponents.count - index)) } if index < targetComponents.count { result.append(contentsOf: targetComponents[index...]) } if result.isEmpty { return "." } return result.joined(separator: "/") }
nonisolated private static func directoryURL(for path: URL) -> URL { if path.hasDirectoryPath { return path } return path.deletingLastPathComponent() }
nonisolated private func runGitWorktreeRemove( rootPath: String, worktreePath: String ) async throws { _ = try await runGit( operation: .worktreeRemove, arguments: [ "-C", rootPath, "worktree", "remove", "--force", worktreePath, ] ) }
nonisolated private static func relocateWorktreeDirectory(_ worktreeURL: URL) -> URL? { let fileManager = FileManager.default let worktreePath = worktreeURL.path(percentEncoded: false) guard fileManager.fileExists(atPath: worktreePath) else { return nil } let candidates = [ URL(filePath: "/tmp", directoryHint: .isDirectory), fileManager.temporaryDirectory, ] for baseURL in candidates { let trashBaseURL = baseURL.appending( path: "prowl-worktree-trash", directoryHint: URL.DirectoryHint.isDirectory ) do { try fileManager.createDirectory(at: trashBaseURL, withIntermediateDirectories: true) } catch { continue } let destinationURL = trashBaseURL.appending( path: "\(worktreeURL.lastPathComponent)-\(UUID().uuidString)", directoryHint: URL.DirectoryHint.isDirectory ) do { try fileManager.moveItem(at: worktreeURL, to: destinationURL) return destinationURL } catch { continue } } return nil }
nonisolated private static func worktreeDirectoryHasGitMetadata(_ worktreeURL: URL) -> Bool { let gitMetadataURL = worktreeURL.appending(path: ".git") return FileManager.default.fileExists(atPath: gitMetadataURL.path(percentEncoded: false)) }
/// Resolves filesystem symlinks and normalizes a worktree path so Git's raw on-disk paths /// compare equal to the URLs Prowl tracks internally. nonisolated static func canonicalWorktreePath(_ path: String) -> String { URL(fileURLWithPath: path) .standardizedFileURL .resolvingSymlinksInPath() .standardizedFileURL .path(percentEncoded: false) }
nonisolated static func parseGitWorktreePorcelainPaths(_ output: String) -> Set<String> { Set( output .split(whereSeparator: \.isNewline) .compactMap { line -> String? in let prefix = "worktree " guard line.hasPrefix(prefix) else { return nil } return String(line.dropFirst(prefix.count)) .trimmingCharacters(in: .whitespacesAndNewlines) } .filter { !$0.isEmpty } ) }
nonisolated static func parseRepositoryWebInfo(_ remoteURL: String) -> GitRemoteWebInfo? { let trimmed = remoteURL.trimmingCharacters(in: .whitespacesAndNewlines) guard !trimmed.isEmpty else { return nil } if trimmed.hasPrefix("git@") { let parts = trimmed.split(separator: "@", maxSplits: 1, omittingEmptySubsequences: true) guard parts.count == 2 else { return nil } let hostAndPath = parts[1] let hostParts = hostAndPath.split(separator: ":", maxSplits: 1, omittingEmptySubsequences: true) guard hostParts.count == 2 else { return nil } return parseRepositoryWebInfo(host: String(hostParts[0]), port: nil, path: String(hostParts[1])) } guard let url = URL(string: trimmed), let host = url.host else { return nil } let path = url.path.trimmingCharacters(in: CharacterSet(charactersIn: "/")) return parseRepositoryWebInfo(host: host, port: url.port, path: path) }
nonisolated static func parseGithubRemoteInfo(_ remoteURL: String) -> GithubRemoteInfo? { guard let remoteWebInfo = parseRepositoryWebInfo(remoteURL) else { return nil } return parseGithubRemoteInfo(remoteWebInfo) }
nonisolated private static func parseRepositoryWebInfo( host: String, port: Int?, path: String ) -> GitRemoteWebInfo? { let components = path.split(separator: "/", omittingEmptySubsequences: true) guard components.count >= 2 else { return nil } var repositoryPath = components.map(String.init).joined(separator: "/") if repositoryPath.hasSuffix(".git") { repositoryPath = String(repositoryPath.dropLast(4)) } guard !repositoryPath.isEmpty else { return nil } return GitRemoteWebInfo(host: host, repositoryPath: repositoryPath, port: port) }
nonisolated private static func parseGithubRemoteInfo(_ remoteWebInfo: GitRemoteWebInfo) -> GithubRemoteInfo? { let normalizedHost = remoteWebInfo.host.lowercased() guard normalizedHost.contains("github") else { return nil } let components = remoteWebInfo.repositoryPath.split(separator: "/", omittingEmptySubsequences: true) guard components.count >= 2 else { return nil } let owner = String(components[0]) let repo = String(components[1]) guard !owner.isEmpty, !repo.isEmpty else { return nil } return GithubRemoteInfo(host: remoteWebInfo.host, owner: owner, repo: repo) }
}
/// Byte scans over `Data`'s contiguous storage.////// `Data` conforms to `Sequence`, so `reduce` and `contains` walk it through/// `Data.Iterator` with a value-witness call per byte. Sampling a running instance/// for 300 s attributed roughly one whole core to exactly that path inside/// `countLines` — 31% in `Sequence.reduce`, 30% in `Data.Iterator.next`, 22% in/// value witnesses — about 72% of everything the process was burning, while/// counting lines in untracked files. `memchr` scans sparse matches in vectorized/// segments; a raw-pointer fallback bounds the cost when matches are dense.extension Data { /// Occurrences of `byte` in the whole buffer. fileprivate nonisolated func countOccurrences(of byte: UInt8) -> Int { withUnsafeBytes { raw -> Int in guard let base = raw.baseAddress, !raw.isEmpty else { return 0 } var count = 0 var scanned = 0 let denseMatchLimit = 2_048 while scanned < raw.count, let hit = memchr(base + scanned, Int32(byte), raw.count - scanned) { // memchr returns the hit itself, so resume one byte past it. scanned = base.distance(to: UnsafeRawPointer(hit)) + 1 count += 1 if count == denseMatchLimit { let remaining = raw.count - scanned let bytes = (base + scanned).assumingMemoryBound(to: UInt8.self) for index in 0..<remaining { count += bytes[index] == byte ? 1 : 0 } break } } return count } }
/// Whether `byte` occurs anywhere in the buffer. Stops at the first hit. fileprivate nonisolated func containsByte(_ byte: UInt8) -> Bool { withUnsafeBytes { raw -> Bool in guard let base = raw.baseAddress, !raw.isEmpty else { return false } return memchr(base, Int32(byte), raw.count) != nil } }}