]> source.dussan.org Git - gitblit.git/commitdiff
Server-side hook script selection. Documentation.
authorJames Moger <james.moger@gitblit.com>
Mon, 19 Dec 2011 22:58:43 +0000 (17:58 -0500)
committerJames Moger <james.moger@gitblit.com>
Mon, 19 Dec 2011 22:58:43 +0000 (17:58 -0500)
distrib/gitblit.properties
docs/01_setup.mkd
resources/bootstrap.gb.css
src/com/gitblit/GitBlit.java
src/com/gitblit/GitServlet.java
src/com/gitblit/wicket/GitBlitWebApp.properties
src/com/gitblit/wicket/pages/EditRepositoryPage.html
src/com/gitblit/wicket/pages/EditRepositoryPage.java
test-gitblit.properties

index ebaa29a6decbc8591a56b9f909d9df105c7b0127..d51dfb7e7ac1b81cf0a317e8e9dd4f095c69a481 100644 (file)
@@ -36,6 +36,7 @@ git.enableGitServlet = true
 # Use forward slashes even on Windows!!\r
 # e.g. c:/groovy\r
 #\r
+# RESTART REQUIRED\r
 # SINCE 0.8.0\r
 groovy.scriptsFolder = groovy\r
 \r
@@ -46,6 +47,9 @@ groovy.scriptsFolder = groovy
 # push in this script based on the repository and branch the push is attempting\r
 # to change.\r
 #\r
+# Script names are case-sensitive on case-sensitive file systems.  You may omit\r
+# the traditional ".groovy" from this list if your file extension is ".groovy" \r
+#\r
 # NOTE:\r
 # These scripts are only executed when pushing to *Gitblit*, not to other Git\r
 # tooling you may be using.  Also note that these scripts are shared between\r
@@ -54,6 +58,7 @@ groovy.scriptsFolder = groovy
 # *repository* variable.\r
 #\r
 # SPACE-DELIMITED\r
+# CASE-SENSITIVE\r
 # SINCE 0.8.0\r
 groovy.preReceiveScripts =\r
 \r
@@ -62,6 +67,9 @@ groovy.preReceiveScripts =
 # These scripts execute AFTER an incoming push has been applied to a repository.\r
 # You might trigger a continuous-integration build here or send a notification.\r
 #\r
+# Script names are case-sensitive on case-sensitive file systems.  You may omit\r
+# the traditional ".groovy" from this list if your file extension is ".groovy" \r
+#\r
 # NOTE:\r
 # These scripts are only executed when pushing to *Gitblit*, not to other Git\r
 # tooling you may be using.  Also note that these scripts are shared between\r
@@ -70,6 +78,7 @@ groovy.preReceiveScripts =
 # *repository* variable.\r
 # \r
 # SPACE-DELIMITED\r
+# CASE-SENSITIVE\r
 # SINCE 0.8.0\r
 groovy.postReceiveScripts =\r
 \r
index 27a4e6af4bc2bbaa226929c00c0bbbe3d1e7a2b4..1cc89bf2ae7c99ea969da9471b82a7cef40bd194 100644 (file)
@@ -232,12 +232,32 @@ The preferred hook mechanism is Groovy.  This mechanism only executes when pushi
 \r
 The Groovy hook mechanism allows for dynamic extension of Gitblit to execute custom tasks on receiving and processing push events.  The scripts run within the context of your Gitblit instance and therefore have access to Gitblit's internals at runtime.\r
 \r
