美文网首页
实现类似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

相关文章

  • git常用命令

    分支管理 git 切换分支 git 查看远程分支 git 查看本地分支 git 创建本地分支 git 删除本地分支...

  • Git命令整理

    Git命令 ———————————————— git配置: git基本步骤: git分支管理: 创建分支命令: 切...

  • GIT分支管理

    GIT 分支管理 参考:在阿里,我们如何管理代码分支?GitHub Flow & Git Flow 基于Git...

  • git常用操作

    Basic Operation 分支管理切换分支git checkout git checkout -b #...

  • git提交代码规范管理

    GIT分支管理 git远程分支主要包括:master develop fixbugmaster:整个项目主分支,...

  • 2021-11-30

    一、分支管理 1、创建分支 git branch 2、查看分支 git branch...

  • git分支仓库管理

    git分支和标签管理 创建分支 git branch banchName git checkout -b bra...

  • Git:TortoiseGit 实现 Git 分支管理

    创建分支; 在工作区依次点击 鼠标右键 -> TortoiseGit -> Create Branch...; 下...

  • git分支管理与使用规范

    git分支管理与使用规范 分支管理 flow git flow github flow gitlab flow f...

  • git 创建分支提交远程分支

    Git创建与管理远程分支 1.创建本地分支 git branch 分支名,例如:git branch 2.0.1....

网友评论

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

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