@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
|
@@ -8,6 +8,7 @@ import { WorkspaceMutex } from '../../kb-fs/mutex.js';
|
|
|
8
8
|
import { isAbsence } from '../../../shared/fs.contract.js';
|
|
9
9
|
import { assertInsideRepo } from '../../kb-fs/repo-path.js';
|
|
10
10
|
import { cloneTrackingConfigArgs, SAFE_IMPLICIT_FETCH_ARGS } from '../../kb-fs/clone-config.js';
|
|
11
|
+
import { readRefSha } from '../../kb-fs/index.js';
|
|
11
12
|
import { assertValidBranchName, assertValidRelativePath, isBranchAuthoredBy, isOwnSuggestionsBranch, } from '../../kb-fs/branch-name.js';
|
|
12
13
|
import { AppliedChangeMismatchError, BranchAuthorshipError, BranchDeleteRefusedError, WorkflowDomainError, WorkflowValidationError, ProtectedBranchError, PullRebaseConflictError, RemoteBranchGoneError, VersionNotOnBranchError, isMissingRemoteBranchFailure, } from '../../../shared/domain-errors.js';
|
|
13
14
|
import { GitRunError, assertOneSpecPerLine, isGitTimeout, redactGitToken, } from '../../../shared/git.contract.js';
|
|
@@ -243,6 +244,24 @@ export class GitService {
|
|
|
243
244
|
// concurrent processes worst case — fast for typical (<8) feature-branch
|
|
244
245
|
// counts, calm under large branch lists.
|
|
245
246
|
static MAX_AHEAD_BEHIND_CONCURRENCY = 8;
|
|
247
|
+
/**
|
|
248
|
+
* A change request's changed entries, keyed by the two commits its diff
|
|
249
|
+
* was read between. Two commits never change, and neither does the
|
|
250
|
+
* merge-base between them, so the three-dot diff of a pair is a fact: a
|
|
251
|
+
* push moves a tip, the pair changes, the old entry is simply never asked
|
|
252
|
+
* for again. No TTL, no invalidation.
|
|
253
|
+
*
|
|
254
|
+
* This is what a change-request listing was paying git for on every read:
|
|
255
|
+
* one `diff` per open request, polled every minute and refetched on every
|
|
256
|
+
* event, where nothing had moved. Now git is asked once per pair, which is
|
|
257
|
+
* once per push. Keyed by commits alone, not by workspace: every clone of
|
|
258
|
+
* the knowledge base holds the same objects, so the answer is the same in
|
|
259
|
+
* all of them. Bounded FIFO; the bound only caps memory.
|
|
260
|
+
*/
|
|
261
|
+
changedEntriesByCommits = new Map();
|
|
262
|
+
/** Diffs being read right now, so concurrent misses on one pair share one `git diff`. */
|
|
263
|
+
changedEntriesInFlight = new Map();
|
|
264
|
+
static CHANGED_ENTRIES_MAX = 4096;
|
|
246
265
|
constructor(workspaceService,
|
|
247
266
|
/**
|
|
248
267
|
* Workflow lifecycle hooks. This service only consults the ADVISORY
|
|
@@ -321,7 +340,9 @@ export class GitService {
|
|
|
321
340
|
* above rather than kept separately, so it cannot disagree with it.
|
|
322
341
|
*/
|
|
323
342
|
lastRemoteContact() {
|
|
324
|
-
|
|
343
|
+
// The repository's fetches are every worktree's; the per-workspace record
|
|
344
|
+
// is a full clone's.
|
|
345
|
+
let latest = this.workspaceService.repository?.lastContact() ?? null;
|
|
325
346
|
for (const [workspaceId, at] of this.lastImplicitFetchAt) {
|
|
326
347
|
if (latest === null || at > latest.at) {
|
|
327
348
|
latest = { at, ok: this.lastImplicitFetchOk.get(workspaceId) ?? false };
|
|
@@ -419,12 +440,22 @@ export class GitService {
|
|
|
419
440
|
// Union local heads with origin's remote-tracking refs: a fresh per-user
|
|
420
441
|
// clone only has one local head (HEAD's default), so without this we'd hide
|
|
421
442
|
// target-company-state and every draft pushed by another user.
|
|
443
|
+
//
|
|
444
|
+
// The workspace is a worktree of the one repository, which holds every
|
|
445
|
+
// OTHER worktree's local branch too. Those are not this workspace's: a
|
|
446
|
+
// clone never had them. A local head checked out in another worktree
|
|
447
|
+
// is read as that branch's remote-tracking ref alone (and not at all
|
|
448
|
+
// once origin no longer has it), so a branch deleted on origin is not
|
|
449
|
+
// kept alive by its own worktree's local ref — the orphan sweep reads
|
|
450
|
+
// this list. A branch is checked out in at most one worktree, so "not
|
|
451
|
+
// this one's HEAD" is "another's".
|
|
422
452
|
const { stdout } = await this.git(cwd, [
|
|
423
453
|
'for-each-ref',
|
|
424
|
-
'--format=%(refname:short)%09%(refname)',
|
|
454
|
+
'--format=%(refname:short)%09%(refname)%09%(worktreepath)',
|
|
425
455
|
'refs/heads',
|
|
426
456
|
'refs/remotes/origin',
|
|
427
457
|
]);
|
|
458
|
+
const ownBranch = (await this.git(cwd, ['symbolic-ref', '--quiet', '--short', 'HEAD']).then((r) => r.stdout, () => '')).trim();
|
|
428
459
|
const buckets = new Map();
|
|
429
460
|
for (const line of stdout.split('\n')) {
|
|
430
461
|
const trimmed = line.trim();
|
|
@@ -434,7 +465,7 @@ export class GitService {
|
|
|
434
465
|
if (tab < 0)
|
|
435
466
|
continue;
|
|
436
467
|
const rawShort = trimmed.slice(0, tab);
|
|
437
|
-
const ref = trimmed.slice(tab + 1);
|
|
468
|
+
const [ref = '', worktreePath = ''] = trimmed.slice(tab + 1).split('\t');
|
|
438
469
|
if (!rawShort || !ref)
|
|
439
470
|
continue;
|
|
440
471
|
// Skip symbolic refs up-front. Different git versions report
|
|
@@ -447,6 +478,8 @@ export class GitService {
|
|
|
447
478
|
const shortName = isLocal ? rawShort : rawShort.replace(/^origin\//, '');
|
|
448
479
|
if (!shortName)
|
|
449
480
|
continue;
|
|
481
|
+
if (isLocal && worktreePath !== '' && shortName !== ownBranch)
|
|
482
|
+
continue;
|
|
450
483
|
// Final guard: the strip above turns `origin/HEAD` into `HEAD`. Even
|
|
451
484
|
// if a future git version lands us here, don't bucket it as a branch.
|
|
452
485
|
if (shortName === 'HEAD')
|
|
@@ -530,6 +563,20 @@ export class GitService {
|
|
|
530
563
|
* and the object store, neither of which checkout/status/commit touch.
|
|
531
564
|
*/
|
|
532
565
|
async fetchOriginIfStale(cwd, workspaceId, force = false) {
|
|
566
|
+
// A worktree's remote-tracking refs are the repository's: ONE fetch for
|
|
567
|
+
// every workspace, throttled, joined and reserved there. The per-workspace
|
|
568
|
+
// record below is for a full clone from an earlier release, which fetches
|
|
569
|
+
// on its own until the boot phase converts it.
|
|
570
|
+
const repository = (await this.workspaceService.repositoryOfCheckout?.(cwd)) ?? null;
|
|
571
|
+
if (repository) {
|
|
572
|
+
return repository.fetch({
|
|
573
|
+
maxAgeMs: GitService.IMPLICIT_FETCH_TTL_MS,
|
|
574
|
+
// A host that is down is not asked again by every poll inside the
|
|
575
|
+
// window, as the per-workspace record below stamps a failure too.
|
|
576
|
+
failureBackoffMs: GitService.IMPLICIT_FETCH_TTL_MS,
|
|
577
|
+
force,
|
|
578
|
+
});
|
|
579
|
+
}
|
|
533
580
|
const last = this.lastImplicitFetchAt.get(workspaceId) ?? 0;
|
|
534
581
|
if (!force && Date.now() - last < GitService.IMPLICIT_FETCH_TTL_MS) {
|
|
535
582
|
return this.lastImplicitFetchOk.get(workspaceId) ?? true;
|
|
@@ -603,8 +650,9 @@ export class GitService {
|
|
|
603
650
|
// navigation to the new draft's URL fails with
|
|
604
651
|
// "Remote branch <name> not found in upstream origin". Roll the local
|
|
605
652
|
// ref back on failure so a retry isn't blocked by "branch already exists".
|
|
653
|
+
// No `-u`: the local head is dropped right below, and its upstream with it.
|
|
606
654
|
try {
|
|
607
|
-
await this.git(cwd, ['push', '
|
|
655
|
+
await this.git(cwd, ['push', 'origin', name]);
|
|
608
656
|
}
|
|
609
657
|
catch (err) {
|
|
610
658
|
await this.git(cwd, ['branch', '-D', name]).catch(() => undefined);
|
|
@@ -725,7 +773,15 @@ export class GitService {
|
|
|
725
773
|
let localTip = null;
|
|
726
774
|
if (await this.refExists(cwd, `refs/heads/${name}`)) {
|
|
727
775
|
localTip = await this.revParseOrNull(cwd, `refs/heads/${name}`);
|
|
728
|
-
await this.git(cwd, ['branch', '-D', name])
|
|
776
|
+
await this.git(cwd, ['branch', '-D', name]).catch((err) => {
|
|
777
|
+
// In a repository of worktrees the branch's local ref is shared, and
|
|
778
|
+
// git keeps it while the branch's own workspace has it checked out.
|
|
779
|
+
// Removing that workspace — which the caller does next — takes the
|
|
780
|
+
// worktree and the ref with it.
|
|
781
|
+
if (/checked out at|used by worktree/i.test(err instanceof Error ? err.message : String(err)))
|
|
782
|
+
return;
|
|
783
|
+
throw err;
|
|
784
|
+
});
|
|
729
785
|
}
|
|
730
786
|
return { lastCommit: remoteTip ?? localTip };
|
|
731
787
|
});
|
|
@@ -1221,7 +1277,14 @@ export class GitService {
|
|
|
1221
1277
|
const touched = await this.unpushedTouchedPaths(cwd);
|
|
1222
1278
|
await this.assertCanWriteAtRef(workspaceId, `origin/${branch}`, user.email, touched);
|
|
1223
1279
|
}
|
|
1224
|
-
|
|
1280
|
+
// No `-u`: it writes the upstream into the config during the push, and
|
|
1281
|
+
// a worktree's config is the repository's, shared by every workspace —
|
|
1282
|
+
// git refuses a second writer of it rather than waiting. The upstream is
|
|
1283
|
+
// stamped after the push instead, as one short write made as the
|
|
1284
|
+
// repository's administrator (see `git`).
|
|
1285
|
+
await this.git(cwd, ['push', 'origin', branch]);
|
|
1286
|
+
await this.git(cwd, ['config', '--replace-all', `branch.${branch}.remote`, 'origin']);
|
|
1287
|
+
await this.git(cwd, ['config', '--replace-all', `branch.${branch}.merge`, `refs/heads/${branch}`]);
|
|
1225
1288
|
}
|
|
1226
1289
|
}
|
|
1227
1290
|
// `forcePush` method removed. The workflow layer no longer auto-force-pushes
|
|
@@ -2745,9 +2808,8 @@ export class GitService {
|
|
|
2745
2808
|
await this.fetchPrRefs(cwd, baseBranch, headBranch);
|
|
2746
2809
|
}
|
|
2747
2810
|
return this.mutex.run(workspaceId, async () => {
|
|
2748
|
-
const
|
|
2749
|
-
|
|
2750
|
-
return this.prChangedPathsAt(cwd, baseRef, headRef, opts);
|
|
2811
|
+
const at = pinned ?? (await this.publishedPrEnds(cwd, baseBranch, headBranch));
|
|
2812
|
+
return flattenChangedPaths(await this.prChangedEntriesBetweenCommits(cwd, at.base, at.head), opts);
|
|
2751
2813
|
});
|
|
2752
2814
|
}
|
|
2753
2815
|
/**
|
|
@@ -2800,12 +2862,49 @@ export class GitService {
|
|
|
2800
2862
|
await this.fetchPrRefs(cwd, baseBranch, headBranch);
|
|
2801
2863
|
}
|
|
2802
2864
|
return this.mutex.run(workspaceId, async () => {
|
|
2803
|
-
const
|
|
2804
|
-
const
|
|
2805
|
-
const entries = await this.prChangedEntriesAt(cwd, baseRef, headRef);
|
|
2865
|
+
const at = pinned ?? (await this.publishedPrEnds(cwd, baseBranch, headBranch));
|
|
2866
|
+
const entries = await this.prChangedEntriesBetweenCommits(cwd, at.base, at.head);
|
|
2806
2867
|
return { paths: flattenChangedPaths(entries, {}), pairs: changedPathPairs(entries) };
|
|
2807
2868
|
});
|
|
2808
2869
|
}
|
|
2870
|
+
/**
|
|
2871
|
+
* {@link prChangedEntriesAt} for a pair of COMMITS, read once per pair
|
|
2872
|
+
* (see `changedEntriesByCommits`). The caller holds the reservation, as for
|
|
2873
|
+
* `prChangedEntriesAt`; a memo hit holds it for nothing, which is cheaper
|
|
2874
|
+
* than a second code path around the lock.
|
|
2875
|
+
*/
|
|
2876
|
+
async prChangedEntriesBetweenCommits(cwd, baseSha, headSha) {
|
|
2877
|
+
const key = `${baseSha}\0${headSha}`;
|
|
2878
|
+
const known = this.changedEntriesByCommits.get(key);
|
|
2879
|
+
if (known)
|
|
2880
|
+
return known;
|
|
2881
|
+
let pending = this.changedEntriesInFlight.get(key);
|
|
2882
|
+
if (!pending) {
|
|
2883
|
+
pending = this.prChangedEntriesAt(cwd, baseSha, headSha)
|
|
2884
|
+
.then((entries) => {
|
|
2885
|
+
if (this.changedEntriesByCommits.size >= GitService.CHANGED_ENTRIES_MAX) {
|
|
2886
|
+
const oldest = this.changedEntriesByCommits.keys().next().value;
|
|
2887
|
+
if (oldest !== undefined)
|
|
2888
|
+
this.changedEntriesByCommits.delete(oldest);
|
|
2889
|
+
}
|
|
2890
|
+
this.changedEntriesByCommits.set(key, entries);
|
|
2891
|
+
return entries;
|
|
2892
|
+
})
|
|
2893
|
+
.finally(() => {
|
|
2894
|
+
if (this.changedEntriesInFlight.get(key) === pending)
|
|
2895
|
+
this.changedEntriesInFlight.delete(key);
|
|
2896
|
+
});
|
|
2897
|
+
// Same bound as the memo: evicting an in-flight entry costs at most one
|
|
2898
|
+
// redundant diff for that pair, never a lost answer.
|
|
2899
|
+
if (this.changedEntriesInFlight.size >= GitService.CHANGED_ENTRIES_MAX) {
|
|
2900
|
+
const oldest = this.changedEntriesInFlight.keys().next().value;
|
|
2901
|
+
if (oldest !== undefined)
|
|
2902
|
+
this.changedEntriesInFlight.delete(oldest);
|
|
2903
|
+
}
|
|
2904
|
+
this.changedEntriesInFlight.set(key, pending);
|
|
2905
|
+
}
|
|
2906
|
+
return pending;
|
|
2907
|
+
}
|
|
2809
2908
|
/**
|
|
2810
2909
|
* The merge-base commit of a change request's two branches (their origin
|
|
2811
2910
|
* refs), or null when they share no history. This is the "before" the CR's
|
|
@@ -3103,18 +3202,20 @@ export class GitService {
|
|
|
3103
3202
|
* broken for some other reason.
|
|
3104
3203
|
*/
|
|
3105
3204
|
async publishedPrCommits(cwd, baseBranch, headBranch) {
|
|
3106
|
-
|
|
3107
|
-
|
|
3108
|
-
|
|
3109
|
-
|
|
3110
|
-
|
|
3111
|
-
|
|
3112
|
-
|
|
3113
|
-
return null;
|
|
3114
|
-
throw err;
|
|
3115
|
-
}
|
|
3205
|
+
try {
|
|
3206
|
+
return await this.publishedPrEnds(cwd, baseBranch, headBranch);
|
|
3207
|
+
}
|
|
3208
|
+
catch (err) {
|
|
3209
|
+
if (err instanceof WorkflowValidationError)
|
|
3210
|
+
return null;
|
|
3211
|
+
throw err;
|
|
3116
3212
|
}
|
|
3117
|
-
|
|
3213
|
+
}
|
|
3214
|
+
/** The two commits a change request is read between, each resolved as {@link resolvePublishedBranch} does. */
|
|
3215
|
+
async publishedPrEnds(cwd, baseBranch, headBranch) {
|
|
3216
|
+
const base = (await this.resolvePublishedBranch(cwd, baseBranch)).sha;
|
|
3217
|
+
const head = (await this.resolvePublishedBranch(cwd, headBranch)).sha;
|
|
3218
|
+
return { base, head };
|
|
3118
3219
|
}
|
|
3119
3220
|
/**
|
|
3120
3221
|
* `resolveBranchRef` with the preference reversed: the PUBLISHED ref
|
|
@@ -3135,6 +3236,15 @@ export class GitService {
|
|
|
3135
3236
|
*/
|
|
3136
3237
|
async resolvePublishedBranch(cwd, branch) {
|
|
3137
3238
|
for (const ref of [`refs/remotes/origin/${branch}`, `refs/heads/${branch}`]) {
|
|
3239
|
+
// The ref file first (`readRefSha`): no process, same answer, same
|
|
3240
|
+
// freshness. Git is asked only for what the files cannot decide — a
|
|
3241
|
+
// missing ref is decided by the files and the loop moves on; an
|
|
3242
|
+
// unreadable one is git's to answer, as the authority.
|
|
3243
|
+
const fromFiles = await readRefSha(cwd, ref);
|
|
3244
|
+
if (fromFiles === 'missing')
|
|
3245
|
+
continue;
|
|
3246
|
+
if (fromFiles !== 'unreadable')
|
|
3247
|
+
return { ref, sha: fromFiles.sha };
|
|
3138
3248
|
try {
|
|
3139
3249
|
const { stdout } = await this.git(cwd, ['rev-parse', '--verify', '--quiet', ref]);
|
|
3140
3250
|
return { ref, sha: stdout.trim() };
|
|
@@ -3482,6 +3592,30 @@ export class GitService {
|
|
|
3482
3592
|
const { stdout } = await this.git(cwd, ['ls-tree', '-r', '-z', '--name-only', ref, '--', folder]);
|
|
3483
3593
|
return stdout.split('\0').filter((line) => line.length > 0);
|
|
3484
3594
|
}
|
|
3595
|
+
/**
|
|
3596
|
+
* The commit `ref` names right now, or null when it names none (an unborn
|
|
3597
|
+
* `HEAD`, a ref that does not exist). A caller that reads several things
|
|
3598
|
+
* "at HEAD" pins the commit once with this and asks each read at that
|
|
3599
|
+
* commit, so a commit landing between the reads cannot hand it one
|
|
3600
|
+
* snapshot's files with another's rules.
|
|
3601
|
+
*
|
|
3602
|
+
* Null is ONLY "names no commit": `rev-parse --verify --quiet` says that
|
|
3603
|
+
* with exit status 1 and nothing on stderr. Any other failure — no
|
|
3604
|
+
* repository there, git itself failing, a deadline — is thrown, so a
|
|
3605
|
+
* caller cannot read a broken clone as an empty one.
|
|
3606
|
+
*/
|
|
3607
|
+
async commitAtRef(workspaceId, ref) {
|
|
3608
|
+
const cwd = await this.repoDir(workspaceId);
|
|
3609
|
+
try {
|
|
3610
|
+
const { stdout } = await this.git(cwd, ['rev-parse', '--verify', '--quiet', `${ref}^{commit}`]);
|
|
3611
|
+
return stdout.trim() || null;
|
|
3612
|
+
}
|
|
3613
|
+
catch (err) {
|
|
3614
|
+
if (err instanceof GitRunError && !err.timedOut && err.exitCode === 1 && !(err.stderr ?? '').trim())
|
|
3615
|
+
return null;
|
|
3616
|
+
throw err;
|
|
3617
|
+
}
|
|
3618
|
+
}
|
|
3485
3619
|
/**
|
|
3486
3620
|
* A clean `git status` for a path has two honest readings (already
|
|
3487
3621
|
* committed; a queued re-apply of something that landed) and one dishonest
|
|
@@ -3534,7 +3668,7 @@ export class GitService {
|
|
|
3534
3668
|
* from a real failure are unaffected.
|
|
3535
3669
|
*/
|
|
3536
3670
|
git(cwd, args, opts) {
|
|
3537
|
-
|
|
3671
|
+
const run = () => this.gitRunner.run(cwd, args, {
|
|
3538
3672
|
...opts,
|
|
3539
3673
|
// `GIT_LITERAL_PATHSPECS=1` makes git treat every pathspec literally
|
|
3540
3674
|
// instead of interpreting `[`, `]`, `*`, `?`, `!`, or `:(magic)` as glob
|
|
@@ -3549,6 +3683,17 @@ export class GitService {
|
|
|
3549
3683
|
// a search for a file literally named that.
|
|
3550
3684
|
env: { GIT_LITERAL_PATHSPECS: '1', ...opts?.env },
|
|
3551
3685
|
});
|
|
3686
|
+
// A write to the git config is a write to the ONE file every worktree of
|
|
3687
|
+
// the repository shares, which git refuses a second writer of rather than
|
|
3688
|
+
// waiting: it runs as the repository's administrator. Everything else —
|
|
3689
|
+
// a fetch, a commit, a branch delete — git serializes with its own locks,
|
|
3690
|
+
// as it does for any two worktrees of one repository.
|
|
3691
|
+
return writesSharedConfig(args) ? this.asRepositoryAdministrator(cwd, run) : run();
|
|
3692
|
+
}
|
|
3693
|
+
/** `run` as the repository's administrator when `cwd` is a worktree of it; as itself for a full clone. */
|
|
3694
|
+
async asRepositoryAdministrator(cwd, run) {
|
|
3695
|
+
const repository = (await this.workspaceService.repositoryOfCheckout?.(cwd)) ?? null;
|
|
3696
|
+
return repository ? repository.exclusive(run) : run();
|
|
3552
3697
|
}
|
|
3553
3698
|
/**
|
|
3554
3699
|
* {@link git}, with stdout as raw BYTES.
|
|
@@ -3722,4 +3867,36 @@ export function parseNumstatZ(out) {
|
|
|
3722
3867
|
}
|
|
3723
3868
|
return entries;
|
|
3724
3869
|
}
|
|
3870
|
+
/**
|
|
3871
|
+
* Whether a git command WRITES the config — the one file every worktree of
|
|
3872
|
+
* the knowledge base's repository shares, which git refuses a second writer
|
|
3873
|
+
* of rather than waiting — and so must run as the repository's administrator
|
|
3874
|
+
* (`IKbRepository.exclusive`): a `config` that sets or unsets, a `remote`
|
|
3875
|
+
* change, a branch rename, delete or upstream change (its config section is
|
|
3876
|
+
* written, or removed with the branch), a push that sets the upstream. Reads
|
|
3877
|
+
* of the config are not writes. Nothing else is: the refs, `packed-refs` and
|
|
3878
|
+
* the object store are locked by git itself, and the worktree list is the
|
|
3879
|
+
* repository's own to serialize (`KbRepository`).
|
|
3880
|
+
*
|
|
3881
|
+
* Leading `-c key=value` / `-C dir` options are skipped to find the subcommand.
|
|
3882
|
+
*/
|
|
3883
|
+
function writesSharedConfig(args) {
|
|
3884
|
+
let i = 0;
|
|
3885
|
+
while (i < args.length && (args[i] === '-c' || args[i] === '-C'))
|
|
3886
|
+
i += 2;
|
|
3887
|
+
const sub = args[i];
|
|
3888
|
+
const rest = args.slice(i + 1);
|
|
3889
|
+
switch (sub) {
|
|
3890
|
+
case 'remote':
|
|
3891
|
+
return true;
|
|
3892
|
+
case 'config':
|
|
3893
|
+
return !rest.some((a) => /^(--get|--get-all|--get-regexp|--list|-l|--show-origin)$/.test(a));
|
|
3894
|
+
case 'branch':
|
|
3895
|
+
return rest.some((a) => /^(-d|-D|--delete|-m|-M|--move|-u|--set-upstream-to=.*|--unset-upstream)$/.test(a));
|
|
3896
|
+
case 'push':
|
|
3897
|
+
return rest.some((a) => a === '-u' || a === '--set-upstream');
|
|
3898
|
+
default:
|
|
3899
|
+
return false;
|
|
3900
|
+
}
|
|
3901
|
+
}
|
|
3725
3902
|
//# sourceMappingURL=git.service.js.map
|