最近工作中在使用svn进行代码合并时,手动操作很费时费力废我,且办公电脑卡的要命,合并提交一次代码需要耗费我好几分钟,想着研究个方法使用jenkins流水线进行合并。

        自己编写java类,打成jar包,通过jenkins前端传入svn号,调用jar包进行合并代码。jenkins流水线中配置接收的参数类型为Multi-line String Parameter,可以填入多个svn号,各个svn号换行分割,java执行时进行遍历svn号依次进行代码合并。(代码中逻辑自行修改,本代码中修改的逻辑仅适用我自己

注:最开始写了方式一和方式二,但各有优缺点,所以最终优化出了第三版本方式三;

        强烈推荐使用方式三 。

1.创建自由风格流水线,配置如下

2.编写java代码

方式一(不推荐):

优点

  • svn所有操作均使用官方提供的svnkit库,官方保障

缺点

  • 合代码分支固定,无法根据路径进行更改;
  • 仓库层级较浅,如果代码库代码量超级庞大,则svn操作会很慢;
  • 在更新代码前使用doRevert方法还原,无法回退非版本管控下的文件

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>mergeCode</artifactId>
    <version>1.0-SNAPSHOT</version>




    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>


    <dependencies>

        <!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.24</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.slf4j/slf4j-api -->
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>1.7.36</version>
        </dependency>

        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>1.2.3</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/cn.hutool/hutool-all -->
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.8.38</version>
        </dependency>

        <dependency>
            <groupId>org.tmatesoft.svnkit</groupId>
            <artifactId>svnkit</artifactId>
            <version>1.10.1</version>
        </dependency>


    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-assembly-plugin</artifactId>
                <version>3.3.0</version>
                <configuration>
                    <descriptorRefs>
                        <descriptorRef>jar-with-dependencies</descriptorRef>
                    </descriptorRefs>
                    <archive>
                        <manifest>
                            <mainClass>org.example.Main</mainClass>
                        </manifest>
                    </archive>
                </configuration>
                <executions>
                    <execution>
                        <phase>package</phase>
                        <goals>
                            <goal>single</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>

SVNMergeTool.java

package org.example.utils;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.map.MapUtil;
import lombok.extern.slf4j.Slf4j;
import org.tmatesoft.svn.core.*;
import org.tmatesoft.svn.core.auth.ISVNAuthenticationManager;
import org.tmatesoft.svn.core.internal.io.svn.SVNRepositoryFactoryImpl;
import org.tmatesoft.svn.core.internal.wc.DefaultSVNOptions;
import org.tmatesoft.svn.core.io.SVNRepository;
import org.tmatesoft.svn.core.io.SVNRepositoryFactory;
import org.tmatesoft.svn.core.wc.*;

import java.io.File;
import java.io.IOException;
import java.util.Collection;
import java.util.Map;
/**
 * svn合并代码
 * 优点:svn所有操作均使用官方提供的svnkit库,官方保障
 * 缺点:合代码分支固定,无法根据路径进行更改;
 *      仓库层级较浅,如果代码库代码量超级庞大,则svn操作会很慢
 */
@Slf4j
public class SVNMergeTool {
    // SVN仓库地址
    private static final String SIT_REPOSITORY = "http://****/svn/****/branches/sit";
    private static final String UAT_REPOSITORY = "http://****/svn/****/test/uat";

    // 本地工作副本目录
    private static final String LOCAL_WORKING_COPY = "/temp/UAT";

    // SVN认证信息
    private static final String USERNAME = "***";
    private static final String PASSWORD = "***";

    public static void merge(String sitVision) {
        // 初始化SVNKit库
        setupLibrary();

        long sitRevision = Long.parseLong(sitVision);
        log.info("获取的SIT分支的版本号:  {}", sitRevision);

        try {
            // 执行合并操作
            mergeSitToUat(sitRevision);
            log.info("合并成功完成!");
            log.info("=======================================================================================================");
        } catch (SVNException e) {
            log.error("合并过程中发生错误: " + e.getMessage());
            e.printStackTrace();
            log.info("=======================================================================================================");
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    private static void setupLibrary() {
        // 初始化SVNKit库
        SVNRepositoryFactoryImpl.setup();
        // 不再需要调用 SVNClientManager.setup();
    }

    private static void mergeSitToUat(long sitRevision) throws SVNException, IOException {
        // 创建认证管理器
        ISVNAuthenticationManager authManager =
                SVNWCUtil.createDefaultAuthenticationManager(USERNAME, PASSWORD.toCharArray());

        // 创建SVN客户端管理器 - 使用新的API
        SVNClientManager clientManager = SVNClientManager.newInstance(
                new DefaultSVNOptions(), authManager);

        // 获取UAT工作副本
        SVNUpdateClient updateClient = clientManager.getUpdateClient();
        updateClient.setIgnoreExternals(false);

        File workingCopyDir = new File(LOCAL_WORKING_COPY);

        // 检查工作副本是否存在,如果不存在则checkout
        if (!workingCopyDir.exists() || !SVNWCUtil.isWorkingCopyRoot(workingCopyDir)) {
            log.info("正在检出UAT工作副本...");
            updateClient.doCheckout(
                    SVNURL.parseURIEncoded(UAT_REPOSITORY),
                    workingCopyDir,
                    SVNRevision.HEAD,
                    SVNRevision.HEAD,
                    SVNDepth.INFINITY,
                    false
            );
        } else {
            // 在更新前先执行还原操作,确保工作副本干净
            log.info("正在还原UAT工作副本...");
            revertWorkingCopy(clientManager, workingCopyDir);

            log.info("正在更新UAT工作副本...");
            updateClient.doUpdate(
                    workingCopyDir,
                    SVNRevision.HEAD,
                    SVNDepth.INFINITY,
                    false,
                    false
            );
        }

        // 获取SIT版本的日志信息
        SVNRepository sitRepository = SVNRepositoryFactory.create(
                SVNURL.parseURIEncoded(SIT_REPOSITORY));
        sitRepository.setAuthenticationManager(authManager);

        // 获取SIT版本的提交信息和作者
        Collection<SVNLogEntry> logEntries = sitRepository.log(
                new String[]{""}, // 空数组表示根目录
                null,
                sitRevision,
                sitRevision,
                true,
                true
        );

        if (logEntries.isEmpty()) {
            SVNErrorMessage e = SVNErrorMessage.create(SVNErrorCode.UNKNOWN,
                    "无法找到版本 " + sitRevision + " 的日志信息");
            throw new SVNException(e);
        }

        SVNLogEntry logEntry = logEntries.iterator().next();
        //sit分支提交的log
        String sitLogMessage = logEntry.getMessage().trim();
        //sit分支提交的作者
        String sitAuthor = logEntry.getAuthor();
        //sit分支提交代码列表清单
        Map<String, SVNLogEntryPath> changedPaths = logEntry.getChangedPaths();

        //代码服务名
        String serverName = "【" + (CollectionUtil.newArrayList(changedPaths.keySet())).get(0).trim().split("/")[4] + "】";

        String serverName1 = serverName.replace("【", "[").replace("】", "]");

        //遍历代码清单
        log.info("要合并的代码清单:");
        for (String path : changedPaths.keySet()) {
            log.info(path);
   /*         SVNLogEntryPath svnLogEntryPath = changedPaths.get(path);
            //更改类型: 'A' 添加, 'D' 删除, 'M' 修改, 'R' 替换等
            System.out.println(svnLogEntryPath.getType());*/
        }

        // 构建提交消息 例如:【icap-mbank】 新增登录controller xuezhiqian 52158
        String commitMessage;
        if (sitLogMessage.startsWith(serverName)) {
            commitMessage = String.format("%s %s %d",
                    sitLogMessage, sitAuthor, sitRevision);
        } else if (sitLogMessage.startsWith(serverName1)) {
            commitMessage = String.format("%s %s %d",
                    sitLogMessage.replace(serverName1, serverName), sitAuthor, sitRevision);
        } else {
            commitMessage = String.format("%s %s  %s  %d",
                    serverName, sitLogMessage, sitAuthor, sitRevision);
        }
        log.info("uat分支提交log信息:");
        log.info(commitMessage);


        log.info("正在合并SIT版本 " + sitRevision + " 到 UAT...");
        // 执行合并操作
        SVNDiffClient diffClient = clientManager.getDiffClient();
        diffClient.doMerge(
                SVNURL.parseURIEncoded(SIT_REPOSITORY),
                SVNRevision.create(sitRevision - 1),
                SVNURL.parseURIEncoded(SIT_REPOSITORY),
                SVNRevision.create(sitRevision),
                workingCopyDir,
                SVNDepth.INFINITY,
                true,
                false,
                false,
                false
        );

        // 检查是否有冲突
        SVNStatusClient statusClient = clientManager.getStatusClient();
        SVNStatus status = statusClient.doStatus(workingCopyDir, false);
        if (status != null && status.getContentsStatus() == SVNStatusType.STATUS_CONFLICTED) {
            log.error("警告: 合并过程中发现冲突,请手动解决冲突后重新提交!");
            return;
        }

        // 提交更改
        log.info("正在提交更改到UAT...");
        SVNCommitClient commitClient = clientManager.getCommitClient();
        SVNCommitInfo commitInfo = commitClient.doCommit(
                new File[]{workingCopyDir},
                false,
                commitMessage,
                null,
                null,
                false,
                false,
                SVNDepth.INFINITY
        );

        log.info("提交完成! 新版本号: " + commitInfo.getNewRevision());
    }


    /**
     * 还原工作副本中的所有本地修改
     */
    private static void revertWorkingCopy(SVNClientManager clientManager, File workingCopyDir)
            throws SVNException {
        SVNWCClient wcClient = clientManager.getWCClient();
        // 还原整个工作副本,包括所有子目录
        wcClient.doRevert(
                new File[]{workingCopyDir},
                SVNDepth.INFINITY,
                null
        );
    }


    /**
     * 获取指定版本的所有变更路径
     */
    private static SVNLogEntryPath[] getChangedPathsForRevision(SVNRepository repository, long revision)
            throws SVNException {
        Collection<SVNLogEntry> logEntries = repository.log(
                new String[]{""},
                null,
                revision,
                revision,
                true,
                true
        );

        if (logEntries.isEmpty()) {
            return new SVNLogEntryPath[0];
        }

        SVNLogEntry logEntry = logEntries.iterator().next();
        return logEntry.getChangedPaths().values().toArray(new SVNLogEntryPath[0]);
    }


    /**
     * 合并单个文件
     */
    private static void mergeSingleFile(
            SVNClientManager clientManager,
            File workingCopyDir,
            String sitPath,
            String uatPath,
            long sitRevision) throws SVNException {

        SVNDiffClient diffClient = clientManager.getDiffClient();

        try {
            // 获取SIT文件的URL
            SVNURL sitFileUrl = SVNURL.parseURIEncoded(SIT_REPOSITORY + sitPath);

            // 获取UAT工作副本中的文件路径
            File uatFile = new File(workingCopyDir, uatPath);

            // 确保目标目录存在
            uatFile.getParentFile().mkdirs();

            // 执行合并
            diffClient.doMerge(
                    sitFileUrl,
                    SVNRevision.create(sitRevision - 1),
                    sitFileUrl,
                    SVNRevision.create(sitRevision),
                    uatFile,
                    SVNDepth.EMPTY,
                    true,
                    false,
                    false,
                    false
            );
        } catch (SVNException e) {
            log.error("合并文件失败: " + sitPath + " -> " + uatPath + ": " + e.getMessage());
            // 可以选择继续处理其他文件
        }
    }
}

Main.java

package org.example;

import lombok.extern.slf4j.Slf4j;
import org.example.utils.SVNMergeTool;

import java.io.IOException;

@Slf4j
public class Main {
    public static void main(String[] args) throws IOException {

        String arg = args[0];
        for (String sitRevision : arg.trim().split("\n")) {
            SVNMergeTool.merge(sitRevision.trim());
        }
    }
}

方式二(推荐):

优点

  • 代码分支可以代码中自定义更改路径匹配规则,通用性强;
  • 仓库层级较相对方式一更深,更加精确的路径下进行svn操作,会相对较快;
  • 使用命令还原代码,且可以删除非版本管控下的文件,还原的干净

缺点

  • svn部分操作均通过java执行命令方式进行,需自行更改逻辑或代码

 pom.xml  同上

LinuxCommandExecutor.java

package org.example.utils;

import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * Linux命令执行工具类
 * 提供多种重载方法执行Linux命令
 */
public class LinuxCommandExecutor {

    /**
     * 执行Linux命令并返回结果
     *
     * @param command 要执行的命令
     * @return 命令执行结果
     * @throws IOException          如果发生I/O错误
     * @throws InterruptedException 如果执行过程被中断
     */
    public static String executeCommand(String command) throws IOException, InterruptedException {
        return executeCommand(command, null, null, 0);
    }

    /**
     * 执行Linux命令并返回结果
     *
     * @param command 要执行的命令
     * @param timeout 超时时间(秒)
     * @return 命令执行结果
     * @throws IOException          如果发生I/O错误
     * @throws InterruptedException 如果执行过程被中断
     */
    public static String executeCommand(String command, long timeout) throws IOException, InterruptedException {
        return executeCommand(command, null, null, timeout);
    }

    /**
     * 执行Linux命令并返回结果
     *
     * @param command          要执行的命令
     * @param workingDirectory 工作目录
     * @return 命令执行结果
     * @throws IOException          如果发生I/O错误
     * @throws InterruptedException 如果执行过程被中断
     */
    public static String executeCommand(String command, File workingDirectory) throws IOException, InterruptedException {
        return executeCommand(command, workingDirectory, null, 0);
    }

    /**
     * 执行Linux命令并返回结果
     *
     * @param command          要执行的命令
     * @param workingDirectory 工作目录
     * @param timeout          超时时间(秒)
     * @return 命令执行结果
     * @throws IOException          如果发生I/O错误
     * @throws InterruptedException 如果执行过程被中断
     */
    public static String executeCommand(String command, File workingDirectory, long timeout) throws IOException, InterruptedException {
        return executeCommand(command, workingDirectory, null, timeout);
    }

    /**
     * 执行Linux命令并返回结果
     *
     * @param command 要执行的命令
     * @param envVars 环境变量
     * @return 命令执行结果
     * @throws IOException          如果发生I/O错误
     * @throws InterruptedException 如果执行过程被中断
     */
    public static String executeCommand(String command, String[] envVars) throws IOException, InterruptedException {
        return executeCommand(command, null, envVars, 0);
    }

    /**
     * 执行Linux命令并返回结果
     *
     * @param command 要执行的命令
     * @param envVars 环境变量
     * @param timeout 超时时间(秒)
     * @return 命令执行结果
     * @throws IOException          如果发生I/O错误
     * @throws InterruptedException 如果执行过程被中断
     */
    public static String executeCommand(String command, String[] envVars, long timeout) throws IOException, InterruptedException {
        return executeCommand(command, null, envVars, timeout);
    }

    /**
     * 执行Linux命令并返回结果
     *
     * @param command          要执行的命令
     * @param workingDirectory 工作目录
     * @param envVars          环境变量
     * @param timeout          超时时间(秒)
     * @return 命令执行结果
     * @throws IOException          如果发生I/O错误
     * @throws InterruptedException 如果执行过程被中断
     */
    public static String executeCommand(String command, File workingDirectory, String[] envVars, long timeout)
            throws IOException, InterruptedException {

        // 使用ProcessBuilder执行命令
        ProcessBuilder processBuilder = new ProcessBuilder("/bin/bash", "-c", command);

        // 设置工作目录
        if (workingDirectory != null) {
            processBuilder.directory(workingDirectory);
        }

        // 设置环境变量
        if (envVars != null) {
            for (String envVar : envVars) {
                String[] parts = envVar.split("=", 2);
                if (parts.length == 2) {
                    processBuilder.environment().put(parts[0], parts[1]);
                }
            }
        }

        // 重定向错误流到标准输出
        processBuilder.redirectErrorStream(true);

        Process process = processBuilder.start();

        // 读取命令输出
        StringBuilder output = new StringBuilder();
        try (InputStream inputStream = process.getInputStream();
             BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))) {

            String line;
            while ((line = reader.readLine()) != null) {
                output.append(line).append("\n");
            }
        }

        // 等待命令执行完成或超时
        if (timeout > 0) {
            if (!process.waitFor(timeout, TimeUnit.SECONDS)) {
                process.destroy();
                throw new InterruptedException("Command execution timed out after " + timeout + " seconds");
            }
        } else {
            process.waitFor();
        }

        return output.toString();
    }

    /**
     * 执行Linux命令并返回结果和退出码
     *
     * @param command 要执行的命令
     * @return 包含退出码和输出的CommandResult对象
     * @throws IOException          如果发生I/O错误
     * @throws InterruptedException 如果执行过程被中断
     */
    public static CommandResult executeCommandWithExitCode(String command) throws IOException, InterruptedException {
        return executeCommandWithExitCode(command, null, null, 0);
    }

    /**
     * 执行Linux命令并返回结果和退出码
     *
     * @param command 要执行的命令
     * @param timeout 超时时间(秒)
     * @return 包含退出码和输出的CommandResult对象
     * @throws IOException          如果发生I/O错误
     * @throws InterruptedException 如果执行过程被中断
     */
    public static CommandResult executeCommandWithExitCode(String command, long timeout) throws IOException, InterruptedException {
        return executeCommandWithExitCode(command, null, null, timeout);
    }

    /**
     * 执行Linux命令并返回结果和退出码
     *
     * @param command          要执行的命令
     * @param workingDirectory 工作目录
     * @param envVars          环境变量
     * @param timeout          超时时间(秒)
     * @return 包含退出码和输出的CommandResult对象
     * @throws IOException          如果发生I/O错误
     * @throws InterruptedException 如果执行过程被中断
     */
    public static CommandResult executeCommandWithExitCode(String command, File workingDirectory, String[] envVars, long timeout)
            throws IOException, InterruptedException {

        ProcessBuilder processBuilder = new ProcessBuilder("/bin/bash", "-c", command);

        if (workingDirectory != null) {
            processBuilder.directory(workingDirectory);
        }

        if (envVars != null) {
            for (String envVar : envVars) {
                String[] parts = envVar.split("=", 2);
                if (parts.length == 2) {
                    processBuilder.environment().put(parts[0], parts[1]);
                }
            }
        }

        processBuilder.redirectErrorStream(true);
        Process process = processBuilder.start();

        StringBuilder output = new StringBuilder();
        try (InputStream inputStream = process.getInputStream();
             BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))) {

            String line;
            while ((line = reader.readLine()) != null) {
                output.append(line).append("\n");
            }
        }

        int exitCode;
        if (timeout > 0) {
            if (process.waitFor(timeout, TimeUnit.SECONDS)) {
                exitCode = process.exitValue();
            } else {
                process.destroy();
                throw new InterruptedException("Command execution timed out after " + timeout + " seconds");
            }
        } else {
            exitCode = process.waitFor();
        }

        return new CommandResult(exitCode, output.toString());
    }

    /**
     * 命令执行结果封装类
     */
    public static class CommandResult {
        private final int exitCode;
        private final String output;

        public CommandResult(int exitCode, String output) {
            this.exitCode = exitCode;
            this.output = output;
        }

        public int getExitCode() {
            return exitCode;
        }

        public String getOutput() {
            return output;
        }

        @Override
        public String toString() {
            return "Exit Code: " + exitCode + "\nOutput:\n" + output;
        }
    }

    /**
     * 测试方法
     */
    public static void main(String[] args) {
        try {
            // 示例1:执行简单命令
            System.out.println("=== 执行 'ls -l' 命令 ===");
            String result1 = executeCommand("ls -l");
            System.out.println(result1);

            // 示例2:执行带超时的命令
            System.out.println("=== 执行 'sleep 2' 命令(带超时)===");
            String result2 = executeCommand("sleep 2", 3);
            System.out.println("命令执行完成: " + result2);

            // 示例3:执行命令并获取退出码
            System.out.println("=== 执行 'pwd' 命令并获取退出码 ===");
            CommandResult result3 = executeCommandWithExitCode("pwd");
            System.out.println(result3);

            // 示例4:设置环境变量执行命令
            System.out.println("=== 设置环境变量执行命令 ===");
            String[] envVars = {"MY_VAR=HelloWorld"};
            String result4 = executeCommand("echo $MY_VAR", envVars);
            System.out.println(result4);

        } catch (IOException | InterruptedException e) {
            e.printStackTrace();
        }
    }
}

