A branches.<name>.autoBuild.times entry is now either a plain HH:MM string or an object with time, its own buildCommand, and a name, so a nightly slot can run a fuller check than the on-commit builds. The watcher passes the slot's command and name to the executor, persisted in the build result — UI restarts, gittally retry, and the startup recovery repeat a build with the command and name it originally ran under. A named slot (e.g. master@nightly) gets its own pool: repository grouping, retention count, branches-view row (sorted after its branch), latest status, and permanent latest-green artifact link are keyed by the build name, while origin lookups, gone-branch pruning, worktrees, and Gitea links/statuses stay keyed by the real branch. Without a name, slot builds share the branch's pool as before. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
134 lines
4.5 KiB
Kotlin
134 lines
4.5 KiB
Kotlin
package de.hoennig.gittally.server
|
|
|
|
import de.hoennig.gittally.build.BuildResult
|
|
import de.hoennig.gittally.build.BuildStatus
|
|
import java.time.Instant
|
|
|
|
/** JSON statuses are lowercase like the legacy TSV/HTML statuses. */
|
|
val BuildStatus.jsonName: String
|
|
get() = name.lowercase()
|
|
|
|
data class BuildResultDto(
|
|
/** The git branch that was built; the UI links this to Gitea. */
|
|
val branch: String,
|
|
/** The build name the result is recorded under; the UI displays and restarts by this (= [branch] unless a named auto-build slot). */
|
|
val name: String = branch,
|
|
val commit: String,
|
|
val status: String,
|
|
val startedAt: Instant,
|
|
/** When the build left the queue; the UI derives the live build time from this, the wait time from [startedAt]. */
|
|
val runningSince: Instant? = null,
|
|
val durationSeconds: Long?,
|
|
val artifactKey: String,
|
|
/** The permanent branch URL, set only on the build it resolves to — the name's latest green build. */
|
|
val latestGreenUrl: String? = null,
|
|
) {
|
|
companion object {
|
|
fun from(
|
|
result: BuildResult,
|
|
isLatestGreen: Boolean = false,
|
|
) = BuildResultDto(
|
|
branch = result.branch,
|
|
name = result.name,
|
|
commit = result.commit,
|
|
status = result.status.jsonName,
|
|
startedAt = result.startedAt,
|
|
runningSince = result.runningSince,
|
|
durationSeconds = result.duration?.seconds,
|
|
artifactKey = result.artifactKey,
|
|
latestGreenUrl = if (isLatestGreen) BranchPermalinks.permanentUrl(result.name) else null,
|
|
)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* One entry of `GET /api/branches`, like a legacy branches-view row: an origin
|
|
* branch with its latest build, or an `unknown` placeholder when never built.
|
|
* [latestGreenUrl] is the permanent artifact URL of the branch's latest green
|
|
* build; set only when this row's build is that green build, so the link appears
|
|
* where it resolves to.
|
|
*/
|
|
data class BranchDto(
|
|
/** The git branch; the UI links this to Gitea. */
|
|
val branch: String,
|
|
/** The row's build name; = [branch], except for the extra rows of named auto-build slots. */
|
|
val name: String = branch,
|
|
val commit: String,
|
|
val status: String,
|
|
val startedAt: Instant?,
|
|
val runningSince: Instant? = null,
|
|
val durationSeconds: Long?,
|
|
val artifactKey: String,
|
|
val latestGreenUrl: String? = null,
|
|
) {
|
|
companion object {
|
|
fun from(
|
|
branch: String,
|
|
headCommit: String,
|
|
latest: BuildResult?,
|
|
isLatestGreen: Boolean = false,
|
|
name: String = branch,
|
|
) = if (latest == null) {
|
|
BranchDto(
|
|
branch = branch,
|
|
name = name,
|
|
commit = headCommit,
|
|
status = CommitStatusDto.UNKNOWN_STATUS,
|
|
startedAt = null,
|
|
durationSeconds = null,
|
|
artifactKey = "",
|
|
)
|
|
} else {
|
|
BranchDto(
|
|
branch = branch,
|
|
name = name,
|
|
commit = latest.commit,
|
|
status = latest.status.jsonName,
|
|
startedAt = latest.startedAt,
|
|
runningSince = latest.runningSince,
|
|
durationSeconds = latest.duration?.seconds,
|
|
artifactKey = latest.artifactKey,
|
|
latestGreenUrl = if (isLatestGreen) BranchPermalinks.permanentUrl(name) else null,
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
/** One entry of `GET /api/builds/current`; the log grows while the build runs. */
|
|
data class CurrentBuildDto(
|
|
val branch: String,
|
|
/** The build name; = [branch] unless a named auto-build slot triggered this build. */
|
|
val name: String = branch,
|
|
val commit: String,
|
|
val artifactKey: String,
|
|
val status: String,
|
|
val startedAt: Instant,
|
|
val runningSince: Instant? = null,
|
|
val logSize: Long,
|
|
)
|
|
|
|
/** Incremental live-log chunk; poll again with `offset = nextOffset`. */
|
|
data class LogTailDto(
|
|
val artifactKey: String,
|
|
val offset: Long,
|
|
val nextOffset: Long,
|
|
val content: String,
|
|
)
|
|
|
|
/**
|
|
* Effective status of a commit: the Gitea status when available, otherwise the
|
|
* local repository status, otherwise `unknown`. A Gitea failure is an explicit
|
|
* [giteaError] value — the request answers HTTP 200 and never hangs.
|
|
*/
|
|
data class CommitStatusDto(
|
|
val commit: String,
|
|
val status: String,
|
|
val localStatus: String?,
|
|
val giteaStatus: String?,
|
|
val giteaError: String?,
|
|
) {
|
|
companion object {
|
|
const val UNKNOWN_STATUS = "unknown"
|
|
}
|
|
}
|