Skip to content

Commit c2348c0

Browse files
authored
Merge pull request #1 from jenkinsci/git-forensics
Add Git repository miner
2 parents c2bba32 + 5db9575 commit c2348c0

File tree

11 files changed

+160
-165
lines changed

11 files changed

+160
-165
lines changed

.dependabot/config.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
version: 1
2+
3+
update_configs:
4+
- package_manager: "java:maven"
5+
directory: "/"
6+
update_schedule: "daily"

.github/release-drafter.yml

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
_extends: .github
2+
tag-template: git-forensics-$NEXT_MINOR_VERSION
3+
name-template: Git Forensics $NEXT_PATCH_VERSION 🎁
4+
version-template: $MAJOR.$MINOR.$PATCH
5+
6+
template: |
7+
$CHANGES
8+
9+
categories:
10+
- title: 🚨 Removed
11+
label: removed
12+
- title: ⚠️ Deprecated
13+
label: deprecated
14+
- title: 🚀 New features and improvements
15+
labels:
16+
- enhancement
17+
- feature
18+
- title: 🐛 Bug Fixes
19+
labels:
20+
- bug
21+
- fix
22+
- bugfix
23+
- regression
24+
- title: 📖 Documentation updates
25+
label: documentation
26+
- title: 📦 Dependency updates
27+
label: dependencies
28+
- title: 🔧 Internal changes
29+
label: internal
30+
- title: 🚦 Tests
31+
labels:
32+
- test
33+
- tests

CHANGELOG.md

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
1-
# Changelog
2-
All notable changes to this project will be documented in this file.
1+
# Changelog
32