StringUtils.java

package org.example.utils;

public class StringUtils {
    /**
     * 通用方法:获取字符串中第n个字符的索引(n从1开始)
     *
     * @param str 目标字符串(如文件路径)
     * @param n   要查找的字符的序号(如第五个则n=5)
     * @return 第n个字符的索引,若不存在则返回-1
     */
    public static int getNthSlashIndex(String str, String chars, int n) {
        // 非法输入校验(字符串为null或n≤0)
        if (str == null || n <= 0) {
            return -1;
        }

        int currentIndex = -1; // 初始查找位置(从字符串开头前一位开始)
        for (int i = 0; i < n; i++) {
            // 从“上一个/的下一位”开始查找下一个/
            currentIndex = str.indexOf(chars, currentIndex + 1);
            // 若中途找不到“/”,直接返回-1
            if (currentIndex == -1) {
                break;
            }
        }
        return currentIndex;
    }
}

AutoMergeCode.java

package org.example.utils;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.StrUtil;
import lombok.extern.slf4j.Slf4j;
import org.tmatesoft.svn.core.*;
import org.tmatesoft.svn.core.auth.ISVNAuthenticationManager;
import org.tmatesoft.svn.core.internal.io.svn.SVNRepositoryFactoryImpl;
import org.tmatesoft.svn.core.internal.wc.DefaultSVNOptions;
import org.tmatesoft.svn.core.io.SVNRepository;
import org.tmatesoft.svn.core.io.SVNRepositoryFactory;
import org.tmatesoft.svn.core.wc.*;

