Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 0 additions & 5 deletions src/cmd/lib/checkout.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,11 +144,6 @@ export const checkoutCmd = new Command()
},
);

// Note that if they try to check out to the same branch, we can't
// even figure that out, because we store the branch ID, not the
// branch name. So this warning, while useful, won't show up if they
// are checking out FROM a branch that has been deleted and currently
// does not exist.
if (currentBranchData.name === branchName) {
spinner.warn(
`You are already on branch "${dryCheckoutResult.fromBranch.name}"`,
Expand Down
1 change: 1 addition & 0 deletions src/cmd/lib/clone.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ export const cloneCmd = new Command()
rootPath: clonePath,
valName,
username: ownerName,
branchName,
});

if (editorFiles) {
Expand Down
26 changes: 24 additions & 2 deletions src/cmd/tests/checkout_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,12 @@ import { doWithNewVal } from "~/vt/lib/tests/utils.ts";
import { doWithTempDir } from "~/vt/lib/utils/misc.ts";
import { join } from "@std/path";
import sdk from "~/sdk.ts";
import { runVtCommand, streamVtCommand } from "~/cmd/tests/utils.ts";
import { assert, assertStringIncludes } from "@std/assert";
import {
readPersistedBranchName,
runVtCommand,
streamVtCommand,
} from "~/cmd/tests/utils.ts";
import { assert, assertEquals, assertStringIncludes } from "@std/assert";
import { exists } from "@std/fs";
import type ValTown from "@valtown/sdk";

Expand Down Expand Up @@ -50,6 +54,12 @@ Deno.test({
tmpDir,
);

assertEquals(
await readPersistedBranchName(fullPath),
"main",
"clone should persist the checked-out branch name",
);

// Make a remote change to main branch after cloning
await sdk.vals.files.update(
val.id,
Expand Down Expand Up @@ -85,6 +95,12 @@ Deno.test({
"feature.ts should exist after checkout; we're not on feature branch",
);

assertEquals(
await readPersistedBranchName(fullPath),
"feature-branch",
"checkout should persist the target branch name",
);

const [statusOutput] = await runVtCommand(["status"], fullPath);
assertStringIncludes(
statusOutput,
Expand Down Expand Up @@ -147,6 +163,12 @@ Deno.test({
checkoutOutput,
'Created and switched to new branch "feature-with-changes"',
);

assertEquals(
await readPersistedBranchName(fullPath),
"feature-with-changes",
"checkout -b should persist the new branch name",
);
});

await t.step("verify local changes are preserved", async () => {
Expand Down
31 changes: 31 additions & 0 deletions src/cmd/tests/clone_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,15 @@ import {
import { exists } from "@std/fs";
import { join } from "@std/path";
import {
readPersistedBranchName,
runVtCommand,
streamVtCommand,
waitForStable,
} from "~/cmd/tests/utils.ts";
import { doWithTempDir } from "~/vt/lib/utils/misc.ts";
import sdk, { getCurrentUser, randomValName } from "~/sdk.ts";
import type { ValFileType } from "~/types.ts";
import { DEFAULT_BRANCH_NAME } from "~/consts.ts";

Deno.test({
name: "clone preserves custom deno.json and .vtignore",
Expand Down Expand Up @@ -129,6 +131,12 @@ Deno.test({
], tmpDir);
assertStringIncludes(output, "cloned to");

assertEquals(
await readPersistedBranchName(cloneDir),
DEFAULT_BRANCH_NAME,
"clone should persist the checked-out branch name",
);

// Verify the files exist
const testJsExists = await exists(join(cloneDir, "test.js"));
assertEquals(testJsExists, true, "test.js should exist");
Expand All @@ -148,6 +156,29 @@ Deno.test({
"content of test_inner.js should match",
);
});

await t.step("clone a non-default branch", async () => {
const branchName = "feature-branch";
await sdk.vals.branches.create(
val.id,
{ name: branchName, branchId: branch.id },
);

const cloneDir = join(tmpDir, "feature-clone");
await runVtCommand([
"clone",
val.name,
cloneDir,
branchName,
"--no-editor-files",
], tmpDir);

assertEquals(
await readPersistedBranchName(cloneDir),
branchName,
"clone should persist a non-default checked-out branch name",
);
});
});
});
},
Expand Down
12 changes: 11 additions & 1 deletion src/cmd/tests/create_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,11 @@ import sdk, {
listValItems,
randomValName,
} from "~/sdk.ts";
import { runVtCommand, streamVtCommand } from "~/cmd/tests/utils.ts";
import {
readPersistedBranchName,
runVtCommand,
streamVtCommand,
} from "~/cmd/tests/utils.ts";
import { DEFAULT_BRANCH_NAME } from "~/consts.ts";
import { delay } from "@std/async";

Expand Down Expand Up @@ -112,6 +116,12 @@ Deno.test({
await exists(join(tmpDir, newValName)),
"val was not cloned to target",
);

assertEquals(
await readPersistedBranchName(join(tmpDir, newValName)),
DEFAULT_BRANCH_NAME,
"create should persist the initial branch name",
);
});
});
} finally {
Expand Down
12 changes: 9 additions & 3 deletions src/cmd/tests/remix_test.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { doWithNewVal } from "~/vt/lib/tests/utils.ts";
import { join } from "@std/path";
import sdk, { getCurrentUser } from "~/sdk.ts";
import { runVtCommand } from "~/cmd/tests/utils.ts";
import { assert, assertStringIncludes } from "@std/assert";
import { readPersistedBranchName, runVtCommand } from "~/cmd/tests/utils.ts";
import { assert, assertEquals, assertStringIncludes } from "@std/assert";
import { exists } from "@std/fs";
import { META_FOLDER_NAME } from "~/consts.ts";
import { DEFAULT_BRANCH_NAME, META_FOLDER_NAME } from "~/consts.ts";
import { doWithTempDir } from "~/vt/lib/utils/misc.ts";

Deno.test({
Expand Down Expand Up @@ -44,6 +44,12 @@ Deno.test({
await exists(join(remixedValPath, META_FOLDER_NAME)),
"remixed Val should have .vt metadata folder",
);

assertEquals(
await readPersistedBranchName(remixedValPath),
DEFAULT_BRANCH_NAME,
"remix should persist the initial branch name",
);
});

// Clean up the remixed val
Expand Down
10 changes: 10 additions & 0 deletions src/cmd/tests/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { ENTRYPOINT_NAME } from "~/consts.ts";
import { doWithTempDir } from "~/vt/lib/utils/misc.ts";
import { parseValUri } from "~/cmd/lib/utils/parsing.ts";
import { delay } from "@std/async";
import VTMeta from "~/vt/vt/VTMeta.ts";

/**
* Creates and spawns a Deno child process for the vt.ts script.
Expand Down Expand Up @@ -42,6 +43,15 @@ export function runVtProc(
return command.spawn();
}

/**
* Reads the branch name persisted in a Val's local VT state file.
*/
export async function readPersistedBranchName(
rootPath: string,
): Promise<string | undefined> {
return (await new VTMeta(rootPath).loadVtState()).branch.name;
}

/**
* Runs the vt.ts script with provided arguments.
*
Expand Down
6 changes: 4 additions & 2 deletions src/vt/vt/VTClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ export default class VTClient {

await vt.getMeta().saveVtState({
val: { id: valId },
branch: { id: branch.id, version: version },
branch: { id: branch.id, name: branch.name, version: version },
});

return vt;
Expand Down Expand Up @@ -427,7 +427,7 @@ export default class VTClient {
// Save the VT state
await vt.getMeta().saveVtState({
val: { id: valId },
branch: { id: branch.id, version },
branch: { id: branch.id, name: branch.name, version },
});

// Perform the clone
Expand Down Expand Up @@ -608,6 +608,7 @@ export default class VTClient {
if (!baseParams.dryRun) {
if (result.toBranch) {
vtState.branch.id = result.toBranch.id;
vtState.branch.name = result.toBranch.name;
vtState.branch.version = FIRST_VERSION_NUMBER; // Set version to 1 for the new branch
}
}
Expand Down Expand Up @@ -637,6 +638,7 @@ export default class VTClient {
if (!baseParams.dryRun) {
if (result.toBranch) {
vtState.branch.id = result.toBranch.id;
vtState.branch.name = result.toBranch.name;
vtState.branch.version = result.toBranch.version; // Use the target branch's version
}
}
Expand Down
8 changes: 8 additions & 0 deletions src/vt/vt/schemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,14 @@ export const VTStateSchema = z
.catch({ id: "" }),
branch: z.object({
id: z.string().uuid(),
// Store the exact Val Town branch name in JSON so shell prompts and
// other local tooling can read it without making an API call.
//
// Keep this optional so existing checkouts created before this field was
// added continue to load; new state writes populate it whenever the
// branch is known. JSON.stringify will safely escape control characters
// if a branch name is displayed by bash/zsh prompt integrations.
name: z.string().optional(),
version: z.number().gte(0),
}),
lastRun: z.object({
Expand Down