Commit 3078d39ae2

3078d39ae26f4c077bb9186ce0ec692263d589ef

parent: b0ba9d0cc4

Unsigned

cmc <hello@cleberg.net> · 2026-04-14 01:32 UTC

fix: more sonarqube quality fixes

Layout: unified · split

Hutch.xcodeproj/project.pbxproj +8 −8
@@ -515,7 +515,7 @@
515 ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 515 ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
516 CODE_SIGN_ENTITLEMENTS = Hutch/Hutch.entitlements; 516 CODE_SIGN_ENTITLEMENTS = Hutch/Hutch.entitlements;
517 CODE_SIGN_STYLE = Automatic; 517 CODE_SIGN_STYLE = Automatic;
518 CURRENT_PROJECT_VERSION = 72; 518 CURRENT_PROJECT_VERSION = 73;
519 DEVELOPMENT_TEAM = ZCNAX3VL9D; 519 DEVELOPMENT_TEAM = ZCNAX3VL9D;
520 ENABLE_PREVIEWS = YES; 520 ENABLE_PREVIEWS = YES;
521 GENERATE_INFOPLIST_FILE = YES; 521 GENERATE_INFOPLIST_FILE = YES;
@@ -532,7 +532,7 @@
532 "$(inherited)", 532 "$(inherited)",
533 "@executable_path/Frameworks", 533 "@executable_path/Frameworks",
534 ); 534 );
535 MARKETING_VERSION = 3.1.4; 535 MARKETING_VERSION = 3.1.5;
536 PRODUCT_BUNDLE_IDENTIFIER = net.cleberg.Hutch; 536 PRODUCT_BUNDLE_IDENTIFIER = net.cleberg.Hutch;
537 PRODUCT_NAME = "$(TARGET_NAME)"; 537 PRODUCT_NAME = "$(TARGET_NAME)";
538 STRING_CATALOG_GENERATE_SYMBOLS = YES; 538 STRING_CATALOG_GENERATE_SYMBOLS = YES;
@@ -552,7 +552,7 @@
552 ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 552 ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
553 CODE_SIGN_ENTITLEMENTS = Hutch/Hutch.entitlements; 553 CODE_SIGN_ENTITLEMENTS = Hutch/Hutch.entitlements;
554 CODE_SIGN_STYLE = Automatic; 554 CODE_SIGN_STYLE = Automatic;
555 CURRENT_PROJECT_VERSION = 72; 555 CURRENT_PROJECT_VERSION = 73;
556 DEVELOPMENT_TEAM = ZCNAX3VL9D; 556 DEVELOPMENT_TEAM = ZCNAX3VL9D;
557 ENABLE_PREVIEWS = YES; 557 ENABLE_PREVIEWS = YES;
558 GENERATE_INFOPLIST_FILE = YES; 558 GENERATE_INFOPLIST_FILE = YES;
@@ -569,7 +569,7 @@
569 "$(inherited)", 569 "$(inherited)",
570 "@executable_path/Frameworks", 570 "@executable_path/Frameworks",
571 ); 571 );
572 MARKETING_VERSION = 3.1.4; 572 MARKETING_VERSION = 3.1.5;
573 PRODUCT_BUNDLE_IDENTIFIER = net.cleberg.Hutch; 573 PRODUCT_BUNDLE_IDENTIFIER = net.cleberg.Hutch;
574 PRODUCT_NAME = "$(TARGET_NAME)"; 574 PRODUCT_NAME = "$(TARGET_NAME)";
575 STRING_CATALOG_GENERATE_SYMBOLS = YES; 575 STRING_CATALOG_GENERATE_SYMBOLS = YES;
@@ -632,7 +632,7 @@
632 APPLICATION_EXTENSION_API_ONLY = YES; 632 APPLICATION_EXTENSION_API_ONLY = YES;
633 CODE_SIGN_ENTITLEMENTS = HutchWidgetExtension/HutchWidgetExtension.entitlements; 633 CODE_SIGN_ENTITLEMENTS = HutchWidgetExtension/HutchWidgetExtension.entitlements;
634 CODE_SIGN_STYLE = Automatic; 634 CODE_SIGN_STYLE = Automatic;
635 CURRENT_PROJECT_VERSION = 72; 635 CURRENT_PROJECT_VERSION = 73;
636 DEVELOPMENT_TEAM = ZCNAX3VL9D; 636 DEVELOPMENT_TEAM = ZCNAX3VL9D;
637 GENERATE_INFOPLIST_FILE = NO; 637 GENERATE_INFOPLIST_FILE = NO;
638 INFOPLIST_FILE = HutchWidgetExtension/Info.plist; 638 INFOPLIST_FILE = HutchWidgetExtension/Info.plist;
@@ -642,7 +642,7 @@
642 "@executable_path/Frameworks", 642 "@executable_path/Frameworks",
643 "@executable_path/../../Frameworks", 643 "@executable_path/../../Frameworks",
644 ); 644 );
645 MARKETING_VERSION = 3.1.4; 645 MARKETING_VERSION = 3.1.5;
646 PRODUCT_BUNDLE_IDENTIFIER = net.cleberg.Hutch.HutchWidgetExtension; 646 PRODUCT_BUNDLE_IDENTIFIER = net.cleberg.Hutch.HutchWidgetExtension;
647 PRODUCT_NAME = "$(TARGET_NAME)"; 647 PRODUCT_NAME = "$(TARGET_NAME)";
648 SKIP_INSTALL = YES; 648 SKIP_INSTALL = YES;
@@ -661,7 +661,7 @@
661 APPLICATION_EXTENSION_API_ONLY = YES; 661 APPLICATION_EXTENSION_API_ONLY = YES;
662 CODE_SIGN_ENTITLEMENTS = HutchWidgetExtension/HutchWidgetExtension.entitlements; 662 CODE_SIGN_ENTITLEMENTS = HutchWidgetExtension/HutchWidgetExtension.entitlements;
663 CODE_SIGN_STYLE = Automatic; 663 CODE_SIGN_STYLE = Automatic;
664 CURRENT_PROJECT_VERSION = 72; 664 CURRENT_PROJECT_VERSION = 73;
665 DEVELOPMENT_TEAM = ZCNAX3VL9D; 665 DEVELOPMENT_TEAM = ZCNAX3VL9D;
666 GENERATE_INFOPLIST_FILE = NO; 666 GENERATE_INFOPLIST_FILE = NO;
667 INFOPLIST_FILE = HutchWidgetExtension/Info.plist; 667 INFOPLIST_FILE = HutchWidgetExtension/Info.plist;
@@ -671,7 +671,7 @@
671 "@executable_path/Frameworks", 671 "@executable_path/Frameworks",
672 "@executable_path/../../Frameworks", 672 "@executable_path/../../Frameworks",
673 ); 673 );
674 MARKETING_VERSION = 3.1.4; 674 MARKETING_VERSION = 3.1.5;
675 PRODUCT_BUNDLE_IDENTIFIER = net.cleberg.Hutch.HutchWidgetExtension; 675 PRODUCT_BUNDLE_IDENTIFIER = net.cleberg.Hutch.HutchWidgetExtension;
676 PRODUCT_NAME = "$(TARGET_NAME)"; 676 PRODUCT_NAME = "$(TARGET_NAME)";
677 SKIP_INSTALL = YES; 677 SKIP_INSTALL = YES;
Hutch/Networking/SRHTClient.swift +13 −12
@@ -23,13 +23,13 @@ final class SRHTClient: Sendable {
23 23
24 /// The personal access token used for `Authorization: Bearer` headers. 24 /// The personal access token used for `Authorization: Bearer` headers.
25 /// Loaded from Keychain on init; can be refreshed via ``reloadToken()``. 25 /// Loaded from Keychain on init; can be refreshed via ``reloadToken()``.
26 private let _token: OSAllocatedUnfairLock<String?> 26 private let tokenLock: OSAllocatedUnfairLock<String?>
27 27
28 /// In-memory response cache for stale-while-revalidate pattern. 28 /// In-memory response cache for stale-while-revalidate pattern.
29 let responseCache = ResponseCache() 29 let responseCache = ResponseCache()
30 30
31 var hasToken: Bool { 31 var hasToken: Bool {
32 _token.withLock { $0 != nil } 32 tokenLock.withLock { $0 != nil }
33 } 33 }
34 34
35 init(session: URLSession = .shared, token: String? = nil) { 35 init(session: URLSession = .shared, token: String? = nil) {
@@ -37,12 +37,12 @@ final class SRHTClient: Sendable {
37 self.decoder = JSONDecoder() 37 self.decoder = JSONDecoder()
38 self.decoder.dateDecodingStrategy = .srhtFlexible 38 self.decoder.dateDecodingStrategy = .srhtFlexible
39 self.encoder = JSONEncoder() 39 self.encoder = JSONEncoder()
40 self._token = OSAllocatedUnfairLock(initialState: token) 40 self.tokenLock = OSAllocatedUnfairLock(initialState: token)
41 } 41 }
42 42
43 /// Update the stored token (e.g. after the user saves a new one in Keychain). 43 /// Update the stored token (e.g. after the user saves a new one in Keychain).
44 func setToken(_ token: String?) { 44 func setToken(_ token: String?) {
45 _token.withLock { $0 = token } 45 tokenLock.withLock { $0 = token }
46 } 46 }
47 47
48 /// Execute a GraphQL query or mutation against a SourceHut service. 48 /// Execute a GraphQL query or mutation against a SourceHut service.
@@ -59,7 +59,7 @@ final class SRHTClient: Sendable {
59 variables: [String: any Sendable]? = nil, 59 variables: [String: any Sendable]? = nil,
60 responseType _: T.Type 60 responseType _: T.Type
61 ) async throws -> T { 61 ) async throws -> T {
62 guard let token = _token.withLock({ $0 }), !token.isEmpty else { 62 guard let token = tokenLock.withLock({ $0 }), !token.isEmpty else {
63 throw SRHTError.unauthorized 63 throw SRHTError.unauthorized
64 } 64 }
65 65
@@ -173,7 +173,7 @@ final class SRHTClient: Sendable {
173 file: MultipartUploadFile, 173 file: MultipartUploadFile,
174 responseType _: T.Type 174 responseType _: T.Type
175 ) async throws -> T { 175 ) async throws -> T {
176 guard let token = _token.withLock({ $0 }), !token.isEmpty else { 176 guard let token = tokenLock.withLock({ $0 }), !token.isEmpty else {
177 throw SRHTError.unauthorized 177 throw SRHTError.unauthorized
178 } 178 }
179 179
@@ -307,7 +307,7 @@ final class SRHTClient: Sendable {
307 files: [MultipartUploadFile], 307 files: [MultipartUploadFile],
308 responseType _: T.Type 308 responseType _: T.Type
309 ) async throws -> T { 309 ) async throws -> T {
310 guard let token = _token.withLock({ $0 }), !token.isEmpty else { 310 guard let token = tokenLock.withLock({ $0 }), !token.isEmpty else {
311 throw SRHTError.unauthorized 311 throw SRHTError.unauthorized
312 } 312 }
313 313
@@ -417,7 +417,7 @@ final class SRHTClient: Sendable {
417 service: service, 417 service: service,
418 query: query, 418 query: query,
419 variables: variables, 419 variables: variables,
420 responseType: responseType, 420 responseType: T.self,
421 cacheKey: cacheKey 421 cacheKey: cacheKey
422 ) 422 )
423 } 423 }
@@ -430,7 +430,7 @@ final class SRHTClient: Sendable {
430 responseType _: T.Type, 430 responseType _: T.Type,
431 cacheKey: String 431 cacheKey: String
432 ) async throws -> T { 432 ) async throws -> T {
433 guard let token = _token.withLock({ $0 }), !token.isEmpty else { 433 guard let token = tokenLock.withLock({ $0 }), !token.isEmpty else {
434 throw SRHTError.unauthorized 434 throw SRHTError.unauthorized
435 } 435 }
436 436
@@ -527,7 +527,7 @@ final class SRHTClient: Sendable {
527 /// Fetch the contents of a URL as plain text, using the same authorization header. 527 /// Fetch the contents of a URL as plain text, using the same authorization header.
528 /// Used for build logs and other non-GraphQL resources. 528 /// Used for build logs and other non-GraphQL resources.
529 func fetchText(url: URL) async throws -> String { 529 func fetchText(url: URL) async throws -> String {
530 guard let token = _token.withLock({ $0 }), !token.isEmpty else { 530 guard let token = tokenLock.withLock({ $0 }), !token.isEmpty else {
531 throw SRHTError.unauthorized 531 throw SRHTError.unauthorized
532 } 532 }
533 guard Self.isTrustedAuthenticatedTextURL(url) else { 533 guard Self.isTrustedAuthenticatedTextURL(url) else {
@@ -593,12 +593,13 @@ final class SRHTClient: Sendable {
593 resultKeyPath: String 593 resultKeyPath: String
594 ) async throws -> [T] { 594 ) async throws -> [T] {
595 var all: [T] = [] 595 var all: [T] = []
596 for try await element in paginated( 596 let pages: SRHTPaginatedSequence<T> = paginated(
597 service: service, 597 service: service,
598 query: query, 598 query: query,
599 variables: variables, 599 variables: variables,
600 resultKeyPath: resultKeyPath 600 resultKeyPath: resultKeyPath
601 ) { 601 )
602 for try await element in pages {
602 all.append(element) 603 all.append(element)
603 } 604 }
604 return all 605 return all
Hutch/Views/Builds/BuildListViewModel.swift +3 −2
@@ -182,12 +182,13 @@ final class BuildListViewModel {
182 /// Fetch the first page of jobs. Shows cached data instantly if available, 182 /// Fetch the first page of jobs. Shows cached data instantly if available,
183 /// then refreshes from the network in the background. 183 /// then refreshes from the network in the background.
184 func loadJobs() async { 184 func loadJobs() async {
185 // Show cached data immediately on first load 185 // Show cached data immediately on first load (may populate `jobs` from cache).
186 if jobs.isEmpty { 186 if jobs.isEmpty {
187 loadFromCache() 187 loadFromCache()
188 } 188 }
189 189
190 if jobs.isEmpty { 190 let treatAsInitialLoad = jobs.isEmpty
191 if treatAsInitialLoad {
191 isLoading = true 192 isLoading = true
192 } else { 193 } else {
193 isRefreshing = true 194 isRefreshing = true
Hutch/Views/Pastes/PasteListView.swift +4 −4
@@ -276,14 +276,14 @@ private struct CreatePasteSheet: View {
276 NavigationStack { 276 NavigationStack {
277 Form { 277 Form {
278 Section("Files") { 278 Section("Files") {
279 ForEach($files) { $file in 279 ForEach($files) { fileBinding in
280 VStack(alignment: .leading, spacing: 8) { 280 VStack(alignment: .leading, spacing: 8) {
281 TextField("Filename (optional)", text: $file.filename) 281 TextField("Filename (optional)", text: fileBinding.filename)
282 .autocorrectionDisabled() 282 .autocorrectionDisabled()
283 .textInputAutocapitalization(.never) 283 .textInputAutocapitalization(.never)
284 284
285 ZStack(alignment: .topLeading) { 285 ZStack(alignment: .topLeading) {
286 if file.contents.isEmpty { 286 if fileBinding.wrappedValue.contents.isEmpty {
287 Text("Paste contents") 287 Text("Paste contents")
288 .foregroundStyle(.tertiary) 288 .foregroundStyle(.tertiary)
289 .padding(.top, 8) 289 .padding(.top, 8)
@@ -291,7 +291,7 @@ private struct CreatePasteSheet: View {
291 .allowsHitTesting(false) 291 .allowsHitTesting(false)
292 } 292 }
293 293
294 TextEditor(text: $file.contents) 294 TextEditor(text: fileBinding.contents)
295 .font(.system(.body, design: .monospaced)) 295 .font(.system(.body, design: .monospaced))
296 .frame(minHeight: 180) 296 .frame(minHeight: 180)
297 } 297 }
Hutch/Views/Repositories/CommitDetailViewModel.swift +6 −3
@@ -7,8 +7,11 @@ private struct CommitResponse: Decodable, Sendable {
7} 7}
8 8
9private struct CommitRepository: Decodable, Sendable { 9private struct CommitRepository: Decodable, Sendable {
10 // swiftlint:disable:next identifier_name 10 let revparseSingle: CommitDetail
11 let revparse_single: CommitDetail 11
12 enum CodingKeys: String, CodingKey {
13 case revparseSingle = "revparse_single"
14 }
12} 15}
13 16
14// MARK: - View Model 17// MARK: - View Model
@@ -66,7 +69,7 @@ final class CommitDetailViewModel {
66 69
67 do { 70 do {
68 let result = try await executeWithRetry() 71 let result = try await executeWithRetry()
69 commit = result.repository?.revparse_single 72 commit = result.repository?.revparseSingle
70 } catch { 73 } catch {
71 self.error = error.userFacingMessage 74 self.error = error.userFacingMessage
72 } 75 }
Hutch/Views/Repositories/FileTreeViewModel.swift +6 −2
@@ -7,7 +7,11 @@ private struct RevparseResponse: Decodable, Sendable {
7} 7}
8 8
9private struct RevparseRepository: Decodable, Sendable { 9private struct RevparseRepository: Decodable, Sendable {
10 let revparse_single: RevparseCommit? 10 let revparseSingle: RevparseCommit?
11
12 enum CodingKeys: String, CodingKey {
13 case revparseSingle = "revparse_single"
14 }
11} 15}
12 16
13private struct RevparseCommit: Decodable, Sendable { 17private struct RevparseCommit: Decodable, Sendable {
@@ -277,7 +281,7 @@ final class FileTreeViewModel {
277 } 281 }
278 throw error 282 throw error
279 } 283 }
280 if let tree = result.repository?.revparse_single?.tree, 284 if let tree = result.repository?.revparseSingle?.tree,
281 let rootId = tree.id { 285 let rootId = tree.id {
282 navStack = [FileNavEntry(name: "root", treeId: rootId)] 286 navStack = [FileNavEntry(name: "root", treeId: rootId)]
283 var allEntries = tree.entries?.results ?? [] 287 var allEntries = tree.entries?.results ?? []