@gitkraken/core-gitlens 0.20.1 → 0.21.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. package/CHANGELOG.md +19 -1
  2. package/dist/git/models/issue.d.ts +30 -1
  3. package/dist/git/models/issue.js +3 -1
  4. package/dist/git/models/issue.js.map +1 -1
  5. package/dist/git/providers/config.d.ts +4 -1
  6. package/dist/git/providers/remotes.d.ts +18 -0
  7. package/dist/git/utils/issue.utils.js +2 -1
  8. package/dist/git/utils/issue.utils.js.map +1 -1
  9. package/dist/git-cli/exec/git.js +1 -1
  10. package/dist/git-cli/exec/git.js.map +1 -1
  11. package/dist/git-cli/providers/config.d.ts +6 -2
  12. package/dist/git-cli/providers/config.js +47 -7
  13. package/dist/git-cli/providers/config.js.map +1 -1
  14. package/dist/git-cli/providers/remotes.d.ts +9 -1
  15. package/dist/git-cli/providers/remotes.js +79 -4
  16. package/dist/git-cli/providers/remotes.js.map +1 -1
  17. package/dist/plus/integrations/models/integration.d.ts +13 -1
  18. package/dist/plus/integrations/models/integration.js +8 -1
  19. package/dist/plus/integrations/models/integration.js.map +1 -1
  20. package/dist/plus/integrations/providers/issueSorts.d.ts +1 -0
  21. package/dist/plus/integrations/providers/linearIssuesEtag.d.ts +20 -4
  22. package/dist/plus/integrations/providers/linearIssuesEtag.js +42 -6
  23. package/dist/plus/integrations/providers/linearIssuesEtag.js.map +1 -1
  24. package/dist/plus/integrations/providers/models.js +25 -13
  25. package/dist/plus/integrations/providers/models.js.map +1 -1
  26. package/dist/plus/integrations/providers/providerErrors.d.ts +2 -0
  27. package/dist/plus/integrations/providers/providerErrors.js +5 -2
  28. package/dist/plus/integrations/providers/providerErrors.js.map +1 -1
  29. package/dist/plus/integrations/reads/drains.d.ts +1 -0
  30. package/dist/plus/integrations/reads/etag.js +3 -1
  31. package/dist/plus/integrations/reads/etag.js.map +1 -1
  32. package/docs/integrations.md +12 -1
  33. package/package.json +2 -2
@@ -1,3 +1,4 @@
1
+ import { CancellationError } from "../../utils/cancellation.js";
1
2
  import { getScopedLogger } from "../../utils/logger.scoped.js";
2
3
  import { debug } from "../../utils/decorators/log.js";
3
4
  import __decorate from "../../_virtual/@oxc-project+runtime/helpers/esm/decorate.js";
@@ -7,6 +8,40 @@ import { RemotesGitProviderBase } from "../../git/providers/shared/remotes.js";
7
8
  import { createRemoteProviderMatcher } from "../../git/remotes/matcher.js";
8
9
  import { parseGitRemotes } from "../parsers/remoteParser.js";
9
10
  //#region ../git-cli/src/providers/remotes.ts
