From d29fbef60abdffaee190b758530dde9212745ed2 Mon Sep 17 00:00:00 2001 From: Christian Cleberg Date: Mon, 20 Apr 2026 12:51:22 -0500 Subject: fix: ensure all branches and tags are accounted for and not cutoff due to pagination --- .../Repositories/RepositoryDetailViewModel.swift | 80 +++++++++++++++++++--- 1 file changed, 69 insertions(+), 11 deletions(-) (limited to 'Hutch/Views/Repositories/RepositoryDetailViewModel.swift') diff --git a/Hutch/Views/Repositories/RepositoryDetailViewModel.swift b/Hutch/Views/Repositories/RepositoryDetailViewModel.swift index 8dec51b..9aff1a4 100644 --- a/Hutch/Views/Repositories/RepositoryDetailViewModel.swift +++ b/Hutch/Views/Repositories/RepositoryDetailViewModel.swift @@ -240,9 +240,9 @@ final class RepositoryDetailViewModel { // MARK: - References private static let refsQuery = """ - query refs($rid: ID!) { + query refs($rid: ID!, $cursor: Cursor) { repository(rid: $rid) { - references { + references(cursor: $cursor) { results { name target @@ -263,15 +263,15 @@ final class RepositoryDetailViewModel { error = nil do { - let result = try await client.execute( - service: service, - query: Self.refsQuery, - variables: ["rid": repository.rid], - responseType: RefsResponse.self - ) - let allRefs = result.repository?.references.results ?? [] - branches = allRefs.filter { $0.name.hasPrefix("refs/heads/") }.map { $0.toDetail() } - tags = allRefs.filter { $0.name.hasPrefix("refs/tags/") }.map { $0.toDetail() } + let allRefs = try await fetchAllReferences() + branches = allRefs + .filter { $0.name.hasPrefix("refs/heads/") } + .map { $0.toDetail() } + .sorted(by: Self.sortBranches) + tags = allRefs + .filter { $0.name.hasPrefix("refs/tags/") } + .map { $0.toDetail() } + .sorted(by: Self.sortTags) } catch { if isEmptyRepositoryError(error) { branches = [] @@ -284,6 +284,64 @@ final class RepositoryDetailViewModel { isLoadingRefs = false } + var defaultBranchReference: ReferenceDetail? { + if let headName = repository.head?.name, + let branch = branches.first(where: { $0.name == headName }) { + return branch + } + return branches.first + } + + var latestTagReference: ReferenceDetail? { + tags.first + } + + private func fetchAllReferences() async throws -> [ReferencePayload] { + var allRefs: [ReferencePayload] = [] + var cursor: String? + + repeat { + let page = try await fetchReferencePage(cursor: cursor) + allRefs.append(contentsOf: page.results) + cursor = page.cursor + } while cursor != nil + + return allRefs + } + + private func fetchReferencePage(cursor: String?) async throws -> RefsPage { + var variables: [String: any Sendable] = ["rid": repository.rid] + if let cursor { + variables["cursor"] = cursor + } + + let result = try await client.execute( + service: service, + query: Self.refsQuery, + variables: variables, + responseType: RefsResponse.self + ) + + return result.repository?.references ?? RefsPage(results: [], cursor: nil) + } + + nonisolated private static func sortBranches(_ lhs: ReferenceDetail, _ rhs: ReferenceDetail) -> Bool { + lhs.name.localizedCaseInsensitiveCompare(rhs.name) == .orderedAscending + } + + nonisolated private static func sortTags(_ lhs: ReferenceDetail, _ rhs: ReferenceDetail) -> Bool { + switch (lhs.date, rhs.date) { + case let (lhsDate?, rhsDate?) where lhsDate != rhsDate: + return lhsDate > rhsDate + case (.some, nil): + return true + case (nil, .some): + return false + default: + return lhs.name.localizedCaseInsensitiveCompare(rhs.name) == .orderedAscending + } + } + // MARK: - README private static let readmeQuery = """ -- cgit v1.2.3