Nevar pievienot vairāk kā 25 tēmas Tēmai ir jāsākas ar burtu vai ciparu, tā var saturēt domu zīmes ('-') un var būt līdz 35 simboliem gara.

JGitUtils.java 29KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949
  1. package com.gitblit.utils;
  2. import java.io.ByteArrayOutputStream;
  3. import java.io.File;
  4. import java.io.IOException;
  5. import java.io.InputStream;
  6. import java.nio.charset.Charset;
  7. import java.text.DateFormat;
  8. import java.text.ParseException;
  9. import java.text.SimpleDateFormat;
  10. import java.util.ArrayList;
  11. import java.util.Collection;
  12. import java.util.Collections;
  13. import java.util.Date;
  14. import java.util.HashMap;
  15. import java.util.List;
  16. import java.util.Map;
  17. import java.util.Set;
  18. import java.util.concurrent.atomic.AtomicInteger;
  19. import org.eclipse.jgit.api.Git;
  20. import org.eclipse.jgit.diff.DiffEntry;
  21. import org.eclipse.jgit.diff.DiffEntry.ChangeType;
  22. import org.eclipse.jgit.diff.DiffFormatter;
  23. import org.eclipse.jgit.diff.RawTextComparator;
  24. import org.eclipse.jgit.errors.ConfigInvalidException;
  25. import org.eclipse.jgit.errors.IncorrectObjectTypeException;
  26. import org.eclipse.jgit.errors.MissingObjectException;
  27. import org.eclipse.jgit.errors.StopWalkException;
  28. import org.eclipse.jgit.lib.AnyObjectId;
  29. import org.eclipse.jgit.lib.Constants;
  30. import org.eclipse.jgit.lib.FileMode;
  31. import org.eclipse.jgit.lib.ObjectId;
  32. import org.eclipse.jgit.lib.ObjectLoader;
  33. import org.eclipse.jgit.lib.PersonIdent;
  34. import org.eclipse.jgit.lib.Ref;
  35. import org.eclipse.jgit.lib.Repository;
  36. import org.eclipse.jgit.lib.StoredConfig;
  37. import org.eclipse.jgit.revwalk.RevBlob;
  38. import org.eclipse.jgit.revwalk.RevCommit;
  39. import org.eclipse.jgit.revwalk.RevObject;
  40. import org.eclipse.jgit.revwalk.RevSort;
  41. import org.eclipse.jgit.revwalk.RevTree;
  42. import org.eclipse.jgit.revwalk.RevWalk;
  43. import org.eclipse.jgit.revwalk.filter.RevFilter;
  44. import org.eclipse.jgit.treewalk.TreeWalk;
  45. import org.eclipse.jgit.treewalk.filter.AndTreeFilter;
  46. import org.eclipse.jgit.treewalk.filter.OrTreeFilter;
  47. import org.eclipse.jgit.treewalk.filter.PathFilter;
  48. import org.eclipse.jgit.treewalk.filter.PathFilterGroup;
  49. import org.eclipse.jgit.treewalk.filter.PathSuffixFilter;
  50. import org.eclipse.jgit.treewalk.filter.TreeFilter;
  51. import org.eclipse.jgit.util.io.DisabledOutputStream;
  52. import org.slf4j.Logger;
  53. import org.slf4j.LoggerFactory;
  54. import com.gitblit.wicket.models.Metric;
  55. import com.gitblit.wicket.models.PathModel;
  56. import com.gitblit.wicket.models.PathModel.PathChangeModel;
  57. import com.gitblit.wicket.models.RefModel;
  58. import com.gitblit.wicket.models.TicketModel;
  59. import com.gitblit.wicket.models.TicketModel.Comment;
  60. public class JGitUtils {
  61. /** Prefix for notes refs */
  62. public static final String R_NOTES = "refs/notes/";
  63. /** Standard notes ref */
  64. public static final String R_NOTES_COMMITS = R_NOTES + "commits";
  65. private final static Logger LOGGER = LoggerFactory.getLogger(JGitUtils.class);
  66. public static Repository createRepository(File repositoriesFolder, String name, boolean bare) {
  67. Git git = Git.init().setDirectory(new File(repositoriesFolder, name)).setBare(bare).call();
  68. return git.getRepository();
  69. }
  70. public static List<String> getRepositoryList(File repositoriesFolder, boolean exportAll, boolean readNested) {
  71. List<String> list = new ArrayList<String>();
  72. list.addAll(getNestedRepositories(repositoriesFolder, repositoriesFolder, exportAll, readNested));
  73. Collections.sort(list);
  74. return list;
  75. }
  76. public static List<String> getNestedRepositories(File repositoriesFolder, File folder, boolean exportAll, boolean readNested) {
  77. String basefile = repositoriesFolder.getAbsolutePath();
  78. List<String> list = new ArrayList<String>();
  79. for (File file : folder.listFiles()) {
  80. if (file.isDirectory() && !file.getName().equalsIgnoreCase(Constants.DOT_GIT)) {
  81. // if this is a git repository add it to the list
  82. //
  83. // first look for standard folder/.git structure
  84. File gitFolder = new File(file, Constants.DOT_GIT);
  85. boolean isGitRepository = gitFolder.exists() && gitFolder.isDirectory();
  86. // then look for folder.git/HEAD
  87. if (!isGitRepository) {
  88. if (file.getName().endsWith(Constants.DOT_GIT_EXT) && new File(file, Constants.HEAD).exists()) {
  89. gitFolder = file;
  90. isGitRepository = true;
  91. }
  92. }
  93. boolean exportRepository = isGitRepository && (exportAll || new File(gitFolder, "git-daemon-export-ok").exists());
  94. if (exportRepository) {
  95. // determine repository name relative to repositories folder
  96. String filename = file.getAbsolutePath();
  97. String repo = filename.substring(basefile.length()).replace('\\', '/');
  98. if (repo.charAt(0) == '/') {
  99. repo = repo.substring(1);
  100. }
  101. list.add(repo);
  102. }
  103. // look for nested repositories
  104. if (readNested) {
  105. list.addAll(getNestedRepositories(repositoriesFolder, file, exportAll, readNested));
  106. }
  107. }
  108. }
  109. return list;
  110. }
  111. public static RevCommit getFirstCommit(Repository r, String branch) {
  112. if (StringUtils.isEmpty(branch)) {
  113. branch = Constants.HEAD;
  114. }
  115. try {
  116. RevWalk walk = new RevWalk(r);
  117. walk.sort(RevSort.REVERSE);
  118. RevCommit head = walk.parseCommit(r.resolve(branch));
  119. walk.markStart(head);
  120. RevCommit commit = walk.next();
  121. walk.dispose();
  122. return commit;
  123. } catch (Throwable t) {
  124. LOGGER.error("Failed to determine first commit", t);
  125. }
  126. return null;
  127. }
  128. public static Date getFirstChange(Repository r, String branch) {
  129. try {
  130. RevCommit commit = getFirstCommit(r, branch);
  131. if (commit == null) {
  132. // fresh repository
  133. return new Date(r.getDirectory().lastModified());
  134. }
  135. return getCommitDate(commit);
  136. } catch (Throwable t) {
  137. LOGGER.error("Failed to determine first change", t);
  138. }
  139. return null;
  140. }
  141. public static Date getLastChange(Repository r) {
  142. RevCommit commit = getCommit(r, Constants.HEAD);
  143. if (commit == null) {
  144. // fresh repository
  145. return new Date(r.getDirectory().lastModified());
  146. }
  147. return getCommitDate(commit);
  148. }
  149. public static RevCommit getCommit(Repository r, String objectId) {
  150. RevCommit commit = null;
  151. try {
  152. if (objectId == null || objectId.trim().length() == 0) {
  153. objectId = Constants.HEAD;
  154. }
  155. ObjectId object = r.resolve(objectId);
  156. RevWalk walk = new RevWalk(r);
  157. RevCommit rev = walk.parseCommit(object);
  158. commit = rev;
  159. walk.dispose();
  160. } catch (Throwable t) {
  161. LOGGER.error("Failed to determine last change", t);
  162. }
  163. return commit;
  164. }
  165. public static Map<ObjectId, List<String>> getAllRefs(Repository r) {
  166. Map<ObjectId, List<String>> refs = new HashMap<ObjectId, List<String>>();
  167. Map<AnyObjectId, Set<Ref>> allRefs = r.getAllRefsByPeeledObjectId();
  168. for (AnyObjectId id : allRefs.keySet()) {
  169. List<String> list = new ArrayList<String>();
  170. for (Ref setRef : allRefs.get(id)) {
  171. String name = setRef.getName();
  172. list.add(name);
  173. }
  174. refs.put(id.toObjectId(), list);
  175. }
  176. return refs;
  177. }
  178. public static Map<ObjectId, List<String>> getRefs(Repository r, String baseRef) {
  179. Map<ObjectId, List<String>> refs = new HashMap<ObjectId, List<String>>();
  180. Map<AnyObjectId, Set<Ref>> allRefs = r.getAllRefsByPeeledObjectId();
  181. for (AnyObjectId id : allRefs.keySet()) {
  182. List<String> list = new ArrayList<String>();
  183. for (Ref setRef : allRefs.get(id)) {
  184. String name = setRef.getName();
  185. if (name.startsWith(baseRef)) {
  186. list.add(name);
  187. }
  188. }
  189. refs.put(id.toObjectId(), list);
  190. }
  191. return refs;
  192. }
  193. /**
  194. * Lookup an entry stored in a tree, failing if not present.
  195. *
  196. * @param tree
  197. * the tree to search.
  198. * @param path
  199. * the path to find the entry of.
  200. * @return the parsed object entry at this path
  201. * @throws Exception
  202. */
  203. public static RevObject getRevObject(Repository r, final RevTree tree, final String path) {
  204. RevObject ro = null;
  205. RevWalk rw = new RevWalk(r);
  206. TreeWalk tw = new TreeWalk(r);
  207. tw.setFilter(PathFilterGroup.createFromStrings(Collections.singleton(path)));
  208. try {
  209. tw.reset(tree);
  210. while (tw.next()) {
  211. if (tw.isSubtree() && !path.equals(tw.getPathString())) {
  212. tw.enterSubtree();
  213. continue;
  214. }
  215. ObjectId entid = tw.getObjectId(0);
  216. FileMode entmode = tw.getFileMode(0);
  217. ro = rw.lookupAny(entid, entmode.getObjectType());
  218. rw.parseBody(ro);
  219. }
  220. } catch (Throwable t) {
  221. LOGGER.error("Can't find " + path + " in tree " + tree.name(), t);
  222. } finally {
  223. if (rw != null) {
  224. rw.dispose();
  225. }
  226. }
  227. return ro;
  228. }
  229. public static byte[] getRawContent(Repository r, RevBlob blob) {
  230. ByteArrayOutputStream os = new ByteArrayOutputStream();
  231. try {
  232. ObjectLoader ldr = r.open(blob.getId(), Constants.OBJ_BLOB);
  233. byte[] tmp = new byte[1024];
  234. InputStream in = ldr.openStream();
  235. int n;
  236. while ((n = in.read(tmp)) > 0) {
  237. os.write(tmp, 0, n);
  238. }
  239. in.close();
  240. } catch (Throwable t) {
  241. LOGGER.error("Failed to read raw content", t);
  242. }
  243. return os.toByteArray();
  244. }
  245. public static String getRawContentAsString(Repository r, RevBlob blob) {
  246. byte [] content = getRawContent(r, blob);
  247. return new String(content, Charset.forName("UTF-8"));
  248. }
  249. public static String getRawContentAsString(Repository r, RevCommit commit, String blobPath) {
  250. RevObject obj = getRevObject(r, commit.getTree(), blobPath);
  251. byte [] content = getRawContent(r, (RevBlob) obj);
  252. return new String(content, Charset.forName("UTF-8"));
  253. }
  254. public static List<PathModel> getFilesInPath(Repository r, String basePath, String objectId) {
  255. RevCommit commit = getCommit(r, objectId);
  256. return getFilesInPath(r, basePath, commit);
  257. }
  258. public static List<PathModel> getFilesInPath(Repository r, String basePath, RevCommit commit) {
  259. List<PathModel> list = new ArrayList<PathModel>();
  260. if (commit == null) {
  261. return list;
  262. }
  263. final TreeWalk walk = new TreeWalk(r);
  264. try {
  265. walk.addTree(commit.getTree());
  266. if (basePath != null && basePath.length() > 0) {
  267. PathFilter f = PathFilter.create(basePath);
  268. walk.setFilter(f);
  269. walk.setRecursive(false);
  270. boolean foundFolder = false;
  271. while (walk.next()) {
  272. if (!foundFolder && walk.isSubtree()) {
  273. walk.enterSubtree();
  274. }
  275. if (walk.getPathString().equals(basePath)) {
  276. foundFolder = true;
  277. continue;
  278. }
  279. if (foundFolder) {
  280. list.add(getPathModel(walk, basePath, commit));
  281. }
  282. }
  283. } else {
  284. walk.setRecursive(false);
  285. while (walk.next()) {
  286. list.add(getPathModel(walk, null, commit));
  287. }
  288. }
  289. } catch (IOException e) {
  290. LOGGER.error("Failed to get files for commit " + commit.getName(), e);
  291. } finally {
  292. walk.release();
  293. }
  294. Collections.sort(list);
  295. return list;
  296. }
  297. public static List<PathChangeModel> getFilesInCommit(Repository r, String commitId) {
  298. RevCommit commit = getCommit(r, commitId);
  299. return getFilesInCommit(r, commit);
  300. }
  301. public static List<PathChangeModel> getFilesInCommit(Repository r, RevCommit commit) {
  302. List<PathChangeModel> list = new ArrayList<PathChangeModel>();
  303. try {
  304. final RevWalk rw = new RevWalk(r);
  305. RevCommit parent = rw.parseCommit(commit.getParent(0).getId());
  306. RevTree parentTree = parent.getTree();
  307. RevTree commitTree = commit.getTree();
  308. final TreeWalk walk = new TreeWalk(r);
  309. walk.reset();
  310. walk.setRecursive(true);
  311. walk.addTree(parentTree);
  312. walk.addTree(commitTree);
  313. walk.setFilter(TreeFilter.ANY_DIFF);
  314. RawTextComparator cmp = RawTextComparator.DEFAULT;
  315. DiffFormatter df = new DiffFormatter(DisabledOutputStream.INSTANCE);
  316. df.setRepository(r);
  317. df.setDiffComparator(cmp);
  318. df.setDetectRenames(true);
  319. List<DiffEntry> diffs = df.scan(parentTree, commitTree);
  320. for (DiffEntry diff : diffs) {
  321. if (diff.getChangeType().equals(ChangeType.DELETE)) {
  322. list.add(new PathChangeModel(diff.getOldPath(), diff.getOldPath(), 0, diff.getNewMode().getBits(), commit.getId().getName(), diff.getChangeType()));
  323. } else {
  324. list.add(new PathChangeModel(diff.getNewPath(), diff.getNewPath(), 0, diff.getNewMode().getBits(), commit.getId().getName(), diff.getChangeType()));
  325. }
  326. }
  327. } catch (Throwable t) {
  328. LOGGER.error("failed to determine files in commit!", t);
  329. }
  330. return list;
  331. }
  332. public static List<PathModel> getDocuments(Repository r, List<String> extensions) {
  333. List<PathModel> list = new ArrayList<PathModel>();
  334. RevCommit commit = getCommit(r, Constants.HEAD);
  335. final TreeWalk walk = new TreeWalk(r);
  336. try {
  337. walk.addTree(commit.getTree());
  338. if (extensions != null && extensions.size() > 0) {
  339. Collection<TreeFilter> suffixFilters = new ArrayList<TreeFilter>();
  340. for (String extension:extensions) {
  341. if (extension.charAt(0) == '.') {
  342. suffixFilters.add(PathSuffixFilter.create(extension));
  343. } else {
  344. // escape the . since this is a regexp filter
  345. suffixFilters.add(PathSuffixFilter.create("\\." + extension));
  346. }
  347. }
  348. TreeFilter filter = OrTreeFilter.create(suffixFilters);
  349. walk.setFilter(filter);
  350. walk.setRecursive(true);
  351. while (walk.next()) {
  352. list.add(getPathModel(walk, null, commit));
  353. }
  354. } else {
  355. while (walk.next()) {
  356. list.add(getPathModel(walk, null, commit));
  357. }
  358. }
  359. } catch (IOException e) {
  360. LOGGER.error("Failed to get files for commit " + commit.getName(), e);
  361. } finally {
  362. walk.release();
  363. }
  364. Collections.sort(list);
  365. return list;
  366. }
  367. public static Map<ChangeType, AtomicInteger> getChangedPathsStats(List<PathChangeModel> paths) {
  368. Map<ChangeType, AtomicInteger> stats = new HashMap<ChangeType, AtomicInteger>();
  369. for (PathChangeModel path : paths) {
  370. if (!stats.containsKey(path.changeType)) {
  371. stats.put(path.changeType, new AtomicInteger(0));
  372. }
  373. stats.get(path.changeType).incrementAndGet();
  374. }
  375. return stats;
  376. }
  377. public static enum DiffOutputType {
  378. PLAIN, GITWEB, GITBLIT;
  379. public static DiffOutputType forName(String name) {
  380. for (DiffOutputType type : values()) {
  381. if (type.name().equalsIgnoreCase(name)) {
  382. return type;
  383. }
  384. }
  385. return null;
  386. }
  387. }
  388. public static String getCommitDiff(Repository r, RevCommit commit, DiffOutputType outputType) {
  389. return getCommitDiff(r, null, commit, null, outputType);
  390. }
  391. public static String getCommitDiff(Repository r, RevCommit commit, String path, DiffOutputType outputType) {
  392. return getCommitDiff(r, null, commit, path, outputType);
  393. }
  394. public static String getCommitDiff(Repository r, RevCommit baseCommit, RevCommit commit, DiffOutputType outputType) {
  395. return getCommitDiff(r, baseCommit, commit, null, outputType);
  396. }
  397. public static String getCommitDiff(Repository r, RevCommit baseCommit, RevCommit commit, String path, DiffOutputType outputType) {
  398. try {
  399. RevTree baseTree;
  400. if (baseCommit == null) {
  401. final RevWalk rw = new RevWalk(r);
  402. RevCommit parent = rw.parseCommit(commit.getParent(0).getId());
  403. rw.dispose();
  404. baseTree = parent.getTree();
  405. } else {
  406. baseTree = baseCommit.getTree();
  407. }
  408. RevTree commitTree = commit.getTree();
  409. final TreeWalk walk = new TreeWalk(r);
  410. walk.reset();
  411. walk.setRecursive(true);
  412. walk.addTree(baseTree);
  413. walk.addTree(commitTree);
  414. walk.setFilter(TreeFilter.ANY_DIFF);
  415. final ByteArrayOutputStream os = new ByteArrayOutputStream();
  416. RawTextComparator cmp = RawTextComparator.DEFAULT;
  417. DiffFormatter df;
  418. switch (outputType) {
  419. case GITWEB:
  420. df = new GitWebDiffFormatter(os);
  421. break;
  422. case GITBLIT:
  423. df = new GitBlitDiffFormatter(os);
  424. break;
  425. case PLAIN:
  426. default:
  427. df = new DiffFormatter(os);
  428. break;
  429. }
  430. df.setRepository(r);
  431. df.setDiffComparator(cmp);
  432. df.setDetectRenames(true);
  433. List<DiffEntry> diffs = df.scan(baseTree, commitTree);
  434. if (path != null && path.length() > 0) {
  435. for (DiffEntry diff : diffs) {
  436. if (diff.getNewPath().equalsIgnoreCase(path)) {
  437. df.format(diff);
  438. break;
  439. }
  440. }
  441. } else {
  442. df.format(diffs);
  443. }
  444. String diff;
  445. if (df instanceof GitWebDiffFormatter) {
  446. // workaround for complex private methods in DiffFormatter
  447. diff = ((GitWebDiffFormatter) df).getHtml();
  448. } else {
  449. diff = os.toString();
  450. }
  451. df.flush();
  452. return diff;
  453. } catch (Throwable t) {
  454. LOGGER.error("failed to generate commit diff!", t);
  455. }
  456. return null;
  457. }
  458. public static String getCommitPatch(Repository r, RevCommit commit) {
  459. return getCommitPatch(r, commit);
  460. }
  461. public static String getCommitPatch(Repository r, RevCommit commit, String path) {
  462. return getCommitPatch(r, null, commit, path);
  463. }
  464. public static String getCommitPatch(Repository r, RevCommit baseCommit, RevCommit commit, String path) {
  465. try {
  466. RevTree baseTree;
  467. if (baseCommit == null) {
  468. final RevWalk rw = new RevWalk(r);
  469. RevCommit parent = rw.parseCommit(commit.getParent(0).getId());
  470. baseTree = parent.getTree();
  471. } else {
  472. baseTree = baseCommit.getTree();
  473. }
  474. RevTree commitTree = commit.getTree();
  475. final TreeWalk walk = new TreeWalk(r);
  476. walk.reset();
  477. walk.setRecursive(true);
  478. walk.addTree(baseTree);
  479. walk.addTree(commitTree);
  480. walk.setFilter(TreeFilter.ANY_DIFF);
  481. final ByteArrayOutputStream os = new ByteArrayOutputStream();
  482. RawTextComparator cmp = RawTextComparator.DEFAULT;
  483. PatchFormatter df = new PatchFormatter(os);
  484. df.setRepository(r);
  485. df.setDiffComparator(cmp);
  486. df.setDetectRenames(true);
  487. List<DiffEntry> diffs = df.scan(baseTree, commitTree);
  488. if (path != null && path.length() > 0) {
  489. for (DiffEntry diff : diffs) {
  490. if (diff.getNewPath().equalsIgnoreCase(path)) {
  491. df.format(diff);
  492. break;
  493. }
  494. }
  495. } else {
  496. df.format(diffs);
  497. }
  498. String diff = df.getPatch(commit);
  499. df.flush();
  500. return diff;
  501. } catch (Throwable t) {
  502. LOGGER.error("failed to generate commit diff!", t);
  503. }
  504. return null;
  505. }
  506. private static PathModel getPathModel(TreeWalk walk, String basePath, RevCommit commit) {
  507. String name;
  508. long size = 0;
  509. if (basePath == null) {
  510. name = walk.getPathString();
  511. } else {
  512. try {
  513. name = walk.getPathString().substring(basePath.length() + 1);
  514. } catch (Throwable t) {
  515. name = walk.getPathString();
  516. }
  517. }
  518. try {
  519. if (!walk.isSubtree()) {
  520. size = walk.getObjectReader().getObjectSize(walk.getObjectId(0), Constants.OBJ_BLOB);
  521. }
  522. } catch (Throwable t) {
  523. LOGGER.error("Failed to retrieve blob size", t);
  524. }
  525. return new PathModel(name, walk.getPathString(), size, walk.getFileMode(0).getBits(), commit.getName());
  526. }
  527. public static String getPermissionsFromMode(int mode) {
  528. if (FileMode.TREE.equals(mode)) {
  529. return "drwxr-xr-x";
  530. } else if (FileMode.REGULAR_FILE.equals(mode)) {
  531. return "-rw-r--r--";
  532. } else if (FileMode.EXECUTABLE_FILE.equals(mode)) {
  533. return "-rwxr-xr-x";
  534. } else if (FileMode.SYMLINK.equals(mode)) {
  535. // FIXME symlink permissions
  536. return "symlink";
  537. } else if (FileMode.GITLINK.equals(mode)) {
  538. // FIXME gitlink permissions
  539. return "gitlink";
  540. } else if (FileMode.MISSING.equals(mode)) {
  541. // FIXME missing permissions
  542. return "missing";
  543. }
  544. return "" + mode;
  545. }
  546. public static boolean isTreeFromMode(int mode) {
  547. return FileMode.TREE.equals(mode);
  548. }
  549. public static List<RevCommit> getRevLog(Repository r, int maxCount) {
  550. return getRevLog(r, Constants.HEAD, 0, maxCount);
  551. }
  552. public static List<RevCommit> getRevLog(Repository r, String objectId, int offset, int maxCount) {
  553. return getRevLog(r, objectId, null, offset, maxCount);
  554. }
  555. public static List<RevCommit> getRevLog(Repository r, String objectId, String path, int offset, int maxCount) {
  556. List<RevCommit> list = new ArrayList<RevCommit>();
  557. try {
  558. if (objectId == null || objectId.trim().length() == 0) {
  559. objectId = Constants.HEAD;
  560. }
  561. RevWalk walk = new RevWalk(r);
  562. ObjectId object = r.resolve(objectId);
  563. walk.markStart(walk.parseCommit(object));
  564. if (!StringUtils.isEmpty(path)) {
  565. TreeFilter filter = AndTreeFilter.create(PathFilterGroup.createFromStrings(Collections.singleton(path)), TreeFilter.ANY_DIFF);
  566. walk.setTreeFilter(filter);
  567. }
  568. Iterable<RevCommit> revlog = walk;
  569. if (offset > 0) {
  570. int count = 0;
  571. for (RevCommit rev : revlog) {
  572. count++;
  573. if (count > offset) {
  574. list.add(rev);
  575. if (maxCount > 0 && list.size() == maxCount) {
  576. break;
  577. }
  578. }
  579. }
  580. } else {
  581. for (RevCommit rev : revlog) {
  582. list.add(rev);
  583. if (maxCount > 0 && list.size() == maxCount) {
  584. break;
  585. }
  586. }
  587. }
  588. walk.dispose();
  589. } catch (Throwable t) {
  590. LOGGER.error("Failed to determine last change", t);
  591. }
  592. return list;
  593. }
  594. public static enum SearchType {
  595. AUTHOR, COMMITTER, COMMIT;
  596. public static SearchType forName(String name) {
  597. for (SearchType type : values()) {
  598. if (type.name().equalsIgnoreCase(name)) {
  599. return type;
  600. }
  601. }
  602. return null;
  603. }
  604. public String toString() {
  605. return name().toLowerCase();
  606. }
  607. }
  608. public static List<RevCommit> searchRevlogs(Repository r, String objectId, String value, final SearchType type, int offset, int maxCount) {
  609. final String lcValue = value.toLowerCase();
  610. List<RevCommit> list = new ArrayList<RevCommit>();
  611. try {
  612. if (objectId == null || objectId.trim().length() == 0) {
  613. objectId = Constants.HEAD;
  614. }
  615. RevWalk walk = new RevWalk(r);
  616. walk.setRevFilter(new RevFilter() {
  617. @Override
  618. public RevFilter clone() {
  619. return this;
  620. }
  621. @Override
  622. public boolean include(RevWalk walker, RevCommit commit) throws StopWalkException, MissingObjectException, IncorrectObjectTypeException, IOException {
  623. switch (type) {
  624. case AUTHOR:
  625. return (commit.getAuthorIdent().getName().toLowerCase().indexOf(lcValue) > -1) || (commit.getAuthorIdent().getEmailAddress().toLowerCase().indexOf(lcValue) > -1);
  626. case COMMITTER:
  627. return (commit.getCommitterIdent().getName().toLowerCase().indexOf(lcValue) > -1) || (commit.getCommitterIdent().getEmailAddress().toLowerCase().indexOf(lcValue) > -1);
  628. case COMMIT:
  629. return commit.getFullMessage().toLowerCase().indexOf(lcValue) > -1;
  630. }
  631. return false;
  632. }
  633. });
  634. ObjectId object = r.resolve(objectId);
  635. walk.markStart(walk.parseCommit(object));
  636. Iterable<RevCommit> revlog = walk;
  637. if (offset > 0) {
  638. int count = 0;
  639. for (RevCommit rev : revlog) {
  640. count++;
  641. if (count > offset) {
  642. list.add(rev);
  643. if (maxCount > 0 && list.size() == maxCount) {
  644. break;
  645. }
  646. }
  647. }
  648. } else {
  649. for (RevCommit rev : revlog) {
  650. list.add(rev);
  651. if (maxCount > 0 && list.size() == maxCount) {
  652. break;
  653. }
  654. }
  655. }
  656. walk.dispose();
  657. } catch (Throwable t) {
  658. LOGGER.error("Failed to determine last change", t);
  659. }
  660. return list;
  661. }
  662. public static List<RefModel> getTags(Repository r, int maxCount) {
  663. return getRefs(r, Constants.R_TAGS, maxCount);
  664. }
  665. public static List<RefModel> getLocalBranches(Repository r, int maxCount) {
  666. return getRefs(r, Constants.R_HEADS, maxCount);
  667. }
  668. public static List<RefModel> getRemoteBranches(Repository r, int maxCount) {
  669. return getRefs(r, Constants.R_REMOTES, maxCount);
  670. }
  671. public static List<RefModel> getRefs(Repository r, String refs, int maxCount) {
  672. List<RefModel> list = new ArrayList<RefModel>();
  673. try {
  674. Map<String, Ref> map = r.getRefDatabase().getRefs(refs);
  675. for (String name : map.keySet()) {
  676. Ref ref = map.get(name);
  677. RevCommit commit = getCommit(r, ref.getObjectId().getName());
  678. list.add(new RefModel(name, ref, commit));
  679. }
  680. Collections.sort(list);
  681. Collections.reverse(list);
  682. if (maxCount > 0 && list.size() > maxCount) {
  683. list = new ArrayList<RefModel>(list.subList(0, maxCount));
  684. }
  685. } catch (IOException e) {
  686. LOGGER.error("Failed to retrieve " + refs, e);
  687. }
  688. return list;
  689. }
  690. public static Ref getRef(Repository r, String id) {
  691. try {
  692. Map<String, Ref> map = r.getRefDatabase().getRefs(id);
  693. for (String name : map.keySet()) {
  694. return map.get(name);
  695. }
  696. } catch (IOException e) {
  697. LOGGER.error("Failed to retrieve ref " + id, e);
  698. }
  699. return null;
  700. }
  701. public static Date getCommitDate(RevCommit commit) {
  702. return new Date(commit.getCommitTime() * 1000l);
  703. }
  704. public static String getDisplayName(PersonIdent person) {
  705. final StringBuilder r = new StringBuilder();
  706. r.append(person.getName());
  707. r.append(" <");
  708. r.append(person.getEmailAddress());
  709. r.append(">");
  710. return r.toString();
  711. }
  712. public static StoredConfig readConfig(Repository r) {
  713. StoredConfig c = r.getConfig();
  714. if (c != null) {
  715. try {
  716. c.load();
  717. } catch (ConfigInvalidException cex) {
  718. LOGGER.error("Repository configuration is invalid!", cex);
  719. } catch (IOException cex) {
  720. LOGGER.error("Could not open repository configuration!", cex);
  721. }
  722. return c;
  723. }
  724. return null;
  725. }
  726. public static List<Metric> getDateMetrics(Repository r) {
  727. final List<RefModel> tags = getTags(r, -1);
  728. final Map<ObjectId, RefModel> tagMap = new HashMap<ObjectId, RefModel>();
  729. for (RefModel tag : tags) {
  730. tagMap.put(tag.getCommitId(), tag);
  731. }
  732. Metric total = new Metric("TOTAL");
  733. final Map<String, Metric> metricMap = new HashMap<String, Metric>();
  734. try {
  735. RevWalk walk = new RevWalk(r);
  736. ObjectId object = r.resolve(Constants.HEAD);
  737. RevCommit firstCommit = getFirstCommit(r, Constants.HEAD);
  738. RevCommit lastCommit = walk.parseCommit(object);
  739. int diffDays = (lastCommit.getCommitTime() - firstCommit.getCommitTime()) / (60 * 60 * 24);
  740. total.duration = diffDays;
  741. DateFormat df;
  742. if (diffDays <= 90) {
  743. // Days
  744. df = new SimpleDateFormat("yyyy-MM-dd");
  745. } else if (diffDays > 90 && diffDays < 365) {
  746. // Weeks
  747. df = new SimpleDateFormat("yyyy-MM (w)");
  748. } else {
  749. // Months
  750. df = new SimpleDateFormat("yyyy-MM");
  751. }
  752. walk.markStart(lastCommit);
  753. Iterable<RevCommit> revlog = walk;
  754. for (RevCommit rev : revlog) {
  755. Date d = getCommitDate(rev);
  756. String p = df.format(d);
  757. if (!metricMap.containsKey(p))
  758. metricMap.put(p, new Metric(p));
  759. Metric m = metricMap.get(p);
  760. m.count++;
  761. total.count++;
  762. if (tagMap.containsKey(rev.getId())) {
  763. m.tag++;
  764. total.tag++;
  765. }
  766. }
  767. } catch (Throwable t) {
  768. LOGGER.error("Failed to mine log history for metrics", t);
  769. }
  770. List<String> keys = new ArrayList<String>(metricMap.keySet());
  771. Collections.sort(keys);
  772. List<Metric> metrics = new ArrayList<Metric>();
  773. for (String key : keys) {
  774. metrics.add(metricMap.get(key));
  775. }
  776. metrics.add(0, total);
  777. return metrics;
  778. }
  779. public static RefModel getTicketsBranch(Repository r) {
  780. RefModel ticgitBranch = null;
  781. try {
  782. // search for ticgit branch in local heads
  783. for (RefModel ref : getLocalBranches(r, -1)) {
  784. if (ref.getDisplayName().endsWith("ticgit")) {
  785. ticgitBranch = ref;
  786. break;
  787. }
  788. }
  789. // search for ticgit branch in remote heads
  790. if (ticgitBranch == null) {
  791. for (RefModel ref : getRemoteBranches(r, -1)) {
  792. if (ref.getDisplayName().endsWith("ticgit")) {
  793. ticgitBranch = ref;
  794. break;
  795. }
  796. }
  797. }
  798. } catch (Throwable t) {
  799. LOGGER.error("Failed to find ticgit branch!", t);
  800. }
  801. return ticgitBranch;
  802. }
  803. public static List<TicketModel> getTickets(Repository r) {
  804. RefModel ticgitBranch = getTicketsBranch(r);
  805. List<PathModel> paths = getFilesInPath(r, null, ticgitBranch.getCommit());
  806. List<TicketModel> tickets = new ArrayList<TicketModel>();
  807. for (PathModel ticketFolder : paths) {
  808. if (ticketFolder.isTree()) {
  809. try {
  810. TicketModel t = new TicketModel(ticketFolder.name);
  811. readTicketContents(r, ticgitBranch, t);
  812. tickets.add(t);
  813. } catch (Throwable t) {
  814. LOGGER.error("Failed to get a ticket!", t);
  815. }
  816. }
  817. }
  818. Collections.sort(tickets);
  819. Collections.reverse(tickets);
  820. return tickets;
  821. }
  822. public static TicketModel getTicket(Repository r, String ticketFolder) {
  823. RefModel ticketsBranch = getTicketsBranch(r);
  824. if (ticketsBranch != null) {
  825. try {
  826. TicketModel ticket = new TicketModel(ticketFolder);
  827. readTicketContents(r, ticketsBranch, ticket);
  828. return ticket;
  829. } catch (Throwable t) {
  830. LOGGER.error("Failed to get ticket " + ticketFolder, t);
  831. }
  832. }
  833. return null;
  834. }
  835. private static void readTicketContents(Repository r, RefModel ticketsBranch, TicketModel ticket) {
  836. List<PathModel> ticketFiles = getFilesInPath(r, ticket.name, ticketsBranch.getCommit());
  837. for (PathModel file : ticketFiles) {
  838. String content = getRawContentAsString(r, ticketsBranch.getCommit(), file.path).trim();
  839. if (file.name.equals("TICKET_ID")) {
  840. ticket.id = content;
  841. } else if (file.name.equals("TITLE")) {
  842. ticket.title = content;
  843. } else {
  844. String[] chunks = file.name.split("_");
  845. if (chunks[0].equals("ASSIGNED")) {
  846. ticket.handler = content;
  847. } else if (chunks[0].equals("COMMENT")) {
  848. try {
  849. Comment c = new Comment(file.name, content);
  850. ticket.comments.add(c);
  851. } catch (ParseException e) {
  852. e.printStackTrace();
  853. }
  854. } else if (chunks[0].equals("TAG")) {
  855. if (content.startsWith("TAG_")) {
  856. ticket.tags.add(content.substring(4));
  857. } else {
  858. ticket.tags.add(content);
  859. }
  860. } else if (chunks[0].equals("STATE")) {
  861. ticket.state = content;
  862. }
  863. }
  864. }
  865. Collections.sort(ticket.comments);
  866. }
  867. public static String getTicketContent(Repository r, String filePath) {
  868. RefModel ticketsBranch = getTicketsBranch(r);
  869. if (ticketsBranch != null) {
  870. return getRawContentAsString(r, ticketsBranch.getCommit(), filePath);
  871. }
  872. return "";
  873. }
  874. }