import java.io.File;
import java.io.IOException;
import java.util.Collection;
import java.util.Map;

/**
 * svn合并代码
 * 优点:
 *      代码分支可以代码中自定义更改路径匹配规则,通用性强;
 *      仓库层级较相对方式一更深,更加精确的路径下进行svn操作,会相对较快;
 *      使用命令还原代码,且可以删除非版本管控下的文件,还原的干净
 * 缺点:
 *      svn部分操作均通过java执行命令方式进行,需自行更改逻辑或代码
 */

@Slf4j
public class AutoMergeCode {
    // SVN仓库地址
    private static String SIT_REPOSITORY = "http://****/svn/****/branches/sit";
    private static String UAT_REPOSITORY = "http://****/svn/****/test/uat";


    // 本地工作副本目录
    private static String LOCAL_WORKING_COPY = "/temp/uat_working_copy";

    // SVN认证信息
    private static final String USERNAME = "****";
    private static final String PASSWORD = "****";

    public static void merge(String sitVision) {
        // 初始化SVNKit库
        setupLibrary();

        long sitRevision = Long.parseLong(sitVision);
        log.info("获取的SIT分支的版本号:  {}", sitRevision);

        try {
            // 执行合并操作
            mergeSitToUat(sitRevision);
            log.info("合并成功完成!");
        } catch (SVNException e) {
            log.error("合并过程中发生错误: " + e.getMessage());
            e.printStackTrace();
        } catch (IOException | InterruptedException e) {
            e.printStackTrace();
        } finally {
            //还原静态成员变量
            SIT_REPOSITORY = "http://****/svn/****P/branches/sit";
            UAT_REPOSITORY = "http://****/svn/****/test/uat";
            LOCAL_WORKING_COPY = "/temp/uat_working_copy";
            log.info("=======================================================================================================");
        }
    }

