Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/api/api.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,7 @@ export interface Repository {

addRemote(name: string, url: string): Promise<void>;
removeRemote(name: string): Promise<void>;
renameRemote(name: string, newName: string): Promise<void>;

fetch(remote?: string, ref?: string, depth?: number): Promise<void>;
pull(unshallow?: boolean): Promise<void>;
Expand Down
109 changes: 100 additions & 9 deletions src/github/githubRepository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,16 @@ import { Octokit } from '@octokit/rest';
import * as OctokitTypes from '@octokit/types';
import Logger from '../common/logger';
import { Remote, parseRemote } from '../common/remote';
import { IAccount, RepoAccessAndMergeMethods, PullRequestMergeability, IMilestone } from './interface';
import { IAccount, RepoAccessAndMergeMethods, PullRequestMergeability, IMilestone, Issue } from './interface';
import { PullRequestModel } from './pullRequestModel';
import { CredentialStore, GitHub } from './credentials';
import { AuthenticationError } from '../common/authentication';
import { QueryOptions, MutationOptions, ApolloQueryResult, NetworkStatus, FetchResult } from 'apollo-boost';
import { PRCommentController } from '../view/prCommentController';
import { convertRESTPullRequestToRawPullRequest, parseMergeability, parseGraphQLPullRequest, parseGraphQLIssue, parseMilestone } from './utils';
import { PullRequestResponse, MentionableUsersResponse, AssignableUsersResponse, MilestoneIssuesResponse, IssuesResponse, IssuesSearchResponse, MaxIssueResponse } from './graphql';
import { convertRESTPullRequestToRawPullRequest, parseMergeability, parseGraphQLPullRequest, parseGraphQLIssue, parseMilestone, parseGraphQLViewerPermission } from './utils';
import { PullRequestResponse, MentionableUsersResponse, AssignableUsersResponse, MilestoneIssuesResponse, IssuesResponse, IssuesSearchResponse, MaxIssueResponse, ViewerPermissionResponse, ForkDetailsResponse } from './graphql';
import { IssueModel } from './issueModel';
import { Protocol } from '../common/protocol';
const defaultSchema = require('./queries.gql');

export const PULL_REQUEST_PAGE_SIZE = 20;
Expand All @@ -42,6 +43,23 @@ export interface MilestoneData extends ItemsData {
hasMorePages: boolean;
}

export enum ViewerPermission {
Unknown = 'unknown',
Admin = 'ADMIN',
Maintain = 'MAINTAIN',
Read = 'READ',
Triage = 'TRIAGE',
Write = 'WRITE'
}

export interface ForkDetails {
isFork: boolean;
parent: {
owner: string,
name: string
};
}

export interface IMetadata extends OctokitTypes.ReposGetResponseData {
currentUser: any;
}
Expand Down Expand Up @@ -278,6 +296,16 @@ export class GitHubRepository implements vscode.Disposable {
}
}

private getRepoForIssue(githubRepository: GitHubRepository, parsedIssue: Issue): GitHubRepository {
if (parsedIssue.repositoryName && parsedIssue.repositoryUrl &&
((githubRepository.remote.owner !== parsedIssue.repositoryOwner) ||
(githubRepository.remote.repositoryName !== parsedIssue.repositoryName))) {
const remote = new Remote(parsedIssue.repositoryName, parsedIssue.repositoryUrl, new Protocol(parsedIssue.repositoryUrl));
githubRepository = new GitHubRepository(remote, this._credentialStore);
}
return githubRepository;
}

