summaryrefslogtreecommitdiff
path: root/Hutch/Views/Repositories/CommitDetailViewModel.swift
blob: f0898f502e61e3ab48c9b0693f6506fcbbdf4444 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
import Foundation

// MARK: - Response types (file-private to avoid @MainActor Decodable issues)

private struct CommitResponse: Decodable, Sendable {
    let repository: CommitRepository?
}

private struct CommitRepository: Decodable, Sendable {
    // swiftlint:disable:next identifier_name
    let revparse_single: CommitDetail
}

// MARK: - View Model

@Observable
@MainActor
final class CommitDetailViewModel {

    let repositoryRid: String
    let service: SRHTService
    private let client: SRHTClient

    private(set) var commit: CommitDetail?
    private(set) var isLoading = false
    var error: String?

    init(repositoryRid: String, service: SRHTService, commitId: String, client: SRHTClient) {
        self.repositoryRid = repositoryRid
        self.service = service
        self.commitId = commitId
        self.client = client
    }

    private let commitId: String

    // MARK: - Query

    private static let query = """
    query commit($rid: ID!, $id: String!) {
        repository(rid: $rid) {
            revparse_single(revspec: $id) {
                id
                shortId
                author { name email time }
                committer { name email time }
                message
                diff
                trailers { name value }
                parents { id shortId author { name } }
                tree {
                    entries {
                        results { id name mode object { type id shortId } }
                        cursor
                    }
                }
            }
        }
    }
    """

    func loadCommit() async {
        guard !isLoading else { return }
        isLoading = true
        error = nil

        do {
            let result = try await executeWithRetry()
            commit = result.repository?.revparse_single
        } catch {
            self.error = error.userFacingMessage
        }

        isLoading = false
    }

    /// Execute the commit query, retrying once after a 1-second delay on 502/503.
    private func executeWithRetry() async throws -> CommitResponse {
        do {
            return try await client.execute(
                service: service,
                query: Self.query,
                variables: [
                    "rid": repositoryRid,
                    "id": commitId
                ],
                responseType: CommitResponse.self
            )
        } catch let SRHTError.httpError(code) where code == 502 || code == 503 {
            try await Task.sleep(for: .seconds(1))
            return try await client.execute(
                service: service,
                query: Self.query,
                variables: [
                    "rid": repositoryRid,
                    "id": commitId
                ],
                responseType: CommitResponse.self
            )
        }
    }
}