使用Java实现SVN的webhook远程调用

本文介绍了一种利用Java编写的webhook包,实现在WindowsServer/VisualSVN环境下,SVN与Jenkins自动化编译的集成方案。通过在SVN的Post-commitHook中调用该工具,可以识别具体提交的项目并触发Jenkins构建。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言

实际上项目团队已经在推Git了,但是大多数老的项目依然在用SVN管理,现在需要大力推行Jenkins自动化编译上传仓库,但是看了下SVN带的hook也就是调用系统脚本的功能,也只能对一个repository设置hook,无法直接得到项目名。考虑再三,决定拿Java写一个webhook包,在服务器上设置SVN的Post-commit Hook来调用,用了一段时间还比较稳定(现有环境:Windows Server/ VisualSVN,其他部署环境也可尝试)。

代码

整个工具尽可能简单,由一个pom.xml 和一个 java代码文件组成,配置和代码加起来大概200行的样子。

  1. 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>cn.hook</groupId>
  <artifactId>svn-hook</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>1.8</maven.compiler.source>
    <maven.compiler.target>1.8</maven.compiler.target>
  </properties>
  <build>
  		<plugins>
  	  		<plugin>
                <artifactId>maven-assembly-plugin</artifactId>
                <configuration>
                    <descriptorRefs>
                        <descriptorRef>jar-with-dependencies</descriptorRef>
                    </descriptorRefs>
                    <archive>
                        <manifest>
                            <mainClass>cn.hook.SVNHook</mainClass>
                        </manifest>
                    </archive>
                </configuration>
                <executions>
                <execution>
                    <id>make-assembly</id>
                    <phase>package</phase>
                    <goals>
                        <goal>single</goal>
                    </goals>
                </execution>
            </executions>
            </plugin>
          </plugins>
  </build>
  <dependencies>
  	<dependency>
	    <groupId>commons-io</groupId>
	    <artifactId>commons-io</artifactId>
	    <version>2.5</version>
	</dependency>
	<dependency>
	    <groupId>commons-lang</groupId>
	    <artifactId>commons-lang</artifactId>
	    <version>2.6</version>
	</dependency>
  </dependencies>
</project>

  1. SVNHook.java
package cn.hook;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.net.HttpURLConnection;
import java.net.URL;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.Properties;

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.IOCase;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;

public class SVNHook{
	
	private static File logFile = null;
	
    public static void main(String[] args){
    	if(args.length < 1) {
    		printLog("Missing config");
    		return;
    	}
    	String configPath = args[0];
    	File configFile = new File(configPath);
    	Properties config;
    	if(!configFile.exists()) {
    		printLog("Config not exists:"+configPath);
    		return;
    	}else {
    		try {
				config = initProp(configFile);
			} catch (IOException e) {
				printLog(e);
				return;
			}
    		String logPath = config.getProperty("log");
    		logFile = new File(logPath);
    	}
    	if(args.length < 3) {
    		printLog("Missing parameter:"+StringUtils.join(args, ","));
    		return;
    	}
    	String repoPath = args[1];
    	String revision = args[2];
    	File repoDir = new File(repoPath);
    	if(!repoDir.exists()) {
    		printLog("Repo not exists:"+repoPath);
    		return;
    	}
    	printLog("Call!Repo path:"+repoPath+",Revision:"+revision);
    	try {
	    	String project = getChangeProject(repoPath, revision);
	    	if(project !=null) {
	    		String repoName = repoDir.getName();
				printLog("Dirs-changed!Repo:"+repoName+",Project:"+project);
				String filter = config.getProperty("filter");
				boolean isMatch = false;
				if(StringUtils.isNotBlank(filter)) {
					String[] matchStrArr = filter.split(",");
					for(String matchStr : matchStrArr) {
						isMatch = FilenameUtils.wildcardMatch(project, matchStr, IOCase.SENSITIVE);
						if(isMatch) break;
					}
				}
				if(isMatch) {
					String requestURL = config.getProperty("request");
					if(StringUtils.isNotBlank(requestURL)) {
						String urlString = StringUtils.replaceEach(requestURL, 
								new String[]{"{group}","{project}"},
								new String[] { repoName , project});
						request(urlString);
					}else {
						printLog("Ignore due to empty request url!");
					}
				}else {
					printLog("Ignore because not match filter!");
				}
	    	}
    	}catch(IOException e) {
    		printLog(e);
    	}	
    }
    