async getIssuesForUserByMilestone(page?: number): Promise<MilestoneData | undefined> {
try {
Logger.debug(`Fetch all issues - enter`, GitHubRepository.ID);
Expand All @@ -293,13 +321,16 @@ export class GitHubRepository implements vscode.Disposable {
Logger.debug(`Fetch all issues - done`, GitHubRepository.ID);

const milestones: { milestone: IMilestone, issues: IssueModel[] }[] = [];
let githubRepository: GitHubRepository = this;
if (data && data.repository.milestones && data.repository.milestones.nodes) {
data.repository.milestones.nodes.forEach(raw => {
const milestone = parseMilestone(raw);
if (milestone) {
const issues: IssueModel[] = [];
raw.issues.edges.forEach(issue => {
issues.push(new IssueModel(this, remote, parseGraphQLIssue(issue.node, this)));
const parsedIssue = parseGraphQLIssue(issue.node, this);
githubRepository = this.getRepoForIssue(githubRepository, parsedIssue);
issues.push(new IssueModel(githubRepository, githubRepository.remote, parsedIssue));
});
milestones.push({ milestone, issues });
}
Expand Down Expand Up @@ -330,10 +361,13 @@ export class GitHubRepository implements vscode.Disposable {
Logger.debug(`Fetch issues without milestone - done`, GitHubRepository.ID);

const issues: IssueModel[] = [];
let githubRepository: GitHubRepository = this;
if (data && data.repository.issues.edges) {
data.repository.issues.edges.forEach(raw => {
if (raw.node.id) {
issues.push(new IssueModel(this, remote, parseGraphQLIssue(raw.node, this)));
const parsedIssue = parseGraphQLIssue(raw.node, this);
githubRepository = this.getRepoForIssue(githubRepository, parsedIssue);
issues.push(new IssueModel(githubRepository, githubRepository.remote, parsedIssue));
}
});
}
Expand All @@ -350,7 +384,7 @@ export class GitHubRepository implements vscode.Disposable {
async getIssues(page?: number, queryString?: string): Promise<IssueData | undefined> {
try {
Logger.debug(`Fetch issues with query - enter`, GitHubRepository.ID);
const { query, remote, schema } = await this.ensure();
const { query, schema } = await this.ensure();
const { data } = await query<IssuesSearchResponse>({
query: schema.Issues,
variables: {
Expand All @@ -360,10 +394,13 @@ export class GitHubRepository implements vscode.Disposable {
Logger.debug(`Fetch issues with query - done`, GitHubRepository.ID);

const issues: IssueModel[] = [];
let githubRepository: GitHubRepository = this;
if (data && data.search.edges) {
data.search.edges.forEach(raw => {
if (raw.node.id) {
issues.push(new IssueModel(this, remote, parseGraphQLIssue(raw.node, this)));
const parsedIssue = parseGraphQLIssue(raw.node, this);
githubRepository = this.getRepoForIssue(githubRepository, parsedIssue);
issues.push(new IssueModel(githubRepository, githubRepository.remote, parsedIssue));
}
});
}
Expand Down Expand Up @@ -400,6 +437,56 @@ export class GitHubRepository implements vscode.Disposable {
}
}

async getViewerPermission(): Promise<ViewerPermission> {
try {
Logger.debug(`Fetch viewer permission - enter`, GitHubRepository.ID);
const { query, remote, schema } = await this.ensure();
const { data } = await query<ViewerPermissionResponse>({
query: schema.GetViewerPermission,
variables: {
owner: remote.owner,
name: remote.repositoryName
}
});
Logger.debug(`Fetch viewer permission - done`, GitHubRepository.ID);
return parseGraphQLViewerPermission(data);
} catch (e) {
Logger.appendLine(`GithubRepository> Unable to fetch viewer permission: ${e}`);
return ViewerPermission.Unknown;
}
}

async fork(): Promise<string | undefined> {
try {
Logger.debug(`Fork repository`, GitHubRepository.ID);
const { octokit, remote } = await this.ensure();
const result = await octokit.repos.createFork({ owner: remote.owner, repo: remote.repositoryName });
return result.data.clone_url;
} catch (e) {
Logger.appendLine(`GitHubRepository> Forking repository failed: ${e}`);
return undefined;
}
}

async getRepositoryForkDetails(): Promise<ForkDetails | undefined> {
try {
Logger.debug(`Fetch viewer permission - enter`, GitHubRepository.ID);
const { query, remote, schema } = await this.ensure();
const { data } = await query<ForkDetailsResponse>({
query: schema.GetViewerPermission,
variables: {
owner: remote.owner,
name: remote.repositoryName
}
});
Logger.debug(`Fetch viewer permission - done`, GitHubRepository.ID);
return data.repository;
} catch (e) {
Logger.appendLine(`GithubRepository> Unable to fetch viewer permission: ${e}`);
return;
}
}

async getAuthenticatedUser(): Promise<string> {
const { octokit } = await this.ensure();
const user = await octokit.users.getAuthenticated({});
Expand Down Expand Up @@ -495,8 +582,12 @@ export class GitHubRepository implements vscode.Disposable {
}
});
Logger.debug(`Fetch issue ${id} - done`, GitHubRepository.ID);

return new IssueModel(this, remote, parseGraphQLPullRequest(data, this));
let githubRepository: GitHubRepository = this;
const parsedIssue = parseGraphQLPullRequest(data, this);
if ((githubRepository.remote.url !== parsedIssue.repositoryUrl) && (parsedIssue.repositoryName && parsedIssue.repositoryUrl)) {
githubRepository = new GitHubRepository(new Remote(parsedIssue.repositoryName, parsedIssue.repositoryUrl, new Protocol(parsedIssue.repositoryUrl)), this._credentialStore);
}
return new IssueModel(githubRepository, remote, parsedIssue);
} catch (e) {
Logger.appendLine(`GithubRepository> Unable to fetch PR: ${e}`);
return;
Expand Down
12 changes: 12 additions & 0 deletions src/github/graphql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { ForkDetails } from './githubRepository';

export interface MergedEvent {
__typename: string;
id: string;
Expand Down Expand Up @@ -471,6 +473,16 @@ export interface MaxIssueResponse {
};
}

export interface ViewerPermissionResponse {
repository: {
viewerPermission: string
};
}

export interface ForkDetailsResponse {
repository: ForkDetails;
}

export interface QueryWithRateLimit {
rateLimit: RateLimit;
}
Expand Down
7 changes: 1 addition & 6 deletions src/github/issueModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import * as vscode from 'vscode';
import { Remote } from '../common/remote';
import { GitHubRepository } from './githubRepository';
import { IAccount, Issue, GithubItemStateEnum, IMilestone } from './interface';
import { Protocol } from '../common/protocol';

export class IssueModel {
public id: number;
Expand All @@ -28,11 +27,7 @@ export class IssueModel {

constructor(githubRepository: GitHubRepository, remote: Remote, item: Issue) {
this.githubRepository = githubRepository;
if (item.repositoryName && item.repositoryOwner && item.repositoryUrl) {
this.remote = new Remote(item.repositoryName, item.repositoryUrl, new Protocol(item.repositoryUrl));
} else {
this.remote = remote;
}
this.remote = remote;
this.item = item;
this.update(item);
}
Expand Down
4 changes: 4 additions & 0 deletions src/github/pullRequestManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -427,6 +427,10 @@ export class PullRequestManager implements vscode.Disposable {
return this._credentialStore;
}

get repositories(): GitHubRepository[] {
return this._githubRepositories;
}

async clearCredentialCache(): Promise<void> {
await this._credentialStore.reset();
this.state = PRManagerState.Initializing;
Expand Down
16 changes: 16 additions & 0 deletions src/github/queries.gql
Original file line number Diff line number Diff line change
Expand Up @@ -733,3 +733,19 @@ query Issues($query: String!) {
}
}
}

query GetViewerPermission($owner: String!, $name: String!) {
repository(owner: $owner, name: $name) {
viewerPermission
}
}

query GetRepositoryForkDetails($owner: String!, $name: String!) {
repository(owner:$owner, name: $name) {
isFork
parent {
name
owner
}
}
}
15 changes: 14 additions & 1 deletion src/github/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import * as Common from '../common/timelineEvent';
import * as GraphQL from './graphql';
import { Resource } from '../common/resources';
import { uniqBy } from '../common/utils';
import { GitHubRepository } from './githubRepository';
import { GitHubRepository, ViewerPermission } from './githubRepository';
import { GHPRCommentThread, GHPRComment } from './prComment';
import { ThreadData } from '../view/treeNodes/pullRequestNode';
import { OctokitCommon } from './common';
Expand Down Expand Up @@ -689,3 +689,16 @@ export function getRelatedUsersFromTimelineEvents(timelineEvents: Common.Timelin

return ret;
}

export function parseGraphQLViewerPermission(viewerPermissionResponse: GraphQL.ViewerPermissionResponse): ViewerPermission {
if (viewerPermissionResponse && viewerPermissionResponse.repository.viewerPermission) {
switch (viewerPermissionResponse.repository.viewerPermission) {
case ViewerPermission.Admin: return ViewerPermission.Admin;
case ViewerPermission.Maintain: return ViewerPermission.Maintain;
case ViewerPermission.Read: return ViewerPermission.Read;
case ViewerPermission.Triage: return ViewerPermission.Triage;
case ViewerPermission.Write: return ViewerPermission.Write;
}
}
return ViewerPermission.Unknown;
}
11 changes: 6 additions & 5 deletions src/issues/currentIssue.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import * as vscode from 'vscode';
import { ISSUES_CONFIGURATION, variableSubstitution, BRANCH_NAME_CONFIGURATION, getIssueNumberLabel, BRANCH_CONFIGURATION, SCM_MESSAGE_CONFIGURATION, BRANCH_NAME_CONFIGURATION_DEPRECATED } from './util';
import { Repository } from '../typings/git';
import { StateManager, IssueState } from './stateManager';
import { Remote } from '../common/remote';

export class CurrentIssue {
private statusBarItem: vscode.StatusBarItem | undefined;
Expand All @@ -17,17 +18,17 @@ export class CurrentIssue {
private user: string | undefined;
private repo: Repository | undefined;
private repoDefaults: PullRequestDefaults | undefined;
constructor(private issueModel: IssueModel, private manager: PullRequestManager, private stateManager: StateManager, private shouldPromptForBranch?: boolean) {
this.setRepo();
constructor(private issueModel: IssueModel, private manager: PullRequestManager, private stateManager: StateManager, remote?: Remote, private shouldPromptForBranch?: boolean) {
this.setRepo(remote ?? this.issueModel.githubRepository.remote);
}

private setRepo() {
private setRepo(repoRemote: Remote) {
for (let i = 0; i < this.stateManager.gitAPI.repositories.length; i++) {
const repo = this.stateManager.gitAPI.repositories[i];
for (let j = 0; j < repo.state.remotes.length; j++) {
const remote = repo.state.remotes[j];
if (remote.name === this.issueModel.githubRepository.remote.remoteName &&
(remote.fetchUrl?.toLowerCase().search(`${this.issueModel.githubRepository.remote.owner.toLowerCase()}/${this.issueModel.githubRepository.remote.repositoryName.toLowerCase()}`) !== -1)) {
if (remote.name === repoRemote?.remoteName &&
(remote.fetchUrl?.toLowerCase().search(`${repoRemote.owner.toLowerCase()}/${repoRemote.repositoryName.toLowerCase()}`) !== -1)) {
this.repo = repo;
return;
}
Expand Down
Loading