@gitkraken/core-gitlens 0.15.3 → 0.15.5
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 +45 -1
- package/dist/git/cache.d.ts +28 -18
- package/dist/git/cache.js +71 -25
- package/dist/git/cache.js.map +1 -1
- package/dist/git/context.d.ts +19 -2
- package/dist/git/context.js.map +1 -1
- package/dist/git/models/pullRequest.d.ts +7 -5
- package/dist/git/models/pullRequest.js.map +1 -1
- package/dist/git/models/reference.d.ts +8 -0
- package/dist/git/providers/branches.d.ts +43 -0
- package/dist/git/providers/provider.d.ts +7 -0
- package/dist/git/service.d.ts +5 -1
- package/dist/git/service.js +8 -4
- package/dist/git/service.js.map +1 -1
- package/dist/git/watching/changeEvent.d.ts +29 -3
- package/dist/git/watching/changeEvent.js +39 -3
- package/dist/git/watching/changeEvent.js.map +1 -1
- package/dist/git/watching/configSections.d.ts +35 -0
- package/dist/git/watching/configSections.js +210 -0
- package/dist/git/watching/configSections.js.map +1 -0
- package/dist/git/watching/watchGroup.d.ts +37 -4
- package/dist/git/watching/watchGroup.js +108 -2
- package/dist/git/watching/watchGroup.js.map +1 -1
- package/dist/git/watching/watchService.d.ts +14 -3
- package/dist/git/watching/watchService.js +10 -7
- package/dist/git/watching/watchService.js.map +1 -1
- package/dist/git/watching/watchSession.d.ts +52 -20
- package/dist/git/watching/watchSession.js +166 -43
- package/dist/git/watching/watchSession.js.map +1 -1
- package/dist/git/watching/watcherPatterns.js +1 -0
- package/dist/git/watching/watcherPatterns.js.map +1 -1
- package/dist/git/watching/worktreeSummary.d.ts +21 -3
- package/dist/git/watching/worktreeSummary.js +118 -17
- package/dist/git/watching/worktreeSummary.js.map +1 -1
- package/dist/git-cli/cliGitProvider.d.ts +6 -1
- package/dist/git-cli/cliGitProvider.js +45 -5
- package/dist/git-cli/cliGitProvider.js.map +1 -1
- package/dist/git-cli/parsers/refParser.js +2 -1
- package/dist/git-cli/parsers/refParser.js.map +1 -1
- package/dist/git-cli/providers/branches.d.ts +10 -3
- package/dist/git-cli/providers/branches.js +99 -25
- package/dist/git-cli/providers/branches.js.map +1 -1
- package/dist/git-cli/providers/config.d.ts +45 -9
- package/dist/git-cli/providers/config.js +150 -24
- package/dist/git-cli/providers/config.js.map +1 -1
- package/dist/git-cli/providers/operations.d.ts +35 -1
- package/dist/git-cli/providers/operations.js +175 -16
- package/dist/git-cli/providers/operations.js.map +1 -1
- package/dist/git-cli/providers/stash.d.ts +1 -0
- package/dist/git-cli/providers/stash.js +9 -3
- package/dist/git-cli/providers/stash.js.map +1 -1
- package/dist/git-cli/providers/worktrees.js +11 -4
- package/dist/git-cli/providers/worktrees.js.map +1 -1
- package/dist/git-cli/service.js +5 -1
- package/dist/git-cli/service.js.map +1 -1
- package/dist/plus/integrations/models/gitHostIntegration.d.ts +29 -14
- package/dist/plus/integrations/models/gitHostIntegration.js +41 -19
- package/dist/plus/integrations/models/gitHostIntegration.js.map +1 -1
- package/dist/plus/integrations/providers/azure/azure.d.ts +16 -16
- package/dist/plus/integrations/providers/azure/azure.js +45 -25
- package/dist/plus/integrations/providers/azure/azure.js.map +1 -1
- package/dist/plus/integrations/providers/azure/azure.utils.d.ts +12 -0
- package/dist/plus/integrations/providers/azure/azure.utils.js +17 -1
- package/dist/plus/integrations/providers/azure/azure.utils.js.map +1 -1
- package/dist/plus/integrations/providers/azure/search.d.ts +4 -0
- package/dist/plus/integrations/providers/azure/search.js +46 -2
- package/dist/plus/integrations/providers/azure/search.js.map +1 -1
- package/dist/plus/integrations/providers/azure/workItemSearch.d.ts +11 -0
- package/dist/plus/integrations/providers/azure/workItemSearch.js +54 -0
- package/dist/plus/integrations/providers/azure/workItemSearch.js.map +1 -0
- package/dist/plus/integrations/providers/azure/workItemStateCategories.d.ts +2 -2
- package/dist/plus/integrations/providers/azure/workItemStateCategories.js +19 -15
- package/dist/plus/integrations/providers/azure/workItemStateCategories.js.map +1 -1
- package/dist/plus/integrations/providers/azureDevOps.d.ts +33 -20
- package/dist/plus/integrations/providers/azureDevOps.js +109 -48
- package/dist/plus/integrations/providers/azureDevOps.js.map +1 -1
- package/dist/plus/integrations/providers/azureDevOpsServer.d.ts +16 -47
- package/dist/plus/integrations/providers/azureDevOpsServer.js +52 -91
- package/dist/plus/integrations/providers/azureDevOpsServer.js.map +1 -1
- package/dist/plus/integrations/providers/issueSorts.d.ts +0 -1
- package/dist/plus/integrations/providers/models.d.ts +5 -0
- package/dist/plus/integrations/providers/models.js +14 -3
- package/dist/plus/integrations/providers/models.js.map +1 -1
- package/dist/plus/integrations/reads/drains.d.ts +2 -3
- package/dist/plus/integrations/reads/drains.js +4 -4
- package/dist/plus/integrations/reads/drains.js.map +1 -1
- package/dist/plus/integrations/reads/issues.js +7 -4
- package/dist/plus/integrations/reads/issues.js.map +1 -1
- package/dist/plus/integrations/reads/resolveRepository.js +5 -5
- package/dist/plus/integrations/reads/resolveRepository.js.map +1 -1
- package/package.json +1 -1
|
@@ -7,6 +7,21 @@ import { DeprecatedGkConfigKeys, GitConfigKeys, GitConfigSubProvider, GitConfigT
|
|
|
7
7
|
import { Git } from "../exec/git.js";
|
|
8
8
|
import { CliGitProviderInternal } from "../cliGitProvider.js";
|
|
9
9
|
//#region ../git-cli/src/providers/config.d.ts
|
|
10
|
+
/** The wall clock a read's fingerprint is checked against (see {@link isGkConfigStatRacy}), exported for testing */
|
|
11
|
+
export declare const gkConfigFingerprintClock: {
|
|
12
|
+
now: () => number;
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* Whether a stat of `.git/gk/config` taken at `statTimeMs` came too soon after the file's last change to vouch for
|
|
16
|
+
* the file staying unchanged while it matches — git's "racily clean" index entries. A later write in the same tick of
|
|
17
|
+
* the filesystem's clock as that change, of the same size and given the inode the stat saw (freed by the write in
|
|
18
|
+
* between and reused), gets the same fingerprint; once the stat is a tick past the change, any later write carries a
|
|
19
|
+
* later time. Times that are whole seconds come from a filesystem that keeps no finer (ext4 with 128-byte inodes,
|
|
20
|
+
* HFS+, FAT's 2-second mtime, some network mounts), so the stat must be 2s past; otherwise 50ms covers the coarsest
|
|
21
|
+
* tick a finer clock still has (a 10ms jiffy, Windows' 15.6ms timer) and the wall clock lagging it by one. A file
|
|
22
|
+
* changed after `statTimeMs` (a clock ahead of ours) is racy too. Exported for testing.
|
|
23
|
+
*/
|
|
24
|
+
export declare function isGkConfigStatRacy(mtimeNs: bigint, ctimeNs: bigint, statTimeMs: number): boolean;
|
|
10
25
|
/**
|
|
11
26
|
* Canonicalizes a git config key for lookup against `--get-regexp` output, which git emits with the
|
|
12
27
|
* section and variable-name lowercased and only the subsection case-preserved. Without this a
|
|
@@ -31,6 +46,13 @@ export declare function canonicalizeGitConfigKey(key: string): string;
|
|
|
31
46
|
export declare function parseConfigRegexOutput(data: string | undefined, options?: {
|
|
32
47
|
includeValueless?: boolean;
|
|
33
48
|
}): Map<string, string>;
|
|
49
|
+
/**
|
|
50
|
+
* Parses `git config --null --get-regexp` output into every value of each key, in the order git printed them. Each
|
|
51
|
+
* entry is its key, a newline and its value, ended by a NUL, so a value may hold newlines or spaces unambiguously.
|
|
52
|
+
* A valueless entry (`key`, no `=`), which git prints with no newline, is skipped, as {@link parseConfigRegexOutput}
|
|
53
|
+
* skips it by default.
|
|
54
|
+
*/
|
|
55
|
+
export declare function parseConfigRegexNullOutput(data: string | undefined): Map<string, string[]>;
|
|
34
56
|
export declare function parseGitBoolean(value: string | undefined): boolean;
|
|
35
57
|
/** Whether a git config value spells boolean-false (`false`/`no`/`off`/`0`/empty), per git's maybe-bool parsing. */
|
|
36
58
|
export declare function isGitBooleanFalse(value: string): boolean;
|
|
@@ -52,8 +74,6 @@ export declare class ConfigGitSubProvider implements GitConfigSubProvider {
|
|
|
52
74
|
* skip option joins it; any other call chains after it, so one key's writes land in call order. Cleared on settle.
|
|
53
75
|
*/
|
|
54
76
|
private readonly _pendingGkConfigWrites;
|
|
55
|
-
/** Maps from failed bulk reads, which a reconcile must not diff as the file's content */
|
|
56
|
-
private readonly _failedGkConfigMaps;
|
|
57
77
|
/** Writes each `.git/gk/config` one at a time — concurrent `git config` writes fail on its lock */
|
|
58
78
|
private readonly _gkConfigWrites;
|
|
59
79
|
getConfig(repoPath: string | undefined, key: GitConfigKeys, options?: {
|
|
@@ -66,6 +86,12 @@ export declare class ConfigGitSubProvider implements GitConfigSubProvider {
|
|
|
66
86
|
global?: boolean;
|
|
67
87
|
runGitLocally?: boolean;
|
|
68
88
|
}): Promise<Map<string, string>>;
|
|
89
|
+
/**
|
|
90
|
+
* Every value of each key matching `pattern` (`git config --get-regexp`), for a key git lets repeat, like
|
|
91
|
+
* `remote.<name>.fetch`: {@link getConfigRegex} keeps only each key's last value. Cached beside it, so the same
|
|
92
|
+
* writes and config changes clear it. Rejects when git couldn't read the configuration.
|
|
93
|
+
*/
|
|
94
|
+
getConfigRegexAll(repoPath: string, pattern: string): Promise<Map<string, string[]>>;
|
|
69
95
|
private getConfigRegexCore;
|
|
70
96
|
setConfig(repoPath: string | undefined, key: GitConfigKeys, value: string | undefined, options?: {
|
|
71
97
|
file?: string;
|
|
@@ -89,6 +115,14 @@ export declare class ConfigGitSubProvider implements GitConfigSubProvider {
|
|
|
89
115
|
* namespaces costs one `git config` call instead of one per namespace.
|
|
90
116
|
*/
|
|
91
117
|
private getGkConfigMap;
|
|
118
|
+
/** Records the read that is creating `repoPath`'s cached gk config map (see {@link gkConfigMapReads}) */
|
|
119
|
+
private trackGkConfigMapRead;
|
|
120
|
+
/**
|
|
121
|
+
* Hard-evicts the cached gk config map, unless `.git/gk/config` is unchanged since the read behind it stat'd the
|
|
122
|
+
* file, or that read is yet to spawn. The stat is taken before the read, so a write landing between the two
|
|
123
|
+
* only costs a re-read on its report, never a missed change. Returns the read it kept, if any.
|
|
124
|
+
*/
|
|
125
|
+
private evictGkConfigMapIfChanged;
|
|
92
126
|
private getGkConfigRegexCore;
|
|
93
127
|
setGkConfig(repoPath: string, key: GkConfigKeys | DeprecatedGkConfigKeys, value: string | undefined, options?: {
|
|
94
128
|
skipInvalidation?: readonly GkConfigInvalidationTarget[];
|
|
@@ -111,10 +145,12 @@ export declare class ConfigGitSubProvider implements GitConfigSubProvider {
|
|
|
111
145
|
private runGkConfigSectionCommand;
|
|
112
146
|
/**
|
|
113
147
|
* Reconciles a watcher-observed `'gkConfig'` change — registered with the shared `Cache` as its
|
|
114
|
-
* gk-config reconciler (see the constructor).
|
|
115
|
-
*
|
|
148
|
+
* gk-config reconciler (see the constructor). Hard-evicts the bulk map as the change is reported, unless the file
|
|
149
|
+
* is unchanged since the map was read (see `evictGkConfigMapIfChanged`), then reads it (from disk, or the map
|
|
150
|
+
* kept) and diffs it against `priorSnapshot` to cascade
|
|
116
151
|
* `baseBranchName`/`branchOverviews` only for refs whose merge-relevant keys actually changed, and to evict the
|
|
117
|
-
* branches only when a branch metadata key (another process's activity stamp) changed.
|
|
152
|
+
* branches only when a branch metadata key (another process's activity stamp) changed. The diff runs on a kept
|
|
153
|
+
* map too: a read can land between an external write and its report, and already hold the change.
|
|
118
154
|
*
|
|
119
155
|
* Serialized per commonPath: a reconcile is a read-then-diff against a single shared baseline, so
|
|
120
156
|
* two overlapping passes would both diff against the same snapshot and the later change could fall
|
|
@@ -122,10 +158,10 @@ export declare class ConfigGitSubProvider implements GitConfigSubProvider {
|
|
|
122
158
|
* diffs against the baseline the previous pass just established.
|
|
123
159
|
*
|
|
124
160
|
* `.git/gk/config` lives in the common dir, so the watcher delivers one event per worktree sharing it
|
|
125
|
-
* (see `WatchGroup.onCommonEvent`). Siblings never run concurrently — the
|
|
126
|
-
* in-flight pass — but folding in arms the trailing pass,
|
|
127
|
-
*
|
|
128
|
-
*
|
|
161
|
+
* (see `WatchGroup.onCommonEvent`), and core's own write is reported too. Siblings never run concurrently — the
|
|
162
|
+
* later ones fold into the in-flight pass — but folding in arms the trailing pass, which re-diffs the map unless
|
|
163
|
+
* the file changed again. However many siblings land while a pass is running, they coalesce into that single
|
|
164
|
+
* trailing pass; the cost is the price of never dropping a real change.
|
|
129
165
|
* Every reporting path is retained because `Repository` matches the follow-up change event on exact
|
|
130
166
|
* path, and a sibling worktree that never receives one would sit on stale derived values.
|
|
131
167
|
*/
|
|
@@ -10,7 +10,7 @@ import { WorkspaceUntrustedError } from "../../git/errors.js";
|
|
|
10
10
|
import { fsExists } from "../../utils/env/node/exec.js";
|
|
11
11
|
import { KeyedSequencer } from "../exec/indexLock.js";
|
|
12
12
|
import { gate } from "../../utils/decorators/gate.js";
|
|
13
|
-
import { promises } from "fs";
|
|
13
|
+
import { promises, statSync } from "fs";
|
|
14
14
|
import { env } from "process";
|
|
15
15
|
import { hostname, userInfo } from "os";
|
|
16
16
|
//#region ../git-cli/src/providers/config.ts
|
|
@@ -19,6 +19,58 @@ const emptyArray = Object.freeze([]);
|
|
|
19
19
|
/** Catch-all pattern matching every key in `.git/gk/config` for the one-shot bulk read. */
|
|
20
20
|
const gkConfigAllPattern = ".";
|
|
21
21
|
/**
|
|
22
|
+
* Maps from failed bulk reads, which a reconcile must not diff as the file's content, each to the read that produced
|
|
23
|
+
* it. Module-wide, like {@link gkConfigMapReads}: a reconcile can join a read another provider sharing its `Cache`
|
|
24
|
+
* started.
|
|
25
|
+
*/
|
|
26
|
+
const failedGkConfigMaps = /* @__PURE__ */ new WeakMap();
|
|
27
|
+
/**
|
|
28
|
+
* The read behind each commonPath's cached gk config map, per `Cache`. Recorded as the read starts, which is when
|
|
29
|
+
* the cache creates the entry for it, so it always describes the entry cached now. Kept per `Cache` rather than per
|
|
30
|
+
* provider, since providers sharing a `Cache` serve each other's reads.
|
|
31
|
+
*/
|
|
32
|
+
const gkConfigMapReads = /* @__PURE__ */ new WeakMap();
|
|
33
|
+
/** The wall clock a read's fingerprint is checked against (see {@link isGkConfigStatRacy}), exported for testing */
|
|
34
|
+
const gkConfigFingerprintClock = { now: () => Date.now() };
|
|
35
|
+
const nsPerMs = 1000000n;
|
|
36
|
+
const nsPerSecond = 1000000000n;
|
|
37
|
+
/**
|
|
38
|
+
* Whether a stat of `.git/gk/config` taken at `statTimeMs` came too soon after the file's last change to vouch for
|
|
39
|
+
* the file staying unchanged while it matches — git's "racily clean" index entries. A later write in the same tick of
|
|
40
|
+
* the filesystem's clock as that change, of the same size and given the inode the stat saw (freed by the write in
|
|
41
|
+
* between and reused), gets the same fingerprint; once the stat is a tick past the change, any later write carries a
|
|
42
|
+
* later time. Times that are whole seconds come from a filesystem that keeps no finer (ext4 with 128-byte inodes,
|
|
43
|
+
* HFS+, FAT's 2-second mtime, some network mounts), so the stat must be 2s past; otherwise 50ms covers the coarsest
|
|
44
|
+
* tick a finer clock still has (a 10ms jiffy, Windows' 15.6ms timer) and the wall clock lagging it by one. A file
|
|
45
|
+
* changed after `statTimeMs` (a clock ahead of ours) is racy too. Exported for testing.
|
|
46
|
+
*/
|
|
47
|
+
function isGkConfigStatRacy(mtimeNs, ctimeNs, statTimeMs) {
|
|
48
|
+
const changedNs = mtimeNs > ctimeNs ? mtimeNs : ctimeNs;
|
|
49
|
+
const marginNs = mtimeNs % nsPerSecond === 0n && ctimeNs % nsPerSecond === 0n ? 2n * nsPerSecond : 50n * nsPerMs;
|
|
50
|
+
return BigInt(Math.floor(statTimeMs)) * nsPerMs - changedNs < marginNs;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* A stat of `.git/gk/config` that two stats share only if no write landed between them: `git config` writes a lock
|
|
54
|
+
* file and renames it over the original, so every write gives the file a new inode, and its size and times tell
|
|
55
|
+
* apart a later write that lands on a freed inode — unless that write fell in the same clock tick as the change the
|
|
56
|
+
* first stat saw, which `statTimeMs` rules out (see {@link isGkConfigStatRacy}): given the time the stat is taken,
|
|
57
|
+
* a stat that racy is `undefined`, never matching. `'absent'` when there is no file, `undefined` when it can't be
|
|
58
|
+
* stat'd.
|
|
59
|
+
*/
|
|
60
|
+
function getGkConfigFingerprint(path, statTimeMs) {
|
|
61
|
+
try {
|
|
62
|
+
const stat = statSync(path, {
|
|
63
|
+
bigint: true,
|
|
64
|
+
throwIfNoEntry: false
|
|
65
|
+
});
|
|
66
|
+
if (stat == null) return "absent";
|
|
67
|
+
if (statTimeMs != null && isGkConfigStatRacy(stat.mtimeNs, stat.ctimeNs, statTimeMs)) return void 0;
|
|
68
|
+
return `${stat.dev}:${stat.ino}:${stat.size}:${stat.mtimeNs}:${stat.ctimeNs}`;
|
|
69
|
+
} catch {
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
22
74
|
* Canonicalizes a git config key for lookup against `--get-regexp` output, which git emits with the
|
|
23
75
|
* section and variable-name lowercased and only the subsection case-preserved. Without this a
|
|
24
76
|
* subsection-less key like `gk.defaultRemote` would miss the lowercased `gk.defaultremote` in the map.
|
|
@@ -59,6 +111,26 @@ function parseConfigRegexOutput(data, options) {
|
|
|
59
111
|
}
|
|
60
112
|
return configMap;
|
|
61
113
|
}
|
|
114
|
+
/**
|
|
115
|
+
* Parses `git config --null --get-regexp` output into every value of each key, in the order git printed them. Each
|
|
116
|
+
* entry is its key, a newline and its value, ended by a NUL, so a value may hold newlines or spaces unambiguously.
|
|
117
|
+
* A valueless entry (`key`, no `=`), which git prints with no newline, is skipped, as {@link parseConfigRegexOutput}
|
|
118
|
+
* skips it by default.
|
|
119
|
+
*/
|
|
120
|
+
function parseConfigRegexNullOutput(data) {
|
|
121
|
+
const configMap = /* @__PURE__ */ new Map();
|
|
122
|
+
if (!data) return configMap;
|
|
123
|
+
for (const entry of data.split("\0")) {
|
|
124
|
+
const newlineIndex = entry.indexOf("\n");
|
|
125
|
+
if (newlineIndex === -1) continue;
|
|
126
|
+
const key = entry.substring(0, newlineIndex);
|
|
127
|
+
const value = entry.substring(newlineIndex + 1);
|
|
128
|
+
const values = configMap.get(key);
|
|
129
|
+
if (values != null) values.push(value);
|
|
130
|
+
else configMap.set(key, [value]);
|
|
131
|
+
}
|
|
132
|
+
return configMap;
|
|
133
|
+
}
|
|
62
134
|
function parseGitBoolean(value) {
|
|
63
135
|
if (value == null) return false;
|
|
64
136
|
const normalized = value.toLowerCase().trim();
|
|
@@ -93,8 +165,6 @@ var ConfigGitSubProvider = class {
|
|
|
93
165
|
* skip option joins it; any other call chains after it, so one key's writes land in call order. Cleared on settle.
|
|
94
166
|
*/
|
|
95
167
|
_pendingGkConfigWrites = /* @__PURE__ */ new Map();
|
|
96
|
-
/** Maps from failed bulk reads, which a reconcile must not diff as the file's content */
|
|
97
|
-
_failedGkConfigMaps = /* @__PURE__ */ new WeakSet();
|
|
98
168
|
/** Writes each `.git/gk/config` one at a time — concurrent `git config` writes fail on its lock */
|
|
99
169
|
_gkConfigWrites = new KeyedSequencer();
|
|
100
170
|
getConfig(repoPath, key, options) {
|
|
@@ -117,6 +187,22 @@ var ConfigGitSubProvider = class {
|
|
|
117
187
|
const global = options?.global || repoPath == null;
|
|
118
188
|
return this.cache.getConfigRegex(global ? void 0 : repoPath, pattern, () => this.getConfigRegexCore(repoPath, pattern, options));
|
|
119
189
|
}
|
|
190
|
+
/**
|
|
191
|
+
* Every value of each key matching `pattern` (`git config --get-regexp`), for a key git lets repeat, like
|
|
192
|
+
* `remote.<name>.fetch`: {@link getConfigRegex} keeps only each key's last value. Cached beside it, so the same
|
|
193
|
+
* writes and config changes clear it. Rejects when git couldn't read the configuration.
|
|
194
|
+
*/
|
|
195
|
+
async getConfigRegexAll(repoPath, pattern) {
|
|
196
|
+
const joined = await this.cache.getConfigRegex(repoPath, `\0all\0${pattern}`, async () => {
|
|
197
|
+
const result = await this.git.run({
|
|
198
|
+
cwd: this.cache.getCommonPath(repoPath),
|
|
199
|
+
errors: "ignore"
|
|
200
|
+
}, "config", "--null", "--get-regexp", pattern);
|
|
201
|
+
if (result.completion.status !== "exited" || result.exitCode !== 0 && result.exitCode !== 1) throw new Error(`Unable to read the config keys matching '${pattern}'`);
|
|
202
|
+
return new Map(Array.from(parseConfigRegexNullOutput(result.stdout), ([key, values]) => [key, values.join("\0")]));
|
|
203
|
+
});
|
|
204
|
+
return new Map(Array.from(joined, ([key, value]) => [key, value.split("\0")]));
|
|
205
|
+
}
|
|
120
206
|
async getConfigRegexCore(repoPath, pattern, options) {
|
|
121
207
|
const args = ["config", "--get-regex"];
|
|
122
208
|
if (options?.file) args.push("-f", options.file);
|
|
@@ -249,22 +335,54 @@ var ConfigGitSubProvider = class {
|
|
|
249
335
|
* namespaces costs one `git config` call instead of one per namespace.
|
|
250
336
|
*/
|
|
251
337
|
async getGkConfigMap(repoPath) {
|
|
252
|
-
|
|
338
|
+
const commonPath = this.cache.getCommonPath(repoPath);
|
|
339
|
+
if (!this._migratedRepos.has(commonPath)) await this.migrateGkConfigFromGitConfig(commonPath);
|
|
253
340
|
return this.cache.getGkConfigMap(repoPath, async (cacheable) => {
|
|
254
|
-
const
|
|
341
|
+
const read = this.trackGkConfigMapRead(repoPath);
|
|
342
|
+
const { map, failed } = await this.getGkConfigRegexCore(repoPath, gkConfigAllPattern, read);
|
|
255
343
|
if (failed) {
|
|
256
344
|
cacheable.invalidate();
|
|
257
|
-
|
|
345
|
+
failedGkConfigMaps.set(map, read);
|
|
258
346
|
}
|
|
259
347
|
return map;
|
|
260
348
|
});
|
|
261
349
|
}
|
|
262
|
-
|
|
350
|
+
/** Records the read that is creating `repoPath`'s cached gk config map (see {@link gkConfigMapReads}) */
|
|
351
|
+
trackGkConfigMapRead(repoPath) {
|
|
352
|
+
let reads = gkConfigMapReads.get(this.cache);
|
|
353
|
+
if (reads == null) {
|
|
354
|
+
reads = /* @__PURE__ */ new Map();
|
|
355
|
+
gkConfigMapReads.set(this.cache, reads);
|
|
356
|
+
}
|
|
357
|
+
const read = { spawned: false };
|
|
358
|
+
reads.set(this.cache.getCommonPath(repoPath), read);
|
|
359
|
+
return read;
|
|
360
|
+
}
|
|
361
|
+
/**
|
|
362
|
+
* Hard-evicts the cached gk config map, unless `.git/gk/config` is unchanged since the read behind it stat'd the
|
|
363
|
+
* file, or that read is yet to spawn. The stat is taken before the read, so a write landing between the two
|
|
364
|
+
* only costs a re-read on its report, never a missed change. Returns the read it kept, if any.
|
|
365
|
+
*/
|
|
366
|
+
evictGkConfigMapIfChanged(repoPath) {
|
|
367
|
+
const read = gkConfigMapReads.get(this.cache)?.get(this.cache.getCommonPath(repoPath));
|
|
368
|
+
if (read != null) {
|
|
369
|
+
if (!read.spawned) return read;
|
|
370
|
+
if (read.path != null && read.fingerprint != null && getGkConfigFingerprint(read.path) === read.fingerprint) return read;
|
|
371
|
+
}
|
|
372
|
+
this.cache.deleteGkConfigMap(repoPath);
|
|
373
|
+
}
|
|
374
|
+
async getGkConfigRegexCore(repoPath, pattern, read) {
|
|
263
375
|
const gkConfigPath = await this.getGkConfigPath(repoPath);
|
|
264
|
-
if (!gkConfigPath)
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
376
|
+
if (!gkConfigPath) {
|
|
377
|
+
read.spawned = true;
|
|
378
|
+
return {
|
|
379
|
+
map: /* @__PURE__ */ new Map(),
|
|
380
|
+
failed: false
|
|
381
|
+
};
|
|
382
|
+
}
|
|
383
|
+
read.path = gkConfigPath;
|
|
384
|
+
read.fingerprint = getGkConfigFingerprint(gkConfigPath, gkConfigFingerprintClock.now());
|
|
385
|
+
read.spawned = true;
|
|
268
386
|
const result = await this.git.run({
|
|
269
387
|
cwd: repoPath,
|
|
270
388
|
errors: "ignore",
|
|
@@ -355,10 +473,12 @@ var ConfigGitSubProvider = class {
|
|
|
355
473
|
}
|
|
356
474
|
/**
|
|
357
475
|
* Reconciles a watcher-observed `'gkConfig'` change — registered with the shared `Cache` as its
|
|
358
|
-
* gk-config reconciler (see the constructor).
|
|
359
|
-
*
|
|
476
|
+
* gk-config reconciler (see the constructor). Hard-evicts the bulk map as the change is reported, unless the file
|
|
477
|
+
* is unchanged since the map was read (see `evictGkConfigMapIfChanged`), then reads it (from disk, or the map
|
|
478
|
+
* kept) and diffs it against `priorSnapshot` to cascade
|
|
360
479
|
* `baseBranchName`/`branchOverviews` only for refs whose merge-relevant keys actually changed, and to evict the
|
|
361
|
-
* branches only when a branch metadata key (another process's activity stamp) changed.
|
|
480
|
+
* branches only when a branch metadata key (another process's activity stamp) changed. The diff runs on a kept
|
|
481
|
+
* map too: a read can land between an external write and its report, and already hold the change.
|
|
362
482
|
*
|
|
363
483
|
* Serialized per commonPath: a reconcile is a read-then-diff against a single shared baseline, so
|
|
364
484
|
* two overlapping passes would both diff against the same snapshot and the later change could fall
|
|
@@ -366,10 +486,10 @@ var ConfigGitSubProvider = class {
|
|
|
366
486
|
* diffs against the baseline the previous pass just established.
|
|
367
487
|
*
|
|
368
488
|
* `.git/gk/config` lives in the common dir, so the watcher delivers one event per worktree sharing it
|
|
369
|
-
* (see `WatchGroup.onCommonEvent`). Siblings never run concurrently — the
|
|
370
|
-
* in-flight pass — but folding in arms the trailing pass,
|
|
371
|
-
*
|
|
372
|
-
*
|
|
489
|
+
* (see `WatchGroup.onCommonEvent`), and core's own write is reported too. Siblings never run concurrently — the
|
|
490
|
+
* later ones fold into the in-flight pass — but folding in arms the trailing pass, which re-diffs the map unless
|
|
491
|
+
* the file changed again. However many siblings land while a pass is running, they coalesce into that single
|
|
492
|
+
* trailing pass; the cost is the price of never dropping a real change.
|
|
373
493
|
* Every reporting path is retained because `Repository` matches the follow-up change event on exact
|
|
374
494
|
* path, and a sibling worktree that never receives one would sit on stale derived values.
|
|
375
495
|
*/
|
|
@@ -377,6 +497,7 @@ var ConfigGitSubProvider = class {
|
|
|
377
497
|
const commonPath = this.cache.getCommonPath(repoPath);
|
|
378
498
|
const inflight = this._gkConfigReconciles.get(commonPath);
|
|
379
499
|
if (inflight != null) {
|
|
500
|
+
this.evictGkConfigMapIfChanged(repoPath);
|
|
380
501
|
inflight.paths.set(repoPath, this.cache.getCloseGeneration(repoPath));
|
|
381
502
|
inflight.pending = true;
|
|
382
503
|
return "deferred";
|
|
@@ -401,9 +522,13 @@ var ConfigGitSubProvider = class {
|
|
|
401
522
|
entry.pending = false;
|
|
402
523
|
const readPath = first(this.pruneClosedReconcilePaths(entry.paths).keys());
|
|
403
524
|
if (readPath == null) return "reconciled";
|
|
404
|
-
this.
|
|
405
|
-
|
|
406
|
-
if (
|
|
525
|
+
const kept = this.evictGkConfigMapIfChanged(readPath);
|
|
526
|
+
let freshMap = await this.getGkConfigMap(readPath);
|
|
527
|
+
if (kept != null && failedGkConfigMaps.get(freshMap) === kept) {
|
|
528
|
+
this.cache.deleteGkConfigMap(readPath);
|
|
529
|
+
freshMap = await this.getGkConfigMap(readPath);
|
|
530
|
+
}
|
|
531
|
+
if (failedGkConfigMaps.has(freshMap)) throw new Error(`Failed to read the gk config through '${readPath}'`);
|
|
407
532
|
if (!this.pruneClosedReconcilePaths(entry.paths).size) return "reconciled";
|
|
408
533
|
if (this.cache.reconcileGkConfigMap(readPath, snapshot, freshMap)) for (const path of entry.paths.keys()) this.provider.notifyHost(path, ["gkConfig"]);
|
|
409
534
|
snapshot = this.cache.getGkConfigSnapshot(readPath);
|
|
@@ -644,12 +769,13 @@ var ConfigGitSubProvider = class {
|
|
|
644
769
|
};
|
|
645
770
|
__decorate([trace()], ConfigGitSubProvider.prototype, "getConfig", null);
|
|
646
771
|
__decorate([trace()], ConfigGitSubProvider.prototype, "getConfigRegex", null);
|
|
772
|
+
__decorate([trace()], ConfigGitSubProvider.prototype, "getConfigRegexAll", null);
|
|
647
773
|
__decorate([debug()], ConfigGitSubProvider.prototype, "setConfig", null);
|
|
648
774
|
__decorate([gate(), debug()], ConfigGitSubProvider.prototype, "getCurrentUser", null);
|
|
649
775
|
__decorate([gate(), trace({ exit: (r) => `returned ${r}` })], ConfigGitSubProvider.prototype, "getDefaultWorktreePath", null);
|
|
650
776
|
__decorate([gate(), trace({ exit: (r) => `returned ${r.uri.toString(true)}, commonUri=${r.commonUri?.toString(true)}, parentUri=${r.parentUri?.toString(true)}` })], ConfigGitSubProvider.prototype, "getGitDir", null);
|
|
651
|
-
__decorate([
|
|
652
|
-
__decorate([
|
|
777
|
+
__decorate([trace()], ConfigGitSubProvider.prototype, "getGkConfig", null);
|
|
778
|
+
__decorate([trace()], ConfigGitSubProvider.prototype, "getGkConfigRegex", null);
|
|
653
779
|
__decorate([debug()], ConfigGitSubProvider.prototype, "setGkConfig", null);
|
|
654
780
|
__decorate([debug()], ConfigGitSubProvider.prototype, "removeGkConfigBranchSection", null);
|
|
655
781
|
__decorate([debug()], ConfigGitSubProvider.prototype, "renameGkConfigBranchSection", null);
|
|
@@ -658,6 +784,6 @@ __decorate([debug()], ConfigGitSubProvider.prototype, "validateSigningSetup", nu
|
|
|
658
784
|
__decorate([debug()], ConfigGitSubProvider.prototype, "setSigningConfig", null);
|
|
659
785
|
__decorate([gate(), debug()], ConfigGitSubProvider.prototype, "migrateGkConfigFromGitConfig", null);
|
|
660
786
|
//#endregion
|
|
661
|
-
export { ConfigGitSubProvider, canonicalizeGitConfigKey, isGitBooleanFalse, parseConfigRegexOutput, parseGitBoolean };
|
|
787
|
+
export { ConfigGitSubProvider, canonicalizeGitConfigKey, gkConfigFingerprintClock, isGitBooleanFalse, isGkConfigStatRacy, parseConfigRegexNullOutput, parseConfigRegexOutput, parseGitBoolean };
|
|
662
788
|
|
|
663
789
|
//# sourceMappingURL=config.js.map
|