krz/hutch

an ios client for sourcehut

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

v3.2.0: Hutch/Models/Git.swift · raw

  1import Foundation
  2
  3// MARK: - Enums
  4
  5/// Repository visibility level.
  6enum Visibility: String, Codable, Sendable {
  7    case publicVisibility = "PUBLIC"
  8    case unlisted = "UNLISTED"
  9    case privateVisibility = "PRIVATE"
 10}
 11
 12/// Repository access mode.
 13enum AccessMode: String, Codable, Sendable, CaseIterable {
 14    case ro = "RO"
 15    case rw = "RW"
 16}
 17
 18// MARK: - Entity
 19
 20/// The `Entity` GraphQL interface from git.sr.ht. Represents the owner of a
 21/// resource (typically a user).
 22struct Entity: Codable, Sendable, Hashable {
 23    let canonicalName: String
 24}
 25
 26// MARK: - Repository
 27
 28/// A git repository from git.sr.ht.
 29struct Repository: Codable, Sendable, Identifiable {
 30    let id: Int
 31    let created: Date
 32    let updated: Date
 33    let name: String
 34    let description: String?
 35    let visibility: Visibility
 36    let readme: String?
 37    let accessMode: AccessMode
 38    let owner: Entity
 39
 40    enum CodingKeys: String, CodingKey {
 41        case id, created, updated, name, description, visibility, readme
 42        case accessMode = "access"
 43        case owner
 44    }
 45}
 46
 47// MARK: - Signature
 48
 49/// A Git commit/tag signature (author or committer).
 50struct Signature: Codable, Sendable {
 51    let name: String
 52    let email: String
 53    let time: Date
 54}
 55
 56// MARK: - Trailer
 57
 58/// A Git commit trailer (e.g. "Signed-off-by", "Co-authored-by").
 59struct Trailer: Codable, Sendable {
 60    let name: String
 61    let value: String
 62}
 63
 64// MARK: - Commit
 65
 66/// A Git commit from git.sr.ht.
 67struct Commit: Codable, Sendable, Identifiable {
 68    let id: String
 69    let shortId: String
 70    let author: Signature
 71    let committer: Signature
 72    let message: String
 73    let diff: String?
 74    let trailers: [Trailer]
 75}
 76
 77// MARK: - Reference
 78
 79/// A Git reference (branch or tag name).
 80struct Reference: Codable, Sendable, Hashable {
 81    let name: String
 82    let target: String?
 83}
 84
 85/// A Git reference enriched with the date of its tip commit or tag, for display in the Refs tab.
 86struct ReferenceDetail: Sendable, Hashable {
 87    let name: String
 88    let target: String?
 89    let date: Date?
 90}
 91
 92// MARK: - TreeEntry
 93
 94/// An entry in a Git tree (file or directory).
 95struct TreeEntry: Codable, Sendable, Identifiable {
 96    let id: String
 97    let name: String
 98    let mode: Int?
 99    let object: GitObject?
100}
101
102/// A Git object returned by the git.sr.ht GraphQL API.
103/// The API returns `type` as "TREE", "BLOB", "COMMIT", or "TAG".
104/// Both TextBlob and BinaryBlob have type == "BLOB"; they are
105/// distinguished by the presence of the "text" key (TextBlob) vs
106/// the "content" key (BinaryBlob).
107enum GitObject: Sendable {
108    case tree(GitTree)
109    case textBlob(GitTextBlob)
110    case binaryBlob(GitBinaryBlob)
111    case unknown
112}
113
114struct GitTree: Codable, Sendable {
115    let id: String?
116    let shortId: String?
117    let entries: GitTreeEntryPage?
118}
119
120struct GitTextBlob: Codable, Sendable {
121    let id: String?
122    let shortId: String?
123    let text: String?
124    let size: Int?
125}
126
127struct GitBinaryBlob: Codable, Sendable {
128    let id: String?
129    let shortId: String?
130    let size: Int?
131    let content: String?
132}
133
134struct GitTreeEntryPage: Codable, Sendable {
135    let results: [TreeEntry]
136    let cursor: String?
137}
138
139// MARK: - GitObject Codable
140
141extension GitObject: Codable {
142    private enum CodingKeys: String, CodingKey {
143        case type, id, shortId, entries, text, size, content
144        case typename = "__typename"
145    }
146
147    init(from decoder: any Decoder) throws {
148        let container = try decoder.container(keyedBy: CodingKeys.self)
149        let type = try container.decodeIfPresent(String.self, forKey: .type)
150        let typename = try container.decodeIfPresent(String.self, forKey: .typename)
151
152        switch type ?? typename {
153        case "TREE", "Tree":
154            let tree = GitTree(
155                id: try container.decodeIfPresent(String.self, forKey: .id),
156                shortId: try container.decodeIfPresent(String.self, forKey: .shortId),
157                entries: try container.decodeIfPresent(GitTreeEntryPage.self, forKey: .entries)
158            )
159            self = .tree(tree)
160
161        case "BLOB", "TextBlob", "BinaryBlob":
162            if typename == "TextBlob" || container.contains(.text) {
163                let blob = GitTextBlob(
164                    id: try container.decodeIfPresent(String.self, forKey: .id),
165                    shortId: try container.decodeIfPresent(String.self, forKey: .shortId),
166                    text: try container.decodeIfPresent(String.self, forKey: .text),
167                    size: try container.decodeIfPresent(Int.self, forKey: .size)
168                )
169                self = .textBlob(blob)
170            } else {
171                let blob = GitBinaryBlob(
172                    id: try container.decodeIfPresent(String.self, forKey: .id),
173                    shortId: try container.decodeIfPresent(String.self, forKey: .shortId),
174                    size: try container.decodeIfPresent(Int.self, forKey: .size),
175                    content: try container.decodeIfPresent(String.self, forKey: .content)
176                )
177                self = .binaryBlob(blob)
178            }
179
180        default:
181            self = .unknown
182        }
183    }
184
185    func encode(to encoder: any Encoder) throws {
186        var container = encoder.container(keyedBy: CodingKeys.self)
187        switch self {
188        case .tree(let tree):
189            try container.encode("TREE", forKey: .type)
190            try container.encodeIfPresent(tree.id, forKey: .id)
191            try container.encodeIfPresent(tree.shortId, forKey: .shortId)
192            try container.encodeIfPresent(tree.entries, forKey: .entries)
193        case .textBlob(let blob):
194            try container.encode("BLOB", forKey: .type)
195            try container.encode("TextBlob", forKey: .typename)
196            try container.encodeIfPresent(blob.id, forKey: .id)
197            try container.encodeIfPresent(blob.shortId, forKey: .shortId)
198            try container.encodeIfPresent(blob.text, forKey: .text)
199            try container.encodeIfPresent(blob.size, forKey: .size)
200        case .binaryBlob(let blob):
201            try container.encode("BLOB", forKey: .type)
202            try container.encode("BinaryBlob", forKey: .typename)
203            try container.encodeIfPresent(blob.id, forKey: .id)
204            try container.encodeIfPresent(blob.shortId, forKey: .shortId)
205            try container.encodeIfPresent(blob.size, forKey: .size)
206            try container.encodeIfPresent(blob.content, forKey: .content)
207        case .unknown:
208            break
209        }
210    }
211}
212
213/// Convenience helpers for checking object type.
214extension GitObject {
215    var isTree: Bool {
216        if case .tree = self { return true }
217        return false
218    }
219
220    var treeId: String? {
221        switch self {
222        case .tree(let t): t.id
223        case .textBlob(let b): b.id
224        case .binaryBlob(let b): b.id
225        case .unknown: nil
226        }
227    }
228}
229
230// MARK: - Tag
231
232/// An annotated Git tag from git.sr.ht.
233struct Tag: Codable, Sendable, Identifiable {
234    let id: String
235    let shortId: String
236    let name: String
237    let message: String?
238    let tagger: Signature?
239}
240
241// MARK: - Artifact
242
243/// A release artifact attached to a Git tag.
244struct Artifact: Codable, Sendable, Identifiable {
245    let id: Int
246    let created: Date
247    let filename: String
248    let checksum: String
249    let size: Int
250    let url: URL
251}