Skip to content

Commit 8dff8ab

Browse files
committed
Merge remote-tracking branch 'remotes/origin/git-forensics'
# Conflicts: # pom.xml # src/main/java/io/jenkins/plugins/git/forensics/miner/FilesCollector.java # src/main/java/io/jenkins/plugins/git/forensics/miner/GitMinerFactory.java # src/main/java/io/jenkins/plugins/git/forensics/miner/GitRepositoryMiner.java # src/test/java/io/jenkins/plugins/git/forensics/miner/GitMinerITest.java
2 parents b5456e3 + 2b119ab commit 8dff8ab

File tree

10 files changed

+586
-23
lines changed

10 files changed

+586
-23
lines changed

pom.xml

Lines changed: 4 additions & 12 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.1-SNAPSHOT</version>
15+
<version>0.1-alpha-6-SNAPSHOT</version>
1616
<description>Jenkins plug-in that mines and analyzes data from a Git repository.</description>
1717

1818
<properties>
@@ -29,7 +29,7 @@
2929
<eclipse-collections.version>9.2.0</eclipse-collections.version>
3030

3131
<!-- Jenkins Plugin Dependencies Versions -->
32-
<forensics-api-plugin.version>0.1.1</forensics-api-plugin.version>
32+
<forensics-api-plugin.version>0.2-alpha-1-SNAPSHOT</forensics-api-plugin.version>
3333
<git-plugin.version>3.9.1</git-plugin.version>
3434

3535
<!-- Test Library Dependencies Versions -->
@@ -62,14 +62,6 @@
6262

6363
</properties>
6464

65-
<developers>
66-
<developer>
67-
<name>Ullrich Hafner</name>
68-
<id>uhafner</id>
69-
<email>ullrich.hafner@gmail.com</email>
70-
</developer>
71-
</developers>
72-
7365
<licenses>
7466
<license>
7567
<name>MIT license</name>
@@ -561,14 +553,14 @@
561553
<repositories>
562554
<repository>
563555
<id>repo.jenkins-ci.org</id>
564-
<url>https://repo.jenkins-ci.org/public/</url>
556+
<url>http://repo.jenkins-ci.org/public/</url>
565557
</repository>
566558
</repositories>
567559

568560
<pluginRepositories>
569561
<pluginRepository>
570562
<id>repo.jenkins-ci.org</id>
571-
<url>https://repo.jenkins-ci.org/public/</url>
563+
<url>http://repo.jenkins-ci.org/public/</url>
572564
</pluginRepository>
573565
</pluginRepositories>
574566

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,13 @@
1818
import io.jenkins.plugins.forensics.util.FilteredLog;
1919

