gitpod/components/server/src/github/github-repository-provider.ts
2022-03-10 15:46:17 +05:30

199 lines
8.1 KiB
TypeScript

/**
* 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 { injectable, inject } from 'inversify';
import { User, Repository } from "@gitpod/gitpod-protocol"
import { GitHubGraphQlEndpoint, GitHubRestApi } from "./api";
import { RepositoryProvider } from '../repohost/repository-provider';
import { RepoURL } from '../repohost/repo-url';
import { Branch, CommitInfo } from '@gitpod/gitpod-protocol/src/protocol';
@injectable()
export class GithubRepositoryProvider implements RepositoryProvider {
@inject(GitHubRestApi) protected readonly github: GitHubRestApi;
@inject(GitHubGraphQlEndpoint) protected readonly githubQueryApi: GitHubGraphQlEndpoint;
async getRepo(user: User, owner: string, repo: string): Promise<Repository> {
const repository = await this.github.getRepository(user, { owner, repo });
const cloneUrl = repository.clone_url;
const host = RepoURL.parseRepoUrl(cloneUrl)!.host;
const description = repository.description;
const avatarUrl = repository.owner.avatar_url;
const webUrl = repository.html_url;
const defaultBranch = repository.default_branch;
return { host, owner, name: repo, cloneUrl, description, avatarUrl, webUrl, defaultBranch };
}
async getBranch(user: User, owner: string, repo: string, branch: string): Promise<Branch> {
const result = await this.github.getBranch(user, { repo, owner, branch });
return result;
}
async getBranches(user: User, owner: string, repo: string): Promise<Branch[]> {
const branches: Branch[] = [];
let endCursor: string | undefined;
let hasNextPage: boolean = true;
while (hasNextPage) {
const result: any = await this.githubQueryApi.runQuery(user, `
query {
repository(name: "${repo}", owner: "${owner}") {
refs(refPrefix: "refs/heads/", orderBy: {field: TAG_COMMIT_DATE, direction: ASC}, first: 100 ${endCursor ? `, after: "${endCursor}"` : ""}) {
nodes {
name
target {
... on Commit {
oid
history(first: 1) {
nodes {
messageHeadline
committedDate
oid
authoredDate
tree {
id
}
treeUrl
author {
avatarUrl
name
date
}
}
}
}
}
}
pageInfo {
endCursor
hasNextPage
hasPreviousPage
startCursor
}
totalCount
}
}
}
`);
endCursor = result.data.repository?.refs?.pageInfo?.endCursor;
hasNextPage = result.data.repository?.refs?.pageInfo?.hasNextPage;
const nodes = result.data.repository?.refs?.nodes;
for (const node of (nodes || [])) {
branches.push({
name: node.name,
commit: {
sha: node.target.oid,
commitMessage: node.target.history.nodes[0].messageHeadline,
author: node.target.history.nodes[0].author.name,
authorAvatarUrl: node.target.history.nodes[0].author.avatarUrl,
authorDate: node.target.history.nodes[0].author.date,
},
htmlUrl: node.target.history.nodes[0].treeUrl.replace(node.target.oid, node.name)
});
}
}
return branches;
}
async getCommitInfo(user: User, owner: string, repo: string, ref: string): Promise<CommitInfo | undefined> {
try {
return await this.github.getCommit(user, { repo, owner, ref });
} catch (error) {
console.error(error);
return undefined;
}
}
public async getCommitHistory(user: User, owner: string, repo: string, ref: string, maxDepth: number = 100): Promise<string[]> {
try {
if (ref.length != 40) {
throw new Error(`Invalid commit ID ${ref}.`);
}
// TODO(janx): To get more results than GitHub API's max page size (seems to be 100), pagination should be handled.
// These additional history properties may be helfpul:
// totalCount,
// pageInfo {
// haxNextPage,
// },
const result: any = await this.githubQueryApi.runQuery(user, `
query {
repository(name: "${repo}", owner: "${owner}") {
object(oid: "${ref}") {
... on Commit {
history(first: ${maxDepth}) {
edges {
node {
oid
}
}
}
}
}
}
}
`);
if (result.data.repository === null) {
throw new Error(`couldn't find repository ${owner}/${repo} on ${this.github.baseURL}`);
}
const commit = result.data.repository.object;
if (commit === null) {
throw new Error(`Couldn't find commit ${ref} in repository ${owner}/${repo}.`);
}
return commit.history.edges.slice(1).map((e: any) => e.node.oid) || [];
} catch (e) {
console.error(e);
return [];
}
}
async getUserRepos(user: User): Promise<string[]> {
// Hint: Use this to get richer results:
// node {
// nameWithOwner
// shortDescriptionHTML(limit: 120)
// url
// }
const result: any = await this.githubQueryApi.runQuery(user, `
query {
viewer {
repositoriesContributedTo(includeUserRepositories: true, first: 100) {
edges {
node {
url
}
}
}
}
}`);
return (result.data.viewer?.repositoriesContributedTo?.edges || []).map((edge: any) => edge.node.url)
}
async hasReadAccess(user: User, owner: string, repo: string): Promise<boolean> {
try {
// If you have no "viewerPermission" on a repository you may not read it
// Ref: https://docs.github.com/en/graphql/reference/enums#repositorypermission
const result: any = await this.githubQueryApi.runQuery(user, `
query {
repository(name: "${repo}", owner: "${owner}") {
viewerPermission
}
}
`);
return result.data.repository !== null;
} catch (err) {
return false;
}
}
}