You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

GitServlet.java 10KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311
  1. /*
  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. */
  16. package com.gitblit;
  17. import groovy.lang.Binding;
  18. import groovy.util.GroovyScriptEngine;
  19. import java.io.BufferedReader;
  20. import java.io.BufferedWriter;
  21. import java.io.File;
  22. import java.io.IOException;
  23. import java.io.InputStreamReader;
  24. import java.io.OutputStreamWriter;
  25. import java.text.MessageFormat;
  26. import java.util.Collection;
  27. import java.util.List;
  28. import javax.servlet.ServletConfig;
  29. import javax.servlet.ServletException;
  30. import javax.servlet.http.HttpServletRequest;
  31. import org.eclipse.jgit.http.server.resolver.DefaultReceivePackFactory;
  32. import org.eclipse.jgit.lib.PersonIdent;
  33. import org.eclipse.jgit.lib.Repository;
  34. import org.eclipse.jgit.transport.PostReceiveHook;
  35. import org.eclipse.jgit.transport.PreReceiveHook;
  36. import org.eclipse.jgit.transport.ReceiveCommand;
  37. import org.eclipse.jgit.transport.ReceiveCommand.Result;
  38. import org.eclipse.jgit.transport.ReceivePack;
  39. import org.eclipse.jgit.transport.resolver.ServiceNotAuthorizedException;
  40. import org.eclipse.jgit.transport.resolver.ServiceNotEnabledException;
  41. import org.slf4j.Logger;
  42. import org.slf4j.LoggerFactory;
  43. import com.gitblit.models.RepositoryModel;
  44. import com.gitblit.models.UserModel;
  45. import com.gitblit.utils.HttpUtils;
  46. import com.gitblit.utils.StringUtils;
  47. /**
  48. * The GitServlet exists to force configuration of the JGit GitServlet based on
  49. * the Gitblit settings from either gitblit.properties or from context
  50. * parameters in the web.xml file.
  51. *
  52. * It also implements and registers the Groovy hook mechanism.
  53. *
  54. * Access to this servlet is protected by the GitFilter.
  55. *
  56. * @author James Moger
  57. *
  58. */
  59. public class GitServlet extends org.eclipse.jgit.http.server.GitServlet {
  60. private static final long serialVersionUID = 1L;
  61. private GroovyScriptEngine gse;
  62. /**
  63. * Configure the servlet from Gitblit's configuration.
  64. */
  65. @Override
  66. public String getInitParameter(String name) {
  67. if (name.equals("base-path")) {
  68. return GitBlit.getRepositoriesFolder().getAbsolutePath();
  69. } else if (name.equals("export-all")) {
  70. return "1";
  71. }
  72. return super.getInitParameter(name);
  73. }
  74. @Override
  75. public void init(ServletConfig config) throws ServletException {
  76. String groovyRoot = GitBlit.getString(Keys.groovy.scriptsFolder, "groovy");
  77. try {
  78. gse = new GroovyScriptEngine(groovyRoot);
  79. } catch (IOException e) {
  80. throw new ServletException("Failed to instantiate Groovy Script Engine!", e);
  81. }
  82. // set the Gitblit receive hook
  83. setReceivePackFactory(new DefaultReceivePackFactory() {
  84. @Override
  85. public ReceivePack create(HttpServletRequest req, Repository db)
  86. throws ServiceNotEnabledException, ServiceNotAuthorizedException {
  87. ReceivePack rp = super.create(req, db);
  88. GitblitReceiveHook hook = new GitblitReceiveHook();
  89. hook.gitblitUrl = HttpUtils.getGitblitURL(req);
  90. rp.setPreReceiveHook(hook);
  91. rp.setPostReceiveHook(hook);
  92. return rp;
  93. }
  94. });
  95. super.init(config);
  96. }
  97. /**
  98. * The Gitblit receive hook allows for special processing on push events.
  99. * That might include rejecting writes to specific branches or executing a
  100. * script.
  101. *
  102. * @author James Moger
  103. *
  104. */
  105. private class GitblitReceiveHook implements PreReceiveHook, PostReceiveHook {
  106. protected final Logger logger = LoggerFactory.getLogger(GitblitReceiveHook.class);
  107. protected String gitblitUrl;
  108. /**
  109. * Instrumentation point where the incoming push event has been parsed,
  110. * validated, objects created BUT refs have not been updated. You might
  111. * use this to enforce a branch-write permissions model.
  112. */
  113. @Override
  114. public void onPreReceive(ReceivePack rp, Collection<ReceiveCommand> commands) {
  115. List<String> scripts = GitBlit.getStrings(Keys.groovy.preReceiveScripts);
  116. RepositoryModel repository = getRepositoryModel(rp);
  117. scripts.addAll(repository.preReceiveScripts);
  118. UserModel user = getUserModel(rp);
  119. runGroovy(repository, user, commands, scripts);
  120. for (ReceiveCommand cmd : commands) {
  121. if (!Result.NOT_ATTEMPTED.equals(cmd.getResult())) {
  122. logger.warn(MessageFormat.format("{0} {1} because \"{2}\"", cmd.getNewId()
  123. .getName(), cmd.getResult(), cmd.getMessage()));
  124. }
  125. }
  126. // Experimental
  127. // runNativeScript(rp, "hooks/pre-receive", commands);
  128. }
  129. /**
  130. * Instrumentation point where the incoming push has been applied to the
  131. * repository. This is the point where we would trigger a Jenkins build
  132. * or send an email.
  133. */
  134. @Override
  135. public void onPostReceive(ReceivePack rp, Collection<ReceiveCommand> commands) {
  136. if (commands.size() == 0) {
  137. logger.info("skipping post-receive hooks, no refs created, updated, or removed");
  138. return;
  139. }
  140. List<String> scripts = GitBlit.getStrings(Keys.groovy.postReceiveScripts);
  141. RepositoryModel repository = getRepositoryModel(rp);
  142. scripts.addAll(repository.postReceiveScripts);
  143. UserModel user = getUserModel(rp);
  144. runGroovy(repository, user, commands, scripts);
  145. // Experimental
  146. // runNativeScript(rp, "hooks/post-receive", commands);
  147. }
  148. /**
  149. * Returns the RepositoryModel for the repository we are pushing into.
  150. *
  151. * @param rp
  152. * @return a RepositoryModel
  153. */
  154. protected RepositoryModel getRepositoryModel(ReceivePack rp) {
  155. Repository repository = rp.getRepository();
  156. String rootPath = GitBlit.getRepositoriesFolder().getAbsolutePath();
  157. String repositoryName = repository.getDirectory().getAbsolutePath();
  158. repositoryName = repositoryName.substring(rootPath.length() + 1);
  159. RepositoryModel model = GitBlit.self().getRepositoryModel(repositoryName);
  160. return model;
  161. }
  162. /**
  163. * Returns the UserModel for the user pushing the changes.
  164. *
  165. * @param rp
  166. * @return a UserModel
  167. */
  168. protected UserModel getUserModel(ReceivePack rp) {
  169. PersonIdent person = rp.getRefLogIdent();
  170. UserModel user = GitBlit.self().getUserModel(person.getName());
  171. if (user == null) {
  172. // anonymous push, create a temporary usermodel
  173. user = new UserModel(person.getName());
  174. }
  175. return user;
  176. }
  177. /**
  178. * Runs the specified Groovy hook scripts.
  179. *
  180. * @param repository
  181. * @param user
  182. * @param commands
  183. * @param scripts
  184. */
  185. protected void runGroovy(RepositoryModel repository, UserModel user,
  186. Collection<ReceiveCommand> commands, List<String> scripts) {
  187. if (scripts == null || scripts.size() == 0) {
  188. // no Groovy scripts to execute
  189. return;
  190. }
  191. Binding binding = new Binding();
  192. binding.setVariable("gitblit", GitBlit.self());
  193. binding.setVariable("repository", repository);
  194. binding.setVariable("user", user);
  195. binding.setVariable("commands", commands);
  196. binding.setVariable("url", gitblitUrl);
  197. binding.setVariable("logger", logger);
  198. for (String script : scripts) {
  199. if (StringUtils.isEmpty(script)) {
  200. continue;
  201. }
  202. try {
  203. Object result = gse.run(script, binding);
  204. if (result instanceof Boolean) {
  205. if (!((Boolean) result)) {
  206. logger.error(MessageFormat.format(
  207. "Groovy script {0} has failed! Hook scripts aborted.", script));
  208. break;
  209. }
  210. }
  211. } catch (Exception e) {
  212. logger.error(
  213. MessageFormat.format("Failed to execute Groovy script {0}", script), e);
  214. }
  215. }
  216. }
  217. /**
  218. * Runs the native push hook script.
  219. *
  220. * http://book.git-scm.com/5_git_hooks.html
  221. * http://longair.net/blog/2011/04/09/missing-git-hooks-documentation/
  222. *
  223. * @param rp
  224. * @param script
  225. * @param commands
  226. */
  227. @SuppressWarnings("unused")
  228. protected void runNativeScript(ReceivePack rp, String script,
  229. Collection<ReceiveCommand> commands) {
  230. Repository repository = rp.getRepository();
  231. File scriptFile = new File(repository.getDirectory(), script);
  232. int resultCode = 0;
  233. if (scriptFile.exists()) {
  234. try {
  235. logger.debug("executing " + scriptFile);
  236. Process process = Runtime.getRuntime().exec(scriptFile.getAbsolutePath(), null,
  237. repository.getDirectory());
  238. BufferedReader reader = new BufferedReader(new InputStreamReader(
  239. process.getInputStream()));
  240. BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(
  241. process.getOutputStream()));
  242. for (ReceiveCommand command : commands) {
  243. switch (command.getType()) {
  244. case UPDATE:
  245. // updating a ref
  246. writer.append(MessageFormat.format("{0} {1} {2}\n", command.getOldId()
  247. .getName(), command.getNewId().getName(), command.getRefName()));
  248. break;
  249. case CREATE:
  250. // new ref
  251. // oldrev hard-coded to 40? weird.
  252. writer.append(MessageFormat.format("40 {0} {1}\n", command.getNewId()
  253. .getName(), command.getRefName()));
  254. break;
  255. }
  256. }
  257. resultCode = process.waitFor();
  258. // read and buffer stdin
  259. // this is supposed to be piped back to the git client.
  260. // not sure how to do that right now.
  261. StringBuilder sb = new StringBuilder();
  262. String line = null;
  263. while ((line = reader.readLine()) != null) {
  264. sb.append(line).append('\n');
  265. }
  266. logger.debug(sb.toString());
  267. } catch (Throwable e) {
  268. resultCode = -1;
  269. logger.error(
  270. MessageFormat.format("Failed to execute {0}",
  271. scriptFile.getAbsolutePath()), e);
  272. }
  273. }
  274. // reject push
  275. if (resultCode != 0) {
  276. for (ReceiveCommand command : commands) {
  277. command.setResult(Result.REJECTED_OTHER_REASON, MessageFormat.format(
  278. "Native script {0} rejected push or failed",
  279. scriptFile.getAbsolutePath()));
  280. }
  281. }
  282. }
  283. }
  284. }