-Your Groovy scripts should be stored in the *groovy.scriptsFolder* as specified in `gitblit.properties` or `web.xml`.\r
+### Rules & Requirements\r
+1. Your Groovy scripts must be stored in the *groovy.scriptsFolder* as specified in `gitblit.properties` or `web.xml`.\r
+2. All script files must have the *.groovy* extension. Because of this you may omit the extension when specifying the script.\r
+3. Scripts must be explicitly specified to be executed, no scripts are *automatically* executed by name or extension.\r
+4. A script can be specified to run on *all repositories* by adding the script file name to *groovy.preReceiveScripts* or *groovy.postReceiveScripts* in `gitblit.properties` or `web.xml`.\r
+5. Scripts may also be specified per-repository in the repository's settings.\r
+6. Global/shared scripts are executed first in their listed order, followed by per-repository scripts in their listed order.\r
+7. A script may only be defined once in a pre-receive list and once in a post-receive list.  \r
+You may execute the same script on pre-receive and post-receive, just not multiple times within a pre-receive or post-receive event.\r
+8. Gitblit does not differentiate between what can be a pre-receive script and what can be a post-receive script.\r
+9. If a script *returns false* then the hook chain is aborted and none of the subsequent scripts will execute.\r
+\r
+Some sample scripts are included in the GO and WAR distributions to show you how you can tap into Gitblit with the provided bound variables.  Additional implementation details may be specified in the header comment of these examples.  \r
+Hook contributions and improvements are welcome.\r
 \r
-Scripts must be explicitly specified to be executed.  A script can be run on *all repositories* by adding the script file name to *groovy.preReceiveScripts* or *groovy.postReceiveScripts* in `gitblit.properties` or `web.xml`. Alternatively, you may specify per-repository scripts in the repository settings.  Global/shared scripts are executed first in their listed order, followed by per-repository scripts in their listed order.\r
+### Pre-Receive\r
 \r
-Some primitive sample scripts are included in the GO and WAR distributions to show you how you can tap into Gitblit with the provided bound variables.  \r
-Hook contributions and improvements are welcome.\r
+Pre-Receive scripts execute after the pushed objects have all been written to the Git repository but before the refs have been updated to point to these new objects.\r
+\r
+This is the appropriate point to block a push and is how many Git tools implement branch-write permissions.\r
+\r
+### Post-Receive\r
+\r
+Post-Receive scripts execute after all refs have been updated.\r
+\r
+This is the appropriate point to trigger continuous integration builds or send email notifications, etc.\r
 \r
 ## Client Setup and Configuration\r
 ### Https with Self-Signed Certificates\r
index 32aac08a5f52f19902516507360eb7fccef610b2..6eeec389a2b34315c7cdeb3c8263e88f2949673d 100644 (file)
@@ -11,6 +11,11 @@ hr {
        margin-bottom: 10px;\r
 }\r
 \r
+.settings h3 {\r
+       margin-bottom: 0.5em;    \r
+       border-bottom: 2px solid #000080 !important;\r
+}\r
+\r
 .page-header h1, .page-header h2 {\r
        color: #0069D6;\r
 }\r
index 8afa6df31f917e2818bd964ac797be232087ed0c..f0122791fb9ac18bccb5e44f53c1a5278d70dcb3 100644 (file)
@@ -27,9 +27,11 @@ import java.util.ArrayList;
 import java.util.Arrays;\r
 import java.util.Collections;\r
 import java.util.HashMap;\r
+import java.util.HashSet;\r
 import java.util.List;\r
 import java.util.Map;\r
 import java.util.Map.Entry;\r
+import java.util.Set;\r
 import java.util.concurrent.ConcurrentHashMap;\r
 import java.util.concurrent.Executors;\r
 import java.util.concurrent.ScheduledExecutorService;\r
@@ -295,6 +297,16 @@ public class GitBlit implements ServletContextListener {
                return getFileOrFolder(Keys.federation.proposalsFolder, "proposals");\r
        }\r
 \r
