From 54b41aba2a43ba713dfd4e86e15aae3b15ce0863 Mon Sep 17 00:00:00 2001 From: phlpsong Date: Thu, 21 Mar 2024 19:31:55 +0800 Subject: [PATCH 1/3] feat: source control repo add branch track info --- .../Git/Client/GitClient+Branches.swift | 73 ++++++++++++++++--- .../Git/Client/Models/GitBranch.swift | 2 + ...SourceControlNavigatorRepositoryItem.swift | 15 ++++ 3 files changed, 78 insertions(+), 12 deletions(-) diff --git a/CodeEdit/Features/Git/Client/GitClient+Branches.swift b/CodeEdit/Features/Git/Client/GitClient+Branches.swift index 187df7bda8..4126ffb6e5 100644 --- a/CodeEdit/Features/Git/Client/GitClient+Branches.swift +++ b/CodeEdit/Features/Git/Client/GitClient+Branches.swift @@ -11,20 +11,30 @@ extension GitClient { /// Get branches /// - Returns: Array of branches func getBranches() async throws -> [GitBranch] { - let command = "branch --format \"%(refname:short)|%(refname)|%(upstream:short)\" -a" + let command = "branch --format \"%(refname:short)|%(refname)|%(upstream:short) %(upstream:track)\" -a" return try await run(command) .components(separatedBy: "\n") .filter { $0 != "" && !$0.contains("HEAD") } .compactMap { line in - let components = line.components(separatedBy: "|") - let name = components[0] - let upstream = components[safe: 2] + let components = line.components(separatedBy: " ") + .filter { !$0.isEmpty } + guard let branchPart = line.components(separatedBy: " ").first else { return nil } + let branchComponents = branchPart.components(separatedBy: "|") + let name = branchComponents[0] + let upstream = branchComponents[safe: 2] + + let trackInfoString = line + .dropFirst(branchPart.count) + .trimmingCharacters(in: .whitespacesWithoutNewlines) + let trackInfo = parseBranchTrackInfo(from: trackInfoString) return .init( name: name, - longName: components[safe: 1] ?? name, - upstream: upstream?.isEmpty == true ? nil : upstream + longName: branchComponents[safe: 1] ?? name, + upstream: upstream?.isEmpty == true ? nil : upstream, + ahead: trackInfo.ahead, + behind: trackInfo.behind ) } } @@ -32,18 +42,26 @@ extension GitClient { /// Get current branch func getCurrentBranch() async throws -> GitBranch? { let branchName = try await run("branch --show-current").trimmingCharacters(in: .whitespacesAndNewlines) - let components = try await run( - "for-each-ref --format=\"%(refname)|%(upstream:short)\" refs/heads/\(branchName)" + let output = try await run( + "for-each-ref --format=\"%(refname)|%(upstream:short) %(upstream:track)\" refs/heads/\(branchName)" ) .trimmingCharacters(in: .whitespacesAndNewlines) - .components(separatedBy: "|") - let upstream = components[safe: 1] + guard let branchPart = output.components(separatedBy: " ").first else { return nil } + let branchComponents = branchPart.components(separatedBy: "|") + let upstream = branchComponents[safe: 1] + + let trackInfoString = output + .dropFirst(branchPart.count) + .trimmingCharacters(in: .whitespacesWithoutNewlines) + let trackInfo = parseBranchTrackInfo(from: trackInfoString) return .init( name: branchName, - longName: components[0], - upstream: upstream?.isEmpty == true ? nil : upstream + longName: branchComponents[0], + upstream: upstream?.isEmpty == true ? nil : upstream, + ahead: trackInfo.ahead, + behind: trackInfo.behind ) } @@ -100,4 +118,35 @@ extension GitClient { } } } + + private func parseBranchTrackInfo(from infoString: String) -> (ahead: Int, behind: Int) { + let pattern = "\\[ahead (\\d+)(?:, behind (\\d+))?\\]|\\[behind (\\d+)\\]" + // Create a regular expression object + guard let regex = try? NSRegularExpression(pattern: pattern, options: []) else { + fatalError("Invalid regular expression pattern") + } + var ahead = 0 + var behind = 0 + // Match the input string with the regular expression + if let match = regex.firstMatch( + in: infoString, + options: [], + range: NSRange(location: 0, length: infoString.utf16.count) + ) { + // Extract the captured groups + if let aheadRange = Range(match.range(at: 1), in: infoString), + let aheadValue = Int(infoString[aheadRange]) { + ahead = aheadValue + } + if let behindRange = Range(match.range(at: 2), in: infoString), + let behindValue = Int(infoString[behindRange]) { + behind = behindValue + } + if let behindRange = Range(match.range(at: 3), in: infoString), + let behindValue = Int(infoString[behindRange]) { + behind = behindValue + } + } + return (ahead, behind) + } } diff --git a/CodeEdit/Features/Git/Client/Models/GitBranch.swift b/CodeEdit/Features/Git/Client/Models/GitBranch.swift index e9ea448406..338108c454 100644 --- a/CodeEdit/Features/Git/Client/Models/GitBranch.swift +++ b/CodeEdit/Features/Git/Client/Models/GitBranch.swift @@ -11,6 +11,8 @@ struct GitBranch: Hashable { let name: String let longName: String let upstream: String? + let ahead: Int + let behind: Int /// Is local branch var isLocal: Bool { diff --git a/CodeEdit/Features/NavigatorArea/SourceControlNavigator/Repository/Views/SourceControlNavigatorRepositoryItem.swift b/CodeEdit/Features/NavigatorArea/SourceControlNavigator/Repository/Views/SourceControlNavigatorRepositoryItem.swift index eb43da3347..0a15b1d51f 100644 --- a/CodeEdit/Features/NavigatorArea/SourceControlNavigator/Repository/Views/SourceControlNavigatorRepositoryItem.swift +++ b/CodeEdit/Features/NavigatorArea/SourceControlNavigator/Repository/Views/SourceControlNavigatorRepositoryItem.swift @@ -25,6 +25,21 @@ struct SourceControlNavigatorRepositoryItem: View { .foregroundStyle(.secondary) .font(.system(size: 11)) } + Spacer() + HStack(spacing: 0) { + if let ahead = item.branch?.ahead, ahead > 0 { + Text("\(ahead)") + .font(.system(size: 11)) + Image(systemName: "arrow.up") + .imageScale(.small) + } + if let behind = item.branch?.behind, behind > 0 { + Text("\(behind)") + .font(.system(size: 11)) + Image(systemName: "arrow.down") + .imageScale(.small) + } + } }, icon: { if item.symbolImage != nil { Image(symbol: item.symbolImage ?? "") From 7ffb6c24cf8b92a0a745b7463812b2520edc36a2 Mon Sep 17 00:00:00 2001 From: phlpsong Date: Fri, 22 Mar 2024 18:05:51 +0800 Subject: [PATCH 2/3] fix: add spacing between track info and update text and icon order --- ...SourceControlNavigatorRepositoryItem.swift | 26 +++++++++++-------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/CodeEdit/Features/NavigatorArea/SourceControlNavigator/Repository/Views/SourceControlNavigatorRepositoryItem.swift b/CodeEdit/Features/NavigatorArea/SourceControlNavigator/Repository/Views/SourceControlNavigatorRepositoryItem.swift index 0a15b1d51f..caf40bc235 100644 --- a/CodeEdit/Features/NavigatorArea/SourceControlNavigator/Repository/Views/SourceControlNavigatorRepositoryItem.swift +++ b/CodeEdit/Features/NavigatorArea/SourceControlNavigator/Repository/Views/SourceControlNavigatorRepositoryItem.swift @@ -26,18 +26,22 @@ struct SourceControlNavigatorRepositoryItem: View { .font(.system(size: 11)) } Spacer() - HStack(spacing: 0) { - if let ahead = item.branch?.ahead, ahead > 0 { - Text("\(ahead)") - .font(.system(size: 11)) - Image(systemName: "arrow.up") - .imageScale(.small) - } + HStack(spacing: 5) { if let behind = item.branch?.behind, behind > 0 { - Text("\(behind)") - .font(.system(size: 11)) - Image(systemName: "arrow.down") - .imageScale(.small) + HStack(spacing: 0) { + Image(systemName: "arrow.down") + .imageScale(.small) + Text("\(behind)") + .font(.system(size: 11)) + } + } + if let ahead = item.branch?.ahead, ahead > 0 { + HStack(spacing: 0) { + Image(systemName: "arrow.up") + .imageScale(.small) + Text("\(ahead)") + .font(.system(size: 11)) + } } } }, icon: { From d74414a62005c79d85f34abdbe57e1fb52eaa826 Mon Sep 17 00:00:00 2001 From: phlpsong Date: Sat, 23 Mar 2024 09:23:16 +0800 Subject: [PATCH 3/3] fix: remove unused line --- CodeEdit/Features/Git/Client/GitClient+Branches.swift | 2 -- 1 file changed, 2 deletions(-) diff --git a/CodeEdit/Features/Git/Client/GitClient+Branches.swift b/CodeEdit/Features/Git/Client/GitClient+Branches.swift index 4126ffb6e5..ac92e6b421 100644 --- a/CodeEdit/Features/Git/Client/GitClient+Branches.swift +++ b/CodeEdit/Features/Git/Client/GitClient+Branches.swift @@ -17,8 +17,6 @@ extension GitClient { .components(separatedBy: "\n") .filter { $0 != "" && !$0.contains("HEAD") } .compactMap { line in - let components = line.components(separatedBy: " ") - .filter { !$0.isEmpty } guard let branchPart = line.components(separatedBy: " ").first else { return nil } let branchComponents = branchPart.components(separatedBy: "|") let name = branchComponents[0]