    private static void setupLibrary() {
        // 初始化SVNKit库
        SVNRepositoryFactoryImpl.setup();
    }


    /**
     * 合并操作
     *
     * @param sitRevision sit分支svn版本号
     * @throws SVNException
     * @throws IOException
     * @throws InterruptedException
     */
    private static void mergeSitToUat(long sitRevision) throws SVNException, IOException, InterruptedException {
        // 创建认证管理器
        ISVNAuthenticationManager authManager =
                SVNWCUtil.createDefaultAuthenticationManager(USERNAME, PASSWORD.toCharArray());

        // 创建SVN客户端管理器 - 使用新的API
        SVNClientManager clientManager = SVNClientManager.newInstance(
                new DefaultSVNOptions(), authManager);


        // 获取SIT版本的日志信息
        SVNRepository sitRepository = SVNRepositoryFactory.create(
                SVNURL.parseURIEncoded(SIT_REPOSITORY));
        sitRepository.setAuthenticationManager(authManager);

        // 获取SIT版本的提交信息和作者
        /*
        *   new String[]{""}:指定需要获取日志的路径数组。这里传入空字符串数组{""},表示获取版本库根目录的日志(空字符串在 SVN 中通常代表根路径)。
            null:用于过滤日志的修订版本范围(通常是SVNRevisionRange数组)。传入null表示不过滤特定范围,即包含符合条件的所有相关修订版。
            sitRevision:日志查询的起始修订版本(包含该版本)。
            sitRevision:日志查询的结束修订版本(包含该版本)。
            这里起始和结束版本相同,说明是获取单一特定修订版(sitRevision) 的日志。
            true:是否递归获取子目录的日志。true表示包含所有子目录的日志,false则只获取指定路径本身的日志。
            true:是否获取日志的详细信息。true表示返回的SVNLogEntry会包含完整信息(如作者、提交时间、日志注释、修改的文件列表等);false可能只返回基础信息(如修订版号)。
            * */
        Collection<SVNLogEntry> logEntries = sitRepository.log(
                new String[]{""},
                null,
                sitRevision,
                sitRevision,
                true,
                true
        );

        if (logEntries.isEmpty()) {
            SVNErrorMessage e = SVNErrorMessage.create(SVNErrorCode.UNKNOWN,
                    "无法找到版本 " + sitRevision + " 的日志信息");
            throw new SVNException(e);
        }

        SVNLogEntry logEntry = logEntries.iterator().next();
        /*
        *   修订版号:logEntry.getRevision()
            提交作者:logEntry.getAuthor()
            提交时间:logEntry.getDate()
            提交注释:logEntry.getMessage()
            修改的文件列表:logEntry.getChangedPaths()
            * */
        //sit分支提交的log
        String sitLogMessage = logEntry.getMessage().trim();
        //sit分支提交的作者
        String sitAuthor = logEntry.getAuthor();
        //sit分支提交代码列表清单
        Map<String, SVNLogEntryPath> changedPaths = logEntry.getChangedPaths();

        //代码服务名
        String serverName = "【" + (CollectionUtil.newArrayList(changedPaths.keySet())).get(0).trim()
                .split("/")[4] + "】";
        String server = (CollectionUtil.newArrayList(changedPaths.keySet())).get(0).trim().split("/")[4];
        log.info("应用名:{}", server);

        //获取分支仓库下具体服务地址,如:/Dubbo/icap-dubbo-cc
        //   s:  /branches/sit/Servers/icap-mbank/test/java/cn/com/common/util/StringUtil.java
        String s = (CollectionUtil.newArrayList(changedPaths.keySet())).get(0).trim();
        String s1 = s.substring(StringUtils.getNthSlashIndex(s, "/", 3), StringUtils.getNthSlashIndex(s, "/", 5));
        if (StrUtil.equals(server, "icap-hf-mbank")) {
            SIT_REPOSITORY = SIT_REPOSITORY + s1;
            UAT_REPOSITORY = UAT_REPOSITORY + s1.replace("icap-hf-mbank", "icap-mbank-hf");
            LOCAL_WORKING_COPY += "/" + server.replace("icap-hf-mbank", "icap-mbank-hf");
        } else if (StrUtil.equals(server, "icap-jl-mbank")) {
            SIT_REPOSITORY = SIT_REPOSITORY + s1;
            UAT_REPOSITORY = UAT_REPOSITORY + s1.replace("icap-jl-mbank", "icap-mbank-jl");
            LOCAL_WORKING_COPY += "/" + server.replace("icap-jl-mbank", "icap-mbank-jl");
        } else {
            SIT_REPOSITORY = SIT_REPOSITORY + s1;
            UAT_REPOSITORY = UAT_REPOSITORY + s1;
            LOCAL_WORKING_COPY += "/" + server;
        }

       /* System.out.println("SIT_REPOSITORY " + SIT_REPOSITORY);
        System.out.println("UAT_REPOSITORY " + UAT_REPOSITORY);
        System.out.println("LOCAL_WORKING_COPY " + LOCAL_WORKING_COPY);*/

        File workingCopyDir = new File(LOCAL_WORKING_COPY);

        //工作副本检查还原
        log.info("检查并还原工作副本...");
        //删除非版本化文件及目录
        LinuxCommandExecutor.executeCommand(
                "svn status | grep '^?' | awk '{print $2}' | xargs rm -rf",
                workingCopyDir);

        //还原版本化文件
//        log.info((LinuxCommandExecutor.executeCommand("svn revert -R .", workingCopyDir))); //命令形式
        revertWorkingCopy(clientManager, workingCopyDir);

        log.info("更新工作副本...");
//        log.info((LinuxCommandExecutor.executeCommand("svn up", workingCopyDir))); //命令形式
        long l = updateWorkingCopy(clientManager, workingCopyDir);
        log.info("工作副本已更新到版本 {}", l);


       /* System.out.println((LinuxCommandExecutor.executeCommand(
                "svn status | grep '^?' | awk '{print $2}' | xargs rm -rf",
                new File(LOCAL_WORKING_COPY.toString() + "/" + server))));*/


        String serverName1 = serverName.replace("【", "[").replace("】", "]");

        //遍历代码清单
        log.info("要合并的代码清单:");
        for (String path : changedPaths.keySet()) {
            log.info(path);
   /*         SVNLogEntryPath svnLogEntryPath = changedPaths.get(path);
            //更改类型: 'A' 添加, 'D' 删除, 'M' 修改, 'R' 替换等
            System.out.println(svnLogEntryPath.getType());*/
        }

        // 构建提交消息 例如:【icap-mbank】 新增登录controller xuezhiqian 52158
        String commitMessage;
        if (sitLogMessage.startsWith(serverName)) {
            commitMessage = String.format("%s %s %d",
                    sitLogMessage, sitAuthor, sitRevision);
        } else if (sitLogMessage.startsWith(serverName1)) {
            commitMessage = String.format("%s %s %d",
                    sitLogMessage.replace(serverName1, serverName), sitAuthor, sitRevision);
        } else {
            commitMessage = String.format("%s %s  %s  %d",
                    serverName, sitLogMessage, sitAuthor, sitRevision);
        }
        log.info("uat分支提交log信息: {}", commitMessage);


        log.info("正在合并SIT版本 " + sitRevision + " 到 UAT...");
        // 执行合并操作
        mergeRepositoryRevision(clientManager, SIT_REPOSITORY, sitRevision, workingCopyDir);


        // 检查是否有冲突
        SVNStatusClient statusClient = clientManager.getStatusClient();
        SVNStatus status = statusClient.doStatus(workingCopyDir, false);
        if (status != null && status.getContentsStatus() == SVNStatusType.STATUS_CONFLICTED) {
            log.error("警告: 合并过程中发现冲突,请手动解决冲突后重新提交!");
            return;
        }

        // 提交更改
        log.info("正在提交更改到UAT...");
        SVNCommitClient commitClient = clientManager.getCommitClient();
        SVNCommitInfo commitInfo = commitClient.doCommit(
                new File[]{workingCopyDir},
                false,
                commitMessage,
                null,
                null,
                false,
                false,
                SVNDepth.INFINITY
        );

        log.info("提交完成! 新版本号: " + commitInfo.getNewRevision());
    }


