You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@mina.apache.org by gn...@apache.org on 2014/06/04 20:32:16 UTC

[4/4] git commit: [SSHD-241] Git/SSH command support

[SSHD-241] Git/SSH command support

Project: http://git-wip-us.apache.org/repos/asf/mina-sshd/repo
Commit: http://git-wip-us.apache.org/repos/asf/mina-sshd/commit/aca9f534
Tree: http://git-wip-us.apache.org/repos/asf/mina-sshd/tree/aca9f534
Diff: http://git-wip-us.apache.org/repos/asf/mina-sshd/diff/aca9f534

Branch: refs/heads/master
Commit: aca9f53497d8b9b1bdb48b5d688c919e9cce720a
Parents: b82143f
Author: Guillaume Nodet <gn...@apache.org>
Authored: Wed Jun 4 20:31:59 2014 +0200
Committer: Guillaume Nodet <gn...@apache.org>
Committed: Wed Jun 4 20:31:59 2014 +0200

----------------------------------------------------------------------
 pom.xml                                         |  17 +-
 sshd-git/pom.xml                                |  16 +-
 .../java/org/apache/sshd/git/GitCommand.java    | 177 --------------
 .../org/apache/sshd/git/GitCommandFactory.java  |  51 ----
 .../apache/sshd/git/pack/GitPackCommand.java    | 193 +++++++++++++++
 .../sshd/git/pack/GitPackCommandFactory.java    |  54 +++++
 .../sshd/git/pgm/EmbeddedCommandRunner.java     | 238 +++++++++++++++++++
 .../org/apache/sshd/git/pgm/GitPgmCommand.java  | 183 ++++++++++++++
 .../sshd/git/pgm/GitPgmCommandFactory.java      |  54 +++++
 .../git/transport/GitSshdSessionFactory.java    | 136 +++++++++++
 .../java/org/apache/sshd/git/GitServerTest.java |  67 ------
 .../sshd/git/pack/GitPackCommandTest.java       |  82 +++++++
 .../apache/sshd/git/pgm/GitPgmCommandTest.java  |  98 ++++++++
 .../java/org/apache/sshd/git/util/Utils.java    |  14 ++
 14 files changed, 1077 insertions(+), 303 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/aca9f534/pom.xml
----------------------------------------------------------------------
diff --git a/pom.xml b/pom.xml
index 6223d71..92d65aa 100644
--- a/pom.xml
+++ b/pom.xml
@@ -74,6 +74,11 @@
             <name>Apache ServiceMix Repository</name>
             <url>http://svn.apache.org/repos/asf/servicemix/m2-repo</url>
         </repository>
+        <repository>
+            <id>jgit</id>
+            <name>Eclipse JGit repository</name>
+            <url>http://repo.eclipse.org/content/groups/jgit</url>
+        </repository>
     </repositories>
 
     <scm>
@@ -185,6 +190,16 @@
                 <artifactId>ganymed-ssh2</artifactId>
                 <version>build210</version>
             </dependency>
+            <dependency>
+                <groupId>org.eclipse.jgit</groupId>
+                <artifactId>org.eclipse.jgit</artifactId>
+                <version>3.4.0.201405281120-rc2</version>
+            </dependency>
+            <dependency>
+                <groupId>org.eclipse.jgit</groupId>
+                <artifactId>org.eclipse.jgit.pgm</artifactId>
+                <version>3.4.0.201405281120-rc2</version>
+            </dependency>
         </dependencies>
     </dependencyManagement>
 
@@ -287,9 +302,7 @@
         <module>sshd-core</module>
         <module>sshd-pam</module>
         <module>sshd-sftp</module>
-        <!--
         <module>sshd-git</module>
-        -->
         <module>assembly</module>
     </modules>
 

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/aca9f534/sshd-git/pom.xml
----------------------------------------------------------------------
diff --git a/sshd-git/pom.xml b/sshd-git/pom.xml
index 3dcdc4a..904a0dd 100644
--- a/sshd-git/pom.xml
+++ b/sshd-git/pom.xml
@@ -24,12 +24,12 @@
     <parent>
         <groupId>org.apache.sshd</groupId>
         <artifactId>sshd</artifactId>
-        <version>0.9.0-SNAPSHOT</version>
+        <version>0.11.1-SNAPSHOT</version>
     </parent>
 
     <groupId>org.apache.sshd</groupId>
     <artifactId>sshd-git</artifactId>
-    <version>0.9.0-SNAPSHOT</version>
+    <version>0.11.1-SNAPSHOT</version>
     <name>Apache Mina SSHD :: Git</name>
     <packaging>jar</packaging>
     <inceptionYear>2008</inceptionYear>
@@ -46,12 +46,10 @@
         <dependency>
             <groupId>org.eclipse.jgit</groupId>
             <artifactId>org.eclipse.jgit</artifactId>
-            <version>3.1.0-SNAPSHOT</version>
         </dependency>
         <dependency>
             <groupId>org.eclipse.jgit</groupId>
             <artifactId>org.eclipse.jgit.pgm</artifactId>
-            <version>3.1.0-SNAPSHOT</version>
         </dependency>
 
         <dependency>
@@ -70,8 +68,13 @@
             <scope>test</scope>
         </dependency>
         <dependency>
-            <groupId>bouncycastle</groupId>
-            <artifactId>bcprov-jdk15</artifactId>
+            <groupId>org.bouncycastle</groupId>
+            <artifactId>bcpg-jdk15on</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.bouncycastle</groupId>
+            <artifactId>bcpkix-jdk15on</artifactId>
             <scope>test</scope>
         </dependency>
     </dependencies>
@@ -92,6 +95,7 @@
                     <instructions>
                         <Bundle-SymbolicName>org.apache.sshd.git</Bundle-SymbolicName>
                         <Import-Package>
