Allow GitHub URL as well as NWO (#1241)

This commit is contained in:
Shati Patel
2022-03-29 12:45:46 +01:00
committed by GitHub
parent 3bee2905e5
commit c86c602e39
2 changed files with 83 additions and 12 deletions

View File

@@ -86,15 +86,15 @@ export async function promptImportGithubDatabase(
maxStep: 2 maxStep: 2
}); });
const githubRepo = await window.showInputBox({ const githubRepo = await window.showInputBox({
title: 'Enter a GitHub repository in the format <owner>/<repo> (e.g. github/codeql)', title: 'Enter a GitHub repository URL or "name with owner" (e.g. https://github.com/github/codeql or github/codeql)',
placeHolder: '<owner>/<repo>', placeHolder: 'https://github.com/<owner>/<repo> or <owner>/<repo>',
ignoreFocusOut: true, ignoreFocusOut: true,
}); });
if (!githubRepo) { if (!githubRepo) {
return; return;
} }
if (!REPO_REGEX.test(githubRepo)) { if (!looksLikeGithubRepo(githubRepo)) {
throw new Error(`Invalid GitHub repository: ${githubRepo}`); throw new Error(`Invalid GitHub repository: ${githubRepo}`);
} }
@@ -465,18 +465,62 @@ export async function findDirWithFile(
return; return;
} }
/**
* The URL pattern is https://github.com/{owner}/{name}/{subpages}.
*
* This function accepts any URL that matches the pattern above. It also accepts just the
* name with owner (NWO): `<owner>/<repo>`.
*
* @param githubRepo The GitHub repository URL or NWO
*
* @return true if this looks like a valid GitHub repository URL or NWO
*/
export function looksLikeGithubRepo(
githubRepo: string | undefined
): githubRepo is string {
if (!githubRepo) {
return false;
}
if (REPO_REGEX.test(githubRepo) || convertGitHubUrlToNwo(githubRepo)) {
return true;
}
return false;
}
/**
* Converts a GitHub repository URL to the corresponding NWO.
* @param githubUrl The GitHub repository URL
* @return The corresponding NWO, or undefined if the URL is not valid
*/
function convertGitHubUrlToNwo(githubUrl: string): string | undefined {
try {
const uri = Uri.parse(githubUrl, true);
if (uri.scheme !== 'https') {
return;
}
if (uri.authority !== 'github.com' && uri.authority !== 'www.github.com') {
return;
}
const paths = uri.path.split('/').filter((segment: string) => segment);
const nwo = `${paths[0]}/${paths[1]}`;
if (REPO_REGEX.test(nwo)) {
return nwo;
}
return;
} catch (e) {
// Ignore the error here, since we catch failures at a higher level.
// In particular: returning undefined leads to an error in 'promptImportGithubDatabase'.
return;
}
}
export async function convertGithubNwoToDatabaseUrl( export async function convertGithubNwoToDatabaseUrl(
githubRepo: string, githubRepo: string,
credentials: Credentials, credentials: Credentials,
progress: ProgressCallback): Promise<string | undefined> { progress: ProgressCallback): Promise<string | undefined> {
try { try {
// TODO: In future, we could accept GitHub URLs in addition to NWOs. const nwo = convertGitHubUrlToNwo(githubRepo) || githubRepo;
// Similar to "looksLikeLgtmUrl". const [owner, repo] = nwo.split('/');
if (!REPO_REGEX.test(githubRepo)) {
throw new Error('Invalid repository format. Must be in the format <owner>/<repo> (e.g. github/codeql)');
}
const [owner, repo] = githubRepo.split('/');
const octokit = await credentials.getOctokit(); const octokit = await credentials.getOctokit();
const response = await octokit.request('GET /repos/:owner/:repo/code-scanning/codeql/databases', { owner, repo }); const response = await octokit.request('GET /repos/:owner/:repo/code-scanning/codeql/databases', { owner, repo });
@@ -531,7 +575,7 @@ export function looksLikeLgtmUrl(lgtmUrl: string | undefined): lgtmUrl is string
return false; return false;
} }
const paths = uri.path.split('/').filter((segment) => segment); const paths = uri.path.split('/').filter((segment: string) => segment);
return paths.length >= 4 && paths[0] === 'projects'; return paths.length >= 4 && paths[0] === 'projects';
} catch (e) { } catch (e) {
return false; return false;
@@ -604,7 +648,7 @@ export async function convertLgtmUrlToDatabaseUrl(
async function downloadLgtmProjectMetadata(lgtmUrl: string): Promise<any> { async function downloadLgtmProjectMetadata(lgtmUrl: string): Promise<any> {
const uri = Uri.parse(lgtmUrl, true); const uri = Uri.parse(lgtmUrl, true);
const paths = ['api', 'v1.0'].concat( const paths = ['api', 'v1.0'].concat(
uri.path.split('/').filter((segment) => segment) uri.path.split('/').filter((segment: string) => segment)
).slice(0, 6); ).slice(0, 6);
const projectUrl = `https://lgtm.com/${paths.join('/')}`; const projectUrl = `https://lgtm.com/${paths.join('/')}`;
const projectResponse = await fetch(projectUrl); const projectResponse = await fetch(projectUrl);

View File

@@ -12,6 +12,7 @@ import {
convertLgtmUrlToDatabaseUrl, convertLgtmUrlToDatabaseUrl,
looksLikeLgtmUrl, looksLikeLgtmUrl,
findDirWithFile, findDirWithFile,
looksLikeGithubRepo,
} from '../../databaseFetcher'; } from '../../databaseFetcher';
import { ProgressCallback } from '../../commandRunner'; import { ProgressCallback } from '../../commandRunner';
import * as pq from 'proxyquire'; import * as pq from 'proxyquire';
@@ -209,6 +210,32 @@ describe('databaseFetcher', function() {
}); });
}); });
describe('looksLikeGithubRepo', () => {
it('should handle invalid urls', () => {
expect(looksLikeGithubRepo(''))
.to.be.false;
expect(looksLikeGithubRepo('http://github.com/foo/bar'))
.to.be.false;
expect(looksLikeGithubRepo('https://ww.github.com/foo/bar'))
.to.be.false;
expect(looksLikeGithubRepo('https://ww.github.com/foo'))
.to.be.false;
expect(looksLikeGithubRepo('foo'))
.to.be.false;
});
it('should handle valid urls', () => {
expect(looksLikeGithubRepo('https://github.com/foo/bar'))
.to.be.true;
expect(looksLikeGithubRepo('https://www.github.com/foo/bar'))
.to.be.true;
expect(looksLikeGithubRepo('https://github.com/foo/bar/sub/pages'))
.to.be.true;
expect(looksLikeGithubRepo('foo/bar'))
.to.be.true;
});
});
describe('looksLikeLgtmUrl', () => { describe('looksLikeLgtmUrl', () => {
it('should handle invalid urls', () => { it('should handle invalid urls', () => {
expect(looksLikeLgtmUrl('')).to.be.false; expect(looksLikeLgtmUrl('')).to.be.false;