    /**
     * 还原工作副本中的所有本地修改(版本化内)
     *
     * @param clientManager  SVN 客户端管理器,用于获取操作 SVN 工作副本的客户端对象
     * @param workingCopyDir 需要还原的 SVN 工作副本目录(本地文件系统中的路径)
     * @throws SVNException 声明可能抛出 SVN 操作相关的异常(如路径不存在、权限不足等)
     */
    private static void revertWorkingCopy(SVNClientManager clientManager, File workingCopyDir) throws SVNException {
        SVNWCClient wcClient = clientManager.getWCClient();
        // 还原整个工作副本,包括所有子目录
        /*
        *   第一个参数new File[]{workingCopyDir}:需要还原的路径数组。这里传入包含workingCopyDir的数组,表示要还原整个工作副本目录。
            第二个参数SVNDepth.INFINITY:还原的深度。INFINITY表示递归还原所有子目录和文件(即不仅还原根目录,还包括所有子级内容)。
            第三个参数null:过滤规则(通常用于指定只还原特定类型的文件),null表示无过滤,还原所有内容。
            * */
        wcClient.doRevert(
                new File[]{workingCopyDir},
                SVNDepth.INFINITY,
                null
        );
    }


    /**
     * 更新SVN工作副本到最新版本
     *
     * @param clientManager  SVN客户端管理器
     * @param workingCopyDir 工作副本目录
     * @return 更新后的修订版本号
     * @throws SVNException SVN操作异常
     */
    private static long updateWorkingCopy(SVNClientManager clientManager, File workingCopyDir)
            throws SVNException {
        // 调用重载方法,默认更新到最新版本
        return updateWorkingCopy(clientManager, workingCopyDir, SVNRevision.HEAD);
    }

