基于SVNkit的项目代码贡献量统计
SVNkit
SVNKit (JavaSVN) 是一个纯 Java 的 SVN 客户端库,使用 SVNKit 无需安装任何 SVN 的客户端,支持各种操作系统。
思路
- checkout 仓库
- 筛选需要统计的文件
- 遍历每个文件的修改版本
- 获取文件的修改版本和各版本的修改作者
- 比对各版本相对前一个版本的代码变化数据
- 修改成员的代码贡献的各个参数
实现
环境准备
LineNumber.java
一个用于记录某个成员的共享贡献指标的bean类
/**
* 各成员的代码贡献的各项指标<br/>
* 当前统计:新增代码、和修改行数
*/
class LineNumber{
// 作者
String name;
// 新增行数
long add = 0l;
// 修改行数
long modify = 0l;
// 总数
long sum = 0l;
/**
* 构造器,必须传入该记录所属作者
* @param name 作者姓名
*/
public LineNumber(String name) {
this.name = name;
}
/**
* 设置新增行数,并在总数加上新增行数
* @param add 新增行数
*/
public void setAdd(long add) {
this.add += add;
this.sum += add;
}
/**
* 设置修改行数,并在总数加上修改行数
* @param add 修改行数
*/
public void setModify(long modify) {
this.modify += modify;
this.sum += modify;
}
// getter 和 toString
public long getAdd() {
return add;
}
public long getModify() {
return modify;
}
public long getSum() {
return sum;
}
@Override
public String toString() {
return "LineNumber{" +
"name='" + name + '\'' +
", add=" + add +
", modify=" + modify +
", sum=" + sum +
'}';
}
}
SVNkitUtil.java
基于svnkit的统计功能核心实现,本初代码仅初始化该工具类的基础属性
/**
* Created by zhao.wu on 2017/2/13.
* svn 代码统计工具
*/
public class SvnKitUtils {
static {
// 开启SVN的 http和https的访问方式
DAVRepositoryFactory.setup();
}
// svn各种客户端的管理工具,使用svnkit-hl接口进行开发
private SVNClientManager ourClientManager;
// 仓库的统计开始版本,默认为1
private long defaultPreVersion = 1;
/**
* 初始化工具类,为工具类连接仓库设置认证信息
* @param username 连接用户名
* @param password 连接密码
*/
public SvnKitUtils(String username, String password) {
ISVNOptions options = SVNWCUtil.createDefaultOptions(true);
ourClientManager =SVNClientManager.newInstance((DefaultSVNOptions) options, username, password);
}
// getter 和 setter
public long getDefaultPreVersion() {
return defaultPreVersion;
}
public void setDefaultPreVersion(long defaultPreVersion) {
this.defaultPreVersion = defaultPreVersion;
}
// 其他实现,如checkout等
}
checkOut的实现
当前的统计功能设计一切均是基于本地工作目录中的代码进行操作,然后计算相关指标。所以检出功能为重中之重的核心功能,好在是svnkit的hl接口中提供了这个方法,可以直接使用。
/**
* 从仓库检出代码到工作目录下
* @param repositoryUrl 仓库地址
* @param workCopyPath 本地工作路径
* @return 当前最新版本号
*/
public long checkOut(String repositoryUrl, String workCopyPath) throws SVNException {
SVNURL svnurl = SVNURL.parseURIEncoded(repositoryUrl);
File wcDir = new File(workCopyPath);
wcDir.mkdirs();
SVNUpdateClient updateClient = ourClientManager.getUpdateClient();
updateClient.setIgnoreExternals(false);
return updateClient.doCheckout(svnurl, wcDir, SVNRevision.HEAD, SVNRevision.HEAD, SVNDepth.INFINITY, true);
}
筛选需要统计的文件
通过上边的checkout方法可将目标仓库的最新代码检出到本地工作目录,我们可以在本地工作目录中获取需要统计的文件名和路径等信息。一般情况下,我们仅能统计某一些文本文件,其他的一些安装包等非文本文件是无法进行统计。所以,我们需要对其进行筛选。
/**
* 递归遍历文件夹下指定格式的文件,并返回
* @param rootFolder 遍历的文件根目录
* @param filters 指定的文件类型,如:new String[]{".java", ".html", ".css", ".js", ".jsp", ".properties"}
* @return 目录下所有为指定类型的文件列表
*/
private List<File> scanFolder(File rootFolder, String[] filters){
List<File> paths = new ArrayList<File>();
File[] childFiles = rootFolder.listFiles();
for (File childFile : childFiles) {
if(childFile.isDirectory()){
paths.addAll(scanFolder(childFile, filters));
}else {
for (String filter : filters) {
if(childFile.getName().endsWith(filter)){
paths.add(childFile);
}
}
}
}
return paths;
}
获取某一个文件的修改版本
该方法为统计某一个文件贡献量的文件,循环调用该方法即可完成某个仓库的代码贡献量。该方法线程不安全,切记不可在多线程中调用统一实例的该方法。
/**
* 获取文件的所有更新版本,并遍历
* @param file 需要遍历的文件
* @param authors 用于保存项目作者和其指标的键值对
*/
private void computeDiff(final File file, final Map<String, LineNumber> authors) throws SVNException, IOException {
long preVersion = defaultPreVersion;
final Map<Long, String> versions = new HashMap<Long, String>();
ourClientManager.getLogClient().doLog(new File[]{file}, SVNRevision.create(defaultPreVersion), SVNRevision.HEAD, true, true, 1000000l, new ISVNLogEntryHandler() {
public void handleLogEntry(SVNLogEntry logEntry) throws SVNException {
Map<String, SVNPropertyValue> meta = logEntry.getRevisionProperties().asMap();
String value = meta.get("svn:author").getString();
if(value != null){
if(!authors.containsKey(value)){
authors.put(value, new LineNumber(value));
}
}
versions.put(logEntry.getRevision(), value);
}
});
// 遍历计算
compute(file, versions, authors);
}
遍历各版本的的差异,计算每次修改的代码贡献量
/**
* 遍历文件修改版本<br/>
* 遍历修正版本,生成diff文件
* @param file 本次统计文件
* @param versions 文件的修订版本与修订作者的键值对
* @param authors 作者与作者贡献指标键值对
*/
private void compute(File file, Map<Long, String> versions, Map<String, LineNumber> authors) throws IOException, SVNException {
File out = new File("temp");
List<String> changeList = new ArrayList<String>();
long preVersion = defaultPreVersion;
for (Map.Entry<Long, String> longStringEntry : versions.entrySet()) {
ourClientManager.getDiffClient().doDiff(file,SVNRevision.HEAD, SVNRevision.create(preVersion),SVNRevision.create(longStringEntry.getKey()),SVNDepth.INFINITY,true, new FileOutputStream(out), changeList);
computeFileLineNum(out, authors.get(longStringEntry.getValue()));
preVersion = longStringEntry.getKey();
}
}
计算某次更新记录的代码贡献量
该功能是与某个成员的贡献量指标息息相关的,决定具体统计哪些指标,当前方法仅可统计新增代码量和修改代码量。
/**
* 统计该修改版本的各项指标。<br/>
* 使用BufferedReader遍历文件的每一行数据,
* 然后判断该行代码属于指标属性,
* 最后对该作者对应的指标积进行相关操作
* @param file 版本修改文件(svn的diff文件)
* @param lineNumber 该作者的指标对象
*/
private void computeFileLineNum(File file, LineNumber lineNumber) throws IOException {
int addCount = -1;//需要减去首行‘++++’开始的一行
int removeCount = -1;//需要减去首行‘----’开始的一行
BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(file)));
String line = null;
// 遍历diff文件,计算指标
while((line = reader.readLine())!=null) {
if(line.startsWith("+")){
addCount++;
}else if(line.startsWith("-")) {
removeCount ++;
}
}
reader.close();
// 设置作者指标
lineNumber.setModify(removeCount);
lineNumber.setAdd(addCount-removeCount);
}
统计某一个仓库中代码贡献量
该方法是综合上述各个小功能点而集成的统计接口
/**
* 某一个仓库的代码贡献量的统计故居
* @param repository 仓库地址
* @param workCopyPath 本地工作路径
* @param filters 文件类型过滤字符串数组
* @return 该仓库的代码贡献量的统计结果
*/
public Collection<LineNumber> computeCodeLineForEveryone(String repository, String workCopyPath, String[] filters) throws IOException, SVNException {
// 仓库检出
checkOut(repository, workCopyPath);
// 获取需要统计的文件
List<File> files = scanFolder(new File(workCopyPath), filters );
// 遍历文件,并计算文件各个更新版本中的代码共享量
Map<String, LineNumber> authors = new HashMap<String, LineNumber>();
for (File file : files) {
System.out.println("compute:"+file.getAbsolutePath());
computeDiff(file, authors);
}
// 返回计算记过
return authors.values();
}
测试
写了个简单的main方法进行测试该功能
public static void main(final String[] args) throws SVNException, IOException {
// 初始化统计工具
SvnKitUtils test = new SvnKitUtils("test", "123456");
//test.setDefaultPreVersion(); //设置统计版本起点, 默认从第1版开始
// 仓库地址
String repositoryURL ="http://172.28.1.238:8080/svn/ProductCode/Java/Test";
// 本地工作目录
String workingCopyPath = "E:/temp1/";
// 需要统计的文件格式
String[] filters = new String[]{".java", ".html", ".css", ".js", ".jsp", ".properties"};
// 执行统计
Collection<LineNumber> lineNumbers = test.computeCodeLineForEveryone(repositoryURL, workingCopyPath, filters);
// 输出所有作者的贡献指标
for (LineNumber lineNumber : lineNumbers) {
System.out.println(lineNumber);
}
}
总结
经过半天的磕磕碰碰终于将这个小功能实现了,才发现自己的基础薄弱,知其然,不知其所以然。用svn很久了,却不清楚SVN各个操作的具体原理,导致自己不断的在试错,才让自己完成这个小功能花了如此多的时间。虽然该功能完成了,我知道这绝不是最有的解决方案。就拿我自己知道的就可以从单线程改为多线程,还有一些我不知道的svn内幕,使用low level接口解决效率应该会更高。