You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
570 lines
23 KiB
570 lines
23 KiB
package com.chenhai.chenhaiai.service;
|
|
|
|
import com.chenhai.chenhaiai.entity.git.*;
|
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
|
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
|
|
import jakarta.annotation.PostConstruct;
|
|
import lombok.extern.slf4j.Slf4j;
|
|
import org.springframework.beans.factory.annotation.Autowired;
|
|
import org.springframework.beans.factory.annotation.Value;
|
|
import org.springframework.core.task.TaskExecutor;
|
|
import org.springframework.scheduling.annotation.Async;
|
|
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
|
|
import org.springframework.stereotype.Service;
|
|
|
|
import java.net.http.HttpClient;
|
|
import java.net.http.HttpRequest;
|
|
import java.net.http.HttpResponse;
|
|
import java.time.DayOfWeek;
|
|
import java.time.Duration;
|
|
import java.time.LocalDateTime;
|
|
import java.time.ZonedDateTime;
|
|
import java.time.format.DateTimeFormatter;
|
|
import java.util.*;
|
|
import java.util.concurrent.CompletableFuture;
|
|
import java.util.concurrent.ConcurrentHashMap;
|
|
import java.util.concurrent.TimeUnit;
|
|
import java.util.concurrent.atomic.AtomicInteger;
|
|
import java.util.stream.Collectors;
|
|
|
|
@Slf4j
|
|
@Service
|
|
public class GiteaAnalysisService {
|
|
|
|
@Value("${gitea.url:http://192.168.1.224:3000}")
|
|
private String giteaBaseUrl;
|
|
|
|
@Value("${gitea.token:a9f1c8d3d6fefd73956604f496457faaa3672f89}")
|
|
private String accessToken;
|
|
|
|
@Value("${gitea.analysis.max-active-repos:100}")
|
|
private int maxActiveRepos;
|
|
|
|
@Autowired
|
|
private ThreadPoolTaskExecutor giteaTaskExecutor;
|
|
|
|
@Autowired
|
|
private TaskExecutor taskExecutor;
|
|
|
|
private HttpClient httpClient;
|
|
private ObjectMapper objectMapper;
|
|
|
|
@PostConstruct
|
|
public void init() {
|
|
log.info("初始化Gitea分析服务...");
|
|
|
|
httpClient = HttpClient.newBuilder()
|
|
.connectTimeout(Duration.ofSeconds(10))
|
|
.build();
|
|
|
|
objectMapper = new ObjectMapper();
|
|
objectMapper.registerModule(new JavaTimeModule());
|
|
|
|
log.info("Gitea分析服务初始化完成");
|
|
}
|
|
|
|
/**
|
|
* 异步执行Git分析,直接返回结构化数据
|
|
* @param since 开始时间 (ISO格式: 2025-12-01T00:00:00+08:00)
|
|
* @param until 结束时间 (ISO格式: 2025-12-31T23:59:59+08:00)
|
|
* @return 结构化分析数据
|
|
*/
|
|
@Async("giteaTaskExecutor")
|
|
public CompletableFuture<GitAnalysisData> analyzeGitDataAsync(String since, String until) {
|
|
String taskId = UUID.randomUUID().toString().substring(0, 8);
|
|
long startTime = System.currentTimeMillis();
|
|
|
|
log.info("开始Git分析任务[{}]: {} 至 {}", taskId, since, until);
|
|
|
|
return CompletableFuture.supplyAsync(() -> {
|
|
try {
|
|
GitAnalysisData analysisData = performCompleteAnalysis(since, until, taskId);
|
|
long cost = System.currentTimeMillis() - startTime;
|
|
log.info("Git分析任务[{}]完成,耗时: {}ms", taskId, cost);
|
|
return analysisData;
|
|
} catch (Exception e) {
|
|
log.error("Git分析任务[{}]失败: {}", taskId, e.getMessage(), e);
|
|
throw new RuntimeException("Git分析失败: " + e.getMessage(), e);
|
|
}
|
|
}, giteaTaskExecutor);
|
|
}
|
|
|
|
/**
|
|
* 执行完整分析,返回结构化数据
|
|
*/
|
|
private GitAnalysisData performCompleteAnalysis(String since, String until, String taskId) throws Exception {
|
|
long startTime = System.currentTimeMillis();
|
|
|
|
// 1. 获取所有仓库
|
|
log.debug("任务[{}] 获取仓库列表...", taskId);
|
|
List<GiteaRepository> allRepos = getAllUserRepositories();
|
|
int totalRepos = allRepos.size();
|
|
|
|
if (totalRepos == 0) {
|
|
// 返回空的基础数据
|
|
return buildEmptyGitAnalysisData("无仓库数据");
|
|
}
|
|
|
|
log.info("任务[{}] 发现仓库: {} 个", taskId, totalRepos);
|
|
|
|
// 2. 解析时间范围
|
|
ZonedDateTime sinceTime = ZonedDateTime.parse(since);
|
|
ZonedDateTime untilTime = ZonedDateTime.parse(until);
|
|
|
|
// 3. 快速筛选活跃仓库
|
|
log.debug("任务[{}] 快速筛选活跃仓库...", taskId);
|
|
List<GiteaRepository> activeRepos = findActiveRepositories(allRepos, sinceTime, untilTime, taskId)
|
|
.get(20, TimeUnit.SECONDS);
|
|
int activeRepoCount = activeRepos.size();
|
|
|
|
log.info("任务[{}] 活跃仓库: {} 个", taskId, activeRepoCount);
|
|
|
|
if (activeRepoCount == 0) {
|
|
// 返回简单结果(无活跃仓库)
|
|
return buildSimpleGitAnalysisData(since, until, totalRepos, 0, 0, startTime);
|
|
}
|
|
|
|
// 限制活跃仓库数量
|
|
if (activeRepoCount > maxActiveRepos) {
|
|
log.warn("任务[{}] 活跃仓库过多({}个),采样分析前{}个", taskId, activeRepoCount, maxActiveRepos);
|
|
activeRepos = activeRepos.subList(0, maxActiveRepos);
|
|
activeRepoCount = maxActiveRepos;
|
|
}
|
|
|
|
// 4. 详细分析活跃仓库
|
|
log.debug("任务[{}] 详细分析活跃仓库...", taskId);
|
|
DetailedAnalysisResult detailResult = analyzeActiveRepositories(activeRepos, sinceTime, untilTime, taskId)
|
|
.get(20, TimeUnit.SECONDS);
|
|
|
|
// 5. 构建结构化数据
|
|
long analysisTime = System.currentTimeMillis() - startTime;
|
|
return buildGitAnalysisData(since, until, totalRepos, activeRepoCount, detailResult, analysisTime);
|
|
}
|
|
|
|
/**
|
|
* 快速筛选活跃仓库
|
|
*/
|
|
private CompletableFuture<List<GiteaRepository>> findActiveRepositories(List<GiteaRepository> allRepos,
|
|
ZonedDateTime since,
|
|
ZonedDateTime until,
|
|
String taskId) {
|
|
return CompletableFuture.supplyAsync(() -> {
|
|
List<GiteaRepository> activeRepos = Collections.synchronizedList(new ArrayList<>());
|
|
List<CompletableFuture<Boolean>> futures = new ArrayList<>();
|
|
AtomicInteger checked = new AtomicInteger(0);
|
|
final int totalRepos = allRepos.size();
|
|
|
|
for (GiteaRepository repo : allRepos) {
|
|
final GiteaRepository currentRepo = repo;
|
|
|
|
CompletableFuture<Boolean> future = CompletableFuture.supplyAsync(() -> {
|
|
try {
|
|
return hasCommitsInRange(currentRepo.getFullPath(), since, until);
|
|
} catch (Exception e) {
|
|
log.debug("任务[{}] 仓库 {} 快速检查失败: {}", taskId, currentRepo.getFullPath(), e.getMessage());
|
|
return false;
|
|
}
|
|
}, taskExecutor);
|
|
|
|
future.thenAccept(hasCommits -> {
|
|
if (hasCommits) {
|
|
activeRepos.add(currentRepo);
|
|
}
|
|
int done = checked.incrementAndGet();
|
|
if (done % 20 == 0 || done == totalRepos) {
|
|
log.debug("任务[{}] 快速检查进度: {}/{} | 活跃: {}", taskId, done, totalRepos, activeRepos.size());
|
|
}
|
|
});
|
|
|
|
futures.add(future);
|
|
}
|
|
|
|
// 等待所有检查完成
|
|
CompletableFuture<Void> allChecks = CompletableFuture.allOf(
|
|
futures.toArray(new CompletableFuture[0]));
|
|
|
|
try {
|
|
allChecks.get(10, TimeUnit.SECONDS);
|
|
} catch (Exception e) {
|
|
log.warn("任务[{}] 部分仓库快速检查未完成: {}", taskId, e.getMessage());
|
|
}
|
|
|
|
return activeRepos;
|
|
}, taskExecutor);
|
|
}
|
|
|
|
/**
|
|
* 详细分析活跃仓库
|
|
*/
|
|
private CompletableFuture<DetailedAnalysisResult> analyzeActiveRepositories(List<GiteaRepository> activeRepos,
|
|
ZonedDateTime sinceTime,
|
|
ZonedDateTime untilTime,
|
|
String taskId) {
|
|
return CompletableFuture.supplyAsync(() -> {
|
|
Map<String, DeveloperData> devDataMap = new ConcurrentHashMap<>();
|
|
Map<String, RepoData> repoDataMap = new ConcurrentHashMap<>();
|
|
Map<DayOfWeek, Integer> dayStats = new ConcurrentHashMap<>();
|
|
Map<Integer, Integer> hourStats = new ConcurrentHashMap<>();
|
|
Map<String, Integer> fileTypeStats = new ConcurrentHashMap<>();
|
|
AtomicInteger totalCommits = new AtomicInteger(0);
|
|
AtomicInteger processed = new AtomicInteger(0);
|
|
|
|
List<CompletableFuture<Void>> futures = activeRepos.stream()
|
|
.map(repo -> CompletableFuture.runAsync(() -> {
|
|
try {
|
|
analyzeSingleRepository(repo, sinceTime, untilTime,
|
|
devDataMap, repoDataMap, dayStats, hourStats, fileTypeStats, totalCommits);
|
|
} catch (Exception e) {
|
|
log.debug("任务[{}] 仓库 {} 详细分析失败: {}", taskId, repo.getFullPath(), e.getMessage());
|
|
} finally {
|
|
int done = processed.incrementAndGet();
|
|
if (done % 10 == 0 || done == activeRepos.size()) {
|
|
log.debug("任务[{}] 详细分析进度: {}/{} | 提交: {}", taskId, done, activeRepos.size(), totalCommits.get());
|
|
}
|
|
}
|
|
}, taskExecutor))
|
|
.collect(Collectors.toList());
|
|
|
|
// 等待所有分析完成
|
|
CompletableFuture<Void> allFutures = CompletableFuture.allOf(
|
|
futures.toArray(new CompletableFuture[0]));
|
|
|
|
try {
|
|
allFutures.get(15, TimeUnit.SECONDS);
|
|
} catch (Exception e) {
|
|
log.warn("任务[{}] 部分仓库详细分析未完成: {}", taskId, e.getMessage());
|
|
}
|
|
|
|
return new DetailedAnalysisResult(devDataMap, repoDataMap, dayStats, hourStats, fileTypeStats, totalCommits.get());
|
|
}, taskExecutor);
|
|
}
|
|
|
|
/**
|
|
* 分析单个仓库详情
|
|
*/
|
|
private void analyzeSingleRepository(GiteaRepository repo,
|
|
ZonedDateTime sinceTime,
|
|
ZonedDateTime untilTime,
|
|
Map<String, DeveloperData> devDataMap,
|
|
Map<String, RepoData> repoDataMap,
|
|
Map<DayOfWeek, Integer> dayStats,
|
|
Map<Integer, Integer> hourStats,
|
|
Map<String, Integer> fileTypeStats,
|
|
AtomicInteger totalCommits) throws Exception {
|
|
|
|
if (Thread.currentThread().isInterrupted()) {
|
|
log.debug("任务被中断,跳过仓库分析: {}", repo.getFullPath());
|
|
return;
|
|
}
|
|
|
|
String repoFullName = repo.getFullPath();
|
|
List<GiteaCommit> commits = getCommitsInRange(repoFullName, sinceTime, untilTime);
|
|
|
|
if (!commits.isEmpty()) {
|
|
RepoData repoData = new RepoData();
|
|
repoData.repoName = repoFullName;
|
|
repoData.displayName = repo.getRepoName();
|
|
|
|
for (GiteaCommit commit : commits) {
|
|
if (Thread.currentThread().isInterrupted()) {
|
|
log.debug("处理提交时被中断");
|
|
return;
|
|
}
|
|
|
|
totalCommits.incrementAndGet();
|
|
String author = getAuthorName(commit);
|
|
|
|
// 更新开发者数据
|
|
DeveloperData devData = devDataMap.computeIfAbsent(author, k -> new DeveloperData(author));
|
|
devData.commitCount++;
|
|
devData.repos.add(repoFullName);
|
|
|
|
// 更新仓库数据
|
|
repoData.commitCount++;
|
|
repoData.developers.add(author);
|
|
|
|
// 时间统计
|
|
ZonedDateTime commitTime = commit.getCommitTime();
|
|
if (commitTime != null) {
|
|
DayOfWeek day = commitTime.getDayOfWeek();
|
|
int hour = commitTime.getHour();
|
|
dayStats.merge(day, 1, Integer::sum);
|
|
hourStats.merge(hour, 1, Integer::sum);
|
|
}
|
|
|
|
// 文件类型统计
|
|
if (commit.getFiles() != null) {
|
|
for (GiteaCommit.ChangedFile file : commit.getFiles()) {
|
|
String fileType = file.getFileType();
|
|
fileTypeStats.merge(fileType, 1, Integer::sum);
|
|
}
|
|
}
|
|
}
|
|
|
|
repoDataMap.put(repoFullName, repoData);
|
|
}
|
|
}
|
|
|
|
// ==================== Gitea API调用方法 ====================
|
|
|
|
private boolean hasCommitsInRange(String repoFullName, ZonedDateTime since, ZonedDateTime until) throws Exception {
|
|
// 直接调用 getCommitsInRange 检查是否有提交
|
|
List<GiteaCommit> commits = getCommitsInRange(repoFullName, since, until);
|
|
return !commits.isEmpty();
|
|
}
|
|
|
|
private List<GiteaCommit> getCommitsInRange(String repoFullName, ZonedDateTime since, ZonedDateTime until) throws Exception {
|
|
// 先获取最近3个月的提交(减少数据量)
|
|
LocalDateTime threeMonthsAgo = LocalDateTime.now().minusMonths(3);
|
|
ZonedDateTime recentSince = threeMonthsAgo.atZone(since.getZone());
|
|
|
|
String sinceStr = recentSince.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME);
|
|
String untilStr = until.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME);
|
|
|
|
String baseUrl = String.format("%s/api/v1/repos/%s/commits?since=%s&until=%s",
|
|
giteaBaseUrl, repoFullName, sinceStr, untilStr);
|
|
|
|
List<GiteaCommit> recentCommits = fetchWithPagination(baseUrl, GiteaCommit.class, 8);
|
|
|
|
// 在代码层面再次过滤到精确时间范围
|
|
List<GiteaCommit> filteredCommits = new ArrayList<>();
|
|
for (GiteaCommit commit : recentCommits) {
|
|
ZonedDateTime commitTime = commit.getCommitTime();
|
|
if (commitTime != null &&
|
|
!commitTime.isBefore(since) &&
|
|
!commitTime.isAfter(until)) {
|
|
filteredCommits.add(commit);
|
|
}
|
|
}
|
|
|
|
return filteredCommits;
|
|
}
|
|
|
|
private List<GiteaRepository> getAllUserRepositories() throws Exception {
|
|
String baseUrl = giteaBaseUrl + "/api/v1/user/repos?limit=50";
|
|
return fetchWithPagination(baseUrl, GiteaRepository.class, 10);
|
|
}
|
|
|
|
private <T> List<T> fetchWithPagination(String baseUrl, Class<T> clazz, int timeoutSeconds) throws Exception {
|
|
List<T> results = new ArrayList<>();
|
|
int page = 1;
|
|
int maxPages = 10;
|
|
|
|
while (page <= maxPages) {
|
|
if (Thread.currentThread().isInterrupted()) {
|
|
log.debug("分页获取被中断");
|
|
break;
|
|
}
|
|
|
|
String pageUrl = baseUrl + (baseUrl.contains("?") ? "&" : "?") + "page=" + page;
|
|
|
|
HttpRequest request = HttpRequest.newBuilder()
|
|
.uri(java.net.URI.create(pageUrl))
|
|
.header("Authorization", "token " + accessToken)
|
|
.timeout(Duration.ofSeconds(timeoutSeconds))
|
|
.GET()
|
|
.build();
|
|
|
|
HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
|
|
|
|
if (response.statusCode() != 200) {
|
|
log.warn("API请求失败: {} - {}", response.statusCode(), response.body());
|
|
break;
|
|
}
|
|
|
|
List<T> pageResults = objectMapper.readValue(
|
|
response.body(),
|
|
objectMapper.getTypeFactory().constructCollectionType(List.class, clazz));
|
|
|
|
if (pageResults.isEmpty()) {
|
|
break;
|
|
}
|
|
|
|
results.addAll(pageResults);
|
|
|
|
if (pageResults.size() < 50) {
|
|
break;
|
|
}
|
|
|
|
page++;
|
|
}
|
|
|
|
return results;
|
|
}
|
|
|
|
private String getAuthorName(GiteaCommit commit) {
|
|
if (commit.getCommit() != null &&
|
|
commit.getCommit().getAuthor() != null &&
|
|
commit.getCommit().getAuthor().getName() != null) {
|
|
return commit.getCommit().getAuthor().getName();
|
|
}
|
|
return "未知作者";
|
|
}
|
|
|
|
// ==================== GitAnalysisData构建方法 ====================
|
|
|
|
/**
|
|
* 构建完整的GitAnalysisData
|
|
*/
|
|
private GitAnalysisData buildGitAnalysisData(String since, String until, int totalRepos, int activeRepos,
|
|
DetailedAnalysisResult detailResult, long analysisTime) {
|
|
GitAnalysisData data = new GitAnalysisData();
|
|
|
|
// 基础信息
|
|
data.setBasicInfo(new BasicInfo(
|
|
since + " 至 " + until,
|
|
totalRepos,
|
|
activeRepos,
|
|
detailResult.getDevDataMap().size(),
|
|
detailResult.getTotalCommits(),
|
|
analysisTime,
|
|
"快速筛选 + 详细分析"
|
|
));
|
|
|
|
// 开发者排行榜
|
|
if (!detailResult.getDevDataMap().isEmpty()) {
|
|
List<DeveloperData> devList = new ArrayList<>(detailResult.getDevDataMap().values());
|
|
devList.sort((a, b) -> Integer.compare(b.commitCount, a.commitCount));
|
|
|
|
List<DeveloperRank> developerRanks = new ArrayList<>();
|
|
int rank = 1;
|
|
for (DeveloperData dev : devList) {
|
|
if (rank > 10) break;
|
|
developerRanks.add(new DeveloperRank(rank++, dev.name, dev.commitCount, dev.repos.size()));
|
|
}
|
|
data.setDeveloperRanks(developerRanks);
|
|
}
|
|
|
|
// 仓库排行榜
|
|
if (!detailResult.getRepoDataMap().isEmpty()) {
|
|
List<RepoData> repoList = new ArrayList<>(detailResult.getRepoDataMap().values());
|
|
repoList.sort((a, b) -> Integer.compare(b.commitCount, a.commitCount));
|
|
|
|
List<RepoRank> repoRanks = new ArrayList<>();
|
|
int rank = 1;
|
|
for (RepoData repo : repoList) {
|
|
if (rank > 10) break;
|
|
repoRanks.add(new RepoRank(rank++, repo.repoName, repo.displayName, repo.commitCount, repo.developers.size()));
|
|
}
|
|
data.setRepoRanks(repoRanks);
|
|
}
|
|
|
|
// 时间分布(按星期)
|
|
if (!detailResult.getDayStats().isEmpty()) {
|
|
String[] dayNames = {"周一", "周二", "周三", "周四", "周五", "周六", "周日"};
|
|
DayOfWeek[] days = DayOfWeek.values();
|
|
|
|
List<DayStats> dayStatsList = new ArrayList<>();
|
|
for (int i = 0; i < 7; i++) {
|
|
int count = detailResult.getDayStats().getOrDefault(days[i], 0);
|
|
dayStatsList.add(new DayStats(dayNames[i], count));
|
|
}
|
|
data.setDayStats(dayStatsList);
|
|
}
|
|
|
|
// 文件类型统计
|
|
if (!detailResult.getFileTypeStats().isEmpty()) {
|
|
List<Map.Entry<String, Integer>> fileList = new ArrayList<>(detailResult.getFileTypeStats().entrySet());
|
|
fileList.sort((a, b) -> Integer.compare(b.getValue(), a.getValue()));
|
|
|
|
List<FileTypeStats> fileTypeStatsList = new ArrayList<>();
|
|
for (Map.Entry<String, Integer> entry : fileList) {
|
|
fileTypeStatsList.add(new FileTypeStats(entry.getKey(), entry.getValue()));
|
|
}
|
|
data.setFileTypeStats(fileTypeStatsList);
|
|
}
|
|
|
|
// 生成时间
|
|
data.setGeneratedTime(LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
|
|
|
|
return data;
|
|
}
|
|
|
|
/**
|
|
* 构建空的GitAnalysisData(无仓库时)
|
|
*/
|
|
private GitAnalysisData buildEmptyGitAnalysisData(String message) {
|
|
GitAnalysisData data = new GitAnalysisData();
|
|
data.setBasicInfo(new BasicInfo(
|
|
"无时间范围",
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
"无仓库数据"
|
|
));
|
|
data.setGeneratedTime(LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
|
|
return data;
|
|
}
|
|
|
|
/**
|
|
* 构建简单的GitAnalysisData(无活跃仓库时)
|
|
*/
|
|
private GitAnalysisData buildSimpleGitAnalysisData(String since, String until, int totalRepos,
|
|
int activeRepos, int totalDevs, long startTime) {
|
|
long analysisTime = System.currentTimeMillis() - startTime;
|
|
|
|
GitAnalysisData data = new GitAnalysisData();
|
|
data.setBasicInfo(new BasicInfo(
|
|
since + " 至 " + until,
|
|
totalRepos,
|
|
activeRepos,
|
|
totalDevs,
|
|
0,
|
|
analysisTime,
|
|
"快速筛选"
|
|
));
|
|
data.setGeneratedTime(LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
|
|
|
|
return data;
|
|
}
|
|
|
|
// ==================== 内部数据类 ====================
|
|
|
|
private static class DeveloperData {
|
|
String name;
|
|
int commitCount = 0;
|
|
Set<String> repos = new HashSet<>();
|
|
|
|
DeveloperData(String name) {
|
|
this.name = name;
|
|
}
|
|
}
|
|
|
|
private static class RepoData {
|
|
String repoName;
|
|
String displayName;
|
|
int commitCount = 0;
|
|
Set<String> developers = new HashSet<>();
|
|
}
|
|
|
|
// ==================== 详细分析结果类 ====================
|
|
|
|
private static class DetailedAnalysisResult {
|
|
private final Map<String, DeveloperData> devDataMap;
|
|
private final Map<String, RepoData> repoDataMap;
|
|
private final Map<DayOfWeek, Integer> dayStats;
|
|
private final Map<Integer, Integer> hourStats;
|
|
private final Map<String, Integer> fileTypeStats;
|
|
private final int totalCommits;
|
|
|
|
public DetailedAnalysisResult(Map<String, DeveloperData> devDataMap, Map<String, RepoData> repoDataMap,
|
|
Map<DayOfWeek, Integer> dayStats, Map<Integer, Integer> hourStats,
|
|
Map<String, Integer> fileTypeStats, int totalCommits) {
|
|
this.devDataMap = devDataMap;
|
|
this.repoDataMap = repoDataMap;
|
|
this.dayStats = dayStats;
|
|
this.hourStats = hourStats;
|
|
this.fileTypeStats = fileTypeStats;
|
|
this.totalCommits = totalCommits;
|
|
}
|
|
|
|
public Map<String, DeveloperData> getDevDataMap() { return devDataMap; }
|
|
public Map<String, RepoData> getRepoDataMap() { return repoDataMap; }
|
|
public Map<DayOfWeek, Integer> getDayStats() { return dayStats; }
|
|
public Map<Integer, Integer> getHourStats() { return hourStats; }
|
|
public Map<String, Integer> getFileTypeStats() { return fileTypeStats; }
|
|
public int getTotalCommits() { return totalCommits; }
|
|
}
|
|
}
|