美文网首页
实现类似Xcode里Git分支管理

实现类似Xcode里Git分支管理

作者: O2Space_Xiu | 来源:发表于2023-03-02 14:10 被阅读0次
    Xcode Git分支管理UI

    实现原理是在客户端应用里通过shell执行git相关命令

    需要一个Shell管理类:ShellClient.swift

    import Foundation
    import Combine
    
    public struct ShellClient {
        public var runLive: (_ args: String...) -> AnyPublisher<String, Never>
        public var run: (_ args: String...) throws -> String
        
        public init(
            runLive: @escaping (_ args: String...) -> AnyPublisher<String, Never>,
            run: @escaping (_ args: String...) throws -> String
        ) {
            self.runLive = runLive
            self.run = run
        }
    }
    public extension ShellClient {
        
        static func live() -> Self {
            func generateProcessAndPipe(_ args: [String]) -> (Process, Pipe) {
                var arguments = ["-c"]
                arguments.append(contentsOf: args)
                let task = Process()
                let pipe = Pipe()
                task.standardOutput = pipe
                task.standardError = pipe
                task.arguments = arguments
                task.executableURL = URL(fileURLWithPath: "/bin/zsh")
                return (task, pipe)
            }
    
            var cancellables: [UUID: AnyCancellable] = [:]
    
            func runLive(_ args: String...) -> AnyPublisher<String, Never> {
                let subject = PassthroughSubject<String, Never>()
                let (task, pipe) = generateProcessAndPipe(args)
                let outputHandler = pipe.fileHandleForReading
    
                outputHandler.waitForDataInBackgroundAndNotify()
                let id = UUID()
                cancellables[id] = NotificationCenter
                    .default
                    .publisher(for: .NSFileHandleDataAvailable, object: outputHandler)
                    .sink { _ in
                        let data = outputHandler.availableData
                        guard data.count > 0 else {
                            cancellables.removeValue(forKey: id)
                            subject.send(completion: .finished)
                            return
                        }
                        if let line = String(data: data, encoding: .utf8)?
                            .split(whereSeparator: \.isNewline) {
                            line
                                .map(String.init)
                                .forEach(subject.send(_:))
                        }
                        outputHandler.waitForDataInBackgroundAndNotify()
                    }
                task.launch()
                return subject.eraseToAnyPublisher()
            }
            
            func run(_ args: String...) throws -> String {
                let (task, pipe) = generateProcessAndPipe(args)
                try task.run()
                let data = pipe.fileHandleForReading.readDataToEndOfFile()
                return String(data: data, encoding: .utf8) ?? ""
            }
            
            return ShellClient(
                runLive: runLive(_:),
                run: run(_:)
            )
        }
    }
    

    需要一个Git管理类:GitClient.swift

    import Foundation
    import ShellClient
    
    public struct GitClient {
        public var getCurrentBranchName: () throws -> String
        public var getBranches: (Bool) throws -> [String]
        public var checkoutBranch: (String) throws -> Void
        
        init(
            getCurrentBranchName: @escaping () throws -> String,
            getBranches: @escaping (Bool) throws -> [String],
            checkoutBranch: @escaping (String) throws -> Void
        ) {
            self.getCurrentBranchName = getCurrentBranchName
            self.getBranches = getBranches
            self.checkoutBranch = checkoutBranch
        }
        
        public enum GitClientError: Error {
            case outputError(String)
            case notGitRepository
            case failedToDecodeURL
        }
        
        public static func `default`(
            directoryURL: URL,
            shellClient: ShellClient
        ) -> GitClient {
            
            // 获取当前分支名称
            func getCurrentBranchName() throws -> String {
                let output = try shellClient.run(
                    "cd \(directoryURL.relativePath.escapedWhiteSpaces());git rev-parse --abbrev-ref HEAD"
                )
                    .replacingOccurrences(of: "\n", with: "")
                
                if output.contains("fatal: not a git repository") {
                    throw GitClientError.notGitRepository
                }
                return output
            }
            
            // 参考 https://git-scm.com/docs/git-branch --format参数同 https://git-scm.com/docs/git-for-each-ref
            //git branch --sort committerdate --format '%(committerdate:short) %09 %(authorname) %09 %(refname:short) %09 %(objectname:short=7) %09 %(committer)'
            // committer(全部)、committeremail、committername、committerdate //提交人信息
            // objectname:提交编号
            //
            // 获取分支列表,这里只获取分支名称,其他信息参考上面信息
            func getBranches(_ allBranches: Bool = false) throws -> [String] {
                if allBranches == true {
                    //本地和远程所有分支(remotes/开头的表示远程分支)
                    return try shellClient.run(
                        "cd \(directoryURL.relativePath.escapedWhiteSpaces());git branch -a --format \"%(refname:short)\""
                    )
                        .components(separatedBy: "\n")
                        .filter { $0 != "" }
                }
                //本地所有分支
                return try shellClient.run(
                    "cd \(directoryURL.relativePath.escapedWhiteSpaces());git branch --format \"%(refname:short)\""
                )
                    .components(separatedBy: "\n")
                    .filter { $0 != "" }
            }
            
            // 选择分支
            func checkoutBranch(name: String) throws -> Void {
                guard try getCurrentBranchName() != name else { return }
                let output = try shellClient.run(
                    "cd \(directoryURL.relativePath.escapedWhiteSpaces());git checkout \(name)"
                )
                if output.contains("fatal: not a git repository") {
                    throw GitClientError.notGitRepository
                } else if !output.contains("Switched to branch") && !output.contains("Switched to a new branch") {
                    throw GitClientError.outputError(output)
                }
            }
            
            return GitClient(
                getCurrentBranchName: getCurrentBranchName,
                getBranches: getBranches(_:),
                checkoutBranch: checkoutBranch(name:))
        }
        
    }
    
    private extension String {
        func escapedWhiteSpaces() -> String {
            self.replacingOccurrences(of: " ", with: "\\ ")
        }
    }
    

    调用:

    
    var shellClient = ShellClient.live()
    var gitClient = GitClient.default(directoryURL: "本地git目录",shellClient:shellClient)
    // Git 获取当前分支名称
    var currentBranch = try? gitClient?.getCurrentBranchName()
    
     //Git 获取分支列表(除当前分支)
    var branchName:[String] {
      ((try? gitClient?.getBranches(false)) ?? []).filter{ $0 != currentBranch }
    }
    
    // Git切换分支
    func checkBranch(_ name: String) {
      //切换
      try? gitClient?.checkoutBranch(name)
      //重新获取当前分支名称
      currentBranch = try? gitClient?getCurrentBranchName()
    }
    
    

    最终实现效果

    UI代码就不贴了,采用SwiftUI写的,这里只展示了分支名称,当然可以根据上面注释加入提交人信息和提交编号,当然也可以加入类似Xcode一样的过滤搜索。Popover SwiftUI 采用.popover(isPresented:$“@State修饰变量”, arrowEdge:.bottom)

    最终实现 客户端自定义Git分支管理UI

    相关文章

      网友评论

          本文标题:实现类似Xcode里Git分支管理

          本文链接:https://www.haomeiwen.com/subject/qhjjldtx.html