diff --git a/components/server/src/bitbucket-server/bitbucket-server-api.ts b/components/server/src/bitbucket-server/bitbucket-server-api.ts index e7067a28ae17bb..e2c0d16b9619c1 100644 --- a/components/server/src/bitbucket-server/bitbucket-server-api.ts +++ b/components/server/src/bitbucket-server/bitbucket-server-api.ts @@ -265,7 +265,7 @@ export class BitbucketServerApi { ); } - setWebhook( + async setWebhook( user: User, params: { repoKind: "projects" | "users"; owner: string; repositorySlug: string }, webhook: BitbucketServer.WebhookParams, @@ -301,6 +301,17 @@ export class BitbucketServerApi { } return this.runQuery>(user, `/repos${q}`); } + + async getPullRequest( + user: User, + params: { repoKind: "projects" | "users"; owner: string; repositorySlug: string; nr: number }, + ): Promise { + const result = await this.runQuery( + user, + `/${params.repoKind}/${params.owner}/repos/${params.repositorySlug}/pull-requests/${params.nr}`, + ); + return result; + } } export namespace BitbucketServer { @@ -329,6 +340,7 @@ export namespace BitbucketServer { id: number; name: string; public: boolean; + type: "PERSONAL" | "NORMAL"; } export interface Branch { @@ -375,6 +387,45 @@ export namespace BitbucketServer { message: string; } + export interface PullRequest { + id: number; + version: number; + title: string; + description: string; + state: "OPEN" | string; + open: boolean; + closed: boolean; + createdDate: number; + updatedDate: number; + fromRef: Ref; + toRef: Ref; + locked: boolean; + author: { + user: User; + role: "AUTHOR" | string; + approved: boolean; + status: "UNAPPROVED" | string; + }; + // reviewers: []; + // participants: []; + links: { + self: [ + { + //"https://bitbucket.gitpod-self-hosted.com/projects/FOO/repos/repo123/pull-requests/1" + href: string; + }, + ]; + }; + } + + export interface Ref { + id: string; // "refs/heads/foo" + displayId: string; //"foo" + latestCommit: string; + type: "BRANCH" | string; + repository: Repository; + } + export interface Paginated { isLastPage?: boolean; limit?: number; diff --git a/components/server/src/bitbucket-server/bitbucket-server-context-parser.spec.ts b/components/server/src/bitbucket-server/bitbucket-server-context-parser.spec.ts index 2e8a0fb8c0b5b1..f506431b5b1bfb 100644 --- a/components/server/src/bitbucket-server/bitbucket-server-context-parser.spec.ts +++ b/components/server/src/bitbucket-server/bitbucket-server-context-parser.spec.ts @@ -88,7 +88,7 @@ class TestBitbucketServerContextParser { expect(result).to.deep.include({ ref: "master", refType: "branch", - revision: "535924584468074ec5dcbe935f4e68fbc3f0cb2d", + revision: "9eea1cca9bb98f0caf7ae77c740d5d24548ff33c", path: "", isFile: false, repository: { @@ -115,7 +115,7 @@ class TestBitbucketServerContextParser { expect(result).to.deep.include({ ref: "master", refType: "branch", - revision: "535924584468074ec5dcbe935f4e68fbc3f0cb2d", + revision: "9eea1cca9bb98f0caf7ae77c740d5d24548ff33c", path: "", isFile: false, repository: { @@ -142,7 +142,7 @@ class TestBitbucketServerContextParser { expect(result).to.deep.include({ ref: "main", refType: "branch", - revision: "a15d7d15adee54d0afdbe88148c8e587e8fb609d", + revision: "d4bdb1459f9fc90756154bdda5eb23c39457a89c", path: "", isFile: false, repository: { @@ -158,6 +158,152 @@ class TestBitbucketServerContextParser { title: "alextugarev/tada - main", }); } + + @test async test_commit_context_01() { + const result = await this.parser.handle( + {}, + this.user, + "https://bitbucket.gitpod-self-hosted.com/users/jan/repos/yolo/commits/ec15264e536e9684034ea8e08f3afc3fd485b613", + ); + + expect(result).to.deep.include({ + refType: "revision", + revision: "ec15264e536e9684034ea8e08f3afc3fd485b613", + path: "", + isFile: false, + repository: { + cloneUrl: "https://bitbucket.gitpod-self-hosted.com/scm/~jan/yolo.git", + defaultBranch: "master", + host: "bitbucket.gitpod-self-hosted.com", + name: "YOLO", + owner: "jan", + private: true, + repoKind: "users", + webUrl: "https://bitbucket.gitpod-self-hosted.com/users/jan/repos/yolo", + }, + title: "jan/yolo - ec15264e536e9684034ea8e08f3afc3fd485b613", + }); + } + + @test async test_PR_context_01() { + const result = await this.parser.handle( + {}, + this.user, + "https://bitbucket.gitpod-self-hosted.com/projects/FOO/repos/repo123/pull-requests/1/commits", + ); + + expect(result).to.deep.include({ + title: "Let's do it", + nr: 1, + ref: "foo", + refType: "branch", + revision: "1384b6842d73b8705feaf45f3e8aa41f00529042", + repository: { + host: "bitbucket.gitpod-self-hosted.com", + owner: "FOO", + name: "repo123", + cloneUrl: "https://bitbucket.gitpod-self-hosted.com/scm/foo/repo123.git", + webUrl: "https://bitbucket.gitpod-self-hosted.com/projects/FOO/repos/repo123", + defaultBranch: "master", + private: true, + repoKind: "projects", + }, + base: { + ref: "master", + refType: "branch", + repository: { + host: "bitbucket.gitpod-self-hosted.com", + owner: "FOO", + name: "repo123", + cloneUrl: "https://bitbucket.gitpod-self-hosted.com/scm/foo/repo123.git", + webUrl: "https://bitbucket.gitpod-self-hosted.com/projects/FOO/repos/repo123", + defaultBranch: "master", + private: true, + repoKind: "projects", + }, + }, + }); + } + + @test async test_PR_context_02() { + const result = await this.parser.handle( + {}, + this.user, + "https://bitbucket.gitpod-self-hosted.com/projects/FOO/repos/repo123/pull-requests/2/overview", + ); + + expect(result).to.deep.include({ + title: "Let's do it again", + nr: 2, + ref: "foo", + refType: "branch", + revision: "1384b6842d73b8705feaf45f3e8aa41f00529042", + repository: { + host: "bitbucket.gitpod-self-hosted.com", + owner: "LAL", + name: "repo123", + cloneUrl: "https://bitbucket.gitpod-self-hosted.com/scm/lal/repo123.git", + webUrl: "https://bitbucket.gitpod-self-hosted.com/projects/LAL/repos/repo123", + defaultBranch: "master", + private: true, + repoKind: "projects", + }, + base: { + ref: "master", + refType: "branch", + repository: { + host: "bitbucket.gitpod-self-hosted.com", + owner: "FOO", + name: "repo123", + cloneUrl: "https://bitbucket.gitpod-self-hosted.com/scm/foo/repo123.git", + webUrl: "https://bitbucket.gitpod-self-hosted.com/projects/FOO/repos/repo123", + defaultBranch: "master", + private: true, + repoKind: "projects", + }, + }, + }); + } + + @test async test_PR_context_03() { + const result = await this.parser.handle( + {}, + this.user, + "https://bitbucket.gitpod-self-hosted.com/projects/LAL/repos/repo123/pull-requests/1/overview", + ); + + expect(result).to.deep.include({ + title: "U turn", + nr: 1, + ref: "foo", + refType: "branch", + revision: "1384b6842d73b8705feaf45f3e8aa41f00529042", + repository: { + host: "bitbucket.gitpod-self-hosted.com", + owner: "FOO", + name: "repo123", + cloneUrl: "https://bitbucket.gitpod-self-hosted.com/scm/foo/repo123.git", + webUrl: "https://bitbucket.gitpod-self-hosted.com/projects/FOO/repos/repo123", + defaultBranch: "master", + private: true, + repoKind: "projects", + }, + base: { + ref: "master", + refType: "branch", + repository: { + host: "bitbucket.gitpod-self-hosted.com", + owner: "LAL", + name: "repo123", + cloneUrl: "https://bitbucket.gitpod-self-hosted.com/scm/lal/repo123.git", + webUrl: "https://bitbucket.gitpod-self-hosted.com/projects/LAL/repos/repo123", + defaultBranch: "master", + private: true, + repoKind: "projects", + }, + }, + }); + } } module.exports = new TestBitbucketServerContextParser(); diff --git a/components/server/src/bitbucket-server/bitbucket-server-context-parser.ts b/components/server/src/bitbucket-server/bitbucket-server-context-parser.ts index 8bd2daa6f5db7a..88e2ee2f62d2eb 100644 --- a/components/server/src/bitbucket-server/bitbucket-server-context-parser.ts +++ b/components/server/src/bitbucket-server/bitbucket-server-context-parser.ts @@ -4,7 +4,7 @@ * See License-AGPL.txt in the project root for license information. */ -import { NavigatorContext, Repository, User, WorkspaceContext } from "@gitpod/gitpod-protocol"; +import { NavigatorContext, PullRequestContext, Repository, User, WorkspaceContext } from "@gitpod/gitpod-protocol"; import { log } from "@gitpod/gitpod-protocol/lib/util/logging"; import { TraceContext } from "@gitpod/gitpod-protocol/lib/util/tracing"; import { inject, injectable } from "inversify"; @@ -26,7 +26,7 @@ export class BitbucketServerContextParser extends AbstractContextParser implemen try { const more: Partial = {}; - const { repoKind, host, owner, repoName, /*moreSegments*/ searchParams } = await this.parseURL( + const { repoKind, host, owner, repoName, moreSegments, searchParams } = await this.parseURL( user, contextUrl, ); @@ -36,6 +36,18 @@ export class BitbucketServerContextParser extends AbstractContextParser implemen more.refType = "branch"; } + if (moreSegments[0] === "pull-requests" && !!moreSegments[1]) { + const more = { nr: parseInt(moreSegments[1]) }; + return await this.handlePullRequestContext(ctx, user, repoKind, host, owner, repoName, more); + } + + if (moreSegments[0] === "commits" && !!moreSegments[1]) { + more.ref = ""; + more.revision = moreSegments[1]; + more.refType = "revision"; + return await this.handleNavigatorContext(ctx, user, repoKind, host, owner, repoName, more); + } + return await this.handleNavigatorContext(ctx, user, repoKind, host, owner, repoName, more); } catch (e) { span.addTags({ contextUrl }).log({ error: e }); @@ -203,4 +215,46 @@ export class BitbucketServerContextParser extends AbstractContextParser implemen return result; } + + protected async handlePullRequestContext( + ctx: TraceContext, + user: User, + repoKind: "projects" | "users", + host: string, + owner: string, + repoName: string, + more: Partial & { nr: number }, + ): Promise { + const pr = await this.api.getPullRequest(user, { + repoKind, + repositorySlug: repoName, + owner, + nr: more.nr, + }); + + const getRepository = async (ref: BitbucketServer.Ref) => { + const repoKindFromRef = ref.repository.project.type === "PERSONAL" ? "users" : "projects"; + const defaultBranchFromRef = await this.api.getDefaultBranch(user, { + repoKind: repoKindFromRef, + owner: ref.repository.project.owner ? ref.repository.project.owner.slug : ref.repository.project.key, + repositorySlug: ref.repository.slug, + }); + return this.toRepository(host, ref.repository, repoKindFromRef, defaultBranchFromRef); + }; + + return { + repository: await getRepository(pr.fromRef), + title: pr.title, + ref: pr.fromRef.displayId, + refType: "branch", + revision: pr.fromRef.latestCommit, + base: { + repository: await getRepository(pr.toRef), + ref: pr.toRef.displayId, + refType: "branch", + }, + ...more, + owner, + }; + } }