Skip to content

Commit 96bd025

Browse files
committed
workspace: reject mixed reset mode
Return exit 129 for git reset --mixed so the CLI matches its documented unsupported-mode contract instead of silently treating the command like a bare reset. Cover hard reset from detached HEAD and reset that path by checking out the resolved oid directly, avoiding a write through the symbolic HEAD name.
1 parent 382e7f7 commit 96bd025

4 files changed

Lines changed: 28 additions & 2 deletions

File tree

packages/workspace/src/git/cli.test.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1891,6 +1891,14 @@ describe("runGitCli — reset argv parsing", () => {
18911891
expect(res.exitCode).toBe(129);
18921892
expect(res.stderr).toContain("--soft is not supported");
18931893
});
1894+
1895+
it("--mixed is rejected as unsupported", async () => {
1896+
const { client, calls } = fakeClient();
1897+
const res = await runGitCli(client, { argv: ["reset", "--mixed", "HEAD"] });
1898+
expect(res.exitCode).toBe(129);
1899+
expect(res.stderr).toContain("--mixed is not supported");
1900+
expect(calls.reset).toEqual([]);
1901+
});
18941902
});
18951903

18961904
describe("runGitCli — clean argv parsing", () => {

packages/workspace/src/git/cli.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2005,6 +2005,9 @@ async function runReset(
20052005
if (parsed.flags.soft === true) {
20062006
return { stdout: "", stderr: "git reset: --soft is not supported\n", exitCode: 129 };
20072007
}
2008+
if (parsed.flags.mixed === true) {
2009+
return { stdout: "", stderr: "git reset: --mixed is not supported\n", exitCode: 129 };
2010+
}
20082011
const sep = args.indexOf("--");
20092012
const positional =
20102013
sep === -1 ? parsed.positional : args.slice(0, sep).filter((a) => !a.startsWith("-"));

packages/workspace/src/git/worktree.test.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,22 @@ describe("resetWith", () => {
114114
expect(await statusOf("a.txt")).toEqual([1, 1, 1]);
115115
});
116116

117+
it("hard reset works from detached HEAD", async () => {
118+
await init();
119+
const first = await commit("a.txt", "v1\n", "first");
120+
const second = await commit("a.txt", "v2\n", "second");
121+
await git.checkout({ fs: memfs, dir: DIR, ref: second });
122+
expect(await git.currentBranch({ fs: memfs, dir: DIR })).toBeUndefined();
123+
await memfs.promises.writeFile(`${DIR}/a.txt`, "dirty\n");
124+
125+
await resetWith({ git: resetClient, fs: memfs, dir: DIR, hard: true, ref: first });
126+
127+
expect(await git.resolveRef({ fs: memfs, dir: DIR, ref: "HEAD" })).toBe(first);
128+
expect(await git.currentBranch({ fs: memfs, dir: DIR })).toBeUndefined();
129+
expect(await memfs.promises.readFile(`${DIR}/a.txt`, "utf8")).toBe("v1\n");
130+
expect(await statusOf("a.txt")).toEqual([1, 1, 1]);
131+
});
132+
117133
it("hard reset throws NotARepositoryError outside a repo", async () => {
118134
await memfs.promises.mkdir("/loose", { recursive: true });
119135
await expect(

packages/workspace/src/git/worktree.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -176,8 +176,7 @@ async function hardReset(opts: ResetWithDeps, dir: string, ref: string): Promise
176176
await opts.git.checkout({ fs: opts.fs, dir, ref: branch, force: true, cache: opts.cache });
177177
return;
178178
}
179-
await opts.git.writeRef({ fs: opts.fs, dir, ref: "HEAD", value: oid, force: true });
180-
await opts.git.checkout({ fs: opts.fs, dir, ref: "HEAD", force: true, cache: opts.cache });
179+
await opts.git.checkout({ fs: opts.fs, dir, ref: oid, force: true, cache: opts.cache });
181180
}
182181

183182
async function stagedPaths(opts: ResetWithDeps, dir: string): Promise<string[]> {

0 commit comments

Comments
 (0)