@gitkraken/core-gitlens 0.11.1 → 0.13.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 +84 -1
- package/dist/git/cache.d.ts +84 -29
- package/dist/git/cache.js +188 -54
- package/dist/git/cache.js.map +1 -1
- package/dist/git/features.d.ts +1 -1
- package/dist/git/features.js +4 -0
- package/dist/git/features.js.map +1 -1
- package/dist/git/models/branch.d.ts +15 -2
- package/dist/git/models/branch.js +27 -5
- package/dist/git/models/branch.js.map +1 -1
- package/dist/git/models/issue.d.ts +3 -1
- package/dist/git/models/issue.js.map +1 -1
- package/dist/git/models/status.js +1 -1
- package/dist/git/models/worktree.d.ts +4 -1
- package/dist/git/models/worktree.js +4 -2
- package/dist/git/models/worktree.js.map +1 -1
- package/dist/git/providers/branches.d.ts +73 -8
- package/dist/git/providers/pausedOperations.d.ts +6 -0
- package/dist/git/providers/provider.d.ts +34 -2
- package/dist/git/providers/status.d.ts +8 -2
- package/dist/git/remotes/azure-devops.js +61 -53
- package/dist/git/remotes/azure-devops.js.map +1 -1
- package/dist/git/service.d.ts +8 -5
- package/dist/git/service.js +16 -8
- package/dist/git/service.js.map +1 -1
- package/dist/git/utils/branch.utils.d.ts +8 -1
- package/dist/git/utils/branch.utils.js +30 -1
- package/dist/git/utils/branch.utils.js.map +1 -1
- package/dist/git/utils/issue.utils.d.ts +3 -1
- package/dist/git/utils/issue.utils.js +5 -1
- package/dist/git/utils/issue.utils.js.map +1 -1
- package/dist/git/watching/changeEvent.d.ts +13 -2
- package/dist/git/watching/changeEvent.js +17 -3
- package/dist/git/watching/changeEvent.js.map +1 -1
- package/dist/git/watching/gitDirPoller.d.ts +111 -0
- package/dist/git/watching/gitDirPoller.js +266 -0
- package/dist/git/watching/gitDirPoller.js.map +1 -0
- package/dist/git/watching/provider.d.ts +16 -0
- package/dist/git/watching/watchGroup.d.ts +40 -4
- package/dist/git/watching/watchGroup.js +143 -17
- package/dist/git/watching/watchGroup.js.map +1 -1
- package/dist/git/watching/watchService.d.ts +70 -1
- package/dist/git/watching/watchService.js +158 -13
- package/dist/git/watching/watchService.js.map +1 -1
- package/dist/git/watching/watchSession.d.ts +44 -0
- package/dist/git/watching/watchSession.js +91 -6
- package/dist/git/watching/watchSession.js.map +1 -1
- package/dist/git/watching/watcherPatterns.d.ts +21 -0
- package/dist/git/watching/watcherPatterns.js +67 -1
- package/dist/git/watching/watcherPatterns.js.map +1 -1
- package/dist/git/watching/worktreeSummary.d.ts +34 -7
- package/dist/git/watching/worktreeSummary.js +39 -12
- package/dist/git/watching/worktreeSummary.js.map +1 -1
- package/dist/git-cli/cliGitProvider.d.ts +34 -2
- package/dist/git-cli/cliGitProvider.js +119 -17
- package/dist/git-cli/cliGitProvider.js.map +1 -1
- package/dist/git-cli/exec/commandClassifier.d.ts +15 -0
- package/dist/git-cli/exec/commandClassifier.js +48 -2
- package/dist/git-cli/exec/commandClassifier.js.map +1 -1
- package/dist/git-cli/exec/git.d.ts +15 -3
- package/dist/git-cli/exec/git.js +38 -17
- package/dist/git-cli/exec/git.js.map +1 -1
- package/dist/git-cli/exec/gitQueue.d.ts +2 -0
- package/dist/git-cli/exec/gitQueue.js +2 -1
- package/dist/git-cli/exec/gitQueue.js.map +1 -1
- package/dist/git-cli/exec/indexLock.d.ts +38 -0
- package/dist/git-cli/exec/indexLock.js +150 -0
- package/dist/git-cli/exec/indexLock.js.map +1 -0
- package/dist/git-cli/parsers/worktreeParser.js +42 -2
- package/dist/git-cli/parsers/worktreeParser.js.map +1 -1
- package/dist/git-cli/providers/branches.d.ts +109 -10
- package/dist/git-cli/providers/branches.js +624 -131
- package/dist/git-cli/providers/branches.js.map +1 -1
- package/dist/git-cli/providers/config.d.ts +9 -5
- package/dist/git-cli/providers/config.js +60 -27
- package/dist/git-cli/providers/config.js.map +1 -1
- package/dist/git-cli/providers/operations.d.ts +17 -0
- package/dist/git-cli/providers/operations.js +153 -96
- package/dist/git-cli/providers/operations.js.map +1 -1
- package/dist/git-cli/providers/pausedOperations.d.ts +8 -0
- package/dist/git-cli/providers/pausedOperations.js +60 -40
- package/dist/git-cli/providers/pausedOperations.js.map +1 -1
- package/dist/git-cli/providers/refs.js +11 -8
- package/dist/git-cli/providers/refs.js.map +1 -1
- package/dist/git-cli/providers/remotes.js +19 -10
- package/dist/git-cli/providers/remotes.js.map +1 -1
- package/dist/git-cli/providers/staging.js +40 -15
- package/dist/git-cli/providers/staging.js.map +1 -1
- package/dist/git-cli/providers/stash.d.ts +2 -0
- package/dist/git-cli/providers/stash.js +33 -18
- package/dist/git-cli/providers/stash.js.map +1 -1
- package/dist/git-cli/providers/status.d.ts +3 -1
- package/dist/git-cli/providers/status.js +38 -20
- package/dist/git-cli/providers/status.js.map +1 -1
- package/dist/git-cli/providers/tags.js +6 -4
- package/dist/git-cli/providers/tags.js.map +1 -1
- package/dist/git-cli/providers/worktrees.d.ts +1 -0
- package/dist/git-cli/providers/worktrees.js +60 -31
- package/dist/git-cli/providers/worktrees.js.map +1 -1
- package/dist/git-cli/service.d.ts +6 -0
- package/dist/git-cli/service.js +5 -2
- package/dist/git-cli/service.js.map +1 -1
- package/dist/ipc/discovery.js +1 -1
- package/dist/plus/agents/providers/claudeCodeTranscript.js +6 -6
- package/dist/plus/agents/providers/claudeCodeTranscript.js.map +1 -1
- package/dist/plus/ai/providers/openAICompatibleProviderBase.js +1 -1
- package/dist/plus/git-github/providers/github/status.d.ts +3 -1
- package/dist/plus/git-github/providers/github/status.js +1 -1
- package/dist/plus/git-github/providers/github/status.js.map +1 -1
- package/dist/plus/integrations/collectionMetadata.d.ts +13 -2
- package/dist/plus/integrations/collectionMetadata.js +14 -2
- package/dist/plus/integrations/collectionMetadata.js.map +1 -1
- package/dist/plus/integrations/index.d.ts +2 -2
- package/dist/plus/integrations/index.js.map +1 -1
- package/dist/plus/integrations/integrationService.d.ts +1 -0
- package/dist/plus/integrations/integrationService.js +1 -1
- package/dist/plus/integrations/integrationService.js.map +1 -1
- package/dist/plus/integrations/manager.d.ts +1 -0
- package/dist/plus/integrations/models/gitHostIntegration.d.ts +41 -5
- package/dist/plus/integrations/models/gitHostIntegration.js +109 -30
- package/dist/plus/integrations/models/gitHostIntegration.js.map +1 -1
- package/dist/plus/integrations/models/integration.js +1 -1
- package/dist/plus/integrations/models/issueReads.d.ts +17 -4
- package/dist/plus/integrations/providers/apiClients.d.ts +2 -0
- package/dist/plus/integrations/providers/apiClients.js +7 -1
- package/dist/plus/integrations/providers/apiClients.js.map +1 -1
- package/dist/plus/integrations/providers/azure/azure.d.ts +5 -4
- package/dist/plus/integrations/providers/azure/azure.js +38 -77
- package/dist/plus/integrations/providers/azure/azure.js.map +1 -1
- package/dist/plus/integrations/providers/azure/azure.utils.js +20 -17
- package/dist/plus/integrations/providers/azure/azure.utils.js.map +1 -1
- package/dist/plus/integrations/providers/azure/models.d.ts +27 -10
- package/dist/plus/integrations/providers/azure/models.js +40 -20
- package/dist/plus/integrations/providers/azure/models.js.map +1 -1
- package/dist/plus/integrations/providers/azure/workItemStateCategories.d.ts +23 -0
- package/dist/plus/integrations/providers/azure/workItemStateCategories.js +84 -0
- package/dist/plus/integrations/providers/azure/workItemStateCategories.js.map +1 -0
- package/dist/plus/integrations/providers/azureDevOps.d.ts +44 -0
- package/dist/plus/integrations/providers/azureDevOps.js +113 -22
- package/dist/plus/integrations/providers/azureDevOps.js.map +1 -1
- package/dist/plus/integrations/providers/azureDevOpsServer.d.ts +45 -1
- package/dist/plus/integrations/providers/azureDevOpsServer.js +121 -6
- package/dist/plus/integrations/providers/azureDevOpsServer.js.map +1 -1
- package/dist/plus/integrations/providers/issueSorts.d.ts +0 -1
- package/dist/plus/integrations/providers/jira-server.js +29 -19
- package/dist/plus/integrations/providers/jira-server.js.map +1 -1
- package/dist/plus/integrations/providers/jira.d.ts +5 -2
- package/dist/plus/integrations/providers/jira.js +63 -44
- package/dist/plus/integrations/providers/jira.js.map +1 -1
- package/dist/plus/integrations/providers/jiraIssueByKey.d.ts +3 -4
- package/dist/plus/integrations/providers/jiraIssueByKey.js +3 -4
- package/dist/plus/integrations/providers/jiraIssueByKey.js.map +1 -1
- package/dist/plus/integrations/providers/linearIssuesEtag.d.ts +5 -5
- package/dist/plus/integrations/providers/linearIssuesEtag.js +6 -6
- package/dist/plus/integrations/providers/linearIssuesEtag.js.map +1 -1
- package/dist/plus/integrations/providers/models.d.ts +12 -3
- package/dist/plus/integrations/providers/models.js +16 -9
- package/dist/plus/integrations/providers/models.js.map +1 -1
- package/dist/plus/integrations/providers/providersApi.d.ts +21 -33
- package/dist/plus/integrations/providers/providersApi.js +159 -49
- package/dist/plus/integrations/providers/providersApi.js.map +1 -1
- package/dist/plus/integrations/providers/utils/jiraIssueDrains.d.ts +49 -15
- package/dist/plus/integrations/providers/utils/jiraIssueDrains.js +132 -10
- package/dist/plus/integrations/providers/utils/jiraIssueDrains.js.map +1 -1
- package/dist/plus/integrations/providers/utils/providerPaging.js +8 -2
- package/dist/plus/integrations/providers/utils/providerPaging.js.map +1 -1
- package/dist/plus/integrations/reads/cursors.js +1 -1
- package/dist/plus/integrations/reads/drains.d.ts +0 -1
- package/dist/plus/integrations/reads/filters.d.ts +3 -3
- package/dist/plus/integrations/reads/filters.js.map +1 -1
- package/dist/plus/integrations/reads/hierarchy.js +2 -2
- package/dist/plus/integrations/reads/hierarchy.js.map +1 -1
- package/dist/plus/integrations/reads/hierarchy.utils.d.ts +13 -0
- package/dist/plus/integrations/reads/hierarchy.utils.js +26 -1
- package/dist/plus/integrations/reads/hierarchy.utils.js.map +1 -1
- package/dist/plus/integrations/reads/issueBatch.d.ts +2 -1
- package/dist/plus/integrations/reads/issueBatch.js +1 -1
- package/dist/plus/integrations/reads/issueBatch.js.map +1 -1
- package/dist/plus/integrations/reads/issueTracker.js +27 -18
- package/dist/plus/integrations/reads/issueTracker.js.map +1 -1
- package/dist/plus/integrations/reads/issues.d.ts +2 -1
- package/dist/plus/integrations/reads/issues.js +19 -4
- package/dist/plus/integrations/reads/issues.js.map +1 -1
- package/dist/plus/integrations/reads/pullRequestBatch.js +1 -1
- package/dist/plus/integrations/reads/resolveRepository.js +12 -3
- package/dist/plus/integrations/reads/resolveRepository.js.map +1 -1
- package/dist/plus/integrations/reads/warnings.d.ts +11 -4
- package/dist/plus/integrations/reads/warnings.js +15 -5
- package/dist/plus/integrations/reads/warnings.js.map +1 -1
- package/dist/plus/integrations/results.d.ts +48 -4
- package/dist/plus/integrations/results.js +33 -13
- package/dist/plus/integrations/results.js.map +1 -1
- package/dist/plus/integrations/utils/integration.utils.d.ts +6 -1
- package/dist/plus/integrations/utils/integration.utils.js +8 -1
- package/dist/plus/integrations/utils/integration.utils.js.map +1 -1
- package/dist/utils/env/node/exec.js +2 -2
- package/dist/utils/promiseCache.d.ts +10 -0
- package/dist/utils/promiseCache.js +17 -2
- package/dist/utils/promiseCache.js.map +1 -1
- package/docs/integrations.md +93 -31
- package/package.json +2 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"issue.utils.js","names":[],"sources":["../../../../git/src/utils/issue.utils.ts"],"sourcesContent":["import type {\n\tIssue,\n\tIssueRepositoryIdentityDescriptor,\n\tIssueShape,\n\tIssueSortField,\n\tIssueSorting,\n} from '../models/issue.js';\nimport { defaultIssueSort } from '../models/issue.js';\n\n/**\n * How each sort field is read off an {@link IssueShape}, for the reads that MERGE several provider queries and so\n * have to order the union themselves instead of letting the provider do it.\n *\n * Only the fields the shape actually carries appear. `priority`, `dueDate` and `resolved` are absent because\n * `IssueOrPullRequest`/`IssueShape` doesn't model them: a provider may well order by them server-side on a\n * single-query read, but a merged one cannot — which is exactly what {@link getIssueComparator} returning\n * `undefined` lets its caller refuse, instead of concatenating unordered runs and calling them sorted.\n */\nconst issueSortValues: Partial<Record<IssueSortField, (issue: IssueShape) => number | Date | string | undefined>> = {\n\tcreated: i => i.createdDate,\n\tupdated: i => i.updatedDate,\n\tclosed: i => i.closedDate,\n\tcomments: i => i.commentsCount,\n\treactions: i => i.thumbsUpCount,\n\ttitle: i => i.title,\n};\n\n/**\n * The order a read that ALWAYS orders is being made under: the caller's key, or the facade's default.\n *\n * The one place `?? defaultIssueSort` may be written. Several things downstream must agree on the answer — the\n * emitted provider qualifier, the fingerprint sealed into a cursor, the comparator that re-sorts a merged page and\n * the ceiling warning that names the order — and re-deriving the fallback at each is how they come to disagree,\n * which is not cosmetic: a cursor sealed with a key its query wasn't built with makes the fingerprint check refuse\n * valid resumptions.\n *\n * Note this is for a surface that CAN always order. A read whose provider may not express even the default resolves\n * through `resolveIssueSort` instead, which can answer \"leave the provider's own order\".\n */\nexport function effectiveIssueSort(sort: IssueSorting | undefined): IssueSorting {\n\treturn sort ?? defaultIssueSort;\n}\n\n/**\n * A comparator for one sort key over normalized issues, or `undefined` when the field isn't derivable from an\n * {@link IssueShape} — the signal that the key is only honorable on a SINGLE-origin read, where the provider\n * already ordered the page.\n *\n * `@gitkraken/provider-apis` carries a twin of this over its own `Issue` shape (`src/issueSort.ts`), which it uses\n * for the merges it performs itself. It is not importable — the SDK does not export it, and it takes a different\n * shape — so the two are separate implementations of one rule, and the rule below is the part that must not drift.\n *\n * Missing values sort LAST in both directions. That is deliberately not the same as negating the descending\n * comparator (which would float them to the front when ascending), and it is why they're partitioned before any\n * arithmetic rather than mapped to an infinite sentinel: two missing values have to compare equal, and\n * `-Infinity - -Infinity` is `NaN`, which makes the comparator inconsistent and the resulting order\n * implementation-defined.\n *\n * Note it orders whatever it is handed. Sorting a page that was already capped does not make it the top N.\n *\n * KNOWN DIVERGENCE, `title` only: strings are compared by code unit, so `'B' < 'a'`. A single-scope read is\n * ordered by the provider's own database instead — case-insensitive and accent-aware — so the same `title:asc`\n * yields \"Banana, apple, ábaco\" from a merged page and \"ábaco, apple, Banana\" from one repository. Deliberately\n * NOT fixed with `localeCompare`: the SDK's twin comparator (which performs the merges core doesn't, notably\n * GitLab's repo-ids aggregate) compares by code unit, so switching here would make two paths of the SAME\n * multi-repo read disagree — a worse divergence than the one it closes, and it would break the parity test in\n * `issueSortCapabilities.test.ts`. Closing this properly means changing the rule on BOTH sides at once; until\n * then it is a cosmetic ordering difference on one key, not wrong data.\n */\nexport function getIssueComparator(sort: IssueSorting): ((a: IssueShape, b: IssueShape) => number) | undefined {\n\t// The union's shape guarantees both halves, so this needs no validation — only the cast tsc can't infer.\n\tconst [field, direction] = sort.split(':') as [IssueSortField, 'asc' | 'desc'];\n\tconst getValue = issueSortValues[field];\n\tif (getValue == null) return undefined;\n\n\treturn (a, b) => {\n\t\tconst left = toComparable(getValue(a));\n\t\tconst right = toComparable(getValue(b));\n\t\tif (left == null || right == null) {\n\t\t\tif (left == null && right == null) return 0;\n\t\t\treturn left == null ? 1 : -1;\n\t\t}\n\n\t\tconst ordered = left < right ? -1 : left > right ? 1 : 0;\n\t\treturn direction === 'asc' ? ordered : -ordered;\n\t};\n}\n\nfunction toComparable(value: number | Date | string | undefined): number | string | undefined {\n\treturn value instanceof Date ? value.getTime() : value;\n}\n\nexport function getRepositoryIdentityForIssue(issue: IssueShape | Issue): IssueRepositoryIdentityDescriptor {\n\tif (issue.repository == null) throw new Error('Missing repository');\n\n\treturn {\n\t\tremote: {\n\t\t\turl: issue.repository.url,\n\t\t\tdomain: issue.provider.domain,\n\t\t},\n\t\tname: `${issue.repository.owner}/${issue.repository.repo}`,\n\t\tprovider: {\n\t\t\tid: issue.provider.id,\n\t\t\tdomain: issue.provider.domain,\n\t\t\trepoDomain: issue.repository.owner,\n\t\t\trepoName: issue.repository.repo,\n\t\t\trepoOwnerDomain: issue.repository.owner,\n\t\t},\n\t};\n}\n\n/**\n * Deliberately leaves `projection` out: this copy drops `repository.accessLevel`, so a tag would have field presence\n * report the viewer's access as fetched when the copy no longer has it.\n */\nexport function serializeIssue(value: IssueShape): IssueShape {\n\tconst serialized: IssueShape = {\n\t\ttype: value.type,\n\t\tprovider: {\n\t\t\tid: value.provider.id,\n\t\t\tname: value.provider.name,\n\t\t\tdomain: value.provider.domain,\n\t\t\ticon: value.provider.icon,\n\t\t},\n\t\tid: value.id,\n\t\tnodeId: value.nodeId,\n\t\tissueType: value.issueType,\n\t\ttitle: value.title,\n\t\turl: value.url,\n\t\tcreatedDate: value.createdDate,\n\t\tupdatedDate: value.updatedDate,\n\t\tclosedDate: value.closedDate,\n\t\tclosed: value.closed,\n\t\tstate: value.state,\n\t\tproviderState:\n\t\t\tvalue.providerState == null\n\t\t\t\t? undefined\n\t\t\t\t: {\n\t\t\t\t\t\tid: value.providerState.id,\n\t\t\t\t\t\tname: value.providerState.name,\n\t\t\t\t\t\tcolor: value.providerState.color,\n\t\t\t\t\t\tcategory: value.providerState.category,\n\t\t\t\t\t},\n\t\tauthor:\n\t\t\tvalue.author == null\n\t\t\t\t? undefined\n\t\t\t\t: {\n\t\t\t\t\t\tid: value.author.id,\n\t\t\t\t\t\tname: value.author.name,\n\t\t\t\t\t\tusername: value.author.username,\n\t\t\t\t\t\tavatarUrl: value.author.avatarUrl,\n\t\t\t\t\t\turl: value.author.url,\n\t\t\t\t\t},\n\t\trepository:\n\t\t\tvalue.repository == null\n\t\t\t\t? undefined\n\t\t\t\t: {\n\t\t\t\t\t\towner: value.repository.owner,\n\t\t\t\t\t\trepo: value.repository.repo,\n\t\t\t\t\t\turl: value.repository.url,\n\t\t\t\t\t},\n\t\tproject:\n\t\t\tvalue.project == null\n\t\t\t\t? undefined\n\t\t\t\t: {\n\t\t\t\t\t\tid: value.project.id,\n\t\t\t\t\t\tname: value.project.name,\n\t\t\t\t\t\tresourceId: value.project.resourceId,\n\t\t\t\t\t\tresourceName: value.project.resourceName,\n\t\t\t\t\t},\n\t\tassignees: value.assignees.map(assignee => ({\n\t\t\tid: assignee.id,\n\t\t\tname: assignee.name,\n\t\t\tusername: assignee.username,\n\t\t\tavatarUrl: assignee.avatarUrl,\n\t\t\turl: assignee.url,\n\t\t})),\n\t\tlabels:\n\t\t\tvalue.labels == null\n\t\t\t\t? undefined\n\t\t\t\t: value.labels.map(label => ({\n\t\t\t\t\t\tcolor: label.color,\n\t\t\t\t\t\tname: label.name,\n\t\t\t\t\t})),\n\t\tcommentsCount: value.commentsCount,\n\t\tthumbsUpCount: value.thumbsUpCount,\n\t\tbody: value.body,\n\t\tbodyFormat: value.bodyFormat,\n\t\titerations:\n\t\t\tvalue.iterations == null\n\t\t\t\t? undefined\n\t\t\t\t: value.iterations.map(iteration => ({\n\t\t\t\t\t\tid: iteration.id,\n\t\t\t\t\t\tname: iteration.name,\n\t\t\t\t\t\tisActive: iteration.isActive,\n\t\t\t\t\t\tstartDate: iteration.startDate,\n\t\t\t\t\t\tendDate: iteration.endDate,\n\t\t\t\t\t})),\n\t};\n\treturn serialized;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"issue.utils.js","names":[],"sources":["../../../../git/src/utils/issue.utils.ts"],"sourcesContent":["import type {\n\tIssue,\n\tIssueProviderState,\n\tIssueRepositoryIdentityDescriptor,\n\tIssueShape,\n\tIssueSortField,\n\tIssueSorting,\n} from '../models/issue.js';\nimport { defaultIssueSort } from '../models/issue.js';\n\n/**\n * How each sort field is read off an {@link IssueShape}, for the reads that MERGE several provider queries and so\n * have to order the union themselves instead of letting the provider do it.\n *\n * Only the fields the shape actually carries appear. `priority`, `dueDate` and `resolved` are absent because\n * `IssueOrPullRequest`/`IssueShape` doesn't model them: a provider may well order by them server-side on a\n * single-query read, but a merged one cannot — which is exactly what {@link getIssueComparator} returning\n * `undefined` lets its caller refuse, instead of concatenating unordered runs and calling them sorted.\n */\nconst issueSortValues: Partial<Record<IssueSortField, (issue: IssueShape) => number | Date | string | undefined>> = {\n\tcreated: i => i.createdDate,\n\tupdated: i => i.updatedDate,\n\tclosed: i => i.closedDate,\n\tcomments: i => i.commentsCount,\n\treactions: i => i.thumbsUpCount,\n\ttitle: i => i.title,\n};\n\n/**\n * The order a read that ALWAYS orders is being made under: the caller's key, or the facade's default.\n *\n * The one place `?? defaultIssueSort` may be written. Several things downstream must agree on the answer — the\n * emitted provider qualifier, the fingerprint sealed into a cursor, the comparator that re-sorts a merged page and\n * the ceiling warning that names the order — and re-deriving the fallback at each is how they come to disagree,\n * which is not cosmetic: a cursor sealed with a key its query wasn't built with makes the fingerprint check refuse\n * valid resumptions.\n *\n * Note this is for a surface that CAN always order. A read whose provider may not express even the default resolves\n * through `resolveIssueSort` instead, which can answer \"leave the provider's own order\".\n */\nexport function effectiveIssueSort(sort: IssueSorting | undefined): IssueSorting {\n\treturn sort ?? defaultIssueSort;\n}\n\n/**\n * A comparator for one sort key over normalized issues, or `undefined` when the field isn't derivable from an\n * {@link IssueShape} — the signal that the key is only honorable on a SINGLE-origin read, where the provider\n * already ordered the page.\n *\n * `@gitkraken/provider-apis` carries a twin of this over its own `Issue` shape (`src/issueSort.ts`), which it uses\n * for the merges it performs itself. It is not importable — the SDK does not export it, and it takes a different\n * shape — so the two are separate implementations of one rule, and the rule below is the part that must not drift.\n *\n * Missing values sort LAST in both directions. That is deliberately not the same as negating the descending\n * comparator (which would float them to the front when ascending), and it is why they're partitioned before any\n * arithmetic rather than mapped to an infinite sentinel: two missing values have to compare equal, and\n * `-Infinity - -Infinity` is `NaN`, which makes the comparator inconsistent and the resulting order\n * implementation-defined.\n *\n * Note it orders whatever it is handed. Sorting a page that was already capped does not make it the top N.\n *\n * KNOWN DIVERGENCE, `title` only: strings are compared by code unit, so `'B' < 'a'`. A single-scope read is\n * ordered by the provider's own database instead — case-insensitive and accent-aware — so the same `title:asc`\n * yields \"Banana, apple, ábaco\" from a merged page and \"ábaco, apple, Banana\" from one repository. Deliberately\n * NOT fixed with `localeCompare`: the SDK's twin comparator (which performs the merges core doesn't, notably\n * GitLab's repo-ids aggregate) compares by code unit, so switching here would make two paths of the SAME\n * multi-repo read disagree — a worse divergence than the one it closes, and it would break the parity test in\n * `issueSortCapabilities.test.ts`. Closing this properly means changing the rule on BOTH sides at once; until\n * then it is a cosmetic ordering difference on one key, not wrong data.\n */\nexport function getIssueComparator(sort: IssueSorting): ((a: IssueShape, b: IssueShape) => number) | undefined {\n\t// The union's shape guarantees both halves, so this needs no validation — only the cast tsc can't infer.\n\tconst [field, direction] = sort.split(':') as [IssueSortField, 'asc' | 'desc'];\n\tconst getValue = issueSortValues[field];\n\tif (getValue == null) return undefined;\n\n\treturn (a, b) => {\n\t\tconst left = toComparable(getValue(a));\n\t\tconst right = toComparable(getValue(b));\n\t\tif (left == null || right == null) {\n\t\t\tif (left == null && right == null) return 0;\n\t\t\treturn left == null ? 1 : -1;\n\t\t}\n\n\t\tconst ordered = left < right ? -1 : left > right ? 1 : 0;\n\t\treturn direction === 'asc' ? ordered : -ordered;\n\t};\n}\n\nfunction toComparable(value: number | Date | string | undefined): number | string | undefined {\n\treturn value instanceof Date ? value.getTime() : value;\n}\n\nexport function getRepositoryIdentityForIssue(issue: IssueShape | Issue): IssueRepositoryIdentityDescriptor {\n\tif (issue.repository == null) throw new Error('Missing repository');\n\n\treturn {\n\t\tremote: {\n\t\t\turl: issue.repository.url,\n\t\t\tdomain: issue.provider.domain,\n\t\t},\n\t\tname: `${issue.repository.owner}/${issue.repository.repo}`,\n\t\tprovider: {\n\t\t\tid: issue.provider.id,\n\t\t\tdomain: issue.provider.domain,\n\t\t\trepoDomain: issue.repository.owner,\n\t\t\trepoName: issue.repository.repo,\n\t\t\trepoOwnerDomain: issue.repository.owner,\n\t\t},\n\t};\n}\n\n/**\n * Deliberately leaves `projection` out: this copy drops `repository.accessLevel`, so a tag would have field presence\n * report the viewer's access as fetched when the copy no longer has it.\n */\nexport function serializeIssue(value: IssueShape): IssueShape {\n\tconst serialized: IssueShape = {\n\t\ttype: value.type,\n\t\tprovider: {\n\t\t\tid: value.provider.id,\n\t\t\tname: value.provider.name,\n\t\t\tdomain: value.provider.domain,\n\t\t\ticon: value.provider.icon,\n\t\t},\n\t\tid: value.id,\n\t\tnodeId: value.nodeId,\n\t\tissueType: value.issueType,\n\t\ttitle: value.title,\n\t\turl: value.url,\n\t\tcreatedDate: value.createdDate,\n\t\tupdatedDate: value.updatedDate,\n\t\tclosedDate: value.closedDate,\n\t\tclosed: value.closed,\n\t\tstate: value.state,\n\t\tproviderState:\n\t\t\tvalue.providerState == null\n\t\t\t\t? undefined\n\t\t\t\t: {\n\t\t\t\t\t\tid: value.providerState.id,\n\t\t\t\t\t\tname: value.providerState.name,\n\t\t\t\t\t\tcolor: value.providerState.color,\n\t\t\t\t\t\tcategory: value.providerState.category,\n\t\t\t\t\t},\n\t\tauthor:\n\t\t\tvalue.author == null\n\t\t\t\t? undefined\n\t\t\t\t: {\n\t\t\t\t\t\tid: value.author.id,\n\t\t\t\t\t\tname: value.author.name,\n\t\t\t\t\t\tusername: value.author.username,\n\t\t\t\t\t\tavatarUrl: value.author.avatarUrl,\n\t\t\t\t\t\turl: value.author.url,\n\t\t\t\t\t},\n\t\trepository:\n\t\t\tvalue.repository == null\n\t\t\t\t? undefined\n\t\t\t\t: {\n\t\t\t\t\t\towner: value.repository.owner,\n\t\t\t\t\t\trepo: value.repository.repo,\n\t\t\t\t\t\turl: value.repository.url,\n\t\t\t\t\t},\n\t\tproject:\n\t\t\tvalue.project == null\n\t\t\t\t? undefined\n\t\t\t\t: {\n\t\t\t\t\t\tid: value.project.id,\n\t\t\t\t\t\tname: value.project.name,\n\t\t\t\t\t\tresourceId: value.project.resourceId,\n\t\t\t\t\t\tresourceName: value.project.resourceName,\n\t\t\t\t\t},\n\t\tassignees: value.assignees.map(assignee => ({\n\t\t\tid: assignee.id,\n\t\t\tname: assignee.name,\n\t\t\tusername: assignee.username,\n\t\t\tavatarUrl: assignee.avatarUrl,\n\t\t\turl: assignee.url,\n\t\t})),\n\t\tlabels:\n\t\t\tvalue.labels == null\n\t\t\t\t? undefined\n\t\t\t\t: value.labels.map(label => ({\n\t\t\t\t\t\tcolor: label.color,\n\t\t\t\t\t\tname: label.name,\n\t\t\t\t\t})),\n\t\tcommentsCount: value.commentsCount,\n\t\tthumbsUpCount: value.thumbsUpCount,\n\t\tbody: value.body,\n\t\tbodyFormat: value.bodyFormat,\n\t\titerations:\n\t\t\tvalue.iterations == null\n\t\t\t\t? undefined\n\t\t\t\t: value.iterations.map(iteration => ({\n\t\t\t\t\t\tid: iteration.id,\n\t\t\t\t\t\tname: iteration.name,\n\t\t\t\t\t\tisActive: iteration.isActive,\n\t\t\t\t\t\tstartDate: iteration.startDate,\n\t\t\t\t\t\tendDate: iteration.endDate,\n\t\t\t\t\t})),\n\t};\n\treturn serialized;\n}\n\n/** Whether a provider's workflow category ends the issue: done or abandoned (see {@link IssueProviderState.category}). */\nexport function isTerminalIssueStateCategory(category: IssueProviderState['category']): boolean {\n\treturn category === 'DONE' || category === 'CANCELED';\n}\n"],"mappings":"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"}
|
|
@@ -27,12 +27,23 @@ export declare class WatcherRepoChangeEvent {
|
|
|
27
27
|
protected readonly _changes: Set<RepositoryChange>;
|
|
28
28
|
private readonly _refs;
|
|
29
29
|
private readonly _unnamedRefKinds;
|
|
30
|
+
private readonly _announced;
|
|
30
31
|
constructor(
|
|
31
32
|
/** The repository path this event is for */
|
|
32
33
|
repoPath: string, changes: RepositoryChange[],
|
|
33
34
|
/** Which refs changed; when omitted, every ref kind in `changes` is unnamed (any ref of that kind may have moved) */
|
|
34
|
-
refs?: WatcherRefChanges
|
|
35
|
+
refs?: WatcherRefChanges,
|
|
36
|
+
/** The kinds of `changes` that come only from core's own announced writes (see {@link announced}) */
|
|
37
|
+
announced?: Iterable<RepositoryChange>);
|
|
35
38
|
get changes(): ReadonlySet<RepositoryChange>;
|
|
39
|
+
/**
|
|
40
|
+
* The kinds in {@link changes} that come only from core's own writes, which core already announced through
|
|
41
|
+
* `hooks.repository.onChanged` after evicting their caches. A host that handled that announcement can skip them;
|
|
42
|
+
* a kind the watcher or an injected change also reported is not among them.
|
|
43
|
+
*/
|
|
44
|
+
get announced(): ReadonlySet<RepositoryChange>;
|
|
45
|
+
/** The kinds in {@link changes} that aren't {@link announced}: what a cache that core already evicted for its write still has to clear */
|
|
46
|
+
getUnannouncedChanges(): RepositoryChange[];
|
|
36
47
|
/** Full ref names (or ref directories, or pseudo-refs) known to have changed. See {@link WatcherRefChanges.names}. */
|
|
37
48
|
get refs(): ReadonlySet<string>;
|
|
38
49
|
/** Ref kinds that changed without a name — any ref of these kinds may have moved */
|
|
@@ -66,7 +77,7 @@ export declare class WatcherRepoChangeEvent {
|
|
|
66
77
|
changedExclusive(...affected: RepositoryChange[]): boolean;
|
|
67
78
|
/**
|
|
68
79
|
* Coalesce: return a new event with all changes and refs from both this and the given set. When `refs` is omitted,
|
|
69
|
-
* every ref kind in `changes` counts as unnamed.
|
|
80
|
+
* every ref kind in `changes` counts as unnamed. A kind in `changes` is no longer {@link announced}.
|
|
70
81
|
*/
|
|
71
82
|
with(changes: RepositoryChange[], refs?: WatcherRefChanges): WatcherRepoChangeEvent;
|
|
72
83
|
}
|
|
@@ -26,7 +26,8 @@ var WatcherRepoChangeEvent = class WatcherRepoChangeEvent {
|
|
|
26
26
|
_changes;
|
|
27
27
|
_refs;
|
|
28
28
|
_unnamedRefKinds;
|
|
29
|
-
|
|
29
|
+
_announced;
|
|
30
|
+
constructor(repoPath, changes, refs, announced) {
|
|
30
31
|
this.repoPath = repoPath;
|
|
31
32
|
this._changes = new Set(changes);
|
|
32
33
|
if (refs == null) {
|
|
@@ -36,10 +37,23 @@ var WatcherRepoChangeEvent = class WatcherRepoChangeEvent {
|
|
|
36
37
|
this._refs = new Set(refs.names);
|
|
37
38
|
this._unnamedRefKinds = new Set(refs.unnamed);
|
|
38
39
|
}
|
|
40
|
+
this._announced = announced != null ? new Set(filter(announced, (c) => this._changes.has(c))) : /* @__PURE__ */ new Set();
|
|
39
41
|
}
|
|
40
42
|
get changes() {
|
|
41
43
|
return this._changes;
|
|
42
44
|
}
|
|
45
|
+
/**
|
|
46
|
+
* The kinds in {@link changes} that come only from core's own writes, which core already announced through
|
|
47
|
+
* `hooks.repository.onChanged` after evicting their caches. A host that handled that announcement can skip them;
|
|
48
|
+
* a kind the watcher or an injected change also reported is not among them.
|
|
49
|
+
*/
|
|
50
|
+
get announced() {
|
|
51
|
+
return this._announced;
|
|
52
|
+
}
|
|
53
|
+
/** The kinds in {@link changes} that aren't {@link announced}: what a cache that core already evicted for its write still has to clear */
|
|
54
|
+
getUnannouncedChanges() {
|
|
55
|
+
return this._announced.size ? [...filter(this._changes, (c) => !this._announced.has(c))] : [...this._changes];
|
|
56
|
+
}
|
|
43
57
|
/** Full ref names (or ref directories, or pseudo-refs) known to have changed. See {@link WatcherRefChanges.names}. */
|
|
44
58
|
get refs() {
|
|
45
59
|
return this._refs;
|
|
@@ -101,7 +115,7 @@ var WatcherRepoChangeEvent = class WatcherRepoChangeEvent {
|
|
|
101
115
|
}
|
|
102
116
|
/**
|
|
103
117
|
* Coalesce: return a new event with all changes and refs from both this and the given set. When `refs` is omitted,
|
|
104
|
-
* every ref kind in `changes` counts as unnamed.
|
|
118
|
+
* every ref kind in `changes` counts as unnamed. A kind in `changes` is no longer {@link announced}.
|
|
105
119
|
*/
|
|
106
120
|
with(changes, refs) {
|
|
107
121
|
const names = [...this._refs];
|
|
@@ -114,7 +128,7 @@ var WatcherRepoChangeEvent = class WatcherRepoChangeEvent {
|
|
|
114
128
|
return new WatcherRepoChangeEvent(this.repoPath, [...this._changes, ...changes], {
|
|
115
129
|
names,
|
|
116
130
|
unnamed
|
|
117
|
-
});
|
|
131
|
+
}, filter(this._announced, (c) => !changes.includes(c)));
|
|
118
132
|
}
|
|
119
133
|
};
|
|
120
134
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"changeEvent.js","names":[],"sources":["../../../../git/src/watching/changeEvent.ts"],"sourcesContent":["import { filter, some } from '@gitlens/utils/iterable.js';\nimport type { RepositoryChange } from '../models/repository.js';\n\n/** The {@link RepositoryChange} kinds that describe ref movement, which an event can attribute to named refs */\nexport type RefChangeKind = 'heads' | 'remotes' | 'tags' | 'stash';\n\nexport interface WatcherRefChanges {\n\t/** Full ref names whose files changed (`refs/heads/main`), plus per-worktree pseudo-refs (`HEAD`, `ORIG_HEAD`). A name may be a ref directory and then covers every ref beneath it. */\n\treadonly names?: Iterable<string>;\n\t/** Ref kinds that changed without a name (packed-refs rewrite, config, shallow/replace, an injected change): any ref of that kind may have moved. */\n\treadonly unnamed?: Iterable<RefChangeKind>;\n}\n\nexport function isRefChangeKind(change: RepositoryChange): change is RefChangeKind {\n\treturn change === 'heads' || change === 'remotes' || change === 'tags' || change === 'stash';\n}\n\n/**\n * The kind of ref a full ref name (or ref directory) belongs to. A pseudo-ref (no `refs/` prefix, e.g. `HEAD`,\n * `ORIG_HEAD`) counts as `heads`; a ref outside the watched namespaces (e.g. `refs/notes/x`) has no kind.\n */\nexport function getRefChangeKind(ref: string): RefChangeKind | undefined {\n\tif (!ref.startsWith('refs/')) return 'heads';\n\tif (ref.startsWith('refs/heads/')) return 'heads';\n\tif (ref.startsWith('refs/remotes/')) return 'remotes';\n\tif (ref.startsWith('refs/tags/')) return 'tags';\n\tif (ref === 'refs/stash') return 'stash';\n\n\treturn undefined;\n}\n\n/**\n * An event describing changes to a repository, produced by the watching pipeline.\n *\n * Changes are coalesced within a debounce window — a single event may represent\n * multiple filesystem changes. Use {@link changed} to test for specific change types,\n * and {@link mayHaveChangedRef} to test whether a specific ref may have moved.\n */\nexport class WatcherRepoChangeEvent {\n\tprotected readonly _changes: Set<RepositoryChange>;\n\tprivate readonly _refs: Set<string>;\n\tprivate readonly _unnamedRefKinds: Set<RefChangeKind>;\n\n\tconstructor(\n\t\t/** The repository path this event is for */\n\t\tpublic readonly repoPath: string,\n\t\tchanges: RepositoryChange[],\n\t\t/** Which refs changed; when omitted, every ref kind in `changes` is unnamed (any ref of that kind may have moved) */\n\t\trefs?: WatcherRefChanges,\n\t) {\n\t\tthis._changes = new Set(changes);\n\t\tif (refs == null) {\n\t\t\tthis._refs = new Set();\n\t\t\tthis._unnamedRefKinds = new Set(filter(changes, isRefChangeKind));\n\t\t} else {\n\t\t\tthis._refs = new Set(refs.names);\n\t\t\tthis._unnamedRefKinds = new Set(refs.unnamed);\n\t\t}\n\t}\n\n\tget changes(): ReadonlySet<RepositoryChange> {\n\t\treturn this._changes;\n\t}\n\n\t/** Full ref names (or ref directories, or pseudo-refs) known to have changed. See {@link WatcherRefChanges.names}. */\n\tget refs(): ReadonlySet<string> {\n\t\treturn this._refs;\n\t}\n\n\t/** Ref kinds that changed without a name — any ref of these kinds may have moved */\n\tget unnamedRefKinds(): ReadonlySet<RefChangeKind> {\n\t\treturn this._unnamedRefKinds;\n\t}\n\n\ttoString(changesOnly: boolean = false): string {\n\t\tconst changeList = [...this._changes].join(', ');\n\t\tconst refCount = this._refs.size;\n\t\tif (changesOnly) return refCount ? `changes=${changeList}, refs=${refCount}` : `changes=${changeList}`;\n\n\t\treturn refCount\n\t\t\t? `{ repoPath: ${this.repoPath}, changes: ${changeList}, refs: ${refCount} }`\n\t\t\t: `{ repoPath: ${this.repoPath}, changes: ${changeList} }`;\n\t}\n\n\t/**\n\t * Test whether the given ref may have moved in this event.\n\t *\n\t * @param ref - A full ref name (`refs/heads/main`) or a pseudo-ref (`HEAD`).\n\t * @returns `false` when the ref's kind didn't change (or the ref is outside the watched kinds); `true` when its kind\n\t * changed without a name, or when the ref itself or a ref directory above it changed. `HEAD` is usually a symref\n\t * to the checked-out branch, so it may have moved whenever any `heads` ref did.\n\t */\n\tmayHaveChangedRef(ref: string): boolean {\n\t\tconst kind = getRefChangeKind(ref);\n\t\tif (kind == null || !this._changes.has(kind)) return false;\n\t\tif (this._unnamedRefKinds.has(kind) || ref === 'HEAD') return true;\n\n\t\treturn findCoveringRefName(this._refs, ref) != null;\n\t}\n\n\t/**\n\t * Test whether any of the specified change types are present in the event.\n\t *\n\t * @param affected - Change types to test for.\n\t * @returns `true` if any of the specified changes are present.\n\t */\n\tchanged(...affected: RepositoryChange[]): boolean {\n\t\treturn some(this._changes, c => affected.includes(c));\n\t}\n\n\t/**\n\t * Test whether the event contains ONLY the specified change types.\n\t *\n\t * `pausedOp` is treated as a union of `cherryPick`, `merge`, `rebase`,\n\t * and `revert` for convenience.\n\t *\n\t * @param affected - Change types to test for.\n\t * @returns `true` if the event contains only the specified changes.\n\t */\n\tchangedExclusive(...affected: RepositoryChange[]): boolean {\n\t\tlet changes = this._changes;\n\n\t\t// When checking for specific paused operation types, also accept the union type\n\t\tif (\n\t\t\taffected.includes('cherryPick') ||\n\t\t\taffected.includes('merge') ||\n\t\t\taffected.includes('rebase') ||\n\t\t\taffected.includes('revert')\n\t\t) {\n\t\t\tif (!affected.includes('pausedOp')) {\n\t\t\t\taffected = [...affected, 'pausedOp'];\n\t\t\t}\n\t\t} else if (affected.includes('pausedOp')) {\n\t\t\t// When checking for the union type exclusively, ignore specific subtypes\n\t\t\tchanges = new Set(changes);\n\t\t\tchanges.delete('cherryPick');\n\t\t\tchanges.delete('merge');\n\t\t\tchanges.delete('rebase');\n\t\t\tchanges.delete('revert');\n\t\t}\n\n\t\tconst intersection = [...filter(changes, c => affected.includes(c))];\n\t\treturn intersection.length === changes.size;\n\t}\n\n\t/**\n\t * Coalesce: return a new event with all changes and refs from both this and the given set. When `refs` is omitted,\n\t * every ref kind in `changes` counts as unnamed.\n\t */\n\twith(changes: RepositoryChange[], refs?: WatcherRefChanges): WatcherRepoChangeEvent {\n\t\tconst names = [...this._refs];\n\t\tconst unnamed = [...this._unnamedRefKinds];\n\t\tif (refs == null) {\n\t\t\tunnamed.push(...filter(changes, isRefChangeKind));\n\t\t} else {\n\t\t\tif (refs.names != null) {\n\t\t\t\tnames.push(...refs.names);\n\t\t\t}\n\t\t\tif (refs.unnamed != null) {\n\t\t\t\tunnamed.push(...refs.unnamed);\n\t\t\t}\n\t\t}\n\n\t\treturn new WatcherRepoChangeEvent(this.repoPath, [...this._changes, ...changes], {\n\t\t\tnames: names,\n\t\t\tunnamed: unnamed,\n\t\t});\n\t}\n}\n\n/**\n * Returns the name in `names` that covers `ref` — `ref` itself, or a ref directory above it (compared on `/`\n * boundaries, so `refs/heads/fe` doesn't cover `refs/heads/feature`) — or `undefined` if none does.\n */\nexport function findCoveringRefName(names: ReadonlySet<string>, ref: string): string | undefined {\n\tif (names.size === 0) return undefined;\n\tif (names.has(ref)) return ref;\n\n\tlet index = ref.lastIndexOf('/');\n\twhile (index > 0) {\n\t\tconst dir = ref.substring(0, index);\n\t\tif (names.has(dir)) return dir;\n\n\t\tindex = ref.lastIndexOf('/', index - 1);\n\t}\n\treturn undefined;\n}\n\n/**\n * An event describing working tree file changes, produced by the working tree pipeline.\n *\n * Paths have already been filtered through the gitignore filter and noise filter.\n */\nexport interface WorkingTreeChangeEvent {\n\treadonly repoPath: string;\n\t/**\n\t * Absolute paths of changed files or directories (post-gitignore filtering). A directory stands for everything\n\t * beneath it. Partial when {@link incomplete}.\n\t */\n\treadonly paths: ReadonlySet<string>;\n\t/**\n\t * `true` when more paths changed than the accumulator retains, so {@link paths} is a partial set and a\n\t * miss does NOT mean the file is unchanged. Consumers testing membership must treat this as a match.\n\t */\n\treadonly incomplete: boolean;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"changeEvent.js","names":[],"sources":["../../../../git/src/watching/changeEvent.ts"],"sourcesContent":["import { filter, some } from '@gitlens/utils/iterable.js';\nimport type { RepositoryChange } from '../models/repository.js';\n\n/** The {@link RepositoryChange} kinds that describe ref movement, which an event can attribute to named refs */\nexport type RefChangeKind = 'heads' | 'remotes' | 'tags' | 'stash';\n\nexport interface WatcherRefChanges {\n\t/** Full ref names whose files changed (`refs/heads/main`), plus per-worktree pseudo-refs (`HEAD`, `ORIG_HEAD`). A name may be a ref directory and then covers every ref beneath it. */\n\treadonly names?: Iterable<string>;\n\t/** Ref kinds that changed without a name (packed-refs rewrite, config, shallow/replace, an injected change): any ref of that kind may have moved. */\n\treadonly unnamed?: Iterable<RefChangeKind>;\n}\n\nexport function isRefChangeKind(change: RepositoryChange): change is RefChangeKind {\n\treturn change === 'heads' || change === 'remotes' || change === 'tags' || change === 'stash';\n}\n\n/**\n * The kind of ref a full ref name (or ref directory) belongs to. A pseudo-ref (no `refs/` prefix, e.g. `HEAD`,\n * `ORIG_HEAD`) counts as `heads`; a ref outside the watched namespaces (e.g. `refs/notes/x`) has no kind.\n */\nexport function getRefChangeKind(ref: string): RefChangeKind | undefined {\n\tif (!ref.startsWith('refs/')) return 'heads';\n\tif (ref.startsWith('refs/heads/')) return 'heads';\n\tif (ref.startsWith('refs/remotes/')) return 'remotes';\n\tif (ref.startsWith('refs/tags/')) return 'tags';\n\tif (ref === 'refs/stash') return 'stash';\n\n\treturn undefined;\n}\n\n/**\n * An event describing changes to a repository, produced by the watching pipeline.\n *\n * Changes are coalesced within a debounce window — a single event may represent\n * multiple filesystem changes. Use {@link changed} to test for specific change types,\n * and {@link mayHaveChangedRef} to test whether a specific ref may have moved.\n */\nexport class WatcherRepoChangeEvent {\n\tprotected readonly _changes: Set<RepositoryChange>;\n\tprivate readonly _refs: Set<string>;\n\tprivate readonly _unnamedRefKinds: Set<RefChangeKind>;\n\tprivate readonly _announced: Set<RepositoryChange>;\n\n\tconstructor(\n\t\t/** The repository path this event is for */\n\t\tpublic readonly repoPath: string,\n\t\tchanges: RepositoryChange[],\n\t\t/** Which refs changed; when omitted, every ref kind in `changes` is unnamed (any ref of that kind may have moved) */\n\t\trefs?: WatcherRefChanges,\n\t\t/** The kinds of `changes` that come only from core's own announced writes (see {@link announced}) */\n\t\tannounced?: Iterable<RepositoryChange>,\n\t) {\n\t\tthis._changes = new Set(changes);\n\t\tif (refs == null) {\n\t\t\tthis._refs = new Set();\n\t\t\tthis._unnamedRefKinds = new Set(filter(changes, isRefChangeKind));\n\t\t} else {\n\t\t\tthis._refs = new Set(refs.names);\n\t\t\tthis._unnamedRefKinds = new Set(refs.unnamed);\n\t\t}\n\t\tthis._announced = announced != null ? new Set(filter(announced, c => this._changes.has(c))) : new Set();\n\t}\n\n\tget changes(): ReadonlySet<RepositoryChange> {\n\t\treturn this._changes;\n\t}\n\n\t/**\n\t * The kinds in {@link changes} that come only from core's own writes, which core already announced through\n\t * `hooks.repository.onChanged` after evicting their caches. A host that handled that announcement can skip them;\n\t * a kind the watcher or an injected change also reported is not among them.\n\t */\n\tget announced(): ReadonlySet<RepositoryChange> {\n\t\treturn this._announced;\n\t}\n\n\t/** The kinds in {@link changes} that aren't {@link announced}: what a cache that core already evicted for its write still has to clear */\n\tgetUnannouncedChanges(): RepositoryChange[] {\n\t\treturn this._announced.size ? [...filter(this._changes, c => !this._announced.has(c))] : [...this._changes];\n\t}\n\n\t/** Full ref names (or ref directories, or pseudo-refs) known to have changed. See {@link WatcherRefChanges.names}. */\n\tget refs(): ReadonlySet<string> {\n\t\treturn this._refs;\n\t}\n\n\t/** Ref kinds that changed without a name — any ref of these kinds may have moved */\n\tget unnamedRefKinds(): ReadonlySet<RefChangeKind> {\n\t\treturn this._unnamedRefKinds;\n\t}\n\n\ttoString(changesOnly: boolean = false): string {\n\t\tconst changeList = [...this._changes].join(', ');\n\t\tconst refCount = this._refs.size;\n\t\tif (changesOnly) return refCount ? `changes=${changeList}, refs=${refCount}` : `changes=${changeList}`;\n\n\t\treturn refCount\n\t\t\t? `{ repoPath: ${this.repoPath}, changes: ${changeList}, refs: ${refCount} }`\n\t\t\t: `{ repoPath: ${this.repoPath}, changes: ${changeList} }`;\n\t}\n\n\t/**\n\t * Test whether the given ref may have moved in this event.\n\t *\n\t * @param ref - A full ref name (`refs/heads/main`) or a pseudo-ref (`HEAD`).\n\t * @returns `false` when the ref's kind didn't change (or the ref is outside the watched kinds); `true` when its kind\n\t * changed without a name, or when the ref itself or a ref directory above it changed. `HEAD` is usually a symref\n\t * to the checked-out branch, so it may have moved whenever any `heads` ref did.\n\t */\n\tmayHaveChangedRef(ref: string): boolean {\n\t\tconst kind = getRefChangeKind(ref);\n\t\tif (kind == null || !this._changes.has(kind)) return false;\n\t\tif (this._unnamedRefKinds.has(kind) || ref === 'HEAD') return true;\n\n\t\treturn findCoveringRefName(this._refs, ref) != null;\n\t}\n\n\t/**\n\t * Test whether any of the specified change types are present in the event.\n\t *\n\t * @param affected - Change types to test for.\n\t * @returns `true` if any of the specified changes are present.\n\t */\n\tchanged(...affected: RepositoryChange[]): boolean {\n\t\treturn some(this._changes, c => affected.includes(c));\n\t}\n\n\t/**\n\t * Test whether the event contains ONLY the specified change types.\n\t *\n\t * `pausedOp` is treated as a union of `cherryPick`, `merge`, `rebase`,\n\t * and `revert` for convenience.\n\t *\n\t * @param affected - Change types to test for.\n\t * @returns `true` if the event contains only the specified changes.\n\t */\n\tchangedExclusive(...affected: RepositoryChange[]): boolean {\n\t\tlet changes = this._changes;\n\n\t\t// When checking for specific paused operation types, also accept the union type\n\t\tif (\n\t\t\taffected.includes('cherryPick') ||\n\t\t\taffected.includes('merge') ||\n\t\t\taffected.includes('rebase') ||\n\t\t\taffected.includes('revert')\n\t\t) {\n\t\t\tif (!affected.includes('pausedOp')) {\n\t\t\t\taffected = [...affected, 'pausedOp'];\n\t\t\t}\n\t\t} else if (affected.includes('pausedOp')) {\n\t\t\t// When checking for the union type exclusively, ignore specific subtypes\n\t\t\tchanges = new Set(changes);\n\t\t\tchanges.delete('cherryPick');\n\t\t\tchanges.delete('merge');\n\t\t\tchanges.delete('rebase');\n\t\t\tchanges.delete('revert');\n\t\t}\n\n\t\tconst intersection = [...filter(changes, c => affected.includes(c))];\n\t\treturn intersection.length === changes.size;\n\t}\n\n\t/**\n\t * Coalesce: return a new event with all changes and refs from both this and the given set. When `refs` is omitted,\n\t * every ref kind in `changes` counts as unnamed. A kind in `changes` is no longer {@link announced}.\n\t */\n\twith(changes: RepositoryChange[], refs?: WatcherRefChanges): WatcherRepoChangeEvent {\n\t\tconst names = [...this._refs];\n\t\tconst unnamed = [...this._unnamedRefKinds];\n\t\tif (refs == null) {\n\t\t\tunnamed.push(...filter(changes, isRefChangeKind));\n\t\t} else {\n\t\t\tif (refs.names != null) {\n\t\t\t\tnames.push(...refs.names);\n\t\t\t}\n\t\t\tif (refs.unnamed != null) {\n\t\t\t\tunnamed.push(...refs.unnamed);\n\t\t\t}\n\t\t}\n\n\t\treturn new WatcherRepoChangeEvent(\n\t\t\tthis.repoPath,\n\t\t\t[...this._changes, ...changes],\n\t\t\t{ names: names, unnamed: unnamed },\n\t\t\tfilter(this._announced, c => !changes.includes(c)),\n\t\t);\n\t}\n}\n\n/**\n * Returns the name in `names` that covers `ref` — `ref` itself, or a ref directory above it (compared on `/`\n * boundaries, so `refs/heads/fe` doesn't cover `refs/heads/feature`) — or `undefined` if none does.\n */\nexport function findCoveringRefName(names: ReadonlySet<string>, ref: string): string | undefined {\n\tif (names.size === 0) return undefined;\n\tif (names.has(ref)) return ref;\n\n\tlet index = ref.lastIndexOf('/');\n\twhile (index > 0) {\n\t\tconst dir = ref.substring(0, index);\n\t\tif (names.has(dir)) return dir;\n\n\t\tindex = ref.lastIndexOf('/', index - 1);\n\t}\n\treturn undefined;\n}\n\n/**\n * An event describing working tree file changes, produced by the working tree pipeline.\n *\n * Paths have already been filtered through the gitignore filter and noise filter.\n */\nexport interface WorkingTreeChangeEvent {\n\treadonly repoPath: string;\n\t/**\n\t * Absolute paths of changed files or directories (post-gitignore filtering). A directory stands for everything\n\t * beneath it. Partial when {@link incomplete}.\n\t */\n\treadonly paths: ReadonlySet<string>;\n\t/**\n\t * `true` when more paths changed than the accumulator retains, so {@link paths} is a partial set and a\n\t * miss does NOT mean the file is unchanged. Consumers testing membership must treat this as a match.\n\t */\n\treadonly incomplete: boolean;\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { UnifiedDisposable } from "../../utils/disposable.js";
|
|
2
|
+
import { FileWatchEvent } from "./provider.js";
|
|
3
|
+
import { WatchPollingOptions } from "./watchService.js";
|
|
4
|
+
//#region ../git/src/watching/gitDirPoller.d.ts
|
|
5
|
+
type GitDirStat = Awaited<ReturnType<WatchPollingOptions['stat']>>;
|
|
6
|
+
/** Something the {@link GitDirPoller} reads once per pass */
|
|
7
|
+
export interface PollTarget {
|
|
8
|
+
poll(): Promise<void>;
|
|
9
|
+
}
|
|
10
|
+
/** A file or directory a poll stats, relative to the directory it watches in place of a watcher */
|
|
11
|
+
export interface GitDirProbe {
|
|
12
|
+
/** `/`-separated, relative to the target's base path */
|
|
13
|
+
readonly path: string;
|
|
14
|
+
/** The relative paths a move is reported as, in place of `path` */
|
|
15
|
+
readonly emitAs?: readonly string[];
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Runs passes over the poll targets of every watcher that can't be trusted, reading at most 4 targets at once across
|
|
19
|
+
* passes and first readings. A target is read once soon after it is added, so its baseline doesn't wait for the next
|
|
20
|
+
* pass; targets added together are read together.
|
|
21
|
+
*
|
|
22
|
+
* With the default gate (no `isActive`), one timer runs a pass every `intervalMs`, stopped by {@link suspend} and
|
|
23
|
+
* restarted by {@link resume}, which runs a pass at once. With a host's `isActive`, whose changes core can't see, one
|
|
24
|
+
* timer looks at it every `activeCheckMs`: a pass is due once `intervalMs` has gone by since the last one, or at the
|
|
25
|
+
* first look after the gate opens (or after {@link nudge}), so a window that comes back between passes doesn't wait out
|
|
26
|
+
* the rest of one.
|
|
27
|
+
*/
|
|
28
|
+
export declare class GitDirPoller implements UnifiedDisposable {
|
|
29
|
+
readonly options: WatchPollingOptions;
|
|
30
|
+
private readonly targets;
|
|
31
|
+
/** Targets added since the last pass and not yet read */
|
|
32
|
+
private readonly unread;
|
|
33
|
+
/** Each target's last read asked for; a target is never read twice at once, so the next waits for it */
|
|
34
|
+
private readonly reading;
|
|
35
|
+
/** Reads waiting for one of the {@link defaultPollConcurrency} slots, which a finished read hands over */
|
|
36
|
+
private readonly waiting;
|
|
37
|
+
/**
|
|
38
|
+
* The stats of the batches (a pass, or first readings) in flight, shared by every target that probes the same path,
|
|
39
|
+
* as the main checkout's root and its common dir do
|
|
40
|
+
*/
|
|
41
|
+
private readonly stats;
|
|
42
|
+
private readonly intervalMs;
|
|
43
|
+
private readonly activeCheckMs;
|
|
44
|
+
private timer;
|
|
45
|
+
private running;
|
|
46
|
+
private batches;
|
|
47
|
+
private slotsInUse;
|
|
48
|
+
/** The next look runs a pass whatever the interval says */
|
|
49
|
+
private due;
|
|
50
|
+
private lastPassAt;
|
|
51
|
+
private suspended;
|
|
52
|
+
private disposed;
|
|
53
|
+
constructor(options: WatchPollingOptions);
|
|
54
|
+
[Symbol.dispose](): void;
|
|
55
|
+
dispose(): void;
|
|
56
|
+
get size(): number;
|
|
57
|
+
add(target: PollTarget): void;
|
|
58
|
+
delete(target: PollTarget): void;
|
|
59
|
+
/** Makes the next look a pass, and looks now */
|
|
60
|
+
nudge(): void;
|
|
61
|
+
/** Under the default gate, stops passes (and the timer) until {@link resume}; a host's `isActive` ignores it */
|
|
62
|
+
suspend(): void;
|
|
63
|
+
/** Lifts {@link suspend}, and runs a pass now, so what moved meanwhile is polled rather than at the next look */
|
|
64
|
+
resume(): void;
|
|
65
|
+
/** One look at the gate: runs a pass when one is due, the gate is open and none is in flight */
|
|
66
|
+
tick(): Promise<void>;
|
|
67
|
+
/** Stats `path` once for every target that probes it in the batches in flight */
|
|
68
|
+
stat(path: string): Promise<GitDirStat>;
|
|
69
|
+
/** `path` changed since it was statted, so the next target to probe it in a batch in flight stats it again */
|
|
70
|
+
invalidate(path: string): void;
|
|
71
|
+
private pass;
|
|
72
|
+
private readUnread;
|
|
73
|
+
private readAll;
|
|
74
|
+
/** Reads `target` once any read of it in flight ends, so the reading is fresh and its probes current */
|
|
75
|
+
private read;
|
|
76
|
+
private acquireSlot;
|
|
77
|
+
private releaseSlot;
|
|
78
|
+
private startTimer;
|
|
79
|
+
private stopTimer;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Stands in for a watcher on `basePath`: stats each probe and reports one whose `mtime:size` moved as a `change` of
|
|
83
|
+
* the paths it is emitted as, through the same handler the watcher's own events take. A probe's first read (and a
|
|
84
|
+
* probe new to the set, as after a branch switch) only records; one that was missing and is now present moved. A stat
|
|
85
|
+
* that throws keeps the last reading, so it never counts as a move.
|
|
86
|
+
*/
|
|
87
|
+
export declare class GitDirProbeTarget implements PollTarget {
|
|
88
|
+
private readonly basePath;
|
|
89
|
+
private readonly stats;
|
|
90
|
+
private readonly getProbes;
|
|
91
|
+
/** For each path a probe may be emitted as, the paths of the probes emitted as it */
|
|
92
|
+
private readonly probesByEmittedPath;
|
|
93
|
+
private readonly onMoved;
|
|
94
|
+
private readonly signatures;
|
|
95
|
+
/** For each poll whose stats are in flight, the paths {@link forget} cleared meanwhile, which it must not record */
|
|
96
|
+
private readonly forgottenDuringPolls;
|
|
97
|
+
constructor(basePath: string, stats: Pick<GitDirPoller, 'stat' | 'invalidate'>, getProbes: () => Iterable<GitDirProbe>,
|
|
98
|
+
/** For each path a probe may be emitted as, the paths of the probes emitted as it */
|
|
99
|
+
probesByEmittedPath: ReadonlyMap<string, readonly string[]>, onMoved: (event: FileWatchEvent, probe: GitDirProbe) => void);
|
|
100
|
+
poll(): Promise<void>;
|
|
101
|
+
/**
|
|
102
|
+
* The watcher reported `path` itself, so the next poll records the probes it touched (the path, a ref's lock file
|
|
103
|
+
* standing for the ref, the directories above it, and the probes emitted as it, like `logs/HEAD` as `HEAD`, which
|
|
104
|
+
* the watcher never sees) without reporting them again.
|
|
105
|
+
*/
|
|
106
|
+
forget(path: string): void;
|
|
107
|
+
private clearSignature;
|
|
108
|
+
private read;
|
|
109
|
+
}
|
|
110
|
+
//#endregion
|
|
111
|
+
//# sourceMappingURL=gitDirPoller.d.ts.map
|
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
import { isDescendant, joinPaths, relative } from "../../utils/path.js";
|
|
2
|
+
//#region ../git/src/watching/gitDirPoller.ts
|
|
3
|
+
const defaultPollIntervalMs = 6e4;
|
|
4
|
+
const defaultPollActiveCheckMs = 5e3;
|
|
5
|
+
const defaultPollConcurrency = 4;
|
|
6
|
+
/**
|
|
7
|
+
* Runs passes over the poll targets of every watcher that can't be trusted, reading at most 4 targets at once across
|
|
8
|
+
* passes and first readings. A target is read once soon after it is added, so its baseline doesn't wait for the next
|
|
9
|
+
* pass; targets added together are read together.
|
|
10
|
+
*
|
|
11
|
+
* With the default gate (no `isActive`), one timer runs a pass every `intervalMs`, stopped by {@link suspend} and
|
|
12
|
+
* restarted by {@link resume}, which runs a pass at once. With a host's `isActive`, whose changes core can't see, one
|
|
13
|
+
* timer looks at it every `activeCheckMs`: a pass is due once `intervalMs` has gone by since the last one, or at the
|
|
14
|
+
* first look after the gate opens (or after {@link nudge}), so a window that comes back between passes doesn't wait out
|
|
15
|
+
* the rest of one.
|
|
16
|
+
*/
|
|
17
|
+
var GitDirPoller = class {
|
|
18
|
+
options;
|
|
19
|
+
targets = /* @__PURE__ */ new Set();
|
|
20
|
+
/** Targets added since the last pass and not yet read */
|
|
21
|
+
unread = /* @__PURE__ */ new Set();
|
|
22
|
+
/** Each target's last read asked for; a target is never read twice at once, so the next waits for it */
|
|
23
|
+
reading = /* @__PURE__ */ new Map();
|
|
24
|
+
/** Reads waiting for one of the {@link defaultPollConcurrency} slots, which a finished read hands over */
|
|
25
|
+
waiting = [];
|
|
26
|
+
/**
|
|
27
|
+
* The stats of the batches (a pass, or first readings) in flight, shared by every target that probes the same path,
|
|
28
|
+
* as the main checkout's root and its common dir do
|
|
29
|
+
*/
|
|
30
|
+
stats = /* @__PURE__ */ new Map();
|
|
31
|
+
intervalMs;
|
|
32
|
+
activeCheckMs;
|
|
33
|
+
timer;
|
|
34
|
+
running;
|
|
35
|
+
batches = 0;
|
|
36
|
+
slotsInUse = 0;
|
|
37
|
+
/** The next look runs a pass whatever the interval says */
|
|
38
|
+
due = true;
|
|
39
|
+
lastPassAt = 0;
|
|
40
|
+
suspended = false;
|
|
41
|
+
disposed = false;
|
|
42
|
+
constructor(options) {
|
|
43
|
+
this.options = options;
|
|
44
|
+
this.intervalMs = options.intervalMs ?? defaultPollIntervalMs;
|
|
45
|
+
this.activeCheckMs = options.activeCheckMs ?? defaultPollActiveCheckMs;
|
|
46
|
+
}
|
|
47
|
+
[Symbol.dispose]() {
|
|
48
|
+
this.dispose();
|
|
49
|
+
}
|
|
50
|
+
dispose() {
|
|
51
|
+
this.disposed = true;
|
|
52
|
+
this.targets.clear();
|
|
53
|
+
this.unread.clear();
|
|
54
|
+
this.stats.clear();
|
|
55
|
+
this.stopTimer();
|
|
56
|
+
}
|
|
57
|
+
get size() {
|
|
58
|
+
return this.targets.size;
|
|
59
|
+
}
|
|
60
|
+
add(target) {
|
|
61
|
+
if (this.disposed || this.targets.has(target)) return;
|
|
62
|
+
this.targets.add(target);
|
|
63
|
+
this.unread.add(target);
|
|
64
|
+
if (this.unread.size === 1) queueMicrotask(() => this.readUnread());
|
|
65
|
+
this.startTimer();
|
|
66
|
+
}
|
|
67
|
+
delete(target) {
|
|
68
|
+
this.unread.delete(target);
|
|
69
|
+
if (!this.targets.delete(target) || this.targets.size !== 0) return;
|
|
70
|
+
this.stopTimer();
|
|
71
|
+
}
|
|
72
|
+
/** Makes the next look a pass, and looks now */
|
|
73
|
+
nudge() {
|
|
74
|
+
this.due = true;
|
|
75
|
+
this.tick();
|
|
76
|
+
}
|
|
77
|
+
/** Under the default gate, stops passes (and the timer) until {@link resume}; a host's `isActive` ignores it */
|
|
78
|
+
suspend() {
|
|
79
|
+
this.suspended = true;
|
|
80
|
+
if (this.options.isActive == null) this.stopTimer();
|
|
81
|
+
}
|
|
82
|
+
/** Lifts {@link suspend}, and runs a pass now, so what moved meanwhile is polled rather than at the next look */
|
|
83
|
+
resume() {
|
|
84
|
+
this.suspended = false;
|
|
85
|
+
this.nudge();
|
|
86
|
+
}
|
|
87
|
+
/** One look at the gate: runs a pass when one is due, the gate is open and none is in flight */
|
|
88
|
+
tick() {
|
|
89
|
+
if (this.disposed || this.targets.size === 0) return Promise.resolve();
|
|
90
|
+
if (!(this.options.isActive?.() ?? !this.suspended)) {
|
|
91
|
+
this.due = true;
|
|
92
|
+
return Promise.resolve();
|
|
93
|
+
}
|
|
94
|
+
if (this.running != null) return this.running;
|
|
95
|
+
const now = performance.now();
|
|
96
|
+
if (!this.due && now - this.lastPassAt < this.intervalMs) return Promise.resolve();
|
|
97
|
+
if (this.options.isActive == null) {
|
|
98
|
+
this.stopTimer();
|
|
99
|
+
this.startTimer();
|
|
100
|
+
}
|
|
101
|
+
this.due = false;
|
|
102
|
+
this.lastPassAt = now;
|
|
103
|
+
this.running = this.pass().finally(() => {
|
|
104
|
+
this.running = void 0;
|
|
105
|
+
if (this.due) this.tick();
|
|
106
|
+
});
|
|
107
|
+
return this.running;
|
|
108
|
+
}
|
|
109
|
+
/** Stats `path` once for every target that probes it in the batches in flight */
|
|
110
|
+
stat(path) {
|
|
111
|
+
if (this.batches === 0) return this.options.stat(path);
|
|
112
|
+
let stat = this.stats.get(path);
|
|
113
|
+
if (stat == null) {
|
|
114
|
+
stat = this.options.stat(path);
|
|
115
|
+
this.stats.set(path, stat);
|
|
116
|
+
}
|
|
117
|
+
return stat;
|
|
118
|
+
}
|
|
119
|
+
/** `path` changed since it was statted, so the next target to probe it in a batch in flight stats it again */
|
|
120
|
+
invalidate(path) {
|
|
121
|
+
this.stats.delete(path);
|
|
122
|
+
}
|
|
123
|
+
pass() {
|
|
124
|
+
this.stats.clear();
|
|
125
|
+
this.unread.clear();
|
|
126
|
+
return this.readAll([...this.targets]);
|
|
127
|
+
}
|
|
128
|
+
readUnread() {
|
|
129
|
+
if (this.unread.size === 0) return;
|
|
130
|
+
const targets = [...this.unread];
|
|
131
|
+
this.unread.clear();
|
|
132
|
+
this.readAll(targets);
|
|
133
|
+
}
|
|
134
|
+
async readAll(targets) {
|
|
135
|
+
this.batches++;
|
|
136
|
+
try {
|
|
137
|
+
await Promise.allSettled(targets.map((target) => this.read(target)));
|
|
138
|
+
} finally {
|
|
139
|
+
this.batches--;
|
|
140
|
+
if (this.batches === 0) this.stats.clear();
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
/** Reads `target` once any read of it in flight ends, so the reading is fresh and its probes current */
|
|
144
|
+
read(target) {
|
|
145
|
+
const poll = async () => {
|
|
146
|
+
if (!this.targets.has(target)) return;
|
|
147
|
+
await this.acquireSlot();
|
|
148
|
+
try {
|
|
149
|
+
if (this.targets.has(target)) await target.poll();
|
|
150
|
+
} finally {
|
|
151
|
+
this.releaseSlot();
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
const previous = this.reading.get(target);
|
|
155
|
+
const reading = (previous != null ? previous.then(poll, poll) : poll()).finally(() => {
|
|
156
|
+
if (this.reading.get(target) === reading) this.reading.delete(target);
|
|
157
|
+
});
|
|
158
|
+
this.reading.set(target, reading);
|
|
159
|
+
return reading;
|
|
160
|
+
}
|
|
161
|
+
acquireSlot() {
|
|
162
|
+
if (this.slotsInUse < defaultPollConcurrency) {
|
|
163
|
+
this.slotsInUse++;
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
return new Promise((resolve) => this.waiting.push(resolve));
|
|
167
|
+
}
|
|
168
|
+
releaseSlot() {
|
|
169
|
+
const next = this.waiting.shift();
|
|
170
|
+
if (next != null) next();
|
|
171
|
+
else this.slotsInUse--;
|
|
172
|
+
}
|
|
173
|
+
startTimer() {
|
|
174
|
+
if (this.timer != null || this.disposed || this.targets.size === 0) return;
|
|
175
|
+
if (this.options.isActive != null) this.timer = setInterval(() => void this.tick(), this.activeCheckMs);
|
|
176
|
+
else if (!this.suspended) this.timer = setTimeout(() => {
|
|
177
|
+
this.timer = void 0;
|
|
178
|
+
this.due = true;
|
|
179
|
+
this.tick();
|
|
180
|
+
}, this.intervalMs);
|
|
181
|
+
else return;
|
|
182
|
+
if (typeof this.timer !== "number") this.timer.unref();
|
|
183
|
+
}
|
|
184
|
+
stopTimer() {
|
|
185
|
+
if (this.timer == null) return;
|
|
186
|
+
if (this.options.isActive != null) clearInterval(this.timer);
|
|
187
|
+
else clearTimeout(this.timer);
|
|
188
|
+
this.timer = void 0;
|
|
189
|
+
this.due = true;
|
|
190
|
+
}
|
|
191
|
+
};
|
|
192
|
+
/**
|
|
193
|
+
* Stands in for a watcher on `basePath`: stats each probe and reports one whose `mtime:size` moved as a `change` of
|
|
194
|
+
* the paths it is emitted as, through the same handler the watcher's own events take. A probe's first read (and a
|
|
195
|
+
* probe new to the set, as after a branch switch) only records; one that was missing and is now present moved. A stat
|
|
196
|
+
* that throws keeps the last reading, so it never counts as a move.
|
|
197
|
+
*/
|
|
198
|
+
var GitDirProbeTarget = class {
|
|
199
|
+
basePath;
|
|
200
|
+
stats;
|
|
201
|
+
getProbes;
|
|
202
|
+
probesByEmittedPath;
|
|
203
|
+
onMoved;
|
|
204
|
+
signatures = /* @__PURE__ */ new Map();
|
|
205
|
+
/** For each poll whose stats are in flight, the paths {@link forget} cleared meanwhile, which it must not record */
|
|
206
|
+
forgottenDuringPolls = /* @__PURE__ */ new Set();
|
|
207
|
+
constructor(basePath, stats, getProbes, probesByEmittedPath, onMoved) {
|
|
208
|
+
this.basePath = basePath;
|
|
209
|
+
this.stats = stats;
|
|
210
|
+
this.getProbes = getProbes;
|
|
211
|
+
this.probesByEmittedPath = probesByEmittedPath;
|
|
212
|
+
this.onMoved = onMoved;
|
|
213
|
+
}
|
|
214
|
+
async poll() {
|
|
215
|
+
const probes = [...this.getProbes()];
|
|
216
|
+
const forgotten = /* @__PURE__ */ new Set();
|
|
217
|
+
this.forgottenDuringPolls.add(forgotten);
|
|
218
|
+
const readings = await Promise.all(probes.map((p) => this.read(p.path)));
|
|
219
|
+
this.forgottenDuringPolls.delete(forgotten);
|
|
220
|
+
const seen = /* @__PURE__ */ new Set();
|
|
221
|
+
const moved = [];
|
|
222
|
+
for (let i = 0; i < probes.length; i++) {
|
|
223
|
+
const probe = probes[i];
|
|
224
|
+
seen.add(probe.path);
|
|
225
|
+
const reading = readings[i];
|
|
226
|
+
if (reading == null || forgotten.has(probe.path)) continue;
|
|
227
|
+
const previous = this.signatures.get(probe.path);
|
|
228
|
+
this.signatures.set(probe.path, reading);
|
|
229
|
+
if (previous != null && previous !== reading) moved.push(probe);
|
|
230
|
+
}
|
|
231
|
+
for (const path of this.signatures.keys()) if (!seen.has(path)) this.signatures.delete(path);
|
|
232
|
+
for (const probe of moved) for (const path of probe.emitAs ?? [probe.path]) this.onMoved({
|
|
233
|
+
path: joinPaths(this.basePath, path),
|
|
234
|
+
reason: "change"
|
|
235
|
+
}, probe);
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* The watcher reported `path` itself, so the next poll records the probes it touched (the path, a ref's lock file
|
|
239
|
+
* standing for the ref, the directories above it, and the probes emitted as it, like `logs/HEAD` as `HEAD`, which
|
|
240
|
+
* the watcher never sees) without reporting them again.
|
|
241
|
+
*/
|
|
242
|
+
forget(path) {
|
|
243
|
+
if (!isDescendant(path, this.basePath)) return;
|
|
244
|
+
let relativePath = relative(this.basePath, path);
|
|
245
|
+
if (relativePath.endsWith(".lock")) relativePath = relativePath.substring(0, relativePath.length - 5);
|
|
246
|
+
for (let p = relativePath; p.length > 0; p = p.substring(0, Math.max(0, p.lastIndexOf("/")))) this.clearSignature(p);
|
|
247
|
+
for (const probePath of this.probesByEmittedPath.get(relativePath) ?? []) this.clearSignature(probePath);
|
|
248
|
+
}
|
|
249
|
+
clearSignature(path) {
|
|
250
|
+
this.signatures.delete(path);
|
|
251
|
+
this.stats.invalidate(joinPaths(this.basePath, path));
|
|
252
|
+
for (const forgotten of this.forgottenDuringPolls) forgotten.add(path);
|
|
253
|
+
}
|
|
254
|
+
async read(path) {
|
|
255
|
+
try {
|
|
256
|
+
const stat = await this.stats.stat(joinPaths(this.basePath, path));
|
|
257
|
+
return stat == null ? "missing" : `${stat.mtime}:${stat.size}`;
|
|
258
|
+
} catch {
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
};
|
|
263
|
+
//#endregion
|
|
264
|
+
export { GitDirPoller, GitDirProbeTarget };
|
|
265
|
+
|
|
266
|
+
//# sourceMappingURL=gitDirPoller.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gitDirPoller.js","names":[],"sources":["../../../../git/src/watching/gitDirPoller.ts"],"sourcesContent":["import type { UnifiedDisposable } from '@gitlens/utils/disposable.js';\nimport { isDescendant, joinPaths, relative } from '@gitlens/utils/path.js';\nimport type { FileWatchEvent } from './provider.js';\nimport type { WatchPollingOptions } from './watchService.js';\n\nconst defaultPollIntervalMs = 60_000;\nconst defaultPollActiveCheckMs = 5_000;\nconst defaultPollConcurrency = 4;\n\ntype GitDirStat = Awaited<ReturnType<WatchPollingOptions['stat']>>;\n\n/** Something the {@link GitDirPoller} reads once per pass */\nexport interface PollTarget {\n\tpoll(): Promise<void>;\n}\n\n/** A file or directory a poll stats, relative to the directory it watches in place of a watcher */\nexport interface GitDirProbe {\n\t/** `/`-separated, relative to the target's base path */\n\treadonly path: string;\n\t/** The relative paths a move is reported as, in place of `path` */\n\treadonly emitAs?: readonly string[];\n}\n\n/**\n * Runs passes over the poll targets of every watcher that can't be trusted, reading at most 4 targets at once across\n * passes and first readings. A target is read once soon after it is added, so its baseline doesn't wait for the next\n * pass; targets added together are read together.\n *\n * With the default gate (no `isActive`), one timer runs a pass every `intervalMs`, stopped by {@link suspend} and\n * restarted by {@link resume}, which runs a pass at once. With a host's `isActive`, whose changes core can't see, one\n * timer looks at it every `activeCheckMs`: a pass is due once `intervalMs` has gone by since the last one, or at the\n * first look after the gate opens (or after {@link nudge}), so a window that comes back between passes doesn't wait out\n * the rest of one.\n */\nexport class GitDirPoller implements UnifiedDisposable {\n\tprivate readonly targets = new Set<PollTarget>();\n\t/** Targets added since the last pass and not yet read */\n\tprivate readonly unread = new Set<PollTarget>();\n\t/** Each target's last read asked for; a target is never read twice at once, so the next waits for it */\n\tprivate readonly reading = new Map<PollTarget, Promise<void>>();\n\t/** Reads waiting for one of the {@link defaultPollConcurrency} slots, which a finished read hands over */\n\tprivate readonly waiting: (() => void)[] = [];\n\t/**\n\t * The stats of the batches (a pass, or first readings) in flight, shared by every target that probes the same path,\n\t * as the main checkout's root and its common dir do\n\t */\n\tprivate readonly stats = new Map<string, Promise<GitDirStat>>();\n\tprivate readonly intervalMs: number;\n\tprivate readonly activeCheckMs: number;\n\tprivate timer: ReturnType<typeof setTimeout> | undefined;\n\tprivate running: Promise<void> | undefined;\n\tprivate batches = 0;\n\tprivate slotsInUse = 0;\n\t/** The next look runs a pass whatever the interval says */\n\tprivate due = true;\n\tprivate lastPassAt = 0;\n\tprivate suspended = false;\n\tprivate disposed = false;\n\n\tconstructor(readonly options: WatchPollingOptions) {\n\t\tthis.intervalMs = options.intervalMs ?? defaultPollIntervalMs;\n\t\tthis.activeCheckMs = options.activeCheckMs ?? defaultPollActiveCheckMs;\n\t}\n\n\t[Symbol.dispose](): void {\n\t\tthis.dispose();\n\t}\n\n\tdispose(): void {\n\t\tthis.disposed = true;\n\t\tthis.targets.clear();\n\t\tthis.unread.clear();\n\t\tthis.stats.clear();\n\t\tthis.stopTimer();\n\t}\n\n\tget size(): number {\n\t\treturn this.targets.size;\n\t}\n\n\tadd(target: PollTarget): void {\n\t\tif (this.disposed || this.targets.has(target)) return;\n\n\t\tthis.targets.add(target);\n\t\tthis.unread.add(target);\n\t\tif (this.unread.size === 1) {\n\t\t\tqueueMicrotask(() => this.readUnread());\n\t\t}\n\t\tthis.startTimer();\n\t}\n\n\tdelete(target: PollTarget): void {\n\t\tthis.unread.delete(target);\n\t\tif (!this.targets.delete(target) || this.targets.size !== 0) return;\n\n\t\tthis.stopTimer();\n\t}\n\n\t/** Makes the next look a pass, and looks now */\n\tnudge(): void {\n\t\tthis.due = true;\n\t\tvoid this.tick();\n\t}\n\n\t/** Under the default gate, stops passes (and the timer) until {@link resume}; a host's `isActive` ignores it */\n\tsuspend(): void {\n\t\tthis.suspended = true;\n\t\tif (this.options.isActive == null) {\n\t\t\tthis.stopTimer();\n\t\t}\n\t}\n\n\t/** Lifts {@link suspend}, and runs a pass now, so what moved meanwhile is polled rather than at the next look */\n\tresume(): void {\n\t\tthis.suspended = false;\n\t\tthis.nudge();\n\t}\n\n\t/** One look at the gate: runs a pass when one is due, the gate is open and none is in flight */\n\ttick(): Promise<void> {\n\t\tif (this.disposed || this.targets.size === 0) return Promise.resolve();\n\n\t\tif (!(this.options.isActive?.() ?? !this.suspended)) {\n\t\t\tthis.due = true;\n\t\t\treturn Promise.resolve();\n\t\t}\n\n\t\t// A pass asked for while one is in flight runs once it ends; an elapsed interval just waits for the next look\n\t\tif (this.running != null) return this.running;\n\n\t\tconst now = performance.now();\n\t\tif (!this.due && now - this.lastPassAt < this.intervalMs) return Promise.resolve();\n\n\t\t// Under the default gate the timer counts the interval from this pass, whatever started it\n\t\tif (this.options.isActive == null) {\n\t\t\tthis.stopTimer();\n\t\t\tthis.startTimer();\n\t\t}\n\t\tthis.due = false;\n\t\tthis.lastPassAt = now;\n\t\tthis.running = this.pass().finally(() => {\n\t\t\tthis.running = undefined;\n\t\t\tif (this.due) {\n\t\t\t\tvoid this.tick();\n\t\t\t}\n\t\t});\n\t\treturn this.running;\n\t}\n\n\t/** Stats `path` once for every target that probes it in the batches in flight */\n\tstat(path: string): Promise<GitDirStat> {\n\t\tif (this.batches === 0) return this.options.stat(path);\n\n\t\tlet stat = this.stats.get(path);\n\t\tif (stat == null) {\n\t\t\tstat = this.options.stat(path);\n\t\t\tthis.stats.set(path, stat);\n\t\t}\n\t\treturn stat;\n\t}\n\n\t/** `path` changed since it was statted, so the next target to probe it in a batch in flight stats it again */\n\tinvalidate(path: string): void {\n\t\tthis.stats.delete(path);\n\t}\n\n\tprivate pass(): Promise<void> {\n\t\t// A pass reads its own stats, never ones from before it began\n\t\tthis.stats.clear();\n\t\t// The pass baselines any target not read yet\n\t\tthis.unread.clear();\n\t\treturn this.readAll([...this.targets]);\n\t}\n\n\tprivate readUnread(): void {\n\t\tif (this.unread.size === 0) return;\n\n\t\tconst targets = [...this.unread];\n\t\tthis.unread.clear();\n\t\tvoid this.readAll(targets);\n\t}\n\n\tprivate async readAll(targets: readonly PollTarget[]): Promise<void> {\n\t\tthis.batches++;\n\t\ttry {\n\t\t\t// A target that fails is read again next pass; nothing it saw this time is reported\n\t\t\tawait Promise.allSettled(targets.map(target => this.read(target)));\n\t\t} finally {\n\t\t\tthis.batches--;\n\t\t\tif (this.batches === 0) {\n\t\t\t\tthis.stats.clear();\n\t\t\t}\n\t\t}\n\t}\n\n\t/** Reads `target` once any read of it in flight ends, so the reading is fresh and its probes current */\n\tprivate read(target: PollTarget): Promise<void> {\n\t\tconst poll = async (): Promise<void> => {\n\t\t\tif (!this.targets.has(target)) return;\n\n\t\t\tawait this.acquireSlot();\n\t\t\ttry {\n\t\t\t\tif (this.targets.has(target)) {\n\t\t\t\t\tawait target.poll();\n\t\t\t\t}\n\t\t\t} finally {\n\t\t\t\tthis.releaseSlot();\n\t\t\t}\n\t\t};\n\t\tconst previous = this.reading.get(target);\n\t\tconst reading: Promise<void> = (previous != null ? previous.then(poll, poll) : poll()).finally(() => {\n\t\t\tif (this.reading.get(target) === reading) {\n\t\t\t\tthis.reading.delete(target);\n\t\t\t}\n\t\t});\n\t\tthis.reading.set(target, reading);\n\t\treturn reading;\n\t}\n\n\tprivate acquireSlot(): Promise<void> | undefined {\n\t\tif (this.slotsInUse < defaultPollConcurrency) {\n\t\t\tthis.slotsInUse++;\n\t\t\treturn undefined;\n\t\t}\n\n\t\treturn new Promise(resolve => this.waiting.push(resolve));\n\t}\n\n\tprivate releaseSlot(): void {\n\t\tconst next = this.waiting.shift();\n\t\tif (next != null) {\n\t\t\tnext();\n\t\t} else {\n\t\t\tthis.slotsInUse--;\n\t\t}\n\t}\n\n\tprivate startTimer(): void {\n\t\tif (this.timer != null || this.disposed || this.targets.size === 0) return;\n\n\t\tif (this.options.isActive != null) {\n\t\t\tthis.timer = setInterval(() => void this.tick(), this.activeCheckMs);\n\t\t} else if (!this.suspended) {\n\t\t\tthis.timer = setTimeout(() => {\n\t\t\t\tthis.timer = undefined;\n\t\t\t\tthis.due = true;\n\t\t\t\tvoid this.tick();\n\t\t\t}, this.intervalMs);\n\t\t} else {\n\t\t\treturn;\n\t\t}\n\n\t\t// A poll alone must not keep a (Node) process alive; in the browser timers are numbers\n\t\tif (typeof this.timer !== 'number') {\n\t\t\tthis.timer.unref();\n\t\t}\n\t}\n\n\tprivate stopTimer(): void {\n\t\tif (this.timer == null) return;\n\n\t\tif (this.options.isActive != null) {\n\t\t\tclearInterval(this.timer);\n\t\t} else {\n\t\t\tclearTimeout(this.timer);\n\t\t}\n\t\tthis.timer = undefined;\n\t\tthis.due = true;\n\t}\n}\n\n/**\n * Stands in for a watcher on `basePath`: stats each probe and reports one whose `mtime:size` moved as a `change` of\n * the paths it is emitted as, through the same handler the watcher's own events take. A probe's first read (and a\n * probe new to the set, as after a branch switch) only records; one that was missing and is now present moved. A stat\n * that throws keeps the last reading, so it never counts as a move.\n */\nexport class GitDirProbeTarget implements PollTarget {\n\tprivate readonly signatures = new Map<string, string>();\n\t/** For each poll whose stats are in flight, the paths {@link forget} cleared meanwhile, which it must not record */\n\tprivate readonly forgottenDuringPolls = new Set<Set<string>>();\n\n\tconstructor(\n\t\tprivate readonly basePath: string,\n\t\tprivate readonly stats: Pick<GitDirPoller, 'stat' | 'invalidate'>,\n\t\tprivate readonly getProbes: () => Iterable<GitDirProbe>,\n\t\t/** For each path a probe may be emitted as, the paths of the probes emitted as it */\n\t\tprivate readonly probesByEmittedPath: ReadonlyMap<string, readonly string[]>,\n\t\tprivate readonly onMoved: (event: FileWatchEvent, probe: GitDirProbe) => void,\n\t) {}\n\n\tasync poll(): Promise<void> {\n\t\tconst probes = [...this.getProbes()];\n\t\tconst forgotten = new Set<string>();\n\t\tthis.forgottenDuringPolls.add(forgotten);\n\t\tconst readings = await Promise.all(probes.map(p => this.read(p.path)));\n\t\tthis.forgottenDuringPolls.delete(forgotten);\n\n\t\tconst seen = new Set<string>();\n\t\tconst moved: GitDirProbe[] = [];\n\t\tfor (let i = 0; i < probes.length; i++) {\n\t\t\tconst probe = probes[i];\n\t\t\tseen.add(probe.path);\n\n\t\t\tconst reading = readings[i];\n\t\t\tif (reading == null || forgotten.has(probe.path)) continue;\n\n\t\t\tconst previous = this.signatures.get(probe.path);\n\t\t\tthis.signatures.set(probe.path, reading);\n\t\t\tif (previous != null && previous !== reading) {\n\t\t\t\tmoved.push(probe);\n\t\t\t}\n\t\t}\n\n\t\t// A probe that left the set starts over if it comes back\n\t\tfor (const path of this.signatures.keys()) {\n\t\t\tif (!seen.has(path)) {\n\t\t\t\tthis.signatures.delete(path);\n\t\t\t}\n\t\t}\n\n\t\tfor (const probe of moved) {\n\t\t\tfor (const path of probe.emitAs ?? [probe.path]) {\n\t\t\t\tthis.onMoved({ path: joinPaths(this.basePath, path), reason: 'change' }, probe);\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * The watcher reported `path` itself, so the next poll records the probes it touched (the path, a ref's lock file\n\t * standing for the ref, the directories above it, and the probes emitted as it, like `logs/HEAD` as `HEAD`, which\n\t * the watcher never sees) without reporting them again.\n\t */\n\tforget(path: string): void {\n\t\tif (!isDescendant(path, this.basePath)) return;\n\n\t\tlet relativePath = relative(this.basePath, path);\n\t\tif (relativePath.endsWith('.lock')) {\n\t\t\trelativePath = relativePath.substring(0, relativePath.length - 5);\n\t\t}\n\n\t\tfor (let p = relativePath; p.length > 0; p = p.substring(0, Math.max(0, p.lastIndexOf('/')))) {\n\t\t\tthis.clearSignature(p);\n\t\t}\n\n\t\tfor (const probePath of this.probesByEmittedPath.get(relativePath) ?? []) {\n\t\t\tthis.clearSignature(probePath);\n\t\t}\n\t}\n\n\tprivate clearSignature(path: string): void {\n\t\tthis.signatures.delete(path);\n\t\tthis.stats.invalidate(joinPaths(this.basePath, path));\n\t\tfor (const forgotten of this.forgottenDuringPolls) {\n\t\t\tforgotten.add(path);\n\t\t}\n\t}\n\n\tprivate async read(path: string): Promise<string | undefined> {\n\t\ttry {\n\t\t\tconst stat = await this.stats.stat(joinPaths(this.basePath, path));\n\t\t\treturn stat == null ? 'missing' : `${stat.mtime}:${stat.size}`;\n\t\t} catch {\n\t\t\treturn undefined;\n\t\t}\n\t}\n}\n"],"mappings":"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"}
|
|
@@ -17,6 +17,15 @@ export interface FileWatchEvent {
|
|
|
17
17
|
}
|
|
18
18
|
/** Opaque token representing a filesystem watcher */
|
|
19
19
|
export interface FileWatcher extends UnifiedDisposable {}
|
|
20
|
+
/**
|
|
21
|
+
* How much of what changes under a watcher's `basePath` it reports:
|
|
22
|
+
* - `reliable`: everything (the state of a watcher that never reports one);
|
|
23
|
+
* - `unreliable`: what changes on this machine, but maybe not another's edits (a network volume), so core polls
|
|
24
|
+
* beside it;
|
|
25
|
+
* - `unwatchable`: nothing (a filesystem without change notifications, or a watch that went down), so core polls
|
|
26
|
+
* instead.
|
|
27
|
+
*/
|
|
28
|
+
export type FileWatcherReliability = 'reliable' | 'unreliable' | 'unwatchable';
|
|
20
29
|
export interface FileWatcherOptions {
|
|
21
30
|
/**
|
|
22
31
|
* Directories to leave unwatched, each with everything beneath it. Entries are `/`-separated and relative to
|
|
@@ -26,6 +35,13 @@ export interface FileWatcherOptions {
|
|
|
26
35
|
* (Map each entry to your backend's syntax — e.g. picomatch-based watchers need both `X` and `X/**`.)
|
|
27
36
|
*/
|
|
28
37
|
readonly excludes?: readonly string[];
|
|
38
|
+
/**
|
|
39
|
+
* Reports this watcher's {@link FileWatcherReliability} whenever it changes; passed only when the host opted into
|
|
40
|
+
* polling (`WatchServiceOptions.polling`). May be called synchronously inside `createWatcher`, and again later
|
|
41
|
+
* (a watch going down, or coming back). Each watcher reports its own state, so one created on a path that is
|
|
42
|
+
* already down reports `unwatchable` itself. Going from `unwatchable` to anything else makes core rescan.
|
|
43
|
+
*/
|
|
44
|
+
readonly onDidChangeReliability?: (state: FileWatcherReliability) => void;
|
|
29
45
|
}
|
|
30
46
|
/**
|
|
31
47
|
* Factory for creating filesystem watchers. The library calls this
|