Skip to content

Commit

Permalink
Info on committer activity (#3654)
Browse files Browse the repository at this point in the history
  • Loading branch information
jkschneider authored Nov 1, 2023
1 parent e343554 commit a5f1772
Show file tree
Hide file tree
Showing 12 changed files with 310 additions and 19 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,11 @@
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.lib.*;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.transport.RemoteConfig;
import org.eclipse.jgit.transport.URIish;
import org.eclipse.jgit.treewalk.WorkingTreeOptions;
import org.openrewrite.Incubating;
import org.openrewrite.internal.lang.Nullable;
import org.openrewrite.marker.ci.BuildEnvironment;
import org.openrewrite.marker.ci.IncompleteGitConfigException;
Expand All @@ -35,10 +37,11 @@
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.file.Path;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.time.LocalDate;
import java.time.ZonedDateTime;
import java.util.*;

import static java.util.Collections.emptyList;
import static org.openrewrite.Tree.randomId;

@Value
Expand All @@ -52,6 +55,7 @@ public class GitProvenance implements Marker {
@Nullable
String branch;

@Nullable
String change;

@Nullable
Expand All @@ -60,6 +64,10 @@ public class GitProvenance implements Marker {
@Nullable
EOL eol;

@Nullable
@Incubating(since = "8.9.0")
List<Committer> committers;

/**
* Extract the organization name, including sub-organizations for git hosting services which support such a concept,
* from the origin URL. Needs to be supplied with the
Expand Down Expand Up @@ -140,8 +148,8 @@ public static GitProvenance fromProjectDirectory(Path projectDir) {
* determined from a {@link BuildEnvironment} marker if possible.
* @return A marker containing git provenance information.
*/
public static @Nullable GitProvenance fromProjectDirectory(Path projectDir, @Nullable BuildEnvironment environment) {

@Nullable
public static GitProvenance fromProjectDirectory(Path projectDir, @Nullable BuildEnvironment environment) {
if (environment != null) {
if (environment instanceof JenkinsBuildEnvironment) {
JenkinsBuildEnvironment jenkinsBuildEnvironment = (JenkinsBuildEnvironment) environment;
Expand Down Expand Up @@ -182,6 +190,7 @@ private static void printRequireGitDirOrWorkTreeException(Exception e) {
}
}

@Nullable
private static GitProvenance fromGitConfig(Path projectDir) {
String branch = null;
try (Repository repository = new RepositoryBuilder().findGitDir(projectDir.toFile()).build()) {
Expand All @@ -198,17 +207,18 @@ private static GitProvenance fromGitConfig(Path projectDir) {
}
}

private static GitProvenance fromGitConfig(Repository repository, @Nullable String branch, String changeset) {
private static GitProvenance fromGitConfig(Repository repository, @Nullable String branch, @Nullable String changeset) {
if (branch == null) {
branch = resolveBranchFromGitConfig(repository);
}
return new GitProvenance(randomId(), getOrigin(repository), branch, changeset,
getAutocrlf(repository), getEOF(repository));
getAutocrlf(repository), getEOF(repository),
getCommitters(repository));
}

@Nullable
static String resolveBranchFromGitConfig(Repository repository) {
String branch = null;
String branch;
try {
try (Git git = Git.open(repository.getDirectory())) {
ObjectId commit = repository.resolve(Constants.HEAD);
Expand Down Expand Up @@ -256,7 +266,7 @@ private static String localBranchName(Repository repository, @Nullable String re
List<RemoteConfig> remotes = git.remoteList().call();
for (RemoteConfig remote : remotes) {
if (remoteBranch.startsWith(remote.getName()) &&
(branch == null || branch.length() > remoteBranch.length() - remote.getName().length() - 1)) {
(branch == null || branch.length() > remoteBranch.length() - remote.getName().length() - 1)) {
branch = remoteBranch.substring(remote.getName().length() + 1); // +1 for the forward slash
}
}
Expand Down Expand Up @@ -308,6 +318,29 @@ private static EOL getEOF(Repository repository) {
}
}

private static List<Committer> getCommitters(Repository repository) {
try (Git git = Git.open(repository.getDirectory())) {
ObjectId head = repository.readOrigHead();
if(head == null) {
return emptyList();
}

Map<String, Committer> committers = new TreeMap<>();
for (RevCommit commit : git.log().add(head).call()) {
PersonIdent who = commit.getAuthorIdent();
Committer committer = committers.computeIfAbsent(who.getEmailAddress(),
email -> new Committer(who.getName(), email, new TreeMap<>()));
committer.getCommitsByDay().compute(ZonedDateTime
.ofInstant(who.getWhen().toInstant(), who.getTimeZone().toZoneId())
.toLocalDate(),
(day, count) -> count == null ? 1 : count + 1);
}
return new ArrayList<>(committers.values());
} catch (IOException | GitAPIException e) {
return emptyList();
}
}

@Nullable
private static String getChangeset(Repository repository) throws IOException {
ObjectId head = repository.resolve(Constants.HEAD);
Expand Down Expand Up @@ -340,4 +373,12 @@ public enum EOL {
LF,
Native
}

@Value
@With
public static class Committer {
String name;
String email;
NavigableMap<LocalDate, Integer> commitsByDay;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,11 @@
import org.openrewrite.internal.StringUtils;
import org.openrewrite.marker.GitProvenance;

import java.util.Collections;
import java.util.UUID;
import java.util.function.UnaryOperator;

import static java.util.Collections.emptyList;
import static org.openrewrite.Tree.randomId;
import static org.openrewrite.marker.OperatingSystemProvenance.hostname;

Expand Down Expand Up @@ -67,6 +69,6 @@ public GitProvenance buildGitProvenance() throws IncompleteGitConfigException {
throw new IncompleteGitConfigException();
}
return new GitProvenance(UUID.randomUUID(), repositoryURL, StringUtils.isBlank(branch)? tag : branch,
sha1, null, null);
sha1, null, null, emptyList());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import java.util.UUID;
import java.util.function.UnaryOperator;

import static java.util.Collections.emptyList;
import static org.openrewrite.Tree.randomId;

@Value
Expand All @@ -50,7 +51,8 @@ public GitProvenance buildGitProvenance() throws IncompleteGitConfigException {
|| StringUtils.isBlank(sha)) {
throw new IncompleteGitConfigException();
} else {
return new GitProvenance(UUID.randomUUID(), cloneURL, ref, sha, null, null);
return new GitProvenance(UUID.randomUUID(), cloneURL, ref, sha,
null, null, emptyList());
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import java.util.UUID;
import java.util.function.UnaryOperator;

import static java.util.Collections.emptyList;
import static org.openrewrite.Tree.randomId;
import static org.openrewrite.marker.OperatingSystemProvenance.hostname;

Expand Down Expand Up @@ -67,7 +68,8 @@ public GitProvenance buildGitProvenance() throws IncompleteGitConfigException {
throw new IncompleteGitConfigException();
}
return new GitProvenance(UUID.randomUUID(), remoteURL,
StringUtils.isBlank(branch)? tag: branch, commitSha, null, null);
StringUtils.isBlank(branch)? tag: branch, commitSha,
null, null, emptyList());
}

public String getBuildUrl() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import java.util.UUID;
import java.util.function.UnaryOperator;

import static java.util.Collections.emptyList;
import static org.openrewrite.Tree.randomId;

@Value
Expand Down Expand Up @@ -75,7 +76,6 @@ public GitProvenance buildGitProvenance() throws IncompleteGitConfigException {
}

return new GitProvenance(UUID.randomUUID(), host + "/" + getRepository()
+ ".git", gitRef, getSha(), null, null);
+ ".git", gitRef, getSha(), null, null, emptyList());
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import java.util.UUID;
import java.util.function.UnaryOperator;

import static java.util.Collections.emptyList;
import static org.openrewrite.Tree.randomId;

@Value
Expand Down Expand Up @@ -60,6 +61,7 @@ public GitProvenance buildGitProvenance() throws IncompleteGitConfigException {
|| StringUtils.isBlank(ciCommitSha)) {
throw new IncompleteGitConfigException();
}
return new GitProvenance(UUID.randomUUID(), ciRepositoryUrl, ciCommitRefName, ciCommitSha, null, null);
return new GitProvenance(UUID.randomUUID(), ciRepositoryUrl, ciCommitRefName, ciCommitSha,
null, null, emptyList());
}
}
102 changes: 102 additions & 0 deletions rewrite-core/src/main/java/org/openrewrite/search/FindCommitters.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
/*
* Copyright 2023 the original author or authors.
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* https://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.openrewrite.search;

import org.openrewrite.*;
import org.openrewrite.internal.lang.Nullable;
import org.openrewrite.marker.GitProvenance;
import org.openrewrite.marker.SearchResult;
import org.openrewrite.table.CommitsByDay;
import org.openrewrite.table.DistinctCommitters;

import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import java.util.TreeMap;

public class FindCommitters extends ScanningRecipe<Map<String, GitProvenance.Committer>> {
private transient final DistinctCommitters committers = new DistinctCommitters(this);
private transient final CommitsByDay commitsByDay = new CommitsByDay(this);

@Override
public String getDisplayName() {
return "Find committers on repositories";
}

@Override
public String getDescription() {
return "List the committers on a repository.";
}

@Override
public Map<String, GitProvenance.Committer> getInitialValue(ExecutionContext ctx) {
return new TreeMap<>();
}

@Override
public TreeVisitor<?, ExecutionContext> getScanner(Map<String, GitProvenance.Committer> acc) {
return new TreeVisitor<Tree, ExecutionContext>() {
@Override
public @Nullable Tree visit(@Nullable Tree tree, ExecutionContext ctx) {
if (tree instanceof SourceFile) {
SourceFile sourceFile = (SourceFile) tree;
sourceFile.getMarkers().findFirst(GitProvenance.class).ifPresent(provenance -> {
if (provenance.getCommitters() != null) {
for (GitProvenance.Committer committer : provenance.getCommitters()) {
acc.put(committer.getEmail(), committer);
}
}
});
}
return tree;
}
};
}

@Override
public Collection<? extends SourceFile> generate(Map<String, GitProvenance.Committer> acc, ExecutionContext ctx) {
for (GitProvenance.Committer committer : acc.values()) {
committers.insertRow(ctx, new DistinctCommitters.Row(
committer.getName(),
committer.getEmail(),
committer.getCommitsByDay().lastKey(),
committer.getCommitsByDay().values().stream().mapToInt(Integer::intValue).sum()
));

committer.getCommitsByDay().forEach((day, commits) -> commitsByDay.insertRow(ctx, new CommitsByDay.Row(
committer.getName(),
committer.getEmail(),
day,
commits
)));
}
return Collections.emptyList();
}

@Override
public TreeVisitor<?, ExecutionContext> getVisitor(Map<String, GitProvenance.Committer> acc) {
return new TreeVisitor<Tree, ExecutionContext>() {
@Override
@Nullable
public Tree visit(@Nullable Tree tree, ExecutionContext ctx) {
if (tree instanceof SourceFile) {
return SearchResult.found(tree, String.join("\n", acc.keySet()));
}
return tree;
}
};
}
}
39 changes: 39 additions & 0 deletions rewrite-core/src/main/java/org/openrewrite/table/CommitsByDay.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*
* Copyright 2023 the original author or authors.
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* https://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.openrewrite.table;

import lombok.Value;
import org.openrewrite.DataTable;
import org.openrewrite.Recipe;

import java.time.LocalDate;

public class CommitsByDay extends DataTable<CommitsByDay.Row> {

public CommitsByDay(Recipe recipe) {
super(recipe,
"Commits by day",
"The commit activity by day by committer.");
}

@Value
public static class Row {
String name;
String email;
LocalDate day;
int commits;
}
}
Loading

0 comments on commit a5f1772

Please sign in to comment.