# 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
# 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
# *repository* variable.\r
#\r
# SPACE-DELIMITED\r
+# CASE-SENSITIVE\r
# SINCE 0.8.0\r
groovy.preReceiveScripts =\r
\r
# 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
# *repository* variable.\r
# \r
# SPACE-DELIMITED\r
+# CASE-SENSITIVE\r
# SINCE 0.8.0\r
groovy.postReceiveScripts =\r
\r
\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
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
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
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
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
setting.currentValue = settings.getString(key, "");\r
}\r
}\r
+ settingsModel.pushScripts = getAvailableScripts();\r
return settingsModel;\r
}\r
\r
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
\r
private GroovyScriptEngine gse;\r
\r
+ private File groovyDir;\r
+\r
/**\r
* Configure the servlet from Gitblit's configuration.\r
*/\r
\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
*/\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
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
* @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
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
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
<!-- 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" /> <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" /> <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" /> <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" /> <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" /> <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
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
// 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
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
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
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