@bevel-software/platform-core-backend 0.10.0 → 0.11.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/dist/core/create-core-server.d.ts.map +1 -1
- package/dist/core/create-core-server.js +12 -1
- package/dist/core/create-core-server.js.map +1 -1
- package/dist/core/create-core-services.d.ts +22 -0
- package/dist/core/create-core-services.d.ts.map +1 -1
- package/dist/core/create-core-services.js +34 -1
- package/dist/core/create-core-services.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/modules/access/access-control.interface.d.ts +54 -28
- package/dist/modules/access/access-control.interface.d.ts.map +1 -1
- package/dist/modules/access/access-control.service.d.ts +129 -14
- package/dist/modules/access/access-control.service.d.ts.map +1 -1
- package/dist/modules/access/access-control.service.js +435 -66
- package/dist/modules/access/access-control.service.js.map +1 -1
- package/dist/modules/access/access-declarations.d.ts.map +1 -1
- package/dist/modules/access/access-declarations.js +5 -3
- package/dist/modules/access/access-declarations.js.map +1 -1
- package/dist/modules/access/access-mutation.service.d.ts +39 -6
- package/dist/modules/access/access-mutation.service.d.ts.map +1 -1
- package/dist/modules/access/access-mutation.service.js +78 -18
- package/dist/modules/access/access-mutation.service.js.map +1 -1
- package/dist/modules/access/access-splice.d.ts +31 -4
- package/dist/modules/access/access-splice.d.ts.map +1 -1
- package/dist/modules/access/access-splice.js +40 -16
- package/dist/modules/access/access-splice.js.map +1 -1
- package/dist/modules/access/access.routes.d.ts.map +1 -1
- package/dist/modules/access/access.routes.js +204 -82
- package/dist/modules/access/access.routes.js.map +1 -1
- package/dist/modules/access/admin-locked-commit.d.ts +134 -0
- package/dist/modules/access/admin-locked-commit.d.ts.map +1 -0
- package/dist/modules/access/admin-locked-commit.js +277 -0
- package/dist/modules/access/admin-locked-commit.js.map +1 -0
- package/dist/modules/access/admin-route-helpers.d.ts +32 -0
- package/dist/modules/access/admin-route-helpers.d.ts.map +1 -0
- package/dist/modules/access/admin-route-helpers.js +44 -0
- package/dist/modules/access/admin-route-helpers.js.map +1 -0
- package/dist/modules/access/capability-registry.d.ts +41 -0
- package/dist/modules/access/capability-registry.d.ts.map +1 -0
- package/dist/modules/access/capability-registry.js +46 -0
- package/dist/modules/access/capability-registry.js.map +1 -0
- package/dist/modules/access/directory-sync-bot.d.ts +13 -0
- package/dist/modules/access/directory-sync-bot.d.ts.map +1 -0
- package/dist/modules/access/directory-sync-bot.js +64 -0
- package/dist/modules/access/directory-sync-bot.js.map +1 -0
- package/dist/modules/access/group-files.d.ts +83 -0
- package/dist/modules/access/group-files.d.ts.map +1 -0
- package/dist/modules/access/group-files.js +167 -0
- package/dist/modules/access/group-files.js.map +1 -0
- package/dist/modules/access/groups-admin.routes.d.ts +19 -0
- package/dist/modules/access/groups-admin.routes.d.ts.map +1 -0
- package/dist/modules/access/groups-admin.routes.js +98 -0
- package/dist/modules/access/groups-admin.routes.js.map +1 -0
- package/dist/modules/access/groups-admin.service.d.ts +166 -0
- package/dist/modules/access/groups-admin.service.d.ts.map +1 -0
- package/dist/modules/access/groups-admin.service.js +442 -0
- package/dist/modules/access/groups-admin.service.js.map +1 -0
- package/dist/modules/access/groups-edit.d.ts +58 -0
- package/dist/modules/access/groups-edit.d.ts.map +1 -0
- package/dist/modules/access/groups-edit.js +162 -0
- package/dist/modules/access/groups-edit.js.map +1 -0
- package/dist/modules/access/reference-scan.d.ts +141 -0
- package/dist/modules/access/reference-scan.d.ts.map +1 -0
- package/dist/modules/access/reference-scan.js +440 -0
- package/dist/modules/access/reference-scan.js.map +1 -0
- package/dist/modules/access/roles-admin.service.d.ts +88 -119
- package/dist/modules/access/roles-admin.service.d.ts.map +1 -1
- package/dist/modules/access/roles-admin.service.js +230 -384
- package/dist/modules/access/roles-admin.service.js.map +1 -1
- package/dist/modules/access/roles-edit.d.ts +51 -25
- package/dist/modules/access/roles-edit.d.ts.map +1 -1
- package/dist/modules/access/roles-edit.js +133 -59
- package/dist/modules/access/roles-edit.js.map +1 -1
- package/dist/modules/access/synced-groups-committer.d.ts +28 -0
- package/dist/modules/access/synced-groups-committer.d.ts.map +1 -0
- package/dist/modules/access/synced-groups-committer.js +139 -0
- package/dist/modules/access/synced-groups-committer.js.map +1 -0
- package/dist/modules/access/synced-groups-writer.d.ts +78 -0
- package/dist/modules/access/synced-groups-writer.d.ts.map +1 -0
- package/dist/modules/access/synced-groups-writer.js +219 -0
- package/dist/modules/access/synced-groups-writer.js.map +1 -0
- package/dist/modules/database/core-schema.d.ts +17 -0
- package/dist/modules/database/core-schema.d.ts.map +1 -1
- package/dist/modules/database/core-schema.js +9 -0
- package/dist/modules/database/core-schema.js.map +1 -1
- package/dist/modules/mcp/mcp-auth.middleware.d.ts +13 -2
- package/dist/modules/mcp/mcp-auth.middleware.d.ts.map +1 -1
- package/dist/modules/mcp/mcp-auth.middleware.js +61 -2
- package/dist/modules/mcp/mcp-auth.middleware.js.map +1 -1
- package/dist/modules/mcp/mcp.routes.d.ts +9 -3
- package/dist/modules/mcp/mcp.routes.d.ts.map +1 -1
- package/dist/modules/mcp/mcp.routes.js +126 -2
- package/dist/modules/mcp/mcp.routes.js.map +1 -1
- package/dist/modules/mcp/mcp.service.d.ts +14 -0
- package/dist/modules/mcp/mcp.service.d.ts.map +1 -1
- package/dist/modules/mcp/mcp.service.js +6 -1
- package/dist/modules/mcp/mcp.service.js.map +1 -1
- package/dist/modules/workflow/file-lock.service.d.ts +11 -1
- package/dist/modules/workflow/file-lock.service.d.ts.map +1 -1
- package/dist/modules/workflow/file-lock.service.js +15 -1
- package/dist/modules/workflow/file-lock.service.js.map +1 -1
- package/dist/modules/workflow/git/git.service.d.ts +34 -11
- package/dist/modules/workflow/git/git.service.d.ts.map +1 -1
- package/dist/modules/workflow/git/git.service.js +165 -37
- package/dist/modules/workflow/git/git.service.js.map +1 -1
- package/dist/modules/workflow/locking-filesystem.d.ts +4 -0
- package/dist/modules/workflow/locking-filesystem.d.ts.map +1 -1
- package/dist/modules/workflow/locking-filesystem.js +181 -28
- package/dist/modules/workflow/locking-filesystem.js.map +1 -1
- package/dist/modules/workflow/pending-commits.service.d.ts +10 -0
- package/dist/modules/workflow/pending-commits.service.d.ts.map +1 -1
- package/dist/modules/workflow/pending-commits.service.js +19 -1
- package/dist/modules/workflow/pending-commits.service.js.map +1 -1
- package/dist/modules/workflow/workflow.service.d.ts +17 -2
- package/dist/modules/workflow/workflow.service.d.ts.map +1 -1
- package/dist/modules/workflow/workflow.service.js +113 -19
- package/dist/modules/workflow/workflow.service.js.map +1 -1
- package/kb-template/AGENTS.md +4 -1
- package/migrations/0004_file_lock_mode.sql +2 -0
- package/migrations/meta/0004_snapshot.json +1564 -0
- package/migrations/meta/_journal.json +7 -0
- package/package.json +3 -3
- package/src/core/create-core-server.ts +13 -0
- package/src/core/create-core-services.ts +68 -0
- package/src/index.ts +10 -0
- package/src/modules/access/__tests__/access-control.service.test.ts +9 -3
- package/src/modules/access/__tests__/access-groups.test.ts +427 -0
- package/src/modules/access/__tests__/access-mutation.service.test.ts +171 -5
- package/src/modules/access/__tests__/access-splice.test.ts +65 -0
- package/src/modules/access/__tests__/access.routes.group-grant.test.ts +337 -0
- package/src/modules/access/__tests__/access.routes.revoke.test.ts +48 -0
- package/src/modules/access/__tests__/admin-locked-commit.test.ts +221 -0
- package/src/modules/access/__tests__/admin-route-helpers.test.ts +61 -0
- package/src/modules/access/__tests__/directory-sync-bot.test.ts +106 -0
- package/src/modules/access/__tests__/grant-sources.test.ts +67 -0
- package/src/modules/access/__tests__/groups-admin.service.test.ts +440 -0
- package/src/modules/access/__tests__/reference-scan.test.ts +288 -0
- package/src/modules/access/__tests__/roles-admin.service.test.ts +161 -83
- package/src/modules/access/__tests__/roles-capabilities.test.ts +325 -0
- package/src/modules/access/__tests__/roles-edit.test.ts +104 -28
- package/src/modules/access/__tests__/roles.routes.test.ts +63 -40
- package/src/modules/access/__tests__/synced-groups-committer.test.ts +238 -0
- package/src/modules/access/__tests__/synced-groups-writer.test.ts +249 -0
- package/src/modules/access/access-control.interface.ts +66 -32
- package/src/modules/access/access-control.service.ts +536 -73
- package/src/modules/access/access-declarations.ts +5 -2
- package/src/modules/access/access-mutation.service.ts +88 -14
- package/src/modules/access/access-splice.ts +55 -17
- package/src/modules/access/access.routes.ts +227 -93
- package/src/modules/access/admin-locked-commit.ts +331 -0
- package/src/modules/access/admin-route-helpers.ts +55 -0
- package/src/modules/access/capability-registry.ts +74 -0
- package/src/modules/access/directory-sync-bot.ts +76 -0
- package/src/modules/access/group-files.ts +212 -0
- package/src/modules/access/groups-admin.routes.ts +113 -0
- package/src/modules/access/groups-admin.service.ts +551 -0
- package/src/modules/access/groups-edit.ts +187 -0
- package/src/modules/access/reference-scan.ts +513 -0
- package/src/modules/access/roles-admin.service.ts +290 -418
- package/src/modules/access/roles-edit.ts +134 -61
- package/src/modules/access/synced-groups-committer.ts +177 -0
- package/src/modules/access/synced-groups-writer.ts +303 -0
- package/src/modules/database/core-schema.ts +9 -0
- package/src/modules/mcp/__tests__/mcp-auth.middleware.test.ts +116 -0
- package/src/modules/mcp/__tests__/mcp.e2e.test.ts +3 -1
- package/src/modules/mcp/__tests__/mcp.routes.delete.test.ts +6 -1
- package/src/modules/mcp/__tests__/mcp.routes.local-token.test.ts +301 -0
- package/src/modules/mcp/mcp-auth.middleware.ts +61 -1
- package/src/modules/mcp/mcp.routes.ts +137 -2
- package/src/modules/mcp/mcp.service.ts +6 -1
- package/src/modules/workflow/__tests__/locking-filesystem.test.ts +336 -0
- package/src/modules/workflow/__tests__/preserve-roles-yaml.test.ts +1 -1
- package/src/modules/workflow/__tests__/workflow.service.commitFileWhileLocked.test.ts +32 -0
- package/src/modules/workflow/__tests__/workflow.service.facade.test.ts +13 -2
- package/src/modules/workflow/__tests__/workflow.service.releaseLock.test.ts +139 -1
- package/src/modules/workflow/file-lock.service.ts +15 -0
- package/src/modules/workflow/git/__tests__/git.service.accessGating.test.ts +0 -1
- package/src/modules/workflow/git/__tests__/git.service.commitChanges.test.ts +132 -0
- package/src/modules/workflow/git/__tests__/git.service.deleteBranch.test.ts +0 -1
- package/src/modules/workflow/git/__tests__/pull-request.service.test.ts +0 -1
- package/src/modules/workflow/git/git.service.ts +174 -35
- package/src/modules/workflow/locking-filesystem.ts +188 -26
- package/src/modules/workflow/pending-commits.service.ts +27 -1
- package/src/modules/workflow/review-workflow/__tests__/approval-states.test.ts +0 -1
- package/src/modules/workflow/review-workflow/__tests__/cancel-pr.test.ts +0 -1
- package/src/modules/workflow/workflow.service.ts +140 -20
|
@@ -17,6 +17,17 @@ import { PROTECTED_BRANCHES } from './branch-name.js';
|
|
|
17
17
|
* we keep the new path and discard the old one.
|
|
18
18
|
*/
|
|
19
19
|
export declare function parsePorcelainZ(stdout: string): string[];
|
|
20
|
+
/**
|
|
21
|
+
* Like {@link parsePorcelainZ}, but also reports each rename/copy entry's
|
|
22
|
+
* OLD path (keyed by the new path). A pathspec-scoped commit needs the old
|
|
23
|
+
* path too: committing only the new path of a staged rename records an ADD
|
|
24
|
+
* and leaves the deletion of the old path behind — the rename becomes two
|
|
25
|
+
* files.
|
|
26
|
+
*/
|
|
27
|
+
export declare function parsePorcelainZDetailed(stdout: string): {
|
|
28
|
+
paths: string[];
|
|
29
|
+
renameOldByNew: Map<string, string>;
|
|
30
|
+
};
|
|
20
31
|
export declare class GitService implements IGitService {
|
|
21
32
|
private readonly workspaceService;
|
|
22
33
|
/**
|
|
@@ -159,20 +170,26 @@ export declare class GitService implements IGitService {
|
|
|
159
170
|
discardPath(workspaceId: string, relativePath: string): Promise<void>;
|
|
160
171
|
commitFile(workspaceId: string, user: AuthUser, relativePath: string, summary?: string, skipValidator?: boolean): Promise<CommitAttribution | null>;
|
|
161
172
|
/**
|
|
162
|
-
* Atomic multi-file commit — commits
|
|
163
|
-
*
|
|
164
|
-
*
|
|
165
|
-
*
|
|
166
|
-
*
|
|
167
|
-
*
|
|
173
|
+
* Atomic multi-file commit — commits the caller's batch as ONE commit
|
|
174
|
+
* attributed to `user`, then returns its attribution (or null when nothing
|
|
175
|
+
* in scope is dirty — idempotent re-apply). The multi-file sibling of
|
|
176
|
+
* `commit()`, WITHOUT the one-file-per-change guard — the caller has
|
|
177
|
+
* assembled + written + validated a batch (bulk node upload; the lock-aware
|
|
178
|
+
* saves and admin roster writes via `LockingFilesystem` /
|
|
179
|
+
* `AdminLockedCommits`).
|
|
180
|
+
*
|
|
181
|
+
* `onlyPaths` (workspace-relative) scopes staging + commit to the caller's
|
|
182
|
+
* own files via git pathspecs: on the shared per-branch workspace other
|
|
183
|
+
* paths may be dirty from a concurrent save whose commit is still queued,
|
|
184
|
+
* and sweeping them in would land them under this caller's author/summary.
|
|
185
|
+
* Omitted → the whole dirty set (`git add -A`), for flows that own the
|
|
186
|
+
* workspace.
|
|
168
187
|
*
|
|
169
188
|
* Deliberately does NOT write file content itself — disk writes are the
|
|
170
|
-
* caller's job. This is the git layer: it only stages + commits what is
|
|
171
|
-
* on disk
|
|
172
|
-
* `git add -A` stages exactly the caller's batch). Caller pushes separately
|
|
173
|
-
* (mirrors `commitFile` + `push`).
|
|
189
|
+
* caller's job. This is the git layer: it only stages + commits what is
|
|
190
|
+
* already on disk. Caller pushes separately (mirrors `commitFile` + `push`).
|
|
174
191
|
*/
|
|
175
|
-
commitChanges(workspaceId: string, user: AuthUser, summary: string): Promise<CommitAttribution | null>;
|
|
192
|
+
commitChanges(workspaceId: string, user: AuthUser, summary: string, onlyPaths?: string[]): Promise<CommitAttribution | null>;
|
|
176
193
|
push(workspaceId: string, user: AuthUser, opts?: {
|
|
177
194
|
systemAuthorized?: boolean;
|
|
178
195
|
}): Promise<void>;
|
|
@@ -289,6 +306,12 @@ export declare class GitService implements IGitService {
|
|
|
289
306
|
* Used by the pending-commits worker's no-op arm: a clean tree does NOT
|
|
290
307
|
* mean "nothing to share" when a prior best-effort push (the autosave
|
|
291
308
|
* path) failed and left the committed change stranded locally.
|
|
309
|
+
*
|
|
310
|
+
* Serialized under the workspace mutex: a pull-rebase in flight moves HEAD
|
|
311
|
+
* through states where the local commits are momentarily unreachable, and
|
|
312
|
+
* an unserialized read there answers "nothing unpushed" about work that is
|
|
313
|
+
* about to be replayed — callers use this as a publication PROOF, so it may
|
|
314
|
+
* only ever see settled states.
|
|
292
315
|
*/
|
|
293
316
|
hasUnpushedCommits(workspaceId: string): Promise<boolean>;
|
|
294
317
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"git.service.d.ts","sourceRoot":"","sources":["../../../../src/modules/workflow/git/git.service.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EACV,QAAQ,EACR,UAAU,EACV,iBAAiB,EACjB,WAAW,EACX,YAAY,EACZ,eAAe,EACf,mBAAmB,EACnB,iBAAiB,EAClB,MAAM,iCAAiC,CAAC;AACzC,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,sCAAsC,CAAC;AAC7E,OAAO,KAAK,EAAE,aAAa,EAA2B,MAAM,sBAAsB,CAAC;AACnF,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,0CAA0C,CAAC;AAE/E,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAE5C,OAAO,EAML,kBAAkB,EACnB,MAAM,kBAAkB,CAAC;AA8B1B;;;;;GAKG;AACH;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,
|
|
1
|
+
{"version":3,"file":"git.service.d.ts","sourceRoot":"","sources":["../../../../src/modules/workflow/git/git.service.ts"],"names":[],"mappings":"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"}
|
|
@@ -34,8 +34,19 @@ const BOT_EMAIL = 'bevel-workflow@bevel.software';
|
|
|
34
34
|
* we keep the new path and discard the old one.
|
|
35
35
|
*/
|
|
36
36
|
export function parsePorcelainZ(stdout) {
|
|
37
|
+
return parsePorcelainZDetailed(stdout).paths;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Like {@link parsePorcelainZ}, but also reports each rename/copy entry's
|
|
41
|
+
* OLD path (keyed by the new path). A pathspec-scoped commit needs the old
|
|
42
|
+
* path too: committing only the new path of a staged rename records an ADD
|
|
43
|
+
* and leaves the deletion of the old path behind — the rename becomes two
|
|
44
|
+
* files.
|
|
45
|
+
*/
|
|
46
|
+
export function parsePorcelainZDetailed(stdout) {
|
|
37
47
|
const tokens = stdout.split('\0').filter((t) => t.length > 0);
|
|
38
48
|
const paths = new Set();
|
|
49
|
+
const renameOldByNew = new Map();
|
|
39
50
|
let i = 0;
|
|
40
51
|
while (i < tokens.length) {
|
|
41
52
|
const token = tokens[i];
|
|
@@ -46,12 +57,21 @@ export function parsePorcelainZ(stdout) {
|
|
|
46
57
|
}
|
|
47
58
|
const x = token[0];
|
|
48
59
|
const y = token[1];
|
|
49
|
-
|
|
60
|
+
const newPath = token.slice(3);
|
|
61
|
+
paths.add(newPath);
|
|
50
62
|
// Rename or copy in either the index or worktree position carries an
|
|
51
|
-
// extra NUL-separated old-path field
|
|
52
|
-
|
|
63
|
+
// extra NUL-separated old-path field.
|
|
64
|
+
if (x === 'R' || x === 'C' || y === 'R' || y === 'C') {
|
|
65
|
+
const oldPath = tokens[i + 1];
|
|
66
|
+
if (oldPath)
|
|
67
|
+
renameOldByNew.set(newPath, oldPath);
|
|
68
|
+
i += 2;
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
i += 1;
|
|
72
|
+
}
|
|
53
73
|
}
|
|
54
|
-
return Array.from(paths);
|
|
74
|
+
return { paths: Array.from(paths), renameOldByNew };
|
|
55
75
|
}
|
|
56
76
|
/**
|
|
57
77
|
* Default commit subject used when a caller (typically `LockingFilesystem`'s
|
|
@@ -839,20 +859,26 @@ export class GitService {
|
|
|
839
859
|
});
|
|
840
860
|
}
|
|
841
861
|
/**
|
|
842
|
-
* Atomic multi-file commit — commits
|
|
843
|
-
*
|
|
844
|
-
*
|
|
845
|
-
*
|
|
846
|
-
*
|
|
847
|
-
*
|
|
862
|
+
* Atomic multi-file commit — commits the caller's batch as ONE commit
|
|
863
|
+
* attributed to `user`, then returns its attribution (or null when nothing
|
|
864
|
+
* in scope is dirty — idempotent re-apply). The multi-file sibling of
|
|
865
|
+
* `commit()`, WITHOUT the one-file-per-change guard — the caller has
|
|
866
|
+
* assembled + written + validated a batch (bulk node upload; the lock-aware
|
|
867
|
+
* saves and admin roster writes via `LockingFilesystem` /
|
|
868
|
+
* `AdminLockedCommits`).
|
|
869
|
+
*
|
|
870
|
+
* `onlyPaths` (workspace-relative) scopes staging + commit to the caller's
|
|
871
|
+
* own files via git pathspecs: on the shared per-branch workspace other
|
|
872
|
+
* paths may be dirty from a concurrent save whose commit is still queued,
|
|
873
|
+
* and sweeping them in would land them under this caller's author/summary.
|
|
874
|
+
* Omitted → the whole dirty set (`git add -A`), for flows that own the
|
|
875
|
+
* workspace.
|
|
848
876
|
*
|
|
849
877
|
* Deliberately does NOT write file content itself — disk writes are the
|
|
850
|
-
* caller's job. This is the git layer: it only stages + commits what is
|
|
851
|
-
* on disk
|
|
852
|
-
* `git add -A` stages exactly the caller's batch). Caller pushes separately
|
|
853
|
-
* (mirrors `commitFile` + `push`).
|
|
878
|
+
* caller's job. This is the git layer: it only stages + commits what is
|
|
879
|
+
* already on disk. Caller pushes separately (mirrors `commitFile` + `push`).
|
|
854
880
|
*/
|
|
855
|
-
async commitChanges(workspaceId, user, summary) {
|
|
881
|
+
async commitChanges(workspaceId, user, summary, onlyPaths) {
|
|
856
882
|
assertValidAuthor(user);
|
|
857
883
|
const subject = summary?.trim();
|
|
858
884
|
if (!subject)
|
|
@@ -866,8 +892,28 @@ export class GitService {
|
|
|
866
892
|
const branch = await this.currentBranch(cwd);
|
|
867
893
|
// Nothing dirty → no-op (idempotent). Compute touched BEFORE `git add` so
|
|
868
894
|
// the protected-branch gate sees the same path set `commit()` would.
|
|
869
|
-
|
|
870
|
-
|
|
895
|
+
// `--untracked-files=all` lists every untracked FILE individually —
|
|
896
|
+
// without it a brand-new directory shows as one `?? dir/` entry, so a
|
|
897
|
+
// scoped batch creating files under a new folder would match nothing
|
|
898
|
+
// against `onlyPaths` and silently no-op.
|
|
899
|
+
const { stdout: porcelain } = await this.git(cwd, ['status', '--porcelain=v1', '-z', '--untracked-files=all']);
|
|
900
|
+
const detailed = parsePorcelainZDetailed(porcelain);
|
|
901
|
+
let touched = detailed.paths;
|
|
902
|
+
// Scope to the caller's own paths (see the interface doc): other files
|
|
903
|
+
// may be dirty from a concurrent same-branch save whose commit is still
|
|
904
|
+
// queued — sweeping them in would land them under this caller's
|
|
905
|
+
// author/summary, and gate the commit on paths the caller never touched.
|
|
906
|
+
// An in-scope RENAME keeps its old path too: a pathspec naming only the
|
|
907
|
+
// new path records an add and strands the deletion of the old one.
|
|
908
|
+
if (onlyPaths) {
|
|
909
|
+
const scope = new Set(onlyPaths.map((p) => this.stripRepoPrefix(p)));
|
|
910
|
+
touched = touched.filter((p) => scope.has(p));
|
|
911
|
+
for (const p of touched.slice()) {
|
|
912
|
+
const oldPath = detailed.renameOldByNew.get(p);
|
|
913
|
+
if (oldPath && !touched.includes(oldPath))
|
|
914
|
+
touched.push(oldPath);
|
|
915
|
+
}
|
|
916
|
+
}
|
|
871
917
|
if (touched.length === 0)
|
|
872
918
|
return null;
|
|
873
919
|
// Protected-branch access gate (defence in depth — callers already gate at
|
|
@@ -876,19 +922,85 @@ export class GitService {
|
|
|
876
922
|
if (isProtectedBranch(branch)) {
|
|
877
923
|
await this.assertCanWriteAtRef(workspaceId, 'HEAD', user.email, touched);
|
|
878
924
|
}
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
925
|
+
// Scoped → stage the batch (a brand-new file must be `add`ed before a
|
|
926
|
+
// pathspec commit will know it), then commit WITH the pathspec: it
|
|
927
|
+
// records exactly those paths, so neither other dirty files nor an
|
|
928
|
+
// unrelated entry someone left staged can ride along.
|
|
929
|
+
//
|
|
930
|
+
// Both stages feed the pathspecs over STDIN (`--pathspec-from-file=-`,
|
|
931
|
+
// NUL-separated) instead of argv: a several-hundred-file batch of long
|
|
932
|
+
// KB paths would otherwise blow Windows' ~32K command-line limit. The
|
|
933
|
+
// add is ONE spawn for the whole batch; only when git reports the ONE
|
|
934
|
+
// expected miss — a fully-staged deletion (or the old half of a rename)
|
|
935
|
+
// has nothing left in the worktree to match, which fails the entire
|
|
936
|
+
// batched add — do we fall back to per-path adds with the same
|
|
937
|
+
// tolerance, in argv-safe chunks of one path per spawn.
|
|
938
|
+
if (onlyPaths) {
|
|
939
|
+
const pathspecInput = touched.join('\0');
|
|
940
|
+
const pathspecArgs = ['--pathspec-from-file=-', '--pathspec-file-nul'];
|
|
941
|
+
const isExpectedMiss = (err) => {
|
|
942
|
+
// (LC_ALL=C pins the message to English.)
|
|
943
|
+
const e = err;
|
|
944
|
+
return /did not match any files/.test(`${e.stderr ?? ''}\n${e.message}`);
|
|
945
|
+
};
|
|
946
|
+
try {
|
|
947
|
+
await this.git(cwd, ['add', '-A', ...pathspecArgs], { input: pathspecInput });
|
|
948
|
+
}
|
|
949
|
+
catch (err) {
|
|
950
|
+
// Any other add failure — index.lock contention, I/O — must abort:
|
|
951
|
+
// committing without staging would record stale index content as
|
|
952
|
+
// this caller's change.
|
|
953
|
+
if (!isExpectedMiss(err))
|
|
954
|
+
throw err;
|
|
955
|
+
for (const p of touched) {
|
|
956
|
+
try {
|
|
957
|
+
await this.git(cwd, ['add', '-A', '--', p]);
|
|
958
|
+
}
|
|
959
|
+
catch (perPathErr) {
|
|
960
|
+
if (!isExpectedMiss(perPathErr))
|
|
961
|
+
throw perPathErr;
|
|
962
|
+
}
|
|
963
|
+
}
|
|
964
|
+
}
|
|
965
|
+
await this.git(cwd, ['commit', `--author=${user.name} <${user.email}>`, '-m', subject, ...pathspecArgs], { input: pathspecInput });
|
|
966
|
+
}
|
|
967
|
+
else {
|
|
968
|
+
await this.git(cwd, ['add', '-A']);
|
|
969
|
+
await this.git(cwd, ['commit', `--author=${user.name} <${user.email}>`, '-m', subject]);
|
|
970
|
+
}
|
|
971
|
+
// CONTRACT: past this point the commit EXISTS, so this method must not
|
|
972
|
+
// throw — callers (writeAndCommitLocked) treat a throw as "nothing was
|
|
973
|
+
// committed" and restore their pre-edit bytes, which here would publish
|
|
974
|
+
// a compensating revert of a commit that DID land. The attribution read
|
|
975
|
+
// is best-effort; on failure fall back to what we already know.
|
|
976
|
+
let sha = '';
|
|
977
|
+
let authorName = user.name;
|
|
978
|
+
let authorEmail = user.email;
|
|
979
|
+
let subj = subject;
|
|
980
|
+
let committedAt = '';
|
|
981
|
+
try {
|
|
982
|
+
const { stdout } = await this.git(cwd, [
|
|
983
|
+
'log', '-1', '--pretty=format:%H%x00%an%x00%ae%x00%s%x00%aI',
|
|
984
|
+
]);
|
|
985
|
+
[sha, authorName, authorEmail, subj, committedAt] = stdout.split('\x00');
|
|
986
|
+
}
|
|
987
|
+
catch {
|
|
988
|
+
// The commit landed; only its attribution read failed. Give the sha
|
|
989
|
+
// one simpler second chance — callers use it as the commit id, and an
|
|
990
|
+
// empty sha would make a landed change unidentifiable downstream.
|
|
991
|
+
try {
|
|
992
|
+
const { stdout } = await this.git(cwd, ['rev-parse', 'HEAD']);
|
|
993
|
+
sha = stdout;
|
|
994
|
+
}
|
|
995
|
+
catch { /* keep the fallback attribution */ }
|
|
996
|
+
}
|
|
885
997
|
this.accessControl?.invalidate(workspaceId);
|
|
886
998
|
return {
|
|
887
999
|
sha: sha?.trim() ?? '',
|
|
888
1000
|
authorName: authorName ?? '',
|
|
889
1001
|
authorEmail: authorEmail ?? '',
|
|
890
1002
|
subject: subj ?? subject,
|
|
891
|
-
committedAt: committedAt?.trim()
|
|
1003
|
+
committedAt: committedAt?.trim() || new Date().toISOString(),
|
|
892
1004
|
};
|
|
893
1005
|
});
|
|
894
1006
|
}
|
|
@@ -1289,19 +1401,27 @@ export class GitService {
|
|
|
1289
1401
|
* Used by the pending-commits worker's no-op arm: a clean tree does NOT
|
|
1290
1402
|
* mean "nothing to share" when a prior best-effort push (the autosave
|
|
1291
1403
|
* path) failed and left the committed change stranded locally.
|
|
1404
|
+
*
|
|
1405
|
+
* Serialized under the workspace mutex: a pull-rebase in flight moves HEAD
|
|
1406
|
+
* through states where the local commits are momentarily unreachable, and
|
|
1407
|
+
* an unserialized read there answers "nothing unpushed" about work that is
|
|
1408
|
+
* about to be replayed — callers use this as a publication PROOF, so it may
|
|
1409
|
+
* only ever see settled states.
|
|
1292
1410
|
*/
|
|
1293
1411
|
async hasUnpushedCommits(workspaceId) {
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1412
|
+
return this.mutex.run(workspaceId, async () => {
|
|
1413
|
+
const cwd = await this.repoDir(workspaceId);
|
|
1414
|
+
const branch = await this.currentBranch(cwd);
|
|
1415
|
+
try {
|
|
1416
|
+
const { stdout } = await this.git(cwd, [
|
|
1417
|
+
'rev-list', '--count', `refs/remotes/origin/${branch}..HEAD`,
|
|
1418
|
+
]);
|
|
1419
|
+
return Number(stdout.trim()) > 0;
|
|
1420
|
+
}
|
|
1421
|
+
catch {
|
|
1422
|
+
return true;
|
|
1423
|
+
}
|
|
1424
|
+
});
|
|
1305
1425
|
}
|
|
1306
1426
|
/**
|
|
1307
1427
|
* Hard-reset the workspace's checked-out branch to `origin/<branch>`, fetching
|
|
@@ -2010,9 +2130,9 @@ export class GitService {
|
|
|
2010
2130
|
]);
|
|
2011
2131
|
return stdout.trim().length > 0;
|
|
2012
2132
|
}
|
|
2013
|
-
async git(cwd, args) {
|
|
2133
|
+
async git(cwd, args, opts) {
|
|
2014
2134
|
try {
|
|
2015
|
-
const
|
|
2135
|
+
const pending = execFileAsync('git', args, {
|
|
2016
2136
|
cwd,
|
|
2017
2137
|
// `GIT_LITERAL_PATHSPECS=1` makes git treat every pathspec literally
|
|
2018
2138
|
// instead of interpreting `[`, `]`, `*`, `?`, `!`, or `:(magic)` as
|
|
@@ -2033,6 +2153,14 @@ export class GitService {
|
|
|
2033
2153
|
env: { ...process.env, GIT_LITERAL_PATHSPECS: '1', LC_ALL: 'C', LANG: 'C' },
|
|
2034
2154
|
maxBuffer: 32 * 1024 * 1024,
|
|
2035
2155
|
});
|
|
2156
|
+
if (opts?.input !== undefined && pending.child.stdin) {
|
|
2157
|
+
// A dying git can close stdin mid-write; the promise below still
|
|
2158
|
+
// rejects with the exit code, which is the error we want to surface.
|
|
2159
|
+
pending.child.stdin.on('error', () => undefined);
|
|
2160
|
+
pending.child.stdin.write(opts.input);
|
|
2161
|
+
pending.child.stdin.end();
|
|
2162
|
+
}
|
|
2163
|
+
const { stdout, stderr } = await pending;
|
|
2036
2164
|
return { stdout: stdout.toString(), stderr: stderr.toString() };
|
|
2037
2165
|
}
|
|
2038
2166
|
catch (err) {
|