    /**
     * 更新SVN工作副本到指定版本
     *
     * @param clientManager  SVN客户端管理器
     * @param workingCopyDir 工作副本目录
     * @param targetRevision 目标版本(SVNRevision.HEAD表示最新版本)
     * @return 更新后的实际修订版本号
     * @throws SVNException SVN操作异常
     */
    private static long updateWorkingCopy(SVNClientManager clientManager, File workingCopyDir, SVNRevision targetRevision)
            throws SVNException {
        // 获取SVN更新客户端
        SVNUpdateClient updateClient = clientManager.getUpdateClient();

        // 配置更新选项:是否忽略外部项目(如svn:externals定义的依赖)
        updateClient.setIgnoreExternals(false);

        // 执行更新操作
        /*
         * 参数说明:
         * 1. workingCopyDir:要更新的工作副本目录
         * 2. targetRevision:目标版本(HEAD表示最新版本)
         * 3. depth:更新深度(INFINITY表示递归更新所有子目录和文件)
         * 4. allowUnversionedObstructions:是否允许未版本化的文件存在
         * 5. ignoreExternals:是否忽略外部项目
         */
        long updatedRevision = updateClient.doUpdate(
                workingCopyDir,
                targetRevision,
                SVNDepth.INFINITY,
                false,  // 对应原来的allowUnversionedObstructions参数
                false   // 对应原来的ignoreExternals参数
        );

        return updatedRevision;
    }


    /**
     * 获取指定版本的所有变更路径
     */
    private static SVNLogEntryPath[] getChangedPathsForRevision(SVNRepository repository, long revision)
            throws SVNException {
        Collection<SVNLogEntry> logEntries = repository.log(
                new String[]{""},
                null,
                revision,
                revision,
                true,
                true
        );

        if (logEntries.isEmpty()) {
            return new SVNLogEntryPath[0];
        }

        SVNLogEntry logEntry = logEntries.iterator().next();
        return logEntry.getChangedPaths().values().toArray(new SVNLogEntryPath[0]);
    }


    /**
     * 将SVN仓库中两个连续版本的差异合并到本地工作副本
     *
     * @param clientManager  SVN客户端管理器
     * @param repositoryUrl  SVN仓库sit分支URL字符串
     * @param targetRevision 目标版本号,(将合并该版本与上一版本的差异):如sit合并到uat,此版本号即为sit版本号
     * @param workingCopyDir 本地uat工作副本目录
     * @throws SVNException 当SVN操作失败时抛出(如URL无效、版本不存在等)
     */
    private static void mergeRepositoryRevision(SVNClientManager clientManager,
                                                String repositoryUrl,
                                                long targetRevision,
                                                File workingCopyDir) throws SVNException {
        // 校验目标版本号有效性(至少为1,因为需要比较上一版本)
        if (targetRevision < 1) {
            throw new IllegalArgumentException("目标版本号必须大于等于1");
        }

        // 获取差异合并客户端
        SVNDiffClient diffClient = clientManager.getDiffClient();

        // 执行合并操作:将repositoryUrl仓库中(targetRevision-1)到targetRevision的差异合并到工作副本
        diffClient.doMerge(
                SVNURL.parseURIEncoded(repositoryUrl),  // 源仓库URL
                SVNRevision.create(targetRevision - 1), // 源版本(上一版本)
                SVNURL.parseURIEncoded(repositoryUrl),  // 目标仓库URL(与源仓库相同)
                SVNRevision.create(targetRevision),     // 目标版本
                workingCopyDir,                         // 合并到的本地工作副本
                SVNDepth.INFINITY,                      // 递归合并所有子目录和文件
                true,                                   // 忽略文件祖先关系
                false,                                  // 不强制合并(避免覆盖本地修改)
                false,                                  // 实际执行合并(非模拟)
                false                                   // 应用变更到文件(非仅记录)
        );
    }
}

Main.java

package org.example;

import lombok.extern.slf4j.Slf4j;
import org.example.utils.AutoMergeCode;
import org.example.utils.SVNMergeTool;

import java.io.IOException;

@Slf4j
public class Main {
    public static void main(String[] args) throws IOException {

        String arg = args[0];
        for (String sitRevision : arg.trim().split("\n")) {
            AutoMergeCode.merge(sitRevision.trim());
        }
    }
}

方法三:(强烈推荐)

优点:在方式一上进行更改增强,合并层级更加精确,提高了速度

SVNMergeCodeTool.java

