Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Git in Swift

Avatar for kazu42 kazu42
September 19, 2025
610

Git in Swift

Avatar for kazu42

kazu42

September 19, 2025
Tweet

Transcript

  1. git cloneͷ΍ͬͯΔ͜ͱ git init <dir> cd <dir> git remote add

    origin <url> git fetch origin git checkout -b <default_branch> origin/<default_branch> *ຊ౰͸΋ͬͱෳࡶͳ͜ͱΛ͍ͯ͠·͢ɻ
  2. Exelͱͷҧ͍ * c3d9e71 (HEAD -> main, tag: v2.0-external) 社外提出版のマージ |\

    | * 5b4a912 (feature/ver2, tag: v2.0-internal) ver2 社内レビュー用完成 | * 8e3f17a ver2 中間修正その1 | * 1f4a2cc ver2 作成開始(feature/ver2ブランチ作成) |/ * 3a7c9ff (tag: v1.0-final) 初版報告書 最終版 * 88b2d2a v1.0 社内レビュー修正 * 72d6f1e 初版作成 Exel Git
  3. Gitͷ4ͭͷ৔ॴ Remote Repo Local Repo Staging Area Working Directory Local

    Remote git add git commit git push git merge git pull git fetch git clone git checkout ಈࢺͷ commit
  4. Git in swift!!! • Git͸ɺίϯςϯπ؅ཧγεςϜʂ • Key Value Storeͱͯ͠อ؅ͯ͠Δ •

    Key͸ɺsha1 hash (sha256ʹҠߦத) • Value͸zlibѹॖ͞Εͨίϯςϯπ • add → blob ΦϒδΣΫτΛੜ੒ • commit → commit ΦϒδΣΫτΛੜ੒ʢtree/parentࢀরʣ • fetch → ϦϞʔτ͔ΒΦϒδΣΫτΛίϐʔʢ؆қόʔδϣϯʣ • cat- f ile -p → ΦϒδΣΫτΛల։ͯ͠த਎Λ֬ೝ
  5. Valueͷ࡞Γํ • “Hello World\n”ɿ12bytes • “Blob 12 \0Hello World\n”Λzlibѹॖ͢Δ •

    ❯ hexdump -C .git/objects/3b/18e512dba79e4c8300dd08aeb37f8e728b8dad 00000000 78 01 4b ca c9 4f 52 30 34 62 c8 48 cd c9 c9 57 |x.K..OR04b.H...W| 00000010 28 cf 2f ca 49 e1 02 00 44 11 06 89 |(./.I...D...| 0000001c
  6. git init import Foundation func gitInit() throws { let fm

    = FileManager.default try fm.createDirectory(atPath: ".git/objects", withIntermediateDirectories: true) try fm.createDirectory(atPath: ".git/refs/heads", withIntermediateDirectories: true) let headPath = ".git/HEAD" let headContent = "ref: refs/heads/main\n" try headContent.write(toFile: headPath, atomically: true, encoding: .utf8) print("Initialized empty SwiftGit repository in .git/") }
  7. git add import Foundation import CryptoKit import Compression func gitAdd(

    fi lePath: String) throws -> String { let data = try Data(contentsOf: URL( fi leURLWithPath: fi lePath)) let header = "blob \(data.count)\0".data(using: .utf8)! let store = header + data // SHA-1 let sha1 = Insecure.SHA1.hash(data: store) let hash = sha1.map { String(format: "%02x", $0) }.joined() // zlibѹॖ let compressed = try (store as NSData).compressed(using: .zlib) // .git/objects/xx/xxxx... let dir = ".git/objects/" + String(hash.pre fi x(2)) let fi le = String(hash.dropFirst(2)) try FileManager.default.createDirectory(atPath: dir, withIntermediateDirectories: true) try compressed.write(to: URL( fi leURLWithPath: "\(dir)/\( fi le)")) return hash }
  8. git commit func gitCommit(treeHash: String, parent: String?, message: String, author:

    String) throws -> String { var body = "tree \(treeHash)\n" if let p = parent { body += "parent \(p)\n" } let date = Int(Date().timeIntervalSince1970) body += "author \(author) \(date) +0000\n" body += "committer \(author) \(date) +0000\n\n" body += message + "\n" let data = body.data(using: .utf8)! let header = "commit \(data.count)\0".data(using: .utf8)! let store = header + data let sha1 = Insecure.SHA1.hash(data: store) let hash = sha1.map { String(format: "%02x", $0) }.joined() let compressed = try (store as NSData).compressed(using: .zlib) let dir = ".git/objects/" + String(hash.pre fi x(2)) let fi le = String(hash.dropFirst(2)) try FileManager.default.createDirectory(atPath: dir, withIntermediateDirectories: true) try compressed.write(to: URL( fi leURLWithPath: "\(dir)/\( fi le)")) return hash }
  9. git fetch func gitFetch(from remotePath: String, hash: String) throws {

    let dir = ".git/objects/" + String(hash.pre fi x(2)) let fi le = String(hash.dropFirst(2)) try FileManager.default.createDirectory(atPath: dir, withIntermediateDirectories: true) let src = "\(remotePath)/.git/objects/\(String(hash.pre fi x(2)))/\( fi le)" let dst = "\(dir)/\( fi le)" if !FileManager.default. fi leExists(atPath: dst) { try FileManager.default.copyItem(atPath: src, toPath: dst) } }
  10. git fetchͷ಺෦ϓϩηε 1. ࢀরͷൃݟ ΫϥΠΞϯτʮrefs/heads/*Λڭ͑ͯʯ 2. ωΰγΤʔγϣϯ ʮࢲ͸A,B,CΛ͍࣋ͬͯΔɻD,E͕ཉ͍͠ʯ 3. ύοΫੜ੒

    αʔόʔ͕ඞཁͳ෼͚ͩύοΫϑΝΠϧ࡞੒ 4. సૹ ࠷దԽ͞ΕͨόΠφϦܗࣜͰૹ৴ 5. ల։ɾΠϯσοΫε ϩʔΧϧͷobjects/ʹอଘ
  11. git cat- f ile -p func gitCatFile(hash: String) throws ->

    String { let dir = ".git/objects/" + String(hash.pre fi x(2)) let fi le = String(hash.dropFirst(2)) let compressed = try Data(contentsOf: URL( fi leURLWithPath: "\(dir)/\( fi le)")) // zlibల։ let decompressed = try (compressed as NSData).decompressed(using: .zlib) let str = String(data: decompressed as Data, encoding: .utf8)! // header ("blob 123\0" ͳͲ) Λ֎ͯ͠ฦ͢ if let range = str.range(of: "\0") { return String(str[range.upperBound...]) } return str }
  12. git log func gitLog() throws { // ݱࡏͷHEAD͕ͲͷϒϥϯνΛࢦ͔֬͢ೝ let headRef

    = try String(contentsOfFile: ".git/HEAD").trimmingCharacters(in: .whitespacesAndNewlines) guard headRef.starts(with: "ref: ") else { return } let refPath = ".git/" + headRef.dropFirst(5) let currentCommit = try String(contentsOfFile: refPath).trimmingCharacters(in: .whitespacesAndNewlines) var commitHash: String? = currentCommit while let hash = commitHash { // commitΦϒδΣΫτͷల։ let content = try gitCatFile(hash: hash) print("commit \(hash)") // message෦෼Λදࣔʢ຤ඌʹ͋Δʣ if let msgRange = content.range(of: "\n\n") { let message = content[msgRange.upperBound...].trimmingCharacters(in: .whitespacesAndNewlines) print(" \(message)") } // parentΛ୳͢ if let parentRange = content.range(of: "parent ") { let line = content[parentRange.upperBound...].components(separatedBy: "\n"). fi rst! commitHash = String(line) } else { commitHash = nil } print("") } }