@gitkraken/core-gitlens 0.15.4 → 0.15.6

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.
Files changed (61) hide show
  1. package/CHANGELOG.md +25 -1
  2. package/dist/git/cache.d.ts +9 -12
  3. package/dist/git/cache.js +34 -16
  4. package/dist/git/cache.js.map +1 -1
  5. package/dist/git/context.d.ts +19 -2
  6. package/dist/git/context.js.map +1 -1
  7. package/dist/git/providers/provider.d.ts +7 -0
  8. package/dist/git/service.d.ts +5 -1
  9. package/dist/git/service.js +8 -4
  10. package/dist/git/service.js.map +1 -1
  11. package/dist/git/watching/changeEvent.d.ts +25 -1
  12. package/dist/git/watching/changeEvent.js +36 -2
  13. package/dist/git/watching/changeEvent.js.map +1 -1
  14. package/dist/git/watching/configSections.d.ts +35 -0
  15. package/dist/git/watching/configSections.js +210 -0
  16. package/dist/git/watching/configSections.js.map +1 -0
  17. package/dist/git/watching/watchGroup.d.ts +36 -3
  18. package/dist/git/watching/watchGroup.js +107 -2
  19. package/dist/git/watching/watchGroup.js.map +1 -1
  20. package/dist/git/watching/watchService.d.ts +14 -3
  21. package/dist/git/watching/watchService.js +10 -7
  22. package/dist/git/watching/watchService.js.map +1 -1
  23. package/dist/git/watching/watchSession.d.ts +52 -21
  24. package/dist/git/watching/watchSession.js +164 -42
  25. package/dist/git/watching/watchSession.js.map +1 -1
  26. package/dist/git/watching/worktreeSummary.d.ts +9 -7
  27. package/dist/git/watching/worktreeSummary.js +21 -10
  28. package/dist/git/watching/worktreeSummary.js.map +1 -1
  29. package/dist/git-cli/cliGitProvider.d.ts +6 -1
  30. package/dist/git-cli/cliGitProvider.js +45 -5
  31. package/dist/git-cli/cliGitProvider.js.map +1 -1
  32. package/dist/git-cli/providers/branches.d.ts +2 -2
  33. package/dist/git-cli/providers/branches.js +17 -15
  34. package/dist/git-cli/providers/branches.js.map +1 -1
  35. package/dist/git-cli/providers/config.d.ts +13 -0
  36. package/dist/git-cli/providers/config.js +38 -1
  37. package/dist/git-cli/providers/config.js.map +1 -1
  38. package/dist/git-cli/providers/operations.d.ts +35 -1
  39. package/dist/git-cli/providers/operations.js +175 -16
  40. package/dist/git-cli/providers/operations.js.map +1 -1
  41. package/dist/git-cli/providers/stash.d.ts +1 -0
  42. package/dist/git-cli/providers/stash.js +9 -3
  43. package/dist/git-cli/providers/stash.js.map +1 -1
  44. package/dist/git-cli/providers/worktrees.js +11 -4
  45. package/dist/git-cli/providers/worktrees.js.map +1 -1
  46. package/dist/git-cli/service.js +5 -1
  47. package/dist/git-cli/service.js.map +1 -1
  48. package/dist/plus/integrations/collectionMetadata.js +4 -2
  49. package/dist/plus/integrations/collectionMetadata.js.map +1 -1
  50. package/dist/plus/integrations/providers/issueSorts.d.ts +0 -1
  51. package/dist/plus/integrations/providers/providerErrors.d.ts +22 -0
  52. package/dist/plus/integrations/providers/providerErrors.js +43 -2
  53. package/dist/plus/integrations/providers/providerErrors.js.map +1 -1
  54. package/dist/plus/integrations/providers/providersApi.js +12 -8
  55. package/dist/plus/integrations/providers/providersApi.js.map +1 -1
  56. package/dist/plus/integrations/reads/drains.d.ts +0 -1
  57. package/dist/plus/integrations/results.d.ts +13 -3
  58. package/dist/plus/integrations/results.js +12 -2
  59. package/dist/plus/integrations/results.js.map +1 -1
  60. package/docs/integrations.md +14 -0
  61. package/package.json +1 -1
