@gitkraken/core-gitlens 0.16.3 → 0.16.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +32 -1
- package/dist/git/cache.d.ts +64 -5
- package/dist/git/cache.js +75 -5
- package/dist/git/cache.js.map +1 -1
- package/dist/git/models/branch.d.ts +0 -6
- package/dist/git/models/branch.js.map +1 -1
- package/dist/git/models/issue.d.ts +20 -1
- package/dist/git/models/issue.js +3 -1
- package/dist/git/models/issue.js.map +1 -1
- package/dist/git/providers/branches.d.ts +65 -1
- package/dist/git/providers/refs.d.ts +3 -2
- package/dist/git/providers/shared/remotes.js +5 -5
- package/dist/git/providers/shared/remotes.js.map +1 -1
- package/dist/git/utils/issue.utils.js +5 -1
- package/dist/git/utils/issue.utils.js.map +1 -1
- package/dist/git-cli/providers/branches.d.ts +135 -39
- package/dist/git-cli/providers/branches.js +846 -264
- package/dist/git-cli/providers/branches.js.map +1 -1
- package/dist/git-cli/providers/commits.js +37 -20
- package/dist/git-cli/providers/commits.js.map +1 -1
- package/dist/git-cli/providers/config.js +16 -10
- package/dist/git-cli/providers/config.js.map +1 -1
- package/dist/git-cli/providers/contributors.js +3 -3
- package/dist/git-cli/providers/contributors.js.map +1 -1
- package/dist/git-cli/providers/diff.js +15 -12
- package/dist/git-cli/providers/diff.js.map +1 -1
- package/dist/git-cli/providers/graph.js +17 -14
- package/dist/git-cli/providers/graph.js.map +1 -1
- package/dist/git-cli/providers/maintenance.js +3 -3
- package/dist/git-cli/providers/maintenance.js.map +1 -1
- package/dist/git-cli/providers/patch.js +4 -1
- package/dist/git-cli/providers/patch.js.map +1 -1
- package/dist/git-cli/providers/pausedOperations.js +2 -2
- package/dist/git-cli/providers/pausedOperations.js.map +1 -1
- package/dist/git-cli/providers/refs.js +16 -13
- package/dist/git-cli/providers/refs.js.map +1 -1
- package/dist/git-cli/providers/remotes.js +4 -1
- package/dist/git-cli/providers/remotes.js.map +1 -1
- package/dist/git-cli/providers/revision.js +14 -11
- package/dist/git-cli/providers/revision.js.map +1 -1
- package/dist/git-cli/providers/stash.js +2 -2
- package/dist/git-cli/providers/stash.js.map +1 -1
- package/dist/git-cli/providers/status.js +7 -7
- package/dist/git-cli/providers/status.js.map +1 -1
- package/dist/git-cli/providers/tags.js +6 -3
- package/dist/git-cli/providers/tags.js.map +1 -1
- package/dist/git-cli/providers/worktrees.js +3 -3
- package/dist/git-cli/providers/worktrees.js.map +1 -1
- package/dist/plus/git-github/providers/github/branches.d.ts +9 -1
- package/dist/plus/git-github/providers/github/branches.js +54 -8
- package/dist/plus/git-github/providers/github/branches.js.map +1 -1
- package/dist/plus/integrations/models/gitHostIntegration.d.ts +13 -0
- package/dist/plus/integrations/models/gitHostIntegration.js +20 -3
- package/dist/plus/integrations/models/gitHostIntegration.js.map +1 -1
- package/dist/plus/integrations/models/integration.d.ts +12 -0
- package/dist/plus/integrations/models/integration.js +7 -1
- package/dist/plus/integrations/models/integration.js.map +1 -1
- package/dist/plus/integrations/providers/azure/azure.d.ts +13 -0
- package/dist/plus/integrations/providers/azure/azure.js +11 -3
- package/dist/plus/integrations/providers/azure/azure.js.map +1 -1
- package/dist/plus/integrations/providers/azure/models.d.ts +3 -1
- package/dist/plus/integrations/providers/azure/models.js +11 -5
- package/dist/plus/integrations/providers/azure/models.js.map +1 -1
- package/dist/plus/integrations/providers/azureDevOps.d.ts +33 -5
- package/dist/plus/integrations/providers/azureDevOps.js +75 -11
- package/dist/plus/integrations/providers/azureDevOps.js.map +1 -1
- package/dist/plus/integrations/providers/azureDevOpsServer.js +1 -0
- package/dist/plus/integrations/providers/azureDevOpsServer.js.map +1 -1
- package/dist/plus/integrations/providers/issueSorts.d.ts +1 -0
- package/dist/plus/integrations/providers/models.d.ts +19 -0
- package/dist/plus/integrations/providers/models.js +12 -3
- package/dist/plus/integrations/providers/models.js.map +1 -1
- package/dist/plus/integrations/reads/drains.d.ts +1 -0
- package/dist/plus/integrations/reads/etag.js +6 -1
- package/dist/plus/integrations/reads/etag.js.map +1 -1
- package/dist/utils/decorators/log.d.ts +3 -1
- package/dist/utils/decorators/log.js +61 -45
- package/dist/utils/decorators/log.js.map +1 -1
- package/dist/utils/logger.d.ts +15 -1
- package/dist/utils/logger.js +30 -1
- package/dist/utils/logger.js.map +1 -1
- package/dist/utils/logger.scoped.d.ts +12 -1
- package/dist/utils/logger.scoped.js +38 -4
- package/dist/utils/logger.scoped.js.map +1 -1
- package/package.json +1 -1
|
@@ -69,26 +69,13 @@ function parseAheadBehindField(field) {
|
|
|
69
69
|
function toRefPatterns(refs) {
|
|
70
70
|
return sum(refs, (r) => r.length + 1) < 15e3 ? [...refs] : void 0;
|
|
71
71
|
}
|
|
72
|
-
/**
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
if (reads == null) {
|
|
80
|
-
reads = /* @__PURE__ */ new Map();
|
|
81
|
-
memo.set(records, reads);
|
|
82
|
-
}
|
|
83
|
-
let pending = reads.get(key);
|
|
84
|
-
if (pending == null) {
|
|
85
|
-
pending = read();
|
|
86
|
-
reads.set(key, pending);
|
|
87
|
-
}
|
|
88
|
-
const answer = await pending;
|
|
89
|
-
if (answer === void 0 && reads.get(key) === pending) reads.delete(key);
|
|
90
|
-
return answer;
|
|
91
|
-
}
|
|
72
|
+
/** The stored keys a merge target is read from: the user's pick, the detected target and its legacy key, and the recorded base */
|
|
73
|
+
const storedMergeKeys = [
|
|
74
|
+
"gk-merge-target-user",
|
|
75
|
+
"gk-merge-target",
|
|
76
|
+
"gk-target-base",
|
|
77
|
+
"gk-merge-base"
|
|
78
|
+
];
|
|
92
79
|
var BranchesGitSubProvider = class {
|
|
93
80
|
context;
|
|
94
81
|
git;
|
|
@@ -97,20 +84,22 @@ var BranchesGitSubProvider = class {
|
|
|
97
84
|
/** Ahead/behind counts by `<baseSha>...<tipSha>` range, from the per-tip fallback; a pair of commits always counts the same */
|
|
98
85
|
aheadBehindBySha = new LruMap(4096);
|
|
99
86
|
/**
|
|
100
|
-
* For a base that moved only forward, what its reflog reaches (else `false`), by `<
|
|
101
|
-
*
|
|
102
|
-
*
|
|
87
|
+
* For a base that moved only forward, what its reflog reaches (else `false`), by `<commonPath>\0<ref>`, then by the
|
|
88
|
+
* tip it moved from. A fetch moves one base under every worktree at once, so they share the one check, joining it
|
|
89
|
+
* while it runs. The reads hold for the tip the ref moved to, the reflog they read, stamped by its file's size and
|
|
90
|
+
* mtime (else by the cached ref list, through what it derives so the list itself isn't held, when there's no file
|
|
91
|
+
* to stamp), and the replace refs, which change what's reachable; a change to any of them starts the ref over. Every
|
|
92
|
+
* move appends to the reflog, so a ref moved away and back to the same tip between reads doesn't find the answer
|
|
93
|
+
* taken before
|
|
103
94
|
*/
|
|
104
|
-
baseMovedForward =
|
|
95
|
+
baseMovedForward = new LruMap(64);
|
|
105
96
|
/**
|
|
106
97
|
* For a base that moved, the count of the commits its new tip lacks for each local branch a worktree has checked
|
|
107
|
-
* out, by tip sha,
|
|
108
|
-
*
|
|
109
|
-
*
|
|
110
|
-
* checked-out branches are counted: the shared walk runs until every counted branch meets the new tip, so an
|
|
111
|
-
* unrelated old or orphan branch no worktree uses would make it deeper than any worktree's own count
|
|
98
|
+
* out, by tip sha, by `<commonPath>\0<newTip>`, with the replace refs it was counted under and the tips it counted.
|
|
99
|
+
* The counts don't depend on the ref or where it moved from, so each worktree the move reached finds its HEAD's
|
|
100
|
+
* count in the one read, even one whose previous answer was taken at another old tip
|
|
112
101
|
*/
|
|
113
|
-
aheadOfMovedBase =
|
|
102
|
+
aheadOfMovedBase = new LruMap(64);
|
|
114
103
|
/**
|
|
115
104
|
* When each `commonPath\0branch\0key` was last stamped by this process. Bounded: a dropped entry only costs a
|
|
116
105
|
* read of the stored stamp, which still holds off a write under 5 minutes old
|
|
@@ -124,6 +113,11 @@ var BranchesGitSubProvider = class {
|
|
|
124
113
|
* it once
|
|
125
114
|
*/
|
|
126
115
|
_branchCreatedFrom = new LruMap(1024);
|
|
116
|
+
/**
|
|
117
|
+
* The branch HEAD was on when each `commonPath\0branch` was first checked out, as git wrote it, for a branch
|
|
118
|
+
* created from `HEAD`; only found ones, since a later checkout can write one
|
|
119
|
+
*/
|
|
120
|
+
_branchCheckedOutFrom = new LruMap(1024);
|
|
127
121
|
/** Bumped each time a key's created-from is forgotten, so a reflog read in flight across that doesn't store it */
|
|
128
122
|
_branchCreatedFromGenerations = new LruMap(1024);
|
|
129
123
|
/**
|
|
@@ -361,28 +355,17 @@ var BranchesGitSubProvider = class {
|
|
|
361
355
|
};
|
|
362
356
|
};
|
|
363
357
|
try {
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
if (!missing) {
|
|
376
|
-
mergeTarget = prTarget;
|
|
377
|
-
if (pr.stack == null) this.storeMergeTargetBranchName(repoPath, ref, prTarget, { skipInvalidation: ["branchOverviews"] });
|
|
378
|
-
}
|
|
379
|
-
}
|
|
380
|
-
}
|
|
381
|
-
}
|
|
382
|
-
mergeTarget ??= await this.getBaseBranchName(repoPath, ref, priorityOpts, cancellation);
|
|
383
|
-
mergeTarget ??= await this.getDefaultBranchName(repoPath, void 0, priorityOpts, cancellation);
|
|
384
|
-
if (mergeTarget == null) return void 0;
|
|
385
|
-
const resolvedTarget = mergeTarget;
|
|
358
|
+
const associatedPullRequest = options?.associatedPullRequest;
|
|
359
|
+
const target = await this.getMergeTarget(repoPath, ref, {
|
|
360
|
+
getAssociatedPullRequest: associatedPullRequest != null ? () => associatedPullRequest : void 0,
|
|
361
|
+
inferBase: true,
|
|
362
|
+
defaultBranch: "remote",
|
|
363
|
+
getFallbackDefaultBranch: options?.getFallbackDefaultBranch,
|
|
364
|
+
keepSelfAtTip: true,
|
|
365
|
+
priority
|
|
366
|
+
}, cancellation);
|
|
367
|
+
if (target == null) return void 0;
|
|
368
|
+
const resolvedTarget = target.ref;
|
|
386
369
|
return await this.cache.getBranchOverview(repoPath, `${ref}|${resolvedTarget}`, (_commonPath, _cacheable, signal) => fetchBody(resolvedTarget, signal ?? cancellation), { cancellation });
|
|
387
370
|
} catch (ex) {
|
|
388
371
|
scope?.error(ex);
|
|
@@ -1067,75 +1050,63 @@ var BranchesGitSubProvider = class {
|
|
|
1067
1050
|
update = mergeBase != null;
|
|
1068
1051
|
}
|
|
1069
1052
|
if (mergeBase != null) {
|
|
1070
|
-
const
|
|
1071
|
-
if (
|
|
1072
|
-
if (
|
|
1073
|
-
|
|
1053
|
+
const { name, missing } = await this.provider.refs.getSymbolicReferenceNameOrMissing(commonPath, mergeBase, priorityOpts, signal);
|
|
1054
|
+
if (!missing) {
|
|
1055
|
+
if (!name) {
|
|
1056
|
+
cacheable.invalidate();
|
|
1057
|
+
return mergeBase;
|
|
1058
|
+
}
|
|
1059
|
+
if (update) this.storeBaseBranchName(commonPath, ref, name, { skipInvalidation: ["branchOverviews", "baseBranchName"] });
|
|
1060
|
+
return name;
|
|
1074
1061
|
}
|
|
1075
1062
|
}
|
|
1076
1063
|
} catch {}
|
|
1077
|
-
const reflog = await this.getBaseBranchFromReflog(commonPath, ref,
|
|
1078
|
-
|
|
1079
|
-
priority
|
|
1080
|
-
}, signal);
|
|
1081
|
-
if (reflog.branch != null) {
|
|
1082
|
-
this.storeBaseBranchName(commonPath, ref, reflog.branch, { skipInvalidation: ["branchOverviews", "baseBranchName"] });
|
|
1083
|
-
return reflog.branch;
|
|
1084
|
-
}
|
|
1064
|
+
const reflog = await this.getBaseBranchFromReflog(repoPath, commonPath, ref, priority, signal);
|
|
1065
|
+
if (reflog.branch != null) return reflog.branch;
|
|
1085
1066
|
if (reflog.failed) cacheable.invalidate();
|
|
1086
1067
|
}, cancellation);
|
|
1087
1068
|
}
|
|
1088
1069
|
/**
|
|
1089
|
-
* `
|
|
1090
|
-
*
|
|
1070
|
+
* The branch `ref` was created from, else the branch HEAD was on when it was first checked out, each as its
|
|
1071
|
+
* upstream when it has one. `failed` separates "the reflog ran and had no answer" from "the read never happened" —
|
|
1072
|
+
* the caller caches the former forever and must not cache the latter.
|
|
1091
1073
|
*/
|
|
1092
|
-
async getBaseBranchFromReflog(repoPath, ref,
|
|
1093
|
-
const priority = options?.priority;
|
|
1074
|
+
async getBaseBranchFromReflog(repoPath, commonPath, ref, priority, cancellation) {
|
|
1094
1075
|
const priorityOpts = priority != null ? { priority } : void 0;
|
|
1095
1076
|
try {
|
|
1096
|
-
const createdFrom = await this.getReflogCreatedFrom(
|
|
1077
|
+
const createdFrom = await this.getReflogCreatedFrom(commonPath, ref, priority, cancellation);
|
|
1097
1078
|
if (createdFrom == null) return {
|
|
1098
1079
|
branch: void 0,
|
|
1099
1080
|
failed: false
|
|
1100
1081
|
};
|
|
1101
1082
|
if (createdFrom !== "HEAD") {
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
}
|
|
1109
|
-
const name = await this.provider.refs.getSymbolicReferenceName(repoPath, createdFrom, priorityOpts);
|
|
1083
|
+
const upstream = await this.getUpstreamName(commonPath, createdFrom, priorityOpts, cancellation);
|
|
1084
|
+
if (upstream) return {
|
|
1085
|
+
branch: upstream,
|
|
1086
|
+
failed: false
|
|
1087
|
+
};
|
|
1088
|
+
const name = await this.provider.refs.getSymbolicReferenceName(commonPath, createdFrom, priorityOpts);
|
|
1110
1089
|
if (name) return {
|
|
1111
1090
|
branch: name,
|
|
1112
1091
|
failed: false
|
|
1113
1092
|
};
|
|
1114
1093
|
}
|
|
1115
|
-
const
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
}, cancellation);
|
|
1119
|
-
if (!entries.length) return {
|
|
1094
|
+
const branch = ref.replace(/^refs\/heads\//, "");
|
|
1095
|
+
const source = await this.getCheckoutSource(await this.getCheckoutPath(repoPath, branch, cancellation), branch, priority, cancellation);
|
|
1096
|
+
if (source == null) return {
|
|
1120
1097
|
branch: void 0,
|
|
1121
1098
|
failed: false
|
|
1122
1099
|
};
|
|
1123
|
-
const
|
|
1124
|
-
if (
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
name = await this.provider.refs.getSymbolicReferenceName(repoPath, name, priorityOpts);
|
|
1134
|
-
if (name) return {
|
|
1135
|
-
branch: name,
|
|
1136
|
-
failed: false
|
|
1137
|
-
};
|
|
1138
|
-
}
|
|
1100
|
+
const upstream = await this.getUpstreamName(commonPath, source, priorityOpts, cancellation);
|
|
1101
|
+
if (upstream) return {
|
|
1102
|
+
branch: upstream,
|
|
1103
|
+
failed: false
|
|
1104
|
+
};
|
|
1105
|
+
const name = await this.provider.refs.getSymbolicReferenceName(commonPath, source, priorityOpts);
|
|
1106
|
+
if (name) return {
|
|
1107
|
+
branch: name,
|
|
1108
|
+
failed: false
|
|
1109
|
+
};
|
|
1139
1110
|
} catch (ex) {
|
|
1140
1111
|
if (isCancellationError(ex)) throw ex;
|
|
1141
1112
|
const msg = ex?.toString() ?? "";
|
|
@@ -1168,12 +1139,36 @@ var BranchesGitSubProvider = class {
|
|
|
1168
1139
|
return entries[0].message.match(/branch: Created from (.*)$/)?.[1];
|
|
1169
1140
|
}
|
|
1170
1141
|
/**
|
|
1171
|
-
* The
|
|
1172
|
-
*
|
|
1173
|
-
* copy on a remote (`git switch <branch>` creates it from `<remote>/<branch>`). Never the checkout history in
|
|
1174
|
-
* HEAD's reflog, which on a long-lived checkout names whatever was checked out last.
|
|
1142
|
+
* The worktree `branch` is checked out in, whose HEAD reflog holds its checkouts; else the main worktree, never the
|
|
1143
|
+
* caller's, since every worktree of the repository shares the answer
|
|
1175
1144
|
*/
|
|
1176
|
-
async
|
|
1145
|
+
async getCheckoutPath(repoPath, branch, cancellation) {
|
|
1146
|
+
const worktreePath = findRefRecord(await this.provider.refs.getRefs(repoPath, cancellation), `refs/heads/${branch}`)?.worktreePath;
|
|
1147
|
+
return worktreePath ? normalizePath(worktreePath) : this.cache.getCommonPath(repoPath);
|
|
1148
|
+
}
|
|
1149
|
+
/**
|
|
1150
|
+
* The branch HEAD was on when `branch` was first checked out at `repoPath` (the oldest `checkout: moving from
|
|
1151
|
+
* <name> to <branch>` in its HEAD reflog), as git wrote it. `undefined` when there is no such entry. Rejects when the
|
|
1152
|
+
* reflog can't be read.
|
|
1153
|
+
*/
|
|
1154
|
+
async getCheckoutSource(repoPath, branch, priority, cancellation) {
|
|
1155
|
+
const entries = await this.provider.refs.getReflogEntries(repoPath, "HEAD", {
|
|
1156
|
+
grep: `checkout: moving from .* to ${toReflogGrepLiteral(branch)}$`,
|
|
1157
|
+
priority
|
|
1158
|
+
}, cancellation);
|
|
1159
|
+
for (let i = entries.length - 1; i >= 0; i--) {
|
|
1160
|
+
const match = /^checkout: moving from (\S+) to (.+)$/.exec(entries[i].message);
|
|
1161
|
+
if (match?.[2] === branch) return match[1];
|
|
1162
|
+
}
|
|
1163
|
+
}
|
|
1164
|
+
/**
|
|
1165
|
+
* The branch `branch` was created from, as a base for {@link resolveBase}: the name its own reflog gives, when that
|
|
1166
|
+
* is a local or remote-tracking branch in the cached ref list and not `branch` itself or its copy on a remote
|
|
1167
|
+
* (`git switch <branch>` creates it from `<remote>/<branch>`). Never the checkout history in HEAD's reflog, which
|
|
1168
|
+
* on a long-lived checkout names whatever was checked out last. `fromHead` when the reflog says it was created
|
|
1169
|
+
* from `HEAD`.
|
|
1170
|
+
*/
|
|
1171
|
+
async getCreatedFromTarget(repoPath, branch, reads, cancellation) {
|
|
1177
1172
|
const key = this.getBranchActivityKey(repoPath, branch);
|
|
1178
1173
|
let name;
|
|
1179
1174
|
if (this._branchCreatedFrom.has(key)) name = this._branchCreatedFrom.get(key);
|
|
@@ -1183,20 +1178,73 @@ var BranchesGitSubProvider = class {
|
|
|
1183
1178
|
name = await this.getReflogCreatedFrom(repoPath, `refs/heads/${branch}`, void 0, cancellation);
|
|
1184
1179
|
} catch (ex) {
|
|
1185
1180
|
if (isCancellationError(ex)) throw ex;
|
|
1186
|
-
|
|
1181
|
+
reads.pinnable = false;
|
|
1182
|
+
return {
|
|
1183
|
+
name: void 0,
|
|
1184
|
+
fromHead: false
|
|
1185
|
+
};
|
|
1187
1186
|
}
|
|
1188
1187
|
if (cancellation?.aborted) throw new CancellationError();
|
|
1189
1188
|
if (this._branchCreatedFromGenerations.get(key) === generation) this._branchCreatedFrom.set(key, name);
|
|
1190
1189
|
}
|
|
1190
|
+
if (name === "HEAD") return {
|
|
1191
|
+
name: void 0,
|
|
1192
|
+
fromHead: true
|
|
1193
|
+
};
|
|
1194
|
+
return {
|
|
1195
|
+
name: await this.getListedBaseName(repoPath, name, branch, reads, cancellation),
|
|
1196
|
+
fromHead: false
|
|
1197
|
+
};
|
|
1198
|
+
}
|
|
1199
|
+
/**
|
|
1200
|
+
* For a branch its reflog says was created from `HEAD`, the branch HEAD was on when it was first checked out (see
|
|
1201
|
+
* {@link getCheckoutSource}), read in the worktree it is checked out in, else the main worktree, and kept as a base
|
|
1202
|
+
* like {@link getCreatedFromTarget}'s. Remembered once found, as the created-from name is; a reflog with no such
|
|
1203
|
+
* entry yet marks the answer for eviction on the next checkout, which may write one.
|
|
1204
|
+
*/
|
|
1205
|
+
async getInferredCreatedFrom(repoPath, branch, reads, cancellation) {
|
|
1206
|
+
const key = this.getBranchActivityKey(repoPath, branch);
|
|
1207
|
+
let source = this._branchCheckedOutFrom.get(key);
|
|
1208
|
+
if (source == null) {
|
|
1209
|
+
const generation = this._branchCreatedFromGenerations.get(key);
|
|
1210
|
+
try {
|
|
1211
|
+
source = await this.getCheckoutSource(await this.getCheckoutPath(repoPath, branch, cancellation), branch, reads.priority, cancellation);
|
|
1212
|
+
} catch (ex) {
|
|
1213
|
+
if (isCancellationError(ex)) throw ex;
|
|
1214
|
+
reads.pinnable = false;
|
|
1215
|
+
return;
|
|
1216
|
+
}
|
|
1217
|
+
if (cancellation?.aborted) throw new CancellationError();
|
|
1218
|
+
if (source == null) {
|
|
1219
|
+
reads.evictOnCheckout = true;
|
|
1220
|
+
return;
|
|
1221
|
+
}
|
|
1222
|
+
if (this._branchCreatedFromGenerations.get(key) === generation) this._branchCheckedOutFrom.set(key, source);
|
|
1223
|
+
}
|
|
1224
|
+
return this.getListedBaseName(repoPath, source, branch, reads, cancellation);
|
|
1225
|
+
}
|
|
1226
|
+
/**
|
|
1227
|
+
* `name` as a base for `branch`: only a local or remote-tracking branch in the cached ref list — a sha, a tag or a
|
|
1228
|
+
* revision expression (`main~2`) names no `refs/heads/` or `refs/remotes/` ref — and not `branch` itself or its
|
|
1229
|
+
* copy on a remote.
|
|
1230
|
+
*/
|
|
1231
|
+
async getListedBaseName(repoPath, name, branch, reads, cancellation) {
|
|
1191
1232
|
if (!name || name === "HEAD" || name.startsWith("-")) return void 0;
|
|
1192
1233
|
const records = await this.provider.refs.getRefs(repoPath, cancellation);
|
|
1193
1234
|
const fullNames = name.startsWith("refs/heads/") || name.startsWith("refs/remotes/") ? [name] : [`refs/heads/${name}`, `refs/remotes/${name}`];
|
|
1194
1235
|
let listed = false;
|
|
1195
|
-
for (const fullName of fullNames)
|
|
1196
|
-
|
|
1197
|
-
|
|
1236
|
+
for (const fullName of fullNames) {
|
|
1237
|
+
reads.tipRefs.add(fullName);
|
|
1238
|
+
if (await this.getRefTip(repoPath, records, fullName, cancellation) != null) {
|
|
1239
|
+
listed = true;
|
|
1240
|
+
break;
|
|
1241
|
+
}
|
|
1198
1242
|
}
|
|
1199
1243
|
if (!listed) return void 0;
|
|
1244
|
+
if (fullNames.length === 1 && records.length) {
|
|
1245
|
+
const short = name.slice(name.startsWith("refs/heads/") ? 11 : 13);
|
|
1246
|
+
if (resolveRefRecord(records, short)?.name === name) name = short;
|
|
1247
|
+
}
|
|
1200
1248
|
if (await this.isSelfRef(repoPath, name, branch, cancellation)) return void 0;
|
|
1201
1249
|
return name;
|
|
1202
1250
|
}
|
|
@@ -1247,14 +1295,30 @@ var BranchesGitSubProvider = class {
|
|
|
1247
1295
|
}
|
|
1248
1296
|
async getAheadOfBase(repoPath, branch, options, cancellation) {
|
|
1249
1297
|
if (branch != null && isDetachedHead(branch)) branch = void 0;
|
|
1250
|
-
const
|
|
1251
|
-
const
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
if (
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1298
|
+
const reads = createBaseReads("HEAD", branch != null ? [`refs/heads/${branch}`] : [], void 0);
|
|
1299
|
+
const { dependsOn } = reads;
|
|
1300
|
+
const resolved = await this.resolveBase(repoPath, branch, branch, false, options, reads, cancellation);
|
|
1301
|
+
if (resolved.kind === "upstream") return withDefaultBranch(await this.measureAheadOfSelfUpstream(repoPath, resolved.upstream, reads, cancellation), reads.localDefault);
|
|
1302
|
+
if (resolved.kind === "none") {
|
|
1303
|
+
let unborn = false;
|
|
1304
|
+
if (branch != null) try {
|
|
1305
|
+
unborn = findRefTip(await this.provider.refs.getRefs(repoPath, cancellation), `refs/heads/${branch}`) == null && await this.isUnbornHead(repoPath, cancellation);
|
|
1306
|
+
} catch (ex) {
|
|
1307
|
+
if (isCancellationError(ex)) throw ex;
|
|
1308
|
+
}
|
|
1309
|
+
return withDefaultBranch({
|
|
1310
|
+
base: void 0,
|
|
1311
|
+
reason: unborn ? "unborn" : "none",
|
|
1312
|
+
dependsOn: [...dependsOn]
|
|
1313
|
+
}, reads.localDefault);
|
|
1314
|
+
}
|
|
1315
|
+
if (resolved.tier !== "preferred" && branch != null && await this.isSelfAtTip(repoPath, resolved.target.ref, branch, reads, cancellation)) return withDefaultBranch({
|
|
1316
|
+
base: void 0,
|
|
1317
|
+
reason: "self",
|
|
1318
|
+
dependsOn: [...dependsOn]
|
|
1319
|
+
}, reads.localDefault);
|
|
1320
|
+
const tier = resolved.tier;
|
|
1321
|
+
return withDefaultBranch(await this.measureAheadOfBase(repoPath, resolved.target, tier, dependsOn, cancellation), reads.localDefault);
|
|
1258
1322
|
}
|
|
1259
1323
|
async getAheadOfBaseAfterBaseMove(repoPath, branch, previous, moved, cancellation) {
|
|
1260
1324
|
const { head, base } = previous;
|
|
@@ -1291,7 +1355,7 @@ var BranchesGitSubProvider = class {
|
|
|
1291
1355
|
if (cancellation?.aborted) throw new CancellationError();
|
|
1292
1356
|
if (!movedForward) return void 0;
|
|
1293
1357
|
const { offHistory, logged } = movedForward;
|
|
1294
|
-
const aheadOfNewTip =
|
|
1358
|
+
const aheadOfNewTip = await this.getAheadOfMovedBase(repoPath, records, newTip, head);
|
|
1295
1359
|
if (cancellation?.aborted) throw new CancellationError();
|
|
1296
1360
|
if (aheadOfNewTip != null && aheadOfNewTip !== base.ahead) return void 0;
|
|
1297
1361
|
const forkPoint = base.forkPoint;
|
|
@@ -1347,7 +1411,56 @@ var BranchesGitSubProvider = class {
|
|
|
1347
1411
|
* or git can't say.
|
|
1348
1412
|
*/
|
|
1349
1413
|
async getMovedForward(repoPath, records, ref, oldTip, newTip) {
|
|
1350
|
-
|
|
1414
|
+
const commonPath = this.cache.getCommonPath(repoPath);
|
|
1415
|
+
const key = `${commonPath}\0${ref}`;
|
|
1416
|
+
const inputs = getBaseMoveReadInputs(records);
|
|
1417
|
+
const reflog = await this.getReflogStamp(repoPath, ref) ?? inputs;
|
|
1418
|
+
const { replaced } = inputs;
|
|
1419
|
+
let slot = this.baseMovedForward.get(key);
|
|
1420
|
+
if (slot == null || slot.newTip !== newTip || slot.reflog !== reflog || slot.replaced !== replaced) {
|
|
1421
|
+
slot = {
|
|
1422
|
+
newTip,
|
|
1423
|
+
reflog,
|
|
1424
|
+
replaced,
|
|
1425
|
+
reads: /* @__PURE__ */ new Map()
|
|
1426
|
+
};
|
|
1427
|
+
this.baseMovedForward.set(key, slot);
|
|
1428
|
+
} else this.baseMovedForward.touch(key);
|
|
1429
|
+
const { reads } = slot;
|
|
1430
|
+
let pending = reads.get(oldTip);
|
|
1431
|
+
if (pending == null) {
|
|
1432
|
+
pending = this.readMovedForward(commonPath, ref, oldTip, newTip);
|
|
1433
|
+
reads.set(oldTip, pending);
|
|
1434
|
+
}
|
|
1435
|
+
const movedForward = await pending;
|
|
1436
|
+
if (movedForward === void 0 && reads.get(oldTip) === pending) reads.delete(oldTip);
|
|
1437
|
+
return movedForward || void 0;
|
|
1438
|
+
}
|
|
1439
|
+
/**
|
|
1440
|
+
* A stamp of `ref`'s reflog file in the common git directory, by its size and mtime; `undefined` when there's no
|
|
1441
|
+
* file to stat (a reftable repository, or a ref without a reflog)
|
|
1442
|
+
*/
|
|
1443
|
+
async getReflogStamp(repoPath, ref) {
|
|
1444
|
+
try {
|
|
1445
|
+
const gitDir = await this.provider.config.getGitDir(repoPath);
|
|
1446
|
+
const stat = await this.context.fs.stat(joinUriPath(gitDir.commonUri ?? gitDir.uri, "logs", ref));
|
|
1447
|
+
return stat != null ? `${stat.size}\0${stat.mtime}` : void 0;
|
|
1448
|
+
} catch {
|
|
1449
|
+
return;
|
|
1450
|
+
}
|
|
1451
|
+
}
|
|
1452
|
+
/**
|
|
1453
|
+
* A stamp of the common git directory's `shallow` file, by its size and mtime; `null` when the repository isn't
|
|
1454
|
+
* shallow, `undefined` when that can't be read
|
|
1455
|
+
*/
|
|
1456
|
+
async getShallowStamp(repoPath) {
|
|
1457
|
+
try {
|
|
1458
|
+
const gitDir = await this.provider.config.getGitDir(repoPath);
|
|
1459
|
+
const stat = await this.context.fs.stat(joinUriPath(gitDir.commonUri ?? gitDir.uri, "shallow"));
|
|
1460
|
+
return stat != null ? `${stat.size}\0${stat.mtime}` : null;
|
|
1461
|
+
} catch {
|
|
1462
|
+
return;
|
|
1463
|
+
}
|
|
1351
1464
|
}
|
|
1352
1465
|
/**
|
|
1353
1466
|
* Reads {@link getMovedForward} from git, in `commonPath`, which holds the refs, their reflogs and the objects for
|
|
@@ -1401,31 +1514,43 @@ var BranchesGitSubProvider = class {
|
|
|
1401
1514
|
}
|
|
1402
1515
|
}
|
|
1403
1516
|
/**
|
|
1404
|
-
* For a base moved to `newTip`,
|
|
1405
|
-
*
|
|
1406
|
-
* its count too. Only those branches are counted: the one walk runs until every counted branch
|
|
1407
|
-
* unrelated old or orphan branch would make it deeper than any worktree's own count
|
|
1408
|
-
*
|
|
1409
|
-
*
|
|
1410
|
-
*
|
|
1517
|
+
* For a base moved to `newTip`, the count of the commits `newTip` lacks for `head`, from one count of each local
|
|
1518
|
+
* branch a worktree has checked out in `records` (the cached ref list), by tip sha, so a detached HEAD at one of
|
|
1519
|
+
* those tips finds its count too. Only those branches are counted: the one walk runs until every counted branch
|
|
1520
|
+
* meets `newTip`, so an unrelated old or orphan branch would make it deeper than any worktree's own count. Which
|
|
1521
|
+
* branches it counts only bounds that walk, since a commit's count past `newTip` doesn't depend on what else is
|
|
1522
|
+
* counted, so a read that counted `head` answers it for a later list (one with a branch created at `newTip` since,
|
|
1523
|
+
* say) while the replace refs match; a `head` it didn't count starts a read over the current list's checked-out
|
|
1524
|
+
* branches. `undefined` when no checked-out branch is at `head` or `records` can't tell which are checked out, git
|
|
1525
|
+
* can't count them in one pass (before 2.41), or git couldn't say.
|
|
1411
1526
|
*/
|
|
1412
|
-
getAheadOfMovedBase(repoPath, records, newTip) {
|
|
1413
|
-
|
|
1527
|
+
async getAheadOfMovedBase(repoPath, records, newTip, head) {
|
|
1528
|
+
const { checkedOut, checkedOutTips, replaced } = getBaseMoveReadInputs(records);
|
|
1529
|
+
if (!checkedOut?.size || !checkedOutTips.has(head)) return void 0;
|
|
1530
|
+
const commonPath = this.cache.getCommonPath(repoPath);
|
|
1531
|
+
const key = `${commonPath}\0${newTip}`;
|
|
1532
|
+
let slot = this.aheadOfMovedBase.get(key);
|
|
1533
|
+
if (slot == null || slot.replaced !== replaced || !slot.tips.has(head)) {
|
|
1534
|
+
slot = {
|
|
1535
|
+
replaced,
|
|
1536
|
+
tips: checkedOutTips,
|
|
1537
|
+
read: this.readAheadOfMovedBase(commonPath, checkedOut, newTip)
|
|
1538
|
+
};
|
|
1539
|
+
this.aheadOfMovedBase.set(key, slot);
|
|
1540
|
+
} else this.aheadOfMovedBase.touch(key);
|
|
1541
|
+
const { read } = slot;
|
|
1542
|
+
const aheadByTip = await read;
|
|
1543
|
+
if (aheadByTip === void 0 && this.aheadOfMovedBase.get(key)?.read === read) this.aheadOfMovedBase.delete(key);
|
|
1544
|
+
return aheadByTip?.get(head);
|
|
1414
1545
|
}
|
|
1415
1546
|
/**
|
|
1416
|
-
* Reads {@link getAheadOfMovedBase} from git, in `commonPath`, which holds every
|
|
1417
|
-
* one caller's cancellation, since every worktree the move reached rides this one
|
|
1547
|
+
* Reads {@link getAheadOfMovedBase} for the `checkedOut` branches from git, in `commonPath`, which holds every
|
|
1548
|
+
* worktree's branches. Not bound to one caller's cancellation, since every worktree the move reached rides this one
|
|
1549
|
+
* read.
|
|
1418
1550
|
*/
|
|
1419
|
-
async readAheadOfMovedBase(commonPath,
|
|
1551
|
+
async readAheadOfMovedBase(commonPath, checkedOut, newTip) {
|
|
1420
1552
|
try {
|
|
1421
1553
|
if (!await this.git.supports("git:for-each-ref:ahead-behind")) return void 0;
|
|
1422
|
-
const checkedOut = /* @__PURE__ */ new Set();
|
|
1423
|
-
for (const record of records) {
|
|
1424
|
-
if (!record.name.startsWith("refs/heads/")) continue;
|
|
1425
|
-
if (record.worktreePath == null) return void 0;
|
|
1426
|
-
if (record.worktreePath) checkedOut.add(record.name);
|
|
1427
|
-
}
|
|
1428
|
-
if (!checkedOut.size) return void 0;
|
|
1429
1554
|
const patterns = toRefPatterns(checkedOut) ?? ["refs/heads"];
|
|
1430
1555
|
const result = await this.git.run({
|
|
1431
1556
|
cwd: commonPath,
|
|
@@ -1448,60 +1573,376 @@ var BranchesGitSubProvider = class {
|
|
|
1448
1573
|
return;
|
|
1449
1574
|
}
|
|
1450
1575
|
}
|
|
1576
|
+
async getMergeTarget(repoPath, branch, options, cancellation) {
|
|
1577
|
+
if (!branch || isDetachedHead(branch)) return void 0;
|
|
1578
|
+
const cacheKey = getMergeTargetCacheKey(branch, options);
|
|
1579
|
+
let resolved = false;
|
|
1580
|
+
const factory = async (_commonPath, cacheable, signal) => {
|
|
1581
|
+
resolved = true;
|
|
1582
|
+
const { entry, pinnable } = await this.resolveMergeTarget(repoPath, branch, options, signal ?? cancellation);
|
|
1583
|
+
if (!pinnable) cacheable.invalidate();
|
|
1584
|
+
return entry;
|
|
1585
|
+
};
|
|
1586
|
+
let entry = await this.cache.getMergeTarget(repoPath, cacheKey, factory, cancellation);
|
|
1587
|
+
if (!resolved && !await this.isMergeTargetCurrent(repoPath, branch, entry, options, cancellation)) {
|
|
1588
|
+
this.cache.deleteMergeTarget(repoPath, cacheKey, entry);
|
|
1589
|
+
entry = await this.cache.getMergeTarget(repoPath, cacheKey, factory, cancellation);
|
|
1590
|
+
}
|
|
1591
|
+
if (cancellation?.aborted) throw new CancellationError();
|
|
1592
|
+
return entry.selfAtTip && !options?.keepSelfAtTip ? void 0 : entry.result;
|
|
1593
|
+
}
|
|
1451
1594
|
/**
|
|
1452
|
-
*
|
|
1453
|
-
*
|
|
1595
|
+
* Where {@link getMergeTarget} measures `branch` from: its local branch's ref, or — for a remote-tracking branch given
|
|
1596
|
+
* by its short name (`origin/feature`) — that ref, with `selfName` its name on the remote
|
|
1454
1597
|
*/
|
|
1455
|
-
async
|
|
1598
|
+
async getMergeTargetSource(repoPath, branch, records, cancellation) {
|
|
1599
|
+
if (findRefRecord(records, `refs/heads/${branch}`) != null || findRefRecord(records, `refs/remotes/${branch}`) == null) return {
|
|
1600
|
+
from: `refs/heads/${branch}`,
|
|
1601
|
+
selfName: branch,
|
|
1602
|
+
remoteBranch: false
|
|
1603
|
+
};
|
|
1604
|
+
const split = splitRemoteBranchName(branch, await this.getRemoteNames(repoPath, cancellation));
|
|
1605
|
+
return {
|
|
1606
|
+
from: `refs/remotes/${branch}`,
|
|
1607
|
+
selfName: split?.[1] ?? branch,
|
|
1608
|
+
remoteBranch: true
|
|
1609
|
+
};
|
|
1610
|
+
}
|
|
1611
|
+
/** Resolves {@link getMergeTarget}'s answer with what it was read from; `pinnable` is false when it can't be confirmed later */
|
|
1612
|
+
async resolveMergeTarget(repoPath, branch, options, cancellation) {
|
|
1613
|
+
const [records, configKey, shallow] = await Promise.all([
|
|
1614
|
+
this.provider.refs.getRefs(repoPath, cancellation),
|
|
1615
|
+
tolerateRead(() => this.getAheadOfBaseConfigKey(repoPath, cancellation)),
|
|
1616
|
+
this.getShallowStamp(repoPath)
|
|
1617
|
+
]);
|
|
1618
|
+
const { replaced } = getBaseMoveReadInputs(records);
|
|
1619
|
+
const { from, selfName, remoteBranch } = await this.getMergeTargetSource(repoPath, branch, records, cancellation);
|
|
1620
|
+
if (records.length && getListedTip(records, from) == null) return {
|
|
1621
|
+
entry: {
|
|
1622
|
+
result: void 0,
|
|
1623
|
+
selfAtTip: false,
|
|
1624
|
+
tips: [[from, void 0], [`refs/remotes/${branch}`, void 0]],
|
|
1625
|
+
replaced,
|
|
1626
|
+
shallow: shallow ?? null,
|
|
1627
|
+
configKey,
|
|
1628
|
+
stored: void 0,
|
|
1629
|
+
evictOnCheckout: false
|
|
1630
|
+
},
|
|
1631
|
+
pinnable: configKey != null && shallow !== void 0
|
|
1632
|
+
};
|
|
1633
|
+
const reads = createBaseReads(from, [from], options?.priority);
|
|
1634
|
+
reads.pinnable = records.length !== 0 && configKey != null && shallow !== void 0;
|
|
1635
|
+
const resolved = await this.resolveBase(repoPath, branch, selfName, remoteBranch, options, reads, cancellation);
|
|
1636
|
+
let result;
|
|
1637
|
+
let selfAtTip = false;
|
|
1638
|
+
if (resolved.kind === "upstream") {
|
|
1639
|
+
const upstream = resolved.upstream;
|
|
1640
|
+
if (upstream != null) {
|
|
1641
|
+
selfAtTip = upstream.sha === await this.getRefTip(repoPath, records, from, cancellation);
|
|
1642
|
+
result = {
|
|
1643
|
+
ref: upstream.ref,
|
|
1644
|
+
forkPoint: selfAtTip ? upstream.sha : await this.getForkPoint(repoPath, upstream.ref, reads, cancellation),
|
|
1645
|
+
tier: "upstream"
|
|
1646
|
+
};
|
|
1647
|
+
}
|
|
1648
|
+
} else if (resolved.kind === "target") {
|
|
1649
|
+
result = {
|
|
1650
|
+
ref: resolved.target.ref,
|
|
1651
|
+
forkPoint: resolved.target.forkPoint,
|
|
1652
|
+
tier: resolved.tier
|
|
1653
|
+
};
|
|
1654
|
+
selfAtTip = resolved.tier !== "preferred" && await this.isSelfAtTip(repoPath, result.ref, selfName, reads, cancellation);
|
|
1655
|
+
}
|
|
1656
|
+
if (cancellation?.aborted) throw new CancellationError();
|
|
1657
|
+
const tips = [];
|
|
1658
|
+
for (const ref of /* @__PURE__ */ new Set([...reads.dependsOn, ...reads.tipRefs])) tips.push([ref, getListedTip(records, ref)]);
|
|
1659
|
+
return {
|
|
1660
|
+
entry: {
|
|
1661
|
+
result,
|
|
1662
|
+
selfAtTip,
|
|
1663
|
+
tips,
|
|
1664
|
+
replaced,
|
|
1665
|
+
shallow: shallow ?? null,
|
|
1666
|
+
configKey,
|
|
1667
|
+
stored: reads.stored,
|
|
1668
|
+
pullRequest: reads.pullRequest,
|
|
1669
|
+
localDefault: reads.localDefault,
|
|
1670
|
+
remoteDefault: reads.remoteDefault,
|
|
1671
|
+
fallbackDefault: reads.fallbackDefault,
|
|
1672
|
+
evictOnCheckout: reads.evictOnCheckout
|
|
1673
|
+
},
|
|
1674
|
+
pinnable: reads.pinnable
|
|
1675
|
+
};
|
|
1676
|
+
}
|
|
1677
|
+
/** Whether everything `entry` was read from still reads the same; while nothing changed, every read here is cached */
|
|
1678
|
+
async isMergeTargetCurrent(repoPath, branch, entry, options, cancellation) {
|
|
1679
|
+
const records = await this.provider.refs.getRefs(repoPath, cancellation);
|
|
1680
|
+
if (!records.length) return false;
|
|
1681
|
+
for (const [ref, tip] of entry.tips) if (getListedTip(records, ref) !== tip) return false;
|
|
1682
|
+
if (getBaseMoveReadInputs(records).replaced !== entry.replaced) return false;
|
|
1683
|
+
if (entry.shallow != null && await this.getShallowStamp(repoPath) !== entry.shallow) return false;
|
|
1684
|
+
if (await tolerateRead(() => this.getAheadOfBaseConfigKey(repoPath, cancellation)) !== entry.configKey) return false;
|
|
1685
|
+
if (entry.stored != null) {
|
|
1686
|
+
if ((await this.readStoredMergeKeys(repoPath, branch, options?.inferBase, void 0)).some((value, i) => value !== entry.stored[i])) return false;
|
|
1687
|
+
}
|
|
1688
|
+
if (entry.localDefault !== void 0) {
|
|
1689
|
+
if ((await this.getLocalDefaultBranchName(repoPath, /* @__PURE__ */ new Set(), cancellation) ?? null) !== entry.localDefault) return false;
|
|
1690
|
+
}
|
|
1691
|
+
if (entry.remoteDefault != null) {
|
|
1692
|
+
const cached = this.cache.peekDefaultBranchName(repoPath, entry.remoteDefault.remote);
|
|
1693
|
+
if (cached == null) return false;
|
|
1694
|
+
if ((await tolerateRead(() => cached) ?? null) !== entry.remoteDefault.name) return false;
|
|
1695
|
+
}
|
|
1696
|
+
if (entry.fallbackDefault !== void 0) {
|
|
1697
|
+
if ((((await tolerateRead(options?.getFallbackDefaultBranch))?.trim() || void 0) ?? null) !== entry.fallbackDefault) return false;
|
|
1698
|
+
}
|
|
1699
|
+
if (entry.pullRequest !== void 0) {
|
|
1700
|
+
const getAssociatedPullRequest = options?.getAssociatedPullRequest;
|
|
1701
|
+
if (getAssociatedPullRequest == null) return false;
|
|
1702
|
+
if (((await this.getPullRequestBaseCandidate(repoPath, branch, getAssociatedPullRequest, void 0, cancellation))?.ref ?? null) !== entry.pullRequest) return false;
|
|
1703
|
+
}
|
|
1704
|
+
return true;
|
|
1705
|
+
}
|
|
1706
|
+
/**
|
|
1707
|
+
* The base `branch` is measured against from `reads.from`, in {@link getMergeTarget}'s order; `getAheadOfBase`'s
|
|
1708
|
+
* options never reach the pull request, the reflog guess or the remote. `selfName` is the name a self check
|
|
1709
|
+
* compares with: `branch`, or a remote-tracking branch's name on its remote, which has no stored targets, reflog or
|
|
1710
|
+
* upstream of its own. A `'preferred'` target is already known not to be the branch itself at `from`'s commit; any
|
|
1711
|
+
* other target isn't checked here.
|
|
1712
|
+
*/
|
|
1713
|
+
async resolveBase(repoPath, branch, selfName, remoteBranch, options, reads, cancellation) {
|
|
1714
|
+
const preferredTarget = options?.preferredTarget?.trim() || void 0;
|
|
1715
|
+
if (preferredTarget != null) {
|
|
1716
|
+
let target = await this.getNearestForkTarget(repoPath, branch, preferredTarget, reads, cancellation);
|
|
1717
|
+
if (target != null && selfName != null && await this.isSelfAtTip(repoPath, target.ref, selfName, reads, cancellation)) target = void 0;
|
|
1718
|
+
if (cancellation?.aborted) throw new CancellationError();
|
|
1719
|
+
if (target != null) return {
|
|
1720
|
+
kind: "target",
|
|
1721
|
+
target,
|
|
1722
|
+
tier: "preferred"
|
|
1723
|
+
};
|
|
1724
|
+
}
|
|
1456
1725
|
let target;
|
|
1457
1726
|
let tier = "default";
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
if (target != null) {
|
|
1462
|
-
tier = candidate.tier;
|
|
1463
|
-
break;
|
|
1464
|
-
}
|
|
1727
|
+
if (branch != null) {
|
|
1728
|
+
const stored = await this.getStoredBaseTarget(repoPath, branch, remoteBranch, options, reads, cancellation);
|
|
1729
|
+
if (stored != null) ({target, tier} = stored);
|
|
1465
1730
|
}
|
|
1466
1731
|
if (target == null) {
|
|
1467
|
-
const defaultBranch = await this.
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1732
|
+
const defaultBranch = await this.getDefaultBranchForBase(repoPath, options, reads, cancellation);
|
|
1733
|
+
if (branch != null && defaultBranch === branch) {
|
|
1734
|
+
if (remoteBranch) {
|
|
1735
|
+
addRefDependencies(reads.dependsOn, branch);
|
|
1736
|
+
const records = await this.provider.refs.getRefs(repoPath, cancellation);
|
|
1737
|
+
const tip = await this.getRefTip(repoPath, records, reads.from, cancellation);
|
|
1738
|
+
if (cancellation?.aborted) throw new CancellationError();
|
|
1739
|
+
return tip != null ? {
|
|
1740
|
+
kind: "target",
|
|
1741
|
+
target: {
|
|
1742
|
+
ref: branch,
|
|
1743
|
+
forkPoint: tip
|
|
1744
|
+
},
|
|
1745
|
+
tier: "default"
|
|
1746
|
+
} : { kind: "none" };
|
|
1747
|
+
}
|
|
1748
|
+
return {
|
|
1749
|
+
kind: "upstream",
|
|
1750
|
+
upstream: await this.getSelfUpstreamTarget(repoPath, branch, reads, cancellation)
|
|
1751
|
+
};
|
|
1752
|
+
}
|
|
1753
|
+
const createdFrom = branch != null && !remoteBranch ? await this.getCreatedFromSource(repoPath, branch, options, reads, cancellation) : void 0;
|
|
1471
1754
|
if (createdFrom != null) {
|
|
1472
|
-
const created = await this.getCreatedFromForkTarget(repoPath, branch, createdFrom, defaultBranch,
|
|
1755
|
+
const created = await this.getCreatedFromForkTarget(repoPath, branch, createdFrom, defaultBranch, reads, cancellation);
|
|
1473
1756
|
if (created != null) ({target, tier} = created);
|
|
1474
|
-
} else target = await this.getUpstreamNearestForkTarget(repoPath, branch, defaultBranch,
|
|
1757
|
+
} else target = await this.getUpstreamNearestForkTarget(repoPath, branch, defaultBranch, reads, cancellation);
|
|
1475
1758
|
}
|
|
1476
1759
|
if (cancellation?.aborted) throw new CancellationError();
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1760
|
+
return target != null ? {
|
|
1761
|
+
kind: "target",
|
|
1762
|
+
target,
|
|
1763
|
+
tier
|
|
1764
|
+
} : { kind: "none" };
|
|
1765
|
+
}
|
|
1766
|
+
/**
|
|
1767
|
+
* The first usable stored candidate, most trusted first: the user's merge target (unless `skipUserTarget`), the
|
|
1768
|
+
* detected one, the pull request's base (`getAssociatedPullRequest`, read only when neither is usable), then the
|
|
1769
|
+
* recorded base — only its stored value (and with `inferBase`, VS Code's), never `getBaseBranchName`'s reflog
|
|
1770
|
+
* guess, which on a long-lived checkout names whatever branch was last checked out from. A remote-tracking branch
|
|
1771
|
+
* has no stored keys of its own, so only its pull request's base is tried, and never recorded.
|
|
1772
|
+
*/
|
|
1773
|
+
async getStoredBaseTarget(repoPath, branch, remoteBranch, options, reads, cancellation) {
|
|
1774
|
+
let stored = [];
|
|
1775
|
+
if (!remoteBranch) {
|
|
1776
|
+
stored = await this.readStoredMergeKeys(repoPath, branch, options?.inferBase, reads);
|
|
1777
|
+
reads.stored = stored;
|
|
1778
|
+
}
|
|
1779
|
+
const [user, detected, legacyDetected, recorded, vscodeRecorded] = stored;
|
|
1780
|
+
const tried = /* @__PURE__ */ new Set();
|
|
1781
|
+
const measure = async (candidate) => {
|
|
1782
|
+
candidate = candidate?.trim();
|
|
1783
|
+
if (!candidate || tried.has(candidate)) return void 0;
|
|
1784
|
+
tried.add(candidate);
|
|
1785
|
+
return this.getNearestForkTarget(repoPath, branch, candidate, reads, cancellation);
|
|
1786
|
+
};
|
|
1787
|
+
let target;
|
|
1788
|
+
if (!options?.skipUserTarget) {
|
|
1789
|
+
target = await measure(user);
|
|
1790
|
+
if (target != null) return {
|
|
1791
|
+
target,
|
|
1792
|
+
tier: "user"
|
|
1793
|
+
};
|
|
1794
|
+
}
|
|
1795
|
+
target = await measure(detected ?? legacyDetected);
|
|
1796
|
+
if (target != null) return {
|
|
1797
|
+
target,
|
|
1798
|
+
tier: "detected"
|
|
1799
|
+
};
|
|
1800
|
+
if (options?.getAssociatedPullRequest != null) {
|
|
1801
|
+
const pr = await this.getPullRequestBaseCandidate(repoPath, branch, options.getAssociatedPullRequest, reads, cancellation);
|
|
1802
|
+
target = await measure(pr?.ref);
|
|
1803
|
+
if (pr != null && target != null) {
|
|
1804
|
+
if (pr.stacked || remoteBranch) reads.pullRequest = pr.ref;
|
|
1805
|
+
else {
|
|
1806
|
+
this.storeMergeTargetBranchName(repoPath, branch, pr.ref, { skipInvalidation: ["branchOverviews"] });
|
|
1807
|
+
reads.pinnable = false;
|
|
1808
|
+
}
|
|
1809
|
+
return {
|
|
1810
|
+
target,
|
|
1811
|
+
tier: "pr"
|
|
1812
|
+
};
|
|
1813
|
+
}
|
|
1814
|
+
reads.pullRequest = pr?.ref ?? null;
|
|
1815
|
+
}
|
|
1816
|
+
target = await measure(recorded);
|
|
1817
|
+
if (target != null) return {
|
|
1818
|
+
target,
|
|
1819
|
+
tier: "recorded"
|
|
1820
|
+
};
|
|
1821
|
+
if (!recorded?.trim() && vscodeRecorded?.trim()) {
|
|
1822
|
+
target = await measure(vscodeRecorded);
|
|
1823
|
+
if (target != null) {
|
|
1824
|
+
this.storeBaseBranchName(repoPath, branch, vscodeRecorded.trim(), { skipInvalidation: ["branchOverviews", "baseBranchName"] });
|
|
1825
|
+
reads.pinnable = false;
|
|
1826
|
+
return {
|
|
1827
|
+
target,
|
|
1828
|
+
tier: "recorded"
|
|
1829
|
+
};
|
|
1830
|
+
}
|
|
1831
|
+
}
|
|
1832
|
+
}
|
|
1833
|
+
/**
|
|
1834
|
+
* The branch's stored merge keys as read, in {@link storedMergeKeys} order, then VS Code's recorded base when
|
|
1835
|
+
* `inferBase`. A failed read is absent, and leaves `reads`' answer unpinned.
|
|
1836
|
+
*/
|
|
1837
|
+
readStoredMergeKeys(repoPath, branch, inferBase, reads) {
|
|
1838
|
+
const read = async (get) => {
|
|
1839
|
+
try {
|
|
1840
|
+
return await get();
|
|
1481
1841
|
} catch (ex) {
|
|
1482
1842
|
if (isCancellationError(ex)) throw ex;
|
|
1843
|
+
if (reads != null) reads.pinnable = false;
|
|
1844
|
+
return;
|
|
1483
1845
|
}
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1846
|
+
};
|
|
1847
|
+
return Promise.all([...storedMergeKeys.map((key) => read(() => this.provider.config.getGkConfig(repoPath, `branch.${branch}.${key}`))), inferBase ? read(() => this.provider.config.getConfig(repoPath, `branch.${branch}.vscode-merge-base`)) : void 0]);
|
|
1848
|
+
}
|
|
1849
|
+
/**
|
|
1850
|
+
* The pull request's base as a candidate on the branch's remote; `undefined` when there is none, the branch has no
|
|
1851
|
+
* remote, or either can't be read (which leaves `reads`' answer unpinned)
|
|
1852
|
+
*/
|
|
1853
|
+
async getPullRequestBaseCandidate(repoPath, branch, getAssociatedPullRequest, reads, cancellation) {
|
|
1854
|
+
try {
|
|
1855
|
+
const pr = await getAssociatedPullRequest();
|
|
1856
|
+
const base = pr?.refs?.base?.branch?.trim();
|
|
1857
|
+
if (!base) return void 0;
|
|
1858
|
+
const ref = await this.getBranch(repoPath, branch, void 0, cancellation);
|
|
1859
|
+
if (ref == null) return void 0;
|
|
1860
|
+
const { remote } = await resolveBranchRemote(this.provider, repoPath, ref, cancellation);
|
|
1861
|
+
if (remote == null) return void 0;
|
|
1862
|
+
return {
|
|
1863
|
+
ref: `${remote}/${base}`,
|
|
1864
|
+
stacked: pr?.stack != null
|
|
1865
|
+
};
|
|
1866
|
+
} catch (ex) {
|
|
1867
|
+
if (isCancellationError(ex) && (cancellation?.aborted ?? true)) throw ex;
|
|
1868
|
+
if (reads != null) reads.pinnable = false;
|
|
1869
|
+
return;
|
|
1489
1870
|
}
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1871
|
+
}
|
|
1872
|
+
/**
|
|
1873
|
+
* The default branch for {@link resolveBase}: {@link getLocalDefaultBranchName}; when that names none, with
|
|
1874
|
+
* `defaultBranch: 'remote'`, the default the first of those remotes reports; then `getFallbackDefaultBranch`. A
|
|
1875
|
+
* remote-qualified answer is taken as its local branch when there is one
|
|
1876
|
+
*/
|
|
1877
|
+
async getDefaultBranchForBase(repoPath, options, reads, cancellation) {
|
|
1878
|
+
let name = await this.getLocalDefaultBranchName(repoPath, reads.dependsOn, cancellation);
|
|
1879
|
+
reads.localDefault = name ?? null;
|
|
1880
|
+
if (name != null) return name;
|
|
1881
|
+
if (options?.defaultBranch === "remote") {
|
|
1882
|
+
const [remote] = await this.getPreferredRemotes(repoPath, cancellation);
|
|
1883
|
+
if (remote != null) {
|
|
1884
|
+
let symref;
|
|
1885
|
+
try {
|
|
1886
|
+
symref = await this.getDefaultBranchName(repoPath, remote, reads.priority != null ? { priority: reads.priority } : void 0, cancellation);
|
|
1887
|
+
} catch (ex) {
|
|
1888
|
+
if (isCancellationError(ex)) throw ex;
|
|
1889
|
+
reads.pinnable = false;
|
|
1890
|
+
}
|
|
1891
|
+
reads.remoteDefault = {
|
|
1892
|
+
remote,
|
|
1893
|
+
name: symref ?? null
|
|
1894
|
+
};
|
|
1895
|
+
const local = symref?.startsWith(`${remote}/`) ? symref.slice(remote.length + 1) : void 0;
|
|
1896
|
+
if (symref && local) name = await this.preferListedLocalBranch(repoPath, symref, local, reads, cancellation);
|
|
1897
|
+
}
|
|
1898
|
+
}
|
|
1899
|
+
if (name == null && options?.getFallbackDefaultBranch != null) {
|
|
1900
|
+
let fallback;
|
|
1901
|
+
try {
|
|
1902
|
+
fallback = (await options.getFallbackDefaultBranch())?.trim() || void 0;
|
|
1903
|
+
} catch (ex) {
|
|
1904
|
+
if (isCancellationError(ex) && (cancellation?.aborted ?? true)) throw ex;
|
|
1905
|
+
reads.pinnable = false;
|
|
1906
|
+
}
|
|
1907
|
+
reads.fallbackDefault = fallback ?? null;
|
|
1908
|
+
name = fallback;
|
|
1909
|
+
const local = fallback != null ? splitRemoteBranchName(fallback, await this.getRemoteNames(repoPath, cancellation))?.[1] : void 0;
|
|
1910
|
+
if (fallback != null && local) name = await this.preferListedLocalBranch(repoPath, fallback, local, reads, cancellation);
|
|
1911
|
+
}
|
|
1912
|
+
return name;
|
|
1913
|
+
}
|
|
1914
|
+
/** `local` when the cached ref list names that branch (depended on either way), else `remoteName` */
|
|
1915
|
+
async preferListedLocalBranch(repoPath, remoteName, local, reads, cancellation) {
|
|
1916
|
+
addRefDependencies(reads.dependsOn, local);
|
|
1917
|
+
const records = await this.provider.refs.getRefs(repoPath, cancellation);
|
|
1918
|
+
return await this.getRefTip(repoPath, records, local, cancellation) != null ? local : remoteName;
|
|
1919
|
+
}
|
|
1920
|
+
/**
|
|
1921
|
+
* The branch `branch` was created from (tier `'created'`); with `inferBase`, for one created from `HEAD`, the
|
|
1922
|
+
* branch HEAD was on when it was first checked out (tier `'inferred'`)
|
|
1923
|
+
*/
|
|
1924
|
+
async getCreatedFromSource(repoPath, branch, options, reads, cancellation) {
|
|
1925
|
+
const createdFrom = await this.getCreatedFromTarget(repoPath, branch, reads, cancellation);
|
|
1926
|
+
if (createdFrom.name != null) return {
|
|
1927
|
+
name: createdFrom.name,
|
|
1928
|
+
tier: "created"
|
|
1929
|
+
};
|
|
1930
|
+
if (!createdFrom.fromHead || !options?.inferBase) return void 0;
|
|
1931
|
+
const inferred = await this.getInferredCreatedFrom(repoPath, branch, reads, cancellation);
|
|
1932
|
+
return inferred != null ? {
|
|
1933
|
+
name: inferred,
|
|
1934
|
+
tier: "inferred"
|
|
1935
|
+
} : void 0;
|
|
1496
1936
|
}
|
|
1497
1937
|
/**
|
|
1498
1938
|
* The base for a branch created from `createdFrom`: that branch, unless the default branch forked strictly nearer
|
|
1499
1939
|
* HEAD, as it does once `createdFrom` merged into it and the branch was rebased onto it. A stacked branch's
|
|
1500
1940
|
* unmerged parent forked nearer, and stays its base. `undefined` when neither is usable.
|
|
1501
1941
|
*/
|
|
1502
|
-
async getCreatedFromForkTarget(repoPath, branch, createdFrom, defaultBranch,
|
|
1503
|
-
const
|
|
1504
|
-
const
|
|
1942
|
+
async getCreatedFromForkTarget(repoPath, branch, createdFrom, defaultBranch, reads, cancellation) {
|
|
1943
|
+
const { name } = createdFrom;
|
|
1944
|
+
const skipDefault = defaultBranch == null || name === defaultBranch || name === `refs/heads/${defaultBranch}` || name === `refs/remotes/${defaultBranch}`;
|
|
1945
|
+
const [created, fallback] = await Promise.all([this.getUpstreamNearestForkTarget(repoPath, branch, name, reads, cancellation), skipDefault ? void 0 : this.getUpstreamNearestForkTarget(repoPath, branch, defaultBranch, reads, cancellation)]);
|
|
1505
1946
|
if (created == null) return fallback != null ? {
|
|
1506
1947
|
target: fallback,
|
|
1507
1948
|
tier: "default"
|
|
@@ -1512,7 +1953,7 @@ var BranchesGitSubProvider = class {
|
|
|
1512
1953
|
};
|
|
1513
1954
|
return {
|
|
1514
1955
|
target: created,
|
|
1515
|
-
tier:
|
|
1956
|
+
tier: createdFrom.tier
|
|
1516
1957
|
};
|
|
1517
1958
|
}
|
|
1518
1959
|
/** Counts HEAD's commits past `target` for {@link getAheadOfBase} */
|
|
@@ -1538,8 +1979,8 @@ var BranchesGitSubProvider = class {
|
|
|
1538
1979
|
};
|
|
1539
1980
|
}
|
|
1540
1981
|
/** Counts the default branch's own commits past its upstream (see {@link getSelfUpstreamTarget}) for {@link getAheadOfBase} */
|
|
1541
|
-
async measureAheadOfSelfUpstream(repoPath,
|
|
1542
|
-
const
|
|
1982
|
+
async measureAheadOfSelfUpstream(repoPath, upstream, reads, cancellation) {
|
|
1983
|
+
const { dependsOn } = reads;
|
|
1543
1984
|
if (cancellation?.aborted) throw new CancellationError();
|
|
1544
1985
|
if (upstream == null) return {
|
|
1545
1986
|
base: void 0,
|
|
@@ -1557,7 +1998,7 @@ var BranchesGitSubProvider = class {
|
|
|
1557
1998
|
reason: "self",
|
|
1558
1999
|
dependsOn: [...dependsOn]
|
|
1559
2000
|
};
|
|
1560
|
-
const forkPoint = counts.left === 0 ? upstream.sha : await this.getForkPoint(repoPath, upstream.ref, cancellation);
|
|
2001
|
+
const forkPoint = counts.left === 0 ? upstream.sha : await this.getForkPoint(repoPath, upstream.ref, reads, cancellation);
|
|
1561
2002
|
if (cancellation?.aborted) throw new CancellationError();
|
|
1562
2003
|
return {
|
|
1563
2004
|
base: {
|
|
@@ -1747,42 +2188,28 @@ var BranchesGitSubProvider = class {
|
|
|
1747
2188
|
return [];
|
|
1748
2189
|
}
|
|
1749
2190
|
}
|
|
1750
|
-
/** Whether `ref` names `branch` itself (see {@link isSelfRef}) and sits at HEAD's commit; tips are read only after the name matched */
|
|
1751
|
-
async isSelfAtHead(repoPath, ref, branch, cancellation) {
|
|
1752
|
-
if (!await this.isSelfRef(repoPath, ref, branch, cancellation)) return false;
|
|
1753
|
-
const [refSha, headSha] = await Promise.all([this.provider.refs.validateReference(repoPath, ref, void 0, cancellation), this.provider.refs.validateReference(repoPath, "HEAD", void 0, cancellation)]);
|
|
1754
|
-
return refSha != null && refSha === headSha;
|
|
1755
|
-
}
|
|
1756
2191
|
/**
|
|
1757
|
-
*
|
|
1758
|
-
*
|
|
1759
|
-
* guess, which on a long-lived checkout names whatever branch was last checked out from. A failed read is absent.
|
|
2192
|
+
* Whether `ref` names `selfName` itself (see {@link isSelfRef}) and sits at `reads.from`'s commit; tips are read
|
|
2193
|
+
* only after the name matched. HEAD's commit is asked of git, a branch's read from the cached ref list
|
|
1760
2194
|
*/
|
|
1761
|
-
async
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
"
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
];
|
|
1780
|
-
const candidates = [];
|
|
1781
|
-
for (const [i, ref] of reads.entries()) if (ref && !candidates.some((c) => c.ref === ref)) candidates.push({
|
|
1782
|
-
ref,
|
|
1783
|
-
tier: tiers[i]
|
|
1784
|
-
});
|
|
1785
|
-
return candidates;
|
|
2195
|
+
async isSelfAtTip(repoPath, ref, selfName, reads, cancellation) {
|
|
2196
|
+
if (!await this.isSelfRef(repoPath, ref, selfName, cancellation)) return false;
|
|
2197
|
+
if (reads.from === "HEAD") {
|
|
2198
|
+
const [refSha, headSha] = await Promise.all([this.provider.refs.validateReference(repoPath, ref, void 0, cancellation), this.provider.refs.validateReference(repoPath, "HEAD", void 0, cancellation)]);
|
|
2199
|
+
return refSha != null && refSha === headSha;
|
|
2200
|
+
}
|
|
2201
|
+
const records = await this.provider.refs.getRefs(repoPath, cancellation);
|
|
2202
|
+
const [refSha, fromSha] = await Promise.all([this.getRefTip(repoPath, records, ref, cancellation), this.getRefTip(repoPath, records, reads.from, cancellation)]);
|
|
2203
|
+
return refSha != null && refSha === fromSha;
|
|
2204
|
+
}
|
|
2205
|
+
/** The remotes a default branch is read from, in order: `upstream`, `origin`, then the first remote */
|
|
2206
|
+
async getPreferredRemotes(repoPath, cancellation) {
|
|
2207
|
+
const remotes = await this.getRemoteNames(repoPath, cancellation);
|
|
2208
|
+
return [...new Set([
|
|
2209
|
+
remotes.find((r) => r === "upstream"),
|
|
2210
|
+
remotes.find((r) => r === "origin"),
|
|
2211
|
+
remotes.at(0)
|
|
2212
|
+
].filter((r) => r != null))];
|
|
1786
2213
|
}
|
|
1787
2214
|
/**
|
|
1788
2215
|
* The repo's default branch from local refs only, never the network: the `HEAD` symref of the `upstream` remote,
|
|
@@ -1796,12 +2223,7 @@ var BranchesGitSubProvider = class {
|
|
|
1796
2223
|
records ??= await this.provider.refs.getRefs(repoPath, cancellation);
|
|
1797
2224
|
return await this.getRefTip(repoPath, records, ref, cancellation) != null;
|
|
1798
2225
|
};
|
|
1799
|
-
const
|
|
1800
|
-
const preferred = new Set([
|
|
1801
|
-
remotes.find((r) => r === "upstream"),
|
|
1802
|
-
remotes.find((r) => r === "origin"),
|
|
1803
|
-
remotes.at(0)
|
|
1804
|
-
].filter((r) => r != null));
|
|
2226
|
+
const preferred = await this.getPreferredRemotes(repoPath, cancellation);
|
|
1805
2227
|
for (const remote of preferred) {
|
|
1806
2228
|
dependsOn.add(`refs/remotes/${remote}/HEAD`);
|
|
1807
2229
|
const symref = await this.getDefaultBranchName(repoPath, remote, { local: true }, cancellation);
|
|
@@ -1840,7 +2262,7 @@ var BranchesGitSubProvider = class {
|
|
|
1840
2262
|
* remote and ref lists; `undefined` when there is no `upstream` remote, it lacks the branch, or the base is that
|
|
1841
2263
|
* ref itself.
|
|
1842
2264
|
*/
|
|
1843
|
-
async getUpstreamContender(repoPath, base,
|
|
2265
|
+
async getUpstreamContender(repoPath, base, reads, cancellation) {
|
|
1844
2266
|
if (!base) return void 0;
|
|
1845
2267
|
const remotes = await this.getRemoteNames(repoPath, cancellation);
|
|
1846
2268
|
if (!remotes.includes("upstream")) return void 0;
|
|
@@ -1857,33 +2279,37 @@ var BranchesGitSubProvider = class {
|
|
|
1857
2279
|
}
|
|
1858
2280
|
const contender = `upstream/${name}`;
|
|
1859
2281
|
const remoteRef = `refs/remotes/${contender}`;
|
|
1860
|
-
if (contender === base || remoteRef === base
|
|
1861
|
-
|
|
2282
|
+
if (contender === base || remoteRef === base) return void 0;
|
|
2283
|
+
if (findRefTip(records, remoteRef) == null) {
|
|
2284
|
+
reads.tipRefs.add(remoteRef);
|
|
2285
|
+
return;
|
|
2286
|
+
}
|
|
2287
|
+
reads.dependsOn.add(remoteRef);
|
|
1862
2288
|
return contender;
|
|
1863
2289
|
}
|
|
1864
2290
|
/** {@link getNearestForkTarget} with the `upstream` remote's copy of `candidate` as its contender */
|
|
1865
|
-
async getUpstreamNearestForkTarget(repoPath, branch, candidate,
|
|
1866
|
-
return this.getNearestForkTarget(repoPath, branch, candidate,
|
|
2291
|
+
async getUpstreamNearestForkTarget(repoPath, branch, candidate, reads, cancellation) {
|
|
2292
|
+
return this.getNearestForkTarget(repoPath, branch, candidate, reads, cancellation, { contender: await this.getUpstreamContender(repoPath, candidate, reads, cancellation) });
|
|
1867
2293
|
}
|
|
1868
2294
|
/**
|
|
1869
|
-
* A candidate base as the ref
|
|
1870
|
-
* nearer
|
|
2295
|
+
* A candidate base as the ref `reads.from` is measured against: the candidate or its tracking ref, whichever forked
|
|
2296
|
+
* nearer, so a stale local base defers to its upstream while one carrying unpushed work wins over it. A
|
|
1871
2297
|
* `contender` other than the tracking ref is taken only when it forked strictly nearer than both, so a tie keeps
|
|
1872
|
-
* the candidate's own pick. A candidate none forks from is used as-is
|
|
1873
|
-
* candidate is unusable.
|
|
2298
|
+
* the candidate's own pick. A candidate none forks from is used as-is unless git confirms it names no ref: one the
|
|
2299
|
+
* cached ref list names is kept, and so is one whose check fails. `undefined` when the candidate is unusable.
|
|
1874
2300
|
*/
|
|
1875
|
-
async getNearestForkTarget(repoPath, branch, candidate,
|
|
2301
|
+
async getNearestForkTarget(repoPath, branch, candidate, reads, cancellation, options) {
|
|
1876
2302
|
if (!candidate || candidate.startsWith("-")) return void 0;
|
|
1877
2303
|
if (candidate === branch || candidate === `refs/heads/${branch}`) return void 0;
|
|
1878
|
-
addRefDependencies(dependsOn, candidate);
|
|
2304
|
+
addRefDependencies(reads.dependsOn, candidate);
|
|
1879
2305
|
const upstreamName = this.getUpstreamName(repoPath, candidate, void 0, cancellation);
|
|
1880
2306
|
const getUpstreamFork = async () => {
|
|
1881
2307
|
const upstream = await upstreamName;
|
|
1882
2308
|
if (!upstream || upstream.startsWith("-")) return void 0;
|
|
1883
|
-
addRefDependencies(dependsOn, upstream);
|
|
2309
|
+
addRefDependencies(reads.dependsOn, upstream);
|
|
1884
2310
|
return {
|
|
1885
2311
|
ref: upstream,
|
|
1886
|
-
mergeBase: await this.getForkPoint(repoPath, upstream, cancellation)
|
|
2312
|
+
mergeBase: await this.getForkPoint(repoPath, upstream, reads, cancellation)
|
|
1887
2313
|
};
|
|
1888
2314
|
};
|
|
1889
2315
|
const contender = options?.contender;
|
|
@@ -1896,10 +2322,15 @@ var BranchesGitSubProvider = class {
|
|
|
1896
2322
|
const upstream = await upstreamName;
|
|
1897
2323
|
if (upstream && tip === findRefTip(records, upstream)) return void 0;
|
|
1898
2324
|
}
|
|
1899
|
-
return this.getForkPoint(repoPath, contender, cancellation);
|
|
2325
|
+
return this.getForkPoint(repoPath, contender, reads, cancellation);
|
|
2326
|
+
};
|
|
2327
|
+
const candidateReads = {
|
|
2328
|
+
from: reads.from,
|
|
2329
|
+
priority: reads.priority,
|
|
2330
|
+
pinnable: true
|
|
1900
2331
|
};
|
|
1901
2332
|
const [candidateMergeBase, upstreamFork, contenderMergeBase] = await Promise.all([
|
|
1902
|
-
this.getForkPoint(repoPath, candidate, cancellation),
|
|
2333
|
+
this.getForkPoint(repoPath, candidate, candidateReads, cancellation),
|
|
1903
2334
|
getUpstreamFork(),
|
|
1904
2335
|
getContenderMergeBase()
|
|
1905
2336
|
]);
|
|
@@ -1918,18 +2349,33 @@ var BranchesGitSubProvider = class {
|
|
|
1918
2349
|
ref: contender,
|
|
1919
2350
|
mergeBase: contenderMergeBase
|
|
1920
2351
|
};
|
|
1921
|
-
if (nearest != null)
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
2352
|
+
if (nearest != null) {
|
|
2353
|
+
if (!candidateReads.pinnable) reads.pinnable = false;
|
|
2354
|
+
return {
|
|
2355
|
+
ref: nearest.ref,
|
|
2356
|
+
forkPoint: nearest.mergeBase
|
|
2357
|
+
};
|
|
2358
|
+
}
|
|
2359
|
+
const records = await this.provider.refs.getRefs(repoPath, cancellation);
|
|
2360
|
+
if (records.length && findRefTip(records, candidate) != null) {
|
|
2361
|
+
if (!candidateReads.pinnable) reads.pinnable = false;
|
|
2362
|
+
return {
|
|
2363
|
+
ref: candidate,
|
|
2364
|
+
forkPoint: void 0
|
|
2365
|
+
};
|
|
2366
|
+
}
|
|
2367
|
+
const { missing } = await this.provider.refs.getSymbolicReferenceNameOrMissing(repoPath, candidate, reads.priority != null ? { priority: reads.priority } : void 0, cancellation);
|
|
2368
|
+
if (missing) return void 0;
|
|
2369
|
+
reads.pinnable = false;
|
|
2370
|
+
return {
|
|
1926
2371
|
ref: candidate,
|
|
1927
2372
|
forkPoint: void 0
|
|
1928
|
-
}
|
|
2373
|
+
};
|
|
1929
2374
|
}
|
|
1930
2375
|
/**
|
|
1931
2376
|
* `<name>@{u}`, without asking git when the cached ref list shows `name` can't have one: a failed lookup isn't
|
|
1932
|
-
* cached, so asking it of a remote-tracking ref (`origin/main`, the usual default) re-spawns on every read.
|
|
2377
|
+
* cached, so asking it of a remote-tracking ref (`origin/main`, the usual default) re-spawns on every read. The
|
|
2378
|
+
* list's own upstream isn't used: a config-only change (`git branch -u`) doesn't refresh it.
|
|
1933
2379
|
*/
|
|
1934
2380
|
async getUpstreamName(repoPath, name, options, cancellation) {
|
|
1935
2381
|
if (!mayHaveUpstream(await this.provider.refs.getRefs(repoPath, cancellation), name)) return void 0;
|
|
@@ -1940,7 +2386,8 @@ var BranchesGitSubProvider = class {
|
|
|
1940
2386
|
* when an `upstream` remote has it, else `<branch>@{u}`. Read from the cached ref and branch lists; only a
|
|
1941
2387
|
* `<branch>` that isn't checked out spawns, for its `@{u}`. `undefined` when there is neither, or its ref is gone.
|
|
1942
2388
|
*/
|
|
1943
|
-
async getSelfUpstreamTarget(repoPath, branch,
|
|
2389
|
+
async getSelfUpstreamTarget(repoPath, branch, reads, cancellation) {
|
|
2390
|
+
const { dependsOn } = reads;
|
|
1944
2391
|
const records = await this.provider.refs.getRefs(repoPath, cancellation);
|
|
1945
2392
|
let upstream;
|
|
1946
2393
|
if ((await this.getRemoteNames(repoPath, cancellation)).includes("upstream")) {
|
|
@@ -1976,9 +2423,36 @@ var BranchesGitSubProvider = class {
|
|
|
1976
2423
|
if (records.length) return findRefTip(records, name);
|
|
1977
2424
|
return this.provider.refs.validateReference(repoPath, name, void 0, cancellation);
|
|
1978
2425
|
}
|
|
1979
|
-
/**
|
|
1980
|
-
async getForkPoint(repoPath, ref, cancellation) {
|
|
1981
|
-
return await this.
|
|
2426
|
+
/** Where `reads.from` forked from `ref`: git's fork-point reading of `ref`'s reflog when it has one, else their merge base */
|
|
2427
|
+
async getForkPoint(repoPath, ref, reads, cancellation) {
|
|
2428
|
+
return await this.readMergeBase(repoPath, ref, reads.from, true, reads, cancellation) ?? this.readMergeBase(repoPath, reads.from, ref, false, reads, cancellation);
|
|
2429
|
+
}
|
|
2430
|
+
/**
|
|
2431
|
+
* `getMergeBase`, sharing its cached and in-flight reads, but telling git's exit 1 (no common ancestor, or no fork
|
|
2432
|
+
* point) from a read that failed. Both answer `undefined`; a failed one also clears `reads.pinnable`.
|
|
2433
|
+
*/
|
|
2434
|
+
async readMergeBase(repoPath, ref1, ref2, forkPoint, reads, cancellation) {
|
|
2435
|
+
try {
|
|
2436
|
+
const result = await this.git.run({
|
|
2437
|
+
cwd: repoPath,
|
|
2438
|
+
cancellation,
|
|
2439
|
+
errors: "throw",
|
|
2440
|
+
expectedExitCodes: [1],
|
|
2441
|
+
caching: {
|
|
2442
|
+
cache: this.cache.gitResults,
|
|
2443
|
+
options: { accessTTL: 3e5 }
|
|
2444
|
+
},
|
|
2445
|
+
...reads.priority != null ? { priority: reads.priority } : void 0
|
|
2446
|
+
}, "merge-base", forkPoint ? "--fork-point" : void 0, ref1, ref2);
|
|
2447
|
+
if (result.completion.status === "exited") {
|
|
2448
|
+
if (result.exitCode === 1) return void 0;
|
|
2449
|
+
const sha = result.stdout.split("\n")[0].trim();
|
|
2450
|
+
if (sha) return sha;
|
|
2451
|
+
}
|
|
2452
|
+
} catch (ex) {
|
|
2453
|
+
if (isCancellationError(ex)) throw ex;
|
|
2454
|
+
}
|
|
2455
|
+
reads.pinnable = false;
|
|
1982
2456
|
}
|
|
1983
2457
|
/**
|
|
1984
2458
|
* The stored merge target — the user's pick, else the one detected from its PR — skipping any whose branch git
|
|
@@ -2025,6 +2499,7 @@ var BranchesGitSubProvider = class {
|
|
|
2025
2499
|
forgetBranchCreatedFrom(repoPath, name) {
|
|
2026
2500
|
const key = this.getBranchActivityKey(repoPath, name);
|
|
2027
2501
|
this._branchCreatedFrom.delete(key);
|
|
2502
|
+
this._branchCheckedOutFrom.delete(key);
|
|
2028
2503
|
this._branchCreatedFromGenerations.set(key, (this._branchCreatedFromGenerations.get(key) ?? 0) + 1);
|
|
2029
2504
|
}
|
|
2030
2505
|
/** Forgets this process's throttle marks for `name`, so its next activity re-reads the stored stamps */
|
|
@@ -2169,32 +2644,66 @@ var BranchesGitSubProvider = class {
|
|
|
2169
2644
|
this.context.hooks?.repository?.onChanged?.(repoPath, ["starred"]);
|
|
2170
2645
|
}
|
|
2171
2646
|
};
|
|
2172
|
-
__decorate([debug()], BranchesGitSubProvider.prototype, "getBranch", null);
|
|
2173
|
-
__decorate([debug({
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
__decorate([debug({
|
|
2647
|
+
__decorate([debug({ quiet: true })], BranchesGitSubProvider.prototype, "getBranch", null);
|
|
2648
|
+
__decorate([debug({
|
|
2649
|
+
quiet: true,
|
|
2650
|
+
args: (repoPath) => ({ repoPath })
|
|
2651
|
+
})], BranchesGitSubProvider.prototype, "getBranches", null);
|
|
2652
|
+
__decorate([debug({ quiet: true })], BranchesGitSubProvider.prototype, "getBranchContributionsOverview", null);
|
|
2653
|
+
__decorate([debug({ quiet: true })], BranchesGitSubProvider.prototype, "getBranchesWithCommits", null);
|
|
2654
|
+
__decorate([debug({ quiet: true })], BranchesGitSubProvider.prototype, "getCurrentBranchReference", null);
|
|
2655
|
+
__decorate([debug({
|
|
2656
|
+
quiet: true,
|
|
2657
|
+
exit: true
|
|
2658
|
+
})], BranchesGitSubProvider.prototype, "getDefaultBranchName", null);
|
|
2178
2659
|
__decorate([debug()], BranchesGitSubProvider.prototype, "setRemoteHeadIfMissing", null);
|
|
2179
2660
|
__decorate([debug()], BranchesGitSubProvider.prototype, "createBranch", null);
|
|
2180
2661
|
__decorate([debug()], BranchesGitSubProvider.prototype, "deleteLocalBranch", null);
|
|
2181
2662
|
__decorate([debug()], BranchesGitSubProvider.prototype, "deleteRemoteBranch", null);
|
|
2182
|
-
__decorate([debug()], BranchesGitSubProvider.prototype, "getBranchMergedStatus", null);
|
|
2183
|
-
__decorate([debug()], BranchesGitSubProvider.prototype, "getLocalBranchByUpstream", null);
|
|
2184
|
-
__decorate([trace({
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
__decorate([debug({
|
|
2189
|
-
__decorate([
|
|
2190
|
-
__decorate([
|
|
2191
|
-
__decorate([debug({
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
__decorate([
|
|
2196
|
-
__decorate([trace({
|
|
2197
|
-
__decorate([
|
|
2663
|
+
__decorate([debug({ quiet: true })], BranchesGitSubProvider.prototype, "getBranchMergedStatus", null);
|
|
2664
|
+
__decorate([debug({ quiet: true })], BranchesGitSubProvider.prototype, "getLocalBranchByUpstream", null);
|
|
2665
|
+
__decorate([trace({
|
|
2666
|
+
quiet: true,
|
|
2667
|
+
exit: true
|
|
2668
|
+
})], BranchesGitSubProvider.prototype, "getPushTarget", null);
|
|
2669
|
+
__decorate([debug({ quiet: true })], BranchesGitSubProvider.prototype, "getPotentialApplyConflicts", null);
|
|
2670
|
+
__decorate([debug({ quiet: true })], BranchesGitSubProvider.prototype, "getPotentialMergeConflicts", null);
|
|
2671
|
+
__decorate([debug({ quiet: true })], BranchesGitSubProvider.prototype, "getPotentialStashReapplyConflicts", null);
|
|
2672
|
+
__decorate([debug({
|
|
2673
|
+
quiet: true,
|
|
2674
|
+
exit: true
|
|
2675
|
+
})], BranchesGitSubProvider.prototype, "getBaseBranchName", null);
|
|
2676
|
+
__decorate([trace({ quiet: true })], BranchesGitSubProvider.prototype, "getAheadOfBaseConfigKey", null);
|
|
2677
|
+
__decorate([trace({ quiet: true })], BranchesGitSubProvider.prototype, "getAheadOfBaseConfigEntries", null);
|
|
2678
|
+
__decorate([debug({
|
|
2679
|
+
quiet: true,
|
|
2680
|
+
exit: true
|
|
2681
|
+
})], BranchesGitSubProvider.prototype, "getAheadOfBase", null);
|
|
2682
|
+
__decorate([debug({
|
|
2683
|
+
quiet: true,
|
|
2684
|
+
exit: true
|
|
2685
|
+
})], BranchesGitSubProvider.prototype, "getAheadOfBaseAfterBaseMove", null);
|
|
2686
|
+
__decorate([debug({
|
|
2687
|
+
quiet: true,
|
|
2688
|
+
exit: true
|
|
2689
|
+
})], BranchesGitSubProvider.prototype, "getMergeTarget", null);
|
|
2690
|
+
__decorate([debug({
|
|
2691
|
+
quiet: true,
|
|
2692
|
+
exit: true
|
|
2693
|
+
})], BranchesGitSubProvider.prototype, "getAheadBehindCounts", null);
|
|
2694
|
+
__decorate([debug({
|
|
2695
|
+
quiet: true,
|
|
2696
|
+
exit: true
|
|
2697
|
+
})], BranchesGitSubProvider.prototype, "getMergedBranches", null);
|
|
2698
|
+
__decorate([debug({
|
|
2699
|
+
quiet: true,
|
|
2700
|
+
exit: true
|
|
2701
|
+
})], BranchesGitSubProvider.prototype, "getStoredMergeTargetBranchName", null);
|
|
2702
|
+
__decorate([trace({
|
|
2703
|
+
quiet: true,
|
|
2704
|
+
exit: true
|
|
2705
|
+
})], BranchesGitSubProvider.prototype, "getStoredDetectedMergeTargetBranchName", null);
|
|
2706
|
+
__decorate([trace({ quiet: true })], BranchesGitSubProvider.prototype, "getStoredUserMergeTargetBranchName", null);
|
|
2198
2707
|
__decorate([trace()], BranchesGitSubProvider.prototype, "onCurrentBranchAccessed", null);
|
|
2199
2708
|
__decorate([trace()], BranchesGitSubProvider.prototype, "onCurrentBranchModified", null);
|
|
2200
2709
|
__decorate([trace()], BranchesGitSubProvider.prototype, "onCurrentBranchAgentActivity", null);
|
|
@@ -2260,15 +2769,46 @@ function getRefRecordsByName(records) {
|
|
|
2260
2769
|
}
|
|
2261
2770
|
return byName;
|
|
2262
2771
|
}
|
|
2772
|
+
/** Each ref list's {@link BaseMoveReadInputs}: every worktree a base move reached derives them from the same cached list */
|
|
2773
|
+
const baseMoveReadInputs = /* @__PURE__ */ new WeakMap();
|
|
2774
|
+
function getBaseMoveReadInputs(records) {
|
|
2775
|
+
let inputs = baseMoveReadInputs.get(records);
|
|
2776
|
+
if (inputs != null) return inputs;
|
|
2777
|
+
let checkedOut = /* @__PURE__ */ new Set();
|
|
2778
|
+
const checkedOutTips = /* @__PURE__ */ new Set();
|
|
2779
|
+
const replaced = [];
|
|
2780
|
+
for (const record of records) {
|
|
2781
|
+
if (record.name.startsWith("refs/replace/")) {
|
|
2782
|
+
replaced.push(`${record.name}\0${record.objectname}`);
|
|
2783
|
+
continue;
|
|
2784
|
+
}
|
|
2785
|
+
if (checkedOut == null || !record.name.startsWith("refs/heads/")) continue;
|
|
2786
|
+
if (record.worktreePath == null) {
|
|
2787
|
+
checkedOut = void 0;
|
|
2788
|
+
checkedOutTips.clear();
|
|
2789
|
+
continue;
|
|
2790
|
+
}
|
|
2791
|
+
if (record.worktreePath) {
|
|
2792
|
+
checkedOut.add(record.name);
|
|
2793
|
+
checkedOutTips.add(record.objectname);
|
|
2794
|
+
}
|
|
2795
|
+
}
|
|
2796
|
+
inputs = {
|
|
2797
|
+
checkedOut,
|
|
2798
|
+
checkedOutTips,
|
|
2799
|
+
replaced: replaced.sort().join("\n")
|
|
2800
|
+
};
|
|
2801
|
+
baseMoveReadInputs.set(records, inputs);
|
|
2802
|
+
return inputs;
|
|
2803
|
+
}
|
|
2263
2804
|
/** The record for the full ref name `fullName` in `records`, the cached ref list; no short-name resolution */
|
|
2264
2805
|
function findRefRecord(records, fullName) {
|
|
2265
2806
|
return getRefRecordsByName(records).get(fullName);
|
|
2266
2807
|
}
|
|
2267
2808
|
function toPushTargetRef(records, ref) {
|
|
2268
|
-
const record = findRefRecord(records, ref);
|
|
2269
2809
|
return {
|
|
2270
2810
|
ref,
|
|
2271
|
-
sha:
|
|
2811
|
+
sha: getListedTip(records, ref)
|
|
2272
2812
|
};
|
|
2273
2813
|
}
|
|
2274
2814
|
/**
|
|
@@ -2335,6 +2875,48 @@ function addRefDependencies(dependsOn, name) {
|
|
|
2335
2875
|
dependsOn.add(`refs/heads/${name}`);
|
|
2336
2876
|
dependsOn.add(`refs/remotes/${name}`);
|
|
2337
2877
|
}
|
|
2878
|
+
function createBaseReads(from, dependsOn, priority) {
|
|
2879
|
+
return {
|
|
2880
|
+
from,
|
|
2881
|
+
priority,
|
|
2882
|
+
dependsOn: new Set(dependsOn),
|
|
2883
|
+
tipRefs: /* @__PURE__ */ new Set(),
|
|
2884
|
+
pinnable: true,
|
|
2885
|
+
evictOnCheckout: false
|
|
2886
|
+
};
|
|
2887
|
+
}
|
|
2888
|
+
/**
|
|
2889
|
+
* Keys a merge target by `branch` and every option that shapes the answer. `keepSelfAtTip` is applied to the cached
|
|
2890
|
+
* answer instead, and `priority` doesn't change it
|
|
2891
|
+
*/
|
|
2892
|
+
function getMergeTargetCacheKey(branch, options) {
|
|
2893
|
+
return `${branch}\0${[
|
|
2894
|
+
options?.skipUserTarget ? "u" : "",
|
|
2895
|
+
options?.getAssociatedPullRequest != null ? "p" : "",
|
|
2896
|
+
options?.inferBase ? "i" : "",
|
|
2897
|
+
options?.defaultBranch === "remote" ? "r" : "",
|
|
2898
|
+
options?.getFallbackDefaultBranch != null ? "f" : ""
|
|
2899
|
+
].join("")}\0${options?.preferredTarget?.trim() ?? ""}`;
|
|
2900
|
+
}
|
|
2901
|
+
/** `read`'s answer, with a failure read as none; cancellation still rejects */
|
|
2902
|
+
async function tolerateRead(read) {
|
|
2903
|
+
if (read == null) return void 0;
|
|
2904
|
+
try {
|
|
2905
|
+
return await read();
|
|
2906
|
+
} catch (ex) {
|
|
2907
|
+
if (isCancellationError(ex)) throw ex;
|
|
2908
|
+
return;
|
|
2909
|
+
}
|
|
2910
|
+
}
|
|
2911
|
+
/** `value` as a literal in `getReflogEntries`' `grep`, a basic regular expression */
|
|
2912
|
+
function toReflogGrepLiteral(value) {
|
|
2913
|
+
return value.replace(/[\\.*[\]^$]/g, "\\$&");
|
|
2914
|
+
}
|
|
2915
|
+
/** The tip of the full ref name `fullName` in `records`, the cached ref list; `undefined` when it isn't listed */
|
|
2916
|
+
function getListedTip(records, fullName) {
|
|
2917
|
+
const record = findRefRecord(records, fullName);
|
|
2918
|
+
return record != null ? record.peeledObjectname || record.objectname : void 0;
|
|
2919
|
+
}
|
|
2338
2920
|
//#endregion
|
|
2339
2921
|
export { BranchesGitSubProvider };
|
|
2340
2922
|
|