@bevel-software/platform-core-backend 0.28.1 → 0.30.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/agent-guide/access-control.md +15 -0
- package/agent-guide/skills.md +8 -0
- package/dist/__tests__/test-repositories.d.ts +3 -0
- package/dist/__tests__/test-repositories.d.ts.map +1 -0
- package/dist/__tests__/test-repositories.js +18 -0
- package/dist/__tests__/test-repositories.js.map +1 -0
- package/dist/core/catalog-revision.d.ts.map +1 -1
- package/dist/core/catalog-revision.js +4 -2
- package/dist/core/catalog-revision.js.map +1 -1
- package/dist/core/compose-embed.d.ts +35 -0
- package/dist/core/compose-embed.d.ts.map +1 -0
- package/dist/core/compose-embed.js +14 -0
- package/dist/core/compose-embed.js.map +1 -0
- package/dist/core/core-ports.d.ts +10 -0
- package/dist/core/core-ports.d.ts.map +1 -1
- package/dist/core/core-ports.js.map +1 -1
- package/dist/core/create-core-server.d.ts.map +1 -1
- package/dist/core/create-core-server.js +8 -2
- package/dist/core/create-core-server.js.map +1 -1
- package/dist/core/create-core-services.d.ts +4 -1
- package/dist/core/create-core-services.d.ts.map +1 -1
- package/dist/core/create-core-services.js +57 -18
- package/dist/core/create-core-services.js.map +1 -1
- package/dist/core/lifecycle.d.ts +19 -6
- package/dist/core/lifecycle.d.ts.map +1 -1
- package/dist/core/lifecycle.js +17 -6
- package/dist/core/lifecycle.js.map +1 -1
- package/dist/modules/access/access-control.interface.d.ts +20 -0
- package/dist/modules/access/access-control.interface.d.ts.map +1 -1
- package/dist/modules/access/access-control.service.d.ts +68 -15
- package/dist/modules/access/access-control.service.d.ts.map +1 -1
- package/dist/modules/access/access-control.service.js +282 -58
- package/dist/modules/access/access-control.service.js.map +1 -1
- package/dist/modules/access/access.routes.d.ts.map +1 -1
- package/dist/modules/access/access.routes.js +20 -6
- package/dist/modules/access/access.routes.js.map +1 -1
- package/dist/modules/agent-instructions/shared-file-rules.d.ts +1 -1
- package/dist/modules/agent-instructions/shared-file-rules.js +1 -1
- package/dist/modules/auth/index.d.ts +15 -0
- package/dist/modules/auth/index.d.ts.map +1 -0
- package/dist/modules/auth/index.js +20 -0
- package/dist/modules/auth/index.js.map +1 -0
- package/dist/modules/diff/diff.service.d.ts.map +1 -1
- package/dist/modules/diff/diff.service.js +7 -1
- package/dist/modules/diff/diff.service.js.map +1 -1
- package/dist/modules/embed/embed.interface.d.ts +22 -0
- package/dist/modules/embed/embed.interface.d.ts.map +1 -1
- package/dist/modules/embed/embed.service.d.ts +28 -14
- package/dist/modules/embed/embed.service.d.ts.map +1 -1
- package/dist/modules/embed/embed.service.js +38 -17
- package/dist/modules/embed/embed.service.js.map +1 -1
- package/dist/modules/embed/embed.tools.d.ts.map +1 -1
- package/dist/modules/embed/embed.tools.js +10 -2
- package/dist/modules/embed/embed.tools.js.map +1 -1
- package/dist/modules/kb-fs/git-dir.d.ts +21 -0
- package/dist/modules/kb-fs/git-dir.d.ts.map +1 -0
- package/dist/modules/kb-fs/git-dir.js +58 -0
- package/dist/modules/kb-fs/git-dir.js.map +1 -0
- package/dist/modules/kb-fs/index.d.ts +9 -0
- package/dist/modules/kb-fs/index.d.ts.map +1 -0
- package/dist/modules/kb-fs/index.js +3 -0
- package/dist/modules/kb-fs/index.js.map +1 -0
- package/dist/modules/kb-fs/ref-files.d.ts +63 -0
- package/dist/modules/kb-fs/ref-files.d.ts.map +1 -0
- package/dist/modules/kb-fs/ref-files.js +249 -0
- package/dist/modules/kb-fs/ref-files.js.map +1 -0
- package/dist/modules/plugins/join-request-jobs.service.d.ts.map +1 -1
- package/dist/modules/plugins/join-request-jobs.service.js +43 -8
- package/dist/modules/plugins/join-request-jobs.service.js.map +1 -1
- package/dist/modules/plugins/teams.routes.d.ts +4 -0
- package/dist/modules/plugins/teams.routes.d.ts.map +1 -1
- package/dist/modules/plugins/teams.routes.js +9 -1
- package/dist/modules/plugins/teams.routes.js.map +1 -1
- package/dist/modules/skills/skills.contract.d.ts.map +1 -1
- package/dist/modules/skills/skills.tools.d.ts +8 -1
- package/dist/modules/skills/skills.tools.d.ts.map +1 -1
- package/dist/modules/skills/skills.tools.js +115 -11
- package/dist/modules/skills/skills.tools.js.map +1 -1
- package/dist/modules/tool-auth/internal-token.service.d.ts +1 -1
- package/dist/modules/tool-auth/tool-auth.middleware.d.ts +1 -2
- package/dist/modules/tool-auth/tool-auth.middleware.d.ts.map +1 -1
- package/dist/modules/tool-auth/tool-auth.middleware.js +1 -2
- package/dist/modules/tool-auth/tool-auth.middleware.js.map +1 -1
- package/dist/modules/tool-helpers/default-branch-write.d.ts +69 -0
- package/dist/modules/tool-helpers/default-branch-write.d.ts.map +1 -0
- package/dist/modules/tool-helpers/default-branch-write.js +63 -0
- package/dist/modules/tool-helpers/default-branch-write.js.map +1 -0
- package/dist/modules/tool-helpers/index.d.ts +1 -0
- package/dist/modules/tool-helpers/index.d.ts.map +1 -1
- package/dist/modules/tool-helpers/index.js +1 -0
- package/dist/modules/tool-helpers/index.js.map +1 -1
- package/dist/modules/tool-helpers/tool-handler.d.ts +2 -2
- package/dist/modules/tool-helpers/tool.contract.d.ts +2 -2
- package/dist/modules/workflow/file-lock.service.d.ts +27 -5
- package/dist/modules/workflow/file-lock.service.d.ts.map +1 -1
- package/dist/modules/workflow/file-lock.service.js +70 -18
- package/dist/modules/workflow/file-lock.service.js.map +1 -1
- package/dist/modules/workflow/git/git.service.d.ts +42 -0
- package/dist/modules/workflow/git/git.service.d.ts.map +1 -1
- package/dist/modules/workflow/git/git.service.js +201 -24
- package/dist/modules/workflow/git/git.service.js.map +1 -1
- package/dist/modules/workflow/index.d.ts +8 -0
- package/dist/modules/workflow/index.d.ts.map +1 -0
- package/dist/modules/workflow/index.js +8 -0
- package/dist/modules/workflow/index.js.map +1 -0
- package/dist/modules/workflow/pending-commits.service.d.ts +2 -17
- package/dist/modules/workflow/pending-commits.service.d.ts.map +1 -1
- package/dist/modules/workflow/pending-commits.service.js +4 -24
- package/dist/modules/workflow/pending-commits.service.js.map +1 -1
- package/dist/modules/workflow/workflow-hooks.d.ts +1 -1
- package/dist/modules/workflow/workflow.routes.d.ts.map +1 -1
- package/dist/modules/workflow/workflow.routes.js +20 -1
- package/dist/modules/workflow/workflow.routes.js.map +1 -1
- package/dist/modules/workflow/workflow.service.d.ts.map +1 -1
- package/dist/modules/workflow/workflow.service.js +5 -3
- package/dist/modules/workflow/workflow.service.js.map +1 -1
- package/dist/modules/workspace/agent-access.gate.d.ts +2 -2
- package/dist/modules/workspace/agent-access.gate.js +2 -2
- package/dist/modules/workspace/bounded-matcher.d.ts +79 -0
- package/dist/modules/workspace/bounded-matcher.d.ts.map +1 -0
- package/dist/modules/workspace/bounded-matcher.js +261 -0
- package/dist/modules/workspace/bounded-matcher.js.map +1 -0
- package/dist/modules/workspace/fetch-instant.d.ts +9 -0
- package/dist/modules/workspace/fetch-instant.d.ts.map +1 -0
- package/dist/modules/workspace/fetch-instant.js +13 -0
- package/dist/modules/workspace/fetch-instant.js.map +1 -0
- package/dist/modules/workspace/index.d.ts +17 -0
- package/dist/modules/workspace/index.d.ts.map +1 -0
- package/dist/modules/workspace/index.js +16 -0
- package/dist/modules/workspace/index.js.map +1 -0
- package/dist/modules/workspace/kb-repository.contract.d.ts +124 -0
- package/dist/modules/workspace/kb-repository.contract.d.ts.map +1 -0
- package/dist/modules/workspace/kb-repository.contract.js +19 -0
- package/dist/modules/workspace/kb-repository.contract.js.map +1 -0
- package/dist/modules/workspace/kb-repository.d.ts +236 -0
- package/dist/modules/workspace/kb-repository.d.ts.map +1 -0
- package/dist/modules/workspace/kb-repository.js +811 -0
- package/dist/modules/workspace/kb-repository.js.map +1 -0
- package/dist/modules/workspace/pattern-matcher.contract.d.ts +50 -0
- package/dist/modules/workspace/pattern-matcher.contract.d.ts.map +1 -0
- package/dist/modules/workspace/pattern-matcher.contract.js +27 -0
- package/dist/modules/workspace/pattern-matcher.contract.js.map +1 -0
- package/dist/modules/workspace/routine-write-policy.d.ts +0 -7
- package/dist/modules/workspace/routine-write-policy.d.ts.map +1 -1
- package/dist/modules/workspace/routine-write-policy.js +0 -5
- package/dist/modules/workspace/routine-write-policy.js.map +1 -1
- package/dist/modules/workspace/set-aside-clone.d.ts +40 -0
- package/dist/modules/workspace/set-aside-clone.d.ts.map +1 -1
- package/dist/modules/workspace/set-aside-clone.js +139 -0
- package/dist/modules/workspace/set-aside-clone.js.map +1 -1
- package/dist/modules/workspace/startup/kb-startup-runner.d.ts +99 -0
- package/dist/modules/workspace/startup/kb-startup-runner.d.ts.map +1 -1
- package/dist/modules/workspace/startup/kb-startup-runner.js +407 -29
- package/dist/modules/workspace/startup/kb-startup-runner.js.map +1 -1
- package/dist/modules/workspace/workspace.service.d.ts +68 -32
- package/dist/modules/workspace/workspace.service.d.ts.map +1 -1
- package/dist/modules/workspace/workspace.service.js +240 -161
- package/dist/modules/workspace/workspace.service.js.map +1 -1
- package/dist/modules/workspace/workspace.tools.d.ts +13 -4
- package/dist/modules/workspace/workspace.tools.d.ts.map +1 -1
- package/dist/modules/workspace/workspace.tools.js +268 -356
- package/dist/modules/workspace/workspace.tools.js.map +1 -1
- package/dist/shared/workspace-id.d.ts +17 -0
- package/dist/shared/workspace-id.d.ts.map +1 -1
- package/dist/shared/workspace-id.js +24 -0
- package/dist/shared/workspace-id.js.map +1 -1
- package/dist/test-setup.js +4 -0
- package/dist/test-setup.js.map +1 -1
- package/mcp-app/page.html +142 -8
- package/package.json +3 -3
- package/src/__tests__/test-repositories.ts +22 -0
- package/src/__tests__/workspace-service.ts +69 -0
- package/src/core/__tests__/compose-embed.test.ts +65 -0
- package/src/core/__tests__/lifecycle.test.ts +45 -26
- package/src/core/catalog-revision.ts +4 -2
- package/src/core/compose-embed.ts +50 -0
- package/src/core/core-ports.ts +10 -0
- package/src/core/create-core-server.ts +8 -2
- package/src/core/create-core-services.ts +63 -24
- package/src/core/lifecycle.ts +34 -12
- package/src/modules/access/__tests__/access-control.atref-cache.test.ts +443 -25
- package/src/modules/access/__tests__/access-control.group-read.test.ts +73 -0
- package/src/modules/access/__tests__/access.routes.group-grant.test.ts +66 -0
- package/src/modules/access/__tests__/access.routes.unknown-email.test.ts +2 -0
- package/src/modules/access/access-control.interface.ts +22 -0
- package/src/modules/access/access-control.service.ts +302 -58
- package/src/modules/access/access.routes.ts +22 -6
- package/src/modules/agent-instructions/shared-file-rules.ts +1 -1
- package/src/modules/auth/index.ts +29 -0
- package/src/modules/diff/__tests__/diff.service.seed-atomicity.test.ts +2 -1
- package/src/modules/diff/__tests__/diff.service.test.ts +2 -1
- package/src/modules/diff/__tests__/diff.service.worktree.test.ts +76 -0
- package/src/modules/diff/diff.service.ts +6 -4
- package/src/modules/embed/__tests__/embed.service.test.ts +76 -3
- package/src/modules/embed/__tests__/embed.tools.test.ts +29 -0
- package/src/modules/embed/embed.interface.ts +24 -0
- package/src/modules/embed/embed.service.ts +44 -19
- package/src/modules/embed/embed.tools.ts +10 -2
- package/src/modules/kb-fs/__tests__/git-dir.test.ts +77 -0
- package/src/modules/kb-fs/__tests__/ref-files.test.ts +338 -0
- package/src/modules/kb-fs/git-dir.ts +53 -0
- package/src/modules/kb-fs/index.ts +8 -0
- package/src/modules/kb-fs/ref-files.ts +279 -0
- package/src/modules/plugins/__tests__/join-request-jobs.service.test.ts +73 -6
- package/src/modules/plugins/__tests__/teams.routes.test.ts +55 -0
- package/src/modules/plugins/join-request-jobs.service.ts +45 -8
- package/src/modules/plugins/teams.routes.ts +14 -1
- package/src/modules/settings/__tests__/setup.routes.layout-phase.test.ts +2 -2
- package/src/modules/settings/__tests__/setup.routes.managed-phase.test.ts +2 -2
- package/src/modules/skills/__tests__/branch-skills.tools.test.ts +1 -1
- package/src/modules/skills/skills.contract.ts +4 -0
- package/src/modules/skills/skills.tools.ts +120 -10
- package/src/modules/tool-auth/internal-token.service.ts +1 -1
- package/src/modules/tool-auth/tool-auth.middleware.ts +1 -2
- package/src/modules/tool-helpers/__tests__/branch-declaration.test.ts +2 -2
- package/src/modules/tool-helpers/__tests__/can-write.tools.test.ts +581 -0
- package/src/modules/tool-helpers/default-branch-write.ts +90 -0
- package/src/modules/tool-helpers/index.ts +5 -0
- package/src/modules/tool-helpers/tool-handler.ts +2 -2
- package/src/modules/tool-helpers/tool.contract.ts +2 -2
- package/src/modules/tool-registry/__tests__/tool-description-length.test.ts +42 -3
- package/src/modules/workflow/__tests__/fake-file-lock-db.ts +13 -2
- package/src/modules/workflow/__tests__/workflow.routes.lock-path.test.ts +168 -1
- package/src/modules/workflow/__tests__/workflow.service.delete-branch.test.ts +102 -6
- package/src/modules/workflow/file-lock.service.ts +79 -18
- package/src/modules/workflow/git/__tests__/git.service.changedPathsMemo.test.ts +175 -0
- package/src/modules/workflow/git/__tests__/git.service.readFileAtRef.test.ts +42 -0
- package/src/modules/workflow/git/__tests__/git.service.worktrees.test.ts +284 -0
- package/src/modules/workflow/git/git.service.ts +207 -22
- package/src/modules/workflow/index.ts +7 -0
- package/src/modules/workflow/pending-commits.service.ts +4 -23
- package/src/modules/workflow/workflow-hooks.ts +1 -1
- package/src/modules/workflow/workflow.routes.ts +24 -1
- package/src/modules/workflow/workflow.service.ts +3 -2
- package/src/modules/workspace/__tests__/agent-downloads.test.ts +2 -2
- package/src/modules/workspace/__tests__/bounded-matcher.test.ts +213 -0
- package/src/modules/workspace/__tests__/git-internals.security.test.ts +22 -7
- package/src/modules/workspace/__tests__/kb-repository.test.ts +481 -0
- package/src/modules/workspace/__tests__/rest-tool-arguments.test.ts +14 -0
- package/src/modules/workspace/__tests__/routine-write-policy.test.ts +0 -11
- package/src/modules/workspace/__tests__/workspace-paths-inside-the-repo.test.ts +2 -2
- package/src/modules/workspace/__tests__/workspace.routes.folder-zip-access.test.ts +2 -2
- package/src/modules/workspace/__tests__/workspace.routes.folders.test.ts +4 -3
- package/src/modules/workspace/__tests__/workspace.service.any-workspace-credentials.test.ts +2 -2
- package/src/modules/workspace/__tests__/workspace.service.ensure-remotes-fetched.test.ts +2 -2
- package/src/modules/workspace/__tests__/workspace.service.forget-clone-races.test.ts +12 -5
- package/src/modules/workspace/__tests__/workspace.service.list-cloned.test.ts +6 -6
- package/src/modules/workspace/__tests__/workspace.service.move-never-overwrites.test.ts +2 -1
- package/src/modules/workspace/__tests__/workspace.service.read-filter.test.ts +2 -1
- package/src/modules/workspace/__tests__/workspace.service.replaced-repository.test.ts +2 -1
- package/src/modules/workspace/__tests__/workspace.service.test.ts +24 -23
- package/src/modules/workspace/__tests__/workspace.service.unknown-branch.test.ts +2 -1
- package/src/modules/workspace/__tests__/workspace.service.worktrees.test.ts +620 -0
- package/src/modules/workspace/__tests__/workspace.tools.agents-file.test.ts +15 -1
- package/src/modules/workspace/__tests__/workspace.tools.branch-errors.test.ts +71 -5
- package/src/modules/workspace/__tests__/workspace.tools.test.ts +174 -309
- package/src/modules/workspace/agent-access.gate.ts +2 -2
- package/src/modules/workspace/bounded-matcher.ts +328 -0
- package/src/modules/workspace/fetch-instant.ts +13 -0
- package/src/modules/workspace/index.ts +26 -0
- package/src/modules/workspace/kb-repository.contract.ts +124 -0
- package/src/modules/workspace/kb-repository.ts +844 -0
- package/src/modules/workspace/pattern-matcher.contract.ts +69 -0
- package/src/modules/workspace/routine-write-policy.ts +0 -15
- package/src/modules/workspace/set-aside-clone.ts +140 -0
- package/src/modules/workspace/startup/__tests__/kb-startup-runner.degraded.test.ts +3 -2
- package/src/modules/workspace/startup/__tests__/kb-startup-runner.test.ts +12 -10
- package/src/modules/workspace/startup/__tests__/kb-startup-runner.worktrees.test.ts +613 -0
- package/src/modules/workspace/startup/kb-startup-runner.ts +422 -29
- package/src/modules/workspace/startup/steps/__tests__/steps.test.ts +4 -3
- package/src/modules/workspace/workspace.service.ts +247 -169
- package/src/modules/workspace/workspace.tools.ts +299 -385
- package/src/shared/workspace-id.ts +24 -0
- package/src/test-setup.ts +5 -0
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
|
2
|
+
import { execFile } from 'node:child_process';
|
|
3
|
+
import { promisify } from 'node:util';
|
|
4
|
+
import fs from 'node:fs/promises';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import os from 'node:os';
|
|
7
|
+
|
|
8
|
+
import { readRefSha, readRevSha } from '../index.js';
|
|
9
|
+
|
|
10
|
+
const execFileAsync = promisify(execFile);
|
|
11
|
+
|
|
12
|
+
async function git(cwd: string, ...args: string[]): Promise<string> {
|
|
13
|
+
const { stdout } = await execFileAsync('git', ['-C', cwd, ...args], {
|
|
14
|
+
encoding: 'utf-8',
|
|
15
|
+
env: {
|
|
16
|
+
...process.env,
|
|
17
|
+
GIT_AUTHOR_NAME: 'Test',
|
|
18
|
+
GIT_AUTHOR_EMAIL: 't@x.com',
|
|
19
|
+
GIT_COMMITTER_NAME: 'Test',
|
|
20
|
+
GIT_COMMITTER_EMAIL: 't@x.com',
|
|
21
|
+
},
|
|
22
|
+
});
|
|
23
|
+
return stdout.trim();
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* The ref files are read exactly as git reads them, so every answer is
|
|
28
|
+
* checked against `git rev-parse` on the same repository: a loose ref, a
|
|
29
|
+
* packed one, a loose one shadowing a packed one, a symbolic ref, a
|
|
30
|
+
* worktree's own HEAD beside the refs it shares, and the three answers the
|
|
31
|
+
* reader gives when it cannot or must not decide.
|
|
32
|
+
*/
|
|
33
|
+
describe('readRefSha', () => {
|
|
34
|
+
let root: string;
|
|
35
|
+
let origin: string;
|
|
36
|
+
let clone: string;
|
|
37
|
+
|
|
38
|
+
beforeEach(async () => {
|
|
39
|
+
root = await fs.mkdtemp(path.join(os.tmpdir(), 'bevel-ref-files-'));
|
|
40
|
+
origin = path.join(root, 'origin.git');
|
|
41
|
+
const seed = path.join(root, 'seed');
|
|
42
|
+
await execFileAsync('git', ['init', '--bare', '-b', 'main', origin]);
|
|
43
|
+
await fs.mkdir(seed);
|
|
44
|
+
await git(seed, 'init', '-q', '-b', 'main');
|
|
45
|
+
await fs.writeFile(path.join(seed, 'a.md'), 'a\n');
|
|
46
|
+
await git(seed, 'add', '-A');
|
|
47
|
+
await git(seed, 'commit', '-qm', 'one');
|
|
48
|
+
await git(seed, 'checkout', '-qb', 'feature');
|
|
49
|
+
await fs.writeFile(path.join(seed, 'b.md'), 'b\n');
|
|
50
|
+
await git(seed, 'add', '-A');
|
|
51
|
+
await git(seed, 'commit', '-qm', 'two');
|
|
52
|
+
await git(seed, 'tag', '-a', 'v1', '-m', 'v1');
|
|
53
|
+
await git(seed, 'tag', 'light');
|
|
54
|
+
await git(seed, 'checkout', '-q', 'main');
|
|
55
|
+
await git(seed, 'remote', 'add', 'origin', origin);
|
|
56
|
+
await git(seed, 'push', '-q', 'origin', 'main', 'feature', 'v1', 'light');
|
|
57
|
+
clone = path.join(root, 'clone');
|
|
58
|
+
await execFileAsync('git', ['clone', '-q', origin, clone]);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
afterEach(async () => {
|
|
62
|
+
await fs.rm(root, { recursive: true, force: true });
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
const expectSha = async (repo: string, ref: string) =>
|
|
66
|
+
expect(await readRefSha(repo, ref)).toEqual({ sha: await git(repo, 'rev-parse', '--verify', `${ref}^{commit}`) });
|
|
67
|
+
|
|
68
|
+
it('reads a loose ref, a remote-tracking ref and HEAD as git resolves them', async () => {
|
|
69
|
+
await expectSha(clone, 'refs/heads/main');
|
|
70
|
+
await expectSha(clone, 'refs/remotes/origin/feature');
|
|
71
|
+
await expectSha(clone, 'HEAD');
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it('follows a symbolic ref', async () => {
|
|
75
|
+
// A clone writes `refs/remotes/origin/HEAD` as `ref: refs/remotes/origin/main`.
|
|
76
|
+
await expectSha(clone, 'refs/remotes/origin/HEAD');
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('reads a packed ref, and lets a newer loose ref shadow the packed one', async () => {
|
|
80
|
+
await git(clone, 'pack-refs', '--all');
|
|
81
|
+
await expect(fs.access(path.join(clone, '.git/refs/heads/main'))).rejects.toThrow();
|
|
82
|
+
await expectSha(clone, 'refs/heads/main');
|
|
83
|
+
await expectSha(clone, 'refs/remotes/origin/feature');
|
|
84
|
+
// A commit on main writes the loose file again; packed-refs still holds
|
|
85
|
+
// the old tip, and git reads the loose one first.
|
|
86
|
+
await fs.writeFile(path.join(clone, 'c.md'), 'c\n');
|
|
87
|
+
await git(clone, 'add', '-A');
|
|
88
|
+
await git(clone, 'commit', '-qm', 'three');
|
|
89
|
+
await expectSha(clone, 'refs/heads/main');
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it("never decides a tag, loose or packed: what it peels to is git's to say", async () => {
|
|
93
|
+
// A clone packs the refs it fetched (with a peel line for the annotated
|
|
94
|
+
// tag); a tag made here is loose and its file holds the tag object's id.
|
|
95
|
+
// Neither is read: a tag may peel to a tree or a blob, which `^{commit}`
|
|
96
|
+
// refuses, and only git applies that rule.
|
|
97
|
+
expect(await readRefSha(clone, 'refs/tags/v1')).toBe('unreadable');
|
|
98
|
+
expect(await readRefSha(clone, 'refs/tags/light')).toBe('unreadable');
|
|
99
|
+
await git(clone, 'tag', '-a', 'v2', '-m', 'v2');
|
|
100
|
+
expect(await readRefSha(clone, 'refs/tags/v2')).toBe('unreadable');
|
|
101
|
+
await git(clone, 'pack-refs', '--all');
|
|
102
|
+
expect(await readRefSha(clone, 'refs/tags/v2')).toBe('unreadable');
|
|
103
|
+
expect(await readRevSha(clone, 'v1')).toBe('unreadable');
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it('declines a repository whose refs live in a reftable', async () => {
|
|
107
|
+
// Git 2.45+ with `extensions.refStorage = reftable` keeps refs under
|
|
108
|
+
// `.git/reftable/`, not in files this reader understands: every name
|
|
109
|
+
// would read as missing. Both marks of that layout decline, so the
|
|
110
|
+
// caller asks git. Laid out by hand, so the check does not depend on
|
|
111
|
+
// the git on this machine knowing the format.
|
|
112
|
+
await fs.mkdir(path.join(clone, '.git/reftable'));
|
|
113
|
+
expect(await readRefSha(clone, 'refs/heads/main')).toBe('unreadable');
|
|
114
|
+
expect(await readRevSha(clone, 'main')).toBe('unreadable');
|
|
115
|
+
await fs.rm(path.join(clone, '.git/reftable'), { recursive: true });
|
|
116
|
+
await expectSha(clone, 'refs/heads/main');
|
|
117
|
+
await fs.appendFile(path.join(clone, '.git/config'), '[extensions]\n\trefStorage = reftable\n');
|
|
118
|
+
expect(await readRefSha(clone, 'refs/heads/main')).toBe('unreadable');
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it('answers for a worktree: its own HEAD, and the refs it shares with the repository', async () => {
|
|
122
|
+
const wt = path.join(root, 'wt');
|
|
123
|
+
await git(clone, 'worktree', 'add', '-q', wt, 'feature');
|
|
124
|
+
const dotGit = await fs.stat(path.join(wt, '.git'));
|
|
125
|
+
expect(dotGit.isFile()).toBe(true);
|
|
126
|
+
await expectSha(wt, 'HEAD');
|
|
127
|
+
await expectSha(wt, 'refs/heads/feature');
|
|
128
|
+
await expectSha(wt, 'refs/heads/main');
|
|
129
|
+
await expectSha(wt, 'refs/remotes/origin/main');
|
|
130
|
+
expect(await readRefSha(wt, 'HEAD')).not.toEqual(await readRefSha(clone, 'HEAD'));
|
|
131
|
+
// A per-worktree ref lives in the worktree's own git directory: git
|
|
132
|
+
// resolves it there and nowhere else. It is not a branch, so the reader
|
|
133
|
+
// leaves it to git — but as something that exists, never as missing.
|
|
134
|
+
const sha = await git(wt, 'rev-parse', 'HEAD');
|
|
135
|
+
await git(wt, 'update-ref', 'refs/worktree/probe', sha);
|
|
136
|
+
expect(await git(wt, 'rev-parse', '--verify', 'refs/worktree/probe^{commit}')).toBe(sha);
|
|
137
|
+
expect(await readRefSha(wt, 'refs/worktree/probe')).toBe('unreadable');
|
|
138
|
+
expect(await readRevSha(wt, 'worktree/probe')).toBe('unreadable');
|
|
139
|
+
await expect(git(clone, 'rev-parse', '--verify', '--quiet', 'refs/worktree/probe^{commit}')).rejects.toThrow();
|
|
140
|
+
expect(await readRefSha(clone, 'refs/worktree/probe')).toBe('missing');
|
|
141
|
+
// The spellings that reach another worktree's refs are git's, never missing.
|
|
142
|
+
expect(await git(wt, 'rev-parse', '--verify', 'main-worktree/HEAD^{commit}')).toBe(await git(clone, 'rev-parse', 'HEAD'));
|
|
143
|
+
expect(await readRevSha(wt, 'main-worktree/HEAD')).toBe('unreadable');
|
|
144
|
+
expect(await git(clone, 'rev-parse', '--verify', 'worktrees/wt/HEAD^{commit}')).toBe(sha);
|
|
145
|
+
expect(await readRevSha(clone, 'worktrees/wt/HEAD')).toBe('unreadable');
|
|
146
|
+
expect(await readRevSha(clone, 'worktrees/wt/refs/worktree/probe')).toBe('unreadable');
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it('says missing for a ref no file holds, before and after packing', async () => {
|
|
150
|
+
expect(await readRefSha(clone, 'refs/heads/nope')).toBe('missing');
|
|
151
|
+
expect(await readRefSha(clone, 'refs/remotes/origin/nope')).toBe('missing');
|
|
152
|
+
await git(clone, 'pack-refs', '--all');
|
|
153
|
+
expect(await readRefSha(clone, 'refs/heads/nope')).toBe('missing');
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
it('reads packed-refs at most once per resolution, however many candidates a name tries', async () => {
|
|
157
|
+
await git(clone, 'pack-refs', '--all');
|
|
158
|
+
const reads = vi.spyOn(fs, 'readFile');
|
|
159
|
+
try {
|
|
160
|
+
// `origin/main` misses two prefixes and lands on the third; `nope`
|
|
161
|
+
// misses all six: one parse of the table each.
|
|
162
|
+
expect(await readRevSha(clone, 'origin/main')).toEqual({
|
|
163
|
+
sha: await git(clone, 'rev-parse', '--verify', 'origin/main^{commit}'),
|
|
164
|
+
});
|
|
165
|
+
const packedReads = () =>
|
|
166
|
+
reads.mock.calls.filter((call) => String(call[0]).endsWith('packed-refs')).length;
|
|
167
|
+
expect(packedReads()).toBe(1);
|
|
168
|
+
reads.mockClear();
|
|
169
|
+
expect(await readRevSha(clone, 'nope')).toBe('missing');
|
|
170
|
+
expect(packedReads()).toBe(1);
|
|
171
|
+
reads.mockClear();
|
|
172
|
+
expect(await readRefSha(clone, 'refs/heads/nope')).toBe('missing');
|
|
173
|
+
expect(packedReads()).toBe(1);
|
|
174
|
+
} finally {
|
|
175
|
+
reads.mockRestore();
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
it('tries the prefixes git tries for a full name too, before calling it missing', async () => {
|
|
180
|
+
// `rev-parse refs/heads/alias` finds a branch literally named
|
|
181
|
+
// `refs/heads/alias`, at refs/heads/refs/heads/alias; so does the reader.
|
|
182
|
+
const sha = await git(clone, 'rev-parse', 'origin/feature');
|
|
183
|
+
await git(clone, 'branch', 'refs/heads/alias', sha);
|
|
184
|
+
expect(await git(clone, 'rev-parse', '--verify', 'refs/heads/alias^{commit}')).toBe(sha);
|
|
185
|
+
expect(await readRefSha(clone, 'refs/heads/alias')).toBe('unreadable');
|
|
186
|
+
expect(await readRevSha(clone, 'refs/heads/alias')).toEqual({ sha });
|
|
187
|
+
expect(await readRevSha(clone, 'refs/heads/nope')).toBe('missing');
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
it('says unreadable, never missing, on a read error that is not absence', async () => {
|
|
191
|
+
// A directory where the ref file should be fails the read with EISDIR —
|
|
192
|
+
// an error, not an absence — and so does one where packed-refs or HEAD
|
|
193
|
+
// should be. None of them may read as "no such ref": git decides.
|
|
194
|
+
const sha = await git(clone, 'rev-parse', 'refs/heads/main');
|
|
195
|
+
await fs.rm(path.join(clone, '.git/refs/heads/main'));
|
|
196
|
+
await fs.mkdir(path.join(clone, '.git/refs/heads/main'));
|
|
197
|
+
expect(await readRefSha(clone, 'refs/heads/main')).toBe('unreadable');
|
|
198
|
+
expect(await readRevSha(clone, 'main')).toBe('unreadable');
|
|
199
|
+
await fs.rmdir(path.join(clone, '.git/refs/heads/main'));
|
|
200
|
+
await fs.writeFile(path.join(clone, '.git/refs/heads/main'), `${sha}\n`);
|
|
201
|
+
await git(clone, 'pack-refs', '--all');
|
|
202
|
+
await fs.rm(path.join(clone, '.git/packed-refs'));
|
|
203
|
+
await fs.mkdir(path.join(clone, '.git/packed-refs'));
|
|
204
|
+
expect(await readRefSha(clone, 'refs/heads/main')).toBe('unreadable');
|
|
205
|
+
expect(await readRefSha(clone, 'refs/heads/nope')).toBe('unreadable');
|
|
206
|
+
await fs.rmdir(path.join(clone, '.git/packed-refs'));
|
|
207
|
+
// A packed-refs file with a record this reader does not understand is
|
|
208
|
+
// not read around: the skipped record could be the one asked for.
|
|
209
|
+
await fs.writeFile(path.join(clone, '.git/packed-refs'), `# pack-refs with: peeled fully-peeled sorted\nnot-a-sha refs/heads/main\n`);
|
|
210
|
+
expect(await readRefSha(clone, 'refs/heads/main')).toBe('unreadable');
|
|
211
|
+
expect(await readRefSha(clone, 'refs/heads/nope')).toBe('unreadable');
|
|
212
|
+
await fs.writeFile(path.join(clone, '.git/packed-refs'), `${sha} refs/heads/main\n^garbage\n`);
|
|
213
|
+
expect(await readRefSha(clone, 'refs/heads/nope')).toBe('unreadable');
|
|
214
|
+
await fs.rm(path.join(clone, '.git/packed-refs'));
|
|
215
|
+
await fs.rm(path.join(clone, '.git/HEAD'));
|
|
216
|
+
await fs.mkdir(path.join(clone, '.git/HEAD'));
|
|
217
|
+
expect(await readRefSha(clone, 'HEAD')).toBe('unreadable');
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
it('says unreadable where it cannot decide: not a repository, a corrupt file, a name it will not turn into a path', async () => {
|
|
221
|
+
expect(await readRefSha(path.join(root, 'nowhere'), 'refs/heads/main')).toBe('unreadable');
|
|
222
|
+
await fs.writeFile(path.join(clone, '.git/refs/heads/main'), 'not a sha\n');
|
|
223
|
+
expect(await readRefSha(clone, 'refs/heads/main')).toBe('unreadable');
|
|
224
|
+
expect(await readRefSha(clone, 'refs/../config')).toBe('unreadable');
|
|
225
|
+
expect(await readRefSha(clone, 'main')).toBe('unreadable');
|
|
226
|
+
expect(await readRefSha(clone, 'refs/heads/a\\b')).toBe('unreadable');
|
|
227
|
+
});
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
describe('readRevSha', () => {
|
|
231
|
+
let root: string;
|
|
232
|
+
let clone: string;
|
|
233
|
+
|
|
234
|
+
beforeEach(async () => {
|
|
235
|
+
root = await fs.mkdtemp(path.join(os.tmpdir(), 'bevel-ref-rev-'));
|
|
236
|
+
const origin = path.join(root, 'origin.git');
|
|
237
|
+
const seed = path.join(root, 'seed');
|
|
238
|
+
await execFileAsync('git', ['init', '--bare', '-b', 'main', origin]);
|
|
239
|
+
await fs.mkdir(seed);
|
|
240
|
+
await git(seed, 'init', '-q', '-b', 'main');
|
|
241
|
+
await fs.writeFile(path.join(seed, 'a.md'), 'a\n');
|
|
242
|
+
await git(seed, 'add', '-A');
|
|
243
|
+
await git(seed, 'commit', '-qm', 'one');
|
|
244
|
+
await git(seed, 'checkout', '-qb', 'feature');
|
|
245
|
+
await fs.writeFile(path.join(seed, 'b.md'), 'b\n');
|
|
246
|
+
await git(seed, 'add', '-A');
|
|
247
|
+
await git(seed, 'commit', '-qm', 'two');
|
|
248
|
+
await git(seed, 'checkout', '-q', 'main');
|
|
249
|
+
await git(seed, 'remote', 'add', 'origin', origin);
|
|
250
|
+
await git(seed, 'push', '-q', 'origin', 'main', 'feature');
|
|
251
|
+
clone = path.join(root, 'clone');
|
|
252
|
+
await execFileAsync('git', ['clone', '-q', origin, clone]);
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
afterEach(async () => {
|
|
256
|
+
await fs.rm(root, { recursive: true, force: true });
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
it("takes the candidates in git's order when real refs compete for a name", async () => {
|
|
260
|
+
const want = async (rev: string) => ({ sha: await git(clone, 'rev-parse', '--verify', `${rev}^{commit}`) });
|
|
261
|
+
const first = await git(clone, 'rev-parse', 'origin/main');
|
|
262
|
+
const second = await git(clone, 'rev-parse', 'origin/feature');
|
|
263
|
+
// A local branch literally named `origin/main`, beside the remote-tracking
|
|
264
|
+
// ref: git takes refs/heads before refs/remotes, and so does the reader.
|
|
265
|
+
await git(clone, 'branch', 'origin/main', second);
|
|
266
|
+
expect(await readRevSha(clone, 'origin/main')).toEqual(await want('origin/main'));
|
|
267
|
+
expect(await readRevSha(clone, 'origin/main')).toEqual({ sha: second });
|
|
268
|
+
// A ref directly under refs/ comes before every namespace — and git does
|
|
269
|
+
// not guarantee what it names, so the reader steps aside for it.
|
|
270
|
+
await git(clone, 'update-ref', 'refs/main', second);
|
|
271
|
+
expect(await git(clone, 'rev-parse', 'main')).toBe(second);
|
|
272
|
+
expect(await readRevSha(clone, 'main')).toBe('unreadable');
|
|
273
|
+
await git(clone, 'update-ref', '-d', 'refs/main');
|
|
274
|
+
expect(await readRevSha(clone, 'main')).toEqual(await want('main'));
|
|
275
|
+
expect(first).not.toBe(second);
|
|
276
|
+
// A tag named like a branch comes before the branch — and a tag is git's
|
|
277
|
+
// to peel, so the reader steps aside rather than answer the branch.
|
|
278
|
+
await git(clone, 'tag', 'feature', first);
|
|
279
|
+
await git(clone, 'branch', 'feature', second);
|
|
280
|
+
expect(await readRevSha(clone, 'feature')).toBe('unreadable');
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
it('resolves the names a gate holds the way rev-parse does', async () => {
|
|
284
|
+
const want = async (rev: string) => ({ sha: await git(clone, 'rev-parse', '--verify', `${rev}^{commit}`) });
|
|
285
|
+
expect(await readRevSha(clone, 'main')).toEqual(await want('main'));
|
|
286
|
+
expect(await readRevSha(clone, 'origin/main')).toEqual(await want('origin/main'));
|
|
287
|
+
expect(await readRevSha(clone, 'origin/feature')).toEqual(await want('origin/feature'));
|
|
288
|
+
expect(await readRevSha(clone, 'HEAD')).toEqual(await want('HEAD'));
|
|
289
|
+
expect(await readRevSha(clone, 'refs/remotes/origin/feature')).toEqual(await want('refs/remotes/origin/feature'));
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
it('is missing only when every candidate is, which is what rev-parse answers nothing for', async () => {
|
|
293
|
+
// `feature` exists on origin but not as a local branch in this clone:
|
|
294
|
+
// git's own answer for the bare name is "no such rev".
|
|
295
|
+
await expect(git(clone, 'rev-parse', '--verify', '--quiet', 'feature^{commit}')).rejects.toThrow();
|
|
296
|
+
expect(await readRevSha(clone, 'feature')).toBe('missing');
|
|
297
|
+
expect(await readRevSha(clone, 'nope')).toBe('missing');
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
it('starts where git starts, at $GIT_DIR/<name>, and leaves whatever is there to git', async () => {
|
|
301
|
+
const sha = await git(clone, 'rev-parse', 'origin/feature');
|
|
302
|
+
// A pseudoref may name a commit (MERGE_HEAD) or a tree (AUTO_MERGE), and
|
|
303
|
+
// only git's ^{commit} tells them apart: anything found there is git's.
|
|
304
|
+
await fs.writeFile(path.join(clone, '.git/MERGE_HEAD'), `${sha}\n`);
|
|
305
|
+
expect(await readRevSha(clone, 'MERGE_HEAD')).toBe('unreadable');
|
|
306
|
+
await fs.writeFile(path.join(clone, '.git/AUTO_MERGE'), `${await git(clone, 'rev-parse', 'HEAD^{tree}')}\n`);
|
|
307
|
+
expect(await readRevSha(clone, 'AUTO_MERGE')).toBe('unreadable');
|
|
308
|
+
// A branch named like a pseudoref file that exists is git's too...
|
|
309
|
+
await git(clone, 'branch', 'ORIG_HEAD', sha);
|
|
310
|
+
await fs.writeFile(path.join(clone, '.git/ORIG_HEAD'), `${sha}\n`);
|
|
311
|
+
expect(await readRevSha(clone, 'ORIG_HEAD')).toBe('unreadable');
|
|
312
|
+
// ...and a name with no such file goes on to the refs/ candidates.
|
|
313
|
+
await fs.rm(path.join(clone, '.git/ORIG_HEAD'));
|
|
314
|
+
expect(await readRevSha(clone, 'ORIG_HEAD')).toEqual({ sha });
|
|
315
|
+
expect(await readRevSha(clone, 'CHERRY_PICK_HEAD')).toBe('missing');
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
it('leaves a dangling symbolic ref to git rather than read a packed entry it shadows', async () => {
|
|
319
|
+
await git(clone, 'pack-refs', '--all');
|
|
320
|
+
const packed = await git(clone, 'rev-parse', 'refs/heads/main');
|
|
321
|
+
// The loose file comes back as a symbolic ref to a branch that is gone:
|
|
322
|
+
// git reports the ref as dangling, not as the packed tip underneath.
|
|
323
|
+
await fs.writeFile(path.join(clone, '.git/refs/heads/main'), 'ref: refs/heads/gone\n');
|
|
324
|
+
await expect(git(clone, 'rev-parse', '--verify', '--quiet', 'refs/heads/main^{commit}')).rejects.toThrow();
|
|
325
|
+
expect(await readRefSha(clone, 'refs/heads/main')).toBe('unreadable');
|
|
326
|
+
expect(await readRevSha(clone, 'main')).toBe('unreadable');
|
|
327
|
+
expect(packed).toMatch(/^(?:[0-9a-f]{40}|[0-9a-f]{64})$/);
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
it('hands git what it does not resolve itself', async () => {
|
|
331
|
+
// `@` alone is git's shorthand for HEAD, `@{u}` an upstream: git's.
|
|
332
|
+
for (const rev of ['', '-x', '@', '@{u}', 'main^{commit}', 'HEAD~1', 'main@{1}', 'abc1234', 'a'.repeat(40), 'a b']) {
|
|
333
|
+
expect(await readRevSha(clone, rev)).toBe('unreadable');
|
|
334
|
+
}
|
|
335
|
+
expect(await readRevSha(clone, '@')).not.toBe('missing');
|
|
336
|
+
expect(await readRevSha(path.join(root, 'nowhere'), 'main')).toBe('unreadable');
|
|
337
|
+
});
|
|
338
|
+
});
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { isAbsence } from '../../shared/fs.contract.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Where a checkout's git directory is, read from its `.git` without spawning
|
|
7
|
+
* git — the one resolver every reader of a worktree's own files uses.
|
|
8
|
+
*
|
|
9
|
+
* A full clone's `.git` is the git directory itself. A worktree's `.git` is a
|
|
10
|
+
* FILE (`gitdir: <path>`) naming its folder in the repository, resolved
|
|
11
|
+
* against the file's own folder as git does.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* The folder a gitfile (`gitdir: <path>`) names; null when the file is absent,
|
|
16
|
+
* is a folder, or says nothing of the kind. Any other read error propagates:
|
|
17
|
+
* a `.git` that could not be read has not been shown to be anything.
|
|
18
|
+
*/
|
|
19
|
+
export async function readGitfile(gitfile: string): Promise<string | null> {
|
|
20
|
+
let text: string;
|
|
21
|
+
try {
|
|
22
|
+
text = await fs.readFile(gitfile, 'utf-8');
|
|
23
|
+
} catch (err) {
|
|
24
|
+
if (isAbsence(err) || (err as NodeJS.ErrnoException).code === 'EISDIR') return null;
|
|
25
|
+
throw err;
|
|
26
|
+
}
|
|
27
|
+
// Exactly what git reads (setup.c, `read_gitfile_gently`): the whole file
|
|
28
|
+
// is `gitdir: ` and a path, with nothing after the path but line ends. A
|
|
29
|
+
// second line, other spacing before the path, or anything after it is no
|
|
30
|
+
// gitfile to git, so it names no folder here either.
|
|
31
|
+
const m = /^gitdir: ([^\r\n]+?)[\r\n]*$/.exec(text);
|
|
32
|
+
if (!m) return null;
|
|
33
|
+
return path.resolve(path.dirname(gitfile), m[1]!);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* The git directory of the checkout at `checkoutDir`: its `.git` folder, or
|
|
38
|
+
* the folder its `.git` file names. Null when there is no `.git`, or a `.git`
|
|
39
|
+
* file that names no folder. Errors other than absence propagate.
|
|
40
|
+
*/
|
|
41
|
+
export async function gitDirOf(checkoutDir: string): Promise<string | null> {
|
|
42
|
+
const dotGit = path.join(checkoutDir, '.git');
|
|
43
|
+
let stat;
|
|
44
|
+
try {
|
|
45
|
+
stat = await fs.stat(dotGit);
|
|
46
|
+
} catch (err) {
|
|
47
|
+
if (isAbsence(err)) return null;
|
|
48
|
+
throw err;
|
|
49
|
+
}
|
|
50
|
+
if (stat.isDirectory()) return dotGit;
|
|
51
|
+
if (!stat.isFile()) return null;
|
|
52
|
+
return readGitfile(dotGit);
|
|
53
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What the kb-fs module offers the other modules. The filesystem and the
|
|
3
|
+
* locks are wired by the composition root; a module that writes through the
|
|
4
|
+
* workspace needs only the shape of the gate every write passes first.
|
|
5
|
+
*/
|
|
6
|
+
export type { WriteValidator } from './locking-filesystem.js';
|
|
7
|
+
export { readRefSha, readRevSha, type RefFileAnswer } from './ref-files.js';
|
|
8
|
+
export { gitDirOf, readGitfile } from './git-dir.js';
|