Repository: semantic-release/semantic-release
Pull request #702 (2018-03-15 22:00:02)

feat: allow to use shorthand for `repositoryUrl`
Per discussion in #695.

- `owner/repo` => `https://github.com/owner/repo.git`
- `gitlab:owner/repo` => `https://gitlab.com/owner/repo.git`
- `bitbucket:owner/repo` => `https://bitbucket.com/owner/repo.git`


Patch:
diff --git a/docs/usage/configuration.md b/docs/usage/configuration.md
index 72d826a..22799c1 100644
--- a/docs/usage/configuration.md
+++ b/docs/usage/configuration.md
@@ -63,8 +63,6 @@ The git repository URL.
 
 Any valid git url format is supported (See [Git protocols](https://git-scm.com/book/en/v2/Git-on-the-Server-The-Protocols)).
 
-**Note**: If the [Github plugin](https://github.com/semantic-release/github) is used the URL must be a valid Github URL that include the `owner`, the `repository` name and the `host`. **The Github shorthand URL is not supported.**
-
 ### tagFormat
 
 Type: `String`
diff --git a/lib/get-config.js b/lib/get-config.js
index abc4bb2..312e09f 100644
--- a/lib/get-config.js
+++ b/lib/get-config.js
@@ -2,9 +2,8 @@ const {castArray, pickBy, isUndefined, isNull, isString, isPlainObject} = requir
 const readPkgUp = require('read-pkg-up');
 const cosmiconfig = require('cosmiconfig');
 const resolveFrom = require('resolve-from');
-const gitUrlParse = require('git-url-parse');
 const debug = require('debug')('semantic-release:config');
-const {repoUrl, verifyAuth} = require('./git');
+const {repoUrl} = require('./git');
 const PLUGINS_DEFINITIONS = require('./definitions/plugins');
 const plugins = require('./plugins');
 const getGitAuthUrl = require('./get-git-auth-url');
@@ -52,9 +51,7 @@ module.exports = async (opts, logger) => {
     ...pickBy(options, option => !isUndefined(option) && !isNull(option)),
   };
 
-  if (!await verifyAuth(options.repositoryUrl, options.branch)) {
-    options.repositoryUrl = options.repositoryUrl ? getGitAuthUrl(options.repositoryUrl) : options.repositoryUrl;
-  }
+  options.repositoryUrl = await getGitAuthUrl(options);
 
   debug('options values: %O', options);
 
@@ -63,12 +60,5 @@ module.exports = async (opts, logger) => {
 
 async function pkgRepoUrl() {
   const {pkg} = await readPkgUp({normalize: false});
-  const repositoryUrl = pkg && (isPlainObject(pkg.repository) ? pkg.repository.url : pkg.repository);
-
-  if (repositoryUrl) {
-    const {protocols} = gitUrlParse(repositoryUrl);
-    return `${gitUrlParse(repositoryUrl).toString(
-      protocols.includes('https') ? 'https' : protocols.includes('http') ? 'http' : undefined
-    )}${protocols.includes('https') || protocols.includes('http') ? '.git' : ''}`;
-  }
+  return pkg && (isPlainObject(pkg.repository) ? pkg.repository.url : pkg.repository);
 }
diff --git a/lib/get-git-auth-url.js b/lib/get-git-auth-url.js
index e1b3d03..71fb955 100644
--- a/lib/get-git-auth-url.js
+++ b/lib/get-git-auth-url.js
@@ -1,30 +1,54 @@
 const {parse, format} = require('url');
 const {isUndefined} = require('lodash');
 const gitUrlParse = require('git-url-parse');
+const hostedGitInfo = require('hosted-git-info');
+const {verifyAuth} = require('./git');
 
 const GIT_TOKENS = ['GIT_CREDENTIALS', 'GH_TOKEN', 'GITHUB_TOKEN', 'GL_TOKEN', 'GITLAB_TOKEN'];
 
 /**
- * Generate the git repository URL with creadentials.
- * If the `gitCredentials` is defined, returns a http or https URL with Basic Authentication (`https://username:passowrd@hostname:port/path.git`).
- * If the `gitCredentials` is undefined, returns the `repositoryUrl`. In that case it's expected for the user to have setup the Git authentication on the CI (for example via SSH keys).
+ * Determine the the git repository URL to use to push, either:
+ * - The `repositoryUrl` as is if allowed to push
+ * - The `repositoryUrl` converted to `https` or `http` with Basic Authentication
  *
- * @param {String} gitCredentials Basic HTTP Authentication credentials, can be `username:password` or a token for certain Git providers.
- * @param {String}  repositoryUrl The git repository URL.
+ * In addition, expand shortcut URLs (`owner/repo` => `https://github.com/owner/repo.git`) and transform `git+https` / `git+http` URLs to `https` / `http`.
+ *
+ * @param {String}  repositoryUrl The user provided Git repository URL.
  * @return {String} The formatted Git repository URL.
  */
-module.exports = repositoryUrl => {
-  const envVar = GIT_TOKENS.find(envVar => !isUndefined(process.env[envVar]));
-  const gitCredentials = ['GL_TOKEN', 'GITLAB_TOKEN'].includes(envVar)
-    ? `gitlab-ci-token:${process.env[envVar]}`
-    : process.env[envVar];
+module.exports = async ({repositoryUrl, branch}) => {
+  if (repositoryUrl) {
+    const info = hostedGitInfo.fromUrl(repositoryUrl, {noGitPlus: true});
 
-  const {protocols} = gitUrlParse(repositoryUrl);
-  const protocol = protocols.includes('https') ? 'https' : protocols.includes('http') ? 'http' : 'https';
+    if (info && info.getDefaultRepresentation() === 'shortcut') {
+      // Expand shorthand URLs (such as `owner/repo` or `gitlab:owner/repo`)
+      repositoryUrl = info.https();
+    } else {
+      const {protocols} = gitUrlParse(repositoryUrl);
 
-  if (!gitCredentials) {
-    return protocols.includes('https') ? `${gitUrlParse(repositoryUrl).toString(protocol)}.git` : repositoryUrl;
-  }
+      // Replace `git+https` and `git+http` with `https` or `http`
+      if (protocols.includes('http') || protocols.includes('https')) {
+        repositoryUrl = format({
+          ...parse(repositoryUrl),
+          ...{protocol: protocols.includes('https') ? 'https' : 'http'},
+        });
+      }
+    }
 
-  return format({...parse(`${gitUrlParse(repositoryUrl).toString(protocol)}.git`), ...{auth: gitCredentials}});
+    // Test if push is allowed without transforming the URL (e.g. is ssh keys are set up)
+    if (!await verifyAuth(repositoryUrl, branch)) {
+      const envVar = GIT_TOKENS.find(envVar => !isUndefined(process.env[envVar]));
+      const gitCredentials = ['GL_TOKEN', 'GITLAB_TOKEN'].includes(envVar)
+        ? `gitlab-ci-token:${process.env[envVar]}`
+        : process.env[envVar];
+      const {protocols} = gitUrlParse(repositoryUrl);
+      const protocol = protocols.includes('https') ? 'https' : protocols.includes('http') ? 'http' : 'https';
+
+      // If credentials are set via anvironment variables, convert the URL to http/https and add basic auth, otherwise return `repositoryUrl` as is
+      return gitCredentials
+        ? format({...parse(`${gitUrlParse(repositoryUrl).toString(protocol)}.git`), ...{auth: gitCredentials}})
+        : repositoryUrl;
+    }
+  }
+  return repositoryUrl;
 };
diff --git a/package.json b/package.json
index 318a636..46bb79b 100644
--- a/package.json
+++ b/package.json
@@ -35,6 +35,7 @@
     "git-log-parser": "^1.2.0",
     "git-url-parse": "^8.1.0",
     "hook-std": "^0.4.0",
+    "hosted-git-info": "^2.6.0",
     "lodash": "^4.17.4",
     "marked": "^0.3.9",
     "marked-terminal": "^2.0.0",


Test patch:
diff --git a/test/get-config.test.js b/test/get-config.test.js
index 57702f6..145adec 100644
--- a/test/get-config.test.js
+++ b/test/get-config.test.js
@@ -32,12 +32,12 @@ test.afterEach.always(() => {
 
 test.serial('Default values, reading repositoryUrl from package.json', async t => {
   process.env.GIT_CREDENTIALS = 'user:pass';
-  const pkg = {repository: 'https://package.com/owner/module.git'};
+  const pkg = {repository: 'https://host.null/owner/package.git'};
   // Create a git repository, set the current working directory at the root of the repo
   await gitRepo();
   await gitCommits(['First']);
   // Add remote.origin.url config
-  await gitAddConfig('remote.origin.url', 'git@repo.com:owner/module.git');
+  await gitAddConfig('remote.origin.url', 'git@host.null:owner/repo.git');
   // Create package.json in repository root
   await outputJson('./package.json', pkg);
 
@@ -45,7 +45,7 @@ test.serial('Default values, reading repositoryUrl from package.json', async t =
 
   // Verify the default options are set
   t.is(options.branch, 'master');
-  t.is(options.repositoryUrl, 'https://user:pass@package.com/owner/module.git');
+  t.is(options.repositoryUrl, 'https://user:pass@host.null/owner/package.git');
   t.is(options.tagFormat, `v\${version}`);
 });
 
@@ -54,19 +54,19 @@ test.serial('Default values, reading repositoryUrl from repo if not set in packa
   // Create a git repository, set the current working directory at the root of the repo
   await gitRepo();
   // Add remote.origin.url config
-  await gitAddConfig('remote.origin.url', 'https://hostname.com/owner/module.git');
+  await gitAddConfig('remote.origin.url', 'https://host.null/owner/module.git');
 
   const {options} = await t.context.getConfig();
 
   // Verify the default options are set
   t.is(options.branch, 'master');
-  t.is(options.repositoryUrl, 'https://user:pass@hostname.com/owner/module.git');
+  t.is(options.repositoryUrl, 'https://user:pass@host.null/owner/module.git');
   t.is(options.tagFormat, `v\${version}`);
 });
 
 test.serial('Default values, reading repositoryUrl (http url) from package.json if not set in repo', async t => {
   process.env.GIT_CREDENTIALS = 'user:pass';
-  const pkg = {repository: 'https://hostname.com/owner/module.git'};
+  const pkg = {repository: 'https://host.null/owner/module.git'};
   // Create a git repository, set the current working directory at the root of the repo
   await gitRepo();
   // Create package.json in repository root
@@ -76,10 +76,45 @@ test.serial('Default values, reading repositoryUrl (http url) from package.json
 
   // Verify the default options are set
   t.is(options.branch, 'master');
-  t.is(options.repositoryUrl, 'https://user:pass@hostname.com/owner/module.git');
+  t.is(options.repositoryUrl, 'https://user:pass@host.null/owner/module.git');
   t.is(options.tagFormat, `v\${version}`);
 });
 
+test.serial('Default values, reading repositoryUrl (shorthand url) from package.json if not set in repo', async t => {
+  process.env.GIT_CREDENTIALS = 'user:pass';
+  const pkg = {repository: 'owner/module'};
+  // Create a git repository, set the current working directory at the root of the repo
+  await gitRepo();
+  // Create package.json in repository root
+  await outputJson('./package.json', pkg);
+
+  const {options} = await t.context.getConfig();
+
+  // Verify the default options are set
+  t.is(options.branch, 'master');
+  t.is(options.repositoryUrl, 'https://user:pass@github.com/owner/module.git');
+  t.is(options.tagFormat, `v\${version}`);
+});
+
+test.serial(
+  'Default values, reading repositoryUrl (gitlab shorthand url) from package.json if not set in repo',
+  async t => {
+    process.env.GIT_CREDENTIALS = 'user:pass';
+    const pkg = {repository: 'gitlab:owner/module'};
+    // Create a git repository, set the current working directory at the root of the repo
+    await gitRepo();
+    // Create package.json in repository root
+    await outputJson('./package.json', pkg);
+
+    const {options} = await t.context.getConfig();
+
+    // Verify the default options are set
+    t.is(options.branch, 'master');
+    t.is(options.repositoryUrl, 'https://user:pass@gitlab.com/owner/module.git');
+    t.is(options.tagFormat, `v\${version}`);
+  }
+);
+
 test.serial('Do not add git credential to repositoryUrl if push is allowed', async t => {
   process.env.GIT_CREDENTIALS = 'user:pass';
   // Create a git repository, set the current working directory at the root of the repo
@@ -98,7 +133,7 @@ test.serial('Read options from package.json', async t => {
     analyzeCommits: {path: 'analyzeCommits', param: 'analyzeCommits_param'},
     generateNotes: 'generateNotes',
     branch: 'test_branch',
-    repositoryUrl: 'https://hostname.com/owner/module.git',
+    repositoryUrl: 'https://host.null/owner/module.git',
     tagFormat: `v\${version}`,
   };
 
@@ -119,7 +154,7 @@ test.serial('Read options from .releaserc.yml', async t => {
   const release = {
     analyzeCommits: {path: 'analyzeCommits', param: 'analyzeCommits_param'},
     branch: 'test_branch',
-    repositoryUrl: 'https://hostname.com/owner/module.git',
+    repositoryUrl: 'https://host.null/owner/module.git',
     tagFormat: `v\${version}`,
   };
 
@@ -140,7 +175,7 @@ test.serial('Read options from .releaserc.json', async t => {
   const release = {
     analyzeCommits: {path: 'analyzeCommits', param: 'analyzeCommits_param'},
     branch: 'test_branch',
-    repositoryUrl: 'https://hostname.com/owner/module.git',
+    repositoryUrl: 'https://host.null/owner/module.git',
     tagFormat: `v\${version}`,
   };
 
@@ -161,7 +196,7 @@ test.serial('Read options from .releaserc.js', async t => {
   const release = {
     analyzeCommits: {path: 'analyzeCommits', param: 'analyzeCommits_param'},
     branch: 'test_branch',
-    repositoryUrl: 'https://hostname.com/owner/module.git',
+    repositoryUrl: 'https://host.null/owner/module.git',
     tagFormat: `v\${version}`,
   };
 
@@ -182,7 +217,7 @@ test.serial('Read options from release.config.js', async t => {
   const release = {
     analyzeCommits: {path: 'analyzeCommits', param: 'analyzeCommits_param'},
     branch: 'test_branch',
-    repositoryUrl: 'https://hostname.com/owner/module.git',
+    repositoryUrl: 'https://host.null/owner/module.git',
     tagFormat: `v\${version}`,
   };
 
@@ -210,7 +245,7 @@ test.serial('Prioritise CLI/API parameters over file configuration and git repo'
     repositoryUrl: 'http://cli-url.com/owner/package',
     tagFormat: `cli\${version}`,
   };
-  const pkg = {release, repository: 'git@hostname.com:owner/module.git'};
+  const pkg = {release, repository: 'git@host.null:owner/module.git'};
   // Create a git repository, set the current working directory at the root of the repo
   const repo = await gitRepo();
   await gitCommits(['First']);
@@ -233,7 +268,7 @@ test.serial('Read configuration from file path in "extends"', async t => {
     analyzeCommits: {path: 'analyzeCommits', param: 'analyzeCommits_param'},
     generateNotes: 'generateNotes',
     branch: 'test_branch',
-    repositoryUrl: 'https://hostname.com/owner/module.git',
+    repositoryUrl: 'https://host.null/owner/module.git',
     tagFormat: `v\${version}`,
   };
 
@@ -261,7 +296,7 @@ test.serial('Read configuration from module path in "extends"', async t => {
     analyzeCommits: {path: 'analyzeCommits', param: 'analyzeCommits_param'},
     generateNotes: 'generateNotes',
     branch: 'test_branch',
-    repositoryUrl: 'https://hostname.com/owner/module.git',
+    repositoryUrl: 'https://host.null/owner/module.git',
     tagFormat: `v\${version}`,
   };
 
@@ -289,7 +324,7 @@ test.serial('Read configuration from an array of paths in "extends"', async t =>
     verifyRelease: 'verifyRelease1',
     analyzeCommits: {path: 'analyzeCommits1', param: 'analyzeCommits_param1'},
     branch: 'test_branch',
-    repositoryUrl: 'https://hostname.com/owner/module.git',
+    repositoryUrl: 'https://host.null/owner/module.git',
   };
 
   const shareable2 = {
@@ -334,7 +369,7 @@ test.serial('Prioritize configuration from config file over "extends"', async t
     generateNotes: 'generateNotesShareable',
     publish: [{path: 'publishShareable', param: 'publishShareable_param'}],
     branch: 'test_branch',
-    repositoryUrl: 'https://hostname.com/owner/module.git',
+    repositoryUrl: 'https://host.null/owner/module.git',
     tagFormat: `v\${version}`,
   };
 
@@ -362,7 +397,7 @@ test.serial('Prioritize configuration from cli/API options over "extends"', asyn
     extends: './shareable2.json',
     branch: 'branch_opts',
     publish: [{path: 'publishOpts', param: 'publishOpts_param'}],
-    repositoryUrl: 'https://hostname.com/owner/module.git',
+    repositoryUrl: 'https://host.null/owner/module.git',
   };
   const release = {
     extends: './shareable1.json',
@@ -375,7 +410,7 @@ test.serial('Prioritize configuration from cli/API options over "extends"', asyn
     generateNotes: 'generateNotesShareable1',
     publish: [{path: 'publishShareable', param: 'publishShareable_param1'}],
     branch: 'test_branch1',
-    repositoryUrl: 'https://hostname.com/owner/module.git',
+    repositoryUrl: 'https://host.null/owner/module.git',
   };
   const shareable2 = {
     analyzeCommits: 'analyzeCommits2',
@@ -404,7 +439,7 @@ test.serial('Allow to unset properties defined in shareable config with "null"',
     extends: './shareable.json',
     analyzeCommits: null,
     branch: 'test_branch',
-    repositoryUrl: 'https://hostname.com/owner/module.git',
+    repositoryUrl: 'https://host.null/owner/module.git',
   };
   const shareable = {
     generateNotes: 'generateNotes',
@@ -438,7 +473,7 @@ test.serial('Allow to unset properties defined in shareable config with "undefin
     extends: './shareable.json',
     analyzeCommits: undefined,
     branch: 'test_branch',
-    repositoryUrl: 'https://hostname.com/owner/module.git',
+    repositoryUrl: 'https://host.null/owner/module.git',
   };
   const shareable = {
     generateNotes: 'generateNotes',
diff --git a/test/get-git-auth-url.test.js b/test/get-git-auth-url.test.js
index 7f2775a..d8009c7 100644
--- a/test/get-git-auth-url.test.js
+++ b/test/get-git-auth-url.test.js
@@ -1,105 +1,157 @@
 import test from 'ava';
 import getAuthUrl from '../lib/get-git-auth-url';
+import {gitRepo} from './helpers/git-utils';
 
 // Save the current process.env
 const envBackup = Object.assign({}, process.env);
+// Save the current working diretory
+const cwd = process.cwd();
 
 test.beforeEach(() => {
-  // Restore process.env
-  process.env = {};
+  delete process.env.GIT_CREDENTIALS;
+  delete process.env.GH_TOKEN;
+  delete process.env.GITHUB_TOKEN;
+  delete process.env.GL_TOKEN;
+  delete process.env.GITLAB_TOKEN;
 });
 
 test.afterEach.always(() => {
   // Restore process.env
   process.env = envBackup;
+  // Restore the current working directory
+  process.chdir(cwd);
 });
 
-test.serial('Return the same "git" formatted URL if "gitCredentials" is not defined', t => {
-  t.is(getAuthUrl('git@host.com:owner/repo.git'), 'git@host.com:owner/repo.git');
+test.serial('Return the same "git" formatted URL if "gitCredentials" is not defined', async t => {
+  t.is(await getAuthUrl({repositoryUrl: 'git@host.null:owner/repo.git'}), 'git@host.null:owner/repo.git');
 });
 
-test.serial('Return the same "https" formatted URL if "gitCredentials" is not defined', t => {
-  t.is(getAuthUrl('https://host.com/owner/repo.git'), 'https://host.com/owner/repo.git');
+test.serial('Return the same "https" formatted URL if "gitCredentials" is not defined', async t => {
+  t.is(await getAuthUrl({repositoryUrl: 'https://host.null/owner/repo.git'}), 'https://host.null/owner/repo.git');
 });
 
 test.serial(
   'Return the "https" formatted URL if "gitCredentials" is not defined and repositoryUrl is a "git+https" URL',
-  t => {
-    t.is(getAuthUrl('git+https://host.com/owner/repo.git'), 'https://host.com/owner/repo.git');
+  async t => {
+    t.is(await getAuthUrl({repositoryUrl: 'git+https://host.null/owner/repo.git'}), 'https://host.null/owner/repo.git');
   }
 );
 
-test.serial('Handle "https" URL with group and subgroup', t => {
-  t.is(getAuthUrl('https://host.com/group/subgroup/owner/repo.git'), 'https://host.com/group/subgroup/owner/repo.git');
+test.serial('Handle "https" URL with group and subgroup', async t => {
+  t.is(
+    await getAuthUrl({repositoryUrl: 'https://host.null/group/subgroup/owner/repo.git'}),
+    'https://host.null/group/subgroup/owner/repo.git'
+  );
 });
 
-test.serial('Handle "git" URL with group and subgroup', t => {
-  t.is(getAuthUrl('git@host.com:group/subgroup/owner/repo.git'), 'git@host.com:group/subgroup/owner/repo.git');
+test.serial('Handle "git" URL with group and subgroup', async t => {
+  t.is(
+    await getAuthUrl({repositoryUrl: 'git@host.null:group/subgroup/owner/repo.git'}),
+    'git@host.null:group/subgroup/owner/repo.git'
+  );
 });
 
-test.serial('Return the "https" formatted URL if "gitCredentials" is defined and repositoryUrl is a "git" URL', t => {
-  process.env.GIT_CREDENTIALS = 'user:pass';
-  t.is(getAuthUrl('git@host.com:owner/repo.git'), 'https://user:pass@host.com/owner/repo.git');
-});
+test.serial(
+  'Return the "https" formatted URL if "gitCredentials" is defined and repositoryUrl is a "git" URL',
+  async t => {
+    process.env.GIT_CREDENTIALS = 'user:pass';
+    t.is(
+      await getAuthUrl({repositoryUrl: 'git@host.null:owner/repo.git'}),
+      'https://user:pass@host.null/owner/repo.git'
+    );
+  }
+);
 
-test.serial('Return the "https" formatted URL if "gitCredentials" is defined and repositoryUrl is a "https" URL', t => {
-  process.env.GIT_CREDENTIALS = 'user:pass';
-  t.is(getAuthUrl('https://host.com/owner/repo.git'), 'https://user:pass@host.com/owner/repo.git');
-});
+test.serial(
+  'Return the "https" formatted URL if "gitCredentials" is defined and repositoryUrl is a "https" URL',
+  async t => {
+    process.env.GIT_CREDENTIALS = 'user:pass';
+    t.is(
+      await getAuthUrl({repositoryUrl: 'https://host.null/owner/repo.git'}),
+      'https://user:pass@host.null/owner/repo.git'
+    );
+  }
+);
 
-test.serial('Return the "http" formatted URL if "gitCredentials" is defined and repositoryUrl is a "http" URL', t => {
-  process.env.GIT_CREDENTIALS = 'user:pass';
-  t.is(getAuthUrl('http://host.com/owner/repo.git'), 'http://user:pass@host.com/owner/repo.git');
-});
+test.serial(
+  'Return the "http" formatted URL if "gitCredentials" is defined and repositoryUrl is a "http" URL',
+  async t => {
+    process.env.GIT_CREDENTIALS = 'user:pass';
+    t.is(
+      await getAuthUrl({repositoryUrl: 'http://host.null/owner/repo.git'}),
+      'http://user:pass@host.null/owner/repo.git'
+    );
+  }
+);
 
 test.serial(
   'Return the "https" formatted URL if "gitCredentials" is defined and repositoryUrl is a "git+https" URL',
-  t => {
+  async t => {
     process.env.GIT_CREDENTIALS = 'user:pass';
-    t.is(getAuthUrl('git+https://host.com/owner/repo.git'), 'https://user:pass@host.com/owner/repo.git');
+    t.is(
+      await getAuthUrl({repositoryUrl: 'git+https://host.null/owner/repo.git'}),
+      'https://user:pass@host.null/owner/repo.git'
+    );
   }
 );
 
 test.serial(
   'Return the "http" formatted URL if "gitCredentials" is defined and repositoryUrl is a "git+http" URL',
-  t => {
+  async t => {
     process.env.GIT_CREDENTIALS = 'user:pass';
-    t.is(getAuthUrl('git+http://host.com/owner/repo.git'), 'http://user:pass@host.com/owner/repo.git');
+    t.is(
+      await getAuthUrl({repositoryUrl: 'git+http://host.null/owner/repo.git'}),
+      'http://user:pass@host.null/owner/repo.git'
+    );
   }
 );
 
-test.serial('Return the "https" formatted URL if "gitCredentials" is defined with "GH_TOKEN"', t => {
+test.serial('Return the "https" formatted URL if "gitCredentials" is defined with "GH_TOKEN"', async t => {
   process.env.GH_TOKEN = 'token';
-  t.is(getAuthUrl('git@host.com:owner/repo.git'), 'https://token@host.com/owner/repo.git');
+  t.is(await getAuthUrl({repositoryUrl: 'git@host.null:owner/repo.git'}), 'https://token@host.null/owner/repo.git');
 });
 
-test.serial('Return the "https" formatted URL if "gitCredentials" is defined with "GITHUB_TOKEN"', t => {
+test.serial('Return the "https" formatted URL if "gitCredentials" is defined with "GITHUB_TOKEN"', async t => {
   process.env.GITHUB_TOKEN = 'token';
-  t.is(getAuthUrl('git@host.com:owner/repo.git'), 'https://token@host.com/owner/repo.git');
+  t.is(await getAuthUrl({repositoryUrl: 'git@host.null:owner/repo.git'}), 'https://token@host.null/owner/repo.git');
 });
 
-test.serial('Return the "https" formatted URL if "gitCredentials" is defined with "GL_TOKEN"', t => {
+test.serial('Return the "https" formatted URL if "gitCredentials" is defined with "GL_TOKEN"', async t => {
   process.env.GL_TOKEN = 'token';
-  t.is(getAuthUrl('git@host.com:owner/repo.git'), 'https://gitlab-ci-token:token@host.com/owner/repo.git');
+  t.is(
+    await getAuthUrl({repositoryUrl: 'git@host.null:owner/repo.git'}),
+    'https://gitlab-ci-token:token@host.null/owner/repo.git'
+  );
 });
 
-test.serial('Return the "https" formatted URL if "gitCredentials" is defined with "GITLAB_TOKEN"', t => {
+test.serial('Return the "https" formatted URL if "gitCredentials" is defined with "GITLAB_TOKEN"', async t => {
   process.env.GITLAB_TOKEN = 'token';
-  t.is(getAuthUrl('git@host.com:owner/repo.git'), 'https://gitlab-ci-token:token@host.com/owner/repo.git');
+  t.is(
+    await getAuthUrl({repositoryUrl: 'git@host.null:owner/repo.git'}),
+    'https://gitlab-ci-token:token@host.null/owner/repo.git'
+  );
 });
 
-test.serial('Handle "https" URL with group and subgroup, with "GIT_CREDENTIALS"', t => {
+test.serial('Handle "https" URL with group and subgroup, with "GIT_CREDENTIALS"', async t => {
   process.env.GIT_CREDENTIALS = 'user:pass';
   t.is(
-    getAuthUrl('https://host.com/group/subgroup/owner/repo.git'),
-    'https://user:pass@host.com/group/subgroup/owner/repo.git'
+    await getAuthUrl({repositoryUrl: 'https://host.null/group/subgroup/owner/repo.git'}),
+    'https://user:pass@host.null/group/subgroup/owner/repo.git'
   );
 });
 
-test.serial('Handle "git" URL with group and subgroup, with "GIT_CREDENTIALS', t => {
+test.serial('Handle "git" URL with group and subgroup, with "GIT_CREDENTIALS', async t => {
   process.env.GIT_CREDENTIALS = 'user:pass';
   t.is(
-    getAuthUrl('git@host.com:group/subgroup/owner/repo.git'),
-    'https://user:pass@host.com/group/subgroup/owner/repo.git'
+    await getAuthUrl({repositoryUrl: 'git@host.null:group/subgroup/owner/repo.git'}),
+    'https://user:pass@host.null/group/subgroup/owner/repo.git'
   );
 });
+
+test.serial('Do not add git credential to repositoryUrl if push is allowed', async t => {
+  process.env.GIT_CREDENTIALS = 'user:pass';
+  // Create a git repository, set the current working directory at the root of the repo
+  const repositoryUrl = await gitRepo(true);
+
+  t.is(await getAuthUrl({repositoryUrl}), repositoryUrl);
+});