diff --git a/components/server/ee/src/auth/host-container-mapping.ts b/components/server/ee/src/auth/host-container-mapping.ts index ae06375629b401..539a8131ddba76 100644 --- a/components/server/ee/src/auth/host-container-mapping.ts +++ b/components/server/ee/src/auth/host-container-mapping.ts @@ -21,6 +21,9 @@ export class HostContainerMappingEE extends HostContainerMapping { return (modules || []).concat([gitlabContainerModuleEE]); case "Bitbucket": return (modules || []).concat([bitbucketContainerModuleEE]); + // case "BitbucketServer": + // FIXME + // return (modules || []).concat([bitbucketContainerModuleEE]); case "GitHub": return (modules || []).concat([gitHubContainerModuleEE]); default: diff --git a/components/server/package.json b/components/server/package.json index f01943bf88d8b2..154f8f40f1a1aa 100644 --- a/components/server/package.json +++ b/components/server/package.json @@ -27,6 +27,7 @@ "/dist" ], "dependencies": { + "@atlassian/bitbucket-server": "^0.0.6", "@gitbeaker/node": "^25.6.0", "@gitpod/content-service": "0.1.5", "@gitpod/gitpod-db": "0.1.5", diff --git a/components/server/src/auth/host-container-mapping.ts b/components/server/src/auth/host-container-mapping.ts index 3db11e1d051d3e..af8d7a268e308c 100644 --- a/components/server/src/auth/host-container-mapping.ts +++ b/components/server/src/auth/host-container-mapping.ts @@ -9,6 +9,7 @@ import { githubContainerModule } from "../github/github-container-module"; import { gitlabContainerModule } from "../gitlab/gitlab-container-module"; import { genericAuthContainerModule } from "./oauth-container-module"; import { bitbucketContainerModule } from "../bitbucket/bitbucket-container-module"; +import { bitbucketServerContainerModule } from "../bitbucket-server/bitbucket-server-container-module"; @injectable() export class HostContainerMapping { @@ -23,6 +24,8 @@ export class HostContainerMapping { return [genericAuthContainerModule]; case "Bitbucket": return [bitbucketContainerModule]; + case "BitbucketServer": + return [bitbucketServerContainerModule]; default: return undefined; } diff --git a/components/server/src/bitbucket-server/bitbucket-server-api.ts b/components/server/src/bitbucket-server/bitbucket-server-api.ts new file mode 100644 index 00000000000000..5c4a59b7573b9d --- /dev/null +++ b/components/server/src/bitbucket-server/bitbucket-server-api.ts @@ -0,0 +1,104 @@ +/** + * Copyright (c) 2022 Gitpod GmbH. All rights reserved. + * Licensed under the GNU Affero General Public License (AGPL). + * See License-AGPL.txt in the project root for license information. + */ + +import fetch from 'node-fetch'; +import { User } from "@gitpod/gitpod-protocol"; +import { inject, injectable } from "inversify"; +import { AuthProviderParams } from "../auth/auth-provider"; +import { BitbucketTokenHelper } from "../bitbucket/bitbucket-token-handler"; + +@injectable() +export class BitbucketServerApi { + + @inject(AuthProviderParams) protected readonly config: AuthProviderParams; + @inject(BitbucketTokenHelper) protected readonly tokenHelper: BitbucketTokenHelper; + + public async runQuery(user: User, urlPath: string): Promise { + const token = (await this.tokenHelper.getTokenWithScopes(user, [])).value; + const fullUrl = `${this.baseUrl}${urlPath}`; + const response = await fetch(fullUrl, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${token}` + } + }); + if (!response.ok) { + throw Error(response.statusText); + } + const result = await response.json(); + return result as T; + } + + protected get baseUrl(): string { + return `https://${this.config.host}/rest/api/1.0`; + } + + getRepository(user: User, params: { kind: "projects" | "users", userOrProject: string; repositorySlug: string; }): Promise { + return this.runQuery(user, `/${params.kind}/${params.userOrProject}/repos/${params.repositorySlug}`); + } + + getCommits(user: User, params: { kind: "projects" | "users", userOrProject: string, repositorySlug: string, q?: { limit: number } }): Promise> { + return this.runQuery>(user, `/${params.kind}/${params.userOrProject}/repos/${params.repositorySlug}/commits`); + } +} + + +export namespace BitbucketServer { + export interface Repository { + id: number; + slug: string; + name: string; + public: boolean; + links: { + clone: { + href: string; + name: string; + }[] + } + project: Project; + } + + export interface Project { + key: string; + id: number; + name: string; + public: boolean; + } + + export interface User { + "name": string, + "emailAddress": string, + "id": number, + "displayName": string, + "active": boolean, + "slug": string, + "type": string, + "links": { + "self": [ + { + "href": string + } + ] + } + } + + export interface Commit { + "id": string, + "displayId": string, + "author": BitbucketServer.User + } + + export interface Paginated { + isLastPage?: boolean; + limit?: number; + size?: number; + start?: number; + values?: T[]; + [k: string]: any; + } + +} \ No newline at end of file diff --git a/components/server/src/bitbucket-server/bitbucket-server-auth-provider.ts b/components/server/src/bitbucket-server/bitbucket-server-auth-provider.ts new file mode 100644 index 00000000000000..a164f50f00fde9 --- /dev/null +++ b/components/server/src/bitbucket-server/bitbucket-server-auth-provider.ts @@ -0,0 +1,111 @@ +/** + * Copyright (c) 2022 Gitpod GmbH. All rights reserved. + * Licensed under the GNU Affero General Public License (AGPL). + * See License-AGPL.txt in the project root for license information. + */ + +import { AuthProviderInfo } from "@gitpod/gitpod-protocol"; +import { log } from "@gitpod/gitpod-protocol/lib/util/logging"; +import * as express from "express"; +import { injectable } from "inversify"; +import fetch from "node-fetch"; +import { AuthUserSetup } from "../auth/auth-provider"; +import { GenericAuthProvider } from "../auth/generic-auth-provider"; +import { BitbucketServerOAuthScopes } from "./bitbucket-server-oauth-scopes"; +import * as BitbucketServer from "@atlassian/bitbucket-server"; + +@injectable() +export class BitbucketServerAuthProvider extends GenericAuthProvider { + + get info(): AuthProviderInfo { + return { + ...this.defaultInfo(), + scopes: BitbucketServerOAuthScopes.ALL, + requirements: { + default: BitbucketServerOAuthScopes.Requirements.DEFAULT, + publicRepo: BitbucketServerOAuthScopes.Requirements.DEFAULT, + privateRepo: BitbucketServerOAuthScopes.Requirements.DEFAULT, + }, + } + } + + /** + * Augmented OAuthConfig for Bitbucket + */ + protected get oauthConfig() { + const oauth = this.params.oauth!; + const scopeSeparator = " "; + return { + ...oauth, + authorizationUrl: oauth.authorizationUrl || `https://${this.params.host}/rest/oauth2/latest/authorize`, + tokenUrl: oauth.tokenUrl || `https://${this.params.host}/rest/oauth2/latest/token`, + settingsUrl: oauth.settingsUrl || `https://${this.params.host}/plugins/servlet/oauth/users/access-tokens/`, + scope: BitbucketServerOAuthScopes.ALL.join(scopeSeparator), + scopeSeparator + }; + } + + protected get tokenUsername(): string { + return "x-token-auth"; + } + + authorize(req: express.Request, res: express.Response, next: express.NextFunction, scope?: string[]): void { + super.authorize(req, res, next, scope ? scope : BitbucketServerOAuthScopes.Requirements.DEFAULT); + } + + protected readAuthUserSetup = async (accessToken: string, _tokenResponse: object) => { + try { + const fetchResult = await fetch(`https://${this.params.host}/plugins/servlet/applinks/whoami`, { + headers: { + "Authorization": `Bearer ${accessToken}`, + } + }); + if (!fetchResult.ok) { + throw new Error(fetchResult.statusText); + } + const username = await fetchResult.text(); + if (!username) { + throw new Error("username missing"); + } + + log.warn(`(${this.strategyName}) username ${username}`); + + const options = { + baseUrl: `https://${this.params.host}`, + }; + const client = new BitbucketServer(options); + + client.authenticate({ type: "token", token: accessToken }); + const result = await client.api.getUser({ userSlug: username }); + + const user = result.data; + + // TODO: check if user.active === true? + + return { + authUser: { + authId: `${user.id!}`, + authName: user.slug!, + primaryEmail: user.emailAddress!, + name: user.displayName!, + // avatarUrl: user.links!.avatar!.href // TODO + }, + currentScopes: BitbucketServerOAuthScopes.ALL, + } + + } catch (error) { + log.error(`(${this.strategyName}) Reading current user info failed`, error, { accessToken, error }); + throw error; + } + } + + protected normalizeScopes(scopes: string[]) { + const set = new Set(scopes); + for (const item of set.values()) { + if (!(BitbucketServerOAuthScopes.Requirements.DEFAULT.includes(item))) { + set.delete(item); + } + } + return Array.from(set).sort(); + } +} \ No newline at end of file diff --git a/components/server/src/bitbucket-server/bitbucket-server-container-module.ts b/components/server/src/bitbucket-server/bitbucket-server-container-module.ts new file mode 100644 index 00000000000000..6a8ae6bc40e128 --- /dev/null +++ b/components/server/src/bitbucket-server/bitbucket-server-container-module.ts @@ -0,0 +1,37 @@ +/** + * Copyright (c) 2020 Gitpod GmbH. All rights reserved. + * Licensed under the GNU Affero General Public License (AGPL). + * See License-AGPL.txt in the project root for license information. + */ + +import { ContainerModule } from "inversify"; +import { AuthProvider } from "../auth/auth-provider"; +import { BitbucketApiFactory } from "../bitbucket/bitbucket-api-factory"; +import { BitbucketFileProvider } from "../bitbucket/bitbucket-file-provider"; +import { BitbucketLanguagesProvider } from "../bitbucket/bitbucket-language-provider"; +import { BitbucketRepositoryProvider } from "../bitbucket/bitbucket-repository-provider"; +import { BitbucketTokenHelper } from "../bitbucket/bitbucket-token-handler"; +import { FileProvider, LanguagesProvider, RepositoryHost, RepositoryProvider } from "../repohost"; +import { IContextParser } from "../workspace/context-parser"; +import { BitbucketServerApi } from "./bitbucket-server-api"; +import { BitbucketServerAuthProvider } from "./bitbucket-server-auth-provider"; +import { BitbucketServerContextParser } from "./bitbucket-server-context-parser"; + +export const bitbucketServerContainerModule = new ContainerModule((bind, _unbind, _isBound, _rebind) => { + bind(RepositoryHost).toSelf().inSingletonScope(); + bind(BitbucketServerApi).toSelf().inSingletonScope(); + bind(BitbucketFileProvider).toSelf().inSingletonScope(); + bind(FileProvider).toService(BitbucketFileProvider); + bind(BitbucketServerContextParser).toSelf().inSingletonScope(); + bind(BitbucketLanguagesProvider).toSelf().inSingletonScope(); + bind(LanguagesProvider).toService(BitbucketLanguagesProvider); + bind(IContextParser).toService(BitbucketServerContextParser); + bind(BitbucketRepositoryProvider).toSelf().inSingletonScope(); + bind(RepositoryProvider).toService(BitbucketRepositoryProvider); + bind(BitbucketServerAuthProvider).toSelf().inSingletonScope(); + bind(AuthProvider).to(BitbucketServerAuthProvider).inSingletonScope(); + bind(BitbucketTokenHelper).toSelf().inSingletonScope(); + bind(BitbucketApiFactory).toSelf().inSingletonScope(); + // bind(BitbucketTokenValidator).toSelf().inSingletonScope(); // TODO + // bind(IGitTokenValidator).toService(BitbucketTokenValidator); +}); diff --git a/components/server/src/bitbucket-server/bitbucket-server-context-parser.ts b/components/server/src/bitbucket-server/bitbucket-server-context-parser.ts new file mode 100644 index 00000000000000..8530842f0b47fe --- /dev/null +++ b/components/server/src/bitbucket-server/bitbucket-server-context-parser.ts @@ -0,0 +1,137 @@ +/** + * Copyright (c) 2022 Gitpod GmbH. All rights reserved. + * Licensed under the GNU Affero General Public License (AGPL). + * See License-AGPL.txt in the project root for license information. + */ + +import { NavigatorContext, 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"; +import { BitbucketTokenHelper } from "../bitbucket/bitbucket-token-handler"; +import { NotFoundError } from "../errors"; +import { AbstractContextParser, IContextParser, URLParts } from "../workspace/context-parser"; +import { URL } from "url"; +import { BitbucketServer, BitbucketServerApi } from "./bitbucket-server-api"; + +const DEFAULT_BRANCH = "master"; + +@injectable() +export class BitbucketServerContextParser extends AbstractContextParser implements IContextParser { + + @inject(BitbucketTokenHelper) protected readonly tokenHelper: BitbucketTokenHelper; + @inject(BitbucketServerApi) protected readonly api: BitbucketServerApi; + + public async handle(ctx: TraceContext, user: User, contextUrl: string): Promise { + const span = TraceContext.startSpan("BitbucketServerContextParser.handle", ctx); + + try { + const { resourceKind, host, owner, repoName, /*moreSegments, searchParams*/ } = await this.parseURL(user, contextUrl); + + return await this.handleNavigatorContext(ctx, user, resourceKind, host, owner, repoName); + } catch (e) { + span.addTags({ contextUrl }).log({ error: e }); + log.error({ userId: user.id }, "Error parsing Bitbucket context", e); + throw e; + } finally { + span.finish(); + } + } + + public async parseURL(user: User, contextUrl: string): Promise<{ resourceKind: string } & URLParts> { + const url = new URL(contextUrl); + const pathname = url.pathname.replace(/^\//, "").replace(/\/$/, ""); // pathname without leading and trailing slash + const segments = pathname.split('/'); + + const host = this.host; // as per contract, cf. `canHandle(user, contextURL)` + + const lenghtOfRelativePath = host.split("/").length - 1; // e.g. "123.123.123.123/gitlab" => length of 1 + if (lenghtOfRelativePath > 0) { + // remove segments from the path to be consider further, which belong to the relative location of the host + // cf. https://github.com/gitpod-io/gitpod/issues/2637 + segments.splice(0, lenghtOfRelativePath); + } + + const resourceKind = segments[0]; + const owner: string = segments[1]; + const repoName: string = segments[3]; + const moreSegmentsStart: number = 4; + const endsWithRepoName = segments.length === moreSegmentsStart; + const searchParams = url.searchParams; + return { + resourceKind, + host, + owner, + repoName: this.parseRepoName(repoName, endsWithRepoName), + moreSegments: endsWithRepoName ? [] : segments.slice(moreSegmentsStart), + searchParams + } + } + + public async fetchCommitHistory(ctx: TraceContext, user: User, contextUrl: string, commit: string, maxDepth: number): Promise { + return undefined; + } + + protected async handleNavigatorContext(ctx: TraceContext, user: User, resourceKind: string, host: string, owner: string, repoName: string, more: Partial = {}): Promise { + const span = TraceContext.startSpan("BitbucketServerContextParser.handleNavigatorContext", ctx); + try { + if (resourceKind !== "users" && resourceKind !== "projects") { + throw new Error("Only /users/ and /projects/ resources are supported."); + } + const repo = (await this.api.getRepository(user, { kind: resourceKind, userOrProject: owner, repositorySlug: repoName })); + + const repository = await this.toRepository(user, host, repo); + span.log({ "request.finished": "" }); + + if (!repo) { + throw await NotFoundError.create(await this.tokenHelper.getCurrentToken(user), user, this.config.host, owner, repoName); + } + + if (!more.revision) { + more.ref = more.ref || repository.defaultBranch; + } + more.refType = more.refType || "branch"; + + if (!more.revision) { + const tipCommitOnDefaultBranch = await this.api.getCommits(user, { kind: resourceKind, userOrProject: owner, repositorySlug: repoName, q: { limit: 1 } }); + const commits = tipCommitOnDefaultBranch?.values || []; + more.revision = commits[0]?.id || ""; + } + + return { + ...more, + title: `${owner}/${repoName} - ${more.ref || more.revision}${more.path ? ':' + more.path : ''}`, + repository, + } as NavigatorContext; + } catch (e) { + span.log({ error: e }); + log.error({ userId: user.id }, "Error parsing Bitbucket navigator request context", e); + throw e; + } finally { + span.finish(); + } + } + + + protected async toRepository(user: User, host: string, repo: BitbucketServer.Repository): Promise { + if (!repo) { + throw new Error('Unknown repository.'); + } + + const owner = repo.project.key; + const name = repo.name; + const cloneUrl = repo.links.clone.find(u => u.name === "http")?.href!; + + const result: Repository = { + cloneUrl, + host, + name, + owner, + private: !repo.public, + defaultBranch: DEFAULT_BRANCH, + } + + return result; + } + +} diff --git a/components/server/src/bitbucket-server/bitbucket-server-file-provider.ts b/components/server/src/bitbucket-server/bitbucket-server-file-provider.ts new file mode 100644 index 00000000000000..8a22763244d52e --- /dev/null +++ b/components/server/src/bitbucket-server/bitbucket-server-file-provider.ts @@ -0,0 +1,49 @@ +/** + * Copyright (c) 2020 Gitpod GmbH. All rights reserved. + * Licensed under the GNU Affero General Public License (AGPL). + * See License-AGPL.txt in the project root for license information. + */ + +import { Commit, Repository, User } from "@gitpod/gitpod-protocol"; +import { injectable } from 'inversify'; +import { FileProvider, MaybeContent } from "../repohost/file-provider"; + +@injectable() +export class BitbucketFileProvider implements FileProvider { + + public async getGitpodFileContent(commit: Commit, user: User): Promise { + return undefined; + // const yamlVersion1 = await Promise.all([ + // this.getFileContent(commit, user, '.gitpod.yml'), + // this.getFileContent(commit, user, '.gitpod') + // ]); + // return yamlVersion1.filter(f => !!f)[0]; + } + + public async getLastChangeRevision(repository: Repository, revisionOrBranch: string, user: User, path: string): Promise { + // try { + // const api = await this.apiFactory.create(user); + // const fileMetaData = (await api.repositories.readSrc({ workspace: repository.owner, repo_slug: repository.name, commit: revisionOrBranch, path, format: "meta" })).data; + // return (fileMetaData as any).commit.hash; + // } catch (err) { + // log.error({ userId: user.id }, err); + // throw new Error(`Could not fetch ${path} of repository ${repository.owner}/${repository.name}: ${err}`); + // } + return "f00"; + } + + public async getFileContent(commit: Commit, user: User, path: string) { + return undefined; + // if (!commit.revision) { + // return undefined; + // } + + // try { + // const api = await this.apiFactory.create(user); + // const contents = (await api.repositories.readSrc({ workspace: commit.repository.owner, repo_slug: commit.repository.name, commit: commit.revision, path })).data; + // return contents as string; + // } catch (err) { + // log.error({ userId: user.id }, err); + // } + } +} diff --git a/components/server/src/bitbucket-server/bitbucket-server-language-provider.ts b/components/server/src/bitbucket-server/bitbucket-server-language-provider.ts new file mode 100644 index 00000000000000..5347291c8c0421 --- /dev/null +++ b/components/server/src/bitbucket-server/bitbucket-server-language-provider.ts @@ -0,0 +1,27 @@ +/** + * Copyright (c) 2020 Gitpod GmbH. All rights reserved. + * Licensed under the GNU Affero General Public License (AGPL). + * See License-AGPL.txt in the project root for license information. + */ + +import { Repository, User } from "@gitpod/gitpod-protocol"; +import { injectable } from 'inversify'; +import { LanguagesProvider } from '../repohost/languages-provider'; + +@injectable() +export class BitbucketLanguagesProvider implements LanguagesProvider { + + async getLanguages(repository: Repository, user: User): Promise { + // try { + // const api = await this.apiFactory.create(user); + // const repo = await api.repositories.get({ workspace: repository.owner, repo_slug: repository.name }); + // const language = repo.data.language; + // if (language) { + // return { [language]: 100.0 }; + // } + // } catch (e) { + // log.warn({ userId: user.id }, "Could not get languages of Bitbucket repo."); + // } + return {}; + } +} diff --git a/components/server/src/bitbucket-server/bitbucket-server-oauth-scopes.ts b/components/server/src/bitbucket-server/bitbucket-server-oauth-scopes.ts new file mode 100644 index 00000000000000..759b97272051b3 --- /dev/null +++ b/components/server/src/bitbucket-server/bitbucket-server-oauth-scopes.ts @@ -0,0 +1,25 @@ +/** + * Copyright (c) 2021 Gitpod GmbH. All rights reserved. + * Licensed under the GNU Affero General Public License (AGPL). + * See License-AGPL.txt in the project root for license information. + */ + +// https://confluence.atlassian.com/bitbucketserver/bitbucket-oauth-2-0-provider-api-1108483661.html#BitbucketOAuth2.0providerAPI-scopesScopes + +export namespace BitbucketServerOAuthScopes { + /** View projects and repositories that are publicly accessible, including pulling code and cloning repositories. */ + export const PUBLIC_REPOS = "PUBLIC_REPOS"; + /** View projects and repositories the user account can view, including pulling code, cloning, and forking repositories. Create and comment on pull requests. */ + export const REPOSITORY_READ = "REPO_READ"; + /** Push over https, fork repo */ + export const REPOSITORY_WRITE = "REPO_WRITE"; + + export const ALL = [PUBLIC_REPOS, REPOSITORY_READ, REPOSITORY_WRITE]; + + export const Requirements = { + /** + * Minimal required permission. + */ + DEFAULT: ALL + } +} diff --git a/components/server/src/bitbucket-server/bitbucket-server-repository-provider.ts b/components/server/src/bitbucket-server/bitbucket-server-repository-provider.ts new file mode 100644 index 00000000000000..0931a1c140604c --- /dev/null +++ b/components/server/src/bitbucket-server/bitbucket-server-repository-provider.ts @@ -0,0 +1,103 @@ +/** + * Copyright (c) 2022 Gitpod GmbH. All rights reserved. + * Licensed under the GNU Affero General Public License (AGPL). + * See License-AGPL.txt in the project root for license information. + */ + +import { Branch, CommitInfo, Repository, User } from "@gitpod/gitpod-protocol"; +import { injectable } from 'inversify'; +import { RepositoryProvider } from '../repohost/repository-provider'; + +@injectable() +export class BitbucketRepositoryProvider implements RepositoryProvider { + + getUserRepos(user: User): Promise { + throw new Error("getUserRepos not implemented."); + } + + async getRepo(user: User, owner: string, name: string): Promise { + // const api = await this.apiFactory.create(user); + // const repo = (await api.repositories.get({ workspace: owner, repo_slug: name })).data; + // let cloneUrl = repo.links!.clone!.find((x: any) => x.name === "https")!.href!; + // if (cloneUrl) { + // const url = new URL(cloneUrl); + // url.username = ''; + // cloneUrl = url.toString(); + // } + // const host = RepoURL.parseRepoUrl(cloneUrl)!.host; + // const description = repo.description; + // const avatarUrl = repo.owner!.links!.avatar!.href; + // const webUrl = repo.links!.html!.href; + // const defaultBranch = repo.mainbranch?.name; + // return { host, owner, name, cloneUrl, description, avatarUrl, webUrl, defaultBranch }; + throw new Error("getRepo unimplemented"); + } + + async getBranch(user: User, owner: string, repo: string, branchName: string): Promise { + // const api = await this.apiFactory.create(user); + // const response = await api.repositories.getBranch({ + // workspace: owner, + // repo_slug: repo, + // name: branchName + // }) + + // const branch = response.data; + + // return { + // htmlUrl: branch.links?.html?.href!, + // name: branch.name!, + // commit: { + // sha: branch.target?.hash!, + // author: branch.target?.author?.user?.display_name!, + // authorAvatarUrl: branch.target?.author?.user?.links?.avatar?.href, + // authorDate: branch.target?.date!, + // commitMessage: branch.target?.message || "missing commit message", + // } + // }; + throw new Error("getBranch unimplemented"); + } + + async getBranches(user: User, owner: string, repo: string): Promise { + const branches: Branch[] = []; + // const api = await this.apiFactory.create(user); + // const response = await api.repositories.listBranches({ + // workspace: owner, + // repo_slug: repo, + // sort: "target.date" + // }) + + // for (const branch of response.data.values!) { + // branches.push({ + // htmlUrl: branch.links?.html?.href!, + // name: branch.name!, + // commit: { + // sha: branch.target?.hash!, + // author: branch.target?.author?.user?.display_name!, + // authorAvatarUrl: branch.target?.author?.user?.links?.avatar?.href, + // authorDate: branch.target?.date!, + // commitMessage: branch.target?.message || "missing commit message", + // } + // }); + // } + + return branches; + } + + async getCommitInfo(user: User, owner: string, repo: string, ref: string): Promise { + return undefined; + // const api = await this.apiFactory.create(user); + // const response = await api.commits.get({ + // workspace: owner, + // repo_slug: repo, + // commit: ref + // }) + // const commit = response.data; + // return { + // sha: commit.hash!, + // author: commit.author?.user?.display_name!, + // authorDate: commit.date!, + // commitMessage: commit.message || "missing commit message", + // authorAvatarUrl: commit.author?.user?.links?.avatar?.href, + // }; + } +} diff --git a/yarn.lock b/yarn.lock index 6faa6a3cf4591f..31b9133750661a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,6 +2,18 @@ # yarn lockfile v1 +"@atlassian/bitbucket-server@^0.0.6": + version "0.0.6" + resolved "https://registry.yarnpkg.com/@atlassian/bitbucket-server/-/bitbucket-server-0.0.6.tgz#7a0678264083c851e50a66216e66021efe1638cf" + integrity sha512-92EpKlSPw0ZZXiS4Qt+1DKuDxSIntL2j8Q0CWc8o/nUNOJAW/D9szIgcef5VPTbVslHeb2C3gBSMNnETdykdmQ== + dependencies: + before-after-hook "^1.1.0" + btoa-lite "^1.0.0" + debug "^3.1.0" + is-plain-object "^2.0.4" + node-fetch "^2.1.2" + url-template "^2.0.8" + "@babel/code-frame@7.10.4": version "7.10.4" resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.10.4.tgz#168da1a36e90da68ae8d49c0f1b48c7c6249213a" @@ -4580,6 +4592,11 @@ bcrypt-pbkdf@^1.0.0: dependencies: tweetnacl "^0.14.3" +before-after-hook@^1.1.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/before-after-hook/-/before-after-hook-1.4.0.tgz#2b6bf23dca4f32e628fd2747c10a37c74a4b484d" + integrity sha512-l5r9ir56nda3qu14nAXIlyq1MmUSs0meCIaFAh8HwkFwP1F8eToOuS3ah2VAHHcY04jaYD7FpJC5JTXHYRbkzg== + before-after-hook@^2.1.0, before-after-hook@^2.2.0: version "2.2.2" resolved "https://registry.yarnpkg.com/before-after-hook/-/before-after-hook-2.2.2.tgz#a6e8ca41028d90ee2c24222f201c90956091613e" @@ -12029,6 +12046,13 @@ node-emoji@^1.11.0: dependencies: lodash "^4.17.21" +node-fetch@^2.1.2: + version "2.6.7" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad" + integrity sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ== + dependencies: + whatwg-url "^5.0.0" + node-fetch@^2.6.0, node-fetch@^2.6.1, node-fetch@^2.6.5: version "2.6.6" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.6.tgz#1751a7c01834e8e1697758732e9efb6eeadfaf89"