@@ -2,10 +2,12 @@ import { chunkByStringLength } from "../../utils/iterable.js";
2
2
  import { Logger as _logger } from "../../utils/logger.js";
3
3
  import { getScopedLogger } from "../../utils/logger.scoped.js";
4
4
  import { debug } from "../../utils/decorators/log.js";
5
+ import { joinUriPath } from "../../utils/uri.js";
5
6
  import { normalizePath, splitPath } from "../../utils/path.js";
6
7
  import { isBranchReference } from "../../git/utils/reference.utils.js";
7
8
  import __decorate from "../../_virtual/@oxc-project+runtime/helpers/esm/decorate.js";
8
9
  import { CheckoutError, CherryPickError, CommitError, FetchError, MergeError, PullError, PushError, RebaseError, ResetError, RevertError, SigningError } from "../../git/errors.js";
10
+ import { getRefChangeKind } from "../../git/watching/changeEvent.js";
9
11
  import { mayWriteTracking } from "../exec/commandClassifier.js";
10
12
  import { GitErrors, classifySigningError, getGitCommandError, gitConfigsPull, inferSigningFormatFromError, isRunHookFailure, maxGitCliLength } from "../exec/git.js";
11
13
  import { resolveBranchRemote } from "./branchRemote.js";
@@ -13,6 +15,51 @@ import { parseGitBoolean } from "./config.js";
13
15
  import { sequentialize } from "../../utils/decorators/sequentialize.js";
14
16
  import * as l10n from "@vscode/l10n";
15
17
  //#region ../git-cli/src/providers/operations.ts
18
+ /** The ref kinds a fetch can move, in the order its announcement lists them */
19
+ const fetchRefChangeKinds = [
20
+ "remotes",
21
+ "heads",
22
+ "tags",
23
+ "stash"
24
+ ];
25
+ /** The destinations a fetch's snapshot covers when the remotes' configured refspecs can't be read */
26
+ const broadFetchDestinations = ["refs/heads", "refs/remotes"];
27
+ /**
28
+ * The ref a fetch refspec stores into, qualified as git's fetch does — `heads/`, `tags/` and `remotes/` get `refs/`,
29
+ * and any other unqualified name is a local branch — or `undefined` when it stores nothing (no `:`, or nothing after
30
+ * it). A glob destination keeps its glob.
31
+ */
32
+ function getRefspecDestination(refspec) {
33
+ const stripped = refspec.startsWith("+") ? refspec.slice(1) : refspec;
34
+ const colonIndex = stripped.indexOf(":");
35
+ if (colonIndex === -1) return void 0;
36
+ const destination = stripped.slice(colonIndex + 1);
37
+ if (!destination) return void 0;
38
+ if (destination.startsWith("refs/")) return destination;
39
+ return /^(?:heads|tags|remotes)\//.test(destination) ? `refs/${destination}` : `refs/heads/${destination}`;
40
+ }
41
+ /**
42
+ * The for-each-ref patterns covering `destinations` (the refs a fetch may store into) and tags, which a fetch follows
43
+ * into what it fetched. A pattern's `*` doesn't match across `/`, so a glob destination is widened to the directory
44
+ * above its first glob.
45
+ */
46
+ function getFetchSnapshotPatterns(destinations) {
47
+ const patterns = ["refs/tags"];
48
+ for (let destination of destinations) {
49
+ const globIndex = destination.search(/[*?[]/);
50
+ if (globIndex !== -1) destination = destination.substring(0, destination.lastIndexOf("/", globIndex));
51
+ if (patterns.some((p) => destination === p || destination.startsWith(`${p}/`))) continue;
52
+ patterns.push(destination);
53
+ }
54
+ return patterns;
55
+ }
56
+ /** The refs created, moved, retargeted or deleted between two snapshots */
57
+ function getMovedRefs(before, after) {
58
+ const moved = [];
59
+ for (const [ref, sha] of after) if (before.get(ref) !== sha) moved.push(ref);
60
+ for (const ref of before.keys()) if (!after.has(ref)) moved.push(ref);
61
+ return moved;
62
+ }
16
63
  var OperationsGitSubProvider = class {
17
64
  context;
18
65
  git;
@@ -53,7 +100,7 @@ var OperationsGitSubProvider = class {
53
100
  }
54
101
  }, ex));
