fix(cli): read wmill.yaml from the branch a git-sync deploy writes to (#11236)

* fix(cli): read wmill.yaml from the branch a git-sync deploy writes to

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix(cli): create the stateful dir once the deploy branch's config is read

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Ruben Fiszel
2026-09-18 20:50:23 +02:00
committed by GitHub
co-authored by Claude Opus 5
parent 8f9a94328c
commit 974b0265f8
2 changed files with 112 additions and 10 deletions
+21 -10
View File
@@ -3594,12 +3594,15 @@ export async function pull(
) {
if ((opts as any).jsonOutput) log.setSilent(true);
const originalCliOpts = { ...opts };
opts = await mergeConfigWithConfigFile(opts);
// --include-secrets overrides skipSecrets from wmill.yaml
if ((originalCliOpts as any).includeSecrets) {
opts.skipSecrets = false;
}
const withConfigFile = async () => {
const merged = await mergeConfigWithConfigFile({ ...originalCliOpts });
// --include-secrets overrides skipSecrets from wmill.yaml
if ((originalCliOpts as any).includeSecrets) {
merged.skipSecrets = false;
}
return merged;
};
opts = await withConfigFile();
// Resolve workspace name for config lookups.
// --branch resolves git branch → workspace name (deprecated but still supported).
@@ -3634,10 +3637,6 @@ export async function pull(
throw error;
}
if (opts.stateful) {
await mkdir(path.join(process.cwd(), ".wmill"), { recursive: true });
}
const workspace = await resolveWorkspace(opts, wsNameForConfig);
await requireLogin(opts);
@@ -3729,6 +3728,14 @@ export async function pull(
});
return;
}
// The pull writes into the branch now checked out, so its wmill.yaml
// applies, not the cloned branch's: a fork branch that turned on
// `dedupeLockfiles` would otherwise get one lockfile per script back.
if (getCurrentGitBranch() !== clonedBranchName) {
opts = await withConfigFile();
wsNameForConfig = resolveWsNameForConfigFromFlags(opts);
}
}
// If wsNameForConfig wasn't set from flags, infer from the resolved profile
@@ -3763,6 +3770,10 @@ export async function pull(
// Merge CLI flags with resolved settings (CLI flags take precedence only for explicit overrides)
opts = mergeCliWithEffectiveOptions(originalCliOpts, effectiveOpts);
if (opts.stateful) {
await mkdir(path.join(process.cwd(), ".wmill"), { recursive: true });
}
const codebases = await listSyncCodebases(opts);
log.info(
@@ -0,0 +1,91 @@
import { expect, test } from "bun:test";
import { execFileSync } from "node:child_process";
import { existsSync } from "node:fs";
import { mkdtemp, rm, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { createRemoteWorkspaceDeps, withTestBackend } from "./test_backend.ts";
function git(cwd: string, ...args: string[]): string {
return execFileSync("git", args, { cwd, encoding: "utf8" }).trim();
}
// The git-sync deploy callback starts in a clone of the tracked branch and
// switches to the fork's branch before pulling. What it writes there must follow
// that branch's wmill.yaml: a fork branch that turned on `dedupeLockfiles` keeps
// its shared lockfile instead of getting a `.script.lock` per script back.
test("git-sync fork deploy follows the fork branch's wmill.yaml", async () => {
await withTestBackend(async (backend) => {
const post = (path: string, body: unknown) =>
backend.apiRequest!(`/api/w/${backend.workspace}${path}`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(body),
});
await createRemoteWorkspaceDeps(backend, "python3", "wmill\n");
await post("/folders/create", { name: "dedupe" });
const script = await post("/scripts/create", {
path: "f/dedupe/a",
summary: "",
description: "",
content: "def main():\n pass\n",
language: "python3",
lock: "wmill==1.0.0\n",
});
expect(script.ok).toBe(true);
const bare = await mkdtemp(join(tmpdir(), "wmill_deploy_cfg_bare_"));
const seed = await mkdtemp(join(tmpdir(), "wmill_deploy_cfg_seed_"));
const work = await mkdtemp(join(tmpdir(), "wmill_deploy_cfg_work_"));
try {
execFileSync("git", ["init", "--bare", "--initial-branch=main", bare]);
git(seed, "init", "--initial-branch=main");
git(seed, "config", "user.email", "seed@windmill.dev");
git(seed, "config", "user.name", "seed");
git(seed, "remote", "add", "origin", `file://${bare}`);
const wmillYaml = "defaultTs: bun\nincludes:\n - f/dedupe/**\nexcludes: []\n";
await writeFile(join(seed, "wmill.yaml"), wmillYaml);
git(seed, "add", "-A");
git(seed, "commit", "-m", "main");
git(seed, "push", "origin", "main");
// Any parent id makes the callback deploy as a fork, to this branch.
const forkBranch = `wm-fork/main/${backend.workspace}`;
git(seed, "checkout", "-b", forkBranch);
await writeFile(
join(seed, "wmill.yaml"),
wmillYaml + "dedupeLockfiles: true\n",
);
const pulled = await backend.runCLICommand(["sync", "pull", "--yes"], seed);
expect(pulled.code).toBe(0);
expect(existsSync(join(seed, "locks/requirements.in.lock"))).toBe(true);
git(seed, "add", "-A");
git(seed, "commit", "-m", "dedupe");
git(seed, "push", "origin", forkBranch);
git(work, "clone", `file://${bare}`, ".");
const deployed = await backend.runCLICommand(
[
"sync",
"git-deploy",
"--repository",
"u/test/unused",
"--git-deploy-items",
JSON.stringify([
{ path_type: "script", path: "f/dedupe/a", commit_msg: "deploy" },
]),
"--parent-workspace-id",
"parent",
],
work,
);
expect(deployed.code).toBe(0);
expect(git(work, "rev-parse", "--abbrev-ref", "HEAD")).toBe(forkBranch);
expect(git(work, "status", "--porcelain")).toBe("");
} finally {
await rm(bare, { recursive: true, force: true });
await rm(seed, { recursive: true, force: true });
await rm(work, { recursive: true, force: true });
}
});
});