mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-10-03 02:21:44 +00:00
fix(storage): keep subdirectories of a clone out of shared stores (#3374)
Trigger: `analyze --skip-git <clone>/pkg` inside a clone with a registered sibling of the same origin joined, or founded, a clone store. getRemoteUrl answers from any subdirectory, so siblingCloneStore saw the enclosing clone's remote. That broke the shared-store invariant that only tree roots participate. Fix: resolveOptedInStore now returns undefined for a path with no `.git` entry. It uses hasGitDir, which accepts a directory or a linked-worktree file, the same as resolveSharedStore's readCommonDir gate and run-analyze's repoHasGit. `--share-with` from such a path throws a clear error. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
a882494e36
commit
3e9035e322
2 changed files with 38 additions and 2 deletions
|
|
@ -20,7 +20,12 @@ import { existsSync, constants as fsConstants } from 'fs';
|
|||
import fs from 'fs/promises';
|
||||
import path from 'path';
|
||||
import { acquireIndexLock, requireExclusiveIndexLock } from '../storage/index-lock.js';
|
||||
import { commitDistanceToHead, getRemoteUrl, isWorkingTreePristine } from '../storage/git.js';
|
||||
import {
|
||||
commitDistanceToHead,
|
||||
getRemoteUrl,
|
||||
hasGitDir,
|
||||
isWorkingTreePristine,
|
||||
} from '../storage/git.js';
|
||||
import {
|
||||
canonicalizePath,
|
||||
findRegistryEntryByRepoPath,
|
||||
|
|
@ -534,12 +539,21 @@ const siblingCloneStore = (
|
|||
* `--share-with`, the one its registry entry already points into, or a
|
||||
* sibling clone's store (#3352). `--share-with` requires the normalized remote
|
||||
* URL to match the member it names (R3); `analyze --no-share` records an
|
||||
* opt-out that stops automatic joining.
|
||||
* opt-out that stops automatic joining. Only tree roots participate, as in
|
||||
* `resolveSharedStore`: `getRemoteUrl` answers from any subdirectory, so
|
||||
* without this gate `analyze --skip-git <clone>/pkg` would join (#3374).
|
||||
*/
|
||||
export const resolveOptedInStore = async (
|
||||
repoPath: string,
|
||||
shareWith: string | undefined,
|
||||
): Promise<SharedStoreLayout | undefined> => {
|
||||
if (!hasGitDir(repoPath)) {
|
||||
if (!shareWith) return undefined;
|
||||
throw new Error(
|
||||
`--share-with: "${repoPath}" is not the root of a git checkout. ` +
|
||||
'Only a clone root can share an index store.',
|
||||
);
|
||||
}
|
||||
const entries = await readRegistry();
|
||||
if (shareWith) {
|
||||
let target;
|
||||
|
|
|
|||
|
|
@ -309,4 +309,26 @@ describe('shared store founder key for concurrent sibling clones (#3374)', () =>
|
|||
const joiner = await cloneAndRegister('aaa-joiner');
|
||||
expect((await resolveOptedInStore(joiner, undefined))?.key).toBe(existing);
|
||||
});
|
||||
|
||||
// #3374 S8 — `getRemoteUrl` answers from any subdirectory, so only the
|
||||
// tree-root gate keeps `analyze --skip-git <clone>/pkg` out of the store.
|
||||
it('a subdirectory of a clone with a registered sibling neither joins nor founds a store', async () => {
|
||||
await cloneAndRegister('member', cloneStoreKey(path.join(root, 'zz-founder')));
|
||||
const clone = await cloneAndRegister('other');
|
||||
const subdir = path.join(clone, 'pkg');
|
||||
await fs.mkdir(subdir);
|
||||
|
||||
expect(await resolveOptedInStore(subdir, undefined)).toBeUndefined();
|
||||
expect(await resolveOptedInStore(clone, undefined)).toBeDefined();
|
||||
});
|
||||
|
||||
it('--share-with refuses a subdirectory of a clone', async () => {
|
||||
const member = await cloneAndRegister('member', cloneStoreKey(path.join(root, 'zz-founder')));
|
||||
const subdir = path.join(await cloneAndRegister('other'), 'pkg');
|
||||
await fs.mkdir(subdir);
|
||||
|
||||
await expect(resolveOptedInStore(subdir, member)).rejects.toThrow(
|
||||
/--share-with: .* is not the root of a git checkout/,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue