Commit 63a1edda14
Unsigned
Layout: unified · split
gitbay/Repos/RepoSettings.swift +2
| @@ -8,6 +8,7 @@ nonisolated struct RepoSettings: Decodable, Sendable, Hashable { | ||
| 8 | 8 | let requireChecks: Bool? |
| 9 | 9 | let requireApprovals: Int? |
| 10 | 10 | let requireResolved: Bool? |
| 11 | let requireCodeowners: Bool? | |
| 11 | 12 | let gitDaemon: Bool? |
| 12 | 13 | let archived: Bool? |
| 13 | 14 | let website: String? |
| @@ -18,6 +19,7 @@ nonisolated struct RepoSettings: Decodable, Sendable, Hashable { | ||
| 18 | 19 | case requireChecks = "require_checks" |
| 19 | 20 | case requireApprovals = "require_approvals" |
| 20 | 21 | case requireResolved = "require_resolved" |
| 22 | case requireCodeowners = "require_codeowners" | |
| 21 | 23 | case gitDaemon = "git_daemon" |
| 22 | 24 | case archived, website |
| 23 | 25 | } |
gitbay/Repos/RepoSettingsViewModel.swift +4
| @@ -107,6 +107,10 @@ final class RepoSettingsViewModel { | ||
| 107 | 107 | await perform(["repo", "settings", "require-signed", repoPath, on ? "on" : "off"]) |
| 108 | 108 | } |
| 109 | 109 | |
| 110 | func setRequireCodeowners(_ on: Bool) async { | |
| 111 | await perform(["repo", "settings", "require-codeowners", repoPath, on ? "on" : "off"]) | |
| 112 | } | |
| 113 | ||
| 110 | 114 | func addTopic(_ topic: String) async { |
| 111 | 115 | await perform(["repo", "topics", "add", repoPath, topic]) |
| 112 | 116 | } |
gitbay/Views/Repos/RepoSettingsView.swift +3
| @@ -210,6 +210,9 @@ struct RepoSettingsView: View { | ||
| 210 | 210 | toggle("Require signed commits", loaded.settings.requireSignedCommits ?? false) { |
| 211 | 211 | await model.setRequireSigned($0) |
| 212 | 212 | } |
| 213 | toggle("Require code owner review", loaded.settings.requireCodeowners ?? false) { | |
| 214 | await model.setRequireCodeowners($0) | |
| 215 | } | |
| 213 | 216 | } header: { |
| 214 | 217 | Text("Merge requirements") |
| 215 | 218 | } footer: { |
gitbayTests/RepoManagementTests.swift +5 −2
| @@ -21,7 +21,7 @@ private let okJSON = #"{"protocol_version":1,"data":{},"exit_code":0}"# | ||
| 21 | 21 | |
| 22 | 22 | private let settingsShowJSON = """ |
| 23 | 23 | {"protocol_version":1,"data":{"protected_branches":["main"],"require_approvals":1,\ |
| 24 | "require_resolved":true,"website":"https://gitbay.org"},"exit_code":0} | |
| 24 | "require_resolved":true,"require_codeowners":true,"website":"https://gitbay.org"},"exit_code":0} | |
| 25 | 25 | """ |
| 26 | 26 | |
| 27 | 27 | private let repoShowJSON = """ |
| @@ -50,6 +50,7 @@ struct RepoSettingsViewModelTests { | ||
| 50 | 50 | #expect(loaded.settings.requireResolved == true) |
| 51 | 51 | // Absent means default, not unknown. |
| 52 | 52 | #expect(loaded.settings.requireChecks == nil) |
| 53 | #expect(loaded.settings.requireCodeowners == true) | |
| 53 | 54 | #expect(loaded.detail.topics == ["git"]) |
| 54 | 55 | } |
| 55 | 56 | |
| @@ -72,7 +73,7 @@ struct RepoSettingsViewModelTests { | ||
| 72 | 73 | |
| 73 | 74 | @Test func everyKnobSendsItsOwnCommand() async throws { |
| 74 | 75 | let (model, stub) = try await loadedModel() |
| 75 | for _ in 0..<12 { | |
| 76 | for _ in 0..<13 { | |
| 76 | 77 | stub.enqueue(.init(status: 200, json: okJSON, match: "cmd")) |
| 77 | 78 | stub.enqueue(.init(status: 200, json: settingsShowJSON, match: "argv=settings")) |
| 78 | 79 | stub.enqueue(.init(status: 200, json: repoShowJSON, match: "argv=show&argv=krz")) |
| @@ -88,6 +89,7 @@ struct RepoSettingsViewModelTests { | ||
| 88 | 89 | await model.setRequireResolved(false) |
| 89 | 90 | await model.setRequireChecks(true) |
| 90 | 91 | await model.setRequireSigned(true) |
| 92 | await model.setRequireCodeowners(true) | |
| 91 | 93 | await model.addTopic("ios") |
| 92 | 94 | await model.removeTopic("git") |
| 93 | 95 | |
| @@ -103,6 +105,7 @@ struct RepoSettingsViewModelTests { | ||
| 103 | 105 | ["repo", "settings", "require-resolved", "krz/gitbay", "off"], |
| 104 | 106 | ["repo", "settings", "require-checks", "krz/gitbay", "on"], |
| 105 | 107 | ["repo", "settings", "require-signed", "krz/gitbay", "on"], |
| 108 | ["repo", "settings", "require-codeowners", "krz/gitbay", "on"], | |
| 106 | 109 | ["repo", "topics", "add", "krz/gitbay", "ios"], |
| 107 | 110 | ["repo", "topics", "remove", "krz/gitbay", "git"], |
| 108 | 111 | ]) |
gitbayUITests/LiveSmokeUITests.swift +16
| @@ -479,6 +479,22 @@ extension LiveSmokeUITests { | ||
| 479 | 479 | control() |
| 480 | 480 | XCTAssertTrue(waitForValue(resolved, "0", timeout: 10), "toggle did not persist off") |
| 481 | 481 | |
| 482 | // The code-owner rule is one more toggle in the same section. | |
| 483 | let codeowners = app.switches["Require code owner review"].firstMatch | |
| 484 | XCTAssertTrue(scrollTo(codeowners), "code owner toggle not reachable") | |
| 485 | let innerCodeowners = codeowners.switches.firstMatch | |
| 486 | let flipCodeowners: () -> Void = { | |
| 487 | if innerCodeowners.exists && innerCodeowners != codeowners { | |
| 488 | innerCodeowners.tap() | |
| 489 | } else { | |
| 490 | codeowners.coordinate(withNormalizedOffset: CGVector(dx: 0.93, dy: 0.5)).tap() | |
| 491 | } | |
| 492 | } | |
| 493 | flipCodeowners() | |
| 494 | XCTAssertTrue(waitForValue(codeowners, "1", timeout: 10), "code owner toggle did not persist on") | |
| 495 | flipCodeowners() | |
| 496 | XCTAssertTrue(waitForValue(codeowners, "0", timeout: 10), "code owner toggle did not persist off") | |
| 497 | ||
| 482 | 498 | back() // settings -> repo |
| 483 | 499 | |
| 484 | 500 | // --- nothing to trigger without a job file --- |