    private static void request(String urlString) throws IOException {
    	printLog("Request:"+urlString);
    	URL url = new URL(urlString);
    	HttpURLConnection  connection = (HttpURLConnection)url.openConnection();
    	connection.setRequestMethod("GET");
    	connection.connect();
    	int responseCode = connection.getResponseCode();
    	InputStream inputStream = connection.getInputStream();
        String response = IOUtils.toString(inputStream,"utf8");
        if(StringUtils.isNotBlank(response)) {
        	printLog(String.format("Response(%d):%s", responseCode,response));
        }else {
        	printLog(String.format("Response(%d)!", responseCode));
        }
    	connection.disconnect();
    }
    
    private static Properties initProp(File configFile) throws IOException {
    	Properties prop = new Properties();
    	InputStream is = new FileInputStream(configFile);
    	prop.load(is);
    	return prop;
    }
    
    private static String getChangeProject(String repoPath,String revision) throws IOException {
    	ProcessBuilder builder = new ProcessBuilder();
		builder.command("svnlook","dirs-changed",repoPath,"-r",revision);
		Process process = builder.start();
		InputStream is = process.getInputStream();
		List<String> lines = IOUtils.readLines(is, "gbk");
		if(lines.size() > 0) {
			String headLine = lines.get(0);
			String projectName = headLine.split("/")[0];
			return projectName;
		}
		return null;
    }
    
    private static void printLog(Throwable e){
    	StringWriter sw = new StringWriter();  
    	PrintWriter pw = new PrintWriter(sw);  
    	e.printStackTrace(pw);  
    	String msg=sw.toString();
    	printLog(msg);
    }
    
    private static void printLog(String log) {
    	SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    	String outLog = String.format("[%s] %s\r\n", format.format(new Date()),log);
    	if(logFile !=null) {
    		if(!logFile.canWrite()) {
    			System.out.println("Log file can not write:"+logFile.getAbsolutePath());
    			System.out.print(outLog);
    			return;
    		}
	    	try {
				FileUtils.write(logFile, outLog, "utf8",true);
			} catch (IOException e) {
				e.printStackTrace();
			}
    	}else {
    		System.out.print(outLog);
    	}
    }
}

打包、部署、使用

  1. 打包:mvn clean package
  2. 部署:取出编译输出路径下的svn-hook-0.0.1-SNAPSHOT-jar-with-dependencies.jar,简单改名成svn-hook.jar,放到服务器上的目录(这里是:D:\svn-hook),另外在该目录下放一个配置文件config.properties:
log=D:/svn-hook/hook.log
filter=*-lib
request=http://192.168.1.1/job/my-job/buildWithParameters?token=mytoken&group={group}&project={project}

此配置文件中:

  • log:日志文件,即每次有新提交调用过来这里会输出日志。
  • filter:一个项目过滤器,逗号分隔,可使用通配符,只有符合此过滤规则的项目才会发起远程调用。这里*-lib即代表-lib结尾的项目,详细实现即代码中的FilenameUtils.wildcardMatch 调用。
  • request:一个请求地址,这里指向一个Jenkins远程调用地址,其中{group}和{project}代表可动态替换的参数,请求时会替换成repository名称和项目名称,这样在Jenkins构建时就知道了哪个SVN项目有最新提交。
  1. 使用:在SVN服务上创建一个Post-commit Hook,调用脚本内容:java -jar D:\svn-hook\svn-hook.jar D:\svn-hook\config.properties %1 %2

总结说明

本文描述的方法解决以下问题:

  • SVN服务的hook功能简单,只提供脚本调用,不同平台Windows/Linux还需要对应平台脚本支持,使用java编写的工具包可跨平台使用且语言功能足以实现更灵活的webhook
  • SVN服务的hook不会传递具体提交项目,此工具根据传递的参数调用svnlook获取具体提交项目来执行远程调用,且可灵活过滤使指定的项目才会调用
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值