11
+ /** Long enough for a slow host to answer, short enough that a blackholed one doesn't stall the wizard */
12
+ const probeRemoteTimeoutMs = 5e3;
13
+ /**
14
+ * Keeps the probe from asking anyone for credentials: an empty `GIT_ASKPASS` stops git's askpass lookup
15
+ * outright (it doesn't fall through to `core.askPass` or `SSH_ASKPASS`), so the editor's askpass can't
16
+ * open a credential prompt over the wizard, and `GIT_TERMINAL_PROMPT=0` refuses the tty fallback.
17
+ * Non-interactive credential helpers still run, so a stored credential still authenticates.
18
+ */
19
+ const probeRemoteEnv = {
20
+ GIT_ASKPASS: "",
21
+ GIT_TERMINAL_PROMPT: "0",
22
+ GCM_INTERACTIVE: "NEVER",
23
+ SSH_ASKPASS: ""
24
+ };
25
+ function getProbeFailureReason(stderr) {
26
+ if (/Could not resolve host|nodename nor servname|Name or service not known|No such host/i.test(stderr)) return "hostNotFound";
27
+ if (/Host key verification failed/i.test(stderr)) return "hostKeyUnverified";
28
+ if (/could not read (?:Username|Password)|terminal prompts disabled|Authentication failed|Permission denied|returned error: 40[13]\b/i.test(stderr)) return "authRequired";
29
+ if (/Repository not found|repository '.*' not found|does not appear to be a git repository|returned error: 404\b/i.test(stderr)) return "notFound";
30
+ return "other";
31
+ }
32
+ /** SSH's generic tail, which follows (and buries) the host's own explanation of the failure */
33
+ const genericProbeFailureLine = /^(?:fatal:\s*)?(?:Could not read from remote repository|Please make sure you have the correct access rights|and the repository exists)/;
34
+ /**
35
+ * The line of stderr that names what went wrong most plainly: SSH's generic trailer is dropped first so the host's
36
+ * message before it (e.g. `ERROR: Repository not found.`) can win; then the last `fatal:` line (prefix stripped),
37
+ * else the last line. If only the generic trailer remains, it is used rather than returning nothing.
38
+ */
39
+ function getProbeFailureMessage(stderr) {
40
+ const lines = stderr.split(/\r?\n/).map((l) => l.trim()).filter((l) => l.length);
41
+ const specific = lines.filter((l) => !genericProbeFailureLine.test(l));
42
+ const candidates = specific.length ? specific : lines;
43
+ return (candidates.findLast((l) => l.startsWith("fatal:")) ?? candidates.at(-1))?.replace(/^fatal:\s*/, "") || void 0;
44
+ }
10
45
  var RemotesGitSubProvider = class extends RemotesGitProviderBase {
11
46
  git;
12
47
  constructor(context, git, cache, provider) {
@@ -42,11 +77,16 @@ var RemotesGitSubProvider = class extends RemotesGitProviderBase {
42
77
  return remotes;
43
78
  }
44
79
  async addRemote(repoPath, name, url, options) {
80
+ if (!options?.fetch) {
81
+ await this.git.run({ cwd: repoPath }, "remote", "add", name, url);
82
+ this.context.hooks?.cache?.onReset?.(repoPath, "remotes");
83
+ this.context.hooks?.repository?.onChanged?.(repoPath, ["config", "remotes"]);
84
+ return;
85
+ }
45
86
  try {
46
- await this.git.run({ cwd: repoPath }, "remote", "add", options?.fetch ? "-f" : void 0, name, url);
87
+ await this.git.run({ cwd: repoPath }, "remote", "add", "-f", name, url);
47
88
  } finally {
48
- if (options?.fetch) this.context.hooks?.cache?.onReset?.(repoPath, "remotes", "branches");
49
- else this.context.hooks?.cache?.onReset?.(repoPath, "remotes");
89
+ this.context.hooks?.cache?.onReset?.(repoPath, "remotes", "branches");
50
90
  this.context.hooks?.repository?.onChanged?.(repoPath, ["config", "remotes"]);
51
91
  }
52
92
  }
@@ -55,6 +95,40 @@ var RemotesGitSubProvider = class extends RemotesGitProviderBase {
55
95
  const [remote] = await this.getRemotes(repoPath, { filter: (r) => r.url === url });
56
96
  return remote;
57
97
  }
98
+ async probeRemote(repoPath, url, cancellation) {
99
+ if (url.startsWith("-")) return {
100
+ reachable: false,
101
+ reason: "other"
102
+ };
103
+ const timeoutSignal = AbortSignal.timeout(probeRemoteTimeoutMs);
104
+ const signal = cancellation != null ? AbortSignal.any([cancellation, timeoutSignal]) : timeoutSignal;
105
+ const result = await this.git.run({
106
+ cwd: repoPath,
107
+ cancellation: signal,
108
+ errors: "ignore",
109
+ env: probeRemoteEnv,
110
+ priority: "interactive"
111
+ }, "ls-remote", url, "HEAD");
112
+ const { completion } = result;
113
+ if (completion.status === "exited" && completion.code === 0) return { reachable: true };
114
+ if (completion.status === "cancelled") {
115
+ if (cancellation?.aborted) throw new CancellationError(completion.error, "aborted");
116
+ return {
117
+ reachable: false,
118
+ reason: "timeout"
119
+ };
120
+ }
121
+ if (completion.status === "failed") {
122
+ if (cancellation?.aborted) throw new CancellationError(completion.error, "aborted");
123
+ throw completion.error;
124
+ }
125
+ const stderr = typeof result.stderr === "string" ? result.stderr : "";
126
+ return {
127
+ reachable: false,
128
+ reason: getProbeFailureReason(stderr),
129
+ message: getProbeFailureMessage(stderr)
130
+ };
131
+ }
58
132
  async pruneRemote(repoPath, name) {
59
133
  try {
60
134
  await this.git.run({ cwd: repoPath }, "remote", "prune", name);
@@ -78,9 +152,10 @@ __decorate([debug({
78
152
  })], RemotesGitSubProvider.prototype, "getRemotes", null);
79
153
  __decorate([gate(), debug()], RemotesGitSubProvider.prototype, "addRemote", null);
80
154
  __decorate([gate(), debug()], RemotesGitSubProvider.prototype, "addRemoteWithResult", null);
155
+ __decorate([debug({ args: (repoPath) => ({ repoPath }) })], RemotesGitSubProvider.prototype, "probeRemote", null);
81
156
  __decorate([gate(), debug()], RemotesGitSubProvider.prototype, "pruneRemote", null);
82
157
  __decorate([gate(), debug()], RemotesGitSubProvider.prototype, "removeRemote", null);
83
158
  //#endregion
84
- export { RemotesGitSubProvider };
159
+ export { RemotesGitSubProvider, getProbeFailureMessage, getProbeFailureReason };
85
160
 
86
161
  //# sourceMappingURL=remotes.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"remotes.js","names":[],"sources":["../../../../git-cli/src/providers/remotes.ts"],"sourcesContent":["import type { Cache } from '@gitlens/git/cache.js';\nimport type { GitServiceContext } from '@gitlens/git/context.js';\nimport type { GitRemote } from '@gitlens/git/models/remote.js';\nimport type { GitRemotesSubProvider } from '@gitlens/git/providers/remotes.js';\nimport { RemotesGitProviderBase } from '@gitlens/git/providers/shared/remotes.js';\nimport { createRemoteProviderMatcher } from '@gitlens/git/remotes/matcher.js';\nimport { sortRemotes } from '@gitlens/git/utils/sorting.js';\nimport { gate } from '@gitlens/utils/decorators/gate.js';\nimport { debug } from '@gitlens/utils/decorators/log.js';\nimport { getScopedLogger } from '@gitlens/utils/logger.scoped.js';\nimport type { CliGitProviderInternal } from '../cliGitProvider.js';\nimport type { Git } from '../exec/git.js';\nimport { parseGitRemotes } from '../parsers/remoteParser.js';\n\nexport class RemotesGitSubProvider extends RemotesGitProviderBase implements GitRemotesSubProvider {\n\tconstructor(\n\t\tcontext: GitServiceContext,\n\t\tprivate readonly git: Git,\n\t\tcache: Cache,\n\t\tprovider: CliGitProviderInternal,\n\t) {\n\t\tsuper(context, cache, provider);\n\t}\n\n\t@debug({ quiet: true, args: repoPath => ({ repoPath: repoPath }) })\n\tasync getRemotes(\n\t\trepoPath: string | undefined,\n\t\toptions?: { filter?: (remote: GitRemote) => boolean; sort?: boolean },\n\t\tcancellation?: AbortSignal,\n\t): Promise<GitRemote[]> {\n\t\tif (repoPath == null) return [];\n\n\t\tconst scope = getScopedLogger();\n\n\t\tlet remotes = await this.cache.getRemotes(\n\t\t\trepoPath,\n\t\t\tasync (commonPath, cacheable, signal) => {\n\t\t\t\t// Prefer the aggregate signal from the cache; fall back to the caller's cancellation.\n\t\t\t\tsignal ??= cancellation;\n\t\t\t\tconst configs = await this.context.remotes?.getCustomProviders?.(repoPath);\n\t\t\t\tconst remoteProviderMatcher = createRemoteProviderMatcher(configs, this.context.remotes);\n\n\t\t\t\ttry {\n\t\t\t\t\tconst result = await this.git.run(\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tcwd: repoPath,\n\t\t\t\t\t\t\tcancellation: signal,\n\t\t\t\t\t\t\tcaching: { cache: this.cache.gitResults, commonPath: commonPath },\n\t\t\t\t\t\t},\n\t\t\t\t\t\t'remote',\n\t\t\t\t\t\t'-v',\n\t\t\t\t\t);\n\n\t\t\t\t\t// Fetch default remote name from GK config before parsing so it's set at construction time\n\t\t\t\t\tconst defaultName = await this.provider.config.getGkConfig?.(repoPath, 'gk.defaultRemote');\n\t\t\t\t\tconst remotes = parseGitRemotes(result.stdout, commonPath, remoteProviderMatcher, defaultName);\n\n\t\t\t\t\treturn remotes;\n\t\t\t\t} catch (ex) {\n\t\t\t\t\tcacheable.invalidate();\n\t\t\t\t\tscope?.error(ex);\n\t\t\t\t\treturn [];\n\t\t\t\t}\n\t\t\t},\n\t\t\tcancellation,\n\t\t);\n\n\t\tif (options?.filter != null) {\n\t\t\tremotes = remotes.filter(options.filter);\n\t\t}\n\n\t\tif (options?.sort) {\n\t\t\tsortRemotes(remotes);\n\t\t}\n\n\t\treturn remotes;\n\t}\n\n\t@gate()\n\t@debug()\n\tasync addRemote(repoPath: string, name: string, url: string, options?: { fetch?: boolean }): Promise<void> {\n\t\ttry {\n\t\t\tawait this.git.run({ cwd: repoPath }, 'remote', 'add', options?.fetch ? '-f' : undefined, name, url);\n\t\t} finally {\n\t\t\t// A fetch creates the remote's tracking branches, which the branch, ref and history caches hold. The\n\t\t\t// remote is added before the fetch runs, so a failed `-f` still leaves it behind.\n\t\t\tif (options?.fetch) {\n\t\t\t\tthis.context.hooks?.cache?.onReset?.(repoPath, 'remotes', 'branches');\n\t\t\t} else {\n\t\t\t\tthis.context.hooks?.cache?.onReset?.(repoPath, 'remotes');\n\t\t\t}\n\t\t\tthis.context.hooks?.repository?.onChanged?.(repoPath, ['config', 'remotes']);\n\t\t}\n\t}\n\n\t@gate()\n\t@debug()\n\tasync addRemoteWithResult(\n\t\trepoPath: string,\n\t\tname: string,\n\t\turl: string,\n\t\toptions?: { fetch?: boolean },\n\t): Promise<GitRemote | undefined> {\n\t\tawait this.addRemote(repoPath, name, url, options);\n\t\tconst [remote] = await this.getRemotes(repoPath, { filter: r => r.url === url });\n\t\treturn remote;\n\t}\n\n\t@gate()\n\t@debug()\n\tasync pruneRemote(repoPath: string, name: string): Promise<void> {\n\t\ttry {\n\t\t\tawait this.git.run({ cwd: repoPath }, 'remote', 'prune', name);\n\t\t} finally {\n\t\t\tthis.context.hooks?.cache?.onReset?.(repoPath, 'remotes', 'branches');\n\t\t\tthis.context.hooks?.repository?.onChanged?.(repoPath, ['remotes']);\n\t\t}\n\t}\n\n\t@gate()\n\t@debug()\n\tasync removeRemote(repoPath: string, name: string): Promise<void> {\n\t\ttry {\n\t\t\tawait this.git.run({ cwd: repoPath }, 'remote', 'remove', name);\n\t\t} finally {\n\t\t\t// Removing a remote deletes its tracking branches too\n\t\t\tthis.context.hooks?.cache?.onReset?.(repoPath, 'remotes', 'branches');\n\t\t\tthis.context.hooks?.repository?.onChanged?.(repoPath, ['config', 'remotes']);\n\t\t}\n\t}\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"remotes.js","names":[],"sources":["../../../../git-cli/src/providers/remotes.ts"],"sourcesContent":["import type { Cache } from '@gitlens/git/cache.js';\nimport type { GitServiceContext } from '@gitlens/git/context.js';\nimport type { GitRemote } from '@gitlens/git/models/remote.js';\nimport type {\n\tGitRemoteProbeFailureReason,\n\tGitRemoteProbeResult,\n\tGitRemotesSubProvider,\n} from '@gitlens/git/providers/remotes.js';\nimport { RemotesGitProviderBase } from '@gitlens/git/providers/shared/remotes.js';\nimport { createRemoteProviderMatcher } from '@gitlens/git/remotes/matcher.js';\nimport { sortRemotes } from '@gitlens/git/utils/sorting.js';\nimport { CancellationError } from '@gitlens/utils/cancellation.js';\nimport { gate } from '@gitlens/utils/decorators/gate.js';\nimport { debug } from '@gitlens/utils/decorators/log.js';\nimport { getScopedLogger } from '@gitlens/utils/logger.scoped.js';\nimport type { CliGitProviderInternal } from '../cliGitProvider.js';\nimport type { Git } from '../exec/git.js';\nimport { parseGitRemotes } from '../parsers/remoteParser.js';\n\n/** Long enough for a slow host to answer, short enough that a blackholed one doesn't stall the wizard */\nconst probeRemoteTimeoutMs = 5000;\n\n/**\n * Keeps the probe from asking anyone for credentials: an empty `GIT_ASKPASS` stops git's askpass lookup\n * outright (it doesn't fall through to `core.askPass` or `SSH_ASKPASS`), so the editor's askpass can't\n * open a credential prompt over the wizard, and `GIT_TERMINAL_PROMPT=0` refuses the tty fallback.\n * Non-interactive credential helpers still run, so a stored credential still authenticates.\n */\nconst probeRemoteEnv: Record<string, string> = {\n\tGIT_ASKPASS: '',\n\tGIT_TERMINAL_PROMPT: '0',\n\tGCM_INTERACTIVE: 'NEVER',\n\tSSH_ASKPASS: '',\n};\n\nexport function getProbeFailureReason(stderr: string): GitRemoteProbeFailureReason {\n\tif (/Could not resolve host|nodename nor servname|Name or service not known|No such host/i.test(stderr)) {\n\t\treturn 'hostNotFound';\n\t}\n\t// Checked before auth: the probe forbids ssh's prompt to trust an unknown host, so a correct URL on a host\n\t// not yet in known_hosts fails here, and that isn't an authentication problem\n\tif (/Host key verification failed/i.test(stderr)) {\n\t\treturn 'hostKeyUnverified';\n\t}\n\t// HTTP statuses are matched on git's own wording so digits elsewhere (e.g. a `/403/` URL path) don't count\n\tif (\n\t\t/could not read (?:Username|Password)|terminal prompts disabled|Authentication failed|Permission denied|returned error: 40[13]\\b/i.test(\n\t\t\tstderr,\n\t\t)\n\t) {\n\t\treturn 'authRequired';\n\t}\n\tif (\n\t\t/Repository not found|repository '.*' not found|does not appear to be a git repository|returned error: 404\\b/i.test(\n\t\t\tstderr,\n\t\t)\n\t) {\n\t\treturn 'notFound';\n\t}\n\treturn 'other';\n}\n\n/** SSH's generic tail, which follows (and buries) the host's own explanation of the failure */\nconst genericProbeFailureLine =\n\t/^(?:fatal:\\s*)?(?:Could not read from remote repository|Please make sure you have the correct access rights|and the repository exists)/;\n\n/**\n * The line of stderr that names what went wrong most plainly: SSH's generic trailer is dropped first so the host's\n * message before it (e.g. `ERROR: Repository not found.`) can win; then the last `fatal:` line (prefix stripped),\n * else the last line. If only the generic trailer remains, it is used rather than returning nothing.\n */\nexport function getProbeFailureMessage(stderr: string): string | undefined {\n\tconst lines = stderr\n\t\t.split(/\\r?\\n/)\n\t\t.map(l => l.trim())\n\t\t.filter(l => l.length);\n\tconst specific = lines.filter(l => !genericProbeFailureLine.test(l));\n\tconst candidates = specific.length ? specific : lines;\n\tconst fatal = candidates.findLast(l => l.startsWith('fatal:'));\n\treturn (fatal ?? candidates.at(-1))?.replace(/^fatal:\\s*/, '') || undefined;\n}\n\nexport class RemotesGitSubProvider extends RemotesGitProviderBase implements GitRemotesSubProvider {\n\tconstructor(\n\t\tcontext: GitServiceContext,\n\t\tprivate readonly git: Git,\n\t\tcache: Cache,\n\t\tprovider: CliGitProviderInternal,\n\t) {\n\t\tsuper(context, cache, provider);\n\t}\n\n\t@debug({ quiet: true, args: repoPath => ({ repoPath: repoPath }) })\n\tasync getRemotes(\n\t\trepoPath: string | undefined,\n\t\toptions?: { filter?: (remote: GitRemote) => boolean; sort?: boolean },\n\t\tcancellation?: AbortSignal,\n\t): Promise<GitRemote[]> {\n\t\tif (repoPath == null) return [];\n\n\t\tconst scope = getScopedLogger();\n\n\t\tlet remotes = await this.cache.getRemotes(\n\t\t\trepoPath,\n\t\t\tasync (commonPath, cacheable, signal) => {\n\t\t\t\t// Prefer the aggregate signal from the cache; fall back to the caller's cancellation.\n\t\t\t\tsignal ??= cancellation;\n\t\t\t\tconst configs = await this.context.remotes?.getCustomProviders?.(repoPath);\n\t\t\t\tconst remoteProviderMatcher = createRemoteProviderMatcher(configs, this.context.remotes);\n\n\t\t\t\ttry {\n\t\t\t\t\tconst result = await this.git.run(\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tcwd: repoPath,\n\t\t\t\t\t\t\tcancellation: signal,\n\t\t\t\t\t\t\tcaching: { cache: this.cache.gitResults, commonPath: commonPath },\n\t\t\t\t\t\t},\n\t\t\t\t\t\t'remote',\n\t\t\t\t\t\t'-v',\n\t\t\t\t\t);\n\n\t\t\t\t\t// Fetch default remote name from GK config before parsing so it's set at construction time\n\t\t\t\t\tconst defaultName = await this.provider.config.getGkConfig?.(repoPath, 'gk.defaultRemote');\n\t\t\t\t\tconst remotes = parseGitRemotes(result.stdout, commonPath, remoteProviderMatcher, defaultName);\n\n\t\t\t\t\treturn remotes;\n\t\t\t\t} catch (ex) {\n\t\t\t\t\tcacheable.invalidate();\n\t\t\t\t\tscope?.error(ex);\n\t\t\t\t\treturn [];\n\t\t\t\t}\n\t\t\t},\n\t\t\tcancellation,\n\t\t);\n\n\t\tif (options?.filter != null) {\n\t\t\tremotes = remotes.filter(options.filter);\n\t\t}\n\n\t\tif (options?.sort) {\n\t\t\tsortRemotes(remotes);\n\t\t}\n\n\t\treturn remotes;\n\t}\n\n\t@gate()\n\t@debug()\n\tasync addRemote(repoPath: string, name: string, url: string, options?: { fetch?: boolean }): Promise<void> {\n\t\tif (!options?.fetch) {\n\t\t\tawait this.git.run({ cwd: repoPath }, 'remote', 'add', name, url);\n\t\t\tthis.context.hooks?.cache?.onReset?.(repoPath, 'remotes');\n\t\t\tthis.context.hooks?.repository?.onChanged?.(repoPath, ['config', 'remotes']);\n\t\t\treturn;\n\t\t}\n\n\t\ttry {\n\t\t\tawait this.git.run({ cwd: repoPath }, 'remote', 'add', '-f', name, url);\n\t\t} finally {\n\t\t\t// Reset even on failure: `remote add -f` keeps the remote when only its fetch fails, and a\n\t\t\t// caller checking whether the remote now exists must not read the pre-add cache.\n\t\t\t// A fetch creates the remote's tracking branches, which the branch, ref and history caches hold\n\t\t\tthis.context.hooks?.cache?.onReset?.(repoPath, 'remotes', 'branches');\n\t\t\tthis.context.hooks?.repository?.onChanged?.(repoPath, ['config', 'remotes']);\n\t\t}\n\t}\n\n\t@gate()\n\t@debug()\n\tasync addRemoteWithResult(\n\t\trepoPath: string,\n\t\tname: string,\n\t\turl: string,\n\t\toptions?: { fetch?: boolean },\n\t): Promise<GitRemote | undefined> {\n\t\tawait this.addRemote(repoPath, name, url, options);\n\t\tconst [remote] = await this.getRemotes(repoPath, { filter: r => r.url === url });\n\t\treturn remote;\n\t}\n\n\t// The URL is left out of the log — it can carry credentials (`https://<token>@host/...`)\n\t@debug({ args: repoPath => ({ repoPath: repoPath }) })\n\tasync probeRemote(repoPath: string, url: string, cancellation?: AbortSignal): Promise<GitRemoteProbeResult> {\n\t\t// A leading `-` would be parsed as an option rather than the repository to query\n\t\tif (url.startsWith('-')) return { reachable: false, reason: 'other' };\n\n\t\tconst timeoutSignal = AbortSignal.timeout(probeRemoteTimeoutMs);\n\t\tconst signal = cancellation != null ? AbortSignal.any([cancellation, timeoutSignal]) : timeoutSignal;\n\n\t\tconst result = await this.git.run(\n\t\t\t{ cwd: repoPath, cancellation: signal, errors: 'ignore', env: probeRemoteEnv, priority: 'interactive' },\n\t\t\t// No `--exit-code`: a freshly created, empty repository advertises no HEAD but is a valid remote\n\t\t\t'ls-remote',\n\t\t\turl,\n\t\t\t'HEAD',\n\t\t);\n\n\t\tconst { completion } = result;\n\t\tif (completion.status === 'exited' && completion.code === 0) return { reachable: true };\n\n\t\tif (completion.status === 'cancelled') {\n\t\t\tif (cancellation?.aborted) throw new CancellationError(completion.error, 'aborted');\n\n\t\t\treturn { reachable: false, reason: 'timeout' };\n\t\t}\n\n\t\t// A run that never exited (spawn failure, missing binary, queue rejection) says nothing about the remote;\n\t\t// throw so callers treat it as unprobed rather than unreachable\n\t\tif (completion.status === 'failed') {\n\t\t\tif (cancellation?.aborted) throw new CancellationError(completion.error, 'aborted');\n\n\t\t\tthrow completion.error;\n\t\t}\n\n\t\t// Under `errors: 'ignore'` a non-zero exit resolves with its stderr; a run that never exited has none\n\t\tconst stderr = typeof result.stderr === 'string' ? result.stderr : '';\n\t\treturn {\n\t\t\treachable: false,\n\t\t\treason: getProbeFailureReason(stderr),\n\t\t\tmessage: getProbeFailureMessage(stderr),\n\t\t};\n\t}\n\n\t@gate()\n\t@debug()\n\tasync pruneRemote(repoPath: string, name: string): Promise<void> {\n\t\ttry {\n\t\t\tawait this.git.run({ cwd: repoPath }, 'remote', 'prune', name);\n\t\t} finally {\n\t\t\tthis.context.hooks?.cache?.onReset?.(repoPath, 'remotes', 'branches');\n\t\t\tthis.context.hooks?.repository?.onChanged?.(repoPath, ['remotes']);\n\t\t}\n\t}\n\n\t@gate()\n\t@debug()\n\tasync removeRemote(repoPath: string, name: string): Promise<void> {\n\t\ttry {\n\t\t\tawait this.git.run({ cwd: repoPath }, 'remote', 'remove', name);\n\t\t} finally {\n\t\t\t// Removing a remote deletes its tracking branches too\n\t\t\tthis.context.hooks?.cache?.onReset?.(repoPath, 'remotes', 'branches');\n\t\t\tthis.context.hooks?.repository?.onChanged?.(repoPath, ['config', 'remotes']);\n\t\t}\n\t}\n}\n"],"mappings":"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"}
@@ -3,7 +3,7 @@ import { Emitter, Event } from "../../../utils/event.js";
3
3
  import { ResourceDescriptor } from "../../../git/models/resourceDescriptor.js";
4
4
  import { AutolinkReference, DynamicAutolinkReference } from "../../../git/models/autolink.js";
5
5
  import { IssueOrPullRequest, IssueOrPullRequestState, IssueOrPullRequestType } from "../../../git/models/issueOrPullRequest.js";
6
- import { Issue as Issue$1, IssueShape } from "../../../git/models/issue.js";
6
+ import { Issue as Issue$1, IssueShape, IssueSyncedWith } from "../../../git/models/issue.js";
7
7
  import { PullRequest, PullRequestMergeableState, PullRequestReviewDecision, PullRequestReviewState, PullRequestStatusCheckRollupState } from "../../../git/models/pullRequest.js";
8
8
  import { ScopedLogger } from "../../../utils/logger.scoped.js";
9
9
  import { Account as Account$1 } from "../../../git/models/author.js";
@@ -135,6 +135,13 @@ export interface IssueEtagFields {
135
135
  * it never compares unchanged and is read in full again rather than kept without one.
136
136
  */
137
137
  viewerRule?: number;
138
+ /**
139
+ * Linear only: the row's `syncedWith` links as {@link linearSyncedWithEtagKeys} spells them, so a link added, removed
140
+ * or moved to other coordinates changes the etag whether or not the issue's update time moves. `null` on a
141
+ * full row without `syncedWith` (held from before it existed, or read while Linear failed to resolve it), which
142
+ * never compares unchanged with a cheap check's list.
143
+ */
144
+ syncedWithKeys?: readonly string[] | null;
138
145
  }
139
146
  /**
140
147
  * {@link IssueEtagFields.descriptionRule}: the version of the rule an Azure DevOps work item's body is read by, stamped
@@ -154,6 +161,11 @@ export declare const azureWorkItemDescriptionRule = 1;
154
161
  * identity in the work item's organization (gitkraken/kepler#4291).
155
162
  */
156
163
  export declare const azureWorkItemViewerRule = 1;
164
+ /**
165
+ * A Linear row's sync links in its etag, sorted: each as its GitHub id and, when known, the coordinates a host matches
166
+ * the GitHub row by, so a link whose coordinates Linear fills in or changes later compares changed.
167
+ */
168
+ export declare function linearSyncedWithEtagKeys(links: readonly IssueSyncedWith[]): string[];
157
169
  /** A batch read's target, in the form its provider addresses it by: a repository coordinate, or a tracker's resource. */
158
170
  type BatchTarget = {
159
171
  owner: string;
@@ -74,6 +74,13 @@ const azureWorkItemDescriptionRule = 1;
74
74
  */
75
75
  const azureWorkItemViewerRule = 1;
76
76
  /**
77
+ * A Linear row's sync links in its etag, sorted: each as its GitHub id and, when known, the coordinates a host matches
78
+ * the GitHub row by, so a link whose coordinates Linear fills in or changes later compares changed.
79
+ */
80
+ function linearSyncedWithEtagKeys(links) {
81
+ return links.map((link) => link.owner != null ? `${link.id}:${link.owner}/${link.repo}#${link.number}` : `${link.id}:`).sort();
82
+ }
83
+ /**
77
84
  * The scope a batch read records a refused target under. It is decided here rather than in the read, which has the
78
85
  * same targets, because naming the refusal's cause (`describeRefusal`) needs it too. Azure DevOps: the organization
79
86
  * and project, since its refusals come from those (an organization's OAuth policy or tenant, a project's permissions)
@@ -968,6 +975,6 @@ __decorate([trace()], IntegrationBase.prototype, "getLinkedIssueOrPullRequest",
968
975
  __decorate([trace()], IntegrationBase.prototype, "getIssueResultCore", null);
969
976
  __decorate([trace()], IntegrationBase.prototype, "getPullRequest", null);
970
977
  //#endregion
971
- export { IntegrationBase, ReadSessionFailure, azureWorkItemDescriptionRule, azureWorkItemViewerRule, isReadSessionFailure, issueEtagIncludes, pullRequestEtagIncludes, pullRequestRevision };
978
+ export { IntegrationBase, ReadSessionFailure, azureWorkItemDescriptionRule, azureWorkItemViewerRule, isReadSessionFailure, issueEtagIncludes, linearSyncedWithEtagKeys, pullRequestEtagIncludes, pullRequestRevision };
972
979
 
973
980
  //# sourceMappingURL=integration.js.map