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.

GitblitReceivePack.java 30KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928
  1. /*
  2. * Copyright 2013 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.git;
  17. import static org.eclipse.jgit.transport.BasePackPushConnection.CAPABILITY_SIDE_BAND_64K;
  18. import java.io.File;
  19. import java.io.IOException;
  20. import java.text.MessageFormat;
  21. import java.util.Collection;
  22. import java.util.LinkedHashMap;
  23. import java.util.LinkedHashSet;
  24. import java.util.List;
  25. import java.util.Map;
  26. import java.util.Set;
  27. import java.util.SortedMap;
  28. import java.util.TreeMap;
  29. import java.util.concurrent.TimeUnit;
  30. import org.codehaus.groovy.runtime.InvokerHelper;
  31. import org.eclipse.jgit.lib.BatchRefUpdate;
  32. import org.eclipse.jgit.lib.NullProgressMonitor;
  33. import org.eclipse.jgit.lib.ObjectId;
  34. import org.eclipse.jgit.lib.ObjectLoader;
  35. import org.eclipse.jgit.lib.PersonIdent;
  36. import org.eclipse.jgit.lib.ProgressMonitor;
  37. import org.eclipse.jgit.lib.Ref;
  38. import org.eclipse.jgit.lib.Repository;
  39. import org.eclipse.jgit.revwalk.RevCommit;
  40. import org.eclipse.jgit.revwalk.RevTree;
  41. import org.eclipse.jgit.revwalk.RevWalk;
  42. import org.eclipse.jgit.transport.PostReceiveHook;
  43. import org.eclipse.jgit.transport.PreReceiveHook;
  44. import org.eclipse.jgit.transport.ReceiveCommand;
  45. import org.eclipse.jgit.transport.ReceiveCommand.Result;
  46. import org.eclipse.jgit.transport.ReceivePack;
  47. import org.eclipse.jgit.treewalk.TreeWalk;
  48. import org.slf4j.Logger;
  49. import org.slf4j.LoggerFactory;
  50. import com.gitblit.Constants;
  51. import com.gitblit.Constants.AccessRestrictionType;
  52. import com.gitblit.IStoredSettings;
  53. import com.gitblit.Keys;
  54. import com.gitblit.client.Translation;
  55. import com.gitblit.extensions.ReceiveHook;
  56. import com.gitblit.manager.IGitblit;
  57. import com.gitblit.models.RepositoryModel;
  58. import com.gitblit.models.TicketModel;
  59. import com.gitblit.models.TicketModel.Change;
  60. import com.gitblit.models.TicketModel.Field;
  61. import com.gitblit.models.TicketModel.Status;
  62. import com.gitblit.models.TicketModel.TicketLink;
  63. import com.gitblit.models.UserModel;
  64. import com.gitblit.tickets.BranchTicketService;
  65. import com.gitblit.tickets.ITicketService;
  66. import com.gitblit.tickets.TicketNotifier;
  67. import com.gitblit.utils.ArrayUtils;
  68. import com.gitblit.utils.ClientLogger;
  69. import com.gitblit.utils.CommitCache;
  70. import com.gitblit.utils.JGitUtils;
  71. import com.gitblit.utils.RefLogUtils;
  72. import com.gitblit.utils.StringUtils;
  73. import groovy.lang.Binding;
  74. import groovy.util.GroovyScriptEngine;
  75. /**
  76. * GitblitReceivePack processes receive commands. It also executes Groovy pre-
  77. * and post- receive hooks.
  78. *
  79. * The general execution flow is:
  80. * <ol>
  81. * <li>onPreReceive()</li>
  82. * <li>executeCommands()</li>
  83. * <li>onPostReceive()</li>
  84. * </ol>
  85. *
  86. * @author Android Open Source Project
  87. * @author James Moger
  88. *
  89. */
  90. public class GitblitReceivePack extends ReceivePack implements PreReceiveHook, PostReceiveHook {
  91. private static final Logger LOGGER = LoggerFactory.getLogger(GitblitReceivePack.class);
  92. protected final RepositoryModel repository;
  93. protected final UserModel user;
  94. protected final File groovyDir;
  95. protected String gitblitUrl;
  96. protected GroovyScriptEngine gse;
  97. protected final IStoredSettings settings;
  98. protected final IGitblit gitblit;
  99. protected final ITicketService ticketService;
  100. protected final TicketNotifier ticketNotifier;
  101. public GitblitReceivePack(
  102. IGitblit gitblit,
  103. Repository db,
  104. RepositoryModel repository,
  105. UserModel user) {
  106. super(db);
  107. this.settings = gitblit.getSettings();
  108. this.gitblit = gitblit;
  109. this.repository = repository;
  110. this.user = user;
  111. this.groovyDir = gitblit.getHooksFolder();
  112. try {
  113. // set Grape root
  114. File grapeRoot = gitblit.getGrapesFolder();
  115. grapeRoot.mkdirs();
  116. System.setProperty("grape.root", grapeRoot.getAbsolutePath());
  117. this.gse = new GroovyScriptEngine(groovyDir.getAbsolutePath());
  118. } catch (IOException e) {
  119. }
  120. if (gitblit.getTicketService().isAcceptingTicketUpdates(repository)) {
  121. this.ticketService = gitblit.getTicketService();
  122. this.ticketNotifier = this.ticketService.createNotifier();
  123. } else {
  124. this.ticketService = null;
  125. this.ticketNotifier = null;
  126. }
  127. // set advanced ref permissions
  128. setAllowCreates(user.canCreateRef(repository));
  129. setAllowDeletes(user.canDeleteRef(repository));
  130. setAllowNonFastForwards(user.canRewindRef(repository));
  131. int maxObjectSz = settings.getInteger(Keys.git.maxObjectSizeLimit, -1);
  132. if (maxObjectSz >= 0) {
  133. setMaxObjectSizeLimit(maxObjectSz);
  134. }
  135. int maxPackSz = settings.getInteger(Keys.git.maxPackSizeLimit, -1);
  136. if (maxPackSz >= 0) {
  137. setMaxPackSizeLimit(maxPackSz);
  138. }
  139. setCheckReceivedObjects(settings.getBoolean(Keys.git.checkReceivedObjects, true));
  140. setCheckReferencedObjectsAreReachable(settings.getBoolean(Keys.git.checkReferencedObjectsAreReachable, true));
  141. // setup pre and post receive hook
  142. setPreReceiveHook(this);
  143. setPostReceiveHook(this);
  144. }
  145. /**
  146. * Returns true if the user is permitted to apply the receive commands to
  147. * the repository.
  148. *
  149. * @param commands
  150. * @return true if the user may push these commands
  151. */
  152. protected boolean canPush(Collection<ReceiveCommand> commands) {
  153. // TODO Consider supporting branch permissions here (issue-36)
  154. // Not sure if that should be Gerrit-style, refs/meta/config, or
  155. // gitolite-style, permissions in users.conf
  156. //
  157. // How could commands be empty?
  158. //
  159. // Because a subclass, like PatchsetReceivePack, filters receive
  160. // commands before this method is called. This makes it possible for
  161. // this method to test an empty list. In this case, we assume that the
  162. // subclass receive pack properly enforces push restrictions. for the
  163. // ref.
  164. //
  165. // The empty test is not explicitly required, it's written here to
  166. // clarify special-case behavior.
  167. return commands.isEmpty() ? true : user.canPush(repository);
  168. }
  169. /**
  170. * Instrumentation point where the incoming push event has been parsed,
  171. * validated, objects created BUT refs have not been updated. You might
  172. * use this to enforce a branch-write permissions model.
  173. */
  174. @Override
  175. public void onPreReceive(ReceivePack rp, Collection<ReceiveCommand> commands) {
  176. if (commands.size() == 0) {
  177. // no receive commands to process
  178. // this can happen if receive pack subclasses intercept and filter
  179. // the commands
  180. LOGGER.debug("skipping pre-receive processing, no refs created, updated, or removed");
  181. return;
  182. }
  183. if (repository.isMirror) {
  184. // repository is a mirror
  185. for (ReceiveCommand cmd : commands) {
  186. sendRejection(cmd, "Gitblit does not allow pushes to \"{0}\" because it is a mirror!", repository.name);
  187. }
  188. return;
  189. }
  190. if (repository.isFrozen) {
  191. // repository is frozen/readonly
  192. for (ReceiveCommand cmd : commands) {
  193. sendRejection(cmd, "Gitblit does not allow pushes to \"{0}\" because it is frozen!", repository.name);
  194. }
  195. return;
  196. }
  197. if (!repository.isBare) {
  198. // repository has a working copy
  199. for (ReceiveCommand cmd : commands) {
  200. sendRejection(cmd, "Gitblit does not allow pushes to \"{0}\" because it has a working copy!", repository.name);
  201. }
  202. return;
  203. }
  204. if (!canPush(commands)) {
  205. // user does not have push permissions
  206. for (ReceiveCommand cmd : commands) {
  207. sendRejection(cmd, "User \"{0}\" does not have push permissions for \"{1}\"!", user.username, repository.name);
  208. }
  209. return;
  210. }
  211. if (repository.accessRestriction.atLeast(AccessRestrictionType.PUSH) && repository.verifyCommitter) {
  212. // enforce committer verification
  213. if (StringUtils.isEmpty(user.emailAddress)) {
  214. // reject the push because the pushing account does not have an email address
  215. for (ReceiveCommand cmd : commands) {
  216. sendRejection(cmd, "Sorry, the account \"{0}\" does not have an email address set for committer verification!", user.username);
  217. }
  218. return;
  219. }
  220. // Optionally enforce that the committer of first parent chain
  221. // match the account being used to push the commits.
  222. //
  223. // This requires all merge commits are executed with the "--no-ff"
  224. // option to force a merge commit even if fast-forward is possible.
  225. // This ensures that the chain first parents has the commit
  226. // identity of the merging user.
  227. boolean allRejected = false;
  228. for (ReceiveCommand cmd : commands) {
  229. String firstParent = null;
  230. try {
  231. List<RevCommit> commits = JGitUtils.getRevLog(rp.getRepository(), cmd.getOldId().name(), cmd.getNewId().name());
  232. for (RevCommit commit : commits) {
  233. if (firstParent != null) {
  234. if (!commit.getName().equals(firstParent)) {
  235. // ignore: commit is right-descendant of a merge
  236. continue;
  237. }
  238. }
  239. // update expected next commit id
  240. if (commit.getParentCount() == 0) {
  241. firstParent = null;
  242. } else {
  243. firstParent = commit.getParents()[0].getId().getName();
  244. }
  245. PersonIdent committer = commit.getCommitterIdent();
  246. if (!user.is(committer.getName(), committer.getEmailAddress())) {
  247. // verification failed
  248. String reason = MessageFormat.format("{0} by {1} <{2}> was not committed by {3} ({4}) <{5}>",
  249. commit.getId().name(), committer.getName(), StringUtils.isEmpty(committer.getEmailAddress()) ? "?":committer.getEmailAddress(), user.getDisplayName(), user.username, user.emailAddress);
  250. LOGGER.warn(reason);
  251. cmd.setResult(Result.REJECTED_OTHER_REASON, reason);
  252. allRejected &= true;
  253. break;
  254. } else {
  255. allRejected = false;
  256. }
  257. }
  258. } catch (Exception e) {
  259. LOGGER.error("Failed to verify commits were made by pushing user", e);
  260. }
  261. }
  262. if (allRejected) {
  263. // all ref updates rejected, abort
  264. return;
  265. }
  266. }
  267. for (ReceiveCommand cmd : commands) {
  268. String ref = cmd.getRefName();
  269. if (ref.startsWith(Constants.R_HEADS)) {
  270. switch (cmd.getType()) {
  271. case UPDATE_NONFASTFORWARD:
  272. case DELETE:
  273. // reset branch commit cache on REWIND and DELETE
  274. CommitCache.instance().clear(repository.name, ref);
  275. break;
  276. default:
  277. break;
  278. }
  279. } else if (ref.equals(BranchTicketService.BRANCH)) {
  280. // ensure pushing user is an administrator OR an owner
  281. // i.e. prevent ticket tampering
  282. boolean permitted = user.canAdmin() || repository.isOwner(user.username);
  283. if (!permitted) {
  284. sendRejection(cmd, "{0} is not permitted to push to {1}", user.username, ref);
  285. }
  286. } else if (ref.startsWith(Constants.R_FOR)) {
  287. // prevent accidental push to refs/for
  288. sendRejection(cmd, "{0} is not configured to receive patchsets", repository.name);
  289. }
  290. }
  291. // call pre-receive plugins
  292. for (ReceiveHook hook : gitblit.getExtensions(ReceiveHook.class)) {
  293. try {
  294. hook.onPreReceive(this, commands);
  295. } catch (Exception e) {
  296. LOGGER.error("Failed to execute extension", e);
  297. }
  298. }
  299. Set<String> scripts = new LinkedHashSet<String>();
  300. scripts.addAll(gitblit.getPreReceiveScriptsInherited(repository));
  301. if (!ArrayUtils.isEmpty(repository.preReceiveScripts)) {
  302. scripts.addAll(repository.preReceiveScripts);
  303. }
  304. runGroovy(commands, scripts);
  305. runGitblitfile(commands, scripts, "PreReceive");
  306. for (ReceiveCommand cmd : commands) {
  307. if (!Result.NOT_ATTEMPTED.equals(cmd.getResult())) {
  308. LOGGER.warn(MessageFormat.format("{0} {1} because \"{2}\"", cmd.getNewId()
  309. .getName(), cmd.getResult(), cmd.getMessage()));
  310. }
  311. }
  312. }
  313. /**
  314. * Instrumentation point where the incoming push has been applied to the
  315. * repository. This is the point where we would trigger a Jenkins build
  316. * or send an email.
  317. */
  318. @Override
  319. public void onPostReceive(ReceivePack rp, Collection<ReceiveCommand> commands) {
  320. if (commands.size() == 0) {
  321. LOGGER.debug("skipping post-receive processing, no refs created, updated, or removed");
  322. return;
  323. }
  324. logRefChange(commands);
  325. updateIncrementalPushTags(commands);
  326. updateGitblitRefLog(commands);
  327. // check for updates pushed to the BranchTicketService branch
  328. // if the BranchTicketService is active it will reindex, as appropriate
  329. for (ReceiveCommand cmd : commands) {
  330. if (Result.OK.equals(cmd.getResult())
  331. && BranchTicketService.BRANCH.equals(cmd.getRefName())) {
  332. rp.getRepository().fireEvent(new ReceiveCommandEvent(repository, cmd));
  333. }
  334. }
  335. // call post-receive plugins
  336. for (ReceiveHook hook : gitblit.getExtensions(ReceiveHook.class)) {
  337. try {
  338. hook.onPostReceive(this, commands);
  339. } catch (Exception e) {
  340. LOGGER.error("Failed to execute extension", e);
  341. }
  342. }
  343. // run Groovy hook scripts
  344. Set<String> scripts = new LinkedHashSet<String>();
  345. scripts.addAll(gitblit.getPostReceiveScriptsInherited(repository));
  346. if (!ArrayUtils.isEmpty(repository.postReceiveScripts)) {
  347. scripts.addAll(repository.postReceiveScripts);
  348. }
  349. runGroovy(commands, scripts);
  350. runGitblitfile(commands, scripts, "PostReceive");
  351. }
  352. /**
  353. * Log the ref changes in the container log.
  354. *
  355. * @param commands
  356. */
  357. protected void logRefChange(Collection<ReceiveCommand> commands) {
  358. boolean isRefCreationOrDeletion = false;
  359. // log ref changes
  360. for (ReceiveCommand cmd : commands) {
  361. if (Result.OK.equals(cmd.getResult())) {
  362. // add some logging for important ref changes
  363. switch (cmd.getType()) {
  364. case DELETE:
  365. LOGGER.info(MessageFormat.format("{0} DELETED {1} in {2} ({3})", user.username, cmd.getRefName(), repository.name, cmd.getOldId().name()));
  366. isRefCreationOrDeletion = true;
  367. break;
  368. case CREATE:
  369. LOGGER.info(MessageFormat.format("{0} CREATED {1} in {2}", user.username, cmd.getRefName(), repository.name));
  370. isRefCreationOrDeletion = true;
  371. break;
  372. case UPDATE:
  373. LOGGER.info(MessageFormat.format("{0} UPDATED {1} in {2} (from {3} to {4})", user.username, cmd.getRefName(), repository.name, cmd.getOldId().name(), cmd.getNewId().name()));
  374. break;
  375. case UPDATE_NONFASTFORWARD:
  376. LOGGER.info(MessageFormat.format("{0} UPDATED NON-FAST-FORWARD {1} in {2} (from {3} to {4})", user.username, cmd.getRefName(), repository.name, cmd.getOldId().name(), cmd.getNewId().name()));
  377. break;
  378. default:
  379. break;
  380. }
  381. }
  382. }
  383. if (isRefCreationOrDeletion) {
  384. gitblit.resetRepositoryCache(repository.name);
  385. }
  386. }
  387. /**
  388. * Optionally update the incremental push tags.
  389. *
  390. * @param commands
  391. */
  392. protected void updateIncrementalPushTags(Collection<ReceiveCommand> commands) {
  393. if (!repository.useIncrementalPushTags) {
  394. return;
  395. }
  396. // tag each pushed branch tip
  397. String emailAddress = user.emailAddress == null ? getRefLogIdent().getEmailAddress() : user.emailAddress;
  398. PersonIdent userIdent = new PersonIdent(user.getDisplayName(), emailAddress);
  399. for (ReceiveCommand cmd : commands) {
  400. if (!cmd.getRefName().startsWith(Constants.R_HEADS)) {
  401. // only tag branch ref changes
  402. continue;
  403. }
  404. if (!ReceiveCommand.Type.DELETE.equals(cmd.getType())
  405. && ReceiveCommand.Result.OK.equals(cmd.getResult())) {
  406. String objectId = cmd.getNewId().getName();
  407. String branch = cmd.getRefName().substring(Constants.R_HEADS.length());
  408. // get translation based on the server's locale setting
  409. String template = Translation.get("gb.incrementalPushTagMessage");
  410. String msg = MessageFormat.format(template, branch);
  411. String prefix;
  412. if (StringUtils.isEmpty(repository.incrementalPushTagPrefix)) {
  413. prefix = settings.getString(Keys.git.defaultIncrementalPushTagPrefix, "r");
  414. } else {
  415. prefix = repository.incrementalPushTagPrefix;
  416. }
  417. JGitUtils.createIncrementalRevisionTag(
  418. getRepository(),
  419. objectId,
  420. userIdent,
  421. prefix,
  422. "0",
  423. msg);
  424. }
  425. }
  426. }
  427. /**
  428. * Update Gitblit's internal reflog.
  429. *
  430. * @param commands
  431. */
  432. protected void updateGitblitRefLog(Collection<ReceiveCommand> commands) {
  433. try {
  434. RefLogUtils.updateRefLog(user, getRepository(), commands);
  435. LOGGER.debug(MessageFormat.format("{0} reflog updated", repository.name));
  436. } catch (Exception e) {
  437. LOGGER.error(MessageFormat.format("Failed to update {0} reflog", repository.name), e);
  438. }
  439. }
  440. /** Execute commands to update references. */
  441. @Override
  442. protected void executeCommands() {
  443. List<ReceiveCommand> toApply = filterCommands(Result.NOT_ATTEMPTED);
  444. if (toApply.isEmpty()) {
  445. return;
  446. }
  447. ProgressMonitor updating = NullProgressMonitor.INSTANCE;
  448. boolean sideBand = isCapabilityEnabled(CAPABILITY_SIDE_BAND_64K);
  449. if (sideBand) {
  450. SideBandProgressMonitor pm = new SideBandProgressMonitor(msgOut);
  451. pm.setDelayStart(250, TimeUnit.MILLISECONDS);
  452. updating = pm;
  453. }
  454. BatchRefUpdate batch = getRepository().getRefDatabase().newBatchUpdate();
  455. batch.setAllowNonFastForwards(isAllowNonFastForwards());
  456. batch.setRefLogIdent(getRefLogIdent());
  457. batch.setRefLogMessage("push", true);
  458. for (ReceiveCommand cmd : toApply) {
  459. if (Result.NOT_ATTEMPTED != cmd.getResult()) {
  460. // Already rejected by the core receive process.
  461. continue;
  462. }
  463. batch.addCommand(cmd);
  464. }
  465. if (!batch.getCommands().isEmpty()) {
  466. try {
  467. batch.execute(getRevWalk(), updating);
  468. } catch (IOException err) {
  469. for (ReceiveCommand cmd : toApply) {
  470. if (cmd.getResult() == Result.NOT_ATTEMPTED) {
  471. sendRejection(cmd, "lock error: {0}", err.getMessage());
  472. }
  473. }
  474. }
  475. }
  476. //
  477. // if there are ref update receive commands that were
  478. // successfully processed and there is an active ticket service for the repository
  479. // then process any referenced tickets
  480. //
  481. if (ticketService != null) {
  482. List<ReceiveCommand> allUpdates = ReceiveCommand.filter(batch.getCommands(), Result.OK);
  483. if (!allUpdates.isEmpty()) {
  484. int ticketsProcessed = 0;
  485. for (ReceiveCommand cmd : allUpdates) {
  486. switch (cmd.getType()) {
  487. case CREATE:
  488. case UPDATE:
  489. if (cmd.getRefName().startsWith(Constants.R_HEADS)) {
  490. Collection<TicketModel> tickets = processReferencedTickets(cmd);
  491. ticketsProcessed += tickets.size();
  492. for (TicketModel ticket : tickets) {
  493. ticketNotifier.queueMailing(ticket);
  494. }
  495. }
  496. break;
  497. case UPDATE_NONFASTFORWARD:
  498. if (cmd.getRefName().startsWith(Constants.R_HEADS)) {
  499. String base = JGitUtils.getMergeBase(getRepository(), cmd.getOldId(), cmd.getNewId());
  500. List<TicketLink> deletedRefs = JGitUtils.identifyTicketsBetweenCommits(getRepository(), settings, base, cmd.getOldId().name());
  501. for (TicketLink link : deletedRefs) {
  502. link.isDelete = true;
  503. }
  504. Change deletion = new Change(user.username);
  505. deletion.pendingLinks = deletedRefs;
  506. ticketService.updateTicket(repository, 0, deletion);
  507. Collection<TicketModel> tickets = processReferencedTickets(cmd);
  508. ticketsProcessed += tickets.size();
  509. for (TicketModel ticket : tickets) {
  510. ticketNotifier.queueMailing(ticket);
  511. }
  512. }
  513. break;
  514. case DELETE:
  515. //Identify if the branch has been merged
  516. SortedMap<Integer, String> bases = new TreeMap<Integer, String>();
  517. try {
  518. ObjectId dObj = cmd.getOldId();
  519. Collection<Ref> tips = getRepository().getRefDatabase().getRefs(Constants.R_HEADS).values();
  520. for (Ref ref : tips) {
  521. ObjectId iObj = ref.getObjectId();
  522. String mergeBase = JGitUtils.getMergeBase(getRepository(), dObj, iObj);
  523. if (mergeBase != null) {
  524. int d = JGitUtils.countCommits(getRepository(), getRevWalk(), mergeBase, dObj.name());
  525. bases.put(d, mergeBase);
  526. //All commits have been merged into some other branch
  527. if (d == 0) {
  528. break;
  529. }
  530. }
  531. }
  532. if (bases.isEmpty()) {
  533. //TODO: Handle orphan branch case
  534. } else {
  535. if (bases.firstKey() > 0) {
  536. //Delete references from the remaining commits that haven't been merged
  537. String mergeBase = bases.get(bases.firstKey());
  538. List<TicketLink> deletedRefs = JGitUtils.identifyTicketsBetweenCommits(getRepository(),
  539. settings, mergeBase, dObj.name());
  540. for (TicketLink link : deletedRefs) {
  541. link.isDelete = true;
  542. }
  543. Change deletion = new Change(user.username);
  544. deletion.pendingLinks = deletedRefs;
  545. ticketService.updateTicket(repository, 0, deletion);
  546. }
  547. }
  548. } catch (IOException e) {
  549. LOGGER.error(null, e);
  550. }
  551. break;
  552. default:
  553. break;
  554. }
  555. }
  556. if (ticketsProcessed == 1) {
  557. sendInfo("1 ticket updated");
  558. } else if (ticketsProcessed > 1) {
  559. sendInfo("{0} tickets updated", ticketsProcessed);
  560. }
  561. }
  562. // reset the ticket caches for the repository
  563. ticketService.resetCaches(repository);
  564. }
  565. }
  566. protected void setGitblitUrl(String url) {
  567. this.gitblitUrl = url;
  568. }
  569. public void sendRejection(final ReceiveCommand cmd, final String why, Object... objects) {
  570. String text;
  571. if (ArrayUtils.isEmpty(objects)) {
  572. text = why;
  573. } else {
  574. text = MessageFormat.format(why, objects);
  575. }
  576. cmd.setResult(Result.REJECTED_OTHER_REASON, text);
  577. LOGGER.error(text + " (" + user.username + ")");
  578. }
  579. public void sendHeader(String msg, Object... objects) {
  580. sendInfo("--> ", msg, objects);
  581. }
  582. public void sendInfo(String msg, Object... objects) {
  583. sendInfo(" ", msg, objects);
  584. }
  585. private void sendInfo(String prefix, String msg, Object... objects) {
  586. String text;
  587. if (ArrayUtils.isEmpty(objects)) {
  588. text = msg;
  589. super.sendMessage(prefix + msg);
  590. } else {
  591. text = MessageFormat.format(msg, objects);
  592. super.sendMessage(prefix + text);
  593. }
  594. if (!StringUtils.isEmpty(msg)) {
  595. LOGGER.info(text + " (" + user.username + ")");
  596. }
  597. }
  598. public void sendError(String msg, Object... objects) {
  599. String text;
  600. if (ArrayUtils.isEmpty(objects)) {
  601. text = msg;
  602. super.sendError(msg);
  603. } else {
  604. text = MessageFormat.format(msg, objects);
  605. super.sendError(text);
  606. }
  607. if (!StringUtils.isEmpty(msg)) {
  608. LOGGER.error(text + " (" + user.username + ")");
  609. }
  610. }
  611. /**
  612. * Runs the specified Groovy hook scripts.
  613. *
  614. * @param repository
  615. * @param user
  616. * @param commands
  617. * @param scripts
  618. */
  619. private void runGroovy(Collection<ReceiveCommand> commands, Set<String> scripts) {
  620. if (scripts == null || scripts.size() == 0) {
  621. // no Groovy scripts to execute
  622. return;
  623. }
  624. Binding binding = new Binding();
  625. binding.setVariable("gitblit", gitblit);
  626. binding.setVariable("repository", repository);
  627. binding.setVariable("receivePack", this);
  628. binding.setVariable("user", user);
  629. binding.setVariable("commands", commands);
  630. binding.setVariable("url", gitblitUrl);
  631. binding.setVariable("logger", LOGGER);
  632. binding.setVariable("clientLogger", new ClientLogger(this));
  633. for (String script : scripts) {
  634. if (StringUtils.isEmpty(script)) {
  635. continue;
  636. }
  637. // allow script to be specified without .groovy extension
  638. // this is easier to read in the settings
  639. File file = new File(groovyDir, script);
  640. if (!file.exists() && !script.toLowerCase().endsWith(".groovy")) {
  641. file = new File(groovyDir, script + ".groovy");
  642. if (file.exists()) {
  643. script = file.getName();
  644. }
  645. }
  646. try {
  647. Object result = gse.run(script, binding);
  648. if (result instanceof Boolean) {
  649. if (!((Boolean) result)) {
  650. LOGGER.error(MessageFormat.format(
  651. "Groovy script {0} has failed! Hook scripts aborted.", script));
  652. break;
  653. }
  654. }
  655. } catch (Exception e) {
  656. LOGGER.error(
  657. MessageFormat.format("Failed to execute Groovy script {0}", script), e);
  658. }
  659. }
  660. }
  661. /**
  662. * Runs the specified Gitblitfile hook scripts.
  663. *
  664. * @param commands
  665. * @param scripts
  666. * @param event
  667. */
  668. private void runGitblitfile(Collection<ReceiveCommand> commands, Set<String> scripts, String event) {
  669. LOGGER.info("Start executing the Gitblitfile script...");
  670. @SuppressWarnings("rawtypes")
  671. Class cls = loadGitblitfile();
  672. if (null == cls) {
  673. // no Gitblitfile scripts to execute
  674. LOGGER.info("No found Gitblitfile script to execute.");
  675. return;
  676. }
  677. Binding binding = new Binding();
  678. binding.setVariable("gitblit", gitblit);
  679. binding.setVariable("repository", repository);
  680. binding.setVariable("receivePack", this);
  681. binding.setVariable("user", user);
  682. binding.setVariable("commands", commands);
  683. binding.setVariable("url", gitblitUrl);
  684. binding.setVariable("logger", LOGGER);
  685. binding.setVariable("clientLogger", new ClientLogger(this));
  686. binding.setVariable("event", event);
  687. try {
  688. Object result = InvokerHelper.createScript(cls, binding).run();
  689. if (result instanceof Boolean && !((Boolean) result)) {
  690. LOGGER.error("Gitblitfile has failed! Script aborted.");
  691. }
  692. } catch (Exception e) {
  693. LOGGER.error("Failed to execute Gitblitfile", e);
  694. }
  695. }
  696. @SuppressWarnings({ "rawtypes" })
  697. private Class loadGitblitfile() {
  698. Repository db = getRepository();
  699. try {
  700. Ref ref = db.findRef(db.getBranch());
  701. ObjectId objId = ref.getObjectId();
  702. RevCommit revCommit = getRevWalk().parseCommit(objId);
  703. RevTree revTree = revCommit.getTree();
  704. TreeWalk treeWalk = TreeWalk.forPath(db, "Gitblitfile", revTree);
  705. if (null == treeWalk) {
  706. return null;
  707. }
  708. ObjectId blobId = treeWalk.getObjectId(0);
  709. ObjectLoader loader = db.open(blobId);
  710. byte[] bytes = loader.getBytes();
  711. if (null == bytes || bytes.length == 0) {
  712. return null;
  713. }
  714. return gse.getGroovyClassLoader().parseClass(new String(bytes));
  715. } catch (Exception e) {
  716. LOGGER.error("Failed to parse Gitblitfile script", e);
  717. return null;
  718. }
  719. }
  720. public IGitblit getGitblit() {
  721. return gitblit;
  722. }
  723. public RepositoryModel getRepositoryModel() {
  724. return repository;
  725. }
  726. public UserModel getUserModel() {
  727. return user;
  728. }
  729. /**
  730. * Automatically closes open tickets and adds references to tickets if made in the commit message.
  731. *
  732. * @param cmd
  733. */
  734. private Collection<TicketModel> processReferencedTickets(ReceiveCommand cmd) {
  735. Map<Long, TicketModel> changedTickets = new LinkedHashMap<Long, TicketModel>();
  736. final RevWalk rw = getRevWalk();
  737. try {
  738. rw.reset();
  739. rw.markStart(rw.parseCommit(cmd.getNewId()));
  740. if (!ObjectId.zeroId().equals(cmd.getOldId())) {
  741. rw.markUninteresting(rw.parseCommit(cmd.getOldId()));
  742. }
  743. RevCommit c;
  744. while ((c = rw.next()) != null) {
  745. rw.parseBody(c);
  746. List<TicketLink> ticketLinks = JGitUtils.identifyTicketsFromCommitMessage(getRepository(), settings, c);
  747. if (ticketLinks == null) {
  748. continue;
  749. }
  750. for (TicketLink link : ticketLinks) {
  751. TicketModel ticket = ticketService.getTicket(repository, link.targetTicketId);
  752. if (ticket == null) {
  753. continue;
  754. }
  755. Change change = null;
  756. String commitSha = c.getName();
  757. String branchName = Repository.shortenRefName(cmd.getRefName());
  758. switch (link.action) {
  759. case Commit: {
  760. //A commit can reference a ticket in any branch even if the ticket is closed.
  761. //This allows developers to identify and communicate related issues
  762. change = new Change(user.username);
  763. change.referenceCommit(commitSha);
  764. } break;
  765. case Close: {
  766. // As this isn't a patchset theres no merging taking place when closing a ticket
  767. if (ticket.isClosed()) {
  768. continue;
  769. }
  770. change = new Change(user.username);
  771. change.setField(Field.status, Status.Fixed);
  772. if (StringUtils.isEmpty(ticket.responsible)) {
  773. // unassigned tickets are assigned to the closer
  774. change.setField(Field.responsible, user.username);
  775. }
  776. }
  777. default: {
  778. //No action
  779. } break;
  780. }
  781. if (change != null) {
  782. ticket = ticketService.updateTicket(repository, ticket.number, change);
  783. }
  784. if (ticket != null) {
  785. sendInfo("");
  786. sendHeader("#{0,number,0}: {1}", ticket.number, StringUtils.trimString(ticket.title, Constants.LEN_SHORTLOG));
  787. switch (link.action) {
  788. case Commit: {
  789. sendInfo("referenced by push of {0} to {1}", commitSha, branchName);
  790. changedTickets.put(ticket.number, ticket);
  791. } break;
  792. case Close: {
  793. sendInfo("closed by push of {0} to {1}", commitSha, branchName);
  794. changedTickets.put(ticket.number, ticket);
  795. } break;
  796. default: { }
  797. }
  798. sendInfo(ticketService.getTicketUrl(ticket));
  799. sendInfo("");
  800. } else {
  801. switch (link.action) {
  802. case Commit: {
  803. sendError("FAILED to reference ticket {0} by push of {1}", link.targetTicketId, commitSha);
  804. } break;
  805. case Close: {
  806. sendError("FAILED to close ticket {0} by push of {1}", link.targetTicketId, commitSha);
  807. } break;
  808. default: { }
  809. }
  810. }
  811. }
  812. }
  813. } catch (IOException e) {
  814. LOGGER.error("Can't scan for changes to reference or close", e);
  815. } finally {
  816. rw.reset();
  817. }
  818. return changedTickets.values();
  819. }
  820. }