mirror of
https://github.com/garrytan/gstack.git
synced 2026-09-13 00:19:03 +02:00
fix(relink): canonicalize link targets before the ownership check
Pre-landing review finding: the ownership gate compared readlink output textually against INSTALL_DIR and RENDER_DIR, so two shapes of gstack's OWN entries read as foreign and were left behind on a mode flip — a legacy relative link (`gstack/qa/SKILL.md`, resolved against $PWD instead of the link's directory) and an entry linked against the real path of a symlinked install dir (~/.claude/skills/gstack -> checkout). Both now resolve: relative targets anchor at the link's directory, the directory part is canonicalized with pwd -P (the basename stays verbatim so a dangling managed target is not misread), and both spellings of each root are accepted. Two regression tests. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5.1
parent
e5e01d1a7f
commit
b23b784bcf
@@ -764,6 +764,38 @@ describe('gstack-relink ownership gate (#2119)', () => {
|
||||
expect(out).not.toContain('skipped');
|
||||
});
|
||||
|
||||
test('a legacy RELATIVE symlink into the install (gstack/qa/SKILL.md) is ours and is cleaned', () => {
|
||||
setupMockInstall(['qa']);
|
||||
// Older setups wrote relative links; the skills dir sits beside the install
|
||||
// dir named `gstack`, so `gstack/qa/SKILL.md` resolves into INSTALL_DIR.
|
||||
const installAlias = path.join(skillsDir, 'gstack');
|
||||
fs.symlinkSync(installDir, installAlias);
|
||||
fs.mkdirSync(path.join(skillsDir, 'gstack-qa'));
|
||||
fs.symlinkSync('../gstack/qa/SKILL.md', path.join(skillsDir, 'gstack-qa', 'SKILL.md'));
|
||||
setPrefix('false');
|
||||
const out = relink();
|
||||
expect(fs.existsSync(path.join(skillsDir, 'gstack-qa'))).toBe(false);
|
||||
expect(out).not.toContain('skipped');
|
||||
});
|
||||
|
||||
test('an entry linked against the REAL path of a symlinked install dir is ours', () => {
|
||||
setupMockInstall(['qa']);
|
||||
const realInstall = fs.realpathSync(installDir);
|
||||
const linkInstall = path.join(tmpDir, 'install-link');
|
||||
fs.symlinkSync(realInstall, linkInstall);
|
||||
fs.mkdirSync(path.join(skillsDir, 'gstack-qa'));
|
||||
fs.symlinkSync(path.join(realInstall, 'qa', 'SKILL.md'), path.join(skillsDir, 'gstack-qa', 'SKILL.md'));
|
||||
// relink detects the install through the symlinked spelling.
|
||||
run(`${path.join(installDir, 'bin', 'gstack-config')} set skill_prefix false`, {
|
||||
GSTACK_INSTALL_DIR: linkInstall, GSTACK_SKILLS_DIR: skillsDir,
|
||||
});
|
||||
const out = run(`${path.join(installDir, 'bin', 'gstack-relink')} 2>&1`, {
|
||||
GSTACK_INSTALL_DIR: linkInstall, GSTACK_SKILLS_DIR: skillsDir,
|
||||
});
|
||||
expect(fs.existsSync(path.join(skillsDir, 'gstack-qa'))).toBe(false);
|
||||
expect(out).not.toContain('skipped');
|
||||
});
|
||||
|
||||
test('a real-file copy WITHOUT the marker is foreign and survives', () => {
|
||||
setupMockInstall(['qa']);
|
||||
setPrefix('false');
|
||||
|
||||
Reference in New Issue
Block a user