@gitkraken/core-gitlens 0.11.0 → 0.12.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 +41 -1
- package/dist/git/cache.js +2 -0
- package/dist/git/cache.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/providers/branches.d.ts +4 -2
- 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/worktreeSummary.d.ts +20 -9
- package/dist/git/watching/worktreeSummary.js +24 -22
- package/dist/git/watching/worktreeSummary.js.map +1 -1
- package/dist/git-cli/providers/branches.js +26 -4
- package/dist/git-cli/providers/branches.js.map +1 -1
- package/dist/plus/integrations/models/gitHostIntegration.js +3 -1
- package/dist/plus/integrations/models/gitHostIntegration.js.map +1 -1
- package/dist/plus/integrations/models/integration.js +1 -1
- package/dist/plus/integrations/providers/azure/azure.js +3 -2
- package/dist/plus/integrations/providers/azure/azure.js.map +1 -1
- package/dist/plus/integrations/providers/azure/models.d.ts +27 -10
- package/dist/plus/integrations/providers/azure/models.js +39 -19
- package/dist/plus/integrations/providers/azure/models.js.map +1 -1
- package/dist/plus/integrations/providers/azure/workItemStateCategories.d.ts +19 -0
- package/dist/plus/integrations/providers/azure/workItemStateCategories.js +32 -0
- package/dist/plus/integrations/providers/azure/workItemStateCategories.js.map +1 -0
- package/dist/plus/integrations/providers/azureDevOps.js +5 -5
- package/dist/plus/integrations/providers/azureDevOps.js.map +1 -1
- package/dist/plus/integrations/providers/issueSorts.d.ts +0 -1
- package/dist/plus/integrations/providers/jira-server.js +11 -5
- 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 +45 -30
- 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 +11 -1
- package/dist/plus/integrations/providers/models.js +9 -9
- package/dist/plus/integrations/providers/models.js.map +1 -1
- package/dist/plus/integrations/providers/providersApi.d.ts +30 -32
- package/dist/plus/integrations/providers/providersApi.js +149 -46
- package/dist/plus/integrations/providers/providersApi.js.map +1 -1
- package/dist/plus/integrations/providers/utils/jiraIssueDrains.d.ts +27 -15
- package/dist/plus/integrations/providers/utils/jiraIssueDrains.js +72 -10
- package/dist/plus/integrations/providers/utils/jiraIssueDrains.js.map +1 -1
- package/dist/plus/integrations/reads/batchEtags.d.ts +5 -2
- package/dist/plus/integrations/reads/batchEtags.js +31 -10
- package/dist/plus/integrations/reads/batchEtags.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/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/issueTracker.js +4 -4
- package/dist/plus/integrations/reads/issueTracker.js.map +1 -1
- package/dist/plus/integrations/reads/issues.js +1 -1
- package/dist/plus/integrations/reads/pullRequestBranches.js +10 -3
- package/dist/plus/integrations/reads/pullRequestBranches.js.map +1 -1
- package/dist/plus/integrations/results.d.ts +28 -3
- package/dist/plus/integrations/results.js +22 -3
- 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/docs/integrations.md +54 -19
- package/package.json +2 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"issue.js","names":[],"sources":["../../../../git/src/models/issue.ts"],"sourcesContent":["import { loggable } from '@gitlens/utils/decorators/log.js';\nimport type { RequireSome, RequireSomeWithProps } from '@gitlens/utils/types.js';\nimport type { IssueOrPullRequest, IssueOrPullRequestState } from './issueOrPullRequest.js';\nimport type { ProviderReference } from './remoteProvider.js';\nimport type { RepositoryIdentityDescriptor } from './repositoryIdentities.js';\n\n/** Selects which issue states a read should include. `all` covers open + closed. */\nexport type IssueStateFilter = 'open' | 'closed' | 'all';\n\n/**\n * One relationship a filtered issue search can constrain on.\n *\n * The last two are USER-INDEPENDENT — they describe the issue, not the caller — and neither is a SCOPE: either\n * one alone matches every such issue on the host, so a search using one still needs a repository/org scope.\n */\nexport type IssueSearchRelationship =\n\t/** Authored by the current user. */\n\t| 'authored'\n\t/** Assigned to the current user. */\n\t| 'assigned'\n\t/** Mentions the current user. */\n\t| 'mentioned'\n\t/** Assigned to ANYONE. Excludes unassigned issues — see {@link IssueSearchCriteria.relationships}. */\n\t| 'any-assignee'\n\t/** Assigned to nobody. The complement of `any-assignee`, not a broadening of it. */\n\t| 'unassigned';\n\n/**\n * A field an issue read can be ordered by, provider-neutral. No provider expresses all of them: each read\n * validates the requested key against the capability its provider declares and refuses one it can't express\n * server-side, rather than serving a differently-ordered list under the name of the one that was asked for.\n *\n * `closed` and `resolved` are deliberately SEPARATE, not two names for the same date. Azure DevOps tracks both\n * and they differ (an issue can be closed without being resolved and resolved without being closed), and Jira\n * has no close date at all — it models the equivalent as `resolutiondate`. Collapsing them would force one of\n * those two providers to lie about which date it ordered by.\n */\nexport type IssueSortField =\n\t/** Creation date. */\n\t| 'created'\n\t/** Last activity. */\n\t| 'updated'\n\t/** Close date. Only where the provider orders by it server-side — notably NOT GitHub. */\n\t| 'closed'\n\t/** Resolution date (Jira `resolutiondate`, Azure `ResolvedDate`). Distinct from `closed`. */\n\t| 'resolved'\n\t/** Number of comments. */\n\t| 'comments'\n\t/** Reactions/upvotes. */\n\t| 'reactions'\n\t/** The provider's own priority field. */\n\t| 'priority'\n\t/** Due date. */\n\t| 'dueDate'\n\t/** Alphabetical by title. */\n\t| 'title';\n\n/**\n * How an issue read is ordered, as `field:direction` — the same shape `BranchSorting`/`TagSorting` already use,\n * so it is one serializable string a consumer can persist, compare, or bind straight to a setting or a dropdown.\n */\nexport type IssueSorting = `${IssueSortField}:asc` | `${IssueSortField}:desc`;\n\n/**\n * The order every issue read that HAS a default applies when the caller asks for none: most recently updated\n * first, which is what those reads served before ordering was an option.\n *\n * One definition rather than one per layer, because it is a promise made in several places at once — the criteria\n * model documents it, the GitHub query emits it, and the result-ceiling warning quotes it — and three copies would\n * be free to disagree about what \"the default\" is.\n *\n * Note it is not a default every read has, and the two layers differ. The API client's account-wide \"my issues\"\n * search (`GitHubApi.searchMyIssues`) never requested an order, so a direct caller omitting `sort` still gets\n * GitHub's relevance order — the default is opt-in there, to keep that read's already-shipped results unchanged.\n * The FACADE does apply it: GitHub declares `updated:desc` among its account-wide keys, so\n * `readAccountWideIssuesPage` resolves the omission to this default and forwards it, and that read's emitted query\n * gained a `sort:updated` qualifier it did not have before. A read whose surface cannot express even this key is\n * the only one the facade leaves in the provider's own order.\n */\nexport const defaultIssueSort: IssueSorting = 'updated:desc';\n\n/**\n * What a filtered issue search narrows on: a provider-neutral criteria set, translated to each provider's own\n * query language by its integration.\n *\n * Structured rather than a provider query string so a consumer can render filter chips and hide the ones a\n * provider can't express, and so the same criteria mean the same thing on a provider whose query language looks\n * nothing like GitHub's search syntax. Every field is validated against the provider's declared capability\n * BEFORE the read runs, all-or-nothing: a criterion the provider can't express server-side refuses the whole\n * read rather than serving a list that was never narrowed. Client-side narrowing is not an option — the dropped\n * items still counted toward the page and cursor the provider produced, so a post-filter would leave the items\n * describing a different result set than the paging that came with them.\n */\nexport interface IssueSearchCriteria {\n\t/**\n\t * Relationships to constrain on, OR-ed together (each becomes its own provider query, unioned and deduped).\n\t * Omitted means NO relationship constraint at all — every issue in scope, assigned or not — which requires a\n\t * repository/org scope.\n\t *\n\t * `any-assignee` and `unassigned` partition the scope between them and are mutually exclusive; passing both is\n\t * refused rather than silently dropping one. Note that \"all visible issues\" is the OMITTED case, NOT\n\t * `any-assignee`, which excludes unassigned issues.\n\t */\n\trelationships?: IssueSearchRelationship[];\n\t/**\n\t * Free text, matched by the provider's own relevance rules — NOT a substring match, and not a way to smuggle\n\t * qualifiers: a token that would read as one is dropped, as are quotes and control characters, so text can\n\t * never re-scope the search. The structured criteria are the qualifier channel.\n\t */\n\ttext?: string;\n\t/** Issue states to include. Omitted reads open issues only. */\n\tstate?: IssueStateFilter;\n\t/** Labels the issue must carry — AND-ed, matching GitHub's own `label:` semantics. */\n\tlabels?: string[];\n\tmilestone?: string;\n\t/** ISO date (`YYYY-MM-DD`). The most effective narrowing criterion by far on a large scope. */\n\tupdatedAfter?: string;\n\t/** ISO date (`YYYY-MM-DD`). */\n\tcreatedAfter?: string;\n\t/** Issues with no linked pull request. */\n\twithoutLinkedPullRequest?: boolean;\n\t/** Includes issues in archived repositories, which are excluded by default. */\n\tincludeArchived?: boolean;\n\t/**\n\t * How to order the results. Omitted means `updated:desc`, which is what this search has always served.\n\t *\n\t * Validated like every other criterion — all-or-nothing against {@link IssueSearchCapabilities.sorts}: a key\n\t * the provider can't express server-side refuses the WHOLE read rather than falling back to the default.\n\t * Serving another order is not a smaller error than serving a wider result: combined with the provider's\n\t * result ceiling it returns a different subset than was asked for, and the paging that comes with it describes\n\t * that other subset.\n\t *\n\t * Do not change it mid-pagination. A cursor carries the sort it was produced under, and threading it under a\n\t * different key is refused rather than serving a sequence with gaps and repeats: drop the cursor instead.\n\t */\n\tsort?: IssueSorting;\n}\n\n/**\n * Which {@link IssueSearchCriteria} fields a provider's filtered issue search can express server-side.\n *\n * An empty `relationships` with every flag false means the provider has NO filtered issue search — the read is\n * refused outright, so a consumer should offer neither the surface nor its chips.\n */\nexport interface IssueSearchCapabilities {\n\t/** Relationships the search can constrain on. Empty means the search itself is unsupported. */\n\trelationships: IssueSearchRelationship[];\n\ttext: boolean;\n\tlabels: boolean;\n\tmilestone: boolean;\n\tupdatedAfter: boolean;\n\tcreatedAfter: boolean;\n\twithoutLinkedPullRequest: boolean;\n\t/** Whether {@link IssueSearchCriteria.state} can select anything other than the provider's default (open). */\n\tstates: boolean;\n\t/**\n\t * Sort keys the search can express IN SERVER. Always contains at least `updated:desc` — the historical default\n\t * — when the search exists at all, so it is never empty for a usable surface; a provider without a filtered\n\t * search reports an empty `relationships`, which is already the signal that there is no surface to order.\n\t */\n\tsorts: IssueSorting[];\n}\n\n/**\n * The read that produced an issue row, as a key for `getIssueFieldPresence` (`@gitlens/integrations`). It names a\n * READ, not a field set: the same tag can carry different fields on different providers.\n */\nexport type IssueProjection =\n\t/** A single-item read. */\n\t| 'point'\n\t/** The filtered search. */\n\t| 'search'\n\t/** The account-wide list, and the trackers' \"my issues\". */\n\t| 'account'\n\t/** A repository-scoped list. */\n\t| 'repos'\n\t/** A tracker project's issues (`listIssueTrackerIssuesPage`, `getIssuesForProject`). */\n\t| 'project'\n\t/** The batch read. */\n\t| 'batch';\n\nexport interface IssueShape extends IssueOrPullRequest {\n\t/** `undefined` when the provider can't resolve the author, e.g. a deleted GitHub account */\n\tauthor: IssueMember | undefined;\n\tassignees: IssueMember[];\n\t/** The provider's own workflow state, when it exposes one beyond the normalized open/closed state. */\n\tproviderState?: IssueProviderState;\n\trepository?: IssueRepository;\n\tlabels?: IssueLabel[];\n\tbody?: string;\n\t/**\n\t * The syntax used by `body`. When omitted, consumers should treat `body` as Markdown — except that Azure DevOps\n\t * work items and Bitbucket Cloud issues carry HTML with no `bodyFormat` set.\n\t */\n\tbodyFormat?: IssueBodyFormat;\n\tproject?: IssueProject;\n\tissueType?: string;\n\t/** An issue can belong to several Jira sprints; Azure reports one iteration. */\n\titerations?: IssueIteration[];\n\t/** The read that produced this row. Absent means unknown: a legacy row, or a provider or read not yet tagged. */\n\treadonly projection?: IssueProjection;\n}\n\n@loggable(i => i.id)\nexport class Issue implements IssueShape {\n\treadonly type = 'issue';\n\n\tconstructor(\n\t\tpublic readonly provider: ProviderReference,\n\t\tpublic readonly id: string,\n\t\tpublic readonly nodeId: string | undefined,\n\t\tpublic readonly title: string,\n\t\tpublic readonly url: string,\n\t\tpublic readonly createdDate: Date,\n\t\tpublic readonly updatedDate: Date,\n\t\tpublic readonly closed: boolean,\n\t\tpublic readonly state: IssueOrPullRequestState,\n\t\tpublic readonly author: IssueMember | undefined,\n\t\tpublic readonly assignees: IssueMember[],\n\t\tpublic readonly repository?: IssueRepository,\n\t\tpublic readonly closedDate?: Date,\n\t\tpublic readonly labels?: IssueLabel[],\n\t\tpublic readonly commentsCount?: number,\n\t\tpublic readonly thumbsUpCount?: number,\n\t\tpublic readonly body?: string,\n\t\tpublic readonly project?: IssueProject,\n\t\tpublic readonly number?: string,\n\t\tpublic readonly issueType?: string,\n\t\tpublic readonly providerState?: IssueProviderState,\n\t\tpublic readonly bodyFormat?: IssueBodyFormat,\n\t\tpublic readonly iterations?: IssueIteration[],\n\t\tpublic readonly projection?: IssueProjection,\n\t) {}\n\n\tstatic is(issue: unknown): issue is Issue {\n\t\treturn issue instanceof Issue;\n\t}\n}\n\nexport type IssueBodyFormat = 'markdown' | 'jira-wiki';\n\nexport const enum RepositoryAccessLevel {\n\tAdmin = 100,\n\tMaintain = 40,\n\tWrite = 30,\n\tTriage = 20,\n\tRead = 10,\n\tNone = 0,\n}\n\nexport interface IssueLabel {\n\tcolor?: string;\n\tname: string;\n}\n\n/**\n * Sprint metadata is optional because some providers only report an iteration path. An absent `isActive`\n * means unknown, not inactive, so consumers must not use it to exclude those iterations by default.\n */\nexport interface IssueIteration {\n\t/**\n\t * A sprint id on Jira, the verbatim iteration path on Azure — so it is only meaningful within the provider's\n\t * project, never across providers. Unique at read time but NOT durable: Azure rewrites the path when an\n\t * iteration node is renamed or re-parented, so don't persist it as a long-lived id.\n\t */\n\tid: string;\n\tname: string;\n\tisActive?: boolean;\n\tstartDate?: Date;\n\tendDate?: Date;\n}\n\nexport interface IssueMember {\n\tid: string;\n\t/**\n\t * Absent when the provider exposes no display name for the member. Optional on purpose: a fallback string\n\t * invented here (`'unknown'`, `''`) can't be told apart from a real name downstream, so it can't be undone by a\n\t * consumer that needs to render something else — or nothing at all, as when the member feeds an AI prompt.\n\t * Each consumer picks its own presentation.\n\t */\n\tname?: string;\n\t/** The provider's handle for this person, when it has one — GitHub's login, Azure's `uniqueName` (a UPN,\n\t * so an email), Bitbucket's mutable `nickname`. Display/labelling only: it is neither guaranteed present\n\t * (GitLab's native mapper has none) nor a stable identity, so never key a match off it. */\n\tusername?: string;\n\tavatarUrl?: string;\n\turl?: string;\n}\n\nexport type IssueProviderState = {\n\tid?: string;\n\tname: string;\n\tcolor?: string;\n\tcategory?: 'TO_DO' | 'IN_PROGRESS' | 'DONE';\n};\n\nexport interface IssueProject {\n\tid: string;\n\tname: string;\n\tresourceId: string;\n\tresourceName: string;\n}\n\nexport interface IssueRepository {\n\towner: string;\n\trepo: string;\n\taccessLevel?: RepositoryAccessLevel;\n\turl?: string;\n\tid?: string;\n}\n\nexport type IssueRepositoryIdentityDescriptor = RequireSomeWithProps<\n\tRequireSome<RepositoryIdentityDescriptor<string>, 'provider'>,\n\t'provider',\n\t'id' | 'domain' | 'repoDomain' | 'repoName'\n> &\n\tRequireSomeWithProps<RequireSome<RepositoryIdentityDescriptor<string>, 'remote'>, 'remote', 'domain'>;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AA+EA,MAAa,mBAAiC;AA6HvC,IAAM,QAAA,SAAN,MAAM,MAA4B;CAIvB;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CA1BjB,OAAgB;CAEhB,YACC,UACA,IACA,QACA,OACA,KACA,aACA,aACA,QACA,OACA,QACA,WACA,YACA,YACA,QACA,eACA,eACA,MACA,SACA,QACA,WACA,eACA,YACA,YACA,YACC;EAxBe,KAAA,WAAA;EACA,KAAA,KAAA;EACA,KAAA,SAAA;EACA,KAAA,QAAA;EACA,KAAA,MAAA;EACA,KAAA,cAAA;EACA,KAAA,cAAA;EACA,KAAA,SAAA;EACA,KAAA,QAAA;EACA,KAAA,SAAA;EACA,KAAA,YAAA;EACA,KAAA,aAAA;EACA,KAAA,aAAA;EACA,KAAA,SAAA;EACA,KAAA,gBAAA;EACA,KAAA,gBAAA;EACA,KAAA,OAAA;EACA,KAAA,UAAA;EACA,KAAA,SAAA;EACA,KAAA,YAAA;EACA,KAAA,gBAAA;EACA,KAAA,aAAA;EACA,KAAA,aAAA;EACA,KAAA,aAAA;CACd;CAEH,OAAO,GAAG,OAAgC;EACzC,OAAO,iBAAA;CACR;AACD;AAlCC,QAAA,SAAA,WAAA,CAAA,UAAS,MAAK,EAAE,EAAE,CAAA,GAAA,KAAA;AAsCnB,IAAkB,wBAAX,yBAAA,uBAAA;CACN,sBAAA,sBAAA,WAAA,OAAA;CACA,sBAAA,sBAAA,cAAA,MAAA;CACA,sBAAA,sBAAA,WAAA,MAAA;CACA,sBAAA,sBAAA,YAAA,MAAA;CACA,sBAAA,sBAAA,UAAA,MAAA;CACA,sBAAA,sBAAA,UAAA,KAAA;;AACD,EAAA,CAAA,CAAA"}
|
|
1
|
+
{"version":3,"file":"issue.js","names":[],"sources":["../../../../git/src/models/issue.ts"],"sourcesContent":["import { loggable } from '@gitlens/utils/decorators/log.js';\nimport type { RequireSome, RequireSomeWithProps } from '@gitlens/utils/types.js';\nimport type { IssueOrPullRequest, IssueOrPullRequestState } from './issueOrPullRequest.js';\nimport type { ProviderReference } from './remoteProvider.js';\nimport type { RepositoryIdentityDescriptor } from './repositoryIdentities.js';\n\n/** Selects which issue states a read should include. `all` covers open + closed. */\nexport type IssueStateFilter = 'open' | 'closed' | 'all';\n\n/**\n * One relationship a filtered issue search can constrain on.\n *\n * The last two are USER-INDEPENDENT — they describe the issue, not the caller — and neither is a SCOPE: either\n * one alone matches every such issue on the host, so a search using one still needs a repository/org scope.\n */\nexport type IssueSearchRelationship =\n\t/** Authored by the current user. */\n\t| 'authored'\n\t/** Assigned to the current user. */\n\t| 'assigned'\n\t/** Mentions the current user. */\n\t| 'mentioned'\n\t/** Assigned to ANYONE. Excludes unassigned issues — see {@link IssueSearchCriteria.relationships}. */\n\t| 'any-assignee'\n\t/** Assigned to nobody. The complement of `any-assignee`, not a broadening of it. */\n\t| 'unassigned';\n\n/**\n * A field an issue read can be ordered by, provider-neutral. No provider expresses all of them: each read\n * validates the requested key against the capability its provider declares and refuses one it can't express\n * server-side, rather than serving a differently-ordered list under the name of the one that was asked for.\n *\n * `closed` and `resolved` are deliberately SEPARATE, not two names for the same date. Azure DevOps tracks both\n * and they differ (an issue can be closed without being resolved and resolved without being closed), and Jira\n * has no close date at all — it models the equivalent as `resolutiondate`. Collapsing them would force one of\n * those two providers to lie about which date it ordered by.\n */\nexport type IssueSortField =\n\t/** Creation date. */\n\t| 'created'\n\t/** Last activity. */\n\t| 'updated'\n\t/** Close date. Only where the provider orders by it server-side — notably NOT GitHub. */\n\t| 'closed'\n\t/** Resolution date (Jira `resolutiondate`, Azure `ResolvedDate`). Distinct from `closed`. */\n\t| 'resolved'\n\t/** Number of comments. */\n\t| 'comments'\n\t/** Reactions/upvotes. */\n\t| 'reactions'\n\t/** The provider's own priority field. */\n\t| 'priority'\n\t/** Due date. */\n\t| 'dueDate'\n\t/** Alphabetical by title. */\n\t| 'title';\n\n/**\n * How an issue read is ordered, as `field:direction` — the same shape `BranchSorting`/`TagSorting` already use,\n * so it is one serializable string a consumer can persist, compare, or bind straight to a setting or a dropdown.\n */\nexport type IssueSorting = `${IssueSortField}:asc` | `${IssueSortField}:desc`;\n\n/**\n * The order every issue read that HAS a default applies when the caller asks for none: most recently updated\n * first, which is what those reads served before ordering was an option.\n *\n * One definition rather than one per layer, because it is a promise made in several places at once — the criteria\n * model documents it, the GitHub query emits it, and the result-ceiling warning quotes it — and three copies would\n * be free to disagree about what \"the default\" is.\n *\n * Note it is not a default every read has, and the two layers differ. The API client's account-wide \"my issues\"\n * search (`GitHubApi.searchMyIssues`) never requested an order, so a direct caller omitting `sort` still gets\n * GitHub's relevance order — the default is opt-in there, to keep that read's already-shipped results unchanged.\n * The FACADE does apply it: GitHub declares `updated:desc` among its account-wide keys, so\n * `readAccountWideIssuesPage` resolves the omission to this default and forwards it, and that read's emitted query\n * gained a `sort:updated` qualifier it did not have before. A read whose surface cannot express even this key is\n * the only one the facade leaves in the provider's own order.\n */\nexport const defaultIssueSort: IssueSorting = 'updated:desc';\n\n/**\n * What a filtered issue search narrows on: a provider-neutral criteria set, translated to each provider's own\n * query language by its integration.\n *\n * Structured rather than a provider query string so a consumer can render filter chips and hide the ones a\n * provider can't express, and so the same criteria mean the same thing on a provider whose query language looks\n * nothing like GitHub's search syntax. Every field is validated against the provider's declared capability\n * BEFORE the read runs, all-or-nothing: a criterion the provider can't express server-side refuses the whole\n * read rather than serving a list that was never narrowed. Client-side narrowing is not an option — the dropped\n * items still counted toward the page and cursor the provider produced, so a post-filter would leave the items\n * describing a different result set than the paging that came with them.\n */\nexport interface IssueSearchCriteria {\n\t/**\n\t * Relationships to constrain on, OR-ed together (each becomes its own provider query, unioned and deduped).\n\t * Omitted means NO relationship constraint at all — every issue in scope, assigned or not — which requires a\n\t * repository/org scope.\n\t *\n\t * `any-assignee` and `unassigned` partition the scope between them and are mutually exclusive; passing both is\n\t * refused rather than silently dropping one. Note that \"all visible issues\" is the OMITTED case, NOT\n\t * `any-assignee`, which excludes unassigned issues.\n\t */\n\trelationships?: IssueSearchRelationship[];\n\t/**\n\t * Free text, matched by the provider's own relevance rules — NOT a substring match, and not a way to smuggle\n\t * qualifiers: a token that would read as one is dropped, as are quotes and control characters, so text can\n\t * never re-scope the search. The structured criteria are the qualifier channel.\n\t */\n\ttext?: string;\n\t/** Issue states to include. Omitted reads open issues only. */\n\tstate?: IssueStateFilter;\n\t/** Labels the issue must carry — AND-ed, matching GitHub's own `label:` semantics. */\n\tlabels?: string[];\n\tmilestone?: string;\n\t/** ISO date (`YYYY-MM-DD`). The most effective narrowing criterion by far on a large scope. */\n\tupdatedAfter?: string;\n\t/** ISO date (`YYYY-MM-DD`). */\n\tcreatedAfter?: string;\n\t/** Issues with no linked pull request. */\n\twithoutLinkedPullRequest?: boolean;\n\t/** Includes issues in archived repositories, which are excluded by default. */\n\tincludeArchived?: boolean;\n\t/**\n\t * How to order the results. Omitted means `updated:desc`, which is what this search has always served.\n\t *\n\t * Validated like every other criterion — all-or-nothing against {@link IssueSearchCapabilities.sorts}: a key\n\t * the provider can't express server-side refuses the WHOLE read rather than falling back to the default.\n\t * Serving another order is not a smaller error than serving a wider result: combined with the provider's\n\t * result ceiling it returns a different subset than was asked for, and the paging that comes with it describes\n\t * that other subset.\n\t *\n\t * Do not change it mid-pagination. A cursor carries the sort it was produced under, and threading it under a\n\t * different key is refused rather than serving a sequence with gaps and repeats: drop the cursor instead.\n\t */\n\tsort?: IssueSorting;\n}\n\n/**\n * Which {@link IssueSearchCriteria} fields a provider's filtered issue search can express server-side.\n *\n * An empty `relationships` with every flag false means the provider has NO filtered issue search — the read is\n * refused outright, so a consumer should offer neither the surface nor its chips.\n */\nexport interface IssueSearchCapabilities {\n\t/** Relationships the search can constrain on. Empty means the search itself is unsupported. */\n\trelationships: IssueSearchRelationship[];\n\ttext: boolean;\n\tlabels: boolean;\n\tmilestone: boolean;\n\tupdatedAfter: boolean;\n\tcreatedAfter: boolean;\n\twithoutLinkedPullRequest: boolean;\n\t/** Whether {@link IssueSearchCriteria.state} can select anything other than the provider's default (open). */\n\tstates: boolean;\n\t/**\n\t * Sort keys the search can express IN SERVER. Always contains at least `updated:desc` — the historical default\n\t * — when the search exists at all, so it is never empty for a usable surface; a provider without a filtered\n\t * search reports an empty `relationships`, which is already the signal that there is no surface to order.\n\t */\n\tsorts: IssueSorting[];\n}\n\n/**\n * The read that produced an issue row, as a key for `getIssueFieldPresence` (`@gitlens/integrations`). It names a\n * READ, not a field set: the same tag can carry different fields on different providers.\n */\nexport type IssueProjection =\n\t/** A single-item read. */\n\t| 'point'\n\t/** The filtered search. */\n\t| 'search'\n\t/** The account-wide list, and the trackers' \"my issues\". */\n\t| 'account'\n\t/** A repository-scoped list. */\n\t| 'repos'\n\t/** A tracker project's issues (`listIssueTrackerIssuesPage`, `getIssuesForProject`). */\n\t| 'project'\n\t/** The batch read. */\n\t| 'batch';\n\nexport interface IssueShape extends IssueOrPullRequest {\n\t/** `undefined` when the provider can't resolve the author, e.g. a deleted GitHub account */\n\tauthor: IssueMember | undefined;\n\tassignees: IssueMember[];\n\t/** The provider's own workflow state, when it exposes one beyond the normalized open/closed state. */\n\tproviderState?: IssueProviderState;\n\trepository?: IssueRepository;\n\tlabels?: IssueLabel[];\n\tbody?: string;\n\t/**\n\t * The syntax used by `body`. When omitted, consumers should treat `body` as Markdown — except that Azure DevOps\n\t * work items and Bitbucket Cloud issues carry HTML with no `bodyFormat` set.\n\t */\n\tbodyFormat?: IssueBodyFormat;\n\tproject?: IssueProject;\n\tissueType?: string;\n\t/** An issue can belong to several Jira sprints; Azure reports one iteration. */\n\titerations?: IssueIteration[];\n\t/** The read that produced this row. Absent means unknown: a legacy row, or a provider or read not yet tagged. */\n\treadonly projection?: IssueProjection;\n}\n\n@loggable(i => i.id)\nexport class Issue implements IssueShape {\n\treadonly type = 'issue';\n\n\tconstructor(\n\t\tpublic readonly provider: ProviderReference,\n\t\tpublic readonly id: string,\n\t\tpublic readonly nodeId: string | undefined,\n\t\tpublic readonly title: string,\n\t\tpublic readonly url: string,\n\t\tpublic readonly createdDate: Date,\n\t\tpublic readonly updatedDate: Date,\n\t\tpublic readonly closed: boolean,\n\t\tpublic readonly state: IssueOrPullRequestState,\n\t\tpublic readonly author: IssueMember | undefined,\n\t\tpublic readonly assignees: IssueMember[],\n\t\tpublic readonly repository?: IssueRepository,\n\t\tpublic readonly closedDate?: Date,\n\t\tpublic readonly labels?: IssueLabel[],\n\t\tpublic readonly commentsCount?: number,\n\t\tpublic readonly thumbsUpCount?: number,\n\t\tpublic readonly body?: string,\n\t\tpublic readonly project?: IssueProject,\n\t\tpublic readonly number?: string,\n\t\tpublic readonly issueType?: string,\n\t\tpublic readonly providerState?: IssueProviderState,\n\t\tpublic readonly bodyFormat?: IssueBodyFormat,\n\t\tpublic readonly iterations?: IssueIteration[],\n\t\tpublic readonly projection?: IssueProjection,\n\t) {}\n\n\tstatic is(issue: unknown): issue is Issue {\n\t\treturn issue instanceof Issue;\n\t}\n}\n\nexport type IssueBodyFormat = 'markdown' | 'jira-wiki';\n\nexport const enum RepositoryAccessLevel {\n\tAdmin = 100,\n\tMaintain = 40,\n\tWrite = 30,\n\tTriage = 20,\n\tRead = 10,\n\tNone = 0,\n}\n\nexport interface IssueLabel {\n\tcolor?: string;\n\tname: string;\n}\n\n/**\n * Sprint metadata is optional because some providers only report an iteration path. An absent `isActive`\n * means unknown, not inactive, so consumers must not use it to exclude those iterations by default.\n */\nexport interface IssueIteration {\n\t/**\n\t * A sprint id on Jira, the verbatim iteration path on Azure — so it is only meaningful within the provider's\n\t * project, never across providers. Unique at read time but NOT durable: Azure rewrites the path when an\n\t * iteration node is renamed or re-parented, so don't persist it as a long-lived id.\n\t */\n\tid: string;\n\tname: string;\n\tisActive?: boolean;\n\tstartDate?: Date;\n\tendDate?: Date;\n}\n\nexport interface IssueMember {\n\tid: string;\n\t/**\n\t * Absent when the provider exposes no display name for the member. Optional on purpose: a fallback string\n\t * invented here (`'unknown'`, `''`) can't be told apart from a real name downstream, so it can't be undone by a\n\t * consumer that needs to render something else — or nothing at all, as when the member feeds an AI prompt.\n\t * Each consumer picks its own presentation.\n\t */\n\tname?: string;\n\t/** The provider's handle for this person, when it has one — GitHub's login, Azure's `uniqueName` (a UPN,\n\t * so an email), Bitbucket's mutable `nickname`. Display/labelling only: it is neither guaranteed present\n\t * (GitLab's native mapper has none) nor a stable identity, so never key a match off it. */\n\tusername?: string;\n\tavatarUrl?: string;\n\turl?: string;\n}\n\nexport type IssueProviderState = {\n\tid?: string;\n\tname: string;\n\tcolor?: string;\n\t/** The provider's workflow category. `CANCELED` is abandoned work (Linear's canceled and duplicate states, Azure's\n\t * `Removed` category): closed like `DONE`, kept apart so a consumer can tell the two outcomes apart. */\n\tcategory?: 'TO_DO' | 'IN_PROGRESS' | 'DONE' | 'CANCELED';\n};\n\nexport interface IssueProject {\n\tid: string;\n\tname: string;\n\tresourceId: string;\n\tresourceName: string;\n}\n\nexport interface IssueRepository {\n\towner: string;\n\trepo: string;\n\taccessLevel?: RepositoryAccessLevel;\n\turl?: string;\n\tid?: string;\n}\n\nexport type IssueRepositoryIdentityDescriptor = RequireSomeWithProps<\n\tRequireSome<RepositoryIdentityDescriptor<string>, 'provider'>,\n\t'provider',\n\t'id' | 'domain' | 'repoDomain' | 'repoName'\n> &\n\tRequireSomeWithProps<RequireSome<RepositoryIdentityDescriptor<string>, 'remote'>, 'remote', 'domain'>;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AA+EA,MAAa,mBAAiC;AA6HvC,IAAM,QAAA,SAAN,MAAM,MAA4B;CAIvB;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CA1BjB,OAAgB;CAEhB,YACC,UACA,IACA,QACA,OACA,KACA,aACA,aACA,QACA,OACA,QACA,WACA,YACA,YACA,QACA,eACA,eACA,MACA,SACA,QACA,WACA,eACA,YACA,YACA,YACC;EAxBe,KAAA,WAAA;EACA,KAAA,KAAA;EACA,KAAA,SAAA;EACA,KAAA,QAAA;EACA,KAAA,MAAA;EACA,KAAA,cAAA;EACA,KAAA,cAAA;EACA,KAAA,SAAA;EACA,KAAA,QAAA;EACA,KAAA,SAAA;EACA,KAAA,YAAA;EACA,KAAA,aAAA;EACA,KAAA,aAAA;EACA,KAAA,SAAA;EACA,KAAA,gBAAA;EACA,KAAA,gBAAA;EACA,KAAA,OAAA;EACA,KAAA,UAAA;EACA,KAAA,SAAA;EACA,KAAA,YAAA;EACA,KAAA,gBAAA;EACA,KAAA,aAAA;EACA,KAAA,aAAA;EACA,KAAA,aAAA;CACd;CAEH,OAAO,GAAG,OAAgC;EACzC,OAAO,iBAAA;CACR;AACD;AAlCC,QAAA,SAAA,WAAA,CAAA,UAAS,MAAK,EAAE,EAAE,CAAA,GAAA,KAAA;AAsCnB,IAAkB,wBAAX,yBAAA,uBAAA;CACN,sBAAA,sBAAA,WAAA,OAAA;CACA,sBAAA,sBAAA,cAAA,MAAA;CACA,sBAAA,sBAAA,WAAA,MAAA;CACA,sBAAA,sBAAA,YAAA,MAAA;CACA,sBAAA,sBAAA,UAAA,MAAA;CACA,sBAAA,sBAAA,UAAA,KAAA;;AACD,EAAA,CAAA,CAAA"}
|
|
@@ -28,6 +28,8 @@ export interface AheadOfBase {
|
|
|
28
28
|
readonly ref: string;
|
|
29
29
|
/** Commits on HEAD not on `ref` */
|
|
30
30
|
readonly ahead: number;
|
|
31
|
+
/** The commit `ref` pointed at, from the cached ref list; `undefined` when no listed ref matches it */
|
|
32
|
+
readonly sha: string | undefined;
|
|
31
33
|
} | undefined;
|
|
32
34
|
/** Full ref names (`refs/heads/main`, `refs/remotes/origin/main`, `refs/remotes/origin/HEAD`) whose movement can change this answer, given with or without a base so a watcher also learns when one appears: the branch, every candidate compared up to the chosen one, their tracking refs, and the default-branch symref when the default was consulted. Lets a watcher recompute only when one of them moves. */
|
|
33
35
|
readonly dependsOn: readonly string[];
|
|
@@ -103,8 +105,8 @@ export interface GitBranchesSubProvider {
|
|
|
103
105
|
* target (one naming no ref, a flag-like one, or the branch itself) falls through to the next, and the local default
|
|
104
106
|
* branch is used when none survives. The count is the right side of `<base>...HEAD`. `base` is `undefined` when no
|
|
105
107
|
* base resolves, when the base is the branch itself (by name, or as its copy on one of the repo's remotes) at HEAD's
|
|
106
|
-
* own commit, or when HEAD is detached or has no commit yet; `dependsOn` is given either way.
|
|
107
|
-
* can't be read.
|
|
108
|
+
* own commit, or when HEAD is detached or has no commit yet; `dependsOn` is given either way. `base.sha` is the tip of
|
|
109
|
+
* `ref`, read from the cached ref list. Rejects when the count can't be read.
|
|
108
110
|
*/
|
|
109
111
|
getAheadOfBase?(repoPath: string, branch: string, cancellation?: AbortSignal): Promise<AheadOfBase>;
|
|
110
112
|
getBaseBranchName?(repoPath: string, ref: string, options?: {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Issue, IssueRepositoryIdentityDescriptor, IssueShape, IssueSorting } from "../models/issue.js";
|
|
1
|
+
import { Issue, IssueProviderState, IssueRepositoryIdentityDescriptor, IssueShape, IssueSorting } from "../models/issue.js";
|
|
2
2
|
//#region ../git/src/utils/issue.utils.d.ts
|
|
3
3
|
/**
|
|
4
4
|
* The order a read that ALWAYS orders is being made under: the caller's key, or the facade's default.
|
|
@@ -46,5 +46,7 @@ export declare function getRepositoryIdentityForIssue(issue: IssueShape | Issue)
|
|
|
46
46
|
* report the viewer's access as fetched when the copy no longer has it.
|
|
47
47
|
*/
|
|
48
48
|
export declare function serializeIssue(value: IssueShape): IssueShape;
|
|
49
|
+
/** Whether a provider's workflow category ends the issue: done or abandoned (see {@link IssueProviderState.category}). */
|
|
50
|
+
export declare function isTerminalIssueStateCategory(category: IssueProviderState['category']): boolean;
|
|
49
51
|
//#endregion
|
|
50
52
|
//# sourceMappingURL=issue.utils.d.ts.map
|
|
@@ -164,7 +164,11 @@ function serializeIssue(value) {
|
|
|
164
164
|
}))
|
|
165
165
|
};
|
|
166
166
|
}
|
|
167
|
+
/** Whether a provider's workflow category ends the issue: done or abandoned (see {@link IssueProviderState.category}). */
|
|
168
|
+
function isTerminalIssueStateCategory(category) {
|
|
169
|
+
return category === "DONE" || category === "CANCELED";
|
|
170
|
+
}
|
|
167
171
|
//#endregion
|
|
168
|
-
export { effectiveIssueSort, getIssueComparator, getRepositoryIdentityForIssue, serializeIssue };
|
|
172
|
+
export { effectiveIssueSort, getIssueComparator, getRepositoryIdentityForIssue, isTerminalIssueStateCategory, serializeIssue };
|
|
169
173
|
|
|
170
174
|
//# sourceMappingURL=issue.utils.js.map
|
|
@@ -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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"}
|
|
@@ -21,6 +21,8 @@ export interface WorktreeSummaryBase {
|
|
|
21
21
|
/** The ref the count was taken against (see `AheadOfBase.base`) */
|
|
22
22
|
readonly ref: string;
|
|
23
23
|
readonly ahead: number;
|
|
24
|
+
/** The commit {@link ref} pointed at when the count was read; `undefined` when it couldn't be resolved */
|
|
25
|
+
readonly sha: string | undefined;
|
|
24
26
|
}
|
|
25
27
|
export interface WorktreeSummary {
|
|
26
28
|
/** The repository path in {@link getRepositoryKey} form */
|
|
@@ -28,6 +30,8 @@ export interface WorktreeSummary {
|
|
|
28
30
|
/** The checked-out branch; `undefined` when detached (or unborn with no name) */
|
|
29
31
|
readonly branch: string | undefined;
|
|
30
32
|
readonly detached: boolean;
|
|
33
|
+
/** HEAD's commit; `undefined` when HEAD has no commit yet */
|
|
34
|
+
readonly sha: string | undefined;
|
|
31
35
|
readonly upstream: WorktreeSummaryUpstream | undefined;
|
|
32
36
|
/** Commits ahead of the upstream; 0 without one */
|
|
33
37
|
readonly ahead: number;
|
|
@@ -37,10 +41,17 @@ export interface WorktreeSummary {
|
|
|
37
41
|
readonly dirty: boolean;
|
|
38
42
|
/** How far the branch is past its base; `undefined` when detached, unsupported, or there is no base */
|
|
39
43
|
readonly base: WorktreeSummaryBase | undefined;
|
|
44
|
+
/**
|
|
45
|
+
* Full ref names whose movement can change {@link base}, as `getAheadOfBase` reports them (see
|
|
46
|
+
* `AheadOfBase.dependsOn`) — given with or without a base, so a watcher also learns when one appears. Empty when
|
|
47
|
+
* the base isn't read (detached, or a provider without `getAheadOfBase`), and when the base read failed with no
|
|
48
|
+
* earlier list for this branch (the first read, or the one after a checkout).
|
|
49
|
+
*/
|
|
50
|
+
readonly baseDependsOn: readonly string[];
|
|
40
51
|
/**
|
|
41
52
|
* The latest recompute failed (worktree gone, lock contention); the other fields hold the last values read. Also set
|
|
42
|
-
* when only the base read failed: the other fields are fresh, and `base`
|
|
43
|
-
* branch changed.
|
|
53
|
+
* when only the base read failed: the other fields are fresh, and `base` and `baseDependsOn` are their last values,
|
|
54
|
+
* or `undefined` and empty if the branch changed.
|
|
44
55
|
*/
|
|
45
56
|
readonly stale: boolean;
|
|
46
57
|
}
|
|
@@ -74,13 +85,13 @@ export interface WorktreeSummaryServiceOptions {
|
|
|
74
85
|
* the service's own listening subscriptions never constrain it.
|
|
75
86
|
*
|
|
76
87
|
* **Field-scoped recompute.** The summary is read in three groups — `dirty` (working changes), `branch` (branch,
|
|
77
|
-
* upstream, ahead/behind) and `base` (ahead of base
|
|
78
|
-
* working-tree edit, an index or ignore-rule change reads only
|
|
79
|
-
* ref the base depends on reads only `base`; a `gk` config change
|
|
80
|
-
* `.git/gk/config`) re-reads the branch's stored merge targets and recorded
|
|
81
|
-
* them changed; the checked-out branch moving, or a HEAD change, reads all of
|
|
82
|
-
* and the like read nothing. One run is in flight per worktree at a
|
|
83
|
-
*
|
|
88
|
+
* HEAD's commit, upstream, ahead/behind) and `base` (ahead of base, the base's tip, and the refs it depends on) — and
|
|
89
|
+
* an event recomputes only the groups it can affect: a working-tree edit, an index or ignore-rule change reads only
|
|
90
|
+
* `dirty`; the upstream moving reads only `branch`; a ref the base depends on reads only `base`; a `gk` config change
|
|
91
|
+
* (mostly bookkeeping writes to the shared `.git/gk/config`) re-reads the branch's stored merge targets and recorded
|
|
92
|
+
* base, and reads `base` only when one of them changed; the checked-out branch moving, or a HEAD change, reads all of
|
|
93
|
+
* them. Stash, unrelated tags and branches, and the like read nothing. One run is in flight per worktree at a time;
|
|
94
|
+
* events during a run are merged into a single follow-up run.
|
|
84
95
|
*
|
|
85
96
|
* **Value-change-only notification.** {@link WorktreeSummarySubscription.onDidChange} and {@link onDidChangeSummary}
|
|
86
97
|
* fire only when some field's value differs from the previous summary — a recompute that reads the same answer is
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { areEqual } from "../../utils/array.js";
|
|
1
2
|
import { CancellationError, isCancellationError, raceWithSignal } from "../../utils/cancellation.js";
|
|
2
3
|
import { Logger as _logger } from "../../utils/logger.js";
|
|
3
4
|
import { Emitter } from "../../utils/event.js";
|
|
@@ -11,6 +12,7 @@ const baseField = 4;
|
|
|
11
12
|
const baseKeysField = 8;
|
|
12
13
|
/** Every group a full read covers — a base read refreshes the {@link BaseKeys} snapshot itself */
|
|
13
14
|
const allFields = 7;
|
|
15
|
+
const emptyRefs = Object.freeze([]);
|
|
14
16
|
var SummarySubscription = class {
|
|
15
17
|
entry;
|
|
16
18
|
options;
|
|
@@ -67,13 +69,13 @@ var SummarySubscription = class {
|
|
|
67
69
|
* the service's own listening subscriptions never constrain it.
|
|
68
70
|
*
|
|
69
71
|
* **Field-scoped recompute.** The summary is read in three groups — `dirty` (working changes), `branch` (branch,
|
|
70
|
-
* upstream, ahead/behind) and `base` (ahead of base
|
|
71
|
-
* working-tree edit, an index or ignore-rule change reads only
|
|
72
|
-
* ref the base depends on reads only `base`; a `gk` config change
|
|
73
|
-
* `.git/gk/config`) re-reads the branch's stored merge targets and recorded
|
|
74
|
-
* them changed; the checked-out branch moving, or a HEAD change, reads all of
|
|
75
|
-
* and the like read nothing. One run is in flight per worktree at a
|
|
76
|
-
*
|
|
72
|
+
* HEAD's commit, upstream, ahead/behind) and `base` (ahead of base, the base's tip, and the refs it depends on) — and
|
|
73
|
+
* an event recomputes only the groups it can affect: a working-tree edit, an index or ignore-rule change reads only
|
|
74
|
+
* `dirty`; the upstream moving reads only `branch`; a ref the base depends on reads only `base`; a `gk` config change
|
|
75
|
+
* (mostly bookkeeping writes to the shared `.git/gk/config`) re-reads the branch's stored merge targets and recorded
|
|
76
|
+
* base, and reads `base` only when one of them changed; the checked-out branch moving, or a HEAD change, reads all of
|
|
77
|
+
* them. Stash, unrelated tags and branches, and the like read nothing. One run is in flight per worktree at a time;
|
|
78
|
+
* events during a run are merged into a single follow-up run.
|
|
77
79
|
*
|
|
78
80
|
* **Value-change-only notification.** {@link WorktreeSummarySubscription.onDidChange} and {@link onDidChangeSummary}
|
|
79
81
|
* fire only when some field's value differs from the previous summary — a recompute that reads the same answer is
|
|
@@ -140,7 +142,7 @@ var WorktreeSummaryService = class {
|
|
|
140
142
|
if (entry == null) {
|
|
141
143
|
const repo = this.getRepository(key);
|
|
142
144
|
if (repo == null) throw new Error(`No repository found for '${key}'`);
|
|
143
|
-
return (await readSummary(repo, key, allFields, void 0, void 0,
|
|
145
|
+
return (await readSummary(repo, key, allFields, void 0, void 0, cancellation)).summary;
|
|
144
146
|
}
|
|
145
147
|
if (entry.running != null) await (cancellation != null ? raceWithSignal(entry.running, cancellation) : entry.running);
|
|
146
148
|
if (entry.disposed) throw new CancellationError();
|
|
@@ -198,8 +200,8 @@ var WorktreeSummaryService = class {
|
|
|
198
200
|
if (e.changed("gkConfig")) fields |= baseKeysField;
|
|
199
201
|
if (value.branch != null && e.mayHaveChangedRef(`refs/heads/${value.branch}`)) fields |= allFields;
|
|
200
202
|
if (value.upstream != null && (e.mayHaveChangedRef(`refs/remotes/${value.upstream.name}`) || e.mayHaveChangedRef(`refs/heads/${value.upstream.name}`))) fields |= branchField;
|
|
201
|
-
if (
|
|
202
|
-
for (const ref of
|
|
203
|
+
if ((fields & baseField) === 0) {
|
|
204
|
+
for (const ref of value.baseDependsOn) if (e.mayHaveChangedRef(ref)) {
|
|
203
205
|
fields |= baseField;
|
|
204
206
|
break;
|
|
205
207
|
}
|
|
@@ -226,7 +228,7 @@ var WorktreeSummaryService = class {
|
|
|
226
228
|
try {
|
|
227
229
|
const repo = this.getRepository(entry.key);
|
|
228
230
|
if (repo == null) throw new Error(`No repository found for '${entry.key}'`);
|
|
229
|
-
result = await readSummary(repo, entry.key, fields, previous, entry.
|
|
231
|
+
result = await readSummary(repo, entry.key, fields, previous, entry.baseKeys, entry.abort.signal);
|
|
230
232
|
} catch (ex) {
|
|
231
233
|
if (entry.disposed || isCancellationError(ex)) return;
|
|
232
234
|
this.fail(entry, ex);
|
|
@@ -235,7 +237,6 @@ var WorktreeSummaryService = class {
|
|
|
235
237
|
if (entry.disposed) return;
|
|
236
238
|
if (result.summary.stale && (previous == null || !previous.stale)) _logger.warn(`WorktreeSummaryService: reading the base of '${entry.key}' failed: ${String(result.baseError)}`);
|
|
237
239
|
entry.error = void 0;
|
|
238
|
-
entry.dependsOn = result.dependsOn;
|
|
239
240
|
entry.baseKeys = result.baseKeys;
|
|
240
241
|
if (previous != null && summariesEqual(previous, result.summary)) return;
|
|
241
242
|
entry.value = result.summary;
|
|
@@ -273,7 +274,6 @@ var WorktreeSummaryService = class {
|
|
|
273
274
|
entry.handle?.dispose();
|
|
274
275
|
entry.handle = void 0;
|
|
275
276
|
entry.value = void 0;
|
|
276
|
-
entry.dependsOn = void 0;
|
|
277
277
|
entry.baseKeys = void 0;
|
|
278
278
|
entry.pending = 0;
|
|
279
279
|
entry.running = void 0;
|
|
@@ -288,7 +288,6 @@ function createEntry(key) {
|
|
|
288
288
|
handle: void 0,
|
|
289
289
|
listeners: void 0,
|
|
290
290
|
value: void 0,
|
|
291
|
-
dependsOn: void 0,
|
|
292
291
|
baseKeys: void 0,
|
|
293
292
|
error: void 0,
|
|
294
293
|
pending: 0,
|
|
@@ -301,13 +300,14 @@ function createEntry(key) {
|
|
|
301
300
|
* before the base read, which needs its name; the dirty read runs alongside both. A branch read that finds a
|
|
302
301
|
* different branch (or detached state) also reads the base and dirty groups, and stored base values that differ
|
|
303
302
|
* from `previousBaseKeys` read the base group. A failed base read doesn't fail the rest: the summary comes back stale,
|
|
304
|
-
* keeping the previous base and `
|
|
303
|
+
* keeping the previous base and `baseDependsOn` only while the branch is the same.
|
|
305
304
|
*/
|
|
306
|
-
async function readSummary(repo, key, fields, previous,
|
|
305
|
+
async function readSummary(repo, key, fields, previous, previousBaseKeys, signal) {
|
|
307
306
|
if (previous == null) fields = allFields;
|
|
308
307
|
let dirtyRead = fields & dirtyField ? readDirty(repo, signal) : void 0;
|
|
309
308
|
let branch = previous?.branch;
|
|
310
309
|
let detached = previous?.detached ?? false;
|
|
310
|
+
let sha = previous?.sha;
|
|
311
311
|
let upstream = previous?.upstream;
|
|
312
312
|
let ahead = previous?.ahead ?? 0;
|
|
313
313
|
let behind = previous?.behind ?? 0;
|
|
@@ -315,6 +315,7 @@ async function readSummary(repo, key, fields, previous, previousDependsOn, previ
|
|
|
315
315
|
const current = await repo.branches.getBranch(void 0, void 0, signal);
|
|
316
316
|
detached = current?.detached ?? false;
|
|
317
317
|
branch = detached ? void 0 : current?.name || void 0;
|
|
318
|
+
sha = current?.sha;
|
|
318
319
|
upstream = current?.upstream != null ? await toSummaryUpstream(repo, current.upstream, signal) : void 0;
|
|
319
320
|
ahead = current?.upstream?.state.ahead ?? 0;
|
|
320
321
|
behind = current?.upstream?.state.behind ?? 0;
|
|
@@ -324,7 +325,7 @@ async function readSummary(repo, key, fields, previous, previousDependsOn, previ
|
|
|
324
325
|
}
|
|
325
326
|
}
|
|
326
327
|
let base = previous?.base;
|
|
327
|
-
let dependsOn =
|
|
328
|
+
let dependsOn = previous?.baseDependsOn ?? emptyRefs;
|
|
328
329
|
let baseKeys = previousBaseKeys;
|
|
329
330
|
let baseKeysRead = false;
|
|
330
331
|
let baseFailed = false;
|
|
@@ -338,21 +339,21 @@ async function readSummary(repo, key, fields, previous, previousDependsOn, previ
|
|
|
338
339
|
if (fields & baseField) {
|
|
339
340
|
if (baseBranch == null) {
|
|
340
341
|
base = void 0;
|
|
341
|
-
dependsOn =
|
|
342
|
+
dependsOn = emptyRefs;
|
|
342
343
|
baseKeys = void 0;
|
|
343
344
|
} else {
|
|
344
345
|
if (!baseKeysRead) baseKeys = await readBaseKeys(repo, baseBranch);
|
|
345
346
|
try {
|
|
346
347
|
const aheadOfBase = await repo.branches.getAheadOfBase?.(baseBranch, signal);
|
|
347
348
|
base = aheadOfBase?.base;
|
|
348
|
-
dependsOn = aheadOfBase?.dependsOn;
|
|
349
|
+
dependsOn = aheadOfBase?.dependsOn ?? emptyRefs;
|
|
349
350
|
} catch (ex) {
|
|
350
351
|
if (isCancellationError(ex)) throw ex;
|
|
351
352
|
baseFailed = true;
|
|
352
353
|
baseError = ex;
|
|
353
354
|
const sameBranch = previous != null && branch === previous.branch && detached === previous.detached;
|
|
354
355
|
base = sameBranch ? previous.base : void 0;
|
|
355
|
-
dependsOn = sameBranch ?
|
|
356
|
+
dependsOn = sameBranch ? previous.baseDependsOn : emptyRefs;
|
|
356
357
|
baseKeys = void 0;
|
|
357
358
|
}
|
|
358
359
|
}
|
|
@@ -363,14 +364,15 @@ async function readSummary(repo, key, fields, previous, previousDependsOn, previ
|
|
|
363
364
|
repoPath: key,
|
|
364
365
|
branch,
|
|
365
366
|
detached,
|
|
367
|
+
sha,
|
|
366
368
|
upstream,
|
|
367
369
|
ahead,
|
|
368
370
|
behind,
|
|
369
371
|
dirty,
|
|
370
372
|
base,
|
|
373
|
+
baseDependsOn: dependsOn,
|
|
371
374
|
stale: baseFailed
|
|
372
375
|
},
|
|
373
|
-
dependsOn,
|
|
374
376
|
baseKeys,
|
|
375
377
|
baseError
|
|
376
378
|
};
|
|
@@ -419,7 +421,7 @@ async function toSummaryUpstream(repo, upstream, signal) {
|
|
|
419
421
|
};
|
|
420
422
|
}
|
|
421
423
|
function summariesEqual(a, b) {
|
|
422
|
-
return a.repoPath === b.repoPath && a.branch === b.branch && a.detached === b.detached && a.ahead === b.ahead && a.behind === b.behind && a.dirty === b.dirty && a.stale === b.stale && (a.upstream === b.upstream || a.upstream != null && b.upstream != null && a.upstream.name === b.upstream.name && a.upstream.remote === b.upstream.remote && a.upstream.branch === b.upstream.branch && a.upstream.missing === b.upstream.missing) && (a.base === b.base || a.base != null && b.base != null && a.base.ref === b.base.ref && a.base.ahead === b.base.ahead);
|
|
424
|
+
return a.repoPath === b.repoPath && a.branch === b.branch && a.detached === b.detached && a.sha === b.sha && a.ahead === b.ahead && a.behind === b.behind && a.dirty === b.dirty && a.stale === b.stale && areEqual(a.baseDependsOn, b.baseDependsOn) && (a.upstream === b.upstream || a.upstream != null && b.upstream != null && a.upstream.name === b.upstream.name && a.upstream.remote === b.upstream.remote && a.upstream.branch === b.upstream.branch && a.upstream.missing === b.upstream.missing) && (a.base === b.base || a.base != null && b.base != null && a.base.ref === b.base.ref && a.base.ahead === b.base.ahead && a.base.sha === b.base.sha);
|
|
423
425
|
}
|
|
424
426
|
//#endregion
|
|
425
427
|
export { WorktreeSummaryService };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"worktreeSummary.js","names":[],"sources":["../../../../git/src/watching/worktreeSummary.ts"],"sourcesContent":["import { CancellationError, isCancellationError, raceWithSignal } from '@gitlens/utils/cancellation.js';\nimport type { UnifiedDisposable } from '@gitlens/utils/disposable.js';\nimport type { Event } from '@gitlens/utils/event.js';\nimport { Emitter } from '@gitlens/utils/event.js';\nimport { Logger } from '@gitlens/utils/logger.js';\nimport type { ResourceUsage } from '@gitlens/utils/resourceUsage.js';\nimport { getRepositoryKey } from '@gitlens/utils/uri.js';\nimport type { GitTrackingUpstream } from '../models/branch.js';\nimport type { GitDir } from '../models/repository.js';\nimport type { RepositoryService } from '../repositoryService.js';\nimport type { WatcherRepoChangeEvent } from './changeEvent.js';\nimport type { RepositoryWatchService, WatchHandle } from './watchService.js';\nimport type { RepositoryWatchSession } from './watchSession.js';\n\nexport interface WorktreeSummaryUpstream {\n\t/** The upstream's short name (`origin/main`) */\n\treadonly name: string;\n\t/**\n\t * The remote it tracks: the longest of the repository's remote names that {@link name} starts with, followed by\n\t * `/` (a remote name can hold a slash). `undefined`, with {@link branch}, when it names no known remote (a branch\n\t * tracking another local branch, or a remote since removed)\n\t */\n\treadonly remote: string | undefined;\n\t/** The branch on {@link remote}; `undefined` whenever {@link remote} is */\n\treadonly branch: string | undefined;\n\treadonly missing: boolean;\n}\n\nexport interface WorktreeSummaryBase {\n\t/** The ref the count was taken against (see `AheadOfBase.base`) */\n\treadonly ref: string;\n\treadonly ahead: number;\n}\n\nexport interface WorktreeSummary {\n\t/** The repository path in {@link getRepositoryKey} form */\n\treadonly repoPath: string;\n\t/** The checked-out branch; `undefined` when detached (or unborn with no name) */\n\treadonly branch: string | undefined;\n\treadonly detached: boolean;\n\treadonly upstream: WorktreeSummaryUpstream | undefined;\n\t/** Commits ahead of the upstream; 0 without one */\n\treadonly ahead: number;\n\t/** Commits behind the upstream; 0 without one */\n\treadonly behind: number;\n\t/** Whether there is any staged, unstaged or untracked (non-ignored) change */\n\treadonly dirty: boolean;\n\t/** How far the branch is past its base; `undefined` when detached, unsupported, or there is no base */\n\treadonly base: WorktreeSummaryBase | undefined;\n\t/**\n\t * The latest recompute failed (worktree gone, lock contention); the other fields hold the last values read. Also set\n\t * when only the base read failed: the other fields are fresh, and `base` is its last value, or `undefined` if the\n\t * branch changed.\n\t */\n\treadonly stale: boolean;\n}\n\nexport interface WorktreeSummarySubscribeOptions {\n\t/** Debounce for repo (`.git`) changes this subscriber asks of the watch session; the shortest across subscribers wins */\n\treadonly delayMs?: number;\n\t/** Cap on how long continuous repo activity can defer a recompute; `Infinity` means no cap */\n\treadonly maxWaitMs?: number;\n\t/** Debounce for working-tree changes this subscriber asks of the watch session */\n\treadonly workingTreeDelayMs?: number;\n\t/** Cap on how long continuous working-tree activity can defer a recompute; `Infinity` means no cap */\n\treadonly workingTreeMaxWaitMs?: number;\n}\n\nexport interface WorktreeSummarySubscription extends UnifiedDisposable {\n\t/** The latest summary; `undefined` until the first read succeeds, or after disposal */\n\treadonly current: WorktreeSummary | undefined;\n\t/** Fires when the summary's value changes */\n\treadonly onDidChange: Event<WorktreeSummary>;\n}\n\nexport interface WorktreeSummaryServiceOptions {\n\treadonly watchService: RepositoryWatchService;\n\treadonly getRepository: (repoPath: string) => RepositoryService | undefined;\n}\n\n/** Read groups, as bit flags so a pending set is a number rather than an allocation per event */\nconst dirtyField = 1;\nconst branchField = 2;\nconst baseField = 4;\n/** Re-reads only the stored values the base read consults, and the base itself only when they changed */\nconst baseKeysField = 8;\n/** Every group a full read covers — a base read refreshes the {@link BaseKeys} snapshot itself */\nconst allFields = dirtyField | branchField | baseField;\n\n/** The current branch's stored merge targets (user, detected) and recorded base, as `getAheadOfBase` consults them */\ntype BaseKeys = readonly [string | undefined, string | undefined, string | undefined];\n\ninterface ReadResult {\n\treadonly summary: WorktreeSummary;\n\treadonly dependsOn: readonly string[] | undefined;\n\treadonly baseKeys: BaseKeys | undefined;\n\t/** Why the base read failed when the rest of the summary was read; the summary is then stale */\n\treadonly baseError: unknown;\n}\n\ninterface SummaryEntry {\n\treadonly key: string;\n\t/** Aborted when the last subscriber leaves, cancelling any in-flight read */\n\treadonly abort: AbortController;\n\treadonly subscribers: Set<SummarySubscription>;\n\thandle: WatchHandle | undefined;\n\t/** The timing-neutral listening subscriptions and their event listeners */\n\tlisteners: UnifiedDisposable[] | undefined;\n\tvalue: WorktreeSummary | undefined;\n\t/** Refs whose movement can change {@link WorktreeSummary.base} */\n\tdependsOn: readonly string[] | undefined;\n\t/** The stored base values as of the last base read; a `gk` config change re-reads the base only when they differ */\n\tbaseKeys: BaseKeys | undefined;\n\t/** Why the latest read failed while there was no value to keep */\n\terror: Error | undefined;\n\t/** Read groups due on the next run */\n\tpending: number;\n\t/** The active run (or the start-up, which ends in one); cleared in the same turn the loop sees nothing pending */\n\trunning: Promise<void> | undefined;\n\tdisposed: boolean;\n}\n\nclass SummarySubscription implements WorktreeSummarySubscription {\n\tprivate readonly emitter = new Emitter<WorktreeSummary>();\n\t/** Listener-less session subscriptions that carry this subscriber's timing into the session */\n\tprivate timing: UnifiedDisposable[] | undefined;\n\tprivate disposed = false;\n\n\tconstructor(\n\t\tprivate readonly entry: SummaryEntry,\n\t\tprivate readonly options: WorktreeSummarySubscribeOptions | undefined,\n\t\tprivate readonly onDispose: (subscription: SummarySubscription) => void,\n\t) {}\n\n\tget current(): WorktreeSummary | undefined {\n\t\treturn this.disposed ? undefined : this.entry.value;\n\t}\n\n\tget onDidChange(): Event<WorktreeSummary> {\n\t\treturn this.emitter.event;\n\t}\n\n\t[Symbol.dispose](): void {\n\t\tthis.dispose();\n\t}\n\n\tdispose(): void {\n\t\tif (this.disposed) return;\n\n\t\tthis.disposed = true;\n\t\tif (this.timing != null) {\n\t\t\tfor (const d of this.timing) {\n\t\t\t\td.dispose();\n\t\t\t}\n\t\t\tthis.timing = undefined;\n\t\t}\n\t\tthis.emitter.dispose();\n\t\tthis.onDispose(this);\n\t}\n\n\tattach(session: RepositoryWatchSession): void {\n\t\tif (this.disposed || this.timing != null) return;\n\n\t\tthis.timing = [\n\t\t\tsession.subscribe({ delayMs: this.options?.delayMs, maxWaitMs: this.options?.maxWaitMs }),\n\t\t\tsession.subscribeToWorkingTree({\n\t\t\t\tdelayMs: this.options?.workingTreeDelayMs,\n\t\t\t\tmaxWaitMs: this.options?.workingTreeMaxWaitMs,\n\t\t\t}),\n\t\t];\n\t}\n\n\tfire(summary: WorktreeSummary): void {\n\t\tthis.emitter.fire(summary);\n\t}\n}\n\n/**\n * A cached, change-notifying summary of each subscribed worktree — its branch, upstream and ahead/behind, whether it\n * has working changes, and how far it is past its base — kept current by the repository watch layer.\n *\n * **Lifecycle.** Entries are per repository key and ref-counted across {@link subscribe} calls. The first subscriber\n * starts watching the worktree and reads everything; the last one to leave aborts any in-flight read, releases the\n * watch and drops the value. Each subscriber's timing options join the watch session's cadence (the shortest wins);\n * the service's own listening subscriptions never constrain it.\n *\n * **Field-scoped recompute.** The summary is read in three groups — `dirty` (working changes), `branch` (branch,\n * upstream, ahead/behind) and `base` (ahead of base) — and an event recomputes only the groups it can affect: a\n * working-tree edit, an index or ignore-rule change reads only `dirty`; the upstream moving reads only `branch`; a\n * ref the base depends on reads only `base`; a `gk` config change (mostly bookkeeping writes to the shared\n * `.git/gk/config`) re-reads the branch's stored merge targets and recorded base, and reads `base` only when one of\n * them changed; the checked-out branch moving, or a HEAD change, reads all of them. Tags, stash, unrelated branches\n * and the like read nothing. One run is in flight per worktree at a\n * time; events during a run are merged into a single follow-up run.\n *\n * **Value-change-only notification.** {@link WorktreeSummarySubscription.onDidChange} and {@link onDidChangeSummary}\n * fire only when some field's value differs from the previous summary — a recompute that reads the same answer is\n * silent, so a consumer can re-render on every event without diffing. A failed recompute keeps the last values and\n * sets `stale` (firing once); the next successful one clears it.\n */\nexport class WorktreeSummaryService implements UnifiedDisposable {\n\tprivate readonly _onDidChangeSummary = new Emitter<WorktreeSummary>();\n\t/** Fires when the summary of any subscribed worktree changes value */\n\tget onDidChangeSummary(): Event<WorktreeSummary> {\n\t\treturn this._onDidChangeSummary.event;\n\t}\n\n\tprivate readonly entries = new Map<string, SummaryEntry>();\n\tprivate readonly watchService: RepositoryWatchService;\n\tprivate readonly getRepository: (repoPath: string) => RepositoryService | undefined;\n\tprivate _disposed = false;\n\n\tconstructor(options: WorktreeSummaryServiceOptions) {\n\t\tthis.watchService = options.watchService;\n\t\tthis.getRepository = options.getRepository;\n\t}\n\n\t[Symbol.dispose](): void {\n\t\tthis.dispose();\n\t}\n\n\tdispose(): void {\n\t\tif (this._disposed) return;\n\n\t\tthis._disposed = true;\n\t\tfor (const entry of [...this.entries.values()]) {\n\t\t\tfor (const subscription of [...entry.subscribers]) {\n\t\t\t\tsubscription.dispose();\n\t\t\t}\n\t\t}\n\t\tthis.entries.clear();\n\t\tthis._onDidChangeSummary.dispose();\n\t}\n\n\t/** Subscribes to a worktree's summary, starting to watch and read it if this is its first subscriber */\n\tsubscribe(repoPath: string, options?: WorktreeSummarySubscribeOptions): WorktreeSummarySubscription {\n\t\tconst key = getRepositoryKey(repoPath);\n\n\t\tlet entry = this._disposed ? undefined : this.entries.get(key);\n\t\tconst isNew = entry == null;\n\t\tif (entry == null) {\n\t\t\tentry = createEntry(key);\n\t\t\tif (this._disposed) {\n\t\t\t\tentry.disposed = true;\n\t\t\t} else {\n\t\t\t\tthis.entries.set(key, entry);\n\t\t\t}\n\t\t}\n\n\t\tconst target = entry;\n\t\tconst subscription = new SummarySubscription(target, options, s => this.release(target, s));\n\t\ttarget.subscribers.add(subscription);\n\n\t\tif (isNew) {\n\t\t\tif (!target.disposed) {\n\t\t\t\ttarget.pending = allFields;\n\t\t\t\ttarget.running = this.start(target);\n\t\t\t}\n\t\t} else if (target.handle != null) {\n\t\t\tsubscription.attach(target.handle.session);\n\t\t} else if (target.running == null && !target.disposed) {\n\t\t\t// The start failed (no repository or git directory yet); a new subscriber retries it\n\t\t\ttarget.pending = allFields;\n\t\t\ttarget.running = this.start(target);\n\t\t}\n\n\t\treturn subscription;\n\t}\n\n\t/**\n\t * Returns a worktree's summary. For a subscribed worktree, the cached value — after waiting for any run in flight\n\t * or pending; it rejects with the read's error when there is still no value. Otherwise a one-off full read that\n\t * is neither cached nor watched.\n\t */\n\tasync getSummary(repoPath: string, cancellation?: AbortSignal): Promise<WorktreeSummary> {\n\t\tconst key = getRepositoryKey(repoPath);\n\t\tconst entry = this.entries.get(key);\n\t\tif (entry == null) {\n\t\t\tconst repo = this.getRepository(key);\n\t\t\tif (repo == null) throw new Error(`No repository found for '${key}'`);\n\n\t\t\tconst result = await readSummary(repo, key, allFields, undefined, undefined, undefined, cancellation);\n\t\t\treturn result.summary;\n\t\t}\n\n\t\tif (entry.running != null) {\n\t\t\tawait (cancellation != null ? raceWithSignal(entry.running, cancellation) : entry.running);\n\t\t}\n\t\tif (entry.disposed) throw new CancellationError();\n\t\tif (entry.value != null) return entry.value;\n\n\t\tthrow entry.error ?? new Error(`No summary available for '${key}'`);\n\t}\n\n\tgetResourceUsage(): ResourceUsage {\n\t\treturn { 'worktreeSummary.entries.count': this.entries.size };\n\t}\n\n\tprivate async start(entry: SummaryEntry): Promise<void> {\n\t\tlet gitDir: GitDir | undefined;\n\t\ttry {\n\t\t\tgitDir = await this.getRepository(entry.key)?.config.getGitDir?.();\n\t\t} catch (ex) {\n\t\t\tif (entry.disposed) return;\n\n\t\t\tentry.running = undefined;\n\t\t\tif (!isCancellationError(ex)) {\n\t\t\t\tthis.fail(entry, ex);\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\t\tif (entry.disposed) return;\n\n\t\tconst handle = gitDir != null ? this.watchService.watch(entry.key, gitDir) : undefined;\n\t\tif (handle == null) {\n\t\t\tentry.running = undefined;\n\t\t\tthis.fail(entry, new Error(`Unable to watch '${entry.key}': no repository or git directory`));\n\t\t\treturn;\n\t\t}\n\n\t\tentry.handle = handle;\n\t\tconst session = handle.session;\n\t\t// Timing-neutral: the cadence comes from the subscribers' own session subscriptions (see `attach`)\n\t\tconst repoSub = session.subscribe({ delayMs: Infinity, maxWaitMs: Infinity });\n\t\tconst workingTreeSub = session.subscribeToWorkingTree({ delayMs: Infinity, maxWaitMs: Infinity });\n\t\tentry.listeners = [\n\t\t\trepoSub.onDidChange(e => this.onRepoChanged(entry, e)),\n\t\t\tworkingTreeSub.onDidChangeWorkingTree(() => this.queue(entry, dirtyField)),\n\t\t\trepoSub,\n\t\t\tworkingTreeSub,\n\t\t];\n\t\tfor (const subscription of entry.subscribers) {\n\t\t\tsubscription.attach(session);\n\t\t}\n\n\t\tawait this.drain(entry);\n\t}\n\n\tprivate onRepoChanged(entry: SummaryEntry, e: WatcherRepoChangeEvent): void {\n\t\tconst value = entry.value;\n\t\tif (value == null || e.changed('unknown', 'head')) {\n\t\t\tthis.queue(entry, allFields);\n\t\t\treturn;\n\t\t}\n\n\t\tlet fields = 0;\n\t\tif (e.changed('index', 'ignores')) {\n\t\t\tfields |= dirtyField;\n\t\t}\n\t\tif (e.changed('config')) {\n\t\t\tfields |= branchField | baseField;\n\t\t}\n\t\tif (e.changed('gkConfig')) {\n\t\t\tfields |= baseKeysField;\n\t\t}\n\t\tif (value.branch != null && e.mayHaveChangedRef(`refs/heads/${value.branch}`)) {\n\t\t\tfields |= allFields;\n\t\t}\n\t\t// The upstream's short name doesn't say whether it's remote or local (`--track main`), so test both forms\n\t\tif (\n\t\t\tvalue.upstream != null &&\n\t\t\t(e.mayHaveChangedRef(`refs/remotes/${value.upstream.name}`) ||\n\t\t\t\te.mayHaveChangedRef(`refs/heads/${value.upstream.name}`))\n\t\t) {\n\t\t\tfields |= branchField;\n\t\t}\n\t\tif (entry.dependsOn != null && (fields & baseField) === 0) {\n\t\t\tfor (const ref of entry.dependsOn) {\n\t\t\t\tif (e.mayHaveChangedRef(ref)) {\n\t\t\t\t\tfields |= baseField;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tthis.queue(entry, fields);\n\t}\n\n\tprivate queue(entry: SummaryEntry, fields: number): void {\n\t\tif (entry.disposed || fields === 0) return;\n\n\t\t// Without a value a partial read has nothing to merge into, and a stale value's fields can't be trusted: a\n\t\t// partial read would clear `stale` while vouching for groups it never re-read\n\t\tentry.pending |= entry.value == null || entry.value.stale ? allFields : fields;\n\t\tentry.running ??= this.drain(entry);\n\t}\n\n\t/** Runs until nothing is pending. Never rejects — every failure becomes entry state. */\n\tprivate async drain(entry: SummaryEntry): Promise<void> {\n\t\twhile (entry.pending !== 0 && !entry.disposed) {\n\t\t\tconst fields = entry.pending;\n\t\t\tentry.pending = 0;\n\t\t\tawait this.run(entry, fields);\n\t\t}\n\t\t// Same turn as the loop's last check, so an event can't queue fields that no run will pick up\n\t\tentry.running = undefined;\n\t}\n\n\tprivate async run(entry: SummaryEntry, fields: number): Promise<void> {\n\t\tconst previous = entry.value;\n\n\t\tlet result: ReadResult;\n\t\ttry {\n\t\t\tconst repo = this.getRepository(entry.key);\n\t\t\tif (repo == null) throw new Error(`No repository found for '${entry.key}'`);\n\n\t\t\tresult = await readSummary(\n\t\t\t\trepo,\n\t\t\t\tentry.key,\n\t\t\t\tfields,\n\t\t\t\tprevious,\n\t\t\t\tentry.dependsOn,\n\t\t\t\tentry.baseKeys,\n\t\t\t\tentry.abort.signal,\n\t\t\t);\n\t\t} catch (ex) {\n\t\t\tif (entry.disposed || isCancellationError(ex)) return;\n\n\t\t\tthis.fail(entry, ex);\n\t\t\treturn;\n\t\t}\n\t\tif (entry.disposed) return;\n\n\t\tif (result.summary.stale && (previous == null || !previous.stale)) {\n\t\t\tLogger.warn(\n\t\t\t\t`WorktreeSummaryService: reading the base of '${entry.key}' failed: ${String(result.baseError)}`,\n\t\t\t);\n\t\t}\n\n\t\tentry.error = undefined;\n\t\tentry.dependsOn = result.dependsOn;\n\t\tentry.baseKeys = result.baseKeys;\n\t\tif (previous != null && summariesEqual(previous, result.summary)) return;\n\n\t\tentry.value = result.summary;\n\t\tthis.fireChange(entry, result.summary);\n\t}\n\n\tprivate fail(entry: SummaryEntry, ex: unknown): void {\n\t\tconst previous = entry.value;\n\t\tif (previous == null) {\n\t\t\tif (entry.error == null) {\n\t\t\t\tLogger.warn(`WorktreeSummaryService: reading '${entry.key}' failed: ${String(ex)}`);\n\t\t\t}\n\t\t\tentry.error = ex instanceof Error ? ex : new Error(String(ex));\n\t\t\treturn;\n\t\t}\n\t\tif (previous.stale) return;\n\n\t\tLogger.warn(`WorktreeSummaryService: refreshing '${entry.key}' failed: ${String(ex)}`);\n\t\tconst summary: WorktreeSummary = { ...previous, stale: true };\n\t\tentry.value = summary;\n\t\tthis.fireChange(entry, summary);\n\t}\n\n\tprivate fireChange(entry: SummaryEntry, summary: WorktreeSummary): void {\n\t\tfor (const subscription of entry.subscribers) {\n\t\t\tsubscription.fire(summary);\n\t\t}\n\t\tthis._onDidChangeSummary.fire(summary);\n\t}\n\n\tprivate release(entry: SummaryEntry, subscription: SummarySubscription): void {\n\t\tentry.subscribers.delete(subscription);\n\t\tif (entry.subscribers.size !== 0) return;\n\n\t\tentry.disposed = true;\n\t\tentry.abort.abort();\n\t\tif (entry.listeners != null) {\n\t\t\tfor (const d of entry.listeners) {\n\t\t\t\td.dispose();\n\t\t\t}\n\t\t\tentry.listeners = undefined;\n\t\t}\n\t\tentry.handle?.dispose();\n\t\tentry.handle = undefined;\n\t\tentry.value = undefined;\n\t\tentry.dependsOn = undefined;\n\t\tentry.baseKeys = undefined;\n\t\tentry.pending = 0;\n\t\tentry.running = undefined;\n\n\t\tif (this.entries.get(entry.key) === entry) {\n\t\t\tthis.entries.delete(entry.key);\n\t\t}\n\t}\n}\n\nfunction createEntry(key: string): SummaryEntry {\n\treturn {\n\t\tkey: key,\n\t\tabort: new AbortController(),\n\t\tsubscribers: new Set(),\n\t\thandle: undefined,\n\t\tlisteners: undefined,\n\t\tvalue: undefined,\n\t\tdependsOn: undefined,\n\t\tbaseKeys: undefined,\n\t\terror: undefined,\n\t\tpending: 0,\n\t\trunning: undefined,\n\t\tdisposed: false,\n\t};\n}\n\n/**\n * Reads the due groups and fills the rest from `previous` (every group when there is none). The branch read comes\n * before the base read, which needs its name; the dirty read runs alongside both. A branch read that finds a\n * different branch (or detached state) also reads the base and dirty groups, and stored base values that differ\n * from `previousBaseKeys` read the base group. A failed base read doesn't fail the rest: the summary comes back stale,\n * keeping the previous base and `dependsOn` only while the branch is the same.\n */\nasync function readSummary(\n\trepo: RepositoryService,\n\tkey: string,\n\tfields: number,\n\tprevious: WorktreeSummary | undefined,\n\tpreviousDependsOn: readonly string[] | undefined,\n\tpreviousBaseKeys: BaseKeys | undefined,\n\tsignal: AbortSignal | undefined,\n): Promise<ReadResult> {\n\tif (previous == null) {\n\t\tfields = allFields;\n\t}\n\n\tlet dirtyRead = fields & dirtyField ? readDirty(repo, signal) : undefined;\n\n\tlet branch = previous?.branch;\n\tlet detached = previous?.detached ?? false;\n\tlet upstream = previous?.upstream;\n\tlet ahead = previous?.ahead ?? 0;\n\tlet behind = previous?.behind ?? 0;\n\tif (fields & branchField) {\n\t\tconst current = await repo.branches.getBranch(undefined, undefined, signal);\n\t\tdetached = current?.detached ?? false;\n\t\tbranch = detached ? undefined : current?.name || undefined;\n\t\tupstream = current?.upstream != null ? await toSummaryUpstream(repo, current.upstream, signal) : undefined;\n\t\tahead = current?.upstream?.state.ahead ?? 0;\n\t\tbehind = current?.upstream?.state.behind ?? 0;\n\n\t\tif (previous != null && (branch !== previous.branch || detached !== previous.detached)) {\n\t\t\tfields |= baseField;\n\t\t\tdirtyRead ??= readDirty(repo, signal);\n\t\t}\n\t}\n\n\tlet base = previous?.base;\n\tlet dependsOn = previousDependsOn;\n\tlet baseKeys = previousBaseKeys;\n\tlet baseKeysRead = false;\n\tlet baseFailed = false;\n\tlet baseError: unknown;\n\tconst baseBranch = !detached && repo.branches.getAheadOfBase != null ? branch : undefined;\n\tif (fields & baseKeysField && !(fields & baseField) && baseBranch != null) {\n\t\tbaseKeys = await readBaseKeys(repo, baseBranch);\n\t\tbaseKeysRead = true;\n\t\tif (baseKeys == null || previousBaseKeys == null || !baseKeysEqual(baseKeys, previousBaseKeys)) {\n\t\t\tfields |= baseField;\n\t\t}\n\t}\n\tif (fields & baseField) {\n\t\tif (baseBranch == null) {\n\t\t\tbase = undefined;\n\t\t\tdependsOn = undefined;\n\t\t\tbaseKeys = undefined;\n\t\t} else {\n\t\t\t// Snapshot before the base read, so a write it misses is one the next `gk` config event sees as a change\n\t\t\tif (!baseKeysRead) {\n\t\t\t\tbaseKeys = await readBaseKeys(repo, baseBranch);\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tconst aheadOfBase = await repo.branches.getAheadOfBase?.(baseBranch, signal);\n\t\t\t\tbase = aheadOfBase?.base;\n\t\t\t\tdependsOn = aheadOfBase?.dependsOn;\n\t\t\t} catch (ex) {\n\t\t\t\tif (isCancellationError(ex)) throw ex;\n\n\t\t\t\tbaseFailed = true;\n\t\t\t\tbaseError = ex;\n\t\t\t\t// Keep `dependsOn` for the same branch: a fetch moving one of its refs is what retries the base\n\t\t\t\tconst sameBranch = previous != null && branch === previous.branch && detached === previous.detached;\n\t\t\t\tbase = sameBranch ? previous.base : undefined;\n\t\t\t\tdependsOn = sameBranch ? previousDependsOn : undefined;\n\t\t\t\tbaseKeys = undefined;\n\t\t\t}\n\t\t}\n\t}\n\n\tconst dirty = dirtyRead != null ? await dirtyRead : (previous?.dirty ?? false);\n\n\treturn {\n\t\tsummary: {\n\t\t\trepoPath: key,\n\t\t\tbranch: branch,\n\t\t\tdetached: detached,\n\t\t\tupstream: upstream,\n\t\t\tahead: ahead,\n\t\t\tbehind: behind,\n\t\t\tdirty: dirty,\n\t\t\tbase: base,\n\t\t\tstale: baseFailed,\n\t\t},\n\t\tdependsOn: dependsOn,\n\t\tbaseKeys: baseKeys,\n\t\tbaseError: baseError,\n\t};\n}\n\n/**\n * Reads {@link BaseKeys} for `branch`, normalized the way `getAheadOfBase` reads them. `undefined` when any read\n * failed — an unknown snapshot, so the next `gk` config change reads the base rather than trusting it.\n */\nasync function readBaseKeys(repo: RepositoryService, branch: string): Promise<BaseKeys | undefined> {\n\ttry {\n\t\tconst [user, detected, recorded] = await Promise.all([\n\t\t\trepo.branches.getStoredUserMergeTargetBranchName?.(branch),\n\t\t\trepo.branches.getStoredDetectedMergeTargetBranchName?.(branch),\n\t\t\trepo.config.getGkConfig?.(`branch.${branch}.gk-merge-base`),\n\t\t]);\n\t\treturn [user?.trim() || undefined, detected?.trim() || undefined, recorded?.trim() || undefined];\n\t} catch (ex) {\n\t\tif (isCancellationError(ex)) throw ex;\n\n\t\treturn undefined;\n\t}\n}\n\nfunction baseKeysEqual(a: BaseKeys, b: BaseKeys): boolean {\n\treturn a[0] === b[0] && a[1] === b[1] && a[2] === b[2];\n}\n\nfunction readDirty(repo: RepositoryService, signal: AbortSignal | undefined): Promise<boolean> {\n\tconst read = repo.status.hasWorkingChanges({ untracked: true }, signal);\n\t// Awaited only after the branch and base reads; if one of those throws first, this rejection has no other handler\n\tread.catch(noop);\n\treturn read;\n}\n\nfunction noop(): void {}\n\n/** Splits the upstream at the longest remote name it starts with; no split when none matches or nothing follows it */\nasync function toSummaryUpstream(\n\trepo: RepositoryService,\n\tupstream: GitTrackingUpstream,\n\tsignal: AbortSignal | undefined,\n): Promise<WorktreeSummaryUpstream> {\n\tconst name = upstream.name;\n\n\tlet remote: string | undefined;\n\tfor (const r of await repo.remotes.getRemotes(undefined, signal)) {\n\t\tif (r.name && name.startsWith(`${r.name}/`) && (remote == null || r.name.length > remote.length)) {\n\t\t\tremote = r.name;\n\t\t}\n\t}\n\n\tconst branch = remote != null ? name.substring(remote.length + 1) : undefined;\n\treturn {\n\t\tname: name,\n\t\tremote: branch ? remote : undefined,\n\t\tbranch: branch || undefined,\n\t\tmissing: upstream.missing,\n\t};\n}\n\nfunction summariesEqual(a: WorktreeSummary, b: WorktreeSummary): boolean {\n\treturn (\n\t\ta.repoPath === b.repoPath &&\n\t\ta.branch === b.branch &&\n\t\ta.detached === b.detached &&\n\t\ta.ahead === b.ahead &&\n\t\ta.behind === b.behind &&\n\t\ta.dirty === b.dirty &&\n\t\ta.stale === b.stale &&\n\t\t(a.upstream === b.upstream ||\n\t\t\t(a.upstream != null &&\n\t\t\t\tb.upstream != null &&\n\t\t\t\ta.upstream.name === b.upstream.name &&\n\t\t\t\ta.upstream.remote === b.upstream.remote &&\n\t\t\t\ta.upstream.branch === b.upstream.branch &&\n\t\t\t\ta.upstream.missing === b.upstream.missing)) &&\n\t\t(a.base === b.base ||\n\t\t\t(a.base != null && b.base != null && a.base.ref === b.base.ref && a.base.ahead === b.base.ahead))\n\t);\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"worktreeSummary.js","names":[],"sources":["../../../../git/src/watching/worktreeSummary.ts"],"sourcesContent":["import { areEqual } from '@gitlens/utils/array.js';\nimport { CancellationError, isCancellationError, raceWithSignal } from '@gitlens/utils/cancellation.js';\nimport type { UnifiedDisposable } from '@gitlens/utils/disposable.js';\nimport type { Event } from '@gitlens/utils/event.js';\nimport { Emitter } from '@gitlens/utils/event.js';\nimport { Logger } from '@gitlens/utils/logger.js';\nimport type { ResourceUsage } from '@gitlens/utils/resourceUsage.js';\nimport { getRepositoryKey } from '@gitlens/utils/uri.js';\nimport type { GitTrackingUpstream } from '../models/branch.js';\nimport type { GitDir } from '../models/repository.js';\nimport type { RepositoryService } from '../repositoryService.js';\nimport type { WatcherRepoChangeEvent } from './changeEvent.js';\nimport type { RepositoryWatchService, WatchHandle } from './watchService.js';\nimport type { RepositoryWatchSession } from './watchSession.js';\n\nexport interface WorktreeSummaryUpstream {\n\t/** The upstream's short name (`origin/main`) */\n\treadonly name: string;\n\t/**\n\t * The remote it tracks: the longest of the repository's remote names that {@link name} starts with, followed by\n\t * `/` (a remote name can hold a slash). `undefined`, with {@link branch}, when it names no known remote (a branch\n\t * tracking another local branch, or a remote since removed)\n\t */\n\treadonly remote: string | undefined;\n\t/** The branch on {@link remote}; `undefined` whenever {@link remote} is */\n\treadonly branch: string | undefined;\n\treadonly missing: boolean;\n}\n\nexport interface WorktreeSummaryBase {\n\t/** The ref the count was taken against (see `AheadOfBase.base`) */\n\treadonly ref: string;\n\treadonly ahead: number;\n\t/** The commit {@link ref} pointed at when the count was read; `undefined` when it couldn't be resolved */\n\treadonly sha: string | undefined;\n}\n\nexport interface WorktreeSummary {\n\t/** The repository path in {@link getRepositoryKey} form */\n\treadonly repoPath: string;\n\t/** The checked-out branch; `undefined` when detached (or unborn with no name) */\n\treadonly branch: string | undefined;\n\treadonly detached: boolean;\n\t/** HEAD's commit; `undefined` when HEAD has no commit yet */\n\treadonly sha: string | undefined;\n\treadonly upstream: WorktreeSummaryUpstream | undefined;\n\t/** Commits ahead of the upstream; 0 without one */\n\treadonly ahead: number;\n\t/** Commits behind the upstream; 0 without one */\n\treadonly behind: number;\n\t/** Whether there is any staged, unstaged or untracked (non-ignored) change */\n\treadonly dirty: boolean;\n\t/** How far the branch is past its base; `undefined` when detached, unsupported, or there is no base */\n\treadonly base: WorktreeSummaryBase | undefined;\n\t/**\n\t * Full ref names whose movement can change {@link base}, as `getAheadOfBase` reports them (see\n\t * `AheadOfBase.dependsOn`) — given with or without a base, so a watcher also learns when one appears. Empty when\n\t * the base isn't read (detached, or a provider without `getAheadOfBase`), and when the base read failed with no\n\t * earlier list for this branch (the first read, or the one after a checkout).\n\t */\n\treadonly baseDependsOn: readonly string[];\n\t/**\n\t * The latest recompute failed (worktree gone, lock contention); the other fields hold the last values read. Also set\n\t * when only the base read failed: the other fields are fresh, and `base` and `baseDependsOn` are their last values,\n\t * or `undefined` and empty if the branch changed.\n\t */\n\treadonly stale: boolean;\n}\n\nexport interface WorktreeSummarySubscribeOptions {\n\t/** Debounce for repo (`.git`) changes this subscriber asks of the watch session; the shortest across subscribers wins */\n\treadonly delayMs?: number;\n\t/** Cap on how long continuous repo activity can defer a recompute; `Infinity` means no cap */\n\treadonly maxWaitMs?: number;\n\t/** Debounce for working-tree changes this subscriber asks of the watch session */\n\treadonly workingTreeDelayMs?: number;\n\t/** Cap on how long continuous working-tree activity can defer a recompute; `Infinity` means no cap */\n\treadonly workingTreeMaxWaitMs?: number;\n}\n\nexport interface WorktreeSummarySubscription extends UnifiedDisposable {\n\t/** The latest summary; `undefined` until the first read succeeds, or after disposal */\n\treadonly current: WorktreeSummary | undefined;\n\t/** Fires when the summary's value changes */\n\treadonly onDidChange: Event<WorktreeSummary>;\n}\n\nexport interface WorktreeSummaryServiceOptions {\n\treadonly watchService: RepositoryWatchService;\n\treadonly getRepository: (repoPath: string) => RepositoryService | undefined;\n}\n\n/** Read groups, as bit flags so a pending set is a number rather than an allocation per event */\nconst dirtyField = 1;\nconst branchField = 2;\nconst baseField = 4;\n/** Re-reads only the stored values the base read consults, and the base itself only when they changed */\nconst baseKeysField = 8;\n/** Every group a full read covers — a base read refreshes the {@link BaseKeys} snapshot itself */\nconst allFields = dirtyField | branchField | baseField;\n\nconst emptyRefs: readonly string[] = Object.freeze([]);\n\n/** The current branch's stored merge targets (user, detected) and recorded base, as `getAheadOfBase` consults them */\ntype BaseKeys = readonly [string | undefined, string | undefined, string | undefined];\n\ninterface ReadResult {\n\treadonly summary: WorktreeSummary;\n\treadonly baseKeys: BaseKeys | undefined;\n\t/** Why the base read failed when the rest of the summary was read; the summary is then stale */\n\treadonly baseError: unknown;\n}\n\ninterface SummaryEntry {\n\treadonly key: string;\n\t/** Aborted when the last subscriber leaves, cancelling any in-flight read */\n\treadonly abort: AbortController;\n\treadonly subscribers: Set<SummarySubscription>;\n\thandle: WatchHandle | undefined;\n\t/** The timing-neutral listening subscriptions and their event listeners */\n\tlisteners: UnifiedDisposable[] | undefined;\n\tvalue: WorktreeSummary | undefined;\n\t/** The stored base values as of the last base read; a `gk` config change re-reads the base only when they differ */\n\tbaseKeys: BaseKeys | undefined;\n\t/** Why the latest read failed while there was no value to keep */\n\terror: Error | undefined;\n\t/** Read groups due on the next run */\n\tpending: number;\n\t/** The active run (or the start-up, which ends in one); cleared in the same turn the loop sees nothing pending */\n\trunning: Promise<void> | undefined;\n\tdisposed: boolean;\n}\n\nclass SummarySubscription implements WorktreeSummarySubscription {\n\tprivate readonly emitter = new Emitter<WorktreeSummary>();\n\t/** Listener-less session subscriptions that carry this subscriber's timing into the session */\n\tprivate timing: UnifiedDisposable[] | undefined;\n\tprivate disposed = false;\n\n\tconstructor(\n\t\tprivate readonly entry: SummaryEntry,\n\t\tprivate readonly options: WorktreeSummarySubscribeOptions | undefined,\n\t\tprivate readonly onDispose: (subscription: SummarySubscription) => void,\n\t) {}\n\n\tget current(): WorktreeSummary | undefined {\n\t\treturn this.disposed ? undefined : this.entry.value;\n\t}\n\n\tget onDidChange(): Event<WorktreeSummary> {\n\t\treturn this.emitter.event;\n\t}\n\n\t[Symbol.dispose](): void {\n\t\tthis.dispose();\n\t}\n\n\tdispose(): void {\n\t\tif (this.disposed) return;\n\n\t\tthis.disposed = true;\n\t\tif (this.timing != null) {\n\t\t\tfor (const d of this.timing) {\n\t\t\t\td.dispose();\n\t\t\t}\n\t\t\tthis.timing = undefined;\n\t\t}\n\t\tthis.emitter.dispose();\n\t\tthis.onDispose(this);\n\t}\n\n\tattach(session: RepositoryWatchSession): void {\n\t\tif (this.disposed || this.timing != null) return;\n\n\t\tthis.timing = [\n\t\t\tsession.subscribe({ delayMs: this.options?.delayMs, maxWaitMs: this.options?.maxWaitMs }),\n\t\t\tsession.subscribeToWorkingTree({\n\t\t\t\tdelayMs: this.options?.workingTreeDelayMs,\n\t\t\t\tmaxWaitMs: this.options?.workingTreeMaxWaitMs,\n\t\t\t}),\n\t\t];\n\t}\n\n\tfire(summary: WorktreeSummary): void {\n\t\tthis.emitter.fire(summary);\n\t}\n}\n\n/**\n * A cached, change-notifying summary of each subscribed worktree — its branch, upstream and ahead/behind, whether it\n * has working changes, and how far it is past its base — kept current by the repository watch layer.\n *\n * **Lifecycle.** Entries are per repository key and ref-counted across {@link subscribe} calls. The first subscriber\n * starts watching the worktree and reads everything; the last one to leave aborts any in-flight read, releases the\n * watch and drops the value. Each subscriber's timing options join the watch session's cadence (the shortest wins);\n * the service's own listening subscriptions never constrain it.\n *\n * **Field-scoped recompute.** The summary is read in three groups — `dirty` (working changes), `branch` (branch,\n * HEAD's commit, upstream, ahead/behind) and `base` (ahead of base, the base's tip, and the refs it depends on) — and\n * an event recomputes only the groups it can affect: a working-tree edit, an index or ignore-rule change reads only\n * `dirty`; the upstream moving reads only `branch`; a ref the base depends on reads only `base`; a `gk` config change\n * (mostly bookkeeping writes to the shared `.git/gk/config`) re-reads the branch's stored merge targets and recorded\n * base, and reads `base` only when one of them changed; the checked-out branch moving, or a HEAD change, reads all of\n * them. Stash, unrelated tags and branches, and the like read nothing. One run is in flight per worktree at a time;\n * events during a run are merged into a single follow-up run.\n *\n * **Value-change-only notification.** {@link WorktreeSummarySubscription.onDidChange} and {@link onDidChangeSummary}\n * fire only when some field's value differs from the previous summary — a recompute that reads the same answer is\n * silent, so a consumer can re-render on every event without diffing. A failed recompute keeps the last values and\n * sets `stale` (firing once); the next successful one clears it.\n */\nexport class WorktreeSummaryService implements UnifiedDisposable {\n\tprivate readonly _onDidChangeSummary = new Emitter<WorktreeSummary>();\n\t/** Fires when the summary of any subscribed worktree changes value */\n\tget onDidChangeSummary(): Event<WorktreeSummary> {\n\t\treturn this._onDidChangeSummary.event;\n\t}\n\n\tprivate readonly entries = new Map<string, SummaryEntry>();\n\tprivate readonly watchService: RepositoryWatchService;\n\tprivate readonly getRepository: (repoPath: string) => RepositoryService | undefined;\n\tprivate _disposed = false;\n\n\tconstructor(options: WorktreeSummaryServiceOptions) {\n\t\tthis.watchService = options.watchService;\n\t\tthis.getRepository = options.getRepository;\n\t}\n\n\t[Symbol.dispose](): void {\n\t\tthis.dispose();\n\t}\n\n\tdispose(): void {\n\t\tif (this._disposed) return;\n\n\t\tthis._disposed = true;\n\t\tfor (const entry of [...this.entries.values()]) {\n\t\t\tfor (const subscription of [...entry.subscribers]) {\n\t\t\t\tsubscription.dispose();\n\t\t\t}\n\t\t}\n\t\tthis.entries.clear();\n\t\tthis._onDidChangeSummary.dispose();\n\t}\n\n\t/** Subscribes to a worktree's summary, starting to watch and read it if this is its first subscriber */\n\tsubscribe(repoPath: string, options?: WorktreeSummarySubscribeOptions): WorktreeSummarySubscription {\n\t\tconst key = getRepositoryKey(repoPath);\n\n\t\tlet entry = this._disposed ? undefined : this.entries.get(key);\n\t\tconst isNew = entry == null;\n\t\tif (entry == null) {\n\t\t\tentry = createEntry(key);\n\t\t\tif (this._disposed) {\n\t\t\t\tentry.disposed = true;\n\t\t\t} else {\n\t\t\t\tthis.entries.set(key, entry);\n\t\t\t}\n\t\t}\n\n\t\tconst target = entry;\n\t\tconst subscription = new SummarySubscription(target, options, s => this.release(target, s));\n\t\ttarget.subscribers.add(subscription);\n\n\t\tif (isNew) {\n\t\t\tif (!target.disposed) {\n\t\t\t\ttarget.pending = allFields;\n\t\t\t\ttarget.running = this.start(target);\n\t\t\t}\n\t\t} else if (target.handle != null) {\n\t\t\tsubscription.attach(target.handle.session);\n\t\t} else if (target.running == null && !target.disposed) {\n\t\t\t// The start failed (no repository or git directory yet); a new subscriber retries it\n\t\t\ttarget.pending = allFields;\n\t\t\ttarget.running = this.start(target);\n\t\t}\n\n\t\treturn subscription;\n\t}\n\n\t/**\n\t * Returns a worktree's summary. For a subscribed worktree, the cached value — after waiting for any run in flight\n\t * or pending; it rejects with the read's error when there is still no value. Otherwise a one-off full read that\n\t * is neither cached nor watched.\n\t */\n\tasync getSummary(repoPath: string, cancellation?: AbortSignal): Promise<WorktreeSummary> {\n\t\tconst key = getRepositoryKey(repoPath);\n\t\tconst entry = this.entries.get(key);\n\t\tif (entry == null) {\n\t\t\tconst repo = this.getRepository(key);\n\t\t\tif (repo == null) throw new Error(`No repository found for '${key}'`);\n\n\t\t\tconst result = await readSummary(repo, key, allFields, undefined, undefined, cancellation);\n\t\t\treturn result.summary;\n\t\t}\n\n\t\tif (entry.running != null) {\n\t\t\tawait (cancellation != null ? raceWithSignal(entry.running, cancellation) : entry.running);\n\t\t}\n\t\tif (entry.disposed) throw new CancellationError();\n\t\tif (entry.value != null) return entry.value;\n\n\t\tthrow entry.error ?? new Error(`No summary available for '${key}'`);\n\t}\n\n\tgetResourceUsage(): ResourceUsage {\n\t\treturn { 'worktreeSummary.entries.count': this.entries.size };\n\t}\n\n\tprivate async start(entry: SummaryEntry): Promise<void> {\n\t\tlet gitDir: GitDir | undefined;\n\t\ttry {\n\t\t\tgitDir = await this.getRepository(entry.key)?.config.getGitDir?.();\n\t\t} catch (ex) {\n\t\t\tif (entry.disposed) return;\n\n\t\t\tentry.running = undefined;\n\t\t\tif (!isCancellationError(ex)) {\n\t\t\t\tthis.fail(entry, ex);\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\t\tif (entry.disposed) return;\n\n\t\tconst handle = gitDir != null ? this.watchService.watch(entry.key, gitDir) : undefined;\n\t\tif (handle == null) {\n\t\t\tentry.running = undefined;\n\t\t\tthis.fail(entry, new Error(`Unable to watch '${entry.key}': no repository or git directory`));\n\t\t\treturn;\n\t\t}\n\n\t\tentry.handle = handle;\n\t\tconst session = handle.session;\n\t\t// Timing-neutral: the cadence comes from the subscribers' own session subscriptions (see `attach`)\n\t\tconst repoSub = session.subscribe({ delayMs: Infinity, maxWaitMs: Infinity });\n\t\tconst workingTreeSub = session.subscribeToWorkingTree({ delayMs: Infinity, maxWaitMs: Infinity });\n\t\tentry.listeners = [\n\t\t\trepoSub.onDidChange(e => this.onRepoChanged(entry, e)),\n\t\t\tworkingTreeSub.onDidChangeWorkingTree(() => this.queue(entry, dirtyField)),\n\t\t\trepoSub,\n\t\t\tworkingTreeSub,\n\t\t];\n\t\tfor (const subscription of entry.subscribers) {\n\t\t\tsubscription.attach(session);\n\t\t}\n\n\t\tawait this.drain(entry);\n\t}\n\n\tprivate onRepoChanged(entry: SummaryEntry, e: WatcherRepoChangeEvent): void {\n\t\tconst value = entry.value;\n\t\tif (value == null || e.changed('unknown', 'head')) {\n\t\t\tthis.queue(entry, allFields);\n\t\t\treturn;\n\t\t}\n\n\t\tlet fields = 0;\n\t\tif (e.changed('index', 'ignores')) {\n\t\t\tfields |= dirtyField;\n\t\t}\n\t\tif (e.changed('config')) {\n\t\t\tfields |= branchField | baseField;\n\t\t}\n\t\tif (e.changed('gkConfig')) {\n\t\t\tfields |= baseKeysField;\n\t\t}\n\t\tif (value.branch != null && e.mayHaveChangedRef(`refs/heads/${value.branch}`)) {\n\t\t\tfields |= allFields;\n\t\t}\n\t\t// The upstream's short name doesn't say whether it's remote or local (`--track main`), so test both forms\n\t\tif (\n\t\t\tvalue.upstream != null &&\n\t\t\t(e.mayHaveChangedRef(`refs/remotes/${value.upstream.name}`) ||\n\t\t\t\te.mayHaveChangedRef(`refs/heads/${value.upstream.name}`))\n\t\t) {\n\t\t\tfields |= branchField;\n\t\t}\n\t\tif ((fields & baseField) === 0) {\n\t\t\tfor (const ref of value.baseDependsOn) {\n\t\t\t\tif (e.mayHaveChangedRef(ref)) {\n\t\t\t\t\tfields |= baseField;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tthis.queue(entry, fields);\n\t}\n\n\tprivate queue(entry: SummaryEntry, fields: number): void {\n\t\tif (entry.disposed || fields === 0) return;\n\n\t\t// Without a value a partial read has nothing to merge into, and a stale value's fields can't be trusted: a\n\t\t// partial read would clear `stale` while vouching for groups it never re-read\n\t\tentry.pending |= entry.value == null || entry.value.stale ? allFields : fields;\n\t\tentry.running ??= this.drain(entry);\n\t}\n\n\t/** Runs until nothing is pending. Never rejects — every failure becomes entry state. */\n\tprivate async drain(entry: SummaryEntry): Promise<void> {\n\t\twhile (entry.pending !== 0 && !entry.disposed) {\n\t\t\tconst fields = entry.pending;\n\t\t\tentry.pending = 0;\n\t\t\tawait this.run(entry, fields);\n\t\t}\n\t\t// Same turn as the loop's last check, so an event can't queue fields that no run will pick up\n\t\tentry.running = undefined;\n\t}\n\n\tprivate async run(entry: SummaryEntry, fields: number): Promise<void> {\n\t\tconst previous = entry.value;\n\n\t\tlet result: ReadResult;\n\t\ttry {\n\t\t\tconst repo = this.getRepository(entry.key);\n\t\t\tif (repo == null) throw new Error(`No repository found for '${entry.key}'`);\n\n\t\t\tresult = await readSummary(repo, entry.key, fields, previous, entry.baseKeys, entry.abort.signal);\n\t\t} catch (ex) {\n\t\t\tif (entry.disposed || isCancellationError(ex)) return;\n\n\t\t\tthis.fail(entry, ex);\n\t\t\treturn;\n\t\t}\n\t\tif (entry.disposed) return;\n\n\t\tif (result.summary.stale && (previous == null || !previous.stale)) {\n\t\t\tLogger.warn(\n\t\t\t\t`WorktreeSummaryService: reading the base of '${entry.key}' failed: ${String(result.baseError)}`,\n\t\t\t);\n\t\t}\n\n\t\tentry.error = undefined;\n\t\tentry.baseKeys = result.baseKeys;\n\t\tif (previous != null && summariesEqual(previous, result.summary)) return;\n\n\t\tentry.value = result.summary;\n\t\tthis.fireChange(entry, result.summary);\n\t}\n\n\tprivate fail(entry: SummaryEntry, ex: unknown): void {\n\t\tconst previous = entry.value;\n\t\tif (previous == null) {\n\t\t\tif (entry.error == null) {\n\t\t\t\tLogger.warn(`WorktreeSummaryService: reading '${entry.key}' failed: ${String(ex)}`);\n\t\t\t}\n\t\t\tentry.error = ex instanceof Error ? ex : new Error(String(ex));\n\t\t\treturn;\n\t\t}\n\t\tif (previous.stale) return;\n\n\t\tLogger.warn(`WorktreeSummaryService: refreshing '${entry.key}' failed: ${String(ex)}`);\n\t\tconst summary: WorktreeSummary = { ...previous, stale: true };\n\t\tentry.value = summary;\n\t\tthis.fireChange(entry, summary);\n\t}\n\n\tprivate fireChange(entry: SummaryEntry, summary: WorktreeSummary): void {\n\t\tfor (const subscription of entry.subscribers) {\n\t\t\tsubscription.fire(summary);\n\t\t}\n\t\tthis._onDidChangeSummary.fire(summary);\n\t}\n\n\tprivate release(entry: SummaryEntry, subscription: SummarySubscription): void {\n\t\tentry.subscribers.delete(subscription);\n\t\tif (entry.subscribers.size !== 0) return;\n\n\t\tentry.disposed = true;\n\t\tentry.abort.abort();\n\t\tif (entry.listeners != null) {\n\t\t\tfor (const d of entry.listeners) {\n\t\t\t\td.dispose();\n\t\t\t}\n\t\t\tentry.listeners = undefined;\n\t\t}\n\t\tentry.handle?.dispose();\n\t\tentry.handle = undefined;\n\t\tentry.value = undefined;\n\t\tentry.baseKeys = undefined;\n\t\tentry.pending = 0;\n\t\tentry.running = undefined;\n\n\t\tif (this.entries.get(entry.key) === entry) {\n\t\t\tthis.entries.delete(entry.key);\n\t\t}\n\t}\n}\n\nfunction createEntry(key: string): SummaryEntry {\n\treturn {\n\t\tkey: key,\n\t\tabort: new AbortController(),\n\t\tsubscribers: new Set(),\n\t\thandle: undefined,\n\t\tlisteners: undefined,\n\t\tvalue: undefined,\n\t\tbaseKeys: undefined,\n\t\terror: undefined,\n\t\tpending: 0,\n\t\trunning: undefined,\n\t\tdisposed: false,\n\t};\n}\n\n/**\n * Reads the due groups and fills the rest from `previous` (every group when there is none). The branch read comes\n * before the base read, which needs its name; the dirty read runs alongside both. A branch read that finds a\n * different branch (or detached state) also reads the base and dirty groups, and stored base values that differ\n * from `previousBaseKeys` read the base group. A failed base read doesn't fail the rest: the summary comes back stale,\n * keeping the previous base and `baseDependsOn` only while the branch is the same.\n */\nasync function readSummary(\n\trepo: RepositoryService,\n\tkey: string,\n\tfields: number,\n\tprevious: WorktreeSummary | undefined,\n\tpreviousBaseKeys: BaseKeys | undefined,\n\tsignal: AbortSignal | undefined,\n): Promise<ReadResult> {\n\tif (previous == null) {\n\t\tfields = allFields;\n\t}\n\n\tlet dirtyRead = fields & dirtyField ? readDirty(repo, signal) : undefined;\n\n\tlet branch = previous?.branch;\n\tlet detached = previous?.detached ?? false;\n\tlet sha = previous?.sha;\n\tlet upstream = previous?.upstream;\n\tlet ahead = previous?.ahead ?? 0;\n\tlet behind = previous?.behind ?? 0;\n\tif (fields & branchField) {\n\t\tconst current = await repo.branches.getBranch(undefined, undefined, signal);\n\t\tdetached = current?.detached ?? false;\n\t\tbranch = detached ? undefined : current?.name || undefined;\n\t\tsha = current?.sha;\n\t\tupstream = current?.upstream != null ? await toSummaryUpstream(repo, current.upstream, signal) : undefined;\n\t\tahead = current?.upstream?.state.ahead ?? 0;\n\t\tbehind = current?.upstream?.state.behind ?? 0;\n\n\t\tif (previous != null && (branch !== previous.branch || detached !== previous.detached)) {\n\t\t\tfields |= baseField;\n\t\t\tdirtyRead ??= readDirty(repo, signal);\n\t\t}\n\t}\n\n\tlet base = previous?.base;\n\tlet dependsOn = previous?.baseDependsOn ?? emptyRefs;\n\tlet baseKeys = previousBaseKeys;\n\tlet baseKeysRead = false;\n\tlet baseFailed = false;\n\tlet baseError: unknown;\n\tconst baseBranch = !detached && repo.branches.getAheadOfBase != null ? branch : undefined;\n\tif (fields & baseKeysField && !(fields & baseField) && baseBranch != null) {\n\t\tbaseKeys = await readBaseKeys(repo, baseBranch);\n\t\tbaseKeysRead = true;\n\t\tif (baseKeys == null || previousBaseKeys == null || !baseKeysEqual(baseKeys, previousBaseKeys)) {\n\t\t\tfields |= baseField;\n\t\t}\n\t}\n\tif (fields & baseField) {\n\t\tif (baseBranch == null) {\n\t\t\tbase = undefined;\n\t\t\tdependsOn = emptyRefs;\n\t\t\tbaseKeys = undefined;\n\t\t} else {\n\t\t\t// Snapshot before the base read, so a write it misses is one the next `gk` config event sees as a change\n\t\t\tif (!baseKeysRead) {\n\t\t\t\tbaseKeys = await readBaseKeys(repo, baseBranch);\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tconst aheadOfBase = await repo.branches.getAheadOfBase?.(baseBranch, signal);\n\t\t\t\tbase = aheadOfBase?.base;\n\t\t\t\tdependsOn = aheadOfBase?.dependsOn ?? emptyRefs;\n\t\t\t} catch (ex) {\n\t\t\t\tif (isCancellationError(ex)) throw ex;\n\n\t\t\t\tbaseFailed = true;\n\t\t\t\tbaseError = ex;\n\t\t\t\t// Keep `baseDependsOn` for the same branch: a fetch moving one of its refs is what retries the base\n\t\t\t\tconst sameBranch = previous != null && branch === previous.branch && detached === previous.detached;\n\t\t\t\tbase = sameBranch ? previous.base : undefined;\n\t\t\t\tdependsOn = sameBranch ? previous.baseDependsOn : emptyRefs;\n\t\t\t\tbaseKeys = undefined;\n\t\t\t}\n\t\t}\n\t}\n\n\tconst dirty = dirtyRead != null ? await dirtyRead : (previous?.dirty ?? false);\n\n\treturn {\n\t\tsummary: {\n\t\t\trepoPath: key,\n\t\t\tbranch: branch,\n\t\t\tdetached: detached,\n\t\t\tsha: sha,\n\t\t\tupstream: upstream,\n\t\t\tahead: ahead,\n\t\t\tbehind: behind,\n\t\t\tdirty: dirty,\n\t\t\tbase: base,\n\t\t\tbaseDependsOn: dependsOn,\n\t\t\tstale: baseFailed,\n\t\t},\n\t\tbaseKeys: baseKeys,\n\t\tbaseError: baseError,\n\t};\n}\n\n/**\n * Reads {@link BaseKeys} for `branch`, normalized the way `getAheadOfBase` reads them. `undefined` when any read\n * failed — an unknown snapshot, so the next `gk` config change reads the base rather than trusting it.\n */\nasync function readBaseKeys(repo: RepositoryService, branch: string): Promise<BaseKeys | undefined> {\n\ttry {\n\t\tconst [user, detected, recorded] = await Promise.all([\n\t\t\trepo.branches.getStoredUserMergeTargetBranchName?.(branch),\n\t\t\trepo.branches.getStoredDetectedMergeTargetBranchName?.(branch),\n\t\t\trepo.config.getGkConfig?.(`branch.${branch}.gk-merge-base`),\n\t\t]);\n\t\treturn [user?.trim() || undefined, detected?.trim() || undefined, recorded?.trim() || undefined];\n\t} catch (ex) {\n\t\tif (isCancellationError(ex)) throw ex;\n\n\t\treturn undefined;\n\t}\n}\n\nfunction baseKeysEqual(a: BaseKeys, b: BaseKeys): boolean {\n\treturn a[0] === b[0] && a[1] === b[1] && a[2] === b[2];\n}\n\nfunction readDirty(repo: RepositoryService, signal: AbortSignal | undefined): Promise<boolean> {\n\tconst read = repo.status.hasWorkingChanges({ untracked: true }, signal);\n\t// Awaited only after the branch and base reads; if one of those throws first, this rejection has no other handler\n\tread.catch(noop);\n\treturn read;\n}\n\nfunction noop(): void {}\n\n/** Splits the upstream at the longest remote name it starts with; no split when none matches or nothing follows it */\nasync function toSummaryUpstream(\n\trepo: RepositoryService,\n\tupstream: GitTrackingUpstream,\n\tsignal: AbortSignal | undefined,\n): Promise<WorktreeSummaryUpstream> {\n\tconst name = upstream.name;\n\n\tlet remote: string | undefined;\n\tfor (const r of await repo.remotes.getRemotes(undefined, signal)) {\n\t\tif (r.name && name.startsWith(`${r.name}/`) && (remote == null || r.name.length > remote.length)) {\n\t\t\tremote = r.name;\n\t\t}\n\t}\n\n\tconst branch = remote != null ? name.substring(remote.length + 1) : undefined;\n\treturn {\n\t\tname: name,\n\t\tremote: branch ? remote : undefined,\n\t\tbranch: branch || undefined,\n\t\tmissing: upstream.missing,\n\t};\n}\n\nfunction summariesEqual(a: WorktreeSummary, b: WorktreeSummary): boolean {\n\treturn (\n\t\ta.repoPath === b.repoPath &&\n\t\ta.branch === b.branch &&\n\t\ta.detached === b.detached &&\n\t\ta.sha === b.sha &&\n\t\ta.ahead === b.ahead &&\n\t\ta.behind === b.behind &&\n\t\ta.dirty === b.dirty &&\n\t\ta.stale === b.stale &&\n\t\tareEqual(a.baseDependsOn, b.baseDependsOn) &&\n\t\t(a.upstream === b.upstream ||\n\t\t\t(a.upstream != null &&\n\t\t\t\tb.upstream != null &&\n\t\t\t\ta.upstream.name === b.upstream.name &&\n\t\t\t\ta.upstream.remote === b.upstream.remote &&\n\t\t\t\ta.upstream.branch === b.upstream.branch &&\n\t\t\t\ta.upstream.missing === b.upstream.missing)) &&\n\t\t(a.base === b.base ||\n\t\t\t(a.base != null &&\n\t\t\t\tb.base != null &&\n\t\t\t\ta.base.ref === b.base.ref &&\n\t\t\t\ta.base.ahead === b.base.ahead &&\n\t\t\t\ta.base.sha === b.base.sha))\n\t);\n}\n"],"mappings":"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"}
|