diff --git a/supacode/Clients/Git/GitClient.swift b/supacode/Clients/Git/GitClient.swift index c9303b9a..090e3d3d 100644 --- a/supacode/Clients/Git/GitClient.swift +++ b/supacode/Clients/Git/GitClient.swift @@ -10,6 +10,7 @@ enum GitOperation: String { case branchNames = "branch_names" case branchRefs = "branch_refs" case defaultRemoteBranchRef = "default_remote_branch_ref" + case localHeadRef = "local_head_ref" case branchRename = "branch_rename" case branchDelete = "branch_delete" case lineChanges = "line_changes" @@ -178,7 +179,12 @@ struct GitClient { nonisolated func automaticWorktreeBaseRef(for repoRoot: URL) async -> String? { let resolved = try? await defaultRemoteBranchRef(for: repoRoot) - return resolved ?? nil + 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 createWorktree( @@ -389,6 +395,28 @@ struct GitClient { 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 + } + + 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 { diff --git a/supacodeTests/GitAutomaticBaseRefTests.swift b/supacodeTests/GitAutomaticBaseRefTests.swift new file mode 100644 index 00000000..a4bcb0b5 --- /dev/null +++ b/supacodeTests/GitAutomaticBaseRefTests.swift @@ -0,0 +1,20 @@ +import Testing + +@testable import supacode + +struct GitAutomaticBaseRefTests { + @Test func prefersRemoteWhenAvailable() { + let value = GitClient.preferredBaseRef(remote: "origin/main", localHead: "main") + #expect(value == "origin/main") + } + + @Test func fallsBackToLocalHead() { + let value = GitClient.preferredBaseRef(remote: nil, localHead: "main") + #expect(value == "main") + } + + @Test func returnsNilWhenNoRefs() { + let value = GitClient.preferredBaseRef(remote: nil, localHead: nil) + #expect(value == nil) + } +}