Make the supported Transports extensible and discoverable

The new TransportProtocol type describes what a particular Transport
implementation wants in order to support a connection.  3rd parties
can now plug into the Transport.open() logic by implementing their
own TransportProtocol and Transport classes, and registering with
Transport.register().

GUI applications can help the user configure a connection by looking
at the supported fields of a particular TransportProtocol type, which
makes the GUI more dynamic and may better support new Transports.

Change-Id: Iafd8e3a6285261412aac6cba8e2c333f8b7b76a5
Signed-off-by: Shawn O. Pearce <spearce@spearce.org>
This commit is contained in:
Shawn O. Pearce 2011-03-07 15:01:49 -08:00
parent 2a137d8dea
commit 305a8ac45f
15 changed files with 702 additions and 145 deletions

View File

@ -143,7 +143,7 @@ public void tearDown() throws Exception {
@Test
public void testFilterHidesPrivate() throws Exception {
Map<String, Ref> refs;
TransportLocal t = new TransportLocal(src, uriOf(dst)) {
TransportLocal t = new TransportLocal(src, uriOf(dst), dst.getDirectory()) {
@Override
ReceivePack createReceivePack(final Repository db) {
db.close();
@ -206,7 +206,7 @@ public void testSuccess() throws Exception {
// Push this new content to the remote, doing strict validation.
//
TransportLocal t = new TransportLocal(src, uriOf(dst)) {
TransportLocal t = new TransportLocal(src, uriOf(dst), dst.getDirectory()) {
@Override
ReceivePack createReceivePack(final Repository db) {
db.close();

View File

@ -549,6 +549,6 @@ public void testFileProtocol() throws IllegalArgumentException,
public void testMissingPort() throws URISyntaxException {
final String incorrectSshUrl = "ssh://some-host:/path/to/repository.git";
URIish u = new URIish(incorrectSshUrl);
assertFalse(TransportGitSsh.canHandle(u));
assertFalse(TransportGitSsh.PROTO_SSH.canHandle(null, u, null));
}
}

View File

@ -419,6 +419,14 @@ transportExceptionEmptyRef=Empty ref: {0}
transportExceptionInvalid=Invalid {0} {1}:{2}
transportExceptionMissingAssumed=Missing assumed {0}
transportExceptionReadRef=read {0}
transportProtoAmazonS3=Amazon S3
transportProtoBundleFile=Git Bundle File
transportProtoGitAnon=Anonymous Git
transportProtoFTP=FTP
transportProtoHTTP=HTTP
transportProtoLocal=Local Git Repository
transportProtoSFTP=SFTP
transportProtoSSH=SSH
treeEntryAlreadyExists=Tree entry "{0}" already exists.
treeIteratorDoesNotSupportRemove=TreeIterator does not support remove()
truncatedHunkLinesMissingForAncestor=Truncated hunk, at least {0} lines missing for ancestor {1}

View File

@ -479,6 +479,14 @@ public static JGitText get() {
/***/ public String transportExceptionInvalid;
/***/ public String transportExceptionMissingAssumed;
/***/ public String transportExceptionReadRef;
/***/ public String transportProtoAmazonS3;
/***/ public String transportProtoBundleFile;
/***/ public String transportProtoFTP;
/***/ public String transportProtoGitAnon;
/***/ public String transportProtoHTTP;
/***/ public String transportProtoLocal;
/***/ public String transportProtoSFTP;
/***/ public String transportProtoSSH;
/***/ public String treeEntryAlreadyExists;
/***/ public String treeIteratorDoesNotSupportRemove;
/***/ public String truncatedHunkLinesMissingForAncestor;

View File

@ -50,6 +50,7 @@
import org.eclipse.jgit.JGitText;
import org.eclipse.jgit.api.errors.InvalidRemoteException;
import org.eclipse.jgit.api.errors.JGitInternalException;
import org.eclipse.jgit.errors.NoRemoteRepositoryException;
import org.eclipse.jgit.errors.NotSupportedException;
import org.eclipse.jgit.errors.TransportException;
import org.eclipse.jgit.lib.Constants;
@ -121,27 +122,29 @@ public FetchResult call() throws JGitInternalException,
try {
Transport transport = Transport.open(repo, remote);
transport.setCheckFetchedObjects(checkFetchedObjects);
transport.setRemoveDeletedRefs(removeDeletedRefs);
transport.setTimeout(timeout);
transport.setDryRun(dryRun);
if (tagOption != null)
transport.setTagOpt(tagOption);
transport.setFetchThin(thin);
if (credentialsProvider != null)
transport.setCredentialsProvider(credentialsProvider);
try {
transport.setCheckFetchedObjects(checkFetchedObjects);
transport.setRemoveDeletedRefs(removeDeletedRefs);
transport.setTimeout(timeout);
transport.setDryRun(dryRun);
if (tagOption != null)
transport.setTagOpt(tagOption);
transport.setFetchThin(thin);
if (credentialsProvider != null)
transport.setCredentialsProvider(credentialsProvider);
FetchResult result = transport.fetch(monitor, refSpecs);
return result;
} catch (TransportException e) {
throw new JGitInternalException(
JGitText.get().exceptionCaughtDuringExecutionOfFetchCommand,
e);
} finally {
transport.close();
}
} catch (NoRemoteRepositoryException e) {
throw new InvalidRemoteException(MessageFormat.format(
JGitText.get().invalidRemote, remote), e);
} catch (TransportException e) {
throw new JGitInternalException(
JGitText.get().exceptionCaughtDuringExecutionOfFetchCommand,
e);
} catch (URISyntaxException e) {
throw new InvalidRemoteException(MessageFormat.format(
JGitText.get().invalidRemote, remote));

View File

@ -44,9 +44,17 @@ public class InvalidRemoteException extends GitAPIException {
private static final long serialVersionUID = 1L;
/**
* @param msg
* @param msg message describing the invalid remote.
*/
public InvalidRemoteException(String msg) {
super(msg);
}
/**
* @param msg message describing the invalid remote.
* @param cause why the remote is invalid.
*/
public InvalidRemoteException(String msg, Throwable cause) {
super(msg, cause);
}
}

View File

@ -47,6 +47,7 @@
package org.eclipse.jgit.transport;
import java.io.IOException;
import java.lang.ref.WeakReference;
import java.net.URISyntaxException;
import java.text.MessageFormat;
import java.util.ArrayList;
@ -56,6 +57,7 @@
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CopyOnWriteArrayList;
import org.eclipse.jgit.JGitText;
import org.eclipse.jgit.errors.NotSupportedException;
@ -66,7 +68,6 @@
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.storage.pack.PackConfig;
import org.eclipse.jgit.util.FS;
/**
* Connects two Git repositories together and copies objects between them.
@ -90,6 +91,79 @@ public enum Operation {
PUSH;
}
private static final List<WeakReference<TransportProtocol>> protocols =
new CopyOnWriteArrayList<WeakReference<TransportProtocol>>();
static {
// Registration goes backwards in order of priority.
register(TransportLocal.PROTO_LOCAL);
register(TransportBundleFile.PROTO_BUNDLE);
register(TransportAmazonS3.PROTO_S3);
register(TransportGitAnon.PROTO_GIT);
register(TransportSftp.PROTO_SFTP);
register(TransportHttp.PROTO_FTP);
register(TransportHttp.PROTO_HTTP);
register(TransportGitSsh.PROTO_SSH);
}
/**
* Register a TransportProtocol instance for use during open.
* <p>
* Protocol definitions are held by WeakReference, allowing them to be
* garbage collected when the calling application drops all strongly held
* references to the TransportProtocol. Therefore applications should use a
* singleton pattern as described in {@link TransportProtocol}'s class
* documentation to ensure their protocol does not get disabled by garbage
* collection earlier than expected.
* <p>
* The new protocol is registered in front of all earlier protocols, giving
* it higher priority than the built-in protocol definitions.
*
* @param proto
* the protocol definition. Must not be null.
*/
public static void register(TransportProtocol proto) {
protocols.add(0, new WeakReference<TransportProtocol>(proto));
}
/**
* Unregister a TransportProtocol instance.
* <p>
* Unregistering a protocol usually isn't necessary, as protocols are held
* by weak references and will automatically clear when they are garbage
* collected by the JVM. Matching is handled by reference equality, so the
* exact reference given to {@link #register(TransportProtocol)} must be
* used.
*
* @param proto
* the exact object previously given to register.
*/
public static void unregister(TransportProtocol proto) {
for (WeakReference<TransportProtocol> ref : protocols) {
TransportProtocol refProto = ref.get();
if (refProto == null || refProto == proto)
protocols.remove(ref);
}
}
/**
* Obtain a copy of the registered protocols.
*
* @return an immutable copy of the currently registered protocols.
*/
public static List<TransportProtocol> getTransportProtocols() {
int cnt = protocols.size();
List<TransportProtocol> res = new ArrayList<TransportProtocol>(cnt);
for (WeakReference<TransportProtocol> ref : protocols) {
TransportProtocol proto = ref.get();
if (proto != null)
res.add(proto);
else
protocols.remove(ref);
}
return Collections.unmodifiableList(res);
}
/**
* Open a new transport instance to connect two repositories.
* <p>
@ -107,9 +181,12 @@ public enum Operation {
* file and is not a well-formed URL.
* @throws NotSupportedException
* the protocol specified is not supported.
* @throws TransportException
* the transport cannot open this URI.
*/
public static Transport open(final Repository local, final String remote)
throws NotSupportedException, URISyntaxException {
throws NotSupportedException, URISyntaxException,
TransportException {
return open(local, remote, Operation.FETCH);
}
@ -131,13 +208,15 @@ public static Transport open(final Repository local, final String remote)
* file and is not a well-formed URL.
* @throws NotSupportedException
* the protocol specified is not supported.
* @throws TransportException
* the transport cannot open this URI.
*/
public static Transport open(final Repository local, final String remote,
final Operation op) throws NotSupportedException,
URISyntaxException {
URISyntaxException, TransportException {
final RemoteConfig cfg = new RemoteConfig(local.getConfig(), remote);
if (doesNotExist(cfg))
return open(local, new URIish(remote));
return open(local, new URIish(remote), null);
return open(local, cfg, op);
}
@ -158,10 +237,12 @@ public static Transport open(final Repository local, final String remote,
* file and is not a well-formed URL.
* @throws NotSupportedException
* the protocol specified is not supported.
* @throws TransportException
* the transport cannot open this URI.
*/
public static List<Transport> openAll(final Repository local,
final String remote) throws NotSupportedException,
URISyntaxException {
URISyntaxException, TransportException {
return openAll(local, remote, Operation.FETCH);
}
@ -183,14 +264,17 @@ public static List<Transport> openAll(final Repository local,
* file and is not a well-formed URL.
* @throws NotSupportedException
* the protocol specified is not supported.
* @throws TransportException
* the transport cannot open this URI.
*/
public static List<Transport> openAll(final Repository local,
final String remote, final Operation op)
throws NotSupportedException, URISyntaxException {
throws NotSupportedException, URISyntaxException,
TransportException {
final RemoteConfig cfg = new RemoteConfig(local.getConfig(), remote);
if (doesNotExist(cfg)) {
final ArrayList<Transport> transports = new ArrayList<Transport>(1);
transports.add(open(local, new URIish(remote)));
transports.add(open(local, new URIish(remote), null));
return transports;
}
return openAll(local, cfg, op);
@ -210,12 +294,14 @@ public static List<Transport> openAll(final Repository local,
* in remote configuration, only the first is chosen.
* @throws NotSupportedException
* the protocol specified is not supported.
* @throws TransportException
* the transport cannot open this URI.
* @throws IllegalArgumentException
* if provided remote configuration doesn't have any URI
* associated.
*/
public static Transport open(final Repository local, final RemoteConfig cfg)
throws NotSupportedException {
throws NotSupportedException, TransportException {
return open(local, cfg, Operation.FETCH);
}
@ -234,18 +320,20 @@ public static Transport open(final Repository local, final RemoteConfig cfg)
* in remote configuration, only the first is chosen.
* @throws NotSupportedException
* the protocol specified is not supported.
* @throws TransportException
* the transport cannot open this URI.
* @throws IllegalArgumentException
* if provided remote configuration doesn't have any URI
* associated.
*/
public static Transport open(final Repository local,
final RemoteConfig cfg, final Operation op)
throws NotSupportedException {
throws NotSupportedException, TransportException {
final List<URIish> uris = getURIs(cfg, op);
if (uris.isEmpty())
throw new IllegalArgumentException(MessageFormat.format(
JGitText.get().remoteConfigHasNoURIAssociated, cfg.getName()));
final Transport tn = open(local, uris.get(0));
final Transport tn = open(local, uris.get(0), cfg.getName());
tn.applyConfig(cfg);
return tn;
}
@ -264,9 +352,12 @@ public static Transport open(final Repository local,
* configuration.
* @throws NotSupportedException
* the protocol specified is not supported.
* @throws TransportException
* the transport cannot open this URI.
*/
public static List<Transport> openAll(final Repository local,
final RemoteConfig cfg) throws NotSupportedException {
final RemoteConfig cfg) throws NotSupportedException,
TransportException {
return openAll(local, cfg, Operation.FETCH);
}
@ -285,14 +376,16 @@ public static List<Transport> openAll(final Repository local,
* configuration.
* @throws NotSupportedException
* the protocol specified is not supported.
* @throws TransportException
* the transport cannot open this URI.
*/
public static List<Transport> openAll(final Repository local,
final RemoteConfig cfg, final Operation op)
throws NotSupportedException {
throws NotSupportedException, TransportException {
final List<URIish> uris = getURIs(cfg, op);
final List<Transport> transports = new ArrayList<Transport>(uris.size());
for (final URIish uri : uris) {
final Transport tn = open(local, uri);
final Transport tn = open(local, uri, cfg.getName());
tn.applyConfig(cfg);
transports.add(tn);
}
@ -320,37 +413,21 @@ private static boolean doesNotExist(final RemoteConfig cfg) {
}
/**
* Determines whether the transport can handle the given URIish.
* Open a new transport instance to connect two repositories.
*
* @param remote
* @param local
* existing local repository.
* @param uri
* location of the remote repository.
* @param fs
* type of filesystem the local repository is stored on.
* @return true if the protocol is supported.
* @return the new transport instance. Never null.
* @throws NotSupportedException
* the protocol specified is not supported.
* @throws TransportException
* the transport cannot open this URI.
*/
public static boolean canHandleProtocol(final URIish remote, final FS fs) {
if (TransportGitSsh.canHandle(remote))
return true;
else if (TransportHttp.canHandle(remote))
return true;
else if (TransportSftp.canHandle(remote))
return true;
else if (TransportGitAnon.canHandle(remote))
return true;
else if (TransportAmazonS3.canHandle(remote))
return true;
else if (TransportBundleFile.canHandle(remote, fs))
return true;
else if (TransportLocal.canHandle(remote, fs))
return true;
return false;
public static Transport open(final Repository local, final URIish uri)
throws NotSupportedException, TransportException {
return open(local, uri, null);
}
/**
@ -358,36 +435,31 @@ else if (TransportLocal.canHandle(remote, fs))
*
* @param local
* existing local repository.
* @param remote
* @param uri
* location of the remote repository.
* @param remoteName
* name of the remote, if the remote as configured in
* {@code local}; otherwise null.
* @return the new transport instance. Never null.
* @throws NotSupportedException
* the protocol specified is not supported.
* @throws TransportException
* the transport cannot open this URI.
*/
public static Transport open(final Repository local, final URIish remote)
throws NotSupportedException {
if (TransportGitSsh.canHandle(remote))
return new TransportGitSsh(local, remote);
public static Transport open(Repository local, URIish uri, String remoteName)
throws NotSupportedException, TransportException {
for (WeakReference<TransportProtocol> ref : protocols) {
TransportProtocol proto = ref.get();
if (proto == null) {
protocols.remove(ref);
continue;
}
else if (TransportHttp.canHandle(remote))
return new TransportHttp(local, remote);
if (proto.canHandle(local, uri, remoteName))
return proto.open(local, uri, remoteName);
}
else if (TransportSftp.canHandle(remote))
return new TransportSftp(local, remote);
else if (TransportGitAnon.canHandle(remote))
return new TransportGitAnon(local, remote);
else if (TransportAmazonS3.canHandle(remote))
return new TransportAmazonS3(local, remote);
else if (TransportBundleFile.canHandle(remote, local.getFS()))
return new TransportBundleFile(local, remote);
else if (TransportLocal.canHandle(remote, local.getFS()))
return new TransportLocal(local, remote);
throw new NotSupportedException(MessageFormat.format(JGitText.get().URINotSupported, remote));
throw new NotSupportedException(MessageFormat.format(JGitText.get().URINotSupported, uri));
}
/**

View File

@ -53,9 +53,12 @@
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.TreeMap;
import org.eclipse.jgit.JGitText;
@ -66,9 +69,9 @@
import org.eclipse.jgit.lib.ObjectIdRef;
import org.eclipse.jgit.lib.ProgressMonitor;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.Ref.Storage;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.lib.SymbolicRef;
import org.eclipse.jgit.lib.Ref.Storage;
/**
* Transport over the non-Git aware Amazon S3 protocol.
@ -97,11 +100,29 @@
public class TransportAmazonS3 extends HttpTransport implements WalkTransport {
static final String S3_SCHEME = "amazon-s3";
static boolean canHandle(final URIish uri) {
if (!uri.isRemote())
return false;
return S3_SCHEME.equals(uri.getScheme());
}
static final TransportProtocol PROTO_S3 = new TransportProtocol() {
public String getName() {
return "Amazon S3";
}
public Set<String> getSchemes() {
return Collections.singleton(S3_SCHEME);
}
public Set<URIishField> getRequiredFields() {
return Collections.unmodifiableSet(EnumSet.of(URIishField.USER,
URIishField.HOST, URIishField.PATH));
}
public Set<URIishField> getOptionalFields() {
return Collections.unmodifiableSet(EnumSet.of(URIishField.PASS));
}
public Transport open(Repository local, URIish uri, String remoteName)
throws NotSupportedException {
return new TransportAmazonS3(local, uri);
}
};
/** User information necessary to connect to S3. */
private final AmazonS3 s3;

View File

@ -50,32 +50,67 @@
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.Set;
import org.eclipse.jgit.JGitText;
import org.eclipse.jgit.errors.NotSupportedException;
import org.eclipse.jgit.errors.TransportException;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.util.FS;
class TransportBundleFile extends Transport implements TransportBundle {
static boolean canHandle(final URIish uri, FS fs) {
if (uri.getHost() != null || uri.getPort() > 0 || uri.getUser() != null
|| uri.getPass() != null || uri.getPath() == null)
return false;
static final TransportProtocol PROTO_BUNDLE = new TransportProtocol() {
private final String[] schemeNames = { "bundle", "file" }; //$NON-NLS-1$ //$NON-NLS-2$
if ("file".equals(uri.getScheme()) || uri.getScheme() == null) {
final File f = fs.resolve(new File("."), uri.getPath());
return f.isFile() || f.getName().endsWith(".bundle");
private final Set<String> schemeSet = Collections
.unmodifiableSet(new LinkedHashSet<String>(Arrays
.asList(schemeNames)));
@Override
public String getName() {
return JGitText.get().transportProtoBundleFile;
}
return false;
}
public Set<String> getSchemes() {
return schemeSet;
}
@Override
public boolean canHandle(Repository local, URIish uri, String remoteName) {
if (uri.getPath() == null
|| uri.getPort() > 0
|| uri.getUser() != null
|| uri.getPass() != null
|| uri.getHost() != null
|| (uri.getScheme() != null && !getSchemes().contains(uri.getScheme())))
return false;
return true;
}
@Override
public Transport open(Repository local, URIish uri, String remoteName)
throws NotSupportedException, TransportException {
if ("bundle".equals(uri.getScheme())) {
File path = local.getFS().resolve(new File("."), uri.getPath());
return new TransportBundleFile(local, uri, path);
}
// This is an ambiguous reference, it could be a bundle file
// or it could be a Git repository. Allow TransportLocal to
// resolve the path and figure out which type it is by testing
// the target.
//
return TransportLocal.PROTO_LOCAL.open(local, uri, remoteName);
}
};
private final File bundle;
TransportBundleFile(final Repository local, final URIish uri) {
TransportBundleFile(Repository local, URIish uri, File bundlePath) {
super(local, uri);
bundle = local.getFS().resolve(new File("."), uri.getPath()).getAbsoluteFile();
bundle = bundlePath;
}
@Override

View File

@ -55,8 +55,12 @@
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.Collections;
import java.util.EnumSet;
import java.util.Set;
import org.eclipse.jgit.JGitText;
import org.eclipse.jgit.errors.NotSupportedException;
import org.eclipse.jgit.errors.TransportException;
import org.eclipse.jgit.lib.Repository;
@ -70,9 +74,33 @@
class TransportGitAnon extends TcpTransport implements PackTransport {
static final int GIT_PORT = Daemon.DEFAULT_PORT;
static boolean canHandle(final URIish uri) {
return "git".equals(uri.getScheme());
}
static final TransportProtocol PROTO_GIT = new TransportProtocol() {
public String getName() {
return JGitText.get().transportProtoGitAnon;
}
public Set<String> getSchemes() {
return Collections.singleton("git"); //$NON-NLS-1$
}
public Set<URIishField> getRequiredFields() {
return Collections.unmodifiableSet(EnumSet.of(URIishField.HOST,
URIishField.PATH));
}
public Set<URIishField> getOptionalFields() {
return Collections.unmodifiableSet(EnumSet.of(URIishField.PORT));
}
public int getDefaultPort() {
return GIT_PORT;
}
public Transport open(Repository local, URIish uri, String remoteName)
throws NotSupportedException {
return new TransportGitAnon(local, uri);
}
};
TransportGitAnon(final Repository local, final URIish uri) {
super(local, uri);

View File

@ -53,10 +53,16 @@
import java.io.PipedOutputStream;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.EnumSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import org.eclipse.jgit.JGitText;
import org.eclipse.jgit.errors.NoRemoteRepositoryException;
import org.eclipse.jgit.errors.NotSupportedException;
import org.eclipse.jgit.errors.TransportException;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.Repository;
@ -80,20 +86,52 @@
* enumeration, save file modification and hook execution.
*/
public class TransportGitSsh extends SshTransport implements PackTransport {
static boolean canHandle(final URIish uri) {
if (!uri.isRemote())
return false;
final String scheme = uri.getScheme();
if ("ssh".equals(scheme))
return true;
if ("ssh+git".equals(scheme))
return true;
if ("git+ssh".equals(scheme))
return true;
if (scheme == null && uri.getHost() != null && uri.getPath() != null)
return true;
return false;
}
static final TransportProtocol PROTO_SSH = new TransportProtocol() {
private final String[] schemeNames = { "ssh", "ssh+git", "git+ssh" }; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
private final Set<String> schemeSet = Collections
.unmodifiableSet(new LinkedHashSet<String>(Arrays
.asList(schemeNames)));
public String getName() {
return JGitText.get().transportProtoSSH;
}
public Set<String> getSchemes() {
return schemeSet;
}
public Set<URIishField> getRequiredFields() {
return Collections.unmodifiableSet(EnumSet.of(URIishField.HOST,
URIishField.PATH));
}
public Set<URIishField> getOptionalFields() {
return Collections.unmodifiableSet(EnumSet.of(URIishField.USER,
URIishField.PASS, URIishField.PORT));
}
public int getDefaultPort() {
return 22;
}
@Override
public boolean canHandle(Repository local, URIish uri, String remoteName) {
if (uri.getScheme() == null) {
// scp-style URI "host:path" does not have scheme.
return uri.getHost() != null
&& uri.getPath() != null
&& uri.getHost().length() != 0
&& uri.getPath().length() != 0;
}
return super.canHandle(local, uri, remoteName);
}
public Transport open(Repository local, URIish uri, String remoteName)
throws NotSupportedException {
return new TransportGitSsh(local, uri);
}
};
TransportGitSsh(final Repository local, final URIish uri) {
super(local, uri);

View File

@ -72,7 +72,11 @@
import java.security.cert.X509Certificate;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
@ -130,12 +134,69 @@ public class TransportHttp extends HttpTransport implements WalkTransport,
private static final String userAgent = computeUserAgent();
static boolean canHandle(final URIish uri) {
if (!uri.isRemote())
return false;
final String s = uri.getScheme();
return "http".equals(s) || "https".equals(s) || "ftp".equals(s); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
}
static final TransportProtocol PROTO_HTTP = new TransportProtocol() {
private final String[] schemeNames = { "http", "https" }; //$NON-NLS-1$ //$NON-NLS-2$
private final Set<String> schemeSet = Collections
.unmodifiableSet(new LinkedHashSet<String>(Arrays
.asList(schemeNames)));
public String getName() {
return JGitText.get().transportProtoHTTP;
}
public Set<String> getSchemes() {
return schemeSet;
}
public Set<URIishField> getRequiredFields() {
return Collections.unmodifiableSet(EnumSet.of(URIishField.HOST,
URIishField.PATH));
}
public Set<URIishField> getOptionalFields() {
return Collections.unmodifiableSet(EnumSet.of(URIishField.USER,
URIishField.PASS, URIishField.PORT));
}
public int getDefaultPort() {
return 80;
}
public Transport open(Repository local, URIish uri, String remoteName)
throws NotSupportedException {
return new TransportHttp(local, uri);
}
};
static final TransportProtocol PROTO_FTP = new TransportProtocol() {
public String getName() {
return JGitText.get().transportProtoFTP;
}
public Set<String> getSchemes() {
return Collections.singleton("ftp"); //$NON-NLS-1$
}
public Set<URIishField> getRequiredFields() {
return Collections.unmodifiableSet(EnumSet.of(URIishField.HOST,
URIishField.PATH));
}
public Set<URIishField> getOptionalFields() {
return Collections.unmodifiableSet(EnumSet.of(URIishField.USER,
URIishField.PASS, URIishField.PORT));
}
public int getDefaultPort() {
return 21;
}
public Transport open(Repository local, URIish uri, String remoteName)
throws NotSupportedException {
return new TransportHttp(local, uri);
}
};
private static String computeUserAgent() {
String version;

View File

@ -55,15 +55,17 @@
import java.io.OutputStream;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
import java.util.Collections;
import java.util.Map;
import java.util.Set;
import org.eclipse.jgit.JGitText;
import org.eclipse.jgit.errors.NoRemoteRepositoryException;
import org.eclipse.jgit.errors.NotSupportedException;
import org.eclipse.jgit.errors.TransportException;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.lib.RepositoryCache;
import org.eclipse.jgit.storage.file.FileRepository;
import org.eclipse.jgit.util.FS;
import org.eclipse.jgit.util.io.MessageWriter;
import org.eclipse.jgit.util.io.StreamCopyThread;
@ -91,27 +93,50 @@
* system pipe to transfer data.
*/
class TransportLocal extends Transport implements PackTransport {
private static final String PWD = ".";
static final TransportProtocol PROTO_LOCAL = new TransportProtocol() {
@Override
public String getName() {
return JGitText.get().transportProtoLocal;
}
static boolean canHandle(final URIish uri, FS fs) {
if (uri.getHost() != null || uri.getPort() > 0 || uri.getUser() != null
|| uri.getPass() != null || uri.getPath() == null)
return false;
public Set<String> getSchemes() {
return Collections.singleton("file"); //$NON-NLS-1$
}
if ("file".equals(uri.getScheme()) || uri.getScheme() == null)
return fs.resolve(new File(PWD), uri.getPath()).isDirectory();
return false;
}
@Override
public boolean canHandle(Repository local, URIish uri, String remoteName) {
if (uri.getPath() == null
|| uri.getPort() > 0
|| uri.getUser() != null
|| uri.getPass() != null
|| uri.getHost() != null
|| (uri.getScheme() != null && !getSchemes().contains(uri.getScheme())))
return false;
return true;
}
@Override
public Transport open(Repository local, URIish uri, String remoteName)
throws NoRemoteRepositoryException {
// If the reference is to a local file, C Git behavior says
// assume this is a bundle, since repositories are directories.
//
File path = local.getFS().resolve(new File("."), uri.getPath());
if (path.isFile())
return new TransportBundleFile(local, uri, path);
File gitDir = RepositoryCache.FileKey.resolve(path, local.getFS());
if (gitDir == null)
throw new NoRemoteRepositoryException(uri, JGitText.get().notFound);
return new TransportLocal(local, uri, gitDir);
}
};
private final File remoteGitDir;
TransportLocal(final Repository local, final URIish uri) {
TransportLocal(Repository local, URIish uri, File gitDir) {
super(local, uri);
File d = local.getFS().resolve(new File(PWD), uri.getPath()).getAbsoluteFile();
if (new File(d, Constants.DOT_GIT).isDirectory())
d = new File(d, Constants.DOT_GIT);
remoteGitDir = d;
remoteGitDir = gitDir;
}
UploadPack createUploadPack(final Repository dst) {

View File

@ -0,0 +1,221 @@
/*
* Copyright (C) 2011, Google Inc.
* and other copyright owners as documented in the project's IP log.
*
* This program and the accompanying materials are made available
* under the terms of the Eclipse Distribution License v1.0 which
* accompanies this distribution, is reproduced below, and is
* available at http://www.eclipse.org/org/documents/edl-v10.php
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or
* without modification, are permitted provided that the following
* conditions are met:
*
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following
* disclaimer in the documentation and/or other materials provided
* with the distribution.
*
* - Neither the name of the Eclipse Foundation, Inc. nor the
* names of its contributors may be used to endorse or promote
* products derived from this software without specific prior
* written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
* CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package org.eclipse.jgit.transport;
import java.util.Collections;
import java.util.EnumSet;
import java.util.Set;
import org.eclipse.jgit.errors.NotSupportedException;
import org.eclipse.jgit.errors.TransportException;
import org.eclipse.jgit.lib.Repository;
/**
* Describes a way to connect to another Git repository.
* <p>
* Implementations of this class are typically immutable singletons held by
* static class members, for example:
*
* <pre>
* class MyTransport extends Transport {
* static final TransportProtocol PROTO = new TransportProtocol() {
* public String getName() {
* return &quot;My Protocol&quot;;
* }
* };
* }
* </pre>
*
* Applications may register additional protocols for use by JGit by calling
* {@link Transport#register(TransportProtocol)}. Because that API holds onto
* the protocol object by a WeakReference, applications must ensure their own
* ClassLoader retains the TransportProtocol for the life of the application.
* Using a static singleton pattern as above will ensure the protocol is valid
* so long as the ClassLoader that defines it remains valid.
*/
public abstract class TransportProtocol {
/** Fields within a {@link URIish} that a transport uses. */
public static enum URIishField {
/** the user field */
USER,
/** the pass (aka password) field */
PASS,
/** the host field */
HOST,
/** the port field */
PORT,
/** the path field */
PATH,
}
/** @return text name of the protocol suitable for display to a user. */
public abstract String getName();
/** @return immutable set of schemes supported by this protocol. */
public Set<String> getSchemes() {
return Collections.emptySet();
}
/** @return immutable set of URIishFields that must be filled in. */
public Set<URIishField> getRequiredFields() {
return Collections.unmodifiableSet(EnumSet.of(URIishField.PATH));
}
/** @return immutable set of URIishFields that may be filled in. */
public Set<URIishField> getOptionalFields() {
return Collections.emptySet();
}
/** @return if a port is supported, the default port, else -1. */
public int getDefaultPort() {
return -1;
}
/**
* Determine if this protocol can handle a particular URI.
* <p>
* Implementations should try to avoid looking at the local filesystem, but
* may look at implementation specific configuration options in the remote
* block of {@code local.getConfig()} using {@code remoteName} if the name
* is non-null.
* <p>
* The default implementation of this method matches the scheme against
* {@link #getSchemes()}, required fields against
* {@link #getRequiredFields()}, and optional fields against
* {@link #getOptionalFields()}, returning true only if all of the fields
* match the specification.
*
* @param local
* the local repository that will communicate with the other Git
* repository.
* @param uri
* address of the Git repository; never null.
* @param remoteName
* name of the remote, if the remote as configured in
* {@code local}; otherwise null.
* @return true if this protocol can handle this URI; false otherwise.
*/
public boolean canHandle(Repository local, URIish uri, String remoteName) {
if (!getSchemes().isEmpty() && !getSchemes().contains(uri.getScheme()))
return false;
for (URIishField field : getRequiredFields()) {
switch (field) {
case USER:
if (uri.getUser() == null || uri.getUser().length() == 0)
return false;
break;
case PASS:
if (uri.getPass() == null || uri.getPass().length() == 0)
return false;
break;
case HOST:
if (uri.getHost() == null || uri.getHost().length() == 0)
return false;
break;
case PORT:
if (uri.getPort() <= 0)
return false;
break;
case PATH:
if (uri.getPath() == null || uri.getPath().length() == 0)
return false;
break;
default:
return false;
}
}
Set<URIishField> canHave = EnumSet.copyOf(getRequiredFields());
canHave.addAll(getOptionalFields());
if (uri.getUser() != null && !canHave.contains(URIishField.USER))
return false;
if (uri.getPass() != null && !canHave.contains(URIishField.PASS))
return false;
if (uri.getHost() != null && !canHave.contains(URIishField.HOST))
return false;
if (uri.getPort() > 0 && !canHave.contains(URIishField.PORT))
return false;
if (uri.getPath() != null && !canHave.contains(URIishField.PATH))
return false;
return true;
}
/**
* Open a Transport instance to the other repository.
* <p>
* Implementations should avoid making remote connections until an operation
* on the returned Transport is invoked, however they may fail fast here if
* they know a connection is impossible, such as when using the local
* filesystem and the target path does not exist.
* <p>
* Implementations may access implementation-specific configuration options
* within {@code local.getConfig()} using the remote block named by the
* {@code remoteName}, if the name is non-null.
*
* @param local
* the local repository that will communicate with the other Git
* repository.
* @param uri
* address of the Git repository.
* @param remoteName
* name of the remote, if the remote as configured in
* {@code local}; otherwise null.
* @return the transport.
* @throws NotSupportedException
* this protocol does not support the URI.
* @throws TransportException
* the transport cannot open this URI.
*/
public abstract Transport open(Repository local, URIish uri,
String remoteName)
throws NotSupportedException, TransportException;
}

View File

@ -51,20 +51,24 @@
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import org.eclipse.jgit.JGitText;
import org.eclipse.jgit.errors.NotSupportedException;
import org.eclipse.jgit.errors.TransportException;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectIdRef;
import org.eclipse.jgit.lib.ProgressMonitor;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.Ref.Storage;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.lib.SymbolicRef;
import org.eclipse.jgit.lib.Ref.Storage;
import com.jcraft.jsch.Channel;
import com.jcraft.jsch.ChannelSftp;
@ -93,9 +97,34 @@
* @see WalkFetchConnection
*/
public class TransportSftp extends SshTransport implements WalkTransport {
static boolean canHandle(final URIish uri) {
return uri.isRemote() && "sftp".equals(uri.getScheme());
}
static final TransportProtocol PROTO_SFTP = new TransportProtocol() {
public String getName() {
return JGitText.get().transportProtoSFTP;
}
public Set<String> getSchemes() {
return Collections.singleton("sftp"); //$NON-NLS-1$
}
public Set<URIishField> getRequiredFields() {
return Collections.unmodifiableSet(EnumSet.of(URIishField.HOST,
URIishField.PATH));
}
public Set<URIishField> getOptionalFields() {
return Collections.unmodifiableSet(EnumSet.of(URIishField.USER,
URIishField.PASS, URIishField.PORT));
}
public int getDefaultPort() {
return 22;
}
public Transport open(Repository local, URIish uri, String remoteName)
throws NotSupportedException {
return new TransportSftp(local, uri);
}
};
TransportSftp(final Repository local, final URIish uri) {
super(local, uri);