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.

RepositoryManager.java 63KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829
  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.manager;
  17. import java.io.File;
  18. import java.io.FileFilter;
  19. import java.io.IOException;
  20. import java.lang.reflect.Field;
  21. import java.net.URI;
  22. import java.net.URISyntaxException;
  23. import java.text.MessageFormat;
  24. import java.text.SimpleDateFormat;
  25. import java.util.ArrayList;
  26. import java.util.Arrays;
  27. import java.util.Calendar;
  28. import java.util.Collection;
  29. import java.util.Collections;
  30. import java.util.Date;
  31. import java.util.HashSet;
  32. import java.util.LinkedHashMap;
  33. import java.util.LinkedHashSet;
  34. import java.util.List;
  35. import java.util.Map;
  36. import java.util.Map.Entry;
  37. import java.util.Set;
  38. import java.util.TreeSet;
  39. import java.util.concurrent.ConcurrentHashMap;
  40. import java.util.concurrent.Executors;
  41. import java.util.concurrent.ScheduledExecutorService;
  42. import java.util.concurrent.TimeUnit;
  43. import java.util.concurrent.atomic.AtomicInteger;
  44. import java.util.concurrent.atomic.AtomicReference;
  45. import org.eclipse.jgit.lib.Repository;
  46. import org.eclipse.jgit.lib.RepositoryCache;
  47. import org.eclipse.jgit.lib.RepositoryCache.FileKey;
  48. import org.eclipse.jgit.lib.StoredConfig;
  49. import org.eclipse.jgit.storage.file.FileBasedConfig;
  50. import org.eclipse.jgit.storage.file.WindowCacheConfig;
  51. import org.eclipse.jgit.util.FS;
  52. import org.eclipse.jgit.util.FileUtils;
  53. import org.slf4j.Logger;
  54. import org.slf4j.LoggerFactory;
  55. import com.gitblit.Constants;
  56. import com.gitblit.Constants.AccessPermission;
  57. import com.gitblit.Constants.AccessRestrictionType;
  58. import com.gitblit.Constants.AuthorizationControl;
  59. import com.gitblit.Constants.CommitMessageRenderer;
  60. import com.gitblit.Constants.FederationStrategy;
  61. import com.gitblit.Constants.PermissionType;
  62. import com.gitblit.Constants.RegistrantType;
  63. import com.gitblit.GitBlitException;
  64. import com.gitblit.IStoredSettings;
  65. import com.gitblit.Keys;
  66. import com.gitblit.models.ForkModel;
  67. import com.gitblit.models.Metric;
  68. import com.gitblit.models.RefModel;
  69. import com.gitblit.models.RegistrantAccessPermission;
  70. import com.gitblit.models.RepositoryModel;
  71. import com.gitblit.models.SearchResult;
  72. import com.gitblit.models.TeamModel;
  73. import com.gitblit.models.UserModel;
  74. import com.gitblit.service.GarbageCollectorService;
  75. import com.gitblit.service.LuceneService;
  76. import com.gitblit.service.MirrorService;
  77. import com.gitblit.utils.ArrayUtils;
  78. import com.gitblit.utils.ByteFormat;
  79. import com.gitblit.utils.CommitCache;
  80. import com.gitblit.utils.DeepCopier;
  81. import com.gitblit.utils.JGitUtils;
  82. import com.gitblit.utils.JGitUtils.LastChange;
  83. import com.gitblit.utils.MetricUtils;
  84. import com.gitblit.utils.ModelUtils;
  85. import com.gitblit.utils.ObjectCache;
  86. import com.gitblit.utils.StringUtils;
  87. import com.gitblit.utils.TimeUtils;
  88. /**
  89. * Repository manager creates, updates, deletes and caches git repositories. It
  90. * also starts services to mirror, index, and cleanup repositories.
  91. *
  92. * @author James Moger
  93. *
  94. */
  95. public class RepositoryManager implements IRepositoryManager {
  96. private final Logger logger = LoggerFactory.getLogger(getClass());
  97. private final ScheduledExecutorService scheduledExecutor = Executors.newScheduledThreadPool(5);
  98. private final ObjectCache<Long> repositorySizeCache = new ObjectCache<Long>();
  99. private final ObjectCache<List<Metric>> repositoryMetricsCache = new ObjectCache<List<Metric>>();
  100. private final Map<String, RepositoryModel> repositoryListCache = new ConcurrentHashMap<String, RepositoryModel>();
  101. private final AtomicReference<String> repositoryListSettingsChecksum = new AtomicReference<String>("");
  102. private final IStoredSettings settings;
  103. private final IRuntimeManager runtimeManager;
  104. private final IUserManager userManager;
  105. private final File repositoriesFolder;
  106. private LuceneService luceneExecutor;
  107. private GarbageCollectorService gcExecutor;
  108. private MirrorService mirrorExecutor;
  109. public RepositoryManager(
  110. IRuntimeManager runtimeManager,
  111. IUserManager userManager) {
  112. this.settings = runtimeManager.getSettings();
  113. this.runtimeManager = runtimeManager;
  114. this.userManager = userManager;
  115. this.repositoriesFolder = runtimeManager.getFileOrFolder(Keys.git.repositoriesFolder, "${baseFolder}/git");
  116. }
  117. @Override
  118. public RepositoryManager start() {
  119. logger.info("Repositories folder : {}", repositoriesFolder.getAbsolutePath());
  120. // initialize utilities
  121. String prefix = settings.getString(Keys.git.userRepositoryPrefix, "~");
  122. ModelUtils.setUserRepoPrefix(prefix);
  123. // calculate repository list settings checksum for future config changes
  124. repositoryListSettingsChecksum.set(getRepositoryListSettingsChecksum());
  125. // build initial repository list
  126. if (settings.getBoolean(Keys.git.cacheRepositoryList, true)) {
  127. logger.info("Identifying repositories...");
  128. getRepositoryList();
  129. }
  130. configureLuceneIndexing();
  131. configureGarbageCollector();
  132. configureMirrorExecutor();
  133. configureJGit();
  134. configureCommitCache();
  135. confirmWriteAccess();
  136. return this;
  137. }
  138. @Override
  139. public RepositoryManager stop() {
  140. scheduledExecutor.shutdownNow();
  141. luceneExecutor.close();
  142. gcExecutor.close();
  143. mirrorExecutor.close();
  144. closeAll();
  145. return this;
  146. }
  147. /**
  148. * Returns the most recent change date of any repository served by Gitblit.
  149. *
  150. * @return a date
  151. */
  152. @Override
  153. public Date getLastActivityDate() {
  154. Date date = null;
  155. for (String name : getRepositoryList()) {
  156. Repository r = getRepository(name);
  157. Date lastChange = JGitUtils.getLastChange(r).when;
  158. r.close();
  159. if (lastChange != null && (date == null || lastChange.after(date))) {
  160. date = lastChange;
  161. }
  162. }
  163. return date;
  164. }
  165. /**
  166. * Returns the path of the repositories folder. This method checks to see if
  167. * Gitblit is running on a cloud service and may return an adjusted path.
  168. *
  169. * @return the repositories folder path
  170. */
  171. @Override
  172. public File getRepositoriesFolder() {
  173. return repositoriesFolder;
  174. }
  175. /**
  176. * Returns the path of the Groovy folder. This method checks to see if
  177. * Gitblit is running on a cloud service and may return an adjusted path.
  178. *
  179. * @return the Groovy scripts folder path
  180. */
  181. @Override
  182. public File getHooksFolder() {
  183. return runtimeManager.getFileOrFolder(Keys.groovy.scriptsFolder, "${baseFolder}/groovy");
  184. }
  185. /**
  186. * Returns the path of the Groovy Grape folder. This method checks to see if
  187. * Gitblit is running on a cloud service and may return an adjusted path.
  188. *
  189. * @return the Groovy Grape folder path
  190. */
  191. @Override
  192. public File getGrapesFolder() {
  193. return runtimeManager.getFileOrFolder(Keys.groovy.grapeFolder, "${baseFolder}/groovy/grape");
  194. }
  195. /**
  196. *
  197. * @return true if we are running the gc executor
  198. */
  199. @Override
  200. public boolean isCollectingGarbage() {
  201. return gcExecutor != null && gcExecutor.isRunning();
  202. }
  203. /**
  204. * Returns true if Gitblit is actively collecting garbage in this repository.
  205. *
  206. * @param repositoryName
  207. * @return true if actively collecting garbage
  208. */
  209. @Override
  210. public boolean isCollectingGarbage(String repositoryName) {
  211. return gcExecutor != null && gcExecutor.isCollectingGarbage(repositoryName);
  212. }
  213. /**
  214. * Returns the effective list of permissions for this user, taking into account
  215. * team memberships, ownerships.
  216. *
  217. * @param user
  218. * @return the effective list of permissions for the user
  219. */
  220. @Override
  221. public List<RegistrantAccessPermission> getUserAccessPermissions(UserModel user) {
  222. if (StringUtils.isEmpty(user.username)) {
  223. // new user
  224. return new ArrayList<RegistrantAccessPermission>();
  225. }
  226. Set<RegistrantAccessPermission> set = new LinkedHashSet<RegistrantAccessPermission>();
  227. set.addAll(user.getRepositoryPermissions());
  228. // Flag missing repositories
  229. for (RegistrantAccessPermission permission : set) {
  230. if (permission.mutable && PermissionType.EXPLICIT.equals(permission.permissionType)) {
  231. RepositoryModel rm = getRepositoryModel(permission.registrant);
  232. if (rm == null) {
  233. permission.permissionType = PermissionType.MISSING;
  234. permission.mutable = false;
  235. continue;
  236. }
  237. }
  238. }
  239. // TODO reconsider ownership as a user property
  240. // manually specify personal repository ownerships
  241. for (RepositoryModel rm : repositoryListCache.values()) {
  242. if (rm.isUsersPersonalRepository(user.username) || rm.isOwner(user.username)) {
  243. RegistrantAccessPermission rp = new RegistrantAccessPermission(rm.name, AccessPermission.REWIND,
  244. PermissionType.OWNER, RegistrantType.REPOSITORY, null, false);
  245. // user may be owner of a repository to which they've inherited
  246. // a team permission, replace any existing perm with owner perm
  247. set.remove(rp);
  248. set.add(rp);
  249. }
  250. }
  251. List<RegistrantAccessPermission> list = new ArrayList<RegistrantAccessPermission>(set);
  252. Collections.sort(list);
  253. return list;
  254. }
  255. /**
  256. * Returns the list of users and their access permissions for the specified
  257. * repository including permission source information such as the team or
  258. * regular expression which sets the permission.
  259. *
  260. * @param repository
  261. * @return a list of RegistrantAccessPermissions
  262. */
  263. @Override
  264. public List<RegistrantAccessPermission> getUserAccessPermissions(RepositoryModel repository) {
  265. List<RegistrantAccessPermission> list = new ArrayList<RegistrantAccessPermission>();
  266. if (AccessRestrictionType.NONE.equals(repository.accessRestriction)) {
  267. // no permissions needed, REWIND for everyone!
  268. return list;
  269. }
  270. if (AuthorizationControl.AUTHENTICATED.equals(repository.authorizationControl)) {
  271. // no permissions needed, REWIND for authenticated!
  272. return list;
  273. }
  274. // NAMED users and teams
  275. for (UserModel user : userManager.getAllUsers()) {
  276. RegistrantAccessPermission ap = user.getRepositoryPermission(repository);
  277. if (ap.permission.exceeds(AccessPermission.NONE)) {
  278. list.add(ap);
  279. }
  280. }
  281. return list;
  282. }
  283. /**
  284. * Sets the access permissions to the specified repository for the specified users.
  285. *
  286. * @param repository
  287. * @param permissions
  288. * @return true if the user models have been updated
  289. */
  290. @Override
  291. public boolean setUserAccessPermissions(RepositoryModel repository, Collection<RegistrantAccessPermission> permissions) {
  292. List<UserModel> users = new ArrayList<UserModel>();
  293. for (RegistrantAccessPermission up : permissions) {
  294. if (up.mutable) {
  295. // only set editable defined permissions
  296. UserModel user = userManager.getUserModel(up.registrant);
  297. user.setRepositoryPermission(repository.name, up.permission);
  298. users.add(user);
  299. }
  300. }
  301. return userManager.updateUserModels(users);
  302. }
  303. /**
  304. * Returns the list of all users who have an explicit access permission
  305. * for the specified repository.
  306. *
  307. * @see IUserService.getUsernamesForRepositoryRole(String)
  308. * @param repository
  309. * @return list of all usernames that have an access permission for the repository
  310. */
  311. @Override
  312. public List<String> getRepositoryUsers(RepositoryModel repository) {
  313. return userManager.getUsernamesForRepositoryRole(repository.name);
  314. }
  315. /**
  316. * Returns the list of teams and their access permissions for the specified
  317. * repository including the source of the permission such as the admin flag
  318. * or a regular expression.
  319. *
  320. * @param repository
  321. * @return a list of RegistrantAccessPermissions
  322. */
  323. @Override
  324. public List<RegistrantAccessPermission> getTeamAccessPermissions(RepositoryModel repository) {
  325. List<RegistrantAccessPermission> list = new ArrayList<RegistrantAccessPermission>();
  326. for (TeamModel team : userManager.getAllTeams()) {
  327. RegistrantAccessPermission ap = team.getRepositoryPermission(repository);
  328. if (ap.permission.exceeds(AccessPermission.NONE)) {
  329. list.add(ap);
  330. }
  331. }
  332. Collections.sort(list);
  333. return list;
  334. }
  335. /**
  336. * Sets the access permissions to the specified repository for the specified teams.
  337. *
  338. * @param repository
  339. * @param permissions
  340. * @return true if the team models have been updated
  341. */
  342. @Override
  343. public boolean setTeamAccessPermissions(RepositoryModel repository, Collection<RegistrantAccessPermission> permissions) {
  344. List<TeamModel> teams = new ArrayList<TeamModel>();
  345. for (RegistrantAccessPermission tp : permissions) {
  346. if (tp.mutable) {
  347. // only set explicitly defined access permissions
  348. TeamModel team = userManager.getTeamModel(tp.registrant);
  349. team.setRepositoryPermission(repository.name, tp.permission);
  350. teams.add(team);
  351. }
  352. }
  353. return userManager.updateTeamModels(teams);
  354. }
  355. /**
  356. * Returns the list of all teams who have an explicit access permission for
  357. * the specified repository.
  358. *
  359. * @see IUserService.getTeamnamesForRepositoryRole(String)
  360. * @param repository
  361. * @return list of all teamnames with explicit access permissions to the repository
  362. */
  363. @Override
  364. public List<String> getRepositoryTeams(RepositoryModel repository) {
  365. return userManager.getTeamNamesForRepositoryRole(repository.name);
  366. }
  367. /**
  368. * Adds the repository to the list of cached repositories if Gitblit is
  369. * configured to cache the repository list.
  370. *
  371. * @param model
  372. */
  373. @Override
  374. public void addToCachedRepositoryList(RepositoryModel model) {
  375. if (settings.getBoolean(Keys.git.cacheRepositoryList, true)) {
  376. repositoryListCache.put(model.name.toLowerCase(), model);
  377. // update the fork origin repository with this repository clone
  378. if (!StringUtils.isEmpty(model.originRepository)) {
  379. if (repositoryListCache.containsKey(model.originRepository)) {
  380. RepositoryModel origin = repositoryListCache.get(model.originRepository);
  381. origin.addFork(model.name);
  382. }
  383. }
  384. }
  385. }
  386. /**
  387. * Removes the repository from the list of cached repositories.
  388. *
  389. * @param name
  390. * @return the model being removed
  391. */
  392. private RepositoryModel removeFromCachedRepositoryList(String name) {
  393. if (StringUtils.isEmpty(name)) {
  394. return null;
  395. }
  396. return repositoryListCache.remove(name.toLowerCase());
  397. }
  398. /**
  399. * Clears all the cached metadata for the specified repository.
  400. *
  401. * @param repositoryName
  402. */
  403. private void clearRepositoryMetadataCache(String repositoryName) {
  404. repositorySizeCache.remove(repositoryName);
  405. repositoryMetricsCache.remove(repositoryName);
  406. CommitCache.instance().clear(repositoryName);
  407. }
  408. /**
  409. * Resets the repository list cache.
  410. *
  411. */
  412. @Override
  413. public void resetRepositoryListCache() {
  414. logger.info("Repository cache manually reset");
  415. repositoryListCache.clear();
  416. repositorySizeCache.clear();
  417. repositoryMetricsCache.clear();
  418. CommitCache.instance().clear();
  419. }
  420. /**
  421. * Calculate the checksum of settings that affect the repository list cache.
  422. * @return a checksum
  423. */
  424. private String getRepositoryListSettingsChecksum() {
  425. StringBuilder ns = new StringBuilder();
  426. ns.append(settings.getString(Keys.git.cacheRepositoryList, "")).append('\n');
  427. ns.append(settings.getString(Keys.git.onlyAccessBareRepositories, "")).append('\n');
  428. ns.append(settings.getString(Keys.git.searchRepositoriesSubfolders, "")).append('\n');
  429. ns.append(settings.getString(Keys.git.searchRecursionDepth, "")).append('\n');
  430. ns.append(settings.getString(Keys.git.searchExclusions, "")).append('\n');
  431. String checksum = StringUtils.getSHA1(ns.toString());
  432. return checksum;
  433. }
  434. /**
  435. * Compare the last repository list setting checksum to the current checksum.
  436. * If different then clear the cache so that it may be rebuilt.
  437. *
  438. * @return true if the cached repository list is valid since the last check
  439. */
  440. private boolean isValidRepositoryList() {
  441. String newChecksum = getRepositoryListSettingsChecksum();
  442. boolean valid = newChecksum.equals(repositoryListSettingsChecksum.get());
  443. repositoryListSettingsChecksum.set(newChecksum);
  444. if (!valid && settings.getBoolean(Keys.git.cacheRepositoryList, true)) {
  445. logger.info("Repository list settings have changed. Clearing repository list cache.");
  446. repositoryListCache.clear();
  447. }
  448. return valid;
  449. }
  450. /**
  451. * Returns the list of all repositories available to Gitblit. This method
  452. * does not consider user access permissions.
  453. *
  454. * @return list of all repositories
  455. */
  456. @Override
  457. public List<String> getRepositoryList() {
  458. if (repositoryListCache.size() == 0 || !isValidRepositoryList()) {
  459. // we are not caching OR we have not yet cached OR the cached list is invalid
  460. long startTime = System.currentTimeMillis();
  461. List<String> repositories = JGitUtils.getRepositoryList(repositoriesFolder,
  462. settings.getBoolean(Keys.git.onlyAccessBareRepositories, false),
  463. settings.getBoolean(Keys.git.searchRepositoriesSubfolders, true),
  464. settings.getInteger(Keys.git.searchRecursionDepth, -1),
  465. settings.getStrings(Keys.git.searchExclusions));
  466. if (!settings.getBoolean(Keys.git.cacheRepositoryList, true)) {
  467. // we are not caching
  468. StringUtils.sortRepositorynames(repositories);
  469. return repositories;
  470. } else {
  471. // we are caching this list
  472. String msg = "{0} repositories identified in {1} msecs";
  473. if (settings.getBoolean(Keys.web.showRepositorySizes, true)) {
  474. // optionally (re)calculate repository sizes
  475. msg = "{0} repositories identified with calculated folder sizes in {1} msecs";
  476. }
  477. for (String repository : repositories) {
  478. getRepositoryModel(repository);
  479. }
  480. // rebuild fork networks
  481. for (RepositoryModel model : repositoryListCache.values()) {
  482. if (!StringUtils.isEmpty(model.originRepository)) {
  483. if (repositoryListCache.containsKey(model.originRepository)) {
  484. RepositoryModel origin = repositoryListCache.get(model.originRepository);
  485. origin.addFork(model.name);
  486. }
  487. }
  488. }
  489. long duration = System.currentTimeMillis() - startTime;
  490. logger.info(MessageFormat.format(msg, repositoryListCache.size(), duration));
  491. }
  492. }
  493. // return sorted copy of cached list
  494. List<String> list = new ArrayList<String>();
  495. for (RepositoryModel model : repositoryListCache.values()) {
  496. list.add(model.name);
  497. }
  498. StringUtils.sortRepositorynames(list);
  499. return list;
  500. }
  501. /**
  502. * Returns the JGit repository for the specified name.
  503. *
  504. * @param repositoryName
  505. * @return repository or null
  506. */
  507. @Override
  508. public Repository getRepository(String repositoryName) {
  509. return getRepository(repositoryName, true);
  510. }
  511. /**
  512. * Returns the JGit repository for the specified name.
  513. *
  514. * @param repositoryName
  515. * @param logError
  516. * @return repository or null
  517. */
  518. @Override
  519. public Repository getRepository(String repositoryName, boolean logError) {
  520. // Decode url-encoded repository name (issue-278)
  521. // http://stackoverflow.com/questions/17183110
  522. repositoryName = repositoryName.replace("%7E", "~").replace("%7e", "~");
  523. if (isCollectingGarbage(repositoryName)) {
  524. logger.warn(MessageFormat.format("Rejecting request for {0}, busy collecting garbage!", repositoryName));
  525. return null;
  526. }
  527. File dir = FileKey.resolve(new File(repositoriesFolder, repositoryName), FS.DETECTED);
  528. if (dir == null)
  529. return null;
  530. Repository r = null;
  531. try {
  532. FileKey key = FileKey.exact(dir, FS.DETECTED);
  533. r = RepositoryCache.open(key, true);
  534. } catch (IOException e) {
  535. if (logError) {
  536. logger.error("GitBlit.getRepository(String) failed to find "
  537. + new File(repositoriesFolder, repositoryName).getAbsolutePath());
  538. }
  539. }
  540. return r;
  541. }
  542. /**
  543. * Returns the list of repository models that are accessible to the user.
  544. *
  545. * @param user
  546. * @return list of repository models accessible to user
  547. */
  548. @Override
  549. public List<RepositoryModel> getRepositoryModels(UserModel user) {
  550. long methodStart = System.currentTimeMillis();
  551. List<String> list = getRepositoryList();
  552. List<RepositoryModel> repositories = new ArrayList<RepositoryModel>();
  553. for (String repo : list) {
  554. RepositoryModel model = getRepositoryModel(user, repo);
  555. if (model != null) {
  556. if (!model.hasCommits) {
  557. // only add empty repositories that user can push to
  558. if (UserModel.ANONYMOUS.canPush(model)
  559. || user != null && user.canPush(model)) {
  560. repositories.add(model);
  561. }
  562. } else {
  563. repositories.add(model);
  564. }
  565. }
  566. }
  567. long duration = System.currentTimeMillis() - methodStart;
  568. logger.info(MessageFormat.format("{0} repository models loaded for {1} in {2} msecs",
  569. repositories.size(), user == null ? "anonymous" : user.username, duration));
  570. return repositories;
  571. }
  572. /**
  573. * Returns a repository model if the repository exists and the user may
  574. * access the repository.
  575. *
  576. * @param user
  577. * @param repositoryName
  578. * @return repository model or null
  579. */
  580. @Override
  581. public RepositoryModel getRepositoryModel(UserModel user, String repositoryName) {
  582. RepositoryModel model = getRepositoryModel(repositoryName);
  583. if (model == null) {
  584. return null;
  585. }
  586. if (user == null) {
  587. user = UserModel.ANONYMOUS;
  588. }
  589. if (user.canView(model)) {
  590. return model;
  591. }
  592. return null;
  593. }
  594. /**
  595. * Returns the repository model for the specified repository. This method
  596. * does not consider user access permissions.
  597. *
  598. * @param repositoryName
  599. * @return repository model or null
  600. */
  601. @Override
  602. public RepositoryModel getRepositoryModel(String repositoryName) {
  603. // Decode url-encoded repository name (issue-278)
  604. // http://stackoverflow.com/questions/17183110
  605. repositoryName = repositoryName.replace("%7E", "~").replace("%7e", "~");
  606. if (!repositoryListCache.containsKey(repositoryName)) {
  607. RepositoryModel model = loadRepositoryModel(repositoryName);
  608. if (model == null) {
  609. return null;
  610. }
  611. addToCachedRepositoryList(model);
  612. return DeepCopier.copy(model);
  613. }
  614. // cached model
  615. RepositoryModel model = repositoryListCache.get(repositoryName.toLowerCase());
  616. if (gcExecutor.isCollectingGarbage(model.name)) {
  617. // Gitblit is busy collecting garbage, use our cached model
  618. RepositoryModel rm = DeepCopier.copy(model);
  619. rm.isCollectingGarbage = true;
  620. return rm;
  621. }
  622. // check for updates
  623. Repository r = getRepository(model.name);
  624. if (r == null) {
  625. // repository is missing
  626. removeFromCachedRepositoryList(repositoryName);
  627. logger.error(MessageFormat.format("Repository \"{0}\" is missing! Removing from cache.", repositoryName));
  628. return null;
  629. }
  630. FileBasedConfig config = (FileBasedConfig) getRepositoryConfig(r);
  631. if (config.isOutdated()) {
  632. // reload model
  633. logger.debug(MessageFormat.format("Config for \"{0}\" has changed. Reloading model and updating cache.", repositoryName));
  634. model = loadRepositoryModel(model.name);
  635. removeFromCachedRepositoryList(model.name);
  636. addToCachedRepositoryList(model);
  637. } else {
  638. // update a few repository parameters
  639. if (!model.hasCommits) {
  640. // update hasCommits, assume a repository only gains commits :)
  641. model.hasCommits = JGitUtils.hasCommits(r);
  642. }
  643. updateLastChangeFields(r, model);
  644. }
  645. r.close();
  646. // return a copy of the cached model
  647. return DeepCopier.copy(model);
  648. }
  649. /**
  650. * Returns the star count of the repository.
  651. *
  652. * @param repository
  653. * @return the star count
  654. */
  655. @Override
  656. public long getStarCount(RepositoryModel repository) {
  657. long count = 0;
  658. for (UserModel user : userManager.getAllUsers()) {
  659. if (user.getPreferences().isStarredRepository(repository.name)) {
  660. count++;
  661. }
  662. }
  663. return count;
  664. }
  665. /**
  666. * Workaround JGit. I need to access the raw config object directly in order
  667. * to see if the config is dirty so that I can reload a repository model.
  668. * If I use the stock JGit method to get the config it already reloads the
  669. * config. If the config changes are made within Gitblit this is fine as
  670. * the returned config will still be flagged as dirty. BUT... if the config
  671. * is manipulated outside Gitblit then it fails to recognize this as dirty.
  672. *
  673. * @param r
  674. * @return a config
  675. */
  676. private StoredConfig getRepositoryConfig(Repository r) {
  677. try {
  678. Field f = r.getClass().getDeclaredField("repoConfig");
  679. f.setAccessible(true);
  680. StoredConfig config = (StoredConfig) f.get(r);
  681. return config;
  682. } catch (Exception e) {
  683. logger.error("Failed to retrieve \"repoConfig\" via reflection", e);
  684. }
  685. return r.getConfig();
  686. }
  687. /**
  688. * Create a repository model from the configuration and repository data.
  689. *
  690. * @param repositoryName
  691. * @return a repositoryModel or null if the repository does not exist
  692. */
  693. private RepositoryModel loadRepositoryModel(String repositoryName) {
  694. Repository r = getRepository(repositoryName);
  695. if (r == null) {
  696. return null;
  697. }
  698. RepositoryModel model = new RepositoryModel();
  699. model.isBare = r.isBare();
  700. File basePath = getRepositoriesFolder();
  701. if (model.isBare) {
  702. model.name = com.gitblit.utils.FileUtils.getRelativePath(basePath, r.getDirectory());
  703. } else {
  704. model.name = com.gitblit.utils.FileUtils.getRelativePath(basePath, r.getDirectory().getParentFile());
  705. }
  706. if (StringUtils.isEmpty(model.name)) {
  707. // Repository is NOT located relative to the base folder because it
  708. // is symlinked. Use the provided repository name.
  709. model.name = repositoryName;
  710. }
  711. model.projectPath = StringUtils.getFirstPathElement(repositoryName);
  712. StoredConfig config = r.getConfig();
  713. boolean hasOrigin = !StringUtils.isEmpty(config.getString("remote", "origin", "url"));
  714. if (config != null) {
  715. // Initialize description from description file
  716. if (getConfig(config,"description", null) == null) {
  717. File descFile = new File(r.getDirectory(), "description");
  718. if (descFile.exists()) {
  719. String desc = com.gitblit.utils.FileUtils.readContent(descFile, System.getProperty("line.separator"));
  720. if (!desc.toLowerCase().startsWith("unnamed repository")) {
  721. config.setString(Constants.CONFIG_GITBLIT, null, "description", desc);
  722. }
  723. }
  724. }
  725. model.description = getConfig(config, "description", "");
  726. model.originRepository = getConfig(config, "originRepository", null);
  727. model.addOwners(ArrayUtils.fromString(getConfig(config, "owner", "")));
  728. model.acceptNewPatchsets = getConfig(config, "acceptNewPatchsets", true);
  729. model.acceptNewTickets = getConfig(config, "acceptNewTickets", true);
  730. model.requireApproval = getConfig(config, "requireApproval", settings.getBoolean(Keys.tickets.requireApproval, false));
  731. model.useIncrementalPushTags = getConfig(config, "useIncrementalPushTags", false);
  732. model.incrementalPushTagPrefix = getConfig(config, "incrementalPushTagPrefix", null);
  733. model.allowForks = getConfig(config, "allowForks", true);
  734. model.accessRestriction = AccessRestrictionType.fromName(getConfig(config,
  735. "accessRestriction", settings.getString(Keys.git.defaultAccessRestriction, "PUSH")));
  736. model.authorizationControl = AuthorizationControl.fromName(getConfig(config,
  737. "authorizationControl", settings.getString(Keys.git.defaultAuthorizationControl, null)));
  738. model.verifyCommitter = getConfig(config, "verifyCommitter", false);
  739. model.showRemoteBranches = getConfig(config, "showRemoteBranches", hasOrigin);
  740. model.isFrozen = getConfig(config, "isFrozen", false);
  741. model.skipSizeCalculation = getConfig(config, "skipSizeCalculation", false);
  742. model.skipSummaryMetrics = getConfig(config, "skipSummaryMetrics", false);
  743. model.commitMessageRenderer = CommitMessageRenderer.fromName(getConfig(config, "commitMessageRenderer",
  744. settings.getString(Keys.web.commitMessageRenderer, null)));
  745. model.federationStrategy = FederationStrategy.fromName(getConfig(config,
  746. "federationStrategy", null));
  747. model.federationSets = new ArrayList<String>(Arrays.asList(config.getStringList(
  748. Constants.CONFIG_GITBLIT, null, "federationSets")));
  749. model.isFederated = getConfig(config, "isFederated", false);
  750. model.gcThreshold = getConfig(config, "gcThreshold", settings.getString(Keys.git.defaultGarbageCollectionThreshold, "500KB"));
  751. model.gcPeriod = getConfig(config, "gcPeriod", settings.getInteger(Keys.git.defaultGarbageCollectionPeriod, 7));
  752. try {
  753. model.lastGC = new SimpleDateFormat(Constants.ISO8601).parse(getConfig(config, "lastGC", "1970-01-01'T'00:00:00Z"));
  754. } catch (Exception e) {
  755. model.lastGC = new Date(0);
  756. }
  757. model.maxActivityCommits = getConfig(config, "maxActivityCommits", settings.getInteger(Keys.web.maxActivityCommits, 0));
  758. model.origin = config.getString("remote", "origin", "url");
  759. if (model.origin != null) {
  760. model.origin = model.origin.replace('\\', '/');
  761. model.isMirror = config.getBoolean("remote", "origin", "mirror", false);
  762. }
  763. model.preReceiveScripts = new ArrayList<String>(Arrays.asList(config.getStringList(
  764. Constants.CONFIG_GITBLIT, null, "preReceiveScript")));
  765. model.postReceiveScripts = new ArrayList<String>(Arrays.asList(config.getStringList(
  766. Constants.CONFIG_GITBLIT, null, "postReceiveScript")));
  767. model.mailingLists = new ArrayList<String>(Arrays.asList(config.getStringList(
  768. Constants.CONFIG_GITBLIT, null, "mailingList")));
  769. model.indexedBranches = new ArrayList<String>(Arrays.asList(config.getStringList(
  770. Constants.CONFIG_GITBLIT, null, "indexBranch")));
  771. model.metricAuthorExclusions = new ArrayList<String>(Arrays.asList(config.getStringList(
  772. Constants.CONFIG_GITBLIT, null, "metricAuthorExclusions")));
  773. // Custom defined properties
  774. model.customFields = new LinkedHashMap<String, String>();
  775. for (String aProperty : config.getNames(Constants.CONFIG_GITBLIT, Constants.CONFIG_CUSTOM_FIELDS)) {
  776. model.customFields.put(aProperty, config.getString(Constants.CONFIG_GITBLIT, Constants.CONFIG_CUSTOM_FIELDS, aProperty));
  777. }
  778. }
  779. model.HEAD = JGitUtils.getHEADRef(r);
  780. model.availableRefs = JGitUtils.getAvailableHeadTargets(r);
  781. model.sparkleshareId = JGitUtils.getSparkleshareId(r);
  782. model.hasCommits = JGitUtils.hasCommits(r);
  783. updateLastChangeFields(r, model);
  784. r.close();
  785. if (StringUtils.isEmpty(model.originRepository) && model.origin != null && model.origin.startsWith("file://")) {
  786. // repository was cloned locally... perhaps as a fork
  787. try {
  788. File folder = new File(new URI(model.origin));
  789. String originRepo = com.gitblit.utils.FileUtils.getRelativePath(getRepositoriesFolder(), folder);
  790. if (!StringUtils.isEmpty(originRepo)) {
  791. // ensure origin still exists
  792. File repoFolder = new File(getRepositoriesFolder(), originRepo);
  793. if (repoFolder.exists()) {
  794. model.originRepository = originRepo.toLowerCase();
  795. // persist the fork origin
  796. updateConfiguration(r, model);
  797. }
  798. }
  799. } catch (URISyntaxException e) {
  800. logger.error("Failed to determine fork for " + model, e);
  801. }
  802. }
  803. return model;
  804. }
  805. /**
  806. * Determines if this server has the requested repository.
  807. *
  808. * @param n
  809. * @return true if the repository exists
  810. */
  811. @Override
  812. public boolean hasRepository(String repositoryName) {
  813. return hasRepository(repositoryName, false);
  814. }
  815. /**
  816. * Determines if this server has the requested repository.
  817. *
  818. * @param n
  819. * @param caseInsensitive
  820. * @return true if the repository exists
  821. */
  822. @Override
  823. public boolean hasRepository(String repositoryName, boolean caseSensitiveCheck) {
  824. if (!caseSensitiveCheck && settings.getBoolean(Keys.git.cacheRepositoryList, true)) {
  825. // if we are caching use the cache to determine availability
  826. // otherwise we end up adding a phantom repository to the cache
  827. return repositoryListCache.containsKey(repositoryName.toLowerCase());
  828. }
  829. Repository r = getRepository(repositoryName, false);
  830. if (r == null) {
  831. return false;
  832. }
  833. r.close();
  834. return true;
  835. }
  836. /**
  837. * Determines if the specified user has a fork of the specified origin
  838. * repository.
  839. *
  840. * @param username
  841. * @param origin
  842. * @return true the if the user has a fork
  843. */
  844. @Override
  845. public boolean hasFork(String username, String origin) {
  846. return getFork(username, origin) != null;
  847. }
  848. /**
  849. * Gets the name of a user's fork of the specified origin
  850. * repository.
  851. *
  852. * @param username
  853. * @param origin
  854. * @return the name of the user's fork, null otherwise
  855. */
  856. @Override
  857. public String getFork(String username, String origin) {
  858. String userProject = ModelUtils.getPersonalPath(username);
  859. if (settings.getBoolean(Keys.git.cacheRepositoryList, true)) {
  860. String userPath = userProject + "/";
  861. // collect all origin nodes in fork network
  862. Set<String> roots = new HashSet<String>();
  863. roots.add(origin);
  864. RepositoryModel originModel = repositoryListCache.get(origin);
  865. while (originModel != null) {
  866. if (!ArrayUtils.isEmpty(originModel.forks)) {
  867. for (String fork : originModel.forks) {
  868. if (!fork.startsWith(userPath)) {
  869. roots.add(fork);
  870. }
  871. }
  872. }
  873. if (originModel.originRepository != null) {
  874. roots.add(originModel.originRepository);
  875. originModel = repositoryListCache.get(originModel.originRepository);
  876. } else {
  877. // break
  878. originModel = null;
  879. }
  880. }
  881. for (String repository : repositoryListCache.keySet()) {
  882. if (repository.startsWith(userPath)) {
  883. RepositoryModel model = repositoryListCache.get(repository);
  884. if (!StringUtils.isEmpty(model.originRepository)) {
  885. if (roots.contains(model.originRepository)) {
  886. // user has a fork in this graph
  887. return model.name;
  888. }
  889. }
  890. }
  891. }
  892. } else {
  893. // not caching
  894. File subfolder = new File(getRepositoriesFolder(), userProject);
  895. List<String> repositories = JGitUtils.getRepositoryList(subfolder,
  896. settings.getBoolean(Keys.git.onlyAccessBareRepositories, false),
  897. settings.getBoolean(Keys.git.searchRepositoriesSubfolders, true),
  898. settings.getInteger(Keys.git.searchRecursionDepth, -1),
  899. settings.getStrings(Keys.git.searchExclusions));
  900. for (String repository : repositories) {
  901. RepositoryModel model = getRepositoryModel(userProject + "/" + repository);
  902. if (model.originRepository != null && model.originRepository.equalsIgnoreCase(origin)) {
  903. // user has a fork
  904. return model.name;
  905. }
  906. }
  907. }
  908. // user does not have a fork
  909. return null;
  910. }
  911. /**
  912. * Returns the fork network for a repository by traversing up the fork graph
  913. * to discover the root and then down through all children of the root node.
  914. *
  915. * @param repository
  916. * @return a ForkModel
  917. */
  918. @Override
  919. public ForkModel getForkNetwork(String repository) {
  920. if (settings.getBoolean(Keys.git.cacheRepositoryList, true)) {
  921. // find the root, cached
  922. RepositoryModel model = repositoryListCache.get(repository.toLowerCase());
  923. while (model.originRepository != null) {
  924. model = repositoryListCache.get(model.originRepository);
  925. }
  926. ForkModel root = getForkModelFromCache(model.name);
  927. return root;
  928. } else {
  929. // find the root, non-cached
  930. RepositoryModel model = getRepositoryModel(repository.toLowerCase());
  931. while (model.originRepository != null) {
  932. model = getRepositoryModel(model.originRepository);
  933. }
  934. ForkModel root = getForkModel(model.name);
  935. return root;
  936. }
  937. }
  938. private ForkModel getForkModelFromCache(String repository) {
  939. RepositoryModel model = repositoryListCache.get(repository.toLowerCase());
  940. if (model == null) {
  941. return null;
  942. }
  943. ForkModel fork = new ForkModel(model);
  944. if (!ArrayUtils.isEmpty(model.forks)) {
  945. for (String aFork : model.forks) {
  946. ForkModel fm = getForkModelFromCache(aFork);
  947. if (fm != null) {
  948. fork.forks.add(fm);
  949. }
  950. }
  951. }
  952. return fork;
  953. }
  954. private ForkModel getForkModel(String repository) {
  955. RepositoryModel model = getRepositoryModel(repository.toLowerCase());
  956. if (model == null) {
  957. return null;
  958. }
  959. ForkModel fork = new ForkModel(model);
  960. if (!ArrayUtils.isEmpty(model.forks)) {
  961. for (String aFork : model.forks) {
  962. ForkModel fm = getForkModel(aFork);
  963. if (fm != null) {
  964. fork.forks.add(fm);
  965. }
  966. }
  967. }
  968. return fork;
  969. }
  970. /**
  971. * Updates the last changed fields and optionally calculates the size of the
  972. * repository. Gitblit caches the repository sizes to reduce the performance
  973. * penalty of recursive calculation. The cache is updated if the repository
  974. * has been changed since the last calculation.
  975. *
  976. * @param model
  977. * @return size in bytes of the repository
  978. */
  979. @Override
  980. public long updateLastChangeFields(Repository r, RepositoryModel model) {
  981. LastChange lc = JGitUtils.getLastChange(r);
  982. model.lastChange = lc.when;
  983. model.lastChangeAuthor = lc.who;
  984. if (!settings.getBoolean(Keys.web.showRepositorySizes, true) || model.skipSizeCalculation) {
  985. model.size = null;
  986. return 0L;
  987. }
  988. if (!repositorySizeCache.hasCurrent(model.name, model.lastChange)) {
  989. File gitDir = r.getDirectory();
  990. long sz = com.gitblit.utils.FileUtils.folderSize(gitDir);
  991. repositorySizeCache.updateObject(model.name, model.lastChange, sz);
  992. }
  993. long size = repositorySizeCache.getObject(model.name);
  994. ByteFormat byteFormat = new ByteFormat();
  995. model.size = byteFormat.format(size);
  996. return size;
  997. }
  998. /**
  999. * Returns true if the repository is idle (not being accessed).
  1000. *
  1001. * @param repository
  1002. * @return true if the repository is idle
  1003. */
  1004. @Override
  1005. public boolean isIdle(Repository repository) {
  1006. try {
  1007. // Read the use count.
  1008. // An idle use count is 2:
  1009. // +1 for being in the cache
  1010. // +1 for the repository parameter in this method
  1011. Field useCnt = Repository.class.getDeclaredField("useCnt");
  1012. useCnt.setAccessible(true);
  1013. int useCount = ((AtomicInteger) useCnt.get(repository)).get();
  1014. return useCount == 2;
  1015. } catch (Exception e) {
  1016. logger.warn(MessageFormat
  1017. .format("Failed to reflectively determine use count for repository {0}",
  1018. repository.getDirectory().getPath()), e);
  1019. }
  1020. return false;
  1021. }
  1022. /**
  1023. * Ensures that all cached repository are completely closed and their resources
  1024. * are properly released.
  1025. */
  1026. @Override
  1027. public void closeAll() {
  1028. for (String repository : getRepositoryList()) {
  1029. close(repository);
  1030. }
  1031. }
  1032. /**
  1033. * Ensure that a cached repository is completely closed and its resources
  1034. * are properly released.
  1035. *
  1036. * @param repositoryName
  1037. */
  1038. @Override
  1039. public void close(String repositoryName) {
  1040. Repository repository = getRepository(repositoryName);
  1041. if (repository == null) {
  1042. return;
  1043. }
  1044. RepositoryCache.close(repository);
  1045. // assume 2 uses in case reflection fails
  1046. int uses = 2;
  1047. try {
  1048. // The FileResolver caches repositories which is very useful
  1049. // for performance until you want to delete a repository.
  1050. // I have to use reflection to call close() the correct
  1051. // number of times to ensure that the object and ref databases
  1052. // are properly closed before I can delete the repository from
  1053. // the filesystem.
  1054. Field useCnt = Repository.class.getDeclaredField("useCnt");
  1055. useCnt.setAccessible(true);
  1056. uses = ((AtomicInteger) useCnt.get(repository)).get();
  1057. } catch (Exception e) {
  1058. logger.warn(MessageFormat
  1059. .format("Failed to reflectively determine use count for repository {0}",
  1060. repositoryName), e);
  1061. }
  1062. if (uses > 0) {
  1063. logger.debug(MessageFormat
  1064. .format("{0}.useCnt={1}, calling close() {2} time(s) to close object and ref databases",
  1065. repositoryName, uses, uses));
  1066. for (int i = 0; i < uses; i++) {
  1067. repository.close();
  1068. }
  1069. }
  1070. // close any open index writer/searcher in the Lucene executor
  1071. luceneExecutor.close(repositoryName);
  1072. }
  1073. /**
  1074. * Returns the metrics for the default branch of the specified repository.
  1075. * This method builds a metrics cache. The cache is updated if the
  1076. * repository is updated. A new copy of the metrics list is returned on each
  1077. * call so that modifications to the list are non-destructive.
  1078. *
  1079. * @param model
  1080. * @param repository
  1081. * @return a new array list of metrics
  1082. */
  1083. @Override
  1084. public List<Metric> getRepositoryDefaultMetrics(RepositoryModel model, Repository repository) {
  1085. if (repositoryMetricsCache.hasCurrent(model.name, model.lastChange)) {
  1086. return new ArrayList<Metric>(repositoryMetricsCache.getObject(model.name));
  1087. }
  1088. List<Metric> metrics = MetricUtils.getDateMetrics(repository, null, true, null, runtimeManager.getTimezone());
  1089. repositoryMetricsCache.updateObject(model.name, model.lastChange, metrics);
  1090. return new ArrayList<Metric>(metrics);
  1091. }
  1092. /**
  1093. * Returns the gitblit string value for the specified key. If key is not
  1094. * set, returns defaultValue.
  1095. *
  1096. * @param config
  1097. * @param field
  1098. * @param defaultValue
  1099. * @return field value or defaultValue
  1100. */
  1101. private String getConfig(StoredConfig config, String field, String defaultValue) {
  1102. String value = config.getString(Constants.CONFIG_GITBLIT, null, field);
  1103. if (StringUtils.isEmpty(value)) {
  1104. return defaultValue;
  1105. }
  1106. return value;
  1107. }
  1108. /**
  1109. * Returns the gitblit boolean value for the specified key. If key is not
  1110. * set, returns defaultValue.
  1111. *
  1112. * @param config
  1113. * @param field
  1114. * @param defaultValue
  1115. * @return field value or defaultValue
  1116. */
  1117. private boolean getConfig(StoredConfig config, String field, boolean defaultValue) {
  1118. return config.getBoolean(Constants.CONFIG_GITBLIT, field, defaultValue);
  1119. }
  1120. /**
  1121. * Returns the gitblit string value for the specified key. If key is not
  1122. * set, returns defaultValue.
  1123. *
  1124. * @param config
  1125. * @param field
  1126. * @param defaultValue
  1127. * @return field value or defaultValue
  1128. */
  1129. private int getConfig(StoredConfig config, String field, int defaultValue) {
  1130. String value = config.getString(Constants.CONFIG_GITBLIT, null, field);
  1131. if (StringUtils.isEmpty(value)) {
  1132. return defaultValue;
  1133. }
  1134. try {
  1135. return Integer.parseInt(value);
  1136. } catch (Exception e) {
  1137. }
  1138. return defaultValue;
  1139. }
  1140. /**
  1141. * Creates/updates the repository model keyed by reopsitoryName. Saves all
  1142. * repository settings in .git/config. This method allows for renaming
  1143. * repositories and will update user access permissions accordingly.
  1144. *
  1145. * All repositories created by this method are bare and automatically have
  1146. * .git appended to their names, which is the standard convention for bare
  1147. * repositories.
  1148. *
  1149. * @param repositoryName
  1150. * @param repository
  1151. * @param isCreate
  1152. * @throws GitBlitException
  1153. */
  1154. @Override
  1155. public void updateRepositoryModel(String repositoryName, RepositoryModel repository,
  1156. boolean isCreate) throws GitBlitException {
  1157. if (gcExecutor.isCollectingGarbage(repositoryName)) {
  1158. throw new GitBlitException(MessageFormat.format("sorry, Gitblit is busy collecting garbage in {0}",
  1159. repositoryName));
  1160. }
  1161. Repository r = null;
  1162. String projectPath = StringUtils.getFirstPathElement(repository.name);
  1163. if (!StringUtils.isEmpty(projectPath)) {
  1164. if (projectPath.equalsIgnoreCase(settings.getString(Keys.web.repositoryRootGroupName, "main"))) {
  1165. // strip leading group name
  1166. repository.name = repository.name.substring(projectPath.length() + 1);
  1167. }
  1168. }
  1169. if (isCreate) {
  1170. // ensure created repository name ends with .git
  1171. if (!repository.name.toLowerCase().endsWith(org.eclipse.jgit.lib.Constants.DOT_GIT_EXT)) {
  1172. repository.name += org.eclipse.jgit.lib.Constants.DOT_GIT_EXT;
  1173. }
  1174. if (hasRepository(repository.name)) {
  1175. throw new GitBlitException(MessageFormat.format(
  1176. "Can not create repository ''{0}'' because it already exists.",
  1177. repository.name));
  1178. }
  1179. // create repository
  1180. logger.info("create repository " + repository.name);
  1181. String shared = settings.getString(Keys.git.createRepositoriesShared, "FALSE");
  1182. r = JGitUtils.createRepository(repositoriesFolder, repository.name, shared);
  1183. } else {
  1184. // rename repository
  1185. if (!repositoryName.equalsIgnoreCase(repository.name)) {
  1186. if (!repository.name.toLowerCase().endsWith(
  1187. org.eclipse.jgit.lib.Constants.DOT_GIT_EXT)) {
  1188. repository.name += org.eclipse.jgit.lib.Constants.DOT_GIT_EXT;
  1189. }
  1190. if (new File(repositoriesFolder, repository.name).exists()) {
  1191. throw new GitBlitException(MessageFormat.format(
  1192. "Failed to rename ''{0}'' because ''{1}'' already exists.",
  1193. repositoryName, repository.name));
  1194. }
  1195. close(repositoryName);
  1196. File folder = new File(repositoriesFolder, repositoryName);
  1197. File destFolder = new File(repositoriesFolder, repository.name);
  1198. if (destFolder.exists()) {
  1199. throw new GitBlitException(
  1200. MessageFormat
  1201. .format("Can not rename repository ''{0}'' to ''{1}'' because ''{1}'' already exists.",
  1202. repositoryName, repository.name));
  1203. }
  1204. File parentFile = destFolder.getParentFile();
  1205. if (!parentFile.exists() && !parentFile.mkdirs()) {
  1206. throw new GitBlitException(MessageFormat.format(
  1207. "Failed to create folder ''{0}''", parentFile.getAbsolutePath()));
  1208. }
  1209. if (!folder.renameTo(destFolder)) {
  1210. throw new GitBlitException(MessageFormat.format(
  1211. "Failed to rename repository ''{0}'' to ''{1}''.", repositoryName,
  1212. repository.name));
  1213. }
  1214. // rename the roles
  1215. if (!userManager.renameRepositoryRole(repositoryName, repository.name)) {
  1216. throw new GitBlitException(MessageFormat.format(
  1217. "Failed to rename repository permissions ''{0}'' to ''{1}''.",
  1218. repositoryName, repository.name));
  1219. }
  1220. // rename fork origins in their configs
  1221. if (!ArrayUtils.isEmpty(repository.forks)) {
  1222. for (String fork : repository.forks) {
  1223. Repository rf = getRepository(fork);
  1224. try {
  1225. StoredConfig config = rf.getConfig();
  1226. String origin = config.getString("remote", "origin", "url");
  1227. origin = origin.replace(repositoryName, repository.name);
  1228. config.setString("remote", "origin", "url", origin);
  1229. config.setString(Constants.CONFIG_GITBLIT, null, "originRepository", repository.name);
  1230. config.save();
  1231. } catch (Exception e) {
  1232. logger.error("Failed to update repository fork config for " + fork, e);
  1233. }
  1234. rf.close();
  1235. }
  1236. }
  1237. // update this repository's origin's fork list
  1238. if (!StringUtils.isEmpty(repository.originRepository)) {
  1239. RepositoryModel origin = repositoryListCache.get(repository.originRepository);
  1240. if (origin != null && !ArrayUtils.isEmpty(origin.forks)) {
  1241. origin.forks.remove(repositoryName);
  1242. origin.forks.add(repository.name);
  1243. }
  1244. }
  1245. // clear the cache
  1246. clearRepositoryMetadataCache(repositoryName);
  1247. repository.resetDisplayName();
  1248. }
  1249. // load repository
  1250. logger.info("edit repository " + repository.name);
  1251. r = getRepository(repository.name);
  1252. }
  1253. // update settings
  1254. if (r != null) {
  1255. updateConfiguration(r, repository);
  1256. // Update the description file
  1257. File descFile = new File(r.getDirectory(), "description");
  1258. if (repository.description != null)
  1259. {
  1260. com.gitblit.utils.FileUtils.writeContent(descFile, repository.description);
  1261. }
  1262. else if (descFile.exists() && !descFile.isDirectory()) {
  1263. descFile.delete();
  1264. }
  1265. // only update symbolic head if it changes
  1266. String currentRef = JGitUtils.getHEADRef(r);
  1267. if (!StringUtils.isEmpty(repository.HEAD) && !repository.HEAD.equals(currentRef)) {
  1268. logger.info(MessageFormat.format("Relinking {0} HEAD from {1} to {2}",
  1269. repository.name, currentRef, repository.HEAD));
  1270. if (JGitUtils.setHEADtoRef(r, repository.HEAD)) {
  1271. // clear the cache
  1272. clearRepositoryMetadataCache(repository.name);
  1273. }
  1274. }
  1275. // Adjust permissions in case we updated the config files
  1276. JGitUtils.adjustSharedPerm(new File(r.getDirectory().getAbsolutePath(), "config"),
  1277. settings.getString(Keys.git.createRepositoriesShared, "FALSE"));
  1278. JGitUtils.adjustSharedPerm(new File(r.getDirectory().getAbsolutePath(), "HEAD"),
  1279. settings.getString(Keys.git.createRepositoriesShared, "FALSE"));
  1280. // close the repository object
  1281. r.close();
  1282. }
  1283. // update repository cache
  1284. removeFromCachedRepositoryList(repositoryName);
  1285. // model will actually be replaced on next load because config is stale
  1286. addToCachedRepositoryList(repository);
  1287. }
  1288. /**
  1289. * Updates the Gitblit configuration for the specified repository.
  1290. *
  1291. * @param r
  1292. * the Git repository
  1293. * @param repository
  1294. * the Gitblit repository model
  1295. */
  1296. @Override
  1297. public void updateConfiguration(Repository r, RepositoryModel repository) {
  1298. StoredConfig config = r.getConfig();
  1299. config.setString(Constants.CONFIG_GITBLIT, null, "description", repository.description);
  1300. config.setString(Constants.CONFIG_GITBLIT, null, "originRepository", repository.originRepository);
  1301. config.setString(Constants.CONFIG_GITBLIT, null, "owner", ArrayUtils.toString(repository.owners));
  1302. config.setBoolean(Constants.CONFIG_GITBLIT, null, "acceptNewPatchsets", repository.acceptNewPatchsets);
  1303. config.setBoolean(Constants.CONFIG_GITBLIT, null, "acceptNewTickets", repository.acceptNewTickets);
  1304. if (settings.getBoolean(Keys.tickets.requireApproval, false) == repository.requireApproval) {
  1305. // use default
  1306. config.unset(Constants.CONFIG_GITBLIT, null, "requireApproval");
  1307. } else {
  1308. // override default
  1309. config.setBoolean(Constants.CONFIG_GITBLIT, null, "requireApproval", repository.requireApproval);
  1310. }
  1311. config.setBoolean(Constants.CONFIG_GITBLIT, null, "useIncrementalPushTags", repository.useIncrementalPushTags);
  1312. if (StringUtils.isEmpty(repository.incrementalPushTagPrefix) ||
  1313. repository.incrementalPushTagPrefix.equals(settings.getString(Keys.git.defaultIncrementalPushTagPrefix, "r"))) {
  1314. config.unset(Constants.CONFIG_GITBLIT, null, "incrementalPushTagPrefix");
  1315. } else {
  1316. config.setString(Constants.CONFIG_GITBLIT, null, "incrementalPushTagPrefix", repository.incrementalPushTagPrefix);
  1317. }
  1318. config.setBoolean(Constants.CONFIG_GITBLIT, null, "allowForks", repository.allowForks);
  1319. config.setString(Constants.CONFIG_GITBLIT, null, "accessRestriction", repository.accessRestriction.name());
  1320. config.setString(Constants.CONFIG_GITBLIT, null, "authorizationControl", repository.authorizationControl.name());
  1321. config.setBoolean(Constants.CONFIG_GITBLIT, null, "verifyCommitter", repository.verifyCommitter);
  1322. config.setBoolean(Constants.CONFIG_GITBLIT, null, "showRemoteBranches", repository.showRemoteBranches);
  1323. config.setBoolean(Constants.CONFIG_GITBLIT, null, "isFrozen", repository.isFrozen);
  1324. config.setBoolean(Constants.CONFIG_GITBLIT, null, "skipSizeCalculation", repository.skipSizeCalculation);
  1325. config.setBoolean(Constants.CONFIG_GITBLIT, null, "skipSummaryMetrics", repository.skipSummaryMetrics);
  1326. config.setString(Constants.CONFIG_GITBLIT, null, "federationStrategy",
  1327. repository.federationStrategy.name());
  1328. config.setBoolean(Constants.CONFIG_GITBLIT, null, "isFederated", repository.isFederated);
  1329. config.setString(Constants.CONFIG_GITBLIT, null, "gcThreshold", repository.gcThreshold);
  1330. if (repository.gcPeriod == settings.getInteger(Keys.git.defaultGarbageCollectionPeriod, 7)) {
  1331. // use default from config
  1332. config.unset(Constants.CONFIG_GITBLIT, null, "gcPeriod");
  1333. } else {
  1334. config.setInt(Constants.CONFIG_GITBLIT, null, "gcPeriod", repository.gcPeriod);
  1335. }
  1336. if (repository.lastGC != null) {
  1337. config.setString(Constants.CONFIG_GITBLIT, null, "lastGC", new SimpleDateFormat(Constants.ISO8601).format(repository.lastGC));
  1338. }
  1339. if (repository.maxActivityCommits == settings.getInteger(Keys.web.maxActivityCommits, 0)) {
  1340. // use default from config
  1341. config.unset(Constants.CONFIG_GITBLIT, null, "maxActivityCommits");
  1342. } else {
  1343. config.setInt(Constants.CONFIG_GITBLIT, null, "maxActivityCommits", repository.maxActivityCommits);
  1344. }
  1345. CommitMessageRenderer defaultRenderer = CommitMessageRenderer.fromName(settings.getString(Keys.web.commitMessageRenderer, null));
  1346. if (repository.commitMessageRenderer == null || repository.commitMessageRenderer == defaultRenderer) {
  1347. // use default from config
  1348. config.unset(Constants.CONFIG_GITBLIT, null, "commitMessageRenderer");
  1349. } else {
  1350. // repository overrides default
  1351. config.setString(Constants.CONFIG_GITBLIT, null, "commitMessageRenderer",
  1352. repository.commitMessageRenderer.name());
  1353. }
  1354. updateList(config, "federationSets", repository.federationSets);
  1355. updateList(config, "preReceiveScript", repository.preReceiveScripts);
  1356. updateList(config, "postReceiveScript", repository.postReceiveScripts);
  1357. updateList(config, "mailingList", repository.mailingLists);
  1358. updateList(config, "indexBranch", repository.indexedBranches);
  1359. updateList(config, "metricAuthorExclusions", repository.metricAuthorExclusions);
  1360. // User Defined Properties
  1361. if (repository.customFields != null) {
  1362. if (repository.customFields.size() == 0) {
  1363. // clear section
  1364. config.unsetSection(Constants.CONFIG_GITBLIT, Constants.CONFIG_CUSTOM_FIELDS);
  1365. } else {
  1366. for (Entry<String, String> property : repository.customFields.entrySet()) {
  1367. // set field
  1368. String key = property.getKey();
  1369. String value = property.getValue();
  1370. config.setString(Constants.CONFIG_GITBLIT, Constants.CONFIG_CUSTOM_FIELDS, key, value);
  1371. }
  1372. }
  1373. }
  1374. try {
  1375. config.save();
  1376. } catch (IOException e) {
  1377. logger.error("Failed to save repository config!", e);
  1378. }
  1379. }
  1380. private void updateList(StoredConfig config, String field, List<String> list) {
  1381. // a null list is skipped, not cleared
  1382. // this is for RPC administration where an older manager might be used
  1383. if (list == null) {
  1384. return;
  1385. }
  1386. if (ArrayUtils.isEmpty(list)) {
  1387. config.unset(Constants.CONFIG_GITBLIT, null, field);
  1388. } else {
  1389. config.setStringList(Constants.CONFIG_GITBLIT, null, field, list);
  1390. }
  1391. }
  1392. /**
  1393. * Deletes the repository from the file system and removes the repository
  1394. * permission from all repository users.
  1395. *
  1396. * @param model
  1397. * @return true if successful
  1398. */
  1399. @Override
  1400. public boolean deleteRepositoryModel(RepositoryModel model) {
  1401. return deleteRepository(model.name);
  1402. }
  1403. /**
  1404. * Deletes the repository from the file system and removes the repository
  1405. * permission from all repository users.
  1406. *
  1407. * @param repositoryName
  1408. * @return true if successful
  1409. */
  1410. @Override
  1411. public boolean deleteRepository(String repositoryName) {
  1412. try {
  1413. close(repositoryName);
  1414. // clear the repository cache
  1415. clearRepositoryMetadataCache(repositoryName);
  1416. RepositoryModel model = removeFromCachedRepositoryList(repositoryName);
  1417. if (model != null && !ArrayUtils.isEmpty(model.forks)) {
  1418. resetRepositoryListCache();
  1419. }
  1420. File folder = new File(repositoriesFolder, repositoryName);
  1421. if (folder.exists() && folder.isDirectory()) {
  1422. FileUtils.delete(folder, FileUtils.RECURSIVE | FileUtils.RETRY);
  1423. if (userManager.deleteRepositoryRole(repositoryName)) {
  1424. logger.info(MessageFormat.format("Repository \"{0}\" deleted", repositoryName));
  1425. return true;
  1426. }
  1427. }
  1428. } catch (Throwable t) {
  1429. logger.error(MessageFormat.format("Failed to delete repository {0}", repositoryName), t);
  1430. }
  1431. return false;
  1432. }
  1433. /**
  1434. * Returns the list of all Groovy push hook scripts. Script files must have
  1435. * .groovy extension
  1436. *
  1437. * @return list of available hook scripts
  1438. */
  1439. @Override
  1440. public List<String> getAllScripts() {
  1441. File groovyFolder = getHooksFolder();
  1442. File[] files = groovyFolder.listFiles(new FileFilter() {
  1443. @Override
  1444. public boolean accept(File pathname) {
  1445. return pathname.isFile() && pathname.getName().endsWith(".groovy");
  1446. }
  1447. });
  1448. List<String> scripts = new ArrayList<String>();
  1449. if (files != null) {
  1450. for (File file : files) {
  1451. String script = file.getName().substring(0, file.getName().lastIndexOf('.'));
  1452. scripts.add(script);
  1453. }
  1454. }
  1455. return scripts;
  1456. }
  1457. /**
  1458. * Returns the list of pre-receive scripts the repository inherited from the
  1459. * global settings and team affiliations.
  1460. *
  1461. * @param repository
  1462. * if null only the globally specified scripts are returned
  1463. * @return a list of scripts
  1464. */
  1465. @Override
  1466. public List<String> getPreReceiveScriptsInherited(RepositoryModel repository) {
  1467. Set<String> scripts = new LinkedHashSet<String>();
  1468. // Globals
  1469. for (String script : settings.getStrings(Keys.groovy.preReceiveScripts)) {
  1470. if (script.endsWith(".groovy")) {
  1471. scripts.add(script.substring(0, script.lastIndexOf('.')));
  1472. } else {
  1473. scripts.add(script);
  1474. }
  1475. }
  1476. // Team Scripts
  1477. if (repository != null) {
  1478. for (String teamname : userManager.getTeamNamesForRepositoryRole(repository.name)) {
  1479. TeamModel team = userManager.getTeamModel(teamname);
  1480. if (!ArrayUtils.isEmpty(team.preReceiveScripts)) {
  1481. scripts.addAll(team.preReceiveScripts);
  1482. }
  1483. }
  1484. }
  1485. return new ArrayList<String>(scripts);
  1486. }
  1487. /**
  1488. * Returns the list of all available Groovy pre-receive push hook scripts
  1489. * that are not already inherited by the repository. Script files must have
  1490. * .groovy extension
  1491. *
  1492. * @param repository
  1493. * optional parameter
  1494. * @return list of available hook scripts
  1495. */
  1496. @Override
  1497. public List<String> getPreReceiveScriptsUnused(RepositoryModel repository) {
  1498. Set<String> inherited = new TreeSet<String>(getPreReceiveScriptsInherited(repository));
  1499. // create list of available scripts by excluding inherited scripts
  1500. List<String> scripts = new ArrayList<String>();
  1501. for (String script : getAllScripts()) {
  1502. if (!inherited.contains(script)) {
  1503. scripts.add(script);
  1504. }
  1505. }
  1506. return scripts;
  1507. }
  1508. /**
  1509. * Returns the list of post-receive scripts the repository inherited from
  1510. * the global settings and team affiliations.
  1511. *
  1512. * @param repository
  1513. * if null only the globally specified scripts are returned
  1514. * @return a list of scripts
  1515. */
  1516. @Override
  1517. public List<String> getPostReceiveScriptsInherited(RepositoryModel repository) {
  1518. Set<String> scripts = new LinkedHashSet<String>();
  1519. // Global Scripts
  1520. for (String script : settings.getStrings(Keys.groovy.postReceiveScripts)) {
  1521. if (script.endsWith(".groovy")) {
  1522. scripts.add(script.substring(0, script.lastIndexOf('.')));
  1523. } else {
  1524. scripts.add(script);
  1525. }
  1526. }
  1527. // Team Scripts
  1528. if (repository != null) {
  1529. for (String teamname : userManager.getTeamNamesForRepositoryRole(repository.name)) {
  1530. TeamModel team = userManager.getTeamModel(teamname);
  1531. if (!ArrayUtils.isEmpty(team.postReceiveScripts)) {
  1532. scripts.addAll(team.postReceiveScripts);
  1533. }
  1534. }
  1535. }
  1536. return new ArrayList<String>(scripts);
  1537. }
  1538. /**
  1539. * Returns the list of unused Groovy post-receive push hook scripts that are
  1540. * not already inherited by the repository. Script files must have .groovy
  1541. * extension
  1542. *
  1543. * @param repository
  1544. * optional parameter
  1545. * @return list of available hook scripts
  1546. */
  1547. @Override
  1548. public List<String> getPostReceiveScriptsUnused(RepositoryModel repository) {
  1549. Set<String> inherited = new TreeSet<String>(getPostReceiveScriptsInherited(repository));
  1550. // create list of available scripts by excluding inherited scripts
  1551. List<String> scripts = new ArrayList<String>();
  1552. for (String script : getAllScripts()) {
  1553. if (!inherited.contains(script)) {
  1554. scripts.add(script);
  1555. }
  1556. }
  1557. return scripts;
  1558. }
  1559. /**
  1560. * Search the specified repositories using the Lucene query.
  1561. *
  1562. * @param query
  1563. * @param page
  1564. * @param pageSize
  1565. * @param repositories
  1566. * @return
  1567. */
  1568. @Override
  1569. public List<SearchResult> search(String query, int page, int pageSize, List<String> repositories) {
  1570. List<SearchResult> srs = luceneExecutor.search(query, page, pageSize, repositories);
  1571. return srs;
  1572. }
  1573. protected void configureLuceneIndexing() {
  1574. luceneExecutor = new LuceneService(settings, this);
  1575. int period = 2;
  1576. scheduledExecutor.scheduleAtFixedRate(luceneExecutor, 1, period, TimeUnit.MINUTES);
  1577. logger.info("Lucene will process indexed branches every {} minutes.", period);
  1578. }
  1579. protected void configureGarbageCollector() {
  1580. // schedule gc engine
  1581. gcExecutor = new GarbageCollectorService(settings, this);
  1582. if (gcExecutor.isReady()) {
  1583. logger.info("Garbage Collector (GC) will scan repositories every 24 hours.");
  1584. Calendar c = Calendar.getInstance();
  1585. c.set(Calendar.HOUR_OF_DAY, settings.getInteger(Keys.git.garbageCollectionHour, 0));
  1586. c.set(Calendar.MINUTE, 0);
  1587. c.set(Calendar.SECOND, 0);
  1588. c.set(Calendar.MILLISECOND, 0);
  1589. Date cd = c.getTime();
  1590. Date now = new Date();
  1591. int delay = 0;
  1592. if (cd.before(now)) {
  1593. c.add(Calendar.DATE, 1);
  1594. cd = c.getTime();
  1595. }
  1596. delay = (int) ((cd.getTime() - now.getTime())/TimeUtils.MIN);
  1597. String when = delay + " mins";
  1598. if (delay > 60) {
  1599. when = MessageFormat.format("{0,number,0.0} hours", delay / 60f);
  1600. }
  1601. logger.info(MessageFormat.format("Next scheculed GC scan is in {0}", when));
  1602. scheduledExecutor.scheduleAtFixedRate(gcExecutor, delay, 60 * 24, TimeUnit.MINUTES);
  1603. } else {
  1604. logger.info("Garbage Collector (GC) is disabled.");
  1605. }
  1606. }
  1607. protected void configureMirrorExecutor() {
  1608. mirrorExecutor = new MirrorService(settings, this);
  1609. if (mirrorExecutor.isReady()) {
  1610. int mins = TimeUtils.convertFrequencyToMinutes(settings.getString(Keys.git.mirrorPeriod, "30 mins"));
  1611. if (mins < 5) {
  1612. mins = 5;
  1613. }
  1614. int delay = 1;
  1615. scheduledExecutor.scheduleAtFixedRate(mirrorExecutor, delay, mins, TimeUnit.MINUTES);
  1616. logger.info("Mirror service will fetch updates every {} minutes.", mins);
  1617. logger.info("Next scheduled mirror fetch is in {} minutes", delay);
  1618. } else {
  1619. logger.info("Mirror service is disabled.");
  1620. }
  1621. }
  1622. protected void configureJGit() {
  1623. // Configure JGit
  1624. WindowCacheConfig cfg = new WindowCacheConfig();
  1625. cfg.setPackedGitWindowSize(settings.getFilesize(Keys.git.packedGitWindowSize, cfg.getPackedGitWindowSize()));
  1626. cfg.setPackedGitLimit(settings.getFilesize(Keys.git.packedGitLimit, cfg.getPackedGitLimit()));
  1627. cfg.setDeltaBaseCacheLimit(settings.getFilesize(Keys.git.deltaBaseCacheLimit, cfg.getDeltaBaseCacheLimit()));
  1628. cfg.setPackedGitOpenFiles(settings.getFilesize(Keys.git.packedGitOpenFiles, cfg.getPackedGitOpenFiles()));
  1629. cfg.setStreamFileThreshold(settings.getFilesize(Keys.git.streamFileThreshold, cfg.getStreamFileThreshold()));
  1630. cfg.setPackedGitMMAP(settings.getBoolean(Keys.git.packedGitMmap, cfg.isPackedGitMMAP()));
  1631. try {
  1632. cfg.install();
  1633. logger.debug(MessageFormat.format("{0} = {1,number,0}", Keys.git.packedGitWindowSize, cfg.getPackedGitWindowSize()));
  1634. logger.debug(MessageFormat.format("{0} = {1,number,0}", Keys.git.packedGitLimit, cfg.getPackedGitLimit()));
  1635. logger.debug(MessageFormat.format("{0} = {1,number,0}", Keys.git.deltaBaseCacheLimit, cfg.getDeltaBaseCacheLimit()));
  1636. logger.debug(MessageFormat.format("{0} = {1,number,0}", Keys.git.packedGitOpenFiles, cfg.getPackedGitOpenFiles()));
  1637. logger.debug(MessageFormat.format("{0} = {1,number,0}", Keys.git.streamFileThreshold, cfg.getStreamFileThreshold()));
  1638. logger.debug(MessageFormat.format("{0} = {1}", Keys.git.packedGitMmap, cfg.isPackedGitMMAP()));
  1639. } catch (IllegalArgumentException e) {
  1640. logger.error("Failed to configure JGit parameters!", e);
  1641. }
  1642. }
  1643. protected void configureCommitCache() {
  1644. int daysToCache = settings.getInteger(Keys.web.activityCacheDays, 14);
  1645. if (daysToCache <= 0) {
  1646. logger.info("Commit cache is disabled");
  1647. } else {
  1648. long start = System.nanoTime();
  1649. long repoCount = 0;
  1650. long commitCount = 0;
  1651. logger.info(MessageFormat.format("Preparing {0} day commit cache. please wait...", daysToCache));
  1652. CommitCache.instance().setCacheDays(daysToCache);
  1653. Date cutoff = CommitCache.instance().getCutoffDate();
  1654. for (String repositoryName : getRepositoryList()) {
  1655. RepositoryModel model = getRepositoryModel(repositoryName);
  1656. if (model != null && model.hasCommits && model.lastChange.after(cutoff)) {
  1657. repoCount++;
  1658. Repository repository = getRepository(repositoryName);
  1659. for (RefModel ref : JGitUtils.getLocalBranches(repository, true, -1)) {
  1660. if (!ref.getDate().after(cutoff)) {
  1661. // branch not recently updated
  1662. continue;
  1663. }
  1664. List<?> commits = CommitCache.instance().getCommits(repositoryName, repository, ref.getName());
  1665. if (commits.size() > 0) {
  1666. logger.info(MessageFormat.format(" cached {0} commits for {1}:{2}",
  1667. commits.size(), repositoryName, ref.getName()));
  1668. commitCount += commits.size();
  1669. }
  1670. }
  1671. repository.close();
  1672. }
  1673. }
  1674. logger.info(MessageFormat.format("built {0} day commit cache of {1} commits across {2} repositories in {3} msecs",
  1675. daysToCache, commitCount, repoCount, TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start)));
  1676. }
  1677. }
  1678. protected void confirmWriteAccess() {
  1679. if (runtimeManager.isServingRepositories()) {
  1680. try {
  1681. File file = File.createTempFile(".test-", ".txt", getRepositoriesFolder());
  1682. file.delete();
  1683. } catch (Exception e) {
  1684. logger.error("");
  1685. logger.error(Constants.BORDER2);
  1686. logger.error("Please check filesystem permissions!");
  1687. logger.error("FAILED TO WRITE TO REPOSITORIES FOLDER!!", e);
  1688. logger.error(Constants.BORDER2);
  1689. logger.error("");
  1690. }
  1691. }
  1692. }
  1693. }