mirror of
https://github.com/garrytan/gstack.git
synced 2026-11-03 15:27:56 +01:00
freeze: compare Git Bash paths by cygpath's drive spelling (#3623)
Git Bash mounts %TEMP% at /tmp and converts HOME through its mounts, so '~/x' and 'D:\...\x' named one file in two spellings and the hook denied a legit edit (windows-free-shard 2 on #3758). Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
This commit is contained in:
1 parent
2034fbb61d
commit
4a28416dbc
2 files changed
+46
No files matched your search
@@ -318,6 +318,14 @@ _resolve_path() {
|
||||
else
|
||||
_p="$_lead$_tail"
|
||||
fi
|
||||
# Git Bash spells one folder several ways through its mounts (/tmp is
|
||||
# %TEMP%, and HOME converts through them); cygpath -m gives the one drive
|
||||
# spelling Claude Code reports.
|
||||
if [ -n "$_p" ] && [ "$GSTACK_HOOK_IS_WINDOWS" = 1 ] && command -v cygpath >/dev/null 2>&1; then
|
||||
_p=$(cygpath -m "$_p" && printf x) || return 1
|
||||
_p="${_p%"$_FREEZE_NL"x}"
|
||||
case "$_p" in ''|*"$_FREEZE_NL"*|*"$_FREEZE_CR"*) return 1 ;; esac
|
||||
fi
|
||||
fi
|
||||
[ -n "$_p" ] || _p="/"
|
||||
gstack_hook_normalize_path "$_p"
|
||||
|
||||
@@ -1643,6 +1643,44 @@ describe('check-freeze.sh', () => {
|
||||
});
|
||||
});
|
||||
|
||||
// Git Bash reaches one folder through several mounts (/tmp is %TEMP%, and
|
||||
// HOME converts through them), so '~/x' and 'D:\\...\\x' can name one file
|
||||
// in two spellings; the hook compares cygpath's drive spelling.
|
||||
test.skipIf(process.platform === 'win32')('Windows bash: a folder reached through a Git Bash mount compares by its drive spelling', () => {
|
||||
withTree(({ base, home, src, outside }) => {
|
||||
const bin = path.join(base, 'bin');
|
||||
fs.mkdirSync(bin);
|
||||
const mnt = path.join(base, 'mnt');
|
||||
fs.writeFileSync(path.join(bin, 'cygpath'), [
|
||||
'#!/bin/bash',
|
||||
'case "$2" in',
|
||||
' "$FAKE_MNT"/in/*) printf "%s\\n" "$FAKE_IN${2#"$FAKE_MNT"/in}" ;;',
|
||||
' "$FAKE_MNT"/out/*) printf "%s\\n" "$FAKE_OUT${2#"$FAKE_MNT"/out}" ;;',
|
||||
' *) printf "%s\\n" "$2" ;;',
|
||||
'esac',
|
||||
'',
|
||||
].join('\n'), { mode: 0o755 });
|
||||
const decideWithCygpath = (filePath: string): string => {
|
||||
let decision = '';
|
||||
withFreezeDir(src + '/', (stateDir) => {
|
||||
const r = spawnSync('bash', ['-c', 'OSTYPE=msys; . "$0"', FREEZE_SCRIPT], {
|
||||
input: JSON.stringify(freezeInput(filePath)),
|
||||
env: { ...process.env, ...freezeEnv(stateDir, { HOME: home }), PATH: bin + path.delimiter + (process.env.PATH ?? ''), FAKE_MNT: mnt, FAKE_IN: home, FAKE_OUT: outside },
|
||||
cwd: base,
|
||||
timeout: 5000,
|
||||
});
|
||||
expect(r.status).toBe(0);
|
||||
const raw = r.stdout.toString().trim();
|
||||
decision = raw === '{}' ? 'allow' : (JSON.parse(raw).hookSpecificOutput?.permissionDecision ?? raw);
|
||||
});
|
||||
return decision;
|
||||
};
|
||||
expect(decideWithCygpath(mnt + '/in/proj/src/x.ts')).toBe('allow');
|
||||
expect(decideWithCygpath(mnt + '/out/proj/src/x.ts')).toBe('deny');
|
||||
expect(decideWithCygpath(src + '/x.ts')).toBe('allow');
|
||||
});
|
||||
});
|
||||
|
||||
// Linux and macOS read a leading '//' as '/', so neither a '//' path nor a
|
||||
// link to '//' may borrow a UNC boundary, and a link to '/' followed by
|
||||
// '/' stays a plain absolute path.
|
||||
|
||||
Reference in new issue
Block a user