krz/hutch

an ios client for sourcehut

clone: git clone https://gitbay.org/krz/hutch.git

v2.9.1: Hutch/Views/Repositories/CommitDetailViewModel.swift · raw

  1import Foundation
  2
  3// MARK: - Response types (file-private to avoid @MainActor Decodable issues)
  4
  5private struct CommitResponse: Decodable, Sendable {
  6    let repository: CommitRepository?
  7}
  8
  9private struct CommitRepository: Decodable, Sendable {
 10    // swiftlint:disable:next identifier_name
 11    let revparse_single: CommitDetail
 12}
 13
 14// MARK: - View Model
 15
 16@Observable
 17@MainActor
 18final class CommitDetailViewModel {
 19
 20    let repositoryRid: String
 21    let service: SRHTService
 22    private let client: SRHTClient
 23
 24    private(set) var commit: CommitDetail?
 25    private(set) var isLoading = false
 26    var error: String?
 27
 28    init(repositoryRid: String, service: SRHTService, commitId: String, client: SRHTClient) {
 29        self.repositoryRid = repositoryRid
 30        self.service = service
 31        self.commitId = commitId
 32        self.client = client
 33    }
 34
 35    private let commitId: String
 36
 37    // MARK: - Query
 38
 39    private static let query = """
 40    query commit($rid: ID!, $id: String!) {
 41        repository(rid: $rid) {
 42            revparse_single(revspec: $id) {
 43                id
 44                shortId
 45                author { name email time }
 46                committer { name email time }
 47                message
 48                diff
 49                trailers { name value }
 50                parents { id shortId author { name } }
 51                tree {
 52                    entries {
 53                        results { id name mode object { type id shortId } }
 54                        cursor
 55                    }
 56                }
 57            }
 58        }
 59    }
 60    """
 61
 62    func loadCommit() async {
 63        guard !isLoading else { return }
 64        isLoading = true
 65        error = nil
 66
 67        do {
 68            let result = try await executeWithRetry()
 69            commit = result.repository?.revparse_single
 70        } catch {
 71            self.error = error.userFacingMessage
 72        }
 73
 74        isLoading = false
 75    }
 76
 77    /// Execute the commit query, retrying once after a 1-second delay on 502/503.
 78    private func executeWithRetry() async throws -> CommitResponse {
 79        do {
 80            return try await client.execute(
 81                service: service,
 82                query: Self.query,
 83                variables: [
 84                    "rid": repositoryRid,
 85                    "id": commitId
 86                ],
 87                responseType: CommitResponse.self
 88            )
 89        } catch let SRHTError.httpError(code) where code == 502 || code == 503 {
 90            try await Task.sleep(for: .seconds(1))
 91            return try await client.execute(
 92                service: service,
 93                query: Self.query,
 94                variables: [
 95                    "rid": repositoryRid,
 96                    "id": commitId
 97                ],
 98                responseType: CommitResponse.self
 99            )
100        }
101    }
102}