@gitkraken/core-gitlens 0.12.0 → 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +78 -1
- package/dist/git/cache.d.ts +84 -29
- package/dist/git/cache.js +188 -54
- package/dist/git/cache.js.map +1 -1
- package/dist/git/features.d.ts +1 -1
- package/dist/git/features.js +5 -0
- package/dist/git/features.js.map +1 -1
- package/dist/git/models/branch.d.ts +20 -2
- package/dist/git/models/branch.js +28 -6
- package/dist/git/models/branch.js.map +1 -1
- package/dist/git/models/reference.d.ts +3 -0
- package/dist/git/models/status.js +2 -2
- package/dist/git/models/worktree.d.ts +4 -1
- package/dist/git/models/worktree.js +4 -2
- package/dist/git/models/worktree.js.map +1 -1
- package/dist/git/parsers/diffParser.js +1 -1
- package/dist/git/providers/branches.d.ts +73 -8
- package/dist/git/providers/operations.d.ts +3 -2
- package/dist/git/providers/pausedOperations.d.ts +6 -0
- package/dist/git/providers/provider.d.ts +45 -2
- package/dist/git/providers/revision.d.ts +17 -3
- package/dist/git/providers/status.d.ts +8 -2
- package/dist/git/remotes/azure-devops.js +61 -53
- package/dist/git/remotes/azure-devops.js.map +1 -1
- package/dist/git/run.types.d.ts +12 -0
- package/dist/git/service.d.ts +8 -5
- package/dist/git/service.js +16 -8
- package/dist/git/service.js.map +1 -1
- package/dist/git/utils/branch.utils.d.ts +12 -2
- package/dist/git/utils/branch.utils.js +49 -5
- package/dist/git/utils/branch.utils.js.map +1 -1
- package/dist/git/utils/reference.utils.d.ts +1 -0
- package/dist/git/utils/reference.utils.js.map +1 -1
- package/dist/git/watching/changeEvent.d.ts +13 -2
- package/dist/git/watching/changeEvent.js +17 -3
- package/dist/git/watching/changeEvent.js.map +1 -1
- package/dist/git/watching/gitDirPoller.d.ts +111 -0
- package/dist/git/watching/gitDirPoller.js +266 -0
- package/dist/git/watching/gitDirPoller.js.map +1 -0
- package/dist/git/watching/provider.d.ts +16 -0
- package/dist/git/watching/watchGroup.d.ts +40 -4
- package/dist/git/watching/watchGroup.js +143 -17
- package/dist/git/watching/watchGroup.js.map +1 -1
- package/dist/git/watching/watchService.d.ts +70 -1
- package/dist/git/watching/watchService.js +158 -13
- package/dist/git/watching/watchService.js.map +1 -1
- package/dist/git/watching/watchSession.d.ts +44 -0
- package/dist/git/watching/watchSession.js +91 -6
- package/dist/git/watching/watchSession.js.map +1 -1
- package/dist/git/watching/watcherPatterns.d.ts +21 -0
- package/dist/git/watching/watcherPatterns.js +67 -1
- package/dist/git/watching/watcherPatterns.js.map +1 -1
- package/dist/git/watching/worktreeSummary.d.ts +34 -7
- package/dist/git/watching/worktreeSummary.js +39 -12
- package/dist/git/watching/worktreeSummary.js.map +1 -1
- package/dist/git-cli/cliGitProvider.d.ts +40 -2
- package/dist/git-cli/cliGitProvider.js +182 -18
- package/dist/git-cli/cliGitProvider.js.map +1 -1
- package/dist/git-cli/exec/commandClassifier.d.ts +15 -0
- package/dist/git-cli/exec/commandClassifier.js +48 -2
- package/dist/git-cli/exec/commandClassifier.js.map +1 -1
- package/dist/git-cli/exec/git.d.ts +20 -3
- package/dist/git-cli/exec/git.js +63 -19
- package/dist/git-cli/exec/git.js.map +1 -1
- package/dist/git-cli/exec/gitQueue.d.ts +2 -0
- package/dist/git-cli/exec/gitQueue.js +2 -1
- package/dist/git-cli/exec/gitQueue.js.map +1 -1
- package/dist/git-cli/exec/indexLock.d.ts +39 -0
- package/dist/git-cli/exec/indexLock.js +160 -0
- package/dist/git-cli/exec/indexLock.js.map +1 -0
- package/dist/git-cli/parsers/blameParser.js +2 -2
- package/dist/git-cli/parsers/refParser.js +4 -2
- package/dist/git-cli/parsers/refParser.js.map +1 -1
- package/dist/git-cli/parsers/statusParser.d.ts +6 -0
- package/dist/git-cli/parsers/statusParser.js +33 -3
- package/dist/git-cli/parsers/statusParser.js.map +1 -1
- package/dist/git-cli/parsers/worktreeParser.js +42 -2
- package/dist/git-cli/parsers/worktreeParser.js.map +1 -1
- package/dist/git-cli/providers/branchRemote.d.ts +14 -0
- package/dist/git-cli/providers/branchRemote.js +36 -0
- package/dist/git-cli/providers/branchRemote.js.map +1 -0
- package/dist/git-cli/providers/branches.d.ts +109 -10
- package/dist/git-cli/providers/branches.js +630 -134
- package/dist/git-cli/providers/branches.js.map +1 -1
- package/dist/git-cli/providers/commits.js +2 -2
- package/dist/git-cli/providers/config.d.ts +9 -5
- package/dist/git-cli/providers/config.js +58 -25
- package/dist/git-cli/providers/config.js.map +1 -1
- package/dist/git-cli/providers/contributors.js +1 -1
- package/dist/git-cli/providers/graph.js +9 -6
- package/dist/git-cli/providers/graph.js.map +1 -1
- package/dist/git-cli/providers/operations.d.ts +17 -0
- package/dist/git-cli/providers/operations.js +172 -111
- package/dist/git-cli/providers/operations.js.map +1 -1
- package/dist/git-cli/providers/pausedOperations.d.ts +8 -0
- package/dist/git-cli/providers/pausedOperations.js +59 -39
- package/dist/git-cli/providers/pausedOperations.js.map +1 -1
- package/dist/git-cli/providers/refs.js +11 -8
- package/dist/git-cli/providers/refs.js.map +1 -1
- package/dist/git-cli/providers/remotes.js +19 -10
- package/dist/git-cli/providers/remotes.js.map +1 -1
- package/dist/git-cli/providers/revision.d.ts +6 -2
- package/dist/git-cli/providers/revision.js +38 -12
- package/dist/git-cli/providers/revision.js.map +1 -1
- package/dist/git-cli/providers/staging.js +39 -14
- package/dist/git-cli/providers/staging.js.map +1 -1
- package/dist/git-cli/providers/stash.d.ts +2 -0
- package/dist/git-cli/providers/stash.js +33 -18
- package/dist/git-cli/providers/stash.js.map +1 -1
- package/dist/git-cli/providers/status.d.ts +3 -1
- package/dist/git-cli/providers/status.js +40 -22
- package/dist/git-cli/providers/status.js.map +1 -1
- package/dist/git-cli/providers/tags.js +6 -4
- package/dist/git-cli/providers/tags.js.map +1 -1
- package/dist/git-cli/providers/worktrees.d.ts +1 -0
- package/dist/git-cli/providers/worktrees.js +60 -31
- package/dist/git-cli/providers/worktrees.js.map +1 -1
- package/dist/git-cli/service.d.ts +6 -0
- package/dist/git-cli/service.js +5 -2
- package/dist/git-cli/service.js.map +1 -1
- package/dist/plus/git-github/providers/github/revision.d.ts +6 -2
- package/dist/plus/git-github/providers/github/revision.js +15 -10
- package/dist/plus/git-github/providers/github/revision.js.map +1 -1
- package/dist/plus/git-github/providers/github/status.d.ts +3 -1
- package/dist/plus/git-github/providers/github/status.js +2 -2
- package/dist/plus/git-github/providers/github/status.js.map +1 -1
- package/dist/plus/integrations/collectionMetadata.d.ts +13 -2
- package/dist/plus/integrations/collectionMetadata.js +14 -2
- package/dist/plus/integrations/collectionMetadata.js.map +1 -1
- package/dist/plus/integrations/index.d.ts +2 -2
- package/dist/plus/integrations/index.js.map +1 -1
- package/dist/plus/integrations/integrationService.d.ts +1 -0
- package/dist/plus/integrations/integrationService.js +1 -1
- package/dist/plus/integrations/integrationService.js.map +1 -1
- package/dist/plus/integrations/manager.d.ts +1 -0
- package/dist/plus/integrations/models/gitHostIntegration.d.ts +41 -5
- package/dist/plus/integrations/models/gitHostIntegration.js +106 -29
- package/dist/plus/integrations/models/gitHostIntegration.js.map +1 -1
- package/dist/plus/integrations/models/issueReads.d.ts +17 -4
- package/dist/plus/integrations/providers/apiClients.d.ts +2 -0
- package/dist/plus/integrations/providers/apiClients.js +7 -1
- package/dist/plus/integrations/providers/apiClients.js.map +1 -1
- package/dist/plus/integrations/providers/azure/azure.d.ts +5 -4
- package/dist/plus/integrations/providers/azure/azure.js +36 -76
- package/dist/plus/integrations/providers/azure/azure.js.map +1 -1
- package/dist/plus/integrations/providers/azure/azure.utils.js +20 -17
- package/dist/plus/integrations/providers/azure/azure.utils.js.map +1 -1
- package/dist/plus/integrations/providers/azure/models.d.ts +2 -2
- package/dist/plus/integrations/providers/azure/models.js +4 -4
- package/dist/plus/integrations/providers/azure/models.js.map +1 -1
- package/dist/plus/integrations/providers/azure/workItemStateCategories.d.ts +21 -12
- package/dist/plus/integrations/providers/azure/workItemStateCategories.js +74 -17
- package/dist/plus/integrations/providers/azure/workItemStateCategories.js.map +1 -1
- package/dist/plus/integrations/providers/azureDevOps.d.ts +44 -0
- package/dist/plus/integrations/providers/azureDevOps.js +108 -17
- package/dist/plus/integrations/providers/azureDevOps.js.map +1 -1
- package/dist/plus/integrations/providers/azureDevOpsServer.d.ts +45 -1
- package/dist/plus/integrations/providers/azureDevOpsServer.js +121 -6
- package/dist/plus/integrations/providers/azureDevOpsServer.js.map +1 -1
- package/dist/plus/integrations/providers/jira-server.js +18 -14
- package/dist/plus/integrations/providers/jira-server.js.map +1 -1
- package/dist/plus/integrations/providers/jira.js +18 -14
- package/dist/plus/integrations/providers/jira.js.map +1 -1
- package/dist/plus/integrations/providers/models.d.ts +1 -2
- package/dist/plus/integrations/providers/models.js +9 -2
- package/dist/plus/integrations/providers/models.js.map +1 -1
- package/dist/plus/integrations/providers/providersApi.d.ts +7 -17
- package/dist/plus/integrations/providers/providersApi.js +85 -78
- package/dist/plus/integrations/providers/providersApi.js.map +1 -1
- package/dist/plus/integrations/providers/utils/jiraIssueDrains.d.ts +22 -0
- package/dist/plus/integrations/providers/utils/jiraIssueDrains.js +69 -9
- package/dist/plus/integrations/providers/utils/jiraIssueDrains.js.map +1 -1
- package/dist/plus/integrations/providers/utils/providerPaging.js +8 -2
- package/dist/plus/integrations/providers/utils/providerPaging.js.map +1 -1
- package/dist/plus/integrations/reads/filters.d.ts +3 -3
- package/dist/plus/integrations/reads/filters.js.map +1 -1
- package/dist/plus/integrations/reads/issueBatch.d.ts +2 -1
- package/dist/plus/integrations/reads/issueBatch.js +1 -1
- package/dist/plus/integrations/reads/issueBatch.js.map +1 -1
- package/dist/plus/integrations/reads/issueTracker.js +23 -14
- package/dist/plus/integrations/reads/issueTracker.js.map +1 -1
- package/dist/plus/integrations/reads/issues.d.ts +2 -1
- package/dist/plus/integrations/reads/issues.js +18 -3
- package/dist/plus/integrations/reads/issues.js.map +1 -1
- package/dist/plus/integrations/reads/pullRequestBatch.js +1 -1
- package/dist/plus/integrations/reads/resolveRepository.js +12 -3
- package/dist/plus/integrations/reads/resolveRepository.js.map +1 -1
- package/dist/plus/integrations/reads/warnings.d.ts +11 -4
- package/dist/plus/integrations/reads/warnings.js +15 -5
- package/dist/plus/integrations/reads/warnings.js.map +1 -1
- package/dist/plus/integrations/results.d.ts +36 -2
- package/dist/plus/integrations/results.js +33 -13
- package/dist/plus/integrations/results.js.map +1 -1
- package/dist/utils/promiseCache.d.ts +10 -0
- package/dist/utils/promiseCache.js +17 -2
- package/dist/utils/promiseCache.js.map +1 -1
- package/docs/integrations.md +52 -20
- package/package.json +2 -2
|
@@ -1,33 +1,74 @@
|
|
|
1
|
+
import { sum } from "../../utils/iterable.js";
|
|
1
2
|
import { ensureArray, filterMap } from "../../utils/array.js";
|
|
2
3
|
import { CancellationError, isCancellationError } from "../../utils/cancellation.js";
|
|
3
4
|
import { Logger as _logger } from "../../utils/logger.js";
|
|
4
5
|
import { getScopedLogger } from "../../utils/logger.scoped.js";
|
|
5
|
-
import { getSettledValue } from "../../utils/promise.js";
|
|
6
|
-
import { debug } from "../../utils/decorators/log.js";
|
|
6
|
+
import { getSettledValue, mapBounded } from "../../utils/promise.js";
|
|
7
|
+
import { debug, trace } from "../../utils/decorators/log.js";
|
|
7
8
|
import { getBranchId } from "../../utils/gitRefs.js";
|
|
8
9
|
import { normalizePath } from "../../utils/path.js";
|
|
9
10
|
import { createRevisionRange, shortenRevision } from "../../git/utils/revision.utils.js";
|
|
11
|
+
import { formatDetachedHeadName, getLocalBranchByUpstream, isDetachedHead, isRemoteHEAD, parseBranchMetadataGkConfigKey, parseRefName, parseUpstream, setBranchMetadataValue } from "../../git/utils/branch.utils.js";
|
|
10
12
|
import { createReference } from "../../git/utils/reference.utils.js";
|
|
11
13
|
import __decorate from "../../_virtual/@oxc-project+runtime/helpers/esm/decorate.js";
|
|
12
14
|
import { BranchError, GitWarnings } from "../../git/errors.js";
|
|
15
|
+
import { mayWriteTracking } from "../exec/commandClassifier.js";
|
|
16
|
+
import { KeyedSequencer } from "../exec/indexLock.js";
|
|
13
17
|
import { GitError, GitErrors, getGitCommandError, gitConfigsBranch, gitConfigsLog } from "../exec/git.js";
|
|
14
18
|
import { maybeStopWatch } from "../../utils/stopwatch.js";
|
|
15
19
|
import _usingCtx from "../../_virtual/@oxc-project+runtime/helpers/esm/usingCtx.js";
|
|
16
|
-
import { formatDetachedHeadName, getLocalBranchByUpstream, isDetachedHead, isRemoteHEAD, parseRefName, parseUpstream } from "../../git/utils/branch.utils.js";
|
|
17
20
|
import { GitBranch } from "../../git/models/branch.js";
|
|
18
21
|
import { createConflictDetectionError } from "../../git/utils/mergeConflicts.utils.js";
|
|
19
22
|
import { sortBranches, sortContributors } from "../../git/utils/sorting.js";
|
|
20
|
-
import {
|
|
23
|
+
import { LruMap } from "../../utils/lruMap.js";
|
|
21
24
|
import { PageableResult, emptyPagedResult } from "../../utils/paging.js";
|
|
22
25
|
import { parseMergeTreeConflict } from "../parsers/mergeTreeParser.js";
|
|
26
|
+
import { resolveBranchRemote } from "./branchRemote.js";
|
|
23
27
|
//#region ../git-cli/src/providers/branches.ts
|
|
24
28
|
/** Minimum time between writes to the config for last accessed/modified dates */
|
|
25
29
|
const dateMetadataStaleThresholdMs = 3e5;
|
|
30
|
+
/**
|
|
31
|
+
* Run options that keep an unrequested remote read from prompting for credentials or hanging on a dead host.
|
|
32
|
+
* An empty `GIT_ASKPASS` also stops git consulting `core.askPass` and `SSH_ASKPASS` itself.
|
|
33
|
+
*/
|
|
34
|
+
const nonInteractiveNetworkOptions = {
|
|
35
|
+
configs: ["-c", "credential.interactive=false"],
|
|
36
|
+
env: {
|
|
37
|
+
GIT_TERMINAL_PROMPT: "0",
|
|
38
|
+
GIT_ASKPASS: "",
|
|
39
|
+
SSH_ASKPASS: "",
|
|
40
|
+
SSH_ASKPASS_REQUIRE: "never",
|
|
41
|
+
GCM_INTERACTIVE: "never"
|
|
42
|
+
},
|
|
43
|
+
timeout: 1e4
|
|
44
|
+
};
|
|
45
|
+
const branchActivityKeys = [
|
|
46
|
+
"gk-last-accessed",
|
|
47
|
+
"gk-last-modified",
|
|
48
|
+
"gk-agent-last-activity"
|
|
49
|
+
];
|
|
26
50
|
var BranchesGitSubProvider = class {
|
|
27
51
|
context;
|
|
28
52
|
git;
|
|
29
53
|
cache;
|
|
30
54
|
provider;
|
|
55
|
+
/** Ahead/behind counts by `<baseSha>...<tipSha>` range, from the per-tip fallback; a pair of commits always counts the same */
|
|
56
|
+
aheadBehindBySha = new LruMap(4096);
|
|
57
|
+
/**
|
|
58
|
+
* When each `commonPath\0branch\0key` was last stamped by this process. Bounded: a dropped entry only costs a
|
|
59
|
+
* read of the stored stamp, which still holds off a write under 5 minutes old
|
|
60
|
+
*/
|
|
61
|
+
_branchActivityStamps = new LruMap(1024);
|
|
62
|
+
/** One call per `commonPath\0branch` at a time, so a call held off by another's write in flight settles after it */
|
|
63
|
+
_branchActivityCalls = new KeyedSequencer();
|
|
64
|
+
/**
|
|
65
|
+
* What each `commonPath\0branch`'s reflog says it was created from, as git wrote it; `undefined` when it says
|
|
66
|
+
* nothing. A branch's creation never changes, so the base read, which reruns on every commit and ref move, reads
|
|
67
|
+
* it once
|
|
68
|
+
*/
|
|
69
|
+
_branchCreatedFrom = new LruMap(1024);
|
|
70
|
+
/** Bumped each time a key's created-from is forgotten, so a reflog read in flight across that doesn't store it */
|
|
71
|
+
_branchCreatedFromGenerations = new LruMap(1024);
|
|
31
72
|
constructor(context, git, cache, provider) {
|
|
32
73
|
this.context = context;
|
|
33
74
|
this.git = git;
|
|
@@ -116,7 +157,7 @@ var BranchesGitSubProvider = class {
|
|
|
116
157
|
const fullName = record.name;
|
|
117
158
|
if (!fullName.startsWith("refs/heads/") && !fullName.startsWith("refs/remotes/")) continue;
|
|
118
159
|
if (isRemoteHEAD(fullName)) continue;
|
|
119
|
-
const upstream = parseUpstream(record.upstream, record.upstreamTracking);
|
|
160
|
+
const upstream = parseUpstream(record.upstream, record.upstreamTracking, record.upstreamRemote);
|
|
120
161
|
const metadata = metadataMap.get(parseRefName(fullName).name);
|
|
121
162
|
const worktreePath = record.worktreePath ? normalizePath(record.worktreePath) : void 0;
|
|
122
163
|
branches.push(new GitBranch(commonPath, fullName, false, record.committerDate ? new Date(record.committerDate) : void 0, metadata, record.objectname, upstream, worktreePathSupported ? worktreePath ? {
|
|
@@ -134,7 +175,8 @@ var BranchesGitSubProvider = class {
|
|
|
134
175
|
remote: false,
|
|
135
176
|
upstream: branch.upstream?.name ? {
|
|
136
177
|
name: branch.upstream.name,
|
|
137
|
-
missing: branch.upstream.missing
|
|
178
|
+
missing: branch.upstream.missing,
|
|
179
|
+
remote: branch.upstream.remote
|
|
138
180
|
} : void 0,
|
|
139
181
|
sha: branch.sha
|
|
140
182
|
});
|
|
@@ -246,7 +288,8 @@ var BranchesGitSubProvider = class {
|
|
|
246
288
|
if (pr?.refs?.base != null) {
|
|
247
289
|
const branch = await this.getBranch(repoPath, ref, void 0, cancellation);
|
|
248
290
|
if (branch == null) return void 0;
|
|
249
|
-
const
|
|
291
|
+
const { remote: remoteName } = await resolveBranchRemote(this.provider, repoPath, branch, cancellation);
|
|
292
|
+
const prTarget = `${remoteName}/${pr.refs.base.branch}`;
|
|
250
293
|
const { missing } = await this.provider.refs.getSymbolicReferenceNameOrMissing(repoPath, prTarget, priorityOpts, cancellation);
|
|
251
294
|
if (!missing) {
|
|
252
295
|
mergeTarget = prTarget;
|
|
@@ -431,6 +474,23 @@ var BranchesGitSubProvider = class {
|
|
|
431
474
|
}
|
|
432
475
|
}, cancellation);
|
|
433
476
|
}
|
|
477
|
+
/**
|
|
478
|
+
* Writes the missing `refs/remotes/<remote>/HEAD` symref from the remote (`remote set-head -a`) in the background,
|
|
479
|
+
* never prompting for credentials; nothing runs past the cached local check when the symref exists. Rejects when
|
|
480
|
+
* the remote can't be read.
|
|
481
|
+
*/
|
|
482
|
+
async setRemoteHeadIfMissing(repoPath, remote) {
|
|
483
|
+
if (await this.getDefaultBranchName(repoPath, remote, {
|
|
484
|
+
priority: "background",
|
|
485
|
+
local: true
|
|
486
|
+
}) != null) return;
|
|
487
|
+
await this.git.run({
|
|
488
|
+
cwd: repoPath,
|
|
489
|
+
priority: "background",
|
|
490
|
+
...nonInteractiveNetworkOptions
|
|
491
|
+
}, "remote", "set-head", "-a", remote);
|
|
492
|
+
this.cache.deleteDefaultBranchName(repoPath, `${remote}:local`);
|
|
493
|
+
}
|
|
434
494
|
async createBranch(repoPath, name, ref, options) {
|
|
435
495
|
const args = [
|
|
436
496
|
"branch",
|
|
@@ -441,8 +501,7 @@ var BranchesGitSubProvider = class {
|
|
|
441
501
|
try {
|
|
442
502
|
await this.git.run({ cwd: repoPath }, ...args);
|
|
443
503
|
this.cache.deleteBaseBranchName(repoPath, name);
|
|
444
|
-
this.
|
|
445
|
-
this.context.hooks?.repository?.onChanged?.(repoPath, ["heads"]);
|
|
504
|
+
this.forgetBranchCreatedFrom(repoPath, name);
|
|
446
505
|
} catch (ex) {
|
|
447
506
|
throw getGitCommandError("branch", ex, (reason) => new BranchError({
|
|
448
507
|
reason: reason ?? "other",
|
|
@@ -453,6 +512,9 @@ var BranchesGitSubProvider = class {
|
|
|
453
512
|
args
|
|
454
513
|
}
|
|
455
514
|
}, ex));
|
|
515
|
+
} finally {
|
|
516
|
+
this.context.hooks?.cache?.onReset?.(repoPath, "branches");
|
|
517
|
+
this.context.hooks?.repository?.onChanged?.(repoPath, mayWriteTracking(ref, options?.noTracking) ? ["config", "heads"] : ["heads"]);
|
|
456
518
|
}
|
|
457
519
|
}
|
|
458
520
|
async deleteLocalBranch(repoPath, names, options) {
|
|
@@ -466,11 +528,11 @@ var BranchesGitSubProvider = class {
|
|
|
466
528
|
await this.git.run({ cwd: repoPath }, ...args);
|
|
467
529
|
for (const branch of branches) await this.forgetDeletedBranch(repoPath, branch);
|
|
468
530
|
this.context.hooks?.cache?.onReset?.(repoPath, "branches");
|
|
469
|
-
this.context.hooks?.repository?.onChanged?.(repoPath, ["heads"]);
|
|
531
|
+
this.context.hooks?.repository?.onChanged?.(repoPath, ["config", "heads"]);
|
|
470
532
|
} catch (ex) {
|
|
471
533
|
if (await this.cleanupDeletedBranchMetadata(repoPath, branches)) {
|
|
472
534
|
this.context.hooks?.cache?.onReset?.(repoPath, "branches");
|
|
473
|
-
this.context.hooks?.repository?.onChanged?.(repoPath, ["heads"]);
|
|
535
|
+
this.context.hooks?.repository?.onChanged?.(repoPath, ["config", "heads"]);
|
|
474
536
|
}
|
|
475
537
|
if (ex instanceof BranchError) throw ex.update({
|
|
476
538
|
action: options?.force ? "force delete" : "delete",
|
|
@@ -500,6 +562,8 @@ var BranchesGitSubProvider = class {
|
|
|
500
562
|
async forgetDeletedBranch(repoPath, name) {
|
|
501
563
|
this.cache.deleteBaseBranchName(repoPath, name);
|
|
502
564
|
await this.provider.config.removeGkConfigBranchSection(repoPath, name);
|
|
565
|
+
this.clearBranchActivityStamps(repoPath, name);
|
|
566
|
+
this.forgetBranchCreatedFrom(repoPath, name);
|
|
503
567
|
}
|
|
504
568
|
/**
|
|
505
569
|
* Drops the cached and persisted per-branch metadata for whichever of `names` no longer exists. Only
|
|
@@ -539,8 +603,6 @@ var BranchesGitSubProvider = class {
|
|
|
539
603
|
];
|
|
540
604
|
try {
|
|
541
605
|
await this.git.run({ cwd: repoPath }, ...args);
|
|
542
|
-
this.context.hooks?.cache?.onReset?.(repoPath, "branches");
|
|
543
|
-
this.context.hooks?.repository?.onChanged?.(repoPath, ["remotes"]);
|
|
544
606
|
} catch (ex) {
|
|
545
607
|
if (ex instanceof BranchError) throw ex.update({
|
|
546
608
|
action: "delete",
|
|
@@ -559,6 +621,9 @@ var BranchesGitSubProvider = class {
|
|
|
559
621
|
args
|
|
560
622
|
}
|
|
561
623
|
}, ex));
|
|
624
|
+
} finally {
|
|
625
|
+
this.context.hooks?.cache?.onReset?.(repoPath, "branches");
|
|
626
|
+
this.context.hooks?.repository?.onChanged?.(repoPath, ["remotes"]);
|
|
562
627
|
}
|
|
563
628
|
}
|
|
564
629
|
async getBranchMergedStatus(repoPath, branch, into, cancellation) {
|
|
@@ -908,33 +973,26 @@ var BranchesGitSubProvider = class {
|
|
|
908
973
|
const priority = options?.priority;
|
|
909
974
|
const priorityOpts = priority != null ? { priority } : void 0;
|
|
910
975
|
try {
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
priority
|
|
914
|
-
}, cancellation);
|
|
915
|
-
if (entries.length !== 1) return {
|
|
976
|
+
const createdFrom = await this.getReflogCreatedFrom(repoPath, ref, priority, cancellation);
|
|
977
|
+
if (createdFrom == null) return {
|
|
916
978
|
branch: void 0,
|
|
917
979
|
failed: false
|
|
918
980
|
};
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
const upstream = await this.provider.refs.getSymbolicReferenceName(repoPath, `${name}@{u}`, priorityOpts);
|
|
925
|
-
if (upstream) return {
|
|
926
|
-
branch: upstream,
|
|
927
|
-
failed: false
|
|
928
|
-
};
|
|
929
|
-
}
|
|
930
|
-
name = await this.provider.refs.getSymbolicReferenceName(repoPath, name, priorityOpts);
|
|
931
|
-
if (name) return {
|
|
932
|
-
branch: name,
|
|
981
|
+
if (createdFrom !== "HEAD") {
|
|
982
|
+
if (options?.upstream) {
|
|
983
|
+
const upstream = await this.provider.refs.getSymbolicReferenceName(repoPath, `${createdFrom}@{u}`, priorityOpts);
|
|
984
|
+
if (upstream) return {
|
|
985
|
+
branch: upstream,
|
|
933
986
|
failed: false
|
|
934
987
|
};
|
|
935
988
|
}
|
|
989
|
+
const name = await this.provider.refs.getSymbolicReferenceName(repoPath, createdFrom, priorityOpts);
|
|
990
|
+
if (name) return {
|
|
991
|
+
branch: name,
|
|
992
|
+
failed: false
|
|
993
|
+
};
|
|
936
994
|
}
|
|
937
|
-
entries = await this.provider.refs.getReflogEntries(repoPath, "HEAD", {
|
|
995
|
+
const entries = await this.provider.refs.getReflogEntries(repoPath, "HEAD", {
|
|
938
996
|
grep: `checkout: moving from .* to ${ref.replace("refs/heads/", "")}`,
|
|
939
997
|
priority
|
|
940
998
|
}, cancellation);
|
|
@@ -942,7 +1000,7 @@ var BranchesGitSubProvider = class {
|
|
|
942
1000
|
branch: void 0,
|
|
943
1001
|
failed: false
|
|
944
1002
|
};
|
|
945
|
-
match = entries.at(-1).message.match(/checkout: moving from ([^\s]+)\s/);
|
|
1003
|
+
const match = entries.at(-1).message.match(/checkout: moving from ([^\s]+)\s/);
|
|
946
1004
|
if (match?.length === 2) {
|
|
947
1005
|
let name = match[1];
|
|
948
1006
|
if (options?.upstream) {
|
|
@@ -976,59 +1034,320 @@ var BranchesGitSubProvider = class {
|
|
|
976
1034
|
failed: false
|
|
977
1035
|
};
|
|
978
1036
|
}
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
1037
|
+
/**
|
|
1038
|
+
* The start point `ref`'s own reflog says it was created from (`branch: Created from <name>`), as git wrote it:
|
|
1039
|
+
* a branch, `HEAD`, a sha, a tag or any other revision. `undefined` unless exactly one entry says so. Rejects
|
|
1040
|
+
* when the reflog can't be read, including for a ref that doesn't exist.
|
|
1041
|
+
*/
|
|
1042
|
+
async getReflogCreatedFrom(repoPath, ref, priority, cancellation) {
|
|
1043
|
+
const entries = await this.provider.refs.getReflogEntries(repoPath, ref, {
|
|
1044
|
+
grep: "branch: Created from *.",
|
|
1045
|
+
priority
|
|
1046
|
+
}, cancellation);
|
|
1047
|
+
if (entries.length !== 1) return void 0;
|
|
1048
|
+
return entries[0].message.match(/branch: Created from (.*)$/)?.[1];
|
|
1049
|
+
}
|
|
1050
|
+
/**
|
|
1051
|
+
* The branch `branch` was created from, as a base for {@link getAheadOfBaseInOrder}: the name its own reflog
|
|
1052
|
+
* gives, when that is a local or remote-tracking branch in the cached ref list and not `branch` itself or its
|
|
1053
|
+
* copy on a remote (`git switch <branch>` creates it from `<remote>/<branch>`). Never the checkout history in
|
|
1054
|
+
* HEAD's reflog, which on a long-lived checkout names whatever was checked out last.
|
|
1055
|
+
*/
|
|
1056
|
+
async getCreatedFromTarget(repoPath, branch, cancellation) {
|
|
1057
|
+
const key = this.getBranchActivityKey(repoPath, branch);
|
|
1058
|
+
let name;
|
|
1059
|
+
if (this._branchCreatedFrom.has(key)) name = this._branchCreatedFrom.get(key);
|
|
1060
|
+
else {
|
|
1061
|
+
const generation = this._branchCreatedFromGenerations.get(key);
|
|
1062
|
+
try {
|
|
1063
|
+
name = await this.getReflogCreatedFrom(repoPath, `refs/heads/${branch}`, void 0, cancellation);
|
|
1064
|
+
} catch (ex) {
|
|
1065
|
+
if (isCancellationError(ex)) throw ex;
|
|
1066
|
+
return;
|
|
1067
|
+
}
|
|
1068
|
+
if (cancellation?.aborted) throw new CancellationError();
|
|
1069
|
+
if (this._branchCreatedFromGenerations.get(key) === generation) this._branchCreatedFrom.set(key, name);
|
|
1070
|
+
}
|
|
1071
|
+
if (!name || name === "HEAD" || name.startsWith("-")) return void 0;
|
|
1072
|
+
const records = await this.provider.refs.getRefs(repoPath, cancellation);
|
|
1073
|
+
const fullNames = name.startsWith("refs/heads/") || name.startsWith("refs/remotes/") ? [name] : [`refs/heads/${name}`, `refs/remotes/${name}`];
|
|
1074
|
+
let listed = false;
|
|
1075
|
+
for (const fullName of fullNames) if (await this.getRefTip(repoPath, records, fullName, cancellation) != null) {
|
|
1076
|
+
listed = true;
|
|
1077
|
+
break;
|
|
1078
|
+
}
|
|
1079
|
+
if (!listed) return void 0;
|
|
1080
|
+
if (await this.isSelfRef(repoPath, name, branch, cancellation)) return void 0;
|
|
1081
|
+
return name;
|
|
1082
|
+
}
|
|
1083
|
+
async getAheadOfBase(repoPath, branch, options, cancellation) {
|
|
1084
|
+
if (branch != null && isDetachedHead(branch)) branch = void 0;
|
|
1085
|
+
const dependsOn = new Set(branch != null ? [`refs/heads/${branch}`] : []);
|
|
1086
|
+
const preferredTarget = options?.preferredTarget?.trim() || void 0;
|
|
1087
|
+
if (preferredTarget == null) return this.getAheadOfBaseInOrder(repoPath, branch, options?.skipUserTarget, dependsOn, cancellation);
|
|
1088
|
+
let target = await this.getNearestForkTarget(repoPath, branch, preferredTarget, dependsOn, cancellation);
|
|
1089
|
+
if (target != null && branch != null && await this.isSelfAtHead(repoPath, target.ref, branch, cancellation)) target = void 0;
|
|
1090
|
+
if (cancellation?.aborted) throw new CancellationError();
|
|
1091
|
+
if (target != null) return this.measureAheadOfBase(repoPath, target, "preferred", dependsOn, cancellation);
|
|
1092
|
+
return this.getAheadOfBaseInOrder(repoPath, branch, options?.skipUserTarget, dependsOn, cancellation);
|
|
1093
|
+
}
|
|
1094
|
+
/**
|
|
1095
|
+
* Resolves the base from the recorded candidates, then the branch it was created from or the default branch, for
|
|
1096
|
+
* {@link getAheadOfBase}
|
|
1097
|
+
*/
|
|
1098
|
+
async getAheadOfBaseInOrder(repoPath, branch, skipUserTarget, dependsOn, cancellation) {
|
|
985
1099
|
let target;
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
1100
|
+
let tier = "default";
|
|
1101
|
+
if (branch != null) for (const candidate of await this.getRecordedBaseCandidates(repoPath, branch, skipUserTarget)) {
|
|
1102
|
+
target = await this.getNearestForkTarget(repoPath, branch, candidate.ref, dependsOn, cancellation);
|
|
1103
|
+
if (target != null) {
|
|
1104
|
+
tier = candidate.tier;
|
|
1105
|
+
break;
|
|
1106
|
+
}
|
|
989
1107
|
}
|
|
990
1108
|
if (target == null) {
|
|
991
1109
|
const defaultBranch = await this.getLocalDefaultBranchName(repoPath, dependsOn, cancellation);
|
|
992
|
-
|
|
1110
|
+
if (branch != null && defaultBranch === branch) return this.measureAheadOfSelfUpstream(repoPath, branch, dependsOn, cancellation);
|
|
1111
|
+
const createdFrom = branch != null ? await this.getCreatedFromTarget(repoPath, branch, cancellation) : void 0;
|
|
1112
|
+
if (createdFrom != null) {
|
|
1113
|
+
const created = await this.getCreatedFromForkTarget(repoPath, branch, createdFrom, defaultBranch, dependsOn, cancellation);
|
|
1114
|
+
if (created != null) ({target, tier} = created);
|
|
1115
|
+
} else target = await this.getUpstreamNearestForkTarget(repoPath, branch, defaultBranch, dependsOn, cancellation);
|
|
993
1116
|
}
|
|
994
1117
|
if (cancellation?.aborted) throw new CancellationError();
|
|
995
|
-
if (target == null)
|
|
1118
|
+
if (target == null) {
|
|
1119
|
+
let unborn = false;
|
|
1120
|
+
if (branch != null) try {
|
|
1121
|
+
unborn = findRefTip(await this.provider.refs.getRefs(repoPath, cancellation), `refs/heads/${branch}`) == null && await this.isUnbornHead(repoPath, cancellation);
|
|
1122
|
+
} catch (ex) {
|
|
1123
|
+
if (isCancellationError(ex)) throw ex;
|
|
1124
|
+
}
|
|
1125
|
+
return {
|
|
1126
|
+
base: void 0,
|
|
1127
|
+
reason: unborn ? "unborn" : "none",
|
|
1128
|
+
dependsOn: [...dependsOn]
|
|
1129
|
+
};
|
|
1130
|
+
}
|
|
1131
|
+
if (branch != null && await this.isSelfAtHead(repoPath, target.ref, branch, cancellation)) return {
|
|
1132
|
+
base: void 0,
|
|
1133
|
+
reason: "self",
|
|
1134
|
+
dependsOn: [...dependsOn]
|
|
1135
|
+
};
|
|
1136
|
+
return this.measureAheadOfBase(repoPath, target, tier, dependsOn, cancellation);
|
|
1137
|
+
}
|
|
1138
|
+
/**
|
|
1139
|
+
* The base for a branch created from `createdFrom`: that branch, unless the default branch forked strictly nearer
|
|
1140
|
+
* HEAD, as it does once `createdFrom` merged into it and the branch was rebased onto it. A stacked branch's
|
|
1141
|
+
* unmerged parent forked nearer, and stays its base. `undefined` when neither is usable.
|
|
1142
|
+
*/
|
|
1143
|
+
async getCreatedFromForkTarget(repoPath, branch, createdFrom, defaultBranch, dependsOn, cancellation) {
|
|
1144
|
+
const skipDefault = defaultBranch == null || createdFrom === defaultBranch || createdFrom === `refs/heads/${defaultBranch}` || createdFrom === `refs/remotes/${defaultBranch}`;
|
|
1145
|
+
const [created, fallback] = await Promise.all([this.getUpstreamNearestForkTarget(repoPath, branch, createdFrom, dependsOn, cancellation), skipDefault ? void 0 : this.getUpstreamNearestForkTarget(repoPath, branch, defaultBranch, dependsOn, cancellation)]);
|
|
1146
|
+
if (created == null) return fallback != null ? {
|
|
1147
|
+
target: fallback,
|
|
1148
|
+
tier: "default"
|
|
1149
|
+
} : void 0;
|
|
1150
|
+
if (created.forkPoint != null && fallback?.forkPoint != null && created.forkPoint !== fallback.forkPoint && await this.provider.commits.isAncestorOf(repoPath, created.forkPoint, fallback.forkPoint, cancellation)) return {
|
|
1151
|
+
target: fallback,
|
|
1152
|
+
tier: "default"
|
|
1153
|
+
};
|
|
1154
|
+
return {
|
|
1155
|
+
target: created,
|
|
1156
|
+
tier: "created"
|
|
1157
|
+
};
|
|
1158
|
+
}
|
|
1159
|
+
/** Counts HEAD's commits past `target` for {@link getAheadOfBase} */
|
|
1160
|
+
async measureAheadOfBase(repoPath, target, tier, dependsOn, cancellation) {
|
|
1161
|
+
const [countsResult, refsResult] = await Promise.allSettled([this.countAgainstBase(repoPath, target.ref, cancellation), this.provider.refs.getRefs(repoPath, cancellation)]);
|
|
1162
|
+
if (cancellation?.aborted) throw new CancellationError();
|
|
1163
|
+
if (countsResult.status === "rejected") throw countsResult.reason;
|
|
1164
|
+
const counts = countsResult.value;
|
|
1165
|
+
if (counts == null) return {
|
|
1166
|
+
base: void 0,
|
|
1167
|
+
reason: "unborn",
|
|
1168
|
+
dependsOn: [...dependsOn]
|
|
1169
|
+
};
|
|
1170
|
+
return {
|
|
1171
|
+
base: {
|
|
1172
|
+
ref: target.ref,
|
|
1173
|
+
ahead: counts.right,
|
|
1174
|
+
sha: findRefTip(getSettledValue(refsResult, []), target.ref),
|
|
1175
|
+
forkPoint: target.forkPoint,
|
|
1176
|
+
tier
|
|
1177
|
+
},
|
|
1178
|
+
dependsOn: [...dependsOn]
|
|
1179
|
+
};
|
|
1180
|
+
}
|
|
1181
|
+
/** Counts the default branch's own commits past its upstream (see {@link getSelfUpstreamTarget}) for {@link getAheadOfBase} */
|
|
1182
|
+
async measureAheadOfSelfUpstream(repoPath, branch, dependsOn, cancellation) {
|
|
1183
|
+
const upstream = await this.getSelfUpstreamTarget(repoPath, branch, dependsOn, cancellation);
|
|
1184
|
+
if (cancellation?.aborted) throw new CancellationError();
|
|
1185
|
+
if (upstream == null) return {
|
|
996
1186
|
base: void 0,
|
|
1187
|
+
reason: "self",
|
|
997
1188
|
dependsOn: [...dependsOn]
|
|
998
1189
|
};
|
|
999
|
-
|
|
1190
|
+
const counts = await this.countAgainstBase(repoPath, upstream.ref, cancellation);
|
|
1191
|
+
if (counts == null) return {
|
|
1000
1192
|
base: void 0,
|
|
1193
|
+
reason: "unborn",
|
|
1001
1194
|
dependsOn: [...dependsOn]
|
|
1002
1195
|
};
|
|
1003
|
-
|
|
1004
|
-
|
|
1196
|
+
if (counts.left === 0 && counts.right === 0) return {
|
|
1197
|
+
base: void 0,
|
|
1198
|
+
reason: "self",
|
|
1199
|
+
dependsOn: [...dependsOn]
|
|
1200
|
+
};
|
|
1201
|
+
const forkPoint = counts.left === 0 ? upstream.sha : await this.getForkPoint(repoPath, upstream.ref, cancellation);
|
|
1005
1202
|
if (cancellation?.aborted) throw new CancellationError();
|
|
1006
|
-
let counts = getSettledValue(countsResult);
|
|
1007
|
-
if (counts == null) try {
|
|
1008
|
-
counts = await this.getLeftRightCountUncached(repoPath, range, cancellation);
|
|
1009
|
-
} catch (ex) {
|
|
1010
|
-
if (isCancellationError(ex)) throw ex;
|
|
1011
|
-
if (await this.isUnbornHead(repoPath, cancellation)) return {
|
|
1012
|
-
base: void 0,
|
|
1013
|
-
dependsOn: [...dependsOn]
|
|
1014
|
-
};
|
|
1015
|
-
throw ex;
|
|
1016
|
-
}
|
|
1017
1203
|
return {
|
|
1018
1204
|
base: {
|
|
1019
|
-
ref:
|
|
1205
|
+
ref: upstream.ref,
|
|
1020
1206
|
ahead: counts.right,
|
|
1021
|
-
sha:
|
|
1207
|
+
sha: upstream.sha,
|
|
1208
|
+
forkPoint,
|
|
1209
|
+
tier: "upstream"
|
|
1022
1210
|
},
|
|
1023
1211
|
dependsOn: [...dependsOn]
|
|
1024
1212
|
};
|
|
1025
1213
|
}
|
|
1026
|
-
/**
|
|
1027
|
-
|
|
1214
|
+
/**
|
|
1215
|
+
* HEAD's count against `ref` from their merge base: `left` only `ref` has, `right` only HEAD has. `undefined`
|
|
1216
|
+
* before HEAD's first commit, when there is nothing to count; any other failure rejects with git's error.
|
|
1217
|
+
*/
|
|
1218
|
+
async countAgainstBase(repoPath, ref, cancellation) {
|
|
1219
|
+
const range = createRevisionRange(ref, "HEAD", "...");
|
|
1220
|
+
let counts;
|
|
1221
|
+
try {
|
|
1222
|
+
counts = await this.provider.commits.getLeftRightCommitCount(repoPath, range, void 0, cancellation);
|
|
1223
|
+
} catch (ex) {
|
|
1224
|
+
if (isCancellationError(ex)) throw ex;
|
|
1225
|
+
}
|
|
1226
|
+
if (cancellation?.aborted) throw new CancellationError();
|
|
1227
|
+
if (counts != null) return counts;
|
|
1228
|
+
try {
|
|
1229
|
+
return await this.getLeftRightCountUncached(repoPath, range, cancellation);
|
|
1230
|
+
} catch (ex) {
|
|
1231
|
+
if (isCancellationError(ex)) throw ex;
|
|
1232
|
+
if (await this.isUnbornHead(repoPath, cancellation)) return void 0;
|
|
1233
|
+
throw ex;
|
|
1234
|
+
}
|
|
1235
|
+
}
|
|
1236
|
+
async getAheadBehindCounts(repoPath, base, options, cancellation) {
|
|
1237
|
+
const counts = /* @__PURE__ */ new Map();
|
|
1238
|
+
const refs = options?.refs != null ? new Set(options.refs) : void 0;
|
|
1239
|
+
if (refs?.size === 0) return counts;
|
|
1240
|
+
let patterns;
|
|
1241
|
+
if (refs == null) patterns = options?.remotes ? ["refs/heads", "refs/remotes"] : ["refs/heads"];
|
|
1242
|
+
else if (sum(refs, (r) => r.length + 1) < 15e3) patterns = [...refs];
|
|
1243
|
+
else patterns = ["refs/heads", "refs/remotes"];
|
|
1244
|
+
if (!base.includes(")") && await this.git.supports("git:for-each-ref:ahead-behind")) {
|
|
1245
|
+
const result = await this.git.run({
|
|
1246
|
+
cwd: repoPath,
|
|
1247
|
+
cancellation,
|
|
1248
|
+
errors: "throw"
|
|
1249
|
+
}, "for-each-ref", `--format=%(refname)%09%(symref)%09%(ahead-behind:${base})`, ...patterns);
|
|
1250
|
+
if (result.completion.status === "cancelled" || cancellation?.aborted) throw new CancellationError();
|
|
1251
|
+
for (const line of result.stdout.split("\n")) {
|
|
1252
|
+
if (!line) continue;
|
|
1253
|
+
const [ref, symref, counted] = line.split(" ");
|
|
1254
|
+
if (symref || refs != null && !refs.has(ref)) continue;
|
|
1255
|
+
const [ahead, behind] = (counted ?? "").split(" ").map((n) => parseInt(n, 10));
|
|
1256
|
+
if (isNaN(ahead) || isNaN(behind)) throw new Error(`Unexpected output counting the commits ahead of and behind '${base}': ${line}`);
|
|
1257
|
+
counts.set(ref, {
|
|
1258
|
+
ahead,
|
|
1259
|
+
behind
|
|
1260
|
+
});
|
|
1261
|
+
}
|
|
1262
|
+
return counts;
|
|
1263
|
+
}
|
|
1264
|
+
if (base.startsWith("-")) throw new Error(`Invalid base '${base}'`);
|
|
1265
|
+
const [refsResult, baseResult] = await Promise.allSettled([this.git.run({
|
|
1266
|
+
cwd: repoPath,
|
|
1267
|
+
cancellation,
|
|
1268
|
+
errors: "throw"
|
|
1269
|
+
}, "for-each-ref", "--format=%(refname)%09%(symref)%09%(objectname)", ...patterns), this.git.run({
|
|
1270
|
+
cwd: repoPath,
|
|
1271
|
+
cancellation,
|
|
1272
|
+
errors: "throw"
|
|
1273
|
+
}, "rev-parse", "--is-shallow-repository", "--verify", `${base}^{commit}`)]);
|
|
1274
|
+
if (refsResult.status === "rejected") throw refsResult.reason;
|
|
1275
|
+
const result = refsResult.value;
|
|
1276
|
+
if (result.completion.status === "cancelled" || cancellation?.aborted) throw new CancellationError();
|
|
1277
|
+
const refsByTip = /* @__PURE__ */ new Map();
|
|
1278
|
+
for (const line of result.stdout.split("\n")) {
|
|
1279
|
+
const [ref, symref, sha] = line.split(" ");
|
|
1280
|
+
if (!ref || symref || !sha || refs != null && !refs.has(ref)) continue;
|
|
1281
|
+
const tipRefs = refsByTip.get(sha);
|
|
1282
|
+
if (tipRefs == null) refsByTip.set(sha, [ref]);
|
|
1283
|
+
else tipRefs.push(ref);
|
|
1284
|
+
}
|
|
1285
|
+
if (!refsByTip.size) return counts;
|
|
1286
|
+
if (baseResult.status === "rejected") throw baseResult.reason;
|
|
1287
|
+
if (baseResult.value.completion.status === "cancelled" || cancellation?.aborted) throw new CancellationError();
|
|
1288
|
+
const [shallow, baseSha] = baseResult.value.stdout.trim().split("\n");
|
|
1289
|
+
const cacheable = shallow === "false";
|
|
1290
|
+
const countsByTip = /* @__PURE__ */ new Map();
|
|
1291
|
+
const uncounted = [];
|
|
1292
|
+
for (const sha of refsByTip.keys()) {
|
|
1293
|
+
if (sha === baseSha) {
|
|
1294
|
+
countsByTip.set(sha, {
|
|
1295
|
+
ahead: 0,
|
|
1296
|
+
behind: 0
|
|
1297
|
+
});
|
|
1298
|
+
continue;
|
|
1299
|
+
}
|
|
1300
|
+
const range = createRevisionRange(baseSha, sha, "...");
|
|
1301
|
+
const cached = cacheable ? this.aheadBehindBySha.get(range) : void 0;
|
|
1302
|
+
if (cached != null) {
|
|
1303
|
+
this.aheadBehindBySha.touch(range);
|
|
1304
|
+
countsByTip.set(sha, cached);
|
|
1305
|
+
} else uncounted.push(sha);
|
|
1306
|
+
}
|
|
1307
|
+
const results = await mapBounded(uncounted, 8, async (sha) => {
|
|
1308
|
+
const range = createRevisionRange(baseSha, sha, "...");
|
|
1309
|
+
const lr = await this.getLeftRightCountUncached(repoPath, range, cancellation, "background");
|
|
1310
|
+
const tipCounts = {
|
|
1311
|
+
ahead: lr.right,
|
|
1312
|
+
behind: lr.left
|
|
1313
|
+
};
|
|
1314
|
+
if (cacheable) this.aheadBehindBySha.set(range, tipCounts);
|
|
1315
|
+
return {
|
|
1316
|
+
sha,
|
|
1317
|
+
counts: tipCounts
|
|
1318
|
+
};
|
|
1319
|
+
});
|
|
1320
|
+
for (const { sha, counts: tipCounts } of results) countsByTip.set(sha, tipCounts);
|
|
1321
|
+
for (const [sha, tipRefs] of refsByTip) {
|
|
1322
|
+
const tipCounts = countsByTip.get(sha);
|
|
1323
|
+
for (const ref of tipRefs) counts.set(ref, { ...tipCounts });
|
|
1324
|
+
}
|
|
1325
|
+
return counts;
|
|
1326
|
+
}
|
|
1327
|
+
async getMergedBranches(repoPath, into, options, cancellation) {
|
|
1328
|
+
const patterns = options?.remotes ? ["refs/heads", "refs/remotes"] : ["refs/heads"];
|
|
1028
1329
|
const result = await this.git.run({
|
|
1029
1330
|
cwd: repoPath,
|
|
1030
1331
|
cancellation,
|
|
1031
1332
|
errors: "throw"
|
|
1333
|
+
}, "for-each-ref", `--merged=${into}`, "--format=%(refname)%09%(symref)", ...patterns);
|
|
1334
|
+
if (result.completion.status === "cancelled" || cancellation?.aborted) throw new CancellationError();
|
|
1335
|
+
const merged = /* @__PURE__ */ new Set();
|
|
1336
|
+
for (const line of result.stdout.split("\n")) {
|
|
1337
|
+
if (!line) continue;
|
|
1338
|
+
const [ref, symref] = line.split(" ");
|
|
1339
|
+
if (symref) continue;
|
|
1340
|
+
merged.add(ref);
|
|
1341
|
+
}
|
|
1342
|
+
return merged;
|
|
1343
|
+
}
|
|
1344
|
+
/** Counts `range` with a fresh `rev-list`, bypassing the cached count. Rejects on any failure, with git's error */
|
|
1345
|
+
async getLeftRightCountUncached(repoPath, range, cancellation, priority) {
|
|
1346
|
+
const result = await this.git.run({
|
|
1347
|
+
cwd: repoPath,
|
|
1348
|
+
cancellation,
|
|
1349
|
+
errors: "throw",
|
|
1350
|
+
priority
|
|
1032
1351
|
}, "rev-list", "--left-right", "--count", range, "--");
|
|
1033
1352
|
if (result.completion.status === "cancelled" || cancellation?.aborted) throw new CancellationError();
|
|
1034
1353
|
const parts = result.stdout.split(" ");
|
|
@@ -1050,20 +1369,27 @@ var BranchesGitSubProvider = class {
|
|
|
1050
1369
|
}, "rev-parse", "--verify", "--quiet", "HEAD")).exitCode === 1;
|
|
1051
1370
|
}
|
|
1052
1371
|
/**
|
|
1053
|
-
* Whether `ref` names `branch` itself: the same name, or `<remote>/<branch>` for one of the repo's actual remotes
|
|
1054
|
-
* Splitting at the first slash instead would call a local `release/main` the same branch
|
|
1372
|
+
* Whether `ref` names `branch` itself: the same name, or `<remote>/<branch>` for one of the repo's actual remotes,
|
|
1373
|
+
* short or as a full ref. Splitting at the first slash instead would call a local `release/main` the same branch
|
|
1374
|
+
* as `main`.
|
|
1055
1375
|
*/
|
|
1056
1376
|
async isSelfRef(repoPath, ref, branch, cancellation) {
|
|
1377
|
+
if (ref.startsWith("refs/heads/")) return ref.slice(11) === branch;
|
|
1378
|
+
if (ref.startsWith("refs/remotes/")) ref = ref.slice(13);
|
|
1057
1379
|
if (ref === branch) return true;
|
|
1058
1380
|
const suffix = `/${branch}`;
|
|
1059
1381
|
if (!ref.endsWith(suffix)) return false;
|
|
1060
1382
|
const remoteName = ref.slice(0, -suffix.length);
|
|
1061
1383
|
if (!remoteName) return false;
|
|
1384
|
+
return (await this.getRemoteNames(repoPath, cancellation)).includes(remoteName);
|
|
1385
|
+
}
|
|
1386
|
+
/** The repo's remote names from the cached remote list; empty when it can't be read */
|
|
1387
|
+
async getRemoteNames(repoPath, cancellation) {
|
|
1062
1388
|
try {
|
|
1063
|
-
return (await this.provider.remotes.getRemotes(repoPath, void 0, cancellation)).
|
|
1389
|
+
return (await this.provider.remotes.getRemotes(repoPath, void 0, cancellation)).map((r) => r.name);
|
|
1064
1390
|
} catch (ex) {
|
|
1065
1391
|
if (isCancellationError(ex)) throw ex;
|
|
1066
|
-
return
|
|
1392
|
+
return [];
|
|
1067
1393
|
}
|
|
1068
1394
|
}
|
|
1069
1395
|
/** Whether `ref` names `branch` itself (see {@link isSelfRef}) and sits at HEAD's commit; tips are read only after the name matched */
|
|
@@ -1077,7 +1403,7 @@ var BranchesGitSubProvider = class {
|
|
|
1077
1403
|
* base a launch recorded. Only the stored value of the latter — `getBaseBranchName` would fall back to a reflog
|
|
1078
1404
|
* guess, which on a long-lived checkout names whatever branch was last checked out from. A failed read is absent.
|
|
1079
1405
|
*/
|
|
1080
|
-
async getRecordedBaseCandidates(repoPath, branch) {
|
|
1406
|
+
async getRecordedBaseCandidates(repoPath, branch, skipUserTarget) {
|
|
1081
1407
|
const tolerate = async (read) => {
|
|
1082
1408
|
try {
|
|
1083
1409
|
return (await read())?.trim() || void 0;
|
|
@@ -1087,28 +1413,56 @@ var BranchesGitSubProvider = class {
|
|
|
1087
1413
|
}
|
|
1088
1414
|
};
|
|
1089
1415
|
const reads = await Promise.all([
|
|
1090
|
-
tolerate(() => this.getStoredUserMergeTargetBranchName(repoPath, branch)),
|
|
1416
|
+
skipUserTarget ? void 0 : tolerate(() => this.getStoredUserMergeTargetBranchName(repoPath, branch)),
|
|
1091
1417
|
tolerate(() => this.getStoredDetectedMergeTargetBranchName(repoPath, branch)),
|
|
1092
1418
|
tolerate(() => this.provider.config.getGkConfig(repoPath, `branch.${branch}.gk-merge-base`))
|
|
1093
1419
|
]);
|
|
1420
|
+
const tiers = [
|
|
1421
|
+
"user",
|
|
1422
|
+
"detected",
|
|
1423
|
+
"recorded"
|
|
1424
|
+
];
|
|
1094
1425
|
const candidates = [];
|
|
1095
|
-
for (const
|
|
1426
|
+
for (const [i, ref] of reads.entries()) if (ref && !candidates.some((c) => c.ref === ref)) candidates.push({
|
|
1427
|
+
ref,
|
|
1428
|
+
tier: tiers[i]
|
|
1429
|
+
});
|
|
1096
1430
|
return candidates;
|
|
1097
1431
|
}
|
|
1098
1432
|
/**
|
|
1099
|
-
* The repo's default branch from local refs only: the `
|
|
1100
|
-
* `
|
|
1433
|
+
* The repo's default branch from local refs only, never the network: the `HEAD` symref of the `upstream` remote,
|
|
1434
|
+
* else of `origin`, else of the first remote, as its local branch or else as the remote ref; then, in the same
|
|
1435
|
+
* remote order, `<remote>/main` or `<remote>/master`, again as the local branch of that name or else the remote
|
|
1436
|
+
* ref; then `init.defaultBranch`, `main`, `master` — each only if it names a commit.
|
|
1101
1437
|
*/
|
|
1102
1438
|
async getLocalDefaultBranchName(repoPath, dependsOn, cancellation) {
|
|
1103
|
-
|
|
1104
|
-
const
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1439
|
+
let records;
|
|
1440
|
+
const exists = async (ref) => {
|
|
1441
|
+
records ??= await this.provider.refs.getRefs(repoPath, cancellation);
|
|
1442
|
+
return await this.getRefTip(repoPath, records, ref, cancellation) != null;
|
|
1443
|
+
};
|
|
1444
|
+
const remotes = await this.getRemoteNames(repoPath, cancellation);
|
|
1445
|
+
const preferred = new Set([
|
|
1446
|
+
remotes.find((r) => r === "upstream"),
|
|
1447
|
+
remotes.find((r) => r === "origin"),
|
|
1448
|
+
remotes.at(0)
|
|
1449
|
+
].filter((r) => r != null));
|
|
1450
|
+
for (const remote of preferred) {
|
|
1451
|
+
dependsOn.add(`refs/remotes/${remote}/HEAD`);
|
|
1452
|
+
const symref = await this.getDefaultBranchName(repoPath, remote, { local: true }, cancellation);
|
|
1453
|
+
const local = symref?.startsWith(`${remote}/`) ? symref.slice(remote.length + 1) : void 0;
|
|
1454
|
+
if (!symref || !local) continue;
|
|
1455
|
+
addRefDependencies(dependsOn, local);
|
|
1456
|
+
if (await exists(local)) return local;
|
|
1457
|
+
addRefDependencies(dependsOn, symref);
|
|
1458
|
+
if (await exists(symref)) return symref;
|
|
1459
|
+
}
|
|
1460
|
+
for (const remote of preferred) for (const name of ["main", "master"]) {
|
|
1461
|
+
const remoteRef = `refs/remotes/${remote}/${name}`;
|
|
1462
|
+
dependsOn.add(remoteRef);
|
|
1463
|
+
if (!await exists(remoteRef)) continue;
|
|
1464
|
+
addRefDependencies(dependsOn, name);
|
|
1465
|
+
return await exists(name) ? name : `${remote}/${name}`;
|
|
1112
1466
|
}
|
|
1113
1467
|
let fromConfig;
|
|
1114
1468
|
try {
|
|
@@ -1121,20 +1475,55 @@ var BranchesGitSubProvider = class {
|
|
|
1121
1475
|
]) {
|
|
1122
1476
|
if (!candidate) continue;
|
|
1123
1477
|
addRefDependencies(dependsOn, candidate);
|
|
1124
|
-
if (await
|
|
1478
|
+
if (await exists(candidate)) return candidate;
|
|
1479
|
+
}
|
|
1480
|
+
}
|
|
1481
|
+
/**
|
|
1482
|
+
* `upstream/<name>` for a created-from or default base `<name>` (or `<remote>/<name>`), as another ref that base
|
|
1483
|
+
* is measured against: in a fork whose `main` tracks `origin/main`, a branch taken off (or since rebased onto) a
|
|
1484
|
+
* newer `upstream/main` would otherwise count the commits `origin` hasn't caught up on. Read from the cached
|
|
1485
|
+
* remote and ref lists; `undefined` when there is no `upstream` remote, it lacks the branch, or the base is that
|
|
1486
|
+
* ref itself.
|
|
1487
|
+
*/
|
|
1488
|
+
async getUpstreamContender(repoPath, base, dependsOn, cancellation) {
|
|
1489
|
+
if (!base) return void 0;
|
|
1490
|
+
const remotes = await this.getRemoteNames(repoPath, cancellation);
|
|
1491
|
+
if (!remotes.includes("upstream")) return void 0;
|
|
1492
|
+
const records = await this.provider.refs.getRefs(repoPath, cancellation);
|
|
1493
|
+
let name = base;
|
|
1494
|
+
if (name.startsWith("refs/heads/")) name = name.slice(11);
|
|
1495
|
+
else {
|
|
1496
|
+
const isRemoteRef = name.startsWith("refs/remotes/");
|
|
1497
|
+
if (isRemoteRef) name = name.slice(13);
|
|
1498
|
+
if (isRemoteRef || findRefTip(records, `refs/heads/${name}`) == null) {
|
|
1499
|
+
const remote = remotes.find((r) => name.startsWith(`${r}/`));
|
|
1500
|
+
if (remote != null) name = name.slice(remote.length + 1);
|
|
1501
|
+
}
|
|
1125
1502
|
}
|
|
1503
|
+
const contender = `upstream/${name}`;
|
|
1504
|
+
const remoteRef = `refs/remotes/${contender}`;
|
|
1505
|
+
if (contender === base || remoteRef === base || findRefTip(records, remoteRef) == null) return void 0;
|
|
1506
|
+
dependsOn.add(remoteRef);
|
|
1507
|
+
return contender;
|
|
1508
|
+
}
|
|
1509
|
+
/** {@link getNearestForkTarget} with the `upstream` remote's copy of `candidate` as its contender */
|
|
1510
|
+
async getUpstreamNearestForkTarget(repoPath, branch, candidate, dependsOn, cancellation) {
|
|
1511
|
+
return this.getNearestForkTarget(repoPath, branch, candidate, dependsOn, cancellation, { contender: await this.getUpstreamContender(repoPath, candidate, dependsOn, cancellation) });
|
|
1126
1512
|
}
|
|
1127
1513
|
/**
|
|
1128
1514
|
* A candidate base as the ref HEAD is measured against: the candidate or its tracking ref, whichever forked
|
|
1129
1515
|
* nearer HEAD, so a stale local base defers to its upstream while one carrying unpushed work wins over it. A
|
|
1130
|
-
*
|
|
1516
|
+
* `contender` other than the tracking ref is taken only when it forked strictly nearer than both, so a tie keeps
|
|
1517
|
+
* the candidate's own pick. A candidate none forks from is used as-is if it names a commit. `undefined` when the
|
|
1518
|
+
* candidate is unusable.
|
|
1131
1519
|
*/
|
|
1132
|
-
async getNearestForkTarget(repoPath, branch, candidate, dependsOn, cancellation) {
|
|
1520
|
+
async getNearestForkTarget(repoPath, branch, candidate, dependsOn, cancellation, options) {
|
|
1133
1521
|
if (!candidate || candidate.startsWith("-")) return void 0;
|
|
1134
|
-
if (candidate === branch) return void 0;
|
|
1522
|
+
if (candidate === branch || candidate === `refs/heads/${branch}`) return void 0;
|
|
1135
1523
|
addRefDependencies(dependsOn, candidate);
|
|
1524
|
+
const upstreamName = this.provider.refs.getSymbolicReferenceName(repoPath, `${candidate}@{u}`, void 0, cancellation);
|
|
1136
1525
|
const getUpstreamFork = async () => {
|
|
1137
|
-
const upstream = await
|
|
1526
|
+
const upstream = await upstreamName;
|
|
1138
1527
|
if (!upstream || upstream.startsWith("-")) return void 0;
|
|
1139
1528
|
addRefDependencies(dependsOn, upstream);
|
|
1140
1529
|
return {
|
|
@@ -1142,7 +1531,23 @@ var BranchesGitSubProvider = class {
|
|
|
1142
1531
|
mergeBase: await this.getForkPoint(repoPath, upstream, cancellation)
|
|
1143
1532
|
};
|
|
1144
1533
|
};
|
|
1145
|
-
const
|
|
1534
|
+
const contender = options?.contender;
|
|
1535
|
+
const getContenderMergeBase = async () => {
|
|
1536
|
+
if (contender == null) return void 0;
|
|
1537
|
+
const records = await this.provider.refs.getRefs(repoPath, cancellation);
|
|
1538
|
+
const tip = findRefTip(records, contender);
|
|
1539
|
+
if (tip != null) {
|
|
1540
|
+
if (tip === findRefTip(records, candidate)) return void 0;
|
|
1541
|
+
const upstream = await upstreamName;
|
|
1542
|
+
if (upstream && tip === findRefTip(records, upstream)) return void 0;
|
|
1543
|
+
}
|
|
1544
|
+
return this.getForkPoint(repoPath, contender, cancellation);
|
|
1545
|
+
};
|
|
1546
|
+
const [candidateMergeBase, upstreamFork, contenderMergeBase] = await Promise.all([
|
|
1547
|
+
this.getForkPoint(repoPath, candidate, cancellation),
|
|
1548
|
+
getUpstreamFork(),
|
|
1549
|
+
getContenderMergeBase()
|
|
1550
|
+
]);
|
|
1146
1551
|
let nearest;
|
|
1147
1552
|
for (const fork of [{
|
|
1148
1553
|
ref: candidate,
|
|
@@ -1154,8 +1559,59 @@ var BranchesGitSubProvider = class {
|
|
|
1154
1559
|
mergeBase: fork.mergeBase
|
|
1155
1560
|
};
|
|
1156
1561
|
}
|
|
1157
|
-
if (nearest != null
|
|
1158
|
-
|
|
1562
|
+
if (contender != null && contender !== upstreamFork?.ref && contenderMergeBase != null && nearest != null && nearest.mergeBase !== contenderMergeBase && await this.provider.commits.isAncestorOf(repoPath, nearest.mergeBase, contenderMergeBase, cancellation)) nearest = {
|
|
1563
|
+
ref: contender,
|
|
1564
|
+
mergeBase: contenderMergeBase
|
|
1565
|
+
};
|
|
1566
|
+
if (nearest != null) return {
|
|
1567
|
+
ref: nearest.ref,
|
|
1568
|
+
forkPoint: nearest.mergeBase
|
|
1569
|
+
};
|
|
1570
|
+
return await this.provider.refs.validateReference(repoPath, candidate, void 0, cancellation) ? {
|
|
1571
|
+
ref: candidate,
|
|
1572
|
+
forkPoint: void 0
|
|
1573
|
+
} : void 0;
|
|
1574
|
+
}
|
|
1575
|
+
/**
|
|
1576
|
+
* The base for a branch that is itself the default branch, with its tip: its own upstream, `upstream/<branch>`
|
|
1577
|
+
* when an `upstream` remote has it, else `<branch>@{u}`. Read from the cached ref and branch lists; only a
|
|
1578
|
+
* `<branch>` that isn't checked out spawns, for its `@{u}`. `undefined` when there is neither, or its ref is gone.
|
|
1579
|
+
*/
|
|
1580
|
+
async getSelfUpstreamTarget(repoPath, branch, dependsOn, cancellation) {
|
|
1581
|
+
const records = await this.provider.refs.getRefs(repoPath, cancellation);
|
|
1582
|
+
let upstream;
|
|
1583
|
+
if ((await this.getRemoteNames(repoPath, cancellation)).includes("upstream")) {
|
|
1584
|
+
const remoteRef = `refs/remotes/upstream/${branch}`;
|
|
1585
|
+
dependsOn.add(remoteRef);
|
|
1586
|
+
if (await this.getRefTip(repoPath, records, remoteRef, cancellation) != null) upstream = `upstream/${branch}`;
|
|
1587
|
+
}
|
|
1588
|
+
if (upstream == null) {
|
|
1589
|
+
let tracking;
|
|
1590
|
+
const current = await this.getBranch(repoPath, void 0, void 0, cancellation);
|
|
1591
|
+
if (current?.name === branch && !current.detached && !current.remote) {
|
|
1592
|
+
tracking = current.upstream?.name;
|
|
1593
|
+
if (tracking && current.upstream?.missing) {
|
|
1594
|
+
addRefDependencies(dependsOn, tracking);
|
|
1595
|
+
return;
|
|
1596
|
+
}
|
|
1597
|
+
} else tracking = await this.provider.refs.getSymbolicReferenceName(repoPath, `${branch}@{u}`, void 0, cancellation);
|
|
1598
|
+
if (!tracking || tracking.startsWith("-")) return void 0;
|
|
1599
|
+
addRefDependencies(dependsOn, tracking);
|
|
1600
|
+
upstream = tracking;
|
|
1601
|
+
}
|
|
1602
|
+
const sha = await this.getRefTip(repoPath, records, upstream, cancellation);
|
|
1603
|
+
return sha != null ? {
|
|
1604
|
+
ref: upstream,
|
|
1605
|
+
sha
|
|
1606
|
+
} : void 0;
|
|
1607
|
+
}
|
|
1608
|
+
/**
|
|
1609
|
+
* {@link findRefTip} in `records`, the cached ref list. `getRefs` answers a failed enumeration with an empty list,
|
|
1610
|
+
* so an empty one is asked of git instead, rather than read as every ref being absent.
|
|
1611
|
+
*/
|
|
1612
|
+
async getRefTip(repoPath, records, name, cancellation) {
|
|
1613
|
+
if (records.length) return findRefTip(records, name);
|
|
1614
|
+
return this.provider.refs.validateReference(repoPath, name, void 0, cancellation);
|
|
1159
1615
|
}
|
|
1160
1616
|
/** The merge base of HEAD and `ref`, preferring git's fork-point reading of `ref`'s reflog when it has one */
|
|
1161
1617
|
async getForkPoint(repoPath, ref, cancellation) {
|
|
@@ -1191,25 +1647,61 @@ var BranchesGitSubProvider = class {
|
|
|
1191
1647
|
async getStoredUserMergeTargetBranchName(repoPath, ref) {
|
|
1192
1648
|
return (await this.provider.config.getGkConfig(repoPath, `branch.${ref}.gk-merge-target-user`))?.trim() || void 0;
|
|
1193
1649
|
}
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
}
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1650
|
+
onCurrentBranchAccessed(repoPath) {
|
|
1651
|
+
return this.recordBranchActivity(repoPath, ["gk-last-accessed"]);
|
|
1652
|
+
}
|
|
1653
|
+
onCurrentBranchModified(repoPath) {
|
|
1654
|
+
return this.recordBranchActivity(repoPath, ["gk-last-accessed", "gk-last-modified"]);
|
|
1655
|
+
}
|
|
1656
|
+
onCurrentBranchAgentActivity(repoPath) {
|
|
1657
|
+
return this.recordBranchActivity(repoPath, ["gk-agent-last-activity"]);
|
|
1658
|
+
}
|
|
1659
|
+
getBranchActivityKey(repoPath, name) {
|
|
1660
|
+
return `${this.cache.getCommonPath(repoPath)}\0${name}`;
|
|
1661
|
+
}
|
|
1662
|
+
forgetBranchCreatedFrom(repoPath, name) {
|
|
1663
|
+
const key = this.getBranchActivityKey(repoPath, name);
|
|
1664
|
+
this._branchCreatedFrom.delete(key);
|
|
1665
|
+
this._branchCreatedFromGenerations.set(key, (this._branchCreatedFromGenerations.get(key) ?? 0) + 1);
|
|
1666
|
+
}
|
|
1667
|
+
/** Forgets this process's throttle marks for `name`, so its next activity re-reads the stored stamps */
|
|
1668
|
+
clearBranchActivityStamps(repoPath, name) {
|
|
1669
|
+
const branchKey = this.getBranchActivityKey(repoPath, name);
|
|
1670
|
+
for (const key of branchActivityKeys) this._branchActivityStamps.delete(`${branchKey}\0${key}`);
|
|
1671
|
+
}
|
|
1672
|
+
/** Never rejects — activity stamps are bookkeeping and callers fire them without a catch */
|
|
1673
|
+
async recordBranchActivity(repoPath, keys) {
|
|
1674
|
+
const scope = getScopedLogger();
|
|
1675
|
+
try {
|
|
1676
|
+
const branch = await this.getBranch(repoPath);
|
|
1677
|
+
if (branch == null || branch.remote || branch.detached) return;
|
|
1678
|
+
const branchKey = this.getBranchActivityKey(repoPath, branch.name);
|
|
1679
|
+
await this._branchActivityCalls.run(branchKey, void 0, async () => {
|
|
1680
|
+
const now = Date.now();
|
|
1681
|
+
const due = [];
|
|
1682
|
+
for (const key of keys) {
|
|
1683
|
+
const stampKey = `${branchKey}\0${key}`;
|
|
1684
|
+
const last = this._branchActivityStamps.get(stampKey);
|
|
1685
|
+
if (last != null && now - last < dateMetadataStaleThresholdMs) continue;
|
|
1686
|
+
this._branchActivityStamps.set(stampKey, now);
|
|
1687
|
+
due.push(key);
|
|
1688
|
+
}
|
|
1689
|
+
if (!due.length) return;
|
|
1690
|
+
const stored = await Promise.allSettled(due.map((key) => this.provider.config.getGkConfig(repoPath, `branch.${branch.name}.${key}`)));
|
|
1691
|
+
const value = new Date(now).toISOString();
|
|
1692
|
+
for (const [i, key] of due.entries()) {
|
|
1693
|
+
const stamp = getSettledValue(stored[i]);
|
|
1694
|
+
const last = stamp != null ? new Date(stamp).getTime() : NaN;
|
|
1695
|
+
if (now - last < dateMetadataStaleThresholdMs) {
|
|
1696
|
+
this._branchActivityStamps.set(`${branchKey}\0${key}`, last);
|
|
1697
|
+
continue;
|
|
1698
|
+
}
|
|
1699
|
+
await this.provider.config.setGkConfig(repoPath, `branch.${branch.name}.${key}`, value);
|
|
1700
|
+
}
|
|
1701
|
+
});
|
|
1702
|
+
} catch (ex) {
|
|
1703
|
+
scope?.error(ex, `Failed to record branch activity (${keys.join(", ")})`);
|
|
1704
|
+
}
|
|
1213
1705
|
}
|
|
1214
1706
|
async renameBranch(repoPath, oldName, newName) {
|
|
1215
1707
|
const args = [
|
|
@@ -1223,8 +1715,10 @@ var BranchesGitSubProvider = class {
|
|
|
1223
1715
|
await this.provider.config.renameGkConfigBranchSection(repoPath, oldName, newName);
|
|
1224
1716
|
this.cache.deleteBaseBranchName(repoPath, oldName);
|
|
1225
1717
|
this.cache.deleteBaseBranchName(repoPath, newName);
|
|
1226
|
-
this.
|
|
1227
|
-
this.
|
|
1718
|
+
this.clearBranchActivityStamps(repoPath, oldName);
|
|
1719
|
+
this.clearBranchActivityStamps(repoPath, newName);
|
|
1720
|
+
this.forgetBranchCreatedFrom(repoPath, oldName);
|
|
1721
|
+
this.forgetBranchCreatedFrom(repoPath, newName);
|
|
1228
1722
|
} catch (ex) {
|
|
1229
1723
|
throw getGitCommandError("branch", ex, (reason) => new BranchError({
|
|
1230
1724
|
reason: reason ?? "other",
|
|
@@ -1235,6 +1729,9 @@ var BranchesGitSubProvider = class {
|
|
|
1235
1729
|
args
|
|
1236
1730
|
}
|
|
1237
1731
|
}, ex));
|
|
1732
|
+
} finally {
|
|
1733
|
+
this.context.hooks?.cache?.onReset?.(repoPath, "branches");
|
|
1734
|
+
this.context.hooks?.repository?.onChanged?.(repoPath, ["config", "heads"]);
|
|
1238
1735
|
}
|
|
1239
1736
|
}
|
|
1240
1737
|
async setUpstreamBranch(repoPath, name, upstream) {
|
|
@@ -1250,8 +1747,6 @@ var BranchesGitSubProvider = class {
|
|
|
1250
1747
|
];
|
|
1251
1748
|
try {
|
|
1252
1749
|
await this.git.run({ cwd: repoPath }, ...args);
|
|
1253
|
-
this.context.hooks?.cache?.onReset?.(repoPath, "branches");
|
|
1254
|
-
this.context.hooks?.repository?.onChanged?.(repoPath, ["heads"]);
|
|
1255
1750
|
} catch (ex) {
|
|
1256
1751
|
throw getGitCommandError("branch", ex, (reason) => new BranchError({
|
|
1257
1752
|
reason: reason ?? "other",
|
|
@@ -1262,6 +1757,9 @@ var BranchesGitSubProvider = class {
|
|
|
1262
1757
|
args
|
|
1263
1758
|
}
|
|
1264
1759
|
}, ex));
|
|
1760
|
+
} finally {
|
|
1761
|
+
this.context.hooks?.cache?.onReset?.(repoPath, "branches");
|
|
1762
|
+
this.context.hooks?.repository?.onChanged?.(repoPath, ["config", "heads"]);
|
|
1265
1763
|
}
|
|
1266
1764
|
}
|
|
1267
1765
|
async storeBaseBranchName(repoPath, ref, base, options) {
|
|
@@ -1286,22 +1784,14 @@ var BranchesGitSubProvider = class {
|
|
|
1286
1784
|
const configMap = await this.provider.config.getGkConfigRegex(commonPath, "^branch\\..*\\.gk-(last-(accessed|modified)|disposition|agent-last-activity)$");
|
|
1287
1785
|
if (!configMap.size) return metadataMap;
|
|
1288
1786
|
for (const [key, value] of configMap) {
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
const metaKey = keyParts.at(-1);
|
|
1293
|
-
const branchName = keyParts.slice(1, -1).join(".");
|
|
1294
|
-
let metadata = metadataMap.get(branchName);
|
|
1787
|
+
const parsed = parseBranchMetadataGkConfigKey(key);
|
|
1788
|
+
if (parsed == null) continue;
|
|
1789
|
+
let metadata = metadataMap.get(parsed.branch);
|
|
1295
1790
|
if (metadata == null) {
|
|
1296
1791
|
metadata = {};
|
|
1297
|
-
metadataMap.set(
|
|
1298
|
-
}
|
|
1299
|
-
if (metaKey === "gk-last-accessed") metadata.lastAccessedAt = value;
|
|
1300
|
-
else if (metaKey === "gk-last-modified") metadata.lastModifiedAt = value;
|
|
1301
|
-
else if (metaKey === "gk-agent-last-activity") metadata.agentLastActivityAt = value;
|
|
1302
|
-
else if (metaKey === "gk-disposition") {
|
|
1303
|
-
if (value === "starred" || value === "archived") metadata.disposition = value;
|
|
1792
|
+
metadataMap.set(parsed.branch, metadata);
|
|
1304
1793
|
}
|
|
1794
|
+
setBranchMetadataValue(metadata, parsed.key, value);
|
|
1305
1795
|
}
|
|
1306
1796
|
} catch (ex) {
|
|
1307
1797
|
debugger;
|
|
@@ -1310,11 +1800,6 @@ var BranchesGitSubProvider = class {
|
|
|
1310
1800
|
return metadataMap;
|
|
1311
1801
|
});
|
|
1312
1802
|
}
|
|
1313
|
-
async storeBranchMetadata(repoPath, key, ref, date) {
|
|
1314
|
-
const value = await this.provider.config.getGkConfig(repoPath, `branch.${ref}.${key}`);
|
|
1315
|
-
if (value != null && date.getTime() - new Date(value).getTime() < dateMetadataStaleThresholdMs) return;
|
|
1316
|
-
return this.provider.config.setGkConfig(repoPath, `branch.${ref}.${key}`, date.toISOString());
|
|
1317
|
-
}
|
|
1318
1803
|
async setBranchDisposition(repoPath, branchName, disposition) {
|
|
1319
1804
|
await this.provider.config.setGkConfig(repoPath, `branch.${branchName}.gk-disposition`, disposition);
|
|
1320
1805
|
this.context.hooks?.cache?.onReset?.(repoPath, "branches");
|
|
@@ -1327,6 +1812,7 @@ __decorate([debug()], BranchesGitSubProvider.prototype, "getBranchContributionsO
|
|
|
1327
1812
|
__decorate([debug()], BranchesGitSubProvider.prototype, "getBranchesWithCommits", null);
|
|
1328
1813
|
__decorate([debug()], BranchesGitSubProvider.prototype, "getCurrentBranchReference", null);
|
|
1329
1814
|
__decorate([debug({ exit: true })], BranchesGitSubProvider.prototype, "getDefaultBranchName", null);
|
|
1815
|
+
__decorate([debug()], BranchesGitSubProvider.prototype, "setRemoteHeadIfMissing", null);
|
|
1330
1816
|
__decorate([debug()], BranchesGitSubProvider.prototype, "createBranch", null);
|
|
1331
1817
|
__decorate([debug()], BranchesGitSubProvider.prototype, "deleteLocalBranch", null);
|
|
1332
1818
|
__decorate([debug()], BranchesGitSubProvider.prototype, "deleteRemoteBranch", null);
|
|
@@ -1337,22 +1823,32 @@ __decorate([debug()], BranchesGitSubProvider.prototype, "getPotentialMergeConfli
|
|
|
1337
1823
|
__decorate([debug()], BranchesGitSubProvider.prototype, "getPotentialStashReapplyConflicts", null);
|
|
1338
1824
|
__decorate([debug({ exit: true })], BranchesGitSubProvider.prototype, "getBaseBranchName", null);
|
|
1339
1825
|
__decorate([debug({ exit: true })], BranchesGitSubProvider.prototype, "getAheadOfBase", null);
|
|
1826
|
+
__decorate([debug({ exit: true })], BranchesGitSubProvider.prototype, "getAheadBehindCounts", null);
|
|
1827
|
+
__decorate([debug({ exit: true })], BranchesGitSubProvider.prototype, "getMergedBranches", null);
|
|
1340
1828
|
__decorate([debug({ exit: true })], BranchesGitSubProvider.prototype, "getStoredMergeTargetBranchName", null);
|
|
1341
1829
|
__decorate([debug({ exit: true })], BranchesGitSubProvider.prototype, "getStoredDetectedMergeTargetBranchName", null);
|
|
1342
1830
|
__decorate([debug()], BranchesGitSubProvider.prototype, "getStoredUserMergeTargetBranchName", null);
|
|
1343
|
-
__decorate([
|
|
1344
|
-
__decorate([
|
|
1345
|
-
__decorate([
|
|
1831
|
+
__decorate([trace()], BranchesGitSubProvider.prototype, "onCurrentBranchAccessed", null);
|
|
1832
|
+
__decorate([trace()], BranchesGitSubProvider.prototype, "onCurrentBranchModified", null);
|
|
1833
|
+
__decorate([trace()], BranchesGitSubProvider.prototype, "onCurrentBranchAgentActivity", null);
|
|
1346
1834
|
__decorate([debug()], BranchesGitSubProvider.prototype, "renameBranch", null);
|
|
1347
1835
|
__decorate([debug()], BranchesGitSubProvider.prototype, "setUpstreamBranch", null);
|
|
1348
1836
|
__decorate([debug()], BranchesGitSubProvider.prototype, "storeBaseBranchName", null);
|
|
1349
1837
|
__decorate([debug()], BranchesGitSubProvider.prototype, "storeMergeTargetBranchName", null);
|
|
1350
1838
|
__decorate([debug()], BranchesGitSubProvider.prototype, "storeUserMergeTargetBranchName", null);
|
|
1839
|
+
/** Each ref list {@link findRefTip} searched, by full name: one base read looks up many names in the same cached list */
|
|
1840
|
+
const refRecordsByName = /* @__PURE__ */ new WeakMap();
|
|
1351
1841
|
/**
|
|
1352
1842
|
* The commit `name` resolves to among `records`, tried in git's own order for a short name (`refs/tags/` before
|
|
1353
1843
|
* `refs/heads/` before `refs/remotes/`); annotated tags peel to their commit. `undefined` when no listed ref matches.
|
|
1354
1844
|
*/
|
|
1355
1845
|
function findRefTip(records, name) {
|
|
1846
|
+
let byName = refRecordsByName.get(records);
|
|
1847
|
+
if (byName == null) {
|
|
1848
|
+
byName = /* @__PURE__ */ new Map();
|
|
1849
|
+
for (const record of records) if (!byName.has(record.name)) byName.set(record.name, record);
|
|
1850
|
+
refRecordsByName.set(records, byName);
|
|
1851
|
+
}
|
|
1356
1852
|
for (const candidate of [
|
|
1357
1853
|
name,
|
|
1358
1854
|
`refs/${name}`,
|
|
@@ -1361,7 +1857,7 @@ function findRefTip(records, name) {
|
|
|
1361
1857
|
`refs/remotes/${name}`,
|
|
1362
1858
|
`refs/remotes/${name}/HEAD`
|
|
1363
1859
|
]) {
|
|
1364
|
-
const record =
|
|
1860
|
+
const record = byName.get(candidate);
|
|
1365
1861
|
if (record != null) return record.peeledObjectname || record.objectname;
|
|
1366
1862
|
}
|
|
1367
1863
|
}
|