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.

JGitUtils.java 66KB

11 years ago
11 years ago
10 years ago
10 years ago
10 years ago
10 years ago
13 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
13 years ago
13 years ago
13 years ago
13 years ago
11 years ago
11 years ago
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099
  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.utils;
  17. import java.io.ByteArrayOutputStream;
  18. import java.io.File;
  19. import java.io.IOException;
  20. import java.io.InputStream;
  21. import java.text.DecimalFormat;
  22. import java.text.MessageFormat;
  23. import java.util.ArrayList;
  24. import java.util.Arrays;
  25. import java.util.Collections;
  26. import java.util.Date;
  27. import java.util.HashMap;
  28. import java.util.Iterator;
  29. import java.util.List;
  30. import java.util.Map;
  31. import java.util.Map.Entry;
  32. import java.util.regex.Pattern;
  33. import org.apache.commons.io.filefilter.TrueFileFilter;
  34. import org.eclipse.jgit.api.CloneCommand;
  35. import org.eclipse.jgit.api.FetchCommand;
  36. import org.eclipse.jgit.api.Git;
  37. import org.eclipse.jgit.api.TagCommand;
  38. import org.eclipse.jgit.api.errors.GitAPIException;
  39. import org.eclipse.jgit.diff.DiffEntry;
  40. import org.eclipse.jgit.diff.DiffEntry.ChangeType;
  41. import org.eclipse.jgit.diff.DiffFormatter;
  42. import org.eclipse.jgit.diff.RawTextComparator;
  43. import org.eclipse.jgit.errors.ConfigInvalidException;
  44. import org.eclipse.jgit.errors.IncorrectObjectTypeException;
  45. import org.eclipse.jgit.errors.MissingObjectException;
  46. import org.eclipse.jgit.errors.StopWalkException;
  47. import org.eclipse.jgit.lib.BlobBasedConfig;
  48. import org.eclipse.jgit.lib.CommitBuilder;
  49. import org.eclipse.jgit.lib.Constants;
  50. import org.eclipse.jgit.lib.FileMode;
  51. import org.eclipse.jgit.lib.ObjectId;
  52. import org.eclipse.jgit.lib.ObjectInserter;
  53. import org.eclipse.jgit.lib.ObjectLoader;
  54. import org.eclipse.jgit.lib.PersonIdent;
  55. import org.eclipse.jgit.lib.Ref;
  56. import org.eclipse.jgit.lib.RefUpdate;
  57. import org.eclipse.jgit.lib.RefUpdate.Result;
  58. import org.eclipse.jgit.lib.Repository;
  59. import org.eclipse.jgit.lib.RepositoryCache.FileKey;
  60. import org.eclipse.jgit.lib.StoredConfig;
  61. import org.eclipse.jgit.lib.TreeFormatter;
  62. import org.eclipse.jgit.revwalk.RevBlob;
  63. import org.eclipse.jgit.revwalk.RevCommit;
  64. import org.eclipse.jgit.revwalk.RevObject;
  65. import org.eclipse.jgit.revwalk.RevSort;
  66. import org.eclipse.jgit.revwalk.RevTree;
  67. import org.eclipse.jgit.revwalk.RevWalk;
  68. import org.eclipse.jgit.revwalk.filter.CommitTimeRevFilter;
  69. import org.eclipse.jgit.revwalk.filter.RevFilter;
  70. import org.eclipse.jgit.storage.file.FileRepositoryBuilder;
  71. import org.eclipse.jgit.transport.CredentialsProvider;
  72. import org.eclipse.jgit.transport.FetchResult;
  73. import org.eclipse.jgit.transport.RefSpec;
  74. import org.eclipse.jgit.treewalk.TreeWalk;
  75. import org.eclipse.jgit.treewalk.filter.AndTreeFilter;
  76. import org.eclipse.jgit.treewalk.filter.OrTreeFilter;
  77. import org.eclipse.jgit.treewalk.filter.PathFilter;
  78. import org.eclipse.jgit.treewalk.filter.PathFilterGroup;
  79. import org.eclipse.jgit.treewalk.filter.PathSuffixFilter;
  80. import org.eclipse.jgit.treewalk.filter.TreeFilter;
  81. import org.eclipse.jgit.util.FS;
  82. import org.slf4j.Logger;
  83. import org.slf4j.LoggerFactory;
  84. import com.gitblit.models.GitNote;
  85. import com.gitblit.models.PathModel;
  86. import com.gitblit.models.PathModel.PathChangeModel;
  87. import com.gitblit.models.RefModel;
  88. import com.gitblit.models.SubmoduleModel;
  89. /**
  90. * Collection of static methods for retrieving information from a repository.
  91. *
  92. * @author James Moger
  93. *
  94. */
  95. public class JGitUtils {
  96. static final Logger LOGGER = LoggerFactory.getLogger(JGitUtils.class);
  97. /**
  98. * Log an error message and exception.
  99. *
  100. * @param t
  101. * @param repository
  102. * if repository is not null it MUST be the {0} parameter in the
  103. * pattern.
  104. * @param pattern
  105. * @param objects
  106. */
  107. private static void error(Throwable t, Repository repository, String pattern, Object... objects) {
  108. List<Object> parameters = new ArrayList<Object>();
  109. if (objects != null && objects.length > 0) {
  110. for (Object o : objects) {
  111. parameters.add(o);
  112. }
  113. }
  114. if (repository != null) {
  115. parameters.add(0, repository.getDirectory().getAbsolutePath());
  116. }
  117. LOGGER.error(MessageFormat.format(pattern, parameters.toArray()), t);
  118. }
  119. /**
  120. * Returns the displayable name of the person in the form "Real Name <email
  121. * address>". If the email address is empty, just "Real Name" is returned.
  122. *
  123. * @param person
  124. * @return "Real Name <email address>" or "Real Name"
  125. */
  126. public static String getDisplayName(PersonIdent person) {
  127. if (StringUtils.isEmpty(person.getEmailAddress())) {
  128. return person.getName();
  129. }
  130. final StringBuilder r = new StringBuilder();
  131. r.append(person.getName());
  132. r.append(" <");
  133. r.append(person.getEmailAddress());
  134. r.append('>');
  135. return r.toString().trim();
  136. }
  137. /**
  138. * Encapsulates the result of cloning or pulling from a repository.
  139. */
  140. public static class CloneResult {
  141. public String name;
  142. public FetchResult fetchResult;
  143. public boolean createdRepository;
  144. }
  145. /**
  146. * Clone or Fetch a repository. If the local repository does not exist,
  147. * clone is called. If the repository does exist, fetch is called. By
  148. * default the clone/fetch retrieves the remote heads, tags, and notes.
  149. *
  150. * @param repositoriesFolder
  151. * @param name
  152. * @param fromUrl
  153. * @return CloneResult
  154. * @throws Exception
  155. */
  156. public static CloneResult cloneRepository(File repositoriesFolder, String name, String fromUrl)
  157. throws Exception {
  158. return cloneRepository(repositoriesFolder, name, fromUrl, true, null);
  159. }
  160. /**
  161. * Clone or Fetch a repository. If the local repository does not exist,
  162. * clone is called. If the repository does exist, fetch is called. By
  163. * default the clone/fetch retrieves the remote heads, tags, and notes.
  164. *
  165. * @param repositoriesFolder
  166. * @param name
  167. * @param fromUrl
  168. * @param bare
  169. * @param credentialsProvider
  170. * @return CloneResult
  171. * @throws Exception
  172. */
  173. public static CloneResult cloneRepository(File repositoriesFolder, String name, String fromUrl,
  174. boolean bare, CredentialsProvider credentialsProvider) throws Exception {
  175. CloneResult result = new CloneResult();
  176. if (bare) {
  177. // bare repository, ensure .git suffix
  178. if (!name.toLowerCase().endsWith(Constants.DOT_GIT_EXT)) {
  179. name += Constants.DOT_GIT_EXT;
  180. }
  181. } else {
  182. // normal repository, strip .git suffix
  183. if (name.toLowerCase().endsWith(Constants.DOT_GIT_EXT)) {
  184. name = name.substring(0, name.indexOf(Constants.DOT_GIT_EXT));
  185. }
  186. }
  187. result.name = name;
  188. File folder = new File(repositoriesFolder, name);
  189. if (folder.exists()) {
  190. File gitDir = FileKey.resolve(new File(repositoriesFolder, name), FS.DETECTED);
  191. Repository repository = new FileRepositoryBuilder().setGitDir(gitDir).build();
  192. result.fetchResult = fetchRepository(credentialsProvider, repository);
  193. repository.close();
  194. } else {
  195. CloneCommand clone = new CloneCommand();
  196. clone.setBare(bare);
  197. clone.setCloneAllBranches(true);
  198. clone.setURI(fromUrl);
  199. clone.setDirectory(folder);
  200. if (credentialsProvider != null) {
  201. clone.setCredentialsProvider(credentialsProvider);
  202. }
  203. Repository repository = clone.call().getRepository();
  204. // Now we have to fetch because CloneCommand doesn't fetch
  205. // refs/notes nor does it allow manual RefSpec.
  206. result.createdRepository = true;
  207. result.fetchResult = fetchRepository(credentialsProvider, repository);
  208. repository.close();
  209. }
  210. return result;
  211. }
  212. /**
  213. * Fetch updates from the remote repository. If refSpecs is unspecifed,
  214. * remote heads, tags, and notes are retrieved.
  215. *
  216. * @param credentialsProvider
  217. * @param repository
  218. * @param refSpecs
  219. * @return FetchResult
  220. * @throws Exception
  221. */
  222. public static FetchResult fetchRepository(CredentialsProvider credentialsProvider,
  223. Repository repository, RefSpec... refSpecs) throws Exception {
  224. Git git = new Git(repository);
  225. FetchCommand fetch = git.fetch();
  226. List<RefSpec> specs = new ArrayList<RefSpec>();
  227. if (refSpecs == null || refSpecs.length == 0) {
  228. specs.add(new RefSpec("+refs/heads/*:refs/remotes/origin/*"));
  229. specs.add(new RefSpec("+refs/tags/*:refs/tags/*"));
  230. specs.add(new RefSpec("+refs/notes/*:refs/notes/*"));
  231. } else {
  232. specs.addAll(Arrays.asList(refSpecs));
  233. }
  234. if (credentialsProvider != null) {
  235. fetch.setCredentialsProvider(credentialsProvider);
  236. }
  237. fetch.setRefSpecs(specs);
  238. FetchResult fetchRes = fetch.call();
  239. return fetchRes;
  240. }
  241. /**
  242. * Creates a bare repository.
  243. *
  244. * @param repositoriesFolder
  245. * @param name
  246. * @return Repository
  247. */
  248. public static Repository createRepository(File repositoriesFolder, String name) {
  249. return createRepository(repositoriesFolder, name, "FALSE");
  250. }
  251. /**
  252. * Creates a bare, shared repository.
  253. *
  254. * @param repositoriesFolder
  255. * @param name
  256. * @param shared
  257. * the setting for the --shared option of "git init".
  258. * @return Repository
  259. */
  260. public static Repository createRepository(File repositoriesFolder, String name, String shared) {
  261. try {
  262. Repository repo = null;
  263. try {
  264. Git git = Git.init().setDirectory(new File(repositoriesFolder, name)).setBare(true).call();
  265. repo = git.getRepository();
  266. } catch (GitAPIException e) {
  267. throw new RuntimeException(e);
  268. }
  269. GitConfigSharedRepository sharedRepository = new GitConfigSharedRepository(shared);
  270. if (sharedRepository.isShared()) {
  271. StoredConfig config = repo.getConfig();
  272. config.setString("core", null, "sharedRepository", sharedRepository.getValue());
  273. config.setBoolean("receive", null, "denyNonFastforwards", true);
  274. config.save();
  275. if (! JnaUtils.isWindows()) {
  276. Iterator<File> iter = org.apache.commons.io.FileUtils.iterateFilesAndDirs(repo.getDirectory(),
  277. TrueFileFilter.INSTANCE, TrueFileFilter.INSTANCE);
  278. // Adjust permissions on file/directory
  279. while (iter.hasNext()) {
  280. adjustSharedPerm(iter.next(), sharedRepository);
  281. }
  282. }
  283. }
  284. return repo;
  285. } catch (IOException e) {
  286. throw new RuntimeException(e);
  287. }
  288. }
  289. private enum GitConfigSharedRepositoryValue
  290. {
  291. UMASK("0", 0), FALSE("0", 0), OFF("0", 0), NO("0", 0),
  292. GROUP("1", 0660), TRUE("1", 0660), ON("1", 0660), YES("1", 0660),
  293. ALL("2", 0664), WORLD("2", 0664), EVERYBODY("2", 0664),
  294. Oxxx(null, -1);
  295. private String configValue;
  296. private int permValue;
  297. private GitConfigSharedRepositoryValue(String config, int perm) { configValue = config; permValue = perm; };
  298. public String getConfigValue() { return configValue; };
  299. public int getPerm() { return permValue; };
  300. }
  301. private static class GitConfigSharedRepository
  302. {
  303. private int intValue;
  304. private GitConfigSharedRepositoryValue enumValue;
  305. GitConfigSharedRepository(String s) {
  306. if ( s == null || s.trim().isEmpty() ) {
  307. enumValue = GitConfigSharedRepositoryValue.GROUP;
  308. }
  309. else {
  310. try {
  311. // Try one of the string values
  312. enumValue = GitConfigSharedRepositoryValue.valueOf(s.trim().toUpperCase());
  313. } catch (IllegalArgumentException iae) {
  314. try {
  315. // Try if this is an octal number
  316. int i = Integer.parseInt(s, 8);
  317. if ( (i & 0600) != 0600 ) {
  318. String msg = String.format("Problem with core.sharedRepository filemode value (0%03o).\nThe owner of files must always have read and write permissions.", i);
  319. throw new IllegalArgumentException(msg);
  320. }
  321. intValue = i & 0666;
  322. enumValue = GitConfigSharedRepositoryValue.Oxxx;
  323. } catch (NumberFormatException nfe) {
  324. throw new IllegalArgumentException("Bad configuration value for 'shared': '" + s + "'");
  325. }
  326. }
  327. }
  328. }
  329. String getValue() {
  330. if ( enumValue == GitConfigSharedRepositoryValue.Oxxx ) {
  331. if (intValue == 0) return "0";
  332. return String.format("0%o", intValue);
  333. }
  334. return enumValue.getConfigValue();
  335. }
  336. int getPerm() {
  337. if ( enumValue == GitConfigSharedRepositoryValue.Oxxx ) return intValue;
  338. return enumValue.getPerm();
  339. }
  340. boolean isCustom() {
  341. return enumValue == GitConfigSharedRepositoryValue.Oxxx;
  342. }
  343. boolean isShared() {
  344. return (enumValue.getPerm() > 0) || enumValue == GitConfigSharedRepositoryValue.Oxxx;
  345. }
  346. }
  347. /**
  348. * Adjust file permissions of a file/directory for shared repositories
  349. *
  350. * @param path
  351. * File that should get its permissions changed.
  352. * @param configShared
  353. * Configuration string value for the shared mode.
  354. * @return Upon successful completion, a value of 0 is returned. Otherwise, a value of -1 is returned.
  355. */
  356. public static int adjustSharedPerm(File path, String configShared) {
  357. return adjustSharedPerm(path, new GitConfigSharedRepository(configShared));
  358. }
  359. /**
  360. * Adjust file permissions of a file/directory for shared repositories
  361. *
  362. * @param path
  363. * File that should get its permissions changed.
  364. * @param configShared
  365. * Configuration setting for the shared mode.
  366. * @return Upon successful completion, a value of 0 is returned. Otherwise, a value of -1 is returned.
  367. */
  368. public static int adjustSharedPerm(File path, GitConfigSharedRepository configShared) {
  369. if (! configShared.isShared()) return 0;
  370. if (! path.exists()) return -1;
  371. int perm = configShared.getPerm();
  372. JnaUtils.Filestat stat = JnaUtils.getFilestat(path);
  373. if (stat == null) return -1;
  374. int mode = stat.mode;
  375. if (mode < 0) return -1;
  376. // Now, here is the kicker: Under Linux, chmod'ing a sgid file whose guid is different from the process'
  377. // effective guid will reset the sgid flag of the file. Since there is no way to get the sgid flag back in
  378. // that case, we decide to rather not touch is and getting the right permissions will have to be achieved
  379. // in a different way, e.g. by using an appropriate umask for the Gitblit process.
  380. if (System.getProperty("os.name").toLowerCase().startsWith("linux")) {
  381. if ( ((mode & (JnaUtils.S_ISGID | JnaUtils.S_ISUID)) != 0)
  382. && stat.gid != JnaUtils.getegid() ) {
  383. LOGGER.debug("Not adjusting permissions to prevent clearing suid/sgid bits for '" + path + "'" );
  384. return 0;
  385. }
  386. }
  387. // If the owner has no write access, delete it from group and other, too.
  388. if ((mode & JnaUtils.S_IWUSR) == 0) perm &= ~0222;
  389. // If the owner has execute access, set it for all blocks that have read access.
  390. if ((mode & JnaUtils.S_IXUSR) == JnaUtils.S_IXUSR) perm |= (perm & 0444) >> 2;
  391. if (configShared.isCustom()) {
  392. // Use the custom value for access permissions.
  393. mode = (mode & ~0777) | perm;
  394. }
  395. else {
  396. // Just add necessary bits to existing permissions.
  397. mode |= perm;
  398. }
  399. if (path.isDirectory()) {
  400. mode |= (mode & 0444) >> 2;
  401. mode |= JnaUtils.S_ISGID;
  402. }
  403. return JnaUtils.setFilemode(path, mode);
  404. }
  405. /**
  406. * Returns a list of repository names in the specified folder.
  407. *
  408. * @param repositoriesFolder
  409. * @param onlyBare
  410. * if true, only bare repositories repositories are listed. If
  411. * false all repositories are included.
  412. * @param searchSubfolders
  413. * recurse into subfolders to find grouped repositories
  414. * @param depth
  415. * optional recursion depth, -1 = infinite recursion
  416. * @param exclusions
  417. * list of regex exclusions for matching to folder names
  418. * @return list of repository names
  419. */
  420. public static List<String> getRepositoryList(File repositoriesFolder, boolean onlyBare,
  421. boolean searchSubfolders, int depth, List<String> exclusions) {
  422. List<String> list = new ArrayList<String>();
  423. if (repositoriesFolder == null || !repositoriesFolder.exists()) {
  424. return list;
  425. }
  426. List<Pattern> patterns = new ArrayList<Pattern>();
  427. if (!ArrayUtils.isEmpty(exclusions)) {
  428. for (String regex : exclusions) {
  429. patterns.add(Pattern.compile(regex));
  430. }
  431. }
  432. list.addAll(getRepositoryList(repositoriesFolder.getAbsolutePath(), repositoriesFolder,
  433. onlyBare, searchSubfolders, depth, patterns));
  434. StringUtils.sortRepositorynames(list);
  435. list.remove(".git"); // issue-256
  436. return list;
  437. }
  438. /**
  439. * Recursive function to find git repositories.
  440. *
  441. * @param basePath
  442. * basePath is stripped from the repository name as repositories
  443. * are relative to this path
  444. * @param searchFolder
  445. * @param onlyBare
  446. * if true only bare repositories will be listed. if false all
  447. * repositories are included.
  448. * @param searchSubfolders
  449. * recurse into subfolders to find grouped repositories
  450. * @param depth
  451. * recursion depth, -1 = infinite recursion
  452. * @param patterns
  453. * list of regex patterns for matching to folder names
  454. * @return
  455. */
  456. private static List<String> getRepositoryList(String basePath, File searchFolder,
  457. boolean onlyBare, boolean searchSubfolders, int depth, List<Pattern> patterns) {
  458. File baseFile = new File(basePath);
  459. List<String> list = new ArrayList<String>();
  460. if (depth == 0) {
  461. return list;
  462. }
  463. int nextDepth = (depth == -1) ? -1 : depth - 1;
  464. for (File file : searchFolder.listFiles()) {
  465. if (file.isDirectory()) {
  466. boolean exclude = false;
  467. for (Pattern pattern : patterns) {
  468. String path = FileUtils.getRelativePath(baseFile, file).replace('\\', '/');
  469. if (pattern.matcher(path).matches()) {
  470. LOGGER.debug(MessageFormat.format("excluding {0} because of rule {1}", path, pattern.pattern()));
  471. exclude = true;
  472. break;
  473. }
  474. }
  475. if (exclude) {
  476. // skip to next file
  477. continue;
  478. }
  479. File gitDir = FileKey.resolve(new File(searchFolder, file.getName()), FS.DETECTED);
  480. if (gitDir != null) {
  481. if (onlyBare && gitDir.getName().equals(".git")) {
  482. continue;
  483. }
  484. if (gitDir.equals(file) || gitDir.getParentFile().equals(file)) {
  485. // determine repository name relative to base path
  486. String repository = FileUtils.getRelativePath(baseFile, file);
  487. list.add(repository);
  488. } else if (searchSubfolders && file.canRead()) {
  489. // look for repositories in subfolders
  490. list.addAll(getRepositoryList(basePath, file, onlyBare, searchSubfolders,
  491. nextDepth, patterns));
  492. }
  493. } else if (searchSubfolders && file.canRead()) {
  494. // look for repositories in subfolders
  495. list.addAll(getRepositoryList(basePath, file, onlyBare, searchSubfolders,
  496. nextDepth, patterns));
  497. }
  498. }
  499. }
  500. return list;
  501. }
  502. /**
  503. * Returns the first commit on a branch. If the repository does not exist or
  504. * is empty, null is returned.
  505. *
  506. * @param repository
  507. * @param branch
  508. * if unspecified, HEAD is assumed.
  509. * @return RevCommit
  510. */
  511. public static RevCommit getFirstCommit(Repository repository, String branch) {
  512. if (!hasCommits(repository)) {
  513. return null;
  514. }
  515. RevCommit commit = null;
  516. try {
  517. // resolve branch
  518. ObjectId branchObject;
  519. if (StringUtils.isEmpty(branch)) {
  520. branchObject = getDefaultBranch(repository);
  521. } else {
  522. branchObject = repository.resolve(branch);
  523. }
  524. RevWalk walk = new RevWalk(repository);
  525. walk.sort(RevSort.REVERSE);
  526. RevCommit head = walk.parseCommit(branchObject);
  527. walk.markStart(head);
  528. commit = walk.next();
  529. walk.dispose();
  530. } catch (Throwable t) {
  531. error(t, repository, "{0} failed to determine first commit");
  532. }
  533. return commit;
  534. }
  535. /**
  536. * Returns the date of the first commit on a branch. If the repository does
  537. * not exist, Date(0) is returned. If the repository does exist bit is
  538. * empty, the last modified date of the repository folder is returned.
  539. *
  540. * @param repository
  541. * @param branch
  542. * if unspecified, HEAD is assumed.
  543. * @return Date of the first commit on a branch
  544. */
  545. public static Date getFirstChange(Repository repository, String branch) {
  546. RevCommit commit = getFirstCommit(repository, branch);
  547. if (commit == null) {
  548. if (repository == null || !repository.getDirectory().exists()) {
  549. return new Date(0);
  550. }
  551. // fresh repository
  552. return new Date(repository.getDirectory().lastModified());
  553. }
  554. return getCommitDate(commit);
  555. }
  556. /**
  557. * Determine if a repository has any commits. This is determined by checking
  558. * the for loose and packed objects.
  559. *
  560. * @param repository
  561. * @return true if the repository has commits
  562. */
  563. public static boolean hasCommits(Repository repository) {
  564. if (repository != null && repository.getDirectory().exists()) {
  565. return (new File(repository.getDirectory(), "objects").list().length > 2)
  566. || (new File(repository.getDirectory(), "objects/pack").list().length > 0);
  567. }
  568. return false;
  569. }
  570. /**
  571. * Encapsulates the result of cloning or pulling from a repository.
  572. */
  573. public static class LastChange {
  574. public Date when;
  575. public String who;
  576. LastChange() {
  577. when = new Date(0);
  578. }
  579. LastChange(long lastModified) {
  580. this.when = new Date(lastModified);
  581. }
  582. }
  583. /**
  584. * Returns the date and author of the most recent commit on a branch. If the
  585. * repository does not exist Date(0) is returned. If it does exist but is
  586. * empty, the last modified date of the repository folder is returned.
  587. *
  588. * @param repository
  589. * @return a LastChange object
  590. */
  591. public static LastChange getLastChange(Repository repository) {
  592. if (!hasCommits(repository)) {
  593. // null repository
  594. if (repository == null) {
  595. return new LastChange();
  596. }
  597. // fresh repository
  598. return new LastChange(repository.getDirectory().lastModified());
  599. }
  600. List<RefModel> branchModels = getLocalBranches(repository, true, -1);
  601. if (branchModels.size() > 0) {
  602. // find most recent branch update
  603. LastChange lastChange = new LastChange();
  604. for (RefModel branchModel : branchModels) {
  605. if (branchModel.getDate().after(lastChange.when)) {
  606. lastChange.when = branchModel.getDate();
  607. lastChange.who = branchModel.getAuthorIdent().getName();
  608. }
  609. }
  610. return lastChange;
  611. }
  612. // default to the repository folder modification date
  613. return new LastChange(repository.getDirectory().lastModified());
  614. }
  615. /**
  616. * Retrieves a Java Date from a Git commit.
  617. *
  618. * @param commit
  619. * @return date of the commit or Date(0) if the commit is null
  620. */
  621. public static Date getCommitDate(RevCommit commit) {
  622. if (commit == null) {
  623. return new Date(0);
  624. }
  625. return new Date(commit.getCommitTime() * 1000L);
  626. }
  627. /**
  628. * Retrieves a Java Date from a Git commit.
  629. *
  630. * @param commit
  631. * @return date of the commit or Date(0) if the commit is null
  632. */
  633. public static Date getAuthorDate(RevCommit commit) {
  634. if (commit == null) {
  635. return new Date(0);
  636. }
  637. return commit.getAuthorIdent().getWhen();
  638. }
  639. /**
  640. * Returns the specified commit from the repository. If the repository does
  641. * not exist or is empty, null is returned.
  642. *
  643. * @param repository
  644. * @param objectId
  645. * if unspecified, HEAD is assumed.
  646. * @return RevCommit
  647. */
  648. public static RevCommit getCommit(Repository repository, String objectId) {
  649. if (!hasCommits(repository)) {
  650. return null;
  651. }
  652. RevCommit commit = null;
  653. try {
  654. // resolve object id
  655. ObjectId branchObject;
  656. if (StringUtils.isEmpty(objectId)) {
  657. branchObject = getDefaultBranch(repository);
  658. } else {
  659. branchObject = repository.resolve(objectId);
  660. }
  661. RevWalk walk = new RevWalk(repository);
  662. RevCommit rev = walk.parseCommit(branchObject);
  663. commit = rev;
  664. walk.dispose();
  665. } catch (Throwable t) {
  666. error(t, repository, "{0} failed to get commit {1}", objectId);
  667. }
  668. return commit;
  669. }
  670. /**
  671. * Retrieves the raw byte content of a file in the specified tree.
  672. *
  673. * @param repository
  674. * @param tree
  675. * if null, the RevTree from HEAD is assumed.
  676. * @param path
  677. * @return content as a byte []
  678. */
  679. public static byte[] getByteContent(Repository repository, RevTree tree, final String path, boolean throwError) {
  680. RevWalk rw = new RevWalk(repository);
  681. TreeWalk tw = new TreeWalk(repository);
  682. tw.setFilter(PathFilterGroup.createFromStrings(Collections.singleton(path)));
  683. byte[] content = null;
  684. try {
  685. if (tree == null) {
  686. ObjectId object = getDefaultBranch(repository);
  687. if (object == null)
  688. return null;
  689. RevCommit commit = rw.parseCommit(object);
  690. tree = commit.getTree();
  691. }
  692. tw.reset(tree);
  693. while (tw.next()) {
  694. if (tw.isSubtree() && !path.equals(tw.getPathString())) {
  695. tw.enterSubtree();
  696. continue;
  697. }
  698. ObjectId entid = tw.getObjectId(0);
  699. FileMode entmode = tw.getFileMode(0);
  700. if (entmode != FileMode.GITLINK) {
  701. RevObject ro = rw.lookupAny(entid, entmode.getObjectType());
  702. rw.parseBody(ro);
  703. ByteArrayOutputStream os = new ByteArrayOutputStream();
  704. ObjectLoader ldr = repository.open(ro.getId(), Constants.OBJ_BLOB);
  705. byte[] tmp = new byte[4096];
  706. InputStream in = ldr.openStream();
  707. int n;
  708. while ((n = in.read(tmp)) > 0) {
  709. os.write(tmp, 0, n);
  710. }
  711. in.close();
  712. content = os.toByteArray();
  713. }
  714. }
  715. } catch (Throwable t) {
  716. if (throwError) {
  717. error(t, repository, "{0} can't find {1} in tree {2}", path, tree.name());
  718. }
  719. } finally {
  720. rw.dispose();
  721. tw.release();
  722. }
  723. return content;
  724. }
  725. /**
  726. * Returns the UTF-8 string content of a file in the specified tree.
  727. *
  728. * @param repository
  729. * @param tree
  730. * if null, the RevTree from HEAD is assumed.
  731. * @param blobPath
  732. * @param charsets optional
  733. * @return UTF-8 string content
  734. */
  735. public static String getStringContent(Repository repository, RevTree tree, String blobPath, String... charsets) {
  736. byte[] content = getByteContent(repository, tree, blobPath, true);
  737. if (content == null) {
  738. return null;
  739. }
  740. return StringUtils.decodeString(content, charsets);
  741. }
  742. /**
  743. * Gets the raw byte content of the specified blob object.
  744. *
  745. * @param repository
  746. * @param objectId
  747. * @return byte [] blob content
  748. */
  749. public static byte[] getByteContent(Repository repository, String objectId) {
  750. RevWalk rw = new RevWalk(repository);
  751. byte[] content = null;
  752. try {
  753. RevBlob blob = rw.lookupBlob(ObjectId.fromString(objectId));
  754. rw.parseBody(blob);
  755. ByteArrayOutputStream os = new ByteArrayOutputStream();
  756. ObjectLoader ldr = repository.open(blob.getId(), Constants.OBJ_BLOB);
  757. byte[] tmp = new byte[4096];
  758. InputStream in = ldr.openStream();
  759. int n;
  760. while ((n = in.read(tmp)) > 0) {
  761. os.write(tmp, 0, n);
  762. }
  763. in.close();
  764. content = os.toByteArray();
  765. } catch (Throwable t) {
  766. error(t, repository, "{0} can't find blob {1}", objectId);
  767. } finally {
  768. rw.dispose();
  769. }
  770. return content;
  771. }
  772. /**
  773. * Gets the UTF-8 string content of the blob specified by objectId.
  774. *
  775. * @param repository
  776. * @param objectId
  777. * @param charsets optional
  778. * @return UTF-8 string content
  779. */
  780. public static String getStringContent(Repository repository, String objectId, String... charsets) {
  781. byte[] content = getByteContent(repository, objectId);
  782. if (content == null) {
  783. return null;
  784. }
  785. return StringUtils.decodeString(content, charsets);
  786. }
  787. /**
  788. * Returns the list of files in the specified folder at the specified
  789. * commit. If the repository does not exist or is empty, an empty list is
  790. * returned.
  791. *
  792. * @param repository
  793. * @param path
  794. * if unspecified, root folder is assumed.
  795. * @param commit
  796. * if null, HEAD is assumed.
  797. * @return list of files in specified path
  798. */
  799. public static List<PathModel> getFilesInPath(Repository repository, String path,
  800. RevCommit commit) {
  801. List<PathModel> list = new ArrayList<PathModel>();
  802. if (!hasCommits(repository)) {
  803. return list;
  804. }
  805. if (commit == null) {
  806. commit = getCommit(repository, null);
  807. }
  808. final TreeWalk tw = new TreeWalk(repository);
  809. try {
  810. tw.addTree(commit.getTree());
  811. if (!StringUtils.isEmpty(path)) {
  812. PathFilter f = PathFilter.create(path);
  813. tw.setFilter(f);
  814. tw.setRecursive(false);
  815. boolean foundFolder = false;
  816. while (tw.next()) {
  817. if (!foundFolder && tw.isSubtree()) {
  818. tw.enterSubtree();
  819. }
  820. if (tw.getPathString().equals(path)) {
  821. foundFolder = true;
  822. continue;
  823. }
  824. if (foundFolder) {
  825. list.add(getPathModel(tw, path, commit));
  826. }
  827. }
  828. } else {
  829. tw.setRecursive(false);
  830. while (tw.next()) {
  831. list.add(getPathModel(tw, null, commit));
  832. }
  833. }
  834. } catch (IOException e) {
  835. error(e, repository, "{0} failed to get files for commit {1}", commit.getName());
  836. } finally {
  837. tw.release();
  838. }
  839. Collections.sort(list);
  840. return list;
  841. }
  842. /**
  843. * Returns the list of files changed in a specified commit. If the
  844. * repository does not exist or is empty, an empty list is returned.
  845. *
  846. * @param repository
  847. * @param commit
  848. * if null, HEAD is assumed.
  849. * @return list of files changed in a commit
  850. */
  851. public static List<PathChangeModel> getFilesInCommit(Repository repository, RevCommit commit) {
  852. return getFilesInCommit(repository, commit, true);
  853. }
  854. /**
  855. * Returns the list of files changed in a specified commit. If the
  856. * repository does not exist or is empty, an empty list is returned.
  857. *
  858. * @param repository
  859. * @param commit
  860. * if null, HEAD is assumed.
  861. * @param calculateDiffStat
  862. * if true, each PathChangeModel will have insertions/deletions
  863. * @return list of files changed in a commit
  864. */
  865. public static List<PathChangeModel> getFilesInCommit(Repository repository, RevCommit commit, boolean calculateDiffStat) {
  866. List<PathChangeModel> list = new ArrayList<PathChangeModel>();
  867. if (!hasCommits(repository)) {
  868. return list;
  869. }
  870. RevWalk rw = new RevWalk(repository);
  871. try {
  872. if (commit == null) {
  873. ObjectId object = getDefaultBranch(repository);
  874. commit = rw.parseCommit(object);
  875. }
  876. if (commit.getParentCount() == 0) {
  877. TreeWalk tw = new TreeWalk(repository);
  878. tw.reset();
  879. tw.setRecursive(true);
  880. tw.addTree(commit.getTree());
  881. while (tw.next()) {
  882. list.add(new PathChangeModel(tw.getPathString(), tw.getPathString(), 0, tw
  883. .getRawMode(0), tw.getObjectId(0).getName(), commit.getId().getName(),
  884. ChangeType.ADD));
  885. }
  886. tw.release();
  887. } else {
  888. RevCommit parent = rw.parseCommit(commit.getParent(0).getId());
  889. DiffStatFormatter df = new DiffStatFormatter(commit.getName());
  890. df.setRepository(repository);
  891. df.setDiffComparator(RawTextComparator.DEFAULT);
  892. df.setDetectRenames(true);
  893. List<DiffEntry> diffs = df.scan(parent.getTree(), commit.getTree());
  894. for (DiffEntry diff : diffs) {
  895. // create the path change model
  896. PathChangeModel pcm = PathChangeModel.from(diff, commit.getName());
  897. if (calculateDiffStat) {
  898. // update file diffstats
  899. df.format(diff);
  900. PathChangeModel pathStat = df.getDiffStat().getPath(pcm.path);
  901. if (pathStat != null) {
  902. pcm.insertions = pathStat.insertions;
  903. pcm.deletions = pathStat.deletions;
  904. }
  905. }
  906. list.add(pcm);
  907. }
  908. }
  909. } catch (Throwable t) {
  910. error(t, repository, "{0} failed to determine files in commit!");
  911. } finally {
  912. rw.dispose();
  913. }
  914. return list;
  915. }
  916. /**
  917. * Returns the list of files changed in a specified commit. If the
  918. * repository does not exist or is empty, an empty list is returned.
  919. *
  920. * @param repository
  921. * @param startCommit
  922. * earliest commit
  923. * @param endCommit
  924. * most recent commit. if null, HEAD is assumed.
  925. * @return list of files changed in a commit range
  926. */
  927. public static List<PathChangeModel> getFilesInRange(Repository repository, RevCommit startCommit, RevCommit endCommit) {
  928. List<PathChangeModel> list = new ArrayList<PathChangeModel>();
  929. if (!hasCommits(repository)) {
  930. return list;
  931. }
  932. try {
  933. DiffFormatter df = new DiffFormatter(null);
  934. df.setRepository(repository);
  935. df.setDiffComparator(RawTextComparator.DEFAULT);
  936. df.setDetectRenames(true);
  937. List<DiffEntry> diffEntries = df.scan(startCommit.getTree(), endCommit.getTree());
  938. for (DiffEntry diff : diffEntries) {
  939. PathChangeModel pcm = PathChangeModel.from(diff, null);
  940. list.add(pcm);
  941. }
  942. Collections.sort(list);
  943. } catch (Throwable t) {
  944. error(t, repository, "{0} failed to determine files in range {1}..{2}!", startCommit, endCommit);
  945. }
  946. return list;
  947. }
  948. /**
  949. * Returns the list of files in the repository on the default branch that
  950. * match one of the specified extensions. This is a CASE-SENSITIVE search.
  951. * If the repository does not exist or is empty, an empty list is returned.
  952. *
  953. * @param repository
  954. * @param extensions
  955. * @return list of files in repository with a matching extension
  956. */
  957. public static List<PathModel> getDocuments(Repository repository, List<String> extensions) {
  958. return getDocuments(repository, extensions, null);
  959. }
  960. /**
  961. * Returns the list of files in the repository in the specified commit that
  962. * match one of the specified extensions. This is a CASE-SENSITIVE search.
  963. * If the repository does not exist or is empty, an empty list is returned.
  964. *
  965. * @param repository
  966. * @param extensions
  967. * @param objectId
  968. * @return list of files in repository with a matching extension
  969. */
  970. public static List<PathModel> getDocuments(Repository repository, List<String> extensions,
  971. String objectId) {
  972. List<PathModel> list = new ArrayList<PathModel>();
  973. if (!hasCommits(repository)) {
  974. return list;
  975. }
  976. RevCommit commit = getCommit(repository, objectId);
  977. final TreeWalk tw = new TreeWalk(repository);
  978. try {
  979. tw.addTree(commit.getTree());
  980. if (extensions != null && extensions.size() > 0) {
  981. List<TreeFilter> suffixFilters = new ArrayList<TreeFilter>();
  982. for (String extension : extensions) {
  983. if (extension.charAt(0) == '.') {
  984. suffixFilters.add(PathSuffixFilter.create("\\" + extension));
  985. } else {
  986. // escape the . since this is a regexp filter
  987. suffixFilters.add(PathSuffixFilter.create("\\." + extension));
  988. }
  989. }
  990. TreeFilter filter;
  991. if (suffixFilters.size() == 1) {
  992. filter = suffixFilters.get(0);
  993. } else {
  994. filter = OrTreeFilter.create(suffixFilters);
  995. }
  996. tw.setFilter(filter);
  997. tw.setRecursive(true);
  998. }
  999. while (tw.next()) {
  1000. list.add(getPathModel(tw, null, commit));
  1001. }
  1002. } catch (IOException e) {
  1003. error(e, repository, "{0} failed to get documents for commit {1}", commit.getName());
  1004. } finally {
  1005. tw.release();
  1006. }
  1007. Collections.sort(list);
  1008. return list;
  1009. }
  1010. /**
  1011. * Returns a path model of the current file in the treewalk.
  1012. *
  1013. * @param tw
  1014. * @param basePath
  1015. * @param commit
  1016. * @return a path model of the current file in the treewalk
  1017. */
  1018. private static PathModel getPathModel(TreeWalk tw, String basePath, RevCommit commit) {
  1019. String name;
  1020. long size = 0;
  1021. if (StringUtils.isEmpty(basePath)) {
  1022. name = tw.getPathString();
  1023. } else {
  1024. name = tw.getPathString().substring(basePath.length() + 1);
  1025. }
  1026. ObjectId objectId = tw.getObjectId(0);
  1027. try {
  1028. if (!tw.isSubtree() && (tw.getFileMode(0) != FileMode.GITLINK)) {
  1029. size = tw.getObjectReader().getObjectSize(objectId, Constants.OBJ_BLOB);
  1030. }
  1031. } catch (Throwable t) {
  1032. error(t, null, "failed to retrieve blob size for " + tw.getPathString());
  1033. }
  1034. return new PathModel(name, tw.getPathString(), size, tw.getFileMode(0).getBits(),
  1035. objectId.getName(), commit.getName());
  1036. }
  1037. /**
  1038. * Returns a permissions representation of the mode bits.
  1039. *
  1040. * @param mode
  1041. * @return string representation of the mode bits
  1042. */
  1043. public static String getPermissionsFromMode(int mode) {
  1044. if (FileMode.TREE.equals(mode)) {
  1045. return "drwxr-xr-x";
  1046. } else if (FileMode.REGULAR_FILE.equals(mode)) {
  1047. return "-rw-r--r--";
  1048. } else if (FileMode.EXECUTABLE_FILE.equals(mode)) {
  1049. return "-rwxr-xr-x";
  1050. } else if (FileMode.SYMLINK.equals(mode)) {
  1051. return "symlink";
  1052. } else if (FileMode.GITLINK.equals(mode)) {
  1053. return "submodule";
  1054. }
  1055. return "missing";
  1056. }
  1057. /**
  1058. * Returns a list of commits since the minimum date starting from the
  1059. * specified object id.
  1060. *
  1061. * @param repository
  1062. * @param objectId
  1063. * if unspecified, HEAD is assumed.
  1064. * @param minimumDate
  1065. * @return list of commits
  1066. */
  1067. public static List<RevCommit> getRevLog(Repository repository, String objectId, Date minimumDate) {
  1068. List<RevCommit> list = new ArrayList<RevCommit>();
  1069. if (!hasCommits(repository)) {
  1070. return list;
  1071. }
  1072. try {
  1073. // resolve branch
  1074. ObjectId branchObject;
  1075. if (StringUtils.isEmpty(objectId)) {
  1076. branchObject = getDefaultBranch(repository);
  1077. } else {
  1078. branchObject = repository.resolve(objectId);
  1079. }
  1080. RevWalk rw = new RevWalk(repository);
  1081. rw.markStart(rw.parseCommit(branchObject));
  1082. rw.setRevFilter(CommitTimeRevFilter.after(minimumDate));
  1083. Iterable<RevCommit> revlog = rw;
  1084. for (RevCommit rev : revlog) {
  1085. list.add(rev);
  1086. }
  1087. rw.dispose();
  1088. } catch (Throwable t) {
  1089. error(t, repository, "{0} failed to get {1} revlog for minimum date {2}", objectId,
  1090. minimumDate);
  1091. }
  1092. return list;
  1093. }
  1094. /**
  1095. * Returns a list of commits starting from HEAD and working backwards.
  1096. *
  1097. * @param repository
  1098. * @param maxCount
  1099. * if < 0, all commits for the repository are returned.
  1100. * @return list of commits
  1101. */
  1102. public static List<RevCommit> getRevLog(Repository repository, int maxCount) {
  1103. return getRevLog(repository, null, 0, maxCount);
  1104. }
  1105. /**
  1106. * Returns a list of commits starting from the specified objectId using an
  1107. * offset and maxCount for paging. This is similar to LIMIT n OFFSET p in
  1108. * SQL. If the repository does not exist or is empty, an empty list is
  1109. * returned.
  1110. *
  1111. * @param repository
  1112. * @param objectId
  1113. * if unspecified, HEAD is assumed.
  1114. * @param offset
  1115. * @param maxCount
  1116. * if < 0, all commits are returned.
  1117. * @return a paged list of commits
  1118. */
  1119. public static List<RevCommit> getRevLog(Repository repository, String objectId, int offset,
  1120. int maxCount) {
  1121. return getRevLog(repository, objectId, null, offset, maxCount);
  1122. }
  1123. /**
  1124. * Returns a list of commits for the repository or a path within the
  1125. * repository. Caller may specify ending revision with objectId. Caller may
  1126. * specify offset and maxCount to achieve pagination of results. If the
  1127. * repository does not exist or is empty, an empty list is returned.
  1128. *
  1129. * @param repository
  1130. * @param objectId
  1131. * if unspecified, HEAD is assumed.
  1132. * @param path
  1133. * if unspecified, commits for repository are returned. If
  1134. * specified, commits for the path are returned.
  1135. * @param offset
  1136. * @param maxCount
  1137. * if < 0, all commits are returned.
  1138. * @return a paged list of commits
  1139. */
  1140. public static List<RevCommit> getRevLog(Repository repository, String objectId, String path,
  1141. int offset, int maxCount) {
  1142. List<RevCommit> list = new ArrayList<RevCommit>();
  1143. if (maxCount == 0) {
  1144. return list;
  1145. }
  1146. if (!hasCommits(repository)) {
  1147. return list;
  1148. }
  1149. try {
  1150. // resolve branch
  1151. ObjectId startRange = null;
  1152. ObjectId endRange;
  1153. if (StringUtils.isEmpty(objectId)) {
  1154. endRange = getDefaultBranch(repository);
  1155. } else {
  1156. if( objectId.contains("..") ) {
  1157. // range expression
  1158. String[] parts = objectId.split("\\.\\.");
  1159. startRange = repository.resolve(parts[0]);
  1160. endRange = repository.resolve(parts[1]);
  1161. } else {
  1162. // objectid
  1163. endRange= repository.resolve(objectId);
  1164. }
  1165. }
  1166. if (endRange == null) {
  1167. return list;
  1168. }
  1169. RevWalk rw = new RevWalk(repository);
  1170. rw.markStart(rw.parseCommit(endRange));
  1171. if (startRange != null) {
  1172. rw.markUninteresting(rw.parseCommit(startRange));
  1173. }
  1174. if (!StringUtils.isEmpty(path)) {
  1175. TreeFilter filter = AndTreeFilter.create(
  1176. PathFilterGroup.createFromStrings(Collections.singleton(path)),
  1177. TreeFilter.ANY_DIFF);
  1178. rw.setTreeFilter(filter);
  1179. }
  1180. Iterable<RevCommit> revlog = rw;
  1181. if (offset > 0) {
  1182. int count = 0;
  1183. for (RevCommit rev : revlog) {
  1184. count++;
  1185. if (count > offset) {
  1186. list.add(rev);
  1187. if (maxCount > 0 && list.size() == maxCount) {
  1188. break;
  1189. }
  1190. }
  1191. }
  1192. } else {
  1193. for (RevCommit rev : revlog) {
  1194. list.add(rev);
  1195. if (maxCount > 0 && list.size() == maxCount) {
  1196. break;
  1197. }
  1198. }
  1199. }
  1200. rw.dispose();
  1201. } catch (Throwable t) {
  1202. error(t, repository, "{0} failed to get {1} revlog for path {2}", objectId, path);
  1203. }
  1204. return list;
  1205. }
  1206. /**
  1207. * Returns a list of commits for the repository within the range specified
  1208. * by startRangeId and endRangeId. If the repository does not exist or is
  1209. * empty, an empty list is returned.
  1210. *
  1211. * @param repository
  1212. * @param startRangeId
  1213. * the first commit (not included in results)
  1214. * @param endRangeId
  1215. * the end commit (included in results)
  1216. * @return a list of commits
  1217. */
  1218. public static List<RevCommit> getRevLog(Repository repository, String startRangeId,
  1219. String endRangeId) {
  1220. List<RevCommit> list = new ArrayList<RevCommit>();
  1221. if (!hasCommits(repository)) {
  1222. return list;
  1223. }
  1224. try {
  1225. ObjectId endRange = repository.resolve(endRangeId);
  1226. ObjectId startRange = repository.resolve(startRangeId);
  1227. RevWalk rw = new RevWalk(repository);
  1228. rw.markStart(rw.parseCommit(endRange));
  1229. if (startRange.equals(ObjectId.zeroId())) {
  1230. // maybe this is a tag or an orphan branch
  1231. list.add(rw.parseCommit(endRange));
  1232. rw.dispose();
  1233. return list;
  1234. } else {
  1235. rw.markUninteresting(rw.parseCommit(startRange));
  1236. }
  1237. Iterable<RevCommit> revlog = rw;
  1238. for (RevCommit rev : revlog) {
  1239. list.add(rev);
  1240. }
  1241. rw.dispose();
  1242. } catch (Throwable t) {
  1243. error(t, repository, "{0} failed to get revlog for {1}..{2}", startRangeId, endRangeId);
  1244. }
  1245. return list;
  1246. }
  1247. /**
  1248. * Search the commit history for a case-insensitive match to the value.
  1249. * Search results require a specified SearchType of AUTHOR, COMMITTER, or
  1250. * COMMIT. Results may be paginated using offset and maxCount. If the
  1251. * repository does not exist or is empty, an empty list is returned.
  1252. *
  1253. * @param repository
  1254. * @param objectId
  1255. * if unspecified, HEAD is assumed.
  1256. * @param value
  1257. * @param type
  1258. * AUTHOR, COMMITTER, COMMIT
  1259. * @param offset
  1260. * @param maxCount
  1261. * if < 0, all matches are returned
  1262. * @return matching list of commits
  1263. */
  1264. public static List<RevCommit> searchRevlogs(Repository repository, String objectId,
  1265. String value, final com.gitblit.Constants.SearchType type, int offset, int maxCount) {
  1266. List<RevCommit> list = new ArrayList<RevCommit>();
  1267. if (StringUtils.isEmpty(value)) {
  1268. return list;
  1269. }
  1270. if (maxCount == 0) {
  1271. return list;
  1272. }
  1273. if (!hasCommits(repository)) {
  1274. return list;
  1275. }
  1276. final String lcValue = value.toLowerCase();
  1277. try {
  1278. // resolve branch
  1279. ObjectId branchObject;
  1280. if (StringUtils.isEmpty(objectId)) {
  1281. branchObject = getDefaultBranch(repository);
  1282. } else {
  1283. branchObject = repository.resolve(objectId);
  1284. }
  1285. RevWalk rw = new RevWalk(repository);
  1286. rw.setRevFilter(new RevFilter() {
  1287. @Override
  1288. public RevFilter clone() {
  1289. // FindBugs complains about this method name.
  1290. // This is part of JGit design and unrelated to Cloneable.
  1291. return this;
  1292. }
  1293. @Override
  1294. public boolean include(RevWalk walker, RevCommit commit) throws StopWalkException,
  1295. MissingObjectException, IncorrectObjectTypeException, IOException {
  1296. boolean include = false;
  1297. switch (type) {
  1298. case AUTHOR:
  1299. include = (commit.getAuthorIdent().getName().toLowerCase().indexOf(lcValue) > -1)
  1300. || (commit.getAuthorIdent().getEmailAddress().toLowerCase()
  1301. .indexOf(lcValue) > -1);
  1302. break;
  1303. case COMMITTER:
  1304. include = (commit.getCommitterIdent().getName().toLowerCase()
  1305. .indexOf(lcValue) > -1)
  1306. || (commit.getCommitterIdent().getEmailAddress().toLowerCase()
  1307. .indexOf(lcValue) > -1);
  1308. break;
  1309. case COMMIT:
  1310. include = commit.getFullMessage().toLowerCase().indexOf(lcValue) > -1;
  1311. break;
  1312. }
  1313. return include;
  1314. }
  1315. });
  1316. rw.markStart(rw.parseCommit(branchObject));
  1317. Iterable<RevCommit> revlog = rw;
  1318. if (offset > 0) {
  1319. int count = 0;
  1320. for (RevCommit rev : revlog) {
  1321. count++;
  1322. if (count > offset) {
  1323. list.add(rev);
  1324. if (maxCount > 0 && list.size() == maxCount) {
  1325. break;
  1326. }
  1327. }
  1328. }
  1329. } else {
  1330. for (RevCommit rev : revlog) {
  1331. list.add(rev);
  1332. if (maxCount > 0 && list.size() == maxCount) {
  1333. break;
  1334. }
  1335. }
  1336. }
  1337. rw.dispose();
  1338. } catch (Throwable t) {
  1339. error(t, repository, "{0} failed to {1} search revlogs for {2}", type.name(), value);
  1340. }
  1341. return list;
  1342. }
  1343. /**
  1344. * Returns the default branch to use for a repository. Normally returns
  1345. * whatever branch HEAD points to, but if HEAD points to nothing it returns
  1346. * the most recently updated branch.
  1347. *
  1348. * @param repository
  1349. * @return the objectid of a branch
  1350. * @throws Exception
  1351. */
  1352. public static ObjectId getDefaultBranch(Repository repository) throws Exception {
  1353. ObjectId object = repository.resolve(Constants.HEAD);
  1354. if (object == null) {
  1355. // no HEAD
  1356. // perhaps non-standard repository, try local branches
  1357. List<RefModel> branchModels = getLocalBranches(repository, true, -1);
  1358. if (branchModels.size() > 0) {
  1359. // use most recently updated branch
  1360. RefModel branch = null;
  1361. Date lastDate = new Date(0);
  1362. for (RefModel branchModel : branchModels) {
  1363. if (branchModel.getDate().after(lastDate)) {
  1364. branch = branchModel;
  1365. lastDate = branch.getDate();
  1366. }
  1367. }
  1368. object = branch.getReferencedObjectId();
  1369. }
  1370. }
  1371. return object;
  1372. }
  1373. /**
  1374. * Returns the target of the symbolic HEAD reference for a repository.
  1375. * Normally returns a branch reference name, but when HEAD is detached,
  1376. * the commit is matched against the known tags. The most recent matching
  1377. * tag ref name will be returned if it references the HEAD commit. If
  1378. * no match is found, the SHA1 is returned.
  1379. *
  1380. * @param repository
  1381. * @return the ref name or the SHA1 for a detached HEAD
  1382. */
  1383. public static String getHEADRef(Repository repository) {
  1384. String target = null;
  1385. try {
  1386. target = repository.getFullBranch();
  1387. if (!target.startsWith(Constants.R_HEADS)) {
  1388. // refers to an actual commit, probably a tag
  1389. // find latest tag that matches the commit, if any
  1390. List<RefModel> tagModels = getTags(repository, true, -1);
  1391. if (tagModels.size() > 0) {
  1392. RefModel tag = null;
  1393. Date lastDate = new Date(0);
  1394. for (RefModel tagModel : tagModels) {
  1395. if (tagModel.getReferencedObjectId().getName().equals(target) &&
  1396. tagModel.getDate().after(lastDate)) {
  1397. tag = tagModel;
  1398. lastDate = tag.getDate();
  1399. }
  1400. }
  1401. target = tag.getName();
  1402. }
  1403. }
  1404. } catch (Throwable t) {
  1405. error(t, repository, "{0} failed to get symbolic HEAD target");
  1406. }
  1407. return target;
  1408. }
  1409. /**
  1410. * Sets the symbolic ref HEAD to the specified target ref. The
  1411. * HEAD will be detached if the target ref is not a branch.
  1412. *
  1413. * @param repository
  1414. * @param targetRef
  1415. * @return true if successful
  1416. */
  1417. public static boolean setHEADtoRef(Repository repository, String targetRef) {
  1418. try {
  1419. // detach HEAD if target ref is not a branch
  1420. boolean detach = !targetRef.startsWith(Constants.R_HEADS);
  1421. RefUpdate.Result result;
  1422. RefUpdate head = repository.updateRef(Constants.HEAD, detach);
  1423. if (detach) { // Tag
  1424. RevCommit commit = getCommit(repository, targetRef);
  1425. head.setNewObjectId(commit.getId());
  1426. result = head.forceUpdate();
  1427. } else {
  1428. result = head.link(targetRef);
  1429. }
  1430. switch (result) {
  1431. case NEW:
  1432. case FORCED:
  1433. case NO_CHANGE:
  1434. case FAST_FORWARD:
  1435. return true;
  1436. default:
  1437. LOGGER.error(MessageFormat.format("{0} HEAD update to {1} returned result {2}",
  1438. repository.getDirectory().getAbsolutePath(), targetRef, result));
  1439. }
  1440. } catch (Throwable t) {
  1441. error(t, repository, "{0} failed to set HEAD to {1}", targetRef);
  1442. }
  1443. return false;
  1444. }
  1445. /**
  1446. * Sets the local branch ref to point to the specified commit id.
  1447. *
  1448. * @param repository
  1449. * @param branch
  1450. * @param commitId
  1451. * @return true if successful
  1452. */
  1453. public static boolean setBranchRef(Repository repository, String branch, String commitId) {
  1454. String branchName = branch;
  1455. if (!branchName.startsWith(Constants.R_HEADS)) {
  1456. branchName = Constants.R_HEADS + branch;
  1457. }
  1458. try {
  1459. RefUpdate refUpdate = repository.updateRef(branchName, false);
  1460. refUpdate.setNewObjectId(ObjectId.fromString(commitId));
  1461. RefUpdate.Result result = refUpdate.forceUpdate();
  1462. switch (result) {
  1463. case NEW:
  1464. case FORCED:
  1465. case NO_CHANGE:
  1466. case FAST_FORWARD:
  1467. return true;
  1468. default:
  1469. LOGGER.error(MessageFormat.format("{0} {1} update to {2} returned result {3}",
  1470. repository.getDirectory().getAbsolutePath(), branchName, commitId, result));
  1471. }
  1472. } catch (Throwable t) {
  1473. error(t, repository, "{0} failed to set {1} to {2}", branchName, commitId);
  1474. }
  1475. return false;
  1476. }
  1477. /**
  1478. * Deletes the specified branch ref.
  1479. *
  1480. * @param repository
  1481. * @param branch
  1482. * @return true if successful
  1483. */
  1484. public static boolean deleteBranchRef(Repository repository, String branch) {
  1485. String branchName = branch;
  1486. if (!branchName.startsWith(Constants.R_HEADS)) {
  1487. branchName = Constants.R_HEADS + branch;
  1488. }
  1489. try {
  1490. RefUpdate refUpdate = repository.updateRef(branchName, false);
  1491. refUpdate.setForceUpdate(true);
  1492. RefUpdate.Result result = refUpdate.delete();
  1493. switch (result) {
  1494. case NEW:
  1495. case FORCED:
  1496. case NO_CHANGE:
  1497. case FAST_FORWARD:
  1498. return true;
  1499. default:
  1500. LOGGER.error(MessageFormat.format("{0} failed to delete to {1} returned result {2}",
  1501. repository.getDirectory().getAbsolutePath(), branchName, result));
  1502. }
  1503. } catch (Throwable t) {
  1504. error(t, repository, "{0} failed to delete {1}", branchName);
  1505. }
  1506. return false;
  1507. }
  1508. /**
  1509. * Get the full branch and tag ref names for any potential HEAD targets.
  1510. *
  1511. * @param repository
  1512. * @return a list of ref names
  1513. */
  1514. public static List<String> getAvailableHeadTargets(Repository repository) {
  1515. List<String> targets = new ArrayList<String>();
  1516. for (RefModel branchModel : JGitUtils.getLocalBranches(repository, true, -1)) {
  1517. targets.add(branchModel.getName());
  1518. }
  1519. for (RefModel tagModel : JGitUtils.getTags(repository, true, -1)) {
  1520. targets.add(tagModel.getName());
  1521. }
  1522. return targets;
  1523. }
  1524. /**
  1525. * Returns all refs grouped by their associated object id.
  1526. *
  1527. * @param repository
  1528. * @return all refs grouped by their referenced object id
  1529. */
  1530. public static Map<ObjectId, List<RefModel>> getAllRefs(Repository repository) {
  1531. return getAllRefs(repository, true);
  1532. }
  1533. /**
  1534. * Returns all refs grouped by their associated object id.
  1535. *
  1536. * @param repository
  1537. * @param includeRemoteRefs
  1538. * @return all refs grouped by their referenced object id
  1539. */
  1540. public static Map<ObjectId, List<RefModel>> getAllRefs(Repository repository, boolean includeRemoteRefs) {
  1541. List<RefModel> list = getRefs(repository, org.eclipse.jgit.lib.RefDatabase.ALL, true, -1);
  1542. Map<ObjectId, List<RefModel>> refs = new HashMap<ObjectId, List<RefModel>>();
  1543. for (RefModel ref : list) {
  1544. if (!includeRemoteRefs && ref.getName().startsWith(Constants.R_REMOTES)) {
  1545. continue;
  1546. }
  1547. ObjectId objectid = ref.getReferencedObjectId();
  1548. if (!refs.containsKey(objectid)) {
  1549. refs.put(objectid, new ArrayList<RefModel>());
  1550. }
  1551. refs.get(objectid).add(ref);
  1552. }
  1553. return refs;
  1554. }
  1555. /**
  1556. * Returns the list of tags in the repository. If repository does not exist
  1557. * or is empty, an empty list is returned.
  1558. *
  1559. * @param repository
  1560. * @param fullName
  1561. * if true, /refs/tags/yadayadayada is returned. If false,
  1562. * yadayadayada is returned.
  1563. * @param maxCount
  1564. * if < 0, all tags are returned
  1565. * @return list of tags
  1566. */
  1567. public static List<RefModel> getTags(Repository repository, boolean fullName, int maxCount) {
  1568. return getRefs(repository, Constants.R_TAGS, fullName, maxCount);
  1569. }
  1570. /**
  1571. * Returns the list of local branches in the repository. If repository does
  1572. * not exist or is empty, an empty list is returned.
  1573. *
  1574. * @param repository
  1575. * @param fullName
  1576. * if true, /refs/heads/yadayadayada is returned. If false,
  1577. * yadayadayada is returned.
  1578. * @param maxCount
  1579. * if < 0, all local branches are returned
  1580. * @return list of local branches
  1581. */
  1582. public static List<RefModel> getLocalBranches(Repository repository, boolean fullName,
  1583. int maxCount) {
  1584. return getRefs(repository, Constants.R_HEADS, fullName, maxCount);
  1585. }
  1586. /**
  1587. * Returns the list of remote branches in the repository. If repository does
  1588. * not exist or is empty, an empty list is returned.
  1589. *
  1590. * @param repository
  1591. * @param fullName
  1592. * if true, /refs/remotes/yadayadayada is returned. If false,
  1593. * yadayadayada is returned.
  1594. * @param maxCount
  1595. * if < 0, all remote branches are returned
  1596. * @return list of remote branches
  1597. */
  1598. public static List<RefModel> getRemoteBranches(Repository repository, boolean fullName,
  1599. int maxCount) {
  1600. return getRefs(repository, Constants.R_REMOTES, fullName, maxCount);
  1601. }
  1602. /**
  1603. * Returns the list of note branches. If repository does not exist or is
  1604. * empty, an empty list is returned.
  1605. *
  1606. * @param repository
  1607. * @param fullName
  1608. * if true, /refs/notes/yadayadayada is returned. If false,
  1609. * yadayadayada is returned.
  1610. * @param maxCount
  1611. * if < 0, all note branches are returned
  1612. * @return list of note branches
  1613. */
  1614. public static List<RefModel> getNoteBranches(Repository repository, boolean fullName,
  1615. int maxCount) {
  1616. return getRefs(repository, Constants.R_NOTES, fullName, maxCount);
  1617. }
  1618. /**
  1619. * Returns the list of refs in the specified base ref. If repository does
  1620. * not exist or is empty, an empty list is returned.
  1621. *
  1622. * @param repository
  1623. * @param fullName
  1624. * if true, /refs/yadayadayada is returned. If false,
  1625. * yadayadayada is returned.
  1626. * @return list of refs
  1627. */
  1628. public static List<RefModel> getRefs(Repository repository, String baseRef) {
  1629. return getRefs(repository, baseRef, true, -1);
  1630. }
  1631. /**
  1632. * Returns a list of references in the repository matching "refs". If the
  1633. * repository is null or empty, an empty list is returned.
  1634. *
  1635. * @param repository
  1636. * @param refs
  1637. * if unspecified, all refs are returned
  1638. * @param fullName
  1639. * if true, /refs/something/yadayadayada is returned. If false,
  1640. * yadayadayada is returned.
  1641. * @param maxCount
  1642. * if < 0, all references are returned
  1643. * @return list of references
  1644. */
  1645. private static List<RefModel> getRefs(Repository repository, String refs, boolean fullName,
  1646. int maxCount) {
  1647. List<RefModel> list = new ArrayList<RefModel>();
  1648. if (maxCount == 0) {
  1649. return list;
  1650. }
  1651. if (!hasCommits(repository)) {
  1652. return list;
  1653. }
  1654. try {
  1655. Map<String, Ref> map = repository.getRefDatabase().getRefs(refs);
  1656. RevWalk rw = new RevWalk(repository);
  1657. for (Entry<String, Ref> entry : map.entrySet()) {
  1658. Ref ref = entry.getValue();
  1659. RevObject object = rw.parseAny(ref.getObjectId());
  1660. String name = entry.getKey();
  1661. if (fullName && !StringUtils.isEmpty(refs)) {
  1662. name = refs + name;
  1663. }
  1664. list.add(new RefModel(name, ref, object));
  1665. }
  1666. rw.dispose();
  1667. Collections.sort(list);
  1668. Collections.reverse(list);
  1669. if (maxCount > 0 && list.size() > maxCount) {
  1670. list = new ArrayList<RefModel>(list.subList(0, maxCount));
  1671. }
  1672. } catch (IOException e) {
  1673. error(e, repository, "{0} failed to retrieve {1}", refs);
  1674. }
  1675. return list;
  1676. }
  1677. /**
  1678. * Returns a RefModel for the gh-pages branch in the repository. If the
  1679. * branch can not be found, null is returned.
  1680. *
  1681. * @param repository
  1682. * @return a refmodel for the gh-pages branch or null
  1683. */
  1684. public static RefModel getPagesBranch(Repository repository) {
  1685. return getBranch(repository, "gh-pages");
  1686. }
  1687. /**
  1688. * Returns a RefModel for a specific branch name in the repository. If the
  1689. * branch can not be found, null is returned.
  1690. *
  1691. * @param repository
  1692. * @return a refmodel for the branch or null
  1693. */
  1694. public static RefModel getBranch(Repository repository, String name) {
  1695. RefModel branch = null;
  1696. try {
  1697. // search for the branch in local heads
  1698. for (RefModel ref : JGitUtils.getLocalBranches(repository, false, -1)) {
  1699. if (ref.reference.getName().endsWith(name)) {
  1700. branch = ref;
  1701. break;
  1702. }
  1703. }
  1704. // search for the branch in remote heads
  1705. if (branch == null) {
  1706. for (RefModel ref : JGitUtils.getRemoteBranches(repository, false, -1)) {
  1707. if (ref.reference.getName().endsWith(name)) {
  1708. branch = ref;
  1709. break;
  1710. }
  1711. }
  1712. }
  1713. } catch (Throwable t) {
  1714. LOGGER.error(MessageFormat.format("Failed to find {0} branch!", name), t);
  1715. }
  1716. return branch;
  1717. }
  1718. /**
  1719. * Returns the list of submodules for this repository.
  1720. *
  1721. * @param repository
  1722. * @param commit
  1723. * @return list of submodules
  1724. */
  1725. public static List<SubmoduleModel> getSubmodules(Repository repository, String commitId) {
  1726. RevCommit commit = getCommit(repository, commitId);
  1727. return getSubmodules(repository, commit.getTree());
  1728. }
  1729. /**
  1730. * Returns the list of submodules for this repository.
  1731. *
  1732. * @param repository
  1733. * @param commit
  1734. * @return list of submodules
  1735. */
  1736. public static List<SubmoduleModel> getSubmodules(Repository repository, RevTree tree) {
  1737. List<SubmoduleModel> list = new ArrayList<SubmoduleModel>();
  1738. byte [] blob = getByteContent(repository, tree, ".gitmodules", false);
  1739. if (blob == null) {
  1740. return list;
  1741. }
  1742. try {
  1743. BlobBasedConfig config = new BlobBasedConfig(repository.getConfig(), blob);
  1744. for (String module : config.getSubsections("submodule")) {
  1745. String path = config.getString("submodule", module, "path");
  1746. String url = config.getString("submodule", module, "url");
  1747. list.add(new SubmoduleModel(module, path, url));
  1748. }
  1749. } catch (ConfigInvalidException e) {
  1750. LOGGER.error("Failed to load .gitmodules file for " + repository.getDirectory(), e);
  1751. }
  1752. return list;
  1753. }
  1754. /**
  1755. * Returns the submodule definition for the specified path at the specified
  1756. * commit. If no module is defined for the path, null is returned.
  1757. *
  1758. * @param repository
  1759. * @param commit
  1760. * @param path
  1761. * @return a submodule definition or null if there is no submodule
  1762. */
  1763. public static SubmoduleModel getSubmoduleModel(Repository repository, String commitId, String path) {
  1764. for (SubmoduleModel model : getSubmodules(repository, commitId)) {
  1765. if (model.path.equals(path)) {
  1766. return model;
  1767. }
  1768. }
  1769. return null;
  1770. }
  1771. public static String getSubmoduleCommitId(Repository repository, String path, RevCommit commit) {
  1772. String commitId = null;
  1773. RevWalk rw = new RevWalk(repository);
  1774. TreeWalk tw = new TreeWalk(repository);
  1775. tw.setFilter(PathFilterGroup.createFromStrings(Collections.singleton(path)));
  1776. try {
  1777. tw.reset(commit.getTree());
  1778. while (tw.next()) {
  1779. if (tw.isSubtree() && !path.equals(tw.getPathString())) {
  1780. tw.enterSubtree();
  1781. continue;
  1782. }
  1783. if (FileMode.GITLINK == tw.getFileMode(0)) {
  1784. commitId = tw.getObjectId(0).getName();
  1785. break;
  1786. }
  1787. }
  1788. } catch (Throwable t) {
  1789. error(t, repository, "{0} can't find {1} in commit {2}", path, commit.name());
  1790. } finally {
  1791. rw.dispose();
  1792. tw.release();
  1793. }
  1794. return commitId;
  1795. }
  1796. /**
  1797. * Returns the list of notes entered about the commit from the refs/notes
  1798. * namespace. If the repository does not exist or is empty, an empty list is
  1799. * returned.
  1800. *
  1801. * @param repository
  1802. * @param commit
  1803. * @return list of notes
  1804. */
  1805. public static List<GitNote> getNotesOnCommit(Repository repository, RevCommit commit) {
  1806. List<GitNote> list = new ArrayList<GitNote>();
  1807. if (!hasCommits(repository)) {
  1808. return list;
  1809. }
  1810. List<RefModel> noteBranches = getNoteBranches(repository, true, -1);
  1811. for (RefModel notesRef : noteBranches) {
  1812. RevTree notesTree = JGitUtils.getCommit(repository, notesRef.getName()).getTree();
  1813. // flat notes list
  1814. String notePath = commit.getName();
  1815. String text = getStringContent(repository, notesTree, notePath);
  1816. if (!StringUtils.isEmpty(text)) {
  1817. List<RevCommit> history = getRevLog(repository, notesRef.getName(), notePath, 0, -1);
  1818. RefModel noteRef = new RefModel(notesRef.displayName, null, history.get(history
  1819. .size() - 1));
  1820. GitNote gitNote = new GitNote(noteRef, text);
  1821. list.add(gitNote);
  1822. continue;
  1823. }
  1824. // folder structure
  1825. StringBuilder sb = new StringBuilder(commit.getName());
  1826. sb.insert(2, '/');
  1827. notePath = sb.toString();
  1828. text = getStringContent(repository, notesTree, notePath);
  1829. if (!StringUtils.isEmpty(text)) {
  1830. List<RevCommit> history = getRevLog(repository, notesRef.getName(), notePath, 0, -1);
  1831. RefModel noteRef = new RefModel(notesRef.displayName, null, history.get(history
  1832. .size() - 1));
  1833. GitNote gitNote = new GitNote(noteRef, text);
  1834. list.add(gitNote);
  1835. }
  1836. }
  1837. return list;
  1838. }
  1839. /**
  1840. * this method creates an incremental revision number as a tag according to
  1841. * the amount of already existing tags, which start with a defined prefix.
  1842. *
  1843. * @param repository
  1844. * @param objectId
  1845. * @param tagger
  1846. * @param prefix
  1847. * @param intPattern
  1848. * @param message
  1849. * @return true if operation was successful, otherwise false
  1850. */
  1851. public static boolean createIncrementalRevisionTag(Repository repository,
  1852. String objectId, PersonIdent tagger, String prefix, String intPattern, String message) {
  1853. boolean result = false;
  1854. Iterator<Entry<String, Ref>> iterator = repository.getTags().entrySet().iterator();
  1855. long lastRev = 0;
  1856. while (iterator.hasNext()) {
  1857. Entry<String, Ref> entry = iterator.next();
  1858. if (entry.getKey().startsWith(prefix)) {
  1859. try {
  1860. long val = Long.parseLong(entry.getKey().substring(prefix.length()));
  1861. if (val > lastRev) {
  1862. lastRev = val;
  1863. }
  1864. } catch (Exception e) {
  1865. // this tag is NOT an incremental revision tag
  1866. }
  1867. }
  1868. }
  1869. DecimalFormat df = new DecimalFormat(intPattern);
  1870. result = createTag(repository, objectId, tagger, prefix + df.format((lastRev + 1)), message);
  1871. return result;
  1872. }
  1873. /**
  1874. * creates a tag in a repository
  1875. *
  1876. * @param repository
  1877. * @param objectId, the ref the tag points towards
  1878. * @param tagger, the person tagging the object
  1879. * @param tag, the string label
  1880. * @param message, the string message
  1881. * @return boolean, true if operation was successful, otherwise false
  1882. */
  1883. public static boolean createTag(Repository repository, String objectId, PersonIdent tagger, String tag, String message) {
  1884. try {
  1885. Git gitClient = Git.open(repository.getDirectory());
  1886. TagCommand tagCommand = gitClient.tag();
  1887. tagCommand.setTagger(tagger);
  1888. tagCommand.setMessage(message);
  1889. if (objectId != null) {
  1890. RevObject revObj = getCommit(repository, objectId);
  1891. tagCommand.setObjectId(revObj);
  1892. }
  1893. tagCommand.setName(tag);
  1894. Ref call = tagCommand.call();
  1895. return call != null ? true : false;
  1896. } catch (Exception e) {
  1897. error(e, repository, "Failed to create tag {1} in repository {0}", objectId, tag);
  1898. }
  1899. return false;
  1900. }
  1901. /**
  1902. * Create an orphaned branch in a repository.
  1903. *
  1904. * @param repository
  1905. * @param branchName
  1906. * @param author
  1907. * if unspecified, Gitblit will be the author of this new branch
  1908. * @return true if successful
  1909. */
  1910. public static boolean createOrphanBranch(Repository repository, String branchName,
  1911. PersonIdent author) {
  1912. boolean success = false;
  1913. String message = "Created branch " + branchName;
  1914. if (author == null) {
  1915. author = new PersonIdent("Gitblit", "gitblit@localhost");
  1916. }
  1917. try {
  1918. ObjectInserter odi = repository.newObjectInserter();
  1919. try {
  1920. // Create a blob object to insert into a tree
  1921. ObjectId blobId = odi.insert(Constants.OBJ_BLOB,
  1922. message.getBytes(Constants.CHARACTER_ENCODING));
  1923. // Create a tree object to reference from a commit
  1924. TreeFormatter tree = new TreeFormatter();
  1925. tree.append(".branch", FileMode.REGULAR_FILE, blobId);
  1926. ObjectId treeId = odi.insert(tree);
  1927. // Create a commit object
  1928. CommitBuilder commit = new CommitBuilder();
  1929. commit.setAuthor(author);
  1930. commit.setCommitter(author);
  1931. commit.setEncoding(Constants.CHARACTER_ENCODING);
  1932. commit.setMessage(message);
  1933. commit.setTreeId(treeId);
  1934. // Insert the commit into the repository
  1935. ObjectId commitId = odi.insert(commit);
  1936. odi.flush();
  1937. RevWalk revWalk = new RevWalk(repository);
  1938. try {
  1939. RevCommit revCommit = revWalk.parseCommit(commitId);
  1940. if (!branchName.startsWith("refs/")) {
  1941. branchName = "refs/heads/" + branchName;
  1942. }
  1943. RefUpdate ru = repository.updateRef(branchName);
  1944. ru.setNewObjectId(commitId);
  1945. ru.setRefLogMessage("commit: " + revCommit.getShortMessage(), false);
  1946. Result rc = ru.forceUpdate();
  1947. switch (rc) {
  1948. case NEW:
  1949. case FORCED:
  1950. case FAST_FORWARD:
  1951. success = true;
  1952. break;
  1953. default:
  1954. success = false;
  1955. }
  1956. } finally {
  1957. revWalk.release();
  1958. }
  1959. } finally {
  1960. odi.release();
  1961. }
  1962. } catch (Throwable t) {
  1963. error(t, repository, "Failed to create orphan branch {1} in repository {0}", branchName);
  1964. }
  1965. return success;
  1966. }
  1967. /**
  1968. * Reads the sparkleshare id, if present, from the repository.
  1969. *
  1970. * @param repository
  1971. * @return an id or null
  1972. */
  1973. public static String getSparkleshareId(Repository repository) {
  1974. byte[] content = getByteContent(repository, null, ".sparkleshare", false);
  1975. if (content == null) {
  1976. return null;
  1977. }
  1978. return StringUtils.decodeString(content);
  1979. }
  1980. }