import Foundation /// One row of `repo list`. nonisolated struct RepoSummary: Decodable, Sendable, Hashable, Identifiable { let path: String let visibility: String let description: String? let archived: Bool? /// Present on `repo search` rows, absent on `repo list`. let topics: [String]? var id: String { path } var isArchived: Bool { archived ?? false } var isPrivate: Bool { visibility == "private" } /// "krz/gitbay" → ("krz", "gitbay") var owner: String { String(path.split(separator: "/").first ?? "") } var name: String { String(path.split(separator: "/").last ?? "") } } /// `repo show `. nonisolated struct RepoDetail: Decodable, Sendable, Hashable { let path: String let description: String? let website: String? let visibility: String let defaultBranch: String let protectedBranches: [String]? let archived: Bool? let topics: [String]? enum CodingKeys: String, CodingKey { case path, description, website, visibility, archived, topics case defaultBranch = "default_branch" case protectedBranches = "protected_branches" } var isArchived: Bool { archived ?? false } } /// `repo refs ` — the branches and tags the server knows. nonisolated struct RepoRefs: Decodable, Sendable, Hashable { let branches: [RepoRef] let tags: [RepoRef] } nonisolated struct RepoRef: Decodable, Sendable, Hashable, Identifiable { let name: String let sha: String var id: String { name } } /// `repo tree` — one directory listing. nonisolated struct TreeListing: Decodable, Sendable, Hashable { let path: String let ref: String let dir: String let entries: [TreeEntry] } /// One entry of a tree. The sha is the git object id — content at a sha /// never changes, which is what makes caching on it sound. nonisolated struct TreeEntry: Decodable, Sendable, Hashable, Identifiable { let name: String let type: String // blob | tree let mode: String let sha: String let size: Int64? var id: String { sha + name } var isDirectory: Bool { type == "tree" } var isSymlink: Bool { mode == "120000" } } /// `repo cat` — one file. Exactly one of `content`/`base64` is set. nonisolated struct FileContent: Decodable, Sendable, Hashable { let path: String let ref: String let file: String let size: Int let binary: Bool let truncated: Bool? let content: String? let base64: String? var isTruncated: Bool { truncated ?? false } var data: Data? { if let content { return Data(content.utf8) } if let base64 { return Data(base64Encoded: base64) } return nil } } /// `repo log` — one commit, with its signature verdict. nonisolated struct Commit: Decodable, Sendable, Hashable, Identifiable { let sha: String let subject: String let authorName: String let authorEmail: String let committerEmail: String? let date: Date let signature: Signature enum CodingKeys: String, CodingKey { case sha, subject, date, signature case authorName = "author_name" case authorEmail = "author_email" case committerEmail = "committer_email" } var id: String { sha } var shortSHA: String { String(sha.prefix(10)) } nonisolated struct Signature: Decodable, Sendable, Hashable { let state: SignatureState let signer: String? let keyFingerprint: String? enum CodingKeys: String, CodingKey { case state, signer case keyFingerprint = "key_fingerprint" } } } /// The server's verdict on a commit signature; `internal/sig/verify.go`. /// Unknown future states decode as `.unrecognized` rather than failing the /// whole log. nonisolated enum SignatureState: Sendable, Hashable, Decodable { case verified case signedUnknownKey case signedEmailMismatch case signedKeyExpired case signedKeyRevoked case badSignature case unsigned case unrecognized(String) init(from decoder: any Decoder) throws { switch try decoder.singleValueContainer().decode(String.self) { case "verified": self = .verified case "signed_unknown_key": self = .signedUnknownKey case "signed_email_mismatch": self = .signedEmailMismatch case "signed_key_expired": self = .signedKeyExpired case "signed_key_revoked": self = .signedKeyRevoked case "bad_signature": self = .badSignature case "unsigned": self = .unsigned case let other: self = .unrecognized(other) } } }