krz/orgstar

A native macOS editor for org-mode files. editor org-mode swift

Sources/OrgApp/GitBranch.swift

abca8dc581329472f7fb8ccd3af147e5fb882f29
orgstar/Sources/OrgApp/GitBranch.swift history · blame · raw

41 lines · 1998 bytes

 1import Foundation
 2
 3/// The checked-out branch of the repository holding a file, read from `HEAD` without running
 4/// git.
 5public enum GitBranch {
 6    /// The branch name, the first 7 characters of the commit when HEAD is detached, or nil
 7    /// outside a repository.
 8    public static func current(for path: String) -> String? {
 9        var directory = URL(fileURLWithPath: path).deletingLastPathComponent()
10        while true {
11            let dotGit = directory.appendingPathComponent(".git")
12            if let head = head(dotGit) { return parse(head) }
13            let parent = directory.deletingLastPathComponent()
14            if parent.path == directory.path { return nil }
15            directory = parent
16        }
17    }
18
19    /// `HEAD` of a `.git` folder, or of the folder a `.git` file (worktree, submodule) names.
20    static func head(_ dotGit: URL) -> String? {
21        var isDirectory: ObjCBool = false
22        guard FileManager.default.fileExists(atPath: dotGit.path, isDirectory: &isDirectory) else { return nil }
23        var gitDir = dotGit
24        if !isDirectory.boolValue {
25            guard let link = try? String(contentsOf: dotGit, encoding: .utf8),
26                  let line = link.split(separator: "\n").first, line.hasPrefix("gitdir: ") else { return nil }
27            let target = String(line.dropFirst("gitdir: ".count))
28            gitDir = target.hasPrefix("/") ? URL(fileURLWithPath: target) : dotGit.deletingLastPathComponent().appendingPathComponent(target)
29        }
30        return try? String(contentsOf: gitDir.appendingPathComponent("HEAD"), encoding: .utf8)
31    }
32
33    static func parse(_ head: String) -> String? {
34        let line = head.trimmingCharacters(in: .whitespacesAndNewlines)
35        if line.hasPrefix("ref: ") {
36            let ref = line.dropFirst("ref: ".count)
37            return ref.hasPrefix("refs/heads/") ? String(ref.dropFirst("refs/heads/".count)) : String(ref)
38        }
39        return line.isEmpty ? nil : String(line.prefix(7))
40    }
41}