package org.example.utils;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.StrUtil;
import lombok.extern.slf4j.Slf4j;
import org.tmatesoft.svn.core.*;
import org.tmatesoft.svn.core.auth.ISVNAuthenticationManager;
import org.tmatesoft.svn.core.internal.io.svn.SVNRepositoryFactoryImpl;
import org.tmatesoft.svn.core.internal.wc.DefaultSVNOptions;
import org.tmatesoft.svn.core.io.SVNRepository;
import org.tmatesoft.svn.core.io.SVNRepositoryFactory;
import org.tmatesoft.svn.core.wc.*;

import java.io.File;
import java.io.IOException;
import java.util.Collection;
import java.util.Map;

/**
 * svn合并代码
 * 优点:
 * svn所有操作均使用官方提供的svnkit库,官方保障
 * 缺点:
 * 合代码分支固定,无法根据路径进行更改;
 * 仓库层级较浅,如果代码库代码量超级庞大,则svn操作会很慢
 */
@Slf4j
public class SVNMergeCodeTool {
    // SVN仓库地址
    private static String SIT_REPOSITORY = "http://192.168.206.135/svn/ICAP/branches/sit";
    private static String UAT_REPOSITORY = "http://192.168.206.135/svn/ICAP/test/uat";

    // 本地工作副本目录
    private static String LOCAL_WORKING_COPY = "/temp/UAT";

    // SVN认证信息
    private static final String USERNAME = "lwx1091923";
    private static final String PASSWORD = "lmy1024++";

    public static void merge(String sitVision) {
        // 初始化SVNKit库
        setupLibrary();

        long sitRevision = Long.parseLong(sitVision);
        log.info("获取的SIT分支的版本号:  {}", sitRevision);

        try {
            // 执行合并操作
            mergeSitToUat(sitRevision);
            log.info("合并成功完成!");
        } catch (SVNException e) {
            log.error("合并过程中发生错误: " + e.getMessage());
            e.printStackTrace();
        } catch (IOException e) {
            throw new RuntimeException(e);
        } finally {
            //还原静态成员变量
            SIT_REPOSITORY = "http://192.168.206.135/svn/ICAP/branches/sit";
            UAT_REPOSITORY = "http://192.168.206.135/svn/ICAP/test/uat";
            LOCAL_WORKING_COPY = "/temp/UAT";
            log.info("=======================================================================================================");
        }
    }

    private static void setupLibrary() {
        // 初始化SVNKit库
        SVNRepositoryFactoryImpl.setup();
        // 不再需要调用 SVNClientManager.setup();
    }

    private static void mergeSitToUat(long sitRevision) throws SVNException, IOException {
        // 创建认证管理器
        ISVNAuthenticationManager authManager =
                SVNWCUtil.createDefaultAuthenticationManager(USERNAME, PASSWORD.toCharArray());

        // 创建SVN客户端管理器 - 使用新的API
        SVNClientManager clientManager = SVNClientManager.newInstance(
                new DefaultSVNOptions(), authManager);

        // 获取UAT工作副本
        SVNUpdateClient updateClient = clientManager.getUpdateClient();
        updateClient.setIgnoreExternals(false);

        File workingCopyDirRoot = new File(LOCAL_WORKING_COPY);


        // 获取SIT版本的日志信息
        SVNRepository sitRepository = SVNRepositoryFactory.create(
                SVNURL.parseURIEncoded(SIT_REPOSITORY));
        sitRepository.setAuthenticationManager(authManager);

        // 获取SIT版本的提交信息和作者
        Collection<SVNLogEntry> logEntries = sitRepository.log(
                new String[]{""}, // 空数组表示根目录
                null,
                sitRevision,
                sitRevision,
                true,
                true
        );

        if (logEntries.isEmpty()) {
            SVNErrorMessage e = SVNErrorMessage.create(SVNErrorCode.UNKNOWN,
                    "无法找到版本 " + sitRevision + " 的日志信息");
            throw new SVNException(e);
        }

        SVNLogEntry logEntry = logEntries.iterator().next();
        //sit分支提交的log
        String sitLogMessage = logEntry.getMessage().trim();
        //sit分支提交的作者
        String sitAuthor = logEntry.getAuthor();
        //sit分支提交代码列表清单
        Map<String, SVNLogEntryPath> changedPaths = logEntry.getChangedPaths();

        //代码服务名
        String server = (CollectionUtil.newArrayList(changedPaths.keySet())).get(0).trim().split("/")[4];
        String serverName = "【" + server + "】";

        String serverName1 = serverName.replace("【", "[").replace("】", "]");

        //遍历代码清单
        log.info("要合并的代码清单:");
        for (String path : changedPaths.keySet()) {
            log.info(path);
   /*         SVNLogEntryPath svnLogEntryPath = changedPaths.get(path);
            //更改类型: 'A' 添加, 'D' 删除, 'M' 修改, 'R' 替换等
            System.out.println(svnLogEntryPath.getType());*/
        }

        // 构建提交消息 例如:【icap-mbank】 新增登录controller xuezhiqian 52158
        String commitMessage;
        if (sitLogMessage.startsWith(serverName)) {
            commitMessage = String.format("%s %s %d",
                    sitLogMessage, sitAuthor, sitRevision);
        } else if (sitLogMessage.startsWith(serverName1)) {
            commitMessage = String.format("%s %s %d",
                    sitLogMessage.replace(serverName1, serverName), sitAuthor, sitRevision);
        } else {
            commitMessage = String.format("%s %s  %s  %d",
                    serverName, sitLogMessage, sitAuthor, sitRevision);
        }
        log.info("uat分支提交log信息:{}", commitMessage);

        //工作副本
        File workingCopyDir = null;

        // 检查工作副本是否存在,如果不存在则checkout
        if (!workingCopyDirRoot.exists() || !SVNWCUtil.isWorkingCopyRoot(workingCopyDirRoot)) {
            log.info("正在检出UAT工作副本...");
            updateClient.doCheckout(
                    SVNURL.parseURIEncoded(UAT_REPOSITORY),
                    workingCopyDirRoot,
                    SVNRevision.HEAD,
                    SVNRevision.HEAD,
                    SVNDepth.INFINITY,
                    false
            );
        } else {
            //根据要合并的代码服务名重新定义工作副本
            //获取分支仓库下具体服务地址,如:/Dubbo/icap-dubbo-cc
            //   s:  /branches/sit/Servers/icap-mbank/test/java/cn/com/common/util/StringUtil.java
            String s = (CollectionUtil.newArrayList(changedPaths.keySet())).get(0).trim();
            String s1 = s.substring(StringUtils.getNthSlashIndex(s, "/", 3), StringUtils.getNthSlashIndex(s, "/", 5));
            if (StrUtil.equals(server, "icap-hf-mbank")) {
                SIT_REPOSITORY = SIT_REPOSITORY + s1;
                UAT_REPOSITORY = UAT_REPOSITORY + s1.replace("icap-hf-mbank", "icap-mbank-hf");
                LOCAL_WORKING_COPY += "/" + s1.replace("icap-hf-mbank", "icap-mbank-hf");
                workingCopyDir = new File(LOCAL_WORKING_COPY);
            } else if (StrUtil.equals(server, "icap-jl-mbank")) {
                SIT_REPOSITORY = SIT_REPOSITORY + s1;
                UAT_REPOSITORY = UAT_REPOSITORY + s1.replace("icap-jl-mbank", "icap-mbank-jl");
                LOCAL_WORKING_COPY += "/" + s1.replace("icap-jl-mbank", "icap-mbank-jl");
                workingCopyDir = new File(LOCAL_WORKING_COPY);
            } else {
                SIT_REPOSITORY = SIT_REPOSITORY + s1;
                UAT_REPOSITORY = UAT_REPOSITORY + s1;
                LOCAL_WORKING_COPY += "/" + s1;
                workingCopyDir = new File(LOCAL_WORKING_COPY);
            }
            log.info("工作副本:{}", workingCopyDir);

            // 在更新前先执行还原操作,确保工作副本干净
            log.info("正在还原UAT工作副本...");
            revertWorkingCopy(clientManager, workingCopyDir);
            //删除非版本化文件及目录
            log.info("正在删除工作副本非版本化文件及目录...");
            try {
                LinuxCommandExecutor.executeCommand(
                        "svn status | grep '^?' | awk '{print $2}' | xargs rm -rf",
                        workingCopyDir);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }

            log.info("正在更新UAT工作副本...");
            updateClient.doUpdate(
                    workingCopyDir,
                    SVNRevision.HEAD,
                    SVNDepth.INFINITY,
                    false,
                    false
            );
        }


        log.info("正在合并SIT版本 " + sitRevision + " 到 UAT...");
        // 执行合并操作
        SVNDiffClient diffClient = clientManager.getDiffClient();
        diffClient.doMerge(
                SVNURL.parseURIEncoded(SIT_REPOSITORY),
                SVNRevision.create(sitRevision - 1),
                SVNURL.parseURIEncoded(SIT_REPOSITORY),
                SVNRevision.create(sitRevision),
                workingCopyDir,
                SVNDepth.INFINITY,
                true,
                false,
                false,
                false
        );

        // 检查是否有冲突
        SVNStatusClient statusClient = clientManager.getStatusClient();
        SVNStatus status = statusClient.doStatus(workingCopyDir, false);
        if (status != null && status.getContentsStatus() == SVNStatusType.STATUS_CONFLICTED) {
            log.error("警告: 合并过程中发现冲突,请手动解决冲突后重新提交!");
            return;
        }

        // 提交更改
        log.info("正在提交更改到UAT...");
        SVNCommitClient commitClient = clientManager.getCommitClient();
        SVNCommitInfo commitInfo = commitClient.doCommit(
                new File[]{workingCopyDir},
                false,
                commitMessage,
                null,
                null,
                false,
                false,
                SVNDepth.INFINITY
        );

        log.info("提交完成! 新版本号: " + commitInfo.getNewRevision());
    }