55
102
  } finally {
56
- this.announceHeadChanged(repoPath, Boolean(options?.createBranch) && mayWriteTracking(ref, options?.noTracking));
103
+ this.announceHeadChanged(repoPath, options?.createBranch ? `refs/heads/${options.createBranch}` : void 0, Boolean(options?.createBranch) && mayWriteTracking(ref, options?.noTracking));
57
104
  }
58
105
  }
59
106
  async cherryPick(repoPath, revs, options, runOptions) {
@@ -111,6 +158,7 @@ var OperationsGitSubProvider = class {
111
158
  const sign = this.context.config?.signing?.enabled === true;
112
159
  if (sign) params.push("-S");
113
160
  params.push("-F", "-");
161
+ const headBranchBefore = await this.readHeadBranchRef(repoPath);
114
162
  try {
115
163
  try {
116
164
  await this.git.run({
@@ -121,7 +169,8 @@ var OperationsGitSubProvider = class {
121
169
  ...runOptions
122
170
  }, ...params);
123
171
  } finally {
124
- this.announceHeadChanged(repoPath);
172
+ const headBranch = await this.readHeadBranchRef(repoPath);
173
+ this.announceHeadChanged(repoPath, headBranch === headBranchBefore ? headBranch : void 0);
125
174
  }
126
175
  if (sign) {
127
176
  let format;
@@ -148,10 +197,16 @@ var OperationsGitSubProvider = class {
148
197
  const { branch, ...opts } = options ?? {};
149
198
  let ran = false;
150
199
  let fetched;
200
+ let snapshot;
151
201
  try {
152
202
  if (isBranchReference(branch)) {
153
203
  const resolved = await resolveBranchRemote(this.provider, repoPath, branch, runOptions?.cancellation);
154
204
  if (resolved.remote == null) return;
205
+ const patterns = await this.getFetchSnapshotPatterns(repoPath, resolved.remote, options?.pull ? [`${resolved.upstream}:${resolved.name}`] : void 0);
206
+ snapshot = {
207
+ patterns,
208
+ before: await this.snapshotRefs(repoPath, patterns)
209
+ };
155
210
  ran = true;
156
211
  await this.fetchCore(repoPath, {
157
212
  branch: resolved.name,
@@ -161,6 +216,11 @@ var OperationsGitSubProvider = class {
161
216
  preserveFetchHead: options?.preserveFetchHead
162
217
  }, runOptions);
163
218
  } else {
219
+ const patterns = await this.getFetchSnapshotPatterns(repoPath, opts.remote, opts.refspecs);
220
+ snapshot = {
221
+ patterns,
222
+ before: await this.snapshotRefs(repoPath, patterns)
223
+ };
164
224
  ran = true;
165
225
  await this.fetchCore(repoPath, opts, runOptions);
166
226
  if (!opts.refspecs?.length) fetched = opts.remote != null ? { remote: opts.remote } : opts.all ? "all" : "default";
@@ -169,10 +229,7 @@ var OperationsGitSubProvider = class {
169
229
  scope?.error(ex);
170
230
  throw ex;
171
231
  } finally {
172
- if (ran) {
173
- this.context.hooks?.cache?.onReset?.(repoPath, "branches", "tags");
174
- this.context.hooks?.repository?.onChanged?.(repoPath, this.getFetchChanges(branch, options));
175
- }
232
+ if (ran) await this.announceFetch(repoPath, snapshot, () => this.getFetchChanges(branch, options));
176
233
  if (fetched != null) this.followRemoteHeads(repoPath, fetched);
177
234
  }
178
235
  }
@@ -213,18 +270,97 @@ var OperationsGitSubProvider = class {
213
270
  const remotes = await this.provider.remotes.getRemotes(repoPath);
214
271
  return remotes.length === 1 ? remotes[0].name : "origin";
215
272
  }
273
+ /**
274
+ * The for-each-ref patterns covering every ref a fetch from `remote` (`undefined`: from any remote) may write: the
275
+ * destinations of `refspecs`, those of the remote's configured `remote.<name>.fetch` refspecs (which a fetch of
276
+ * explicit refspecs still updates opportunistically, and which may store into local branches, as a mirror's do),
277
+ * and tags. A `remote` with no refspecs of its own may be a remote group (`remotes.<group>`), which fetches each of
278
+ * its remotes, so every remote's destinations stand for it. When the configuration can't be read, all local and
279
+ * remote-tracking branches stand for the configured destinations.
280
+ */
281
+ async getFetchSnapshotPatterns(repoPath, remote, refspecs) {
282
+ const destinations = [];
283
+ const add = (refspec) => {
284
+ const destination = getRefspecDestination(refspec);
285
+ if (destination != null) destinations.push(destination);
286
+ };
287
+ for (const refspec of refspecs ?? []) add(refspec);
288
+ let configured;
289
+ try {
290
+ configured = await this.provider.config.getConfigRegexAll(repoPath, "^remote\\..+\\.fetch$");
291
+ } catch {
292
+ destinations.unshift(...broadFetchDestinations);
293
+ }
294
+ const ownKey = remote != null ? `remote.${remote}.fetch` : void 0;
295
+ const onlyKey = ownKey != null && configured?.has(ownKey) ? ownKey : void 0;
296
+ for (const [key, values] of configured ?? []) {
297
+ if (onlyKey != null && key !== onlyKey) continue;
298
+ for (const refspec of values) add(refspec);
299
+ }
300
+ return getFetchSnapshotPatterns(destinations);
301
+ }
302
+ /**
303
+ * Announces what a fetch moved. Its refs are compared with the snapshot taken before it ran, so a fetch that
304
+ * brought nothing new announces nothing (and resets nothing), and one that did names exactly the refs it moved,
305
+ * letting every worktree that doesn't track them skip its refresh. When either snapshot is missing, it announces
306
+ * `fallback()`'s kinds unnamed, as if every ref of them may have moved; when a ref moved outside the kinds an
307
+ * announcement can name (`refs/notes/*`), the kinds it did see, together with `fallback()`'s, unnamed.
308
+ */
309
+ async announceFetch(repoPath, snapshot, fallback) {
310
+ const after = snapshot?.before != null ? await this.snapshotRefs(repoPath, snapshot.patterns) : void 0;
311
+ let changes;
312
+ let moved;
313
+ if (snapshot?.before != null && after != null) {
314
+ moved = getMovedRefs(snapshot.before, after);
315
+ if (!moved.length) return;
316
+ const kinds = /* @__PURE__ */ new Set();
317
+ let unnameable = false;
318
+ for (const ref of moved) {
319
+ const kind = ref.startsWith("refs/") ? getRefChangeKind(ref) : void 0;
320
+ if (kind != null) kinds.add(kind);
321
+ else unnameable = true;
322
+ }
323
+ if (unnameable) {
324
+ for (const kind of fallback()) kinds.add(kind);
325
+ moved = void 0;
326
+ }
327
+ changes = fetchRefChangeKinds.filter((k) => kinds.has(k));
328
+ }
329
+ this.context.hooks?.cache?.onReset?.(repoPath, "branches", "tags");
330
+ if (changes != null && moved != null) this.context.hooks?.repository?.onChanged?.(repoPath, changes, moved);
331
+ else this.context.hooks?.repository?.onChanged?.(repoPath, changes ?? fallback());
332
+ }
333
+ /**
334
+ * Each ref under `patterns` (for-each-ref patterns) with its object and, for a symbolic ref, its target (a remote's
335
+ * `HEAD` can move to another branch at the same commit), or `undefined` when git couldn't answer. A read, so it
336
+ * announces nothing.
337
+ */
338
+ async snapshotRefs(repoPath, patterns) {
339
+ try {
340
+ const result = await this.git.run({
341
+ cwd: repoPath,
342
+ errors: "ignore"
343
+ }, "for-each-ref", "--format=%(refname)%00%(objectname)%00%(symref)", ...patterns);
344
+ if (result.completion.status !== "exited" || result.exitCode !== 0) return void 0;
345
+ const refs = /* @__PURE__ */ new Map();
346
+ for (const line of result.stdout.split("\n")) {
347
+ const index = line.indexOf("\0");
348
+ if (index === -1) continue;
349
+ refs.set(line.substring(0, index), line.substring(index + 1).trim());
350
+ }
351
+ return refs;
352
+ } catch {
353
+ return;
354
+ }
355
+ }
216
356
  /** `remotes`, plus `heads`/`tags` for any local branch or tag the fetch writes — a `pull` fetch writes `<upstream>:<branch>` */
217
357
  getFetchChanges(branch, options) {
218
358
  if (isBranchReference(branch)) return options?.pull ? ["heads", "remotes"] : ["remotes"];
219
359
  if (!options?.refspecs?.length) return ["remotes"];
220
360
  const changes = ["remotes"];
221
361
  for (const refspec of options.refspecs) {
222
- const stripped = refspec.startsWith("+") ? refspec.slice(1) : refspec;
223
- const colonIndex = stripped.indexOf(":");
224
- if (colonIndex === -1) continue;
225
- let destination = stripped.slice(colonIndex + 1);
226
- if (!destination) continue;
227
- if (!destination.startsWith("refs/")) destination = /^(?:heads|tags|remotes)\//.test(destination) ? `refs/${destination}` : `refs/heads/${destination}`;
362
+ const destination = getRefspecDestination(refspec);
363
+ if (destination == null) continue;
228
364
  if (destination.startsWith("refs/tags/")) {
229
365
  if (!changes.includes("tags")) changes.push("tags");
230
366
  } else if (destination.startsWith("refs/heads/")) {
@@ -772,10 +908,14 @@ var OperationsGitSubProvider = class {
772
908
  }
773
909
  }, ex instanceof Error ? ex : void 0);
774
910
  }
775
- /** Called once git has run, success or not — a rejected write can still have moved HEAD or left state behind */
776
- announceHeadChanged(repoPath, wroteTracking) {
911
+ /**
912
+ * Called once git has run, success or not — a rejected write can still have moved HEAD or left state behind.
913
+ * `branchRef` is the full name of the one branch the write could have moved, when it knows it; `wroteTracking`
914
+ * says it wrote that branch's tracking, to its own config section.
915
+ */
916
+ announceHeadChanged(repoPath, branchRef, wroteTracking) {
777
917
  this.context.hooks?.cache?.onReset?.(repoPath, "branches", "status");
778
- this.context.hooks?.repository?.onChanged?.(repoPath, wroteTracking ? [
918
+ const changes = wroteTracking ? [
779
919
  "config",
780
920
  "head",
781
921
  "heads",
@@ -784,7 +924,26 @@ var OperationsGitSubProvider = class {
784
924
  "head",
785
925
  "heads",
786
926
  "index"
787
- ]);
927
+ ];
928
+ if (branchRef == null) this.context.hooks?.repository?.onChanged?.(repoPath, changes);
929
+ else if (wroteTracking) this.context.hooks?.repository?.onChanged?.(repoPath, changes, [branchRef], { configConfinedToRefs: true });
930
+ else this.context.hooks?.repository?.onChanged?.(repoPath, changes, [branchRef]);
931
+ }
932
+ /**
933
+ * The full name of the branch HEAD is on, from the worktree's own `HEAD` file (`ref: refs/heads/main`): read around
934
+ * a write, so it names the branch that write moved, whatever a switch outside core left in the caches; no git
935
+ * runs. `undefined` when HEAD is detached, unreadable, or not a branch name git allows — a reftable repository's
936
+ * `HEAD` file holds the placeholder `refs/heads/.invalid`, and its real HEAD is in the tables.
937
+ */
938
+ async readHeadBranchRef(repoPath) {
939
+ try {
940
+ const gitDir = await this.provider.config.getGitDir(repoPath);
941
+ const head = new TextDecoder().decode(await this.context.fs.readFile(joinUriPath(gitDir.uri, "HEAD")));
942
+ const ref = /^ref:\s*(refs\/heads\/\S+)\s*$/.exec(head)?.[1];
943
+ return ref != null && !ref.split("/").some((part) => part.startsWith(".")) ? ref : void 0;
944
+ } catch {
945
+ return;
946
+ }
788
947
  }
789
948
  /** The caller has already announced the change — the conflict is what the failed run left behind */
790
949
  async createConflictResult(repoPath, command) {