+                            org.eclipse.jgit.pgm;resolution:=optional,
                             *
                         </Import-Package>
                         <Export-Package>

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/aca9f534/sshd-git/src/main/java/org/apache/sshd/git/GitCommand.java
----------------------------------------------------------------------
diff --git a/sshd-git/src/main/java/org/apache/sshd/git/GitCommand.java b/sshd-git/src/main/java/org/apache/sshd/git/GitCommand.java
deleted file mode 100644
index 9a8684b..0000000
--- a/sshd-git/src/main/java/org/apache/sshd/git/GitCommand.java
+++ /dev/null
@@ -1,177 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.sshd.git;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.util.ArrayList;
-import java.util.List;
-
-import org.apache.sshd.common.channel.ChannelOutputStream;
-import org.apache.sshd.server.Command;
-import org.apache.sshd.server.Environment;
-import org.apache.sshd.server.ExitCallback;
-import org.eclipse.jgit.pgm.EmbeddedCommandRunner;
-import org.eclipse.jgit.pgm.TextBuiltin;
-import org.eclipse.jgit.pgm.opt.SubcommandHandler;
-import org.kohsuke.args4j.Argument;
-import org.kohsuke.args4j.Option;
-
-/**
- * TODO Add javadoc
- *
- * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
- */
-public class GitCommand implements Command, Runnable {
-
-    private String command;
-    private InputStream in;
-    private OutputStream out;
-    private OutputStream err;
-    private ExitCallback callback;
-
-    public GitCommand(String command) {
-        this.command = command;
-    }
-
-    public void setInputStream(InputStream in) {
-        this.in = in;
-    }
-
-    public void setOutputStream(OutputStream out) {
-        this.out = out;
-        if (out instanceof ChannelOutputStream) {
-            ((ChannelOutputStream) out).setNoDelay(true);
-        }
-    }
-
-    public void setErrorStream(OutputStream err) {
-        this.err = err;
-        if (err instanceof ChannelOutputStream) {
-            ((ChannelOutputStream) err).setNoDelay(true);
-        }
-    }
-
-    public void setExitCallback(ExitCallback callback) {
-        this.callback = callback;
-    }
-
-    public void start(Environment env) throws IOException {
-        new Thread(this).start();
-    }
-
-    public void run() {
-        try {
-            List<String> strs = parseDelimitedString(command, " ", true);
-            String[] args = strs.toArray(new String[strs.size()]);
-            for (int i = 0; i < args.length; i++) {
-                if (args[i].startsWith("'") && args[i].endsWith("'")) {
-                    args[i] = args[i].substring(1, args[i].length() - 1);
-                }
-                if (args[i].startsWith("\"") && args[i].endsWith("\"")) {
-                    args[i] = args[i].substring(1, args[i].length() - 1);
-                }
-            }
-            new EmbeddedCommandRunner().execute(args, in, out, err);
-        } catch (Throwable t) {
-            t.printStackTrace();
-        }
-        if (callback != null) {
-            callback.onExit(0);
-        }
-    }
-
-    public void destroy() {
-        //To change body of implemented methods use File | Settings | File Templates.
-    }
-
-    /**
-     * Parses delimited string and returns an array containing the tokens. This
-     * parser obeys quotes, so the delimiter character will be ignored if it is
-     * inside of a quote. This method assumes that the quote character is not
-     * included in the set of delimiter characters.
-     *
-     * @param value the delimited string to parse.
-     * @param delim the characters delimiting the tokens.
-     * @return a list of string or an empty list if there are none.
-     */
-    private static List<String> parseDelimitedString(String value, String delim, boolean trim) {
-        if (value == null) {
-            value = "";
-        }
-
-        List<String> list = new ArrayList<String>();
-
-        int CHAR = 1;
-        int DELIMITER = 2;
-        int STARTQUOTE = 4;
-        int ENDQUOTE = 8;
-
-        StringBuilder sb = new StringBuilder();
-
-        int expecting = (CHAR | DELIMITER | STARTQUOTE);
-
-        boolean isEscaped = false;
-        for (int i = 0; i < value.length(); i++) {
-            char c = value.charAt(i);
-
-            boolean isDelimiter = (delim.indexOf(c) >= 0);
-
-            if (!isEscaped && (c == '\\')) {
-                isEscaped = true;
-                continue;
-            }
-
-            if (isEscaped) {
-                sb.append(c);
-            } else if (isDelimiter && ((expecting & DELIMITER) > 0)) {
-                if (trim) {
-                    list.add(sb.toString().trim());
-                } else {
-                    list.add(sb.toString());
-                }
-                sb.delete(0, sb.length());
-                expecting = (CHAR | DELIMITER | STARTQUOTE);
-            } else if ((c == '"') && ((expecting & STARTQUOTE) > 0)) {
-                sb.append(c);
-                expecting = CHAR | ENDQUOTE;
-            } else if ((c == '"') && ((expecting & ENDQUOTE) > 0)) {
-                sb.append(c);
-                expecting = (CHAR | STARTQUOTE | DELIMITER);
-            } else if ((expecting & CHAR) > 0) {
-                sb.append(c);
-            } else {
-                throw new IllegalArgumentException("Invalid delimited string: " + value);
-            }
-
-            isEscaped = false;
-        }
-
-        if (sb.length() > 0) {
-            if (trim) {
-                list.add(sb.toString().trim());
-            } else {
-                list.add(sb.toString());
-            }
-        }
-
-        return list;
-    }
-}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/aca9f534/sshd-git/src/main/java/org/apache/sshd/git/GitCommandFactory.java
----------------------------------------------------------------------
diff --git a/sshd-git/src/main/java/org/apache/sshd/git/GitCommandFactory.java b/sshd-git/src/main/java/org/apache/sshd/git/GitCommandFactory.java
deleted file mode 100644
index 3148202..0000000
--- a/sshd-git/src/main/java/org/apache/sshd/git/GitCommandFactory.java
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.sshd.git;
-
-import org.apache.sshd.server.Command;
-import org.apache.sshd.server.CommandFactory;
-import org.apache.sshd.server.command.UnknownCommand;
-
-/**
- * TODO Add javadoc
- *
- * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
- */
-public class GitCommandFactory implements CommandFactory {
-
-    private CommandFactory delegate;
-
-    public GitCommandFactory() {
-    }
-
-    public GitCommandFactory(CommandFactory delegate) {
-        this.delegate = delegate;
-    }
-
-    public Command createCommand(String command) {
-        if (command.startsWith("git-")) {
-            return new GitCommand(command.substring("git-".length()));
-        } else if (delegate != null) {
-            return delegate.createCommand(command);
-        } else {
-            return new UnknownCommand(command);
-        }
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/aca9f534/sshd-git/src/main/java/org/apache/sshd/git/pack/GitPackCommand.java
----------------------------------------------------------------------
diff --git a/sshd-git/src/main/java/org/apache/sshd/git/pack/GitPackCommand.java b/sshd-git/src/main/java/org/apache/sshd/git/pack/GitPackCommand.java
new file mode 100644
index 0000000..d8b4c80
--- /dev/null
+++ b/sshd-git/src/main/java/org/apache/sshd/git/pack/GitPackCommand.java
@@ -0,0 +1,193 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sshd.git.pack;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.sshd.common.channel.ChannelOutputStream;
+import org.apache.sshd.server.Command;
+import org.apache.sshd.server.Environment;
+import org.apache.sshd.server.ExitCallback;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.lib.RepositoryCache;
+import org.eclipse.jgit.transport.ReceivePack;
+import org.eclipse.jgit.transport.UploadPack;
+import org.eclipse.jgit.util.FS;
+
+/**
+ * TODO Add javadoc
+ *
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class GitPackCommand implements Command, Runnable {
+
+    private String rootDir;
+    private String command;
+    private InputStream in;
+    private OutputStream out;
+    private OutputStream err;
+    private ExitCallback callback;
+
+    public GitPackCommand(String rootDir, String command) {
+        this.rootDir = rootDir;
+        this.command = command;
+    }
+
+    public void setInputStream(InputStream in) {
+        this.in = in;
+    }
+
+    public void setOutputStream(OutputStream out) {
+        this.out = out;
+        if (out instanceof ChannelOutputStream) {
+            ((ChannelOutputStream) out).setNoDelay(true);
+        }
+    }
+
+    public void setErrorStream(OutputStream err) {
+        this.err = err;
+        if (err instanceof ChannelOutputStream) {
+            ((ChannelOutputStream) err).setNoDelay(true);
+        }
+    }
+
+    public void setExitCallback(ExitCallback callback) {
+        this.callback = callback;
+    }
+
+    public void start(Environment env) throws IOException {
+        new Thread(this).start();
+    }
+
+    public void run() {
+        try {
+            List<String> strs = parseDelimitedString(command, " ", true);
+            String[] args = strs.toArray(new String[strs.size()]);
+            for (int i = 0; i < args.length; i++) {
+                if (args[i].startsWith("'") && args[i].endsWith("'")) {
+                    args[i] = args[i].substring(1, args[i].length() - 1);
+                }
+                if (args[i].startsWith("\"") && args[i].endsWith("\"")) {
+                    args[i] = args[i].substring(1, args[i].length() - 1);
+                }
+            }
+
+            if (args.length != 2) {
+                throw new IllegalArgumentException("Invalid git command line: " + command);
+            }
+            File srcGitdir = new File(rootDir, args[1]);
+            RepositoryCache.FileKey key = RepositoryCache.FileKey.lenient(srcGitdir, FS.DETECTED);
+            Repository db = key.open(true /* must exist */);
+            if ("git-upload-pack".equals(args[0])) {
+                new UploadPack(db).upload(in, out, err);
+            } else if ("git-receive-pack".equals(args[0])) {
+                new ReceivePack(db).receive(in, out, err);
+            } else {
+                throw new IllegalArgumentException("Unknown git command: " + command);
+            }
+        } catch (Throwable t) {
+            t.printStackTrace();
+        }
+        if (callback != null) {
+            callback.onExit(0);
+        }
+    }
+
+    public void destroy() {
+        //To change body of implemented methods use File | Settings | File Templates.
+    }
+
+    /**
+     * Parses delimited string and returns an array containing the tokens. This
+     * parser obeys quotes, so the delimiter character will be ignored if it is
+     * inside of a quote. This method assumes that the quote character is not
+     * included in the set of delimiter characters.
+     *
+     * @param value the delimited string to parse.
+     * @param delim the characters delimiting the tokens.
+     * @return a list of string or an empty list if there are none.
+     */
+    private static List<String> parseDelimitedString(String value, String delim, boolean trim) {
+        if (value == null) {
+            value = "";
+        }
+
+        List<String> list = new ArrayList<String>();
+
+        int CHAR = 1;
+        int DELIMITER = 2;
+        int STARTQUOTE = 4;
+        int ENDQUOTE = 8;
+
+        StringBuilder sb = new StringBuilder();
+
+        int expecting = (CHAR | DELIMITER | STARTQUOTE);
+
+        boolean isEscaped = false;
+        for (int i = 0; i < value.length(); i++) {
+            char c = value.charAt(i);
+
+            boolean isDelimiter = (delim.indexOf(c) >= 0);
+
+            if (!isEscaped && (c == '\\')) {
+                isEscaped = true;
+                continue;
+            }
+
+            if (isEscaped) {
+                sb.append(c);
+            } else if (isDelimiter && ((expecting & DELIMITER) > 0)) {
+                if (trim) {
+                    list.add(sb.toString().trim());
+                } else {
+                    list.add(sb.toString());
+                }
+                sb.delete(0, sb.length());
+                expecting = (CHAR | DELIMITER | STARTQUOTE);
+            } else if ((c == '"') && ((expecting & STARTQUOTE) > 0)) {
+                sb.append(c);
+                expecting = CHAR | ENDQUOTE;
+            } else if ((c == '"') && ((expecting & ENDQUOTE) > 0)) {
+                sb.append(c);
+                expecting = (CHAR | STARTQUOTE | DELIMITER);
+            } else if ((expecting & CHAR) > 0) {
+                sb.append(c);
+            } else {
+                throw new IllegalArgumentException("Invalid delimited string: " + value);
+            }
+
+            isEscaped = false;
+        }
+
+        if (sb.length() > 0) {
+            if (trim) {
+                list.add(sb.toString().trim());
+            } else {
+                list.add(sb.toString());
+            }
+        }
+
+        return list;
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/aca9f534/sshd-git/src/main/java/org/apache/sshd/git/pack/GitPackCommandFactory.java
----------------------------------------------------------------------
diff --git a/sshd-git/src/main/java/org/apache/sshd/git/pack/GitPackCommandFactory.java b/sshd-git/src/main/java/org/apache/sshd/git/pack/GitPackCommandFactory.java
new file mode 100644
index 0000000..87bfc65
--- /dev/null
+++ b/sshd-git/src/main/java/org/apache/sshd/git/pack/GitPackCommandFactory.java
@@ -0,0 +1,54 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sshd.git.pack;
+
+import org.apache.sshd.server.Command;
+import org.apache.sshd.server.CommandFactory;
+import org.apache.sshd.server.command.UnknownCommand;
+
+/**
+ * TODO Add javadoc
+ *
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class GitPackCommandFactory implements CommandFactory {
+
+    private final String rootDir;
+    private final CommandFactory delegate;
+
+    public GitPackCommandFactory(String rootDir) {
+        this(rootDir,  null);
+    }
+
+    public GitPackCommandFactory(String rootDir, CommandFactory delegate) {
+        this.rootDir = rootDir;
+        this.delegate = delegate;
+    }
+
+    public Command createCommand(String command) {
+        if (command.startsWith("git-")) {
+            return new GitPackCommand(rootDir, command);
+        } else if (delegate != null) {
+            return delegate.createCommand(command);
+        } else {
+            return new UnknownCommand(command);
+        }
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/aca9f534/sshd-git/src/main/java/org/apache/sshd/git/pgm/EmbeddedCommandRunner.java
----------------------------------------------------------------------
diff --git a/sshd-git/src/main/java/org/apache/sshd/git/pgm/EmbeddedCommandRunner.java b/sshd-git/src/main/java/org/apache/sshd/git/pgm/EmbeddedCommandRunner.java
new file mode 100644
index 0000000..fc1958d
--- /dev/null
+++ b/sshd-git/src/main/java/org/apache/sshd/git/pgm/EmbeddedCommandRunner.java
@@ -0,0 +1,238 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sshd.git.pgm;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.PrintWriter;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.jgit.api.Git;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.lib.RepositoryBuilder;
+import org.eclipse.jgit.pgm.CommandCatalog;
+import org.eclipse.jgit.pgm.CommandRef;
+import org.eclipse.jgit.pgm.Die;
+import org.eclipse.jgit.pgm.TextBuiltin;
+import org.eclipse.jgit.pgm.internal.CLIText;
+import org.eclipse.jgit.pgm.opt.CmdLineParser;
+import org.eclipse.jgit.pgm.opt.SubcommandHandler;
+import org.eclipse.jgit.util.io.ThrowingPrintWriter;
+import org.kohsuke.args4j.Argument;
+import org.kohsuke.args4j.CmdLineException;
+import org.kohsuke.args4j.ExampleMode;
+import org.kohsuke.args4j.Option;
+
+/**
+ * TODO Add javadoc
+ *
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class EmbeddedCommandRunner {
+    @Option(name = "--help", usage = "usage_displayThisHelpText", aliases = { "-h" })
+    private boolean help;
+
+    @Option(name = "--show-stack-trace", usage = "usage_displayThejavaStackTraceOnExceptions")
+    private boolean showStackTrace;
+
+    @Option(name = "--git-dir", metaVar = "metaVar_gitDir", usage = "usage_setTheGitRepositoryToOperateOn")
+    private String gitdir;
+
+    @Argument(index = 0, metaVar = "metaVar_command", required = true, handler = SubcommandHandler.class)
+    private TextBuiltin subcommand;
+
+    @Argument(index = 1, metaVar = "metaVar_arg")
+    private List<String> arguments = new ArrayList<String>();
+
+    private String rootDir;
+
+    public EmbeddedCommandRunner(String rootDir) {
+        this.rootDir = rootDir;
+    }
+
+    /**
+     * Execute a command.
+     *
+     * @param argv
+     *          the command and its arguments
+     * @param in
+     *          the input stream, may be null in which case the system input stream will be used
+     * @param out
+     *          the output stream, may be null in which case the system output stream will be used
+     * @param err
+     *          the error stream, may be null in which case the system error stream will be used
+     * @throws Exception
+     *          if an error occurs
+     */
+    public void execute(final String[] argv, InputStream in, OutputStream out, OutputStream err) throws Exception {
+        final CmdLineParser clp = new CmdLineParser(this);
+        PrintWriter writer = new PrintWriter(err != null ? err : System.err);
+        try {
+            clp.parseArgument(argv);
+        } catch (CmdLineException e) {
+            if (argv.length > 0 && !help) {
+                writer.println(MessageFormat.format(CLIText.get().fatalError, e.getMessage()));
+                writer.flush();
+                throw new Die(true);
+            }
+        }
+
+        if (argv.length == 0 || help) {
+            final String ex = clp.printExample(ExampleMode.ALL, CLIText.get().resourceBundle());
+            writer.println("jgit" + ex + " command [ARG ...]"); //$NON-NLS-1$
+            if (help) {
+                writer.println();
+                clp.printUsage(writer, CLIText.get().resourceBundle());
+                writer.println();
+            } else if (subcommand == null) {
+                writer.println();
+                writer.println(CLIText.get().mostCommonlyUsedCommandsAre);
+                final CommandRef[] common = CommandCatalog.common();
+                int width = 0;
+                for (final CommandRef c : common)
+                    width = Math.max(width, c.getName().length());
+                width += 2;
+
+                for (final CommandRef c : common) {
+                    writer.print(' ');
+                    writer.print(c.getName());
+                    for (int i = c.getName().length(); i < width; i++)
+                        writer.print(' ');
+                    writer.print(CLIText.get().resourceBundle().getString(c.getUsage()));
+                    writer.println();
+                }
+                writer.println();
+            }
+            writer.flush();
+            throw new Die(true);
+        }
+
+        gitdir = new File(rootDir, gitdir).getPath();
+
+        final TextBuiltin cmd = subcommand;
+//        cmd.ins = in;
+//        cmd.outs = out;
+//        cmd.errs = err;
+//        if (cmd.requiresRepository())
+//            cmd.init(openGitDir(gitdir), null);
+//        else
+//            cmd.init(null, gitdir);
+//        try {
+//            cmd.execute(arguments.toArray(new String[arguments.size()]));
+//        } finally {
+//            if (cmd.outw != null)
+//                cmd.outw.flush();
+//            if (cmd.errw != null)
+//                cmd.errw.flush();
+//        }
+        set(cmd, "ins", in);
+        set(cmd, "outs", out);
+        set(cmd, "errs", err);
+        if ((Boolean) call(cmd, "requiresRepository")) {
+            call(cmd, "init", new Class[] { Repository.class, String.class }, new Object[] { openGitDir(gitdir), gitdir });
+        } else {
+            call(cmd, "init", new Class[] { Repository.class, String.class }, new Object[] { null, gitdir });
+        }
+        try {
+            cmd.execute(arguments.toArray(new String[arguments.size()]));
+        } finally {
+            if (get(cmd, "outw") != null)
+                ((ThrowingPrintWriter) get(cmd, "outw")).flush();
+            if (get(cmd, "errw") != null)
+                ((ThrowingPrintWriter) get(cmd, "errw")).flush();
+        }
+    }
+
+    private Object get(Object obj, String name) throws IllegalAccessException, NoSuchFieldException {
+        Class clazz = obj.getClass();
+        while (clazz != null) {
+            try {
+                Field field = clazz.getDeclaredField(name);
+                field.setAccessible(true);
+                return field.get(obj);
+            } catch (NoSuchFieldException e) {
+                clazz = clazz.getSuperclass();
+            }
+        }
+        throw new NoSuchFieldException(name);
+    }
+
+    private void set(Object obj, String name, Object val) throws IllegalAccessException, NoSuchFieldException {
+        Class clazz = obj.getClass();
+        while (clazz != null) {
+            try {
+                Field field = clazz.getDeclaredField(name);
+                field.setAccessible(true);
+                field.set(obj, val);
+                return;
+            } catch (NoSuchFieldException e) {
+                clazz = clazz.getSuperclass();
+            }
+        }
+        throw new NoSuchFieldException(name);
+    }
+
+    private Object call(Object obj, String name) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
+        return call(obj, name, new Class[0], new Object[0]);
+    }
+
+    private Object call(Object obj, String name, Class<?>[] types, Object[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
+        Class<?> clazz = obj.getClass();
+        while (clazz != null) {
+            try {
+                Method method = clazz.getDeclaredMethod(name, types);
+                method.setAccessible(true);
+                return method.invoke(obj, args);
+            } catch (NoSuchMethodException e) {
+                clazz = clazz.getSuperclass();
+            }
+        }
+        throw new NoSuchMethodException(name);
+    }
+
+    /**
+     * Evaluate the {@code --git-dir} option and open the repository.
+     *
+     * @param gitdir
+     *            the {@code --git-dir} option given on the command line. May be
+     *            null if it was not supplied.
+     * @return the repository to operate on.
+     * @throws IOException
+     *             the repository cannot be opened.
+     */
+    protected Repository openGitDir(String gitdir) throws IOException {
+        return Git.open(new File(gitdir)).getRepository();
+        /*
+        RepositoryBuilder rb = new RepositoryBuilder() //
+                .setGitDir(gitdir != null ? new File(gitdir) : null) //
+                .readEnvironment() //
+                .findGitDir();
+        if (rb.getGitDir() == null)
+            throw new Die(CLIText.get().cantFindGitDirectory);
+        return rb.build();
+        */
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/aca9f534/sshd-git/src/main/java/org/apache/sshd/git/pgm/GitPgmCommand.java
----------------------------------------------------------------------
diff --git a/sshd-git/src/main/java/org/apache/sshd/git/pgm/GitPgmCommand.java b/sshd-git/src/main/java/org/apache/sshd/git/pgm/GitPgmCommand.java
new file mode 100644
index 0000000..22a2f11
--- /dev/null
+++ b/sshd-git/src/main/java/org/apache/sshd/git/pgm/GitPgmCommand.java
@@ -0,0 +1,183 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sshd.git.pgm;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.sshd.common.channel.ChannelOutputStream;
+import org.apache.sshd.server.Command;
+import org.apache.sshd.server.Environment;
+import org.apache.sshd.server.ExitCallback;
+
+/**
+ * TODO Add javadoc
+ *
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class GitPgmCommand implements Command, Runnable {
+
+    private String rootDir;
+    private String command;
+    private InputStream in;
+    private OutputStream out;
+    private OutputStream err;
+    private ExitCallback callback;
+
+    public GitPgmCommand(String rootDir, String command) {
+        this.rootDir = rootDir;
+        this.command = command;
+    }
+
+    public void setInputStream(InputStream in) {
+        this.in = in;
+    }
+
+    public void setOutputStream(OutputStream out) {
+        this.out = out;
+        if (out instanceof ChannelOutputStream) {
+            ((ChannelOutputStream) out).setNoDelay(true);
+        }
+    }
+
+    public void setErrorStream(OutputStream err) {
+        this.err = err;
+        if (err instanceof ChannelOutputStream) {
+            ((ChannelOutputStream) err).setNoDelay(true);
+        }
+    }
+
+    public void setExitCallback(ExitCallback callback) {
+        this.callback = callback;
+    }
+
+    public void start(Environment env) throws IOException {
+        new Thread(this).start();
+    }
+
+    public void run() {
+        try {
+            List<String> strs = parseDelimitedString(command, " ", true);
+            String[] args = strs.toArray(new String[strs.size()]);
+            for (int i = 0; i < args.length; i++) {
+                if (args[i].startsWith("'") && args[i].endsWith("'")) {
+                    args[i] = args[i].substring(1, args[i].length() - 1);
+                }
+                if (args[i].startsWith("\"") && args[i].endsWith("\"")) {
+                    args[i] = args[i].substring(1, args[i].length() - 1);
+                }
+            }
+
+            new EmbeddedCommandRunner(rootDir).execute(args, in, out, err);
+            if (callback != null) {
+                callback.onExit(0);
+            }
+        } catch (Throwable t) {
+            try {
+                err.write((t.getMessage() + "\n").getBytes());
+                err.flush();
+            } catch (IOException e) {
+                e.printStackTrace();
+            }
+            if (callback != null) {
+                callback.onExit(-1);
+            }
+        }
+    }
+
+    public void destroy() {
+        //To change body of implemented methods use File | Settings | File Templates.
+    }
+
+    /**
+     * Parses delimited string and returns an array containing the tokens. This
+     * parser obeys quotes, so the delimiter character will be ignored if it is
+     * inside of a quote. This method assumes that the quote character is not
+     * included in the set of delimiter characters.
+     *
+     * @param value the delimited string to parse.
+     * @param delim the characters delimiting the tokens.
+     * @return a list of string or an empty list if there are none.
+     */
+    private static List<String> parseDelimitedString(String value, String delim, boolean trim) {
+        if (value == null) {
+            value = "";
+        }
+
+        List<String> list = new ArrayList<String>();
+
+        int CHAR = 1;
+        int DELIMITER = 2;
+        int STARTQUOTE = 4;
+        int ENDQUOTE = 8;
+
+        StringBuilder sb = new StringBuilder();
+
+        int expecting = (CHAR | DELIMITER | STARTQUOTE);
+
+        boolean isEscaped = false;
+        for (int i = 0; i < value.length(); i++) {
+            char c = value.charAt(i);
+
+            boolean isDelimiter = (delim.indexOf(c) >= 0);
+
+            if (!isEscaped && (c == '\\')) {
+                isEscaped = true;
+                continue;
+            }
+
+            if (isEscaped) {
+                sb.append(c);
+            } else if (isDelimiter && ((expecting & DELIMITER) > 0)) {
+                if (trim) {
+                    list.add(sb.toString().trim());
+                } else {
+                    list.add(sb.toString());
+                }
+                sb.delete(0, sb.length());
+                expecting = (CHAR | DELIMITER | STARTQUOTE);
+            } else if ((c == '"') && ((expecting & STARTQUOTE) > 0)) {
+                sb.append(c);
+                expecting = CHAR | ENDQUOTE;
+            } else if ((c == '"') && ((expecting & ENDQUOTE) > 0)) {
+                sb.append(c);
+                expecting = (CHAR | STARTQUOTE | DELIMITER);
+            } else if ((expecting & CHAR) > 0) {
+                sb.append(c);
+            } else {
+                throw new IllegalArgumentException("Invalid delimited string: " + value);
+            }
+
+            isEscaped = false;
+        }
+
+        if (sb.length() > 0) {
+            if (trim) {
+                list.add(sb.toString().trim());
+            } else {
+                list.add(sb.toString());
+            }
+        }
+
+        return list;
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/aca9f534/sshd-git/src/main/java/org/apache/sshd/git/pgm/GitPgmCommandFactory.java
----------------------------------------------------------------------
diff --git a/sshd-git/src/main/java/org/apache/sshd/git/pgm/GitPgmCommandFactory.java b/sshd-git/src/main/java/org/apache/sshd/git/pgm/GitPgmCommandFactory.java
new file mode 100644
index 0000000..e3741a4
--- /dev/null
+++ b/sshd-git/src/main/java/org/apache/sshd/git/pgm/GitPgmCommandFactory.java
@@ -0,0 +1,54 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sshd.git.pgm;
+
+import org.apache.sshd.server.Command;
+import org.apache.sshd.server.CommandFactory;
+import org.apache.sshd.server.command.UnknownCommand;
+
+/**
+ * TODO Add javadoc
+ *
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class GitPgmCommandFactory implements CommandFactory {
+
+    private final String rootDir;
+    private final CommandFactory delegate;
+
+    public GitPgmCommandFactory(String rootDir) {
+        this(rootDir,  null);
+    }
+
+    public GitPgmCommandFactory(String rootDir, CommandFactory delegate) {
+        this.rootDir = rootDir;
+        this.delegate = delegate;
+    }
+
+    public Command createCommand(String command) {
+        if (command.startsWith("git ")) {
+            return new GitPgmCommand(rootDir, command.substring("git ".length()));
+        } else if (delegate != null) {
+            return delegate.createCommand(command);
+        } else {
+            return new UnknownCommand(command);
+        }
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/aca9f534/sshd-git/src/main/java/org/apache/sshd/git/transport/GitSshdSessionFactory.java
----------------------------------------------------------------------
diff --git a/sshd-git/src/main/java/org/apache/sshd/git/transport/GitSshdSessionFactory.java b/sshd-git/src/main/java/org/apache/sshd/git/transport/GitSshdSessionFactory.java
new file mode 100644
index 0000000..aca4e3c
--- /dev/null
+++ b/sshd-git/src/main/java/org/apache/sshd/git/transport/GitSshdSessionFactory.java
@@ -0,0 +1,136 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sshd.git.transport;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+import org.apache.sshd.ClientChannel;
+import org.apache.sshd.ClientSession;
+import org.apache.sshd.SshClient;
+import org.apache.sshd.client.channel.ChannelExec;
+import org.eclipse.jgit.errors.TransportException;
+import org.eclipse.jgit.transport.CredentialItem;
+import org.eclipse.jgit.transport.CredentialsProvider;
+import org.eclipse.jgit.transport.RemoteSession;
+import org.eclipse.jgit.transport.SshSessionFactory;
+import org.eclipse.jgit.transport.URIish;
+import org.eclipse.jgit.util.FS;
+
+/**
+ * TODO Add javadoc
+ *
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class GitSshdSessionFactory extends SshSessionFactory {
+
+    @Override
+    public RemoteSession getSession(URIish uri, CredentialsProvider credentialsProvider, FS fs, int tms) throws TransportException {
+        try {
+            return new SshdSession(uri, credentialsProvider, fs, tms);
+        } catch (Exception e) {
+            throw new TransportException("Unable to connect", e);
+        }
+    }
+
+    protected SshClient createClient() {
+        return SshClient.setUpDefaultClient();
+    }
+
+    public class SshdSession implements RemoteSession {
+
+        private final SshClient client;
+        private final ClientSession session;
+
+        public SshdSession(URIish uri, CredentialsProvider credentialsProvider, FS fs, int tms) throws IOException, InterruptedException {
+            String user = uri.getUser();
+            final String pass = uri.getPass();
+            String host = uri.getHost();
+            int port = uri.getPort();
+            char[] pass2 = null;
+
+            if (!credentialsProvider.isInteractive()) {
+                CredentialItem.Username usrItem = new CredentialItem.Username();
+                CredentialItem.Password pwdItem = new CredentialItem.Password();
+                if (credentialsProvider.get(uri, usrItem, pwdItem)) {
+                    if (user == null) {
+                        user = usrItem.getValue();
+                    } else if (user.equals(usrItem.getValue())) {
+                        pass2 = pwdItem.getValue();
+                    }
+                }
+
+            }
+
+            client = createClient();
+            client.start();
+            session = client.connect(user, host, port).await().getSession();
+            if (pass != null) {
+                session.addPasswordIdentity(pass);
+            }
+            if (pass2 != null) {
+                session.addPasswordIdentity(new String(pass2));
+            }
+            session.auth().verify();
+
+        }
+
+        public Process exec(String commandName, int timeout) throws IOException {
+            final ChannelExec channel = session.createExecChannel(commandName);
+            channel.open().verify();
+            return new Process() {
+                @Override
+                public OutputStream getOutputStream() {
+                    return channel.getInvertedIn();
+                }
+
+                @Override
+                public InputStream getInputStream() {
+                    return channel.getInvertedOut();
+                }
+
+                @Override
+                public InputStream getErrorStream() {
+                    return channel.getInvertedErr();
+                }
+
+                @Override
+                public int waitFor() throws InterruptedException {
+                    return channel.waitFor(ClientChannel.CLOSED, 0);
+                }
+
+                @Override
+                public int exitValue() {
+                    return channel.getExitStatus();
+                }
+
+                @Override
+                public void destroy() {
+                    channel.close(true);
+                }
+            };
+        }
+
+        public void disconnect() {
+            client.close(true);
+        }
+
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/aca9f534/sshd-git/src/test/java/org/apache/sshd/git/GitServerTest.java
----------------------------------------------------------------------
diff --git a/sshd-git/src/test/java/org/apache/sshd/git/GitServerTest.java b/sshd-git/src/test/java/org/apache/sshd/git/GitServerTest.java
deleted file mode 100644
index ed0a17e..0000000
--- a/sshd-git/src/test/java/org/apache/sshd/git/GitServerTest.java
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.sshd.git;
-
-import java.util.Arrays;
-
-import org.apache.sshd.SshServer;
-import org.apache.sshd.common.NamedFactory;
-import org.apache.sshd.git.util.BogusPasswordAuthenticator;
-import org.apache.sshd.git.util.EchoShellFactory;
-import org.apache.sshd.git.util.Utils;
-import org.apache.sshd.server.Command;
-import org.apache.sshd.server.CommandFactory;
-import org.apache.sshd.server.command.UnknownCommand;
-import org.apache.sshd.server.sftp.SftpSubsystem;
-import org.junit.Ignore;
-import org.junit.Test;
-
-/**
- */
-public class GitServerTest {
-
-    @Test
-    @Ignore
-    public void testGit() {
-
-    }
-
-    public static void main(String[] args) throws Exception {
-        SshServer sshd = SshServer.setUpDefaultServer();
-        sshd.getProperties().put(SshServer.IDLE_TIMEOUT, "10000");
-        sshd.setPort(8001);
-        sshd.setKeyPairProvider(Utils.createTestHostKeyProvider());
-        sshd.setSubsystemFactories(Arrays.<NamedFactory<Command>>asList(new SftpSubsystem.Factory()));
-        sshd.setShellFactory(new EchoShellFactory());
-//        sshd.setCommandFactory(new ScpCommandFactory());
-        sshd.setCommandFactory(new CommandFactory() {
-            public Command createCommand(String command) {
-                if (command.startsWith("git-")) {
-                    return new GitCommand(command.substring("git-".length()));
-                } else {
-                    return new UnknownCommand(command);
-                }
-            }
-        });
-        sshd.setPasswordAuthenticator(new BogusPasswordAuthenticator());
-        sshd.start();
-        Thread.sleep(100000);
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/aca9f534/sshd-git/src/test/java/org/apache/sshd/git/pack/GitPackCommandTest.java
----------------------------------------------------------------------
diff --git a/sshd-git/src/test/java/org/apache/sshd/git/pack/GitPackCommandTest.java b/sshd-git/src/test/java/org/apache/sshd/git/pack/GitPackCommandTest.java
new file mode 100644
index 0000000..b27becb
--- /dev/null
+++ b/sshd-git/src/test/java/org/apache/sshd/git/pack/GitPackCommandTest.java
@@ -0,0 +1,82 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sshd.git.pack;
+
+import java.io.File;
+import java.util.Arrays;
+
+import com.jcraft.jsch.JSch;
+import org.apache.sshd.SshServer;
+import org.apache.sshd.common.NamedFactory;
+import org.apache.sshd.git.transport.GitSshdSessionFactory;
+import org.apache.sshd.git.util.BogusPasswordAuthenticator;
+import org.apache.sshd.git.util.EchoShellFactory;
+import org.apache.sshd.git.util.Utils;
+import org.apache.sshd.server.Command;
+import org.apache.sshd.server.sftp.SftpSubsystem;
+import org.eclipse.jgit.api.Git;
+import org.eclipse.jgit.transport.CredentialsProvider;
+import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider;
+import org.junit.Test;
+
+/**
+ */
+public class GitPackCommandTest {
+
+    @Test
+    public void testGitPack() throws Exception {
+        SshServer sshd = SshServer.setUpDefaultServer();
+        sshd.setPort(8001);
+        sshd.setKeyPairProvider(Utils.createTestHostKeyProvider());
+        sshd.setSubsystemFactories(Arrays.<NamedFactory<Command>>asList(new SftpSubsystem.Factory()));
+        sshd.setShellFactory(new EchoShellFactory());
+        sshd.setCommandFactory(new GitPackCommandFactory("target/git/server"));
+        sshd.setPasswordAuthenticator(new BogusPasswordAuthenticator());
+        sshd.start();
+
+        File serverDir = new File("target/git/server/test.git");
+        Utils.deleteRecursive(serverDir);
+        Git.init().setBare(true).setDirectory(serverDir).call();
+
+        JSch.setConfig("StrictHostKeyChecking", "no");
+        CredentialsProvider.setDefault(new UsernamePasswordCredentialsProvider("sshd", "sshd"));
+        GitSshdSessionFactory.setInstance(new GitSshdSessionFactory());
+
+        File dir = new File("target/git/local/test.git");
+        Utils.deleteRecursive(dir);
+        Git.cloneRepository()
+                .setURI("ssh://sshd@localhost:8001/test.git")
+                .setDirectory(dir)
+                .call();
+
+        Git git = Git.open(dir);
+        git.commit().setMessage("First Commit").setCommitter("sshd", "sshd@apache.org").call();
+        git.push().call();
+
+        new File("target/git/local/test.git/readme.txt").createNewFile();
+        git.add().addFilepattern("readme.txt").call();
+        git.commit().setMessage("readme").setCommitter("sshd", "sshd@apache.org").call();
+        git.push().call();
+
+        git.pull().setRebase(true).call();
+
+        sshd.stop();
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/aca9f534/sshd-git/src/test/java/org/apache/sshd/git/pgm/GitPgmCommandTest.java
----------------------------------------------------------------------
diff --git a/sshd-git/src/test/java/org/apache/sshd/git/pgm/GitPgmCommandTest.java b/sshd-git/src/test/java/org/apache/sshd/git/pgm/GitPgmCommandTest.java
new file mode 100644
index 0000000..f45f60d
--- /dev/null
+++ b/sshd-git/src/test/java/org/apache/sshd/git/pgm/GitPgmCommandTest.java
@@ -0,0 +1,98 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sshd.git.pgm;
+
+import java.io.File;
+import java.util.Arrays;
+
+import org.apache.sshd.ClientChannel;
+import org.apache.sshd.ClientSession;
+import org.apache.sshd.SshClient;
+import org.apache.sshd.SshServer;
+import org.apache.sshd.client.channel.ChannelExec;
+import org.apache.sshd.common.NamedFactory;
+import org.apache.sshd.git.util.BogusPasswordAuthenticator;
+import org.apache.sshd.git.util.EchoShellFactory;
+import org.apache.sshd.git.util.Utils;
+import org.apache.sshd.server.Command;
+import org.apache.sshd.server.sftp.SftpSubsystem;
+import org.eclipse.jgit.api.Git;
+import org.junit.Test;
+
+/**
+ */
+public class GitPgmCommandTest {
+
+    @Test
+    public void testGitpgm() throws Exception {
+
+        //
+        // TODO: the GitpgmCommandFactory is kept in the test tree
+        // TODO: because it's quite limited for now
+        //
+
+        SshServer sshd = SshServer.setUpDefaultServer();
+        sshd.setPort(8001);
+        sshd.setKeyPairProvider(Utils.createTestHostKeyProvider());
+        sshd.setSubsystemFactories(Arrays.<NamedFactory<Command>>asList(new SftpSubsystem.Factory()));
+        sshd.setShellFactory(new EchoShellFactory());
+        sshd.setCommandFactory(new GitPgmCommandFactory("target/git/pgm"));
+        sshd.setPasswordAuthenticator(new BogusPasswordAuthenticator());
+        sshd.start();
+
+        File serverDir = new File("target/git/pgm");
+        Utils.deleteRecursive(serverDir);
+
+        File repo = new File(serverDir, "test");
+
+        SshClient client = SshClient.setUpDefaultClient();
+        client.start();
+
+        ClientSession session = client.connect("sshd", "localhost", 8001).await().getSession();
+        session.addPasswordIdentity("sshd");
+        session.auth().verify();
+
+        Git.init().setDirectory(repo).call();
+        Git git = Git.open(repo);
+        git.commit().setMessage("First Commit").setCommitter("sshd", "sshd@apache.org").call();
+
+        new File("target/git/pgm/test/readme.txt").createNewFile();
+        execute(session, "git --git-dir test add readme.txt");
+
+        execute(session, "git --git-dir test commit -m \"readme\"");
+
+        client.stop();
+        sshd.stop();
+    }
+
+    private void execute(ClientSession session, String command) throws Exception {
+        ChannelExec channel = session.createExecChannel(command);
+        channel.setOut(System.out);
+        channel.setErr(System.err);
+        channel.open().verify();
+        channel.waitFor(ClientChannel.CLOSED, 0);
+        if (channel.getExitStatus() != null) {
+            int s = channel.getExitStatus();
+            if (s != 0) {
+                throw new Exception("Command failed with status " + s);
+            }
+        }
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/aca9f534/sshd-git/src/test/java/org/apache/sshd/git/util/Utils.java
----------------------------------------------------------------------
diff --git a/sshd-git/src/test/java/org/apache/sshd/git/util/Utils.java b/sshd-git/src/test/java/org/apache/sshd/git/util/Utils.java
index 2558d9c..2ecbd01 100644
--- a/sshd-git/src/test/java/org/apache/sshd/git/util/Utils.java
+++ b/sshd-git/src/test/java/org/apache/sshd/git/util/Utils.java
@@ -55,4 +55,18 @@ public class Utils {
         return f.toString();
     }
 
+    public static void deleteRecursive(File file) {
+        if (file != null) {
+            if (file.isDirectory()) {
+                File[] children = file.listFiles();
+                if (children != null) {
+                    for (File child : children) {
+                        deleteRecursive(child);
+                    }
+                }
+            }
+            file.delete();
+        }
+    }
+
 }