Skip to content

Commit 9cf5d34

Browse files
Merge pull request #98 from refactorfirst/optimize-git
Optimize git
2 parents 0c4e98f + 75449c5 commit 9cf5d34

File tree

8 files changed

+333
-114
lines changed

8 files changed

+333
-114
lines changed

change-proneness-ranker/src/main/java/org/hjug/git/ChangePronenessRanker.java

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,17 @@
44
import java.util.*;
55
import lombok.extern.slf4j.Slf4j;
66
import org.eclipse.jgit.api.errors.GitAPIException;
7-
import org.eclipse.jgit.lib.Repository;
87

98
@Slf4j
109
public class ChangePronenessRanker {
1110

1211
private final TreeMap<Integer, Integer> changeCountsByTimeStamps = new TreeMap<>();
1312
private final Map<String, ScmLogInfo> cachedScmLogInfos = new HashMap<>();
1413

15-
public ChangePronenessRanker(Repository repository, GitLogReader repositoryLogReader) {
14+
public ChangePronenessRanker(GitLogReader repositoryLogReader) {
1615
try {
1716
log.info("Capturing change count based on commit timestamps");
18-
changeCountsByTimeStamps.putAll(repositoryLogReader.captureChangeCountByCommitTimestamp(repository));
17+
changeCountsByTimeStamps.putAll(repositoryLogReader.captureChangeCountByCommitTimestamp());
1918
} catch (IOException | GitAPIException e) {
2019
log.error("Error reading from repository: {}", e.getMessage());
2120
}

change-proneness-ranker/src/main/java/org/hjug/git/GitLogReader.java

Lines changed: 61 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,39 @@
1515
import org.eclipse.jgit.util.io.NullOutputStream;
1616

1717
@Slf4j
18-
public class GitLogReader {
18+
public class GitLogReader implements AutoCloseable {
1919

2020
static final String JAVA_FILE_TYPE = ".java";
2121

22+
private Repository gitRepository;
23+
24+
private Git git;
25+
26+
public GitLogReader() {}
27+
28+
public GitLogReader(File basedir) throws IOException {
29+
FileRepositoryBuilder repositoryBuilder = new FileRepositoryBuilder().findGitDir(basedir);
30+
String gitIndexFileEnvVariable = System.getenv("GIT_INDEX_FILE");
31+
if (Objects.nonNull(gitIndexFileEnvVariable)
32+
&& !gitIndexFileEnvVariable.trim().isEmpty()) {
33+
log.debug("Setting Index File based on Env Variable GIT_INDEX_FILE {}", gitIndexFileEnvVariable);
34+
repositoryBuilder = repositoryBuilder.setIndexFile(new File(gitIndexFileEnvVariable));
35+
}
36+
37+
git = Git.open(repositoryBuilder.getGitDir());
38+
gitRepository = git.getRepository();
39+
}
40+
41+
GitLogReader(Git git) {
42+
this.git = git;
43+
gitRepository = git.getRepository();
44+
}
45+
46+
@Override
47+
public void close() throws Exception {
48+
git.close();
49+
}
50+
2251
// Based on
2352
// https://github.com/Cosium/git-code-format-maven-plugin/blob/master/src/main/java/com/cosium/code/format/AbstractMavenGitCodeFormatMojo.java
2453
// MIT License
@@ -81,14 +110,12 @@ public Map<String, ByteArrayOutputStream> listRepositoryContentsAtHEAD(Repositor
81110
* Returns the number of commits and earliest commit for a given path
82111
* TODO: Move to a different class???
83112
*
84-
* @param repository
85113
* @param path
86114
* @return a LogInfo object
87115
* @throws GitAPIException
88116
*/
89-
public ScmLogInfo fileLog(Repository repository, String path) throws GitAPIException, IOException {
90-
Git git = new Git(repository);
91-
ObjectId branchId = repository.resolve("HEAD");
117+
public ScmLogInfo fileLog(String path) throws GitAPIException, IOException {
118+
ObjectId branchId = gitRepository.resolve("HEAD");
92119
Iterable<RevCommit> revCommits = git.log().add(branchId).addPath(path).call();
93120

94121
int commitCount = 0;
@@ -101,8 +128,7 @@ public ScmLogInfo fileLog(Repository repository, String path) throws GitAPIExcep
101128
}
102129

103130
// based on https://stackoverflow.com/a/59274329/346247
104-
int mostRecentCommit = new Git(repository)
105-
.log()
131+
int mostRecentCommit = git.log()
106132
.add(branchId)
107133
.addPath(path)
108134
.setMaxCount(1)
@@ -115,49 +141,47 @@ public ScmLogInfo fileLog(Repository repository, String path) throws GitAPIExcep
115141
}
116142

117143
// based on https://stackoverflow.com/questions/27361538/how-to-show-changes-between-commits-with-jgit
118-
public TreeMap<Integer, Integer> captureChangeCountByCommitTimestamp(Repository repository)
119-
throws IOException, GitAPIException {
144+
public TreeMap<Integer, Integer> captureChangeCountByCommitTimestamp() throws IOException, GitAPIException {
120145

121146
TreeMap<Integer, Integer> changesByCommitTimestamp = new TreeMap<>();
122147

123-
try (Git git = new Git(repository)) {
124-
ObjectId branchId = repository.resolve("HEAD");
125-
Iterable<RevCommit> commits = git.log().add(branchId).call();
148+
ObjectId branchId = gitRepository.resolve("HEAD");
149+
Iterable<RevCommit> commits = git.log().add(branchId).call();
126150

127-
RevCommit newCommit = null;
151+
RevCommit newCommit = null;
128152

129-
for (Iterator<RevCommit> iterator = commits.iterator(); iterator.hasNext(); ) {
130-
RevCommit oldCommit = iterator.next();
131-
132-
int count = 0;
133-
if (null == newCommit) {
134-
newCommit = oldCommit;
135-
continue;
136-
}
153+
for (Iterator<RevCommit> iterator = commits.iterator(); iterator.hasNext(); ) {
154+
RevCommit oldCommit = iterator.next();
137155

138-
for (DiffEntry entry : getDiffEntries(git, newCommit, oldCommit)) {
139-
if (entry.getNewPath().endsWith(JAVA_FILE_TYPE)
140-
|| entry.getOldPath().endsWith(JAVA_FILE_TYPE)) {
141-
count++;
142-
}
143-
}
156+
int count = 0;
157+
if (null == newCommit) {
158+
newCommit = oldCommit;
159+
continue;
160+
}
144161

145-
if (count > 0) {
146-
changesByCommitTimestamp.put(newCommit.getCommitTime(), count);
162+
for (DiffEntry entry : getDiffEntries(newCommit, oldCommit)) {
163+
if (entry.getNewPath().endsWith(JAVA_FILE_TYPE)
164+
|| entry.getOldPath().endsWith(JAVA_FILE_TYPE)) {
165+
count++;
147166
}
167+
}
148168

149-
// Handle first / initial commit
150-
if (!iterator.hasNext()) {
151-
changesByCommitTimestamp.putAll(walkFirstCommit(repository, oldCommit));
152-
}
169+
if (count > 0) {
170+
changesByCommitTimestamp.put(newCommit.getCommitTime(), count);
171+
}
153172

154-
newCommit = oldCommit;
173+
// Handle first / initial commit
174+
if (!iterator.hasNext()) {
175+
changesByCommitTimestamp.putAll(walkFirstCommit(oldCommit));
155176
}
177+
178+
newCommit = oldCommit;
156179
}
180+
157181
return changesByCommitTimestamp;
158182
}
159183

160-
private List<DiffEntry> getDiffEntries(Git git, RevCommit newCommit, RevCommit oldCommit) throws IOException {
184+
private List<DiffEntry> getDiffEntries(RevCommit newCommit, RevCommit oldCommit) throws IOException {
161185
CanonicalTreeParser oldTreeIter = new CanonicalTreeParser();
162186
CanonicalTreeParser newTreeIter = new CanonicalTreeParser();
163187
try (ObjectReader reader = git.getRepository().newObjectReader()) {
@@ -172,11 +196,11 @@ private List<DiffEntry> getDiffEntries(Git git, RevCommit newCommit, RevCommit o
172196
return df.scan(oldTreeIter, newTreeIter);
173197
}
174198

175-
Map<Integer, Integer> walkFirstCommit(Repository repository, RevCommit firstCommit) throws IOException {
199+
Map<Integer, Integer> walkFirstCommit(RevCommit firstCommit) throws IOException {
176200
Map<Integer, Integer> changesByCommitTimestamp = new TreeMap<>();
177201
int firstCommitCount = 0;
178202
ObjectId treeId = firstCommit.getTree();
179-
try (TreeWalk treeWalk = new TreeWalk(repository)) {
203+
try (TreeWalk treeWalk = new TreeWalk(gitRepository)) {
180204
treeWalk.setRecursive(false);
181205
treeWalk.reset(treeId);
182206
while (treeWalk.next()) {

change-proneness-ranker/src/test/java/org/hjug/git/ChangePronenessRankerTest.java

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
package org.hjug.git;
22

3-
import static org.mockito.ArgumentMatchers.any;
43
import static org.mockito.Mockito.mock;
54
import static org.mockito.Mockito.when;
65

@@ -31,9 +30,9 @@ void testChangePronenessCalculation() throws IOException, GitAPIException {
3130
commitsWithChangeCounts.put(scmLogInfo.getEarliestCommit() + 5 * 60, 3);
3231
commitsWithChangeCounts.put(scmLogInfo.getEarliestCommit() + 10 * 60, 3);
3332

34-
when(repositoryLogReader.captureChangeCountByCommitTimestamp(any())).thenReturn(commitsWithChangeCounts);
33+
when(repositoryLogReader.captureChangeCountByCommitTimestamp()).thenReturn(commitsWithChangeCounts);
3534

36-
changePronenessRanker = new ChangePronenessRanker(null, repositoryLogReader);
35+
changePronenessRanker = new ChangePronenessRanker(repositoryLogReader);
3736
List<ScmLogInfo> scmLogInfos = new ArrayList<>();
3837
scmLogInfos.add(scmLogInfo);
3938
changePronenessRanker.rankChangeProneness(scmLogInfos);
@@ -57,8 +56,8 @@ void testRankChangeProneness() throws IOException, GitAPIException {
5756
commitsWithChangeCounts.put(scmLogInfo2.getEarliestCommit() + 5 * 60, 5);
5857
commitsWithChangeCounts.put(scmLogInfo2.getEarliestCommit() + 10 * 60, 5);
5958

60-
when(repositoryLogReader.captureChangeCountByCommitTimestamp(any())).thenReturn(commitsWithChangeCounts);
61-
changePronenessRanker = new ChangePronenessRanker(null, repositoryLogReader);
59+
when(repositoryLogReader.captureChangeCountByCommitTimestamp()).thenReturn(commitsWithChangeCounts);
60+
changePronenessRanker = new ChangePronenessRanker(repositoryLogReader);
6261

6362
List<ScmLogInfo> scmLogInfos = new ArrayList<>();
6463
scmLogInfos.add(scmLogInfo);

change-proneness-ranker/src/test/java/org/hjug/git/GitLogReaderTest.java

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ void testFileLog() throws IOException, GitAPIException, InterruptedException {
4040
// This path works when referencing the full Tobago repository
4141
// String filePath = "tobago-core/src/main/java/org/apache/myfaces/tobago/facelets/AttributeHandler.java";
4242

43-
GitLogReader gitLogReader = new GitLogReader();
43+
GitLogReader gitLogReader = new GitLogReader(git);
4444

4545
String attributeHandler = "AttributeHandler.java";
4646
InputStream resourceAsStream = getClass().getClassLoader().getResourceAsStream(attributeHandler);
@@ -59,7 +59,7 @@ void testFileLog() throws IOException, GitAPIException, InterruptedException {
5959
git.add().addFilepattern(".").call();
6060
RevCommit secondCommit = git.commit().setMessage("message").call();
6161

62-
ScmLogInfo scmLogInfo = gitLogReader.fileLog(repository, attributeHandler);
62+
ScmLogInfo scmLogInfo = gitLogReader.fileLog(attributeHandler);
6363

6464
Assertions.assertEquals(2, scmLogInfo.getCommitCount());
6565
Assertions.assertEquals(firstCommit.getCommitTime(), scmLogInfo.getEarliestCommit());
@@ -68,23 +68,23 @@ void testFileLog() throws IOException, GitAPIException, InterruptedException {
6868

6969
@Test
7070
void testWalkFirstCommit() throws IOException, GitAPIException {
71-
GitLogReader gitLogReader = new GitLogReader();
71+
GitLogReader gitLogReader = new GitLogReader(git);
7272

7373
String attributeHandler = "AttributeHandler.java";
7474
InputStream resourceAsStream = getClass().getClassLoader().getResourceAsStream(attributeHandler);
7575
writeFile(attributeHandler, convertInputStreamToString(resourceAsStream));
7676
git.add().addFilepattern(".").call();
7777
RevCommit commit = git.commit().setMessage("message").call();
7878

79-
Map<Integer, Integer> result = gitLogReader.walkFirstCommit(repository, commit);
79+
Map<Integer, Integer> result = gitLogReader.walkFirstCommit(commit);
8080

8181
Assertions.assertTrue(result.containsKey(commit.getCommitTime()));
8282
Assertions.assertEquals(1, result.get(commit.getCommitTime()).intValue());
8383
}
8484

8585
@Test
8686
void testCaptureChangCountByCommitTimestamp() throws Exception {
87-
GitLogReader gitLogReader = new GitLogReader();
87+
GitLogReader gitLogReader = new GitLogReader(git);
8888

8989
String attributeHandler = "AttributeHandler.java";
9090
InputStream resourceAsStream = getClass().getClassLoader().getResourceAsStream(attributeHandler);
@@ -106,7 +106,7 @@ void testCaptureChangCountByCommitTimestamp() throws Exception {
106106
git.add().addFilepattern(".").call();
107107
RevCommit secondCommit = git.commit().setMessage("message").call();
108108

109-
Map<Integer, Integer> commitCounts = gitLogReader.captureChangeCountByCommitTimestamp(repository);
109+
Map<Integer, Integer> commitCounts = gitLogReader.captureChangeCountByCommitTimestamp();
110110

111111
Assertions.assertEquals(1, commitCounts.get(firstCommit.getCommitTime()).intValue());
112112
Assertions.assertEquals(

cost-benefit-calculator/src/main/java/org/hjug/cbc/CostBenefitCalculator.java

Lines changed: 32 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818
import net.sourceforge.pmd.*;
1919
import net.sourceforge.pmd.lang.LanguageRegistry;
2020
import org.eclipse.jgit.api.errors.GitAPIException;
21-
import org.eclipse.jgit.lib.Repository;
2221
import org.hjug.cycledetector.CircularReferenceChecker;
2322
import org.hjug.git.ChangePronenessRanker;
2423
import org.hjug.git.GitLogReader;
@@ -33,14 +32,14 @@
3332
import org.jgrapht.graph.DefaultWeightedEdge;
3433

3534
@Slf4j
36-
public class CostBenefitCalculator {
35+
public class CostBenefitCalculator implements AutoCloseable {
3736

3837
private final Map<String, AsSubgraph<String, DefaultWeightedEdge>> renderedSubGraphs = new HashMap<>();
3938

4039
private Report report;
4140
private String repositoryPath;
42-
private final GitLogReader gitLogReader = new GitLogReader();
43-
private Repository repository = null;
41+
private GitLogReader gitLogReader;
42+
4443
private final ChangePronenessRanker changePronenessRanker;
4544
private final JavaProjectParser javaProjectParser = new JavaProjectParser();
4645

@@ -52,16 +51,22 @@ public CostBenefitCalculator(String repositoryPath) {
5251

5352
log.info("Initiating Cost Benefit calculation");
5453
try {
55-
repository = gitLogReader.gitRepository(new File(repositoryPath));
56-
for (String file :
57-
gitLogReader.listRepositoryContentsAtHEAD(repository).keySet()) {
58-
log.info("Files at HEAD: {}", file);
59-
}
54+
gitLogReader = new GitLogReader(new File(repositoryPath));
55+
// repository = gitLogReader.gitRepository(new File(repositoryPath));
56+
// for (String file :
57+
// gitLogReader.listRepositoryContentsAtHEAD(repository).keySet()) {
58+
// log.info("Files at HEAD: {}", file);
59+
// }
6060
} catch (IOException e) {
6161
log.error("Failure to access Git repository", e);
6262
}
6363

64-
changePronenessRanker = new ChangePronenessRanker(repository, gitLogReader);
64+
changePronenessRanker = new ChangePronenessRanker(gitLogReader);
65+
}
66+
67+
@Override
68+
public void close() throws Exception {
69+
gitLogReader.close();
6570
}
6671

6772
public List<RankedCycle> runCycleAnalysis(String outputDirectoryPath, boolean renderImages) {
@@ -148,7 +153,7 @@ public List<RankedCycle> runCycleAnalysis(String outputDirectoryPath, boolean re
148153

149154
private boolean isDuplicateSubGraph(AsSubgraph<String, DefaultWeightedEdge> subGraph, String vertex) {
150155
if (!renderedSubGraphs.isEmpty()) {
151-
for (AsSubgraph<String, DefaultWeightedEdge> renderedSubGraph : renderedSubGraphs.values()) {
156+
for (AsSubgraph<String, DefaultWeightedEdge> renderedSubGraph : renderedSubGraphs.values()) {
152157
if (renderedSubGraph.vertexSet().size() == subGraph.vertexSet().size()
153158
&& renderedSubGraph.edgeSet().size()
154159
== subGraph.edgeSet().size()
@@ -176,7 +181,7 @@ public void runPmdAnalysis() throws IOException {
176181
log.info("files to be scanned: " + Paths.get(repositoryPath));
177182

178183
try (Stream<Path> files = Files.walk(Paths.get(repositoryPath))) {
179-
files.forEach(file -> pmd.files().addFile(file));
184+
files.filter(Files::isRegularFile).forEach(file -> pmd.files().addFile(file));
180185
}
181186

182187
report = pmd.performAnalysisAndCollectReport();
@@ -231,25 +236,23 @@ private List<GodClass> getGodClasses() {
231236
}
232237

233238
<T extends Disharmony> List<ScmLogInfo> getRankedChangeProneness(List<T> disharmonies) {
234-
List<ScmLogInfo> scmLogInfos = new ArrayList<>();
235239
log.info("Calculating Change Proneness");
236-
for (Disharmony disharmony : disharmonies) {
237-
String path = disharmony.getFileName();
238-
ScmLogInfo scmLogInfo = null;
239-
try {
240-
scmLogInfo = gitLogReader.fileLog(repository, path);
241-
log.info("Successfully fetched scmLogInfo for {}", scmLogInfo.getPath());
242-
} catch (GitAPIException | IOException e) {
243-
log.error("Error reading Git repository contents.", e);
244-
} catch (NullPointerException e) {
245-
log.error("Encountered nested class in a class containing a violation. Class: {}", path);
246-
}
247240

248-
if (null != scmLogInfo) {
249-
log.info("adding {}", scmLogInfo.getPath());
250-
scmLogInfos.add(scmLogInfo);
251-
}
252-
}
241+
List<ScmLogInfo> scmLogInfos = disharmonies.parallelStream()
242+
.map(disharmony -> {
243+
String path = disharmony.getFileName();
244+
ScmLogInfo scmLogInfo = null;
245+
try {
246+
scmLogInfo = gitLogReader.fileLog(path);
247+
log.info("Successfully fetched scmLogInfo for {}", scmLogInfo.getPath());
248+
} catch (GitAPIException | IOException e) {
249+
log.error("Error reading Git repository contents.", e);
250+
} catch (NullPointerException e) {
251+
log.error("Encountered nested class in a class containing a violation. Class: {}", path);
252+
}
253+
return scmLogInfo;
254+
})
255+
.collect(Collectors.toList());
253256

254257
changePronenessRanker.rankChangeProneness(scmLogInfos);
255258
return scmLogInfos;

0 commit comments

Comments
 (0)