Skip to content

Commit c86c602

Browse files
authored
Allow GitHub URL as well as NWO (#1241)
1 parent 3bee290 commit c86c602

2 files changed

Lines changed: 83 additions & 12 deletions

File tree

extensions/ql-vscode/src/databaseFetcher.ts

Lines changed: 56 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -86,15 +86,15 @@ export async function promptImportGithubDatabase(
8686
maxStep: 2
8787
});
8888
const githubRepo = await window.showInputBox({
89-
title: 'Enter a GitHub repository in the format <owner>/<repo> (e.g. github/codeql)',
90-
placeHolder: '<owner>/<repo>',
89+
title: 'Enter a GitHub repository URL or "name with owner" (e.g. https://github.com/github/codeql or github/codeql)',
90+
placeHolder: 'https://github.com/<owner>/<repo> or <owner>/<repo>',
9191
ignoreFocusOut: true,
9292
});
9393
if (!githubRepo) {
9494
return;
9595
}
9696

97-
if (!REPO_REGEX.test(githubRepo)) {
97+
if (!looksLikeGithubRepo(githubRepo)) {
9898
throw new Error(`Invalid GitHub repository: ${githubRepo}`);
9999
}
100100

@@ -465,18 +465,62 @@ export async function findDirWithFile(
465465
return;
466466
}
467467

468+
/**
469+
* The URL pattern is https://github.com/{owner}/{name}/{subpages}.
470+
*
471+
* This function accepts any URL that matches the pattern above. It also accepts just the
472+
* name with owner (NWO): `<owner>/<repo>`.
473+
*
474+
* @param githubRepo The GitHub repository URL or NWO
475+
*
476+
* @return true if this looks like a valid GitHub repository URL or NWO
477+
*/
478+
export function looksLikeGithubRepo(
479+
githubRepo: string | undefined
480+
): githubRepo is string {
481+
if (!githubRepo) {
482+
return false;
483+
}
484+
if (REPO_REGEX.test(githubRepo) || convertGitHubUrlToNwo(githubRepo)) {
485+
return true;
486+
}
487+
return false;
488+
}
489+
490+
/**
491+
* Converts a GitHub repository URL to the corresponding NWO.
492+
* @param githubUrl The GitHub repository URL
493+
* @return The corresponding NWO, or undefined if the URL is not valid
494+
*/
495+
function convertGitHubUrlToNwo(githubUrl: string): string | undefined {
496+
try {
497+
const uri = Uri.parse(githubUrl, true);
498+
if (uri.scheme !== 'https') {
499+
return;
500+
}
501+
if (uri.authority !== 'github.com' && uri.authority !== 'www.github.com') {
502+
return;
503+
}
504+
const paths = uri.path.split('/').filter((segment: string) => segment);
505+
const nwo = `${paths[0]}/${paths[1]}`;
506+
if (REPO_REGEX.test(nwo)) {
507+
return nwo;
508+
}
509+
return;
510+
} catch (e) {
511+
// Ignore the error here, since we catch failures at a higher level.
512+
// In particular: returning undefined leads to an error in 'promptImportGithubDatabase'.
513+
return;
514+
}
515+
}
516+
468517
export async function convertGithubNwoToDatabaseUrl(
469518
githubRepo: string,
470519
credentials: Credentials,
471520
progress: ProgressCallback): Promise<string | undefined> {
472521
try {
473-
// TODO: In future, we could accept GitHub URLs in addition to NWOs.
474-
// Similar to "looksLikeLgtmUrl".
475-
if (!REPO_REGEX.test(githubRepo)) {
476-
throw new Error('Invalid repository format. Must be in the format <owner>/<repo> (e.g. github/codeql)');
477-
}
478-
479-
const [owner, repo] = githubRepo.split('/');
522+
const nwo = convertGitHubUrlToNwo(githubRepo) || githubRepo;
523+
const [owner, repo] = nwo.split('/');
480524

481525
const octokit = await credentials.getOctokit();
482526
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
531575
return false;
532576
}
533577

534-
const paths = uri.path.split('/').filter((segment) => segment);
578+
const paths = uri.path.split('/').filter((segment: string) => segment);
535579
return paths.length >= 4 && paths[0] === 'projects';
536580
} catch (e) {
537581
return false;
@@ -604,7 +648,7 @@ export async function convertLgtmUrlToDatabaseUrl(
604648
async function downloadLgtmProjectMetadata(lgtmUrl: string): Promise<any> {
605649
const uri = Uri.parse(lgtmUrl, true);
606650
const paths = ['api', 'v1.0'].concat(
607-
uri.path.split('/').filter((segment) => segment)
651+
uri.path.split('/').filter((segment: string) => segment)
608652
).slice(0, 6);
609653
const projectUrl = `https://lgtm.com/${paths.join('/')}`;
610654
const projectResponse = await fetch(projectUrl);

extensions/ql-vscode/src/vscode-tests/no-workspace/databaseFetcher.test.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
convertLgtmUrlToDatabaseUrl,
1313
looksLikeLgtmUrl,
1414
findDirWithFile,
15+
looksLikeGithubRepo,
1516
} from '../../databaseFetcher';
1617
import { ProgressCallback } from '../../commandRunner';
1718
import * as pq from 'proxyquire';
@@ -209,6 +210,32 @@ describe('databaseFetcher', function() {
209210
});
210211
});
211212

213+
describe('looksLikeGithubRepo', () => {
214+
it('should handle invalid urls', () => {
215+
expect(looksLikeGithubRepo(''))
216+
.to.be.false;
217+
expect(looksLikeGithubRepo('http://github.com/foo/bar'))
218+
.to.be.false;
219+
expect(looksLikeGithubRepo('https://ww.github.com/foo/bar'))
220+
.to.be.false;
221+
expect(looksLikeGithubRepo('https://ww.github.com/foo'))
222+
.to.be.false;
223+
expect(looksLikeGithubRepo('foo'))
224+
.to.be.false;
225+
});
226+
227+
it('should handle valid urls', () => {
228+
expect(looksLikeGithubRepo('https://github.com/foo/bar'))
229+
.to.be.true;
230+
expect(looksLikeGithubRepo('https://www.github.com/foo/bar'))
231+
.to.be.true;
232+
expect(looksLikeGithubRepo('https://github.com/foo/bar/sub/pages'))
233+
.to.be.true;
234+
expect(looksLikeGithubRepo('foo/bar'))
235+
.to.be.true;
236+
});
237+
});
238+
212239
describe('looksLikeLgtmUrl', () => {
213240
it('should handle invalid urls', () => {
214241
expect(looksLikeLgtmUrl('')).to.be.false;

0 commit comments

Comments
 (0)