From 4f8cd45b39cb37c31cd33af5120a8edfd4c4018c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E5=AE=B6=E8=AE=AD?= Date: Wed, 10 Jun 2026 13:39:56 +0800 Subject: [PATCH] =?UTF-8?q?=E5=88=A0=E9=99=A4=E4=BA=86=E6=97=A7git?= =?UTF-8?q?=E6=8F=92=E4=BB=B6=EF=BC=8C=E6=96=B0=E5=A2=9E=E4=BA=86git?= =?UTF-8?q?=E5=90=8C=E6=AD=A5=E6=8F=92=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../example/workutils/biz/git/GitPlugin.java | 170 ----- .../workutils/biz/git/GitSyncPlugin.java | 679 +++++++++++++++++ .../workutils/biz/git/service/GitService.java | 24 - .../biz/git/service/GitServiceImpl.java | 169 ----- .../service/config/GitOptimizationConfig.java | 272 ------- .../internal/GitCommandController.java | 317 -------- .../service/internal/GitCommandExecutor.java | 523 ------------- .../internal/GitRepositoryManager.java | 688 ------------------ .../git/service/internal/GitResultCache.java | 268 ------- .../strategy/CheckBranchChangesStrategy.java | 108 --- .../strategy/CheckoutAndPullStrategy.java | 132 ---- .../internal/strategy/GitCommandStrategy.java | 13 - .../internal/strategy/GitStrategyFactory.java | 49 -- .../biz/git/service/model/GitCommand.java | 59 -- .../system/config/WorkUtilsProperties.java | 7 + .../system/support/FileOperationHelper.java | 39 +- src/main/resources/application.yml | 1 + src/main/resources/config/damaged_repos.cache | 11 + .../resources/config/代码仓库项目路径.txt | 143 ++-- src/main/resources/mybatis-generator.xml | 6 +- 20 files changed, 798 insertions(+), 2880 deletions(-) delete mode 100644 src/main/java/com/example/workutils/biz/git/GitPlugin.java create mode 100644 src/main/java/com/example/workutils/biz/git/GitSyncPlugin.java delete mode 100644 src/main/java/com/example/workutils/biz/git/service/GitService.java delete mode 100644 src/main/java/com/example/workutils/biz/git/service/GitServiceImpl.java delete mode 100644 src/main/java/com/example/workutils/biz/git/service/config/GitOptimizationConfig.java delete mode 100644 src/main/java/com/example/workutils/biz/git/service/internal/GitCommandController.java delete mode 100644 src/main/java/com/example/workutils/biz/git/service/internal/GitCommandExecutor.java delete mode 100644 src/main/java/com/example/workutils/biz/git/service/internal/GitRepositoryManager.java delete mode 100644 src/main/java/com/example/workutils/biz/git/service/internal/GitResultCache.java delete mode 100644 src/main/java/com/example/workutils/biz/git/service/internal/strategy/CheckBranchChangesStrategy.java delete mode 100644 src/main/java/com/example/workutils/biz/git/service/internal/strategy/CheckoutAndPullStrategy.java delete mode 100644 src/main/java/com/example/workutils/biz/git/service/internal/strategy/GitCommandStrategy.java delete mode 100644 src/main/java/com/example/workutils/biz/git/service/internal/strategy/GitStrategyFactory.java delete mode 100644 src/main/java/com/example/workutils/biz/git/service/model/GitCommand.java create mode 100644 src/main/resources/config/damaged_repos.cache diff --git a/src/main/java/com/example/workutils/biz/git/GitPlugin.java b/src/main/java/com/example/workutils/biz/git/GitPlugin.java deleted file mode 100644 index ab47aae..0000000 --- a/src/main/java/com/example/workutils/biz/git/GitPlugin.java +++ /dev/null @@ -1,170 +0,0 @@ -package com.example.workutils.biz.git; - -import com.example.workutils.biz.git.service.GitService; -import com.example.workutils.biz.git.service.internal.GitCommandExecutor; -import com.example.workutils.system.plugin.PluginDescriptor; -import com.example.workutils.system.plugin.Plugin; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Component; - -import java.util.ArrayList; -import java.util.List; -import java.util.Scanner; -import java.util.stream.Collectors; - -/** - * Git仓库健康检测插件(实现 Tool 接口) - */ -@Slf4j -@Component -@RequiredArgsConstructor -public class GitPlugin implements Plugin { - - private static final PluginDescriptor DESCRIPTOR = PluginDescriptor.builder() - .id("git-health") - .name("Git仓库健康检测") - .version("1.0.0") - .description("检测和修复Git仓库健康状态") - .build(); - - private static final String[] MENU_OPTIONS = { - "1. 检测所有仓库健康状态", - "2. 修复损坏仓库", - "0. 返回上级菜单" - }; - - private final GitService gitService; - - @Override - public PluginDescriptor getDescriptor() { - return DESCRIPTOR; - } - - @Override - public String[] getMenuOptions() { - return MENU_OPTIONS; - } - - @Override - public void handleChoice(int choice, Scanner scanner) throws Exception { - switch (choice) { - case 1: checkAllReposHealth(scanner); break; - case 2: fixCorruptedRepos(scanner); break; - default: printInvalidOption(); break; - } - } - - private void checkAllReposHealth(Scanner scanner) { - printMenuTitle("开始检测所有仓库"); - log.info("开始检测所有仓库健康状态"); - - List results = gitService.checkCorruptedRepos(scanner); - - if (results.isEmpty()) { - System.out.println("所有仓库状态正常!"); - log.info("所有仓库状态正常"); - } else { - System.out.println("\n发现 " + results.size() + " 个损坏的仓库:"); - for (int i = 0; i < results.size(); i++) { - GitCommandExecutor.RepoHealthResult result = results.get(i); - System.out.printf("[%d] %s - %s%n", i + 1, - extractProjectName(result.getRepoPath()), - result.getCorruptionReason()); - } - log.info("检测完成,发现 {} 个损坏仓库", results.size()); - } - } - - private void fixCorruptedRepos(Scanner scanner) { - List corruptedRepos = gitService.listCorruptedRepos(); - - if (corruptedRepos.isEmpty()) { - System.out.println("没有损坏的仓库需要修复。"); - System.out.println("请先执行'检测所有仓库健康状态'来检测仓库。"); - return; - } - - printMenuTitle("损坏仓库列表"); - for (int i = 0; i < corruptedRepos.size(); i++) { - System.out.printf("[%d] %s%n", i + 1, corruptedRepos.get(i)); - } - - System.out.print("\n请输入要修复的仓库序号(多个用逗号分隔,如1,2,3,输入a修复全部): "); - String input = scanner.nextLine().trim(); - - List selectedIndices = parseSelectedIndices(input, corruptedRepos.size()); - - if (selectedIndices.isEmpty()) { - System.out.println("未选择任何仓库。"); - return; - } - - System.out.print("确认修复 " + selectedIndices.size() + " 个仓库?(y/n): "); - String confirm = scanner.nextLine().trim(); - if (!"y".equalsIgnoreCase(confirm)) { - System.out.println("已取消修复操作。"); - log.info("用户取消修复操作"); - return; - } - - log.info("开始修复 {} 个仓库", selectedIndices.size()); - int successCount = 0; - int failCount = 0; - for (Integer index : selectedIndices) { - String repoPath = corruptedRepos.get(index - 1); - System.out.println("\n正在修复: " + extractProjectName(repoPath)); - - boolean success = gitService.fixCorruptedRepo(repoPath); - if (success) { - System.out.println("修复成功: " + repoPath); - successCount++; - } else { - System.out.println("修复失败: " + repoPath); - failCount++; - } - } - - printMenuTitle("修复完成"); - System.out.println("成功: " + successCount); - System.out.println("失败: " + failCount); - log.info("修复完成,成功: {},失败: {}", successCount, failCount); - } - - private List parseSelectedIndices(String input, int repoCount) { - if ("a".equalsIgnoreCase(input)) { - List indices = new ArrayList<>(); - for (int i = 1; i <= repoCount; i++) { - indices.add(i); - } - return indices; - } - return parseCommaSeparatedInput(input).stream() - .map(s -> { try { return Integer.parseInt(s); } catch (NumberFormatException e) { return -1; } }) - .filter(i -> i >= 1 && i <= repoCount) - .collect(Collectors.toList()); - } - - private List parseCommaSeparatedInput(String input) { - List result = new ArrayList<>(); - if (input == null || input.trim().isEmpty()) { - return result; - } - String[] parts = input.split(","); - for (String part : parts) { - String trimmed = part.trim(); - if (!trimmed.isEmpty()) { - result.add(trimmed); - } - } - return result; - } - - private String extractProjectName(String projectPath) { - if (projectPath == null || projectPath.isEmpty()) { - return "unknown"; - } - int lastSep = Math.max(projectPath.lastIndexOf('/'), projectPath.lastIndexOf('\\')); - return lastSep >= 0 ? projectPath.substring(lastSep + 1) : projectPath; - } -} diff --git a/src/main/java/com/example/workutils/biz/git/GitSyncPlugin.java b/src/main/java/com/example/workutils/biz/git/GitSyncPlugin.java new file mode 100644 index 0000000..97319c5 --- /dev/null +++ b/src/main/java/com/example/workutils/biz/git/GitSyncPlugin.java @@ -0,0 +1,679 @@ +package com.example.workutils.biz.git; + +import com.example.workutils.system.config.WorkUtilsProperties; +import com.example.workutils.system.plugin.Plugin; +import com.example.workutils.system.plugin.PluginDescriptor; +import com.example.workutils.system.support.FileOperationHelper; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.File; +import java.io.IOException; +import java.io.InputStreamReader; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Scanner; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +import org.springframework.beans.factory.InitializingBean; + +/** + * Git 仓库同步插件 + *

+ * 功能: + * 1. 将所有配置的仓库切换到指定分支 + 拉取最新代码 + * 2. 切换/拉取失败的仓库标记为损坏,列出后由用户确认再删除重拉 + */ +@Slf4j +@Component +@RequiredArgsConstructor +public class GitSyncPlugin implements Plugin, InitializingBean { + + private static final PluginDescriptor DESCRIPTOR = PluginDescriptor.builder() + .id("git-sync") + .name("Git仓库同步") + .version("1.0.0") + .description("切换分支 + 拉取最新代码,损坏确认后重拉") + .build(); + + private static final String[] MENU_OPTIONS = { + "1. 同步所有项目(切换分支 + 拉取)", + "2. 处理已损坏项目", + "0. 返回上级菜单" + }; + + private static final String DEFAULT_BRANCH = "master"; + private static final long CMD_TIMEOUT_SECONDS = 120; + private static final int PARALLEL = 6; // 并发数 + + private final FileOperationHelper fileOperationHelper; + private final WorkUtilsProperties properties; + + /** 缓存上一次扫描出的损坏仓库,供菜单选项 2 使用 */ + private List lastDamagedRepos; + /** 缓存上一次使用的目标分支 */ + private String lastBranch; + + // ==================== 启动时从日志恢复缓存 ==================== + + @Override + public void afterPropertiesSet() { + loadDamagedRepos(); + } + + private void loadDamagedRepos() { + Path file = properties.getDamagedReposFilePath(); + if (!Files.exists(file)) { + return; + } + try { + List lines = Files.readAllLines(file, java.nio.charset.StandardCharsets.UTF_8); + if (lines.isEmpty()) { + return; + } + // 第一行: branch: + String branch = DEFAULT_BRANCH; + int startIdx = 0; + if (lines.get(0).startsWith("branch:")) { + branch = lines.get(0).substring(7); + startIdx = 1; + } + List repos = new ArrayList<>(); + for (int i = startIdx; i < lines.size(); i++) { + String line = lines.get(i).trim(); + if (line.isEmpty()) { + continue; + } + // 格式: name|path|branch|error|remoteUrl + String[] parts = line.split("\\|", 5); + if (parts.length >= 4) { + String remoteUrl = parts.length > 4 ? parts[4] : null; + if ("null".equals(remoteUrl)) remoteUrl = null; + repos.add(new DamagedRepo(parts[1], parts[0], parts[2], parts[3], remoteUrl)); + } + } + if (!repos.isEmpty()) { + this.lastDamagedRepos = repos; + this.lastBranch = branch; + log.info("从缓存文件恢复了 {} 个损坏仓库(分支: {})", repos.size(), branch); + } + } catch (IOException e) { + log.warn("读取损坏仓库缓存文件失败: {}", e.getMessage()); + } + } + + private void saveDamagedRepos() { + Path file = properties.getDamagedReposFilePath(); + try { + Files.createDirectories(file.getParent()); + try (BufferedWriter w = Files.newBufferedWriter(file, java.nio.charset.StandardCharsets.UTF_8)) { + w.write("branch:" + (lastBranch != null ? lastBranch : DEFAULT_BRANCH)); + w.newLine(); + if (lastDamagedRepos != null) { + for (DamagedRepo dr : lastDamagedRepos) { + // 格式: name|path|branch|error|remoteUrl + w.write(dr.name + "|" + dr.path + "|" + dr.branch + "|" + + (dr.error != null ? dr.error : "") + "|" + + (dr.remoteUrl != null ? dr.remoteUrl : "null")); + w.newLine(); + } + } + } + log.info("损坏仓库缓存已保存: {} 个", lastDamagedRepos != null ? lastDamagedRepos.size() : 0); + } catch (IOException e) { + log.error("保存损坏仓库缓存失败: {}", e.getMessage(), e); + } + } + + @Override + public PluginDescriptor getDescriptor() { + return DESCRIPTOR; + } + + @Override + public String[] getMenuOptions() { + return MENU_OPTIONS; + } + + @Override + public void handleChoice(int choice, Scanner scanner) throws Exception { + switch (choice) { + case 1: + syncAll(scanner); + break; + case 2: + handleDamaged(scanner); + break; + default: + printInvalidOption(); + } + } + + // ==================== 主流程 ==================== + + private void syncAll(Scanner scanner) { + // 1. 读项目列表 + List projects; + try { + projects = fileOperationHelper.getProjectPathsWithStatus(); + } catch (IOException e) { + log.error("读取项目路径配置文件失败", e); + printError("读取项目路径配置文件失败: " + e.getMessage()); + return; + } + if (projects.isEmpty()) { + System.out.println("未配置任何项目路径,请在 " + properties.getProjectPathsFilePath() + " 中添加。"); + return; + } + + // 2. 选择分支 + System.out.print("请输入目标分支名(默认 " + DEFAULT_BRANCH + "): "); + String branch = scanner.nextLine().trim(); + if (branch.isEmpty()) { + branch = DEFAULT_BRANCH; + } + + System.out.println("\n========== 阶段一:切换分支 + 拉取(并行 " + PARALLEL + " 线程) =========="); + System.out.println("目标分支: " + branch); + System.out.println("项目总数: " + projects.size()); + + // 2.5 从项目路径文件提取 BASE_URL 映射(仓库损坏时兜底用) + Map baseUrlMap = new LinkedHashMap<>(); + for (FileOperationHelper.ProjectPathInfo proj : projects) { + if (proj.getRemoteBaseUrl() != null && !proj.getRemoteBaseUrl().isEmpty()) { + String url = proj.getRemoteBaseUrl(); + if (!url.endsWith("/")) { + url = url + "/"; + } + baseUrlMap.put(normalizePath(proj.getPath()), url); + } + } + log.info("从项目路径文件加载了 {} 条远程URL映射", baseUrlMap.size()); + + // 3. 并行切换+拉取 + int total = projects.size(); + Map resultMap = new LinkedHashMap<>(); + AtomicInteger completed = new AtomicInteger(0); + CountDownLatch latch = new CountDownLatch(total); + ExecutorService pool = Executors.newFixedThreadPool(PARALLEL); + long startTime = System.currentTimeMillis(); + + for (int i = 0; i < total; i++) { + final FileOperationHelper.ProjectPathInfo proj = projects.get(i); + final String repoPath = proj.getPath(); + final String repoName = extractName(repoPath); + final String targetBranch = branch; + + pool.submit(() -> { + try { + SyncResult result = syncOneRepo(repoPath, targetBranch, baseUrlMap); + resultMap.put(repoPath, result); + int done = completed.incrementAndGet(); + String tag; + switch (result) { + case OK: tag = "成功"; break; + case DAMAGED: tag = "损坏"; break; + default: tag = "失败"; break; + } + System.out.printf("[%d/%d] %s >> %s%n", done, total, repoName, tag); + } catch (Exception e) { + SyncResult r = SyncResult.FAILED; + r.error = e.getMessage(); + resultMap.put(repoPath, r); + int done = completed.incrementAndGet(); + System.out.printf("[%d/%d] %s >> 异常: %s%n", done, total, repoName, e.getMessage()); + } finally { + latch.countDown(); + } + }); + } + + pool.shutdown(); + try { + latch.await(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + pool.shutdownNow(); + printError("并行处理被中断"); + return; + } + + // 4. 汇总结果(保持与原项目顺序一致) + int success = 0, failed = 0; + List damagedRepos = new ArrayList<>(); + + for (FileOperationHelper.ProjectPathInfo proj : projects) { + SyncResult result = resultMap.get(proj.getPath()); + if (result == null) { + failed++; + continue; + } + switch (result) { + case OK: + success++; + break; + case DAMAGED: { + String err = result.error != null ? result.error : "未知错误"; + damagedRepos.add(new DamagedRepo(proj.getPath(), extractName(proj.getPath()), + branch, err, result.remoteUrl)); + break; + } + case FAILED: + failed++; + break; + } + } + + long elapsed = (System.currentTimeMillis() - startTime) / 1000; + System.out.println("\n---------- 阶段一完成 ----------"); + System.out.printf("总计: %d | 成功: %d | 损坏: %d | 失败: %d | 耗时: %ds%n", + projects.size(), success, damagedRepos.size(), failed, elapsed); + + // 缓存损坏仓库,供菜单选项 2 使用 + this.lastDamagedRepos = damagedRepos; + this.lastBranch = branch; + + // 持久化到日志文件,重启不丢失 + saveDamagedRepos(); + + // 4. 有损坏仓库?进入确认阶段 + if (damagedRepos.isEmpty()) { + System.out.println("\n全部同步成功!"); + return; + } + + processDamaged(scanner); + } + + // ==================== 处理损坏仓库(共享,选项1阶段二 & 选项2) ==================== + + private void handleDamaged(Scanner scanner) { + if (lastDamagedRepos == null || lastDamagedRepos.isEmpty()) { + System.out.println("\n暂无损坏仓库记录。请先执行「同步所有项目」。"); + return; + } + System.out.println("\n使用上次扫描结果(分支: " + lastBranch + + ",损坏: " + lastDamagedRepos.size() + " 个)"); + processDamaged(scanner); + } + + private void processDamaged(Scanner scanner) { + // 从项目路径文件重建 BASE_URL 映射(用于拼接 clone URL) + Map baseUrlMap = buildBaseUrlMap(); + + System.out.println("\n========== 处理损坏仓库 =========="); + System.out.println("以下 " + lastDamagedRepos.size() + " 个仓库无法正常切换/拉取:"); + System.out.println(); + for (int i = 0; i < lastDamagedRepos.size(); i++) { + DamagedRepo dr = lastDamagedRepos.get(i); + String cloneUrl = resolveCloneUrl(dr, baseUrlMap); + System.out.printf(" [%d] %s%n", i + 1, dr.name); + System.out.printf(" 路径: %s%n", dr.path); + System.out.printf(" 错误: %s%n", dr.error); + if (cloneUrl != null) { + System.out.printf(" 远程: %s%n", cloneUrl); + } else { + System.out.println(" 远程: (无法获取)"); + } + } + + System.out.println(); + System.out.println("选择处理方式:"); + System.out.println(" 1 - 逐个选择 确认 是否重新拉取(1重新拉,2跳过,0返回上一级)"); + System.out.println(" 2 - 全部删除重拉(删除本地 -> git clone)"); + System.out.println(" 0 - 跳过,不处理"); + System.out.print("请输入: "); + String choice = scanner.nextLine().trim(); + + if ("0".equals(choice)) { + System.out.println("已跳过,损坏仓库保持不变(可通过菜单选项 2 再次处理)。"); + return; + } + + int recloned = 0, recloneFailed = 0; + long startTime = System.currentTimeMillis(); + + if ("2".equals(choice)) { + // 全部删除重拉 + for (DamagedRepo dr : lastDamagedRepos) { + String cloneUrl = resolveCloneUrl(dr, baseUrlMap); + if (cloneUrl == null) { + System.out.printf(" [跳过] %s - 无法获取远程URL%n", dr.name); + recloneFailed++; + continue; + } + System.out.printf("%n 重拉 %s ...%n", dr.name); + if (doReclone(dr, cloneUrl)) { + recloned++; + } else { + recloneFailed++; + } + } + } else if ("1".equals(choice)) { + // 逐个选择 + for (int i = 0; i < lastDamagedRepos.size(); i++) { + DamagedRepo dr = lastDamagedRepos.get(i); + String cloneUrl = resolveCloneUrl(dr, baseUrlMap); + System.out.printf("%n [%d/%d] %s [%s]%n", i + 1, lastDamagedRepos.size(), dr.name, dr.path); + if (cloneUrl != null) { + System.out.printf(" 远程: %s%n", cloneUrl); + } else { + System.out.println(" 远程: (无法获取)"); + } + System.out.println(" 1 - 重新拉取 2 - 跳过 0 - 返回上一级"); + System.out.print(" 请选择: "); + + String sub = scanner.nextLine().trim(); + if ("0".equals(sub)) { + System.out.println(" 已返回。"); + break; + } + if ("1".equals(sub)) { + if (cloneUrl == null) { + System.out.println(" [跳过] 无法获取远程URL,无法重新拉取"); + recloneFailed++; + continue; + } + if (doReclone(dr, cloneUrl)) { + recloned++; + } else { + recloneFailed++; + } + } else { + // 2 或其他输入视为跳过 + System.out.println(" 已跳过。"); + } + } + } + + long elapsed2 = (System.currentTimeMillis() - startTime) / 1000; + System.out.println("\n---------- 处理完成 ----------"); + System.out.printf("重拉成功: %d | 重拉失败: %d | 耗时: %ds%n", recloned, recloneFailed, elapsed2); + + // 处理完成后重新保存缓存(若有重拉成功,下次全扫会确认状态) + saveDamagedRepos(); + } + + /** 从项目路径文件重建 BASE_URL 映射 */ + private Map buildBaseUrlMap() { + Map map = new LinkedHashMap<>(); + try { + List projects = fileOperationHelper.getProjectPathsWithStatus(); + for (FileOperationHelper.ProjectPathInfo proj : projects) { + if (proj.getRemoteBaseUrl() != null && !proj.getRemoteBaseUrl().isEmpty()) { + String url = proj.getRemoteBaseUrl(); + if (!url.endsWith("/")) { + url = url + "/"; + } + map.put(normalizePath(proj.getPath()), url); + } + } + } catch (IOException e) { + log.error("读取项目路径文件失败", e); + } + return map; + } + + /** 解析 clone URL:优先从项目路径文件的 BASE_URL 拼接,其次用缓存的 remoteUrl */ + private String resolveCloneUrl(DamagedRepo dr, Map baseUrlMap) { + // 1. 从项目路径文件的 BASE_URL 拼接(项目路径文件为准) + if (baseUrlMap != null) { + String normalizedPath = normalizePath(dr.path); + String baseUrl = baseUrlMap.get(normalizedPath); + if (baseUrl != null) { + String repoName = extractName(dr.path); + return baseUrl + repoName; + } + } + // 2. 兜底:缓存的 remoteUrl + if (dr.remoteUrl != null && !dr.remoteUrl.isEmpty()) { + return dr.remoteUrl; + } + return null; + } + + // ==================== 单仓库同步核心逻辑(不自动重拉) ==================== + + private SyncResult syncOneRepo(String repoPath, String branch, Map baseUrlMap) { + File repoDir = new File(repoPath); + if (!repoDir.exists() || !new File(repoDir, ".git").exists()) { + SyncResult r = SyncResult.DAMAGED; + r.error = "目录不存在或无 .git"; + r.remoteUrl = getRemoteUrl(repoPath, baseUrlMap); + return r; + } + + // 1. fetch + CmdResult r1 = runGit(repoPath, "git fetch origin " + branch); + if (!r1.ok) { + log.warn("fetch 失败: {} -> {}", repoPath, r1.error); + } + + // 2. checkout + CmdResult r2 = runGit(repoPath, "git checkout " + branch); + if (!r2.ok) { + log.warn("checkout 失败: {} -> {},标记为损坏", repoPath, r2.error); + SyncResult r = SyncResult.DAMAGED; + r.error = "checkout 失败: " + r2.error; + r.remoteUrl = getRemoteUrl(repoPath, baseUrlMap); + return r; + } + + // 3. pull + CmdResult r3 = runGit(repoPath, "git pull origin " + branch); + if (!r3.ok) { + log.warn("pull 失败: {} -> {},标记为损坏", repoPath, r3.error); + SyncResult r = SyncResult.DAMAGED; + r.error = "pull 失败: " + r3.error; + r.remoteUrl = getRemoteUrl(repoPath, baseUrlMap); + return r; + } + + SyncResult result = SyncResult.OK; + result.output = r3.output; + return result; + } + + // ==================== 重拉(clone)逻辑 ==================== + + private boolean doReclone(DamagedRepo dr, String cloneUrl) { + String repoPath = dr.path; + String branch = dr.branch; + + // 删除旧目录 + File dir = new File(repoPath); + if (dir.exists()) { + try { + deleteRecursive(dir.toPath()); + log.info("已删除仓库: {}", repoPath); + System.out.println(" 已删除旧目录"); + } catch (IOException e) { + System.err.println(" 删除失败: " + e.getMessage()); + return false; + } + } + + // clone + String parentDir = dir.getParent(); + System.out.println(" clone URL: " + cloneUrl); + CmdResult cr = runGit(parentDir, + "git clone -b " + branch + " " + cloneUrl + " \"" + repoPath + "\""); + + if (cr.ok) { + System.out.println(" clone 成功"); + return true; + } else { + System.err.println(" clone 失败: " + cr.error); + return false; + } + } + + // ==================== Git 命令执行 ==================== + + private CmdResult runGit(String workingDir, String command) { + CmdResult result = new CmdResult(); + try { + ProcessBuilder pb = isWindows() + ? new ProcessBuilder("cmd.exe", "/c", command) + : new ProcessBuilder("bash", "-c", command); + + if (workingDir != null) { + pb.directory(new File(workingDir)); + } + pb.redirectErrorStream(true); + + Process process = pb.start(); + StringBuilder output = new StringBuilder(); + try (BufferedReader reader = new BufferedReader( + new InputStreamReader(process.getInputStream(), java.nio.charset.StandardCharsets.UTF_8))) { + String line; + while ((line = reader.readLine()) != null) { + output.append(line).append("\n"); + } + } + + boolean finished = process.waitFor(CMD_TIMEOUT_SECONDS, TimeUnit.SECONDS); + if (!finished) { + process.destroyForcibly(); + result.ok = false; + result.error = "命令超时"; + return result; + } + + int exitCode = process.exitValue(); + result.ok = (exitCode == 0); + result.output = output.toString().trim(); + if (!result.ok) { + String detail = output.toString().trim(); + result.error = detail.isEmpty() + ? "exit code " + exitCode + : "exit code " + exitCode + ": " + detail; + } + } catch (Exception e) { + result.ok = false; + result.error = e.getMessage(); + } + return result; + } + + private String getRemoteUrl(String repoPath, Map baseUrlMap) { + // 1. 尝试 git remote get-url + CmdResult r = runGit(repoPath, "git remote get-url origin"); + if (r.ok && !r.output.isEmpty()) { + return r.output.trim(); + } + // 2. 备用: 从 git config 文件直接读 + File gitConfig = new File(repoPath, ".git/config"); + if (gitConfig.exists()) { + try { + for (String line : Files.readAllLines(gitConfig.toPath())) { + line = line.trim(); + if (line.startsWith("url =")) { + return line.substring(5).trim(); + } + } + } catch (IOException ignored) { + } + } + // 3. 兜底: 从项目路径文件中的 BASE_URL 拼接完整 clone URL + // 格式: 项目路径文件 key=仓库路径, value=BASE_URL; clone URL = BASE_URL + cf/ + 仓库名 + if (baseUrlMap != null) { + String normalizedPath = normalizePath(repoPath); + String baseUrl = baseUrlMap.get(normalizedPath); + if (baseUrl != null) { + String repoName = extractName(repoPath); + String fullUrl = baseUrl + repoName; + log.info("从项目路径文件拼接远程URL: {} -> {}", repoPath, fullUrl); + return fullUrl; + } + } + return null; + } + + // ==================== 工具方法 ==================== + + private void deleteRecursive(Path path) throws IOException { + if (Files.isDirectory(path)) { + File[] files = path.toFile().listFiles(); + if (files != null) { + for (File f : files) { + // Windows 下 .git/objects 文件带只读属性,先去掉 + if (!f.canWrite()) { + f.setWritable(true); + } + deleteRecursive(f.toPath()); + } + } + } + // 目录/文件本身也可能只读 + File file = path.toFile(); + if (!file.canWrite()) { + file.setWritable(true); + } + Files.deleteIfExists(path); + } + + private String extractName(String path) { + if (path == null || path.isEmpty()) return "unknown"; + path = normalizePath(path); + int idx = path.lastIndexOf('/'); + return idx >= 0 ? path.substring(idx + 1) : path; + } + + /** 路径规范化:统一用 / 分隔、小写,用于映射表匹配 */ + private String normalizePath(String path) { + if (path == null || path.isEmpty()) return ""; + return path.replace('\\', '/').toLowerCase(); + } + + private boolean isWindows() { + return System.getProperty("os.name").toLowerCase().contains("win"); + } + + // ==================== 内部类 ==================== + + private enum SyncResult { + OK, DAMAGED, FAILED; + String output; + String error; + String remoteUrl; + } + + /** 损坏仓库信息,供阶段二确认用 */ + private static class DamagedRepo { + final String path; + final String name; + final String branch; + final String error; + final String remoteUrl; + + DamagedRepo(String path, String name, String branch, String error, String remoteUrl) { + this.path = path; + this.name = name; + this.branch = branch; + this.error = error; + this.remoteUrl = remoteUrl; + } + + boolean hasRemoteUrl() { + return remoteUrl != null && !remoteUrl.isEmpty(); + } + } + + private static class CmdResult { + boolean ok; + String output = ""; + String error; + } +} diff --git a/src/main/java/com/example/workutils/biz/git/service/GitService.java b/src/main/java/com/example/workutils/biz/git/service/GitService.java deleted file mode 100644 index 98c7f21..0000000 --- a/src/main/java/com/example/workutils/biz/git/service/GitService.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.example.workutils.biz.git.service; - -import com.example.workutils.biz.git.service.internal.GitCommandExecutor; - -import java.util.List; -import java.util.Scanner; - -/** - * Git 服务接口 - */ -public interface GitService { - - void checkBranchChanges(String repoRoot, String branchPairs); - - void checkoutAndPull(String repoRoot, String targetBranch); - - List checkCorruptedRepos(); - - List checkCorruptedRepos(Scanner scanner); - - boolean fixCorruptedRepo(String repoPath); - - List listCorruptedRepos(); -} diff --git a/src/main/java/com/example/workutils/biz/git/service/GitServiceImpl.java b/src/main/java/com/example/workutils/biz/git/service/GitServiceImpl.java deleted file mode 100644 index b660f9a..0000000 --- a/src/main/java/com/example/workutils/biz/git/service/GitServiceImpl.java +++ /dev/null @@ -1,169 +0,0 @@ -package com.example.workutils.biz.git.service; - -import com.example.workutils.biz.git.service.internal.GitCommandExecutor; -import com.example.workutils.biz.git.service.internal.strategy.GitStrategyFactory; -import com.example.workutils.biz.git.service.model.GitCommand; -import com.example.workutils.system.support.FileOperationHelper; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; -import java.util.Scanner; -import java.util.stream.Collectors; - -/** - * Git 服务实现类 - 使用策略模式 - */ -@Service -@RequiredArgsConstructor -@Slf4j -public class GitServiceImpl implements GitService { - - private final GitStrategyFactory strategyFactory; - - @Autowired - private FileOperationHelper fileOperationHelper; - - private static final String DEFAULT_BRANCH = "master"; - - @Override - public void checkBranchChanges(String repoRoot, String branchPairs) { - GitCommand commandDTO = GitCommand.create(repoRoot, branchPairs, - GitCommand.GitCommandType.CHECK_BRANCH_CHANGES); - strategyFactory.executeCommand(commandDTO); - } - - @Override - public void checkoutAndPull(String repoRoot, String targetBranch) { - GitCommand commandDTO = GitCommand.create(repoRoot, targetBranch, - GitCommand.GitCommandType.CHECKOUT_AND_PULL); - strategyFactory.executeCommand(commandDTO); - } - - @Override - public List checkCorruptedRepos() { - return checkCorruptedRepos(null); - } - - @Override - public List checkCorruptedRepos(Scanner scanner) { - List corruptedRepos = new ArrayList<>(); - List projects; - - try { - projects = fileOperationHelper.getProjectPathsWithStatus(); - } catch (IOException e) { - log.error("读取项目路径配置文件失败", e); - return corruptedRepos; - } - - log.info("开始检测 {} 个仓库的健康状态...", projects.size()); - System.out.println("\n========== 开始检测仓库健康状态 =========="); - - int checkedCount = 0; - int totalCount = projects.size(); - boolean isAborted = false; - - for (FileOperationHelper.ProjectPathInfo project : projects) { - String repoPath = project.getPath(); - String repoName = extractProjectName(repoPath); - - log.info("检测仓库: {}", repoPath); - System.out.printf("[%d/%d] 检测: %s ...%n", checkedCount + 1, totalCount, repoName); - - GitCommandExecutor.RepoHealthResult healthResult = GitCommandExecutor.checkRepoHealth(repoPath); - checkedCount++; - - if (healthResult.isHealthy()) { - try { - fileOperationHelper.updateProjectStatus(repoPath, FileOperationHelper.ProjectStatus.NORMAL); - System.out.printf("[%d/%d] %s - 正常%n", checkedCount, totalCount, repoName); - } catch (IOException e) { - log.warn("更新配置文件失败: {}", repoPath, e); - } - } else { - corruptedRepos.add(healthResult); - try { - fileOperationHelper.updateProjectStatus(repoPath, FileOperationHelper.ProjectStatus.CORRUPTED); - System.out.printf("[%d/%d] %s - 损坏: %s%n", checkedCount, totalCount, repoName, healthResult.getCorruptionReason()); - } catch (IOException e) { - log.warn("更新配置文件失败: {}", repoPath, e); - } - } - - try { - if (System.in.available() > 0) { - int ch = System.in.read(); - char inputChar = (char) ch; - if (inputChar == 'a' || inputChar == 'A') { - System.out.println("\n>> 用户中止操作"); - isAborted = true; - break; - } else { - while (System.in.available() > 0) { System.in.read(); } - } - } - } catch (Exception e) { - // 忽略输入检测异常 - } - } - - log.info("检测完成,共 {} 个仓库,损坏 {} 个", checkedCount, corruptedRepos.size()); - System.out.println("========== 检测完成 =========="); - System.out.println("总计: " + checkedCount + " 个仓库,损坏: " + corruptedRepos.size() + " 个"); - - return corruptedRepos; - } - - private String extractProjectName(String projectPath) { - if (projectPath == null || projectPath.isEmpty()) { - return "unknown"; - } - int lastSep = Math.max(projectPath.lastIndexOf('/'), projectPath.lastIndexOf('\\')); - return lastSep >= 0 ? projectPath.substring(lastSep + 1) : projectPath; - } - - @Override - public boolean fixCorruptedRepo(String repoPath) { - log.info("开始修复损坏的仓库: {}", repoPath); - - String remoteUrl = GitCommandExecutor.getRemoteUrl(repoPath); - if (remoteUrl == null) { - log.error("无法获取远程仓库URL: {}", repoPath); - return false; - } - log.info("远程仓库URL: {}", remoteUrl); - - GitCommandExecutor.CommandResult result = GitCommandExecutor.cloneRepo(remoteUrl, repoPath, DEFAULT_BRANCH); - - if (result.isSuccess()) { - log.info("仓库修复成功: {}", repoPath); - try { - fileOperationHelper.updateProjectStatus(repoPath, FileOperationHelper.ProjectStatus.NORMAL); - } catch (IOException e) { - log.warn("更新配置文件失败: {}", repoPath, e); - } - return true; - } else { - log.error("仓库修复失败: {} - {}", repoPath, result.getErrorMessage()); - return false; - } - } - - @Override - public List listCorruptedRepos() { - try { - return fileOperationHelper.getProjectPathsWithStatus().stream() - .filter(p -> p.getStatus() == FileOperationHelper.ProjectStatus.CORRUPTED) - .map(FileOperationHelper.ProjectPathInfo::getPath) - .collect(Collectors.toList()); - } catch (IOException e) { - log.error("读取损坏仓库列表失败", e); - return new ArrayList<>(); - } - } -} diff --git a/src/main/java/com/example/workutils/biz/git/service/config/GitOptimizationConfig.java b/src/main/java/com/example/workutils/biz/git/service/config/GitOptimizationConfig.java deleted file mode 100644 index ccd382a..0000000 --- a/src/main/java/com/example/workutils/biz/git/service/config/GitOptimizationConfig.java +++ /dev/null @@ -1,272 +0,0 @@ -package com.example.workutils.biz.git.service.config; - -import lombok.Data; -import lombok.extern.slf4j.Slf4j; - -/** - * Git操作优化配置类 - * 管理所有Git相关的性能优化配置 - */ -@Data -@Slf4j -public class GitOptimizationConfig { - - // 默认配置实例 - private static GitOptimizationConfig defaultConfig = new GitOptimizationConfig(); - - // 超时配置 - private long defaultCommandTimeoutSeconds = 20; - private long minCommandTimeoutSeconds = 10; - private long maxCommandTimeoutSeconds = 300; // 5分钟 - - // 线程池配置 - private int defaultThreadPoolSize = 8; - private int maxThreadPoolSize = 32; - private int minThreadPoolSize = 1; - private boolean enableThreadPoolAutoScaling = true; - private double cpuMultiplier = 2.0; // 线程池大小 = CPU核心数 * multiplier - - // 缓存配置 - private boolean enableResultCache = true; - private long repoInfoCacheTtlMinutes = 5; - private long branchInfoCacheTtlMinutes = 3; - private long commitInfoCacheTtlMinutes = 2; - private int maxCacheEntries = 1000; - private boolean enableCacheCleanup = true; - private long cacheCleanupIntervalMinutes = 1; - - // 批处理配置 - private boolean enableBatchProcessing = true; - private int maxBatchSize = 10; // 每个仓库最多批处理的命令数 - private boolean preferBatchCommands = true; - - // Fetch优化配置 - private boolean enableSmartFetch = true; - private boolean fetchOnlyMasterByDefault = true; - private boolean enableFetchParallelism = true; - private int maxFetchParallelism = 4; - - // 性能监控配置 - private boolean enablePerformanceMonitoring = true; - private boolean logSlowOperations = true; - private long slowOperationThresholdMs = 5000; // 超过5秒的操作记录为慢操作 - private boolean enableDetailedLogging = false; - - // 网络配置 - private int maxRetries = 3; - private long retryDelayMs = 1000; - private boolean enableRetryWithBackoff = true; - private double backoffMultiplier = 2.0; - - // 仓库大小计算配置 - private boolean enableRepoSizeBasedTimeout = true; - private long timeoutIncreasePer100MbSeconds = 10; - private long maxRepoSizeForTimeoutCalculationMb = 1024; // 1GB - - /** - * 创建默认配置 - */ - public static GitOptimizationConfig createDefault() { - return new GitOptimizationConfig(); - } - - /** - * 创建高性能配置(适用于CI/CD等高频场景) - */ - public static GitOptimizationConfig createHighPerformance() { - GitOptimizationConfig config = new GitOptimizationConfig(); - - // 更大的线程池 - config.setDefaultThreadPoolSize(16); - config.setMaxThreadPoolSize(64); - config.setCpuMultiplier(4.0); - - // 更短的缓存TTL(更频繁的更新) - config.setRepoInfoCacheTtlMinutes(2); - config.setBranchInfoCacheTtlMinutes(1); - config.setCommitInfoCacheTtlMinutes(1); - - // 更大的批处理容量 - config.setMaxBatchSize(20); - - // 更激进的Fetch策略 - config.setEnableFetchParallelism(true); - config.setMaxFetchParallelism(8); - - // 更宽松的超时设置 - config.setMaxCommandTimeoutSeconds(600); // 10分钟 - - return config; - } - - /** - * 创建资源节省配置(适用于资源受限环境) - */ - public static GitOptimizationConfig createResourceConservative() { - GitOptimizationConfig config = new GitOptimizationConfig(); - - // 更小的线程池 - config.setDefaultThreadPoolSize(2); - config.setMaxThreadPoolSize(4); - config.setCpuMultiplier(1.0); - - // 更长的缓存TTL(减少网络请求) - config.setRepoInfoCacheTtlMinutes(15); - config.setBranchInfoCacheTtlMinutes(10); - config.setCommitInfoCacheTtlMinutes(5); - - // 禁用一些高级功能 - config.setEnableFetchParallelism(false); - config.setEnableDetailedLogging(false); - - // 更短的超时(快速失败) - config.setDefaultCommandTimeoutSeconds(15); - config.setMaxCommandTimeoutSeconds(60); - - return config; - } - - /** - * 验证配置的有效性 - */ - public boolean validate() { - boolean isValid = true; - - // 验证超时配置 - if (minCommandTimeoutSeconds <= 0 || maxCommandTimeoutSeconds <= minCommandTimeoutSeconds) { - log.error("Invalid timeout configuration: min={}, max={}", minCommandTimeoutSeconds, maxCommandTimeoutSeconds); - isValid = false; - } - - // 验证线程池配置 - if (minThreadPoolSize <= 0 || maxThreadPoolSize < minThreadPoolSize) { - log.error("Invalid thread pool configuration: min={}, max={}", minThreadPoolSize, maxThreadPoolSize); - isValid = false; - } - - // 验证缓存配置 - if (repoInfoCacheTtlMinutes <= 0 || branchInfoCacheTtlMinutes <= 0 || commitInfoCacheTtlMinutes <= 0) { - log.error("Invalid cache TTL configuration"); - isValid = false; - } - - // 验证批处理配置 - if (maxBatchSize <= 0) { - log.error("Invalid batch size configuration: {}", maxBatchSize); - isValid = false; - } - - return isValid; - } - - /** - * 应用配置到系统 - */ - public void apply() { - if (!validate()) { - throw new IllegalArgumentException("Invalid configuration detected"); - } - - log.info("Applying Git optimization configuration:"); - log.info("- Default timeout: {} seconds", defaultCommandTimeoutSeconds); - log.info("- Thread pool size: {} (auto-scaling: {})", defaultThreadPoolSize, enableThreadPoolAutoScaling); - log.info("- Cache enabled: {} (TTL: {}/{}/{} minutes)", enableResultCache, - repoInfoCacheTtlMinutes, branchInfoCacheTtlMinutes, commitInfoCacheTtlMinutes); - log.info("- Batch processing: {} (max batch size: {})", enableBatchProcessing, maxBatchSize); - log.info("- Smart fetch: {}", enableSmartFetch); - - // 这里可以调用实际的配置应用逻辑 - // 例如更新GitCommandExecutor和GitResultCache的配置 - } - - /** - * 获取配置摘要 - */ - public String getSummary() { - return String.format( - "GitOptimizationConfig{timeout=%ds, threadPool=%d, cache=%s, batch=%s, smartFetch=%s}", - defaultCommandTimeoutSeconds, - defaultThreadPoolSize, - enableResultCache ? "enabled" : "disabled", - enableBatchProcessing ? "enabled" : "disabled", - enableSmartFetch ? "enabled" : "disabled" - ); - } - - /** - * 复制配置 - */ - public GitOptimizationConfig copy() { - GitOptimizationConfig copy = new GitOptimizationConfig(); - - // 超时配置 - copy.setDefaultCommandTimeoutSeconds(this.defaultCommandTimeoutSeconds); - copy.setMinCommandTimeoutSeconds(this.minCommandTimeoutSeconds); - copy.setMaxCommandTimeoutSeconds(this.maxCommandTimeoutSeconds); - - // 线程池配置 - copy.setDefaultThreadPoolSize(this.defaultThreadPoolSize); - copy.setMaxThreadPoolSize(this.maxThreadPoolSize); - copy.setMinThreadPoolSize(this.minThreadPoolSize); - copy.setEnableThreadPoolAutoScaling(this.enableThreadPoolAutoScaling); - copy.setCpuMultiplier(this.cpuMultiplier); - - // 缓存配置 - copy.setEnableResultCache(this.enableResultCache); - copy.setRepoInfoCacheTtlMinutes(this.repoInfoCacheTtlMinutes); - copy.setBranchInfoCacheTtlMinutes(this.branchInfoCacheTtlMinutes); - copy.setCommitInfoCacheTtlMinutes(this.commitInfoCacheTtlMinutes); - copy.setMaxCacheEntries(this.maxCacheEntries); - copy.setEnableCacheCleanup(this.enableCacheCleanup); - copy.setCacheCleanupIntervalMinutes(this.cacheCleanupIntervalMinutes); - - // 批处理配置 - copy.setEnableBatchProcessing(this.enableBatchProcessing); - copy.setMaxBatchSize(this.maxBatchSize); - copy.setPreferBatchCommands(this.preferBatchCommands); - - // Fetch优化配置 - copy.setEnableSmartFetch(this.enableSmartFetch); - copy.setFetchOnlyMasterByDefault(this.fetchOnlyMasterByDefault); - copy.setEnableFetchParallelism(this.enableFetchParallelism); - copy.setMaxFetchParallelism(this.maxFetchParallelism); - - // 性能监控配置 - copy.setEnablePerformanceMonitoring(this.enablePerformanceMonitoring); - copy.setLogSlowOperations(this.logSlowOperations); - copy.setSlowOperationThresholdMs(this.slowOperationThresholdMs); - copy.setEnableDetailedLogging(this.enableDetailedLogging); - - // 网络配置 - copy.setMaxRetries(this.maxRetries); - copy.setRetryDelayMs(this.retryDelayMs); - copy.setEnableRetryWithBackoff(this.enableRetryWithBackoff); - copy.setBackoffMultiplier(this.backoffMultiplier); - - // 仓库大小计算配置 - copy.setEnableRepoSizeBasedTimeout(this.enableRepoSizeBasedTimeout); - copy.setTimeoutIncreasePer100MbSeconds(this.timeoutIncreasePer100MbSeconds); - copy.setMaxRepoSizeForTimeoutCalculationMb(this.maxRepoSizeForTimeoutCalculationMb); - - return copy; - } - - /** - * 获取默认配置 - */ - public static GitOptimizationConfig getDefault() { - return defaultConfig; - } - - /** - * 设置默认配置 - */ - public static void setDefault(GitOptimizationConfig config) { - if (config != null && config.validate()) { - defaultConfig = config.copy(); - log.info("Default Git optimization configuration updated: {}", config.getSummary()); - } else { - log.error("Failed to set default configuration - invalid config provided"); - } - } -} \ No newline at end of file diff --git a/src/main/java/com/example/workutils/biz/git/service/internal/GitCommandController.java b/src/main/java/com/example/workutils/biz/git/service/internal/GitCommandController.java deleted file mode 100644 index 2634cc5..0000000 --- a/src/main/java/com/example/workutils/biz/git/service/internal/GitCommandController.java +++ /dev/null @@ -1,317 +0,0 @@ -package com.example.workutils.biz.git.service.internal; - -import lombok.extern.slf4j.Slf4j; - -import java.util.Scanner; -import java.util.concurrent.*; -import java.util.function.Consumer; - -/** - * Git命令控制器 - 管理git命令的异步执行、跳过、中断功能 - */ -@Slf4j -public class GitCommandController { - - // 命令执行状态 - public enum CommandState { - IDLE, // 空闲 - RUNNING, // 执行中 - SKIPPED, // 已跳过 - INTERRUPTED, // 已中断 - COMPLETED // 已完成 - } - - // 用户操作类型 - public enum UserAction { - NONE, // 无操作 - SKIP, // 跳过当前 - INTERRUPT, // 中断当前 - ABORT // 中止全部 - } - - // 状态存储 - private volatile CommandState currentState = CommandState.IDLE; - private volatile UserAction pendingAction = UserAction.NONE; - private volatile Process currentProcess; - private volatile long startTime; - private volatile String currentTaskName; - - // 超时配置 - private long defaultTimeoutSeconds = 300; // 默认5分钟 - private long warningThresholdSeconds = 60; // 1分钟后开始提示 - private long checkIntervalSeconds = 5; // 每5秒检查一次用户操作 - - // 线程池 - private ExecutorService executorService; - - // 用户输入监听器 - private volatile Scanner userInputScanner; - private Thread inputListenerThread; - - public GitCommandController() { - this.executorService = Executors.newSingleThreadExecutor(); - } - - /** - * 执行可中断的命令 - * @param taskName 任务名称(用于显示) - * @param commandSupplier 执行命令的 supplier - * @param statusConsumer 状态回调(定时输出进度) - * @param scanner 用户输入扫描器(为null时不监听用户输入) - * @return 执行结果 - */ - public CompletableFuture executeInterruptible( - String taskName, - SupplierWithException commandSupplier, - Consumer statusConsumer, - Scanner scanner) { - - currentState = CommandState.RUNNING; - currentTaskName = taskName; - startTime = System.currentTimeMillis(); - pendingAction = UserAction.NONE; - this.userInputScanner = scanner; - - return CompletableFuture.supplyAsync(() -> { - try { - // 启动状态监控线程 - ScheduledExecutorService monitor = Executors.newSingleThreadScheduledExecutor(); - ScheduledFuture monitorFuture = monitor.scheduleAtFixedRate(() -> { - // 检查用户操作 - if (pendingAction == UserAction.SKIP) { - log.info("用户选择跳过任务: {}", taskName); - currentState = CommandState.SKIPPED; - if (currentProcess != null) { - currentProcess.destroy(); - } - } else if (pendingAction == UserAction.INTERRUPT || pendingAction == UserAction.ABORT) { - log.info("用户选择中断任务: {}", taskName); - currentState = CommandState.INTERRUPTED; - if (currentProcess != null) { - currentProcess.destroy(); - } - } - - // 输出状态 - long elapsed = (System.currentTimeMillis() - startTime) / 1000; - if (statusConsumer != null) { - statusConsumer.accept(String.format("[%s] %s (已耗时: %d秒)", - getStateIcon(), taskName, elapsed)); - } - }, warningThresholdSeconds, checkIntervalSeconds, TimeUnit.SECONDS); - - // 启动用户输入监听线程 - if (scanner != null) { - startInputListener(); - } - - try { - // 执行命令 - GitCommandExecutor.CommandResult result = commandSupplier.get(); - result.setSuccess(currentState != CommandState.INTERRUPTED && - currentState != CommandState.SKIPPED); - if (currentState == CommandState.SKIPPED) { - result.setErrorMessage("用户跳过"); - } else if (currentState == CommandState.INTERRUPTED) { - result.setErrorMessage("用户中断"); - } - currentState = CommandState.COMPLETED; - return result; - } finally { - stopInputListener(); - monitorFuture.cancel(true); - monitor.shutdown(); - } - - } catch (Exception e) { - log.error("命令执行异常: {}", taskName, e); - currentState = CommandState.INTERRUPTED; - GitCommandExecutor.CommandResult errorResult = new GitCommandExecutor.CommandResult(); - errorResult.setSuccess(false); - errorResult.setErrorMessage("执行异常: " + e.getMessage()); - return errorResult; - } - }, executorService); - } - - /** - * 启动用户输入监听线程 - */ - private void startInputListener() { - inputListenerThread = new Thread(() -> { - try { - while (currentState == CommandState.RUNNING && pendingAction == UserAction.NONE) { - if (userInputScanner != null && userInputScanner.hasNext()) { - String input = userInputScanner.nextLine().trim().toLowerCase(); - handleUserInput(input); - } - Thread.sleep(200); // 短暂休眠避免CPU占用过高 - } - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - } - }); - inputListenerThread.setDaemon(true); - inputListenerThread.start(); - } - - /** - * 停止用户输入监听线程 - */ - private void stopInputListener() { - if (inputListenerThread != null && inputListenerThread.isAlive()) { - inputListenerThread.interrupt(); - inputListenerThread = null; - } - } - - /** - * 处理用户输入 - */ - private void handleUserInput(String input) { - switch (input) { - case "s": - case "skip": - System.out.println("\n>> 跳过当前任务: " + currentTaskName); - pendingAction = UserAction.SKIP; - if (currentProcess != null) { - currentProcess.destroy(); - } - break; - case "i": - case "interrupt": - System.out.println("\n>> 中断当前任务: " + currentTaskName); - pendingAction = UserAction.INTERRUPT; - if (currentProcess != null) { - currentProcess.destroy(); - } - break; - case "a": - case "abort": - System.out.println("\n>> 中止所有任务"); - pendingAction = UserAction.ABORT; - if (currentProcess != null) { - currentProcess.destroy(); - } - break; - case "p": - case "progress": - long elapsed = (System.currentTimeMillis() - startTime) / 1000; - System.out.printf("\n>> 当前任务: %s | 状态: %s | 已耗时: %d秒%n", - currentTaskName, getStateIcon(), elapsed); - System.out.print("请选择操作: "); - break; - default: - break; - } - } - - /** - * 设置当前进程(供外部调用) - */ - public void setCurrentProcess(Process process) { - this.currentProcess = process; - } - - /** - * 获取当前状态 - */ - public CommandState getCurrentState() { - return currentState; - } - - /** - * 跳过当前命令 - */ - public void skip() { - pendingAction = UserAction.SKIP; - } - - /** - * 中断当前命令 - */ - public void interrupt() { - pendingAction = UserAction.INTERRUPT; - } - - /** - * 中止所有任务 - */ - public void abort() { - pendingAction = UserAction.ABORT; - if (currentProcess != null) { - currentProcess.destroy(); - } - } - - /** - * 获取已耗时(秒) - */ - public long getElapsedSeconds() { - return (System.currentTimeMillis() - startTime) / 1000; - } - - /** - * 获取状态图标 - */ - private String getStateIcon() { - switch (currentState) { - case RUNNING: return "运行中"; - case SKIPPED: return "已跳过"; - case INTERRUPTED: return "已中断"; - case COMPLETED: return "已完成"; - default: return "等待中"; - } - } - - /** - * 获取操作提示信息 - */ - public static String getOperationHint() { - return "\n请选择操作:\n" + - " - 输入 's' 或 'skip': 跳过当前命令,继续下一个\n" + - " - 输入 'i' 或 'interrupt': 中断当前命令\n" + - " - 输入 'a' 或 'abort': 中断所有任务\n" + - " - 输入 'p' 或 'progress': 查看当前进度\n" + - " - 不输入: 继续等待\n"; - } - - /** - * 重置控制器状态 - */ - public void reset() { - currentState = CommandState.IDLE; - pendingAction = UserAction.NONE; - currentProcess = null; - currentTaskName = null; - } - - /** - * 检查是否应该继续 - */ - public boolean shouldContinue() { - return pendingAction != UserAction.ABORT; - } - - /** - * 关闭线程池 - */ - public void shutdown() { - executorService.shutdown(); - try { - if (!executorService.awaitTermination(5, TimeUnit.SECONDS)) { - executorService.shutdownNow(); - } - } catch (InterruptedException e) { - executorService.shutdownNow(); - } - } - - /** - * 带异常处理的Supplier接口 - */ - @FunctionalInterface - public interface SupplierWithException { - T get() throws Exception; - } -} diff --git a/src/main/java/com/example/workutils/biz/git/service/internal/GitCommandExecutor.java b/src/main/java/com/example/workutils/biz/git/service/internal/GitCommandExecutor.java deleted file mode 100644 index 74f6713..0000000 --- a/src/main/java/com/example/workutils/biz/git/service/internal/GitCommandExecutor.java +++ /dev/null @@ -1,523 +0,0 @@ -package com.example.workutils.biz.git.service.internal; - -import lombok.extern.slf4j.Slf4j; - -import java.io.BufferedReader; -import java.io.File; -import java.io.IOException; -import java.io.InputStreamReader; -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.*; -import java.util.function.Consumer; -import java.util.stream.Collectors; - -/** - * Git 命令执行器 - */ -@Slf4j -public class GitCommandExecutor { - - private static final long DEFAULT_COMMAND_TIMEOUT_SECONDS = 20; - private static final int DEFAULT_THREAD_POOL_SIZE = 8; - private static final long MIN_COMMAND_TIMEOUT_SECONDS = 10; - private static final long MAX_COMMAND_TIMEOUT_SECONDS = 300; - - private static volatile ExecutorService executorService; - private static final Object threadPoolLock = new Object(); - - private static String normalizePath(String path) { - if (path == null || path.trim().isEmpty()) { - return path; - } - return path.replace("\\", "/"); - } - - public static CommandResult executeCommands(String repoRoot, List commands) { - String normalizedRepoRoot = normalizePath(repoRoot); - log.debug("Using normalized repo path: {}", normalizedRepoRoot); - - CommandResult result = new CommandResult(); - - for (String command : commands) { - try { - CommandResult singleResult = executeCommand(normalizedRepoRoot, command); - result.getOutputLines().addAll(singleResult.getOutputLines()); - result.getErrorLines().addAll(singleResult.getErrorLines()); - - if (!singleResult.isSuccess()) { - result.setSuccess(false); - result.setErrorMessage(singleResult.getErrorMessage()); - return result; - } - } catch (Exception e) { - log.error("Failed to execute command: {}", command, e); - result.setSuccess(false); - result.setErrorMessage("Command execution failed: " + e.getMessage()); - return result; - } - } - - result.setSuccess(true); - return result; - } - - private static CommandResult executeCommand(String workingDir, String command, Consumer processConsumer) throws IOException, InterruptedException { - String normalizedWorkingDir = normalizePath(workingDir); - log.info("Executing command in directory {}: {}", normalizedWorkingDir, command); - - ProcessBuilder processBuilder = new ProcessBuilder(); - if (isWindows()) { - processBuilder.command("cmd.exe", "/c", command); - } else { - processBuilder.command("bash", "-c", command); - } - - if (normalizedWorkingDir != null && !normalizedWorkingDir.trim().isEmpty()) { - processBuilder.directory(new File(normalizedWorkingDir)); - } - - Process process = processBuilder.start(); - - if (processConsumer != null) { - processConsumer.accept(process); - } - - CommandResult result = new CommandResult(); - - ExecutorService readerPool = Executors.newFixedThreadPool(2); - CountDownLatch latch = new CountDownLatch(2); - - CompletableFuture> outputFuture = new CompletableFuture<>(); - CompletableFuture> errorFuture = new CompletableFuture<>(); - - readerPool.submit(() -> { - try { - List lines = new ArrayList<>(); - try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) { - String line; - while ((line = reader.readLine()) != null) { - lines.add(line); - } - } - outputFuture.complete(lines); - } catch (Exception e) { - outputFuture.completeExceptionally(e); - } finally { - latch.countDown(); - } - }); - - readerPool.submit(() -> { - try { - List lines = new ArrayList<>(); - try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getErrorStream()))) { - String line; - while ((line = reader.readLine()) != null) { - lines.add(line); - } - } - errorFuture.complete(lines); - } catch (Exception e) { - errorFuture.completeExceptionally(e); - } finally { - latch.countDown(); - } - }); - - long timeout = calculateDynamicTimeout(workingDir, command); - try { - boolean finished = process.waitFor(timeout, TimeUnit.SECONDS); - - if (!finished) { - process.destroy(); - try { - Thread.sleep(2000); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - } - - if (process.isAlive()) { - process.destroyForcibly(); - throw new RuntimeException("Command timeout after " + timeout + " seconds"); - } - } - - result.setOutputLines(outputFuture.get()); - result.setErrorLines(errorFuture.get()); - - } catch (ExecutionException e) { - result.setSuccess(false); - result.setErrorMessage("Error reading output: " + e.getMessage()); - } finally { - latch.await(1, TimeUnit.SECONDS); - readerPool.shutdown(); - } - - int exitCode = process.exitValue(); - if (exitCode != 0) { - result.setSuccess(false); - result.setErrorMessage("Command failed with exit code " + exitCode); - } else { - result.setSuccess(true); - } - - return result; - } - - private static CommandResult executeCommand(String workingDir, String command) throws IOException, InterruptedException { - return executeCommand(workingDir, command, null); - } - - public static List executeGitCommandsInRepos(String reposPath, List repoNames, List gitCommands) { - return executeGitCommandsInRepos(reposPath, repoNames, gitCommands, DEFAULT_THREAD_POOL_SIZE); - } - - public static List executeGitCommandsInRepos(String reposPath, List repoNames, List gitCommands, int concurrency) { - String normalizedReposPath = normalizePath(reposPath); - int optimalPoolSize = calculateOptimalThreadPoolSize(repoNames.size(), concurrency); - log.info("Using thread pool size: {} for {} repositories", optimalPoolSize, repoNames.size()); - - ExecutorService threadPool = Executors.newFixedThreadPool(optimalPoolSize); - - try { - List> futures = repoNames.stream() - .map(repoName -> CompletableFuture.supplyAsync(() -> - executeGitCommandsForRepo(normalizedReposPath, repoName, gitCommands), threadPool)) - .collect(Collectors.toList()); - - return futures.stream() - .map(future -> { - try { - return future.get(calculateDynamicTimeout(reposPath, gitCommands.toString()), TimeUnit.SECONDS); - } catch (Exception e) { - log.error("Error executing Git command for repository", e); - RepoResult errorResult = new RepoResult("unknown", "unknown"); - errorResult.setSuccess(false); - errorResult.setErrorMessage("Execution timeout or error: " + e.getMessage()); - return errorResult; - } - }) - .collect(Collectors.toList()); - } finally { - threadPool.shutdown(); - try { - if (!threadPool.awaitTermination(60, TimeUnit.SECONDS)) { - threadPool.shutdownNow(); - } - } catch (InterruptedException e) { - threadPool.shutdownNow(); - Thread.currentThread().interrupt(); - } - } - } - - private static RepoResult executeGitCommandsForRepo(String normalizedReposPath, String repoName, List gitCommands) { - String repoPath = normalizedReposPath + "/" + repoName; - RepoResult repoResult = new RepoResult(repoName, repoPath); - - if (!new File(repoPath).exists()) { - repoResult.setSuccess(false); - repoResult.setErrorMessage("Repository directory not found: " + repoPath); - return repoResult; - } - - try { - CommandResult commandResult = executeCommands(repoPath, gitCommands); - repoResult.setSuccess(commandResult.isSuccess()); - repoResult.setOutputLines(commandResult.getOutputLines()); - repoResult.setErrorLines(commandResult.getErrorLines()); - repoResult.setErrorMessage(commandResult.getErrorMessage()); - } catch (Exception e) { - repoResult.setSuccess(false); - repoResult.setErrorMessage("Failed to execute commands: " + e.getMessage()); - } - - return repoResult; - } - - public static List executeGitCommandsInReposSequential(String reposPath, List repoNames, List gitCommands) { - List results = new ArrayList<>(); - String normalizedReposPath = normalizePath(reposPath); - for (String repoName : repoNames) { - RepoResult repoResult = executeGitCommandsForRepo(normalizedReposPath, repoName, gitCommands); - results.add(repoResult); - } - return results; - } - - private static boolean isWindows() { - return System.getProperty("os.name").toLowerCase().contains("win"); - } - - private static long calculateDynamicTimeout(String workingDir, String command) { - long baseTimeout = DEFAULT_COMMAND_TIMEOUT_SECONDS; - if (command.contains("fetch") || command.contains("clone") || command.contains("pull")) { - baseTimeout *= 2; - } else if (command.contains("merge") || command.contains("rebase")) { - baseTimeout = (long)(baseTimeout * 1.5); - } - try { - if (workingDir != null && !workingDir.isEmpty()) { - File repoDir = new File(workingDir); - if (repoDir.exists()) { - long repoSize = calculateDirectorySize(repoDir); - long sizeMultiplier = repoSize / (100 * 1024 * 1024); - if (sizeMultiplier > 0) { - baseTimeout += sizeMultiplier * 10; - } - } - } - } catch (Exception e) { - log.debug("Failed to calculate repository size for timeout", e); - } - return Math.max(MIN_COMMAND_TIMEOUT_SECONDS, Math.min(baseTimeout, MAX_COMMAND_TIMEOUT_SECONDS)); - } - - private static long calculateDirectorySize(File directory) { - long size = 0; - if (directory.isFile()) { - return directory.length(); - } - File[] files = directory.listFiles(); - if (files != null) { - for (File file : files) { - if (file.isFile()) { - size += file.length(); - } else if (file.isDirectory() && !file.getName().equals(".git")) { - size += calculateDirectorySize(file); - } - } - } - return size; - } - - private static int calculateOptimalThreadPoolSize(int repoCount, int userConcurrency) { - int cpuCores = Runtime.getRuntime().availableProcessors(); - int cpuBasedSize = cpuCores * 2; - int repoBasedSize = Math.min(repoCount, cpuBasedSize); - return Math.max(1, Math.min(userConcurrency, repoBasedSize)); - } - - // ==================== 仓库健康检测 ==================== - - public static class RepoHealthResult { - private String repoPath; - private boolean isHealthy; - private String corruptionReason; - - public RepoHealthResult(String repoPath, boolean isHealthy) { - this.repoPath = repoPath; - this.isHealthy = isHealthy; - } - - public RepoHealthResult(String repoPath, boolean isHealthy, String corruptionReason) { - this.repoPath = repoPath; - this.isHealthy = isHealthy; - this.corruptionReason = corruptionReason; - } - - public String getRepoPath() { return repoPath; } - public boolean isHealthy() { return isHealthy; } - public String getCorruptionReason() { return corruptionReason; } - } - - public static RepoHealthResult checkRepoHealth(String repoPath) { - String normalizedPath = normalizePath(repoPath); - File repoDir = new File(normalizedPath); - - if (!repoDir.exists()) { - return new RepoHealthResult(repoPath, false, "仓库目录不存在"); - } - - File gitDir = new File(normalizedPath + "/.git"); - if (!gitDir.exists() || !gitDir.isDirectory()) { - return new RepoHealthResult(repoPath, false, ".git目录缺失或损坏"); - } - - try { - CommandResult result = executeCommand(normalizedPath, "git rev-parse --git-dir"); - if (!result.isSuccess()) { - return new RepoHealthResult(repoPath, false, "git rev-parse失败: " + result.getErrorMessage()); - } - } catch (Exception e) { - return new RepoHealthResult(repoPath, false, "执行git命令失败: " + e.getMessage()); - } - - try { - CommandResult result = executeCommand(normalizedPath, "git fsck --full"); - if (!result.isSuccess()) { - String errorMsg = result.getErrorMessage(); - if (result.getErrorLines() != null && !result.getErrorLines().isEmpty()) { - return new RepoHealthResult(repoPath, false, "fsck检测失败: " + String.join("; ", result.getErrorLines())); - } - return new RepoHealthResult(repoPath, false, "fsck检测失败: " + errorMsg); - } - } catch (Exception e) { - return new RepoHealthResult(repoPath, false, "执行fsck失败: " + e.getMessage()); - } - - try { - CommandResult result = executeCommand(normalizedPath, "git status"); - if (!result.isSuccess()) { - return new RepoHealthResult(repoPath, false, "git status失败: " + result.getErrorMessage()); - } - } catch (Exception e) { - return new RepoHealthResult(repoPath, false, "执行git status失败: " + e.getMessage()); - } - - return new RepoHealthResult(repoPath, true, null); - } - - public static String getRemoteUrl(String repoPath) { - String normalizedPath = normalizePath(repoPath); - try { - CommandResult result = executeCommand(normalizedPath, "git remote -v"); - if (!result.isSuccess() || result.getOutputLines().isEmpty()) { - log.warn("获取远程仓库URL失败: {}", repoPath); - return null; - } - - for (String line : result.getOutputLines()) { - if (line.contains("(fetch)")) { - String[] parts = line.trim().split("\\s+"); - if (parts.length >= 2) { - return parts[1]; - } - } - } - - return result.getOutputLines().get(0).trim().split("\\s+")[1]; - - } catch (Exception e) { - log.error("获取远程仓库URL异常: {}", repoPath, e); - return null; - } - } - - public static CommandResult cloneRepo(String remoteUrl, String targetPath, String branch) { - String normalizedTarget = normalizePath(targetPath); - String command = String.format("git clone -b %s %s \"%s\"", branch, remoteUrl, normalizedTarget); - log.info("执行克隆命令: {}", command); - - try { - File targetDir = new File(normalizedTarget); - if (targetDir.exists()) { - deleteDirectory(targetDir); - } - - return executeCommand(new File(normalizedTarget).getParent(), command); - } catch (Exception e) { - CommandResult result = new CommandResult(); - result.setSuccess(false); - result.setErrorMessage("克隆失败: " + e.getMessage()); - return result; - } - } - - private static void deleteDirectory(File directory) { - if (directory.exists()) { - File[] files = directory.listFiles(); - if (files != null) { - for (File file : files) { - if (file.isDirectory()) { - deleteDirectory(file); - } else { - file.delete(); - } - } - } - directory.delete(); - } - } - - private static ExecutorService getOrCreateExecutorService() { - if (executorService == null) { - synchronized (threadPoolLock) { - if (executorService == null) { - int optimalSize = calculateOptimalThreadPoolSize(DEFAULT_THREAD_POOL_SIZE, DEFAULT_THREAD_POOL_SIZE); - executorService = Executors.newFixedThreadPool(optimalSize); - log.info("Created default thread pool with size: {}", optimalSize); - } - } - } - return executorService; - } - - public static void shutdownThreadPool() { - synchronized (threadPoolLock) { - if (executorService != null && !executorService.isShutdown()) { - executorService.shutdown(); - try { - if (!executorService.awaitTermination(30, TimeUnit.SECONDS)) { - executorService.shutdownNow(); - if (!executorService.awaitTermination(15, TimeUnit.SECONDS)) { - log.warn("Thread pool did not terminate gracefully"); - } - } - } catch (InterruptedException e) { - executorService.shutdownNow(); - Thread.currentThread().interrupt(); - } - executorService = null; - log.info("Thread pool shutdown completed"); - } - } - } - - public static class CommandResult { - private boolean success; - private String errorMessage; - private List outputLines = new ArrayList<>(); - private List errorLines = new ArrayList<>(); - - public CommandResult() {} - - public CommandResult(boolean success, String errorMessage) { - this.success = success; - this.errorMessage = errorMessage; - } - - public void addOutputLine(String line) { outputLines.add(line); } - public void addErrorLine(String line) { errorLines.add(line); } - - public boolean isSuccess() { return success; } - public void setSuccess(boolean success) { this.success = success; } - public String getErrorMessage() { return errorMessage; } - public void setErrorMessage(String errorMessage) { this.errorMessage = errorMessage; } - public List getOutputLines() { return outputLines; } - public void setOutputLines(List outputLines) { this.outputLines = outputLines; } - public List getErrorLines() { return errorLines; } - public void setErrorLines(List errorLines) { this.errorLines = errorLines; } - - public String getOutput() { return String.join("\n", outputLines); } - public String getErrors() { return String.join("\n", errorLines); } - } - - public static class RepoResult { - private String repoName; - private String repoPath; - private boolean success; - private String errorMessage; - private List outputLines = new ArrayList<>(); - private List errorLines = new ArrayList<>(); - - public RepoResult(String repoName, String repoPath) { - this.repoName = repoName; - this.repoPath = repoPath; - } - - public String getRepoName() { return repoName; } - public void setRepoName(String repoName) { this.repoName = repoName; } - public String getRepoPath() { return repoPath; } - public void setRepoPath(String repoPath) { this.repoPath = repoPath; } - public boolean isSuccess() { return success; } - public void setSuccess(boolean success) { this.success = success; } - public String getErrorMessage() { return errorMessage; } - public void setErrorMessage(String errorMessage) { this.errorMessage = errorMessage; } - public List getOutputLines() { return outputLines; } - public void setOutputLines(List outputLines) { this.outputLines = outputLines; } - public List getErrorLines() { return errorLines; } - public void setErrorLines(List errorLines) { this.errorLines = errorLines; } - } -} diff --git a/src/main/java/com/example/workutils/biz/git/service/internal/GitRepositoryManager.java b/src/main/java/com/example/workutils/biz/git/service/internal/GitRepositoryManager.java deleted file mode 100644 index 5e8e3d8..0000000 --- a/src/main/java/com/example/workutils/biz/git/service/internal/GitRepositoryManager.java +++ /dev/null @@ -1,688 +0,0 @@ -package com.example.workutils.biz.git.service.internal; - -import lombok.extern.slf4j.Slf4j; - -import java.io.File; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Paths; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashSet; -import java.util.List; -import java.util.Set; -import java.util.Map; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.TimeUnit; -import java.util.stream.Collectors; - -/** - * Git 仓库管理器 - */ -@Slf4j -public class GitRepositoryManager { - - /** - * 获取指定目录下的所有 Git 仓库 - */ - public static List getGitRepositories(String repoRoot) { - List repositories = new ArrayList<>(); - File rootDir = new File(repoRoot); - - if (!rootDir.exists() || !rootDir.isDirectory()) { - log.warn("Repository root directory does not exist: {}", repoRoot); - return repositories; - } - - // 首先检查根目录本身是否是Git仓库 - File rootGitDir = new File(rootDir, ".git"); - if (rootGitDir.exists() && rootGitDir.isDirectory()) { - repositories.add(rootDir.getName()); - log.info("Found Git repository at root: {}", repoRoot); - return repositories; - } - - // 如果根目录不是Git仓库,则检查子目录 - File[] subDirs = rootDir.listFiles(File::isDirectory); - if (subDirs == null) { - return repositories; - } - - for (File subDir : subDirs) { - File gitDir = new File(subDir, ".git"); - if (gitDir.exists() && gitDir.isDirectory()) { - repositories.add(subDir.getName()); - } - } - - log.info("Found {} Git repositories in {}", repositories.size(), repoRoot); - return repositories; - } - - /** - * 验证分支是否存在 - */ - public static boolean branchExists(String repoPath, String branchName) { - GitCommandExecutor.CommandResult result = GitCommandExecutor.executeCommands( - repoPath, - Arrays.asList("git branch --all") - ); - - if (result.isSuccess()) { - return result.getOutput().contains(branchName) || - result.getOutput().contains("remotes/origin/" + branchName); - } - - return false; - } - - /** - * 获取当前分支 - */ - public static String getCurrentBranch(String repoPath) { - GitCommandExecutor.CommandResult result = GitCommandExecutor.executeCommands( - repoPath, - Arrays.asList("git rev-parse --abbrev-ref HEAD") - ); - - if (result.isSuccess()) { - return result.getOutput().trim(); - } - - return null; - } - - /** - * 检查是否有未提交的更改 - */ - public static boolean hasUncommittedChanges(String repoPath) { - GitCommandExecutor.CommandResult result = GitCommandExecutor.executeCommands( - repoPath, - Arrays.asList("git status --porcelain") - ); - - if (result.isSuccess()) { - return !result.getOutput().trim().isEmpty(); - } - - return true; // 出错时假设有未提交的更改 - } - - /** - * 智能fetch远程分支 - 优化版本,默认只fetch master分支 - */ - private static GitCommandExecutor.CommandResult smartFetch(String repoPath, String branchName) { - List commands = Arrays.asList("git fetch origin master"); - - // 如果不是master分支且需要更多信息,再fetch特定分支 - if (branchName != null && !"master".equals(branchName) && !"origin/master".equals(branchName)) { - commands = Arrays.asList("git fetch origin " + branchName); - } - - return GitCommandExecutor.executeCommands(repoPath, commands); - } - - /** - * Fetch多个分支(用于分支比较等场景) - */ - private static GitCommandExecutor.CommandResult fetchBranches(String repoPath, String... branchNames) { - List commands = new ArrayList<>(); - commands.add("git fetch origin master"); // 总是fetch master - - // 去重并添加其他分支 - Set branchesToFetch = new HashSet<>(); - for (String branch : branchNames) { - if (branch != null && !branch.isEmpty()) { - // 移除origin/前缀 - String cleanBranch = branch.replace("origin/", ""); - if (!"master".equals(cleanBranch) && !branchesToFetch.contains(cleanBranch)) { - branchesToFetch.add(cleanBranch); - } - } - } - - for (String branch : branchesToFetch) { - commands.add("git fetch origin " + branch); - } - - return GitCommandExecutor.executeCommands(repoPath, commands); - } - - /** - * 获取最新提交的 Commit ID - 优化版本,使用缓存和命令批处理 - */ - public static String getLatestCommitId(String repoPath, String branchName) { - String cleanBranch = branchName.replace("origin/", ""); - - // 先检查缓存 - GitResultCache.CommitInfo cachedInfo = GitResultCache.getCommitInfo(repoPath, cleanBranch); - if (cachedInfo != null && cachedInfo.getCommitId() != null) { - log.debug("Commit ID for {} retrieved from cache: {}", cleanBranch, cachedInfo.getCommitId()); - return cachedInfo.getCommitId(); - } - - // 使用智能fetch和批处理命令 - List commands = new ArrayList<>(); - commands.add("git fetch origin " + (cleanBranch.equals("master") ? "master" : cleanBranch)); - commands.add("git rev-parse " + branchName); - - GitCommandExecutor.CommandResult result = GitCommandExecutor.executeCommands(repoPath, commands); - - String commitId = null; - if (result.isSuccess()) { - String output = result.getOutput().trim(); - String[] lines = output.split("\n"); - // 返回最后一行(commit ID),跳过fetch输出 - for (int i = lines.length - 1; i >= 0; i--) { - String line = lines[i].trim(); - if (!line.isEmpty() && !line.startsWith("From ")) { - commitId = line; - break; - } - } - } - - // 将结果存入缓存 - if (commitId != null) { - GitResultCache.putCommitInfo(repoPath, cleanBranch, commitId); - log.debug("Commit ID for {} cached: {}", cleanBranch, commitId); - } - - return commitId; - } - - /** - * 比较两个分支的差异 - 优化版本 - */ - public static GitCommandExecutor.CommandResult compareBranches(String repoPath, String sourceBranch, String targetBranch) { - // Fetch两个相关分支 - GitCommandExecutor.CommandResult fetchResult = fetchBranches(repoPath, sourceBranch, targetBranch); - if (!fetchResult.isSuccess()) { - log.warn("Fetch failed for branch comparison: {}", fetchResult.getErrorMessage()); - } - - List commands = Arrays.asList("git log --oneline " + sourceBranch + ".." + targetBranch); - return GitCommandExecutor.executeCommands(repoPath, commands); - } - - /** - * 切换分支 - 优化版本 - */ - public static GitCommandExecutor.CommandResult checkoutBranch(String repoPath, String branchName) { - String cleanBranch = branchName.replace("origin/", ""); - - // 智能fetch - GitCommandExecutor.CommandResult fetchResult = smartFetch(repoPath, cleanBranch); - if (!fetchResult.isSuccess()) { - log.warn("Fetch failed for branch {}: {}", branchName, fetchResult.getErrorMessage()); - } - - List commands = Arrays.asList("git checkout " + cleanBranch); - return GitCommandExecutor.executeCommands(repoPath, commands); - } - - /** - * 切换并合并分支 - 优化版本 - */ - public static GitCommandExecutor.CommandResult checkoutAndMerge(String repoPath, String branchName) { - String cleanBranch = branchName.replace("origin/", ""); - - // 智能fetch - GitCommandExecutor.CommandResult fetchResult = smartFetch(repoPath, cleanBranch); - if (!fetchResult.isSuccess()) { - log.warn("Fetch failed for branch {}: {}", branchName, fetchResult.getErrorMessage()); - } - - List commands = Arrays.asList( - "git checkout " + cleanBranch, - "git merge origin/" + cleanBranch - ); - - return GitCommandExecutor.executeCommands(repoPath, commands); - } - - /** - * 切换并拉取最新代码 - 优化版本 - */ - public static GitCommandExecutor.CommandResult checkoutAndPull(String repoPath, String branchName) { - String cleanBranch = branchName.replace("origin/", ""); - - // 智能fetch - GitCommandExecutor.CommandResult fetchResult = smartFetch(repoPath, cleanBranch); - if (!fetchResult.isSuccess()) { - log.warn("Fetch failed for branch {}: {}", branchName, fetchResult.getErrorMessage()); - } - - List commands = Arrays.asList( - "git checkout " + cleanBranch, - "git pull origin " + cleanBranch - ); - - return GitCommandExecutor.executeCommands(repoPath, commands); - } - - /** - * 批量获取多个仓库的状态信息 - 优化版本,使用并发和缓存 - */ - public static List batchGetRepositoryStatus(String reposPath, List repoNames) { - List results = new ArrayList<>(); - - // 规范化基础路径 - String normalizedReposPath = reposPath.replace("\\", "/"); - - // 使用并发获取仓库状态 - List> futures = repoNames.stream() - .map(repoName -> CompletableFuture.supplyAsync(() -> { - String repoPath = normalizedReposPath + "/" + repoName; - RepositoryStatus status = new RepositoryStatus(repoName, repoPath); - - if (!new File(repoPath).exists()) { - status.setError("Repository directory not found: " + repoPath); - return status; - } - - try { - // 批量获取仓库信息(减少命令调用次数) - List commands = Arrays.asList( - "git status --porcelain", - "git rev-parse --abbrev-ref HEAD", - "git branch --all" - ); - - GitCommandExecutor.CommandResult result = GitCommandExecutor.executeCommands(repoPath, commands); - if (result.isSuccess()) { - String output = result.getOutput(); - String[] lines = output.split("\n"); - - // 解析输出 - boolean hasUncommittedChanges = false; - String currentBranch = null; - List branches = new ArrayList<>(); - - boolean statusSection = true; // 第一部分是status输出 - for (String line : lines) { - line = line.trim(); - if (line.isEmpty()) continue; - - if (statusSection && !line.startsWith("fatal:") && !line.startsWith("error:")) { - if (!line.isEmpty() && !line.startsWith("*") && !line.startsWith("remotes/")) { - hasUncommittedChanges = true; - } - } else if (line.startsWith("*") || line.startsWith("remotes/")) { - statusSection = false; - if (line.startsWith("*")) { - currentBranch = line.substring(2).trim(); - } - branches.add(line); - } else if (!statusSection && !line.startsWith("fatal:") && !line.startsWith("error:")) { - branches.add(line); - } - } - - status.setHasUncommittedChanges(hasUncommittedChanges); - status.setCurrentBranch(currentBranch); - status.setAvailableBranches(branches); - - // 更新缓存 - GitResultCache.putRepoInfo(repoPath, hasUncommittedChanges, currentBranch); - } else { - status.setError(result.getErrorMessage()); - } - } catch (Exception e) { - status.setError("Failed to get repository status: " + e.getMessage()); - } - - return status; - })) - .collect(Collectors.toList()); - - // 等待所有任务完成 - for (CompletableFuture future : futures) { - try { - results.add(future.get(30, TimeUnit.SECONDS)); - } catch (Exception e) { - log.error("Error getting repository status", e); - RepositoryStatus errorStatus = new RepositoryStatus("unknown", "unknown"); - errorStatus.setError("Execution timeout or error: " + e.getMessage()); - results.add(errorStatus); - } - } - - return results; - } - - /** - * 批量执行多个Git操作 - 高级批处理优化 - */ - public static BatchOperationResult batchExecuteGitOperations(String reposPath, List repoNames, List operations) { - BatchOperationResult batchResult = new BatchOperationResult(); - - // 按仓库分组操作 - Map> repoOperations = new ConcurrentHashMap<>(); - for (BatchGitOperation operation : operations) { - if (repoNames.contains(operation.getRepoName())) { - repoOperations.computeIfAbsent(operation.getRepoName(), k -> new ArrayList<>()).add(operation); - } - } - - // 并发执行每个仓库的操作 - List> futures = repoOperations.entrySet().stream() - .map(entry -> CompletableFuture.supplyAsync(() -> { - String repoName = entry.getKey(); - List ops = entry.getValue(); - - String repoPath = reposPath.replace("\\", "/") + "/" + repoName; - RepositoryOperationResult repoResult = new RepositoryOperationResult(repoName, repoPath); - - if (!new File(repoPath).exists()) { - repoResult.setError("Repository directory not found: " + repoPath); - return repoResult; - } - - // 合并可以批处理的命令 - List batchCommands = new ArrayList<>(); - List individualCommands = new ArrayList<>(); - - for (BatchGitOperation op : ops) { - if (op.canBatch()) { - batchCommands.addAll(op.getCommands()); - } else { - individualCommands.addAll(op.getCommands()); - } - } - - try { - // 先执行批处理命令 - if (!batchCommands.isEmpty()) { - GitCommandExecutor.CommandResult batchCmdResult = GitCommandExecutor.executeCommands(repoPath, batchCommands); - if (!batchCmdResult.isSuccess()) { - repoResult.setError("Batch commands failed: " + batchCmdResult.getErrorMessage()); - return repoResult; - } - repoResult.addBatchResult(batchCommands, batchCmdResult); - } - - // 再执行个别命令 - for (String command : individualCommands) { - GitCommandExecutor.CommandResult result = GitCommandExecutor.executeCommands(repoPath, Arrays.asList(command)); - repoResult.addIndividualResult(command, result); - } - - // 清理相关缓存 - GitResultCache.clearRepoCache(repoPath); - - } catch (Exception e) { - repoResult.setError("Failed to execute operations: " + e.getMessage()); - } - - return repoResult; - })) - .collect(Collectors.toList()); - - // 收集结果 - for (CompletableFuture future : futures) { - try { - batchResult.addRepositoryResult(future.get(60, TimeUnit.SECONDS)); - } catch (Exception e) { - log.error("Error executing batch operations", e); - RepositoryOperationResult errorResult = new RepositoryOperationResult("unknown", "unknown"); - errorResult.setError("Execution timeout or error: " + e.getMessage()); - batchResult.addRepositoryResult(errorResult); - } - } - - return batchResult; - } - - /** - * 仓库状态信息类 - */ - public static class RepositoryStatus { - private final String repoName; - private final String repoPath; - private boolean hasUncommittedChanges; - private String currentBranch; - private List availableBranches = new ArrayList<>(); - private String error; - - public RepositoryStatus(String repoName, String repoPath) { - this.repoName = repoName; - this.repoPath = repoPath; - } - - public boolean hasError() { - return error != null && !error.isEmpty(); - } - - // Getters and setters - public String getRepoName() { return repoName; } - public String getRepoPath() { return repoPath; } - public boolean hasUncommittedChanges() { return hasUncommittedChanges; } - public void setHasUncommittedChanges(boolean hasUncommittedChanges) { this.hasUncommittedChanges = hasUncommittedChanges; } - public String getCurrentBranch() { return currentBranch; } - public void setCurrentBranch(String currentBranch) { this.currentBranch = currentBranch; } - public List getAvailableBranches() { return availableBranches; } - public void setAvailableBranches(List availableBranches) { this.availableBranches = availableBranches; } - public String getError() { return error; } - public void setError(String error) { this.error = error; } - } - - /** - * 批量Git操作类 - */ - public static class BatchGitOperation { - private final String repoName; - private final String operationType; - private final List commands; - private final boolean canBatch; - - public BatchGitOperation(String repoName, String operationType, List commands, boolean canBatch) { - this.repoName = repoName; - this.operationType = operationType; - this.commands = new ArrayList<>(commands); - this.canBatch = canBatch; - } - - // 创建常用的批量操作 - public static BatchGitOperation checkoutAndPull(String repoName, String branchName) { - return new BatchGitOperation(repoName, "checkout_pull", - Arrays.asList("git fetch origin " + branchName, "git checkout " + branchName, "git pull origin " + branchName), - true); - } - - public static BatchGitOperation getCommitId(String repoName, String branchName) { - return new BatchGitOperation(repoName, "get_commit", - Arrays.asList("git fetch origin " + branchName, "git rev-parse " + branchName), - true); - } - - public static BatchGitOperation getStatus(String repoName) { - return new BatchGitOperation(repoName, "get_status", - Arrays.asList("git status --porcelain", "git rev-parse --abbrev-ref HEAD"), - false); - } - - // Getters - public String getRepoName() { return repoName; } - public String getOperationType() { return operationType; } - public List getCommands() { return new ArrayList<>(commands); } - public boolean canBatch() { return canBatch; } - } - - /** - * 仓库操作结果类 - */ - public static class RepositoryOperationResult { - private final String repoName; - private final String repoPath; - private String error; - private List batchResults = new ArrayList<>(); - private List individualResults = new ArrayList<>(); - - public RepositoryOperationResult(String repoName, String repoPath) { - this.repoName = repoName; - this.repoPath = repoPath; - } - - public void addBatchResult(List commands, GitCommandExecutor.CommandResult result) { - batchResults.add(new BatchCommandResult(commands, result)); - } - - public void addIndividualResult(String command, GitCommandExecutor.CommandResult result) { - individualResults.add(new IndividualCommandResult(command, result)); - } - - public boolean hasError() { - return error != null && !error.isEmpty(); - } - - // Getters and setters - public String getRepoName() { return repoName; } - public String getRepoPath() { return repoPath; } - public String getError() { return error; } - public void setError(String error) { this.error = error; } - public List getBatchResults() { return batchResults; } - public List getIndividualResults() { return individualResults; } - } - - /** - * 批量命令结果类 - */ - public static class BatchCommandResult { - private final List commands; - private final GitCommandExecutor.CommandResult result; - - public BatchCommandResult(List commands, GitCommandExecutor.CommandResult result) { - this.commands = new ArrayList<>(commands); - this.result = result; - } - - // Getters - public List getCommands() { return commands; } - public GitCommandExecutor.CommandResult getResult() { return result; } - } - - /** - * 个别命令结果类 - */ - public static class IndividualCommandResult { - private final String command; - private final GitCommandExecutor.CommandResult result; - - public IndividualCommandResult(String command, GitCommandExecutor.CommandResult result) { - this.command = command; - this.result = result; - } - - // Getters - public String getCommand() { return command; } - public GitCommandExecutor.CommandResult getResult() { return result; } - } - - /** - * 批量操作结果类 - */ - public static class BatchOperationResult { - private List repositoryResults = new ArrayList<>(); - - public void addRepositoryResult(RepositoryOperationResult result) { - repositoryResults.add(result); - } - - public long getSuccessCount() { - return repositoryResults.stream().filter(r -> !r.hasError()).count(); - } - - public long getFailureCount() { - return repositoryResults.stream().filter(RepositoryOperationResult::hasError).count(); - } - - public double getSuccessRate() { - if (repositoryResults.isEmpty()) return 0.0; - return (double) getSuccessCount() / repositoryResults.size() * 100; - } - - // Getters - public List getRepositoryResults() { return repositoryResults; } - } - - /** - * 将结果保存到文件(支持并发结果) - */ - public static void saveResultsToFile(String filePath, List results, String title, boolean isConcurrent) throws IOException { - List lines = new ArrayList<>(); - lines.add("=== " + title + " ==="); - lines.add("执行时间: " + java.time.LocalDateTime.now()); - lines.add("执行模式: " + (isConcurrent ? "并发执行" : "串行执行")); - lines.add("处理仓库数量: " + results.size()); - - // 统计结果 - long successCount = results.stream().mapToLong(r -> r.isSuccess() ? 1 : 0).sum(); - long failureCount = results.size() - successCount; - lines.add("成功数量: " + successCount); - lines.add("失败数量: " + failureCount); - lines.add("成功率: " + String.format("%.2f%%", (double) successCount / results.size() * 100)); - lines.add(""); - - // 按状态分组显示 - List successResults = results.stream() - .filter(GitCommandExecutor.RepoResult::isSuccess) - .collect(java.util.stream.Collectors.toList()); - - List failedResults = results.stream() - .filter(r -> !r.isSuccess()) - .collect(java.util.stream.Collectors.toList()); - - if (!successResults.isEmpty()) { - lines.add("=== 成功处理的仓库 ==="); - for (GitCommandExecutor.RepoResult result : successResults) { - lines.add("✓ " + result.getRepoName() + " (" + result.getRepoPath() + ")"); - } - lines.add(""); - } - - if (!failedResults.isEmpty()) { - lines.add("=== 处理失败的仓库 ==="); - for (GitCommandExecutor.RepoResult result : failedResults) { - lines.add("✗ " + result.getRepoName() + " (" + result.getRepoPath() + ")"); - lines.add(" 错误信息: " + result.getErrorMessage()); - } - lines.add(""); - } - - // 详细输出(仅在失败或需要详细信息时显示) - boolean hasDetailedOutput = results.stream().anyMatch(r -> - !r.getOutputLines().isEmpty() || !r.getErrorLines().isEmpty()); - - if (hasDetailedOutput) { - lines.add("=== 详细输出 ==="); - for (GitCommandExecutor.RepoResult result : results) { - lines.add("仓库: " + result.getRepoName()); - lines.add("路径: " + result.getRepoPath()); - lines.add("状态: " + (result.isSuccess() ? "成功" : "失败")); - - if (!result.isSuccess()) { - lines.add("错误信息: " + result.getErrorMessage()); - } - - if (!result.getOutputLines().isEmpty()) { - lines.add("输出:"); - result.getOutputLines().forEach(line -> lines.add(" " + line)); - } - - if (!result.getErrorLines().isEmpty()) { - lines.add("错误:"); - result.getErrorLines().forEach(line -> lines.add(" " + line)); - } - - lines.add("----------------------------------------"); - lines.add(""); - } - } - - Files.write(Paths.get(filePath), lines); - log.info("Results saved to: {} (Mode: {}, Success: {}/{})", - filePath, isConcurrent ? "Concurrent" : "Sequential", successCount, results.size()); - } -} diff --git a/src/main/java/com/example/workutils/biz/git/service/internal/GitResultCache.java b/src/main/java/com/example/workutils/biz/git/service/internal/GitResultCache.java deleted file mode 100644 index b8438a6..0000000 --- a/src/main/java/com/example/workutils/biz/git/service/internal/GitResultCache.java +++ /dev/null @@ -1,268 +0,0 @@ -package com.example.workutils.biz.git.service.internal; - -import lombok.extern.slf4j.Slf4j; - -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.Executors; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicLong; - -/** - * Git结果缓存管理器 - * 用于缓存频繁访问的Git操作结果,提高执行效率 - */ -@Slf4j -public class GitResultCache { - - // 仓库信息缓存 - private static final ConcurrentHashMap repoInfoCache = new ConcurrentHashMap<>(); - - // 分支信息缓存 - private static final ConcurrentHashMap branchInfoCache = new ConcurrentHashMap<>(); - - // 提交ID缓存 - private static final ConcurrentHashMap commitInfoCache = new ConcurrentHashMap<>(); - - // 缓存过期时间(毫秒) - private static final long REPO_INFO_TTL = 5 * 60 * 1000; // 5分钟 - private static final long BRANCH_INFO_TTL = 3 * 60 * 1000; // 3分钟 - private static final long COMMIT_INFO_TTL = 2 * 60 * 1000; // 2分钟 - - // 定时清理任务 - private static final ScheduledExecutorService cleanupExecutor = Executors.newSingleThreadScheduledExecutor(); - - static { - // 每分钟清理一次过期缓存 - cleanupExecutor.scheduleAtFixedRate(GitResultCache::cleanupExpiredCache, 1, 1, TimeUnit.MINUTES); - log.info("Git result cache initialized with periodic cleanup"); - } - - /** - * 仓库信息缓存类 - */ - public static class RepoInfo { - private final String repoPath; - private final boolean hasUncommittedChanges; - private final String currentBranch; - private final long timestamp; - - public RepoInfo(String repoPath, boolean hasUncommittedChanges, String currentBranch) { - this.repoPath = repoPath; - this.hasUncommittedChanges = hasUncommittedChanges; - this.currentBranch = currentBranch; - this.timestamp = System.currentTimeMillis(); - } - - public boolean isExpired() { - return System.currentTimeMillis() - timestamp > REPO_INFO_TTL; - } - - // Getters - public String getRepoPath() { return repoPath; } - public boolean hasUncommittedChanges() { return hasUncommittedChanges; } - public String getCurrentBranch() { return currentBranch; } - public long getTimestamp() { return timestamp; } - } - - /** - * 分支信息缓存类 - */ - public static class BranchInfo { - private final String repoPath; - private final String branchName; - private final boolean exists; - private final long timestamp; - - public BranchInfo(String repoPath, String branchName, boolean exists) { - this.repoPath = repoPath; - this.branchName = branchName; - this.exists = exists; - this.timestamp = System.currentTimeMillis(); - } - - public boolean isExpired() { - return System.currentTimeMillis() - timestamp > BRANCH_INFO_TTL; - } - - // Getters - public String getRepoPath() { return repoPath; } - public String getBranchName() { return branchName; } - public boolean exists() { return exists; } - public long getTimestamp() { return timestamp; } - } - - /** - * 提交信息缓存类 - */ - public static class CommitInfo { - private final String repoPath; - private final String branchName; - private final String commitId; - private final long timestamp; - - public CommitInfo(String repoPath, String branchName, String commitId) { - this.repoPath = repoPath; - this.branchName = branchName; - this.commitId = commitId; - this.timestamp = System.currentTimeMillis(); - } - - public boolean isExpired() { - return System.currentTimeMillis() - timestamp > COMMIT_INFO_TTL; - } - - // Getters - public String getRepoPath() { return repoPath; } - public String getBranchName() { return branchName; } - public String getCommitId() { return commitId; } - public long getTimestamp() { return timestamp; } - } - - // 仓库信息缓存操作 - public static RepoInfo getRepoInfo(String repoPath) { - RepoInfo info = repoInfoCache.get(repoPath); - if (info != null && !info.isExpired()) { - return info; - } - return null; - } - - public static void putRepoInfo(String repoPath, boolean hasUncommittedChanges, String currentBranch) { - repoInfoCache.put(repoPath, new RepoInfo(repoPath, hasUncommittedChanges, currentBranch)); - } - - // 分支信息缓存操作 - public static BranchInfo getBranchInfo(String repoPath, String branchName) { - String key = repoPath + ":" + branchName; - BranchInfo info = branchInfoCache.get(key); - if (info != null && !info.isExpired()) { - return info; - } - return null; - } - - public static void putBranchInfo(String repoPath, String branchName, boolean exists) { - String key = repoPath + ":" + branchName; - branchInfoCache.put(key, new BranchInfo(repoPath, branchName, exists)); - } - - // 提交信息缓存操作 - public static CommitInfo getCommitInfo(String repoPath, String branchName) { - String key = repoPath + ":" + branchName; - CommitInfo info = commitInfoCache.get(key); - if (info != null && !info.isExpired()) { - return info; - } - return null; - } - - public static void putCommitInfo(String repoPath, String branchName, String commitId) { - String key = repoPath + ":" + branchName; - commitInfoCache.put(key, new CommitInfo(repoPath, branchName, commitId)); - } - - // 缓存管理操作 - public static void clearCache() { - repoInfoCache.clear(); - branchInfoCache.clear(); - commitInfoCache.clear(); - log.info("All git result caches cleared"); - } - - public static void clearRepoCache(String repoPath) { - repoInfoCache.remove(repoPath); - - // 清理相关的分支和提交缓存 - branchInfoCache.entrySet().removeIf(entry -> entry.getKey().startsWith(repoPath + ":")); - commitInfoCache.entrySet().removeIf(entry -> entry.getKey().startsWith(repoPath + ":")); - - log.info("Cache cleared for repository: {}", repoPath); - } - - /** - * 清理过期缓存 - */ - private static void cleanupExpiredCache() { - AtomicLong cleanedCount = new AtomicLong(); - - // 清理过期的仓库信息缓存 - repoInfoCache.entrySet().removeIf(entry -> { - boolean expired = entry.getValue().isExpired(); - if (expired) cleanedCount.incrementAndGet(); - return expired; - }); - - // 清理过期的分支信息缓存 - branchInfoCache.entrySet().removeIf(entry -> { - boolean expired = entry.getValue().isExpired(); - if (expired) cleanedCount.incrementAndGet(); - return expired; - }); - - // 清理过期的提交信息缓存 - commitInfoCache.entrySet().removeIf(entry -> { - boolean expired = entry.getValue().isExpired(); - if (expired) cleanedCount.incrementAndGet(); - return expired; - }); - - if (cleanedCount.get() > 0) { - log.debug("Cleaned up {} expired cache entries", cleanedCount.get()); - } - } - - /** - * 获取缓存统计信息 - */ - public static CacheStats getCacheStats() { - return new CacheStats( - repoInfoCache.size(), - branchInfoCache.size(), - commitInfoCache.size() - ); - } - - /** - * 缓存统计信息类 - */ - public static class CacheStats { - private final int repoInfoCount; - private final int branchInfoCount; - private final int commitInfoCount; - - public CacheStats(int repoInfoCount, int branchInfoCount, int commitInfoCount) { - this.repoInfoCount = repoInfoCount; - this.branchInfoCount = branchInfoCount; - this.commitInfoCount = commitInfoCount; - } - - @Override - public String toString() { - return String.format("CacheStats{repoInfo=%d, branchInfo=%d, commitInfo=%d}", - repoInfoCount, branchInfoCount, commitInfoCount); - } - - // Getters - public int getRepoInfoCount() { return repoInfoCount; } - public int getBranchInfoCount() { return branchInfoCount; } - public int getCommitInfoCount() { return commitInfoCount; } - } - - /** - * 关闭缓存管理器(用于应用程序关闭时清理资源) - */ - public static void shutdown() { - cleanupExecutor.shutdown(); - try { - if (!cleanupExecutor.awaitTermination(5, TimeUnit.SECONDS)) { - cleanupExecutor.shutdownNow(); - } - } catch (InterruptedException e) { - cleanupExecutor.shutdownNow(); - Thread.currentThread().interrupt(); - } - clearCache(); - log.info("Git result cache shutdown completed"); - } -} diff --git a/src/main/java/com/example/workutils/biz/git/service/internal/strategy/CheckBranchChangesStrategy.java b/src/main/java/com/example/workutils/biz/git/service/internal/strategy/CheckBranchChangesStrategy.java deleted file mode 100644 index 8f633eb..0000000 --- a/src/main/java/com/example/workutils/biz/git/service/internal/strategy/CheckBranchChangesStrategy.java +++ /dev/null @@ -1,108 +0,0 @@ -package com.example.workutils.biz.git.service.internal.strategy; - -import com.example.workutils.biz.git.service.internal.GitCommandExecutor; -import com.example.workutils.biz.git.service.internal.GitRepositoryManager; -import com.example.workutils.biz.git.service.model.GitCommand; -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Component; - -import java.io.File; -import java.time.LocalDateTime; -import java.time.format.DateTimeFormatter; -import java.util.List; -import java.util.stream.Collectors; - -/** - * 检查分支变更策略 - */ -@Component -@Slf4j -public class CheckBranchChangesStrategy implements GitCommandStrategy { - - private String normalizePath(String path) { - if (path == null || path.trim().isEmpty()) { - return path; - } - return path.replace("\\", "/"); - } - - private String joinPath(String basePath, String relativePath) { - return normalizePath(basePath) + "/" + relativePath; - } - - @Override - public void execute(GitCommand commandDTO) { - System.out.println("正在检查分支变更情况..."); - System.out.println("仓库根目录: " + commandDTO.getRepoRoot()); - System.out.println("分支对: " + commandDTO.getBranchPairs()); - - try { - String[] branchPair = commandDTO.getBranchPairs().split(","); - if (branchPair.length != 2) { - System.out.println("错误:分支对格式不正确,应为 '源分支,目标分支'"); - return; - } - - String sourceBranch = branchPair[0].trim(); - String targetBranch = branchPair[1].trim(); - - List repositories = GitRepositoryManager.getGitRepositories(commandDTO.getRepoRoot()); - - if (repositories.isEmpty()) { - System.out.println("在指定目录中未找到 Git 仓库: " + commandDTO.getRepoRoot()); - return; - } - - System.out.println("找到 " + repositories.size() + " 个仓库,开始检查..."); - - List results = repositories.stream() - .map(repoName -> { - File rootGitDir = new File(normalizePath(commandDTO.getRepoRoot()), ".git"); - String repoPath; - if (rootGitDir.exists() && rootGitDir.isDirectory()) { - repoPath = normalizePath(commandDTO.getRepoRoot()); - } else { - repoPath = joinPath(commandDTO.getRepoRoot(), repoName); - } - - GitCommandExecutor.RepoResult result = new GitCommandExecutor.RepoResult(repoName, repoPath); - - try { - GitCommandExecutor.CommandResult compareResult = GitRepositoryManager.compareBranches( - repoPath, sourceBranch, targetBranch); - - result.setSuccess(compareResult.isSuccess()); - result.setOutputLines(compareResult.getOutputLines()); - result.setErrorLines(compareResult.getErrorLines()); - result.setErrorMessage(compareResult.getErrorMessage()); - } catch (Exception e) { - result.setSuccess(false); - result.setErrorMessage("比较分支失败: " + e.getMessage()); - } - - return result; - }) - .collect(Collectors.toList()); - - String timestamp = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMdd_HHmmss")); - String outputFile = "logs/branch_changes_" + timestamp + ".txt"; - GitRepositoryManager.saveResultsToFile(outputFile, results, "分支变更检查结果", true); - - System.out.println("\n=== 检查结果摘要 ==="); - long successCount = results.stream().mapToLong(r -> r.isSuccess() ? 1 : 0).sum(); - System.out.println("总仓库数: " + results.size()); - System.out.println("成功检查: " + successCount); - System.out.println("失败: " + (results.size() - successCount)); - System.out.println("详细结果已保存到: " + outputFile); - - } catch (Exception e) { - log.error("检查分支变更失败", e); - System.out.println("执行失败:" + e.getMessage()); - } - } - - @Override - public GitCommand.GitCommandType getSupportedCommandType() { - return GitCommand.GitCommandType.CHECK_BRANCH_CHANGES; - } -} diff --git a/src/main/java/com/example/workutils/biz/git/service/internal/strategy/CheckoutAndPullStrategy.java b/src/main/java/com/example/workutils/biz/git/service/internal/strategy/CheckoutAndPullStrategy.java deleted file mode 100644 index a04190a..0000000 --- a/src/main/java/com/example/workutils/biz/git/service/internal/strategy/CheckoutAndPullStrategy.java +++ /dev/null @@ -1,132 +0,0 @@ -package com.example.workutils.biz.git.service.internal.strategy; - -import com.example.workutils.biz.git.service.internal.GitCommandExecutor; -import com.example.workutils.biz.git.service.internal.GitRepositoryManager; -import com.example.workutils.biz.git.service.model.GitCommand; -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Component; - -import java.io.File; -import java.time.LocalDateTime; -import java.time.format.DateTimeFormatter; -import java.util.List; -import java.util.stream.Collectors; - -/** - * 切换并拉取分支策略 - */ -@Component -@Slf4j -public class CheckoutAndPullStrategy implements GitCommandStrategy { - - private String normalizePath(String path) { - if (path == null || path.trim().isEmpty()) { - return path; - } - return path.replace("\\", "/"); - } - - private String joinPath(String basePath, String relativePath) { - return normalizePath(basePath) + "/" + relativePath; - } - - @Override - public void execute(GitCommand commandDTO) { - System.out.println("正在切换并拉取最新代码..."); - System.out.println("仓库根目录: " + commandDTO.getRepoRoot()); - System.out.println("目标分支: " + commandDTO.getTargetBranch()); - - try { - String targetBranch = commandDTO.getTargetBranch().trim(); - - List repositories = GitRepositoryManager.getGitRepositories(commandDTO.getRepoRoot()); - - if (repositories.isEmpty()) { - System.out.println("在指定目录中未找到 Git 仓库: " + commandDTO.getRepoRoot()); - return; - } - - System.out.println("找到 " + repositories.size() + " 个仓库,开始切换并拉取..."); - - List results = repositories.stream() - .map(repoName -> { - File rootGitDir = new File(normalizePath(commandDTO.getRepoRoot()), ".git"); - String repoPath; - if (rootGitDir.exists() && rootGitDir.isDirectory()) { - repoPath = normalizePath(commandDTO.getRepoRoot()); - } else { - repoPath = joinPath(commandDTO.getRepoRoot(), repoName); - } - - GitCommandExecutor.RepoResult result = new GitCommandExecutor.RepoResult(repoName, repoPath); - - try { - if (GitRepositoryManager.hasUncommittedChanges(repoPath)) { - result.setSuccess(false); - result.setErrorMessage("仓库存在未提交的更改,请先提交或暂存"); - return result; - } - - GitCommandExecutor.CommandResult pullResult = - GitRepositoryManager.checkoutAndPull(repoPath, targetBranch); - - result.setSuccess(pullResult.isSuccess()); - result.setOutputLines(pullResult.getOutputLines()); - result.setErrorLines(pullResult.getErrorLines()); - result.setErrorMessage(pullResult.getErrorMessage()); - - if (pullResult.isSuccess()) { - String currentBranch = GitRepositoryManager.getCurrentBranch(repoPath); - if (targetBranch.equals(currentBranch)) { - result.setOutputLines(java.util.Arrays.asList( - "成功切换到分支: " + targetBranch, - "成功拉取最新代码", - "当前分支: " + currentBranch - )); - } else { - result.setSuccess(false); - result.setErrorMessage("分支切换验证失败,当前分支: " + currentBranch); - } - } - } catch (Exception e) { - result.setSuccess(false); - result.setErrorMessage("切换并拉取分支失败: " + e.getMessage()); - } - - return result; - }) - .collect(Collectors.toList()); - - String timestamp = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMdd_HHmmss")); - String outputFile = "logs/checkout_and_pull_" + timestamp + ".txt"; - GitRepositoryManager.saveResultsToFile(outputFile, results, "分支切换并拉取结果", true); - - System.out.println("\n=== 操作结果摘要 ==="); - long successCount = results.stream().mapToLong(r -> r.isSuccess() ? 1 : 0).sum(); - System.out.println("总仓库数: " + results.size()); - System.out.println("操作成功: " + successCount); - System.out.println("操作失败: " + (results.size() - successCount)); - System.out.println("详细结果已保存到: " + outputFile); - - List failedResults = results.stream() - .filter(r -> !r.isSuccess()) - .collect(Collectors.toList()); - - if (!failedResults.isEmpty()) { - System.out.println("\n=== 操作失败的仓库 ==="); - for (GitCommandExecutor.RepoResult result : failedResults) { - System.out.println(result.getRepoName() + ": " + result.getErrorMessage()); - } - } - - } catch (Exception e) { - log.error("切换并拉取分支失败", e); - System.out.println("执行失败:" + e.getMessage()); - } - } - - @Override - public GitCommand.GitCommandType getSupportedCommandType() { - return GitCommand.GitCommandType.CHECKOUT_AND_PULL; - } -} diff --git a/src/main/java/com/example/workutils/biz/git/service/internal/strategy/GitCommandStrategy.java b/src/main/java/com/example/workutils/biz/git/service/internal/strategy/GitCommandStrategy.java deleted file mode 100644 index b581150..0000000 --- a/src/main/java/com/example/workutils/biz/git/service/internal/strategy/GitCommandStrategy.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.example.workutils.biz.git.service.internal.strategy; - -import com.example.workutils.biz.git.service.model.GitCommand; - -/** - * Git 命令策略接口 - */ -public interface GitCommandStrategy { - - void execute(GitCommand commandDTO); - - GitCommand.GitCommandType getSupportedCommandType(); -} diff --git a/src/main/java/com/example/workutils/biz/git/service/internal/strategy/GitStrategyFactory.java b/src/main/java/com/example/workutils/biz/git/service/internal/strategy/GitStrategyFactory.java deleted file mode 100644 index 888dbc4..0000000 --- a/src/main/java/com/example/workutils/biz/git/service/internal/strategy/GitStrategyFactory.java +++ /dev/null @@ -1,49 +0,0 @@ -package com.example.workutils.biz.git.service.internal.strategy; - -import com.example.workutils.biz.git.service.model.GitCommand; -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Component; - -import java.util.HashMap; -import java.util.Map; -import java.util.Set; - -/** - * Git 策略工厂类 - */ -@Component -@Slf4j -public class GitStrategyFactory { - - private final Map strategyMap = new HashMap<>(); - - public GitStrategyFactory() { - registerStrategy(new CheckBranchChangesStrategy()); - registerStrategy(new CheckoutAndPullStrategy()); - log.debug("Git策略工厂已初始化,共注册{}个策略", strategyMap.size()); - } - - private void registerStrategy(GitCommandStrategy strategy) { - strategyMap.put(strategy.getSupportedCommandType(), strategy); - log.debug("注册Git策略: {} -> {}", - strategy.getSupportedCommandType(), - strategy.getClass().getSimpleName()); - } - - public GitCommandStrategy getStrategy(GitCommand.GitCommandType commandType) { - GitCommandStrategy strategy = strategyMap.get(commandType); - if (strategy == null) { - throw new IllegalArgumentException("不支持的Git命令类型: " + commandType); - } - return strategy; - } - - public void executeCommand(GitCommand commandDTO) { - GitCommandStrategy strategy = getStrategy(commandDTO.getCommandType()); - strategy.execute(commandDTO); - } - - public Set getSupportedCommandTypes() { - return strategyMap.keySet(); - } -} diff --git a/src/main/java/com/example/workutils/biz/git/service/model/GitCommand.java b/src/main/java/com/example/workutils/biz/git/service/model/GitCommand.java deleted file mode 100644 index 42aa8b2..0000000 --- a/src/main/java/com/example/workutils/biz/git/service/model/GitCommand.java +++ /dev/null @@ -1,59 +0,0 @@ -package com.example.workutils.biz.git.service.model; - -import lombok.Data; - -/** - * Git 命令传输对象 - */ -@Data -public class GitCommand { - - private String repoRoot; - private String targetBranch; - private String branchPairs; - private String branches; - private String scriptPath; - private GitCommandType commandType; - - public static GitCommand create(String repoRoot, String value, GitCommandType type) { - GitCommand dto = new GitCommand(); - dto.setRepoRoot(repoRoot); - dto.setCommandType(type); - switch (type) { - case CHECK_BRANCH_CHANGES: - dto.setBranchPairs(value); - dto.setScriptPath("src/main/resources/git/check_branch_changes.cmd"); - break; - case CHECK_RELEASE_BRANCH: - dto.setBranches(value); - dto.setScriptPath("src/main/resources/git/check_release_branch.cmd"); - break; - case GET_COMMIT_ID: - dto.setTargetBranch(value); - dto.setScriptPath("src/main/resources/git/getcommidid.cmd"); - break; - case CHECKOUT: - dto.setTargetBranch(value); - dto.setScriptPath("src/main/resources/git/git_checkout.cmd"); - break; - case CHECKOUT_AND_MERGE: - dto.setTargetBranch(value); - dto.setScriptPath("src/main/resources/git/git_checkout_and_merges.cmd"); - break; - case CHECKOUT_AND_PULL: - dto.setTargetBranch(value); - dto.setScriptPath("src/main/resources/git/git_checkout_and_pull.cmd"); - break; - } - return dto; - } - - public enum GitCommandType { - CHECK_BRANCH_CHANGES, - CHECK_RELEASE_BRANCH, - GET_COMMIT_ID, - CHECKOUT, - CHECKOUT_AND_MERGE, - CHECKOUT_AND_PULL - } -} diff --git a/src/main/java/com/example/workutils/system/config/WorkUtilsProperties.java b/src/main/java/com/example/workutils/system/config/WorkUtilsProperties.java index 6c914ed..2d1415f 100644 --- a/src/main/java/com/example/workutils/system/config/WorkUtilsProperties.java +++ b/src/main/java/com/example/workutils/system/config/WorkUtilsProperties.java @@ -34,6 +34,9 @@ public class WorkUtilsProperties { /** 项目路径配置文件名 */ private String projectPathsFile; + /** 损坏仓库缓存文件名 */ + private String damagedReposFile; + /** 工具启用/禁用配置 */ private ToolsConfig tools; @@ -43,6 +46,10 @@ public class WorkUtilsProperties { return Paths.get(baseDirectory, configDirectory, projectPathsFile); } + public Path getDamagedReposFilePath() { + return Paths.get(baseDirectory, configDirectory, damagedReposFile); + } + public Path getExportBasePath() { return Paths.get(exportDirectory); } diff --git a/src/main/java/com/example/workutils/system/support/FileOperationHelper.java b/src/main/java/com/example/workutils/system/support/FileOperationHelper.java index c38efdb..0a681b8 100644 --- a/src/main/java/com/example/workutils/system/support/FileOperationHelper.java +++ b/src/main/java/com/example/workutils/system/support/FileOperationHelper.java @@ -191,6 +191,8 @@ public class FileOperationHelper { private String path; private ProjectStatus status; private String corruptionReason; + /** 远程仓库 BASE_URL(可选),格式: path=BASE_URL */ + private String remoteBaseUrl; public ProjectPathInfo(String path, ProjectStatus status) { this.path = path; @@ -209,9 +211,14 @@ public class FileOperationHelper { public void setStatus(ProjectStatus status) { this.status = status; } public String getCorruptionReason() { return corruptionReason; } public void setCorruptionReason(String corruptionReason) { this.corruptionReason = corruptionReason; } + public String getRemoteBaseUrl() { return remoteBaseUrl; } + public void setRemoteBaseUrl(String remoteBaseUrl) { this.remoteBaseUrl = remoteBaseUrl; } public String toConfigLine() { - return path + "|" + status.name(); + if (remoteBaseUrl != null && !remoteBaseUrl.isEmpty()) { + return path + "=" + remoteBaseUrl; + } + return path; } } @@ -226,10 +233,11 @@ public class FileOperationHelper { .map(String::trim) .filter(line -> !line.isEmpty() && !line.startsWith("#")) .map(line -> { - if (!line.contains("|")) { - return line; + int eqIdx = line.indexOf('='); + if (eqIdx >= 0) { + return line.substring(0, eqIdx).trim(); } - return line.split("\\|")[0]; + return line; }) .collect(Collectors.toList()); } @@ -245,18 +253,19 @@ public class FileOperationHelper { .map(String::trim) .filter(line -> !line.isEmpty() && !line.startsWith("#")) .map(line -> { - if (!line.contains("|")) { - return new ProjectPathInfo(line, ProjectStatus.NORMAL); + // 格式1: path=BASE_URL + // 格式2: path(纯路径) + int eqIdx = line.indexOf('='); + if (eqIdx >= 0) { + String path = line.substring(0, eqIdx).trim(); + String baseUrl = line.substring(eqIdx + 1).trim(); + ProjectPathInfo info = new ProjectPathInfo(path, ProjectStatus.NORMAL); + if (!baseUrl.isEmpty()) { + info.setRemoteBaseUrl(baseUrl); + } + return info; } - String[] parts = line.split("\\|"); - String path = parts[0].trim(); - ProjectStatus status; - try { - status = ProjectStatus.valueOf(parts[1].trim().toUpperCase()); - } catch (Exception e) { - status = ProjectStatus.NORMAL; - } - return new ProjectPathInfo(path, status); + return new ProjectPathInfo(line, ProjectStatus.NORMAL); }) .collect(Collectors.toList()); } diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index e7a77cb..2a4b10a 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -7,6 +7,7 @@ workutils: config-directory: "src/main/resources/config" export-directory: "D:/workspace/workutils/export" project-paths-file: "代码仓库项目路径.txt" + damaged-repos-file: "damaged_repos.cache" logging: level: diff --git a/src/main/resources/config/damaged_repos.cache b/src/main/resources/config/damaged_repos.cache new file mode 100644 index 0000000..5741e3d --- /dev/null +++ b/src/main/resources/config/damaged_repos.cache @@ -0,0 +1,11 @@ +branch:master +ft-loan-core|D:/bestpay-srdcloud/ft-loan-core|master|checkout 失败: exit code 128|https://srd17321104074@code.srdcloud.cn/bestpay-cf/cf/cf/cf-trade-core +cf-asset-core|D:/bestpay-srdcloud/cf-asset-core|master|checkout 失败: exit code 128|https://srd17321104074@code.srdcloud.cn/bestpay-cf/cf/cf/cf-trade-core +cf-boss|D:/bestpay-srdcloud/cf-boss|master|checkout 失败: exit code 128|https://srd17321104074@code.srdcloud.cn/bestpay-cf/cf/cf/cf-trade-core +cf-credit-prod-batch|D:/bestpay-srdcloud/cf-credit-prod-batch|master|checkout 失败: exit code 128|https://srd17321104074@code.srdcloud.cn/bestpay-cf/cf/cf/cf-trade-core +cf-instalment-mix|D:/bestpay-srdcloud/cf-instalment-mix|master|checkout 失败: exit code 128|https://srd17321104074@code.srdcloud.cn/bestpay-cf/cf/cf/cf-trade-core +cf-marketing-manage|D:/bestpay-srdcloud/cf-marketing-manage|master|checkout 失败: exit code 128|https://srd17321104074@code.srdcloud.cn/bestpay-cf/cf/cf/cf-trade-core +cf-risk-control-core|D:/bestpay-srdcloud/cf-risk-control-core|master|checkout 失败: exit code 128|https://srd17321104074@code.srdcloud.cn/bestpay-cf/cf/cf/cf-trade-core +cf-risktool|D:/bestpay-srdcloud/cf-risktool|master|checkout 失败: exit code 128|https://srd17321104074@code.srdcloud.cn/bestpay-cf/cf/cf/cf-trade-core +cf-thirdpartybiz-prod|D:/bestpay-srdcloud/cf-thirdpartybiz-prod|master|checkout 失败: exit code 128|https://srd17321104074@code.srdcloud.cn/bestpay-cf/cf/cf/cf-trade-core +cf-trade-core|D:/bestpay-srdcloud/cf-trade-core|master|checkout 失败: exit code 128|https://srd17321104074@code.srdcloud.cn/bestpay-cf/cf/cf/cf-trade-core diff --git a/src/main/resources/config/代码仓库项目路径.txt b/src/main/resources/config/代码仓库项目路径.txt index 74feeae..cb7826a 100644 --- a/src/main/resources/config/代码仓库项目路径.txt +++ b/src/main/resources/config/代码仓库项目路径.txt @@ -1,72 +1,71 @@ -D:/bestpay-srdcloud/ft-compensatory-core|NORMAL -D:/bestpay-srdcloud/ft-creative-prod|NORMAL -D:/bestpay-srdcloud/ft-credit-prod|NORMAL -D:/bestpay-srdcloud/ft-customer-core|NORMAL -D:/bestpay-srdcloud/ft-file-core|NORMAL -D:/bestpay-srdcloud/ft-finflux-gw-adapter|NORMAL -D:/bestpay-srdcloud/ft-lending-management-core|NORMAL -D:/bestpay-srdcloud/ft-loancapital-core|NORMAL -D:/bestpay-srdcloud/ft-loan-core|NORMAL -D:/bestpay-srdcloud/ft-organmng-core|NORMAL -D:/bestpay-srdcloud/ft-orgsupport-weixin|NORMAL -D:/bestpay-srdcloud/ft-protocol-core|NORMAL -D:/bestpay-srdcloud/ft-repay-core|NORMAL -D:/bestpay-srdcloud/ft-risk-control-core|NORMAL -D:/bestpay-srdcloud/ft-settlement-core|NORMAL -D:/bestpay-srdcloud/ft-wechatstage-prod|NORMAL -D:/bestpay-srdcloud/cf-agent-admin|NORMAL -D:/bestpay-srdcloud/cf-asset-core|NORMAL -D:/bestpay-srdcloud/cf-batch-creditpayment|NORMAL -D:/bestpay-srdcloud/cf-batch-creditpayment-plus|NORMAL -D:/bestpay-srdcloud/cf-blockchain-prod|NORMAL -D:/bestpay-srdcloud/cf-boss|CORRUPTED -D:/bestpay-srdcloud/cf-clrstl-fee-core|NORMAL -D:/bestpay-srdcloud/cf-clrstl-report-core|NORMAL -D:/bestpay-srdcloud/cf-codeutils|NORMAL -D:/bestpay-srdcloud/cf-codeutils(1)|CORRUPTED -D:/bestpay-srdcloud/cf-collateral-core|NORMAL -D:/bestpay-srdcloud/cf-commodity-core|NORMAL -D:/bestpay-srdcloud/cf-commonservice|NORMAL -D:/bestpay-srdcloud/cf-consumestage-core|NORMAL -D:/bestpay-srdcloud/cf-consumestage-mall|NORMAL -D:/bestpay-srdcloud/cf-credit-bizfront|NORMAL -D:/bestpay-srdcloud/cf-credit-bizfront-plus|NORMAL -D:/bestpay-srdcloud/cf-credit-front|NORMAL -D:/bestpay-srdcloud/cf-credit-prod|NORMAL -D:/bestpay-srdcloud/cf-credit-prod-batch|NORMAL -D:/bestpay-srdcloud/cf-credit-prod-manage|NORMAL -D:/bestpay-srdcloud/cf-creditstagescore|NORMAL -D:/bestpay-srdcloud/cf-credituse-core|NORMAL -D:/bestpay-srdcloud/cf-ctcorp-core|NORMAL -D:/bestpay-srdcloud/cf-customer-core|NORMAL -D:/bestpay-srdcloud/cf-file-core|NORMAL -D:/bestpay-srdcloud/cf-govent-prod|NORMAL -D:/bestpay-srdcloud/cf-grant-core|NORMAL -D:/bestpay-srdcloud/cf-info-collection|NORMAL -D:/bestpay-srdcloud/cf-instalment-admin|NORMAL -D:/bestpay-srdcloud/cf-instalment-batch|NORMAL -D:/bestpay-srdcloud/cf-instalment-mix|CORRUPTED -D:/bestpay-srdcloud/cf-invoice-core|NORMAL -D:/bestpay-srdcloud/cf-lending-management-core|NORMAL -D:/bestpay-srdcloud/cf-loancapital-core|NORMAL -D:/bestpay-srdcloud/cf-marketing-manage|CORRUPTED -D:/bestpay-srdcloud/cf-marketing-manage-plus|NORMAL -D:/bestpay-srdcloud/cf-onlinestage-prod|NORMAL -D:/bestpay-srdcloud/cf-organmng-core|NORMAL -D:/bestpay-srdcloud/cf-precredit-core|NORMAL -D:/bestpay-srdcloud/cf-productcfg-core-plus|NORMAL -D:/bestpay-srdcloud/cf-protocol-core|NORMAL -D:/bestpay-srdcloud/cf-risk-control-core|CORRUPTED -D:/bestpay-srdcloud/cf-risktool|CORRUPTED -D:/bestpay-srdcloud/cf-scheduler|NORMAL -D:/bestpay-srdcloud/cf-search|NORMAL -D:/bestpay-srdcloud/cf-settlement-core|NORMAL -D:/bestpay-srdcloud/cf-thirdpartybiz-prod|CORRUPTED -D:/bestpay-srdcloud/cf-trade-core|CORRUPTED -D:/bestpay-srdcloud/cf-wechatstage-prod|NORMAL -D:/bestpay-srdcloud/ft-approval-core|NORMAL -D:/bestpay-srdcloud/ft-asset-core|NORMAL -D:/bestpay-srdcloud/ft-blockchain-prod|NORMAL -D:/bestpay-srdcloud/ft-collateral-core|NORMAL -D:/bestpay-srdcloud/ft-commodity-core|NORMAL -D:/bestpay-srdcloud/ft-commonservice|NORMAL \ No newline at end of file +D:/bestpay-srdcloud/ft-compensatory-core=https://srd17321104074@code.srdcloud.cn/bestpay-cf/cf/ +D:/bestpay-srdcloud/ft-creative-prod=https://srd17321104074@code.srdcloud.cn/bestpay-cf/cf/ +D:/bestpay-srdcloud/ft-credit-prod=https://srd17321104074@code.srdcloud.cn/bestpay-cf/cf/ +D:/bestpay-srdcloud/ft-customer-core=https://srd17321104074@code.srdcloud.cn/bestpay-cf/cf/ +D:/bestpay-srdcloud/ft-file-core=https://srd17321104074@code.srdcloud.cn/bestpay-cf/cf/ +D:/bestpay-srdcloud/ft-finflux-gw-adapter=https://srd17321104074@code.srdcloud.cn/bestpay-cf/cf/ +D:/bestpay-srdcloud/ft-lending-management-core=https://srd17321104074@code.srdcloud.cn/bestpay-cf/cf/ +D:/bestpay-srdcloud/ft-loancapital-core=https://srd17321104074@code.srdcloud.cn/bestpay-cf/cf/ +D:/bestpay-srdcloud/ft-loan-core=https://srd17321104074@code.srdcloud.cn/bestpay-cf/cf/ +D:/bestpay-srdcloud/ft-organmng-core=https://srd17321104074@code.srdcloud.cn/bestpay-cf/cf/ +D:/bestpay-srdcloud/ft-orgsupport-weixin=https://srd17321104074@code.srdcloud.cn/bestpay-cf/cf/ +D:/bestpay-srdcloud/ft-protocol-core=https://srd17321104074@code.srdcloud.cn/bestpay-cf/cf/ +D:/bestpay-srdcloud/ft-repay-core=https://srd17321104074@code.srdcloud.cn/bestpay-cf/cf/ +D:/bestpay-srdcloud/ft-risk-control-core=https://srd17321104074@code.srdcloud.cn/bestpay-cf/cf/ +D:/bestpay-srdcloud/ft-settlement-core=https://srd17321104074@code.srdcloud.cn/bestpay-cf/cf/ +D:/bestpay-srdcloud/ft-wechatstage-prod=https://srd17321104074@code.srdcloud.cn/bestpay-cf/cf/ +D:/bestpay-srdcloud/cf-agent-admin=https://srd17321104074@code.srdcloud.cn/bestpay-cf/cf/ +D:/bestpay-srdcloud/cf-asset-core=https://srd17321104074@code.srdcloud.cn/bestpay-cf/cf/ +D:/bestpay-srdcloud/cf-batch-creditpayment=https://srd17321104074@code.srdcloud.cn/bestpay-cf/cf/ +D:/bestpay-srdcloud/cf-batch-creditpayment-plus=https://srd17321104074@code.srdcloud.cn/bestpay-cf/cf/ +D:/bestpay-srdcloud/cf-blockchain-prod=https://srd17321104074@code.srdcloud.cn/bestpay-cf/cf/ +D:/bestpay-srdcloud/cf-boss=https://srd17321104074@code.srdcloud.cn/bestpay-cf/cf/ +D:/bestpay-srdcloud/cf-clrstl-fee-core=https://srd17321104074@code.srdcloud.cn/bestpay-cf/cf/ +D:/bestpay-srdcloud/cf-clrstl-report-core=https://srd17321104074@code.srdcloud.cn/bestpay-cf/cf/ +D:/bestpay-srdcloud/cf-codeutils=https://srd17321104074@code.srdcloud.cn/bestpay-cf/cf/ +D:/bestpay-srdcloud/cf-collateral-core=https://srd17321104074@code.srdcloud.cn/bestpay-cf/cf/ +D:/bestpay-srdcloud/cf-commodity-core=https://srd17321104074@code.srdcloud.cn/bestpay-cf/cf/ +D:/bestpay-srdcloud/cf-commonservice=https://srd17321104074@code.srdcloud.cn/bestpay-cf/cf/ +D:/bestpay-srdcloud/cf-consumestage-core=https://srd17321104074@code.srdcloud.cn/bestpay-cf/cf/ +D:/bestpay-srdcloud/cf-consumestage-mall=https://srd17321104074@code.srdcloud.cn/bestpay-cf/cf/ +D:/bestpay-srdcloud/cf-credit-bizfront=https://srd17321104074@code.srdcloud.cn/bestpay-cf/cf/ +D:/bestpay-srdcloud/cf-credit-bizfront-plus=https://srd17321104074@code.srdcloud.cn/bestpay-cf/cf/ +D:/bestpay-srdcloud/cf-credit-front=https://srd17321104074@code.srdcloud.cn/bestpay-cf/cf/ +D:/bestpay-srdcloud/cf-credit-prod=https://srd17321104074@code.srdcloud.cn/bestpay-cf/cf/ +D:/bestpay-srdcloud/cf-credit-prod-batch=https://srd17321104074@code.srdcloud.cn/bestpay-cf/cf/ +D:/bestpay-srdcloud/cf-credit-prod-manage=https://srd17321104074@code.srdcloud.cn/bestpay-cf/cf/ +D:/bestpay-srdcloud/cf-creditstagescore=https://srd17321104074@code.srdcloud.cn/bestpay-cf/cf/ +D:/bestpay-srdcloud/cf-credituse-core=https://srd17321104074@code.srdcloud.cn/bestpay-cf/cf/ +D:/bestpay-srdcloud/cf-ctcorp-core=https://srd17321104074@code.srdcloud.cn/bestpay-cf/cf/ +D:/bestpay-srdcloud/cf-customer-core=https://srd17321104074@code.srdcloud.cn/bestpay-cf/cf/ +D:/bestpay-srdcloud/cf-file-core=https://srd17321104074@code.srdcloud.cn/bestpay-cf/cf/ +D:/bestpay-srdcloud/cf-govent-prod=https://srd17321104074@code.srdcloud.cn/bestpay-cf/cf/ +D:/bestpay-srdcloud/cf-grant-core=https://srd17321104074@code.srdcloud.cn/bestpay-cf/cf/ +D:/bestpay-srdcloud/cf-info-collection=https://srd17321104074@code.srdcloud.cn/bestpay-cf/cf/ +D:/bestpay-srdcloud/cf-instalment-admin=https://srd17321104074@code.srdcloud.cn/bestpay-cf/cf/ +D:/bestpay-srdcloud/cf-instalment-batch=https://srd17321104074@code.srdcloud.cn/bestpay-cf/cf/ +D:/bestpay-srdcloud/cf-instalment-mix=https://srd17321104074@code.srdcloud.cn/bestpay-cf/cf/ +D:/bestpay-srdcloud/cf-invoice-core=https://srd17321104074@code.srdcloud.cn/bestpay-cf/cf/ +D:/bestpay-srdcloud/cf-lending-management-core=https://srd17321104074@code.srdcloud.cn/bestpay-cf/cf/ +D:/bestpay-srdcloud/cf-loancapital-core=https://srd17321104074@code.srdcloud.cn/bestpay-cf/cf/ +D:/bestpay-srdcloud/cf-marketing-manage=https://srd17321104074@code.srdcloud.cn/bestpay-cf/cf/ +D:/bestpay-srdcloud/cf-marketing-manage-plus=https://srd17321104074@code.srdcloud.cn/bestpay-cf/cf/ +D:/bestpay-srdcloud/cf-onlinestage-prod=https://srd17321104074@code.srdcloud.cn/bestpay-cf/cf/ +D:/bestpay-srdcloud/cf-organmng-core=https://srd17321104074@code.srdcloud.cn/bestpay-cf/cf/ +D:/bestpay-srdcloud/cf-precredit-core=https://srd17321104074@code.srdcloud.cn/bestpay-cf/cf/ +D:/bestpay-srdcloud/cf-productcfg-core-plus=https://srd17321104074@code.srdcloud.cn/bestpay-cf/cf/ +D:/bestpay-srdcloud/cf-protocol-core=https://srd17321104074@code.srdcloud.cn/bestpay-cf/cf/ +D:/bestpay-srdcloud/cf-risk-control-core=https://srd17321104074@code.srdcloud.cn/bestpay-cf/cf/ +D:/bestpay-srdcloud/cf-risktool=https://srd17321104074@code.srdcloud.cn/bestpay-cf/cf/ +D:/bestpay-srdcloud/cf-scheduler=https://srd17321104074@code.srdcloud.cn/bestpay-cf/cf/ +D:/bestpay-srdcloud/cf-search=https://srd17321104074@code.srdcloud.cn/bestpay-cf/cf/ +D:/bestpay-srdcloud/cf-settlement-core=https://srd17321104074@code.srdcloud.cn/bestpay-cf/cf/ +D:/bestpay-srdcloud/cf-thirdpartybiz-prod=https://srd17321104074@code.srdcloud.cn/bestpay-cf/cf/ +D:/bestpay-srdcloud/cf-trade-core=https://srd17321104074@code.srdcloud.cn/bestpay-cf/cf/ +D:/bestpay-srdcloud/cf-wechatstage-prod=https://srd17321104074@code.srdcloud.cn/bestpay-cf/cf/ +D:/bestpay-srdcloud/ft-approval-core=https://srd17321104074@code.srdcloud.cn/bestpay-cf/cf/ +D:/bestpay-srdcloud/ft-asset-core=https://srd17321104074@code.srdcloud.cn/bestpay-cf/cf/ +D:/bestpay-srdcloud/ft-blockchain-prod=https://srd17321104074@code.srdcloud.cn/bestpay-cf/cf/ +D:/bestpay-srdcloud/ft-collateral-core=https://srd17321104074@code.srdcloud.cn/bestpay-cf/cf/ +D:/bestpay-srdcloud/ft-commodity-core=https://srd17321104074@code.srdcloud.cn/bestpay-cf/cf/ +D:/bestpay-srdcloud/ft-commonservice=https://srd17321104074@code.srdcloud.cn/bestpay-cf/cf/ \ No newline at end of file diff --git a/src/main/resources/mybatis-generator.xml b/src/main/resources/mybatis-generator.xml index 41de758..1175375 100644 --- a/src/main/resources/mybatis-generator.xml +++ b/src/main/resources/mybatis-generator.xml @@ -51,4 +51,8 @@ - \ No newline at end of file + + +https://srd17321104074@code.srdcloud.cn/bestpay-cf/cf/cf/ft-loan-core + +https://srd17321104074@code.srdcloud.cn/bestpay-cf/cf/ft-loan-core \ No newline at end of file