@gitkraken/core-gitlens 0.9.9 → 0.10.1
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 +27 -1
- package/dist/git/errors.d.ts +7 -0
- package/dist/git/errors.js +14 -1
- package/dist/git/errors.js.map +1 -1
- package/dist/git/providers/status.d.ts +6 -1
- package/dist/git/run.types.d.ts +9 -4
- package/dist/git-cli/exec/git.js +36 -16
- package/dist/git-cli/exec/git.js.map +1 -1
- package/dist/git-cli/providers/diff.d.ts +5 -2
- package/dist/git-cli/providers/diff.js +21 -9
- package/dist/git-cli/providers/diff.js.map +1 -1
- package/dist/git-cli/providers/patch.js +5 -7
- package/dist/git-cli/providers/patch.js.map +1 -1
- package/dist/git-cli/providers/status.d.ts +0 -1
- package/dist/git-cli/providers/status.js +119 -142
- package/dist/git-cli/providers/status.js.map +1 -1
- package/dist/plus/git-github/providers/github/status.d.ts +0 -1
- package/dist/plus/git-github/providers/github/status.js.map +1 -1
- package/dist/plus/integrations/authentication/cloudIntegrationService.js +24 -7
- package/dist/plus/integrations/authentication/cloudIntegrationService.js.map +1 -1
- package/dist/plus/integrations/authentication/integrationAuthenticationProvider.d.ts +7 -0
- package/dist/plus/integrations/authentication/integrationAuthenticationProvider.js +13 -3
- package/dist/plus/integrations/authentication/integrationAuthenticationProvider.js.map +1 -1
- package/dist/plus/integrations/integrationService.js +4 -2
- package/dist/plus/integrations/integrationService.js.map +1 -1
- package/dist/plus/integrations/models/gitHostIntegration.js +20 -20
- package/dist/plus/integrations/models/gitHostIntegration.js.map +1 -1
- package/dist/plus/integrations/models/integration.d.ts +29 -3
- package/dist/plus/integrations/models/integration.js +53 -12
- package/dist/plus/integrations/models/integration.js.map +1 -1
- package/dist/plus/integrations/models/issuesIntegration.js +9 -9
- package/dist/plus/integrations/models/issuesIntegration.js.map +1 -1
- package/dist/plus/integrations/providers/azureDevOps.js +1 -1
- package/dist/plus/integrations/providers/azureDevOps.js.map +1 -1
- package/dist/plus/integrations/providers/azureDevOpsServer.js +1 -1
- package/dist/plus/integrations/providers/azureDevOpsServer.js.map +1 -1
- package/dist/plus/integrations/providers/bitbucket-server.js +1 -1
- package/dist/plus/integrations/providers/bitbucket-server.js.map +1 -1
- package/dist/plus/integrations/providers/bitbucket.js +1 -1
- package/dist/plus/integrations/providers/bitbucket.js.map +1 -1
- package/dist/plus/integrations/providers/github.js +2 -2
- package/dist/plus/integrations/providers/github.js.map +1 -1
- package/dist/plus/integrations/providers/gitlab.js +1 -1
- package/dist/plus/integrations/providers/gitlab.js.map +1 -1
- package/dist/plus/integrations/providers/issueSorts.d.ts +1 -0
- package/dist/plus/integrations/reads/drains.d.ts +1 -0
- package/dist/utils/cancellation.d.ts +4 -1
- package/dist/utils/cancellation.js +4 -2
- package/dist/utils/cancellation.js.map +1 -1
- package/dist/utils/env/node/exec.d.ts +5 -1
- package/dist/utils/env/node/exec.js +8 -5
- package/dist/utils/env/node/exec.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { raceWithTimeout } from "../../utils/cancellation.js";
|
|
2
2
|
import { iterateByDelimiter } from "../../utils/string.js";
|
|
3
3
|
import { getScopedLogger } from "../../utils/logger.scoped.js";
|
|
4
4
|
import { getSettledValue } from "../../utils/promise.js";
|
|
@@ -8,7 +8,7 @@ import { normalizePath, splitPath, stripFolderGlob } from "../../utils/path.js";
|
|
|
8
8
|
import { PromiseMap } from "../../utils/promiseCache.js";
|
|
9
9
|
import __decorate from "../../_virtual/@oxc-project+runtime/helpers/esm/decorate.js";
|
|
10
10
|
import { createDisposable } from "../../utils/disposable.js";
|
|
11
|
-
import { gitConfigsStatus } from "../exec/git.js";
|
|
11
|
+
import { GitErrors, defaultExceptionHandler, gitConfigsStatus } from "../exec/git.js";
|
|
12
12
|
import { GitFileWorkingTreeStatus } from "../../git/models/fileStatus.js";
|
|
13
13
|
import _usingCtx from "../../_virtual/@oxc-project+runtime/helpers/esm/usingCtx.js";
|
|
14
14
|
import { parseGitConflictFiles } from "../parsers/indexParser.js";
|
|
@@ -98,7 +98,8 @@ var StatusGitSubProvider = class {
|
|
|
98
98
|
priority: options?.priority,
|
|
99
99
|
correlationKey: options?.correlationKey,
|
|
100
100
|
untracked: options?.untracked,
|
|
101
|
-
branch: options?.branch
|
|
101
|
+
branch: options?.branch,
|
|
102
|
+
errors: "throw"
|
|
102
103
|
}, cancellation);
|
|
103
104
|
const status = parseGitStatus(result.stdout, repoPath, porcelainVersion);
|
|
104
105
|
if (options?.branch === false) {
|
|
@@ -126,7 +127,9 @@ var StatusGitSubProvider = class {
|
|
|
126
127
|
async getStatusForPathCore(repoPath, pathOrUri, options, cancellation) {
|
|
127
128
|
const relativePath = stripFolderGlob(splitPath(pathOrUri, repoPath)[0]);
|
|
128
129
|
if (options.renames !== false) {
|
|
129
|
-
const status = await this.getStatus(repoPath, void 0, cancellation)
|
|
130
|
+
const status = await this.getStatus(repoPath, void 0, cancellation).catch((ex) => {
|
|
131
|
+
defaultExceptionHandler(ex, repoPath);
|
|
132
|
+
});
|
|
130
133
|
if (status == null) return void 0;
|
|
131
134
|
if (options.exact) {
|
|
132
135
|
const file = status.files.find((f) => f.path === relativePath);
|
|
@@ -155,6 +158,7 @@ var StatusGitSubProvider = class {
|
|
|
155
158
|
configs: gitConfigsStatus,
|
|
156
159
|
env: { GIT_OPTIONAL_LOCKS: "0" },
|
|
157
160
|
correlationKey: options?.correlationKey,
|
|
161
|
+
...options?.errors != null ? { errors: options.errors } : void 0,
|
|
158
162
|
...options?.priority != null ? { priority: options.priority } : void 0
|
|
159
163
|
}, ...params, "--", ...pathspecs);
|
|
160
164
|
}
|
|
@@ -163,179 +167,152 @@ var StatusGitSubProvider = class {
|
|
|
163
167
|
const staged = options?.staged ?? true;
|
|
164
168
|
const unstaged = options?.unstaged ?? true;
|
|
165
169
|
const untracked = options?.untracked ?? true;
|
|
166
|
-
const throwOnError = options?.throwOnError ?? false;
|
|
167
170
|
const priority = options?.priority ?? "default";
|
|
168
|
-
return this.dedupeByStatusGeneration(repoPath, `hasWorkingChanges:${staged}:${unstaged}:${untracked}:${
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
171
|
+
return this.dedupeByStatusGeneration(repoPath, `hasWorkingChanges:${staged}:${unstaged}:${untracked}:${priority}`, async (correlationKey, signal) => {
|
|
172
|
+
if (staged || unstaged) {
|
|
173
|
+
const diffQuiet = (revision) => this.git.run({
|
|
174
|
+
cwd: repoPath,
|
|
175
|
+
cancellation: signal,
|
|
176
|
+
errors: "throw",
|
|
177
|
+
expectedExitCodes: [1],
|
|
178
|
+
correlationKey,
|
|
179
|
+
...options?.priority != null ? { priority: options.priority } : void 0
|
|
180
|
+
}, "diff", "--quiet", revision, "--");
|
|
181
|
+
let result;
|
|
182
|
+
if (staged && unstaged) try {
|
|
183
|
+
result = await diffQuiet("HEAD");
|
|
184
|
+
} catch (ex) {
|
|
185
|
+
if (GitErrors.badRevision.exec(String(ex))?.[1]?.toUpperCase() !== "HEAD") throw ex;
|
|
186
|
+
result = await diffQuiet(await this.provider.revision.getEmptyTreeSha(repoPath));
|
|
183
187
|
}
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
188
|
+
else result = await diffQuiet(staged ? "--staged" : void 0);
|
|
189
|
+
if (result.exitCode === 1) {
|
|
190
|
+
if (staged && unstaged) scope?.addExitInfo("has staged and unstaged changes");
|
|
191
|
+
else if (staged) scope?.addExitInfo("has staged changes");
|
|
192
|
+
else scope?.addExitInfo("has unstaged changes");
|
|
193
|
+
return true;
|
|
189
194
|
}
|
|
190
|
-
scope?.addExitInfo("no working changes");
|
|
191
|
-
return false;
|
|
192
|
-
} catch (ex) {
|
|
193
|
-
if (isCancellationError(ex)) throw ex;
|
|
194
|
-
scope?.error(ex);
|
|
195
|
-
scope?.addExitInfo("error checking for changes");
|
|
196
|
-
if (throwOnError) throw ex;
|
|
197
|
-
return false;
|
|
198
195
|
}
|
|
196
|
+
if (untracked) {
|
|
197
|
+
if (await this.hasUntrackedFiles(repoPath, signal)) {
|
|
198
|
+
scope?.addExitInfo("has untracked files");
|
|
199
|
+
return true;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
scope?.addExitInfo("no working changes");
|
|
203
|
+
return false;
|
|
199
204
|
}, cancellation);
|
|
200
205
|
}
|
|
201
206
|
getWorkingChangesState(repoPath, cancellation) {
|
|
202
207
|
const scope = getScopedLogger();
|
|
203
208
|
return this.dedupeByStatusGeneration(repoPath, "getWorkingChangesState", async (correlationKey, signal) => {
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
unstaged: false,
|
|
234
|
-
untracked: false
|
|
235
|
-
};
|
|
236
|
-
}
|
|
209
|
+
const [stagedResult, unstagedResult, untrackedResult] = await Promise.allSettled([
|
|
210
|
+
this.git.run({
|
|
211
|
+
cwd: repoPath,
|
|
212
|
+
cancellation: signal,
|
|
213
|
+
errors: "throw",
|
|
214
|
+
expectedExitCodes: [1],
|
|
215
|
+
correlationKey
|
|
216
|
+
}, "diff", "--quiet", "--staged"),
|
|
217
|
+
this.git.run({
|
|
218
|
+
cwd: repoPath,
|
|
219
|
+
cancellation: signal,
|
|
220
|
+
errors: "throw",
|
|
221
|
+
expectedExitCodes: [1],
|
|
222
|
+
correlationKey
|
|
223
|
+
}, "diff", "--quiet"),
|
|
224
|
+
this.hasUntrackedFiles(repoPath, signal)
|
|
225
|
+
]);
|
|
226
|
+
for (const settled of [
|
|
227
|
+
stagedResult,
|
|
228
|
+
unstagedResult,
|
|
229
|
+
untrackedResult
|
|
230
|
+
]) if (settled.status === "rejected") throw settled.reason;
|
|
231
|
+
const result = {
|
|
232
|
+
staged: getSettledValue(stagedResult)?.exitCode === 1,
|
|
233
|
+
unstaged: getSettledValue(unstagedResult)?.exitCode === 1,
|
|
234
|
+
untracked: getSettledValue(untrackedResult) === true
|
|
235
|
+
};
|
|
236
|
+
scope?.addExitInfo(result.staged || result.unstaged || result.untracked ? `has ${result.staged ? "staged" : ""}${result.unstaged ? result.staged ? ", unstaged" : "unstaged " : ""}${result.untracked ? result.staged || result.unstaged ? ", untracked" : "untracked" : ""} changes` : "no working changes");
|
|
237
|
+
return result;
|
|
237
238
|
}, cancellation);
|
|
238
239
|
}
|
|
239
240
|
hasConflictingFiles(repoPath, cancellation) {
|
|
240
241
|
return this.dedupeByStatusGeneration(repoPath, "hasConflictingFiles", async (_correlationKey, signal) => {
|
|
241
242
|
try {
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
for await (const _chunk of stream) return true;
|
|
250
|
-
return false;
|
|
251
|
-
} catch (_) {
|
|
252
|
-
_usingCtx$1.e = _;
|
|
253
|
-
} finally {
|
|
254
|
-
_usingCtx$1.d();
|
|
255
|
-
}
|
|
256
|
-
} catch (ex) {
|
|
257
|
-
if (isCancellationError(ex)) throw ex;
|
|
243
|
+
var _usingCtx$1 = _usingCtx();
|
|
244
|
+
const stream = this.git.stream({
|
|
245
|
+
cwd: repoPath,
|
|
246
|
+
cancellation: signal
|
|
247
|
+
}, "ls-files", "--unmerged");
|
|
248
|
+
_usingCtx$1.u(createDisposable(() => void stream.return?.(void 0)));
|
|
249
|
+
for await (const _chunk of stream) return true;
|
|
258
250
|
return false;
|
|
251
|
+
} catch (_) {
|
|
252
|
+
_usingCtx$1.e = _;
|
|
253
|
+
} finally {
|
|
254
|
+
_usingCtx$1.d();
|
|
259
255
|
}
|
|
260
256
|
}, cancellation);
|
|
261
257
|
}
|
|
262
258
|
getConflictingFiles(repoPath, cancellation) {
|
|
263
259
|
const scope = getScopedLogger();
|
|
264
260
|
return this.dedupeByStatusGeneration(repoPath, "getConflictingFiles", async (correlationKey, signal) => {
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
scope?.addExitInfo("no conflicting files");
|
|
274
|
-
return [];
|
|
275
|
-
}
|
|
276
|
-
const files = parseGitConflictFiles(result.stdout, repoPath);
|
|
277
|
-
scope?.addExitInfo(`${String(files.length)} conflicting file(s)`);
|
|
278
|
-
return files;
|
|
279
|
-
} catch (ex) {
|
|
280
|
-
if (isCancellationError(ex)) throw ex;
|
|
281
|
-
scope?.error(ex);
|
|
282
|
-
scope?.addExitInfo("error getting conflicting files");
|
|
261
|
+
const result = await this.git.run({
|
|
262
|
+
cwd: repoPath,
|
|
263
|
+
cancellation: signal,
|
|
264
|
+
errors: "throw",
|
|
265
|
+
correlationKey
|
|
266
|
+
}, "ls-files", "-z", "--unmerged");
|
|
267
|
+
if (!result.stdout) {
|
|
268
|
+
scope?.addExitInfo("no conflicting files");
|
|
283
269
|
return [];
|
|
284
270
|
}
|
|
271
|
+
const files = parseGitConflictFiles(result.stdout, repoPath);
|
|
272
|
+
scope?.addExitInfo(`${String(files.length)} conflicting file(s)`);
|
|
273
|
+
return files;
|
|
285
274
|
}, cancellation);
|
|
286
275
|
}
|
|
287
276
|
async hasUntrackedFiles(repoPath, cancellation) {
|
|
288
277
|
try {
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
for await (const _chunk of stream) return true;
|
|
297
|
-
return false;
|
|
298
|
-
} catch (_) {
|
|
299
|
-
_usingCtx3.e = _;
|
|
300
|
-
} finally {
|
|
301
|
-
_usingCtx3.d();
|
|
302
|
-
}
|
|
303
|
-
} catch (ex) {
|
|
304
|
-
if (isCancellationError(ex)) throw ex;
|
|
278
|
+
var _usingCtx3 = _usingCtx();
|
|
279
|
+
const stream = this.git.stream({
|
|
280
|
+
cwd: repoPath,
|
|
281
|
+
cancellation
|
|
282
|
+
}, "ls-files", "--others", "--exclude-standard");
|
|
283
|
+
_usingCtx3.u(createDisposable(() => void stream.return?.(void 0)));
|
|
284
|
+
for await (const _chunk of stream) return true;
|
|
305
285
|
return false;
|
|
286
|
+
} catch (_) {
|
|
287
|
+
_usingCtx3.e = _;
|
|
288
|
+
} finally {
|
|
289
|
+
_usingCtx3.d();
|
|
306
290
|
}
|
|
307
291
|
}
|
|
308
292
|
getUntrackedFiles(repoPath, cancellation) {
|
|
309
293
|
const scope = getScopedLogger();
|
|
310
294
|
return this.dedupeByStatusGeneration(repoPath, "getUntrackedFiles", async (correlationKey, signal) => {
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
scope?.addExitInfo("no untracked files");
|
|
320
|
-
return [];
|
|
321
|
-
}
|
|
322
|
-
const files = [];
|
|
323
|
-
for (const line of iterateByDelimiter(result.stdout, "\0")) {
|
|
324
|
-
if (!line.length) continue;
|
|
325
|
-
files.push({
|
|
326
|
-
path: line,
|
|
327
|
-
repoPath,
|
|
328
|
-
status: GitFileWorkingTreeStatus.Untracked
|
|
329
|
-
});
|
|
330
|
-
}
|
|
331
|
-
scope?.addExitInfo(`${String(files.length)} untracked file(s)`);
|
|
332
|
-
return files;
|
|
333
|
-
} catch (ex) {
|
|
334
|
-
if (isCancellationError(ex)) throw ex;
|
|
335
|
-
scope?.error(ex);
|
|
336
|
-
scope?.addExitInfo("error getting untracked files");
|
|
295
|
+
const result = await this.git.run({
|
|
296
|
+
cwd: repoPath,
|
|
297
|
+
cancellation: signal,
|
|
298
|
+
errors: "throw",
|
|
299
|
+
correlationKey
|
|
300
|
+
}, "ls-files", "-z", "--others", "--exclude-standard");
|
|
301
|
+
if (!result.stdout) {
|
|
302
|
+
scope?.addExitInfo("no untracked files");
|
|
337
303
|
return [];
|
|
338
304
|
}
|
|
305
|
+
const files = [];
|
|
306
|
+
for (const line of iterateByDelimiter(result.stdout, "\0")) {
|
|
307
|
+
if (!line.length) continue;
|
|
308
|
+
files.push({
|
|
309
|
+
path: line,
|
|
310
|
+
repoPath,
|
|
311
|
+
status: GitFileWorkingTreeStatus.Untracked
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
scope?.addExitInfo(`${String(files.length)} untracked file(s)`);
|
|
315
|
+
return files;
|
|
339
316
|
}, cancellation);
|
|
340
317
|
}
|
|
341
318
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"status.js","names":[],"sources":["../../../../git-cli/src/providers/status.ts"],"sourcesContent":["import type { Cache } from '@gitlens/git/cache.js';\nimport type { GitServiceContext } from '@gitlens/git/context.js';\nimport type { GitFile } from '@gitlens/git/models/file.js';\nimport { GitFileWorkingTreeStatus } from '@gitlens/git/models/fileStatus.js';\nimport type { GitConflictFile } from '@gitlens/git/models/staging.js';\nimport { GitStatus } from '@gitlens/git/models/status.js';\nimport type { GitStatusFile } from '@gitlens/git/models/statusFile.js';\nimport type { GitStatusSubProvider, GitWorkingChangesState } from '@gitlens/git/providers/status.js';\nimport type { GitCommandPriority } from '@gitlens/git/run.types.js';\nimport { isCancellationError, raceWithTimeout } from '@gitlens/utils/cancellation.js';\nimport { debug } from '@gitlens/utils/decorators/log.js';\nimport { createDisposable } from '@gitlens/utils/disposable.js';\nimport { getScopedLogger } from '@gitlens/utils/logger.scoped.js';\nimport { normalizePath, splitPath, stripFolderGlob } from '@gitlens/utils/path.js';\nimport { getSettledValue } from '@gitlens/utils/promise.js';\nimport { PromiseMap } from '@gitlens/utils/promiseCache.js';\nimport { iterateByDelimiter } from '@gitlens/utils/string.js';\nimport type { Uri } from '@gitlens/utils/uri.js';\nimport { toFsPath } from '@gitlens/utils/uri.js';\nimport type { CliGitProviderInternal } from '../cliGitProvider.js';\nimport type { GitResult } from '../exec/exec.types.js';\nimport type { Git } from '../exec/git.js';\nimport { gitConfigsStatus } from '../exec/git.js';\nimport { parseGitConflictFiles } from '../parsers/indexParser.js';\nimport { parseGitStatus } from '../parsers/statusParser.js';\n\n/** Backstop when `advanced.git.timeout` is disabled (0). Independent of the command timeout so disabling it can't\n * also disable deadlock recovery — mirrors `@gate`'s always-on 5-min force-clear. */\nconst disabledTimeoutBackstopMs = 1000 * 60 * 5;\n\n/**\n * Deadlock-backstop duration for a status read (see `dedupeByStatusGeneration`), exported for testing.\n * Always returns a value so a wedged read recovers even with `advanced.git.timeout` disabled (which bounds git\n * *commands*, not deadlock recovery). With a timeout set, `gitTimeout * 2` sits above the per-command timeout so\n * `git.run` reads reject at their own timeout first; the backstop's real job is bounding the timeout-less\n * `git.stream` reads (`hasUntrackedFiles`/`hasConflictingFiles`).\n */\nexport function computeDeadlockBackstopMs(gitTimeout: number | undefined): number {\n\tconst timeout = gitTimeout ?? 60000;\n\treturn timeout > 0 ? timeout * 2 : disabledTimeoutBackstopMs;\n}\n\n/**\n * A non-default `untracked`/`branch` gets its own key, so a full-status caller never joins a narrower run; the\n * default stays `'getStatus'`, which `getStatusForPathCore`'s rename path joins.\n */\nfunction getStatusReadKey(options?: { untracked?: 'no' | 'normal' | 'all'; branch?: false }): string {\n\tif (options?.untracked == null && options?.branch !== false) return 'getStatus';\n\n\tconst parts: string[] = [];\n\tif (options?.untracked != null) {\n\t\tparts.push(`untracked=${options.untracked}`);\n\t}\n\tif (options?.branch === false) {\n\t\tparts.push('branch=false');\n\t}\n\treturn `getStatus:${parts.join(':')}`;\n}\n\nexport class StatusGitSubProvider implements GitStatusSubProvider {\n\tconstructor(\n\t\tprivate readonly context: GitServiceContext,\n\t\tprivate readonly git: Git,\n\t\tprivate readonly cache: Cache,\n\t\tprivate readonly provider: CliGitProviderInternal,\n\t) {}\n\n\t/** In-flight reads, keyed by `<read>\\0<repoPath>\\0<generation>`. `PromiseMap` (not a bare Map) so joiners\n\t * get per-caller cancellation: the shared run aborts only when ALL current callers do. */\n\tprivate readonly _pendingReads = new PromiseMap<string, unknown>();\n\n\t/**\n\t * Single-flight for point-in-time reads of mutable working-tree state — replaces `@gate`, which dedups on\n\t * repoPath with no notion of *when* a run started and so joins a pre-change read to a post-change caller\n\t * (see {@link Cache.getStatusGeneration}). Same-generation callers share one run (via a `PromiseMap`, so a\n\t * joiner cancelling can't reject the others); a caller in a newer generation gets a different key and a\n\t * fresh run; a `force` read advances the clock first, so it fences every later reader too, not just itself.\n\t * The exec-layer command dedup (`Git.pendingCommands`) is fenced automatically: each run carries this entry's\n\t * aggregate signal, whose id is part of the exec cache key, so a newer-generation run never joins an older\n\t * one's process; the generation-derived `correlationKey` is a readable second guard on that key. A `raceWithTimeout` backstop (scaled to `advanced.git.timeout`, see\n\t * {@link computeDeadlockBackstopMs}) replaces `@gate`'s 5-min force-clear so a never-settling read (a hung\n\t * `git.stream`, which has no per-command timeout) can't wedge same-generation callers — including blocking\n\t * awaiters like the commit flow — indefinitely.\n\t */\n\tprivate dedupeByStatusGeneration<T>(\n\t\trepoPath: string,\n\t\tread: string,\n\t\trun: (correlationKey: string, signal: AbortSignal | undefined) => Promise<T>,\n\t\tcancellation?: AbortSignal,\n\t\tforce?: boolean,\n\t): Promise<T> {\n\t\t// A `force` (user refresh) read is the user asserting the working tree may have changed without the watcher\n\t\t// observing it — the same class of event as a watcher tick, so it ADVANCES the clock rather than carving out\n\t\t// a private key for itself. A one-shot nonce would fence only this caller: the pre-assertion run stays\n\t\t// joinable, so the very next ordinary reader still picks up pre-change content — and, stamped with a newer\n\t\t// `Wip.revision` (assigned at producer start, not read start), applies it right back over the refreshed\n\t\t// result. Advancing is also CHEAPER than a nonce: concurrent readers join the one fresh read instead of each\n\t\t// spawning a private one.\n\t\tif (force) {\n\t\t\tthis.cache.incrementStatusGeneration(repoPath);\n\t\t}\n\t\tconst generation = this.cache.getStatusGeneration(repoPath);\n\t\t// `\\0` separators: `read`/`repoPath` are free-form (paths can contain `:`), so a printable delimiter could\n\t\t// collide (`getStatusForPath:/a:x` vs `getStatusForPath:/a` + `:x`).\n\t\tconst key = `${read}\\0${repoPath}\\0${generation}`;\n\t\tconst correlationKey = `status:${generation}`;\n\t\tconst backstopMs = computeDeadlockBackstopMs(this.git.options.gitTimeout);\n\n\t\treturn this._pendingReads.getOrCreate(\n\t\t\tkey,\n\t\t\t(cacheable, signal) => {\n\t\t\t\t// Point-in-time read: never memoize a settled value. `invalidate()` at the start makes the entry\n\t\t\t\t// self-evict on settle, so a later caller in this same generation re-reads (a newer generation\n\t\t\t\t// already gets a different key). `signal` is the aggregate — the run aborts only if every caller\n\t\t\t\t// cancels, so one caller's abort can't reject the others. The backstop rejects a wedged read so\n\t\t\t\t// waiters unblock and the entry self-evicts; it ALSO aborts the underlying git op (via `backstop`\n\t\t\t\t// linked into the run's signal) so a wedged read releases its GitQueue slot/process rather than\n\t\t\t\t// running orphaned — best-effort, since a truly-stuck process may ignore the abort.\n\t\t\t\tcacheable.invalidate();\n\t\t\t\tconst backstop = new AbortController();\n\t\t\t\tconst runSignal = signal != null ? AbortSignal.any([signal, backstop.signal]) : backstop.signal;\n\t\t\t\treturn raceWithTimeout(run(correlationKey, runSignal), backstopMs, backstop);\n\t\t\t},\n\t\t\tcancellation,\n\t\t) as Promise<T>;\n\t}\n\n\t@debug()\n\tgetStatus(\n\t\trepoPath: string | undefined,\n\t\toptions?: {\n\t\t\tpriority?: GitCommandPriority;\n\t\t\tforce?: boolean;\n\t\t\tuntracked?: 'no' | 'normal' | 'all';\n\t\t\tbranch?: false;\n\t\t},\n\t\tcancellation?: AbortSignal,\n\t): Promise<GitStatus | undefined> {\n\t\tif (repoPath == null) return Promise.resolve(undefined);\n\n\t\t// `-u` already means `-uall`, so an explicit `'all'` shares the default run\n\t\tconst opts = options?.untracked === 'all' ? { ...options, untracked: undefined } : options;\n\n\t\treturn this.dedupeByStatusGeneration(\n\t\t\trepoPath,\n\t\t\tgetStatusReadKey(opts),\n\t\t\t(correlationKey, signal) =>\n\t\t\t\tthis.getStatusCore(repoPath, { ...opts, correlationKey: correlationKey }, signal),\n\t\t\tcancellation,\n\t\t\topts?.force,\n\t\t);\n\t}\n\n\tprivate async getStatusCore(\n\t\trepoPath: string,\n\t\toptions?: {\n\t\t\tpriority?: GitCommandPriority;\n\t\t\tcorrelationKey?: string;\n\t\t\tuntracked?: 'no' | 'normal' | 'all';\n\t\t\tbranch?: false;\n\t\t},\n\t\tcancellation?: AbortSignal,\n\t): Promise<GitStatus | undefined> {\n\t\tconst porcelainVersion = (await this.git.supports('git:status:porcelain-v2')) ? 2 : 1;\n\n\t\tconst result = await this.statusCore(\n\t\t\trepoPath,\n\t\t\tporcelainVersion,\n\t\t\t{\n\t\t\t\tsimilarityThreshold: this.context.config?.commits.similarityThreshold,\n\t\t\t\tpriority: options?.priority,\n\t\t\t\tcorrelationKey: options?.correlationKey,\n\t\t\t\tuntracked: options?.untracked,\n\t\t\t\tbranch: options?.branch,\n\t\t\t},\n\t\t\tcancellation,\n\t\t);\n\t\tconst status = parseGitStatus(result.stdout, repoPath, porcelainVersion);\n\n\t\tif (options?.branch === false) {\n\t\t\t// Without `--branch` a clean tree prints nothing at all, which the parser reads as no status\n\t\t\tif (status == null && result.completion.status === 'exited' && result.exitCode === 0) {\n\t\t\t\treturn new GitStatus(normalizePath(repoPath), '', '', []);\n\t\t\t}\n\t\t\t// No branch name either, which `GitStatus` reads as detached, so there is no paused rebase to look up\n\t\t\treturn status;\n\t\t}\n\n\t\tif (status?.detached) {\n\t\t\tconst pausedOpStatus = await this.provider.pausedOps?.getPausedOperationStatus?.(\n\t\t\t\trepoPath,\n\t\t\t\tundefined,\n\t\t\t\tcancellation,\n\t\t\t);\n\t\t\tif (pausedOpStatus?.type === 'rebase') {\n\t\t\t\treturn new GitStatus(\n\t\t\t\t\trepoPath,\n\t\t\t\t\tpausedOpStatus.incoming.name,\n\t\t\t\t\tstatus.sha,\n\t\t\t\t\tstatus.files,\n\t\t\t\t\tstatus.upstream,\n\t\t\t\t\ttrue,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t\treturn status;\n\t}\n\n\t@debug()\n\tasync getStatusForFile(\n\t\trepoPath: string,\n\t\tpathOrUri: string | Uri,\n\t\toptions?: { renames?: boolean },\n\t\tcancellation?: AbortSignal,\n\t): Promise<GitStatusFile | undefined> {\n\t\tconst files = await this.getStatusForPathCore(\n\t\t\trepoPath,\n\t\t\ttoFsPath(pathOrUri),\n\t\t\t{ ...options, exact: true },\n\t\t\tcancellation,\n\t\t);\n\t\treturn files?.[0];\n\t}\n\n\t@debug()\n\tasync getStatusForPath(\n\t\trepoPath: string,\n\t\tpathOrUri: string | Uri,\n\t\toptions?: { renames?: boolean },\n\t\tcancellation?: AbortSignal,\n\t): Promise<GitStatusFile[] | undefined> {\n\t\treturn this.getStatusForPathCore(repoPath, toFsPath(pathOrUri), { ...options, exact: false }, cancellation);\n\t}\n\n\tprivate async getStatusForPathCore(\n\t\trepoPath: string,\n\t\tpathOrUri: string,\n\t\toptions: { exact: boolean; renames?: boolean },\n\t\tcancellation?: AbortSignal,\n\t): Promise<GitStatusFile[] | undefined> {\n\t\tconst relativePath = stripFolderGlob(splitPath(pathOrUri, repoPath)[0]);\n\n\t\t// Rename-aware queries can't scope by pathspec (that disables Git's rename detection), so they run the\n\t\t// SAME full `git status` as `getStatus`. Delegate to it — sharing one process/dedup entry — and filter,\n\t\t// rather than spawning a second identical `git status`.\n\t\tif (options.renames !== false) {\n\t\t\tconst status = await this.getStatus(repoPath, undefined, cancellation);\n\t\t\tif (status == null) return undefined;\n\n\t\t\tif (options.exact) {\n\t\t\t\tconst file = status.files.find(f => f.path === relativePath);\n\t\t\t\treturn file ? [file] : undefined;\n\t\t\t}\n\t\t\treturn status.files.filter(f => f.path.startsWith(relativePath));\n\t\t}\n\n\t\t// Non-rename query: pathspec-scoped `git status` (a distinct command), deduped on its own key. `exact`\n\t\t// is NOT in the key — the pathspec-scoped command is identical for exact/non-exact, so both share one run.\n\t\treturn this.dedupeByStatusGeneration(\n\t\t\trepoPath,\n\t\t\t// Free-form `relativePath` (may contain `:`) goes LAST so the prefix can't be shifted into it.\n\t\t\t`getStatusForPath:${relativePath}`,\n\t\t\t(correlationKey, signal) => this.getStatusForPathScoped(repoPath, relativePath, correlationKey, signal),\n\t\t\tcancellation,\n\t\t);\n\t}\n\n\tprivate async getStatusForPathScoped(\n\t\trepoPath: string,\n\t\trelativePath: string,\n\t\tcorrelationKey: string,\n\t\tcancellation?: AbortSignal,\n\t): Promise<GitStatusFile[] | undefined> {\n\t\tconst porcelainVersion = (await this.git.supports('git:status:porcelain-v2')) ? 2 : 1;\n\t\tconst result = await this.statusCore(\n\t\t\trepoPath,\n\t\t\tporcelainVersion,\n\t\t\t{ similarityThreshold: this.context.config?.commits.similarityThreshold, correlationKey: correlationKey },\n\t\t\tcancellation,\n\t\t\trelativePath,\n\t\t);\n\n\t\tconst status = parseGitStatus(result.stdout, repoPath, porcelainVersion);\n\t\treturn status?.files;\n\t}\n\n\tprivate async statusCore(\n\t\trepoPath: string,\n\t\tporcelainVersion: number = 1,\n\t\toptions?: {\n\t\t\tsimilarityThreshold?: number | null;\n\t\t\tpriority?: GitCommandPriority;\n\t\t\tcorrelationKey?: string;\n\t\t\tuntracked?: 'no' | 'normal' | 'all';\n\t\t\tbranch?: false;\n\t\t},\n\t\tcancellation?: AbortSignal,\n\t\t...pathspecs: string[]\n\t): Promise<GitResult> {\n\t\tconst params = ['status', porcelainVersion >= 2 ? `--porcelain=v${porcelainVersion}` : '--porcelain'];\n\t\tif (options?.branch !== false) {\n\t\t\tparams.push('--branch');\n\t\t}\n\t\tparams.push(options?.untracked != null ? `-u${options.untracked}` : '-u');\n\t\tif (await this.git.supports('git:status:find-renames')) {\n\t\t\tparams.push(\n\t\t\t\t`--find-renames${options?.similarityThreshold == null ? '' : `=${options.similarityThreshold}%`}`,\n\t\t\t);\n\t\t}\n\n\t\treturn this.git.run(\n\t\t\t{\n\t\t\t\tcwd: repoPath,\n\t\t\t\tcancellation: cancellation,\n\t\t\t\tconfigs: gitConfigsStatus,\n\t\t\t\tenv: { GIT_OPTIONAL_LOCKS: '0' },\n\t\t\t\tcorrelationKey: options?.correlationKey,\n\t\t\t\t...(options?.priority != null ? { priority: options.priority } : undefined),\n\t\t\t},\n\t\t\t...params,\n\t\t\t'--',\n\t\t\t...pathspecs,\n\t\t);\n\t}\n\n\t@debug()\n\thasWorkingChanges(\n\t\trepoPath: string,\n\t\toptions?: {\n\t\t\tstaged?: boolean;\n\t\t\tunstaged?: boolean;\n\t\t\tuntracked?: boolean;\n\t\t\tthrowOnError?: boolean;\n\t\t\tpriority?: GitCommandPriority;\n\t\t},\n\t\tcancellation?: AbortSignal,\n\t): Promise<boolean> {\n\t\tconst scope = getScopedLogger();\n\n\t\tconst staged = options?.staged ?? true;\n\t\tconst unstaged = options?.unstaged ?? true;\n\t\tconst untracked = options?.untracked ?? true;\n\t\t// `throwOnError` is in the key because it changes the RESULT on git failure (throw vs `false`) — a\n\t\t// throwing caller must not join, and be silently satisfied by, a non-throwing caller's graceful run.\n\t\t// `priority` is in the key for a different reason: it doesn't change the answer, but a joiner INHERITS\n\t\t// the in-flight run's scheduling. `GitQueue` refuses to start background work while anything is\n\t\t// waiting at normal/interactive, so letting a foreground caller (the overview's dirty pill, the\n\t\t// Worktrees view) join a `background` probe would strand it for the whole graph load. Splitting the\n\t\t// two lanes costs at most one extra spawn; joining costs unbounded foreground latency.\n\t\tconst throwOnError = options?.throwOnError ?? false;\n\t\tconst priority = options?.priority ?? 'default';\n\n\t\treturn this.dedupeByStatusGeneration(\n\t\t\trepoPath,\n\t\t\t`hasWorkingChanges:${staged}:${unstaged}:${untracked}:${throwOnError}:${priority}`,\n\t\t\tasync (correlationKey, signal) => {\n\t\t\t\ttry {\n\t\t\t\t\tif (staged || unstaged) {\n\t\t\t\t\t\tconst result = await this.git.run(\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tcwd: repoPath,\n\t\t\t\t\t\t\t\tcancellation: signal,\n\t\t\t\t\t\t\t\terrors: 'ignore',\n\t\t\t\t\t\t\t\tcorrelationKey: correlationKey,\n\t\t\t\t\t\t\t\t...(options?.priority != null ? { priority: options.priority } : undefined),\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t'diff',\n\t\t\t\t\t\t\t'--quiet',\n\t\t\t\t\t\t\tstaged && unstaged ? 'HEAD' : staged ? '--staged' : undefined,\n\t\t\t\t\t\t);\n\t\t\t\t\t\tif (result.exitCode === 1) {\n\t\t\t\t\t\t\tif (staged && unstaged) {\n\t\t\t\t\t\t\t\tscope?.addExitInfo('has staged and unstaged changes');\n\t\t\t\t\t\t\t} else if (staged) {\n\t\t\t\t\t\t\t\tscope?.addExitInfo('has staged changes');\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tscope?.addExitInfo('has unstaged changes');\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\treturn true;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\t// Check for untracked files. NOTE: this runs through `git.stream`, which spawns directly\n\t\t\t\t\t// and never enters the queue — so `priority` above doesn't reach it. Only clean worktrees\n\t\t\t\t\t// get this far (a dirty one already returned above), and callers fanning out across many\n\t\t\t\t\t// worktrees are expected to bound their own concurrency.\n\t\t\t\t\tif (untracked) {\n\t\t\t\t\t\tconst hasUntracked = await this.hasUntrackedFiles(repoPath, signal);\n\t\t\t\t\t\tif (hasUntracked) {\n\t\t\t\t\t\t\tscope?.addExitInfo('has untracked files');\n\t\t\t\t\t\t\treturn true;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tscope?.addExitInfo('no working changes');\n\t\t\t\t\treturn false;\n\t\t\t\t} catch (ex) {\n\t\t\t\t\t// Re-throw cancellation errors\n\t\t\t\t\tif (isCancellationError(ex)) throw ex;\n\n\t\t\t\t\t// Log other errors and return false for graceful degradation\n\t\t\t\t\tscope?.error(ex);\n\t\t\t\t\tscope?.addExitInfo('error checking for changes');\n\t\t\t\t\tif (throwOnError) throw ex;\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t},\n\t\t\tcancellation,\n\t\t);\n\t}\n\n\t@debug()\n\tgetWorkingChangesState(repoPath: string, cancellation?: AbortSignal): Promise<GitWorkingChangesState> {\n\t\tconst scope = getScopedLogger();\n\n\t\treturn this.dedupeByStatusGeneration(\n\t\t\trepoPath,\n\t\t\t'getWorkingChangesState',\n\t\t\tasync (correlationKey, signal) => {\n\t\t\t\ttry {\n\t\t\t\t\tconst [stagedResult, unstagedResult, untrackedResult] = await Promise.allSettled([\n\t\t\t\t\t\t// Check for staged changes\n\t\t\t\t\t\tthis.git.run(\n\t\t\t\t\t\t\t{ cwd: repoPath, cancellation: signal, errors: 'ignore', correlationKey: correlationKey },\n\t\t\t\t\t\t\t'diff',\n\t\t\t\t\t\t\t'--quiet',\n\t\t\t\t\t\t\t'--staged',\n\t\t\t\t\t\t),\n\t\t\t\t\t\t// Check for unstaged changes\n\t\t\t\t\t\tthis.git.run(\n\t\t\t\t\t\t\t{ cwd: repoPath, cancellation: signal, errors: 'ignore', correlationKey: correlationKey },\n\t\t\t\t\t\t\t'diff',\n\t\t\t\t\t\t\t'--quiet',\n\t\t\t\t\t\t),\n\t\t\t\t\t\t// Check for untracked files\n\t\t\t\t\t\tthis.hasUntrackedFiles(repoPath, signal),\n\t\t\t\t\t]);\n\n\t\t\t\t\tconst result = {\n\t\t\t\t\t\tstaged: getSettledValue(stagedResult)?.exitCode === 1,\n\t\t\t\t\t\tunstaged: getSettledValue(unstagedResult)?.exitCode === 1,\n\t\t\t\t\t\tuntracked: getSettledValue(untrackedResult) === true,\n\t\t\t\t\t};\n\n\t\t\t\t\tscope?.addExitInfo(\n\t\t\t\t\t\tresult.staged || result.unstaged || result.untracked\n\t\t\t\t\t\t\t? `has ${result.staged ? 'staged' : ''}${result.unstaged ? (result.staged ? ', unstaged' : 'unstaged ') : ''}${\n\t\t\t\t\t\t\t\t\tresult.untracked\n\t\t\t\t\t\t\t\t\t\t? result.staged || result.unstaged\n\t\t\t\t\t\t\t\t\t\t\t? ', untracked'\n\t\t\t\t\t\t\t\t\t\t\t: 'untracked'\n\t\t\t\t\t\t\t\t\t\t: ''\n\t\t\t\t\t\t\t\t} changes`\n\t\t\t\t\t\t\t: 'no working changes',\n\t\t\t\t\t);\n\n\t\t\t\t\treturn result;\n\t\t\t\t} catch (ex) {\n\t\t\t\t\tif (isCancellationError(ex)) throw ex;\n\n\t\t\t\t\tscope?.error(ex);\n\t\t\t\t\tscope?.addExitInfo('error checking for changes');\n\t\t\t\t\t// Return all false on error for graceful degradation\n\t\t\t\t\treturn { staged: false, unstaged: false, untracked: false };\n\t\t\t\t}\n\t\t\t},\n\t\t\tcancellation,\n\t\t);\n\t}\n\n\thasConflictingFiles(repoPath: string, cancellation?: AbortSignal): Promise<boolean> {\n\t\t// Route through `dedupeByStatusGeneration` (like every other point-in-time read here) for the join-fence\n\t\t// and the `raceWithTimeout` backstop — a bare `git.stream` has no per-command timeout and could wedge.\n\t\treturn this.dedupeByStatusGeneration(\n\t\t\trepoPath,\n\t\t\t'hasConflictingFiles',\n\t\t\tasync (_correlationKey, signal) => {\n\t\t\t\ttry {\n\t\t\t\t\tconst stream = this.git.stream({ cwd: repoPath, cancellation: signal }, 'ls-files', '--unmerged');\n\t\t\t\t\tusing _streamDisposer = createDisposable(() => void stream.return?.(undefined));\n\n\t\t\t\t\t// Early exit on first chunk - breaking causes SIGPIPE, killing git process\n\t\t\t\t\tfor await (const _chunk of stream) {\n\t\t\t\t\t\treturn true;\n\t\t\t\t\t}\n\n\t\t\t\t\treturn false;\n\t\t\t\t} catch (ex) {\n\t\t\t\t\t// Re-throw cancellation errors\n\t\t\t\t\tif (isCancellationError(ex)) throw ex;\n\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t},\n\t\t\tcancellation,\n\t\t);\n\t}\n\n\t@debug()\n\tgetConflictingFiles(repoPath: string, cancellation?: AbortSignal): Promise<GitConflictFile[]> {\n\t\tconst scope = getScopedLogger();\n\n\t\treturn this.dedupeByStatusGeneration(\n\t\t\trepoPath,\n\t\t\t'getConflictingFiles',\n\t\t\tasync (correlationKey, signal) => {\n\t\t\t\ttry {\n\t\t\t\t\tconst result = await this.git.run(\n\t\t\t\t\t\t{ cwd: repoPath, cancellation: signal, errors: 'ignore', correlationKey: correlationKey },\n\t\t\t\t\t\t'ls-files',\n\t\t\t\t\t\t'-z',\n\t\t\t\t\t\t'--unmerged',\n\t\t\t\t\t);\n\n\t\t\t\t\tif (!result.stdout) {\n\t\t\t\t\t\tscope?.addExitInfo('no conflicting files');\n\t\t\t\t\t\treturn [];\n\t\t\t\t\t}\n\n\t\t\t\t\tconst files = parseGitConflictFiles(result.stdout, repoPath);\n\t\t\t\t\tscope?.addExitInfo(`${String(files.length)} conflicting file(s)`);\n\t\t\t\t\treturn files;\n\t\t\t\t} catch (ex) {\n\t\t\t\t\t// Re-throw cancellation errors\n\t\t\t\t\tif (isCancellationError(ex)) throw ex;\n\n\t\t\t\t\t// Log other errors and return empty array for graceful degradation\n\t\t\t\t\tscope?.error(ex);\n\t\t\t\t\tscope?.addExitInfo('error getting conflicting files');\n\t\t\t\t\treturn [];\n\t\t\t\t}\n\t\t\t},\n\t\t\tcancellation,\n\t\t);\n\t}\n\n\tprivate async hasUntrackedFiles(repoPath: string, cancellation?: AbortSignal): Promise<boolean> {\n\t\ttry {\n\t\t\tconst stream = this.git.stream(\n\t\t\t\t{ cwd: repoPath, cancellation: cancellation },\n\t\t\t\t'ls-files',\n\t\t\t\t// '-z', // Unneeded since we are only looking for presence\n\t\t\t\t'--others',\n\t\t\t\t'--exclude-standard',\n\t\t\t);\n\t\t\tusing _streamDisposer = createDisposable(() => void stream.return?.(undefined));\n\n\t\t\t// Early exit on first chunk - breaking causes SIGPIPE, killing git process\n\t\t\tfor await (const _chunk of stream) {\n\t\t\t\treturn true;\n\t\t\t}\n\n\t\t\treturn false;\n\t\t} catch (ex) {\n\t\t\t// Re-throw cancellation errors\n\t\t\tif (isCancellationError(ex)) throw ex;\n\n\t\t\t// Treat other errors as \"no untracked files\" for graceful degradation\n\t\t\treturn false;\n\t\t}\n\t}\n\n\t@debug()\n\tgetUntrackedFiles(repoPath: string, cancellation?: AbortSignal): Promise<GitFile[]> {\n\t\tconst scope = getScopedLogger();\n\n\t\treturn this.dedupeByStatusGeneration(\n\t\t\trepoPath,\n\t\t\t'getUntrackedFiles',\n\t\t\tasync (correlationKey, signal) => {\n\t\t\t\ttry {\n\t\t\t\t\tconst result = await this.git.run(\n\t\t\t\t\t\t{ cwd: repoPath, cancellation: signal, errors: 'ignore', correlationKey: correlationKey },\n\t\t\t\t\t\t'ls-files',\n\t\t\t\t\t\t'-z',\n\t\t\t\t\t\t'--others',\n\t\t\t\t\t\t'--exclude-standard',\n\t\t\t\t\t);\n\n\t\t\t\t\tif (!result.stdout) {\n\t\t\t\t\t\tscope?.addExitInfo('no untracked files');\n\t\t\t\t\t\treturn [];\n\t\t\t\t\t}\n\n\t\t\t\t\tconst files: GitFile[] = [];\n\n\t\t\t\t\tfor (const line of iterateByDelimiter(result.stdout, '\\0')) {\n\t\t\t\t\t\tif (!line.length) continue;\n\n\t\t\t\t\t\tfiles.push({ path: line, repoPath: repoPath, status: GitFileWorkingTreeStatus.Untracked });\n\t\t\t\t\t}\n\n\t\t\t\t\tscope?.addExitInfo(`${String(files.length)} untracked file(s)`);\n\t\t\t\t\treturn files;\n\t\t\t\t} catch (ex) {\n\t\t\t\t\t// Re-throw cancellation errors\n\t\t\t\t\tif (isCancellationError(ex)) throw ex;\n\n\t\t\t\t\t// Log other errors and return empty array for graceful degradation\n\t\t\t\t\tscope?.error(ex);\n\t\t\t\t\tscope?.addExitInfo('error getting untracked files');\n\t\t\t\t\treturn [];\n\t\t\t\t}\n\t\t\t},\n\t\t\tcancellation,\n\t\t);\n\t}\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"status.js","names":[],"sources":["../../../../git-cli/src/providers/status.ts"],"sourcesContent":["import type { Cache } from '@gitlens/git/cache.js';\nimport type { GitServiceContext } from '@gitlens/git/context.js';\nimport type { GitFile } from '@gitlens/git/models/file.js';\nimport { GitFileWorkingTreeStatus } from '@gitlens/git/models/fileStatus.js';\nimport type { GitConflictFile } from '@gitlens/git/models/staging.js';\nimport { GitStatus } from '@gitlens/git/models/status.js';\nimport type { GitStatusFile } from '@gitlens/git/models/statusFile.js';\nimport type { GitStatusSubProvider, GitWorkingChangesState } from '@gitlens/git/providers/status.js';\nimport type { GitCommandPriority, GitErrorHandling } from '@gitlens/git/run.types.js';\nimport { raceWithTimeout } from '@gitlens/utils/cancellation.js';\nimport { debug } from '@gitlens/utils/decorators/log.js';\nimport { createDisposable } from '@gitlens/utils/disposable.js';\nimport { getScopedLogger } from '@gitlens/utils/logger.scoped.js';\nimport { normalizePath, splitPath, stripFolderGlob } from '@gitlens/utils/path.js';\nimport { getSettledValue } from '@gitlens/utils/promise.js';\nimport { PromiseMap } from '@gitlens/utils/promiseCache.js';\nimport { iterateByDelimiter } from '@gitlens/utils/string.js';\nimport type { Uri } from '@gitlens/utils/uri.js';\nimport { toFsPath } from '@gitlens/utils/uri.js';\nimport type { CliGitProviderInternal } from '../cliGitProvider.js';\nimport type { GitResult } from '../exec/exec.types.js';\nimport type { Git } from '../exec/git.js';\nimport { defaultExceptionHandler, gitConfigsStatus, GitErrors } from '../exec/git.js';\nimport { parseGitConflictFiles } from '../parsers/indexParser.js';\nimport { parseGitStatus } from '../parsers/statusParser.js';\n\n/** Backstop when `advanced.git.timeout` is disabled (0). Independent of the command timeout so disabling it can't\n * also disable deadlock recovery — mirrors `@gate`'s always-on 5-min force-clear. */\nconst disabledTimeoutBackstopMs = 1000 * 60 * 5;\n\n/**\n * Deadlock-backstop duration for a status read (see `dedupeByStatusGeneration`), exported for testing.\n * Always returns a value so a wedged read recovers even with `advanced.git.timeout` disabled (which bounds git\n * *commands*, not deadlock recovery). With a timeout set, `gitTimeout * 2` sits above the per-command timeout so\n * `git.run` reads reject at their own timeout first; the backstop's real job is bounding the timeout-less\n * `git.stream` reads (`hasUntrackedFiles`/`hasConflictingFiles`).\n */\nexport function computeDeadlockBackstopMs(gitTimeout: number | undefined): number {\n\tconst timeout = gitTimeout ?? 60000;\n\treturn timeout > 0 ? timeout * 2 : disabledTimeoutBackstopMs;\n}\n\n/**\n * A non-default `untracked`/`branch` gets its own key, so a full-status caller never joins a narrower run; the\n * default stays `'getStatus'`, which `getStatusForPathCore`'s rename path joins.\n */\nfunction getStatusReadKey(options?: { untracked?: 'no' | 'normal' | 'all'; branch?: false }): string {\n\tif (options?.untracked == null && options?.branch !== false) return 'getStatus';\n\n\tconst parts: string[] = [];\n\tif (options?.untracked != null) {\n\t\tparts.push(`untracked=${options.untracked}`);\n\t}\n\tif (options?.branch === false) {\n\t\tparts.push('branch=false');\n\t}\n\treturn `getStatus:${parts.join(':')}`;\n}\n\nexport class StatusGitSubProvider implements GitStatusSubProvider {\n\tconstructor(\n\t\tprivate readonly context: GitServiceContext,\n\t\tprivate readonly git: Git,\n\t\tprivate readonly cache: Cache,\n\t\tprivate readonly provider: CliGitProviderInternal,\n\t) {}\n\n\t/** In-flight reads, keyed by `<read>\\0<repoPath>\\0<generation>`. `PromiseMap` (not a bare Map) so joiners\n\t * get per-caller cancellation: the shared run aborts only when ALL current callers do. */\n\tprivate readonly _pendingReads = new PromiseMap<string, unknown>();\n\n\t/**\n\t * Single-flight for point-in-time reads of mutable working-tree state — replaces `@gate`, which dedups on\n\t * repoPath with no notion of *when* a run started and so joins a pre-change read to a post-change caller\n\t * (see {@link Cache.getStatusGeneration}). Same-generation callers share one run (via a `PromiseMap`, so a\n\t * joiner cancelling can't reject the others); a caller in a newer generation gets a different key and a\n\t * fresh run; a `force` read advances the clock first, so it fences every later reader too, not just itself.\n\t * The exec-layer command dedup (`Git.pendingCommands`) is fenced automatically: each run carries this entry's\n\t * aggregate signal, whose id is part of the exec cache key, so a newer-generation run never joins an older\n\t * one's process; the generation-derived `correlationKey` is a readable second guard on that key. A `raceWithTimeout` backstop (scaled to `advanced.git.timeout`, see\n\t * {@link computeDeadlockBackstopMs}) replaces `@gate`'s 5-min force-clear so a never-settling read (a hung\n\t * `git.stream`, which has no per-command timeout) can't wedge same-generation callers — including blocking\n\t * awaiters like the commit flow — indefinitely.\n\t */\n\tprivate dedupeByStatusGeneration<T>(\n\t\trepoPath: string,\n\t\tread: string,\n\t\trun: (correlationKey: string, signal: AbortSignal | undefined) => Promise<T>,\n\t\tcancellation?: AbortSignal,\n\t\tforce?: boolean,\n\t): Promise<T> {\n\t\t// A `force` (user refresh) read is the user asserting the working tree may have changed without the watcher\n\t\t// observing it — the same class of event as a watcher tick, so it ADVANCES the clock rather than carving out\n\t\t// a private key for itself. A one-shot nonce would fence only this caller: the pre-assertion run stays\n\t\t// joinable, so the very next ordinary reader still picks up pre-change content — and, stamped with a newer\n\t\t// `Wip.revision` (assigned at producer start, not read start), applies it right back over the refreshed\n\t\t// result. Advancing is also CHEAPER than a nonce: concurrent readers join the one fresh read instead of each\n\t\t// spawning a private one.\n\t\tif (force) {\n\t\t\tthis.cache.incrementStatusGeneration(repoPath);\n\t\t}\n\t\tconst generation = this.cache.getStatusGeneration(repoPath);\n\t\t// `\\0` separators: `read`/`repoPath` are free-form (paths can contain `:`), so a printable delimiter could\n\t\t// collide (`getStatusForPath:/a:x` vs `getStatusForPath:/a` + `:x`).\n\t\tconst key = `${read}\\0${repoPath}\\0${generation}`;\n\t\tconst correlationKey = `status:${generation}`;\n\t\tconst backstopMs = computeDeadlockBackstopMs(this.git.options.gitTimeout);\n\n\t\treturn this._pendingReads.getOrCreate(\n\t\t\tkey,\n\t\t\t(cacheable, signal) => {\n\t\t\t\t// Point-in-time read: never memoize a settled value. `invalidate()` at the start makes the entry\n\t\t\t\t// self-evict on settle, so a later caller in this same generation re-reads (a newer generation\n\t\t\t\t// already gets a different key). `signal` is the aggregate — the run aborts only if every caller\n\t\t\t\t// cancels, so one caller's abort can't reject the others. The backstop rejects a wedged read so\n\t\t\t\t// waiters unblock and the entry self-evicts; it ALSO aborts the underlying git op (via `backstop`\n\t\t\t\t// linked into the run's signal) so a wedged read releases its GitQueue slot/process rather than\n\t\t\t\t// running orphaned — best-effort, since a truly-stuck process may ignore the abort.\n\t\t\t\tcacheable.invalidate();\n\t\t\t\tconst backstop = new AbortController();\n\t\t\t\tconst runSignal = signal != null ? AbortSignal.any([signal, backstop.signal]) : backstop.signal;\n\t\t\t\treturn raceWithTimeout(run(correlationKey, runSignal), backstopMs, backstop);\n\t\t\t},\n\t\t\tcancellation,\n\t\t) as Promise<T>;\n\t}\n\n\t@debug()\n\tgetStatus(\n\t\trepoPath: string | undefined,\n\t\toptions?: {\n\t\t\tpriority?: GitCommandPriority;\n\t\t\tforce?: boolean;\n\t\t\tuntracked?: 'no' | 'normal' | 'all';\n\t\t\tbranch?: false;\n\t\t},\n\t\tcancellation?: AbortSignal,\n\t): Promise<GitStatus | undefined> {\n\t\tif (repoPath == null) return Promise.resolve(undefined);\n\n\t\t// `-u` already means `-uall`, so an explicit `'all'` shares the default run\n\t\tconst opts = options?.untracked === 'all' ? { ...options, untracked: undefined } : options;\n\n\t\treturn this.dedupeByStatusGeneration(\n\t\t\trepoPath,\n\t\t\tgetStatusReadKey(opts),\n\t\t\t(correlationKey, signal) =>\n\t\t\t\tthis.getStatusCore(repoPath, { ...opts, correlationKey: correlationKey }, signal),\n\t\t\tcancellation,\n\t\t\topts?.force,\n\t\t);\n\t}\n\n\tprivate async getStatusCore(\n\t\trepoPath: string,\n\t\toptions?: {\n\t\t\tpriority?: GitCommandPriority;\n\t\t\tcorrelationKey?: string;\n\t\t\tuntracked?: 'no' | 'normal' | 'all';\n\t\t\tbranch?: false;\n\t\t},\n\t\tcancellation?: AbortSignal,\n\t): Promise<GitStatus | undefined> {\n\t\tconst porcelainVersion = (await this.git.supports('git:status:porcelain-v2')) ? 2 : 1;\n\n\t\tconst result = await this.statusCore(\n\t\t\trepoPath,\n\t\t\tporcelainVersion,\n\t\t\t{\n\t\t\t\tsimilarityThreshold: this.context.config?.commits.similarityThreshold,\n\t\t\t\tpriority: options?.priority,\n\t\t\t\tcorrelationKey: options?.correlationKey,\n\t\t\t\tuntracked: options?.untracked,\n\t\t\t\tbranch: options?.branch,\n\t\t\t\terrors: 'throw',\n\t\t\t},\n\t\t\tcancellation,\n\t\t);\n\t\tconst status = parseGitStatus(result.stdout, repoPath, porcelainVersion);\n\n\t\tif (options?.branch === false) {\n\t\t\t// Without `--branch` a clean tree prints nothing at all, which the parser reads as no status\n\t\t\tif (status == null && result.completion.status === 'exited' && result.exitCode === 0) {\n\t\t\t\treturn new GitStatus(normalizePath(repoPath), '', '', []);\n\t\t\t}\n\t\t\t// No branch name either, which `GitStatus` reads as detached, so there is no paused rebase to look up\n\t\t\treturn status;\n\t\t}\n\n\t\tif (status?.detached) {\n\t\t\tconst pausedOpStatus = await this.provider.pausedOps?.getPausedOperationStatus?.(\n\t\t\t\trepoPath,\n\t\t\t\tundefined,\n\t\t\t\tcancellation,\n\t\t\t);\n\t\t\tif (pausedOpStatus?.type === 'rebase') {\n\t\t\t\treturn new GitStatus(\n\t\t\t\t\trepoPath,\n\t\t\t\t\tpausedOpStatus.incoming.name,\n\t\t\t\t\tstatus.sha,\n\t\t\t\t\tstatus.files,\n\t\t\t\t\tstatus.upstream,\n\t\t\t\t\ttrue,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t\treturn status;\n\t}\n\n\t@debug()\n\tasync getStatusForFile(\n\t\trepoPath: string,\n\t\tpathOrUri: string | Uri,\n\t\toptions?: { renames?: boolean },\n\t\tcancellation?: AbortSignal,\n\t): Promise<GitStatusFile | undefined> {\n\t\tconst files = await this.getStatusForPathCore(\n\t\t\trepoPath,\n\t\t\ttoFsPath(pathOrUri),\n\t\t\t{ ...options, exact: true },\n\t\t\tcancellation,\n\t\t);\n\t\treturn files?.[0];\n\t}\n\n\t@debug()\n\tasync getStatusForPath(\n\t\trepoPath: string,\n\t\tpathOrUri: string | Uri,\n\t\toptions?: { renames?: boolean },\n\t\tcancellation?: AbortSignal,\n\t): Promise<GitStatusFile[] | undefined> {\n\t\treturn this.getStatusForPathCore(repoPath, toFsPath(pathOrUri), { ...options, exact: false }, cancellation);\n\t}\n\n\tprivate async getStatusForPathCore(\n\t\trepoPath: string,\n\t\tpathOrUri: string,\n\t\toptions: { exact: boolean; renames?: boolean },\n\t\tcancellation?: AbortSignal,\n\t): Promise<GitStatusFile[] | undefined> {\n\t\tconst relativePath = stripFolderGlob(splitPath(pathOrUri, repoPath)[0]);\n\n\t\t// Rename-aware queries can't scope by pathspec (that disables Git's rename detection), so they run the\n\t\t// SAME full `git status` as `getStatus`. Delegate to it — sharing one process/dedup entry — and filter,\n\t\t// rather than spawning a second identical `git status`.\n\t\tif (options.renames !== false) {\n\t\t\t// The rename path keeps the default handling the scoped read gets\n\t\t\tconst status = await this.getStatus(repoPath, undefined, cancellation).catch((ex: unknown) => {\n\t\t\t\tdefaultExceptionHandler(ex as Error, repoPath);\n\t\t\t\treturn undefined;\n\t\t\t});\n\t\t\tif (status == null) return undefined;\n\n\t\t\tif (options.exact) {\n\t\t\t\tconst file = status.files.find(f => f.path === relativePath);\n\t\t\t\treturn file ? [file] : undefined;\n\t\t\t}\n\t\t\treturn status.files.filter(f => f.path.startsWith(relativePath));\n\t\t}\n\n\t\t// Non-rename query: pathspec-scoped `git status` (a distinct command), deduped on its own key. `exact`\n\t\t// is NOT in the key — the pathspec-scoped command is identical for exact/non-exact, so both share one run.\n\t\treturn this.dedupeByStatusGeneration(\n\t\t\trepoPath,\n\t\t\t// Free-form `relativePath` (may contain `:`) goes LAST so the prefix can't be shifted into it.\n\t\t\t`getStatusForPath:${relativePath}`,\n\t\t\t(correlationKey, signal) => this.getStatusForPathScoped(repoPath, relativePath, correlationKey, signal),\n\t\t\tcancellation,\n\t\t);\n\t}\n\n\tprivate async getStatusForPathScoped(\n\t\trepoPath: string,\n\t\trelativePath: string,\n\t\tcorrelationKey: string,\n\t\tcancellation?: AbortSignal,\n\t): Promise<GitStatusFile[] | undefined> {\n\t\tconst porcelainVersion = (await this.git.supports('git:status:porcelain-v2')) ? 2 : 1;\n\t\tconst result = await this.statusCore(\n\t\t\trepoPath,\n\t\t\tporcelainVersion,\n\t\t\t{ similarityThreshold: this.context.config?.commits.similarityThreshold, correlationKey: correlationKey },\n\t\t\tcancellation,\n\t\t\trelativePath,\n\t\t);\n\n\t\tconst status = parseGitStatus(result.stdout, repoPath, porcelainVersion);\n\t\treturn status?.files;\n\t}\n\n\tprivate async statusCore(\n\t\trepoPath: string,\n\t\tporcelainVersion: number = 1,\n\t\toptions?: {\n\t\t\tsimilarityThreshold?: number | null;\n\t\t\tpriority?: GitCommandPriority;\n\t\t\tcorrelationKey?: string;\n\t\t\tuntracked?: 'no' | 'normal' | 'all';\n\t\t\tbranch?: false;\n\t\t\terrors?: GitErrorHandling;\n\t\t},\n\t\tcancellation?: AbortSignal,\n\t\t...pathspecs: string[]\n\t): Promise<GitResult> {\n\t\tconst params = ['status', porcelainVersion >= 2 ? `--porcelain=v${porcelainVersion}` : '--porcelain'];\n\t\tif (options?.branch !== false) {\n\t\t\tparams.push('--branch');\n\t\t}\n\t\tparams.push(options?.untracked != null ? `-u${options.untracked}` : '-u');\n\t\tif (await this.git.supports('git:status:find-renames')) {\n\t\t\tparams.push(\n\t\t\t\t`--find-renames${options?.similarityThreshold == null ? '' : `=${options.similarityThreshold}%`}`,\n\t\t\t);\n\t\t}\n\n\t\treturn this.git.run(\n\t\t\t{\n\t\t\t\tcwd: repoPath,\n\t\t\t\tcancellation: cancellation,\n\t\t\t\tconfigs: gitConfigsStatus,\n\t\t\t\tenv: { GIT_OPTIONAL_LOCKS: '0' },\n\t\t\t\tcorrelationKey: options?.correlationKey,\n\t\t\t\t...(options?.errors != null ? { errors: options.errors } : undefined),\n\t\t\t\t...(options?.priority != null ? { priority: options.priority } : undefined),\n\t\t\t},\n\t\t\t...params,\n\t\t\t'--',\n\t\t\t...pathspecs,\n\t\t);\n\t}\n\n\t@debug()\n\thasWorkingChanges(\n\t\trepoPath: string,\n\t\toptions?: {\n\t\t\tstaged?: boolean;\n\t\t\tunstaged?: boolean;\n\t\t\tuntracked?: boolean;\n\t\t\tpriority?: GitCommandPriority;\n\t\t},\n\t\tcancellation?: AbortSignal,\n\t): Promise<boolean> {\n\t\tconst scope = getScopedLogger();\n\n\t\tconst staged = options?.staged ?? true;\n\t\tconst unstaged = options?.unstaged ?? true;\n\t\tconst untracked = options?.untracked ?? true;\n\t\t// `priority` is in the key even though it doesn't change the answer: a joiner INHERITS the in-flight run's\n\t\t// scheduling. `GitQueue` refuses to start background work while anything is waiting at normal/interactive,\n\t\t// so letting a foreground caller (the overview's dirty pill, the Worktrees view) join a `background` probe\n\t\t// would strand it for the whole graph load. Splitting the two lanes costs at most one extra spawn; joining\n\t\t// costs unbounded foreground latency.\n\t\tconst priority = options?.priority ?? 'default';\n\n\t\treturn this.dedupeByStatusGeneration(\n\t\t\trepoPath,\n\t\t\t`hasWorkingChanges:${staged}:${unstaged}:${untracked}:${priority}`,\n\t\t\tasync (correlationKey, signal) => {\n\t\t\t\tif (staged || unstaged) {\n\t\t\t\t\tconst diffQuiet = (revision: string | undefined) =>\n\t\t\t\t\t\tthis.git.run(\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tcwd: repoPath,\n\t\t\t\t\t\t\t\tcancellation: signal,\n\t\t\t\t\t\t\t\terrors: 'throw',\n\t\t\t\t\t\t\t\texpectedExitCodes: [1],\n\t\t\t\t\t\t\t\tcorrelationKey: correlationKey,\n\t\t\t\t\t\t\t\t...(options?.priority != null ? { priority: options.priority } : undefined),\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t'diff',\n\t\t\t\t\t\t\t'--quiet',\n\t\t\t\t\t\t\trevision,\n\t\t\t\t\t\t\t'--',\n\t\t\t\t\t\t);\n\n\t\t\t\t\tlet result;\n\t\t\t\t\tif (staged && unstaged) {\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tresult = await diffQuiet('HEAD');\n\t\t\t\t\t\t} catch (ex) {\n\t\t\t\t\t\t\t// Before the first commit HEAD names nothing; everything staged or modified is a change\n\t\t\t\t\t\t\t// against the empty tree. `diff HEAD --` names only HEAD, so its \"bad revision\" is that case\n\t\t\t\t\t\t\t// without re-validating HEAD (a corrupt HEAD then reads as dirty, the safe answer)\n\t\t\t\t\t\t\tif (GitErrors.badRevision.exec(String(ex))?.[1]?.toUpperCase() !== 'HEAD') throw ex;\n\n\t\t\t\t\t\t\tresult = await diffQuiet(await this.provider.revision.getEmptyTreeSha(repoPath));\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\tresult = await diffQuiet(staged ? '--staged' : undefined);\n\t\t\t\t\t}\n\n\t\t\t\t\tif (result.exitCode === 1) {\n\t\t\t\t\t\tif (staged && unstaged) {\n\t\t\t\t\t\t\tscope?.addExitInfo('has staged and unstaged changes');\n\t\t\t\t\t\t} else if (staged) {\n\t\t\t\t\t\t\tscope?.addExitInfo('has staged changes');\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tscope?.addExitInfo('has unstaged changes');\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn true;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// Check for untracked files. NOTE: this runs through `git.stream`, which spawns directly\n\t\t\t\t// and never enters the queue — so `priority` above doesn't reach it. Only clean worktrees\n\t\t\t\t// get this far (a dirty one already returned above), and callers fanning out across many\n\t\t\t\t// worktrees are expected to bound their own concurrency.\n\t\t\t\tif (untracked) {\n\t\t\t\t\tconst hasUntracked = await this.hasUntrackedFiles(repoPath, signal);\n\t\t\t\t\tif (hasUntracked) {\n\t\t\t\t\t\tscope?.addExitInfo('has untracked files');\n\t\t\t\t\t\treturn true;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tscope?.addExitInfo('no working changes');\n\t\t\t\treturn false;\n\t\t\t},\n\t\t\tcancellation,\n\t\t);\n\t}\n\n\t@debug()\n\tgetWorkingChangesState(repoPath: string, cancellation?: AbortSignal): Promise<GitWorkingChangesState> {\n\t\tconst scope = getScopedLogger();\n\n\t\treturn this.dedupeByStatusGeneration(\n\t\t\trepoPath,\n\t\t\t'getWorkingChangesState',\n\t\t\tasync (correlationKey, signal) => {\n\t\t\t\tconst [stagedResult, unstagedResult, untrackedResult] = await Promise.allSettled([\n\t\t\t\t\t// Check for staged changes\n\t\t\t\t\tthis.git.run(\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tcwd: repoPath,\n\t\t\t\t\t\t\tcancellation: signal,\n\t\t\t\t\t\t\terrors: 'throw',\n\t\t\t\t\t\t\texpectedExitCodes: [1],\n\t\t\t\t\t\t\tcorrelationKey: correlationKey,\n\t\t\t\t\t\t},\n\t\t\t\t\t\t'diff',\n\t\t\t\t\t\t'--quiet',\n\t\t\t\t\t\t'--staged',\n\t\t\t\t\t),\n\t\t\t\t\t// Check for unstaged changes\n\t\t\t\t\tthis.git.run(\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tcwd: repoPath,\n\t\t\t\t\t\t\tcancellation: signal,\n\t\t\t\t\t\t\terrors: 'throw',\n\t\t\t\t\t\t\texpectedExitCodes: [1],\n\t\t\t\t\t\t\tcorrelationKey: correlationKey,\n\t\t\t\t\t\t},\n\t\t\t\t\t\t'diff',\n\t\t\t\t\t\t'--quiet',\n\t\t\t\t\t),\n\t\t\t\t\t// Check for untracked files\n\t\t\t\t\tthis.hasUntrackedFiles(repoPath, signal),\n\t\t\t\t]);\n\n\t\t\t\tfor (const settled of [stagedResult, unstagedResult, untrackedResult]) {\n\t\t\t\t\tif (settled.status === 'rejected') throw settled.reason;\n\t\t\t\t}\n\n\t\t\t\tconst result = {\n\t\t\t\t\tstaged: getSettledValue(stagedResult)?.exitCode === 1,\n\t\t\t\t\tunstaged: getSettledValue(unstagedResult)?.exitCode === 1,\n\t\t\t\t\tuntracked: getSettledValue(untrackedResult) === true,\n\t\t\t\t};\n\n\t\t\t\tscope?.addExitInfo(\n\t\t\t\t\tresult.staged || result.unstaged || result.untracked\n\t\t\t\t\t\t? `has ${result.staged ? 'staged' : ''}${result.unstaged ? (result.staged ? ', unstaged' : 'unstaged ') : ''}${\n\t\t\t\t\t\t\t\tresult.untracked ? (result.staged || result.unstaged ? ', untracked' : 'untracked') : ''\n\t\t\t\t\t\t\t} changes`\n\t\t\t\t\t\t: 'no working changes',\n\t\t\t\t);\n\n\t\t\t\treturn result;\n\t\t\t},\n\t\t\tcancellation,\n\t\t);\n\t}\n\n\thasConflictingFiles(repoPath: string, cancellation?: AbortSignal): Promise<boolean> {\n\t\t// Route through `dedupeByStatusGeneration` (like every other point-in-time read here) for the join-fence\n\t\t// and the `raceWithTimeout` backstop — a bare `git.stream` has no per-command timeout and could wedge.\n\t\treturn this.dedupeByStatusGeneration(\n\t\t\trepoPath,\n\t\t\t'hasConflictingFiles',\n\t\t\tasync (_correlationKey, signal) => {\n\t\t\t\tconst stream = this.git.stream({ cwd: repoPath, cancellation: signal }, 'ls-files', '--unmerged');\n\t\t\t\tusing _streamDisposer = createDisposable(() => void stream.return?.(undefined));\n\n\t\t\t\t// Early exit on first chunk - breaking causes SIGPIPE, killing git process\n\t\t\t\tfor await (const _chunk of stream) {\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\n\t\t\t\treturn false;\n\t\t\t},\n\t\t\tcancellation,\n\t\t);\n\t}\n\n\t@debug()\n\tgetConflictingFiles(repoPath: string, cancellation?: AbortSignal): Promise<GitConflictFile[]> {\n\t\tconst scope = getScopedLogger();\n\n\t\treturn this.dedupeByStatusGeneration(\n\t\t\trepoPath,\n\t\t\t'getConflictingFiles',\n\t\t\tasync (correlationKey, signal) => {\n\t\t\t\tconst result = await this.git.run(\n\t\t\t\t\t{ cwd: repoPath, cancellation: signal, errors: 'throw', correlationKey: correlationKey },\n\t\t\t\t\t'ls-files',\n\t\t\t\t\t'-z',\n\t\t\t\t\t'--unmerged',\n\t\t\t\t);\n\n\t\t\t\tif (!result.stdout) {\n\t\t\t\t\tscope?.addExitInfo('no conflicting files');\n\t\t\t\t\treturn [];\n\t\t\t\t}\n\n\t\t\t\tconst files = parseGitConflictFiles(result.stdout, repoPath);\n\t\t\t\tscope?.addExitInfo(`${String(files.length)} conflicting file(s)`);\n\t\t\t\treturn files;\n\t\t\t},\n\t\t\tcancellation,\n\t\t);\n\t}\n\n\tprivate async hasUntrackedFiles(repoPath: string, cancellation?: AbortSignal): Promise<boolean> {\n\t\tconst stream = this.git.stream(\n\t\t\t{ cwd: repoPath, cancellation: cancellation },\n\t\t\t'ls-files',\n\t\t\t// '-z', // Unneeded since we are only looking for presence\n\t\t\t'--others',\n\t\t\t'--exclude-standard',\n\t\t);\n\t\tusing _streamDisposer = createDisposable(() => void stream.return?.(undefined));\n\n\t\t// Early exit on first chunk - breaking causes SIGPIPE, killing git process\n\t\tfor await (const _chunk of stream) {\n\t\t\treturn true;\n\t\t}\n\n\t\treturn false;\n\t}\n\n\t@debug()\n\tgetUntrackedFiles(repoPath: string, cancellation?: AbortSignal): Promise<GitFile[]> {\n\t\tconst scope = getScopedLogger();\n\n\t\treturn this.dedupeByStatusGeneration(\n\t\t\trepoPath,\n\t\t\t'getUntrackedFiles',\n\t\t\tasync (correlationKey, signal) => {\n\t\t\t\tconst result = await this.git.run(\n\t\t\t\t\t{ cwd: repoPath, cancellation: signal, errors: 'throw', correlationKey: correlationKey },\n\t\t\t\t\t'ls-files',\n\t\t\t\t\t'-z',\n\t\t\t\t\t'--others',\n\t\t\t\t\t'--exclude-standard',\n\t\t\t\t);\n\n\t\t\t\tif (!result.stdout) {\n\t\t\t\t\tscope?.addExitInfo('no untracked files');\n\t\t\t\t\treturn [];\n\t\t\t\t}\n\n\t\t\t\tconst files: GitFile[] = [];\n\n\t\t\t\tfor (const line of iterateByDelimiter(result.stdout, '\\0')) {\n\t\t\t\t\tif (!line.length) continue;\n\n\t\t\t\t\tfiles.push({ path: line, repoPath: repoPath, status: GitFileWorkingTreeStatus.Untracked });\n\t\t\t\t}\n\n\t\t\t\tscope?.addExitInfo(`${String(files.length)} untracked file(s)`);\n\t\t\t\treturn files;\n\t\t\t},\n\t\t\tcancellation,\n\t\t);\n\t}\n}\n"],"mappings":"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"}
|
|
@@ -15,7 +15,6 @@ export declare class StatusGitSubProvider implements GitStatusSubProvider {
|
|
|
15
15
|
staged?: boolean;
|
|
16
16
|
unstaged?: boolean;
|
|
17
17
|
untracked?: boolean;
|
|
18
|
-
throwOnError?: boolean;
|
|
19
18
|
priority?: GitCommandPriority;
|
|
20
19
|
}, _cancellation?: AbortSignal): Promise<boolean>;
|
|
21
20
|
getWorkingChangesState(_repoPath: string, _cancellation?: AbortSignal): Promise<GitWorkingChangesState>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"status.js","names":[],"sources":["../../../../../../plus/git-github/src/providers/github/status.ts"],"sourcesContent":["import type { GitFile } from '@gitlens/git/models/file.js';\nimport type { GitConflictFile } from '@gitlens/git/models/staging.js';\nimport { GitStatus } from '@gitlens/git/models/status.js';\nimport type { GitStatusSubProvider, GitWorkingChangesState } from '@gitlens/git/providers/status.js';\nimport type { GitCommandPriority } from '@gitlens/git/run.types.js';\nimport { gate } from '@gitlens/utils/decorators/gate.js';\nimport { debug } from '@gitlens/utils/decorators/log.js';\nimport { HeadType } from '../../context.js';\nimport type { GitHubGitProviderInternal } from '../githubProvider.js';\n\nexport class StatusGitSubProvider implements GitStatusSubProvider {\n\tconstructor(private readonly provider: GitHubGitProviderInternal) {}\n\n\t@gate()\n\t@debug()\n\tasync getStatus(\n\t\trepoPath: string | undefined,\n\t\t_options?: { priority?: GitCommandPriority },\n\t\t_cancellation?: AbortSignal,\n\t): Promise<GitStatus | undefined> {\n\t\tif (repoPath == null) return undefined;\n\n\t\tconst context = await this.provider.ensureRepositoryContext(repoPath);\n\t\tif (context == null) return undefined;\n\n\t\tconst revision = await context.metadata.getRevision();\n\t\tif (revision == null) return undefined;\n\n\t\treturn new GitStatus(\n\t\t\trepoPath,\n\t\t\trevision.name,\n\t\t\trevision.revision,\n\t\t\t[],\n\t\t\trevision.type === HeadType.Branch || revision.type === HeadType.RemoteBranch\n\t\t\t\t? { name: `origin/${revision.name}`, missing: false, state: { ahead: 0, behind: 0 } }\n\t\t\t\t: undefined,\n\t\t);\n\t}\n\n\thasWorkingChanges(\n\t\t_repoPath: string,\n\t\t_options?: {\n\t\t\tstaged?: boolean;\n\t\t\tunstaged?: boolean;\n\t\t\tuntracked?: boolean;\n\t\t\
|
|
1
|
+
{"version":3,"file":"status.js","names":[],"sources":["../../../../../../plus/git-github/src/providers/github/status.ts"],"sourcesContent":["import type { GitFile } from '@gitlens/git/models/file.js';\nimport type { GitConflictFile } from '@gitlens/git/models/staging.js';\nimport { GitStatus } from '@gitlens/git/models/status.js';\nimport type { GitStatusSubProvider, GitWorkingChangesState } from '@gitlens/git/providers/status.js';\nimport type { GitCommandPriority } from '@gitlens/git/run.types.js';\nimport { gate } from '@gitlens/utils/decorators/gate.js';\nimport { debug } from '@gitlens/utils/decorators/log.js';\nimport { HeadType } from '../../context.js';\nimport type { GitHubGitProviderInternal } from '../githubProvider.js';\n\nexport class StatusGitSubProvider implements GitStatusSubProvider {\n\tconstructor(private readonly provider: GitHubGitProviderInternal) {}\n\n\t@gate()\n\t@debug()\n\tasync getStatus(\n\t\trepoPath: string | undefined,\n\t\t_options?: { priority?: GitCommandPriority },\n\t\t_cancellation?: AbortSignal,\n\t): Promise<GitStatus | undefined> {\n\t\tif (repoPath == null) return undefined;\n\n\t\tconst context = await this.provider.ensureRepositoryContext(repoPath);\n\t\tif (context == null) return undefined;\n\n\t\tconst revision = await context.metadata.getRevision();\n\t\tif (revision == null) return undefined;\n\n\t\treturn new GitStatus(\n\t\t\trepoPath,\n\t\t\trevision.name,\n\t\t\trevision.revision,\n\t\t\t[],\n\t\t\trevision.type === HeadType.Branch || revision.type === HeadType.RemoteBranch\n\t\t\t\t? { name: `origin/${revision.name}`, missing: false, state: { ahead: 0, behind: 0 } }\n\t\t\t\t: undefined,\n\t\t);\n\t}\n\n\thasWorkingChanges(\n\t\t_repoPath: string,\n\t\t_options?: {\n\t\t\tstaged?: boolean;\n\t\t\tunstaged?: boolean;\n\t\t\tuntracked?: boolean;\n\t\t\tpriority?: GitCommandPriority;\n\t\t},\n\t\t_cancellation?: AbortSignal,\n\t): Promise<boolean> {\n\t\treturn Promise.resolve(false);\n\t}\n\n\tgetWorkingChangesState(_repoPath: string, _cancellation?: AbortSignal): Promise<GitWorkingChangesState> {\n\t\treturn Promise.resolve({ staged: false, unstaged: false, untracked: false });\n\t}\n\n\thasConflictingFiles(_repoPath: string, _cancellation?: AbortSignal): Promise<boolean> {\n\t\treturn Promise.resolve(false);\n\t}\n\n\tgetConflictingFiles(_repoPath: string, _cancellation?: AbortSignal): Promise<GitConflictFile[]> {\n\t\treturn Promise.resolve([]);\n\t}\n\n\tgetUntrackedFiles(_repoPath: string, _cancellation?: AbortSignal): Promise<GitFile[]> {\n\t\treturn Promise.resolve([]);\n\t}\n}\n"],"mappings":";;;;;;AAUA,IAAa,uBAAb,MAAkE;CACpC;CAA7B,YAAY,UAAsD;EAArC,KAAA,WAAA;CAAsC;CAEnE,MAEM,UACL,UACA,UACA,eACiC;EACjC,IAAI,YAAY,MAAM,OAAO,KAAA;EAE7B,MAAM,UAAU,MAAM,KAAK,SAAS,wBAAwB,QAAQ;EACpE,IAAI,WAAW,MAAM,OAAO,KAAA;EAE5B,MAAM,WAAW,MAAM,QAAQ,SAAS,YAAY;EACpD,IAAI,YAAY,MAAM,OAAO,KAAA;EAE7B,OAAO,IAAI,UACV,UACA,SAAS,MACT,SAAS,UACT,CAAC,GACD,SAAS,SAAS,SAAS,UAAU,SAAS,SAAS,SAAS,eAC7D;GAAE,MAAM,UAAU,SAAS;GAAQ,SAAS;GAAO,OAAO;IAAE,OAAO;IAAG,QAAQ;GAAE;EAAE,IAClF,KAAA,CACJ;CACD;CAEA,kBACC,WACA,UAMA,eACmB;EACnB,OAAO,QAAQ,QAAQ,KAAK;CAC7B;CAEA,uBAAuB,WAAmB,eAA8D;EACvG,OAAO,QAAQ,QAAQ;GAAE,QAAQ;GAAO,UAAU;GAAO,WAAW;EAAM,CAAC;CAC5E;CAEA,oBAAoB,WAAmB,eAA+C;EACrF,OAAO,QAAQ,QAAQ,KAAK;CAC7B;CAEA,oBAAoB,WAAmB,eAAyD;EAC/F,OAAO,QAAQ,QAAQ,CAAC,CAAC;CAC1B;CAEA,kBAAkB,WAAmB,eAAiD;EACrF,OAAO,QAAQ,QAAQ,CAAC,CAAC;CAC1B;AACD;AAtDE,WAAA,CAAA,KAAK,GACL,MAAM,CAAA,GAAA,qBAAA,WAAA,aAAA,IAAA"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { getScopedLogger } from "../../../utils/logger.scoped.js";
|
|
2
|
+
import { RequestRateLimitError } from "../../../git/errors.js";
|
|
2
3
|
import { toCloudIntegrationType } from "./models.js";
|
|
3
4
|
//#region ../plus/integrations/src/authentication/cloudIntegrationService.ts
|
|
4
5
|
/**
|
|
@@ -16,6 +17,15 @@ function isTerminalStatus(status) {
|
|
|
16
17
|
default: return false;
|
|
17
18
|
}
|
|
18
19
|
}
|
|
20
|
+
/**
|
|
21
|
+
* The error for a token fetch the backend refused for a reason a retry can fix. A 429 is typed as a rate limit so
|
|
22
|
+
* a read that needed the token reports a throttle rather than a broken connection (gitkraken/kepler#3546); every
|
|
23
|
+
* other status stays a plain error.
|
|
24
|
+
*/
|
|
25
|
+
function retryableTokenFetchError(status, message) {
|
|
26
|
+
const error = new Error(message);
|
|
27
|
+
return status === 429 ? new RequestRateLimitError(error, void 0, void 0) : error;
|
|
28
|
+
}
|
|
19
29
|
function toSession(data) {
|
|
20
30
|
return {
|
|
21
31
|
type: data.type,
|
|
@@ -27,6 +37,13 @@ function toSession(data) {
|
|
|
27
37
|
appKey: data.appKey
|
|
28
38
|
};
|
|
29
39
|
}
|
|
40
|
+
async function getResponseError(response) {
|
|
41
|
+
try {
|
|
42
|
+
return (await response.json())?.error;
|
|
43
|
+
} catch {
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
30
47
|
var CloudIntegrationService = class {
|
|
31
48
|
ctx;
|
|
32
49
|
constructor(ctx) {
|
|
@@ -36,7 +53,7 @@ var CloudIntegrationService = class {
|
|
|
36
53
|
const scope = getScopedLogger();
|
|
37
54
|
const providersRsp = await this.ctx.account.fetchGkApi("v1/provider-tokens", { method: "GET" });
|
|
38
55
|
if (!providersRsp.ok) {
|
|
39
|
-
const error =
|
|
56
|
+
const error = await getResponseError(providersRsp);
|
|
40
57
|
const errorMessage = typeof error === "string" ? error : error?.message ?? providersRsp.statusText;
|
|
41
58
|
if (error != null) scope?.error(void 0, `Failed to get connected providers from cloud: ${errorMessage}`);
|
|
42
59
|
this.ctx.hooks?.connection?.onConnectionsFetchFailed?.({ code: providersRsp.status });
|
|
@@ -88,7 +105,7 @@ var CloudIntegrationService = class {
|
|
|
88
105
|
const path = connectionId ? `v1/provider-tokens/tokens/${encodeURIComponent(connectionId)}${refresh ? "/refresh" : ""}` : `v1/provider-tokens/${cloudIntegrationType}${refresh ? "/refresh" : ""}`;
|
|
89
106
|
const tokenRsp = await this.ctx.account.fetchGkApi(path, reqInitOptions);
|
|
90
107
|
if (!tokenRsp.ok) {
|
|
91
|
-
const error =
|
|
108
|
+
const error = await getResponseError(tokenRsp);
|
|
92
109
|
const errorMessage = typeof error === "string" ? error : error?.message ?? tokenRsp.statusText;
|
|
93
110
|
if (error != null) scope?.error(void 0, `Failed to ${refresh ? "refresh" : "get"} ${id} token from cloud: ${errorMessage}`);
|
|
94
111
|
this.ctx.hooks?.connection?.onConnectionFetchFailed?.({
|
|
@@ -109,10 +126,10 @@ var CloudIntegrationService = class {
|
|
|
109
126
|
refreshing: false
|
|
110
127
|
});
|
|
111
128
|
if (isTerminalStatus(newTokenRsp.status)) return void 0;
|
|
112
|
-
throw
|
|
129
|
+
throw retryableTokenFetchError(newTokenRsp.status, `Retryable failure (${newTokenRsp.status}) refreshing ${id} token from cloud`);
|
|
113
130
|
}
|
|
114
131
|
if (isTerminalStatus(tokenRsp.status)) return void 0;
|
|
115
|
-
throw
|
|
132
|
+
throw retryableTokenFetchError(tokenRsp.status, `Retryable failure (${tokenRsp.status}) getting ${id} token from cloud`);
|
|
116
133
|
}
|
|
117
134
|
const data = (await tokenRsp.json())?.data;
|
|
118
135
|
return data != null ? toSession(data) : void 0;
|
|
@@ -126,7 +143,7 @@ var CloudIntegrationService = class {
|
|
|
126
143
|
}
|
|
127
144
|
const tokenRsp = await this.ctx.account.fetchGkApi(`v1/provider-tokens/${cloudIntegrationType}`, { method: "DELETE" });
|
|
128
145
|
if (!tokenRsp.ok) {
|
|
129
|
-
const error =
|
|
146
|
+
const error = await getResponseError(tokenRsp);
|
|
130
147
|
const errorMessage = typeof error === "string" ? error : error?.message ?? tokenRsp.statusText;
|
|
131
148
|
if (error != null) scope?.error(void 0, `Failed to disconnect ${id} token from cloud: ${errorMessage}`);
|
|
132
149
|
this.ctx.hooks?.connection?.onDisconnectFailed?.({
|
|
@@ -146,7 +163,7 @@ var CloudIntegrationService = class {
|
|
|
146
163
|
const scope = getScopedLogger();
|
|
147
164
|
const tokenRsp = await this.ctx.account.fetchGkApi(`v1/provider-tokens/tokens/${encodeURIComponent(connectionId)}`, { method: "DELETE" });
|
|
148
165
|
if (!tokenRsp.ok) {
|
|
149
|
-
const error =
|
|
166
|
+
const error = await getResponseError(tokenRsp);
|
|
150
167
|
const errorMessage = typeof error === "string" ? error : error?.message ?? tokenRsp.statusText;
|
|
151
168
|
if (error != null) scope?.error(void 0, `Failed to disconnect connection '${connectionId}' for ${id}: ${errorMessage}`);
|
|
152
169
|
this.ctx.hooks?.connection?.onDisconnectFailed?.({
|
|
@@ -165,7 +182,7 @@ var CloudIntegrationService = class {
|
|
|
165
182
|
const scope = getScopedLogger();
|
|
166
183
|
const tokenRsp = await this.ctx.account.fetchGkApi(`v1/provider-tokens/tokens/${encodeURIComponent(connectionId)}/primary`, { method: "POST" });
|
|
167
184
|
if (!tokenRsp.ok) {
|
|
168
|
-
const error =
|
|
185
|
+
const error = await getResponseError(tokenRsp);
|
|
169
186
|
const errorMessage = typeof error === "string" ? error : error?.message ?? tokenRsp.statusText;
|
|
170
187
|
if (error != null) scope?.error(void 0, `Failed to set primary connection '${connectionId}' for ${id}: ${errorMessage}`);
|
|
171
188
|
return false;
|