Added worktree-layered build config: resolves .gittally.yml from the worktree for per-branch build settings, with precedence worktree > .git > project; pinned secrets, server-side keys, and sandbox policy to .git.

This commit is contained in:
Michael Hoennig
2026-07-09 09:20:34 +02:00
parent 095e6fa44f
commit fb6d4501b9
7 changed files with 209 additions and 17 deletions
@@ -150,6 +150,112 @@ class ConfigLoaderTest : FunSpec() {
loader.load(dir).server.publicBaseUrl shouldBe ""
}
test("loadForWorktree lets the worktree override build config (worktree > .git > project)") {
val dir = Files.createTempDirectory("gittally-test")
dir.resolve(".gittally.yml").toFile().writeText(
"""
branches:
default:
buildCommand: from-project
""".trimIndent(),
)
dir.resolve(".git/gittally").toFile().mkdirs()
dir.resolve(".git/gittally/.gittally.yml").toFile().writeText(
"""
branches:
default:
buildCommand: from-git
""".trimIndent(),
)
val worktree = Files.createTempDirectory("gittally-worktree")
worktree.resolve(".gittally.yml").toFile().writeText(
"""
branches:
default:
buildCommand: from-worktree
""".trimIndent(),
)
loader.loadForWorktree(dir, worktree).branches["default"]!!.buildCommand shouldBe "from-worktree"
}
test("loadForWorktree falls back to .git over project when the worktree sets nothing") {
val dir = Files.createTempDirectory("gittally-test")
dir.resolve(".gittally.yml").toFile().writeText(
"""
branches:
default:
buildCommand: from-project
""".trimIndent(),
)
dir.resolve(".git/gittally").toFile().mkdirs()
dir.resolve(".git/gittally/.gittally.yml").toFile().writeText(
"""
branches:
default:
buildCommand: from-git
""".trimIndent(),
)
val worktree = Files.createTempDirectory("gittally-worktree")
loader.loadForWorktree(dir, worktree).branches["default"]!!.buildCommand shouldBe "from-git"
}
test("loadForWorktree pins secrets and the docker sandbox policy to .git, but allows docker.image") {
val dir = Files.createTempDirectory("gittally-test")
dir.resolve(".git/gittally").toFile().mkdirs()
dir.resolve(".git/gittally/.gittally.yml").toFile().writeText(
"""
git:
token: real-secret
server:
port: 9000
branches:
default:
docker:
enabled: true
network: host
image: trusted-image
""".trimIndent(),
)
val worktree = Files.createTempDirectory("gittally-worktree")
worktree.resolve(".gittally.yml").toFile().writeText(
"""
git:
token: stolen
server:
port: 1234
branches:
default:
docker:
enabled: false
network: none
image: attacker-image
""".trimIndent(),
)
val config = loader.loadForWorktree(dir, worktree)
// pinned: never taken from the worktree
config.git.token shouldBe "real-secret"
config.server.port shouldBe 9000
config.branches["default"]!!.docker.enabled shouldBe true
config.branches["default"]!!.docker.network shouldBe "host"
// overridable: the worktree wins
config.branches["default"]!!.docker.image shouldBe "attacker-image"
}
test("loadForWorktree without a worktree config equals load") {
val dir = Files.createTempDirectory("gittally-test")
dir.resolve(".gittally.yml").toFile().writeText(
"""
gitea:
owner: my-org
branches:
default:
buildCommand: ./mvnw test
""".trimIndent(),
)
val worktree = Files.createTempDirectory("gittally-worktree")
loader.loadForWorktree(dir, worktree) shouldBe loader.load(dir)
}
test("toYaml serializes GitTallyConfig with all sections") {
val yaml = loader.toYaml(GitTallyConfig())
yaml shouldContain "server:"