gitbay/Repos/RepoModels.swift
152 lines · 4651 bytes
1import Foundation
2
3/// One row of `repo list`.
4nonisolated struct RepoSummary: Decodable, Sendable, Hashable, Identifiable {
5 let path: String
6 let visibility: String
7 let description: String?
8 let archived: Bool?
9 /// Present on `repo search` rows, absent on `repo list`.
10 let topics: [String]?
11
12 var id: String { path }
13 var isArchived: Bool { archived ?? false }
14 var isPrivate: Bool { visibility == "private" }
15
16 /// "krz/gitbay" → ("krz", "gitbay")
17 var owner: String { String(path.split(separator: "/").first ?? "") }
18 var name: String { String(path.split(separator: "/").last ?? "") }
19}
20
21/// `repo show <owner/name>`.
22nonisolated struct RepoDetail: Decodable, Sendable, Hashable {
23 let path: String
24 let description: String?
25 let website: String?
26 let visibility: String
27 let defaultBranch: String
28 let protectedBranches: [String]?
29 let archived: Bool?
30 let topics: [String]?
31
32 enum CodingKeys: String, CodingKey {
33 case path, description, website, visibility, archived, topics
34 case defaultBranch = "default_branch"
35 case protectedBranches = "protected_branches"
36 }
37
38 var isArchived: Bool { archived ?? false }
39}
40
41/// `repo refs <owner/name>` — the branches and tags the server knows.
42nonisolated struct RepoRefs: Decodable, Sendable, Hashable {
43 let branches: [RepoRef]
44 let tags: [RepoRef]
45}
46
47nonisolated struct RepoRef: Decodable, Sendable, Hashable, Identifiable {
48 let name: String
49 let sha: String
50 var id: String { name }
51}
52
53/// `repo tree` — one directory listing.
54nonisolated struct TreeListing: Decodable, Sendable, Hashable {
55 let path: String
56 let ref: String
57 let dir: String
58 let entries: [TreeEntry]
59}
60
61/// One entry of a tree. The sha is the git object id — content at a sha
62/// never changes, which is what makes caching on it sound.
63nonisolated struct TreeEntry: Decodable, Sendable, Hashable, Identifiable {
64 let name: String
65 let type: String // blob | tree
66 let mode: String
67 let sha: String
68 let size: Int64?
69
70 var id: String { sha + name }
71 var isDirectory: Bool { type == "tree" }
72 var isSymlink: Bool { mode == "120000" }
73}
74
75/// `repo cat` — one file. Exactly one of `content`/`base64` is set.
76nonisolated struct FileContent: Decodable, Sendable, Hashable {
77 let path: String
78 let ref: String
79 let file: String
80 let size: Int
81 let binary: Bool
82 let truncated: Bool?
83 let content: String?
84 let base64: String?
85
86 var isTruncated: Bool { truncated ?? false }
87
88 var data: Data? {
89 if let content { return Data(content.utf8) }
90 if let base64 { return Data(base64Encoded: base64) }
91 return nil
92 }
93}
94
95/// `repo log` — one commit, with its signature verdict.
96nonisolated struct Commit: Decodable, Sendable, Hashable, Identifiable {
97 let sha: String
98 let subject: String
99 let authorName: String
100 let authorEmail: String
101 let committerEmail: String?
102 let date: Date
103 let signature: Signature
104
105 enum CodingKeys: String, CodingKey {
106 case sha, subject, date, signature
107 case authorName = "author_name"
108 case authorEmail = "author_email"
109 case committerEmail = "committer_email"
110 }
111
112 var id: String { sha }
113 var shortSHA: String { String(sha.prefix(10)) }
114
115 nonisolated struct Signature: Decodable, Sendable, Hashable {
116 let state: SignatureState
117 let signer: String?
118 let keyFingerprint: String?
119
120 enum CodingKeys: String, CodingKey {
121 case state, signer
122 case keyFingerprint = "key_fingerprint"
123 }
124 }
125}
126
127/// The server's verdict on a commit signature; `internal/sig/verify.go`.
128/// Unknown future states decode as `.unrecognized` rather than failing the
129/// whole log.
130nonisolated enum SignatureState: Sendable, Hashable, Decodable {
131 case verified
132 case signedUnknownKey
133 case signedEmailMismatch
134 case signedKeyExpired
135 case signedKeyRevoked
136 case badSignature
137 case unsigned
138 case unrecognized(String)
139
140 init(from decoder: any Decoder) throws {
141 switch try decoder.singleValueContainer().decode(String.self) {
142 case "verified": self = .verified
143 case "signed_unknown_key": self = .signedUnknownKey
144 case "signed_email_mismatch": self = .signedEmailMismatch
145 case "signed_key_expired": self = .signedKeyExpired
146 case "signed_key_revoked": self = .signedKeyRevoked
147 case "bad_signature": self = .badSignature
148 case "unsigned": self = .unsigned
149 case let other: self = .unrecognized(other)
150 }
151 }
152}