+       /**\r
+        * Returns the path of the Groovy folder. This method checks to see if\r
+        * Gitblit is running on a cloud service and may return an adjusted path.\r
+        * \r
+        * @return the Groovy scripts folder path\r
+        */\r
+       public static File getGroovyScriptsFolder() {\r
+               return getFileOrFolder(Keys.groovy.scriptsFolder, "groovy");\r
+       }\r
+\r
        /**\r
         * Updates the list of server settings.\r
         * \r
@@ -1425,6 +1437,48 @@ public class GitBlit implements ServletContextListener {
                return file.delete();\r
        }\r
 \r
+       /**\r
+        * Returns the list of all available Groovy push hook scripts that are not\r
+        * already specified globally for all repositories. Script files must have\r
+        * .groovy extension\r
+        * \r
+        * @return list of available hook scripts\r
+        */\r
+       public List<String> getAvailableScripts() {\r
+               File groovyFolder = getGroovyScriptsFolder();\r
+               File[] files = groovyFolder.listFiles(new FileFilter() {\r
+                       @Override\r
+                       public boolean accept(File pathname) {\r
+                               return pathname.isFile() && pathname.getName().endsWith(".groovy");\r
+                       }\r
+               });\r
+\r
+               Set<String> globals = new HashSet<String>();\r
+               String[] keys = { Keys.groovy.preReceiveScripts, Keys.groovy.postReceiveScripts };\r
+               for (String key : keys) {\r
+                       for (String script : getStrings(key)) {\r
+                               if (script.endsWith(".groovy")) {\r
+                                       globals.add(script.substring(0, script.lastIndexOf('.')));\r
+                               } else {\r
+                                       globals.add(script);\r
+                               }\r
+                       }\r
+               }\r
+\r
+               // create list of available scripts by excluding scripts that are\r
+               // globally specified\r
+               List<String> scripts = new ArrayList<String>();\r
+               if (files != null) {\r
+                       for (File file : files) {\r
+                               String script = file.getName().substring(0, file.getName().lastIndexOf('.'));\r
+                               if (!globals.contains(script)) {\r
+                                       scripts.add(script);\r
+                               }\r
+                       }\r
+               }\r
+               return scripts;\r
+       }\r
+\r
        /**\r
         * Notify the administrators by email.\r
         * \r
@@ -1488,6 +1542,7 @@ public class GitBlit implements ServletContextListener {
                                setting.currentValue = settings.getString(key, "");\r
                        }\r
                }\r
+               settingsModel.pushScripts = getAvailableScripts();\r
                return settingsModel;\r
        }\r
 \r
index b2ee1c7907d5ee82480edf5f109abbf2ddab4531..23fb32a650309e43cb46dfaa452513b939f5d3fc 100644 (file)
@@ -26,7 +26,8 @@ import java.io.InputStreamReader;
 import java.io.OutputStreamWriter;\r
 import java.text.MessageFormat;\r
 import java.util.Collection;\r
-import java.util.List;\r
+import java.util.LinkedHashSet;\r
+import java.util.Set;\r
 \r
 import javax.servlet.ServletConfig;\r
 import javax.servlet.ServletException;\r
@@ -68,6 +69,8 @@ public class GitServlet extends org.eclipse.jgit.http.server.GitServlet {
 \r
        private GroovyScriptEngine gse;\r
 \r
+       private File groovyDir;\r
+\r
        /**\r
         * Configure the servlet from Gitblit's configuration.\r
         */\r
