@gitkraken/core-gitlens 0.25.0 → 0.26.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/CHANGELOG.md +14 -1
- package/dist/git/providers/operations.d.ts +15 -0
- package/dist/git/watching/worktreeSummary.d.ts +15 -3
- package/dist/git/watching/worktreeSummary.js +179 -31
- package/dist/git/watching/worktreeSummary.js.map +1 -1
- package/dist/git-cli/cliGitProvider.d.ts +16 -0
- package/dist/git-cli/cliGitProvider.js +31 -6
- package/dist/git-cli/cliGitProvider.js.map +1 -1
- package/dist/git-cli/exec/git.d.ts +2 -0
- package/dist/git-cli/exec/git.js +4 -0
- package/dist/git-cli/exec/git.js.map +1 -1
- package/dist/git-cli/exec/gitDirResolver.d.ts +26 -0
- package/dist/git-cli/exec/gitDirResolver.js +181 -0
- package/dist/git-cli/exec/gitDirResolver.js.map +1 -0
- package/dist/git-cli/exec/headState.js +3 -3
- package/dist/git-cli/exec/headState.js.map +1 -1
- package/dist/git-cli/exec/indexRefresh.d.ts +78 -0
- package/dist/git-cli/exec/indexRefresh.js +159 -0
- package/dist/git-cli/exec/indexRefresh.js.map +1 -0
- package/dist/git-cli/providers/config.d.ts +2 -0
- package/dist/git-cli/providers/config.js +22 -3
- package/dist/git-cli/providers/config.js.map +1 -1
- package/dist/git-cli/providers/maintenance.js +1 -1
- package/dist/git-cli/providers/operations.d.ts +4 -1
- package/dist/git-cli/providers/operations.js +33 -25
- package/dist/git-cli/providers/operations.js.map +1 -1
- package/dist/git-cli/providers/pausedOperations.d.ts +3 -1
- package/dist/git-cli/providers/pausedOperations.js +10 -7
- package/dist/git-cli/providers/pausedOperations.js.map +1 -1
- package/dist/git-cli/providers/refs.js +25 -1
- package/dist/git-cli/providers/refs.js.map +1 -1
- package/dist/git-cli/providers/staging.js +1 -1
- package/dist/git-cli/providers/stash.d.ts +3 -1
- package/dist/git-cli/providers/stash.js +8 -5
- package/dist/git-cli/providers/stash.js.map +1 -1
- package/dist/git-cli/providers/worktrees.d.ts +6 -1
- package/dist/git-cli/providers/worktrees.js +6 -2
- package/dist/git-cli/providers/worktrees.js.map +1 -1
- package/dist/git-cli/service.d.ts +2 -0
- package/dist/git-cli/service.js +2 -1
- package/dist/git-cli/service.js.map +1 -1
- package/dist/ipc/discovery.js +1 -1
- package/dist/plus/agents/providers/claudeCodeTranscript.js +6 -6
- package/dist/plus/agents/providers/claudeCodeTranscript.js.map +1 -1
- package/dist/plus/ai/providers/openAICompatibleProviderBase.js +1 -1
- package/dist/plus/integrations/providers/azure/azure.js +1 -1
- package/dist/utils/env/node/exec.js +2 -2
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,18 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [0.26.0] - 2026-11-02
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- Adds `refreshIndexAfterWrites?: boolean` to `CliGitProviderOptions` and `createCliGitService`'s options, off by default. When on, `checkout`, `restore`, `merge`, `pull`, `reset`, `cherryPick`, `revert`, `rebase`, `createWorktree`, `clone`, stash apply, pop and push, and abort and continue of a paused operation refresh the checkout's index in the background when the write rewrote it, including one that stops on conflicts. Files written in the index's own second are "racily clean", so every later status or diff re-hashed them (~600 ms on 8,000 files) until something rewrote the index, which core's status reads (run with `GIT_OPTIONAL_LOCKS=0`) never do. Once the clock passes that second, one `git status --porcelain -uno --ignore-submodules=all` with optional locks on and rename detection off writes the refreshed index (~7 ms reads after). A write that left the index alone (already up to date, a soft reset) schedules none; writes to one checkout share one refresh. Each costs a background status of the whole checkout, worth it only where that checkout will be read again, so it is off by default (git-cli)
|
|
14
|
+
- Adds `scheduleIndexRefresh?(repoPath)` to `GitOperationsSubProvider` (`service.forRepo(path).ops?.scheduleIndexRefresh?.()`), for a host that rewrote a checkout through a raw git command: it joins the per-checkout background index refresh core's own writes schedule under `refreshIndexAfterWrites`, and runs whether or not that is on (git, git-cli)
|
|
15
|
+
|
|
16
|
+
### Changed
|
|
17
|
+
|
|
18
|
+
- Changes `getGitDir` to read the git dir and common git dir of a main or linked worktree from disk (its `.git` folder or file, `commondir`), instead of spawning `git rev-parse`, with the same answer. It still asks git for any other layout: a submodule, a bare repository, a worktree nested inside another repository's working tree (including its own main checkout), a `.git` file without `commondir`, `core.worktree`, `core.bare` or an `include` in its config, `GIT_DIR`/`GIT_COMMON_DIR`/`GIT_WORK_TREE`/`GIT_CEILING_DIRECTORIES` in git's environment, a folder owned by another user (not checked on Windows, where the process has no uid), or an untrusted workspace. Resolving N sibling worktrees now runs no git process (was N). `onGitDirResolveFailed` fires only when git is asked (git-cli)
|
|
19
|
+
- Changes `WorktreeSummaryService` to check a subscribed worktree's own branch, its upstream and the ahead/behind counts against it as core's cached `getBranch` answers them, and its base tips against core's cached ref list (one `for-each-ref` shared by the repository's checkouts), when a change may have moved them without naming them — a packed-refs rewrite (deleting a packed branch, `git pack-refs`), a ref directory, an overflow or a poll — and to read only the groups whose refs moved. Deleting a packed branch now costs each subscribed worktree no git process of its own (was ~6–8: `diff --quiet HEAD`, `ls-files --others`, `merge-base --fork-point` ×2, `@{u}`, `rev-list --left-right --count`). A base ref the check finds moved is re-measured rather than read in full (git)
|
|
20
|
+
|
|
9
21
|
## [0.25.0] - 2026-10-30
|
|
10
22
|
|
|
11
23
|
### Added
|
|
@@ -1048,7 +1060,8 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p
|
|
|
1048
1060
|
|
|
1049
1061
|
- Initial release. Bundles `@gitlens/utils`, `@gitlens/git`, `@gitlens/git-cli`, `@gitlens/ai`, and `@gitlens/git-github` into a single core npm package with subpath exports.
|
|
1050
1062
|
|
|
1051
|
-
[unreleased]: https://github.com/gitkraken/vscode-gitlens/compare/releases/core/v0.
|
|
1063
|
+
[unreleased]: https://github.com/gitkraken/vscode-gitlens/compare/releases/core/v0.26.0...HEAD
|
|
1064
|
+
[0.26.0]: https://github.com/gitkraken/vscode-gitlens/compare/releases/core/v0.25.0...gitkraken:releases/core/v0.26.0
|
|
1052
1065
|
[0.25.0]: https://github.com/gitkraken/vscode-gitlens/compare/releases/core/v0.24.0...gitkraken:releases/core/v0.25.0
|
|
1053
1066
|
[0.24.0]: https://github.com/gitkraken/vscode-gitlens/compare/releases/core/v0.23.0...gitkraken:releases/core/v0.24.0
|
|
1054
1067
|
[0.23.0]: https://github.com/gitkraken/vscode-gitlens/compare/releases/core/v0.22.1...gitkraken:releases/core/v0.23.0
|
|
@@ -149,6 +149,21 @@ export interface GitOperationsSubProvider {
|
|
|
149
149
|
editMessage?: boolean;
|
|
150
150
|
source?: unknown;
|
|
151
151
|
}, runOptions?: GitOperationRunOptions): Promise<GitOperationResult>;
|
|
152
|
+
/**
|
|
153
|
+
* Refreshes, in the background, the index of the checkout at `repoPath` once the clock has moved past the second it
|
|
154
|
+
* was written in, so that git trusts the timestamps of the files written alongside it. A write that rewrites many
|
|
155
|
+
* files (a clone, a worktree add, a branch switch, a fast-forward) leaves them all "racily clean", and every later
|
|
156
|
+
* status or diff re-hashes them until something rewrites the index, which core's own status reads (run with
|
|
157
|
+
* `GIT_OPTIONAL_LOCKS=0`) never do.
|
|
158
|
+
*
|
|
159
|
+
* With the git-cli provider's `refreshIndexAfterWrites` on, core's own `checkout`, `restore`, `merge`, `pull`,
|
|
160
|
+
* `reset`, `cherryPick`, `revert` and `rebase`, stash apply, pop and push, abort and continue of a paused operation,
|
|
161
|
+
* and worktree and clone writes do this themselves, including one that stops on conflicts, so only a host that
|
|
162
|
+
* rewrote the checkout through a raw git command needs to call it; it runs whether or not that option is on. Calls
|
|
163
|
+
* share one queue per checkout: one made while a refresh is waiting joins it, and one made while its status runs
|
|
164
|
+
* causes one more after it. Never throws.
|
|
165
|
+
*/
|
|
166
|
+
scheduleIndexRefresh?(repoPath: string): void;
|
|
152
167
|
}
|
|
153
168
|
//#endregion
|
|
154
169
|
//# sourceMappingURL=operations.d.ts.map
|
|
@@ -159,9 +159,13 @@ export interface WorktreeSummaryServiceOptions {
|
|
|
159
159
|
* inputs its last base read consulted (every branch's tracking but those of branches it didn't depend on), the stored
|
|
160
160
|
* values, HEAD's commit or the upstream changed, unless it was confined to the
|
|
161
161
|
* `branch.<name>` sections of branches the summary neither is on, tracks nor bases on (its `configBranches`), which
|
|
162
|
-
* reads nothing; the checked-out branch moving, or a HEAD change, reads all of them.
|
|
163
|
-
*
|
|
164
|
-
*
|
|
162
|
+
* reads nothing; the checked-out branch moving, or a HEAD change, reads all of them. A change that may have moved one
|
|
163
|
+
* of those refs without naming it (a packed-refs rewrite, as deleting a packed branch makes, a ref directory, an
|
|
164
|
+
* overflow or a poll) first checks HEAD, the upstream and the counts against it as core's cached `getBranch` answers
|
|
165
|
+
* them, and the tips the base was read from against core's cached ref list, both shared by the repository's
|
|
166
|
+
* checkouts, and reads only the groups that moved, by the same rules — nothing when none did.
|
|
167
|
+
* Stash, unrelated tags and branches, and the like read nothing. One run is in flight per worktree at a time, at most
|
|
168
|
+
* four per repository (the worktrees sharing a common git directory), and at most eight across the service. Runs take turns first come, first
|
|
165
169
|
* served, except that one whose repository is at its cap lets later runs of other repositories go first, so a burst in
|
|
166
170
|
* one repository never delays another's reads: events while a run waits for its turn join it, and events during a run
|
|
167
171
|
* are merged into a single follow-up run.
|
|
@@ -251,6 +255,14 @@ export declare class WorktreeSummaryService implements UnifiedDisposable {
|
|
|
251
255
|
* the `HEAD` symrefs of `remotes` (every remote's when `undefined`); without `statIndex`, the index is unknown
|
|
252
256
|
*/
|
|
253
257
|
private probeUnwatched;
|
|
258
|
+
/**
|
|
259
|
+
* A subscribed run's {@link ReadProbe}, from cached answers: HEAD when the branch is due or a tip check is, the ref
|
|
260
|
+
* tips (kept in `probed`), and the `HEAD` symrefs of the remotes the base depends on when the base may be read or a
|
|
261
|
+
* tip check is due — every remote's for a first read, which can come to depend on any. A tip check widens `fields` as
|
|
262
|
+
* {@link getTipCheckFields} finds, and is read in full when the probe fails; any other probe that fails leaves
|
|
263
|
+
* the tips its groups read unknown.
|
|
264
|
+
*/
|
|
265
|
+
private probeRead;
|
|
254
266
|
getResourceUsage(): ResourceUsage;
|
|
255
267
|
private start;
|
|
256
268
|
/**
|
|
@@ -20,6 +20,14 @@ const baseConfigField = 16;
|
|
|
20
20
|
* the base's own ref just moved forward, it is re-measured rather than resolved again. {@link baseField} overrides it.
|
|
21
21
|
*/
|
|
22
22
|
const baseMoveField = 32;
|
|
23
|
+
/**
|
|
24
|
+
* Checks the summary against what a probe finds now, for a change that may have moved one of its refs without naming it
|
|
25
|
+
* (a packed-refs rewrite, a ref directory, an overflow or a poll): HEAD, the upstream and the counts against it as
|
|
26
|
+
* `getBranch` answers them, and the refs the base depends on against the tips it was read from (see {@link ReadTips});
|
|
27
|
+
* reads the groups that moved, as an unwatched read would — everything when HEAD's branch or commit did — and nothing
|
|
28
|
+
* when none did
|
|
29
|
+
*/
|
|
30
|
+
const tipCheckField = 64;
|
|
23
31
|
/** Every group a full read covers — a base read refreshes the {@link BaseKeys} snapshot itself */
|
|
24
32
|
const allFields = 7;
|
|
25
33
|
const maxConcurrentReadsPerRepository = 4;
|
|
@@ -27,6 +35,7 @@ const maxConcurrentReads = 8;
|
|
|
27
35
|
const maxUnwatchedReads = 256;
|
|
28
36
|
const maxDeferredDirtyMs = 1e4;
|
|
29
37
|
const emptyRefs = Object.freeze([]);
|
|
38
|
+
const noRemotes = /* @__PURE__ */ new Set();
|
|
30
39
|
const noBaseKeys = [
|
|
31
40
|
void 0,
|
|
32
41
|
void 0,
|
|
@@ -125,9 +134,13 @@ var SummarySubscription = class {
|
|
|
125
134
|
* inputs its last base read consulted (every branch's tracking but those of branches it didn't depend on), the stored
|
|
126
135
|
* values, HEAD's commit or the upstream changed, unless it was confined to the
|
|
127
136
|
* `branch.<name>` sections of branches the summary neither is on, tracks nor bases on (its `configBranches`), which
|
|
128
|
-
* reads nothing; the checked-out branch moving, or a HEAD change, reads all of them.
|
|
129
|
-
*
|
|
130
|
-
*
|
|
137
|
+
* reads nothing; the checked-out branch moving, or a HEAD change, reads all of them. A change that may have moved one
|
|
138
|
+
* of those refs without naming it (a packed-refs rewrite, as deleting a packed branch makes, a ref directory, an
|
|
139
|
+
* overflow or a poll) first checks HEAD, the upstream and the counts against it as core's cached `getBranch` answers
|
|
140
|
+
* them, and the tips the base was read from against core's cached ref list, both shared by the repository's
|
|
141
|
+
* checkouts, and reads only the groups that moved, by the same rules — nothing when none did.
|
|
142
|
+
* Stash, unrelated tags and branches, and the like read nothing. One run is in flight per worktree at a time, at most
|
|
143
|
+
* four per repository (the worktrees sharing a common git directory), and at most eight across the service. Runs take turns first come, first
|
|
131
144
|
* served, except that one whose repository is at its cap lets later runs of other repositories go first, so a burst in
|
|
132
145
|
* one repository never delays another's reads: events while a run waits for its turn join it, and events during a run
|
|
133
146
|
* are merged into a single follow-up run.
|
|
@@ -370,13 +383,11 @@ var WorktreeSummaryService = class {
|
|
|
370
383
|
* the `HEAD` symrefs of `remotes` (every remote's when `undefined`); without `statIndex`, the index is unknown
|
|
371
384
|
*/
|
|
372
385
|
async probeUnwatched(repo, remotes, statIndex, signal) {
|
|
373
|
-
const remoteHeads = /* @__PURE__ */ new Map();
|
|
374
386
|
const stat = statIndex ? this.stat : void 0;
|
|
375
|
-
const [current,
|
|
387
|
+
const [current, refs, gitDir] = await Promise.all([
|
|
376
388
|
repo.branches.getBranch(void 0, void 0, signal),
|
|
377
|
-
repo
|
|
378
|
-
stat != null ? getGitDir(repo) : void 0
|
|
379
|
-
readRemoteHeads(repo, remoteHeads, remotes, signal)
|
|
389
|
+
probeRefs(repo, remotes, signal),
|
|
390
|
+
stat != null ? getGitDir(repo) : void 0
|
|
380
391
|
]);
|
|
381
392
|
let index;
|
|
382
393
|
if (stat != null && gitDir != null) try {
|
|
@@ -387,15 +398,59 @@ var WorktreeSummaryService = class {
|
|
|
387
398
|
}
|
|
388
399
|
const detached = current?.detached ?? false;
|
|
389
400
|
return {
|
|
401
|
+
current,
|
|
390
402
|
branch: detached ? void 0 : current?.name || void 0,
|
|
391
403
|
detached,
|
|
392
404
|
sha: current?.sha,
|
|
393
405
|
upstream: current?.upstream?.name,
|
|
394
|
-
tips:
|
|
395
|
-
remoteHeads,
|
|
406
|
+
tips: refs.tips,
|
|
407
|
+
remoteHeads: refs.remoteHeads,
|
|
396
408
|
index
|
|
397
409
|
};
|
|
398
410
|
}
|
|
411
|
+
/**
|
|
412
|
+
* A subscribed run's {@link ReadProbe}, from cached answers: HEAD when the branch is due or a tip check is, the ref
|
|
413
|
+
* tips (kept in `probed`), and the `HEAD` symrefs of the remotes the base depends on when the base may be read or a
|
|
414
|
+
* tip check is due — every remote's for a first read, which can come to depend on any. A tip check widens `fields` as
|
|
415
|
+
* {@link getTipCheckFields} finds, and is read in full when the probe fails; any other probe that fails leaves
|
|
416
|
+
* the tips its groups read unknown.
|
|
417
|
+
*/
|
|
418
|
+
async probeRead(repo, previous, tips, fields, baseMoves, probed, signal) {
|
|
419
|
+
const check = (fields & tipCheckField) !== 0 && previous != null && (fields & allFields) !== allFields;
|
|
420
|
+
fields &= -65;
|
|
421
|
+
const remotes = previous == null ? void 0 : check || (fields & 60) !== 0 ? getHeadRemotes(previous.baseDependsOn) : noRemotes;
|
|
422
|
+
try {
|
|
423
|
+
if (!check && (fields & branchField) === 0) {
|
|
424
|
+
probed.refs = await probeRefs(repo, remotes, signal);
|
|
425
|
+
return {
|
|
426
|
+
fields,
|
|
427
|
+
baseMoves
|
|
428
|
+
};
|
|
429
|
+
}
|
|
430
|
+
const probe = await this.probeUnwatched(repo, remotes, false, signal);
|
|
431
|
+
probed.refs = probe;
|
|
432
|
+
const head = { branch: probe.current };
|
|
433
|
+
if (!check) return {
|
|
434
|
+
fields,
|
|
435
|
+
baseMoves,
|
|
436
|
+
head
|
|
437
|
+
};
|
|
438
|
+
const moved = getTipCheckFields(probe, previous, tips);
|
|
439
|
+
if (moved.baseMoves != null && baseMoves != null) for (const ref of baseMoves) moved.baseMoves.add(ref);
|
|
440
|
+
return {
|
|
441
|
+
fields: fields | moved.fields,
|
|
442
|
+
baseMoves: moved.baseMoves ?? baseMoves,
|
|
443
|
+
head
|
|
444
|
+
};
|
|
445
|
+
} catch (ex) {
|
|
446
|
+
if (isCancellationError(ex)) throw ex;
|
|
447
|
+
probed.refs = void 0;
|
|
448
|
+
return {
|
|
449
|
+
fields: check ? fields | allFields : fields,
|
|
450
|
+
baseMoves
|
|
451
|
+
};
|
|
452
|
+
}
|
|
453
|
+
}
|
|
399
454
|
getResourceUsage() {
|
|
400
455
|
return { "worktreeSummary.entries.count": this.entries.size };
|
|
401
456
|
}
|
|
@@ -424,6 +479,7 @@ var WorktreeSummaryService = class {
|
|
|
424
479
|
entry.baseKeys = last.result.baseKeys;
|
|
425
480
|
entry.baseConfigKey = last.result.baseConfigKey;
|
|
426
481
|
entry.baseDefaultBranch = last.result.baseDefaultBranch;
|
|
482
|
+
entry.tips = { base: last.inputs.baseTips };
|
|
427
483
|
} else entry.pending |= 23;
|
|
428
484
|
entry.handle = handle;
|
|
429
485
|
entry.slotsKey = getRepositoryKey(gitDir.commonUri ?? gitDir.uri);
|
|
@@ -501,17 +557,23 @@ var WorktreeSummaryService = class {
|
|
|
501
557
|
if (e.changed("index", "ignores")) fields |= dirtyField;
|
|
502
558
|
if (e.changed("config") && e.mayHaveChangedConfig(getConfigRelevantRefs(value))) fields |= 26;
|
|
503
559
|
if (e.changed("gkConfig")) fields |= baseKeysField;
|
|
504
|
-
if (value.branch != null
|
|
505
|
-
|
|
560
|
+
if (value.branch != null) {
|
|
561
|
+
const ref = `refs/heads/${value.branch}`;
|
|
562
|
+
if (e.mayHaveChangedRef(ref)) fields |= isNamedRef(e, ref) ? allFields : tipCheckField;
|
|
563
|
+
}
|
|
564
|
+
if (value.upstream != null) {
|
|
565
|
+
const remoteRef = `refs/remotes/${value.upstream.name}`;
|
|
566
|
+
if (e.mayHaveChangedRef(remoteRef)) fields |= isNamedRef(e, remoteRef) ? branchField : tipCheckField;
|
|
567
|
+
const localRef = `refs/heads/${value.upstream.name}`;
|
|
568
|
+
if (e.mayHaveChangedRef(localRef)) fields |= isNamedRef(e, localRef) ? branchField : tipCheckField;
|
|
569
|
+
}
|
|
506
570
|
let baseMoves;
|
|
507
571
|
if ((fields & baseField) === 0) {
|
|
508
572
|
for (const ref of value.baseDependsOn) {
|
|
509
573
|
if (!e.mayHaveChangedRef(ref)) continue;
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
baseMoves = void 0;
|
|
514
|
-
break;
|
|
574
|
+
if (!isNamedRef(e, ref)) {
|
|
575
|
+
fields |= tipCheckField;
|
|
576
|
+
continue;
|
|
515
577
|
}
|
|
516
578
|
baseMoves ??= [];
|
|
517
579
|
baseMoves.push(ref);
|
|
@@ -612,13 +674,16 @@ var WorktreeSummaryService = class {
|
|
|
612
674
|
return true;
|
|
613
675
|
}
|
|
614
676
|
async run(entry, fields, baseMoves) {
|
|
615
|
-
const { value, seed } = entry;
|
|
677
|
+
const { value, seed, tips } = entry;
|
|
616
678
|
const previous = value ?? seed;
|
|
679
|
+
if (previous?.stale) fields |= 23;
|
|
680
|
+
const probed = {};
|
|
617
681
|
let result;
|
|
618
682
|
try {
|
|
619
683
|
const repo = this.getRepository(entry.key);
|
|
620
684
|
if (repo == null) throw new Error(`No repository found for '${entry.key}'`);
|
|
621
|
-
|
|
685
|
+
const signal = entry.abort.signal;
|
|
686
|
+
result = await readSummary(repo, entry.key, fields, previous, entry.baseKeys, entry.baseConfigKey, entry.baseDefaultBranch, baseMoves, signal, this.verifyBaseRemeasure, (due, moves) => this.probeRead(repo, previous, tips, due, moves, probed, signal));
|
|
622
687
|
} catch (ex) {
|
|
623
688
|
if (entry.disposed || isCancellationError(ex)) return;
|
|
624
689
|
this.fail(entry, ex);
|
|
@@ -628,6 +693,7 @@ var WorktreeSummaryService = class {
|
|
|
628
693
|
if (result.summary.stale && (previous == null || !previous.stale)) _logger.warn(`WorktreeSummaryService: reading the base of '${entry.key}' failed: ${String(result.baseError)}`);
|
|
629
694
|
entry.error = void 0;
|
|
630
695
|
entry.seed = void 0;
|
|
696
|
+
entry.tips = getReadTips(tips, probed.refs, result);
|
|
631
697
|
entry.baseKeys = result.baseKeys;
|
|
632
698
|
entry.baseConfigKey = entry.pending & baseConfigField ? void 0 : result.baseConfigKey;
|
|
633
699
|
entry.baseDefaultBranch = result.baseDefaultBranch;
|
|
@@ -687,6 +753,7 @@ var WorktreeSummaryService = class {
|
|
|
687
753
|
entry.baseKeys = void 0;
|
|
688
754
|
entry.baseConfigKey = void 0;
|
|
689
755
|
entry.baseDefaultBranch = void 0;
|
|
756
|
+
entry.tips = void 0;
|
|
690
757
|
entry.pending = 0;
|
|
691
758
|
entry.baseMoves = void 0;
|
|
692
759
|
entry.running = void 0;
|
|
@@ -709,6 +776,7 @@ function dropSeed(entry) {
|
|
|
709
776
|
entry.baseKeys = void 0;
|
|
710
777
|
entry.baseConfigKey = void 0;
|
|
711
778
|
entry.baseDefaultBranch = void 0;
|
|
779
|
+
entry.tips = void 0;
|
|
712
780
|
}
|
|
713
781
|
function clearDeferredDirty(entry) {
|
|
714
782
|
entry.dirtyReadAt = void 0;
|
|
@@ -730,6 +798,7 @@ function createEntry(key) {
|
|
|
730
798
|
baseKeys: void 0,
|
|
731
799
|
baseConfigKey: void 0,
|
|
732
800
|
baseDefaultBranch: void 0,
|
|
801
|
+
tips: void 0,
|
|
733
802
|
error: void 0,
|
|
734
803
|
pending: 0,
|
|
735
804
|
baseMoves: void 0,
|
|
@@ -747,7 +816,7 @@ function createEntry(key) {
|
|
|
747
816
|
* answer. A failed base read doesn't fail the rest: the summary comes back stale, keeping the previous
|
|
748
817
|
* base and `baseDependsOn` only while the branch is the same.
|
|
749
818
|
*/
|
|
750
|
-
async function readSummary(repo, key, fields, previous, previousBaseKeys, previousBaseConfigKey, previousBaseDefaultBranch, baseMoves, signal, verifyBaseRemeasure) {
|
|
819
|
+
async function readSummary(repo, key, fields, previous, previousBaseKeys, previousBaseConfigKey, previousBaseDefaultBranch, baseMoves, signal, verifyBaseRemeasure, probe) {
|
|
751
820
|
if (previous == null) fields |= allFields;
|
|
752
821
|
let dirtyReadAt;
|
|
753
822
|
const startDirtyRead = () => {
|
|
@@ -755,6 +824,12 @@ async function readSummary(repo, key, fields, previous, previousBaseKeys, previo
|
|
|
755
824
|
return readDirty(repo, signal);
|
|
756
825
|
};
|
|
757
826
|
let dirtyRead = fields & dirtyField ? startDirtyRead() : void 0;
|
|
827
|
+
let head;
|
|
828
|
+
if (probe != null && fields & 102) {
|
|
829
|
+
const probed = await probe(fields, baseMoves);
|
|
830
|
+
({fields, baseMoves, head} = probed);
|
|
831
|
+
if (fields & dirtyField) dirtyRead ??= startDirtyRead();
|
|
832
|
+
}
|
|
758
833
|
let branch = previous?.branch;
|
|
759
834
|
let detached = previous?.detached ?? false;
|
|
760
835
|
let sha = previous?.sha;
|
|
@@ -762,7 +837,7 @@ async function readSummary(repo, key, fields, previous, previousBaseKeys, previo
|
|
|
762
837
|
let ahead = previous?.ahead ?? 0;
|
|
763
838
|
let behind = previous?.behind ?? 0;
|
|
764
839
|
if (fields & branchField) {
|
|
765
|
-
const current = await repo.branches.getBranch(void 0, void 0, signal);
|
|
840
|
+
const current = head != null ? head.branch : await repo.branches.getBranch(void 0, void 0, signal);
|
|
766
841
|
detached = current?.detached ?? false;
|
|
767
842
|
branch = detached ? void 0 : current?.name || void 0;
|
|
768
843
|
sha = current?.sha;
|
|
@@ -867,7 +942,8 @@ async function readSummary(repo, key, fields, previous, previousBaseKeys, previo
|
|
|
867
942
|
baseConfigKey,
|
|
868
943
|
baseDefaultBranch,
|
|
869
944
|
baseError,
|
|
870
|
-
dirtyReadAt
|
|
945
|
+
dirtyReadAt,
|
|
946
|
+
fields
|
|
871
947
|
};
|
|
872
948
|
}
|
|
873
949
|
async function getGitDir(repo) {
|
|
@@ -878,6 +954,18 @@ async function getGitDir(repo) {
|
|
|
878
954
|
return;
|
|
879
955
|
}
|
|
880
956
|
}
|
|
957
|
+
/**
|
|
958
|
+
* Reads the ref tips and the `HEAD` symrefs of `remotes` (every remote's when `undefined`), as core's caches answer
|
|
959
|
+
* them: one ref list shared by the repository's worktrees
|
|
960
|
+
*/
|
|
961
|
+
async function probeRefs(repo, remotes, signal) {
|
|
962
|
+
const remoteHeads = /* @__PURE__ */ new Map();
|
|
963
|
+
const [tips] = await Promise.all([repo.refs.getRefTips(void 0, signal), readRemoteHeads(repo, remoteHeads, remotes, signal)]);
|
|
964
|
+
return {
|
|
965
|
+
tips: getTipsByName(tips),
|
|
966
|
+
remoteHeads
|
|
967
|
+
};
|
|
968
|
+
}
|
|
881
969
|
/** Each ref list's tips by full name, built once per list, which core's cache shares across a repository's worktrees */
|
|
882
970
|
const tipsByNameCache = /* @__PURE__ */ new WeakMap();
|
|
883
971
|
function getTipsByName(tips) {
|
|
@@ -901,14 +989,10 @@ async function getMovedFields(repo, last, probe, probeDirty, signal) {
|
|
|
901
989
|
const previous = last.result.summary;
|
|
902
990
|
if (probe.branch !== inputs.branch || probe.detached !== inputs.detached || probe.sha !== inputs.sha) fields |= allFields;
|
|
903
991
|
else {
|
|
992
|
+
fields |= getUpstreamFields(probe.current, previous);
|
|
904
993
|
if (probe.upstream !== inputs.upstream) fields |= 18;
|
|
905
994
|
else if (getUpstreamTips(probe.tips, probe.upstream) !== inputs.upstreamTips) fields |= branchField;
|
|
906
|
-
|
|
907
|
-
const value = getBaseInput(probe, ref);
|
|
908
|
-
if (value !== null && value === inputs.baseTips.get(ref)) continue;
|
|
909
|
-
baseMoves ??= /* @__PURE__ */ new Set();
|
|
910
|
-
baseMoves.add(ref);
|
|
911
|
-
}
|
|
995
|
+
baseMoves = getMovedBaseRefs(probe, previous.baseDependsOn, inputs.baseTips);
|
|
912
996
|
if (baseMoves != null) fields |= baseMoveField;
|
|
913
997
|
if ((fields & baseConfigField) === 0 && last.result.baseConfigKey != null && repo.branches.getAheadOfBase != null && (probe.detached || probe.branch != null)) {
|
|
914
998
|
const configKey = getBaseConfigKey(await readBaseConfig(repo, signal), previous);
|
|
@@ -921,6 +1005,61 @@ async function getMovedFields(repo, last, probe, probeDirty, signal) {
|
|
|
921
1005
|
baseMoves
|
|
922
1006
|
};
|
|
923
1007
|
}
|
|
1008
|
+
/**
|
|
1009
|
+
* The read groups a tip check (see {@link tipCheckField}) finds moved since `previous` was read, with the refs the base
|
|
1010
|
+
* depends on as `tips` has them: everything when HEAD's branch or commit isn't the one `previous` has, else those
|
|
1011
|
+
* {@link getUpstreamFields} finds, and the base's refs that moved
|
|
1012
|
+
*/
|
|
1013
|
+
function getTipCheckFields(probe, previous, tips) {
|
|
1014
|
+
if (probe.branch !== previous.branch || probe.detached !== previous.detached || probe.sha !== previous.sha) return {
|
|
1015
|
+
fields: allFields,
|
|
1016
|
+
baseMoves: void 0
|
|
1017
|
+
};
|
|
1018
|
+
let fields = getUpstreamFields(probe.current, previous);
|
|
1019
|
+
const baseMoves = getMovedBaseRefs(probe, previous.baseDependsOn, tips?.base);
|
|
1020
|
+
if (baseMoves != null) fields |= baseMoveField;
|
|
1021
|
+
return {
|
|
1022
|
+
fields,
|
|
1023
|
+
baseMoves
|
|
1024
|
+
};
|
|
1025
|
+
}
|
|
1026
|
+
/**
|
|
1027
|
+
* The read groups `current`, HEAD as `getBranch` answers it, finds moved in what `previous` has of the upstream: another
|
|
1028
|
+
* upstream reads the branch and the config inputs, which read the base again when the upstream's identity changed;
|
|
1029
|
+
* one gone or back, or other counts against it, the branch. Core drops its cached branches, counts and all, with its
|
|
1030
|
+
* ref list on any change to branches or remote-tracking refs, the changes a tip check follows, so a probe after one
|
|
1031
|
+
* answers them as of that change.
|
|
1032
|
+
*/
|
|
1033
|
+
function getUpstreamFields(current, previous) {
|
|
1034
|
+
const upstream = current?.upstream;
|
|
1035
|
+
if (upstream?.name !== previous.upstream?.name) return 18;
|
|
1036
|
+
if (upstream?.missing !== previous.upstream?.missing || (upstream?.state.ahead ?? 0) !== previous.ahead || (upstream?.state.behind ?? 0) !== previous.behind) return branchField;
|
|
1037
|
+
return 0;
|
|
1038
|
+
}
|
|
1039
|
+
/**
|
|
1040
|
+
* The refs of `baseDependsOn` that `probe` finds moved since `baseTips`; `undefined` when none did. A tip that's unknown
|
|
1041
|
+
* (`null`, or not kept) moved.
|
|
1042
|
+
*/
|
|
1043
|
+
function getMovedBaseRefs(probe, baseDependsOn, baseTips) {
|
|
1044
|
+
let baseMoves;
|
|
1045
|
+
for (const ref of baseDependsOn) {
|
|
1046
|
+
const value = getBaseInput(probe, ref);
|
|
1047
|
+
if (value !== null && baseTips?.has(ref) && value === baseTips.get(ref)) continue;
|
|
1048
|
+
baseMoves ??= /* @__PURE__ */ new Set();
|
|
1049
|
+
baseMoves.add(ref);
|
|
1050
|
+
}
|
|
1051
|
+
return baseMoves;
|
|
1052
|
+
}
|
|
1053
|
+
/**
|
|
1054
|
+
* The tips a subscribed run leaves its summary read from: from the refs its probe found (`refs`) when it read the base,
|
|
1055
|
+
* unknown when it read it without a probe, and else `last`'s
|
|
1056
|
+
*/
|
|
1057
|
+
function getReadTips(last, refs, result) {
|
|
1058
|
+
const { summary, fields } = result;
|
|
1059
|
+
let base = last?.base;
|
|
1060
|
+
if (fields & 36) base = refs != null && !summary.stale ? getBaseTips(refs, summary.baseDependsOn) : void 0;
|
|
1061
|
+
return { base };
|
|
1062
|
+
}
|
|
924
1063
|
/** The upstream's tips as a remote-tracking and as a local branch, since its short name doesn't say which */
|
|
925
1064
|
function getUpstreamTips(tips, upstream) {
|
|
926
1065
|
if (upstream == null) return void 0;
|
|
@@ -973,18 +1112,22 @@ function getBaseInput(probe, ref) {
|
|
|
973
1112
|
}
|
|
974
1113
|
/** The inputs `summary` was read from, as the probe before its read found them */
|
|
975
1114
|
function toUnwatchedInputs(probe, summary) {
|
|
976
|
-
const baseTips = /* @__PURE__ */ new Map();
|
|
977
|
-
for (const ref of summary.baseDependsOn) baseTips.set(ref, getBaseInput(probe, ref));
|
|
978
1115
|
return {
|
|
979
1116
|
branch: probe.branch,
|
|
980
1117
|
detached: probe.detached,
|
|
981
1118
|
sha: probe.sha,
|
|
982
1119
|
upstream: probe.upstream,
|
|
983
1120
|
upstreamTips: getUpstreamTips(probe.tips, probe.upstream),
|
|
984
|
-
baseTips,
|
|
1121
|
+
baseTips: getBaseTips(probe, summary.baseDependsOn),
|
|
985
1122
|
index: probe.index
|
|
986
1123
|
};
|
|
987
1124
|
}
|
|
1125
|
+
/** What `refs` found of each of `baseDependsOn` (see {@link getBaseInput}) */
|
|
1126
|
+
function getBaseTips(refs, baseDependsOn) {
|
|
1127
|
+
const baseTips = /* @__PURE__ */ new Map();
|
|
1128
|
+
for (const ref of baseDependsOn) baseTips.set(ref, getBaseInput(refs, ref));
|
|
1129
|
+
return baseTips;
|
|
1130
|
+
}
|
|
988
1131
|
async function readBaseConfig(repo, signal) {
|
|
989
1132
|
try {
|
|
990
1133
|
if (repo.branches.getAheadOfBaseConfigEntries != null) return await repo.branches.getAheadOfBaseConfigEntries(signal);
|
|
@@ -1076,6 +1219,11 @@ async function toSummaryUpstream(repo, upstream, signal) {
|
|
|
1076
1219
|
missing
|
|
1077
1220
|
};
|
|
1078
1221
|
}
|
|
1222
|
+
/** Whether `e` names `ref` exactly, so it's known to be a ref that moved rather than one that may have */
|
|
1223
|
+
function isNamedRef(e, ref) {
|
|
1224
|
+
const kind = getRefChangeKind(ref);
|
|
1225
|
+
return kind != null && e.refs.has(ref) && !e.unnamedRefKinds.has(kind);
|
|
1226
|
+
}
|
|
1079
1227
|
function upstreamsEqual(a, b) {
|
|
1080
1228
|
return a === b || a != null && b != null && a.name === b.name && a.remote === b.remote && a.branch === b.branch && a.missing === b.missing;
|
|
1081
1229
|
}
|