@gitkraken/core-gitlens 0.7.2 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +72 -1
- package/dist/git/cache.d.ts +29 -5
- package/dist/git/cache.js +52 -11
- package/dist/git/cache.js.map +1 -1
- package/dist/git/context.d.ts +6 -1
- package/dist/git/context.js.map +1 -1
- package/dist/git/errors.d.ts +28 -1
- package/dist/git/errors.js +45 -1
- package/dist/git/errors.js.map +1 -1
- package/dist/git/features.d.ts +1 -1
- package/dist/git/features.js +1 -0
- package/dist/git/features.js.map +1 -1
- package/dist/git/models/reflog.d.ts +6 -0
- package/dist/git/models/reflog.js.map +1 -1
- package/dist/git/models/revision.d.ts +0 -1
- package/dist/git/models/revision.js +1 -2
- package/dist/git/models/revision.js.map +1 -1
- package/dist/git/providers/branches.d.ts +5 -1
- package/dist/git/providers/commits.d.ts +44 -2
- package/dist/git/providers/config.d.ts +7 -1
- package/dist/git/providers/operations.d.ts +19 -0
- package/dist/git/providers/provider.d.ts +29 -2
- package/dist/git/providers/refs.d.ts +48 -3
- package/dist/git/providers/revision.d.ts +7 -1
- package/dist/git/providers/staging.d.ts +13 -0
- package/dist/git/providers/stash.d.ts +3 -2
- package/dist/git/providers/status.d.ts +4 -0
- package/dist/git/providers/worktrees.d.ts +23 -3
- package/dist/git/run.types.d.ts +22 -0
- package/dist/git-cli/cliGitProvider.d.ts +39 -2
- package/dist/git-cli/cliGitProvider.js +109 -11
- package/dist/git-cli/cliGitProvider.js.map +1 -1
- package/dist/git-cli/exec/commandClassifier.d.ts +31 -0
- package/dist/git-cli/exec/commandClassifier.js +391 -0
- package/dist/git-cli/exec/commandClassifier.js.map +1 -0
- package/dist/git-cli/exec/git.d.ts +36 -5
- package/dist/git-cli/exec/git.js +163 -28
- package/dist/git-cli/exec/git.js.map +1 -1
- package/dist/git-cli/exec/gitQueue.d.ts +4 -1
- package/dist/git-cli/exec/gitQueue.js +1 -1
- package/dist/git-cli/exec/gitQueue.js.map +1 -1
- package/dist/git-cli/parsers/reflogParser.d.ts +7 -0
- package/dist/git-cli/parsers/reflogParser.js +11 -1
- package/dist/git-cli/parsers/reflogParser.js.map +1 -1
- package/dist/git-cli/providers/branches.d.ts +10 -1
- package/dist/git-cli/providers/branches.js +31 -22
- package/dist/git-cli/providers/branches.js.map +1 -1
- package/dist/git-cli/providers/commits.d.ts +27 -3
- package/dist/git-cli/providers/commits.js +112 -12
- package/dist/git-cli/providers/commits.js.map +1 -1
- package/dist/git-cli/providers/config.js +2 -1
- package/dist/git-cli/providers/config.js.map +1 -1
- package/dist/git-cli/providers/diff.js +4 -4
- package/dist/git-cli/providers/diff.js.map +1 -1
- package/dist/git-cli/providers/operations.d.ts +13 -0
- package/dist/git-cli/providers/operations.js +64 -5
- package/dist/git-cli/providers/operations.js.map +1 -1
- package/dist/git-cli/providers/patch.js +21 -45
- package/dist/git-cli/providers/patch.js.map +1 -1
- package/dist/git-cli/providers/refs.d.ts +20 -3
- package/dist/git-cli/providers/refs.js +140 -10
- package/dist/git-cli/providers/refs.js.map +1 -1
- package/dist/git-cli/providers/remotes.js +3 -2
- package/dist/git-cli/providers/remotes.js.map +1 -1
- package/dist/git-cli/providers/revision.d.ts +5 -1
- package/dist/git-cli/providers/revision.js +20 -5
- package/dist/git-cli/providers/revision.js.map +1 -1
- package/dist/git-cli/providers/staging.d.ts +9 -0
- package/dist/git-cli/providers/staging.js +63 -6
- package/dist/git-cli/providers/staging.js.map +1 -1
- package/dist/git-cli/providers/stash.d.ts +3 -2
- package/dist/git-cli/providers/stash.js +34 -22
- package/dist/git-cli/providers/stash.js.map +1 -1
- package/dist/git-cli/providers/status.d.ts +2 -0
- package/dist/git-cli/providers/status.js +27 -9
- package/dist/git-cli/providers/status.js.map +1 -1
- package/dist/git-cli/providers/worktrees.d.ts +31 -2
- package/dist/git-cli/providers/worktrees.js +75 -7
- package/dist/git-cli/providers/worktrees.js.map +1 -1
- package/dist/plus/git-github/api/github.d.ts +64 -3
- package/dist/plus/git-github/api/github.js +237 -24
- package/dist/plus/git-github/api/github.js.map +1 -1
- package/dist/plus/git-github/providers/github/branches.d.ts +3 -1
- package/dist/plus/git-github/providers/github/branches.js +2 -1
- package/dist/plus/git-github/providers/github/branches.js.map +1 -1
- package/dist/plus/git-github/providers/github/commits.d.ts +4 -2
- package/dist/plus/git-github/providers/github/commits.js +3 -1
- package/dist/plus/git-github/providers/github/commits.js.map +1 -1
- package/dist/plus/git-github/providers/github/refs.d.ts +10 -3
- package/dist/plus/git-github/providers/github/refs.js +4 -4
- package/dist/plus/git-github/providers/github/refs.js.map +1 -1
- package/dist/plus/git-github/providers/github/revision.d.ts +4 -1
- package/dist/plus/git-github/providers/github/revision.js +6 -1
- package/dist/plus/git-github/providers/github/revision.js.map +1 -1
- package/dist/plus/integrations/index.d.ts +5 -2
- package/dist/plus/integrations/index.js.map +1 -1
- package/dist/plus/integrations/integrationService.d.ts +35 -9
- package/dist/plus/integrations/integrationService.js +16 -3
- package/dist/plus/integrations/integrationService.js.map +1 -1
- package/dist/plus/integrations/manager.d.ts +107 -37
- package/dist/plus/integrations/models/gitHostIntegration.d.ts +128 -13
- package/dist/plus/integrations/models/gitHostIntegration.js +130 -17
- package/dist/plus/integrations/models/gitHostIntegration.js.map +1 -1
- package/dist/plus/integrations/models/integration.d.ts +51 -3
- package/dist/plus/integrations/models/integration.js +125 -18
- package/dist/plus/integrations/models/integration.js.map +1 -1
- package/dist/plus/integrations/models/issuesIntegration.d.ts +18 -5
- package/dist/plus/integrations/models/issuesIntegration.js +40 -10
- package/dist/plus/integrations/models/issuesIntegration.js.map +1 -1
- package/dist/plus/integrations/providers/azure/azure.d.ts +1 -1
- package/dist/plus/integrations/providers/azure/azure.js +13 -17
- package/dist/plus/integrations/providers/azure/azure.js.map +1 -1
- package/dist/plus/integrations/providers/azure/models.d.ts +29 -0
- package/dist/plus/integrations/providers/azure/models.js +112 -1
- package/dist/plus/integrations/providers/azure/models.js.map +1 -1
- package/dist/plus/integrations/providers/azureDevOps.d.ts +54 -2
- package/dist/plus/integrations/providers/azureDevOps.js +80 -2
- package/dist/plus/integrations/providers/azureDevOps.js.map +1 -1
- package/dist/plus/integrations/providers/azureDevOpsServer.js +2 -1
- package/dist/plus/integrations/providers/azureDevOpsServer.js.map +1 -1
- package/dist/plus/integrations/providers/bitbucket/bitbucket.d.ts +61 -0
- package/dist/plus/integrations/providers/bitbucket/bitbucket.js +165 -23
- package/dist/plus/integrations/providers/bitbucket/bitbucket.js.map +1 -1
- package/dist/plus/integrations/providers/bitbucket/models.d.ts +6 -1
- package/dist/plus/integrations/providers/bitbucket/models.js +4 -3
- package/dist/plus/integrations/providers/bitbucket/models.js.map +1 -1
- package/dist/plus/integrations/providers/bitbucket-server/pullRequestSearch.js +1 -1
- package/dist/plus/integrations/providers/bitbucket-server/pullRequestSearch.js.map +1 -1
- package/dist/plus/integrations/providers/bitbucket-server.d.ts +38 -1
- package/dist/plus/integrations/providers/bitbucket-server.js +33 -1
- package/dist/plus/integrations/providers/bitbucket-server.js.map +1 -1
- package/dist/plus/integrations/providers/bitbucket.d.ts +36 -1
- package/dist/plus/integrations/providers/bitbucket.js +29 -1
- package/dist/plus/integrations/providers/bitbucket.js.map +1 -1
- package/dist/plus/integrations/providers/github.d.ts +48 -4
- package/dist/plus/integrations/providers/github.js +101 -6
- package/dist/plus/integrations/providers/github.js.map +1 -1
- package/dist/plus/integrations/providers/gitlab/gitlab.d.ts +35 -0
- package/dist/plus/integrations/providers/gitlab/gitlab.js +132 -15
- package/dist/plus/integrations/providers/gitlab/gitlab.js.map +1 -1
- package/dist/plus/integrations/providers/gitlab/models.d.ts +1 -0
- package/dist/plus/integrations/providers/gitlab/models.js +1 -1
- package/dist/plus/integrations/providers/gitlab/models.js.map +1 -1
- package/dist/plus/integrations/providers/gitlab.d.ts +48 -1
- package/dist/plus/integrations/providers/gitlab.js +79 -3
- package/dist/plus/integrations/providers/gitlab.js.map +1 -1
- package/dist/plus/integrations/providers/jira-server.d.ts +3 -3
- package/dist/plus/integrations/providers/jira-server.js +3 -3
- package/dist/plus/integrations/providers/jira-server.js.map +1 -1
- package/dist/plus/integrations/providers/models.d.ts +12 -1
- package/dist/plus/integrations/providers/models.js +11 -1
- package/dist/plus/integrations/providers/models.js.map +1 -1
- package/dist/plus/integrations/providers/providerErrors.d.ts +9 -1
- package/dist/plus/integrations/providers/providerErrors.js +10 -1
- package/dist/plus/integrations/providers/providerErrors.js.map +1 -1
- package/dist/plus/integrations/providers/providersApi.d.ts +59 -0
- package/dist/plus/integrations/providers/providersApi.js +136 -1
- package/dist/plus/integrations/providers/providersApi.js.map +1 -1
- package/dist/plus/integrations/providers/utils/providerPaging.d.ts +42 -0
- package/dist/plus/integrations/providers/utils/providerPaging.js +58 -1
- package/dist/plus/integrations/providers/utils/providerPaging.js.map +1 -1
- package/dist/plus/integrations/providers/utils.d.ts +1 -1
- package/dist/plus/integrations/reads/currentAccount.d.ts +35 -0
- package/dist/plus/integrations/reads/currentAccount.js +46 -0
- package/dist/plus/integrations/reads/currentAccount.js.map +1 -0
- package/dist/plus/integrations/reads/drains.d.ts +4 -1
- package/dist/plus/integrations/reads/drains.js +7 -3
- package/dist/plus/integrations/reads/drains.js.map +1 -1
- package/dist/plus/integrations/reads/issueBatch.d.ts +72 -12
- package/dist/plus/integrations/reads/issueBatch.js +193 -48
- package/dist/plus/integrations/reads/issueBatch.js.map +1 -1
- package/dist/plus/integrations/reads/pullRequestBatch.d.ts +57 -0
- package/dist/plus/integrations/reads/pullRequestBatch.js +80 -0
- package/dist/plus/integrations/reads/pullRequestBatch.js.map +1 -0
- package/dist/plus/integrations/reads/pullRequestBranches.d.ts +58 -0
- package/dist/plus/integrations/reads/pullRequestBranches.js +111 -0
- package/dist/plus/integrations/reads/pullRequestBranches.js.map +1 -0
- package/dist/plus/integrations/reads/pullRequests.js +3 -3
- package/dist/plus/integrations/reads/pullRequests.js.map +1 -1
- package/dist/plus/integrations/reads/sweeps.js +3 -3
- package/dist/plus/integrations/reads/sweeps.js.map +1 -1
- package/dist/plus/integrations/results.d.ts +5 -2
- package/dist/plus/integrations/results.js.map +1 -1
- package/dist/utils/env/node/exec.js +1 -1
- package/dist/utils/env/node/exec.js.map +1 -1
- package/docs/integrations.md +90 -36
- package/docs/kepler-git-write-api-parity.md +365 -0
- package/docs/kepler-read-api-parity.md +75 -28
- package/package.json +1 -1
- package/dist/plus/integrations/reads/trackerIssue.d.ts +0 -33
- package/dist/plus/integrations/reads/trackerIssue.js +0 -60
- package/dist/plus/integrations/reads/trackerIssue.js.map +0 -1
|
@@ -0,0 +1,365 @@
|
|
|
1
|
+
# Kepler git write-API parity
|
|
2
|
+
|
|
3
|
+
Decision record for the git WRITE-path gaps Kepler hit while routing every one of its git calls through
|
|
4
|
+
`@gitkraken/core-gitlens` (gitkraken/kepler, branch `kepler/migrate-raw-git-spawns`). It is the sibling of
|
|
5
|
+
[`kepler-read-api-parity.md`](./kepler-read-api-parity.md), which covers provider reads; this one covers the
|
|
6
|
+
`git` sub-providers and the raw-command escape hatch.
|
|
7
|
+
|
|
8
|
+
The shape of the problem: Kepler drives three libraries (`@gitkraken/sync-tools`, `conflict-tools`,
|
|
9
|
+
`compose-tools`) that take a `{ exec }` git port plus optional typed ops, and know nothing of core; the host
|
|
10
|
+
supplies a typed op only where a core call serves it exactly. Kepler also has its own worktree, branch and
|
|
11
|
+
sync services. After the migration every command runs through core's executor, so it already shares the queue, environment, binary
|
|
12
|
+
resolution and logging. What it could NOT do is use a typed sub-provider method for a set of commands core had
|
|
13
|
+
no method for, or had one whose contract did not fit. Each such site stayed on `provider.git.run` with a
|
|
14
|
+
comment naming the missing API. This document is the list of those APIs, what was added for each, and what was
|
|
15
|
+
deliberately left alone.
|
|
16
|
+
|
|
17
|
+
§1–§9 were the first round. §10–§19 are the second: the commands Kepler still ran raw once the first round
|
|
18
|
+
shipped, each because a typed method lacked one option or one behavior.
|
|
19
|
+
|
|
20
|
+
Verdict legend: **added** (a typed method now exists) · **extended** (an existing method gained the missing
|
|
21
|
+
option) · **deferred** (left on the raw escape hatch on purpose, with the reason).
|
|
22
|
+
|
|
23
|
+
## 1. Compare-and-swap ref updates — added
|
|
24
|
+
|
|
25
|
+
`refs.updateReference` ran `git update-ref <ref> <sha>` and swallowed every failure (logged, resolved
|
|
26
|
+
`undefined`), and there was no delete form. `@gitkraken/sync-tools` (0.4.0) publishes, applies and discards a
|
|
27
|
+
sync result itself (`applyPublishedSync`, `discardPublishedSync`), and every ref move in that flow is a
|
|
28
|
+
compare-and-swap: the real branch advances only if nothing else moved it since the library last read it. The
|
|
29
|
+
library asks its host for that through a typed `updateRef(ref, sha, { previousSha, mustNotExist, remove })`
|
|
30
|
+
op, and git's own `update-ref <ref> <new> <old>` is what makes it atomic. A writer that cannot refuse is a
|
|
31
|
+
clobber, so without this a host could only serve the op through the raw escape hatch.
|
|
32
|
+
|
|
33
|
+
- `updateReference(repoPath, ref, sha, options?: { expected?: string | 'absent' }, cancellation?)` now passes
|
|
34
|
+
the old value to git when `expected` is a sha, and the empty old value when it is `'absent'` (create only if
|
|
35
|
+
the ref does not exist yet). It THROWS a typed `ReferenceUpdateError` — reasons `conflict` (the ref moved,
|
|
36
|
+
or exists when it must not), `invalidRef`, `invalidObject`, `notFound` — instead of swallowing.
|
|
37
|
+
- `deleteReference(repoPath, ref, options?: { expected?: string }, cancellation?)` runs
|
|
38
|
+
`update-ref -d --no-deref <ref> [<expected>]` with the same error — `--no-deref` so a symbolic ref is itself
|
|
39
|
+
deleted rather than the ref it points to — and refuses `HEAD` (`checkedOut`). `update-ref -d` has none of
|
|
40
|
+
`git branch -d`'s guards or cleanup, so for a local branch (`refs/heads/<name>`) `deleteReference` supplies
|
|
41
|
+
them: it refuses a branch checked out in any worktree (`checkedOut`, read from `git worktree list`, not the
|
|
42
|
+
cached list), and removes the `branch.<name>` config section and GitLens's per-branch metadata with the ref.
|
|
43
|
+
Only `git branch -d`'s merged check is left out, since a compare-and-swap caller has already decided.
|
|
44
|
+
- Both fire the cache and repository hooks a branch, remote-tracking, tag or `HEAD` move fires; a ref in another
|
|
45
|
+
namespace (`refs/kepler/...`) announces nothing, since nothing core caches can observe it and an `unknown`
|
|
46
|
+
change would make a host refresh everything.
|
|
47
|
+
|
|
48
|
+
The sync-tools op maps onto these one-to-one:
|
|
49
|
+
|
|
50
|
+
| `updateRef` option | core call |
|
|
51
|
+
| ------------------ | ---------------------------------------------------------------- |
|
|
52
|
+
| `previousSha` | `updateReference(repoPath, ref, sha, { expected: previousSha })` |
|
|
53
|
+
| `mustNotExist` | `updateReference(repoPath, ref, sha, { expected: 'absent' })` |
|
|
54
|
+
| `remove` | `deleteReference(repoPath, ref, { expected: previousSha })` |
|
|
55
|
+
| none | `updateReference(repoPath, ref, sha)` |
|
|
56
|
+
|
|
57
|
+
`'absent'` against an existing ref rejects as `conflict` and leaves the ref where it was. Deleting a ref that
|
|
58
|
+
is already gone rejects as `notFound` when `expected` is given (the caller named a value it expected to
|
|
59
|
+
remove), and resolves when it is not.
|
|
60
|
+
|
|
61
|
+
The library reads any rejection as the ref's answer, so the host must tell a cancellation apart from a
|
|
62
|
+
refusal. A cancelled call rejects with core's `CancellationError` (`name === 'CancellationError'`), never a
|
|
63
|
+
`ReferenceUpdateError`, and that includes a signal already aborted when the call is made; a host adapting to
|
|
64
|
+
a library that expects `AbortError` maps it by name. A cancel that lands while git is running says nothing
|
|
65
|
+
about whether the ref moved, so re-read it before acting on the outcome.
|
|
66
|
+
|
|
67
|
+
**Breaking:** a caller that relied on a failed `updateReference` resolving silently now gets a rejection. No
|
|
68
|
+
caller in this repository did.
|
|
69
|
+
|
|
70
|
+
## 2. Who clears after a write, and telling core about a raw one — changed / added
|
|
71
|
+
|
|
72
|
+
A typed mutator announces its write through `hooks.cache.onReset`, and core used to clear nothing itself on
|
|
73
|
+
that path: each host's handler was expected to forward to `Cache.clearCaches`. Two things followed. A host
|
|
74
|
+
with no handler kept serving pre-write reads after every typed write. And `clearCaches` only soft-invalidates
|
|
75
|
+
a shared entry that is still in flight, so even a wired host could hand a caller that arrived after the write
|
|
76
|
+
a read that started before it; GitLens also dropped the executor's pending commands on every reset, Kepler did
|
|
77
|
+
not.
|
|
78
|
+
|
|
79
|
+
A consumer that mutates through `provider.git.run` (a rebase driven by a library, a `clean`, a ref bookkeeping
|
|
80
|
+
write) had nothing to call at all, and typed reads served the pre-mutation answer until a file watcher caught
|
|
81
|
+
up, or forever with no watcher. Kepler classified verbs itself and called `clearCaches` after each one. The
|
|
82
|
+
sharpest case: it fetches PR heads through a raw `fetch` (it needs a per-process credential environment the
|
|
83
|
+
typed fetch cannot carry), and a typed `refs.validateReference` right after it served the pre-fetch tip for up
|
|
84
|
+
to 60s. `clearCaches` fixed that read but fired none of the host's hooks, so nothing else in the host learned
|
|
85
|
+
the refs moved.
|
|
86
|
+
|
|
87
|
+
- The provider wraps `onReset` when it is constructed. On every reset it hard-evicts its own caches for those
|
|
88
|
+
types (`Cache.evictCaches`, so a later caller never joins pre-write work), drops the written repository's
|
|
89
|
+
pending commands (its registered worktrees' included, no other repository's), then calls the host's handler.
|
|
90
|
+
Clears driven by a file watcher reach `clearCaches` directly and keep sharing in-flight reads, which is what
|
|
91
|
+
stops a burst of file events from spawning duplicate reads. A host's handler now only has host-side state to
|
|
92
|
+
update; forwarding to `clearCaches` as well is redundant but harmless.
|
|
93
|
+
- `provider.notifyChanged(repoPath, changes: RepositoryChange[], options?: { cache?: CachedGitTypes[] | 'all' })`
|
|
94
|
+
fires the same `cache.onReset` and `repository.onChanged` a typed write would, so a raw mutation gets the
|
|
95
|
+
same treatment, host listeners included. Without `cache` it resets what `changes` map to, the same caches a
|
|
96
|
+
file watcher's change of those kinds clears; an empty `changes`, or one naming `unknown`, resets everything,
|
|
97
|
+
and `cache: 'all'` always does. A consumer naming its own changes must name every kind the write can touch
|
|
98
|
+
(a `commit` that concludes a merge is `pausedOp` too), or those caches keep their pre-write answer.
|
|
99
|
+
|
|
100
|
+
## 3. Worktree administration — added / extended
|
|
101
|
+
|
|
102
|
+
- `worktrees.pruneWorktrees(repoPath, options?: { expire? })` — `git worktree prune`. Kepler prunes
|
|
103
|
+
after a directory it removed by hand, and before reusing a path git still lists. Like `deleteWorktree`, it
|
|
104
|
+
unregisters each worktree it removed from the cache.
|
|
105
|
+
- `worktrees.lockWorktree(repoPath, path, options?: { reason? })` — `git worktree lock --reason`. The reason
|
|
106
|
+
is how Kepler marks a worktree as task-owned; only `unlockWorktree` existed.
|
|
107
|
+
- `worktrees.createWorktree(repoPath, path, options?, runOptions?)` and
|
|
108
|
+
`deleteWorktree(repoPath, path, options?, runOptions?)` — gained run options. A large worktree's checkout or
|
|
109
|
+
removal legitimately outlasts the default per-command timeout, and a caller may need to cancel either. A
|
|
110
|
+
cancelled call rejects as `CancellationError`, not a worktree error. The thrown `WorktreeDeleteError` keeps git's
|
|
111
|
+
raw output reachable on `original`, which is what a platform-specific retry classifier needs.
|
|
112
|
+
|
|
113
|
+
## 4. `git clean`, and the staging sub-provider's missing hooks — added / fixed
|
|
114
|
+
|
|
115
|
+
- `staging.clean(repoPath, options?: { paths?, directories?, force?, ignored? })` — the only way to
|
|
116
|
+
discard untracked files; `ops.restore` cannot.
|
|
117
|
+
- Every staging mutation (`stageFile(s)`, `stageDirectory`, `stageAll`, `unstage*`, `removeFile(s)`, `clean`)
|
|
118
|
+
now fires `onReset('status', 'diff', 'tracking')` and the index change, except when it targets a temporary
|
|
119
|
+
index, which is not the repository's. Before this, a consumer that staged a conflict resolution through the
|
|
120
|
+
typed API and then read `getConflictingFiles()` could be answered from the index as it stood before the
|
|
121
|
+
stage.
|
|
122
|
+
|
|
123
|
+
## 5. Upstream and hooks config keys — added
|
|
124
|
+
|
|
125
|
+
Kepler writes `branch.<name>.remote` / `branch.<name>.merge` (to set an upstream whose remote ref does not
|
|
126
|
+
exist yet, which `--set-upstream-to` refuses) and reads `core.hooksPath`. All three are now documented members
|
|
127
|
+
of `GitConfigKeys`, so `config.getConfig` / `setConfig` serve them with the usual cache. Writing either
|
|
128
|
+
upstream key also resets the cached branch list, which reports the upstream.
|
|
129
|
+
|
|
130
|
+
## 6. `git pull --ff-only` — extended
|
|
131
|
+
|
|
132
|
+
`ops.pull` gained `fastForward: 'only'`, spelled as `ops.merge` spells it. Kepler advances a local base branch
|
|
133
|
+
before forking a task worktree from it, and must not create a merge commit or start a rebase in a checkout it
|
|
134
|
+
does not own. A refused fast-forward is a `PullError` with the new `noFastForward` reason (the name
|
|
135
|
+
`FetchError` already uses); before this it fell through to `other`. A base branch checked out nowhere has no
|
|
136
|
+
working tree to pull into, so it is advanced by fetching `<upstream>:<branch>`, which git refuses unless it
|
|
137
|
+
fast-forwards; without the option that path only fetches. That fetch runs without `--update-head-ok`: "checked
|
|
138
|
+
out nowhere" comes from the cached worktree list, so git's own refusal is what keeps a branch that is in fact
|
|
139
|
+
checked out from moving. A branch with no upstream is `PullError` reason `noUpstream`, on this path and for a
|
|
140
|
+
checked-out pull alike. `merge`'s `true` / `false` are not offered: a pull configured to rebase ignores `--ff`
|
|
141
|
+
/ `--no-ff`.
|
|
142
|
+
|
|
143
|
+
## 7. A commit from a tree with several parents and an author — added
|
|
144
|
+
|
|
145
|
+
A compose or stack engine writes merge commits and replays commits with their original author, which the
|
|
146
|
+
single-parent, identity-less `commits.createUnreachableCommitFromTree` could not.
|
|
147
|
+
|
|
148
|
+
- `commits.createCommitFromTree(repoPath, tree, { parents, message, author?, committer?, sign?, source? })` runs
|
|
149
|
+
`commit-tree` with the message on stdin and the identity in the per-call environment, returning the sha.
|
|
150
|
+
With `sign` it reports a signing failure as `SigningError` and fires the signing hooks.
|
|
151
|
+
- It is the only path that writes a commit object. `createUnreachableCommitFromTree` is removed, and the patch
|
|
152
|
+
sub-provider's commits (`createUnreachableCommitForPatch`, `createUnreachableCommitsFromPatches`,
|
|
153
|
+
`createEmptyInitialCommit`) go through it with unchanged results.
|
|
154
|
+
|
|
155
|
+
## 8. `git init` — added
|
|
156
|
+
|
|
157
|
+
`GitProvider.clone` existed; `init(path, options?: { defaultBranch?, bare? })` did not. Kepler creates a
|
|
158
|
+
repository for a folder the user adds.
|
|
159
|
+
|
|
160
|
+
## 9. Forced ref reads — added
|
|
161
|
+
|
|
162
|
+
After a change core neither made nor was told about (another process moving a branch, a raw `fetch` the
|
|
163
|
+
consumer has not reported yet), an ordinary read can answer from cache until its TTL runs out. `notifyChanged`
|
|
164
|
+
is the fix when the consumer made the change; `force` is for a read that must be current regardless.
|
|
165
|
+
|
|
166
|
+
- `refs.validateReference(repoPath, ref, { relativePath?, force? }, cancellation?)`,
|
|
167
|
+
`refs.getReference(repoPath, ref, { force? }, cancellation?)`,
|
|
168
|
+
`branches.getBranch(repoPath, name?, { force? }, cancellation?)` and
|
|
169
|
+
`revision.resolveRevision(repoPath, ref, pathOrUri?, { force? })`. A forced read drops its cache entry before
|
|
170
|
+
reading, so it neither returns the cached answer nor joins a read already in flight (a fresh entry gets a
|
|
171
|
+
fresh cancellation aggregate, whose id is part of the executor's dedup key), and it stores the fresh answer
|
|
172
|
+
for later unforced reads. `getStatus({ force })` and `getPausedOperationStatus({ force })` are the precedent.
|
|
173
|
+
- `getBranch({ force })` evicts the whole branch cascade first: branch lists, ref tips, merge bases, commit
|
|
174
|
+
counts and resolved revisions, the set a branch move clears, since each can be derived from a ref that moved.
|
|
175
|
+
`getReference({ force })` forces its validation and branch lookup; tags are not forced.
|
|
176
|
+
- `validateReference`'s `relativePath` moved into the options object, and `getBranch` / `getReference` take
|
|
177
|
+
options before `cancellation`, matching `getStatus`.
|
|
178
|
+
|
|
179
|
+
## 10. An inherited git environment — fixed
|
|
180
|
+
|
|
181
|
+
A host launched by git (a hook, a `rebase -x` step, an editor opened as `GIT_EDITOR`) inherits that
|
|
182
|
+
repository's `GIT_DIR`, `GIT_WORK_TREE`, `GIT_INDEX_FILE` and friends, and git lets them override the working
|
|
183
|
+
directory, so every command core ran targeted the launching repository. The base environment now drops
|
|
184
|
+
`GIT_DIR`, `GIT_WORK_TREE`, `GIT_INDEX_FILE`, `GIT_COMMON_DIR`, `GIT_OBJECT_DIRECTORY`,
|
|
185
|
+
`GIT_ALTERNATE_OBJECT_DIRECTORIES` and `GIT_NAMESPACE` from the inherited `process.env`, plus the state git
|
|
186
|
+
hands a child about its own command: `GIT_CONFIG_PARAMETERS` (its `-c` options), `GIT_EXEC_PATH`, `GIT_PREFIX`
|
|
187
|
+
and `GIT_REFLOG_ACTION`. None is worth keeping when set deliberately: a `GIT_DIR` would point every repository
|
|
188
|
+
the host opens at one. `GIT_AUTHOR_*` / `GIT_COMMITTER_*` are different, since a deliberate identity (direnv,
|
|
189
|
+
say) breaks nothing, so they are dropped only when git launched the host, which it always marks by exporting
|
|
190
|
+
`GIT_EXEC_PATH`: an editor opened for `commit --amend` inherits that commit's author and date. `GIT_CONFIG_COUNT` /
|
|
191
|
+
`GIT_CONFIG_KEY_<n>` / `GIT_CONFIG_VALUE_<n>` are kept: git never sets them itself, so inherited ones are the
|
|
192
|
+
user's deliberate configuration. Only the inherited environment is scrubbed; a host's `env`, `getEnvironment()`
|
|
193
|
+
and a per-call `env` can still set any of these, as temporary-index staging does with `GIT_INDEX_FILE`.
|
|
194
|
+
|
|
195
|
+
## 11. Run options on stash and clean — extended
|
|
196
|
+
|
|
197
|
+
`stash.applyStash`, `stash.saveStash` and `staging.clean` take the trailing `runOptions` (`env`,
|
|
198
|
+
`cancellation`, `timeout`) every other long-running operation already did. No typed default changed: see the
|
|
199
|
+
timeout decision below.
|
|
200
|
+
|
|
201
|
+
## 12. A raw run that announces its own change — added
|
|
202
|
+
|
|
203
|
+
`git.run({ notify })` calls the provider's `notifyChanged` once the run settles, succeeded or failed (a
|
|
204
|
+
command that fails partway may still have moved something). An explicit `RepositoryChange[]` announces exactly
|
|
205
|
+
that. `'infer'` classifies the argv: read-only and object-store-only commands announce nothing, a write
|
|
206
|
+
announces its verb's changes for the repository it ran in (the `-C` target when given). Run under `-C`, a
|
|
207
|
+
write also announces to the caller's own tree the kinds every worktree sharing that `.git` sees (`heads`,
|
|
208
|
+
`tags`, `stash`, `remotes`, `worktrees`, `config`), since the target may be a worktree the cache never
|
|
209
|
+
registered; its HEAD, index, paused operation and FETCH_HEAD are the target's alone. A write it can't
|
|
210
|
+
classify resets both trees in full. Unset, nothing is classified and the run takes the same path as before.
|
|
211
|
+
|
|
212
|
+
## 13. Fetch with explicit refspecs, and without FETCH_HEAD — extended
|
|
213
|
+
|
|
214
|
+
- `ops.fetch(repoPath, { remote, refspecs, prune? })` passes the refspecs verbatim after the remote
|
|
215
|
+
(`+refs/heads/x:refs/remotes/o/x`, `upstream:local`). It never adds `-u`, so git keeps refusing to write
|
|
216
|
+
into a checked-out branch, which a caller fast-forwarding a branch checked out nowhere relies on. A refspec
|
|
217
|
+
whose destination is a local branch or tag also announces `heads` or `tags`. The type refuses `refspecs`
|
|
218
|
+
beside `branch`, `all` or `pull`.
|
|
219
|
+
- `preserveFetchHead` passes `--no-write-fetch-head`, on every form of fetch. A checkout has one
|
|
220
|
+
`FETCH_HEAD`, and a user's own `git pull` reads it back right after its fetch, so a background fetch landing
|
|
221
|
+
in between breaks that pull. Git older than 2.29 has no such flag; there the option is ignored rather than
|
|
222
|
+
failing the fetch.
|
|
223
|
+
|
|
224
|
+
## 14. Clone into an exact folder — extended
|
|
225
|
+
|
|
226
|
+
`clone(url, parentPath, { folderName }, runOptions)`, on both core's `GitProvider` and GitLens's
|
|
227
|
+
`GlGitProvider`. `folderName` is used as given, with no numbering past a taken name, so git's own refusal of a
|
|
228
|
+
non-empty folder surfaces. Without it the folder is derived from the URL and numbered as before. `runOptions`
|
|
229
|
+
carries the clone's credentials, cancellation and timeout.
|
|
230
|
+
|
|
231
|
+
## 15. Status without untracked detail or branch state — extended
|
|
232
|
+
|
|
233
|
+
`status.getStatus(repoPath, { untracked: 'no' | 'normal' | 'all', branch: false })`. `untracked` picks git's
|
|
234
|
+
`-u` mode (default `all`, as before). `branch: false` skips `--branch`, whose ahead/behind against the upstream
|
|
235
|
+
is the slow part on a large divergence; the result's `branch`, `sha`, `upstream` and `detached` are then unset
|
|
236
|
+
and mean nothing. A narrower read gets its own dedup key, so a full-status caller never joins it and loses
|
|
237
|
+
files. `GIT_OPTIONAL_LOCKS=0` was already set on every status read, so there is no lock option.
|
|
238
|
+
|
|
239
|
+
## 16. A reflog read filtered by message — added
|
|
240
|
+
|
|
241
|
+
`refs.getReflogEntries(repoPath, ref, { grep? })` returns `{ sha, message }` per entry, newest first, where
|
|
242
|
+
`grep` is git's `--grep-reflog` pattern. It resolves `[]` when nothing matches or the ref has no reflog, and
|
|
243
|
+
rejects when the read fails, so a caller can tell "no answer" from "never read". It runs `reflog show <ref> --`,
|
|
244
|
+
not `reflog <ref>`: a branch named `delete`, `expire` or `exists` would otherwise run that subcommand, and one
|
|
245
|
+
named like a tracked file would fail as ambiguous. Core's own base-branch inference moved onto it, which
|
|
246
|
+
fixed both cases there.
|
|
247
|
+
|
|
248
|
+
## 17. Commit counts that exclude other refs — extended
|
|
249
|
+
|
|
250
|
+
`commits.getCommitCount(repoPath, rev, { excluding: { branches?, remotes?, tags?, refs?, except? } },
|
|
251
|
+
cancellation?)` counts what `rev` has that other refs don't, such as what deleting a branch would orphan.
|
|
252
|
+
`branches` / `remotes` / `tags` exclude a whole namespace, `refs` adds revisions verbatim, and `except` takes
|
|
253
|
+
full ref names back out of an excluded namespace. Core rewrites each `except` entry to the short form and
|
|
254
|
+
places it right before its own `--branches` / `--remotes` / `--tags`, because git matches `--exclude` against
|
|
255
|
+
the short name and applies it only to the next pseudo-ref option. The spelling that reads naturally,
|
|
256
|
+
`--exclude=refs/heads/x --branches`, excludes nothing. The GitHub provider returns `undefined` for an excluding
|
|
257
|
+
count, which its API cannot answer. A count is cached until a branch, remote-tracking branch or tag changes,
|
|
258
|
+
so a `refs` entry outside those namespaces (a tool's own `refs/<tool>/…`) can move without refreshing it;
|
|
259
|
+
pass its SHA instead, which keys a new read.
|
|
260
|
+
|
|
261
|
+
`commits.getLogShas` takes the same `excluding`, listing the SHAs instead of counting them; an omitted `rev` is
|
|
262
|
+
anchored to `HEAD` (a bare `--not` lists nothing), and the GitHub provider returns no SHAs.
|
|
263
|
+
|
|
264
|
+
**Breaking:** `getCommitCount` takes `options` before `cancellation`.
|
|
265
|
+
|
|
266
|
+
## 18. The empty tree in the repository's object format — added / fixed
|
|
267
|
+
|
|
268
|
+
The empty tree's id differs between SHA-1 and SHA-256 repositories, and core hardcoded the SHA-1 one as
|
|
269
|
+
`rootSha`, so every diff against a root commit's missing parent failed in a SHA-256 repository.
|
|
270
|
+
`revision.getEmptyTreeSha(repoPath)` computes it with `hash-object -t tree --stdin` (no `-w`; git knows the
|
|
271
|
+
empty tree without storing it), once per repository and shared across its worktrees. Every `rootSha` use now
|
|
272
|
+
goes through it, and `rootSha` is removed. GitHub repositories are always SHA-1.
|
|
273
|
+
|
|
274
|
+
**Breaking:** `rootSha` is removed from `@gitlens/git/models/revision`.
|
|
275
|
+
|
|
276
|
+
## 19. A link's clone of a large repository — fixed (GitLens)
|
|
277
|
+
|
|
278
|
+
Not a Kepler gap, found alongside §14: GitLens's deep-link clone ran under `gitlens.advanced.git.timeout` and
|
|
279
|
+
died partway through a large repository. It now runs uncapped, from a cancellable progress notification.
|
|
280
|
+
|
|
281
|
+
## Overlaps with existing APIs, and how each was settled
|
|
282
|
+
|
|
283
|
+
- **Refs containing a sha.** `branches.getBranchesWithCommits(repoPath, [sha], undefined, { all, mode })` and
|
|
284
|
+
`tags.getTagsWithCommit` already answer it, so `getRefTips` did not gain `--contains` / `--points-at`
|
|
285
|
+
filters. Kepler's one use (is a sha on any other local or remote branch) is `getBranchesWithCommits`.
|
|
286
|
+
- **Config keys.** Added to the documented union rather than served by a separate untyped
|
|
287
|
+
`getConfigValue` / `setConfigValue`, which would have been a second way to read every key.
|
|
288
|
+
- **Commit objects.** `createCommitFromTree` replaced `createUnreachableCommitFromTree` and the patch
|
|
289
|
+
sub-provider's own `commit-tree` calls, rather than sitting beside them.
|
|
290
|
+
- **Deleting refs.** `deleteReference` stays beside `branches.deleteLocalBranch` and `tags.deleteTag` on
|
|
291
|
+
purpose. `deleteLocalBranch` keeps `git branch -d`'s merged check, which the delete flow's force-retry relies
|
|
292
|
+
on, and takes several names; `deleteTag` reports a `TagError` the UI renders. `deleteReference` is the
|
|
293
|
+
compare-and-swap form for any namespace, and shares the branch cleanup with `deleteLocalBranch`.
|
|
294
|
+
- **Clearing caches.** `evictCaches` stays beside `clearCaches`: a write hard-evicts, a watcher clear keeps
|
|
295
|
+
sharing in-flight reads (see the decisions below).
|
|
296
|
+
- **Reflog reads.** `getReflogEntries` stays beside `commits.getIncomingActivity`, which walks `HEAD`'s reflog
|
|
297
|
+
for merges and pulls, pages, and aggregates; a message grep on it would change its paging. Core's private
|
|
298
|
+
reflog greps were folded into `getReflogEntries` rather than kept.
|
|
299
|
+
- **Dirty checks.** `getStatus({ untracked, branch: false })` stays beside `status.hasWorkingChanges`, which
|
|
300
|
+
answers yes/no with early exits; a caller counting files needs the list.
|
|
301
|
+
- **Unpushed commits.** `getCommitCount({ excluding })` stays beside `hasUnpublishedCommits` /
|
|
302
|
+
`filterUnpublishedShas`, which are early-exit probes against remotes alone and count nothing.
|
|
303
|
+
|
|
304
|
+
## Deferred, deliberately
|
|
305
|
+
|
|
306
|
+
- **Index and object plumbing** (`read-tree`, `write-tree`, `update-index --index-info`, `mktree`,
|
|
307
|
+
`hash-object -w --stdin`, `cat-file -p`). The libraries drive these through `exec` with a scratch
|
|
308
|
+
`GIT_INDEX_FILE` and parse the results themselves; a typed surface would re-encode the same argv with more
|
|
309
|
+
room to drift. Revisit when a second consumer needs them.
|
|
310
|
+
- **Generic `for-each-ref` with a caller-supplied format.** The `RefRecord` shape is private on purpose.
|
|
311
|
+
- **A typed `worktree remove` for every consumer.** Kept the raw form where a consumer classifies platform
|
|
312
|
+
failures from stderr; §3's `original` makes the typed form usable for that too.
|
|
313
|
+
|
|
314
|
+
## Bridging core into the tools libraries
|
|
315
|
+
|
|
316
|
+
The libraries take their own port and know nothing of core, so a host serving one through core reshapes what
|
|
317
|
+
core rejects with:
|
|
318
|
+
|
|
319
|
+
- **Cancellation.** Core rejects with `CancellationError` (`name === 'CancellationError'`); a library that
|
|
320
|
+
expects `AbortError` gets it mapped by name.
|
|
321
|
+
- **"Git answered no" versus a structural failure.** sync-tools tells them apart by whether `err.code` is a
|
|
322
|
+
number, and compose-tools reads `err.stderr`: the shape of Node's `execFile` error. Core's `GitError`
|
|
323
|
+
carries `stderr` but reports the exit as `exitCode`, which is a number or numeric string for a real exit and
|
|
324
|
+
an errno string (`'ENOENT'`) when git never started. A typed method's own error, such as
|
|
325
|
+
`ReferenceUpdateError`, keeps that `GitError` on `original`.
|
|
326
|
+
|
|
327
|
+
## Decisions worth recording, because each closes off a plausible-looking alternative
|
|
328
|
+
|
|
329
|
+
- `updateReference` throws rather than gaining an `errors: 'throw'` option. A ref writer whose failures are
|
|
330
|
+
invisible by default is the bug, not a mode.
|
|
331
|
+
- `'absent'` rather than `expected: ''`. The empty old value is git's encoding of "must not exist"; a caller
|
|
332
|
+
spelling it as an empty string reads like a mistake, and an argv filter that drops empty strings would turn
|
|
333
|
+
a create into an unconditional overwrite.
|
|
334
|
+
- New config keys join `GitConfigKeys` rather than widening it to `string` or adding an untyped read. The
|
|
335
|
+
union's value is that every listed key is documented; an escape hatch would keep the autocomplete and lose
|
|
336
|
+
the guarantee.
|
|
337
|
+
- `deleteReference` gives a branch `git branch -d`'s guard and cleanup rather than refusing branches. A
|
|
338
|
+
compare-and-swap delete of a branch is legitimate (sync-tools' port contract exercises one), and refusing it
|
|
339
|
+
would push the caller back to a raw `update-ref -d` that orphans the same state.
|
|
340
|
+
- A write hard-evicts while a watcher clear keeps sharing in-flight reads, rather than one policy for both. A
|
|
341
|
+
write is a known point after which an older read is wrong; a watcher tick has no such ordering, and sharing
|
|
342
|
+
the in-flight read is what keeps a burst of file events from multiplying git processes.
|
|
343
|
+
- `notifyChanged` on the provider, and `git.run({ notify })` bound to it rather than a second invalidation
|
|
344
|
+
path. The executor has no cache and no hooks, so the provider binds itself to its `Git` and a `notify` run
|
|
345
|
+
calls the same `notifyChanged`. `'infer'` classifies the argv (read-only and object-store-only commands
|
|
346
|
+
announce nothing; a `-C` write also announces to the caller's tree what every worktree sees), for a
|
|
347
|
+
consumer whose raw runs are too varied to name the changes by hand.
|
|
348
|
+
- Timeouts stay the caller's. No typed operation's default was lifted to "no timeout" for this round, even
|
|
349
|
+
for fetch or clone: GitLens passes its `gitlens.advanced.git.timeout` setting as core's default, and an
|
|
350
|
+
uncapped background fetch would hang with nothing to stop it. A caller that knows an operation is long
|
|
351
|
+
passes `timeout: 0` with a cancellation, as GitLens's deep-link clone now does.
|
|
352
|
+
- `except` takes full ref names and core converts them, rather than exposing git's short-form `--exclude`. The
|
|
353
|
+
short form is what every caller gets wrong, silently.
|
|
354
|
+
- Staging hooks skip the temporary-index case rather than firing unconditionally. A scratch index is not the
|
|
355
|
+
repository's; announcing an index change for it would invalidate status for nothing.
|
|
356
|
+
|
|
357
|
+
**Kepler-side follow-up**, once a core release carries these: serve sync-tools' `updateRef` op through
|
|
358
|
+
`updateReference` / `deleteReference` (mapping in §1), delete the review ref with `deleteReference`, drop the
|
|
359
|
+
verb classifier in `core-git-port.ts` in favour of `git.run({ notify: 'infer' })`, and drop its interim
|
|
360
|
+
environment scrub (§10). Then move the second round's raw sites onto their typed forms: credential fetches onto
|
|
361
|
+
`fetch({ remote, refspecs, preserveFetchHead: true })` (§13), the clone onto `clone(..., { folderName }, { env,
|
|
362
|
+
timeout: 0 })` (§14), the porcelain count onto `getStatus({ untracked: 'normal', branch: false, force: true })`
|
|
363
|
+
(§15), the creation probe onto `getReflogEntries` (§16), the unpushed count onto `getCommitCount({ excluding })`
|
|
364
|
+
(§17, which also fixes its always-zero `--exclude`), and the object-format probe onto `getEmptyTreeSha` (§18).
|
|
365
|
+
Then retire the remaining `TODO(core-gitlens)` residuals.
|
|
@@ -182,34 +182,81 @@ which excludes unassigned issues. A provider that declares no filtered search ke
|
|
|
182
182
|
See the note in `reads/broaden.ts` and
|
|
183
183
|
[`integrations.md` §9](./integrations.md#9-per-provider-behavior-worth-designing-around).
|
|
184
184
|
|
|
185
|
-
**Batch issue resolution (#5802).** `getIssuesBatch` resolves N
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
failure as an absence is the bug that distinction
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
`(resourceId, ABC-123)` — no owner, no repo, and an
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
`resourceId` is
|
|
200
|
-
`resourceUrl`,
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
185
|
+
**Batch issue resolution (#5802, #5810).** `getIssuesBatch` resolves N issues by identity in one call, for the
|
|
186
|
+
identity question a search cannot answer: "which issue does this branch name reference". It uses the point read
|
|
187
|
+
rather than a search, so there is no result ceiling and no ordering — and an absent slot is a PROVEN absence
|
|
188
|
+
rather than "not found within a page budget", which is what lets a consumer CACHE a miss. A target that failed —
|
|
189
|
+
its whole request, or just that target, e.g. in an org enforcing SAML SSO the token isn't authorized for — is not
|
|
190
|
+
returned at all, so the two stay distinguishable; caching a failure as an absence is the bug that distinction
|
|
191
|
+
prevents.
|
|
192
|
+
|
|
193
|
+
A target takes the form its provider addresses an issue by. GitHub/GHE take `(owner, repo, number)` coordinates,
|
|
194
|
+
aliased up to 25 per request. A tracker issue is addressed by `(resourceId, ABC-123)` — no owner, no repo, and an
|
|
195
|
+
identifier that is not a number — so Jira (Cloud and Data Center) and Linear take
|
|
196
|
+
`{ key, resourceId, resourceUrl?, identifier }` targets, each answered by the single-issue read the tracker already
|
|
197
|
+
implements. That replaces a scoped page-walk over `listIssueTrackerIssuesPage`, which cannot prove absence without
|
|
198
|
+
draining the whole scope, where a miss is the common outcome. A key asked of several resources is one call with one
|
|
199
|
+
target per resource. `resourceId` is trusted, so the read performs no resource discovery; Jira Cloud also requires
|
|
200
|
+
`resourceUrl`, because the issue response's `self` is an API endpoint rather than a browser link. Trello refuses:
|
|
201
|
+
its single-issue read can fall back to a capped board scan for a numeric identifier, where a "not found" cannot be
|
|
202
|
+
told from a card beyond the cap. Bitbucket and Bitbucket DC, which have no issues, refuse too.
|
|
203
|
+
|
|
204
|
+
GitLab and Azure DevOps take the same coordinates as GitHub, plus `project` on Azure DevOps, whose work items belong to
|
|
205
|
+
the project rather than to a repository, so `repo` is ignored there. They cost one request per target and replace a
|
|
206
|
+
scoped page-walk over `listIssuesPage`, which could not prove absence either. Neither host's existing single-issue
|
|
207
|
+
read could, so each has a strict one. A GitLab miss costs a second request, because provider-apis reports a reply
|
|
208
|
+
carrying only GraphQL errors the same way as a missing issue. An Azure DevOps miss is trusted only on Azure's own
|
|
209
|
+
not-found body: `WorkItemUnauthorizedAccessException` (TF401232, "does not exist, or you do not have permissions to
|
|
210
|
+
read it", so absent means not visible to this connection) or `ProjectDoesNotExistWithNameException`. An HTML 404, a
|
|
211
|
+
410 or any other failure fails the target. Rows match the repository-scoped `listIssuesPage` rows, except that an
|
|
212
|
+
Azure DevOps row has no `project`.
|
|
213
|
+
|
|
214
|
+
A self-hosted Jira (Data Center, #5872) is served by the same tracker targets once the call carries the host:
|
|
215
|
+
`getIssuesBatch` takes the `domain` its paged siblings take, and a target's `resourceId` is the host itself (the
|
|
216
|
+
instance's single resource; a call with any target naming another host than the read resolves to is refused whole).
|
|
217
|
+
It does NOT fall back to the primary connection the way the paged reads do — a self-managed tracker requires a
|
|
218
|
+
`domain` or a `connectionId` with a configured host and the call is refused otherwise — because its proven absence
|
|
219
|
+
is cacheable and two self-hosted instances routinely issue the same keys, so an answer from whichever host is
|
|
220
|
+
primary would be cached under a key naming a different instance. The branch-association resolver
|
|
221
|
+
(`getIssueFromGitConfigEntityIdentifier`) follows the same rule from the other side: its resolver receives the
|
|
222
|
+
identifier's `domain`, and a Jira Data Center identifier without one is dropped rather than resolved.
|
|
223
|
+
|
|
224
|
+
**Batch pull request resolution.** `getPullRequestsBatch` is the identity read for pull requests: N
|
|
225
|
+
`(owner, repo, number)` coordinates, plus `project` on Azure DevOps, in any state. Before it the facade had only
|
|
226
|
+
list, search, count and sweep reads for pull requests, so a consumer found one by scanning its repository's most
|
|
227
|
+
recently updated pull requests per state and matching the URL — and an older merged pull request could be neither
|
|
228
|
+
found nor proven gone. Same absence/failure contract as `getIssuesBatch`. It serves every git host, Bitbucket and
|
|
229
|
+
Bitbucket DC included: GitHub/GHE alias up to 25 point reads into one document, while GitLab, Bitbucket, Bitbucket
|
|
230
|
+
DC and Azure DevOps have no batch form and cost one request per target. A GitLab miss costs a second request, because
|
|
231
|
+
provider-apis' GitLab read reports a reply that carries only GraphQL errors the same way as a missing merge
|
|
232
|
+
request, so its `null` alone does not prove absence; an Azure DevOps miss is trusted only when Azure's own error
|
|
233
|
+
body names the pull request, repository or project as not found, not on any other 404 or a 410. Rows take the
|
|
234
|
+
list reads' conversion on every host but Bitbucket Cloud, which has no single pull request read in provider-apis
|
|
235
|
+
and so answers from GitLens' own REST read instead, missing `commentsCount`, `isDraft` and the clone URLs. On
|
|
236
|
+
GitLab, a miss the confirming read then contradicts fails the target rather than answering with the confirming
|
|
237
|
+
read's own, differently-identified row. The read is uncached and does not go through
|
|
238
|
+
`IntegrationCacheProvider.getPullRequest`, whose key carries no connection, so the consumer's cache is the only
|
|
239
|
+
one holding the answer.
|
|
240
|
+
|
|
241
|
+
**Pull requests by branch.** `getPullRequestsForBranches` answers "which pull requests have this branch as their
|
|
242
|
+
head, in this repository" with no relationship to the user. Before it, Kepler correlated a local branch by matching
|
|
243
|
+
the account-wide sweep, which holds only pull requests the user authored, is assigned or reviews — so a teammate's
|
|
244
|
+
pull request from the user's branch never correlated — and a cold card paid for that whole sweep before showing
|
|
245
|
+
anything. It matches on the head branch name in every state, so a merged pull request whose branch was deleted is
|
|
246
|
+
still found, and on the head repository, the base one or the `headOwner` fork, so a same-named branch in another
|
|
247
|
+
fork never correlates; a `headOwner` equal to the owner means the base repository, so Kepler can pass the owner of
|
|
248
|
+
the remote the branch was pushed to. Up to 10 per branch; an empty list without `truncated` is a proven none a
|
|
249
|
+
consumer can cache, and a failed target is dropped with `fetchFailed`. GitHub/GHE answer up to 25 branches per
|
|
250
|
+
request, and every other host costs one request per branch. GitLab and Azure DevOps resolve each match (typically
|
|
251
|
+
0–1 per branch) through `getPullRequestsBatch`'s own read, so a pull request's `url`, and so its identity, is the
|
|
252
|
+
same whichever read found it. Bitbucket DC and Azure DevOps serve base-repository branches only and refuse a
|
|
253
|
+
`headOwner` naming another owner.
|
|
254
|
+
|
|
255
|
+
**Current account resolution.** `getCurrentAccount` answers who a git host connection is signed in as, on
|
|
256
|
+
demand. Before it, Kepler learned the viewer's account only when its `IntegrationManagerCacheProvider`
|
|
257
|
+
`getCurrentAccount` hook fired during some other read, so "is this pull request mine" could stay unanswered until
|
|
258
|
+
something else resolved the account. The read goes through that same hook, so there is still one cache. `account`
|
|
259
|
+
is never absent without a warning. Jira, Linear and Trello refuse: they have only a per-resource account.
|
|
213
260
|
|
|
214
261
|
**Kepler-side follow-up:** `ProviderScopeFilter` carries a single `repo?: string` today and needs the criteria
|
|
215
262
|
set; the `provider-data` adapter then routes "All visible" to `searchIssuesPage` + `countIssues`.
|
package/package.json
CHANGED
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
import { IssueShape } from "../../../git/models/issue.js";
|
|
2
|
-
import { IntegrationIds } from "../constants.js";
|
|
3
|
-
import { ProviderResult } from "../results.js";
|
|
4
|
-
import { ProviderReadContext } from "./context.js";
|
|
5
|
-
//#region ../plus/integrations/src/reads/trackerIssue.d.ts
|
|
6
|
-
export interface TrackerIssueResult {
|
|
7
|
-
key: string;
|
|
8
|
-
/**
|
|
9
|
-
* The resolved issue, or `undefined` when it provably does not exist or is not visible to this connection.
|
|
10
|
-
* A failed read returns no item and sets `fetchFailed`.
|
|
11
|
-
*/
|
|
12
|
-
issue?: IssueShape;
|
|
13
|
-
}
|
|
14
|
-
/**
|
|
15
|
-
* `domain` selects the host of a self-managed tracker (Jira Data Center), as it does for
|
|
16
|
-
* `listIssueTrackerIssuesPage`, and is ignored for the cloud trackers, which have a single canonical host.
|
|
17
|
-
*
|
|
18
|
-
* Unlike its siblings, this read never falls back to the primary connection for a self-managed tracker: it
|
|
19
|
-
* requires a `domain` or a `connectionId` with a configured host and refuses otherwise. Two self-hosted
|
|
20
|
-
* instances routinely issue the same project and issue keys, and this read's `issue: undefined` is a proven
|
|
21
|
-
* absence a caller may cache — an answer from whichever host happens to be primary would be cached under a key
|
|
22
|
-
* that names a different instance (#5872).
|
|
23
|
-
*/
|
|
24
|
-
export declare function getTrackerIssue(ctx: ProviderReadContext, options: {
|
|
25
|
-
providerId: IntegrationIds;
|
|
26
|
-
resourceId: string;
|
|
27
|
-
resourceUrl?: string;
|
|
28
|
-
key: string;
|
|
29
|
-
connectionId?: string;
|
|
30
|
-
domain?: string;
|
|
31
|
-
}): Promise<ProviderResult<TrackerIssueResult>>;
|
|
32
|
-
//#endregion
|
|
33
|
-
//# sourceMappingURL=trackerIssue.d.ts.map
|
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
import "../constants.js";
|
|
2
|
-
import { areDomainsOnSameHost, hostFromDomain } from "../utils/domain.utils.js";
|
|
3
|
-
import { isIssuesHostIntegrationId, isIssuesSelfManagedHostIntegrationId } from "../utils/integration.utils.js";
|
|
4
|
-
import { issueTrackerOnlySurfaceWarning, otherWarning } from "./warnings.js";
|
|
5
|
-
import { runCaptured } from "./drains.js";
|
|
6
|
-
import { isIssuesIntegration } from "../models/issuesIntegration.js";
|
|
7
|
-
//#region ../plus/integrations/src/reads/trackerIssue.ts
|
|
8
|
-
/**
|
|
9
|
-
* `domain` selects the host of a self-managed tracker (Jira Data Center), as it does for
|
|
10
|
-
* `listIssueTrackerIssuesPage`, and is ignored for the cloud trackers, which have a single canonical host.
|
|
11
|
-
*
|
|
12
|
-
* Unlike its siblings, this read never falls back to the primary connection for a self-managed tracker: it
|
|
13
|
-
* requires a `domain` or a `connectionId` with a configured host and refuses otherwise. Two self-hosted
|
|
14
|
-
* instances routinely issue the same project and issue keys, and this read's `issue: undefined` is a proven
|
|
15
|
-
* absence a caller may cache — an answer from whichever host happens to be primary would be cached under a key
|
|
16
|
-
* that names a different instance (#5872).
|
|
17
|
-
*/
|
|
18
|
-
async function getTrackerIssue(ctx, options) {
|
|
19
|
-
const refused = (warning) => ({
|
|
20
|
-
items: [],
|
|
21
|
-
warnings: [warning],
|
|
22
|
-
fetchFailed: true
|
|
23
|
-
});
|
|
24
|
-
const surface = "Issue resolution by key";
|
|
25
|
-
if (!isIssuesHostIntegrationId(options.providerId)) return refused(issueTrackerOnlySurfaceWarning(options.providerId, options.connectionId, surface));
|
|
26
|
-
if (options.resourceId.trim().length === 0) return refused(otherWarning(options.providerId, void 0, options.connectionId, `${surface} requires a resource id.`));
|
|
27
|
-
if (options.key.trim().length === 0) return refused(otherWarning(options.providerId, void 0, options.connectionId, `${surface} requires an issue key.`));
|
|
28
|
-
const resourceUrl = options.resourceUrl?.trim() || void 0;
|
|
29
|
-
if (options.providerId === "jira" && resourceUrl == null) return refused(otherWarning(options.providerId, void 0, options.connectionId, `${surface} requires the Jira resource URL so the result contains a browser link without resource discovery.`));
|
|
30
|
-
if (isIssuesSelfManagedHostIntegrationId(options.providerId) && (options.domain != null ? hostFromDomain(options.domain) == null : options.connectionId == null || !ctx.getConfigured(options.providerId).some((c) => c.id === options.connectionId && hostFromDomain(c.domain) != null))) return refused(otherWarning(options.providerId, void 0, options.connectionId, `${surface} requires a domain or a configured connection id for '${options.providerId}': every configured host can hold a different issue under the same key, so the read does not answer from the primary connection.`));
|
|
31
|
-
const integration = await ctx.getIntegrationForRead(options.providerId, options.connectionId, options.domain);
|
|
32
|
-
if (integration == null) {
|
|
33
|
-
const early = ctx.earlyReturnConnectionWarnings(options.providerId, options.connectionId, options.domain);
|
|
34
|
-
return {
|
|
35
|
-
items: [],
|
|
36
|
-
warnings: early.warnings,
|
|
37
|
-
fetchFailed: early.fetchFailed || void 0
|
|
38
|
-
};
|
|
39
|
-
}
|
|
40
|
-
if (!isIssuesIntegration(integration)) return refused(issueTrackerOnlySurfaceWarning(options.providerId, options.connectionId, surface));
|
|
41
|
-
if (!integration.supportsIssueLookupByResourceId) return refused(otherWarning(options.providerId, void 0, options.connectionId, `${surface} is not supported by '${options.providerId}'; its single-issue read cannot prove an absence, so a miss would not be safe to cache.`));
|
|
42
|
-
if (isIssuesSelfManagedHostIntegrationId(options.providerId) && !areDomainsOnSameHost(options.resourceId, integration.domain)) return refused(otherWarning(options.providerId, void 0, options.connectionId, `${surface} requires the resource id of '${options.providerId}' to name the host the read resolved to.`));
|
|
43
|
-
const domain = ctx.domainForRead(integration, options.providerId, options.connectionId, options.domain);
|
|
44
|
-
const issue = await runCaptured(options.providerId, domain, options.connectionId, () => integration.getIssueByResourceIdResult(options.resourceId, options.key, {
|
|
45
|
-
connectionId: options.connectionId,
|
|
46
|
-
resourceUrl
|
|
47
|
-
}), { warnOnMissingSession: true });
|
|
48
|
-
if (issue.warning != null) return refused(issue.warning);
|
|
49
|
-
return {
|
|
50
|
-
items: [{
|
|
51
|
-
key: options.key,
|
|
52
|
-
...issue.value != null ? { issue: issue.value } : {}
|
|
53
|
-
}],
|
|
54
|
-
warnings: []
|
|
55
|
-
};
|
|
56
|
-
}
|
|
57
|
-
//#endregion
|
|
58
|
-
export { getTrackerIssue };
|
|
59
|
-
|
|
60
|
-
//# sourceMappingURL=trackerIssue.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"trackerIssue.js","names":[],"sources":["../../../../../plus/integrations/src/reads/trackerIssue.ts"],"sourcesContent":["import type { IssueShape } from '@gitlens/git/models/issue.js';\nimport type { IntegrationIds } from '../constants.js';\nimport { IssuesCloudHostIntegrationId } from '../constants.js';\nimport { isIssuesIntegration } from '../models/issuesIntegration.js';\nimport type { ProviderResult, ProviderWarning } from '../results.js';\nimport { areDomainsOnSameHost, hostFromDomain } from '../utils/domain.utils.js';\nimport { isIssuesHostIntegrationId, isIssuesSelfManagedHostIntegrationId } from '../utils/integration.utils.js';\nimport type { ProviderReadContext } from './context.js';\nimport { runCaptured } from './drains.js';\nimport { issueTrackerOnlySurfaceWarning, otherWarning } from './warnings.js';\n\nexport interface TrackerIssueResult {\n\tkey: string;\n\t/**\n\t * The resolved issue, or `undefined` when it provably does not exist or is not visible to this connection.\n\t * A failed read returns no item and sets `fetchFailed`.\n\t */\n\tissue?: IssueShape;\n}\n\n/**\n * `domain` selects the host of a self-managed tracker (Jira Data Center), as it does for\n * `listIssueTrackerIssuesPage`, and is ignored for the cloud trackers, which have a single canonical host.\n *\n * Unlike its siblings, this read never falls back to the primary connection for a self-managed tracker: it\n * requires a `domain` or a `connectionId` with a configured host and refuses otherwise. Two self-hosted\n * instances routinely issue the same project and issue keys, and this read's `issue: undefined` is a proven\n * absence a caller may cache — an answer from whichever host happens to be primary would be cached under a key\n * that names a different instance (#5872).\n */\nexport async function getTrackerIssue(\n\tctx: ProviderReadContext,\n\toptions: {\n\t\tproviderId: IntegrationIds;\n\t\tresourceId: string;\n\t\tresourceUrl?: string;\n\t\tkey: string;\n\t\tconnectionId?: string;\n\t\tdomain?: string;\n\t},\n): Promise<ProviderResult<TrackerIssueResult>> {\n\tconst refused = (warning: ProviderWarning): ProviderResult<TrackerIssueResult> => ({\n\t\titems: [],\n\t\twarnings: [warning],\n\t\tfetchFailed: true,\n\t});\n\tconst surface = 'Issue resolution by key';\n\n\tif (!isIssuesHostIntegrationId(options.providerId)) {\n\t\treturn refused(issueTrackerOnlySurfaceWarning(options.providerId, options.connectionId, surface));\n\t}\n\n\tif (options.resourceId.trim().length === 0) {\n\t\treturn refused(\n\t\t\totherWarning(options.providerId, undefined, options.connectionId, `${surface} requires a resource id.`),\n\t\t);\n\t}\n\n\tif (options.key.trim().length === 0) {\n\t\treturn refused(\n\t\t\totherWarning(options.providerId, undefined, options.connectionId, `${surface} requires an issue key.`),\n\t\t);\n\t}\n\n\tconst resourceUrl = options.resourceUrl?.trim() || undefined;\n\tif (options.providerId === IssuesCloudHostIntegrationId.Jira && resourceUrl == null) {\n\t\treturn refused(\n\t\t\totherWarning(\n\t\t\t\toptions.providerId,\n\t\t\t\tundefined,\n\t\t\t\toptions.connectionId,\n\t\t\t\t`${surface} requires the Jira resource URL so the result contains a browser link without resource discovery.`,\n\t\t\t),\n\t\t);\n\t}\n\n\t// A `domain` only selects a host when it parses to one, and a `connectionId` only when it names a configured\n\t// connection that has one; anything else would resolve the primary host instead, which is the fallback this\n\t// read refuses.\n\tif (\n\t\tisIssuesSelfManagedHostIntegrationId(options.providerId) &&\n\t\t(options.domain != null\n\t\t\t? hostFromDomain(options.domain) == null\n\t\t\t: options.connectionId == null ||\n\t\t\t\t!ctx\n\t\t\t\t\t.getConfigured(options.providerId)\n\t\t\t\t\t.some(c => c.id === options.connectionId && hostFromDomain(c.domain) != null))\n\t) {\n\t\treturn refused(\n\t\t\totherWarning(\n\t\t\t\toptions.providerId,\n\t\t\t\tundefined,\n\t\t\t\toptions.connectionId,\n\t\t\t\t`${surface} requires a domain or a configured connection id for '${options.providerId}': every configured host can hold a different issue under the same key, so the read does not answer from the primary connection.`,\n\t\t\t),\n\t\t);\n\t}\n\n\tconst integration = await ctx.getIntegrationForRead(options.providerId, options.connectionId, options.domain);\n\tif (integration == null) {\n\t\t// A supplied connectionId or domain that no longer resolves is a broken target, not an empty account.\n\t\tconst early = ctx.earlyReturnConnectionWarnings(options.providerId, options.connectionId, options.domain);\n\t\treturn { items: [], warnings: early.warnings, fetchFailed: early.fetchFailed || undefined };\n\t}\n\tif (!isIssuesIntegration(integration)) {\n\t\treturn refused(issueTrackerOnlySurfaceWarning(options.providerId, options.connectionId, surface));\n\t}\n\tif (!integration.supportsIssueLookupByResourceId) {\n\t\treturn refused(\n\t\t\totherWarning(\n\t\t\t\toptions.providerId,\n\t\t\t\tundefined,\n\t\t\t\toptions.connectionId,\n\t\t\t\t`${surface} is not supported by '${options.providerId}'; its single-issue read cannot prove an absence, so a miss would not be safe to cache.`,\n\t\t\t),\n\t\t);\n\t}\n\n\t// A self-managed tracker's single resource is its host, and the read is keyed (and cached) by `resourceId`\n\t// while it is addressed to the host `domain`/`connectionId` resolved. When they disagree, host B's answer —\n\t// including a cacheable absence — would be stored under host A's key.\n\tif (\n\t\tisIssuesSelfManagedHostIntegrationId(options.providerId) &&\n\t\t!areDomainsOnSameHost(options.resourceId, integration.domain)\n\t) {\n\t\treturn refused(\n\t\t\totherWarning(\n\t\t\t\toptions.providerId,\n\t\t\t\tundefined,\n\t\t\t\toptions.connectionId,\n\t\t\t\t`${surface} requires the resource id of '${options.providerId}' to name the host the read resolved to.`,\n\t\t\t),\n\t\t);\n\t}\n\n\tconst domain = ctx.domainForRead(integration, options.providerId, options.connectionId, options.domain);\n\tconst issue = await runCaptured(\n\t\toptions.providerId,\n\t\tdomain,\n\t\toptions.connectionId,\n\t\t() =>\n\t\t\tintegration.getIssueByResourceIdResult(options.resourceId, options.key, {\n\t\t\t\tconnectionId: options.connectionId,\n\t\t\t\tresourceUrl: resourceUrl,\n\t\t\t}),\n\t\t{ warnOnMissingSession: true },\n\t);\n\tif (issue.warning != null) {\n\t\treturn refused(issue.warning);\n\t}\n\n\treturn {\n\t\titems: [{ key: options.key, ...(issue.value != null ? { issue: issue.value } : {}) }],\n\t\twarnings: [],\n\t};\n}\n"],"mappings":";;;;;;;;;;;;;;;;;AA8BA,eAAsB,gBACrB,KACA,SAQ8C;CAC9C,MAAM,WAAW,aAAkE;EAClF,OAAO,CAAC;EACR,UAAU,CAAC,OAAO;EAClB,aAAa;CACd;CACA,MAAM,UAAU;CAEhB,IAAI,CAAC,0BAA0B,QAAQ,UAAU,GAChD,OAAO,QAAQ,+BAA+B,QAAQ,YAAY,QAAQ,cAAc,OAAO,CAAC;CAGjG,IAAI,QAAQ,WAAW,KAAK,CAAC,CAAC,WAAW,GACxC,OAAO,QACN,aAAa,QAAQ,YAAY,KAAA,GAAW,QAAQ,cAAc,GAAG,QAAQ,yBAAyB,CACvG;CAGD,IAAI,QAAQ,IAAI,KAAK,CAAC,CAAC,WAAW,GACjC,OAAO,QACN,aAAa,QAAQ,YAAY,KAAA,GAAW,QAAQ,cAAc,GAAG,QAAQ,wBAAwB,CACtG;CAGD,MAAM,cAAc,QAAQ,aAAa,KAAK,KAAK,KAAA;CACnD,IAAI,QAAQ,eAAA,UAAoD,eAAe,MAC9E,OAAO,QACN,aACC,QAAQ,YACR,KAAA,GACA,QAAQ,cACR,GAAG,QAAQ,kGACZ,CACD;CAMD,IACC,qCAAqC,QAAQ,UAAU,MACtD,QAAQ,UAAU,OAChB,eAAe,QAAQ,MAAM,KAAK,OAClC,QAAQ,gBAAgB,QACzB,CAAC,IACC,cAAc,QAAQ,UAAU,CAAC,CACjC,MAAK,MAAK,EAAE,OAAO,QAAQ,gBAAgB,eAAe,EAAE,MAAM,KAAK,IAAI,IAE/E,OAAO,QACN,aACC,QAAQ,YACR,KAAA,GACA,QAAQ,cACR,GAAG,QAAQ,wDAAwD,QAAQ,WAAW,iIACvF,CACD;CAGD,MAAM,cAAc,MAAM,IAAI,sBAAsB,QAAQ,YAAY,QAAQ,cAAc,QAAQ,MAAM;CAC5G,IAAI,eAAe,MAAM;EAExB,MAAM,QAAQ,IAAI,8BAA8B,QAAQ,YAAY,QAAQ,cAAc,QAAQ,MAAM;EACxG,OAAO;GAAE,OAAO,CAAC;GAAG,UAAU,MAAM;GAAU,aAAa,MAAM,eAAe,KAAA;EAAU;CAC3F;CACA,IAAI,CAAC,oBAAoB,WAAW,GACnC,OAAO,QAAQ,+BAA+B,QAAQ,YAAY,QAAQ,cAAc,OAAO,CAAC;CAEjG,IAAI,CAAC,YAAY,iCAChB,OAAO,QACN,aACC,QAAQ,YACR,KAAA,GACA,QAAQ,cACR,GAAG,QAAQ,wBAAwB,QAAQ,WAAW,wFACvD,CACD;CAMD,IACC,qCAAqC,QAAQ,UAAU,KACvD,CAAC,qBAAqB,QAAQ,YAAY,YAAY,MAAM,GAE5D,OAAO,QACN,aACC,QAAQ,YACR,KAAA,GACA,QAAQ,cACR,GAAG,QAAQ,gCAAgC,QAAQ,WAAW,yCAC/D,CACD;CAGD,MAAM,SAAS,IAAI,cAAc,aAAa,QAAQ,YAAY,QAAQ,cAAc,QAAQ,MAAM;CACtG,MAAM,QAAQ,MAAM,YACnB,QAAQ,YACR,QACA,QAAQ,oBAEP,YAAY,2BAA2B,QAAQ,YAAY,QAAQ,KAAK;EACvE,cAAc,QAAQ;EACT;CACd,CAAC,GACF,EAAE,sBAAsB,KAAK,CAC9B;CACA,IAAI,MAAM,WAAW,MACpB,OAAO,QAAQ,MAAM,OAAO;CAG7B,OAAO;EACN,OAAO,CAAC;GAAE,KAAK,QAAQ;GAAK,GAAI,MAAM,SAAS,OAAO,EAAE,OAAO,MAAM,MAAM,IAAI,CAAC;EAAG,CAAC;EACpF,UAAU,CAAC;CACZ;AACD"}
|