lemval
2012-01-31 1c30dad2115fc513791d8a5b292ad0f7d7b85749
commit | author | age
892570 1 /*
JM 2  * Copyright 2011 gitblit.com.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
896c53 16 package com.gitblit;
JM 17
fa54be 18 import groovy.lang.Binding;
JM 19 import groovy.util.GroovyScriptEngine;
20
21 import java.io.BufferedReader;
22 import java.io.BufferedWriter;
23 import java.io.File;
24 import java.io.IOException;
25 import java.io.InputStreamReader;
26 import java.io.OutputStreamWriter;
27 import java.text.MessageFormat;
28 import java.util.Collection;
367505 29 import java.util.Enumeration;
6cc1d4 30 import java.util.LinkedHashSet;
JM 31 import java.util.Set;
fa54be 32
JM 33 import javax.servlet.ServletConfig;
367505 34 import javax.servlet.ServletContext;
fa54be 35 import javax.servlet.ServletException;
JM 36 import javax.servlet.http.HttpServletRequest;
37
38 import org.eclipse.jgit.http.server.resolver.DefaultReceivePackFactory;
39 import org.eclipse.jgit.lib.PersonIdent;
40 import org.eclipse.jgit.lib.Repository;
41 import org.eclipse.jgit.transport.PostReceiveHook;
42 import org.eclipse.jgit.transport.PreReceiveHook;
43 import org.eclipse.jgit.transport.ReceiveCommand;
44 import org.eclipse.jgit.transport.ReceiveCommand.Result;
45 import org.eclipse.jgit.transport.ReceivePack;
46 import org.eclipse.jgit.transport.resolver.ServiceNotAuthorizedException;
47 import org.eclipse.jgit.transport.resolver.ServiceNotEnabledException;
48 import org.slf4j.Logger;
49 import org.slf4j.LoggerFactory;
50
51 import com.gitblit.models.RepositoryModel;
52 import com.gitblit.models.UserModel;
53 import com.gitblit.utils.HttpUtils;
54 import com.gitblit.utils.StringUtils;
55
892570 56 /**
JM 57  * The GitServlet exists to force configuration of the JGit GitServlet based on
58  * the Gitblit settings from either gitblit.properties or from context
59  * parameters in the web.xml file.
fa54be 60  * 
JM 61  * It also implements and registers the Groovy hook mechanism.
892570 62  * 
JM 63  * Access to this servlet is protected by the GitFilter.
64  * 
65  * @author James Moger
66  * 
67  */
896c53 68 public class GitServlet extends org.eclipse.jgit.http.server.GitServlet {
JM 69
70     private static final long serialVersionUID = 1L;
fa54be 71
JM 72     private GroovyScriptEngine gse;
896c53 73
6cc1d4 74     private File groovyDir;
JM 75
fa54be 76     @Override
JM 77     public void init(ServletConfig config) throws ServletException {
367505 78         groovyDir = GitBlit.getGroovyScriptsFolder();
fa54be 79         try {
6cc1d4 80             gse = new GroovyScriptEngine(groovyDir.getAbsolutePath());
fa54be 81         } catch (IOException e) {
JM 82             throw new ServletException("Failed to instantiate Groovy Script Engine!", e);
83         }
84
85         // set the Gitblit receive hook
86         setReceivePackFactory(new DefaultReceivePackFactory() {
87             @Override
88             public ReceivePack create(HttpServletRequest req, Repository db)
89                     throws ServiceNotEnabledException, ServiceNotAuthorizedException {
90                 ReceivePack rp = super.create(req, db);
91                 GitblitReceiveHook hook = new GitblitReceiveHook();
92                 hook.gitblitUrl = HttpUtils.getGitblitURL(req);
93                 rp.setPreReceiveHook(hook);
94                 rp.setPostReceiveHook(hook);
95                 return rp;
96             }
97         });
367505 98         super.init(new GitblitServletConfig(config));
JM 99     }
100
101     /**
d7905a 102      * Transitional wrapper class to configure the JGit 1.2 GitFilter. This
JM 103      * GitServlet will probably be replaced by a GitFilter so that Gitblit can
104      * serve Git repositories on the root URL and not a /git sub-url.
367505 105      * 
JM 106      * @author James Moger
107      * 
108      */
109     private class GitblitServletConfig implements ServletConfig {
110         final ServletConfig config;
111
112         GitblitServletConfig(ServletConfig config) {
113             this.config = config;
114         }
115
116         @Override
117         public String getServletName() {
118             return config.getServletName();
119         }
120
121         @Override
122         public ServletContext getServletContext() {
123             return config.getServletContext();
124         }
125
126         @Override
127         public String getInitParameter(String name) {
128             if (name.equals("base-path")) {
129                 return GitBlit.getRepositoriesFolder().getAbsolutePath();
130             } else if (name.equals("export-all")) {
131                 return "1";
132             }
133             return config.getInitParameter(name);
134         }
135
136         @Override
137         public Enumeration<String> getInitParameterNames() {
138             return config.getInitParameterNames();
139         }
fa54be 140     }
JM 141
142     /**
143      * The Gitblit receive hook allows for special processing on push events.
144      * That might include rejecting writes to specific branches or executing a
145      * script.
146      * 
147      * @author James Moger
148      * 
149      */
150     private class GitblitReceiveHook implements PreReceiveHook, PostReceiveHook {
151
152         protected final Logger logger = LoggerFactory.getLogger(GitblitReceiveHook.class);
153
154         protected String gitblitUrl;
155
156         /**
157          * Instrumentation point where the incoming push event has been parsed,
158          * validated, objects created BUT refs have not been updated. You might
159          * use this to enforce a branch-write permissions model.
160          */
161         @Override
162         public void onPreReceive(ReceivePack rp, Collection<ReceiveCommand> commands) {
163             RepositoryModel repository = getRepositoryModel(rp);
d7905a 164             Set<String> scripts = new LinkedHashSet<String>();
JM 165             scripts.addAll(GitBlit.self().getPreReceiveScriptsInherited(repository));
fa54be 166             scripts.addAll(repository.preReceiveScripts);
JM 167             UserModel user = getUserModel(rp);
168             runGroovy(repository, user, commands, scripts);
169             for (ReceiveCommand cmd : commands) {
170                 if (!Result.NOT_ATTEMPTED.equals(cmd.getResult())) {
171                     logger.warn(MessageFormat.format("{0} {1} because \"{2}\"", cmd.getNewId()
172                             .getName(), cmd.getResult(), cmd.getMessage()));
173                 }
174             }
175
176             // Experimental
177             // runNativeScript(rp, "hooks/pre-receive", commands);
178         }
179
180         /**
181          * Instrumentation point where the incoming push has been applied to the
182          * repository. This is the point where we would trigger a Jenkins build
183          * or send an email.
184          */
185         @Override
186         public void onPostReceive(ReceivePack rp, Collection<ReceiveCommand> commands) {
187             if (commands.size() == 0) {
188                 logger.info("skipping post-receive hooks, no refs created, updated, or removed");
189                 return;
190             }
191             RepositoryModel repository = getRepositoryModel(rp);
d7905a 192             Set<String> scripts = new LinkedHashSet<String>();
JM 193             scripts.addAll(GitBlit.self().getPostReceiveScriptsInherited(repository));
fa54be 194             scripts.addAll(repository.postReceiveScripts);
JM 195             UserModel user = getUserModel(rp);
196             runGroovy(repository, user, commands, scripts);
197
198             // Experimental
199             // runNativeScript(rp, "hooks/post-receive", commands);
200         }
201
202         /**
203          * Returns the RepositoryModel for the repository we are pushing into.
204          * 
205          * @param rp
206          * @return a RepositoryModel
207          */
208         protected RepositoryModel getRepositoryModel(ReceivePack rp) {
209             Repository repository = rp.getRepository();
210             String rootPath = GitBlit.getRepositoriesFolder().getAbsolutePath();
cb285c 211             String repositoryName = StringUtils.getRelativePath(rootPath, repository.getDirectory()
JM 212                     .getAbsolutePath());
fa54be 213             RepositoryModel model = GitBlit.self().getRepositoryModel(repositoryName);
JM 214             return model;
215         }
216
217         /**
218          * Returns the UserModel for the user pushing the changes.
219          * 
220          * @param rp
221          * @return a UserModel
222          */
223         protected UserModel getUserModel(ReceivePack rp) {
224             PersonIdent person = rp.getRefLogIdent();
225             UserModel user = GitBlit.self().getUserModel(person.getName());
226             if (user == null) {
227                 // anonymous push, create a temporary usermodel
228                 user = new UserModel(person.getName());
229             }
230             return user;
231         }
232
233         /**
234          * Runs the specified Groovy hook scripts.
235          * 
236          * @param repository
237          * @param user
238          * @param commands
239          * @param scripts
240          */
241         protected void runGroovy(RepositoryModel repository, UserModel user,
6cc1d4 242                 Collection<ReceiveCommand> commands, Set<String> scripts) {
fa54be 243             if (scripts == null || scripts.size() == 0) {
JM 244                 // no Groovy scripts to execute
245                 return;
246             }
247
248             Binding binding = new Binding();
249             binding.setVariable("gitblit", GitBlit.self());
250             binding.setVariable("repository", repository);
251             binding.setVariable("user", user);
252             binding.setVariable("commands", commands);
253             binding.setVariable("url", gitblitUrl);
254             binding.setVariable("logger", logger);
255             for (String script : scripts) {
256                 if (StringUtils.isEmpty(script)) {
257                     continue;
258                 }
6cc1d4 259                 // allow script to be specified without .groovy extension
JM 260                 // this is easier to read in the settings
261                 File file = new File(groovyDir, script);
262                 if (!file.exists() && !script.toLowerCase().endsWith(".groovy")) {
263                     file = new File(groovyDir, script + ".groovy");
264                     if (file.exists()) {
265                         script = file.getName();
266                     }
267                 }
fa54be 268                 try {
JM 269                     Object result = gse.run(script, binding);
270                     if (result instanceof Boolean) {
271                         if (!((Boolean) result)) {
272                             logger.error(MessageFormat.format(
273                                     "Groovy script {0} has failed!  Hook scripts aborted.", script));
274                             break;
275                         }
276                     }
277                 } catch (Exception e) {
278                     logger.error(
279                             MessageFormat.format("Failed to execute Groovy script {0}", script), e);
280                 }
281             }
282         }
283
284         /**
285          * Runs the native push hook script.
286          * 
287          * http://book.git-scm.com/5_git_hooks.html
288          * http://longair.net/blog/2011/04/09/missing-git-hooks-documentation/
289          * 
290          * @param rp
291          * @param script
292          * @param commands
293          */
294         @SuppressWarnings("unused")
295         protected void runNativeScript(ReceivePack rp, String script,
296                 Collection<ReceiveCommand> commands) {
297
298             Repository repository = rp.getRepository();
299             File scriptFile = new File(repository.getDirectory(), script);
300
301             int resultCode = 0;
302             if (scriptFile.exists()) {
303                 try {
304                     logger.debug("executing " + scriptFile);
305                     Process process = Runtime.getRuntime().exec(scriptFile.getAbsolutePath(), null,
306                             repository.getDirectory());
307                     BufferedReader reader = new BufferedReader(new InputStreamReader(
308                             process.getInputStream()));
309                     BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(
310                             process.getOutputStream()));
311                     for (ReceiveCommand command : commands) {
312                         switch (command.getType()) {
313                         case UPDATE:
314                             // updating a ref
315                             writer.append(MessageFormat.format("{0} {1} {2}\n", command.getOldId()
316                                     .getName(), command.getNewId().getName(), command.getRefName()));
317                             break;
318                         case CREATE:
319                             // new ref
320                             // oldrev hard-coded to 40? weird.
321                             writer.append(MessageFormat.format("40 {0} {1}\n", command.getNewId()
322                                     .getName(), command.getRefName()));
323                             break;
324                         }
325                     }
326                     resultCode = process.waitFor();
327
328                     // read and buffer stdin
329                     // this is supposed to be piped back to the git client.
330                     // not sure how to do that right now.
331                     StringBuilder sb = new StringBuilder();
332                     String line = null;
333                     while ((line = reader.readLine()) != null) {
334                         sb.append(line).append('\n');
335                     }
336                     logger.debug(sb.toString());
337                 } catch (Throwable e) {
338                     resultCode = -1;
339                     logger.error(
340                             MessageFormat.format("Failed to execute {0}",
341                                     scriptFile.getAbsolutePath()), e);
342                 }
343             }
344
345             // reject push
346             if (resultCode != 0) {
347                 for (ReceiveCommand command : commands) {
348                     command.setResult(Result.REJECTED_OTHER_REASON, MessageFormat.format(
349                             "Native script {0} rejected push or failed",
350                             scriptFile.getAbsolutePath()));
351                 }
352             }
353         }
354     }
896c53 355 }