mirror of https://github.com/garrytan/gstack.git
fix(slug): terminate the marker walk-up on dirname's fixed point — hung every bin on Windows
Under git-bash on Windows a mixed-form path walks C:/Users -> C: -> . -> . forever: dirname's fixed point there is never "/", so the walk-up loop spun and every bin that evals gstack-slug (learnings-log first among them) hung until spawn timeout. Caught by windows-free-tests CI on the wave PR. Break on the fixed point itself with a depth cap for exotic forms; regression tests drive the extracted function with hostile path shapes under a hard timeout. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
4fc3731a50
commit
daeec2c017
|
|
@ -68,7 +68,12 @@ _outermost_project_root() {
|
||||||
local dir="$1"
|
local dir="$1"
|
||||||
local outermost_strong=""
|
local outermost_strong=""
|
||||||
local outermost_weak=""
|
local outermost_weak=""
|
||||||
while [[ -n "$dir" && "$dir" != "/" ]]; do
|
local parent="" depth=0
|
||||||
|
# Terminate on dirname's FIXED POINT, not on a literal "/": under git-bash
|
||||||
|
# on Windows a mixed-form path walks C:/Users -> C: -> . -> . forever, which
|
||||||
|
# hung every bin that evals gstack-slug (caught by windows-free-tests CI).
|
||||||
|
# The depth cap is belt-and-braces for exotic path forms (UNC, //server).
|
||||||
|
while [[ -n "$dir" && "$dir" != "/" && $depth -lt 64 ]]; do
|
||||||
if [[ -e "$dir/.git" \
|
if [[ -e "$dir/.git" \
|
||||||
|| -f "$dir/.project.yaml" \
|
|| -f "$dir/.project.yaml" \
|
||||||
|| -f "$dir/package.json" \
|
|| -f "$dir/package.json" \
|
||||||
|
|
@ -84,7 +89,10 @@ _outermost_project_root() {
|
||||||
|| -f "$dir/LICENSE.md" ]]; then
|
|| -f "$dir/LICENSE.md" ]]; then
|
||||||
outermost_weak="$dir"
|
outermost_weak="$dir"
|
||||||
fi
|
fi
|
||||||
dir=$(dirname "$dir")
|
parent=$(dirname "$dir")
|
||||||
|
[[ "$parent" == "$dir" ]] && break # dirname fixed point (C:/, ., //srv)
|
||||||
|
dir="$parent"
|
||||||
|
depth=$((depth + 1))
|
||||||
done
|
done
|
||||||
# Strong markers win over weak; either wins over nothing.
|
# Strong markers win over weak; either wins over nothing.
|
||||||
if [[ -n "$outermost_strong" ]]; then
|
if [[ -n "$outermost_strong" ]]; then
|
||||||
|
|
|
||||||
|
|
@ -289,3 +289,23 @@ describe('gstack-slug — outermost project-root resolution', () => {
|
||||||
expect(slug).toBe('custom-override');
|
expect(slug).toBe('custom-override');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('_outermost_project_root termination (windows-free-tests regression)', () => {
|
||||||
|
// Under git-bash on Windows a mixed-form path walks C:/Users -> C: -> . -> .
|
||||||
|
// forever: dirname's fixed point there is never "/". The loop must break on
|
||||||
|
// the fixed point itself. Extract the function and drive it with hostile
|
||||||
|
// path forms under a hard timeout — a hang fails the spawn, not the suite.
|
||||||
|
const script = fs.readFileSync(path.join(ROOT, 'bin', 'gstack-slug'), 'utf-8');
|
||||||
|
const fnMatch = script.match(/_outermost_project_root\(\) \{[\s\S]*?\n\}/);
|
||||||
|
|
||||||
|
test.each(['C:/Users/nobody/project', '.', '//server/share/dir'])(
|
||||||
|
'terminates on hostile path form: %s',
|
||||||
|
(hostile) => {
|
||||||
|
expect(fnMatch).not.toBeNull();
|
||||||
|
const r = Bun.spawnSync(['bash', '-c', `${fnMatch![0]}\n_outermost_project_root "$1"; echo TERMINATED`, '_', hostile], {
|
||||||
|
timeout: 5000,
|
||||||
|
});
|
||||||
|
expect(r.stdout.toString()).toContain('TERMINATED');
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue