From 42627660fb52eed3500140f32af4ce158f4f5098 Mon Sep 17 00:00:00 2001 From: Viacheslav Kabanovich Date: Wed, 23 Mar 2016 13:21:24 -0700 Subject: [PATCH] Changes: 1) remove tail after .git 2) In case SSH protocol is used and failed, suggest connecting git anonymously. If user cancels, do nothing, just cancel the operation. If user confirms, a) set git:// instead git@ b) set host/ instead host: Steps to reproduce: 1. Clone https://github.com/jenkinsci/jenkins 2. Import into Eclipse as a maven project. 3. Select in cli project in Maven Dependencies javassist-3.19.0.GA.jar and call Maven-> Import project(s) from SCM. 'Select Maven artifacts' press Next, then Finish. 4. If you have ssh key: Expected result: repository will be cloned, and 'Select Maven Projects' page will appear with projects to import. Current failure: 'Select Maven Projects' page appears empty. Fixed by (1 - remove tail after .git) 5. If you do not have ssh key: Expected result: a dialog 'Auth fail' appears that suggests connecting git anonymously. If accepted, continues as in 4. Current failure: 'Select Maven Projects' page appears empty. After fix of (1) - failure - system error dialog reporting exception 'Auth failed'. Fixed by (1) and (2). --- org.sonatype.m2e.egit.feature/pom.xml | 2 +- org.sonatype.m2e.egit.repository/pom.xml | 2 +- org.sonatype.m2e.egit.tests/.classpath | 2 +- .../META-INF/MANIFEST.MF | 5 +- org.sonatype.m2e.egit.tests/pom.xml | 2 +- .../m2e/egit/tests/EgitScmHandlerTest.java | 123 ++++++++++++++++++ org.sonatype.m2e.egit/.classpath | 2 +- org.sonatype.m2e.egit/META-INF/MANIFEST.MF | 4 +- org.sonatype.m2e.egit/pom.xml | 2 +- .../m2e/egit/internal/EgitScmHandler.java | 107 ++++++++++++--- pom.xml | 2 +- 11 files changed, 221 insertions(+), 32 deletions(-) diff --git a/org.sonatype.m2e.egit.feature/pom.xml b/org.sonatype.m2e.egit.feature/pom.xml index 407120f..dcb2d40 100644 --- a/org.sonatype.m2e.egit.feature/pom.xml +++ b/org.sonatype.m2e.egit.feature/pom.xml @@ -12,7 +12,7 @@ org.sonatype.m2e.egit org.sonatype.m2e.egit.parent - 0.14.0-SNAPSHOT + 0.15.0-SNAPSHOT org.sonatype.m2e.egit.feature diff --git a/org.sonatype.m2e.egit.repository/pom.xml b/org.sonatype.m2e.egit.repository/pom.xml index c4b625a..ff7f781 100644 --- a/org.sonatype.m2e.egit.repository/pom.xml +++ b/org.sonatype.m2e.egit.repository/pom.xml @@ -6,7 +6,7 @@ org.sonatype.m2e.egit org.sonatype.m2e.egit.parent - 0.14.0-SNAPSHOT + 0.15.0-SNAPSHOT org.sonatype.m2e.egit.repository diff --git a/org.sonatype.m2e.egit.tests/.classpath b/org.sonatype.m2e.egit.tests/.classpath index 798048d..46cec6e 100644 --- a/org.sonatype.m2e.egit.tests/.classpath +++ b/org.sonatype.m2e.egit.tests/.classpath @@ -1,6 +1,6 @@ - + diff --git a/org.sonatype.m2e.egit.tests/META-INF/MANIFEST.MF b/org.sonatype.m2e.egit.tests/META-INF/MANIFEST.MF index 2fb2652..390c6fc 100644 --- a/org.sonatype.m2e.egit.tests/META-INF/MANIFEST.MF +++ b/org.sonatype.m2e.egit.tests/META-INF/MANIFEST.MF @@ -2,13 +2,14 @@ Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: Maven SCM Handler for EGit Tests Bundle-SymbolicName: org.sonatype.m2e.egit.tests;singleton:=true -Bundle-Version: 0.14.0.qualifier +Bundle-Version: 0.15.0.qualifier Bundle-Vendor: Sonatype -Bundle-RequiredExecutionEnvironment: J2SE-1.5 +Bundle-RequiredExecutionEnvironment: JavaSE-1.7 Require-Bundle: org.eclipse.m2e.core, org.eclipse.m2e.scm, org.eclipse.m2e.tests.common, org.eclipse.m2e.maven.runtime, + org.eclipse.jgit;bundle-version="[3.0.0,5.0.0)", org.sonatype.m2e.egit, org.eclipse.core.runtime, org.eclipse.core.resources, diff --git a/org.sonatype.m2e.egit.tests/pom.xml b/org.sonatype.m2e.egit.tests/pom.xml index 6dbfe64..e23d035 100644 --- a/org.sonatype.m2e.egit.tests/pom.xml +++ b/org.sonatype.m2e.egit.tests/pom.xml @@ -12,7 +12,7 @@ org.sonatype.m2e.egit org.sonatype.m2e.egit.parent - 0.14.0-SNAPSHOT + 0.15.0-SNAPSHOT org.sonatype.m2e.egit.tests diff --git a/org.sonatype.m2e.egit.tests/src/org/sonatype/m2e/egit/tests/EgitScmHandlerTest.java b/org.sonatype.m2e.egit.tests/src/org/sonatype/m2e/egit/tests/EgitScmHandlerTest.java index f270298..3202b59 100644 --- a/org.sonatype.m2e.egit.tests/src/org/sonatype/m2e/egit/tests/EgitScmHandlerTest.java +++ b/org.sonatype.m2e.egit.tests/src/org/sonatype/m2e/egit/tests/EgitScmHandlerTest.java @@ -9,7 +9,16 @@ package org.sonatype.m2e.egit.tests; import java.io.File; +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.net.URISyntaxException; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.NullProgressMonitor; +import org.eclipse.core.runtime.SubMonitor; +import org.eclipse.jgit.api.errors.TransportException; +import org.eclipse.jgit.transport.URIish; +import org.eclipse.m2e.scm.MavenProjectScmInfo; import org.eclipse.ui.PlatformUI; import org.sonatype.m2e.egit.internal.EgitScmHandler; @@ -42,4 +51,118 @@ public void testCheckoutNoMaster() throws Exception { assertWorkspaceProject("git-test"); } + + public void testNormalizeURI() throws Exception { + EgitScmHandlerExt handler = new EgitScmHandlerExt(); + + String uri = EgitScmHandler.GIT_SCM_ID + "git@github.com:errai/errai.git/errai-common"; + + assertEquals("ssh://git@github.com/errai/errai.git", handler.normalizeUri(uri, false)); + assertEquals("git://github.com/errai/errai.git", handler.normalizeUri(uri, true)); + } + + /** + * Authentication success is mocked. + * Expected: + * - no exception, + * - EgitScmHandler.runCloneOperation() is called, + * - EgitScmHandler.onAuthFailed() is not called. + */ + public void testAuthFailed1() throws Exception { + EgitScmHandlerExt handler = new EgitScmHandlerExt(); + String url = EgitScmHandlerExt.GIT_SCM_ID + "git@github.com:errai/errai.git/errai-common"; + MavenProjectScmInfo scmInfo = new MavenProjectScmInfo(url, null, null, "HEAD", url, url); + CoreException exc = null; + try { + handler.checkoutProject(scmInfo, null, new NullProgressMonitor()); + } catch (CoreException e) { + exc = e; + } + assertNull(exc); + assertEquals(1, handler.callsOfrunCloneOperation); + assertEquals(0, handler.callsOfonAuthFailed); + } + + /** + * Authentication failure is mocked, user selects cancel to connect anonymously. + * Expected: + * - no exception, + * - EgitScmHandler.runCloneOperation() is called, + * - EgitScmHandler.onAuthFailed() is called. + */ + public void testAuthFailed2() throws Exception { + EgitScmHandlerExt handler = new EgitScmHandlerExt(true, false); + String url = EgitScmHandlerExt.GIT_SCM_ID + "git@github.com:errai/errai.git/errai-common"; + MavenProjectScmInfo scmInfo = new MavenProjectScmInfo(url, null, null, "HEAD", url, url); + CoreException exc = null; + try { + handler.checkoutProject(scmInfo, null, new NullProgressMonitor()); + } catch (CoreException e) { + exc = e; + } + assertNull(exc); + assertEquals(1, handler.callsOfrunCloneOperation); + assertEquals(1, handler.callsOfonAuthFailed); + } + + /** + * Authentication failure is mocked, user selects ok to connect anonymously. + * Expected: + * - no exception, + * - EgitScmHandler.runCloneOperation() is called twice, + * - EgitScmHandler.onAuthFailed() is called. + */ + public void testAuthFailed3() throws Exception { + EgitScmHandlerExt handler = new EgitScmHandlerExt(true, true); + String url = EgitScmHandlerExt.GIT_SCM_ID + "git@github.com:errai/errai.git/errai-common"; + MavenProjectScmInfo scmInfo = new MavenProjectScmInfo(url, null, null, "HEAD", url, url); + CoreException exc = null; + try { + handler.checkoutProject(scmInfo, null, new NullProgressMonitor()); + } catch (CoreException e) { + exc = e; + } + assertNull(exc); + assertEquals(2, handler.callsOfrunCloneOperation); + assertEquals(1, handler.callsOfonAuthFailed); + } + + class EgitScmHandlerExt extends EgitScmHandler { + boolean throwAuthFailed = false; + boolean accessAnonymously = false; + + int callsOfonAuthFailed = 0; + int callsOfrunCloneOperation = 0; + + EgitScmHandlerExt() {} + + EgitScmHandlerExt(boolean throwAuthFailed, boolean accessAnonymously) { + this.throwAuthFailed = throwAuthFailed; + this.accessAnonymously = accessAnonymously; + } + + //Make normalizeUri(String, boolean) accessible. + @Override + public String normalizeUri(String uri, boolean avoidSSH) throws URISyntaxException { + return super.normalizeUri(uri, avoidSSH); + } + + //Mock super + @Override + protected void runCloneOperation(URIish uri, File location, String refName, SubMonitor pm) + throws InvocationTargetException, IOException, InterruptedException { + callsOfrunCloneOperation++; + if(throwAuthFailed) { + throwAuthFailed = false; //next time run smoothly. + throw new InvocationTargetException(new TransportException("Auth failed")); + } + } + + //Mock super + @Override + protected boolean onAuthFailed() { + callsOfonAuthFailed++; + return accessAnonymously; + } + } } diff --git a/org.sonatype.m2e.egit/.classpath b/org.sonatype.m2e.egit/.classpath index 798048d..46cec6e 100644 --- a/org.sonatype.m2e.egit/.classpath +++ b/org.sonatype.m2e.egit/.classpath @@ -1,6 +1,6 @@ - + diff --git a/org.sonatype.m2e.egit/META-INF/MANIFEST.MF b/org.sonatype.m2e.egit/META-INF/MANIFEST.MF index bfe8475..c01abfa 100644 --- a/org.sonatype.m2e.egit/META-INF/MANIFEST.MF +++ b/org.sonatype.m2e.egit/META-INF/MANIFEST.MF @@ -2,9 +2,9 @@ Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: %Bundle-Name Bundle-SymbolicName: org.sonatype.m2e.egit;singleton:=true -Bundle-Version: 0.14.0.qualifier +Bundle-Version: 0.15.0.qualifier Bundle-Vendor: %Bundle-Vendor -Bundle-RequiredExecutionEnvironment: J2SE-1.5 +Bundle-RequiredExecutionEnvironment: JavaSE-1.7 Require-Bundle: org.eclipse.m2e.core;bundle-version="[1.0.0,2.0.0)", org.eclipse.m2e.scm;bundle-version="[1.0.0,2.0.0)", org.eclipse.m2e.core.ui;bundle-version="[1.0.0,2.0.0)", diff --git a/org.sonatype.m2e.egit/pom.xml b/org.sonatype.m2e.egit/pom.xml index 0504e55..623229f 100644 --- a/org.sonatype.m2e.egit/pom.xml +++ b/org.sonatype.m2e.egit/pom.xml @@ -12,7 +12,7 @@ org.sonatype.m2e.egit org.sonatype.m2e.egit.parent - 0.14.0-SNAPSHOT + 0.15.0-SNAPSHOT org.sonatype.m2e.egit diff --git a/org.sonatype.m2e.egit/src/org/sonatype/m2e/egit/internal/EgitScmHandler.java b/org.sonatype.m2e.egit/src/org/sonatype/m2e/egit/internal/EgitScmHandler.java index b4a6b99..490e4bd 100644 --- a/org.sonatype.m2e.egit/src/org/sonatype/m2e/egit/internal/EgitScmHandler.java +++ b/org.sonatype.m2e.egit/src/org/sonatype/m2e/egit/internal/EgitScmHandler.java @@ -20,14 +20,19 @@ import org.eclipse.core.runtime.Status; import org.eclipse.core.runtime.SubMonitor; import org.eclipse.egit.core.op.CloneOperation; +import org.eclipse.jface.dialogs.MessageDialog; import org.eclipse.jgit.lib.ConfigConstants; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.storage.file.FileBasedConfig; +import org.eclipse.jgit.api.errors.TransportException; import org.eclipse.jgit.internal.storage.file.FileRepository; import org.eclipse.jgit.transport.URIish; import org.eclipse.m2e.scm.MavenProjectScmInfo; import org.eclipse.m2e.scm.spi.ScmHandler; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.ui.PlatformUI; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -48,25 +53,41 @@ public void checkoutProject(MavenProjectScmInfo info, File location, IProgressMo log.debug("Checking out project from {} to {}", info, location); SubMonitor pm = SubMonitor.convert(monitor, 100); - try { - URIish uri = getUri(info); - - String refName = getRefName(info); - CloneOperation clone = new CloneOperation(uri, true /* allSelected */, new ArrayList(), location, refName, - "origin", getTimeout()); - clone.run(pm.newChild(99)); - - fixAutoCRLF(clone.getGitDir()); - } catch(InvocationTargetException e) { - Throwable cause = e.getTargetException(); - throw new CoreException(new Status(IStatus.ERROR, getClass().getName(), cause.getMessage(), cause)); - } catch(IOException e) { - throw new CoreException(new Status(IStatus.ERROR, getClass().getName(), e.getMessage(), e)); - } catch(URISyntaxException e) { - throw new CoreException(new Status(IStatus.ERROR, getClass().getName(), e.getMessage(), e)); - } catch(InterruptedException e) { - // The monitor was canceled + try { + boolean avoidSSH = false; + URIish uri = null; + boolean repeat = true; + //The cycle will run maximum twice, first time with avoidSSH = false, + //second, if user chooses it, with avoidSSH = true. + while(repeat) { + repeat = false; + try { + uri = getUri(info, avoidSSH); + String refName = getRefName(info); + runCloneOperation(uri, location, refName, pm); + // + break; + } catch(InvocationTargetException e) { + Throwable cause = e.getTargetException(); + if(!avoidSSH && uri != null && "ssh".equals(uri.getScheme()) && cause instanceof TransportException) { + boolean accessGitAnonimously = onAuthFailed(); + if(accessGitAnonimously) { + avoidSSH = true; + repeat = true; + continue; + } else { + pm.setCanceled(true); + break; + } + } + throw new CoreException(new Status(IStatus.ERROR, getClass().getName(), cause.getMessage(), cause)); + } catch(IOException | URISyntaxException e) { + throw new CoreException(new Status(IStatus.ERROR, getClass().getName(), e.getMessage(), e)); + } catch(InterruptedException e) { + // The monitor was canceled + } + } } finally { pm.done(); } @@ -76,9 +97,32 @@ protected int getTimeout() { return 30; } - protected URIish getUri(MavenProjectScmInfo info) throws URISyntaxException { + protected void runCloneOperation(URIish uri, File location, String refName, SubMonitor pm) + throws InvocationTargetException, IOException, InterruptedException { + CloneOperation clone = new CloneOperation(uri, true /* allSelected */, new ArrayList(), location, refName, + "origin", getTimeout()); + clone.run(pm.newChild(99)); + + fixAutoCRLF(clone.getGitDir()); + } + + protected boolean onAuthFailed() { + final boolean[] result = new boolean[]{false}; + Display.getDefault().syncExec(new Runnable() { + @Override + public void run() { + Shell shell = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getShell(); + String title = "Authentication failed"; + String message = "The clone URL uses the SSH protocol. It seems you do not have a valid SSH key. Do you want to continue with the GIT protocol anonymously?"; + result[0] = MessageDialog.openConfirm(shell, title, message); + } + }); + return result[0]; + } + + protected URIish getUri(MavenProjectScmInfo info, boolean avoidSSH) throws URISyntaxException { String url = info.getRepositoryUrl(); - url = normalizeUri(url); + url = normalizeUri(url, avoidSSH); URIish uri = new URIish(url); @@ -91,7 +135,7 @@ protected URIish getUri(MavenProjectScmInfo info) throws URISyntaxException { return uri; } - protected String normalizeUri(String uri) throws URISyntaxException { + protected String normalizeUri(String uri, boolean avoidSSH) throws URISyntaxException { if(!uri.startsWith(GIT_SCM_ID)) { return uri; } @@ -101,6 +145,27 @@ protected String normalizeUri(String uri) throws URISyntaxException { throw new URISyntaxException(uri, "Invalid git URI"); } + if(avoidSSH) { + String gitPrefix = "git://"; + //Replace @ with :// + if(uri.startsWith("git@")) { + uri = gitPrefix + uri.substring(4); + } + //Replace ':' after host with '/' for git + if(uri.startsWith(gitPrefix)) { + int slash = uri.indexOf("/", gitPrefix.length()); + int colon = uri.indexOf(":", gitPrefix.length()); + if(colon > 0 && slash > colon) { + uri = uri.substring(0, colon) + "/" + uri.substring(colon + 1); + } + } + } + //3. Remove tail after .git + int dotGit = uri.indexOf(".git"); + if(dotGit >= 0 && uri.length() > dotGit + 4) { + uri = uri.substring(0, dotGit + 4); + } + URIish gitUri = new URIish(uri); if(gitUri.getScheme() == null) { if(gitUri.getHost() == null || "file".equals(gitUri.getHost())) { diff --git a/pom.xml b/pom.xml index 1529895..ee49b6b 100644 --- a/pom.xml +++ b/pom.xml @@ -22,7 +22,7 @@ org.sonatype.m2e.egit org.sonatype.m2e.egit.parent - 0.14.0-SNAPSHOT + 0.15.0-SNAPSHOT pom Maven SCM Handler for EGit Parent