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

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