2020
/**
21-
* A {@link BlamerFactory} gor Git. Handles Git repositories that do not have option ShallowClone set.
21+
* A {@link BlamerFactory} for Git. Handles Git repositories that do not have option ShallowClone set.
2222
*
2323
* @author Ullrich Hafner
2424
*/
2525
@Extension
2626
public class GitBlamerFactory extends BlamerFactory {
27-
static final String INFO_BLAMER_CREATED = "Creating GitBlamer to obtain SCM blame information for affected files";
27+
static final String INFO_BLAMER_CREATED = "Invoking GitMiner to obtain SCM blame information for affected files";
2828
static final String INFO_SHALLOW_CLONE = "Skipping issues blame since Git has been configured with shallow clone";
2929
static final String ERROR_BLAMER = "Exception while creating a GitClient instance";
3030

@@ -34,7 +34,7 @@ public Optional<Blamer> createBlamer(final SCM scm, final Run<?, ?> build,
3434
if (scm instanceof GitSCM) {
3535
return createGitBlamer((GitSCM) scm, build, workspace, listener, logger);
3636
}
37-
logger.logInfo("Skipping blame since SCM '%s' is not of type GitSCM", scm.getType());
37+
logger.logInfo("Skipping blamer since SCM '%s' is not of type GitSCM", scm.getType());
3838
return Optional.empty();
3939
}
4040

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package io.jenkins.plugins.git.forensics.miner;
2+
3+
import java.io.IOException;
4+
import java.util.Collections;
5+
import java.util.HashSet;
6+
import java.util.Set;
7+
8+
import org.eclipse.jgit.lib.ObjectId;
9+
import org.eclipse.jgit.lib.Repository;
10+
import org.eclipse.jgit.revwalk.RevTree;
11+
import org.eclipse.jgit.revwalk.RevWalk;
12+
import org.eclipse.jgit.treewalk.TreeWalk;
13+
14+
/**
15+
* Collects all files of a Git repository.
16+
*
17+
* @author Ullrich Hafner
18+
*/
19+
public class FilesCollector {
20+
private final Repository repository;
21+
22+
FilesCollector(final Repository repository) {
23+
this.repository = repository;
24+
}
25+
26+
Set<String> findAllFor(final ObjectId commitId) {
27+
try {
28+
RevWalk revWalk = new RevWalk(repository);
29+
RevTree tree = revWalk.parseCommit(commitId).getTree();
30+
TreeWalk treeWalk = new TreeWalk(repository);
31+
treeWalk.setRecursive(true);
32+
treeWalk.addTree(tree);
33+
Set<String> files = new HashSet<>();
34+
while (treeWalk.next()) {
35+
files.add(treeWalk.getPathString());
36+
}
37+
return files;
38+
}
39+
catch (IOException exception) { // FIXME: add logging
40+
return Collections.emptySet();
41+
}
42+
}
43+
}
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
package io.jenkins.plugins.git.forensics.miner;
2+
3+
import java.io.IOException;
4+
import java.util.Optional;
5+
6+
import org.jenkinsci.plugins.gitclient.GitClient;
7+
import hudson.EnvVars;
8+
import hudson.Extension;
9+
import hudson.FilePath;
10+
import hudson.model.Run;
11+
import hudson.model.TaskListener;
12+
import hudson.plugins.git.GitSCM;
13+
import hudson.plugins.git.extensions.impl.CloneOption;
14+
import hudson.scm.SCM;
15+
16+
import io.jenkins.plugins.forensics.miner.MinerFactory;
17+
import io.jenkins.plugins.forensics.miner.RepositoryMiner;
18+
import io.jenkins.plugins.forensics.util.FilteredLog;
19+
20+
/**
21+
* A {@link MinerFactory} for Git. Handles Git repositories that do not have option ShallowClone set.
22+
*
23+
* @author Ullrich Hafner
24+
*/
25+
@Extension
26+
public class GitMinerFactory extends MinerFactory {
27+
static final String INFO_BLAMER_CREATED = "Invoking GitMiner to creates statistics for all available repository files.";
28+
static final String INFO_SHALLOW_CLONE = "Skipping issues blame since Git has been configured with shallow clone";
29+
static final String ERROR_BLAMER = "Exception while creating a GitClient instance";
30+
31+
@Override
32+
public Optional<RepositoryMiner> createMiner(final SCM scm, final Run<?, ?> build, final FilePath workspace,
33+
final TaskListener listener, final FilteredLog logger) {
34+
if (scm instanceof GitSCM) {
35+
return createMiner((GitSCM) scm, build, workspace, listener, logger);
36+
}
37+
logger.logInfo("Skipping miner since SCM '%s' is not of type GitSCM", scm.getType());
38+
return Optional.empty();
39+
}
40+
41+
private Optional<RepositoryMiner> createMiner(final GitSCM git, final Run<?, ?> build,
42+
final FilePath workspace, final TaskListener listener, final FilteredLog logger) {
43+
if (isShallow(git)) {
44+
logger.logInfo(INFO_SHALLOW_CLONE);
45+
46+
return Optional.empty();
47+
}
48+
49+
try {
50+
EnvVars environment = build.getEnvironment(listener);
51+
GitClient gitClient = git.createClient(listener, environment, build, workspace);
52+
53+
logger.logInfo(INFO_BLAMER_CREATED);
54+
55+
return Optional.of(new GitRepositoryMiner(gitClient));
56+
}
57+
catch (IOException | InterruptedException e) {
58+
logger.logException(e, ERROR_BLAMER);
59+
60+
return Optional.empty();
61+
}
62+
}
63+
64+
private boolean isShallow(final GitSCM git) {
65+
CloneOption option = git.getExtensions().get(CloneOption.class);
66+
if (option != null) {
67+
return option.isShallow();
68+
}
69+
return false;
70+
}
71+
72+
}
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
package io.jenkins.plugins.git.forensics.miner;
2+
3+
import java.io.IOException;
4+
import java.util.List;
5+
import java.util.Set;
6+
import java.util.stream.Collectors;
7+
8+
import org.apache.commons.lang3.StringUtils;
9+
import org.eclipse.jgit.api.Git;
10+
import org.eclipse.jgit.api.errors.GitAPIException;
11+
import org.eclipse.jgit.lib.Constants;
12+
import org.eclipse.jgit.lib.ObjectId;
13+
import org.eclipse.jgit.lib.PersonIdent;
14+
import org.eclipse.jgit.lib.Repository;
15+
import org.eclipse.jgit.revwalk.RevCommit;
16+
17+
import edu.umd.cs.findbugs.annotations.Nullable;
18+
19+
import org.jenkinsci.plugins.gitclient.GitClient;
20+
import org.jenkinsci.plugins.gitclient.RepositoryCallback;
21+
import hudson.remoting.VirtualChannel;
22+
23+
import io.jenkins.plugins.forensics.miner.FileStatistics;
24+
import io.jenkins.plugins.forensics.miner.RepositoryMiner;
25+
import io.jenkins.plugins.forensics.miner.RepositoryStatistics;
26+
27+
/**
28+
* Mines a Git repository and creates statistics for all available files.
29+
*
30+
* @author Ullrich Hafner
31+
* @see io.jenkins.plugins.forensics.miner.FileStatistics
32+
* @see io.jenkins.plugins.git.forensics.miner.FilesCollector
33+
*/
34+
public class GitRepositoryMiner extends RepositoryMiner {
35+
private static final long serialVersionUID = 1157958118716013983L;
36+
37+
private final GitClient gitClient;
38+
39+
GitRepositoryMiner(final GitClient gitClient) {
40+
this.gitClient = gitClient;
41+
}
42+
43+
@Override
44+
public RepositoryStatistics mine() throws InterruptedException {
45+
try {
46+
return gitClient.withRepository(new RepositoryStatisticsCallback());
47+
}
48+
catch (IOException exception) {
49+
RepositoryStatistics statistics = new RepositoryStatistics();
50+
statistics.logException(exception, "Exception occurred while mining the Git repository using GitClient");
51+
return statistics;
52+
}
53+
}
54+
55+
private static class RepositoryStatisticsCallback implements RepositoryCallback<RepositoryStatistics> {
56+
private static final long serialVersionUID = 7667073858514128136L;
57+
58+
@Override
59+
public RepositoryStatistics invoke(final Repository repository, final VirtualChannel channel) {
60+
try {
61+
ObjectId head = repository.resolve(Constants.HEAD);
62+
Set<String> files = new FilesCollector(repository).findAllFor(head);
63+
return analyze(repository, files);
64+
}
65+
catch (IOException exception) {
66+
RepositoryStatistics statistics = new RepositoryStatistics();
67+
statistics.logException(exception, "Can't obtain HEAD of repository.");
68+
return statistics;
69+
}
70+
}
71+
72+
RepositoryStatistics analyze(final Repository repository, final Set<String> files) {
73+
RepositoryStatistics statistics = new RepositoryStatistics();
74+
statistics.logInfo("Invoking Git miner to create creates statistics for all available files");
75+
76+
List<FileStatistics> fileStatistics = files.stream()
77+
.map(file -> analyzeHistory(repository, file, statistics))
78+
.collect(Collectors.toList());
79+
statistics.addAll(fileStatistics);
80+
81+
return statistics;
82+
}
83+
84+
private FileStatistics analyzeHistory(final Repository repository, final String fileName,
85+
final RepositoryStatistics statistics) {
86+
FileStatistics fileStatistics = new FileStatistics(fileName);
87+
88+
try {
89+
Git git = new Git(repository);
90+
Iterable<RevCommit> commits = git.log().addPath(fileName).call();
91+
commits.forEach(c -> fileStatistics.inspectCommit(c.getCommitTime(), getAuthor(c)));
92+
return fileStatistics;
93+
}
94+
catch (GitAPIException exception) {
95+
statistics.logException(exception, "Can't analyze history of file %s", fileName);
96+
}
97+
return fileStatistics;
98+
}
99+
100+
@Nullable
101+
private String getAuthor(final RevCommit commit) {
102+
PersonIdent author = commit.getAuthorIdent();
103+
if (author != null) {
104+
return StringUtils.defaultString(author.getEmailAddress(), author.getName());
105+
}
106+
PersonIdent committer = commit.getCommitterIdent();
107+
if (committer != null) {
108+
return StringUtils.defaultString(committer.getEmailAddress(), committer.getName());
109+
}
110+
return StringUtils.EMPTY;
111+
}
112+
}
113+
}

src/test/java/io/jenkins/plugins/git/forensics/blame/GitBlamerFactoryITest.java

Lines changed: 0 additions & 7 deletions
This file was deleted.

src/test/java/io/jenkins/plugins/git/forensics/blame/GitBlamerFactoryTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ void shouldSkipIfScmIsNotGit() {
4141
assertThat(factory.createBlamer(new NullSCM(), null, null, NULL_LISTENER, logger)).isEmpty();
4242

4343
assertThat(logger).hasNoErrorMessages();
44-
assertThat(logger).hasInfoMessages("Skipping blame since SCM 'hudson.scm.NullSCM' is not of type GitSCM");
44+
assertThat(logger).hasInfoMessages("Skipping blamer since SCM 'hudson.scm.NullSCM' is not of type GitSCM");
4545
}
4646

4747
@Test

0 commit comments

Comments
 (0)