]> source.dussan.org Git - gitblit.git/commitdiff
Revised Git Daemon to improve thread stopping and to eliminate repository name hack
authorJames Moger <james.moger@gitblit.com>
Fri, 3 May 2013 23:06:50 +0000 (19:06 -0400)
committerJames Moger <james.moger@gitblit.com>
Fri, 3 May 2013 23:09:25 +0000 (19:09 -0400)
releases.moxie
src/main/java/com/gitblit/GitBlit.java
src/main/java/com/gitblit/git/GitDaemon.java
src/main/java/com/gitblit/git/GitDaemonClient.java [new file with mode: 0644]
src/main/java/com/gitblit/git/GitDaemonService.java [new file with mode: 0644]
src/main/java/com/gitblit/git/GitblitReceivePackFactory.java
src/main/java/com/gitblit/git/GitblitUploadPackFactory.java
src/main/java/com/gitblit/git/RepositoryResolver.java
src/test/java/com/gitblit/tests/GitDaemonStopTest.java [new file with mode: 0644]

index c06543533bd9dc09cdd7106cc3f7046d6dba7391..ce717727a169949385cb120904037406c13b6322 100644 (file)
@@ -34,7 +34,7 @@ r17: {
         \r
     additions: \r
         - Added a server setting to force a particular translation/Locale for all sessions\r
-        - Added Git Daemon serving\r
+        - Added smart Git Daemon serving.  If enabled, git:// access will be offered for any repository which permits anonymous access.  If the repository permits anonymous cloning, anonymous git:// clone will be permitted while anonmymous git:// pushes will be rejected.
         - Option to automatically tag branch tips on each push with an incremental revision number\r
      - Implemented multiple repository owners\r
      - Optional periodic LDAP user and team pre-fetching & synchronization\r
index 377a7b3d8985b97ce93bd39b48945a3fb356e8f4..0e4e2e91a83fea3d92a5150dc26e8073a6bf7b7d 100644 (file)
@@ -3240,7 +3240,6 @@ public class GitBlit implements ServletContextListener {
                        try {\r
                                gitDaemon = new GitDaemon(bindInterface, port, getRepositoriesFolder());\r
                                gitDaemon.start();\r
-                               logger.info(MessageFormat.format("Git daemon is listening on {0}:{1,number,0}", bindInterface, port));\r
                        } catch (IOException e) {\r
                                gitDaemon = null;\r
                                logger.error(MessageFormat.format("Failed to start Git daemon on {0}:{1,number,0}", bindInterface, port), e);\r
index 8ec056311089bd09510630aa2735807a91c646ed..7050f87824133751a188a88ddb1e1b64d5ffffa9 100644 (file)
 /*\r
- * Copyright 2013 gitblit.com.\r
+ * Copyright (C) 2013 gitblit.com\r
+ * Copyright (C) 2008-2009, Google Inc.\r
+ * and other copyright owners as documented in the project's IP log.\r
  *\r
- * Licensed under the Apache License, Version 2.0 (the "License");\r
- * you may not use this file except in compliance with the License.\r
- * You may obtain a copy of the License at\r
+ * This program and the accompanying materials are made available\r
+ * under the terms of the Eclipse Distribution License v1.0 which\r
+ * accompanies this distribution, is reproduced below, and is\r
+ * available at http://www.eclipse.org/org/documents/edl-v10.php\r
  *\r
- *     http://www.apache.org/licenses/LICENSE-2.0\r
+ * All rights reserved.\r
  *\r
- * Unless required by applicable law or agreed to in writing, software\r
- * distributed under the License is distributed on an "AS IS" BASIS,\r
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
- * See the License for the specific language governing permissions and\r
- * limitations under the License.\r
+ * Redistribution and use in source and binary forms, with or\r
+ * without modification, are permitted provided that the following\r
+ * conditions are met:\r
+ *\r
+ * - Redistributions of source code must retain the above copyright\r
+ *   notice, this list of conditions and the following disclaimer.\r
+ *\r
+ * - Redistributions in binary form must reproduce the above\r
+ *   copyright notice, this list of conditions and the following\r
+ *   disclaimer in the documentation and/or other materials provided\r
+ *   with the distribution.\r
+ *\r
+ * - Neither the name of the Eclipse Foundation, Inc. nor the\r
+ *   names of its contributors may be used to endorse or promote\r
+ *   products derived from this software without specific prior\r
+ *   written permission.\r
+ *\r
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND\r
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,\r
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES\r
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\r
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR\r
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\r
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT\r
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;\r
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER\r
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,\r
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\r
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF\r
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\r
  */\r
 package com.gitblit.git;\r
 \r
 import java.io.File;\r
+import java.io.IOException;\r
+import java.io.InputStream;\r
+import java.io.InterruptedIOException;\r
+import java.io.OutputStream;\r
 import java.net.InetSocketAddress;\r
+import java.net.ServerSocket;\r
+import java.net.Socket;\r
+import java.net.SocketAddress;\r
+import java.text.MessageFormat;\r
+import java.util.concurrent.atomic.AtomicBoolean;\r
 \r
-import org.eclipse.jgit.transport.Daemon;\r
-import org.eclipse.jgit.transport.DaemonClient;\r
+import org.eclipse.jgit.errors.RepositoryNotFoundException;\r
+import org.eclipse.jgit.internal.JGitText;\r
+import org.eclipse.jgit.lib.Repository;\r
+import org.eclipse.jgit.transport.ReceivePack;\r
+import org.eclipse.jgit.transport.ServiceMayNotContinueException;\r
+import org.eclipse.jgit.transport.UploadPack;\r
+import org.eclipse.jgit.transport.resolver.ReceivePackFactory;\r
+import org.eclipse.jgit.transport.resolver.ServiceNotAuthorizedException;\r
+import org.eclipse.jgit.transport.resolver.ServiceNotEnabledException;\r
+import org.eclipse.jgit.transport.resolver.UploadPackFactory;\r
+import org.slf4j.Logger;\r
+import org.slf4j.LoggerFactory;\r
 \r
 import com.gitblit.utils.StringUtils;\r
 \r
 /**\r
- * Gitblit's Git Daemon ignores any and all per-repository daemon settings\r
- * and integrates into Gitblit's security model.\r
+ * Gitblit's Git Daemon ignores any and all per-repository daemon settings and\r
+ * integrates into Gitblit's security model.\r
  * \r
  * @author James Moger\r
- *\r
+ * \r
  */\r
-public class GitDaemon extends Daemon {\r
+public class GitDaemon {\r
+\r
+       private final Logger logger = LoggerFactory.getLogger(GitDaemon.class);\r
+\r
+       /** 9418: IANA assigned port number for Git. */\r
+       public static final int DEFAULT_PORT = 9418;\r
+\r
+       private static final int BACKLOG = 5;\r
+\r
+       private InetSocketAddress myAddress;\r
+\r
+       private final GitDaemonService[] services;\r
+\r
+       private final ThreadGroup processors;\r
+\r
+       private AtomicBoolean run;\r
+\r
+       private ServerSocket acceptSocket;\r
+\r
+       private Thread acceptThread;\r
+\r
+       private int timeout;\r
+\r
+       private RepositoryResolver<GitDaemonClient> repositoryResolver;\r
+\r
+       private UploadPackFactory<GitDaemonClient> uploadPackFactory;\r
+\r
+       private ReceivePackFactory<GitDaemonClient> receivePackFactory;\r
+\r
+       /** Configure a daemon to listen on any available network port. */\r
+       public GitDaemon() {\r
+               this(null);\r
+       }\r
 \r
        /**\r
         * Construct the Gitblit Git daemon.\r
@@ -43,22 +122,213 @@ public class GitDaemon extends Daemon {
         *            the folder to serve from\r
         */\r
        public GitDaemon(String bindInterface, int port, File folder) {\r
-               super(StringUtils.isEmpty(bindInterface) ? new InetSocketAddress(port) : new InetSocketAddress(bindInterface, port));\r
-               \r
+               this(StringUtils.isEmpty(bindInterface) ? new InetSocketAddress(port)\r
+                               : new InetSocketAddress(bindInterface, port));\r
+\r
                // set the repository resolver and pack factories\r
-               setRepositoryResolver(new RepositoryResolver<DaemonClient>(folder));\r
-               setUploadPackFactory(new GitblitUploadPackFactory<DaemonClient>());\r
-               setReceivePackFactory(new GitblitReceivePackFactory<DaemonClient>());\r
-               \r
-               // configure the git daemon to ignore the per-repository settings,\r
-               // daemon.uploadpack and daemon.receivepack\r
-               getService("git-upload-pack").setOverridable(false);\r
-               getService("git-receive-pack").setOverridable(false);\r
-               \r
-               // enable both the upload and receive services and let the resolver,\r
-               // pack factories, and receive hook handle security\r
-               getService("git-upload-pack").setEnabled(true);\r
-               getService("git-receive-pack").setEnabled(true);\r
+               repositoryResolver = new RepositoryResolver<GitDaemonClient>(folder);\r
        }\r
        \r
+       /**\r
+        * Configure a new daemon for the specified network address.\r
+        * \r
+        * @param addr\r
+        *            address to listen for connections on. If null, any available\r
+        *            port will be chosen on all network interfaces.\r
+        */\r
+       public GitDaemon(final InetSocketAddress addr) {\r
+               myAddress = addr;\r
+               processors = new ThreadGroup("Git-Daemon");\r
+\r
+               run = new AtomicBoolean(false);\r
+               repositoryResolver = null;\r
+               uploadPackFactory = new GitblitUploadPackFactory<GitDaemonClient>();\r
+               receivePackFactory = new GitblitReceivePackFactory<GitDaemonClient>();\r
+\r
+               services = new GitDaemonService[] { new GitDaemonService("upload-pack", "uploadpack") {\r
+                                       {\r
+                                               setEnabled(true);\r
+                                               setOverridable(false);\r
+                                       }\r
+\r
+                                       @Override\r
+                                       protected void execute(final GitDaemonClient dc, final Repository db)\r
+                                                       throws IOException, ServiceNotEnabledException,\r
+                                                       ServiceNotAuthorizedException {\r
+                                               UploadPack up = uploadPackFactory.create(dc, db);\r
+                                               InputStream in = dc.getInputStream();\r
+                                               OutputStream out = dc.getOutputStream();\r
+                                               up.upload(in, out, null);\r
+                                       }\r
+                               }, new GitDaemonService("receive-pack", "receivepack") {\r
+                                       {\r
+                                               setEnabled(true);\r
+                                               setOverridable(false);\r
+                                       }\r
+\r
+                                       @Override\r
+                                       protected void execute(final GitDaemonClient dc, final Repository db)\r
+                                                       throws IOException, ServiceNotEnabledException,\r
+                                                       ServiceNotAuthorizedException {\r
+                                               ReceivePack rp = receivePackFactory.create(dc, db);\r
+                                               InputStream in = dc.getInputStream();\r
+                                               OutputStream out = dc.getOutputStream();\r
+                                               rp.receive(in, out, null);\r
+                                       }\r
+                               } };\r
+       }\r
+\r
+       /** @return timeout (in seconds) before aborting an IO operation. */\r
+       public int getTimeout() {\r
+               return timeout;\r
+       }\r
+\r
+       /**\r
+        * Set the timeout before willing to abort an IO call.\r
+        * \r
+        * @param seconds\r
+        *            number of seconds to wait (with no data transfer occurring)\r
+        *            before aborting an IO read or write operation with the\r
+        *            connected client.\r
+        */\r
+       public void setTimeout(final int seconds) {\r
+               timeout = seconds;\r
+       }\r
+\r
+       /**\r
+        * Start this daemon on a background thread.\r
+        * \r
+        * @throws IOException\r
+        *             the server socket could not be opened.\r
+        * @throws IllegalStateException\r
+        *             the daemon is already running.\r
+        */\r
+       public synchronized void start() throws IOException {\r
+               if (acceptThread != null)\r
+                       throw new IllegalStateException(JGitText.get().daemonAlreadyRunning);\r
+\r
+               final ServerSocket listenSock = new ServerSocket(myAddress != null ? myAddress.getPort()\r
+                               : 0, BACKLOG, myAddress != null ? myAddress.getAddress() : null);\r
+               myAddress = (InetSocketAddress) listenSock.getLocalSocketAddress();\r
+\r
+               run.set(true);\r
+               acceptSocket = listenSock;\r
+               acceptThread = new Thread(processors, "Git-Daemon-Accept") {\r
+                       public void run() {\r
+                               while (isRunning()) {\r
+                                       try {\r
+                                               startClient(listenSock.accept());\r
+                                       } catch (InterruptedIOException e) {\r
+                                               // Test again to see if we should keep accepting.\r
+                                       } catch (IOException e) {\r
+                                               break;\r
+                                       }\r
+                               }\r
+\r
+                               try {\r
+                                       listenSock.close();\r
+                               } catch (IOException err) {\r
+                                       //\r
+                               } finally {\r
+                                       acceptSocket = null;\r
+                                       acceptThread = null;\r
+                               }\r
+                       }\r
+               };\r
+               acceptThread.start();\r
+               \r
+               logger.info(MessageFormat.format("Git Daemon is listening on {0}:{1,number,0}", myAddress.getAddress().getHostAddress(), myAddress.getPort()));\r
+       }\r
+\r
+       /** @return true if this daemon is receiving connections. */\r
+       public boolean isRunning() {\r
+               return run.get();\r
+       }\r
+\r
+       /** Stop this daemon. */\r
+       public synchronized void stop() {\r
+               if (acceptThread != null) {\r
+                       logger.info("Git Daemon stopping...");\r
+                       run.set(false);\r
+                       try {\r
+                               // close the accept socket\r
+                               // this throws a SocketException in the accept thread\r
+                               acceptSocket.close();\r
+                       } catch (IOException e1) {\r
+                       }\r
+                       try {\r
+                               // join the accept thread\r
+                               acceptThread.join();\r
+                               logger.info("Git Daemon stopped.");\r
+                       } catch (InterruptedException e) {\r
+                               logger.error("Accept thread join interrupted", e);\r
+                       }\r
+               }\r
+       }\r
+\r
+       private void startClient(final Socket s) {\r
+               final GitDaemonClient dc = new GitDaemonClient(this);\r
+\r
+               final SocketAddress peer = s.getRemoteSocketAddress();\r
+               if (peer instanceof InetSocketAddress)\r
+                       dc.setRemoteAddress(((InetSocketAddress) peer).getAddress());\r
+\r
+               new Thread(processors, "Git-Daemon-Client " + peer.toString()) {\r
+                       public void run() {\r
+                               try {\r
+                                       dc.execute(s);\r
+                               } catch (ServiceNotEnabledException e) {\r
+                                       // Ignored. Client cannot use this repository.\r
+                               } catch (ServiceNotAuthorizedException e) {\r
+                                       // Ignored. Client cannot use this repository.\r
+                               } catch (IOException e) {\r
+                                       // Ignore unexpected IO exceptions from clients\r
+                               } finally {\r
+                                       try {\r
+                                               s.getInputStream().close();\r
+                                       } catch (IOException e) {\r
+                                               // Ignore close exceptions\r
+                                       }\r
+                                       try {\r
+                                               s.getOutputStream().close();\r
+                                       } catch (IOException e) {\r
+                                               // Ignore close exceptions\r
+                                       }\r
+                               }\r
+                       }\r
+               }.start();\r
+       }\r
+\r
+       synchronized GitDaemonService matchService(final String cmd) {\r
+               for (final GitDaemonService d : services) {\r
+                       if (d.handles(cmd))\r
+                               return d;\r
+               }\r
+               return null;\r
+       }\r
+\r
+       Repository openRepository(GitDaemonClient client, String name)\r
+                       throws ServiceMayNotContinueException {\r
+               // Assume any attempt to use \ was by a Windows client\r
+               // and correct to the more typical / used in Git URIs.\r
+               //\r
+               name = name.replace('\\', '/');\r
+\r
+               // git://thishost/path should always be name="/path" here\r
+               //\r
+               if (!name.startsWith("/")) //$NON-NLS-1$\r
+                       return null;\r
+\r
+               try {\r
+                       return repositoryResolver.open(client, name.substring(1));\r
+               } catch (RepositoryNotFoundException e) {\r
+                       // null signals it "wasn't found", which is all that is suitable\r
+                       // for the remote client to know.\r
+                       return null;\r
+               } catch (ServiceNotEnabledException e) {\r
+                       // null signals it "wasn't found", which is all that is suitable\r
+                       // for the remote client to know.\r
+                       return null;\r
+               }\r
+       }\r
 }\r
diff --git a/src/main/java/com/gitblit/git/GitDaemonClient.java b/src/main/java/com/gitblit/git/GitDaemonClient.java
new file mode 100644 (file)
index 0000000..6972f31
--- /dev/null
@@ -0,0 +1,130 @@
+package com.gitblit.git;\r
+\r
+/*\r
+ * Copyright (C) 2008-2009, Google Inc.\r
+ * and other copyright owners as documented in the project's IP log.\r
+ *\r
+ * This program and the accompanying materials are made available\r
+ * under the terms of the Eclipse Distribution License v1.0 which\r
+ * accompanies this distribution, is reproduced below, and is\r
+ * available at http://www.eclipse.org/org/documents/edl-v10.php\r
+ *\r
+ * All rights reserved.\r
+ *\r
+ * Redistribution and use in source and binary forms, with or\r
+ * without modification, are permitted provided that the following\r
+ * conditions are met:\r
+ *\r
+ * - Redistributions of source code must retain the above copyright\r
+ *   notice, this list of conditions and the following disclaimer.\r
+ *\r
+ * - Redistributions in binary form must reproduce the above\r
+ *   copyright notice, this list of conditions and the following\r
+ *   disclaimer in the documentation and/or other materials provided\r
+ *   with the distribution.\r
+ *\r
+ * - Neither the name of the Eclipse Foundation, Inc. nor the\r
+ *   names of its contributors may be used to endorse or promote\r
+ *   products derived from this software without specific prior\r
+ *   written permission.\r
+ *\r
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND\r
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,\r
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES\r
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\r
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR\r
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\r
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT\r
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;\r
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER\r
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,\r
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\r
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF\r
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\r
+ */\r
+\r
+import java.io.BufferedInputStream;\r
+import java.io.IOException;\r
+import java.io.InputStream;\r
+import java.io.OutputStream;\r
+import java.net.InetAddress;\r
+import java.net.Socket;\r
+\r
+import org.eclipse.jgit.transport.PacketLineIn;\r
+import org.eclipse.jgit.transport.resolver.ServiceNotAuthorizedException;\r
+import org.eclipse.jgit.transport.resolver.ServiceNotEnabledException;\r
+import org.eclipse.jgit.util.io.SafeBufferedOutputStream;\r
+\r
+/** Active network client of {@link Daemon}. */\r
+public class GitDaemonClient {\r
+       private final GitDaemon daemon;\r
+\r
+       private InetAddress peer;\r
+\r
+       private InputStream rawIn;\r
+\r
+       private OutputStream rawOut;\r
+       \r
+       private String repositoryName;\r
+\r
+       GitDaemonClient(final GitDaemon d) {\r
+               daemon = d;\r
+       }\r
+\r
+       void setRemoteAddress(final InetAddress ia) {\r
+               peer = ia;\r
+       }\r
+\r
+       /** @return the daemon which spawned this client. */\r
+       public GitDaemon getDaemon() {\r
+               return daemon;\r
+       }\r
+\r
+       /** @return Internet address of the remote client. */\r
+       public InetAddress getRemoteAddress() {\r
+               return peer;\r
+       }\r
+\r
+       /** @return input stream to read from the connected client. */\r
+       public InputStream getInputStream() {\r
+               return rawIn;\r
+       }\r
+\r
+       /** @return output stream to send data to the connected client. */\r
+       public OutputStream getOutputStream() {\r
+               return rawOut;\r
+       }\r
+       \r
+       public void setRepositoryName(String repositoryName) {\r
+               this.repositoryName = repositoryName;\r
+       }\r
+       \r
+       /** @return the name of the requested repository. */\r
+       public String getRepositoryName() {\r
+               return repositoryName;\r
+       }\r
+\r
+       void execute(final Socket sock) throws IOException,\r
+                       ServiceNotEnabledException, ServiceNotAuthorizedException {\r
+               rawIn = new BufferedInputStream(sock.getInputStream());\r
+               rawOut = new SafeBufferedOutputStream(sock.getOutputStream());\r
+\r
+               if (0 < daemon.getTimeout())\r
+                       sock.setSoTimeout(daemon.getTimeout() * 1000);\r
+               String cmd = new PacketLineIn(rawIn).readStringRaw();\r
+               final int nul = cmd.indexOf('\0');\r
+               if (nul >= 0) {\r
+                       // Newer clients hide a "host" header behind this byte.\r
+                       // Currently we don't use it for anything, so we ignore\r
+                       // this portion of the command.\r
+                       //\r
+                       cmd = cmd.substring(0, nul);\r
+               }\r
+\r
+               final GitDaemonService srv = getDaemon().matchService(cmd);\r
+               if (srv == null)\r
+                       return;\r
+               sock.setSoTimeout(0);\r
+               srv.execute(this, cmd);\r
+       }\r
+}
\ No newline at end of file
diff --git a/src/main/java/com/gitblit/git/GitDaemonService.java b/src/main/java/com/gitblit/git/GitDaemonService.java
new file mode 100644 (file)
index 0000000..03c4e1c
--- /dev/null
@@ -0,0 +1,165 @@
+package com.gitblit.git;\r
+\r
+/*\r
+ * Copyright (C) 2008-2009, Google Inc.\r
+ * Copyright (C) 2009, Robin Rosenberg <robin.rosenberg@dewire.com>\r
+ * and other copyright owners as documented in the project's IP log.\r
+ *\r
+ * This program and the accompanying materials are made available\r
+ * under the terms of the Eclipse Distribution License v1.0 which\r
+ * accompanies this distribution, is reproduced below, and is\r
+ * available at http://www.eclipse.org/org/documents/edl-v10.php\r
+ *\r
+ * All rights reserved.\r
+ *\r
+ * Redistribution and use in source and binary forms, with or\r
+ * without modification, are permitted provided that the following\r
+ * conditions are met:\r
+ *\r
+ * - Redistributions of source code must retain the above copyright\r
+ *   notice, this list of conditions and the following disclaimer.\r
+ *\r
+ * - Redistributions in binary form must reproduce the above\r
+ *   copyright notice, this list of conditions and the following\r
+ *   disclaimer in the documentation and/or other materials provided\r
+ *   with the distribution.\r
+ *\r
+ * - Neither the name of the Eclipse Foundation, Inc. nor the\r
+ *   names of its contributors may be used to endorse or promote\r
+ *   products derived from this software without specific prior\r
+ *   written permission.\r
+ *\r
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND\r
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,\r
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES\r
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\r
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR\r
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\r
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT\r
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;\r
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER\r
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,\r
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\r
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF\r
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\r
+ */\r
+\r
+import java.io.IOException;\r
+\r
+import org.eclipse.jgit.lib.Config;\r
+import org.eclipse.jgit.lib.Config.SectionParser;\r
+import org.eclipse.jgit.lib.Repository;\r
+import org.eclipse.jgit.transport.Daemon;\r
+import org.eclipse.jgit.transport.PacketLineOut;\r
+import org.eclipse.jgit.transport.ServiceMayNotContinueException;\r
+import org.eclipse.jgit.transport.resolver.ServiceNotAuthorizedException;\r
+import org.eclipse.jgit.transport.resolver.ServiceNotEnabledException;\r
+\r
+/** A service exposed by {@link Daemon} over anonymous <code>git://</code>. */\r
+public abstract class GitDaemonService {\r
+       private final String command;\r
+\r
+       private final SectionParser<ServiceConfig> configKey;\r
+\r
+       private boolean enabled;\r
+\r
+       private boolean overridable;\r
+\r
+       GitDaemonService(final String cmdName, final String cfgName) {\r
+               command = cmdName.startsWith("git-") ? cmdName : "git-" + cmdName; //$NON-NLS-1$ //$NON-NLS-2$\r
+               configKey = new SectionParser<ServiceConfig>() {\r
+                       public ServiceConfig parse(final Config cfg) {\r
+                               return new ServiceConfig(GitDaemonService.this, cfg, cfgName);\r
+                       }\r
+               };\r
+               overridable = true;\r
+       }\r
+\r
+       private static class ServiceConfig {\r
+               final boolean enabled;\r
+\r
+               ServiceConfig(final GitDaemonService service, final Config cfg,\r
+                               final String name) {\r
+                       enabled = cfg.getBoolean("daemon", name, service.isEnabled()); //$NON-NLS-1$\r
+               }\r
+       }\r
+\r
+       /** @return is this service enabled for invocation? */\r
+       public boolean isEnabled() {\r
+               return enabled;\r
+       }\r
+\r
+       /**\r
+        * @param on\r
+        *            true to allow this service to be used; false to deny it.\r
+        */\r
+       public void setEnabled(final boolean on) {\r
+               enabled = on;\r
+       }\r
+\r
+       /** @return can this service be configured in the repository config file? */\r
+       public boolean isOverridable() {\r
+               return overridable;\r
+       }\r
+\r
+       /**\r
+        * @param on\r
+        *            true to permit repositories to override this service's enabled\r
+        *            state with the <code>daemon.servicename</code> config setting.\r
+        */\r
+       public void setOverridable(final boolean on) {\r
+               overridable = on;\r
+       }\r
+\r
+       /** @return name of the command requested by clients. */\r
+       public String getCommandName() {\r
+               return command;\r
+       }\r
+\r
+       /**\r
+        * Determine if this service can handle the requested command.\r
+        *\r
+        * @param commandLine\r
+        *            input line from the client.\r
+        * @return true if this command can accept the given command line.\r
+        */\r
+       public boolean handles(final String commandLine) {\r
+               return command.length() + 1 < commandLine.length()\r
+                               && commandLine.charAt(command.length()) == ' '\r
+                               && commandLine.startsWith(command);\r
+       }\r
+\r
+       void execute(final GitDaemonClient client, final String commandLine)\r
+                       throws IOException, ServiceNotEnabledException,\r
+                       ServiceNotAuthorizedException {\r
+               final String name = commandLine.substring(command.length() + 1);\r
+               Repository db;\r
+               try {\r
+                       db = client.getDaemon().openRepository(client, name);\r
+               } catch (ServiceMayNotContinueException e) {\r
+                       // An error when opening the repo means the client is expecting a ref\r
+                       // advertisement, so use that style of error.\r
+                       PacketLineOut pktOut = new PacketLineOut(client.getOutputStream());\r
+                       pktOut.writeString("ERR " + e.getMessage() + "\n"); //$NON-NLS-1$ //$NON-NLS-2$\r
+                       db = null;\r
+               }\r
+               if (db == null)\r
+                       return;\r
+               try {\r
+                       if (isEnabledFor(db))\r
+                               execute(client, db);\r
+               } finally {\r
+                       db.close();\r
+               }\r
+       }\r
+\r
+       private boolean isEnabledFor(final Repository db) {\r
+               if (isOverridable())\r
+                       return db.getConfig().get(configKey).enabled;\r
+               return isEnabled();\r
+       }\r
+\r
+       abstract void execute(GitDaemonClient client, Repository db)\r
+                       throws IOException, ServiceNotEnabledException,\r
+                       ServiceNotAuthorizedException;\r
+}\r
index 8b8ef0e4b989cdce0ce4f91bdee5af0f0766a7d3..77a3df63b7ad21bf73b43252db4697930a577d8c 100644 (file)
@@ -19,7 +19,6 @@ import javax.servlet.http.HttpServletRequest;
 
 import org.eclipse.jgit.lib.PersonIdent;
 import org.eclipse.jgit.lib.Repository;
-import org.eclipse.jgit.transport.DaemonClient;
 import org.eclipse.jgit.transport.ReceivePack;
 import org.eclipse.jgit.transport.resolver.ReceivePackFactory;
 import org.eclipse.jgit.transport.resolver.ServiceNotAuthorizedException;
@@ -50,18 +49,15 @@ public class GitblitReceivePackFactory<X> implements ReceivePackFactory<X> {
 
                final ReceivePack rp = new ReceivePack(db);
                UserModel user = UserModel.ANONYMOUS;
+               String repositoryName = "";
                String origin = "";
                String gitblitUrl = "";
                int timeout = 0;
                
-               // XXX extract the repository name from the config
-               // the name is injected by GitRepositoryResolver
-               String repositoryName = db.getConfig().getString("gitblit", null, "repositoryName");
-               
-               
                if (req instanceof HttpServletRequest) {
                        // http/https request may or may not be authenticated 
                        HttpServletRequest request = (HttpServletRequest) req;
+                       repositoryName = request.getAttribute("gitblitRepositoryName").toString();
                        origin = request.getRemoteHost();
                        gitblitUrl = HttpUtils.getGitblitURL(request);
 
@@ -74,9 +70,10 @@ public class GitblitReceivePackFactory<X> implements ReceivePackFactory<X> {
                                        user = new UserModel(username);
                                }
                        }
-               } else if (req instanceof DaemonClient) {
+               } else if (req instanceof GitDaemonClient) {
                        // git daemon request is alway anonymous
-                       DaemonClient client = (DaemonClient) req;
+                       GitDaemonClient client = (GitDaemonClient) req;
+                       repositoryName = client.getRepositoryName();
                        origin = client.getRemoteAddress().getHostAddress();
                        // set timeout from Git daemon
                        timeout = client.getDaemon().getTimeout();
index 85750f84582e4bd3d8a09fcc2ac0d3ed9e18759c..e953ca499fdb846f989bfa5dd1ec3fd27423e6d9 100644 (file)
@@ -21,7 +21,6 @@ import javax.servlet.http.HttpServletRequest;
 
 import org.eclipse.jgit.lib.Ref;
 import org.eclipse.jgit.lib.Repository;
-import org.eclipse.jgit.transport.DaemonClient;
 import org.eclipse.jgit.transport.RefFilter;
 import org.eclipse.jgit.transport.UploadPack;
 import org.eclipse.jgit.transport.resolver.ServiceNotAuthorizedException;
@@ -56,9 +55,9 @@ public class GitblitUploadPackFactory<X> implements UploadPackFactory<X> {
                        if (user == null) {
                                user = UserModel.ANONYMOUS;
                        }
-               } else if (req instanceof DaemonClient) {
+               } else if (req instanceof GitDaemonClient) {
                        // git daemon request is always anonymous
-                       DaemonClient client = (DaemonClient) req;
+                       GitDaemonClient client = (GitDaemonClient) req;
                        // set timeout from Git daemon
                        timeout = client.getDaemon().getTimeout();
                }
index fb5db7120e9c356f55853936466a946292975e74..21a8376077153aaf930bced7e381c4aef0cd92e0 100644 (file)
@@ -23,7 +23,6 @@ import javax.servlet.http.HttpServletRequest;
 
 import org.eclipse.jgit.errors.RepositoryNotFoundException;
 import org.eclipse.jgit.lib.Repository;
-import org.eclipse.jgit.transport.DaemonClient;
 import org.eclipse.jgit.transport.resolver.FileResolver;
 import org.eclipse.jgit.transport.resolver.ServiceNotEnabledException;
 import org.slf4j.Logger;
@@ -54,10 +53,19 @@ public class RepositoryResolver<X> extends FileResolver<X> {
        public Repository open(final X req, final String name)
                        throws RepositoryNotFoundException, ServiceNotEnabledException {
                Repository repo = super.open(req, name);
-               // XXX Set repository name for the pack factories
+               
+               // Set repository name for the pack factories
                // We do this because the JGit API does not have a consistent way to
                // retrieve the repository name from the pack factories or the hooks.
-               repo.getConfig().setString("gitblit", null, "repositoryName", name);
+               if (req instanceof HttpServletRequest) {
+                       // http/https request
+                       HttpServletRequest client = (HttpServletRequest) req;
+                       client.setAttribute("gitblitRepositoryName", name);
+               } else if (req instanceof GitDaemonClient) {
+                       // git request
+                       GitDaemonClient client = (GitDaemonClient) req;
+                       client.setRepositoryName(name);
+               }
                return repo;
        }
        
@@ -72,10 +80,10 @@ public class RepositoryResolver<X> extends FileResolver<X> {
                UserModel user = null;
                String origin = null;
                
-               if (req instanceof DaemonClient) {
+               if (req instanceof GitDaemonClient) {
                        // git daemon request
                        // this is an anonymous/unauthenticated protocol
-                       DaemonClient client = (DaemonClient) req;
+                       GitDaemonClient client = (GitDaemonClient) req;
                        scheme = "git";
                        origin = client.getRemoteAddress().toString();
                        user = UserModel.ANONYMOUS;
diff --git a/src/test/java/com/gitblit/tests/GitDaemonStopTest.java b/src/test/java/com/gitblit/tests/GitDaemonStopTest.java
new file mode 100644 (file)
index 0000000..7febc7a
--- /dev/null
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2013 gitblit.com.
+ *
+ * Licensed 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 com.gitblit.tests;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import com.gitblit.git.GitDaemon;
+
+public class GitDaemonStopTest extends Assert {
+
+       @Test
+       public void testGitDaemonStop() throws Exception {
+               GitDaemon daemon = new GitDaemon("localhost", GitDaemon.DEFAULT_PORT + 1, GitBlitSuite.REPOSITORIES);
+               daemon.setTimeout(5);
+               daemon.start();
+               Thread.sleep(5000);
+               daemon.stop();
+       }
+}