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
]