Commit 224142832e

224142832e1627948f28a923bafe992d785d11d2

parent: f0267e5ff1

Verified · cmc

cmc <hello@cleberg.net> · 2026-09-20 23:57 UTC

push: normalise both sides of the instance comparison

The payload's instance went through GitbayInstance and the account's
did not, so an account stored before the constructor folded the
default port kept ":443" and matched nothing. Normalise the comparison
side too. What is stored is untouched: rewriting it would change
Account.id and orphan the Keychain token keyed by it.

Ref krz/gitbay#89

Layout: unified · split

gitbay/Push/PushTarget.swift +7 −1
@@ -15,8 +15,14 @@ nonisolated struct PushTarget: Equatable, Sendable {
15 // comparing raw URLs, which a server's unnormalised config can 15 // comparing raw URLs, which a server's unnormalised config can
16 // make unequal even when they name the same instance. 16 // make unequal even when they name the same instance.
17 guard let instance = try? GitbayInstance(url: payload.instance.absoluteString) else { return nil } 17 guard let instance = try? GitbayInstance(url: payload.instance.absoluteString) else { return nil }
18 // Both sides, not just the payload's: an account stored before
19 // the constructor folded the default port still holds an
20 // explicit ":443". Normalising is for the comparison only —
21 // rewriting what is stored would change `Account.id` and orphan
22 // the Keychain token it keys.
18 let match = accounts.first { 23 let match = accounts.first {
19 $0.username == payload.user && $0.instance.baseURL == instance.baseURL 24 $0.username == payload.user
25 && (try? GitbayInstance(url: $0.instance.baseURL.absoluteString))?.baseURL == instance.baseURL
20 } 26 }
21 guard let match else { return nil } 27 guard let match else { return nil }
22 self.accountID = match.id 28 self.accountID = match.id
gitbayTests/PushTargetTests.swift +16
@@ -65,3 +65,19 @@ private func payload(_ instance: String, _ user: String, _ path: String) throws
65 accounts: accounts)) 65 accounts: accounts))
66 #expect(target.accountID == accounts[0].id) 66 #expect(target.accountID == accounts[0].id)
67} 67}
68
69// The other side of the same mismatch: an account stored before the
70// constructor folded the default port keeps ":443" in its baseURL, so
71// decoding is the only way to build one. Its id keeps the port —
72// rewriting that would orphan the Keychain token keyed by it.
73@Test func targetMatchesAnAccountStoredWithAnExplicitDefaultPort() throws {
74 let legacy = try JSONDecoder().decode(
75 GitbayInstance.self, from: Data(#"{"baseURL":"https://gitbay.org:443"}"#.utf8))
76 let accounts = [Account(instance: legacy, username: "cmc")]
77 #expect(accounts[0].id.contains(":443"))
78
79 let target = try #require(PushTarget(
80 payload: try payload("https://gitbay.org", "cmc", "a/b/issues/1"),
81 accounts: accounts))
82 #expect(target.accountID == accounts[0].id)
83}