Keep new worktrees clean when setup changes are uncommitted (#3311)
* fix(worktrees): preserve selected base config New worktrees were overwriting the selected ref's paseo.json with source-checkout bytes, which could create an accidental revert in an otherwise fresh workspace. Keep committed base config authoritative and warn when locally saved setup changes still need to be committed. * fix(worktrees): seed config exclusively * fix(projects): refresh config status on focus
This commit is contained in:
@@ -9,12 +9,14 @@ import {
|
||||
clickReloadProjectSettings,
|
||||
clickRetryProjectSettingsSave,
|
||||
clickSaveProjectSettings,
|
||||
commitPaseoConfig,
|
||||
corruptPaseoConfig,
|
||||
editWorktreeSetup,
|
||||
expectEmptyScriptList,
|
||||
expectProjectHostContextHidden,
|
||||
expectNoEditableTarget,
|
||||
expectNoProjectSettingsError,
|
||||
expectNoUncommittedSetupWarning,
|
||||
expectProjectEditFailed,
|
||||
expectProjectEditName,
|
||||
expectProjectEditSaved,
|
||||
@@ -27,6 +29,7 @@ import {
|
||||
expectSaveButtonDisabled,
|
||||
expectScriptRowCount,
|
||||
expectWriteFailedCalloutActions,
|
||||
expectUncommittedSetupWarning,
|
||||
fillProjectIconUrl,
|
||||
fillProjectName,
|
||||
installDaemonConnectionGate,
|
||||
@@ -216,9 +219,16 @@ test.describe("Projects settings", () => {
|
||||
test("user edits worktree setup from the projects page", async ({ page, editableProject }) => {
|
||||
await openProjects(page);
|
||||
await openProjectSettings(page, editableProject.name);
|
||||
await expectNoUncommittedSetupWarning(page);
|
||||
await editWorktreeSetup(page, updatedSetup);
|
||||
await clickSaveProjectSettings(page);
|
||||
await expectProjectConfigSaved(editableProject);
|
||||
await expectUncommittedSetupWarning(page);
|
||||
|
||||
commitPaseoConfig(editableProject.path);
|
||||
await returnToProjectsList(page);
|
||||
await openProjectSettings(page, editableProject.name);
|
||||
await expectNoUncommittedSetupWarning(page);
|
||||
});
|
||||
|
||||
test("project navigation stays inside the selected host", async ({ page, editableProject }) => {
|
||||
@@ -439,5 +449,7 @@ test.describe("Projects settings — error UX", () => {
|
||||
|
||||
await expectScriptRowCount(page, 0);
|
||||
await expectEmptyScriptList(page);
|
||||
await clickSaveProjectSettings(page);
|
||||
await expectNoUncommittedSetupWarning(page);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { chmod, readFile, writeFile } from "node:fs/promises";
|
||||
import { execFileSync } from "node:child_process";
|
||||
import path from "node:path";
|
||||
import { expect, type Page } from "@playwright/test";
|
||||
import type { WebSocketRoute } from "@playwright/test";
|
||||
@@ -177,6 +178,19 @@ export async function expectSaveButtonDisabled(page: Page): Promise<void> {
|
||||
await expect(page.getByTestId("save-button")).toBeDisabled();
|
||||
}
|
||||
|
||||
export async function expectUncommittedSetupWarning(page: Page): Promise<void> {
|
||||
const warning = page.getByRole("alert").filter({ hasText: "Commit paseo.json changes" });
|
||||
await expect(warning).toContainText("Commit paseo.json changes");
|
||||
await expect(warning).toContainText(
|
||||
"New worktrees use the setup script from the base branch you select.",
|
||||
);
|
||||
}
|
||||
|
||||
export async function expectNoUncommittedSetupWarning(page: Page): Promise<void> {
|
||||
const warning = page.getByRole("alert").filter({ hasText: "Commit paseo.json changes" });
|
||||
await expect(warning).toHaveCount(0);
|
||||
}
|
||||
|
||||
// --- Form-state assertions ---
|
||||
|
||||
export async function expectProjectSettingsFormVisible(page: Page): Promise<void> {
|
||||
@@ -249,6 +263,11 @@ export async function restorePaseoConfig(
|
||||
await writeFile(path.join(repoPath, "paseo.json"), JSON.stringify(config, null, 2) + "\n");
|
||||
}
|
||||
|
||||
export function commitPaseoConfig(repoPath: string): void {
|
||||
execFileSync("git", ["add", "paseo.json"], { cwd: repoPath });
|
||||
execFileSync("git", ["commit", "-m", "Update project config"], { cwd: repoPath });
|
||||
}
|
||||
|
||||
// The daemon writes atomically via a temp file + rename, so blocking writes requires
|
||||
// removing write permission from the *directory*, not just the file.
|
||||
export async function blockPaseoConfigWrites(repoPath: string): Promise<void> {
|
||||
|
||||
Reference in New Issue
Block a user