Commit 174b677412

174b6774123c61271bd6d73415da98622f901d8d

parent: e27fa2c83a

Verified · cmc

cmc <hello@cleberg.net> · 2026-10-14 06:04 UTC

Build log tail: a fetch from the other mode is not shown

A poll that started before the mode changed would land after it and draw
the whole log under the tail's note.

Ref #27

Layout: unified · split

gitbay/Builds/BuildDetailViewModel.swift +8 −2
@@ -5,12 +5,15 @@ nonisolated struct BuildDetail: Sendable {
55 let build: Build
66 let log: String
77 let lines: BuildLog
8 /// Fetched with `--tail`.
9 let tail: Bool
810
911 /// `tail` is a log's last lines only, which may start inside a step
1012 /// and cut its marker off, so it is drawn as one block.
1113 init(build: Build, log: String, tail: Bool = false) {
1214 self.build = build
1315 self.log = log
16 self.tail = tail
1417 var split = build
1518 if tail {
1619 split.steps = nil
@@ -49,7 +52,10 @@ final class BuildDetailViewModel {
4952
5053 func load() async {
5154 do {
52 state = .loaded(try await fetch())
55 let detail = try await fetch()
56 // A fetch from before the mode changed is not this screen's.
57 guard detail.tail == tail else { return }
58 state = .loaded(detail)
5359 } catch {
5460 state = .from(error)
5561 }
@@ -83,7 +89,7 @@ final class BuildDetailViewModel {
8389 func poll() async {
8490 while let build = state.value?.build, Self.isLive(status: build.status) {
8591 guard (try? await Task.sleep(for: pollInterval)) != nil else { return }
86 if let detail = try? await fetch() {
92 if let detail = try? await fetch(), detail.tail == tail {
8793 state = .loaded(detail)
8894 }
8995 }
gitbayTests/BuildLogTests.swift +11
@@ -248,4 +248,15 @@ struct BuildLogTailTests {
248248 #expect(stub.seen.last { $0.url.absoluteString.contains("argv=log") }?.url.query()?.contains("tail") == false)
249249 #expect(model.state.value?.lines.stepped == true)
250250 }
251
252 @Test func aFetchFromTheOtherModeIsNotShown() async throws {
253 let (client, stub) = try makeClient()
254 stub.enqueue(.init(status: 200, json: buildShowJSON, match: "argv=show"))
255 stub.enqueue(.init(status: 200, json: logEnvelope(failedLog), match: "argv=log"))
256 let model = BuildDetailViewModel(client: client, repoPath: "krz/gitbay", number: 1)
257 await model.load()
258 // The detail records the mode it was fetched in.
259 #expect(model.state.value?.tail == false)
260 #expect(BuildDetail(build: try #require(model.state.value).build, log: "x\n", tail: true).tail)
261 }
251262}