summaryrefslogtreecommitdiff
path: root/Hutch/Models/Git.swift
diff options
context:
space:
mode:
Diffstat (limited to 'Hutch/Models/Git.swift')
-rw-r--r--Hutch/Models/Git.swift241
1 files changed, 241 insertions, 0 deletions
diff --git a/Hutch/Models/Git.swift b/Hutch/Models/Git.swift
new file mode 100644
index 0000000..19ec31b
--- /dev/null
+++ b/Hutch/Models/Git.swift
@@ -0,0 +1,241 @@
+import Foundation
+
+// MARK: - Enums
+
+/// Repository visibility level.
+enum Visibility: String, Codable, Sendable {
+ case `public` = "PUBLIC"
+ case unlisted = "UNLISTED"
+ case `private` = "PRIVATE"
+}
+
+/// Repository access mode.
+enum AccessMode: String, Codable, Sendable {
+ case ro = "RO"
+ case rw = "RW"
+}
+
+// MARK: - Entity
+
+/// The `Entity` GraphQL interface from git.sr.ht. Represents the owner of a
+/// resource (typically a user).
+struct Entity: Codable, Sendable, Hashable {
+ let canonicalName: String
+}
+
+// MARK: - Repository
+
+/// A git repository from git.sr.ht.
+struct Repository: Codable, Sendable, Identifiable {
+ let id: Int
+ let created: Date
+ let updated: Date
+ let name: String
+ let description: String?
+ let visibility: Visibility
+ let readme: String?
+ let accessMode: AccessMode
+ let owner: Entity
+
+ enum CodingKeys: String, CodingKey {
+ case id, created, updated, name, description, visibility, readme
+ case accessMode = "access"
+ case owner
+ }
+}
+
+// MARK: - Signature
+
+/// A Git commit/tag signature (author or committer).
+struct Signature: Codable, Sendable {
+ let name: String
+ let email: String
+ let time: Date
+}
+
+// MARK: - Trailer
+
+/// A Git commit trailer (e.g. "Signed-off-by", "Co-authored-by").
+struct Trailer: Codable, Sendable {
+ let name: String
+ let value: String
+}
+
+// MARK: - Commit
+
+/// A Git commit from git.sr.ht.
+struct Commit: Codable, Sendable, Identifiable {
+ let id: String
+ let shortId: String
+ let author: Signature
+ let committer: Signature
+ let message: String
+ let diff: String?
+ let trailers: [Trailer]
+}
+
+// MARK: - Reference
+
+/// A Git reference (branch or tag name).
+struct Reference: Codable, Sendable, Hashable {
+ let name: String
+ let target: String?
+}
+
+// MARK: - TreeEntry
+
+/// An entry in a Git tree (file or directory).
+struct TreeEntry: Codable, Sendable, Identifiable {
+ let id: String
+ let name: String
+ let mode: Int?
+ let object: GitObject?
+}
+
+/// A Git object returned by the git.sr.ht GraphQL API.
+/// The API returns `type` as "TREE", "BLOB", "COMMIT", or "TAG".
+/// Both TextBlob and BinaryBlob have type == "BLOB"; they are
+/// distinguished by the presence of the "text" key (TextBlob) vs
+/// the "content" key (BinaryBlob).
+enum GitObject: Sendable {
+ case tree(GitTree)
+ case textBlob(GitTextBlob)
+ case binaryBlob(GitBinaryBlob)
+ case unknown
+}
+
+struct GitTree: Codable, Sendable {
+ let id: String?
+ let shortId: String?
+ let entries: GitTreeEntryPage?
+}
+
+struct GitTextBlob: Codable, Sendable {
+ let id: String?
+ let shortId: String?
+ let text: String
+ let size: Int?
+}
+
+struct GitBinaryBlob: Codable, Sendable {
+ let id: String?
+ let shortId: String?
+ let size: Int?
+ let content: String?
+}
+
+struct GitTreeEntryPage: Codable, Sendable {
+ let results: [TreeEntry]
+ let cursor: String?
+}
+
+// MARK: - GitObject Codable
+
+extension GitObject: Codable {
+ private enum CodingKeys: String, CodingKey {
+ case type, id, shortId, entries, text, size, content
+ }
+
+ init(from decoder: any Decoder) throws {
+ let container = try decoder.container(keyedBy: CodingKeys.self)
+ let type = try container.decodeIfPresent(String.self, forKey: .type)
+
+ switch type {
+ case "TREE":
+ let tree = GitTree(
+ id: try container.decodeIfPresent(String.self, forKey: .id),
+ shortId: try container.decodeIfPresent(String.self, forKey: .shortId),
+ entries: try container.decodeIfPresent(GitTreeEntryPage.self, forKey: .entries)
+ )
+ self = .tree(tree)
+
+ case "BLOB":
+ // TextBlob has a "text" key; BinaryBlob does not
+ if container.contains(.text) {
+ let blob = GitTextBlob(
+ id: try container.decodeIfPresent(String.self, forKey: .id),
+ shortId: try container.decodeIfPresent(String.self, forKey: .shortId),
+ text: try container.decode(String.self, forKey: .text),
+ size: try container.decodeIfPresent(Int.self, forKey: .size)
+ )
+ self = .textBlob(blob)
+ } else {
+ let blob = GitBinaryBlob(
+ id: try container.decodeIfPresent(String.self, forKey: .id),
+ shortId: try container.decodeIfPresent(String.self, forKey: .shortId),
+ size: try container.decodeIfPresent(Int.self, forKey: .size),
+ content: try container.decodeIfPresent(String.self, forKey: .content)
+ )
+ self = .binaryBlob(blob)
+ }
+
+ default:
+ self = .unknown
+ }
+ }
+
+ func encode(to encoder: any Encoder) throws {
+ var container = encoder.container(keyedBy: CodingKeys.self)
+ switch self {
+ case .tree(let tree):
+ try container.encode("TREE", forKey: .type)
+ try container.encodeIfPresent(tree.id, forKey: .id)
+ try container.encodeIfPresent(tree.shortId, forKey: .shortId)
+ try container.encodeIfPresent(tree.entries, forKey: .entries)
+ case .textBlob(let blob):
+ try container.encode("BLOB", forKey: .type)
+ try container.encodeIfPresent(blob.id, forKey: .id)
+ try container.encodeIfPresent(blob.shortId, forKey: .shortId)
+ try container.encode(blob.text, forKey: .text)
+ try container.encodeIfPresent(blob.size, forKey: .size)
+ case .binaryBlob(let blob):
+ try container.encode("BLOB", forKey: .type)
+ try container.encodeIfPresent(blob.id, forKey: .id)
+ try container.encodeIfPresent(blob.shortId, forKey: .shortId)
+ try container.encodeIfPresent(blob.size, forKey: .size)
+ try container.encodeIfPresent(blob.content, forKey: .content)
+ case .unknown:
+ break
+ }
+ }
+}
+
+/// Convenience helpers for checking object type.
+extension GitObject {
+ var isTree: Bool {
+ if case .tree = self { return true }
+ return false
+ }
+
+ var treeId: String? {
+ switch self {
+ case .tree(let t): t.id
+ case .textBlob(let b): b.id
+ case .binaryBlob(let b): b.id
+ case .unknown: nil
+ }
+ }
+}
+
+// MARK: - Tag
+
+/// An annotated Git tag from git.sr.ht.
+struct Tag: Codable, Sendable, Identifiable {
+ let id: String
+ let shortId: String
+ let name: String
+ let message: String?
+ let tagger: Signature?
+}
+
+// MARK: - Artifact
+
+/// A release artifact attached to a Git tag.
+struct Artifact: Codable, Sendable, Identifiable {
+ let id: Int
+ let created: Date
+ let filename: String
+ let checksum: String
+ let size: Int
+ let url: URL
+}