diff --git a/CHANGELOG.md b/CHANGELOG.md
index f61c5153d6805..c916e5976f50e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,6 +6,10 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p
## [Unreleased]
+### Added
+
+- Adds enhanced integration with Linear ([#4543](https://github.com/gitkraken/vscode-gitlens/issues/4543))
+
## [17.4.1] - 2025-08-26
### Fixed
diff --git a/docs/telemetry-events.md b/docs/telemetry-events.md
index f267fd4528316..5eaeb1a95f388 100644
--- a/docs/telemetry-events.md
+++ b/docs/telemetry-events.md
@@ -668,7 +668,7 @@ void
```typescript
{
'hostingProvider.key': string,
- 'hostingProvider.provider': 'github' | 'gitlab' | 'bitbucket' | 'azureDevOps' | 'bitbucket-server' | 'github-enterprise' | 'cloud-github-enterprise' | 'gitlab-self-hosted' | 'cloud-gitlab-self-hosted' | 'azure-devops-server' | 'jira' | 'trello'
+ 'hostingProvider.provider': 'github' | 'gitlab' | 'bitbucket' | 'azureDevOps' | 'bitbucket-server' | 'github-enterprise' | 'cloud-github-enterprise' | 'gitlab-self-hosted' | 'cloud-gitlab-self-hosted' | 'azure-devops-server' | 'jira' | 'linear' | 'trello'
}
```
@@ -679,7 +679,7 @@ void
```typescript
{
'hostingProvider.key': string,
- 'hostingProvider.provider': 'github' | 'gitlab' | 'bitbucket' | 'azureDevOps' | 'bitbucket-server' | 'github-enterprise' | 'cloud-github-enterprise' | 'gitlab-self-hosted' | 'cloud-gitlab-self-hosted' | 'azure-devops-server' | 'jira' | 'trello'
+ 'hostingProvider.provider': 'github' | 'gitlab' | 'bitbucket' | 'azureDevOps' | 'bitbucket-server' | 'github-enterprise' | 'cloud-github-enterprise' | 'gitlab-self-hosted' | 'cloud-gitlab-self-hosted' | 'azure-devops-server' | 'jira' | 'linear' | 'trello'
}
```
@@ -690,7 +690,7 @@ void
```typescript
{
'issueProvider.key': string,
- 'issueProvider.provider': 'github' | 'gitlab' | 'bitbucket' | 'azureDevOps' | 'bitbucket-server' | 'github-enterprise' | 'cloud-github-enterprise' | 'gitlab-self-hosted' | 'cloud-gitlab-self-hosted' | 'azure-devops-server' | 'jira' | 'trello'
+ 'issueProvider.provider': 'github' | 'gitlab' | 'bitbucket' | 'azureDevOps' | 'bitbucket-server' | 'github-enterprise' | 'cloud-github-enterprise' | 'gitlab-self-hosted' | 'cloud-gitlab-self-hosted' | 'azure-devops-server' | 'jira' | 'linear' | 'trello'
}
```
@@ -701,7 +701,7 @@ void
```typescript
{
'issueProvider.key': string,
- 'issueProvider.provider': 'github' | 'gitlab' | 'bitbucket' | 'azureDevOps' | 'bitbucket-server' | 'github-enterprise' | 'cloud-github-enterprise' | 'gitlab-self-hosted' | 'cloud-gitlab-self-hosted' | 'azure-devops-server' | 'jira' | 'trello'
+ 'issueProvider.provider': 'github' | 'gitlab' | 'bitbucket' | 'azureDevOps' | 'bitbucket-server' | 'github-enterprise' | 'cloud-github-enterprise' | 'gitlab-self-hosted' | 'cloud-gitlab-self-hosted' | 'azure-devops-server' | 'jira' | 'linear' | 'trello'
}
```
@@ -735,7 +735,7 @@ or when connection refresh is skipped due to being a non-cloud session
```typescript
{
- 'integration.id': 'github' | 'gitlab' | 'bitbucket' | 'azureDevOps' | 'bitbucket-server' | 'github-enterprise' | 'cloud-github-enterprise' | 'gitlab-self-hosted' | 'cloud-gitlab-self-hosted' | 'azure-devops-server' | 'jira' | 'trello'
+ 'integration.id': 'github' | 'gitlab' | 'bitbucket' | 'azureDevOps' | 'bitbucket-server' | 'github-enterprise' | 'cloud-github-enterprise' | 'gitlab-self-hosted' | 'cloud-gitlab-self-hosted' | 'azure-devops-server' | 'jira' | 'linear' | 'trello'
}
```
@@ -2892,7 +2892,7 @@ void
```typescript
{
'hostingProvider.key': string,
- 'hostingProvider.provider': 'github' | 'gitlab' | 'bitbucket' | 'azureDevOps' | 'bitbucket-server' | 'github-enterprise' | 'cloud-github-enterprise' | 'gitlab-self-hosted' | 'cloud-gitlab-self-hosted' | 'azure-devops-server' | 'jira' | 'trello',
+ 'hostingProvider.provider': 'github' | 'gitlab' | 'bitbucket' | 'azureDevOps' | 'bitbucket-server' | 'github-enterprise' | 'cloud-github-enterprise' | 'gitlab-self-hosted' | 'cloud-gitlab-self-hosted' | 'azure-devops-server' | 'jira' | 'linear' | 'trello',
// @deprecated: true
'remoteProviders.key': string
}
@@ -2905,7 +2905,7 @@ void
```typescript
{
'hostingProvider.key': string,
- 'hostingProvider.provider': 'github' | 'gitlab' | 'bitbucket' | 'azureDevOps' | 'bitbucket-server' | 'github-enterprise' | 'cloud-github-enterprise' | 'gitlab-self-hosted' | 'cloud-gitlab-self-hosted' | 'azure-devops-server' | 'jira' | 'trello',
+ 'hostingProvider.provider': 'github' | 'gitlab' | 'bitbucket' | 'azureDevOps' | 'bitbucket-server' | 'github-enterprise' | 'cloud-github-enterprise' | 'gitlab-self-hosted' | 'cloud-gitlab-self-hosted' | 'azure-devops-server' | 'jira' | 'linear' | 'trello',
// @deprecated: true
'remoteProviders.key': string
}
diff --git a/images/icons/provider-bitbucket.svg b/images/icons/provider-bitbucket.svg
index 54ddd78d10b7f..6119ea4d5779f 100644
--- a/images/icons/provider-bitbucket.svg
+++ b/images/icons/provider-bitbucket.svg
@@ -1 +1 @@
-
+
diff --git a/images/icons/provider-linear.svg b/images/icons/provider-linear.svg
new file mode 100644
index 0000000000000..79cff26e491b8
--- /dev/null
+++ b/images/icons/provider-linear.svg
@@ -0,0 +1 @@
+
diff --git a/images/icons/template/mapping.json b/images/icons/template/mapping.json
index e3997808159ea..3a6cc0eba8ee9 100644
--- a/images/icons/template/mapping.json
+++ b/images/icons/template/mapping.json
@@ -64,5 +64,6 @@
"repository": 61763,
"worktree": 61764,
"worktree-filled": 61765,
- "repository-cloud": 61766
+ "repository-cloud": 61766,
+ "provider-linear": 61767
}
\ No newline at end of file
diff --git a/package.json b/package.json
index 738f06c263d77..e53d6b1b080b9 100644
--- a/package.json
+++ b/package.json
@@ -10992,6 +10992,13 @@
"fontPath": "dist/glicons.woff2",
"fontCharacter": "\\f146"
}
+ },
+ "gitlens-provider-linear": {
+ "description": "provider-linear icon",
+ "default": {
+ "fontPath": "dist/glicons.woff2",
+ "fontCharacter": "\\f147"
+ }
}
},
"menus": {
@@ -25043,7 +25050,7 @@
},
"dependencies": {
"@gitkraken/gitkraken-components": "13.0.0-vnext.8",
- "@gitkraken/provider-apis": "0.29.6",
+ "@gitkraken/provider-apis": "0.29.7",
"@gitkraken/shared-web-components": "0.1.1-rc.15",
"@gk-nzaytsev/fast-string-truncated-width": "1.1.0",
"@lit-labs/signals": "0.1.3",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 36d40308b3e1a..7c7caf2eef1eb 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -19,8 +19,8 @@ importers:
specifier: 13.0.0-vnext.8
version: 13.0.0-vnext.8(@types/react@19.0.12)(react@19.0.0)
'@gitkraken/provider-apis':
- specifier: 0.29.6
- version: 0.29.6(encoding@0.1.13)
+ specifier: 0.29.7
+ version: 0.29.7(encoding@0.1.13)
'@gitkraken/shared-web-components':
specifier: 0.1.1-rc.15
version: 0.1.1-rc.15
@@ -657,8 +657,8 @@ packages:
peerDependencies:
react: 19.0.0
- '@gitkraken/provider-apis@0.29.6':
- resolution: {integrity: sha512-aRgR7lL6MxnCFtbbNB5AJuQVRGBy6nJlZE+Yn0FZ/G8QrqgxhBkexVtG0ji/wiq2HmQ4DxNk6eo3xMfYqoTq6w==}
+ '@gitkraken/provider-apis@0.29.7':
+ resolution: {integrity: sha512-i1StvQ0L5UPnVNnnud6vN+E80SAIXp/iX1UxlCIXXibYiw088x+cGBsfvzopx6rtkacuDd0rJTN75CMbEKsGwA==}
engines: {node: '>= 14'}
'@gitkraken/shared-web-components@0.1.1-rc.15':
@@ -667,6 +667,11 @@ packages:
'@gk-nzaytsev/fast-string-truncated-width@1.1.0':
resolution: {integrity: sha512-NPKNmdjRFUNpMRzQU3m+AmKzbiQ3WGFXxacMyfmRgm1N+vRhuCzAD3t2dRD29aX1n6a+PNBK2a6hwPwFTfx1rw==}
+ '@graphql-typed-document-node/core@3.2.0':
+ resolution: {integrity: sha512-mB9oAsNCm9aM3/SOv4YtBMqZbYj10R7dkq8byBqxGY/ncFwhf2oQzMV+LCRlWoDSEBJ3COiR1yeDvMtsoOsuFQ==}
+ peerDependencies:
+ graphql: ^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0
+
'@humanfs/core@0.19.1':
resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==}
engines: {node: '>=18.18.0'}
@@ -857,6 +862,10 @@ packages:
resolution: {integrity: sha512-LBSu5K0qAaaQcXX/0WIB9PGDevyCxxpnc1uq13vV/CgObaVxuis5hKl3Eboq/8gcb6ebnkAStW9NB/Em2eYyFA==}
engines: {node: '>= 20'}
+ '@linear/sdk@58.1.0':
+ resolution: {integrity: sha512-sqzo1j+uZsxeJlMTV2mrBH3yukB/liev7IySmkZil0ka7ic6b4RE9Jk3x+ohw8YgYB52IRR3SPWzhWu96E6W9g==}
+ engines: {node: '>=12.x', yarn: 1.x}
+
'@lit-labs/signals@0.1.3':
resolution: {integrity: sha512-P0yWgH5blwVyEwBg+WFspLzeu1i0ypJP1QB0l1Omr9qZLIPsUu0p4Fy2jshOg7oQyha5n163K3GJGeUhQQ682Q==}
@@ -3422,6 +3431,10 @@ packages:
graphemer@1.4.0:
resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==}
+ graphql@15.10.1:
+ resolution: {integrity: sha512-BL/Xd/T9baO6NFzoMpiMD7YUZ62R6viR5tp/MULVEnbYJXZA//kRNW7J0j1w/wXArgL0sCxhDfK5dczSKn3+cg==}
+ engines: {node: '>= 10.x'}
+
gunzip-maybe@1.4.2:
resolution: {integrity: sha512-4haO1M4mLO91PW57BMsDFf75UmwoRX0GkdD+Faw+Lr+r/OZrOCS0pIBwOL1xCKQqnQzbNFGgK2V2CpBUPeFNTw==}
hasBin: true
@@ -3881,6 +3894,9 @@ packages:
resolution: {integrity: sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==}
engines: {node: '>=0.10.0'}
+ isomorphic-unfetch@3.1.0:
+ resolution: {integrity: sha512-geDJjpoZ8N0kWexiwkX8F9NkTsXhetLPVbZFQ+JTW239QNOwvB0gniuR1Wc6f0AMTn7/mFGyXvHTifrCp/GH8Q==}
+
istanbul-lib-coverage@3.2.2:
resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==}
engines: {node: '>=8'}
@@ -5971,6 +5987,9 @@ packages:
undici-types@5.26.5:
resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==}
+ unfetch@4.2.0:
+ resolution: {integrity: sha512-F9p7yYCn6cIW9El1zi0HI6vqpeIvBsr3dSuRO6Xuppb1u5rXpCPmMvLSyECLhybr9isec8Ohl0hPekMVrEinDA==}
+
unicorn-magic@0.1.0:
resolution: {integrity: sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==}
engines: {node: '>=18'}
@@ -6537,8 +6556,9 @@ snapshots:
transitivePeerDependencies:
- '@types/react'
- '@gitkraken/provider-apis@0.29.6(encoding@0.1.13)':
+ '@gitkraken/provider-apis@0.29.7(encoding@0.1.13)':
dependencies:
+ '@linear/sdk': 58.1.0(encoding@0.1.13)
js-base64: 3.7.5
node-fetch: 2.7.0(encoding@0.1.13)
transitivePeerDependencies:
@@ -6551,6 +6571,10 @@ snapshots:
'@gk-nzaytsev/fast-string-truncated-width@1.1.0': {}
+ '@graphql-typed-document-node/core@3.2.0(graphql@15.10.1)':
+ dependencies:
+ graphql: 15.10.1
+
'@humanfs/core@0.19.1': {}
'@humanfs/node@0.16.6':
@@ -6712,6 +6736,14 @@ snapshots:
transitivePeerDependencies:
- supports-color
+ '@linear/sdk@58.1.0(encoding@0.1.13)':
+ dependencies:
+ '@graphql-typed-document-node/core': 3.2.0(graphql@15.10.1)
+ graphql: 15.10.1
+ isomorphic-unfetch: 3.1.0(encoding@0.1.13)
+ transitivePeerDependencies:
+ - encoding
+
'@lit-labs/signals@0.1.3':
dependencies:
lit: 3.3.1
@@ -9540,6 +9572,8 @@ snapshots:
graphemer@1.4.0: {}
+ graphql@15.10.1: {}
+
gunzip-maybe@1.4.2:
dependencies:
browserify-zlib: 0.1.4
@@ -10006,6 +10040,13 @@ snapshots:
isobject@3.0.1: {}
+ isomorphic-unfetch@3.1.0(encoding@0.1.13):
+ dependencies:
+ node-fetch: 2.7.0(encoding@0.1.13)
+ unfetch: 4.2.0
+ transitivePeerDependencies:
+ - encoding
+
istanbul-lib-coverage@3.2.2: {}
istanbul-lib-report@3.0.1:
@@ -12309,6 +12350,8 @@ snapshots:
undici-types@5.26.5: {}
+ unfetch@4.2.0: {}
+
unicorn-magic@0.1.0: {}
unicorn-magic@0.3.0: {}
diff --git a/src/autolinks/autolinksProvider.ts b/src/autolinks/autolinksProvider.ts
index 0e8f4a9527b1d..455cf1523482c 100644
--- a/src/autolinks/autolinksProvider.ts
+++ b/src/autolinks/autolinksProvider.ts
@@ -178,8 +178,11 @@ export class AutolinksProvider implements Disposable {
}
getAutolinkEnrichableId(autolink: Autolink): string {
+ // TODO: this should return linking key for all types of providers: such as TST-123 or #89 or PR 89 (or a pair: key+id).
+ // Each provider should form whatever ID they need in their specific getIssueOrPullRequest() method.
switch (autolink.provider?.id) {
case IssuesCloudHostIntegrationId.Jira:
+ case IssuesCloudHostIntegrationId.Linear:
return `${autolink.prefix}${autolink.id}`;
default:
return autolink.id;
diff --git a/src/autolinks/utils/-webview/autolinks.utils.ts b/src/autolinks/utils/-webview/autolinks.utils.ts
index ff75d9e4bf4e6..9653da5c55f21 100644
--- a/src/autolinks/utils/-webview/autolinks.utils.ts
+++ b/src/autolinks/utils/-webview/autolinks.utils.ts
@@ -32,7 +32,7 @@ export function serializeAutolink(value: Autolink): Autolink {
return serialized;
}
-export const supportedAutolinkIntegrations = [IssuesCloudHostIntegrationId.Jira];
+export const supportedAutolinkIntegrations = [IssuesCloudHostIntegrationId.Jira, IssuesCloudHostIntegrationId.Linear];
export function isDynamic(ref: AutolinkReference | DynamicAutolinkReference): ref is DynamicAutolinkReference {
return !('prefix' in ref) && !('url' in ref);
@@ -154,10 +154,10 @@ export function getBranchAutolinks(branchName: string, refsets: Readonly {
- if (a[0]?.id === IssuesCloudHostIntegrationId.Jira || a[0]?.id === IssuesCloudHostIntegrationId.Trello) {
+ if (a[0]?.id && Object.values(IssuesCloudHostIntegrationId).includes(a[0].id)) {
return -1;
}
- if (b[0]?.id === IssuesCloudHostIntegrationId.Jira || b[0]?.id === IssuesCloudHostIntegrationId.Trello) {
+ if (b[0]?.id && Object.values(IssuesCloudHostIntegrationId).includes(b[0].id)) {
return 1;
}
return 0;
diff --git a/src/constants.integrations.ts b/src/constants.integrations.ts
index a8f828fb78fa2..22f989d2aeed3 100644
--- a/src/constants.integrations.ts
+++ b/src/constants.integrations.ts
@@ -16,6 +16,7 @@ export enum GitSelfManagedHostIntegrationId {
export enum IssuesCloudHostIntegrationId {
Jira = 'jira',
+ Linear = 'linear',
Trello = 'trello',
}
@@ -30,7 +31,10 @@ export type IssuesHostIntegrationIds = IssuesCloudHostIntegrationId;
export type IntegrationIds = GitHostIntegrationIds | IssuesHostIntegrationIds;
-export const supportedOrderedCloudIssuesIntegrationIds = [IssuesCloudHostIntegrationId.Jira];
+export const supportedOrderedCloudIssuesIntegrationIds = [
+ IssuesCloudHostIntegrationId.Jira,
+ IssuesCloudHostIntegrationId.Linear,
+];
export const supportedOrderedCloudIntegrationIds = [
GitCloudHostIntegrationId.GitHub,
GitSelfManagedHostIntegrationId.CloudGitHubEnterprise,
@@ -41,6 +45,7 @@ export const supportedOrderedCloudIntegrationIds = [
GitCloudHostIntegrationId.Bitbucket,
GitSelfManagedHostIntegrationId.BitbucketServer,
IssuesCloudHostIntegrationId.Jira,
+ IssuesCloudHostIntegrationId.Linear,
];
export const integrationIds = [
@@ -55,6 +60,7 @@ export const integrationIds = [
GitSelfManagedHostIntegrationId.GitHubEnterprise,
GitSelfManagedHostIntegrationId.GitLabSelfHosted,
IssuesCloudHostIntegrationId.Jira,
+ IssuesCloudHostIntegrationId.Linear,
IssuesCloudHostIntegrationId.Trello,
];
@@ -142,4 +148,11 @@ export const supportedCloudIntegrationDescriptors: IntegrationDescriptor[] = [
supports: ['issues'],
requiresPro: true,
},
+ {
+ id: IssuesCloudHostIntegrationId.Linear,
+ name: 'Linear',
+ icon: 'gl-provider-linear',
+ supports: ['issues'],
+ requiresPro: true,
+ },
];
diff --git a/src/git/models/commit.ts b/src/git/models/commit.ts
index cb443dfaa0bf5..3603991134d16 100644
--- a/src/git/models/commit.ts
+++ b/src/git/models/commit.ts
@@ -551,7 +551,6 @@ export class GitCommit implements GitRevisionReference {
if (this.isUncommitted) return undefined;
remote ??= await this.container.git.getRepositoryService(this.repoPath).remotes.getBestRemoteWithIntegration();
- if (remote?.provider == null) return undefined;
// TODO@eamodio should we cache these? Seems like we would use more memory than it's worth
// async function getCore(this: GitCommit): Promise