@gitkraken/core-gitlens 0.16.2 → 0.16.4
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 +33 -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/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/watching/classifyChange.js +5 -2
- package/dist/git/watching/classifyChange.js.map +1 -1
- package/dist/git/watching/watchGroup.js +2 -1
- package/dist/git/watching/watchGroup.js.map +1 -1
- package/dist/git-cli/providers/branches.d.ts +151 -31
- package/dist/git-cli/providers/branches.js +918 -253
- 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/providers/issueSorts.d.ts +1 -0
- package/dist/plus/integrations/reads/drains.d.ts +1 -0
- 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
|
@@ -56,6 +56,26 @@ const branchActivityKeys = [
|
|
|
56
56
|
* read costs less
|
|
57
57
|
*/
|
|
58
58
|
const maxOffHistoryCommits = 64;
|
|
59
|
+
/** Reads a `%(ahead-behind:<base>)` field, `<ahead> <behind>`; `undefined` when it isn't one */
|
|
60
|
+
function parseAheadBehindField(field) {
|
|
61
|
+
const [ahead, behind] = (field ?? "").split(" ").map((n) => parseInt(n, 10));
|
|
62
|
+
if (isNaN(ahead) || isNaN(behind)) return void 0;
|
|
63
|
+
return {
|
|
64
|
+
ahead,
|
|
65
|
+
behind
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
/** The named refs as `for-each-ref` patterns, or `undefined` when they're too many to fit one command line */
|
|
69
|
+
function toRefPatterns(refs) {
|
|
70
|
+
return sum(refs, (r) => r.length + 1) < 15e3 ? [...refs] : void 0;
|
|
71
|
+
}
|
|
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
|
+
];
|
|
59
79
|
var BranchesGitSubProvider = class {
|
|
60
80
|
context;
|
|
61
81
|
git;
|
|
@@ -64,12 +84,22 @@ var BranchesGitSubProvider = class {
|
|
|
64
84
|
/** Ahead/behind counts by `<baseSha>...<tipSha>` range, from the per-tip fallback; a pair of commits always counts the same */
|
|
65
85
|
aheadBehindBySha = new LruMap(4096);
|
|
66
86
|
/**
|
|
67
|
-
* For a base that moved only forward,
|
|
68
|
-
*
|
|
69
|
-
*
|
|
70
|
-
*
|
|
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
|
|
71
94
|
*/
|
|
72
|
-
baseMovedForward =
|
|
95
|
+
baseMovedForward = new LruMap(64);
|
|
96
|
+
/**
|
|
97
|
+
* For a base that moved, the count of the commits its new tip lacks for each local branch a worktree has checked
|
|
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
|
|
101
|
+
*/
|
|
102
|
+
aheadOfMovedBase = new LruMap(64);
|
|
73
103
|
/**
|
|
74
104
|
* When each `commonPath\0branch\0key` was last stamped by this process. Bounded: a dropped entry only costs a
|
|
75
105
|
* read of the stored stamp, which still holds off a write under 5 minutes old
|
|
@@ -83,6 +113,11 @@ var BranchesGitSubProvider = class {
|
|
|
83
113
|
* it once
|
|
84
114
|
*/
|
|
85
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);
|
|
86
121
|
/** Bumped each time a key's created-from is forgotten, so a reflog read in flight across that doesn't store it */
|
|
87
122
|
_branchCreatedFromGenerations = new LruMap(1024);
|
|
88
123
|
/**
|
|
@@ -320,28 +355,17 @@ var BranchesGitSubProvider = class {
|
|
|
320
355
|
};
|
|
321
356
|
};
|
|
322
357
|
try {
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
if (!missing) {
|
|
335
|
-
mergeTarget = prTarget;
|
|
336
|
-
if (pr.stack == null) this.storeMergeTargetBranchName(repoPath, ref, prTarget, { skipInvalidation: ["branchOverviews"] });
|
|
337
|
-
}
|
|
338
|
-
}
|
|
339
|
-
}
|
|
340
|
-
}
|
|
341
|
-
mergeTarget ??= await this.getBaseBranchName(repoPath, ref, priorityOpts, cancellation);
|
|
342
|
-
mergeTarget ??= await this.getDefaultBranchName(repoPath, void 0, priorityOpts, cancellation);
|
|
343
|
-
if (mergeTarget == null) return void 0;
|
|
344
|
-
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;
|
|
345
369
|
return await this.cache.getBranchOverview(repoPath, `${ref}|${resolvedTarget}`, (_commonPath, _cacheable, signal) => fetchBody(resolvedTarget, signal ?? cancellation), { cancellation });
|
|
346
370
|
} catch (ex) {
|
|
347
371
|
scope?.error(ex);
|
|
@@ -1026,75 +1050,63 @@ var BranchesGitSubProvider = class {
|
|
|
1026
1050
|
update = mergeBase != null;
|
|
1027
1051
|
}
|
|
1028
1052
|
if (mergeBase != null) {
|
|
1029
|
-
const
|
|
1030
|
-
if (
|
|
1031
|
-
if (
|
|
1032
|
-
|
|
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;
|
|
1033
1061
|
}
|
|
1034
1062
|
}
|
|
1035
1063
|
} catch {}
|
|
1036
|
-
const reflog = await this.getBaseBranchFromReflog(commonPath, ref,
|
|
1037
|
-
|
|
1038
|
-
priority
|
|
1039
|
-
}, signal);
|
|
1040
|
-
if (reflog.branch != null) {
|
|
1041
|
-
this.storeBaseBranchName(commonPath, ref, reflog.branch, { skipInvalidation: ["branchOverviews", "baseBranchName"] });
|
|
1042
|
-
return reflog.branch;
|
|
1043
|
-
}
|
|
1064
|
+
const reflog = await this.getBaseBranchFromReflog(repoPath, commonPath, ref, priority, signal);
|
|
1065
|
+
if (reflog.branch != null) return reflog.branch;
|
|
1044
1066
|
if (reflog.failed) cacheable.invalidate();
|
|
1045
1067
|
}, cancellation);
|
|
1046
1068
|
}
|
|
1047
1069
|
/**
|
|
1048
|
-
* `
|
|
1049
|
-
*
|
|
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.
|
|
1050
1073
|
*/
|
|
1051
|
-
async getBaseBranchFromReflog(repoPath, ref,
|
|
1052
|
-
const priority = options?.priority;
|
|
1074
|
+
async getBaseBranchFromReflog(repoPath, commonPath, ref, priority, cancellation) {
|
|
1053
1075
|
const priorityOpts = priority != null ? { priority } : void 0;
|
|
1054
1076
|
try {
|
|
1055
|
-
const createdFrom = await this.getReflogCreatedFrom(
|
|
1077
|
+
const createdFrom = await this.getReflogCreatedFrom(commonPath, ref, priority, cancellation);
|
|
1056
1078
|
if (createdFrom == null) return {
|
|
1057
1079
|
branch: void 0,
|
|
1058
1080
|
failed: false
|
|
1059
1081
|
};
|
|
1060
1082
|
if (createdFrom !== "HEAD") {
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
}
|
|
1068
|
-
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);
|
|
1069
1089
|
if (name) return {
|
|
1070
1090
|
branch: name,
|
|
1071
1091
|
failed: false
|
|
1072
1092
|
};
|
|
1073
1093
|
}
|
|
1074
|
-
const
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
}, cancellation);
|
|
1078
|
-
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 {
|
|
1079
1097
|
branch: void 0,
|
|
1080
1098
|
failed: false
|
|
1081
1099
|
};
|
|
1082
|
-
const
|
|
1083
|
-
if (
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
name = await this.provider.refs.getSymbolicReferenceName(repoPath, name, priorityOpts);
|
|
1093
|
-
if (name) return {
|
|
1094
|
-
branch: name,
|
|
1095
|
-
failed: false
|
|
1096
|
-
};
|
|
1097
|
-
}
|
|
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
|
+
};
|
|
1098
1110
|
} catch (ex) {
|
|
1099
1111
|
if (isCancellationError(ex)) throw ex;
|
|
1100
1112
|
const msg = ex?.toString() ?? "";
|
|
@@ -1127,12 +1139,36 @@ var BranchesGitSubProvider = class {
|
|
|
1127
1139
|
return entries[0].message.match(/branch: Created from (.*)$/)?.[1];
|
|
1128
1140
|
}
|
|
1129
1141
|
/**
|
|
1130
|
-
* The
|
|
1131
|
-
*
|
|
1132
|
-
|
|
1133
|
-
|
|
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
|
|
1144
|
+
*/
|
|
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.
|
|
1134
1153
|
*/
|
|
1135
|
-
async
|
|
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) {
|
|
1136
1172
|
const key = this.getBranchActivityKey(repoPath, branch);
|
|
1137
1173
|
let name;
|
|
1138
1174
|
if (this._branchCreatedFrom.has(key)) name = this._branchCreatedFrom.get(key);
|
|
@@ -1142,20 +1178,73 @@ var BranchesGitSubProvider = class {
|
|
|
1142
1178
|
name = await this.getReflogCreatedFrom(repoPath, `refs/heads/${branch}`, void 0, cancellation);
|
|
1143
1179
|
} catch (ex) {
|
|
1144
1180
|
if (isCancellationError(ex)) throw ex;
|
|
1145
|
-
|
|
1181
|
+
reads.pinnable = false;
|
|
1182
|
+
return {
|
|
1183
|
+
name: void 0,
|
|
1184
|
+
fromHead: false
|
|
1185
|
+
};
|
|
1146
1186
|
}
|
|
1147
1187
|
if (cancellation?.aborted) throw new CancellationError();
|
|
1148
1188
|
if (this._branchCreatedFromGenerations.get(key) === generation) this._branchCreatedFrom.set(key, name);
|
|
1149
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) {
|
|
1150
1232
|
if (!name || name === "HEAD" || name.startsWith("-")) return void 0;
|
|
1151
1233
|
const records = await this.provider.refs.getRefs(repoPath, cancellation);
|
|
1152
1234
|
const fullNames = name.startsWith("refs/heads/") || name.startsWith("refs/remotes/") ? [name] : [`refs/heads/${name}`, `refs/remotes/${name}`];
|
|
1153
1235
|
let listed = false;
|
|
1154
|
-
for (const fullName of fullNames)
|
|
1155
|
-
|
|
1156
|
-
|
|
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
|
+
}
|
|
1157
1242
|
}
|
|
1158
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
|
+
}
|
|
1159
1248
|
if (await this.isSelfRef(repoPath, name, branch, cancellation)) return void 0;
|
|
1160
1249
|
return name;
|
|
1161
1250
|
}
|
|
@@ -1206,14 +1295,30 @@ var BranchesGitSubProvider = class {
|
|
|
1206
1295
|
}
|
|
1207
1296
|
async getAheadOfBase(repoPath, branch, options, cancellation) {
|
|
1208
1297
|
if (branch != null && isDetachedHead(branch)) branch = void 0;
|
|
1209
|
-
const
|
|
1210
|
-
const
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
if (
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
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);
|
|
1217
1322
|
}
|
|
1218
1323
|
async getAheadOfBaseAfterBaseMove(repoPath, branch, previous, moved, cancellation) {
|
|
1219
1324
|
const { head, base } = previous;
|
|
@@ -1246,17 +1351,24 @@ var BranchesGitSubProvider = class {
|
|
|
1246
1351
|
if (tip === oldTip || tip === newTip) return void 0;
|
|
1247
1352
|
}
|
|
1248
1353
|
if (await this.isShallowRepository(repoPath) !== false) return void 0;
|
|
1249
|
-
const
|
|
1354
|
+
const movedForward = await this.getMovedForward(repoPath, records, record.name, oldTip, newTip);
|
|
1250
1355
|
if (cancellation?.aborted) throw new CancellationError();
|
|
1251
|
-
if (!
|
|
1252
|
-
const
|
|
1253
|
-
|
|
1254
|
-
cancellation,
|
|
1255
|
-
errors: "ignore"
|
|
1256
|
-
}, "rev-list", "--count", head, "--not", newTip, ...base.forkPoint != null ? [base.forkPoint] : [], ...offHistory, "--");
|
|
1356
|
+
if (!movedForward) return void 0;
|
|
1357
|
+
const { offHistory, logged } = movedForward;
|
|
1358
|
+
const aheadOfNewTip = await this.getAheadOfMovedBase(repoPath, records, newTip, head);
|
|
1257
1359
|
if (cancellation?.aborted) throw new CancellationError();
|
|
1258
|
-
if (
|
|
1259
|
-
|
|
1360
|
+
if (aheadOfNewTip != null && aheadOfNewTip !== base.ahead) return void 0;
|
|
1361
|
+
const forkPoint = base.forkPoint;
|
|
1362
|
+
if (aheadOfNewTip == null || offHistory.length !== 0 || forkPoint != null && forkPoint !== oldTip && forkPoint !== newTip && !logged.has(forkPoint)) {
|
|
1363
|
+
const result = await this.git.run({
|
|
1364
|
+
cwd: repoPath,
|
|
1365
|
+
cancellation,
|
|
1366
|
+
errors: "ignore"
|
|
1367
|
+
}, "rev-list", "--count", head, "--not", newTip, ...forkPoint != null ? [forkPoint] : [], ...offHistory, "--");
|
|
1368
|
+
if (cancellation?.aborted) throw new CancellationError();
|
|
1369
|
+
if (result.completion.status !== "exited" || result.exitCode !== 0) return void 0;
|
|
1370
|
+
if (parseInt(result.stdout.trim(), 10) !== base.ahead) return void 0;
|
|
1371
|
+
}
|
|
1260
1372
|
return withDefaultBranch({
|
|
1261
1373
|
base: {
|
|
1262
1374
|
...base,
|
|
@@ -1291,27 +1403,64 @@ var BranchesGitSubProvider = class {
|
|
|
1291
1403
|
}
|
|
1292
1404
|
/**
|
|
1293
1405
|
* For `ref` (a full name in `records`, the cached ref list) moving from `oldTip` to `newTip`, the commits its reflog
|
|
1294
|
-
* reaches off `newTip`'s history when it moved only forward: `oldTip` and every value its reflog
|
|
1295
|
-
* `newTip`'s history. `--fork-point` reads every reflog entry, so one in between that shares
|
|
1296
|
-
* than `newTip` does (a branch's commit that landed and was force-pushed away) moves the fork
|
|
1297
|
-
* stands; an older one off `newTip`'s history may hold such a commit too, which each
|
|
1298
|
-
* Without a reflog it reads only the tip. `undefined` when it didn't move only forward,
|
|
1406
|
+
* holds and those it reaches off `newTip`'s history, when it moved only forward: `oldTip` and every value its reflog
|
|
1407
|
+
* took after it are in `newTip`'s history. `--fork-point` reads every reflog entry, so one in between that shares
|
|
1408
|
+
* more of HEAD's history than `newTip` does (a branch's commit that landed and was force-pushed away) moves the fork
|
|
1409
|
+
* point while the count stands; an older one off `newTip`'s history may hold such a commit too, which each
|
|
1410
|
+
* worktree's count checks for. Without a reflog it reads only the tip. `undefined` when it didn't move only forward,
|
|
1411
|
+
* or git can't say.
|
|
1299
1412
|
*/
|
|
1300
1413
|
async getMovedForward(repoPath, records, ref, oldTip, newTip) {
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
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);
|
|
1305
1434
|
}
|
|
1306
|
-
const
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
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;
|
|
1311
1463
|
}
|
|
1312
|
-
const offHistory = await check;
|
|
1313
|
-
if (offHistory == null && checks.get(key) === check) checks.delete(key);
|
|
1314
|
-
return offHistory || void 0;
|
|
1315
1464
|
}
|
|
1316
1465
|
/**
|
|
1317
1466
|
* Reads {@link getMovedForward} from git, in `commonPath`, which holds the refs, their reflogs and the objects for
|
|
@@ -1356,65 +1505,444 @@ var BranchesGitSubProvider = class {
|
|
|
1356
1505
|
const off = result.stdout.split("\n").filter((sha) => sha.length > 0);
|
|
1357
1506
|
if (off.length > maxOffHistoryCommits) return false;
|
|
1358
1507
|
if (off.some((sha) => since.has(sha))) return false;
|
|
1359
|
-
return
|
|
1508
|
+
return {
|
|
1509
|
+
offHistory: off,
|
|
1510
|
+
logged
|
|
1511
|
+
};
|
|
1512
|
+
} catch {
|
|
1513
|
+
return;
|
|
1514
|
+
}
|
|
1515
|
+
}
|
|
1516
|
+
/**
|
|
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.
|
|
1526
|
+
*/
|
|
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);
|
|
1545
|
+
}
|
|
1546
|
+
/**
|
|
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.
|
|
1550
|
+
*/
|
|
1551
|
+
async readAheadOfMovedBase(commonPath, checkedOut, newTip) {
|
|
1552
|
+
try {
|
|
1553
|
+
if (!await this.git.supports("git:for-each-ref:ahead-behind")) return void 0;
|
|
1554
|
+
const patterns = toRefPatterns(checkedOut) ?? ["refs/heads"];
|
|
1555
|
+
const result = await this.git.run({
|
|
1556
|
+
cwd: commonPath,
|
|
1557
|
+
errors: "ignore"
|
|
1558
|
+
}, "for-each-ref", `--format=%(refname)%09%(objectname)%09%(ahead-behind:${newTip})`, ...patterns);
|
|
1559
|
+
this.provider.maintenance?.request(commonPath, "commit-graph");
|
|
1560
|
+
if (result.completion.status !== "exited" || result.exitCode !== 0) return void 0;
|
|
1561
|
+
const aheadByTip = /* @__PURE__ */ new Map();
|
|
1562
|
+
for (const line of result.stdout.split("\n")) {
|
|
1563
|
+
if (!line) continue;
|
|
1564
|
+
const [ref, sha, field] = line.split(" ");
|
|
1565
|
+
if (!ref || !sha) return void 0;
|
|
1566
|
+
if (!checkedOut.has(ref)) continue;
|
|
1567
|
+
const counted = parseAheadBehindField(field);
|
|
1568
|
+
if (counted == null) continue;
|
|
1569
|
+
aheadByTip.set(sha, counted.ahead);
|
|
1570
|
+
}
|
|
1571
|
+
return aheadByTip;
|
|
1360
1572
|
} catch {
|
|
1361
1573
|
return;
|
|
1362
1574
|
}
|
|
1363
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
|
+
}
|
|
1594
|
+
/**
|
|
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
|
|
1597
|
+
*/
|
|
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
|
+
}
|
|
1364
1706
|
/**
|
|
1365
|
-
*
|
|
1366
|
-
*
|
|
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.
|
|
1367
1712
|
*/
|
|
1368
|
-
async
|
|
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
|
+
}
|
|
1369
1725
|
let target;
|
|
1370
1726
|
let tier = "default";
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
if (target != null) {
|
|
1375
|
-
tier = candidate.tier;
|
|
1376
|
-
break;
|
|
1377
|
-
}
|
|
1727
|
+
if (branch != null) {
|
|
1728
|
+
const stored = await this.getStoredBaseTarget(repoPath, branch, remoteBranch, options, reads, cancellation);
|
|
1729
|
+
if (stored != null) ({target, tier} = stored);
|
|
1378
1730
|
}
|
|
1379
1731
|
if (target == null) {
|
|
1380
|
-
const defaultBranch = await this.
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
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;
|
|
1384
1754
|
if (createdFrom != null) {
|
|
1385
|
-
const created = await this.getCreatedFromForkTarget(repoPath, branch, createdFrom, defaultBranch,
|
|
1755
|
+
const created = await this.getCreatedFromForkTarget(repoPath, branch, createdFrom, defaultBranch, reads, cancellation);
|
|
1386
1756
|
if (created != null) ({target, tier} = created);
|
|
1387
|
-
} else target = await this.getUpstreamNearestForkTarget(repoPath, branch, defaultBranch,
|
|
1757
|
+
} else target = await this.getUpstreamNearestForkTarget(repoPath, branch, defaultBranch, reads, cancellation);
|
|
1388
1758
|
}
|
|
1389
1759
|
if (cancellation?.aborted) throw new CancellationError();
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
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();
|
|
1394
1841
|
} catch (ex) {
|
|
1395
1842
|
if (isCancellationError(ex)) throw ex;
|
|
1843
|
+
if (reads != null) reads.pinnable = false;
|
|
1844
|
+
return;
|
|
1396
1845
|
}
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
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;
|
|
1402
1870
|
}
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
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;
|
|
1409
1936
|
}
|
|
1410
1937
|
/**
|
|
1411
1938
|
* The base for a branch created from `createdFrom`: that branch, unless the default branch forked strictly nearer
|
|
1412
1939
|
* HEAD, as it does once `createdFrom` merged into it and the branch was rebased onto it. A stacked branch's
|
|
1413
1940
|
* unmerged parent forked nearer, and stays its base. `undefined` when neither is usable.
|
|
1414
1941
|
*/
|
|
1415
|
-
async getCreatedFromForkTarget(repoPath, branch, createdFrom, defaultBranch,
|
|
1416
|
-
const
|
|
1417
|
-
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)]);
|
|
1418
1946
|
if (created == null) return fallback != null ? {
|
|
1419
1947
|
target: fallback,
|
|
1420
1948
|
tier: "default"
|
|
@@ -1425,7 +1953,7 @@ var BranchesGitSubProvider = class {
|
|
|
1425
1953
|
};
|
|
1426
1954
|
return {
|
|
1427
1955
|
target: created,
|
|
1428
|
-
tier:
|
|
1956
|
+
tier: createdFrom.tier
|
|
1429
1957
|
};
|
|
1430
1958
|
}
|
|
1431
1959
|
/** Counts HEAD's commits past `target` for {@link getAheadOfBase} */
|
|
@@ -1451,8 +1979,8 @@ var BranchesGitSubProvider = class {
|
|
|
1451
1979
|
};
|
|
1452
1980
|
}
|
|
1453
1981
|
/** Counts the default branch's own commits past its upstream (see {@link getSelfUpstreamTarget}) for {@link getAheadOfBase} */
|
|
1454
|
-
async measureAheadOfSelfUpstream(repoPath,
|
|
1455
|
-
const
|
|
1982
|
+
async measureAheadOfSelfUpstream(repoPath, upstream, reads, cancellation) {
|
|
1983
|
+
const { dependsOn } = reads;
|
|
1456
1984
|
if (cancellation?.aborted) throw new CancellationError();
|
|
1457
1985
|
if (upstream == null) return {
|
|
1458
1986
|
base: void 0,
|
|
@@ -1470,7 +1998,7 @@ var BranchesGitSubProvider = class {
|
|
|
1470
1998
|
reason: "self",
|
|
1471
1999
|
dependsOn: [...dependsOn]
|
|
1472
2000
|
};
|
|
1473
|
-
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);
|
|
1474
2002
|
if (cancellation?.aborted) throw new CancellationError();
|
|
1475
2003
|
return {
|
|
1476
2004
|
base: {
|
|
@@ -1511,8 +2039,7 @@ var BranchesGitSubProvider = class {
|
|
|
1511
2039
|
if (refs?.size === 0) return counts;
|
|
1512
2040
|
let patterns;
|
|
1513
2041
|
if (refs == null) patterns = options?.remotes ? ["refs/heads", "refs/remotes"] : ["refs/heads"];
|
|
1514
|
-
else
|
|
1515
|
-
else patterns = ["refs/heads", "refs/remotes"];
|
|
2042
|
+
else patterns = toRefPatterns(refs) ?? ["refs/heads", "refs/remotes"];
|
|
1516
2043
|
if (!base.includes(")") && await this.git.supports("git:for-each-ref:ahead-behind")) {
|
|
1517
2044
|
const result = await this.git.run({
|
|
1518
2045
|
cwd: repoPath,
|
|
@@ -1524,12 +2051,9 @@ var BranchesGitSubProvider = class {
|
|
|
1524
2051
|
if (!line) continue;
|
|
1525
2052
|
const [ref, symref, counted] = line.split(" ");
|
|
1526
2053
|
if (symref || refs != null && !refs.has(ref)) continue;
|
|
1527
|
-
const
|
|
1528
|
-
if (
|
|
1529
|
-
counts.set(ref,
|
|
1530
|
-
ahead,
|
|
1531
|
-
behind
|
|
1532
|
-
});
|
|
2054
|
+
const aheadBehind = parseAheadBehindField(counted);
|
|
2055
|
+
if (aheadBehind == null) throw new Error(`Unexpected output counting the commits ahead of and behind '${base}': ${line}`);
|
|
2056
|
+
counts.set(ref, aheadBehind);
|
|
1533
2057
|
}
|
|
1534
2058
|
return counts;
|
|
1535
2059
|
}
|
|
@@ -1664,42 +2188,28 @@ var BranchesGitSubProvider = class {
|
|
|
1664
2188
|
return [];
|
|
1665
2189
|
}
|
|
1666
2190
|
}
|
|
1667
|
-
/** Whether `ref` names `branch` itself (see {@link isSelfRef}) and sits at HEAD's commit; tips are read only after the name matched */
|
|
1668
|
-
async isSelfAtHead(repoPath, ref, branch, cancellation) {
|
|
1669
|
-
if (!await this.isSelfRef(repoPath, ref, branch, cancellation)) return false;
|
|
1670
|
-
const [refSha, headSha] = await Promise.all([this.provider.refs.validateReference(repoPath, ref, void 0, cancellation), this.provider.refs.validateReference(repoPath, "HEAD", void 0, cancellation)]);
|
|
1671
|
-
return refSha != null && refSha === headSha;
|
|
1672
|
-
}
|
|
1673
2191
|
/**
|
|
1674
|
-
*
|
|
1675
|
-
*
|
|
1676
|
-
* 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
|
|
1677
2194
|
*/
|
|
1678
|
-
async
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
"
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
];
|
|
1697
|
-
const candidates = [];
|
|
1698
|
-
for (const [i, ref] of reads.entries()) if (ref && !candidates.some((c) => c.ref === ref)) candidates.push({
|
|
1699
|
-
ref,
|
|
1700
|
-
tier: tiers[i]
|
|
1701
|
-
});
|
|
1702
|
-
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))];
|
|
1703
2213
|
}
|
|
1704
2214
|
/**
|
|
1705
2215
|
* The repo's default branch from local refs only, never the network: the `HEAD` symref of the `upstream` remote,
|
|
@@ -1713,12 +2223,7 @@ var BranchesGitSubProvider = class {
|
|
|
1713
2223
|
records ??= await this.provider.refs.getRefs(repoPath, cancellation);
|
|
1714
2224
|
return await this.getRefTip(repoPath, records, ref, cancellation) != null;
|
|
1715
2225
|
};
|
|
1716
|
-
const
|
|
1717
|
-
const preferred = new Set([
|
|
1718
|
-
remotes.find((r) => r === "upstream"),
|
|
1719
|
-
remotes.find((r) => r === "origin"),
|
|
1720
|
-
remotes.at(0)
|
|
1721
|
-
].filter((r) => r != null));
|
|
2226
|
+
const preferred = await this.getPreferredRemotes(repoPath, cancellation);
|
|
1722
2227
|
for (const remote of preferred) {
|
|
1723
2228
|
dependsOn.add(`refs/remotes/${remote}/HEAD`);
|
|
1724
2229
|
const symref = await this.getDefaultBranchName(repoPath, remote, { local: true }, cancellation);
|
|
@@ -1757,7 +2262,7 @@ var BranchesGitSubProvider = class {
|
|
|
1757
2262
|
* remote and ref lists; `undefined` when there is no `upstream` remote, it lacks the branch, or the base is that
|
|
1758
2263
|
* ref itself.
|
|
1759
2264
|
*/
|
|
1760
|
-
async getUpstreamContender(repoPath, base,
|
|
2265
|
+
async getUpstreamContender(repoPath, base, reads, cancellation) {
|
|
1761
2266
|
if (!base) return void 0;
|
|
1762
2267
|
const remotes = await this.getRemoteNames(repoPath, cancellation);
|
|
1763
2268
|
if (!remotes.includes("upstream")) return void 0;
|
|
@@ -1774,33 +2279,37 @@ var BranchesGitSubProvider = class {
|
|
|
1774
2279
|
}
|
|
1775
2280
|
const contender = `upstream/${name}`;
|
|
1776
2281
|
const remoteRef = `refs/remotes/${contender}`;
|
|
1777
|
-
if (contender === base || remoteRef === base
|
|
1778
|
-
|
|
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);
|
|
1779
2288
|
return contender;
|
|
1780
2289
|
}
|
|
1781
2290
|
/** {@link getNearestForkTarget} with the `upstream` remote's copy of `candidate` as its contender */
|
|
1782
|
-
async getUpstreamNearestForkTarget(repoPath, branch, candidate,
|
|
1783
|
-
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) });
|
|
1784
2293
|
}
|
|
1785
2294
|
/**
|
|
1786
|
-
* A candidate base as the ref
|
|
1787
|
-
* 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
|
|
1788
2297
|
* `contender` other than the tracking ref is taken only when it forked strictly nearer than both, so a tie keeps
|
|
1789
|
-
* the candidate's own pick. A candidate none forks from is used as-is
|
|
1790
|
-
* 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.
|
|
1791
2300
|
*/
|
|
1792
|
-
async getNearestForkTarget(repoPath, branch, candidate,
|
|
2301
|
+
async getNearestForkTarget(repoPath, branch, candidate, reads, cancellation, options) {
|
|
1793
2302
|
if (!candidate || candidate.startsWith("-")) return void 0;
|
|
1794
2303
|
if (candidate === branch || candidate === `refs/heads/${branch}`) return void 0;
|
|
1795
|
-
addRefDependencies(dependsOn, candidate);
|
|
2304
|
+
addRefDependencies(reads.dependsOn, candidate);
|
|
1796
2305
|
const upstreamName = this.getUpstreamName(repoPath, candidate, void 0, cancellation);
|
|
1797
2306
|
const getUpstreamFork = async () => {
|
|
1798
2307
|
const upstream = await upstreamName;
|
|
1799
2308
|
if (!upstream || upstream.startsWith("-")) return void 0;
|
|
1800
|
-
addRefDependencies(dependsOn, upstream);
|
|
2309
|
+
addRefDependencies(reads.dependsOn, upstream);
|
|
1801
2310
|
return {
|
|
1802
2311
|
ref: upstream,
|
|
1803
|
-
mergeBase: await this.getForkPoint(repoPath, upstream, cancellation)
|
|
2312
|
+
mergeBase: await this.getForkPoint(repoPath, upstream, reads, cancellation)
|
|
1804
2313
|
};
|
|
1805
2314
|
};
|
|
1806
2315
|
const contender = options?.contender;
|
|
@@ -1813,10 +2322,15 @@ var BranchesGitSubProvider = class {
|
|
|
1813
2322
|
const upstream = await upstreamName;
|
|
1814
2323
|
if (upstream && tip === findRefTip(records, upstream)) return void 0;
|
|
1815
2324
|
}
|
|
1816
|
-
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
|
|
1817
2331
|
};
|
|
1818
2332
|
const [candidateMergeBase, upstreamFork, contenderMergeBase] = await Promise.all([
|
|
1819
|
-
this.getForkPoint(repoPath, candidate, cancellation),
|
|
2333
|
+
this.getForkPoint(repoPath, candidate, candidateReads, cancellation),
|
|
1820
2334
|
getUpstreamFork(),
|
|
1821
2335
|
getContenderMergeBase()
|
|
1822
2336
|
]);
|
|
@@ -1835,18 +2349,33 @@ var BranchesGitSubProvider = class {
|
|
|
1835
2349
|
ref: contender,
|
|
1836
2350
|
mergeBase: contenderMergeBase
|
|
1837
2351
|
};
|
|
1838
|
-
if (nearest != null)
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
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 {
|
|
1843
2371
|
ref: candidate,
|
|
1844
2372
|
forkPoint: void 0
|
|
1845
|
-
}
|
|
2373
|
+
};
|
|
1846
2374
|
}
|
|
1847
2375
|
/**
|
|
1848
2376
|
* `<name>@{u}`, without asking git when the cached ref list shows `name` can't have one: a failed lookup isn't
|
|
1849
|
-
* 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.
|
|
1850
2379
|
*/
|
|
1851
2380
|
async getUpstreamName(repoPath, name, options, cancellation) {
|
|
1852
2381
|
if (!mayHaveUpstream(await this.provider.refs.getRefs(repoPath, cancellation), name)) return void 0;
|
|
@@ -1857,7 +2386,8 @@ var BranchesGitSubProvider = class {
|
|
|
1857
2386
|
* when an `upstream` remote has it, else `<branch>@{u}`. Read from the cached ref and branch lists; only a
|
|
1858
2387
|
* `<branch>` that isn't checked out spawns, for its `@{u}`. `undefined` when there is neither, or its ref is gone.
|
|
1859
2388
|
*/
|
|
1860
|
-
async getSelfUpstreamTarget(repoPath, branch,
|
|
2389
|
+
async getSelfUpstreamTarget(repoPath, branch, reads, cancellation) {
|
|
2390
|
+
const { dependsOn } = reads;
|
|
1861
2391
|
const records = await this.provider.refs.getRefs(repoPath, cancellation);
|
|
1862
2392
|
let upstream;
|
|
1863
2393
|
if ((await this.getRemoteNames(repoPath, cancellation)).includes("upstream")) {
|
|
@@ -1893,9 +2423,36 @@ var BranchesGitSubProvider = class {
|
|
|
1893
2423
|
if (records.length) return findRefTip(records, name);
|
|
1894
2424
|
return this.provider.refs.validateReference(repoPath, name, void 0, cancellation);
|
|
1895
2425
|
}
|
|
1896
|
-
/**
|
|
1897
|
-
async getForkPoint(repoPath, ref, cancellation) {
|
|
1898
|
-
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;
|
|
1899
2456
|
}
|
|
1900
2457
|
/**
|
|
1901
2458
|
* The stored merge target — the user's pick, else the one detected from its PR — skipping any whose branch git
|
|
@@ -1942,6 +2499,7 @@ var BranchesGitSubProvider = class {
|
|
|
1942
2499
|
forgetBranchCreatedFrom(repoPath, name) {
|
|
1943
2500
|
const key = this.getBranchActivityKey(repoPath, name);
|
|
1944
2501
|
this._branchCreatedFrom.delete(key);
|
|
2502
|
+
this._branchCheckedOutFrom.delete(key);
|
|
1945
2503
|
this._branchCreatedFromGenerations.set(key, (this._branchCreatedFromGenerations.get(key) ?? 0) + 1);
|
|
1946
2504
|
}
|
|
1947
2505
|
/** Forgets this process's throttle marks for `name`, so its next activity re-reads the stored stamps */
|
|
@@ -2086,32 +2644,66 @@ var BranchesGitSubProvider = class {
|
|
|
2086
2644
|
this.context.hooks?.repository?.onChanged?.(repoPath, ["starred"]);
|
|
2087
2645
|
}
|
|
2088
2646
|
};
|
|
2089
|
-
__decorate([debug()], BranchesGitSubProvider.prototype, "getBranch", null);
|
|
2090
|
-
__decorate([debug({
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
__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);
|
|
2095
2659
|
__decorate([debug()], BranchesGitSubProvider.prototype, "setRemoteHeadIfMissing", null);
|
|
2096
2660
|
__decorate([debug()], BranchesGitSubProvider.prototype, "createBranch", null);
|
|
2097
2661
|
__decorate([debug()], BranchesGitSubProvider.prototype, "deleteLocalBranch", null);
|
|
2098
2662
|
__decorate([debug()], BranchesGitSubProvider.prototype, "deleteRemoteBranch", null);
|
|
2099
|
-
__decorate([debug()], BranchesGitSubProvider.prototype, "getBranchMergedStatus", null);
|
|
2100
|
-
__decorate([debug()], BranchesGitSubProvider.prototype, "getLocalBranchByUpstream", null);
|
|
2101
|
-
__decorate([trace({
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
__decorate([debug({
|
|
2106
|
-
__decorate([
|
|
2107
|
-
__decorate([
|
|
2108
|
-
__decorate([debug({
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
__decorate([
|
|
2113
|
-
__decorate([trace({
|
|
2114
|
-
__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);
|
|
2115
2707
|
__decorate([trace()], BranchesGitSubProvider.prototype, "onCurrentBranchAccessed", null);
|
|
2116
2708
|
__decorate([trace()], BranchesGitSubProvider.prototype, "onCurrentBranchModified", null);
|
|
2117
2709
|
__decorate([trace()], BranchesGitSubProvider.prototype, "onCurrentBranchAgentActivity", null);
|
|
@@ -2177,15 +2769,46 @@ function getRefRecordsByName(records) {
|
|
|
2177
2769
|
}
|
|
2178
2770
|
return byName;
|
|
2179
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
|
+
}
|
|
2180
2804
|
/** The record for the full ref name `fullName` in `records`, the cached ref list; no short-name resolution */
|
|
2181
2805
|
function findRefRecord(records, fullName) {
|
|
2182
2806
|
return getRefRecordsByName(records).get(fullName);
|
|
2183
2807
|
}
|
|
2184
2808
|
function toPushTargetRef(records, ref) {
|
|
2185
|
-
const record = findRefRecord(records, ref);
|
|
2186
2809
|
return {
|
|
2187
2810
|
ref,
|
|
2188
|
-
sha:
|
|
2811
|
+
sha: getListedTip(records, ref)
|
|
2189
2812
|
};
|
|
2190
2813
|
}
|
|
2191
2814
|
/**
|
|
@@ -2252,6 +2875,48 @@ function addRefDependencies(dependsOn, name) {
|
|
|
2252
2875
|
dependsOn.add(`refs/heads/${name}`);
|
|
2253
2876
|
dependsOn.add(`refs/remotes/${name}`);
|
|
2254
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
|
+
}
|
|
2255
2920
|
//#endregion
|
|
2256
2921
|
export { BranchesGitSubProvider };
|
|
2257
2922
|
|