    /**
     * 还原工作副本中的所有本地修改(版本化内)
     *
     * @param clientManager  SVN 客户端管理器,用于获取操作 SVN 工作副本的客户端对象
     * @param workingCopyDir 需要还原的 SVN 工作副本目录(本地文件系统中的路径)
     * @throws SVNException 声明可能抛出 SVN 操作相关的异常(如路径不存在、权限不足等)
     */
    private static void revertWorkingCopy(SVNClientManager clientManager, File workingCopyDir) throws SVNException {
        SVNWCClient wcClient = clientManager.getWCClient();
        // 还原整个工作副本,包括所有子目录
        /*
        *   第一个参数new File[]{workingCopyDir}:需要还原的路径数组。这里传入包含workingCopyDir的数组,表示要还原整个工作副本目录。
            第二个参数SVNDepth.INFINITY:还原的深度。INFINITY表示递归还原所有子目录和文件(即不仅还原根目录,还包括所有子级内容)。
            第三个参数null:过滤规则(通常用于指定只还原特定类型的文件),null表示无过滤,还原所有内容。
            * */
        wcClient.doRevert(
                new File[]{workingCopyDir},
                SVNDepth.INFINITY,
                null
        );
    }


    /**
     * 获取指定版本的所有变更路径
     */
    private static SVNLogEntryPath[] getChangedPathsForRevision(SVNRepository repository, long revision)
            throws SVNException {
        Collection<SVNLogEntry> logEntries = repository.log(
                new String[]{""},
                null,
                revision,
                revision,
                true,
                true
        );

        if (logEntries.isEmpty()) {
            return new SVNLogEntryPath[0];
        }

        SVNLogEntry logEntry = logEntries.iterator().next();
        return logEntry.getChangedPaths().values().toArray(new SVNLogEntryPath[0]);
    }


    /**
     * 合并单个文件
     */
    private static void mergeSingleFile(
            SVNClientManager clientManager,
            File workingCopyDir,
            String sitPath,
            String uatPath,
            long sitRevision) throws SVNException {

        SVNDiffClient diffClient = clientManager.getDiffClient();

        try {
            // 获取SIT文件的URL
            SVNURL sitFileUrl = SVNURL.parseURIEncoded(SIT_REPOSITORY + sitPath);

            // 获取UAT工作副本中的文件路径
            File uatFile = new File(workingCopyDir, uatPath);

            // 确保目标目录存在
            uatFile.getParentFile().mkdirs();

            // 执行合并
            diffClient.doMerge(
                    sitFileUrl,
                    SVNRevision.create(sitRevision - 1),
                    sitFileUrl,
                    SVNRevision.create(sitRevision),
                    uatFile,
                    SVNDepth.EMPTY,
                    true,
                    false,
                    false,
                    false
            );
        } catch (SVNException e) {
            log.error("合并文件失败: " + sitPath + " -> " + uatPath + ": " + e.getMessage());
            // 可以选择继续处理其他文件
        }
    }
}

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