Skip to content

Commit e13e7da

Browse files
authored
Git branch show track information (#1644)
* feat: source control repo add branch track info * fix: add spacing between track info and update text and icon order * fix: remove unused line
1 parent 63754a2 commit e13e7da

File tree

3 files changed

+80
-12
lines changed

3 files changed

+80
-12
lines changed

CodeEdit/Features/Git/Client/GitClient+Branches.swift

Lines changed: 59 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -11,39 +11,55 @@ extension GitClient {
1111
/// Get branches
1212
/// - Returns: Array of branches
1313
func getBranches() async throws -> [GitBranch] {
14-
let command = "branch --format \"%(refname:short)|%(refname)|%(upstream:short)\" -a"
14+
let command = "branch --format \"%(refname:short)|%(refname)|%(upstream:short) %(upstream:track)\" -a"
1515

1616
return try await run(command)
1717
.components(separatedBy: "\n")
1818
.filter { $0 != "" && !$0.contains("HEAD") }
1919
.compactMap { line in
20-
let components = line.components(separatedBy: "|")
21-
let name = components[0]
22-
let upstream = components[safe: 2]
20+
guard let branchPart = line.components(separatedBy: " ").first else { return nil }
21+
let branchComponents = branchPart.components(separatedBy: "|")
22+
let name = branchComponents[0]
23+
let upstream = branchComponents[safe: 2]
24+
25+
let trackInfoString = line
26+
.dropFirst(branchPart.count)
27+
.trimmingCharacters(in: .whitespacesWithoutNewlines)
28+
let trackInfo = parseBranchTrackInfo(from: trackInfoString)
2329

2430
return .init(
2531
name: name,
26-
longName: components[safe: 1] ?? name,
27-
upstream: upstream?.isEmpty == true ? nil : upstream
32+
longName: branchComponents[safe: 1] ?? name,
33+
upstream: upstream?.isEmpty == true ? nil : upstream,
34+
ahead: trackInfo.ahead,
35+
behind: trackInfo.behind
2836
)
2937
}
3038
}
3139

3240
/// Get current branch
3341
func getCurrentBranch() async throws -> GitBranch? {
3442
let branchName = try await run("branch --show-current").trimmingCharacters(in: .whitespacesAndNewlines)
35-
let components = try await run(
36-
"for-each-ref --format=\"%(refname)|%(upstream:short)\" refs/heads/\(branchName)"
43+
let output = try await run(
44+
"for-each-ref --format=\"%(refname)|%(upstream:short) %(upstream:track)\" refs/heads/\(branchName)"
3745
)
3846
.trimmingCharacters(in: .whitespacesAndNewlines)
39-
.components(separatedBy: "|")
4047

41-
let upstream = components[safe: 1]
48+
guard let branchPart = output.components(separatedBy: " ").first else { return nil }
49+
let branchComponents = branchPart.components(separatedBy: "|")
50+
let upstream = branchComponents[safe: 1]
51+
52+
let trackInfoString = output
53+
.dropFirst(branchPart.count)
54+
.trimmingCharacters(in: .whitespacesWithoutNewlines)
55+
let trackInfo = parseBranchTrackInfo(from: trackInfoString)
4256

4357
return .init(
4458
name: branchName,
45-
longName: components[0],
46-
upstream: upstream?.isEmpty == true ? nil : upstream
59+
longName: branchComponents[0],
60+
upstream: upstream?.isEmpty == true ? nil : upstream,
61+
ahead: trackInfo.ahead,
62+
behind: trackInfo.behind
4763
)
4864
}
4965

@@ -100,4 +116,35 @@ extension GitClient {
100116
}
101117
}
102118
}
119+
120+
private func parseBranchTrackInfo(from infoString: String) -> (ahead: Int, behind: Int) {
121+
let pattern = "\\[ahead (\\d+)(?:, behind (\\d+))?\\]|\\[behind (\\d+)\\]"
122+
// Create a regular expression object
123+
guard let regex = try? NSRegularExpression(pattern: pattern, options: []) else {
124+
fatalError("Invalid regular expression pattern")
125+
}
126+
var ahead = 0
127+
var behind = 0
128+
// Match the input string with the regular expression
129+
if let match = regex.firstMatch(
130+
in: infoString,
131+
options: [],
132+
range: NSRange(location: 0, length: infoString.utf16.count)
133+
) {
134+
// Extract the captured groups
135+
if let aheadRange = Range(match.range(at: 1), in: infoString),
136+
let aheadValue = Int(infoString[aheadRange]) {
137+
ahead = aheadValue
138+
}
139+
if let behindRange = Range(match.range(at: 2), in: infoString),
140+
let behindValue = Int(infoString[behindRange]) {
141+
behind = behindValue
142+
}
143+
if let behindRange = Range(match.range(at: 3), in: infoString),
144+
let behindValue = Int(infoString[behindRange]) {
145+
behind = behindValue
146+
}
147+
}
148+
return (ahead, behind)
149+
}
103150
}

CodeEdit/Features/Git/Client/Models/GitBranch.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ struct GitBranch: Hashable {
1111
let name: String
1212
let longName: String
1313
let upstream: String?
14+
let ahead: Int
15+
let behind: Int
1416

1517
/// Is local branch
1618
var isLocal: Bool {

CodeEdit/Features/NavigatorArea/SourceControlNavigator/Repository/Views/SourceControlNavigatorRepositoryItem.swift

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,25 @@ struct SourceControlNavigatorRepositoryItem: View {
2525
.foregroundStyle(.secondary)
2626
.font(.system(size: 11))
2727
}
28+
Spacer()
29+
HStack(spacing: 5) {
30+
if let behind = item.branch?.behind, behind > 0 {
31+
HStack(spacing: 0) {
32+
Image(systemName: "arrow.down")
33+
.imageScale(.small)
34+
Text("\(behind)")
35+
.font(.system(size: 11))
36+
}
37+
}
38+
if let ahead = item.branch?.ahead, ahead > 0 {
39+
HStack(spacing: 0) {
40+
Image(systemName: "arrow.up")
41+
.imageScale(.small)
42+
Text("\(ahead)")
43+
.font(.system(size: 11))
44+
}
45+
}
46+
}
2847
}, icon: {
2948
if item.symbolImage != nil {
3049
Image(symbol: item.symbolImage ?? "")

0 commit comments

Comments
 (0)