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 56KB

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