Skip to content

Commit 0565305

Browse files
committed
Dynamically resolve gitignore file path.
1 parent dbf0929 commit 0565305

File tree

3 files changed

+196
-161
lines changed

3 files changed

+196
-161
lines changed

CodeEdit/Features/Settings/Pages/SourceControlSettings/Models/IgnorePatternModel.swift

Lines changed: 95 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -21,23 +21,42 @@ class IgnorePatternModel: ObservableObject {
2121
@Published var selection: Set<UUID> = []
2222

2323
private let gitConfig = GitConfigClient(shellClient: currentWorld.shellClient)
24-
private let fileURL = FileManager.default.homeDirectoryForCurrentUser.appendingPathComponent(".gitignore_global")
2524
private var fileMonitor: DispatchSourceFileSystemObject?
2625

2726
init() {
28-
loadPatterns()
29-
startFileMonitor()
27+
Task {
28+
try? await startFileMonitor()
29+
await loadPatterns()
30+
}
3031
}
3132

3233
deinit {
3334
stopFileMonitor()
3435
}
3536

36-
private func startFileMonitor() {
37-
let fileDescriptor = open(fileURL.path, O_EVTONLY)
38-
guard fileDescriptor != -1 else {
39-
return
37+
private func gitIgnoreURL() async throws -> URL {
38+
let excludesfile = try await gitConfig.get(key: "core.excludesfile") ?? ""
39+
if !excludesfile.isEmpty {
40+
if excludesfile.starts(with: "~/") {
41+
let relativePath = String(excludesfile.dropFirst(2)) // Remove "~/"
42+
return FileManager.default.homeDirectoryForCurrentUser.appendingPathComponent(relativePath)
43+
} else if excludesfile.starts(with: "/") {
44+
return URL(fileURLWithPath: excludesfile) // Absolute path
45+
} else {
46+
return FileManager.default.homeDirectoryForCurrentUser.appendingPathComponent(excludesfile)
47+
}
48+
} else {
49+
let defaultPath = ".gitignore_global"
50+
let fileURL = FileManager.default.homeDirectoryForCurrentUser.appendingPathComponent(defaultPath)
51+
await gitConfig.set(key: "core.excludesfile", value: "~/\(defaultPath)", global: true)
52+
return fileURL
4053
}
54+
}
55+
56+
private func startFileMonitor() async throws {
57+
let fileURL = try await gitIgnoreURL()
58+
let fileDescriptor = open(fileURL.path, O_EVTONLY)
59+
guard fileDescriptor != -1 else { return }
4160

4261
let source = DispatchSource.makeFileSystemObjectSource(
4362
fileDescriptor: fileDescriptor,
@@ -46,14 +65,16 @@ class IgnorePatternModel: ObservableObject {
4665
)
4766

4867
source.setEventHandler { [weak self] in
49-
self?.loadPatterns()
68+
Task {
69+
await self?.loadPatterns()
70+
}
5071
}
5172

5273
source.setCancelHandler {
5374
close(fileDescriptor)
5475
}
5576

56-
fileMonitor?.cancel() // Cancel any existing monitor
77+
fileMonitor?.cancel()
5778
fileMonitor = source
5879
source.resume()
5980
}
@@ -63,50 +84,76 @@ class IgnorePatternModel: ObservableObject {
6384
fileMonitor = nil
6485
}
6586

66-
func loadPatterns() {
67-
loadingPatterns = true
87+
func loadPatterns() async {
88+
await MainActor.run { loadingPatterns = true } // Ensure `loadingPatterns` is updated on the main thread
6889

69-
guard FileManager.default.fileExists(atPath: fileURL.path) else {
70-
patterns = []
71-
return
72-
}
90+
do {
91+
let fileURL = try await gitIgnoreURL()
92+
guard FileManager.default.fileExists(atPath: fileURL.path) else {
93+
await MainActor.run {
94+
patterns = []
95+
loadingPatterns = false // Update on the main thread
96+
}
97+
return
98+
}
7399

74-
if let content = try? String(contentsOf: fileURL) {
75-
patterns = content.split(separator: "\n")
76-
.map { $0.trimmingCharacters(in: .whitespacesAndNewlines) }
77-
.filter { !$0.isEmpty && !$0.starts(with: "#") }
78-
.map { GlobPattern(value: String($0)) }
100+
if let content = try? String(contentsOf: fileURL) {
101+
let parsedPatterns = content.split(separator: "\n")
102+
.map { $0.trimmingCharacters(in: .whitespacesAndNewlines) }
103+
.filter { !$0.isEmpty && !$0.starts(with: "#") }
104+
.map { GlobPattern(value: String($0)) }
105+
106+
await MainActor.run {
107+
patterns = parsedPatterns // Update `patterns` on the main thread
108+
loadingPatterns = false // Ensure `loadingPatterns` is updated on the main thread
109+
}
110+
} else {
111+
await MainActor.run {
112+
patterns = []
113+
loadingPatterns = false
114+
}
115+
}
116+
} catch {
117+
print("Error loading patterns: \(error)")
118+
await MainActor.run {
119+
patterns = []
120+
loadingPatterns = false
121+
}
79122
}
80123
}
81124

82-
// Map to track the line numbers of patterns.
83-
var patternLineMapping: [String: Int] = [:]
84-
85125
func getPattern(for id: UUID) -> GlobPattern? {
86126
return patterns.first(where: { $0.id == id })
87127
}
88128

89129
func savePatterns() {
90-
stopFileMonitor() // Suspend the file monitor to avoid self-triggered updates
91-
defer { startFileMonitor() }
92-
93-
guard let fileContent = try? String(contentsOf: fileURL) else {
94-
writeAllPatterns()
95-
return
96-
}
130+
Task {
131+
stopFileMonitor()
132+
defer { Task { try? await startFileMonitor() } }
133+
134+
do {
135+
let fileURL = try await gitIgnoreURL()
136+
guard let fileContent = try? String(contentsOf: fileURL) else {
137+
writeAllPatterns()
138+
return
139+
}
97140

98-
let lines = fileContent.split(separator: "\n", omittingEmptySubsequences: false).map(String.init)
99-
let (patternToLineIndex, nonPatternLines) = mapLines(lines)
100-
let globalCommentLines = extractGlobalComments(nonPatternLines, patternToLineIndex)
141+
let lines = fileContent.split(separator: "\n", omittingEmptySubsequences: false).map(String.init)
142+
let (patternToLineIndex, nonPatternLines) = mapLines(lines)
143+
let globalCommentLines = extractGlobalComments(nonPatternLines, patternToLineIndex)
101144

102-
var reorderedLines = reorderPatterns(globalCommentLines, patternToLineIndex, nonPatternLines, lines)
145+
var reorderedLines = reorderPatterns(globalCommentLines, patternToLineIndex, nonPatternLines, lines)
103146

104-
// Ensure single blank line at the end
105-
reorderedLines = cleanUpWhitespace(in: reorderedLines)
147+
// Ensure single blank line at the end
148+
reorderedLines = cleanUpWhitespace(in: reorderedLines)
106149

107-
// Write the updated content back to the file
108-
let updatedContent = reorderedLines.joined(separator: "\n")
109-
try? updatedContent.write(to: fileURL, atomically: true, encoding: .utf8)
150+
// Write the updated content back to the file
151+
let updatedContent = reorderedLines.joined(separator: "\n")
152+
try updatedContent.write(to: fileURL, atomically: true, encoding: .utf8)
153+
} catch {
154+
print("Error saving patterns: \(error)")
155+
}
156+
}
110157
}
111158

112159
private func mapLines(_ lines: [String]) -> ([String: Int], [(line: String, index: Int)]) {
@@ -181,53 +228,18 @@ class IgnorePatternModel: ObservableObject {
181228
}
182229

183230
private func writeAllPatterns() {
184-
let content = patterns.map(\.value).joined(separator: "\n")
185231
Task {
186-
let excludesfile: String? = try await gitConfig.get(key: "core.excludesfile")
187-
if excludesfile == "" {
188-
await gitConfig.set(key: "core.excludesfile", value: "~/\(fileURL.lastPathComponent)")
189-
}
190-
}
191-
try? content.write(to: fileURL, atomically: true, encoding: .utf8)
192-
}
193-
194-
private func handlePatterns(
195-
_ lines: inout [String],
196-
existingPatterns: inout Set<String>,
197-
patternLineMap: inout [String: Int]
198-
) {
199-
var handledPatterns = Set<String>()
200-
201-
// Update or preserve existing patterns
202-
for pattern in patterns {
203-
let value = pattern.value
204-
if let lineIndex = patternLineMap[value] {
205-
// Pattern already exists, update it in place
206-
lines[lineIndex] = value
207-
handledPatterns.insert(value)
208-
} else {
209-
// Check if the pattern has been edited and corresponds to a previous pattern
210-
if let oldPattern = existingPatterns.first(where: { !handledPatterns.contains($0) && $0 != value }),
211-
let lineIndex = patternLineMap[oldPattern] {
212-
lines[lineIndex] = value
213-
existingPatterns.remove(oldPattern)
214-
patternLineMap[value] = lineIndex
215-
handledPatterns.insert(value)
216-
} else {
217-
// Append new patterns at the end
218-
if let lastLine = lines.last, lastLine.trimmingCharacters(in: .whitespaces).isEmpty {
219-
lines.removeLast() // Remove trailing blank line before appending
220-
}
221-
lines.append(value)
232+
do {
233+
let fileURL = try await gitIgnoreURL()
234+
if !FileManager.default.fileExists(atPath: fileURL.path) {
235+
FileManager.default.createFile(atPath: fileURL.path, contents: nil)
222236
}
223-
}
224-
}
225237

226-
// Remove patterns no longer in the list
227-
let currentPatterns = Set(patterns.map(\.value))
228-
lines = lines.filter { line in
229-
let trimmedLine = line.trimmingCharacters(in: .whitespaces)
230-
return trimmedLine.isEmpty || trimmedLine.hasPrefix("#") || currentPatterns.contains(trimmedLine)
238+
let content = patterns.map(\.value).joined(separator: "\n")
239+
try content.write(to: fileURL, atomically: true, encoding: .utf8)
240+
} catch {
241+
print("Failed to write all patterns: \(error)")
242+
}
231243
}
232244
}
233245

CodeEdit/Features/Settings/Pages/SourceControlSettings/SourceControlGeneralView.swift

Lines changed: 0 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,6 @@ struct SourceControlGeneralView: View {
1313

1414
let gitConfig = GitConfigClient(shellClient: currentWorld.shellClient)
1515

16-
@State private var defaultBranch: String = ""
17-
@State private var hasAppeared = false
18-
1916
var body: some View {
2017
SettingsForm {
2118
Section("Source Control") {
@@ -32,15 +29,6 @@ struct SourceControlGeneralView: View {
3229
Section {
3330
comparisonView
3431
sourceControlNavigator
35-
defaultBranchName
36-
}
37-
}
38-
.onAppear {
39-
Task {
40-
defaultBranch = try await gitConfig.get(key: "init.defaultBranch", global: true) ?? ""
41-
DispatchQueue.main.async {
42-
hasAppeared = true
43-
}
4432
}
4533
}
4634
}
@@ -125,20 +113,4 @@ private extension SourceControlGeneralView {
125113
.tag(SettingsData.ControlNavigatorOrder.sortByDate)
126114
}
127115
}
128-
129-
private var defaultBranchName: some View {
130-
TextField(text: $defaultBranch) {
131-
Text("Default branch name")
132-
Text("Cannot contain spaces, backslashes, or other symbols")
133-
}
134-
.onChange(of: defaultBranch) { newValue in
135-
if hasAppeared {
136-
Limiter.debounce(id: "defaultBranchDebouncer", duration: 0.5) {
137-
Task {
138-
await gitConfig.set(key: "init.defaultBranch", value: newValue, global: true)
139-
}
140-
}
141-
}
142-
}
143-
}
144116
}

0 commit comments

Comments
 (0)