Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions src/main/java/reposense/authorship/AuthorshipReporter.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package reposense.authorship;

import java.util.List;
import java.util.Objects;
import java.util.logging.Logger;
import java.util.stream.Collectors;

Expand All @@ -10,6 +9,7 @@
import reposense.authorship.model.FileResult;
import reposense.model.RepoConfiguration;
import reposense.system.LogsManager;
import reposense.util.function.Failable;


/**
Expand Down Expand Up @@ -47,14 +47,16 @@ public AuthorshipSummary generateAuthorshipSummary(RepoConfiguration config) {

List<FileResult> fileResults = textFileInfos.stream()
.map(fileInfo -> fileInfoAnalyzer.analyzeTextFile(config, fileInfo))
.filter(Objects::nonNull)
.filter(Failable::isPresent)
.map(Failable::get)
.collect(Collectors.toList());

List<FileInfo> binaryFileInfos = fileInfoExtractor.extractBinaryFileInfos(config);

List<FileResult> binaryFileResults = binaryFileInfos.stream()
.map(fileInfo -> fileInfoAnalyzer.analyzeBinaryFile(config, fileInfo))
.filter(Objects::nonNull)
.filter(Failable::isPresent)
.map(Failable::get)
.collect(Collectors.toList());

fileResults.addAll(binaryFileResults);
Expand Down
101 changes: 46 additions & 55 deletions src/main/java/reposense/authorship/FileInfoAnalyzer.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.logging.Logger;

Expand All @@ -23,6 +22,7 @@
import reposense.system.LogsManager;
import reposense.util.FileUtil;
import reposense.util.StringsUtil;
import reposense.util.function.Failable;

/**
* Analyzes the target and information given in the {@link FileInfo}.
Expand All @@ -45,50 +45,41 @@ public class FileInfoAnalyzer {
/**
* Analyzes the lines of the file, given in the {@code fileInfo}, that has changed in the time period provided
* by {@code config}.
* Returns null if the file is missing from the local system, or none of the
* {@link Author} specified in {@code config} contributed to the file in {@code fileInfo}.
* Returns empty {@code Failable<FileResult, CannotFailException>} if the file is missing from the local system,
* or none of the {@link Author} specified in {@code config} contributed to the file in {@code fileInfo}.
*/
public FileResult analyzeTextFile(RepoConfiguration config, FileInfo fileInfo) {
public Failable<FileResult> analyzeTextFile(RepoConfiguration config, FileInfo fileInfo) {
String relativePath = fileInfo.getPath();

if (Files.notExists(Paths.get(config.getRepoRoot(), relativePath))) {
logger.severe(String.format(MESSAGE_FILE_MISSING, relativePath));
return null;
}

if (FileUtil.isEmptyFile(config.getRepoRoot(), relativePath)) {
return null;
}

aggregateBlameAuthorModifiedAndDateInfo(config, fileInfo);
fileInfo.setFileType(config.getFileType(fileInfo.getPath()));

AnnotatorAnalyzer.aggregateAnnotationAuthorInfo(fileInfo, config.getAuthorConfig());

if (!config.getAuthorList().isEmpty() && fileInfo.isAllAuthorsIgnored(config.getAuthorList())) {
return null;
}

return generateTextFileResult(fileInfo);
// note that the predicates in filter() test for the negation of the previous failure conditions
return Failable.ofNullable(() -> relativePath)
.filter(x -> Files.exists(Paths.get(config.getRepoRoot(), x)))
.ifAbsent(() -> logger.severe(String.format(MESSAGE_FILE_MISSING, relativePath)))
.filter(x -> !FileUtil.isEmptyFile(config.getRepoRoot(), x))
.ifPresent(x -> {
aggregateBlameAuthorModifiedAndDateInfo(config, fileInfo);
fileInfo.setFileType(config.getFileType(fileInfo.getPath()));

AnnotatorAnalyzer.aggregateAnnotationAuthorInfo(fileInfo, config.getAuthorConfig());
})
.filter(x -> config.getAuthorList().isEmpty() || !fileInfo.isAllAuthorsIgnored(config.getAuthorList()))
.map(x -> generateTextFileResult(fileInfo));
}

/**
* Analyzes the binary file, given in the {@code fileInfo}, that has changed in the time period provided
* by {@code config}.
* Returns null if the file is missing from the local system, or none of the
* {@link Author} specified in {@code config} contributed to the file in {@code fileInfo}.
* Returns empty {@code Failable<FileResult, CannotFailException>} if the file is missing from the local system,
* or none of the {@link Author} specified in {@code config} contributed to the file in {@code fileInfo}.
*/
public FileResult analyzeBinaryFile(RepoConfiguration config, FileInfo fileInfo) {
public Failable<FileResult> analyzeBinaryFile(RepoConfiguration config, FileInfo fileInfo) {
String relativePath = fileInfo.getPath();

if (Files.notExists(Paths.get(config.getRepoRoot(), relativePath))) {
logger.severe(String.format(MESSAGE_FILE_MISSING, relativePath));
return null;
}

fileInfo.setFileType(config.getFileType(fileInfo.getPath()));

return generateBinaryFileResult(config, fileInfo);
return Failable.ofNullable(() -> relativePath)
.filter(x -> Files.exists(Paths.get(config.getRepoRoot(), relativePath)))
.ifAbsent(() -> logger.severe(String.format(MESSAGE_FILE_MISSING, relativePath)))
.ifPresent(x -> fileInfo.setFileType(config.getFileType(fileInfo.getPath())))
.flatMap(x -> generateBinaryFileResult(config, fileInfo));
}

/**
Expand All @@ -109,29 +100,29 @@ private FileResult generateTextFileResult(FileInfo fileInfo) {
/**
* Generates and returns a {@link FileResult} with the authorship results from binary {@code fileInfo} consolidated.
* Authorship results are indicated in the {@code authorContributionMap} as contributions with zero line counts.
* Returns {@code null} if none of the {@link Author} specified in {@code config} contributed to the file in
* {@code fileInfo}.
* Returns an empty {@code Failable<FileResult, CannotFailException>} if none of the {@link Author} specified in
* {@code config} contributed to the file in {@code fileInfo}.
*/
private FileResult generateBinaryFileResult(RepoConfiguration config, FileInfo fileInfo) {
List<String[]> authorsString = GitLog.getFileAuthors(config, fileInfo.getPath());
if (authorsString.size() == 0) {
return null;
}

private Failable<FileResult> generateBinaryFileResult(
RepoConfiguration config, FileInfo fileInfo) {
Set<Author> authors = new HashSet<>();
HashMap<Author, Integer> authorContributionMap = new HashMap<>();

for (String[] lineDetails : authorsString) {
String authorName = lineDetails[0];
String authorEmail = lineDetails[1];
authors.add(config.getAuthor(authorName, authorEmail));
}

for (Author author : authors) {
authorContributionMap.put(author, 0);
}

return FileResult.createBinaryFileResult(fileInfo.getPath(), fileInfo.getFileType(), authorContributionMap);
return Failable.ofNullable(() -> GitLog.getFileAuthors(config, fileInfo.getPath()))
.filter(x -> !x.isEmpty())
.ifPresent(x -> {
for (String[] lineDetails : x) {
String authorName = lineDetails[0];
String authorEmail = lineDetails[1];
authors.add(config.getAuthor(authorName, authorEmail));
}

for (Author author : authors) {
authorContributionMap.put(author, 0);
}
})
.map(x -> FileResult
.createBinaryFileResult(fileInfo.getPath(), fileInfo.getFileType(), authorContributionMap));
}

/**
Expand Down Expand Up @@ -160,14 +151,14 @@ private void aggregateBlameAuthorModifiedAndDateInfo(RepoConfiguration config, F
String authorName = blameResultLines[lineCount + 1].substring(AUTHOR_NAME_OFFSET);
String authorEmail = blameResultLines[lineCount + 2]
.substring(AUTHOR_EMAIL_OFFSET).replaceAll("<|>", "");
Long commitDateInMs = Long.parseLong(blameResultLines[lineCount + 3].substring(AUTHOR_TIME_OFFSET)) * 1000;
long commitDateInMs = Long.parseLong(blameResultLines[lineCount + 3].substring(AUTHOR_TIME_OFFSET)) * 1000;
LocalDateTime commitDate = LocalDateTime.ofInstant(Instant.ofEpochMilli(commitDateInMs),
config.getZoneId());
Author author = config.getAuthor(authorName, authorEmail);

if (!fileInfo.isFileLineTracked(lineCount / 5) || author.isIgnoringFile(filePath)
|| CommitHash.isInsideCommitList(commitHash, config.getIgnoreCommitList())
|| commitDate.compareTo(sinceDate) < 0 || commitDate.compareTo(untilDate) > 0) {
|| commitDate.isBefore(sinceDate) || commitDate.isAfter(untilDate)) {
author = Author.UNKNOWN_AUTHOR;
}

Expand Down
24 changes: 12 additions & 12 deletions src/main/java/reposense/commits/CommitInfoAnalyzer.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
import java.time.LocalDateTime;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashMap;
Expand All @@ -25,6 +24,7 @@
import reposense.model.FileType;
import reposense.model.RepoConfiguration;
import reposense.system.LogsManager;
import reposense.util.function.Failable;

/**
* Analyzes commit information found in the git log.
Expand Down Expand Up @@ -89,15 +89,15 @@ public CommitResult analyzeCommit(CommitInfo commitInfo, RepoConfiguration confi
Boolean isMergeCommit = elements[PARENT_HASHES_INDEX].split(HASH_SPLITTER).length > 1;
Author author = config.getAuthor(elements[AUTHOR_INDEX], elements[EMAIL_INDEX]);

ZonedDateTime date = null;
try {
date = ZonedDateTime.parse(elements[DATE_INDEX], GIT_STRICT_ISO_DATE_FORMAT);
} catch (DateTimeParseException pe) {
logger.log(Level.WARNING, "Unable to parse the date from git log result for commit.", pe);
}

// Commit date may be in a timezone different from the one given in the config.
LocalDateTime adjustedDate = date.withZoneSameInstant(config.getZoneId()).toLocalDateTime();
// safe map since ZonedDateTime::now returns non-null
Failable<LocalDateTime> date = Failable
.ofNullable(() -> ZonedDateTime.parse(elements[DATE_INDEX], GIT_STRICT_ISO_DATE_FORMAT),
x -> {
logger.log(Level.WARNING,
"Unable to parse the date from git log result for commit.", x);
return ZonedDateTime.now();
})
.map(x -> x.withZoneSameInstant(config.getZoneId()).toLocalDateTime());

String messageTitle = (elements.length > MESSAGE_TITLE_INDEX) ? elements[MESSAGE_TITLE_INDEX] : "";
String messageBody = (elements.length > MESSAGE_BODY_INDEX)
Expand All @@ -114,15 +114,15 @@ public CommitResult analyzeCommit(CommitInfo commitInfo, RepoConfiguration confi
}

if (statLine.isEmpty()) { // empty commit, no files changed
return new CommitResult(author, hash, isMergeCommit, adjustedDate, messageTitle, messageBody, tags);
return new CommitResult(author, hash, isMergeCommit, date.get(), messageTitle, messageBody, tags);
}

String[] statInfos = statLine.split(NEW_LINE_SPLITTER);
String[] fileTypeContributions = Arrays.copyOfRange(statInfos, 0, statInfos.length - 1);
Map<FileType, ContributionPair> fileTypeAndContributionMap =
getFileTypesAndContribution(fileTypeContributions, config);

return new CommitResult(author, hash, isMergeCommit, adjustedDate, messageTitle, messageBody, tags,
return new CommitResult(author, hash, isMergeCommit, date.get(), messageTitle, messageBody, tags,
fileTypeAndContributionMap);
}

Expand Down
49 changes: 23 additions & 26 deletions src/main/java/reposense/model/ConfigRunConfiguration.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Collections;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
Expand All @@ -13,6 +13,7 @@
import reposense.parser.exceptions.InvalidCsvException;
import reposense.parser.exceptions.InvalidHeaderException;
import reposense.system.LogsManager;
import reposense.util.function.Failable;

/**
* Represents RepoSense run configured by config files.
Expand All @@ -38,33 +39,29 @@ public ConfigRunConfiguration(CliArguments cliArguments) {
public List<RepoConfiguration> getRepoConfigurations()
throws IOException, InvalidCsvException, InvalidHeaderException {
List<RepoConfiguration> repoConfigs = new RepoConfigCsvParser(cliArguments.getRepoConfigFilePath()).parse();
List<AuthorConfiguration> authorConfigs;
List<GroupConfiguration> groupConfigs;

Path authorConfigFilePath = cliArguments.getAuthorConfigFilePath();
Path groupConfigFilePath = cliArguments.getGroupConfigFilePath();
// parse the author config file path
Failable.of(cliArguments::getAuthorConfigFilePath)
.filter(Files::exists)
.map(x -> new AuthorConfigCsvParser(cliArguments.getAuthorConfigFilePath()).parse(),
exception -> {
logger.log(Level.WARNING, exception.getMessage(), exception);
return Collections.<AuthorConfiguration>emptyList();
})
.ifPresent(x -> RepoConfiguration.merge(repoConfigs, x))
.ifPresent(() -> RepoConfiguration.setHasAuthorConfigFileToRepoConfigs(repoConfigs, true))
.orElse(Collections.emptyList());


if (authorConfigFilePath != null && Files.exists(authorConfigFilePath)) {
try {
authorConfigs = new AuthorConfigCsvParser(cliArguments.getAuthorConfigFilePath()).parse();
RepoConfiguration.merge(repoConfigs, authorConfigs);
RepoConfiguration.setHasAuthorConfigFileToRepoConfigs(repoConfigs, true);
} catch (IOException | InvalidCsvException e) {
// for all IO and invalid csv exceptions, log the error and continue
logger.log(Level.WARNING, e.getMessage(), e);
}
}

if (groupConfigFilePath != null && Files.exists(groupConfigFilePath)) {
try {
groupConfigs = new GroupConfigCsvParser(cliArguments.getGroupConfigFilePath()).parse();
RepoConfiguration.setGroupConfigsToRepos(repoConfigs, groupConfigs);
} catch (IOException | InvalidCsvException e) {
// for all IO and invalid csv exceptions, log the error and continue
logger.log(Level.WARNING, e.getMessage(), e);
}
}
// parse the group config file path
Failable.of(cliArguments::getGroupConfigFilePath)
.filter(Files::exists)
.map(x -> new GroupConfigCsvParser(x).parse(),
exception -> {
logger.log(Level.WARNING, exception.getMessage(), exception);
return Collections.<GroupConfiguration>emptyList();
})
.ifPresent(x -> RepoConfiguration.setGroupConfigsToRepos(repoConfigs, x))
.orElse(Collections.emptyList());

return repoConfigs;
}
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/reposense/model/RepoConfiguration.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@

import reposense.git.GitBranch;
import reposense.git.exception.GitBranchException;
import reposense.parser.ConfigurationBuildException;
import reposense.parser.exceptions.ConfigurationBuildException;
import reposense.system.LogsManager;
import reposense.util.FileUtil;

Expand Down
20 changes: 10 additions & 10 deletions src/main/java/reposense/parser/GroupConfigCsvParser.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import reposense.model.GroupConfiguration;
import reposense.model.RepoLocation;
import reposense.parser.exceptions.InvalidLocationException;
import reposense.util.function.Failable;

/**
* Container for the values parsed from {@code group-config.csv} file.
Expand Down Expand Up @@ -57,18 +58,17 @@ protected void processLine(List<GroupConfiguration> results, CSVRecord record) t
String groupName = get(record, GROUP_NAME_HEADER);
List<String> globList = getAsList(record, FILES_GLOB_HEADER);

GroupConfiguration groupConfig = null;
groupConfig = findMatchingGroupConfiguration(results, location);

FileType group = new FileType(groupName, globList);
if (groupConfig.containsGroup(group)) {
logger.warning(String.format(
"Skipping group as %s has already been specified for the repository %s",
group.toString(), groupConfig.getLocation()));
return;
}
GroupConfiguration groupConfig = findMatchingGroupConfiguration(results, location);

groupConfig.addGroup(group);
Failable.success(groupConfig)
.filter(x -> !x.containsGroup(group))
.ifAbsent(() -> {
logger.warning(String.format(
"Skipping group as %s has already been specified for the repository %s",
group, groupConfig.getLocation()));
})
.ifPresent(x -> x.addGroup(group));
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package reposense.parser;
package reposense.parser.exceptions;

/**
* Signals that there was an issue building a Configuration (missing parameters, etc.).
Expand Down
Loading
Loading