From 4546ecd67ab2497af51144120667737e773e2dcc Mon Sep 17 00:00:00 2001
From: Winnie Hellmann <winnie@gitlab.com>
Date: Tue, 1 Aug 2017 18:06:06 +0000
Subject: [PATCH] Derive project path from import URL

---
 .../javascripts/projects/project_new.js       |  64 +++++++--
 .../unreleased/winh-derive-project-name.yml   |   4 +
 spec/javascripts/projects/project_new_spec.js | 127 ++++++++++++++++++
 3 files changed, 184 insertions(+), 11 deletions(-)
 create mode 100644 changelogs/unreleased/winh-derive-project-name.yml
 create mode 100644 spec/javascripts/projects/project_new_spec.js

diff --git a/app/assets/javascripts/projects/project_new.js b/app/assets/javascripts/projects/project_new.js
index 2091b275c3d..1dc1dbf356d 100644
--- a/app/assets/javascripts/projects/project_new.js
+++ b/app/assets/javascripts/projects/project_new.js
@@ -1,6 +1,40 @@
-document.addEventListener('DOMContentLoaded', () => {
+let hasUserDefinedProjectPath = false;
+
+const deriveProjectPathFromUrl = ($projectImportUrl, $projectPath) => {
+  if ($projectImportUrl.attr('disabled') || hasUserDefinedProjectPath) {
+    return;
+  }
+
+  let importUrl = $projectImportUrl.val().trim();
+  if (importUrl.length === 0) {
+    return;
+  }
+
+  /*
+    \/?: remove trailing slash
+    (\.git\/?)?: remove trailing .git (with optional trailing slash)
+    (\?.*)?: remove query string
+    (#.*)?: remove fragment identifier
+  */
+  importUrl = importUrl.replace(/\/?(\.git\/?)?(\?.*)?(#.*)?$/, '');
+
+  // extract everything after the last slash
+  const pathMatch = /\/([^/]+)$/.exec(importUrl);
+  if (pathMatch) {
+    $projectPath.val(pathMatch[1]);
+  }
+};
+
+const bindEvents = () => {
+  const $newProjectForm = $('#new_project');
   const importBtnTooltip = 'Please enter a valid project name.';
   const $importBtnWrapper = $('.import_gitlab_project');
+  const $projectImportUrl = $('#project_import_url');
+  const $projectPath = $('#project_path');
+
+  if ($newProjectForm.length !== 1) {
+    return;
+  }
 
   $('.how_to_import_link').on('click', (e) => {
     e.preventDefault();
@@ -13,19 +47,19 @@ document.addEventListener('DOMContentLoaded', () => {
 
   $('.btn_import_gitlab_project').on('click', () => {
     const importHref = $('a.btn_import_gitlab_project').attr('href');
-    $('.btn_import_gitlab_project').attr('href', `${importHref}?namespace_id=${$('#project_namespace_id').val()}&path=${$('#project_path').val()}`);
+    $('.btn_import_gitlab_project').attr('href', `${importHref}?namespace_id=${$('#project_namespace_id').val()}&path=${$projectPath.val()}`);
   });
 
-  $('.btn_import_gitlab_project').attr('disabled', !$('#project_path').val().trim().length);
+  $('.btn_import_gitlab_project').attr('disabled', !$projectPath.val().trim().length);
   $importBtnWrapper.attr('title', importBtnTooltip);
 
-  $('#new_project').on('submit', () => {
-    const $path = $('#project_path');
-    $path.val($path.val().trim());
+  $newProjectForm.on('submit', () => {
+    $projectPath.val($projectPath.val().trim());
   });
 
-  $('#project_path').on('keyup', () => {
-    if ($('#project_path').val().trim().length) {
+  $projectPath.on('keyup', () => {
+    hasUserDefinedProjectPath = $projectPath.val().trim().length > 0;
+    if (hasUserDefinedProjectPath) {
       $('.btn_import_gitlab_project').attr('disabled', false);
       $importBtnWrapper.attr('title', '');
       $importBtnWrapper.removeClass('has-tooltip');
@@ -35,9 +69,17 @@ document.addEventListener('DOMContentLoaded', () => {
     }
   });
 
-  $('#project_import_url').disable();
+  $projectImportUrl.disable();
+  $projectImportUrl.keyup(() => deriveProjectPathFromUrl($projectImportUrl, $projectPath));
+
   $('.import_git').on('click', () => {
-    const $projectImportUrl = $('#project_import_url');
     $projectImportUrl.attr('disabled', !$projectImportUrl.attr('disabled'));
   });
-});
+};
+
+document.addEventListener('DOMContentLoaded', bindEvents);
+
+export default {
+  bindEvents,
+  deriveProjectPathFromUrl,
+};
diff --git a/changelogs/unreleased/winh-derive-project-name.yml b/changelogs/unreleased/winh-derive-project-name.yml
new file mode 100644
index 00000000000..2244d21d768
--- /dev/null
+++ b/changelogs/unreleased/winh-derive-project-name.yml
@@ -0,0 +1,4 @@
+---
+title: Derive project path from import URL
+merge_request: 13131
+author:
diff --git a/spec/javascripts/projects/project_new_spec.js b/spec/javascripts/projects/project_new_spec.js
new file mode 100644
index 00000000000..850768f0e4f
--- /dev/null
+++ b/spec/javascripts/projects/project_new_spec.js
@@ -0,0 +1,127 @@
+import projectNew from '~/projects/project_new';
+
+describe('New Project', () => {
+  let $projectImportUrl;
+  let $projectPath;
+
+  beforeEach(() => {
+    setFixtures(`
+      <input id="project_import_url" />
+      <input id="project_path" />
+    `);
+
+    $projectImportUrl = $('#project_import_url');
+    $projectPath = $('#project_path');
+  });
+
+  describe('deriveProjectPathFromUrl', () => {
+    const dummyImportUrl = `${gl.TEST_HOST}/dummy/import/url.git`;
+
+    beforeEach(() => {
+      projectNew.bindEvents();
+      $projectPath.val('').keyup().val(dummyImportUrl);
+    });
+
+    it('does not change project path for disabled $projectImportUrl', () => {
+      $projectImportUrl.attr('disabled', true);
+
+      projectNew.deriveProjectPathFromUrl($projectImportUrl, $projectPath);
+
+      expect($projectPath.val()).toEqual(dummyImportUrl);
+    });
+
+    describe('for enabled $projectImportUrl', () => {
+      beforeEach(() => {
+        $projectImportUrl.attr('disabled', false);
+      });
+
+      it('does not change project path if it is set by user', () => {
+        $projectPath.keyup();
+
+        projectNew.deriveProjectPathFromUrl($projectImportUrl, $projectPath);
+
+        expect($projectPath.val()).toEqual(dummyImportUrl);
+      });
+
+      it('does not change project path for empty $projectImportUrl', () => {
+        $projectImportUrl.val('');
+
+        projectNew.deriveProjectPathFromUrl($projectImportUrl, $projectPath);
+
+        expect($projectPath.val()).toEqual(dummyImportUrl);
+      });
+
+      it('does not change project path for whitespace $projectImportUrl', () => {
+        $projectImportUrl.val('   ');
+
+        projectNew.deriveProjectPathFromUrl($projectImportUrl, $projectPath);
+
+        expect($projectPath.val()).toEqual(dummyImportUrl);
+      });
+
+      it('does not change project path for $projectImportUrl without slashes', () => {
+        $projectImportUrl.val('has-no-slash');
+
+        projectNew.deriveProjectPathFromUrl($projectImportUrl, $projectPath);
+
+        expect($projectPath.val()).toEqual(dummyImportUrl);
+      });
+
+      it('changes project path to last $projectImportUrl component', () => {
+        $projectImportUrl.val('/this/is/last');
+
+        projectNew.deriveProjectPathFromUrl($projectImportUrl, $projectPath);
+
+        expect($projectPath.val()).toEqual('last');
+      });
+
+      it('ignores trailing slashes in $projectImportUrl', () => {
+        $projectImportUrl.val('/has/trailing/slash/');
+
+        projectNew.deriveProjectPathFromUrl($projectImportUrl, $projectPath);
+
+        expect($projectPath.val()).toEqual('slash');
+      });
+
+      it('ignores fragment identifier in $projectImportUrl', () => {
+        $projectImportUrl.val('/this/has/a#fragment-identifier/');
+
+        projectNew.deriveProjectPathFromUrl($projectImportUrl, $projectPath);
+
+        expect($projectPath.val()).toEqual('a');
+      });
+
+      it('ignores query string in $projectImportUrl', () => {
+        $projectImportUrl.val('/url/with?query=string');
+
+        projectNew.deriveProjectPathFromUrl($projectImportUrl, $projectPath);
+
+        expect($projectPath.val()).toEqual('with');
+      });
+
+      it('ignores trailing .git in $projectImportUrl', () => {
+        $projectImportUrl.val('/repository.git');
+
+        projectNew.deriveProjectPathFromUrl($projectImportUrl, $projectPath);
+
+        expect($projectPath.val()).toEqual('repository');
+      });
+
+      it('changes project path for HTTPS URL in $projectImportUrl', () => {
+        $projectImportUrl.val('https://username:password@gitlab.company.com/group/project.git');
+
+        projectNew.deriveProjectPathFromUrl($projectImportUrl, $projectPath);
+
+        expect($projectPath.val()).toEqual('project');
+      });
+
+      it('changes project path for SSH URL in $projectImportUrl', () => {
+        $projectImportUrl.val('git@gitlab.com:gitlab-org/gitlab-ce.git');
+
+        projectNew.deriveProjectPathFromUrl($projectImportUrl, $projectPath);
+
+        expect($projectPath.val()).toEqual('gitlab-ce');
+      });
+    });
+  });
+});
-- 
GitLab