Files
gstack/test/gstack-wtree-windows-paths.test.ts
e6edff0265 v1.91.89.0 fix(windows,setup): Git Bash hook false-positive recovery, incoming-checker auto-update, setup never deletes its own checkout (Wave 9 Lane A0) (#3140)
Git Bash hands native Bun C:/ paths while gstack-hook-check looked them up as
/c/, so every TypeScript hook was refused and team-mode auto-update held every
release since 1.91.31. The checker maps Bun's paths back to the shell spelling,
the auto-updater lets the incoming release's own checker decide (installed
checker only as a fallback, hook-check-unavailable when neither verifies), and
docs/troubleshooting.md gives stuck installs one recovery command.
/gstack-upgrade and /spec guards use the --show-prefix root check, gstack-wtree
treats drive-letter --git-path answers as absolute, and setup refuses to
replace a runtime root that is or contains its own checkout, Kiro's in-place
root included (#3084), with --status suggesting the move. New sourced helper
bin/gstack-path-spelling.sh and TS twin lib/path-spelling.ts.

Co-authored-by: yuriysan <63948747+yuriysan@users.noreply.github.com>
Co-authored-by: y$un_ <3579304+szsunyuan@users.noreply.github.com>
Co-authored-by: Frederik Reventlow <267160539+dmfrede@users.noreply.github.com>
Co-authored-by: skleinbe-ny <226563731+skleinbe-ny@users.noreply.github.com>
Co-authored-by: bkermen <184220642+bkermen@users.noreply.github.com>
Co-authored-by: Drew Jankowski <3200063+andrewkjankowski@users.noreply.github.com>
2026-10-11 16:48:11 +00:00

89 lines
4.0 KiB
TypeScript

/**
* #3101: gstack-wtree must fingerprint a linked worktree on Windows, where
* `git rev-parse --git-path index|objects` answers with a drive-letter absolute
* path (C:/…). Read as relative, that path was re-rooted under the worktree, so
* the private-object-directory fingerprint (gstack-review-log --start, E4) lost
* the repository's objects and /review and /ship could not record a start.
*
* Windows-safe by construction: gstack-wtree runs through explicit `bash <script>`,
* never shebang execution, and paths are handed to bash in forward-slash form.
*/
import { afterEach, beforeEach, expect, test } from 'bun:test';
import { mkdirSync, mkdtempSync, realpathSync, rmSync, writeFileSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { delimiter, join, resolve } from 'node:path';
import { spawnSync } from 'node:child_process';
const ROOT = resolve(import.meta.dir, '..');
const WTREE = join(ROOT, 'bin', 'gstack-wtree');
const posix = (value: string) => value.replace(/\\/g, '/');
let root = '', worktree = '', objects = '', env: NodeJS.ProcessEnv = {};
const git = (cwd: string, ...args: string[]) => {
const result = spawnSync('git', args, { cwd, env, encoding: 'utf8', timeout: 30_000 });
expect(result.status, result.stderr).toBe(0);
return result.stdout.trim();
};
beforeEach(() => {
root = realpathSync(mkdtempSync(join(tmpdir(), 'gstack-wtree-paths-')));
const main = join(root, 'main');
worktree = join(root, 'linked worktree');
objects = join(root, 'objects');
mkdirSync(main);
mkdirSync(objects);
const globalConfig = join(root, 'gitconfig');
writeFileSync(globalConfig, '');
env = {
...process.env, HOME: root, USERPROFILE: root,
GIT_CONFIG_GLOBAL: globalConfig, GIT_CONFIG_NOSYSTEM: '1', GIT_CONFIG_COUNT: '0',
GIT_DIR: undefined, GIT_WORK_TREE: undefined, GIT_COMMON_DIR: undefined, GIT_INDEX_FILE: undefined,
GIT_OBJECT_DIRECTORY: undefined, GIT_ALTERNATE_OBJECT_DIRECTORIES: undefined,
};
git(main, 'init', '-q');
// A real file, so HEAD's tree lives only in the repository's object store.
writeFileSync(join(main, 'a.txt'), 'hello\n');
git(main, 'add', 'a.txt');
git(main, '-c', 'user.email=fixture@example.test', '-c', 'user.name=Fixture', '-c', 'commit.gpgsign=false', 'commit', '-qm', 'fixture');
git(main, 'worktree', 'add', '-q', '--detach', worktree);
});
afterEach(() => rmSync(root, { recursive: true, force: true }));
// The review-start call shape: new objects go to a private directory the caller owns.
function expectFingerprintOfHead() {
const result = spawnSync('bash', [WTREE], {
cwd: worktree, env: { ...env, GSTACK_WTREE_OBJECT_DIR: posix(objects) }, encoding: 'utf8', timeout: 30_000,
});
expect(result.status, result.stderr).toBe(0);
expect(result.stdout.trim()).toBe(git(worktree, 'rev-parse', 'HEAD^{tree}'));
}
test('a linked worktree with a private object directory gets its fingerprint (#3101)', () => {
expectFingerprintOfHead();
});
// Linux and macOS reproduce Git for Windows: `--git-path` answers in drive-letter
// form, and GIT_ALTERNATE_OBJECT_DIRECTORIES is one ;-separated list in which C:/…
// is absolute. Linux git splits on ':', so the shim hands that list over as one
// quoted entry, the way Windows git reads it.
test.skipIf(process.platform === 'win32')('a drive-letter --git-path answer is not re-rooted under the worktree (#3101)', () => {
const real = JSON.stringify(Bun.which('git')!);
const shim = join(root, 'shim');
mkdirSync(shim);
writeFileSync(join(shim, 'git'), `#!/usr/bin/env bash
case " $* " in *" rev-parse --git-path "*)
out=$(${real} "$@") || exit $?
case "$out" in /*) printf 'C:%s\\n' "$out" ;; *) printf '%s\\n' "$out" ;; esac
exit 0 ;;
esac
if [ -n "\${GIT_ALTERNATE_OBJECT_DIRECTORIES:-}" ]; then
export GIT_ALTERNATE_OBJECT_DIRECTORIES="\\"\${GIT_ALTERNATE_OBJECT_DIRECTORIES#C:}\\""
fi
exec ${real} "$@"
`, { mode: 0o755 });
env.PATH = `${shim}${delimiter}${env.PATH}`;
expect(git(worktree, 'rev-parse', '--git-path', 'objects')).toStartWith('C:/');
expectFingerprintOfHead();
});