4-
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
5-
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
3+
All notable changes of this project will be automatically logged by release drafter in
4+
[GitHub releases](https://github.com/jenkinsci/git-forensics-plugin/releases).
65

7-
## [Unreleased](https://github.com/uhafner/git-forensics-plugin/compare/9f25c5b9d19dccf07e97bfe03f3d565126288633...master)
6+
This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

CONTRIBUTING.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,9 @@ Start reading the code and you'll get the hang of it. A complete description of
2222
coding guidelines is part of a [separate GitHub repository](https://github.com/uhafner/codingstyle), which
2323
is only available in German.
2424

25-
For [IntelliJ IDEA](https://www.jetbrains.com/idea/) or [Eclipse](https://www.eclipse.org/) users:
25+
For [IntelliJ IDEA](https://www.jetbrains.com/idea/) users:
2626
the coding style is stored in configuration files within this project. If you import this project into
27-
IntelliJ or Eclipse this style will used automatically.
27+
IntelliJ this style will used automatically.
2828

2929
Moreover (since this project is about static code analysis :wink:) a configuration for the following static code
3030
analysis tools is defined in the POM and the `etc` and `.idea` folders:

Jenkinsfile

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
node ('linux') {
2-
timeout(60) {
3-
stage ('Checkout') {
1+
node ('maven') {
2+
timeout(200) {
3+
stage ('Linux Checkout') {
44
checkout scm
55
}
66

7-
stage ('Build') {
7+
stage ('Linux Build') {
88
String jdk = '8'
99
String jdkTool = "jdk${jdk}"
1010
List<String> env = [
@@ -32,28 +32,30 @@ node ('linux') {
3232
sh command
3333
}
3434

35+
archiveArtifacts artifacts: '**/target/*.jar', fingerprint: true
36+
3537
junit testResults: '**/target/*-reports/TEST-*.xml'
3638

37-
recordIssues tool: mavenConsole(), referenceJobName: 'Plugins/git-forensics/master'
38-
recordIssues tools: [java(), javaDoc()], sourceCodeEncoding: 'UTF-8', referenceJobName: 'Plugins/git-forensics/master'
39-
recordIssues tool: checkStyle(pattern: 'target/checkstyle-result.xml'), sourceCodeEncoding: 'UTF-8', referenceJobName: 'Plugins/git-forensics/master'
40-
recordIssues tool: errorProne(), sourceCodeEncoding: 'UTF-8', referenceJobName: 'Plugins/git-forensics/master'
41-
recordIssues tool: cpd(pattern: 'target/cpd.xml'), sourceCodeEncoding: 'UTF-8', referenceJobName: 'Plugins/git-forensics/master'
42-
recordIssues tool: pmdParser(pattern: 'target/pmd.xml'), sourceCodeEncoding: 'UTF-8', referenceJobName: 'Plugins/git-forensics/master'
43-
recordIssues tool: spotBugs(pattern: 'target/spotbugsXml.xml'), sourceCodeEncoding: 'UTF-8', referenceJobName: 'Plugins/git-forensics/master'
44-
recordIssues tool: taskScanner(includePattern:'**/*.java', excludePattern:'target/**/*', highTags:'FIXME', normalTags:'TODO'), sourceCodeEncoding: 'UTF-8', referenceJobName: 'Plugins/git-forensics/master'
39+
recordIssues enabledForFailure: true, tool: mavenConsole(), referenceJobName: 'Plugins/git-forensics/master'
40+
recordIssues enabledForFailure: true, tools: [java(), javaDoc()], sourceCodeEncoding: 'UTF-8', referenceJobName: 'Plugins/git-forensics/master'
41+
recordIssues enabledForFailure: true, tool: checkStyle(pattern: 'target/checkstyle-result.xml'), sourceCodeEncoding: 'UTF-8', referenceJobName: 'Plugins/git-forensics/master'
42+
recordIssues enabledForFailure: true, tool: errorProne(), sourceCodeEncoding: 'UTF-8', referenceJobName: 'Plugins/git-forensics/master'
43+
recordIssues enabledForFailure: true, tool: cpd(pattern: 'target/cpd.xml'), sourceCodeEncoding: 'UTF-8', referenceJobName: 'Plugins/git-forensics/master'
44+
recordIssues enabledForFailure: true, tool: pmdParser(pattern: 'target/pmd.xml'), sourceCodeEncoding: 'UTF-8', referenceJobName: 'Plugins/git-forensics/master'
45+
recordIssues enabledForFailure: true, tool: spotBugs(pattern: 'target/spotbugsXml.xml'), sourceCodeEncoding: 'UTF-8', referenceJobName: 'Plugins/git-forensics/master'
46+
recordIssues enabledForFailure: true, tool: taskScanner(includePattern:'**/*.java', excludePattern:'target/**/*', highTags:'FIXME', normalTags:'TODO'), sourceCodeEncoding: 'UTF-8', referenceJobName: 'Plugins/git-forensics/master'
4547
jacoco()
4648
}
4749
}
4850
}
4951

5052
node ('windows') {
51-
timeout(60) {
52-
stage ('Checkout') {
53+
timeout(200) {
54+
stage ('Windows Checkout') {
5355
checkout scm
5456
}
5557

56-
stage ('Build') {
58+
stage ('Windows Build') {
5759
String jdk = '8'
5860
String jdkTool = "jdk${jdk}"
5961
List<String> env = [

pom.xml

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
<groupId>io.jenkins.plugins</groupId>
1313
<packaging>hpi</packaging>
1414
<name>Git Forensics Plugin</name>
15-
<version>0.1-alpha-6-SNAPSHOT</version>
15+
<version>0.2.0-SNAPSHOT</version>
1616
<description>Jenkins plug-in that mines and analyzes data from a Git repository.</description>
1717

1818
<properties>
@@ -21,7 +21,7 @@
2121
<java.level>8</java.level>
2222
<jenkins-test-harness.version>2.49</jenkins-test-harness.version>
2323
<spotbugs.failOnError>false</spotbugs.failOnError>
24-
<slf4j-nop.version>1.7.26</slf4j-nop.version>
24+
<slf4j.version>1.7.26</slf4j.version>
2525

2626
<!-- Library Dependencies Versions -->
2727
<error-prone.version>2.3.3</error-prone.version>
@@ -30,7 +30,7 @@
3030
<eclipse-collections.version>9.2.0</eclipse-collections.version>
3131

3232
<!-- Jenkins Plugin Dependencies Versions -->
33-
<forensics-api-plugin.version>0.2-alpha-1-SNAPSHOT</forensics-api-plugin.version>
33+
<forensics-api-plugin.version>0.2.1</forensics-api-plugin.version>
3434
<git-plugin.version>3.9.1</git-plugin.version>
3535

3636
<!-- Test Library Dependencies Versions -->
@@ -102,6 +102,11 @@
102102
<artifactId>scm-api</artifactId>
103103
<version>2.2.6</version>
104104
</dependency>
105+
<dependency>
106+
<groupId>org.slf4j</groupId>
107+
<artifactId>slf4j-api</artifactId>
108+
<version>${slf4j.version}</version>
109+
</dependency>
105110
</dependencies>
106111
</dependencyManagement>
107112

@@ -111,7 +116,7 @@
111116
<dependency>
112117
<groupId>org.slf4j</groupId>
113118
<artifactId>slf4j-nop</artifactId>
114-
<version>${slf4j-nop.version}</version>
119+
<version>${slf4j.version}</version>
115120
</dependency>
116121
<dependency>
117122
<groupId>com.github.spotbugs</groupId>

src/main/java/io/jenkins/plugins/git/forensics/blame/GitBlamer.java

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,10 @@ public Blames blame(final FileLocations locations) {
8888
String workspacePath = getWorkspacePath();
8989
blames.logInfo("Job workspace = '%s'", workspacePath);
9090

91-
return git.withRepository(new BlameCallback(workspacePath, locations, blames, headCommit));
91+
long nano = System.nanoTime();
92+
Blames filledBlames = git.withRepository(new BlameCallback(workspacePath, locations, blames, headCommit));
93+
filledBlames.logInfo("Blaming of authors took %d seconds", 1 + (System.nanoTime() - nano) / 1_000_000_000L);
94+
return filledBlames;
9295
}
9396
catch (IOException exception) {
9497
blames.logException(exception, BLAME_ERROR);
@@ -131,23 +134,28 @@ static class BlameCallback implements RepositoryCallback<Blames> {
131134
}
132135

133136
@Override
134-
public Blames invoke(final Repository repo, final VirtualChannel channel) throws InterruptedException {
135-
BlameRunner blameRunner = new BlameRunner(repo, headCommit);
137+
public Blames invoke(final Repository repository, final VirtualChannel channel) throws InterruptedException {
138+
try {
139+
BlameRunner blameRunner = new BlameRunner(repository, headCommit);
136140

137-
for (String file : locations.getRelativePaths()) {
138-
run(file, blameRunner);
141+
for (String file : locations.getRelativePaths()) {
142+
run(file, blameRunner);
139143

140-
if (Thread.interrupted()) { // Cancel request by user
141-
String message = "Blaming has been interrupted while computing blame information";
142-
blames.logInfo(message);
144+
if (Thread.interrupted()) { // Cancel request by user
145+
String message = "Blaming has been interrupted while computing blame information";
146+
blames.logInfo(message);
143147

144-
throw new InterruptedException(message);
148+
throw new InterruptedException(message);
149+
}
145150
}
146-
}
147151

148-
blames.logInfo("-> blamed authors of issues in %d files", blames.size());
152+
blames.logInfo("-> blamed authors of issues in %d files", blames.size());
149153

150-
return blames;
154+
return blames;
155+
}
156+
finally {
157+
repository.close();
158+
}
151159
}
152160

153161
/**

src/main/java/io/jenkins/plugins/git/forensics/miner/GitRepositoryMiner.java

Lines changed: 49 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,29 @@
11
package io.jenkins.plugins.git.forensics.miner;
22

33
import java.io.IOException;
4+
import java.nio.file.LinkOption;
5+
import java.nio.file.Paths;
6+
import java.util.Collection;
47
import java.util.List;
58
import java.util.Set;
69
import java.util.stream.Collectors;
710

811
import org.apache.commons.lang3.StringUtils;
912
import org.eclipse.jgit.api.Git;
1013
import org.eclipse.jgit.api.errors.GitAPIException;
14+
import org.eclipse.jgit.dircache.InvalidPathException;
1115
import org.eclipse.jgit.lib.Constants;
1216
import org.eclipse.jgit.lib.ObjectId;
1317
import org.eclipse.jgit.lib.PersonIdent;
1418
import org.eclipse.jgit.lib.Repository;
1519
import org.eclipse.jgit.revwalk.RevCommit;
1620

1721
import edu.umd.cs.findbugs.annotations.Nullable;
22+
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
1823

1924
import org.jenkinsci.plugins.gitclient.GitClient;
2025
import org.jenkinsci.plugins.gitclient.RepositoryCallback;
26+
import hudson.FilePath;
2127
import hudson.remoting.VirtualChannel;
2228

2329
import io.jenkins.plugins.forensics.miner.FileStatistics;
@@ -31,19 +37,31 @@
3137
* @see io.jenkins.plugins.forensics.miner.FileStatistics
3238
* @see io.jenkins.plugins.git.forensics.miner.FilesCollector
3339
*/
40+
@SuppressFBWarnings(value = "SE", justification = "GitClient implementation is Serializable")
3441
public class GitRepositoryMiner extends RepositoryMiner {
3542
private static final long serialVersionUID = 1157958118716013983L;
3643

3744
private final GitClient gitClient;
45+
private final FilePath workspace;
3846

3947
GitRepositoryMiner(final GitClient gitClient) {
48+
super();
49+
4050
this.gitClient = gitClient;
51+
workspace = gitClient.getWorkTree();
4152
}
4253

4354
@Override
44-
public RepositoryStatistics mine() throws InterruptedException {
55+
public RepositoryStatistics mine(final Collection<String> relativeFileNames) throws InterruptedException {
4556
try {
46-
return gitClient.withRepository(new RepositoryStatisticsCallback());
57+
String workspacePath = getWorkspacePath();
58+
59+
long nano = System.nanoTime();
60+
RepositoryStatistics statistics = gitClient.withRepository(
61+
new RepositoryStatisticsCallback(workspacePath, relativeFileNames));
62+
statistics.logInfo("Mining of the Git repository took %d seconds",
63+
1 + (System.nanoTime() - nano) / 1_000_000_000L);
64+
return statistics;
4765
}
4866
catch (IOException exception) {
4967
RepositoryStatistics statistics = new RepositoryStatistics();
@@ -52,24 +70,46 @@ public RepositoryStatistics mine() throws InterruptedException {
5270
}
5371
}
5472

73+
private String getWorkspacePath() {
74+
try {
75+
return Paths.get(workspace.getRemote()).toAbsolutePath().normalize().toRealPath(LinkOption.NOFOLLOW_LINKS).toString();
76+
}
77+
catch (IOException | InvalidPathException exception) {
78+
return workspace.getRemote();
79+
}
80+
}
81+
5582
private static class RepositoryStatisticsCallback implements RepositoryCallback<RepositoryStatistics> {
5683
private static final long serialVersionUID = 7667073858514128136L;
84+
private final String workspacePath;
85+
private final Collection<String> paths;
86+
87+
RepositoryStatisticsCallback(final String workspacePath, final Collection<String> paths) {
88+
this.workspacePath = workspacePath;
89+
this.paths = paths;
90+
}
5791

5892
@Override
5993
public RepositoryStatistics invoke(final Repository repository, final VirtualChannel channel) {
6094
try {
61-
ObjectId head = repository.resolve(Constants.HEAD);
62-
Set<String> files = new FilesCollector(repository).findAllFor(head);
63-
return analyze(repository, files);
95+
if (paths.isEmpty()) { // scan whole repository
96+
ObjectId head = repository.resolve(Constants.HEAD);
97+
Set<String> files = new FilesCollector(repository).findAllFor(head);
98+
return analyze(repository, files);
99+
}
100+
return analyze(repository, paths);
64101
}
65102
catch (IOException exception) {
66103
RepositoryStatistics statistics = new RepositoryStatistics();
67104
statistics.logException(exception, "Can't obtain HEAD of repository.");
68105
return statistics;
69106
}
107+
finally {
108+
repository.close();
109+
}
70110
}
71111

72-
RepositoryStatistics analyze(final Repository repository, final Set<String> files) {
112+
RepositoryStatistics analyze(final Repository repository, final Collection<String> files) {
73113
RepositoryStatistics statistics = new RepositoryStatistics();
74114
statistics.logInfo("Invoking Git miner to create creates statistics for all available files");
75115

@@ -78,12 +118,14 @@ RepositoryStatistics analyze(final Repository repository, final Set<String> file
78118
.collect(Collectors.toList());
79119
statistics.addAll(fileStatistics);
80120

121+
statistics.logInfo("-> created statistics for %d files", statistics.size());
122+
81123
return statistics;
82124
}
83125

84126
private FileStatistics analyzeHistory(final Repository repository, final String fileName,
85127
final RepositoryStatistics statistics) {
86-
FileStatistics fileStatistics = new FileStatistics(fileName);
128+
FileStatistics fileStatistics = new FileStatistics(workspacePath + "/" + fileName);
87129

88130
try {
89131
Git git = new Git(repository);

0 commit comments

Comments
 (0)