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.

GitBlit.java 48KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460
  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 java.io.BufferedReader;
  18. import java.io.File;
  19. import java.io.FileFilter;
  20. import java.io.IOException;
  21. import java.io.InputStream;
  22. import java.io.InputStreamReader;
  23. import java.lang.reflect.Field;
  24. import java.text.MessageFormat;
  25. import java.util.ArrayList;
  26. import java.util.Arrays;
  27. import java.util.Collections;
  28. import java.util.HashMap;
  29. import java.util.List;
  30. import java.util.Map;
  31. import java.util.Map.Entry;
  32. import java.util.concurrent.ConcurrentHashMap;
  33. import java.util.concurrent.Executors;
  34. import java.util.concurrent.ScheduledExecutorService;
  35. import java.util.concurrent.TimeUnit;
  36. import java.util.concurrent.atomic.AtomicInteger;
  37. import javax.mail.Message;
  38. import javax.mail.MessagingException;
  39. import javax.servlet.ServletContext;
  40. import javax.servlet.ServletContextEvent;
  41. import javax.servlet.ServletContextListener;
  42. import javax.servlet.http.Cookie;
  43. import org.apache.wicket.protocol.http.WebResponse;
  44. import org.eclipse.jgit.errors.RepositoryNotFoundException;
  45. import org.eclipse.jgit.lib.Repository;
  46. import org.eclipse.jgit.lib.RepositoryCache.FileKey;
  47. import org.eclipse.jgit.lib.StoredConfig;
  48. import org.eclipse.jgit.transport.resolver.FileResolver;
  49. import org.eclipse.jgit.transport.resolver.RepositoryResolver;
  50. import org.eclipse.jgit.transport.resolver.ServiceNotAuthorizedException;
  51. import org.eclipse.jgit.transport.resolver.ServiceNotEnabledException;
  52. import org.eclipse.jgit.util.FS;
  53. import org.eclipse.jgit.util.FileUtils;
  54. import org.slf4j.Logger;
  55. import org.slf4j.LoggerFactory;
  56. import com.gitblit.Constants.AccessRestrictionType;
  57. import com.gitblit.Constants.FederationRequest;
  58. import com.gitblit.Constants.FederationStrategy;
  59. import com.gitblit.Constants.FederationToken;
  60. import com.gitblit.models.FederationModel;
  61. import com.gitblit.models.FederationProposal;
  62. import com.gitblit.models.FederationSet;
  63. import com.gitblit.models.Metric;
  64. import com.gitblit.models.ObjectCache;
  65. import com.gitblit.models.RepositoryModel;
  66. import com.gitblit.models.ServerSettings;
  67. import com.gitblit.models.ServerStatus;
  68. import com.gitblit.models.SettingModel;
  69. import com.gitblit.models.UserModel;
  70. import com.gitblit.utils.ByteFormat;
  71. import com.gitblit.utils.FederationUtils;
  72. import com.gitblit.utils.JGitUtils;
  73. import com.gitblit.utils.JsonUtils;
  74. import com.gitblit.utils.MetricUtils;
  75. import com.gitblit.utils.StringUtils;
  76. /**
  77. * GitBlit is the servlet context listener singleton that acts as the core for
  78. * the web ui and the servlets. This class is either directly instantiated by
  79. * the GitBlitServer class (Gitblit GO) or is reflectively instantiated from the
  80. * definition in the web.xml file (Gitblit WAR).
  81. *
  82. * This class is the central logic processor for Gitblit. All settings, user
  83. * object, and repository object operations pass through this class.
  84. *
  85. * Repository Resolution. There are two pathways for finding repositories. One
  86. * pathway, for web ui display and repository authentication & authorization, is
  87. * within this class. The other pathway is through the standard GitServlet.
  88. *
  89. * @author James Moger
  90. *
  91. */
  92. public class GitBlit implements ServletContextListener {
  93. private static GitBlit gitblit;
  94. private final Logger logger = LoggerFactory.getLogger(GitBlit.class);
  95. private final ScheduledExecutorService scheduledExecutor = Executors.newScheduledThreadPool(5);
  96. private final List<FederationModel> federationRegistrations = Collections
  97. .synchronizedList(new ArrayList<FederationModel>());
  98. private final Map<String, FederationModel> federationPullResults = new ConcurrentHashMap<String, FederationModel>();
  99. private final ObjectCache<Long> repositorySizeCache = new ObjectCache<Long>();
  100. private final ObjectCache<List<Metric>> repositoryMetricsCache = new ObjectCache<List<Metric>>();
  101. private RepositoryResolver<Void> repositoryResolver;
  102. private ServletContext servletContext;
  103. private File repositoriesFolder;
  104. private boolean exportAll = true;
  105. private IUserService userService;
  106. private IStoredSettings settings;
  107. private ServerSettings settingsModel;
  108. private ServerStatus serverStatus;
  109. private MailExecutor mailExecutor;
  110. public GitBlit() {
  111. if (gitblit == null) {
  112. // set the static singleton reference
  113. gitblit = this;
  114. }
  115. }
  116. /**
  117. * Returns the Gitblit singleton.
  118. *
  119. * @return gitblit singleton
  120. */
  121. public static GitBlit self() {
  122. if (gitblit == null) {
  123. new GitBlit();
  124. }
  125. return gitblit;
  126. }
  127. /**
  128. * Determine if this is the GO variant of Gitblit.
  129. *
  130. * @return true if this is the GO variant of Gitblit.
  131. */
  132. public static boolean isGO() {
  133. return self().settings instanceof FileSettings;
  134. }
  135. /**
  136. * Returns the boolean value for the specified key. If the key does not
  137. * exist or the value for the key can not be interpreted as a boolean, the
  138. * defaultValue is returned.
  139. *
  140. * @see IStoredSettings.getBoolean(String, boolean)
  141. * @param key
  142. * @param defaultValue
  143. * @return key value or defaultValue
  144. */
  145. public static boolean getBoolean(String key, boolean defaultValue) {
  146. return self().settings.getBoolean(key, defaultValue);
  147. }
  148. /**
  149. * Returns the integer value for the specified key. If the key does not
  150. * exist or the value for the key can not be interpreted as an integer, the
  151. * defaultValue is returned.
  152. *
  153. * @see IStoredSettings.getInteger(String key, int defaultValue)
  154. * @param key
  155. * @param defaultValue
  156. * @return key value or defaultValue
  157. */
  158. public static int getInteger(String key, int defaultValue) {
  159. return self().settings.getInteger(key, defaultValue);
  160. }
  161. /**
  162. * Returns the char value for the specified key. If the key does not exist
  163. * or the value for the key can not be interpreted as a character, the
  164. * defaultValue is returned.
  165. *
  166. * @see IStoredSettings.getChar(String key, char defaultValue)
  167. * @param key
  168. * @param defaultValue
  169. * @return key value or defaultValue
  170. */
  171. public static char getChar(String key, char defaultValue) {
  172. return self().settings.getChar(key, defaultValue);
  173. }
  174. /**
  175. * Returns the string value for the specified key. If the key does not exist
  176. * or the value for the key can not be interpreted as a string, the
  177. * defaultValue is returned.
  178. *
  179. * @see IStoredSettings.getString(String key, String defaultValue)
  180. * @param key
  181. * @param defaultValue
  182. * @return key value or defaultValue
  183. */
  184. public static String getString(String key, String defaultValue) {
  185. return self().settings.getString(key, defaultValue);
  186. }
  187. /**
  188. * Returns a list of space-separated strings from the specified key.
  189. *
  190. * @see IStoredSettings.getStrings(String key)
  191. * @param name
  192. * @return list of strings
  193. */
  194. public static List<String> getStrings(String key) {
  195. return self().settings.getStrings(key);
  196. }
  197. /**
  198. * Returns the list of keys whose name starts with the specified prefix. If
  199. * the prefix is null or empty, all key names are returned.
  200. *
  201. * @see IStoredSettings.getAllKeys(String key)
  202. * @param startingWith
  203. * @return list of keys
  204. */
  205. public static List<String> getAllKeys(String startingWith) {
  206. return self().settings.getAllKeys(startingWith);
  207. }
  208. /**
  209. * Is Gitblit running in debug mode?
  210. *
  211. * @return true if Gitblit is running in debug mode
  212. */
  213. public static boolean isDebugMode() {
  214. return self().settings.getBoolean(Keys.web.debugMode, false);
  215. }
  216. /**
  217. * Updates the list of server settings.
  218. *
  219. * @param settings
  220. * @return true if the update succeeded
  221. */
  222. public boolean updateSettings(Map<String, String> updatedSettings) {
  223. return settings.saveSettings(updatedSettings);
  224. }
  225. public ServerStatus getStatus() {
  226. // update heap memory status
  227. serverStatus.heapAllocated = Runtime.getRuntime().totalMemory();
  228. serverStatus.heapFree = Runtime.getRuntime().freeMemory();
  229. return serverStatus;
  230. }
  231. /**
  232. * Returns the list of non-Gitblit clone urls. This allows Gitblit to
  233. * advertise alternative urls for Git client repository access.
  234. *
  235. * @param repositoryName
  236. * @return list of non-gitblit clone urls
  237. */
  238. public List<String> getOtherCloneUrls(String repositoryName) {
  239. List<String> cloneUrls = new ArrayList<String>();
  240. for (String url : settings.getStrings(Keys.web.otherUrls)) {
  241. cloneUrls.add(MessageFormat.format(url, repositoryName));
  242. }
  243. return cloneUrls;
  244. }
  245. /**
  246. * Set the user service. The user service authenticates all users and is
  247. * responsible for managing user permissions.
  248. *
  249. * @param userService
  250. */
  251. public void setUserService(IUserService userService) {
  252. logger.info("Setting up user service " + userService.toString());
  253. this.userService = userService;
  254. this.userService.setup(settings);
  255. }
  256. /**
  257. * Authenticate a user based on a username and password.
  258. *
  259. * @see IUserService.authenticate(String, char[])
  260. * @param username
  261. * @param password
  262. * @return a user object or null
  263. */
  264. public UserModel authenticate(String username, char[] password) {
  265. if (StringUtils.isEmpty(username)) {
  266. // can not authenticate empty username
  267. return null;
  268. }
  269. String pw = new String(password);
  270. if (StringUtils.isEmpty(pw)) {
  271. // can not authenticate empty password
  272. return null;
  273. }
  274. // check to see if this is the federation user
  275. if (canFederate()) {
  276. if (username.equalsIgnoreCase(Constants.FEDERATION_USER)) {
  277. List<String> tokens = getFederationTokens();
  278. if (tokens.contains(pw)) {
  279. // the federation user is an administrator
  280. UserModel federationUser = new UserModel(Constants.FEDERATION_USER);
  281. federationUser.canAdmin = true;
  282. return federationUser;
  283. }
  284. }
  285. }
  286. // delegate authentication to the user service
  287. if (userService == null) {
  288. return null;
  289. }
  290. return userService.authenticate(username, password);
  291. }
  292. /**
  293. * Authenticate a user based on their cookie.
  294. *
  295. * @param cookies
  296. * @return a user object or null
  297. */
  298. public UserModel authenticate(Cookie[] cookies) {
  299. if (userService == null) {
  300. return null;
  301. }
  302. if (userService.supportsCookies()) {
  303. if (cookies != null && cookies.length > 0) {
  304. for (Cookie cookie : cookies) {
  305. if (cookie.getName().equals(Constants.NAME)) {
  306. String value = cookie.getValue();
  307. return userService.authenticate(value.toCharArray());
  308. }
  309. }
  310. }
  311. }
  312. return null;
  313. }
  314. /**
  315. * Sets a cookie for the specified user.
  316. *
  317. * @param response
  318. * @param user
  319. */
  320. public void setCookie(WebResponse response, UserModel user) {
  321. if (userService == null) {
  322. return;
  323. }
  324. if (userService.supportsCookies()) {
  325. Cookie userCookie;
  326. if (user == null) {
  327. // clear cookie for logout
  328. userCookie = new Cookie(Constants.NAME, "");
  329. } else {
  330. // set cookie for login
  331. char[] cookie = userService.getCookie(user);
  332. userCookie = new Cookie(Constants.NAME, new String(cookie));
  333. userCookie.setMaxAge(Integer.MAX_VALUE);
  334. }
  335. userCookie.setPath("/");
  336. response.addCookie(userCookie);
  337. }
  338. }
  339. /**
  340. * Returns the list of all users available to the login service.
  341. *
  342. * @see IUserService.getAllUsernames()
  343. * @return list of all usernames
  344. */
  345. public List<String> getAllUsernames() {
  346. List<String> names = new ArrayList<String>(userService.getAllUsernames());
  347. Collections.sort(names);
  348. return names;
  349. }
  350. /**
  351. * Delete the user object with the specified username
  352. *
  353. * @see IUserService.deleteUser(String)
  354. * @param username
  355. * @return true if successful
  356. */
  357. public boolean deleteUser(String username) {
  358. return userService.deleteUser(username);
  359. }
  360. /**
  361. * Retrieve the user object for the specified username.
  362. *
  363. * @see IUserService.getUserModel(String)
  364. * @param username
  365. * @return a user object or null
  366. */
  367. public UserModel getUserModel(String username) {
  368. UserModel user = userService.getUserModel(username);
  369. return user;
  370. }
  371. /**
  372. * Returns the list of all users who are allowed to bypass the access
  373. * restriction placed on the specified repository.
  374. *
  375. * @see IUserService.getUsernamesForRepositoryRole(String)
  376. * @param repository
  377. * @return list of all usernames that can bypass the access restriction
  378. */
  379. public List<String> getRepositoryUsers(RepositoryModel repository) {
  380. return userService.getUsernamesForRepositoryRole(repository.name);
  381. }
  382. /**
  383. * Sets the list of all uses who are allowed to bypass the access
  384. * restriction placed on the specified repository.
  385. *
  386. * @see IUserService.setUsernamesForRepositoryRole(String, List<String>)
  387. * @param repository
  388. * @param usernames
  389. * @return true if successful
  390. */
  391. public boolean setRepositoryUsers(RepositoryModel repository, List<String> repositoryUsers) {
  392. return userService.setUsernamesForRepositoryRole(repository.name, repositoryUsers);
  393. }
  394. /**
  395. * Adds/updates a complete user object keyed by username. This method allows
  396. * for renaming a user.
  397. *
  398. * @see IUserService.updateUserModel(String, UserModel)
  399. * @param username
  400. * @param user
  401. * @param isCreate
  402. * @throws GitBlitException
  403. */
  404. public void updateUserModel(String username, UserModel user, boolean isCreate)
  405. throws GitBlitException {
  406. if (!username.equalsIgnoreCase(user.username)) {
  407. if (userService.getUserModel(user.username) != null) {
  408. throw new GitBlitException(MessageFormat.format(
  409. "Failed to rename ''{0}'' because ''{1}'' already exists.", username,
  410. user.username));
  411. }
  412. }
  413. if (!userService.updateUserModel(username, user)) {
  414. throw new GitBlitException(isCreate ? "Failed to add user!" : "Failed to update user!");
  415. }
  416. }
  417. /**
  418. * Clears all the cached data for the specified repository.
  419. *
  420. * @param repositoryName
  421. */
  422. public void clearRepositoryCache(String repositoryName) {
  423. repositorySizeCache.remove(repositoryName);
  424. repositoryMetricsCache.remove(repositoryName);
  425. }
  426. /**
  427. * Returns the list of all repositories available to Gitblit. This method
  428. * does not consider user access permissions.
  429. *
  430. * @return list of all repositories
  431. */
  432. public List<String> getRepositoryList() {
  433. return JGitUtils.getRepositoryList(repositoriesFolder, exportAll,
  434. settings.getBoolean(Keys.git.searchRepositoriesSubfolders, true));
  435. }
  436. /**
  437. * Returns the JGit repository for the specified name.
  438. *
  439. * @param repositoryName
  440. * @return repository or null
  441. */
  442. public Repository getRepository(String repositoryName) {
  443. Repository r = null;
  444. try {
  445. r = repositoryResolver.open(null, repositoryName);
  446. } catch (RepositoryNotFoundException e) {
  447. r = null;
  448. logger.error("GitBlit.getRepository(String) failed to find "
  449. + new File(repositoriesFolder, repositoryName).getAbsolutePath());
  450. } catch (ServiceNotAuthorizedException e) {
  451. r = null;
  452. logger.error("GitBlit.getRepository(String) failed to find "
  453. + new File(repositoriesFolder, repositoryName).getAbsolutePath(), e);
  454. } catch (ServiceNotEnabledException e) {
  455. r = null;
  456. logger.error("GitBlit.getRepository(String) failed to find "
  457. + new File(repositoriesFolder, repositoryName).getAbsolutePath(), e);
  458. }
  459. return r;
  460. }
  461. /**
  462. * Returns the list of repository models that are accessible to the user.
  463. *
  464. * @param user
  465. * @return list of repository models accessible to user
  466. */
  467. public List<RepositoryModel> getRepositoryModels(UserModel user) {
  468. List<String> list = getRepositoryList();
  469. List<RepositoryModel> repositories = new ArrayList<RepositoryModel>();
  470. for (String repo : list) {
  471. RepositoryModel model = getRepositoryModel(user, repo);
  472. if (model != null) {
  473. repositories.add(model);
  474. }
  475. }
  476. if (getBoolean(Keys.web.showRepositorySizes, true)) {
  477. int repoCount = 0;
  478. long startTime = System.currentTimeMillis();
  479. ByteFormat byteFormat = new ByteFormat();
  480. for (RepositoryModel model : repositories) {
  481. if (!model.skipSizeCalculation) {
  482. repoCount++;
  483. model.size = byteFormat.format(calculateSize(model));
  484. }
  485. }
  486. long duration = System.currentTimeMillis() - startTime;
  487. logger.info(MessageFormat.format("{0} repository sizes calculated in {1} msecs",
  488. repoCount, duration));
  489. }
  490. return repositories;
  491. }
  492. /**
  493. * Returns a repository model if the repository exists and the user may
  494. * access the repository.
  495. *
  496. * @param user
  497. * @param repositoryName
  498. * @return repository model or null
  499. */
  500. public RepositoryModel getRepositoryModel(UserModel user, String repositoryName) {
  501. RepositoryModel model = getRepositoryModel(repositoryName);
  502. if (model == null) {
  503. return null;
  504. }
  505. if (model.accessRestriction.atLeast(AccessRestrictionType.VIEW)) {
  506. if (user != null && user.canAccessRepository(model.name)) {
  507. return model;
  508. }
  509. return null;
  510. } else {
  511. return model;
  512. }
  513. }
  514. /**
  515. * Returns the repository model for the specified repository. This method
  516. * does not consider user access permissions.
  517. *
  518. * @param repositoryName
  519. * @return repository model or null
  520. */
  521. public RepositoryModel getRepositoryModel(String repositoryName) {
  522. Repository r = getRepository(repositoryName);
  523. if (r == null) {
  524. return null;
  525. }
  526. RepositoryModel model = new RepositoryModel();
  527. model.name = repositoryName;
  528. model.hasCommits = JGitUtils.hasCommits(r);
  529. model.lastChange = JGitUtils.getLastChange(r, null);
  530. StoredConfig config = JGitUtils.readConfig(r);
  531. if (config != null) {
  532. model.description = getConfig(config, "description", "");
  533. model.owner = getConfig(config, "owner", "");
  534. model.useTickets = getConfig(config, "useTickets", false);
  535. model.useDocs = getConfig(config, "useDocs", false);
  536. model.accessRestriction = AccessRestrictionType.fromName(getConfig(config,
  537. "accessRestriction", null));
  538. model.showRemoteBranches = getConfig(config, "showRemoteBranches", false);
  539. model.isFrozen = getConfig(config, "isFrozen", false);
  540. model.showReadme = getConfig(config, "showReadme", false);
  541. model.skipSizeCalculation = getConfig(config, "skipSizeCalculation", false);
  542. model.skipSummaryMetrics = getConfig(config, "skipSummaryMetrics", false);
  543. model.federationStrategy = FederationStrategy.fromName(getConfig(config,
  544. "federationStrategy", null));
  545. model.federationSets = new ArrayList<String>(Arrays.asList(config.getStringList(
  546. "gitblit", null, "federationSets")));
  547. model.isFederated = getConfig(config, "isFederated", false);
  548. model.origin = config.getString("remote", "origin", "url");
  549. }
  550. r.close();
  551. return model;
  552. }
  553. /**
  554. * Returns the size in bytes of the repository. Gitblit caches the
  555. * repository sizes to reduce the performance penalty of recursive
  556. * calculation. The cache is updated if the repository has been changed
  557. * since the last calculation.
  558. *
  559. * @param model
  560. * @return size in bytes
  561. */
  562. public long calculateSize(RepositoryModel model) {
  563. if (repositorySizeCache.hasCurrent(model.name, model.lastChange)) {
  564. return repositorySizeCache.getObject(model.name);
  565. }
  566. File gitDir = FileKey.resolve(new File(repositoriesFolder, model.name), FS.DETECTED);
  567. long size = com.gitblit.utils.FileUtils.folderSize(gitDir);
  568. repositorySizeCache.updateObject(model.name, model.lastChange, size);
  569. return size;
  570. }
  571. /**
  572. * Ensure that a cached repository is completely closed and its resources
  573. * are properly released.
  574. *
  575. * @param repositoryName
  576. */
  577. private void closeRepository(String repositoryName) {
  578. Repository repository = getRepository(repositoryName);
  579. // assume 2 uses in case reflection fails
  580. int uses = 2;
  581. try {
  582. // The FileResolver caches repositories which is very useful
  583. // for performance until you want to delete a repository.
  584. // I have to use reflection to call close() the correct
  585. // number of times to ensure that the object and ref databases
  586. // are properly closed before I can delete the repository from
  587. // the filesystem.
  588. Field useCnt = Repository.class.getDeclaredField("useCnt");
  589. useCnt.setAccessible(true);
  590. uses = ((AtomicInteger) useCnt.get(repository)).get();
  591. } catch (Exception e) {
  592. logger.warn(MessageFormat
  593. .format("Failed to reflectively determine use count for repository {0}",
  594. repositoryName), e);
  595. }
  596. if (uses > 0) {
  597. logger.info(MessageFormat
  598. .format("{0}.useCnt={1}, calling close() {2} time(s) to close object and ref databases",
  599. repositoryName, uses, uses));
  600. for (int i = 0; i < uses; i++) {
  601. repository.close();
  602. }
  603. }
  604. }
  605. /**
  606. * Returns the metrics for the default branch of the specified repository.
  607. * This method builds a metrics cache. The cache is updated if the
  608. * repository is updated. A new copy of the metrics list is returned on each
  609. * call so that modifications to the list are non-destructive.
  610. *
  611. * @param model
  612. * @param repository
  613. * @return a new array list of metrics
  614. */
  615. public List<Metric> getRepositoryDefaultMetrics(RepositoryModel model, Repository repository) {
  616. if (repositoryMetricsCache.hasCurrent(model.name, model.lastChange)) {
  617. return new ArrayList<Metric>(repositoryMetricsCache.getObject(model.name));
  618. }
  619. List<Metric> metrics = MetricUtils.getDateMetrics(repository, null, true, null);
  620. repositoryMetricsCache.updateObject(model.name, model.lastChange, metrics);
  621. return new ArrayList<Metric>(metrics);
  622. }
  623. /**
  624. * Returns the gitblit string value for the specified key. If key is not
  625. * set, returns defaultValue.
  626. *
  627. * @param config
  628. * @param field
  629. * @param defaultValue
  630. * @return field value or defaultValue
  631. */
  632. private String getConfig(StoredConfig config, String field, String defaultValue) {
  633. String value = config.getString("gitblit", null, field);
  634. if (StringUtils.isEmpty(value)) {
  635. return defaultValue;
  636. }
  637. return value;
  638. }
  639. /**
  640. * Returns the gitblit boolean vlaue for the specified key. If key is not
  641. * set, returns defaultValue.
  642. *
  643. * @param config
  644. * @param field
  645. * @param defaultValue
  646. * @return field value or defaultValue
  647. */
  648. private boolean getConfig(StoredConfig config, String field, boolean defaultValue) {
  649. return config.getBoolean("gitblit", field, defaultValue);
  650. }
  651. /**
  652. * Creates/updates the repository model keyed by reopsitoryName. Saves all
  653. * repository settings in .git/config. This method allows for renaming
  654. * repositories and will update user access permissions accordingly.
  655. *
  656. * All repositories created by this method are bare and automatically have
  657. * .git appended to their names, which is the standard convention for bare
  658. * repositories.
  659. *
  660. * @param repositoryName
  661. * @param repository
  662. * @param isCreate
  663. * @throws GitBlitException
  664. */
  665. public void updateRepositoryModel(String repositoryName, RepositoryModel repository,
  666. boolean isCreate) throws GitBlitException {
  667. Repository r = null;
  668. if (isCreate) {
  669. // ensure created repository name ends with .git
  670. if (!repository.name.toLowerCase().endsWith(org.eclipse.jgit.lib.Constants.DOT_GIT_EXT)) {
  671. repository.name += org.eclipse.jgit.lib.Constants.DOT_GIT_EXT;
  672. }
  673. if (new File(repositoriesFolder, repository.name).exists()) {
  674. throw new GitBlitException(MessageFormat.format(
  675. "Can not create repository ''{0}'' because it already exists.",
  676. repository.name));
  677. }
  678. // create repository
  679. logger.info("create repository " + repository.name);
  680. r = JGitUtils.createRepository(repositoriesFolder, repository.name);
  681. } else {
  682. // rename repository
  683. if (!repositoryName.equalsIgnoreCase(repository.name)) {
  684. if (!repository.name.toLowerCase().endsWith(
  685. org.eclipse.jgit.lib.Constants.DOT_GIT_EXT)) {
  686. repository.name += org.eclipse.jgit.lib.Constants.DOT_GIT_EXT;
  687. }
  688. if (new File(repositoriesFolder, repository.name).exists()) {
  689. throw new GitBlitException(MessageFormat.format(
  690. "Failed to rename ''{0}'' because ''{1}'' already exists.",
  691. repositoryName, repository.name));
  692. }
  693. closeRepository(repositoryName);
  694. File folder = new File(repositoriesFolder, repositoryName);
  695. File destFolder = new File(repositoriesFolder, repository.name);
  696. if (destFolder.exists()) {
  697. throw new GitBlitException(
  698. MessageFormat
  699. .format("Can not rename repository ''{0}'' to ''{1}'' because ''{1}'' already exists.",
  700. repositoryName, repository.name));
  701. }
  702. if (!folder.renameTo(destFolder)) {
  703. throw new GitBlitException(MessageFormat.format(
  704. "Failed to rename repository ''{0}'' to ''{1}''.", repositoryName,
  705. repository.name));
  706. }
  707. // rename the roles
  708. if (!userService.renameRepositoryRole(repositoryName, repository.name)) {
  709. throw new GitBlitException(MessageFormat.format(
  710. "Failed to rename repository permissions ''{0}'' to ''{1}''.",
  711. repositoryName, repository.name));
  712. }
  713. // clear the cache
  714. clearRepositoryCache(repositoryName);
  715. }
  716. // load repository
  717. logger.info("edit repository " + repository.name);
  718. try {
  719. r = repositoryResolver.open(null, repository.name);
  720. } catch (RepositoryNotFoundException e) {
  721. logger.error("Repository not found", e);
  722. } catch (ServiceNotAuthorizedException e) {
  723. logger.error("Service not authorized", e);
  724. } catch (ServiceNotEnabledException e) {
  725. logger.error("Service not enabled", e);
  726. }
  727. }
  728. // update settings
  729. if (r != null) {
  730. updateConfiguration(r, repository);
  731. r.close();
  732. }
  733. }
  734. /**
  735. * Updates the Gitblit configuration for the specified repository.
  736. *
  737. * @param r
  738. * the Git repository
  739. * @param repository
  740. * the Gitblit repository model
  741. */
  742. public void updateConfiguration(Repository r, RepositoryModel repository) {
  743. StoredConfig config = JGitUtils.readConfig(r);
  744. config.setString("gitblit", null, "description", repository.description);
  745. config.setString("gitblit", null, "owner", repository.owner);
  746. config.setBoolean("gitblit", null, "useTickets", repository.useTickets);
  747. config.setBoolean("gitblit", null, "useDocs", repository.useDocs);
  748. config.setString("gitblit", null, "accessRestriction", repository.accessRestriction.name());
  749. config.setBoolean("gitblit", null, "showRemoteBranches", repository.showRemoteBranches);
  750. config.setBoolean("gitblit", null, "isFrozen", repository.isFrozen);
  751. config.setBoolean("gitblit", null, "showReadme", repository.showReadme);
  752. config.setBoolean("gitblit", null, "skipSizeCalculation", repository.skipSizeCalculation);
  753. config.setBoolean("gitblit", null, "skipSummaryMetrics", repository.skipSummaryMetrics);
  754. config.setStringList("gitblit", null, "federationSets", repository.federationSets);
  755. config.setString("gitblit", null, "federationStrategy",
  756. repository.federationStrategy.name());
  757. config.setBoolean("gitblit", null, "isFederated", repository.isFederated);
  758. try {
  759. config.save();
  760. } catch (IOException e) {
  761. logger.error("Failed to save repository config!", e);
  762. }
  763. }
  764. /**
  765. * Deletes the repository from the file system and removes the repository
  766. * permission from all repository users.
  767. *
  768. * @param model
  769. * @return true if successful
  770. */
  771. public boolean deleteRepositoryModel(RepositoryModel model) {
  772. return deleteRepository(model.name);
  773. }
  774. /**
  775. * Deletes the repository from the file system and removes the repository
  776. * permission from all repository users.
  777. *
  778. * @param repositoryName
  779. * @return true if successful
  780. */
  781. public boolean deleteRepository(String repositoryName) {
  782. try {
  783. closeRepository(repositoryName);
  784. File folder = new File(repositoriesFolder, repositoryName);
  785. if (folder.exists() && folder.isDirectory()) {
  786. FileUtils.delete(folder, FileUtils.RECURSIVE | FileUtils.RETRY);
  787. if (userService.deleteRepositoryRole(repositoryName)) {
  788. return true;
  789. }
  790. }
  791. // clear the repository cache
  792. clearRepositoryCache(repositoryName);
  793. } catch (Throwable t) {
  794. logger.error(MessageFormat.format("Failed to delete repository {0}", repositoryName), t);
  795. }
  796. return false;
  797. }
  798. /**
  799. * Returns an html version of the commit message with any global or
  800. * repository-specific regular expression substitution applied.
  801. *
  802. * @param repositoryName
  803. * @param text
  804. * @return html version of the commit message
  805. */
  806. public String processCommitMessage(String repositoryName, String text) {
  807. String html = StringUtils.breakLinesForHtml(text);
  808. Map<String, String> map = new HashMap<String, String>();
  809. // global regex keys
  810. if (settings.getBoolean(Keys.regex.global, false)) {
  811. for (String key : settings.getAllKeys(Keys.regex.global)) {
  812. if (!key.equals(Keys.regex.global)) {
  813. String subKey = key.substring(key.lastIndexOf('.') + 1);
  814. map.put(subKey, settings.getString(key, ""));
  815. }
  816. }
  817. }
  818. // repository-specific regex keys
  819. List<String> keys = settings.getAllKeys(Keys.regex._ROOT + "."
  820. + repositoryName.toLowerCase());
  821. for (String key : keys) {
  822. String subKey = key.substring(key.lastIndexOf('.') + 1);
  823. map.put(subKey, settings.getString(key, ""));
  824. }
  825. for (Entry<String, String> entry : map.entrySet()) {
  826. String definition = entry.getValue().trim();
  827. String[] chunks = definition.split("!!!");
  828. if (chunks.length == 2) {
  829. html = html.replaceAll(chunks[0], chunks[1]);
  830. } else {
  831. logger.warn(entry.getKey()
  832. + " improperly formatted. Use !!! to separate match from replacement: "
  833. + definition);
  834. }
  835. }
  836. return html;
  837. }
  838. /**
  839. * Returns Gitblit's scheduled executor service for scheduling tasks.
  840. *
  841. * @return scheduledExecutor
  842. */
  843. public ScheduledExecutorService executor() {
  844. return scheduledExecutor;
  845. }
  846. public static boolean canFederate() {
  847. String passphrase = getString(Keys.federation.passphrase, "");
  848. return !StringUtils.isEmpty(passphrase);
  849. }
  850. /**
  851. * Configures this Gitblit instance to pull any registered federated gitblit
  852. * instances.
  853. */
  854. private void configureFederation() {
  855. boolean validPassphrase = true;
  856. String passphrase = settings.getString(Keys.federation.passphrase, "");
  857. if (StringUtils.isEmpty(passphrase)) {
  858. logger.warn("Federation passphrase is blank! This server can not be PULLED from.");
  859. validPassphrase = false;
  860. }
  861. if (validPassphrase) {
  862. // standard tokens
  863. for (FederationToken tokenType : FederationToken.values()) {
  864. logger.info(MessageFormat.format("Federation {0} token = {1}", tokenType.name(),
  865. getFederationToken(tokenType)));
  866. }
  867. // federation set tokens
  868. for (String set : settings.getStrings(Keys.federation.sets)) {
  869. logger.info(MessageFormat.format("Federation Set {0} token = {1}", set,
  870. getFederationToken(set)));
  871. }
  872. }
  873. // Schedule the federation executor
  874. List<FederationModel> registrations = getFederationRegistrations();
  875. if (registrations.size() > 0) {
  876. FederationPullExecutor executor = new FederationPullExecutor(registrations, true);
  877. scheduledExecutor.schedule(executor, 1, TimeUnit.MINUTES);
  878. }
  879. }
  880. /**
  881. * Returns the list of federated gitblit instances that this instance will
  882. * try to pull.
  883. *
  884. * @return list of registered gitblit instances
  885. */
  886. public List<FederationModel> getFederationRegistrations() {
  887. if (federationRegistrations.isEmpty()) {
  888. federationRegistrations.addAll(FederationUtils.getFederationRegistrations(settings));
  889. }
  890. return federationRegistrations;
  891. }
  892. /**
  893. * Retrieve the specified federation registration.
  894. *
  895. * @param name
  896. * the name of the registration
  897. * @return a federation registration
  898. */
  899. public FederationModel getFederationRegistration(String url, String name) {
  900. // check registrations
  901. for (FederationModel r : getFederationRegistrations()) {
  902. if (r.name.equals(name) && r.url.equals(url)) {
  903. return r;
  904. }
  905. }
  906. // check the results
  907. for (FederationModel r : getFederationResultRegistrations()) {
  908. if (r.name.equals(name) && r.url.equals(url)) {
  909. return r;
  910. }
  911. }
  912. return null;
  913. }
  914. /**
  915. * Returns the list of federation sets.
  916. *
  917. * @return list of federation sets
  918. */
  919. public List<FederationSet> getFederationSets(String gitblitUrl) {
  920. List<FederationSet> list = new ArrayList<FederationSet>();
  921. // generate standard tokens
  922. for (FederationToken type : FederationToken.values()) {
  923. FederationSet fset = new FederationSet(type.toString(), type, getFederationToken(type));
  924. fset.repositories = getRepositories(gitblitUrl, fset.token);
  925. list.add(fset);
  926. }
  927. // generate tokens for federation sets
  928. for (String set : settings.getStrings(Keys.federation.sets)) {
  929. FederationSet fset = new FederationSet(set, FederationToken.REPOSITORIES,
  930. getFederationToken(set));
  931. fset.repositories = getRepositories(gitblitUrl, fset.token);
  932. list.add(fset);
  933. }
  934. return list;
  935. }
  936. /**
  937. * Returns the list of possible federation tokens for this Gitblit instance.
  938. *
  939. * @return list of federation tokens
  940. */
  941. public List<String> getFederationTokens() {
  942. List<String> tokens = new ArrayList<String>();
  943. // generate standard tokens
  944. for (FederationToken type : FederationToken.values()) {
  945. tokens.add(getFederationToken(type));
  946. }
  947. // generate tokens for federation sets
  948. for (String set : settings.getStrings(Keys.federation.sets)) {
  949. tokens.add(getFederationToken(set));
  950. }
  951. return tokens;
  952. }
  953. /**
  954. * Returns the specified federation token for this Gitblit instance.
  955. *
  956. * @param type
  957. * @return a federation token
  958. */
  959. public String getFederationToken(FederationToken type) {
  960. return getFederationToken(type.name());
  961. }
  962. /**
  963. * Returns the specified federation token for this Gitblit instance.
  964. *
  965. * @param value
  966. * @return a federation token
  967. */
  968. public String getFederationToken(String value) {
  969. String passphrase = settings.getString(Keys.federation.passphrase, "");
  970. return StringUtils.getSHA1(passphrase + "-" + value);
  971. }
  972. /**
  973. * Compares the provided token with this Gitblit instance's tokens and
  974. * determines if the requested permission may be granted to the token.
  975. *
  976. * @param req
  977. * @param token
  978. * @return true if the request can be executed
  979. */
  980. public boolean validateFederationRequest(FederationRequest req, String token) {
  981. String all = getFederationToken(FederationToken.ALL);
  982. String unr = getFederationToken(FederationToken.USERS_AND_REPOSITORIES);
  983. String jur = getFederationToken(FederationToken.REPOSITORIES);
  984. switch (req) {
  985. case PULL_REPOSITORIES:
  986. return token.equals(all) || token.equals(unr) || token.equals(jur);
  987. case PULL_USERS:
  988. return token.equals(all) || token.equals(unr);
  989. case PULL_SETTINGS:
  990. return token.equals(all);
  991. }
  992. return false;
  993. }
  994. /**
  995. * Acknowledge and cache the status of a remote Gitblit instance.
  996. *
  997. * @param identification
  998. * the identification of the pulling Gitblit instance
  999. * @param registration
  1000. * the registration from the pulling Gitblit instance
  1001. * @return true if acknowledged
  1002. */
  1003. public boolean acknowledgeFederationStatus(String identification, FederationModel registration) {
  1004. // reset the url to the identification of the pulling Gitblit instance
  1005. registration.url = identification;
  1006. String id = identification;
  1007. if (!StringUtils.isEmpty(registration.folder)) {
  1008. id += "-" + registration.folder;
  1009. }
  1010. federationPullResults.put(id, registration);
  1011. return true;
  1012. }
  1013. /**
  1014. * Returns the list of registration results.
  1015. *
  1016. * @return the list of registration results
  1017. */
  1018. public List<FederationModel> getFederationResultRegistrations() {
  1019. return new ArrayList<FederationModel>(federationPullResults.values());
  1020. }
  1021. /**
  1022. * Submit a federation proposal. The proposal is cached locally and the
  1023. * Gitblit administrator(s) are notified via email.
  1024. *
  1025. * @param proposal
  1026. * the proposal
  1027. * @param gitblitUrl
  1028. * the url of your gitblit instance to send an email to
  1029. * administrators
  1030. * @return true if the proposal was submitted
  1031. */
  1032. public boolean submitFederationProposal(FederationProposal proposal, String gitblitUrl) {
  1033. // convert proposal to json
  1034. String json = JsonUtils.toJsonString(proposal);
  1035. try {
  1036. // make the proposals folder
  1037. File proposalsFolder = new File(getString(Keys.federation.proposalsFolder, "proposals")
  1038. .trim());
  1039. proposalsFolder.mkdirs();
  1040. // cache json to a file
  1041. File file = new File(proposalsFolder, proposal.token + Constants.PROPOSAL_EXT);
  1042. com.gitblit.utils.FileUtils.writeContent(file, json);
  1043. } catch (Exception e) {
  1044. logger.error(MessageFormat.format("Failed to cache proposal from {0}", proposal.url), e);
  1045. }
  1046. // send an email, if possible
  1047. try {
  1048. Message message = mailExecutor.createMessageForAdministrators();
  1049. if (message != null) {
  1050. message.setSubject("Federation proposal from " + proposal.url);
  1051. message.setText("Please review the proposal @ " + gitblitUrl + "/proposal/"
  1052. + proposal.token);
  1053. mailExecutor.queue(message);
  1054. }
  1055. } catch (Throwable t) {
  1056. logger.error("Failed to notify administrators of proposal", t);
  1057. }
  1058. return true;
  1059. }
  1060. /**
  1061. * Returns the list of pending federation proposals
  1062. *
  1063. * @return list of federation proposals
  1064. */
  1065. public List<FederationProposal> getPendingFederationProposals() {
  1066. List<FederationProposal> list = new ArrayList<FederationProposal>();
  1067. File folder = new File(getString(Keys.federation.proposalsFolder, "proposals").trim());
  1068. if (folder.exists()) {
  1069. File[] files = folder.listFiles(new FileFilter() {
  1070. @Override
  1071. public boolean accept(File file) {
  1072. return file.isFile()
  1073. && file.getName().toLowerCase().endsWith(Constants.PROPOSAL_EXT);
  1074. }
  1075. });
  1076. for (File file : files) {
  1077. String json = com.gitblit.utils.FileUtils.readContent(file, null);
  1078. FederationProposal proposal = JsonUtils.fromJsonString(json,
  1079. FederationProposal.class);
  1080. list.add(proposal);
  1081. }
  1082. }
  1083. return list;
  1084. }
  1085. /**
  1086. * Get repositories for the specified token.
  1087. *
  1088. * @param gitblitUrl
  1089. * the base url of this gitblit instance
  1090. * @param token
  1091. * the federation token
  1092. * @return a map of <cloneurl, RepositoryModel>
  1093. */
  1094. public Map<String, RepositoryModel> getRepositories(String gitblitUrl, String token) {
  1095. Map<String, String> federationSets = new HashMap<String, String>();
  1096. for (String set : getStrings(Keys.federation.sets)) {
  1097. federationSets.put(getFederationToken(set), set);
  1098. }
  1099. // Determine the Gitblit clone url
  1100. StringBuilder sb = new StringBuilder();
  1101. sb.append(gitblitUrl);
  1102. sb.append(Constants.GIT_PATH);
  1103. sb.append("{0}");
  1104. String cloneUrl = sb.toString();
  1105. // Retrieve all available repositories
  1106. UserModel user = new UserModel(Constants.FEDERATION_USER);
  1107. user.canAdmin = true;
  1108. List<RepositoryModel> list = getRepositoryModels(user);
  1109. // create the [cloneurl, repositoryModel] map
  1110. Map<String, RepositoryModel> repositories = new HashMap<String, RepositoryModel>();
  1111. for (RepositoryModel model : list) {
  1112. // by default, setup the url for THIS repository
  1113. String url = MessageFormat.format(cloneUrl, model.name);
  1114. switch (model.federationStrategy) {
  1115. case EXCLUDE:
  1116. // skip this repository
  1117. continue;
  1118. case FEDERATE_ORIGIN:
  1119. // federate the origin, if it is defined
  1120. if (!StringUtils.isEmpty(model.origin)) {
  1121. url = model.origin;
  1122. }
  1123. break;
  1124. }
  1125. if (federationSets.containsKey(token)) {
  1126. // include repositories only for federation set
  1127. String set = federationSets.get(token);
  1128. if (model.federationSets.contains(set)) {
  1129. repositories.put(url, model);
  1130. }
  1131. } else {
  1132. // standard federation token for ALL
  1133. repositories.put(url, model);
  1134. }
  1135. }
  1136. return repositories;
  1137. }
  1138. /**
  1139. * Creates a proposal from the token.
  1140. *
  1141. * @param gitblitUrl
  1142. * the url of this Gitblit instance
  1143. * @param token
  1144. * @return a potential proposal
  1145. */
  1146. public FederationProposal createFederationProposal(String gitblitUrl, String token) {
  1147. FederationToken tokenType = FederationToken.REPOSITORIES;
  1148. for (FederationToken type : FederationToken.values()) {
  1149. if (token.equals(getFederationToken(type))) {
  1150. tokenType = type;
  1151. break;
  1152. }
  1153. }
  1154. Map<String, RepositoryModel> repositories = getRepositories(gitblitUrl, token);
  1155. FederationProposal proposal = new FederationProposal(gitblitUrl, tokenType, token,
  1156. repositories);
  1157. return proposal;
  1158. }
  1159. /**
  1160. * Returns the proposal identified by the supplied token.
  1161. *
  1162. * @param token
  1163. * @return the specified proposal or null
  1164. */
  1165. public FederationProposal getPendingFederationProposal(String token) {
  1166. List<FederationProposal> list = getPendingFederationProposals();
  1167. for (FederationProposal proposal : list) {
  1168. if (proposal.token.equals(token)) {
  1169. return proposal;
  1170. }
  1171. }
  1172. return null;
  1173. }
  1174. /**
  1175. * Deletes a pending federation proposal.
  1176. *
  1177. * @param a
  1178. * proposal
  1179. * @return true if the proposal was deleted
  1180. */
  1181. public boolean deletePendingFederationProposal(FederationProposal proposal) {
  1182. File folder = new File(getString(Keys.federation.proposalsFolder, "proposals").trim());
  1183. File file = new File(folder, proposal.token + Constants.PROPOSAL_EXT);
  1184. return file.delete();
  1185. }
  1186. /**
  1187. * Notify the administrators by email.
  1188. *
  1189. * @param subject
  1190. * @param message
  1191. */
  1192. public void notifyAdministrators(String subject, String message) {
  1193. try {
  1194. Message mail = mailExecutor.createMessageForAdministrators();
  1195. if (mail != null) {
  1196. mail.setSubject(subject);
  1197. mail.setText(message);
  1198. mailExecutor.queue(mail);
  1199. }
  1200. } catch (MessagingException e) {
  1201. logger.error("Messaging error", e);
  1202. }
  1203. }
  1204. /**
  1205. * Returns the descriptions/comments of the Gitblit config settings.
  1206. *
  1207. * @return SettingsModel
  1208. */
  1209. public ServerSettings getSettingsModel() {
  1210. // ensure that the current values are updated in the setting models
  1211. for (String key : settings.getAllKeys(null)) {
  1212. SettingModel setting = settingsModel.get(key);
  1213. if (setting != null) {
  1214. setting.currentValue = settings.getString(key, "");
  1215. }
  1216. }
  1217. return settingsModel;
  1218. }
  1219. /**
  1220. * Parse the properties file and aggregate all the comments by the setting
  1221. * key. A setting model tracks the current value, the default value, the
  1222. * description of the setting and and directives about the setting.
  1223. *
  1224. * @return Map<String, SettingModel>
  1225. */
  1226. private ServerSettings loadSettingModels() {
  1227. ServerSettings settingsModel = new ServerSettings();
  1228. try {
  1229. // Read bundled Gitblit properties to extract setting descriptions.
  1230. // This copy is pristine and only used for populating the setting
  1231. // models map.
  1232. InputStream is = servletContext.getResourceAsStream("/WEB-INF/reference.properties");
  1233. BufferedReader propertiesReader = new BufferedReader(new InputStreamReader(is));
  1234. StringBuilder description = new StringBuilder();
  1235. SettingModel setting = new SettingModel();
  1236. String line = null;
  1237. while ((line = propertiesReader.readLine()) != null) {
  1238. if (line.length() == 0) {
  1239. description.setLength(0);
  1240. setting = new SettingModel();
  1241. } else {
  1242. if (line.charAt(0) == '#') {
  1243. if (line.length() > 1) {
  1244. String text = line.substring(1).trim();
  1245. if (SettingModel.CASE_SENSITIVE.equals(text)) {
  1246. setting.caseSensitive = true;
  1247. } else if (SettingModel.RESTART_REQUIRED.equals(text)) {
  1248. setting.restartRequired = true;
  1249. } else if (SettingModel.SPACE_DELIMITED.equals(text)) {
  1250. setting.spaceDelimited = true;
  1251. } else if (text.startsWith(SettingModel.SINCE)) {
  1252. try {
  1253. setting.since = text.split(" ")[1];
  1254. } catch (Exception e) {
  1255. setting.since = text;
  1256. }
  1257. } else {
  1258. description.append(text);
  1259. description.append('\n');
  1260. }
  1261. }
  1262. } else {
  1263. String[] kvp = line.split("=", 2);
  1264. String key = kvp[0].trim();
  1265. setting.name = key;
  1266. setting.defaultValue = kvp[1].trim();
  1267. setting.currentValue = setting.defaultValue;
  1268. setting.description = description.toString().trim();
  1269. settingsModel.add(setting);
  1270. description.setLength(0);
  1271. setting = new SettingModel();
  1272. }
  1273. }
  1274. }
  1275. propertiesReader.close();
  1276. } catch (NullPointerException e) {
  1277. logger.error("Failed to find resource copy of gitblit.properties");
  1278. } catch (IOException e) {
  1279. logger.error("Failed to load resource copy of gitblit.properties");
  1280. }
  1281. return settingsModel;
  1282. }
  1283. /**
  1284. * Configure the Gitblit singleton with the specified settings source. This
  1285. * source may be file settings (Gitblit GO) or may be web.xml settings
  1286. * (Gitblit WAR).
  1287. *
  1288. * @param settings
  1289. */
  1290. public void configureContext(IStoredSettings settings, boolean startFederation) {
  1291. logger.info("Reading configuration from " + settings.toString());
  1292. this.settings = settings;
  1293. repositoriesFolder = new File(settings.getString(Keys.git.repositoriesFolder, "git"));
  1294. logger.info("Git repositories folder " + repositoriesFolder.getAbsolutePath());
  1295. repositoryResolver = new FileResolver<Void>(repositoriesFolder, exportAll);
  1296. serverStatus = new ServerStatus(isGO());
  1297. String realm = settings.getString(Keys.realm.userService, "users.properties");
  1298. IUserService loginService = null;
  1299. try {
  1300. // check to see if this "file" is a login service class
  1301. Class<?> realmClass = Class.forName(realm);
  1302. if (IUserService.class.isAssignableFrom(realmClass)) {
  1303. loginService = (IUserService) realmClass.newInstance();
  1304. }
  1305. } catch (Throwable t) {
  1306. // not a login service class or class could not be instantiated.
  1307. // try to use default file login service
  1308. File realmFile = new File(realm);
  1309. if (!realmFile.exists()) {
  1310. try {
  1311. realmFile.createNewFile();
  1312. } catch (IOException x) {
  1313. logger.error(
  1314. MessageFormat.format("COULD NOT CREATE REALM FILE {0}!", realmFile), x);
  1315. }
  1316. }
  1317. loginService = new FileUserService(realmFile);
  1318. }
  1319. setUserService(loginService);
  1320. mailExecutor = new MailExecutor(settings);
  1321. if (mailExecutor.isReady()) {
  1322. scheduledExecutor.scheduleAtFixedRate(mailExecutor, 1, 2, TimeUnit.MINUTES);
  1323. } else {
  1324. logger.warn("Mail server is not properly configured. Mail services disabled.");
  1325. }
  1326. if (startFederation) {
  1327. configureFederation();
  1328. }
  1329. }
  1330. /**
  1331. * Configure Gitblit from the web.xml, if no configuration has already been
  1332. * specified.
  1333. *
  1334. * @see ServletContextListener.contextInitialize(ServletContextEvent)
  1335. */
  1336. @Override
  1337. public void contextInitialized(ServletContextEvent contextEvent) {
  1338. servletContext = contextEvent.getServletContext();
  1339. settingsModel = loadSettingModels();
  1340. if (settings == null) {
  1341. // Gitblit WAR is running in a servlet container
  1342. WebXmlSettings webxmlSettings = new WebXmlSettings(contextEvent.getServletContext());
  1343. configureContext(webxmlSettings, true);
  1344. }
  1345. serverStatus.servletContainer = servletContext.getServerInfo();
  1346. }
  1347. /**
  1348. * Gitblit is being shutdown either because the servlet container is
  1349. * shutting down or because the servlet container is re-deploying Gitblit.
  1350. */
  1351. @Override
  1352. public void contextDestroyed(ServletContextEvent contextEvent) {
  1353. logger.info("Gitblit context destroyed by servlet container.");
  1354. scheduledExecutor.shutdownNow();
  1355. }
  1356. }