gitbay/Repos/RepoModels.swift
140 lines · 4316 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 tree` — one directory listing.
42nonisolated struct TreeListing: Decodable, Sendable, Hashable {
43 let path: String
44 let ref: String
45 let dir: String
46 let entries: [TreeEntry]
47}
48
49/// One entry of a tree. The sha is the git object id — content at a sha
50/// never changes, which is what makes caching on it sound.
51nonisolated struct TreeEntry: Decodable, Sendable, Hashable, Identifiable {
52 let name: String
53 let type: String // blob | tree
54 let mode: String
55 let sha: String
56 let size: Int64?
57
58 var id: String { sha + name }
59 var isDirectory: Bool { type == "tree" }
60 var isSymlink: Bool { mode == "120000" }
61}
62
63/// `repo cat` — one file. Exactly one of `content`/`base64` is set.
64nonisolated struct FileContent: Decodable, Sendable, Hashable {
65 let path: String
66 let ref: String
67 let file: String
68 let size: Int
69 let binary: Bool
70 let truncated: Bool?
71 let content: String?
72 let base64: String?
73
74 var isTruncated: Bool { truncated ?? false }
75
76 var data: Data? {
77 if let content { return Data(content.utf8) }
78 if let base64 { return Data(base64Encoded: base64) }
79 return nil
80 }
81}
82
83/// `repo log` — one commit, with its signature verdict.
84nonisolated struct Commit: Decodable, Sendable, Hashable, Identifiable {
85 let sha: String
86 let subject: String
87 let authorName: String
88 let authorEmail: String
89 let committerEmail: String?
90 let date: Date
91 let signature: Signature
92
93 enum CodingKeys: String, CodingKey {
94 case sha, subject, date, signature
95 case authorName = "author_name"
96 case authorEmail = "author_email"
97 case committerEmail = "committer_email"
98 }
99
100 var id: String { sha }
101 var shortSHA: String { String(sha.prefix(10)) }
102
103 nonisolated struct Signature: Decodable, Sendable, Hashable {
104 let state: SignatureState
105 let signer: String?
106 let keyFingerprint: String?
107
108 enum CodingKeys: String, CodingKey {
109 case state, signer
110 case keyFingerprint = "key_fingerprint"
111 }
112 }
113}
114
115/// The server's verdict on a commit signature; `internal/sig/verify.go`.
116/// Unknown future states decode as `.unrecognized` rather than failing the
117/// whole log.
118nonisolated enum SignatureState: Sendable, Hashable, Decodable {
119 case verified
120 case signedUnknownKey
121 case signedEmailMismatch
122 case signedKeyExpired
123 case signedKeyRevoked
124 case badSignature
125 case unsigned
126 case unrecognized(String)
127
128 init(from decoder: any Decoder) throws {
129 switch try decoder.singleValueContainer().decode(String.self) {
130 case "verified": self = .verified
131 case "signed_unknown_key": self = .signedUnknownKey
132 case "signed_email_mismatch": self = .signedEmailMismatch
133 case "signed_key_expired": self = .signedKeyExpired
134 case "signed_key_revoked": self = .signedKeyRevoked
135 case "bad_signature": self = .badSignature
136 case "unsigned": self = .unsigned
137 case let other: self = .unrecognized(other)
138 }
139 }
140}