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