Skip to content

Commit 709a825

Browse files
committed
[bitbucket] enable projects
1 parent 38c3d8e commit 709a825

File tree

8 files changed

+161
-36
lines changed

8 files changed

+161
-36
lines changed

components/dashboard/src/projects/NewProject.tsx

Lines changed: 8 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ export default function NewProject() {
143143
}, [selectedAccount]);
144144

145145
useEffect(() => {
146-
if (!selectedProviderHost || isBitbucket()) {
146+
if (!selectedProviderHost) {
147147
return;
148148
}
149149
(async () => {
@@ -164,12 +164,11 @@ export default function NewProject() {
164164
}, [project, sourceOfConfig]);
165165

166166
const isGitHub = () => selectedProviderHost === "github.com";
167-
const isBitbucket = () => selectedProviderHost === "bitbucket.org";
168167

169168
const updateReposInAccounts = async (installationId?: string) => {
170169
setLoaded(false);
171170
setReposInAccounts([]);
172-
if (!selectedProviderHost || isBitbucket()) {
171+
if (!selectedProviderHost) {
173172
return [];
174173
}
175174
try {
@@ -226,7 +225,7 @@ export default function NewProject() {
226225
}
227226

228227
const createProject = async (teamOrUser: Team | User, repo: ProviderRepository) => {
229-
if (!selectedProviderHost || isBitbucket()) {
228+
if (!selectedProviderHost) {
230229
return;
231230
}
232231
const repoSlug = repo.path || repo.name;
@@ -300,7 +299,7 @@ export default function NewProject() {
300299
const showSearchInput = !!repoSearchFilter || filteredRepos.length > 0;
301300

302301
const renderRepos = () => (<>
303-
{!isBitbucket() && <p className="text-gray-500 text-center text-base">Select a Git repository on <strong>{selectedProviderHost}</strong>. (<a className="gp-link cursor-pointer" onClick={() => setShowGitProviders(true)}>change</a>)</p>}
302+
{<p className="text-gray-500 text-center text-base">Select a Git repository on <strong>{selectedProviderHost}</strong>. (<a className="gp-link cursor-pointer" onClick={() => setShowGitProviders(true)}>change</a>)</p>}
304303
<div className={`mt-10 border rounded-xl border-gray-100 dark:border-gray-800 flex-col`}>
305304
<div className="px-8 pt-8 flex flex-col space-y-2" data-analytics='{"label":"Identity"}'>
306305
<ContextMenu classes="w-full left-0 cursor-pointer" menuEntries={getDropDownEntries(accounts)}>
@@ -400,11 +399,11 @@ export default function NewProject() {
400399
setSelectedProviderHost(host);
401400
}
402401

403-
if (!loaded && !isBitbucket()) {
402+
if (!loaded) {
404403
return renderLoadingState();
405404
}
406405

407-
if (showGitProviders || isBitbucket()) {
406+
if (showGitProviders) {
408407
return (<GitProviders onHostSelected={onGitProviderSeleted} authProviders={authProviders} />);
409408
}
410409

@@ -455,18 +454,6 @@ export default function NewProject() {
455454
</>)
456455
};
457456

458-
const renderBitbucketWarning = () => {
459-
return (
460-
<div className="mt-16 flex space-x-2 py-6 px-6 w-96 justify-betweeen bg-gitpod-kumquat-light rounded-xl">
461-
<div className="pr-3 self-center w-6">
462-
<img src={exclamation} />
463-
</div>
464-
<div className="flex-1 flex flex-col">
465-
<p className="text-gitpod-red text-sm">Bitbucket support for projects is not available yet. Follow <a className="gp-link" href="https://github.com/gitpod-io/gitpod/issues/5980">#5980</a> for updates.</p>
466-
</div>
467-
</div>);
468-
}
469-
470457
const onNewWorkspace = async () => {
471458
const redirectToNewWorkspace = () => {
472459
// instead of `history.push` we want forcibly to redirect here in order to avoid a following redirect from `/` -> `/projects` (cf. App.tsx)
@@ -491,8 +478,6 @@ export default function NewProject() {
491478
{selectedRepo && selectedTeamOrUser && (<div></div>)}
492479
</>
493480

494-
{isBitbucket() && renderBitbucketWarning()}
495-
496481
</div>);
497482
} else {
498483
const projectLink = User.is(selectedTeamOrUser) ? `/projects/${project.slug}` : `/t/${selectedTeamOrUser?.slug}/${project.slug}`;
@@ -552,8 +537,8 @@ function GitProviders(props: {
552537
});
553538
}
554539

555-
// for now we exclude bitbucket.org and GitHub Enterprise
556-
const filteredProviders = () => props.authProviders.filter(p => p.host === "github.com" || p.authProviderType === "GitLab");
540+
// for now we exclude GitHub Enterprise
541+
const filteredProviders = () => props.authProviders.filter(p => p.host === "github.com" || p.host === "bitbucket.org" || p.authProviderType === "GitLab");
557542

558543
return (
559544
<div className="mt-8 border rounded-t-xl border-gray-100 dark:border-gray-800 flex-col">
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
/**
2+
* Copyright (c) 2020 Gitpod GmbH. All rights reserved.
3+
* Licensed under the Gitpod Enterprise Source Code License,
4+
* See License.enterprise.txt in the project root folder.
5+
*/
6+
7+
import { AuthProviderInfo, ProviderRepository, User } from "@gitpod/gitpod-protocol";
8+
import { inject, injectable } from "inversify";
9+
import { TokenProvider } from "../../../src/user/token-provider";
10+
import { Bitbucket } from "bitbucket";
11+
12+
@injectable()
13+
export class BitbucketAppSupport {
14+
15+
@inject(TokenProvider) protected readonly tokenProvider: TokenProvider;
16+
17+
async getProviderRepositoriesForUser(params: { user: User, provider: AuthProviderInfo }): Promise<ProviderRepository[]> {
18+
const token = await this.tokenProvider.getTokenForHost(params.user, params.provider.host);
19+
const oauthToken = token.value;
20+
21+
const api = new Bitbucket({
22+
baseUrl: `https://api.${params.provider.host}/2.0`,
23+
auth: {
24+
token: oauthToken
25+
}
26+
});
27+
28+
const result: ProviderRepository[] = [];
29+
const ownersRepos: ProviderRepository[] = [];
30+
31+
const identity = params.user.identities.find(i => i.authProviderId === params.provider.authProviderId);
32+
if (!identity) {
33+
return result;
34+
}
35+
const usersGitLabAccount = identity.authName;
36+
37+
const workspaces = (await api.workspaces.getWorkspaces({ pagelen: 100 })).data.values?.map(w => w.slug!) || [];
38+
39+
const reposPromise = Promise.all(workspaces.map(workspace => api.repositories.list({
40+
workspace,
41+
pagelen: 100,
42+
role: "admin"
43+
}).catch(e => {
44+
45+
})));
46+
47+
const reposInWorkspace = await reposPromise;
48+
for (const repos of reposInWorkspace) {
49+
if (repos) {
50+
for (const repo of (repos.data.values || [])) {
51+
const fullName = repo.full_name!;
52+
const cloneUrl = repo.links!.clone!.find((x: any) => x.name === "https")!.href!;
53+
const updatedAt = repo.updated_on!;
54+
const accountAvatarUrl = repo.links!.avatar?.href!;
55+
const account = fullName.split("/")[0];
56+
57+
(account === usersGitLabAccount ? ownersRepos : result).push({
58+
name: repo.name!,
59+
account,
60+
cloneUrl,
61+
updatedAt,
62+
accountAvatarUrl,
63+
})
64+
}
65+
}
66+
}
67+
68+
// put owner's repos first. the frontend will pick first account to continue with
69+
result.unshift(...ownersRepos);
70+
return result;
71+
}
72+
73+
}

components/server/ee/src/container-module.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ import { GitHubAppSupport } from "./github/github-app-support";
5050
import { GitLabAppSupport } from "./gitlab/gitlab-app-support";
5151
import { Config } from "../../src/config";
5252
import { SnapshotService } from "./workspace/snapshot-service";
53+
import { BitbucketAppSupport } from "./bitbucket/bitbucket-app-support";
5354

5455
export const productionEEContainerModule = new ContainerModule((bind, unbind, isBound, rebind) => {
5556
rebind(Server).to(ServerEE).inSingletonScope();
@@ -68,6 +69,7 @@ export const productionEEContainerModule = new ContainerModule((bind, unbind, is
6869
bind(GitLabApp).toSelf().inSingletonScope();
6970
bind(GitLabAppSupport).toSelf().inSingletonScope();
7071
bind(BitbucketApp).toSelf().inSingletonScope();
72+
bind(BitbucketAppSupport).toSelf().inSingletonScope();
7173

7274
bind(LicenseEvaluator).toSelf().inSingletonScope();
7375
bind(LicenseKeySource).to(DBLicenseKeySource).inSingletonScope();

components/server/ee/src/prebuilds/bitbucket-service.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,17 @@ export class BitbucketService extends RepositoryService {
2727

2828
async canInstallAutomatedPrebuilds(user: User, cloneUrl: string): Promise<boolean> {
2929
const { host } = await this.bitbucketContextParser.parseURL(user, cloneUrl);
30-
return host === this.authProviderConfig.host;
30+
if (host !== this.authProviderConfig.host) {
31+
return false;
32+
}
33+
// TODO: change to actaully check for admin permissions
34+
35+
// const { owner, repoName } = await this.bitbucketContextParser.parseURL(user, cloneUrl);
36+
// const api = await this.api.create(user);
37+
// api.user.listPermissionsForRepos({
38+
// q: `repository.full_name="#{owner}/${repoName}"" AND`
39+
// })
40+
return true;
3141
}
3242

3343
async installAutomatedPrebuilds(user: User, cloneUrl: string): Promise<void> {

components/server/ee/src/workspace/gitpod-server-impl.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ import { Config } from "../../../src/config";
4141
import { SnapshotService, WaitForSnapshotOptions } from "./snapshot-service";
4242
import { SafePromise } from "@gitpod/gitpod-protocol/lib/util/safe-promise";
4343
import { ClientMetadata } from "../../../src/websocket/websocket-connection-manager";
44+
import { BitbucketAppSupport } from "../bitbucket/bitbucket-app-support";
4445

4546
@injectable()
4647
export class GitpodServerEEImpl extends GitpodServerImpl {
@@ -68,6 +69,7 @@ export class GitpodServerEEImpl extends GitpodServerImpl {
6869

6970
@inject(GitHubAppSupport) protected readonly githubAppSupport: GitHubAppSupport;
7071
@inject(GitLabAppSupport) protected readonly gitLabAppSupport: GitLabAppSupport;
72+
@inject(BitbucketAppSupport) protected readonly bitbucketAppSupport: BitbucketAppSupport;
7173

7274
@inject(Config) protected readonly config: Config;
7375

@@ -1429,6 +1431,8 @@ export class GitpodServerEEImpl extends GitpodServerImpl {
14291431

14301432
if (providerHost === "github.com") {
14311433
repositories.push(...(await this.githubAppSupport.getProviderRepositoriesForUser({ user, ...params })));
1434+
} else if (providerHost === "bitbucket.org" && provider) {
1435+
repositories.push(...(await this.bitbucketAppSupport.getProviderRepositoriesForUser({ user, provider })));
14321436
} else if (provider?.authProviderType === "GitLab") {
14331437
repositories.push(...(await this.gitLabAppSupport.getProviderRepositoriesForUser({ user, provider })));
14341438
} else {

components/server/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@
4444
"@probot/get-private-key": "^1.1.1",
4545
"amqplib": "^0.8.0",
4646
"base-64": "^1.0.0",
47-
"bitbucket": "^2.4.2",
47+
"bitbucket": "^2.7.0",
4848
"body-parser": "^1.18.2",
4949
"cookie": "^0.4.1",
5050
"cookie-parser": "^1.4.5",

components/server/src/bitbucket/bitbucket-repository-provider.ts

Lines changed: 58 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -26,18 +26,69 @@ export class BitbucketRepositoryProvider implements RepositoryProvider {
2626
return { host, owner, name, cloneUrl, description, avatarUrl, webUrl };
2727
}
2828

29-
async getBranch(user: User, owner: string, repo: string, branch: string): Promise<Branch> {
30-
// todo
31-
throw new Error("not implemented");
29+
async getBranch(user: User, owner: string, repo: string, branchName: string): Promise<Branch> {
30+
const api = await this.apiFactory.create(user);
31+
const response = await api.repositories.getBranch({
32+
workspace: owner,
33+
repo_slug: repo,
34+
name: branchName
35+
})
36+
37+
const branch = response.data;
38+
39+
return {
40+
htmlUrl: branch.links?.html?.href!,
41+
name: branch.name!,
42+
commit: {
43+
sha: branch.target?.hash!,
44+
author: branch.target?.author?.user?.display_name!,
45+
authorAvatarUrl: branch.target?.author?.user?.links?.avatar?.href,
46+
authorDate: branch.target?.date!,
47+
commitMessage: branch.target?.message || "missing commit message",
48+
}
49+
};
3250
}
3351

3452
async getBranches(user: User, owner: string, repo: string): Promise<Branch[]> {
35-
// todo
36-
return [];
53+
const branches: Branch[] = [];
54+
const api = await this.apiFactory.create(user);
55+
const response = await api.repositories.listBranches({
56+
workspace: owner,
57+
repo_slug: repo,
58+
sort: "target.date"
59+
})
60+
61+
for (const branch of response.data.values!) {
62+
branches.push({
63+
htmlUrl: branch.links?.html?.href!,
64+
name: branch.name!,
65+
commit: {
66+
sha: branch.target?.hash!,
67+
author: branch.target?.author?.user?.display_name!,
68+
authorAvatarUrl: branch.target?.author?.user?.links?.avatar?.href,
69+
authorDate: branch.target?.date!,
70+
commitMessage: branch.target?.message || "missing commit message",
71+
}
72+
});
73+
}
74+
75+
return branches;
3776
}
3877

3978
async getCommitInfo(user: User, owner: string, repo: string, ref: string): Promise<CommitInfo | undefined> {
40-
// todo
41-
return undefined;
79+
const api = await this.apiFactory.create(user);
80+
const response = await api.commits.get({
81+
workspace: owner,
82+
repo_slug: repo,
83+
commit: ref
84+
})
85+
const commit = response.data;
86+
return {
87+
sha: commit.hash!,
88+
author: commit.author?.user?.display_name!,
89+
authorDate: commit.date!,
90+
commitMessage: commit.message || "missing commit message",
91+
authorAvatarUrl: commit.author?.user?.links?.avatar?.href,
92+
};
4293
}
4394
}

yarn.lock

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5041,10 +5041,10 @@ [email protected]:
50415041
resolved "https://registry.yarnpkg.com/bintrees/-/bintrees-1.0.1.tgz#0e655c9b9c2435eaab68bf4027226d2b55a34524"
50425042
integrity sha1-DmVcm5wkNeqraL9AJyJtK1WjRSQ=
50435043

5044-
bitbucket@^2.4.2:
5045-
version "2.6.3"
5046-
resolved "https://registry.yarnpkg.com/bitbucket/-/bitbucket-2.6.3.tgz#e7aa030406720e24c19a40701506b1c366daf544"
5047-
integrity sha512-t23mlPsCchl+7TCGGHqI4Up++mnGd6smaKsNe/t+kGlkGfIzm+QmVdWvBboHl8Nyequ8Wm0Whi2lKq9qmfJmxA==
5044+
bitbucket@^2.7.0:
5045+
version "2.7.0"
5046+
resolved "https://registry.yarnpkg.com/bitbucket/-/bitbucket-2.7.0.tgz#fd11b19a42cc9b89f6a899ff669fd1575183a5b3"
5047+
integrity sha512-6fw3MzXeFp3TLmo6jF7IWFn9tFpFKpzCpDjKek9s5EY559Ff3snbu2hmS5ZKmR7D0XomPbIT0dBN1juoJ/gGyA==
50485048
dependencies:
50495049
before-after-hook "^2.1.0"
50505050
deepmerge "^4.2.2"

0 commit comments

Comments
 (0)