提交 7282ef2b 编写于 作者: T Tomas Vik

refactor(git_service): move getInstanceUrl to separate util function

上级 ed379108
......@@ -28,6 +28,9 @@ module.exports = {
commands: {
executeCommand: jest.fn(),
},
workspace: {
getConfiguration: jest.fn().mockReturnValue({ instanceUrl: 'https://gitlab.com' }),
},
CommentMode: { Preview: 1 },
CommentThreadCollapsibleState: { Expanded: 1 },
Position: function Position(x, y) {
......
......@@ -22,10 +22,8 @@ describe('git_service', () => {
const getDefaultOptions = (): GitServiceOptions => ({
workspaceFolder,
instanceUrl: 'https://gitlab.com',
remoteName: undefined,
pipelineGitRemoteName: undefined,
tokenService: { getInstanceUrls: () => [] },
log: () => {
//
},
......
import * as execa from 'execa';
import * as url from 'url';
import { GITLAB_COM_URL } from './constants';
import { parseGitRemote, GitRemote } from './git/git_remote_parser';
interface TokenService {
getInstanceUrls(): string[];
}
import { getInstanceUrl } from './utils/get_instance_url';
export interface GitServiceOptions {
workspaceFolder: string;
instanceUrl?: string;
remoteName?: string;
pipelineGitRemoteName?: string;
tokenService: TokenService;
log: (line: string) => void;
}
export class GitService {
workspaceFolder: string;
instanceUrl?: string;
remoteName?: string;
pipelineGitRemoteName?: string;
tokenService: TokenService;
log: (line: string) => void;
constructor(options: GitServiceOptions) {
this.instanceUrl = options.instanceUrl;
this.remoteName = options.remoteName;
this.pipelineGitRemoteName = options.pipelineGitRemoteName;
this.workspaceFolder = options.workspaceFolder;
this.tokenService = options.tokenService;
this.log = options.log;
}
private async fetch(cmd: string): Promise<string | null> {
const [git, ...args] = cmd.trim().split(' ');
let currentWorkspaceFolder = this.workspaceFolder;
if (currentWorkspaceFolder == null) {
currentWorkspaceFolder = '';
}
try {
const { stdout } = await execa(git, args, {
cwd: currentWorkspaceFolder,
cwd: this.workspaceFolder,
preferLocal: false,
});
return stdout;
......@@ -74,7 +56,7 @@ export class GitService {
}
if (remoteUrl) {
return parseGitRemote(await this.fetchCurrentInstanceUrl(), remoteUrl);
return parseGitRemote(await getInstanceUrl(this.workspaceFolder), remoteUrl);
}
return null;
......@@ -121,74 +103,4 @@ export class GitService {
return branchName;
}
private async fetchGitRemoteUrls(): Promise<string[]> {
const fetchGitRemotesVerbose = async (): Promise<string[]> => {
const output = await this.fetch('git remote -v');
return (output || '').split('\n');
};
const parseRemoteFromVerboseLine = (line: string) => {
// git remote -v output looks like
// origin[TAB]git@gitlab.com:gitlab-org/gitlab-vscode-extension.git[WHITESPACE](fetch)
// the interesting part is surrounded by a tab symbol and a whitespace
return line.split(/\t| /)[1];
};
const remotes = await fetchGitRemotesVerbose();
const remoteUrls = remotes.map(remote => parseRemoteFromVerboseLine(remote)).filter(Boolean);
// git remote -v returns a (fetch) and a (push) line for each remote,
// so we need to remove duplicates
return [...new Set(remoteUrls)];
}
private async intersectionOfInstanceAndTokenUrls() {
const uriHostname = (uri: string) => url.parse(uri).host;
const instanceUrls = this.tokenService.getInstanceUrls();
const gitRemotes = await this.fetchGitRemoteUrls();
const gitRemoteHosts = gitRemotes.map(uriHostname);
return instanceUrls.filter(host => gitRemoteHosts.includes(uriHostname(host)));
}
private async heuristicInstanceUrl() {
// if the intersection of git remotes and configured PATs exists and is exactly
// one hostname, use it
const intersection = await this.intersectionOfInstanceAndTokenUrls();
if (intersection.length === 1) {
const heuristicUrl = intersection[0];
this.log(
`Found ${heuristicUrl} in the PAT list and git remotes, using it as the instanceUrl`,
);
return heuristicUrl;
}
if (intersection.length > 1) {
this.log(
`Found more than one intersection of git remotes and configured PATs, ${intersection}`,
);
}
return null;
}
async fetchCurrentInstanceUrl(): Promise<string> {
// if the workspace setting exists, use it
if (this.instanceUrl) {
return this.instanceUrl;
}
// try to determine the instance URL heuristically
const heuristicUrl = await this.heuristicInstanceUrl();
if (heuristicUrl) {
return heuristicUrl;
}
// default to Gitlab cloud
return GITLAB_COM_URL;
}
}
......@@ -13,6 +13,7 @@ import { CustomQueryType } from './gitlab/custom_query_type';
import { CustomQuery } from './gitlab/custom_query';
import { getAvatarUrl } from './utils/get_avatar_url';
import { getHttpAgentOptions } from './utils/get_http_agent_options';
import { getInstanceUrl as getInstanceUrlUtil } from './utils/get_instance_url';
interface GitLabProject {
id: number;
......@@ -46,12 +47,7 @@ const normalizeAvatarUrl = (instanceUrl: string) => (issuable: RestIssuable): Re
const projectCache: Record<string, GitLabProject> = {};
let versionCache: string | null = null;
const getInstanceUrl = async () =>
await createGitService(
// fetching of instanceUrl is the only GitService method that doesn't need workspaceFolder
// TODO: remove this default value once we implement https://gitlab.com/gitlab-org/gitlab-vscode-extension/-/issues/260
(await getCurrentWorkspaceFolder()) || '',
).fetchCurrentInstanceUrl();
const getInstanceUrl = async () => await getInstanceUrlUtil(await getCurrentWorkspaceFolder());
async function fetch(path: string, method = 'GET', data?: Record<string, unknown>) {
const instanceUrl = await getInstanceUrl();
......
......@@ -2,7 +2,7 @@ const vscode = require('vscode');
const gitLabService = require('./gitlab_service');
const openers = require('./openers');
const { getCurrentWorkspaceFolderOrSelectOne } = require('./services/workspace_service');
const { createGitService } = require('./service_factory');
const { getInstanceUrl } = require('./utils/get_instance_url');
const parseQuery = (query, noteableType) => {
const params = {};
......@@ -121,7 +121,7 @@ async function showProjectAdvancedSearchInput() {
'Project Advanced Search. (Check extension page for Advanced Search)',
);
const queryString = await encodeURIComponent(query);
const instanceUrl = await createGitService(workspaceFolder).fetchCurrentInstanceUrl();
const instanceUrl = await getInstanceUrl(workspaceFolder);
// Select issues tab by default for Advanced Search
await openers.openUrl(
......
import * as vscode from 'vscode';
import { GitService } from './git_service';
import { tokenService } from './services/token_service';
import { log } from './log';
import { GitLabNewService } from './gitlab/gitlab_new_service';
import { getInstanceUrl } from './utils/get_instance_url';
export function createGitService(workspaceFolder: string): GitService {
const { instanceUrl, remoteName, pipelineGitRemoteName } = vscode.workspace.getConfiguration(
'gitlab',
);
const { remoteName, pipelineGitRemoteName } = vscode.workspace.getConfiguration('gitlab');
// the getConfiguration() returns null for missing attributes, we need to convert them to
// undefined so that we can use optional properties and default function parameters
return new GitService({
workspaceFolder,
instanceUrl: instanceUrl || undefined,
remoteName: remoteName || undefined,
pipelineGitRemoteName: pipelineGitRemoteName || undefined,
tokenService,
log,
});
}
export async function createGitLabNewService(workspaceFolder: string): Promise<GitLabNewService> {
const gitService = createGitService(workspaceFolder);
return new GitLabNewService(await gitService.fetchCurrentInstanceUrl());
return new GitLabNewService(await getInstanceUrl(workspaceFolder));
}
......@@ -9,7 +9,7 @@ async function getWorkspaceFolderForOpenEditor(): Promise<string | undefined> {
return workspaceFolder?.uri.fsPath;
}
export async function getCurrentWorkspaceFolder(): Promise<string | null> {
export async function getCurrentWorkspaceFolder(): Promise<string | undefined> {
const editorFolder = await getWorkspaceFolderForOpenEditor();
if (editorFolder) {
......@@ -21,7 +21,7 @@ export async function getCurrentWorkspaceFolder(): Promise<string | null> {
return workspaceFolders[0].uri.fsPath;
}
return null;
return undefined;
}
export async function getCurrentWorkspaceFolderOrSelectOne(): Promise<string | null> {
......
import * as vscode from 'vscode';
import * as execa from 'execa';
import * as url from 'url';
import { GITLAB_COM_URL } from '../constants';
import { tokenService } from '../services/token_service';
import { log } from '../log';
async function fetch(cmd: string, workspaceFolder: string): Promise<string | null> {
const [git, ...args] = cmd.trim().split(' ');
const { stdout } = await execa(git, args, {
cwd: workspaceFolder,
preferLocal: false,
});
return stdout;
}
async function fetchGitRemoteUrls(workspaceFolder: string): Promise<string[]> {
const fetchGitRemotesVerbose = async (): Promise<string[]> => {
const output = await fetch('git remote -v', workspaceFolder);
return (output || '').split('\n');
};
const parseRemoteFromVerboseLine = (line: string) => {
// git remote -v output looks like
// origin[TAB]git@gitlab.com:gitlab-org/gitlab-vscode-extension.git[WHITESPACE](fetch)
// the interesting part is surrounded by a tab symbol and a whitespace
return line.split(/\t| /)[1];
};
const remotes = await fetchGitRemotesVerbose();
const remoteUrls = remotes.map(remote => parseRemoteFromVerboseLine(remote)).filter(Boolean);
// git remote -v returns a (fetch) and a (push) line for each remote,
// so we need to remove duplicates
return [...new Set(remoteUrls)];
}
async function intersectionOfInstanceAndTokenUrls(workspaceFolder: string) {
const uriHostname = (uri: string) => url.parse(uri).host;
const instanceUrls = tokenService.getInstanceUrls();
const gitRemotes = await fetchGitRemoteUrls(workspaceFolder);
const gitRemoteHosts = gitRemotes.map(uriHostname);
return instanceUrls.filter(host => gitRemoteHosts.includes(uriHostname(host)));
}
async function heuristicInstanceUrl(workspaceFolder: string) {
// if the intersection of git remotes and configured PATs exists and is exactly
// one hostname, use it
const intersection = await intersectionOfInstanceAndTokenUrls(workspaceFolder);
if (intersection.length === 1) {
const heuristicUrl = intersection[0];
log(`Found ${heuristicUrl} in the PAT list and git remotes, using it as the instanceUrl`);
return heuristicUrl;
}
if (intersection.length > 1) {
log(`Found more than one intersection of git remotes and configured PATs, ${intersection}`);
}
return null;
}
export async function getInstanceUrl(workspaceFolder?: string): Promise<string> {
const { instanceUrl } = vscode.workspace.getConfiguration('gitlab');
// if the workspace setting exists, use it
if (instanceUrl) {
return instanceUrl;
}
// legacy logic in GitLabService might not have the workspace folder available
// in that case we just skip the heuristic
if (workspaceFolder) {
// try to determine the instance URL heuristically
const heuristicUrl = await heuristicInstanceUrl(workspaceFolder);
if (heuristicUrl) {
return heuristicUrl;
}
}
// default to Gitlab cloud
return GITLAB_COM_URL;
}
......@@ -46,9 +46,9 @@ describe('workspace_service', () => {
sandbox.restore();
});
it('getCurrentWorkspaceFolder returns null', async () => {
it('getCurrentWorkspaceFolder returns undefined', async () => {
const result = await workspaceService.getCurrentWorkspaceFolder();
assert.strictEqual(result, null);
assert.strictEqual(result, undefined);
});
it('getCurrentWorkspaceFolderOrSelectOne lets user select a workspace', async () => {
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册