Skip to content

Commit

Permalink
fix: support repositories within subgroups
Browse files Browse the repository at this point in the history
  • Loading branch information
pvdlg committed Jan 7, 2018
1 parent ba75444 commit 4d716f8
Show file tree
Hide file tree
Showing 6 changed files with 110 additions and 21 deletions.
10 changes: 10 additions & 0 deletions lib/get-repo-id.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
const parsePath = require('parse-path');
const escapeStringRegexp = require('escape-string-regexp');

module.exports = (gitlabUrl, repositoryUrl) => {
return parsePath(repositoryUrl)
.pathname.replace(new RegExp(`^${escapeStringRegexp(parsePath(gitlabUrl).pathname)}`), '')
.replace(/^\//, '')
.replace(/\/$/, '')
.replace(/\.git$/, '');
};
24 changes: 11 additions & 13 deletions lib/publish.js
Original file line number Diff line number Diff line change
@@ -1,26 +1,27 @@
const parseGithubUrl = require('parse-github-url');
const urlJoin = require('url-join');
const got = require('got');
const debug = require('debug')('semantic-release:github');
const debug = require('debug')('semantic-release:gitlab');
const resolveConfig = require('./resolve-config');
const getRepoId = require('./get-repo-id');

module.exports = async (pluginConfig, {repositoryUrl}, {gitHead, gitTag, notes}, logger) => {
const {gitlabToken, gitlabUrl, gitlabApiPathPrefix} = resolveConfig(pluginConfig);
const {name: repo, owner} = parseGithubUrl(repositoryUrl);
const repoId = encodeURIComponent(getRepoId(gitlabUrl, repositoryUrl));

debug('repoId: %o', repoId);
debug('release name: %o', gitTag);
debug('release ref: %o', gitHead);

try {
// Test if the tag already exists
await got.get(urlJoin(gitlabUrl, gitlabApiPathPrefix, `/projects/${owner}%2F${repo}/repository/tags/${gitTag}`), {
await got.get(urlJoin(gitlabUrl, gitlabApiPathPrefix, `/projects/${repoId}/repository/tags/${gitTag}`), {
json: true,
headers: {'Private-Token': gitlabToken},
});
debug('The git tag %o already exists, update the release description', gitTag);
// Update the release notes
await got.post(
urlJoin(gitlabUrl, gitlabApiPathPrefix, `/projects/${owner}%2F${repo}/repository/tags/${gitTag}/release`),
urlJoin(gitlabUrl, gitlabApiPathPrefix, `/projects/${repoId}/repository/tags/${gitTag}/release`),
{json: true, headers: {'Private-Token': gitlabToken}, body: {tag_name: gitTag, description: notes}} // eslint-disable-line camelcase
);
} catch (err) {
Expand All @@ -29,14 +30,11 @@ module.exports = async (pluginConfig, {repositoryUrl}, {gitHead, gitTag, notes},
throw err;
}
debug('Create git tag %o with commit %o and release description', gitTag, gitHead);
await got.post(
urlJoin(gitlabUrl, gitlabApiPathPrefix, `/projects/${owner}%2F${repo}/repository/tags/${gitTag}/release`),
{
json: true,
headers: {'PRIVATE-TOKEN': gitlabToken},
body: {tag_name: gitTag, ref: gitHead, release_description: notes}, // eslint-disable-line camelcase
}
);
await got.post(urlJoin(gitlabUrl, gitlabApiPathPrefix, `/projects/${repoId}/repository/tags/${gitTag}/release`), {
json: true,
headers: {'PRIVATE-TOKEN': gitlabToken},
body: {tag_name: gitTag, ref: gitHead, release_description: notes}, // eslint-disable-line camelcase
});
}

logger.log('Published GitLab release: %s', gitTag);
Expand Down
16 changes: 10 additions & 6 deletions lib/verify.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
const parseGithubUrl = require('parse-github-url');
const urlJoin = require('url-join');
const got = require('got');
const debug = require('debug')('semantic-release:gitlab');
const SemanticReleaseError = require('@semantic-release/error');
const resolveConfig = require('./resolve-config');
const getRepoId = require('./get-repo-id');

module.exports = async (pluginConfig, {repositoryUrl}, logger) => {
const {gitlabToken, gitlabUrl, gitlabApiPathPrefix} = resolveConfig(pluginConfig);
Expand All @@ -11,8 +12,11 @@ module.exports = async (pluginConfig, {repositoryUrl}, logger) => {
throw new SemanticReleaseError('No GitLab token specified.', 'ENOGLTOKEN');
}

const {name: repo, owner} = parseGithubUrl(repositoryUrl);
if (!owner || !repo) {
const repoId = getRepoId(gitlabUrl, repositoryUrl);

debug('repoId: %o', repoId);

if (!repoId) {
throw new SemanticReleaseError(
`The git repository URL ${repositoryUrl} is not a valid GitLab URL.`,
'EINVALIDGITURL'
Expand All @@ -26,21 +30,21 @@ module.exports = async (pluginConfig, {repositoryUrl}, logger) => {

try {
({body: {permissions: {project_access: projectAccess, group_access: groupAccess}}} = await got.get(
urlJoin(gitlabUrl, gitlabApiPathPrefix, `/projects/${owner}%2F${repo}`),
urlJoin(gitlabUrl, gitlabApiPathPrefix, `/projects/${encodeURIComponent(repoId)}`),
{json: true, headers: {'Private-Token': gitlabToken}}
));
} catch (err) {
if (err.statusCode === 401) {
throw new SemanticReleaseError('Invalid GitLab token.', 'EINVALIDGLTOKEN');
} else if (err.statusCode === 404) {
throw new SemanticReleaseError(`The repository ${owner}/${repo} doesn't exist.`, 'EMISSINGREPO');
throw new SemanticReleaseError(`The repository ${repoId} doesn't exist.`, 'EMISSINGREPO');
}
throw err;
}

if (!((projectAccess && projectAccess.access_level >= 30) || (groupAccess && groupAccess.access_level >= 30))) {
throw new SemanticReleaseError(
`The GitLab token doesn't allow to push on the repository ${owner}/${repo}.`,
`The GitLab token doesn't allow to push on the repository ${repoId}.`,
'EGHNOPERMISSION'
);
}
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,9 @@
"dependencies": {
"@semantic-release/error": "^2.1.0",
"debug": "^3.1.0",
"escape-string-regexp": "^1.0.5",
"got": "^8.0.1",
"parse-github-url": "^1.0.2",
"parse-path": "^3.0.2",
"url-join": "^2.0.2"
},
"devDependencies": {
Expand Down
33 changes: 33 additions & 0 deletions test/get-repo-id.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import test from 'ava';
import getRepoId from '../lib/get-repo-id';

test('Parse repo id with https URL', t => {
t.is(getRepoId('https://gitlbab.com', 'https://gitlab.com/owner/repo.git'), 'owner/repo');
t.is(getRepoId('https://gitlbab.com', 'https://gitlab.com/owner/repo'), 'owner/repo');
});

test('Parse repo id with git URL', t => {
t.is(getRepoId('https://gitlab.com', 'git+ssh://git@gitlab.com/owner/repo.git'), 'owner/repo');
t.is(getRepoId('https://gitlab.com', 'git+ssh://git@gitlab.com/owner/repo'), 'owner/repo');
});

test('Parse repo id with context in repo URL', t => {
t.is(getRepoId('https://gitlbab.com/context', 'https://gitlab.com/context/owner/repo.git'), 'owner/repo');
t.is(getRepoId('https://gitlab.com/context', 'git+ssh://git@gitlab.com/context/owner/repo.git'), 'owner/repo');
});

test('Parse repo id with context not in repo URL', t => {
t.is(getRepoId('https://gitlbab.com/context', 'https://gitlab.com/owner/repo.git'), 'owner/repo');
t.is(getRepoId('https://gitlab.com/context', 'git+ssh://git@gitlab.com/owner/repo.git'), 'owner/repo');
});

test('Parse repo id with organization and subgroup', t => {
t.is(
getRepoId('https://gitlbab.com/context', 'https://gitlab.com/orga/subgroup/owner/repo.git'),
'orga/subgroup/owner/repo'
);
t.is(
getRepoId('https://gitlab.com/context', 'git+ssh://git@gitlab.com/orga/subgroup/owner/repo.git'),
'orga/subgroup/owner/repo'
);
});
45 changes: 44 additions & 1 deletion test/verify.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,46 @@ test.serial('Verify token and repository access and custom URL', async t => {
t.deepEqual(t.context.log.args[0], ['Verify GitLab authentication (%s)', 'https://othertesturl.com:9090/prefix']);
});

test.serial('Verify token and repository access with subgroup git URL', async t => {
const repoUri = 'orga/subgroup/test_user/test_repo';
process.env.GL_TOKEN = 'gitlab_token';
const gitlabUrl = 'https://customurl.com:9090/context';
const gitlabApiPathPrefix = 'prefix';
const gitlab = authenticate({gitlabUrl, gitlabApiPathPrefix})
.get(`/projects/${encodeURIComponent(repoUri)}`)
.reply(200, {permissions: {project_access: {access_level: 40}}});

await t.notThrows(
verify({gitlabUrl, gitlabApiPathPrefix}, {repositoryUrl: `git@customurl.com:${repoUri}.git`}, t.context.logger)
);

t.true(gitlab.isDone());
t.deepEqual(t.context.log.args[0], [
'Verify GitLab authentication (%s)',
'https://customurl.com:9090/context/prefix',
]);
});

test.serial('Verify token and repository access with subgroup http URL', async t => {
const repoUri = 'orga/subgroup/test_user/test_repo';
process.env.GL_TOKEN = 'gitlab_token';
const gitlabUrl = 'https://customurl.com:9090/context';
const gitlabApiPathPrefix = 'prefix';
const gitlab = authenticate({gitlabUrl, gitlabApiPathPrefix})
.get(`/projects/${encodeURIComponent(repoUri)}`)
.reply(200, {permissions: {project_access: {access_level: 40}}});

await t.notThrows(
verify({gitlabUrl, gitlabApiPathPrefix}, {repositoryUrl: `http://customurl.com/${repoUri}.git`}, t.context.logger)
);

t.true(gitlab.isDone());
t.deepEqual(t.context.log.args[0], [
'Verify GitLab authentication (%s)',
'https://customurl.com:9090/context/prefix',
]);
});

test.serial('Verify token and repository access with empty gitlabApiPathPrefix', async t => {
const owner = 'test_user';
const repo = 'test_repo';
Expand Down Expand Up @@ -181,8 +221,11 @@ test.serial('Throw SemanticReleaseError for invalid token', async t => {

test.serial('Throw SemanticReleaseError for invalid repositoryUrl', async t => {
process.env.GITLAB_TOKEN = 'gitlab_token';
const gitlabUrl = 'https://gitlab.com/context';

const error = await t.throws(verify({}, {repositoryUrl: 'invalid_url'}, t.context.logger));
const error = await t.throws(
verify({gitlabUrl}, {repositoryUrl: 'git+ssh://git@gitlab.com/context.git'}, t.context.logger)
);

t.true(error instanceof SemanticReleaseError);
t.is(error.code, 'EINVALIDGITURL');
Expand Down

0 comments on commit 4d716f8

Please sign in to comment.