A build definition carries the whole build; branches is legacy
`builds` and the legacy `branches` are now either/or: `branches` is read only while the merged configuration defines no build at all — a leftover `builds.maxConcurrent` is not one — and ignored with a warning as soon as one exists. Two half-answers to "what does this build run" would silently pull against each other, and the committed configs still carrying both must not change behaviour before they are migrated. A definition therefore gained the settings it was missing: `requirePullRequest` and `docker.enabled`/`network`. Those stay pinned — `stripPinned` now removes them from a branch layer wherever they appear, in a definition as well as in a legacy branch entry. `builds.default` becomes the base every other definition inherits its settings from, never its trigger: `onPush`, `atTimes`, `branches`, and `activeWithin` say when and where *this* build runs. The inheritance is applied after all layers are merged, which is what makes a build invented on a branch inherit the host's sandbox policy instead of the data-class default — otherwise a branch could get a native build past the pinning by defining a job the host has never heard of. Two bugs found on the way, both the same shape as the build command the artifact page used to get wrong: - `FileArtifactStore` read the artifact directories from the plain branch settings, so a job adding its own `artifactDirs` never had them stored. It goes through `GitTallyConfig.buildSettings` now, like everything else that asks what a build runs. - `Watcher.definitionsFor` cached the per-branch definitions by head commit alone, so an edited machine or project config only took effect once the branch moved — on a quiet branch, never. The primary config is part of the cache key now.
This commit is contained in:
@@ -138,7 +138,7 @@ class FileArtifactStore(
|
||||
log.warn("build {} has no workspace; storing only its logs", build.artifactKey)
|
||||
return
|
||||
}
|
||||
for (artifactDir in branchConfig(build.branch, workspace).artifactDirs) {
|
||||
for (artifactDir in buildSettings(build, workspace).artifactDirs) {
|
||||
if (artifactDir.isBlank()) {
|
||||
continue
|
||||
}
|
||||
@@ -159,14 +159,16 @@ class FileArtifactStore(
|
||||
"reports/$artifactDir"
|
||||
}
|
||||
|
||||
/** The build config for [branch], with the build [workspace]'s `.gittally.yml` layered on top (see [ConfigLoader.loadForWorktree]). */
|
||||
private fun branchConfig(
|
||||
branch: String,
|
||||
/**
|
||||
* The settings [build] ran with, from the build [workspace]'s `.gittally.yml` layered
|
||||
* on top of the primary config (see [ConfigLoader.loadForWorktree]) — resolved through
|
||||
* [GitTallyConfig.buildSettings], so a job's own `artifactDirs` are archived and not
|
||||
* only the ones its branch would have used.
|
||||
*/
|
||||
private fun buildSettings(
|
||||
build: BuildResult,
|
||||
workspace: Path,
|
||||
): BranchConfig {
|
||||
val branches = configLoader.loadForWorktree(workingDir, workspace).branches
|
||||
return branches[branch] ?: branches["default"] ?: BranchConfig()
|
||||
}
|
||||
): BranchConfig = configLoader.loadForWorktree(workingDir, workspace).buildSettings(build.branch, build.build)
|
||||
|
||||
private fun copyChildren(
|
||||
sourceDir: Path,
|
||||
|
||||
@@ -175,17 +175,40 @@ class InitCommand(
|
||||
# how many builds may run at the same time (at most one build per branch regardless)
|
||||
maxConcurrent: 1
|
||||
|
||||
# Named build definitions (jobs) over the branches; every key names a build.
|
||||
# A branch may add or override definitions in its own committed .gittally.yml —
|
||||
# they then apply to that branch alone, so a new job can be tried out on a branch.
|
||||
# Named build definitions (jobs); every key names a build.
|
||||
# "default" is the base every other definition inherits its settings from — never
|
||||
# its trigger — and is itself the build of every branch as long as it has one.
|
||||
# A branch may add or override definitions in its own committed .gittally.yml;
|
||||
# they apply to that branch alone, so a new job can be tried out on one branch.
|
||||
builds:
|
||||
# Example definition — triggers (onPush/atTimes), branch selector
|
||||
# (branches/activeWithin), and overrides of the branch settings:
|
||||
default:
|
||||
onPush: true # build every new commit of the selected branches
|
||||
# run before each build
|
||||
cleanCommand: rm -rf build
|
||||
# shell command for each build
|
||||
buildCommand: ./gradlew --console=plain --no-daemon test
|
||||
# directories copied as build artifacts
|
||||
artifactDirs:
|
||||
- build/reports
|
||||
stdoutLog: build.stdout.log # filename for captured stdout
|
||||
stderrLog: build.stderr.log # filename for captured stderr
|
||||
# build only while the branch head matches a pull-request head on origin
|
||||
# (refs/pull/*/head — read via plain git, no API token needed);
|
||||
# pinned: a branch cannot set this in its own committed config
|
||||
requirePullRequest: false
|
||||
docker:
|
||||
enabled: false # run clean/build in a container instead of natively (pinned)
|
||||
image: "" # image for the build container; required when enabled
|
||||
dockerfile: "" # Dockerfile to (re)build the image from when missing or stale; empty pulls the image as-is
|
||||
context: "." # Docker build context used with dockerfile
|
||||
network: "" # Docker network mode for the build container; empty = Docker default (pinned)
|
||||
env: {} # additional environment variables set inside the build container
|
||||
# Further jobs inherit those settings and add their own trigger and selector:
|
||||
# pitest:
|
||||
# atTimes: ["01:00"] # daily UTC times HH:MM ("??:05" = every hour at :05)
|
||||
# branches: ["master"] # names or glob patterns; default: all branches
|
||||
# activeWithin: 24h # only branches with recent commits
|
||||
# buildCommand: ./gradlew piTestFull
|
||||
# buildCommand: ./gradlew pitestFull
|
||||
|
||||
# Build artifact storage and retention.
|
||||
artifacts:
|
||||
@@ -206,40 +229,12 @@ class InitCommand(
|
||||
pollInterval: 10s
|
||||
# max commit age for new origin branches to be pulled automatically
|
||||
newBranchMaxAge: 5d
|
||||
# honor branches.<name>.requirePullRequest; set false for a plain git origin
|
||||
# honor builds.<name>.requirePullRequest; set false for a plain git origin
|
||||
# without pull-request refs (refs/pull/*/head) — gated branches then build on new commits
|
||||
pullRequestGate: true
|
||||
# after enqueueing, fast-forward the primary checkout's local branch refs to origin,
|
||||
# so build tools reading the shared .git see the same refs (diverged branches stay untouched)
|
||||
fastForwardLocalRefs: true
|
||||
|
||||
# Per-branch build configuration.
|
||||
# Use "default" as the fallback for all branches not listed explicitly.
|
||||
branches:
|
||||
default:
|
||||
# run before each build
|
||||
cleanCommand: rm -rf build
|
||||
# shell command for each build
|
||||
buildCommand: ./gradlew --console=plain --no-daemon test
|
||||
# directories copied as build artifacts
|
||||
artifactDirs:
|
||||
- build/reports
|
||||
stdoutLog: build.stdout.log # filename for captured stdout
|
||||
stderrLog: build.stderr.log # filename for captured stderr
|
||||
# build only while the branch head matches a pull-request head on origin
|
||||
# (refs/pull/*/head — read via plain git, no API token needed)
|
||||
requirePullRequest: false
|
||||
# DEPRECATED: define a build with atTimes in the builds section instead
|
||||
autoBuild:
|
||||
enabled: false # whether to rebuild on schedule
|
||||
times: ["01:00"] # UTC times HH:MM for scheduled builds
|
||||
docker:
|
||||
enabled: false # run clean/build commands in a Docker container instead of natively
|
||||
image: "" # image for the build container; required when enabled
|
||||
dockerfile: "" # Dockerfile to (re)build the image from when missing or stale; empty pulls the image as-is
|
||||
context: "." # Docker build context used with dockerfile
|
||||
network: "" # Docker network mode for the build container; empty = Docker default
|
||||
env: {} # additional environment variables set inside the build container
|
||||
""".trimIndent()
|
||||
file.toFile().writeText(content + "\n")
|
||||
println("created ${file.toFile().relativeTo(normalizedWorkingDir.toFile())}")
|
||||
|
||||
@@ -6,8 +6,12 @@ import java.time.Instant
|
||||
/**
|
||||
* A named build (job) over the branches — ADR 0007. In YAML these live in the
|
||||
* top-level `builds` section next to the reserved execution key `maxConcurrent`
|
||||
* (split apart by [ConfigLoader]); a build definition always comes from the repo
|
||||
* install/project config, never from a build worktree.
|
||||
* (split apart by [ConfigLoader]).
|
||||
*
|
||||
* A definition carries the complete description of one build. The `default` entry is
|
||||
* additionally the base every other definition inherits its settings from — but never
|
||||
* its trigger, see [SELECTOR_KEYS][ConfigLoader]. Unset values fall through to the
|
||||
* [BranchConfig] defaults.
|
||||
*
|
||||
* The `default` build records its results under the plain branch name; every other
|
||||
* build records under `<branch>@<name>` with its own history, retention pool, and
|
||||
@@ -42,7 +46,13 @@ data class BuildDefinition(
|
||||
val stdoutLog: String? = null,
|
||||
/** Overrides the branch's stderr log file name; null inherits it. */
|
||||
val stderrLog: String? = null,
|
||||
/** Overrides of the branch's docker image settings; the sandbox policy (`enabled`, `network`) is not overridable. */
|
||||
/**
|
||||
* The watcher builds a selected branch only while its head commit matches a
|
||||
* pull-request head; null inherits. Pinned — a branch's own committed config can
|
||||
* never set it, or it would bypass its own gate.
|
||||
*/
|
||||
val requirePullRequest: Boolean? = null,
|
||||
/** Overrides of the branch's docker settings; null inherits them. */
|
||||
val docker: DockerOverrides? = null,
|
||||
) {
|
||||
/** True when [branch] matches the [branches] patterns (or none are configured). */
|
||||
@@ -78,11 +88,14 @@ data class BuildDefinition(
|
||||
artifactDirs = artifactDirs ?: branchConfig.artifactDirs,
|
||||
stdoutLog = stdoutLog ?: branchConfig.stdoutLog,
|
||||
stderrLog = stderrLog ?: branchConfig.stderrLog,
|
||||
requirePullRequest = requirePullRequest ?: branchConfig.requirePullRequest,
|
||||
docker =
|
||||
branchConfig.docker.copy(
|
||||
enabled = docker?.enabled ?: branchConfig.docker.enabled,
|
||||
image = docker?.image ?: branchConfig.docker.image,
|
||||
dockerfile = docker?.dockerfile ?: branchConfig.docker.dockerfile,
|
||||
context = docker?.context ?: branchConfig.docker.context,
|
||||
network = docker?.network ?: branchConfig.docker.network,
|
||||
env = docker?.env ?: branchConfig.docker.env,
|
||||
),
|
||||
)
|
||||
@@ -106,8 +119,12 @@ data class BuildDefinition(
|
||||
}
|
||||
}
|
||||
|
||||
/** Nullable docker image overrides of a [BuildDefinition]; null values inherit the branch's setting. */
|
||||
/** Nullable docker overrides of a [BuildDefinition]; null values inherit the branch's setting. */
|
||||
data class DockerOverrides(
|
||||
/** Run the build in a container instead of natively. Pinned — a branch must not escape its sandbox. */
|
||||
val enabled: Boolean? = null,
|
||||
/** Docker network mode. Pinned — a branch must not change the sandbox's reachability. */
|
||||
val network: String? = null,
|
||||
val image: String? = null,
|
||||
val dockerfile: String? = null,
|
||||
val context: String? = null,
|
||||
|
||||
@@ -34,6 +34,9 @@ class ConfigLoader(
|
||||
/** Version warnings already reported; the config is loaded on every poll cycle, per branch. */
|
||||
private val warnedVersions = ConcurrentHashMap.newKeySet<String>()
|
||||
|
||||
/** Section-level warnings already reported, keyed by a fixed slug; the config is loaded on every poll cycle. */
|
||||
private val warnedSections = ConcurrentHashMap.newKeySet<String>()
|
||||
|
||||
fun load(workingDir: Path = Paths.get(".")): GitTallyConfig = toConfig(loadRaw(workingDir))
|
||||
|
||||
/**
|
||||
@@ -84,7 +87,7 @@ class ConfigLoader(
|
||||
if (raw.isEmpty()) {
|
||||
GitTallyConfig()
|
||||
} else {
|
||||
yaml.convertValue(mergeBranchDefaults(dropNonDefinitionBuilds(raw)), GitTallyConfig::class.java)
|
||||
yaml.convertValue(resolveBuildSections(dropNonDefinitionBuilds(raw)), GitTallyConfig::class.java)
|
||||
}
|
||||
return defaultPublicBaseUrl(config)
|
||||
}
|
||||
@@ -118,7 +121,8 @@ class ConfigLoader(
|
||||
|
||||
/**
|
||||
* Removes the keys a branch must never override: the secret and host-side top-level
|
||||
* sections, the per-branch trust gate, and the docker sandbox policy.
|
||||
* sections, the trust gate, and the docker sandbox policy — the latter two wherever
|
||||
* they may appear, in a `builds` definition as well as in a legacy `branches` entry.
|
||||
* See [loadWithBranchLayer].
|
||||
*/
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
@@ -128,19 +132,19 @@ class ConfigLoader(
|
||||
}
|
||||
val result = branchLayer.toMutableMap()
|
||||
PINNED_TOP_LEVEL_KEYS.forEach { result.remove(it) }
|
||||
val branches = result["branches"] as? Map<String, Any?>
|
||||
if (branches != null) {
|
||||
result["branches"] = branches.mapValues { (_, value) -> stripPinnedBranchKeys(value) }
|
||||
for (section in listOf("builds", "branches")) {
|
||||
val entries = result[section] as? Map<String, Any?> ?: continue
|
||||
result[section] = entries.mapValues { (_, value) -> stripPinnedSettings(value) }
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
private fun stripPinnedBranchKeys(value: Any?): Any? {
|
||||
val branch = value as? Map<String, Any?> ?: return value
|
||||
val result = branch.toMutableMap()
|
||||
PINNED_BRANCH_KEYS.forEach { result.remove(it) }
|
||||
val docker = branch["docker"] as? Map<String, Any?>
|
||||
private fun stripPinnedSettings(value: Any?): Any? {
|
||||
val entry = value as? Map<String, Any?> ?: return value
|
||||
val result = entry.toMutableMap()
|
||||
PINNED_SETTING_KEYS.forEach { result.remove(it) }
|
||||
val docker = entry["docker"] as? Map<String, Any?>
|
||||
if (docker != null) {
|
||||
val strippedDocker = docker.toMutableMap().apply { PINNED_DOCKER_KEYS.forEach { remove(it) } }
|
||||
if (strippedDocker.isEmpty()) result.remove("docker") else result["docker"] = strippedDocker
|
||||
@@ -148,6 +152,82 @@ class ConfigLoader(
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* Decides which of the two sections describes the builds: `builds` or the legacy
|
||||
* `branches`, never both. As soon as the merged configuration carries one real build
|
||||
* definition — `builds.maxConcurrent` alone is not one, it is already dropped by
|
||||
* [dropNonDefinitionBuilds] — a `branches` section is ignored altogether, because a
|
||||
* definition now carries the complete description of its build and two half-answers
|
||||
* would silently pull against each other.
|
||||
*
|
||||
* Deliberately decided on the *merged* map, after all layers are in: a branch that
|
||||
* brings its own `builds` therefore also switches the host's `branches` off for its
|
||||
* own builds, and — the reason this order matters — a build the branch defines and
|
||||
* the host has never heard of still inherits the host's `builds.default`, sandbox
|
||||
* policy included. Were the sections resolved per layer, that build would start with
|
||||
* an empty docker policy and run natively on the host, which is exactly the escape
|
||||
* the pinned keys exist to prevent.
|
||||
*/
|
||||
private fun resolveBuildSections(raw: Map<String, Any?>): Map<String, Any?> {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
val definitions = raw["builds"] as? Map<String, Any?> ?: emptyMap()
|
||||
if (definitions.isEmpty()) {
|
||||
return mergeBranchDefaults(raw)
|
||||
}
|
||||
if (raw.containsKey("branches") && warnedSections.add(LEGACY_BRANCHES_WARNING)) {
|
||||
log.warn(
|
||||
"ignoring the branches section: this configuration defines builds, and a build definition " +
|
||||
"carries its own settings; move what is still needed into builds — branches is going away",
|
||||
)
|
||||
}
|
||||
warnWhenNothingIsTriggered(definitions)
|
||||
return mergeBuildDefaults(raw - "branches")
|
||||
}
|
||||
|
||||
/**
|
||||
* An explicit `builds.default` replaces the implicit on-push build, so a set of
|
||||
* definitions can end up with no trigger at all — an instance that will never build
|
||||
* anything. That is a plausible intention for a moment and a mistake for a week, so
|
||||
* it is said out loud once instead of being enforced.
|
||||
*/
|
||||
private fun warnWhenNothingIsTriggered(definitions: Map<String, Any?>) {
|
||||
if (BuildDefinition.DEFAULT !in definitions || definitions.values.any { isTriggered(it) }) {
|
||||
return
|
||||
}
|
||||
if (warnedSections.add(NO_TRIGGER_WARNING)) {
|
||||
log.warn("no build defines onPush or atTimes; the watcher will never start a build on its own")
|
||||
}
|
||||
}
|
||||
|
||||
private fun isTriggered(definition: Any?): Boolean {
|
||||
val entry = definition as? Map<*, *> ?: return false
|
||||
return entry["onPush"] == true || (entry["atTimes"] as? List<*>)?.isNotEmpty() == true
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies `builds.default` as the base of every other build definition — the settings
|
||||
* only. A trigger is never inherited: `onPush` and `atTimes` say when *this* build
|
||||
* runs, and the selectors say for which branches, so inheriting them would make every
|
||||
* job fire whenever the default one does.
|
||||
*/
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
private fun mergeBuildDefaults(raw: Map<String, Any?>): Map<String, Any?> {
|
||||
val builds = raw["builds"] as? Map<String, Any?> ?: return raw
|
||||
val base = (builds[BuildDefinition.DEFAULT] as? Map<String, Any?>)?.minus(SELECTOR_KEYS) ?: return raw
|
||||
if (base.isEmpty()) {
|
||||
return raw
|
||||
}
|
||||
val merged =
|
||||
builds.mapValues { (name, value) ->
|
||||
if (name == BuildDefinition.DEFAULT) {
|
||||
value
|
||||
} else {
|
||||
deepMerge(base, value as? Map<String, Any?> ?: emptyMap())
|
||||
}
|
||||
}
|
||||
return raw + ("builds" to merged)
|
||||
}
|
||||
|
||||
/** Legacy default: an empty `server.publicBaseUrl` becomes `https://<nginx.serverName>/`. */
|
||||
private fun defaultPublicBaseUrl(config: GitTallyConfig): GitTallyConfig {
|
||||
if (config.server.publicBaseUrl.isNotBlank() ||
|
||||
@@ -260,19 +340,28 @@ class ConfigLoader(
|
||||
* the credentials, report statuses to another repository, raise the global
|
||||
* concurrency, or turn off the pull-request gate for the whole watcher.
|
||||
* The `builds` section is deliberately *not* pinned: it describes what the branch
|
||||
* builds, and a branch can already run any command via `branches.*.buildCommand`.
|
||||
* builds, which is the branch's own business — only the individual settings in
|
||||
* [PINNED_SETTING_KEYS] and [PINNED_DOCKER_KEYS] are taken out of it.
|
||||
*/
|
||||
private val PINNED_TOP_LEVEL_KEYS = setOf("git", "gitea", "server", "executor", "watcher")
|
||||
|
||||
/**
|
||||
* Per-branch keys a branch must never override: the trust gate that decides
|
||||
* whether the watcher builds this branch at all.
|
||||
* Settings keys a branch must never override, in a build definition as well as in
|
||||
* a legacy branch entry: the trust gate that decides whether the watcher builds
|
||||
* this branch at all.
|
||||
*/
|
||||
private val PINNED_BRANCH_KEYS = setOf("requirePullRequest")
|
||||
private val PINNED_SETTING_KEYS = setOf("requirePullRequest")
|
||||
|
||||
/** Per-branch `docker` keys a branch must never override: the sandbox policy. */
|
||||
/** `docker` keys a branch must never override: the sandbox policy. */
|
||||
private val PINNED_DOCKER_KEYS = setOf("enabled", "network")
|
||||
|
||||
/** Keys of a build definition that say *when* it runs; never inherited from `builds.default`. */
|
||||
private val SELECTOR_KEYS = setOf("onPush", "atTimes", "branches", "activeWithin")
|
||||
|
||||
private const val LEGACY_BRANCHES_WARNING = "legacy-branches-ignored"
|
||||
|
||||
private const val NO_TRIGGER_WARNING = "no-build-triggered"
|
||||
|
||||
private const val ROLLBACK_HINT =
|
||||
"Migrate the file, or roll back to the GitTally version it was written for."
|
||||
|
||||
|
||||
@@ -6,7 +6,6 @@ import de.hoennig.gittally.build.BuildExecutor
|
||||
import de.hoennig.gittally.build.BuildResultRepository
|
||||
import de.hoennig.gittally.build.BuildStatus
|
||||
import de.hoennig.gittally.build.GitWorktreeWorkspaces
|
||||
import de.hoennig.gittally.config.BranchConfig
|
||||
import de.hoennig.gittally.config.BuildDefinition
|
||||
import de.hoennig.gittally.config.ConfigLoader
|
||||
import de.hoennig.gittally.config.DurationParser
|
||||
@@ -209,7 +208,7 @@ class Watcher(
|
||||
gitService.newOriginBranches(DurationParser.parse(config.watcher.newBranchMaxAge), workingDir)
|
||||
val changed = (changedLocal + newOrigin).distinct()
|
||||
for (branch in changed) {
|
||||
val onPush = definitionsFor(branch, heads[branch], workingDir).filterValues { it.onPush }
|
||||
val onPush = definitionsFor(branch, heads[branch], workingDir, config).filterValues { it.onPush }
|
||||
for ((buildName, definition) in onPush) {
|
||||
if (selects(definition, branch, headCommitTimes)) {
|
||||
startBuildIfDue(branch, allowSameCommit = false, config, pullRequestHeads, workingDir, buildName)
|
||||
@@ -228,17 +227,20 @@ class Watcher(
|
||||
* their selectors are evaluated for it alone, so a definition committed on one branch
|
||||
* can never schedule builds of another.
|
||||
*
|
||||
* Cached per branch by its head commit, so the `git show` runs only when the branch
|
||||
* moved. An unreadable branch config falls back to the primary definitions instead of
|
||||
* failing the poll cycle.
|
||||
* Cached per branch by its head commit *and* the primary configuration it was merged
|
||||
* with, so the `git show` runs only when the branch moved — but an edited machine or
|
||||
* project config takes effect on the next poll instead of waiting for a commit that
|
||||
* may never come. An unreadable branch config falls back to the primary definitions
|
||||
* instead of failing the poll cycle.
|
||||
*/
|
||||
private fun definitionsFor(
|
||||
branch: String,
|
||||
headCommit: String?,
|
||||
workingDir: Path,
|
||||
primary: GitTallyConfig,
|
||||
): Map<String, BuildDefinition> {
|
||||
val commit = headCommit ?: return configLoader.load(workingDir).effectiveBuildDefinitions()
|
||||
branchDefinitions[branch]?.takeIf { it.commit == commit }?.let { return it.definitions }
|
||||
val commit = headCommit ?: return primary.effectiveBuildDefinitions()
|
||||
branchDefinitions[branch]?.takeIf { it.commit == commit && it.primary == primary }?.let { return it.definitions }
|
||||
val definitions =
|
||||
try {
|
||||
configLoader
|
||||
@@ -254,12 +256,13 @@ class Watcher(
|
||||
)
|
||||
configLoader.load(workingDir).effectiveBuildDefinitions()
|
||||
}
|
||||
branchDefinitions[branch] = CachedDefinitions(commit, definitions)
|
||||
branchDefinitions[branch] = CachedDefinitions(commit, primary, definitions)
|
||||
return definitions
|
||||
}
|
||||
|
||||
private class CachedDefinitions(
|
||||
val commit: String,
|
||||
val primary: GitTallyConfig,
|
||||
val definitions: Map<String, BuildDefinition>,
|
||||
)
|
||||
|
||||
@@ -298,7 +301,7 @@ class Watcher(
|
||||
return false
|
||||
}
|
||||
if (config.watcher.pullRequestGate &&
|
||||
branchConfig(config, branch).requirePullRequest &&
|
||||
config.buildSettings(branch, build).requirePullRequest &&
|
||||
commit !in pullRequestHeads.value
|
||||
) {
|
||||
log.info("not enqueueing branch {}: no pull request has head commit {}", branch, commit)
|
||||
@@ -309,11 +312,6 @@ class Watcher(
|
||||
return true
|
||||
}
|
||||
|
||||
private fun branchConfig(
|
||||
config: GitTallyConfig,
|
||||
branch: String,
|
||||
): BranchConfig = config.branches[branch] ?: config.branches["default"] ?: BranchConfig()
|
||||
|
||||
/**
|
||||
* Fires the due `atTimes` slot of every build definition for its selected branches,
|
||||
* once per day and slot per result pool. Rebuilding the already-built commit is
|
||||
@@ -332,7 +330,8 @@ class Watcher(
|
||||
val today = LocalDate.ofInstant(now, ZoneOffset.UTC)
|
||||
val timeOfDay = LocalTime.ofInstant(now, ZoneOffset.UTC)
|
||||
for (branch in originBranches) {
|
||||
val scheduled = definitionsFor(branch, heads[branch], workingDir).filterValues { it.atTimes.isNotEmpty() }
|
||||
val scheduled =
|
||||
definitionsFor(branch, heads[branch], workingDir, config).filterValues { it.atTimes.isNotEmpty() }
|
||||
for ((buildName, definition) in scheduled) {
|
||||
if (!selects(definition, branch, headCommitTimes)) {
|
||||
continue
|
||||
|
||||
Reference in New Issue
Block a user