GitParser.java 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602
  1. /*
  2. * MIT License
  3. *
  4. * Copyright (c) 2018 Axis Communications AB
  5. *
  6. * Permission is hereby granted, free of charge, to any person obtaining a copy
  7. * of this software and associated documentation files (the "Software"), to deal
  8. * in the Software without restriction, including without limitation the rights
  9. * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  10. * copies of the Software, and to permit persons to whom the Software is
  11. * furnished to do so, subject to the following conditions:
  12. *
  13. * The above copyright notice and this permission notice shall be included in all
  14. * copies or substantial portions of the Software.
  15. *
  16. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  17. * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  18. * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  19. * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  20. * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  21. * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  22. * SOFTWARE.
  23. */
  24. package parser;
  25. import data.Issues;
  26. import diff.JavaFileExtension;
  27. import diff.CPPFileExtension;
  28. import graph.AnnotationMap;
  29. import graph.FileAnnotationGraph;
  30. import org.eclipse.jgit.api.BlameCommand;
  31. import org.eclipse.jgit.api.errors.GitAPIException;
  32. import org.eclipse.jgit.blame.BlameResult;
  33. import org.eclipse.jgit.diff.DiffEntry;
  34. import org.eclipse.jgit.lib.ObjectId;
  35. import org.eclipse.jgit.lib.ObjectLoader;
  36. import org.eclipse.jgit.lib.ObjectReader;
  37. import org.eclipse.jgit.lib.Repository;
  38. import org.eclipse.jgit.revwalk.RevCommit;
  39. import org.eclipse.jgit.revwalk.RevTree;
  40. import org.eclipse.jgit.revwalk.RevWalk;
  41. import org.eclipse.jgit.storage.file.FileRepositoryBuilder;
  42. import org.eclipse.jgit.treewalk.TreeWalk;
  43. import org.incava.diffj.lang.DiffJException;
  44. import org.json.simple.JSONObject;
  45. import org.json.simple.parser.JSONParser;
  46. import org.json.simple.parser.ParseException;
  47. import org.slf4j.Logger;
  48. import util.CommitUtil;
  49. import util.JSONUtil;
  50. import java.io.File;
  51. import java.io.FileNotFoundException;
  52. import java.io.FileReader;
  53. import java.io.IOException;
  54. import java.io.StringReader;
  55. import java.nio.charset.StandardCharsets;
  56. import java.util.*;
  57. import java.util.concurrent.atomic.AtomicBoolean;
  58. import java.util.function.IntPredicate;
  59. import java.util.stream.Collectors;
  60. /**
  61. * A class which is capable to search and build line mapping graphs from a local repository. Uses
  62. * JGit to parse the repository and the revision trees.
  63. *
  64. * @author Oscar Svensson
  65. */
  66. public class GitParser {
  67. private static final List<String> USELESS_FILE_EXTENSIONS = Arrays.asList(".md", ".txt", ".markdown");
  68. private CommitUtil util;
  69. private Repository repo;
  70. private RevWalk revWalk;
  71. private Issues issues;
  72. private String resultPath;
  73. private Logger logger;
  74. private int depth;
  75. private BlameCommand blameCommand;
  76. /**
  77. * The constructor for the GitParser class. It requires the repository to exist and will fail if
  78. * its not. The resultPath is also created if it's not existing.
  79. *
  80. * @param path the path to where the local repository can be found.
  81. * @param resultPath the path to where the JSON files will be written.
  82. */
  83. public GitParser(String path, String resultPath, int depth, int customContext)
  84. throws IOException {
  85. FileRepositoryBuilder builder = new FileRepositoryBuilder();
  86. builder.setMustExist(true);
  87. builder.addCeilingDirectory(new File(path));
  88. builder.findGitDir(new File(path));
  89. this.repo = builder.build();
  90. this.revWalk = new RevWalk(repo);
  91. this.blameCommand = new BlameCommand(this.repo);
  92. this.resultPath = resultPath;
  93. /*
  94. * Check if the resultpath exists otherwise create it.
  95. */
  96. if (this.resultPath != null) {
  97. File resDirectory = new File(resultPath);
  98. if (!resDirectory.exists()) resDirectory.mkdirs();
  99. } else {
  100. System.err.println("Resultpath not set! Using deafult directory instead.");
  101. this.resultPath = "./results";
  102. }
  103. this.util = new CommitUtil(this.repo, customContext);
  104. this.depth = depth;
  105. }
  106. public String getResultPath() {
  107. return this.resultPath;
  108. }
  109. public Repository getRepository() {
  110. return this.repo;
  111. }
  112. public Issues getIssues() {
  113. return this.issues;
  114. }
  115. public void useLogger(Logger logger) {
  116. this.logger = logger;
  117. }
  118. private boolean isCPlusPlus(String filePath) {
  119. return
  120. filePath.endsWith(".cpp") ||
  121. filePath.endsWith(".cxx") ||
  122. filePath.endsWith(".c++") ||
  123. filePath.endsWith(".hpp") ||
  124. filePath.endsWith(".hxx") ||
  125. filePath.endsWith(".h++") ||
  126. filePath.endsWith(".cc") ||
  127. filePath.endsWith(".hh") ||
  128. filePath.endsWith(".c") ||
  129. filePath.endsWith(".C") ||
  130. filePath.endsWith(".h") ||
  131. filePath.endsWith(".H");
  132. }
  133. private int getSourceLine(BlameResult foundCommit, int index) throws IOException {
  134. foundCommit.computeAll();
  135. try {
  136. return foundCommit.getSourceLine(index);
  137. } catch (ArrayIndexOutOfBoundsException e) {
  138. return -1;
  139. }
  140. }
  141. /**
  142. * Traces a file change that have occured before a given commmit.
  143. *
  144. * @param filePath specifies which file to trace changes on.
  145. * @param source the source commit from which the trace should start at.
  146. */
  147. private FileAnnotationGraph traceFileChanges(String filePath, Commit source, int step)
  148. throws IOException, GitAPIException {
  149. if (step == 0 || !source.diffWithParent.containsKey(filePath)) return null;
  150. if (!isCPlusPlus(filePath)) {
  151. logger.warn("early skipping non-C++ file: " + filePath);
  152. return createEmptyGraph(filePath);
  153. }
  154. /*
  155. * Save all line numbers for the source commits deletions,
  156. * and the lines immediately above and below source commit additions.
  157. */
  158. List<Integer> delIndexes = buildDelIndexes(filePath, source);
  159. /*foundRevisions1.forEach((k,v) -> { logger.warn(k + "[" + v + "]"); });
  160. Map<Integer, Integer> lineMap = foundRevisions1.get(parentRev);
  161. logger.warn("" + lineMap.get(7));
  162. List<Integer> tracedAddIndexes = addIndexes.stream().map(i -> lineMap.get(i)).collect(Collectors.toList());*/
  163. FileAnnotationGraph graph = createEmptyGraph(filePath);
  164. graph.revisions.add(ObjectId.toString(source.commit.toObjectId()));
  165. BlameResult foundDel = callBlameCommand(filePath, source.commit.getParent(0));
  166. Map<RevCommit, Map<Integer, Integer>> foundRevisions = null;
  167. if (foundDel != null) {
  168. foundRevisions = linkRevisionsWithLineNumbers(delIndexes, foundDel);
  169. } else {
  170. List<Integer> addIndexes = buildAddIndexes(filePath, source);
  171. BlameResult foundAdd = callBlameCommand(filePath, source.commit);
  172. if (foundAdd != null) {
  173. foundRevisions = linkRevisionsWithLineNumbers(addIndexes, foundAdd);
  174. } else {
  175. return graph;
  176. }
  177. }
  178. populateGraphWithMappings(graph, foundRevisions);
  179. populateSubgraphs(filePath, step, graph, foundRevisions);
  180. return graph;
  181. }
  182. private List<Integer> buildDelIndexes(String filePath, Commit source) {
  183. List<Integer> delIndexes = source
  184. .diffWithParent
  185. .get(filePath)
  186. .deletions
  187. .stream()
  188. .map(s -> parseInt(s[0]))
  189. .collect(Collectors.toList());
  190. if(filePath.endsWith(".java")) {
  191. Set<Integer> changesFromDiffJ = changesFromDiffJ(filePath, source);
  192. delIndexes = delIndexes.stream().filter(changesFromDiffJ::contains).collect(Collectors.toList());
  193. } else if(isCPlusPlus(filePath)) {
  194. Set<Integer> changesFromCPP = changesFromCPP(filePath, source);
  195. delIndexes = delIndexes.stream().filter(changesFromCPP::contains).collect(Collectors.toList());
  196. }
  197. return delIndexes;
  198. }
  199. private List<Integer> buildAddIndexes(String filePath, Commit source) {
  200. Set<Integer> addIndexes = source
  201. .diffWithParent
  202. .get(filePath)
  203. .insertions
  204. .stream()
  205. .map(s -> parseInt(s[0]))
  206. .collect(Collectors.toSet());
  207. if (addIndexes.isEmpty()) {
  208. return new ArrayList();
  209. }
  210. IntPredicate hasToken = (i) -> true;
  211. int lastLine = Collections.max(addIndexes);
  212. if(isCPlusPlus(filePath)) {
  213. try {
  214. CPPFileExtension file = getCPPFileContentAtRevision(filePath, source.commit);
  215. final Set<Integer> tokenLines = file.allLineNumbers().stream().map(it -> it-1).collect(Collectors.toSet());
  216. addIndexes = addIndexes.stream().filter(tokenLines::contains).collect(Collectors.toSet());
  217. hasToken = (i) -> tokenLines.contains(i);
  218. if (!tokenLines.isEmpty()) {
  219. lastLine = Collections.max(tokenLines);
  220. }
  221. } catch (IOException e){
  222. logger.warn("buildAddIndexes Failed to parse " + filePath + '\n');
  223. }
  224. } else {
  225. //logger.warn("blaming non-C++ file");
  226. logger.warn("skipping non-C++ file: " + filePath);
  227. return new ArrayList(new TreeSet());
  228. }
  229. List<Integer> sortedAddIndexes = new ArrayList(new TreeSet(addIndexes));
  230. if (sortedAddIndexes.isEmpty()) {
  231. return sortedAddIndexes;
  232. }
  233. /* How this works conceptually and how it's implemented slightly differ.
  234. conceptual algorithm:
  235. For each add line, find the line immediately preceding it (skipping
  236. "empty" lines without relevant tokens). If that line is not an add line,
  237. track it, as well as the (non-empty) line after the last add line.
  238. So in a sense, we're actually tracking the ranges of non-add blocks.
  239. actual algorithm:
  240. Track the index of the last add line, but include "empty" lines after
  241. that line, even if they aren't actually add lines.
  242. If this add line is the one after that, we are in a contiguous add block,
  243. so keep going, updating the aforementioned index.
  244. Once that's not the case, we're in a new add block, so add the non-empty
  245. line after the tracked index, and the non-empty line before this one.
  246. Edge cases to be aware of:
  247. - fencepost errors from the first/last add lines
  248. - add lines that are the first/last lines in the file
  249. - add lines that are second to first/last lines in the file
  250. - files without any add lines
  251. */
  252. List<Integer> trackedIndexes = new ArrayList<>();
  253. // last added line, including "empty" lines
  254. int lastAddIndex = -2; // more than one less than the first valid index
  255. //List<Integer> addIndexList = addIndexes.stream().sorted().collect(Collectors.toList());
  256. //logger.warn("addIndexes: " + sortedAddIndexes);
  257. //logger.warn("lastLine: " + lastLine);
  258. for (int i : sortedAddIndexes) {
  259. //logger.warn(i + ",");
  260. if (i == lastAddIndex + 1) {
  261. // contiguous add block, keep going (skipping contiguous "empty" lines)
  262. while (!hasToken.test(++lastAddIndex + 1) && lastAddIndex + 1 <= lastLine){};
  263. continue;
  264. }
  265. if (lastAddIndex >= 0) {
  266. // lastAddIndex points to first line before a non-"empty" line,
  267. // after the last add block
  268. trackedIndexes.add(lastAddIndex + 1);
  269. }
  270. // find the most recent non-"empty" line,
  271. // checking that it's not what we just added
  272. int prevline = i;
  273. while (--prevline > lastAddIndex + 1 && !hasToken.test(prevline)){};
  274. if (prevline > lastAddIndex + 1 && prevline >= 0) {
  275. trackedIndexes.add(prevline);
  276. }
  277. lastAddIndex = i;
  278. // skip contiguous "empty" lines
  279. while (!hasToken.test(lastAddIndex + 1) && lastAddIndex + 1 <= lastLine){lastAddIndex++;}
  280. }
  281. if (lastAddIndex >= 0 && hasToken.test(lastAddIndex + 1)) {
  282. trackedIndexes.add(lastAddIndex + 1);
  283. }
  284. /*logger.warn("\n");
  285. logger.warn("trackedIndexes: ");
  286. for (int i : trackedIndexes) {
  287. logger.warn(i + ",");
  288. }
  289. logger.warn("\n");*/
  290. return trackedIndexes;
  291. }
  292. private Set<Integer> changesFromDiffJ(String filePath, Commit source) {
  293. try {
  294. JavaFileExtension revision = getJavaFileContentAtRevision(filePath, source.commit);
  295. JavaFileExtension parentRev = getJavaFileContentAtRevision(filePath, source.commit.getParent(0));
  296. if(revision == null || parentRev == null) {
  297. return Collections.emptySet();
  298. }
  299. // Converting line numbers to indexes.
  300. return revision.affectedLineNumbers(parentRev).stream().map(it ->
  301. it-1
  302. ).collect(Collectors.toSet());
  303. } catch (Exception e) {
  304. logger.warn(String.format("Exception ### File %s from: %s to: %s", filePath, source.commit.toString(), source.commit.getParent(0).toString()));
  305. return Collections.emptySet();
  306. }
  307. }
  308. private Set<Integer> changesFromCPP(String filePath, Commit source) {
  309. try {
  310. CPPFileExtension revision = getCPPFileContentAtRevision(filePath, source.commit);
  311. CPPFileExtension parentRev = getCPPFileContentAtRevision(filePath, source.commit.getParent(0));
  312. //logger.warn("revision: " + revision.allLineNumbers());
  313. //logger.warn("parentRev: " + parentRev.allLineNumbers());
  314. if(revision == null || parentRev == null) {
  315. logger.warn("Found empty revision: " + revision + " " + parentRev);
  316. return Collections.emptySet();
  317. }
  318. // Converting line numbers to indexes.
  319. return revision.affectedLineNumbers(parentRev).stream().map(it ->
  320. it-1
  321. ).collect(Collectors.toSet());
  322. } catch (Exception e) {
  323. logger.warn(String.format("Exception %s ### File %s from: %s to: %s", e.toString(), filePath, source.commit.toString(), source.commit.getParent(0).toString()));
  324. return Collections.emptySet();
  325. }
  326. }
  327. private String getFileContentAtRevision(String filePath, RevCommit revision) throws IOException {
  328. RevTree tree = revWalk.parseCommit(revision.getId()).getTree();
  329. TreeWalk treeWalk = TreeWalk.forPath(repo, filePath, tree);
  330. if(treeWalk == null) {
  331. return null;
  332. }
  333. ObjectId blobId = treeWalk.getObjectId(0);
  334. ObjectReader objectReader = repo.newObjectReader();
  335. ObjectLoader objectLoader = objectReader.open(blobId);
  336. byte[] bytes = objectLoader.getBytes();
  337. return new String(bytes, StandardCharsets.UTF_8);
  338. }
  339. private JavaFileExtension getJavaFileContentAtRevision(String filePath, RevCommit revision) throws IOException, DiffJException {
  340. String string = getFileContentAtRevision(filePath, revision);
  341. return new JavaFileExtension(string);
  342. }
  343. private CPPFileExtension getCPPFileContentAtRevision(String filePath, RevCommit revision) throws IOException {
  344. String string = getFileContentAtRevision(filePath, revision);
  345. return new CPPFileExtension(filePath + revision.toString(), new StringReader(string));
  346. }
  347. /*
  348. * Start building subgraphs.
  349. */
  350. private void populateSubgraphs(String filePath, int step, FileAnnotationGraph graph, Map<RevCommit, Map<Integer, Integer>> foundRevisions) throws IOException, GitAPIException {
  351. for (Map.Entry<RevCommit, Map<Integer, Integer>> rev : foundRevisions.entrySet()) {
  352. Commit subCommit = this.util.getCommitDiffingLines(rev.getKey());
  353. FileAnnotationGraph subGraph = traceFileChanges(filePath, subCommit, step - 1);
  354. if (subGraph == null) break;
  355. graph.sub_graphs.put(subCommit.getHashString(), subGraph);
  356. }
  357. }
  358. /*
  359. * Save all mappings in the annotationgraph.
  360. */
  361. private void populateGraphWithMappings(FileAnnotationGraph graph, Map<RevCommit, Map<Integer, Integer>> foundRevisions) {
  362. for (Map.Entry<RevCommit, Map<Integer, Integer>> rev : foundRevisions.entrySet()) {
  363. String revSha = ObjectId.toString(rev.getKey().toObjectId());
  364. if (!graph.mappings.containsKey(revSha)) {
  365. graph.revisions.add(revSha);
  366. graph.mappings.put(revSha, rev.getValue());
  367. } else {
  368. Map<Integer, Integer> linemapping = graph.mappings.get(revSha);
  369. // Add missing mappings.
  370. for (Map.Entry<Integer, Integer> entry : rev.getValue().entrySet()) {
  371. if (!linemapping.containsKey(entry.getKey())) {
  372. linemapping.put(entry.getKey(), entry.getValue());
  373. }
  374. }
  375. }
  376. }
  377. }
  378. /*
  379. * Grab the blamed commits and get the line numbers.
  380. */
  381. private Map<RevCommit, Map<Integer, Integer>> linkRevisionsWithLineNumbers(List<Integer> delIndexes, BlameResult found) {
  382. int index;
  383. Map<RevCommit, Map<Integer, Integer>> foundRevisions = new HashMap<>();
  384. for (Integer delIndex : delIndexes) {
  385. index = delIndex;
  386. if (index == -1) continue;
  387. try {
  388. RevCommit foundRev = found.getSourceCommit(index);
  389. if (!foundRevisions.containsKey(foundRev)) {
  390. Map<Integer, Integer> blamedLines = new LinkedHashMap<>();
  391. blamedLines.put(index, getSourceLine(found, index));
  392. foundRevisions.put(foundRev, blamedLines);
  393. } else {
  394. foundRevisions.get(foundRev).put(index, getSourceLine(found, index));
  395. }
  396. } catch (Exception e) {
  397. // This means that a row didn't exist in a previous revision..
  398. }
  399. }
  400. return foundRevisions;
  401. }
  402. private BlameResult callBlameCommand(String filePath, RevCommit startCommit) throws GitAPIException {
  403. blameCommand.setStartCommit(startCommit);
  404. blameCommand.setFilePath(filePath);
  405. return blameCommand.call();
  406. }
  407. /*
  408. * Create a graph to store line mappings in.
  409. */
  410. private FileAnnotationGraph createEmptyGraph(String filePath) {
  411. FileAnnotationGraph graph = new FileAnnotationGraph();
  412. graph.filePath = filePath;
  413. graph.revisions = new LinkedList<>();
  414. graph.mappings = new HashMap<>();
  415. graph.sub_graphs = new HashMap<>();
  416. return graph;
  417. }
  418. /**
  419. * With each revision, check all files and build their line mapping graphs for each changed line.
  420. *
  421. * @param commits list of commits that should be traced.
  422. * @return the map containing annotation graphs for each file change by a commit.
  423. */
  424. private AnnotationMap<String, List<FileAnnotationGraph>> buildLineMappingGraph(
  425. List<Commit> commits) throws IOException, GitAPIException {
  426. AnnotationMap<String, List<FileAnnotationGraph>> fileGraph = new AnnotationMap<>();
  427. for (Commit commit : commits) {
  428. List<FileAnnotationGraph> graphs = new LinkedList<>();
  429. for (Map.Entry<String, DiffEntry.ChangeType> file : commit.changeTypes.entrySet()) {
  430. String filePath = file.getKey();
  431. if (checkFileType(filePath)) {
  432. FileAnnotationGraph tracedCommits = traceFileChanges(filePath, commit, this.depth);
  433. graphs.add(tracedCommits);
  434. }
  435. }
  436. fileGraph.put(commit.getHashString(), graphs);
  437. }
  438. return fileGraph;
  439. }
  440. private boolean checkFileType(String filePath) {
  441. return !filePath.contains("/test/") && checkFileExtension(filePath);
  442. }
  443. private boolean checkFileExtension(String filePath) {
  444. AtomicBoolean validExtension = new AtomicBoolean(true);
  445. USELESS_FILE_EXTENSIONS.forEach(it -> {
  446. validExtension.set(!filePath.endsWith(it));
  447. });
  448. return validExtension.get();
  449. }
  450. /**
  451. * Wrapper method to catch a faulty value.
  452. *
  453. * @param value the string to convert to an int.
  454. * @return the value of the string as an int.
  455. */
  456. private int parseInt(String value) {
  457. try {
  458. return Integer.parseInt(value);
  459. } catch (Exception e) {
  460. return -1;
  461. }
  462. }
  463. /**
  464. * Searchs for commits that have certain keywords in their messages, indicating that they have
  465. * fiexd bugs.
  466. *
  467. * <p>It then saves the found commits and the line mapping graph to two JSON files.
  468. *
  469. * @param commits a set containing references to commits.
  470. */
  471. public AnnotationMap<String, List<FileAnnotationGraph>> annotateCommits(Set<RevCommit> commits)
  472. throws IOException, GitAPIException {
  473. this.logger.info("Parsing difflines for all found commits.");
  474. List<Commit> parsedCommits = this.util.getDiffingLines(commits);
  475. this.logger.info("Saving parsed commits to file");
  476. JSONUtil.saveFoundCommits(parsedCommits, this.resultPath);
  477. this.logger.info("Building line mapping graph.");
  478. AnnotationMap<String, List<FileAnnotationGraph>> mapping = buildLineMappingGraph(parsedCommits);
  479. this.logger.info("Saving results to file");
  480. mapping.saveToJSON(this.resultPath);
  481. return mapping;
  482. }
  483. /**
  484. * Use this method to use already found big fixing changes.
  485. *
  486. * @param path the path to the json file where the changes are stored.
  487. */
  488. public Set<RevCommit> readBugFixCommits(String path) throws IOException {
  489. if (repo == null) return Collections.emptySet();
  490. this.issues = new Issues();
  491. JSONParser commitParser = new JSONParser();
  492. try {
  493. JSONObject object = (JSONObject) commitParser.parse(new FileReader(path));
  494. this.issues.revisions = new HashSet<>();
  495. this.issues.dates = new HashMap<>();
  496. for (Object issue : object.keySet()) {
  497. Map<String, String> issueInfo = (Map<String, String>) object.get(issue);
  498. String rev = issueInfo.get("hash");
  499. RevCommit revCommit = this.repo.parseCommit(this.repo.resolve(rev));
  500. Map<String, String> dates = new HashMap<>();
  501. dates.put("resolutiondate", issueInfo.get("resolutiondate"));
  502. dates.put("commitdate", issueInfo.get("commitdate"));
  503. dates.put("creationdate", issueInfo.get("creationdate"));
  504. this.issues.dates.put(rev, dates);
  505. this.issues.revisions.add(revCommit);
  506. }
  507. } catch (FileNotFoundException | ParseException e) {
  508. return Collections.emptySet();
  509. }
  510. this.logger.info(String.format("Found %d number of commits.", this.issues.revisions.size()));
  511. if (this.issues.revisions.size() == 0) return Collections.emptySet();
  512. return this.issues.revisions;
  513. }
  514. }