From 164b9af5131108fe0691b715f89c00a201306a83 Mon Sep 17 00:00:00 2001
From: Ayoub Kaanich <kayoub5@live.com>
Date: Thu, 8 Nov 2018 13:21:36 +0100
Subject: [PATCH] feat: gitFs over SSH (#2768)

Support setting git clone protocol `http`, `https` or `ssh`.
Config name: `gitFsProtocol`.
Platform independent.

Closes #2708
---
 lib/config/definitions.js                     |  5 ++--
 lib/config/migration.js                       |  9 +++++++
 lib/platform/git/storage.js                   | 18 +++++++++++++
 lib/platform/github/index.js                  |  9 +++----
 lib/platform/gitlab/index.js                  | 17 ++++++-------
 .../__snapshots__/migration.spec.js.snap      |  2 ++
 test/config/migration.spec.js                 |  2 ++
 test/platform/git/storage.spec.js             | 25 +++++++++++++++++++
 website/docs/self-hosted-configuration.md     |  1 +
 9 files changed, 72 insertions(+), 16 deletions(-)

diff --git a/lib/config/definitions.js b/lib/config/definitions.js
index 8ba309ae7f..f0f0f9c68e 100644
--- a/lib/config/definitions.js
+++ b/lib/config/definitions.js
@@ -182,9 +182,10 @@ const options = [
     name: 'gitFs',
     description: 'Use git for FS operations instead of API. GitHub only.',
     stage: 'repository',
-    type: 'boolean',
+    type: 'string',
+    allowedValues: ['https', 'http', 'ssh'],
     admin: true,
-    default: false,
+    default: null,
   },
   {
     name: 'exposeEnv',
diff --git a/lib/config/migration.js b/lib/config/migration.js
index 6ed9b61d28..e58dcefa19 100644
--- a/lib/config/migration.js
+++ b/lib/config/migration.js
@@ -56,6 +56,15 @@ function migrateConfig(config) {
           );
         }
         delete migratedConfig.pathRules;
+      } else if (key === 'gitFs') {
+        if (val === false) {
+          isMigrated = true;
+          migratedConfig.gitFs = null;
+        }
+        if (val === true) {
+          isMigrated = true;
+          migratedConfig.gitFs = 'https';
+        }
       } else if (key === 'packageFiles' && is.array(val)) {
         isMigrated = true;
         const fileList = [];
diff --git a/lib/platform/git/storage.js b/lib/platform/git/storage.js
index 7bdb4af241..12558edd89 100644
--- a/lib/platform/git/storage.js
+++ b/lib/platform/git/storage.js
@@ -1,6 +1,7 @@
 const fs = require('fs-extra');
 const { join } = require('path');
 const path = require('path');
+const URL = require('url');
 const Git = require('simple-git/promise');
 const convertHrtime = require('convert-hrtime');
 
@@ -279,4 +280,21 @@ function localName(branchName) {
   return branchName.replace(/^origin\//, '');
 }
 
+Storage.getUrl = ({ gitFs, auth, hostname, repository }) => {
+  let protocol = gitFs || 'https';
+  // istanbul ignore if
+  if (protocol.toString() === 'true') {
+    protocol = 'https';
+  }
+  if (protocol === 'ssh') {
+    return `git@${hostname}:${repository}.git`;
+  }
+  return URL.format({
+    protocol,
+    auth,
+    hostname,
+    pathname: repository + '.git',
+  });
+};
+
 module.exports = Storage;
diff --git a/lib/platform/github/index.js b/lib/platform/github/index.js
index 322105cfbb..adb110e859 100644
--- a/lib/platform/github/index.js
+++ b/lib/platform/github/index.js
@@ -307,19 +307,18 @@ async function initRepo({
   // istanbul ignore if
   if (gitFs) {
     logger.debug('Enabling Git FS');
-    let { protocol, host } = URL.parse(opts.endpoint);
+    let { host } = URL.parse(opts.endpoint);
     if (host === 'api.github.com') {
       host = null;
     }
     host = host || 'github.com';
-    protocol = protocol || 'https:';
-    const url = URL.format({
-      protocol,
+    const url = GitStorage.getUrl({
+      gitFs,
       auth:
         config.forkToken ||
         (global.appMode ? `x-access-token:${opts.token}` : opts.token),
       hostname: host,
-      pathname: repository + '.git',
+      repository,
     });
     config.storage = new GitStorage();
     await config.storage.initRepo({
diff --git a/lib/platform/gitlab/index.js b/lib/platform/gitlab/index.js
index 6c7f6022fa..18d844b4c8 100644
--- a/lib/platform/gitlab/index.js
+++ b/lib/platform/gitlab/index.js
@@ -145,14 +145,12 @@ async function initRepo({
     // istanbul ignore if
     if (config.gitFs) {
       logger.debug('Enabling Git FS');
-      let { protocol, host } = URL.parse(opts.endpoint);
-      host = host || 'gitlab.com';
-      protocol = protocol || 'https:';
-      const url = URL.format({
-        protocol,
+      const { host } = URL.parse(opts.endpoint);
+      const url = GitStorage.getUrl({
+        gitFs,
         auth: 'oauth2:' + (config.forkToken || opts.token),
-        hostname: host,
-        pathname: repository + '.git',
+        hostname: host || 'gitlab.com',
+        repository,
       });
       config.storage = new GitStorage();
       await config.storage.initRepo({
@@ -241,10 +239,11 @@ async function commitFilesToBranch(
   message,
   parentBranch = config.baseBranch
 ) {
-  // GitLab does not support push with GitFs
+  // GitLab does not support push with GitFs token
+  // See https://gitlab.com/gitlab-org/gitlab-ce/issues/18106
   let storage = config.storage;
   // istanbul ignore if
-  if (config.gitFs) {
+  if (config.gitFs === 'http' || config.gitFs === 'https') {
     storage = new Storage();
     storage.initRepo(config);
   }
diff --git a/test/config/__snapshots__/migration.spec.js.snap b/test/config/__snapshots__/migration.spec.js.snap
index 917a9d2b9f..d71effdf69 100644
--- a/test/config/__snapshots__/migration.spec.js.snap
+++ b/test/config/__snapshots__/migration.spec.js.snap
@@ -21,6 +21,7 @@ Object {
     "config:js-app",
     "config:js-lib",
   ],
+  "gitFs": null,
   "hostRules": Array [
     Object {},
   ],
@@ -29,6 +30,7 @@ Object {
   ],
   "lockFileMaintenance": Object {
     "automerge": true,
+    "gitFs": "https",
     "schedule": "before 5am",
   },
   "major": Object {
diff --git a/test/config/migration.spec.js b/test/config/migration.spec.js
index 265092f30a..d3e695e18d 100644
--- a/test/config/migration.spec.js
+++ b/test/config/migration.spec.js
@@ -11,6 +11,7 @@ describe('config/migration', () => {
         maintainYarnLock: true,
         onboarding: 'false',
         multipleMajorPrs: true,
+        gitFs: false,
         separateMajorReleases: true,
         separatePatchReleases: true,
         automerge: 'none',
@@ -57,6 +58,7 @@ describe('config/migration', () => {
           },
         ],
         lockFileMaintenance: {
+          gitFs: true,
           automerge: 'any',
           schedule: 'before 5am every day',
         },
diff --git a/test/platform/git/storage.spec.js b/test/platform/git/storage.spec.js
index 1cdbc061c3..f4e56d9fd0 100644
--- a/test/platform/git/storage.spec.js
+++ b/test/platform/git/storage.spec.js
@@ -212,4 +212,29 @@ describe('platform/git/storage', () => {
       expect(await git.getCommitMessages()).toMatchSnapshot();
     });
   });
+
+  describe('Storage.getUrl()', () => {
+    const getUrl = GitStorage.getUrl;
+    it('returns https url', () => {
+      expect(
+        getUrl({
+          gitFs: 'https',
+          auth: 'user:pass',
+          hostname: 'host',
+          repository: 'some/repo',
+        })
+      ).toEqual('https://user:pass@host/some/repo.git');
+    });
+
+    it('returns ssh url', () => {
+      expect(
+        getUrl({
+          gitFs: 'ssh',
+          auth: 'user:pass',
+          hostname: 'host',
+          repository: 'some/repo',
+        })
+      ).toEqual('git@host:some/repo.git');
+    });
+  });
 });
diff --git a/website/docs/self-hosted-configuration.md b/website/docs/self-hosted-configuration.md
index b9a0eee7f7..996d197b65 100644
--- a/website/docs/self-hosted-configuration.md
+++ b/website/docs/self-hosted-configuration.md
@@ -42,6 +42,7 @@ RFC5322-compliant string if you wish to customise the git author for commits.
 ## gitFs
 
 This setting is experimental, and works for GitHub repositories only. If enabled, Renovate will `git clone` repos and use `git` for file operations such as creating branches and committing files.
+Set it to a string specifing the transport used by Git (`https`, `http` or `ssh`).
 
 ## gitPrivateKey
 
-- 
GitLab