feat(gerrit-integration): Update the gerrit url resolver
Currently the only Gitiles url that can be resolved is urls that point to the tip of a specific branch. This change adds support for resolving urls that points to the following refs: * HEAD * A commit sha * Tag The old Gitiles url parser may be deprecated. Signed-off-by: Niklas Aronsson <niklasar@axis.com>
This commit is contained in:
@@ -0,0 +1,11 @@
|
||||
---
|
||||
'@backstage/integration': minor
|
||||
---
|
||||
|
||||
The Gerrit integration can now resolve Gitiles urls that point to the following
|
||||
refs:
|
||||
|
||||
- HEAD
|
||||
- A SHA
|
||||
- Tag
|
||||
- Branch
|
||||
@@ -766,6 +766,18 @@ export function parseGiteaUrl(
|
||||
path: string;
|
||||
};
|
||||
|
||||
// @public
|
||||
export function parseGitilesUrlRef(
|
||||
config: GerritIntegrationConfig,
|
||||
url: string,
|
||||
): {
|
||||
project: string;
|
||||
path: string;
|
||||
ref: string;
|
||||
refType: 'sha' | 'branch' | 'tag' | 'head';
|
||||
basePath: string;
|
||||
};
|
||||
|
||||
// @public
|
||||
export function parseHarnessUrl(
|
||||
config: HarnessIntegrationConfig,
|
||||
|
||||
@@ -83,11 +83,10 @@ describe('GerritIntegration', () => {
|
||||
});
|
||||
|
||||
describe('resolves with a relative url', () => {
|
||||
it('works for valid urls', () => {
|
||||
const integration = new GerritIntegration({
|
||||
host: 'gerrit-review.example.com',
|
||||
} as any);
|
||||
|
||||
const integration = new GerritIntegration({
|
||||
host: 'gerrit-review.example.com',
|
||||
} as any);
|
||||
it('works for valid urls pointing to a branch', () => {
|
||||
expect(
|
||||
integration.resolveUrl({
|
||||
url: './skeleton',
|
||||
@@ -97,15 +96,24 @@ describe('GerritIntegration', () => {
|
||||
'https://gerrit-review.example.com/gerrit/plugins/repo/+/refs/heads/master/skeleton',
|
||||
);
|
||||
});
|
||||
it('works for urls pointing to a tag', () => {
|
||||
expect(
|
||||
integration.resolveUrl({
|
||||
url: './skeleton.yaml',
|
||||
base: 'https://gerrit-review.example.com/gerrit/plugins/repo/+/refs/tags/v.1.2.3/src/template.yaml',
|
||||
}),
|
||||
).toBe(
|
||||
'https://gerrit-review.example.com/gerrit/plugins/repo/+/refs/tags/v.1.2.3/src/skeleton.yaml',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('resolves with an absolute url', () => {
|
||||
it('works for valid urls', () => {
|
||||
const integration = new GerritIntegration({
|
||||
host: 'gerrit-review.example.com',
|
||||
gitilesBaseUrl: 'https://gerrit-review.example.com/gitiles',
|
||||
} as any);
|
||||
|
||||
const integration = new GerritIntegration({
|
||||
host: 'gerrit-review.example.com',
|
||||
gitilesBaseUrl: 'https://gerrit-review.example.com/gitiles',
|
||||
} as any);
|
||||
it('works for valid urls pointing to a branch', () => {
|
||||
expect(
|
||||
integration.resolveUrl({
|
||||
url: '/catalog-info.yaml',
|
||||
@@ -115,6 +123,16 @@ describe('GerritIntegration', () => {
|
||||
'https://gerrit-review.example.com/gitiles/repo/+/refs/heads/master/catalog-info.yaml',
|
||||
);
|
||||
});
|
||||
it('works for urls pointing to a tag', () => {
|
||||
expect(
|
||||
integration.resolveUrl({
|
||||
url: '/skeleton.yaml',
|
||||
base: 'https://gerrit-review.example.com/gerrit/plugins/repo/+/refs/tags/v.1.2.3/src/template.yaml',
|
||||
}),
|
||||
).toBe(
|
||||
'https://gerrit-review.example.com/gerrit/plugins/repo/+/refs/tags/v.1.2.3/skeleton.yaml',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('resolve edit URL', () => {
|
||||
|
||||
@@ -20,7 +20,7 @@ import {
|
||||
GerritIntegrationConfig,
|
||||
readGerritIntegrationConfigs,
|
||||
} from './config';
|
||||
import { parseGerritGitilesUrl, buildGerritGitilesUrl } from './core';
|
||||
import { parseGitilesUrlRef } from './core';
|
||||
|
||||
/**
|
||||
* A Gerrit based integration.
|
||||
@@ -60,8 +60,8 @@ export class GerritIntegration implements ScmIntegration {
|
||||
const { url, base, lineNumber } = options;
|
||||
let updated;
|
||||
if (url.startsWith('/')) {
|
||||
const { branch, project } = parseGerritGitilesUrl(this.config, base);
|
||||
return buildGerritGitilesUrl(this.config, project, branch, url);
|
||||
const { basePath } = parseGitilesUrlRef(this.config, base);
|
||||
return basePath + url;
|
||||
}
|
||||
if (url) {
|
||||
updated = new URL(url, base);
|
||||
|
||||
@@ -26,6 +26,7 @@ import {
|
||||
getGerritCloneRepoUrl,
|
||||
getGerritRequestOptions,
|
||||
parseGerritJsonResponse,
|
||||
parseGitilesUrlRef,
|
||||
parseGerritGitilesUrl,
|
||||
getGerritFileContentsApiUrl,
|
||||
} from './core';
|
||||
@@ -148,8 +149,129 @@ describe('gerrit core', () => {
|
||||
).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('parseGitilesUrl', () => {
|
||||
describe('parseGitilesUrlRef', () => {
|
||||
const config: GerritIntegrationConfig = {
|
||||
host: 'gerrit.com',
|
||||
gitilesBaseUrl: 'https://gerrit.googlesource.com',
|
||||
};
|
||||
it('can parse a gitiles urls that points to specific sha.', () => {
|
||||
const gitUrl = parseGitilesUrlRef(
|
||||
config,
|
||||
'https://gerrit.googlesource.com/modules/cached-refdb/+/157f862803d45b9d269f0e390f88aece1ded51e8/Jenkinsfile',
|
||||
);
|
||||
expect(gitUrl).toEqual({
|
||||
basePath:
|
||||
'https://gerrit.googlesource.com/modules/cached-refdb/+/157f862803d45b9d269f0e390f88aece1ded51e8',
|
||||
path: 'Jenkinsfile',
|
||||
project: 'modules/cached-refdb',
|
||||
ref: '157f862803d45b9d269f0e390f88aece1ded51e8',
|
||||
refType: 'sha',
|
||||
});
|
||||
});
|
||||
it('can parse gitiles urls that points to tags.', () => {
|
||||
const gitUrl = parseGitilesUrlRef(
|
||||
config,
|
||||
'https://gerrit.googlesource.com/modules/events-broker/+/refs/tags/v3.5.6/src/main/java/com/gerritforge/gerrit/eventbroker/BrokerApi.java',
|
||||
);
|
||||
expect(gitUrl).toEqual({
|
||||
basePath:
|
||||
'https://gerrit.googlesource.com/modules/events-broker/+/refs/tags/v3.5.6',
|
||||
path: 'src/main/java/com/gerritforge/gerrit/eventbroker/BrokerApi.java',
|
||||
project: 'modules/events-broker',
|
||||
ref: 'v3.5.6',
|
||||
refType: 'tag',
|
||||
});
|
||||
});
|
||||
it('can parse gitiles urls that points to HEAD.', () => {
|
||||
const gitUrl = parseGitilesUrlRef(
|
||||
config,
|
||||
'https://gerrit.googlesource.com/modules/events-broker/+/HEAD/src/main/java/com/gerritforge/gerrit/eventbroker/BrokerApi.java',
|
||||
);
|
||||
expect(gitUrl).toEqual({
|
||||
basePath:
|
||||
'https://gerrit.googlesource.com/modules/events-broker/+/HEAD',
|
||||
path: 'src/main/java/com/gerritforge/gerrit/eventbroker/BrokerApi.java',
|
||||
project: 'modules/events-broker',
|
||||
ref: 'HEAD',
|
||||
refType: 'head',
|
||||
});
|
||||
});
|
||||
it('can parse gitiles urls that points to HEAD without path.', () => {
|
||||
const gitUrl = parseGitilesUrlRef(
|
||||
config,
|
||||
'https://gerrit.googlesource.com/modules/events-broker/+/HEAD',
|
||||
);
|
||||
expect(gitUrl).toEqual({
|
||||
basePath:
|
||||
'https://gerrit.googlesource.com/modules/events-broker/+/HEAD',
|
||||
path: '/',
|
||||
project: 'modules/events-broker',
|
||||
ref: 'HEAD',
|
||||
refType: 'head',
|
||||
});
|
||||
});
|
||||
it('can parse gitiles urls that points to branches.', () => {
|
||||
const gitUrl = parseGitilesUrlRef(
|
||||
config,
|
||||
'https://gerrit.googlesource.com/modules/events-broker/+/refs/heads/master/src/main/java/com/gerritforge/gerrit/eventbroker/BrokerApiModule.java',
|
||||
);
|
||||
expect(gitUrl).toEqual({
|
||||
basePath:
|
||||
'https://gerrit.googlesource.com/modules/events-broker/+/refs/heads/master',
|
||||
path: 'src/main/java/com/gerritforge/gerrit/eventbroker/BrokerApiModule.java',
|
||||
project: 'modules/events-broker',
|
||||
ref: 'master',
|
||||
refType: 'branch',
|
||||
});
|
||||
});
|
||||
it('can parse gitiles urls that points directly to a branch without a path.', () => {
|
||||
const gitUrl = parseGitilesUrlRef(
|
||||
config,
|
||||
'https://gerrit.googlesource.com/modules/events-broker/+/refs/heads/master',
|
||||
);
|
||||
expect(gitUrl).toEqual({
|
||||
basePath:
|
||||
'https://gerrit.googlesource.com/modules/events-broker/+/refs/heads/master',
|
||||
path: '/',
|
||||
project: 'modules/events-broker',
|
||||
ref: 'master',
|
||||
refType: 'branch',
|
||||
});
|
||||
});
|
||||
it('can parse gitiles urls that points to the repo root.', () => {
|
||||
const gitUrl = parseGitilesUrlRef(
|
||||
config,
|
||||
'https://gerrit.googlesource.com/modules/events-broker/+/refs/heads/master/',
|
||||
);
|
||||
expect(gitUrl).toEqual({
|
||||
basePath:
|
||||
'https://gerrit.googlesource.com/modules/events-broker/+/refs/heads/master',
|
||||
path: '/',
|
||||
project: 'modules/events-broker',
|
||||
ref: 'master',
|
||||
refType: 'branch',
|
||||
});
|
||||
});
|
||||
it('can parse a valid authenticated gitiles url.', () => {
|
||||
const gitilesConfig: GerritIntegrationConfig = {
|
||||
host: 'gerrit.com',
|
||||
gitilesBaseUrl: 'https://gerrit.com/gitiles',
|
||||
};
|
||||
const gitUrl = parseGitilesUrlRef(
|
||||
gitilesConfig,
|
||||
'https://gerrit.com/a/gitiles/web/project/+/refs/heads/master/README.md',
|
||||
);
|
||||
expect(gitUrl).toEqual({
|
||||
basePath:
|
||||
'https://gerrit.com/a/gitiles/web/project/+/refs/heads/master',
|
||||
path: 'README.md',
|
||||
project: 'web/project',
|
||||
ref: 'master',
|
||||
refType: 'branch',
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('parseGerritGitilesUrl', () => {
|
||||
it('can parse a valid gitiles urls.', () => {
|
||||
const config: GerritIntegrationConfig = {
|
||||
host: 'gerrit.com',
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { trimStart } from 'lodash';
|
||||
import { join, takeWhile, trimEnd, trimStart } from 'lodash';
|
||||
import { GerritIntegrationConfig } from './config';
|
||||
|
||||
const GERRIT_BODY_PREFIX = ")]}'";
|
||||
@@ -81,6 +81,112 @@ export function parseGerritGitilesUrl(
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses Gitiles urls and returns the following:
|
||||
*
|
||||
* - The project
|
||||
* - The type of ref. I.e: branch name, SHA, HEAD or tag.
|
||||
* - The file path from the repo root.
|
||||
* - The base path as the path that points to the repo root.
|
||||
*
|
||||
* Supported types of gitiles urls that point to:
|
||||
*
|
||||
* - Branches
|
||||
* - Tags
|
||||
* - A commit SHA
|
||||
* - HEAD
|
||||
*
|
||||
* @param config - A Gerrit provider config.
|
||||
* @param url - An url to a file or folder in Gitiles.
|
||||
* @public
|
||||
*/
|
||||
export function parseGitilesUrlRef(
|
||||
config: GerritIntegrationConfig,
|
||||
url: string,
|
||||
): {
|
||||
project: string;
|
||||
path: string;
|
||||
ref: string;
|
||||
refType: 'sha' | 'branch' | 'tag' | 'head';
|
||||
basePath: string;
|
||||
} {
|
||||
const baseUrlParse = new URL(config.gitilesBaseUrl!);
|
||||
const urlParse = new URL(url);
|
||||
// Remove the gerrit authentication prefix '/a/' from the url
|
||||
// In case of the gitilesBaseUrl is https://review.gerrit.com/plugins/gitiles
|
||||
// and the url provided is https://review.gerrit.com/a/plugins/gitiles/...
|
||||
// remove the prefix only if the pathname start with '/a/'
|
||||
const urlPath = trimStart(
|
||||
urlParse.pathname
|
||||
.substring(urlParse.pathname.startsWith('/a/') ? 2 : 0)
|
||||
.replace(baseUrlParse.pathname, ''),
|
||||
'/',
|
||||
);
|
||||
|
||||
// Find the project by taking everything up to "/+/".
|
||||
const parts = urlPath.split('/').filter(p => !!p);
|
||||
const projectParts = takeWhile(parts, p => p !== '+');
|
||||
if (projectParts.length === 0) {
|
||||
throw new Error(`Unable to parse gitiles url: ${url}`);
|
||||
}
|
||||
// Also remove the "+" after the project.
|
||||
const rest = parts.slice(projectParts.length + 1);
|
||||
const project = join(projectParts, '/');
|
||||
|
||||
// match <project>/+/HEAD/<path>
|
||||
if (rest.length > 0 && rest[0] === 'HEAD') {
|
||||
const ref = rest.shift()!;
|
||||
const path = join(rest, '/');
|
||||
return {
|
||||
project,
|
||||
ref,
|
||||
refType: 'head' as const,
|
||||
path: path || '/',
|
||||
basePath: trimEnd(url.replace(path, ''), '/'),
|
||||
};
|
||||
}
|
||||
// match <project>/+/<sha>/<path>
|
||||
if (rest.length > 0 && rest[0].length === 40) {
|
||||
const ref = rest.shift()!;
|
||||
const path = join(rest, '/');
|
||||
return {
|
||||
project,
|
||||
ref,
|
||||
refType: 'sha' as const,
|
||||
path: path || '/',
|
||||
basePath: trimEnd(url.replace(path, ''), '/'),
|
||||
};
|
||||
}
|
||||
const remainingPath = join(rest, '/');
|
||||
// Regexp for matching "refs/tags/<tag>" or "refs/heads/<branch>/"
|
||||
const refsRegexp = /^refs\/(?<refsReference>heads|tags)\/(?<ref>.*?)(\/|$)/;
|
||||
const result = refsRegexp.exec(remainingPath);
|
||||
if (result) {
|
||||
const matchString = result[0];
|
||||
let refType;
|
||||
const { refsReference, ref } = result.groups || {};
|
||||
const path = remainingPath.replace(matchString, '');
|
||||
switch (refsReference) {
|
||||
case 'heads':
|
||||
refType = 'branch' as const;
|
||||
break;
|
||||
case 'tags':
|
||||
refType = 'tag' as const;
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Unable to parse gitiles url: ${url}`);
|
||||
}
|
||||
return {
|
||||
project,
|
||||
ref,
|
||||
refType,
|
||||
path: path || '/',
|
||||
basePath: trimEnd(url.replace(path, ''), '/'),
|
||||
};
|
||||
}
|
||||
throw new Error(`Unable to parse gitiles : ${url}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a Gerrit Gitiles url that targets a specific path.
|
||||
*
|
||||
|
||||
@@ -27,6 +27,7 @@ export {
|
||||
getGerritRequestOptions,
|
||||
parseGerritJsonResponse,
|
||||
parseGerritGitilesUrl,
|
||||
parseGitilesUrlRef,
|
||||
} from './core';
|
||||
|
||||
export type { GerritIntegrationConfig } from './config';
|
||||
|
||||
Reference in New Issue
Block a user