refactor(cli): auto-detect fork branch workflow, drop rt.d.ts refresh and legacy-name warning

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
hugocasa
2026-06-11 12:40:56 +02:00
co-authored by Claude Opus 4.8
parent fa3cb4c536
commit 9b255cd2e5
10 changed files with 228 additions and 134 deletions
-34
View File
@@ -1,6 +1,3 @@
import { existsSync } from "node:fs";
import { join } from "node:path";
import process from "node:process";
import { colors } from "@cliffy/ansi/colors";
import { Command } from "@cliffy/command";
import { Select } from "@cliffy/prompt/select";
@@ -186,37 +183,6 @@ interface CommandOptions {
async function promptsAction(opts: CommandOptions): Promise<void> {
await refreshPrompts({ yes: opts.yes === true });
await refreshRtNamespaceIfPresent(opts);
}
/**
* Keep an existing `rt.d.ts` resource-type namespace in sync when the user
* runs `wmill refresh prompts`. `wmill init` already generates it on first
* bind; here we only refresh it when the file is already present (so we never
* introduce it into projects that don't use it). Best-effort: a missing
* workspace/login or offline run just skips with a warning rather than failing
* the whole refresh.
*
* Not wired into the shared `refreshPrompts` helper on purpose — `wmill init`
* regenerates the namespace itself, and `refreshPrompts` must stay
* network-free for its other callers.
*/
async function refreshRtNamespaceIfPresent(opts: CommandOptions): Promise<void> {
const rtPath = join(process.cwd(), "rt.d.ts");
if (!existsSync(rtPath)) return;
try {
const { generateRTNamespace } = await import(
"../resource-type/resource-type.ts"
);
await generateRTNamespace(opts as any);
} catch (error) {
log.warn(
`Could not refresh rt.d.ts resource type namespace: ${
error instanceof Error ? error.message : error
}`
);
}
}
const command = new Command()
+134 -69
View File
@@ -12,9 +12,15 @@ import {
isGitRepository,
renameCurrentGitBranch,
} from "../../utils/git.ts";
import process from "node:process";
import { WM_FORK_PREFIX } from "../../core/constants.ts";
import { tryResolveBranchWorkspace } from "../../core/context.ts";
import { findWorkspaceByGitBranch, readConfigFile } from "../../core/conf.ts";
import {
findWorkspaceByGitBranch,
getEffectiveGitBranch,
getWorkspaceNames,
readConfigFile,
} from "../../core/conf.ts";
async function createWorkspaceFork(
opts: GlobalOptions & {
@@ -36,48 +42,70 @@ async function createWorkspaceFork(
throw new Error("Could not get git branch name");
}
// `--from-branch` enables the "already on a working branch" workflow: the
// fork is based on <fromBranch> (the parent), and the current branch is
// later renamed onto the fork branch. Without it, the fork is based on the
// current branch and the user checks out a fresh fork branch.
const fromBranch = opts.fromBranch;
const config = await readConfigFile({ warnIfMissing: false });
const originalBranchIfForked = getOriginalBranchForWorkspaceForks(currentBranch);
let workspace;
if (fromBranch) {
if (fromBranch === currentBranch) {
// A "base branch" is one we must not rename onto a fork branch: mapped to a
// workspace in wmill.yaml, or a conventional default (main/master).
const isBaseBranch = (branch: string): boolean =>
branch === "main" ||
branch === "master" ||
findWorkspaceByGitBranch(config.workspaces, branch) !== undefined;
// Decide the base branch the fork links to, and whether to rename the
// current working branch onto the fork branch. Auto-detected from where you
// are; `--from-branch` is the explicit/non-interactive override.
let clonedBranchName: string;
let renameCurrent: boolean;
if (opts.fromBranch) {
// Explicit override: base on <fromBranch>, rename the current branch.
if (opts.fromBranch === currentBranch) {
throw new Error(
`--from-branch is for converting a *different* working branch into the fork branch, but you are already on \`${currentBranch}\`. ` +
`Either omit --from-branch (a fresh fork branch is created with \`git checkout -b\`), or check out the working branch you want to convert first.`,
`Omit --from-branch to create a fresh fork branch with \`git checkout -b\`.`,
);
}
const config = await readConfigFile({ warnIfMissing: false });
// Protect base branches. This workflow renames the *current* branch onto
// the fork branch, so refuse when the current branch is itself a base
// branch — either mapped to a workspace in wmill.yaml, or a conventional
// default (main/master). Renaming one of those would clobber it; the user
// must check out a disposable working branch first. Fail fast, before any
// fork workspace or git branch is created.
const currentBranchIsBase =
currentBranch === "main" ||
currentBranch === "master" ||
findWorkspaceByGitBranch(config.workspaces, currentBranch) !== undefined;
if (currentBranchIsBase) {
if (isBaseBranch(currentBranch)) {
throw new Error(
`Refusing to rename your current branch \`${currentBranch}\` — it looks like a base branch (mapped to a workspace in wmill.yaml, or main/master). ` +
`The --from-branch workflow turns a *disposable working branch* into the fork branch. ` +
`Check out the working branch you want to convert first, or omit --from-branch to create a fresh fork branch with \`git checkout -b\`.`,
`Check out the disposable working branch you want to convert first.`,
);
}
const match = findWorkspaceByGitBranch(config.workspaces, fromBranch);
if (!match) {
if (!findWorkspaceByGitBranch(config.workspaces, opts.fromBranch)) {
throw new Error(
`Could not find a workspace mapped to branch \`${fromBranch}\` in wmill.yaml's workspaces section. ` +
`Could not find a workspace mapped to branch \`${opts.fromBranch}\` in wmill.yaml's workspaces section. ` +
`Pass the base branch your fork should be based on (e.g. the branch bound to the parent workspace).`,
);
}
workspace = await tryResolveBranchWorkspace(opts, match[0]);
clonedBranchName = opts.fromBranch;
renameCurrent = true;
} else if (originalBranchIfForked) {
// Fork of a fork: link to the original branch; user checks out a new branch.
log.info(`You are creating a fork of a fork. The branch will be linked to the original branch this was forked from, i.e. \`${originalBranchIfForked}\`, for all settings and overrides.`);
clonedBranchName = originalBranchIfForked;
renameCurrent = false;
} else if (isBaseBranch(currentBranch)) {
// On a base branch: base the fork on it; user checks out a fresh fork branch.
clonedBranchName = currentBranch;
renameCurrent = false;
} else {
// On a non-base working branch: offer to base the fork on it and rename it.
clonedBranchName = await resolveWorkingBranchBase(config, opts, currentBranch);
renameCurrent = true;
}
// Resolve the parent workspace. When the base differs from the current
// branch (the rename workflows), resolve via the base branch's workspace;
// otherwise use plain branch resolution.
let workspace;
if (clonedBranchName === currentBranch) {
workspace = await tryResolveBranchWorkspace(opts);
} else {
const baseMatch = findWorkspaceByGitBranch(config.workspaces, clonedBranchName);
workspace = baseMatch
? await tryResolveBranchWorkspace(opts, baseMatch[0])
: await tryResolveBranchWorkspace(opts);
}
if (!workspace) {
@@ -86,22 +114,6 @@ async function createWorkspaceFork(
log.info(`You are forking workspace (${workspace.workspaceId})`)
const originalBranchIfForked = getOriginalBranchForWorkspaceForks(currentBranch);
let clonedBranchName: string | null;
if (fromBranch) {
clonedBranchName = fromBranch;
} else if (originalBranchIfForked) {
log.info(`You are creating a fork of a fork. The branch will be linked to the original branch this was forked from, i.e. \`${originalBranchIfForked}\`, for all settings and overrides.`);
clonedBranchName = originalBranchIfForked;
} else {
clonedBranchName = currentBranch;
}
if (!clonedBranchName) {
throw new Error("Failed to get current branch name, aborting operation");
}
if (opts.workspace) {
log.info(
colors.red.bold(
@@ -301,11 +313,13 @@ async function createWorkspaceFork(
const newBranchName = `${WM_FORK_PREFIX}/${clonedBranchName}/${workspaceId}`
// Workflow B (`--from-branch`): turn the current working branch into the
// fork branch in place so its commits become the fork's. Workflow A: leave
// the user on their base branch and have them check out a fresh fork branch.
// Rename workflow: turn the current working branch into the fork branch in
// place so its commits become the fork's. (Consent was already established —
// by `--from-branch`, or the interactive prompt for a non-base branch.)
// Otherwise: leave the user on their branch and have them check out a fresh
// fork branch.
let onForkBranch = false;
if (fromBranch) {
if (renameCurrent) {
if (currentBranch === newBranchName) {
onForkBranch = true;
log.info(colors.green(`Your current branch is already \`${newBranchName}\`.`));
@@ -315,27 +329,13 @@ async function createWorkspaceFork(
`Check out the fork branch yourself (e.g. \`git checkout ${newBranchName}\`).`,
);
} else {
let doRename = opts.yes === true;
if (!doRename) {
const { Select } = await import("@cliffy/prompt/select");
const choice = await Select.prompt({
message: `Rename your current branch \`${currentBranch}\`\`${newBranchName}\` so its commits become the fork's branch?`,
options: [
{ name: "Yes, rename it", value: "confirm" },
{ name: "No, I'll switch branches myself", value: "cancel" },
],
});
doRename = choice === "confirm";
}
if (doRename) {
renameCurrentGitBranch(newBranchName);
onForkBranch = true;
log.info(
colors.green(
`Renamed \`${currentBranch}\`\`${newBranchName}\`. Your existing commits are now on the fork branch.`,
),
);
}
renameCurrentGitBranch(newBranchName);
onForkBranch = true;
log.info(
colors.green(
`Renamed \`${currentBranch}\`\`${newBranchName}\`. Your existing commits are now on the fork branch.`,
),
);
}
}
@@ -356,6 +356,71 @@ To merge changes back to the parent workspace, you can:
See: https://www.windmill.dev/docs/advanced/workspace_forks`);
}
/**
* When `wmill workspace fork` is run from a non-base working branch, confirm
* the user wants to turn it into a fork branch, and resolve which base branch
* the fork should be linked to. Throws in non-interactive mode (where the user
* must pass `--from-branch <base>` instead).
*/
async function resolveWorkingBranchBase(
config: Awaited<ReturnType<typeof readConfigFile>>,
opts: { yes?: boolean },
currentBranch: string,
): Promise<string> {
const interactive = process.stdin.isTTY && opts.yes !== true;
if (!interactive) {
throw new Error(
`You are on working branch \`${currentBranch}\`, which is not a base branch. ` +
`Pass --from-branch <base> to base the fork on a base branch and rename this branch onto the fork branch, ` +
`or check out a base branch and run \`wmill workspace fork\` to create a fresh fork branch.`,
);
}
const { Select } = await import("@cliffy/prompt/select");
const proceed = await Select.prompt({
message: `You're on working branch \`${currentBranch}\`, not a base branch. Base a fork on it and rename it onto the fork branch?`,
options: [
{ name: "Yes, base the fork on this branch and rename it", value: "yes" },
{ name: "No, cancel", value: "no" },
],
});
if (proceed !== "yes") {
throw new Error("Fork cancelled. Check out a base branch to create a fresh fork branch instead.");
}
const baseBranches = listConfiguredBaseBranches(config);
if (baseBranches.length === 0) {
throw new Error(
`No base branches are configured in wmill.yaml's workspaces section, so the fork can't be linked to a parent. ` +
`Add the parent workspace to wmill.yaml, or pass --from-branch <base>.`,
);
}
if (baseBranches.length === 1) {
log.info(`Basing the fork on \`${baseBranches[0]}\`.`);
return baseBranches[0];
}
return await Select.prompt({
message: "Which base branch is this fork based on (the parent)?",
options: baseBranches.map((b) => ({ name: b, value: b })),
});
}
/**
* Base branches configured in wmill.yaml, mirroring how `findWorkspaceByGitBranch`
* keys them (effective git branch = `gitBranch ?? workspaceName`, reserved keys
* excluded) so the chosen base resolves to a workspace afterwards.
*/
function listConfiguredBaseBranches(
config: Awaited<ReturnType<typeof readConfigFile>>,
): string[] {
const workspaces = config.workspaces;
const branches = new Set<string>();
for (const name of getWorkspaceNames(workspaces)) {
branches.add(getEffectiveGitBranch(name, workspaces![name]));
}
return [...branches];
}
async function deleteWorkspaceFork(
opts: GlobalOptions & {
yes?: boolean;
+2 -2
View File
@@ -814,9 +814,9 @@ const command = new Command()
)
.option(
"--from-branch <branch:string>",
"Fork based on this base branch (its bound workspace is the parent) and rename your current working branch onto the fork branch wm-fork/<branch>/<id>. Use when you're already on a branch (e.g. with forked-datatable edits) you want to turn into the fork. Omit to base the fork on the current branch and check out a fresh fork branch instead."
"Non-interactive override for the 'turn my current working branch into the fork' workflow: base the fork on <branch> (its bound workspace is the parent) and rename the current branch onto wm-fork/<branch>/<id>. Usually unneeded — from a working branch `wmill workspace fork` offers this interactively; from a base branch it creates a fresh fork branch."
)
.option("-y --yes", "Skip interactive prompts (defaults datatable behavior to 'skip', and confirms the --from-branch rename)")
.option("-y --yes", "Skip interactive prompts (defaults datatable behavior to 'skip'). On a non-base branch, requires --from-branch since the base branch can't be prompted for.")
.action(createWorkspaceFork as any)
.command("delete-fork")
.description("Delete a forked workspace and git branch")
+5 -3
View File
@@ -156,10 +156,12 @@ Only deploy when the user explicitly asks to deploy, publish, push, or ship —
A **fork** is an isolated copy of a workspace for parallel or experimental work — make changes (including to datatables, which are cloned per fork) without touching the parent, then merge back after review. Each fork is paired with a git branch named \`wm-fork/<base>/<id>\`. Forks require a git repo.
Create one with \`wmill workspace fork\`. There are two branch workflows — pick by where you are:
Just run \`wmill workspace fork\` — it adapts to where you are:
- **Starting from the base branch** (no in-progress work to carry over): run \`wmill workspace fork\`. It bases the fork on your current branch and prints a \`git checkout -b wm-fork/<base>/<id>\` to start the fork branch.
- **Already on a working branch you want to turn into the fork** (e.g. you've branched and already edited a forked datatable): run \`wmill workspace fork --from-branch <base>\`. It bases the fork on \`<base>\` (the parent's branch) and renames your current branch onto \`wm-fork/<base>/<id>\` in place, preserving its commits.
- **On a base branch** (e.g. \`main\`, or a branch bound to a workspace): it bases the fork on that branch and prints a \`git checkout -b wm-fork/<base>/<id>\` to start a fresh fork branch.
- **On a working branch** (e.g. you've branched and already edited a forked datatable): it offers to base the fork on that branch and rename it onto \`wm-fork/<base>/<id>\` in place, preserving its commits — asking which base branch is the parent if there's more than one.
For non-interactive runs from a working branch, pass \`--from-branch <base>\` to skip the prompts. The CLI refuses to rename a base branch.
Merge a fork back into its parent with \`wmill workspace merge\` (or the Merge UI on the fork's home page). Full reference: https://www.windmill.dev/docs/advanced/workspace_forks
+19 -18
View File
@@ -105,14 +105,16 @@ export function currentPromptsHash(nonDottedPaths: boolean): string {
}
/**
* Read AGENTS.wmill.md in the current working directory, compare its embedded
* hash to the current CLI's hash, and print a one-line warning if they
* Read the managed guidance file in the current working directory, compare its
* embedded hash to the current CLI's hash, and print a one-line warning if they
* differ. Silent on every other code path (no managed file, no marker,
* matching hash, IO error, …) so it never gets in the user's way.
*
* Back-compat: if only the legacy `AGENTS.cli.md` is present (no
* `AGENTS.wmill.md` yet), warn that the managed file should be migrated —
* `wmill refresh prompts` renames it and rewrites the include.
* Back-compat: prefers `AGENTS.wmill.md` but falls back to the legacy
* `AGENTS.cli.md` and runs the exact same staleness check on it. We do NOT
* warn merely because the old filename is in use — an up-to-date `AGENTS.cli.md`
* stays quiet; only a stale hash (which `wmill refresh prompts` fixes, and
* which also migrates the filename) trips the warning.
*/
export async function warnIfPromptsStale(opts?: {
cwd?: string;
@@ -122,18 +124,17 @@ export async function warnIfPromptsStale(opts?: {
if (opts?.argv && !shouldRunFreshnessCheck(opts.argv)) return;
const cwd = opts?.cwd ?? process.cwd();
const path = `${cwd}/${AGENTS_WMILL_FILENAME}`;
// Prefer the current filename; fall back to the legacy one for back-compat.
let fileName = AGENTS_WMILL_FILENAME;
let path = `${cwd}/${fileName}`;
if (!(await stat(path).catch(() => null))) {
// No AGENTS.wmill.md. If the legacy AGENTS.cli.md is still around, nudge
// the user to migrate it; otherwise this project just isn't wmill-managed.
const legacyPath = `${cwd}/${LEGACY_AGENTS_CLI_FILENAME}`;
if (await stat(legacyPath).catch(() => null)) {
emitWarning(
"Your AGENTS.cli.md is using the old managed filename. Run `wmill refresh prompts` to migrate it to AGENTS.wmill.md."
);
fileName = LEGACY_AGENTS_CLI_FILENAME;
path = `${cwd}/${fileName}`;
if (!(await stat(path).catch(() => null))) {
// Neither file present — this project just isn't wmill-managed.
return;
}
return;
}
let content: string;
@@ -145,10 +146,10 @@ export async function warnIfPromptsStale(opts?: {
const stored = extractPromptsHash(content);
if (!stored) {
// Older AGENTS.wmill.md without a marker. Warn so the user re-runs
// refresh and picks up the new format.
// Managed file without a marker. Warn so the user re-runs refresh and
// picks up the new format (and the new filename).
emitWarning(
"Your AGENTS.wmill.md predates prompt versioning. Run `wmill refresh prompts` to refresh and add a version marker."
`Your ${fileName} predates prompt versioning. Run \`wmill refresh prompts\` to refresh and add a version marker.`
);
return;
}
@@ -170,7 +171,7 @@ export async function warnIfPromptsStale(opts?: {
const current = currentPromptsHash(nonDottedPaths);
if (stored !== current) {
emitWarning(
"Your AGENTS.wmill.md is out of date. Run `wmill refresh prompts` to refresh."
`Your ${fileName} is out of date. Run \`wmill refresh prompts\` to refresh.`
);
}
}
+2 -2
View File
@@ -6762,8 +6762,8 @@ workspace related commands
- \`--create-workspace-name <workspace_name:string>\` - Specify the workspace name. Ignored if --create is not specified or the workspace already exists. Will default to the workspace id.
- \`--color <color:string>\` - Workspace color (hex code, e.g. #ff0000)
- \`--datatable-behavior <behavior:string>\` - How to handle datatables: skip, schema_only, or schema_and_data (default: interactive prompt)
- \`--from-branch <branch:string>\` - Fork based on this base branch (its bound workspace is the parent) and rename your current working branch onto the fork branch wm-fork/<branch>/<id>. Use when you're already on a branch (e.g. with forked-datatable edits) you want to turn into the fork. Omit to base the fork on the current branch and check out a fresh fork branch instead.
- \`-y --yes\` - Skip interactive prompts (defaults datatable behavior to 'skip', and confirms the --from-branch rename)
- \`--from-branch <branch:string>\` - Non-interactive override for the 'turn my current working branch into the fork' workflow: base the fork on <branch> (its bound workspace is the parent) and rename the current branch onto wm-fork/<branch>/<id>. Usually unneeded — from a working branch \`wmill workspace fork\` offers this interactively; from a base branch it creates a fresh fork branch.
- \`-y --yes\` - Skip interactive prompts (defaults datatable behavior to 'skip'). On a non-base branch, requires --from-branch since the base branch can't be prompted for.
- \`workspace delete-fork <fork_name:string>\` - Delete a forked workspace and git branch
- \`-y --yes\` - Skip confirmation prompt
- \`workspace merge\` - Compare and deploy changes between a fork and its parent workspace
+60
View File
@@ -614,4 +614,64 @@ describe("prompts freshness — additional invariants", () => {
expect(stdoutJoined).not.toContain("out of date");
});
});
// Back-compat: a legacy AGENTS.cli.md is still hash-checked, and we do NOT
// warn merely because of the old filename — only when it's actually stale.
test("warnIfPromptsStale stays silent for an up-to-date legacy AGENTS.cli.md", async () => {
await withTempDir(async (tempDir) => {
const content = injectPromptsHashMarker(
"# Windmill CLI Agent Instructions\nbody\n",
currentPromptsHash(false)
);
await writeFile(join(tempDir, "AGENTS.cli.md"), content, "utf8");
const stderrWrites: string[] = [];
const originalStderr = process.stderr.write.bind(process.stderr);
// @ts-expect-error — overriding write for the test
process.stderr.write = (chunk: any) => {
stderrWrites.push(String(chunk));
return true;
};
try {
await warnIfPromptsStale({
cwd: tempDir,
nonDottedPaths: false,
argv: ["node", "wmill", "sync", "push"],
});
} finally {
process.stderr.write = originalStderr;
}
expect(stderrWrites.join("")).toBe("");
});
});
test("warnIfPromptsStale warns (naming the legacy file) for a stale AGENTS.cli.md", async () => {
await withTempDir(async (tempDir) => {
await writeFile(
join(tempDir, "AGENTS.cli.md"),
"# Windmill CLI Agent Instructions\n<!-- wmill-prompts-hash: 000000000000 -->\nbody\n",
"utf8"
);
const stderrWrites: string[] = [];
const originalStderr = process.stderr.write.bind(process.stderr);
// @ts-expect-error — overriding write for the test
process.stderr.write = (chunk: any) => {
stderrWrites.push(String(chunk));
return true;
};
try {
await warnIfPromptsStale({
cwd: tempDir,
nonDottedPaths: false,
argv: ["node", "wmill", "sync", "push"],
});
} finally {
process.stderr.write = originalStderr;
}
const out = stderrWrites.join("");
expect(out).toContain("out of date");
expect(out).toContain("AGENTS.cli.md");
});
});
});
@@ -726,8 +726,8 @@ workspace related commands
- `--create-workspace-name <workspace_name:string>` - Specify the workspace name. Ignored if --create is not specified or the workspace already exists. Will default to the workspace id.
- `--color <color:string>` - Workspace color (hex code, e.g. #ff0000)
- `--datatable-behavior <behavior:string>` - How to handle datatables: skip, schema_only, or schema_and_data (default: interactive prompt)
- `--from-branch <branch:string>` - Fork based on this base branch (its bound workspace is the parent) and rename your current working branch onto the fork branch wm-fork/<branch>/<id>. Use when you're already on a branch (e.g. with forked-datatable edits) you want to turn into the fork. Omit to base the fork on the current branch and check out a fresh fork branch instead.
- `-y --yes` - Skip interactive prompts (defaults datatable behavior to 'skip', and confirms the --from-branch rename)
- `--from-branch <branch:string>` - Non-interactive override for the 'turn my current working branch into the fork' workflow: base the fork on <branch> (its bound workspace is the parent) and rename the current branch onto wm-fork/<branch>/<id>. Usually unneeded — from a working branch `wmill workspace fork` offers this interactively; from a base branch it creates a fresh fork branch.
- `-y --yes` - Skip interactive prompts (defaults datatable behavior to 'skip'). On a non-base branch, requires --from-branch since the base branch can't be prompted for.
- `workspace delete-fork <fork_name:string>` - Delete a forked workspace and git branch
- `-y --yes` - Skip confirmation prompt
- `workspace merge` - Compare and deploy changes between a fork and its parent workspace
+2 -2
View File
@@ -3277,8 +3277,8 @@ workspace related commands
- \`--create-workspace-name <workspace_name:string>\` - Specify the workspace name. Ignored if --create is not specified or the workspace already exists. Will default to the workspace id.
- \`--color <color:string>\` - Workspace color (hex code, e.g. #ff0000)
- \`--datatable-behavior <behavior:string>\` - How to handle datatables: skip, schema_only, or schema_and_data (default: interactive prompt)
- \`--from-branch <branch:string>\` - Fork based on this base branch (its bound workspace is the parent) and rename your current working branch onto the fork branch wm-fork/<branch>/<id>. Use when you're already on a branch (e.g. with forked-datatable edits) you want to turn into the fork. Omit to base the fork on the current branch and check out a fresh fork branch instead.
- \`-y --yes\` - Skip interactive prompts (defaults datatable behavior to 'skip', and confirms the --from-branch rename)
- \`--from-branch <branch:string>\` - Non-interactive override for the 'turn my current working branch into the fork' workflow: base the fork on <branch> (its bound workspace is the parent) and rename the current branch onto wm-fork/<branch>/<id>. Usually unneeded — from a working branch \`wmill workspace fork\` offers this interactively; from a base branch it creates a fresh fork branch.
- \`-y --yes\` - Skip interactive prompts (defaults datatable behavior to 'skip'). On a non-base branch, requires --from-branch since the base branch can't be prompted for.
- \`workspace delete-fork <fork_name:string>\` - Delete a forked workspace and git branch
- \`-y --yes\` - Skip confirmation prompt
- \`workspace merge\` - Compare and deploy changes between a fork and its parent workspace
@@ -731,8 +731,8 @@ workspace related commands
- `--create-workspace-name <workspace_name:string>` - Specify the workspace name. Ignored if --create is not specified or the workspace already exists. Will default to the workspace id.
- `--color <color:string>` - Workspace color (hex code, e.g. #ff0000)
- `--datatable-behavior <behavior:string>` - How to handle datatables: skip, schema_only, or schema_and_data (default: interactive prompt)
- `--from-branch <branch:string>` - Fork based on this base branch (its bound workspace is the parent) and rename your current working branch onto the fork branch wm-fork/<branch>/<id>. Use when you're already on a branch (e.g. with forked-datatable edits) you want to turn into the fork. Omit to base the fork on the current branch and check out a fresh fork branch instead.
- `-y --yes` - Skip interactive prompts (defaults datatable behavior to 'skip', and confirms the --from-branch rename)
- `--from-branch <branch:string>` - Non-interactive override for the 'turn my current working branch into the fork' workflow: base the fork on <branch> (its bound workspace is the parent) and rename the current branch onto wm-fork/<branch>/<id>. Usually unneeded — from a working branch `wmill workspace fork` offers this interactively; from a base branch it creates a fresh fork branch.
- `-y --yes` - Skip interactive prompts (defaults datatable behavior to 'skip'). On a non-base branch, requires --from-branch since the base branch can't be prompted for.
- `workspace delete-fork <fork_name:string>` - Delete a forked workspace and git branch
- `-y --yes` - Skip confirmation prompt
- `workspace merge` - Compare and deploy changes between a fork and its parent workspace