krz/hutch
an ios client for sourcehut
clone: git clone https://gitbay.org/krz/hutch.git
v2.7.1: Hutch/Models/Git.swift · raw
1import Foundation
2
3// MARK: - Enums
4
5/// Repository visibility level.
6enum Visibility: String, Codable, Sendable {
7 case `public` = "PUBLIC"
8 case unlisted = "UNLISTED"
9 case `private` = "PRIVATE"
10}
11
12/// Repository access mode.
13enum AccessMode: String, Codable, Sendable {
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// MARK: - TreeEntry
86
87/// An entry in a Git tree (file or directory).
88struct TreeEntry: Codable, Sendable, Identifiable {
89 let id: String
90 let name: String
91 let mode: Int?
92 let object: GitObject?
93}
94
95/// A Git object returned by the git.sr.ht GraphQL API.
96/// The API returns `type` as "TREE", "BLOB", "COMMIT", or "TAG".
97/// Both TextBlob and BinaryBlob have type == "BLOB"; they are
98/// distinguished by the presence of the "text" key (TextBlob) vs
99/// the "content" key (BinaryBlob).
100enum GitObject: Sendable {
101 case tree(GitTree)
102 case textBlob(GitTextBlob)
103 case binaryBlob(GitBinaryBlob)
104 case unknown
105}
106
107struct GitTree: Codable, Sendable {
108 let id: String?
109 let shortId: String?
110 let entries: GitTreeEntryPage?
111}
112
113struct GitTextBlob: Codable, Sendable {
114 let id: String?
115 let shortId: String?
116 let text: String?
117 let size: Int?
118}
119
120struct GitBinaryBlob: Codable, Sendable {
121 let id: String?
122 let shortId: String?
123 let size: Int?
124 let content: String?
125}
126
127struct GitTreeEntryPage: Codable, Sendable {
128 let results: [TreeEntry]
129 let cursor: String?
130}
131
132// MARK: - GitObject Codable
133
134extension GitObject: Codable {
135 private enum CodingKeys: String, CodingKey {
136 case type, id, shortId, entries, text, size, content
137 case typename = "__typename"
138 }
139
140 init(from decoder: any Decoder) throws {
141 let container = try decoder.container(keyedBy: CodingKeys.self)
142 let type = try container.decodeIfPresent(String.self, forKey: .type)
143 let typename = try container.decodeIfPresent(String.self, forKey: .typename)
144
145 switch type ?? typename {
146 case "TREE", "Tree":
147 let tree = GitTree(
148 id: try container.decodeIfPresent(String.self, forKey: .id),
149 shortId: try container.decodeIfPresent(String.self, forKey: .shortId),
150 entries: try container.decodeIfPresent(GitTreeEntryPage.self, forKey: .entries)
151 )
152 self = .tree(tree)
153
154 case "BLOB", "TextBlob", "BinaryBlob":
155 if typename == "TextBlob" || container.contains(.text) {
156 let blob = GitTextBlob(
157 id: try container.decodeIfPresent(String.self, forKey: .id),
158 shortId: try container.decodeIfPresent(String.self, forKey: .shortId),
159 text: try container.decodeIfPresent(String.self, forKey: .text),
160 size: try container.decodeIfPresent(Int.self, forKey: .size)
161 )
162 self = .textBlob(blob)
163 } else {
164 let blob = GitBinaryBlob(
165 id: try container.decodeIfPresent(String.self, forKey: .id),
166 shortId: try container.decodeIfPresent(String.self, forKey: .shortId),
167 size: try container.decodeIfPresent(Int.self, forKey: .size),
168 content: try container.decodeIfPresent(String.self, forKey: .content)
169 )
170 self = .binaryBlob(blob)
171 }
172
173 default:
174 self = .unknown
175 }
176 }
177
178 func encode(to encoder: any Encoder) throws {
179 var container = encoder.container(keyedBy: CodingKeys.self)
180 switch self {
181 case .tree(let tree):
182 try container.encode("TREE", forKey: .type)
183 try container.encodeIfPresent(tree.id, forKey: .id)
184 try container.encodeIfPresent(tree.shortId, forKey: .shortId)
185 try container.encodeIfPresent(tree.entries, forKey: .entries)
186 case .textBlob(let blob):
187 try container.encode("BLOB", forKey: .type)
188 try container.encode("TextBlob", forKey: .typename)
189 try container.encodeIfPresent(blob.id, forKey: .id)
190 try container.encodeIfPresent(blob.shortId, forKey: .shortId)
191 try container.encodeIfPresent(blob.text, forKey: .text)
192 try container.encodeIfPresent(blob.size, forKey: .size)
193 case .binaryBlob(let blob):
194 try container.encode("BLOB", forKey: .type)
195 try container.encode("BinaryBlob", forKey: .typename)
196 try container.encodeIfPresent(blob.id, forKey: .id)
197 try container.encodeIfPresent(blob.shortId, forKey: .shortId)
198 try container.encodeIfPresent(blob.size, forKey: .size)
199 try container.encodeIfPresent(blob.content, forKey: .content)
200 case .unknown:
201 break
202 }
203 }
204}
205
206/// Convenience helpers for checking object type.
207extension GitObject {
208 var isTree: Bool {
209 if case .tree = self { return true }
210 return false
211 }
212
213 var treeId: String? {
214 switch self {
215 case .tree(let t): t.id
216 case .textBlob(let b): b.id
217 case .binaryBlob(let b): b.id
218 case .unknown: nil
219 }
220 }
221}
222
223// MARK: - Tag
224
225/// An annotated Git tag from git.sr.ht.
226struct Tag: Codable, Sendable, Identifiable {
227 let id: String
228 let shortId: String
229 let name: String
230 let message: String?
231 let tagger: Signature?
232}
233
234// MARK: - Artifact
235
236/// A release artifact attached to a Git tag.
237struct Artifact: Codable, Sendable, Identifiable {
238 let id: Int
239 let created: Date
240 let filename: String
241 let checksum: String
242 let size: Int
243 let url: URL
244}