@@ -83,9 +86,9 @@ public class GitServlet extends org.eclipse.jgit.http.server.GitServlet {
 \r
        @Override\r
        public void init(ServletConfig config) throws ServletException {\r
-               String groovyRoot = GitBlit.getString(Keys.groovy.scriptsFolder, "groovy");\r
+               groovyDir = GitBlit.getGroovyScriptsFolder();           \r
                try {\r
-                       gse = new GroovyScriptEngine(groovyRoot);\r
+                       gse = new GroovyScriptEngine(groovyDir.getAbsolutePath());\r
                } catch (IOException e) {\r
                        throw new ServletException("Failed to instantiate Groovy Script Engine!", e);\r
                }\r
@@ -127,7 +130,8 @@ public class GitServlet extends org.eclipse.jgit.http.server.GitServlet {
                 */\r
                @Override\r
                public void onPreReceive(ReceivePack rp, Collection<ReceiveCommand> commands) {\r
-                       List<String> scripts = GitBlit.getStrings(Keys.groovy.preReceiveScripts);\r
+                       Set<String> scripts = new LinkedHashSet<String>();\r
+                       scripts.addAll(GitBlit.getStrings(Keys.groovy.preReceiveScripts));\r
                        RepositoryModel repository = getRepositoryModel(rp);\r
                        scripts.addAll(repository.preReceiveScripts);\r
                        UserModel user = getUserModel(rp);\r
@@ -154,7 +158,8 @@ public class GitServlet extends org.eclipse.jgit.http.server.GitServlet {
                                logger.info("skipping post-receive hooks, no refs created, updated, or removed");\r
                                return;\r
                        }\r
-                       List<String> scripts = GitBlit.getStrings(Keys.groovy.postReceiveScripts);\r
+                       Set<String> scripts = new LinkedHashSet<String>();\r
+                       scripts.addAll(GitBlit.getStrings(Keys.groovy.postReceiveScripts));\r
                        RepositoryModel repository = getRepositoryModel(rp);\r
                        scripts.addAll(repository.postReceiveScripts);\r
                        UserModel user = getUserModel(rp);\r
@@ -204,7 +209,7 @@ public class GitServlet extends org.eclipse.jgit.http.server.GitServlet {
                 * @param scripts\r
                 */\r
                protected void runGroovy(RepositoryModel repository, UserModel user,\r
-                               Collection<ReceiveCommand> commands, List<String> scripts) {\r
+                               Collection<ReceiveCommand> commands, Set<String> scripts) {\r
                        if (scripts == null || scripts.size() == 0) {\r
                                // no Groovy scripts to execute\r
                                return;\r
@@ -221,6 +226,15 @@ public class GitServlet extends org.eclipse.jgit.http.server.GitServlet {
                                if (StringUtils.isEmpty(script)) {\r
                                        continue;\r
                                }\r
+                               // allow script to be specified without .groovy extension\r
+                               // this is easier to read in the settings\r
+                               File file = new File(groovyDir, script);\r
+                               if (!file.exists() && !script.toLowerCase().endsWith(".groovy")) {\r
+                                       file = new File(groovyDir, script + ".groovy");\r
+                                       if (file.exists()) {\r
+                                               script = file.getName();\r
+                                       }\r
+                               }\r
                                try {\r
                                        Object result = gse.run(script, binding);\r
                                        if (result instanceof Boolean) {\r
index 181aed9ccbc5959f3cfa27a2ec7bb56dcad725f4..4eeb887c6e36c9de074d18302d109f5e6356a854 100644 (file)
@@ -197,4 +197,7 @@ gb.permittedTeams = permitted teams
 gb.emptyRepository = empty repository\r
 gb.repositoryUrl = repository url\r
 gb.mailRecipients = mail recipients\r
-gb.mailRecipientsDescription = space-delimited, used by sendemail Groovy hook
\ No newline at end of file
+gb.mailRecipientsDescription = space-delimited, used by sendemail Groovy hook\r
+gb.preReceiveScripts = pre-receive scripts\r
+gb.postReceiveScripts = post-receive scripts\r
+gb.groovyHookScripts = hook scripts
\ No newline at end of file
index 43e42acf498a567b510254cc388ea599e6a4ada7..3e2212251d163a152fcbc33d68c4e5973278d1d6 100644 (file)
@@ -9,7 +9,8 @@
        <!-- Repository Table -->\r
        <form style="padding-top:5px;" wicket:id="editForm">\r
                <table class="plain">\r
-                       <tbody>\r
+                       <tbody class="settings">\r
+                               <tr><td colspan="2"><h3><wicket:message key="gb.general"></wicket:message></h3></td></tr>\r
                                <tr><th><wicket:message key="gb.name"></wicket:message></th><td class="edit"><input class="span6" type="text" wicket:id="name" id="name" size="40" tabindex="1" /> &nbsp;<i><wicket:message key="gb.nameDescription"></wicket:message></i></td></tr>\r
                                <tr><th><wicket:message key="gb.description"></wicket:message></th><td class="edit"><input class="span6" type="text" wicket:id="description" size="40" tabindex="2" /></td></tr>\r
                                <tr><th><wicket:message key="gb.origin"></wicket:message></th><td class="edit"><input class="span7" type="text" wicket:id="origin" size="80" tabindex="3" /></td></tr>\r
                                <tr><th><wicket:message key="gb.skipSummaryMetrics"></wicket:message></th><td class="edit"><input type="checkbox" wicket:id="skipSummaryMetrics" tabindex="10" /> &nbsp;<i><wicket:message key="gb.skipSummaryMetricsDescription"></wicket:message></i></td></tr>\r
                                <tr><th><wicket:message key="gb.isFrozen"></wicket:message></th><td class="edit"><input type="checkbox" wicket:id="isFrozen" tabindex="11" /> &nbsp;<i><wicket:message key="gb.isFrozenDescription"></wicket:message></i></td></tr>\r
                                <tr><th><wicket:message key="gb.mailRecipients"></wicket:message></th><td class="edit"><input class="span9" type="text" wicket:id="mailRecipients" size="40" tabindex="12" /> &nbsp;<i><wicket:message key="gb.mailRecipientsDescription"></wicket:message></i></td></tr>\r
-                               <tr><td colspan="2"><hr></hr></td></tr>\r
+                               <tr><td colspan="2"><h3><wicket:message key="gb.accessRestriction"></wicket:message></h3></td></tr>     \r
                                <tr><th><wicket:message key="gb.accessRestriction"></wicket:message></th><td class="edit"><select class="span6" wicket:id="accessRestriction" tabindex="13" /></td></tr>                                \r
                                <tr><th style="vertical-align: top;"><wicket:message key="gb.permittedUsers"></wicket:message></th><td style="padding:2px;"><span wicket:id="users"></span></td></tr>\r
                                <tr><th style="vertical-align: top;"><wicket:message key="gb.permittedTeams"></wicket:message></th><td style="padding:2px;"><span wicket:id="teams"></span></td></tr>\r
-                               <tr><td colspan="2"><hr></hr></td></tr>         \r
+                               <tr><td colspan="2"><h3><wicket:message key="gb.federation"></wicket:message></h3></td></tr>    \r
                                <tr><th><wicket:message key="gb.federationStrategy"></wicket:message></th><td class="edit"><select class="span6" wicket:id="federationStrategy" tabindex="14" /></td></tr>\r
                                <tr><th style="vertical-align: top;"><wicket:message key="gb.federationSets"></wicket:message></th><td style="padding:2px;"><span wicket:id="federationSets"></span></td></tr>\r
+                               <tr><td colspan="2"><h3><wicket:message key="gb.hookScripts"></wicket:message></h3></td></tr>   \r
+                               <tr><th style="vertical-align: top;"><wicket:message key="gb.preReceiveScripts"></wicket:message></th><td style="padding:2px;"><span wicket:id="preReceiveScripts"></span></td></tr>\r
+                               <tr><th style="vertical-align: top;"><wicket:message key="gb.postReceiveScripts"></wicket:message></th><td style="padding:2px;"><span wicket:id="postReceiveScripts"></span></td></tr>\r
                                <tr><th></th><td class="editButton"><input class="btn" type="submit" value="Cancel" wicket:message="value:gb.cancel" wicket:id="cancel" tabindex="15" /> &nbsp; <input class="btn primary" type="submit" value="Save" wicket:message="value:gb.save" wicket:id="save" tabindex="16" /> </td></tr>\r
                        </tbody>\r
                </table>\r
index 56e44f8886ca40a45325882bca4ff16d99f11c6f..492addc38eee3abd1d33efca0ef3d3ed5ad3c60d 100644 (file)
@@ -80,6 +80,9 @@ public class EditRepositoryPage extends RootSubPage {
                List<String> federationSets = new ArrayList<String>();\r
                List<String> repositoryUsers = new ArrayList<String>();\r
                List<String> repositoryTeams = new ArrayList<String>();\r
+               List<String> preReceiveScripts = new ArrayList<String>();\r
+               List<String> postReceiveScripts = new ArrayList<String>();\r
+\r
                if (isCreate) {\r
                        super.setupPage(getString("gb.newRepository"), "");\r
                } else {\r
@@ -101,13 +104,29 @@ public class EditRepositoryPage extends RootSubPage {
                // teams palette\r
                final Palette<String> teamsPalette = new Palette<String>("teams", new ListModel<String>(\r
                                repositoryTeams), new CollectionModel<String>(GitBlit.self().getAllTeamnames()),\r
-                               new ChoiceRenderer<String>("", ""), 10, false);\r
+                               new ChoiceRenderer<String>("", ""), 5, false);\r
 \r
                // federation sets palette\r
                List<String> sets = GitBlit.getStrings(Keys.federation.sets);\r
                final Palette<String> federationSetsPalette = new Palette<String>("federationSets",\r
                                new ListModel<String>(federationSets), new CollectionModel<String>(sets),\r
-                               new ChoiceRenderer<String>("", ""), 10, false);\r
+                               new ChoiceRenderer<String>("", ""), 5, false);\r
+\r
+               // pre-receive palette\r
+               if (repositoryModel.preReceiveScripts != null) {\r
+                       preReceiveScripts.addAll(repositoryModel.preReceiveScripts);\r
+               }\r
+               final Palette<String> preReceivePalette = new Palette<String>("preReceiveScripts",\r
+                               new ListModel<String>(preReceiveScripts), new CollectionModel<String>(GitBlit\r
+                                               .self().getAvailableScripts()), new ChoiceRenderer<String>("", ""), 12, true);\r
+\r
+               // post-receive palette\r
+               if (repositoryModel.postReceiveScripts != null) {\r
+                       postReceiveScripts.addAll(repositoryModel.postReceiveScripts);\r
+               }\r
+               final Palette<String> postReceivePalette = new Palette<String>("postReceiveScripts",\r
+                               new ListModel<String>(postReceiveScripts), new CollectionModel<String>(GitBlit\r
+                                               .self().getAvailableScripts()), new ChoiceRenderer<String>("", ""), 12, true);\r
 \r
                CompoundPropertyModel<RepositoryModel> model = new CompoundPropertyModel<RepositoryModel>(\r
                                repositoryModel);\r
@@ -179,6 +198,22 @@ public class EditRepositoryPage extends RootSubPage {
                                                repositoryModel.mailRecipients = list;\r
                                        }\r
 \r
+                                       // pre-receive scripts\r
+                                       List<String> preReceiveScripts = new ArrayList<String>();\r
+                                       Iterator<String> pres = preReceivePalette.getSelectedChoices();\r
+                                       while (pres.hasNext()) {\r
+                                               preReceiveScripts.add(pres.next());\r
+                                       }\r
+                                       repositoryModel.preReceiveScripts = preReceiveScripts;\r
+\r
+                                       // post-receive scripts\r
+                                       List<String> postReceiveScripts = new ArrayList<String>();\r
+                                       Iterator<String> post = postReceivePalette.getSelectedChoices();\r
+                                       while (post.hasNext()) {\r
+                                               postReceiveScripts.add(post.next());\r
+                                       }\r
+                                       repositoryModel.postReceiveScripts = postReceiveScripts;\r
+\r
                                        // save the repository\r
                                        GitBlit.self().updateRepositoryModel(oldName, repositoryModel, isCreate);\r
 \r
@@ -246,6 +281,8 @@ public class EditRepositoryPage extends RootSubPage {
                form.add(usersPalette);\r
                form.add(teamsPalette);\r
                form.add(federationSetsPalette);\r
+               form.add(preReceivePalette);\r
+               form.add(postReceivePalette);\r
 \r
                form.add(new Button("save"));\r
                Button cancel = new Button("cancel") {\r
index 72a5db4125b29f354a85d0255d1948c0318494ed..5fcc7382ba7f673df130d017a5dadc0442e7a32d 100644 (file)
@@ -6,8 +6,8 @@ git.repositoriesFolder = git
 git.searchRepositoriesSubfolders = true
 git.enableGitServlet = true
 groovy.scriptsFolder = groovy
-groovy.preReceiveScripts = blockpush.groovy
-groovy.postReceiveScripts = sendemail.groovy jenkins.groovy
+groovy.preReceiveScripts = blockpush
+groovy.postReceiveScripts = sendemail jenkins
 web.authenticateViewPages = false
 web.authenticateAdminPages = true
 web.allowCookieAuthentication = true