在之前这篇博客中,我介绍了如何使用JGit获取一次Commit修改的文件列表。那如果我们想获得更多的统计信息呢?如每一个文件被修改的代码行数。
一般情况下,可以使用诸如:
git log --date-order --stat > change-details.txt
和:
git log --date-order --pretty=fuller --name-status > version-log-order.txt
的Git Log命令来获取多个Git Log的文本文件,再通过分析文本文件即可以得到。但是问题在于,例如这样的记录:
fs/readdir.c | 128 +++++++++++++++++++++++++++++++++++++++++++----------------
如果文件路径特别长(在C、C++项目中一般不会出现,但是在较复杂的Java项目中是可能出现的),导致文件路径中可能出现省略号,从而使文件关联不准确。这时候我们就需要一些更准确的获取方法。
在给出代码之前,简单提一句,对于下面代码段中这条语句中的方法:
ObjectId versionId=Repo.resolve(versionTag);
可以看到其Java Doc:https://download.eclipse.org/jgit/site/5.3.0.201903130848-r/apidocs/org/eclipse/jgit/lib/Repository.html#resolve-java.lang.String-
JGit完整API文档链接:https://download.eclipse.org/jgit/site/5.3.0.201903130848-r/apidocs/index.html (可能会由版本变化而变化)
中介绍,其字符串参数可以是:SHA-1,也可以是Tag:id^{tag}: ensure id is a tag
我当时看文档的时候是在不了解id^{}是个什么鬼。尝试了一下,发现直接给出tag对应的字符串即可。
下面直接给出代码,主要参考了这位网友的介绍:https://blog.csdn.net/u012621115/article/details/52171679
(CSDN这个排版不能正常缩进了,也是呵呵呵呵了)
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.diff.DiffEntry;
import org.eclipse.jgit.diff.DiffFormatter;
import org.eclipse.jgit.diff.Edit;
import org.eclipse.jgit.diff.EditList;
import org.eclipse.jgit.diff.RawTextComparator;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectReader;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.patch.FileHeader;
import org.eclipse.jgit.patch.HunkHeader;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.storage.file.FileRepositoryBuilder;
import org.eclipse.jgit.treewalk.CanonicalTreeParser;
public class JGitMetricsBack {
static Repository Repo;
static void printTime(int commitTime) {
SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String timestampString=String.valueOf(commitTime);
Long timestamp = Long.parseLong(timestampString) * 1000;
String date = formatter.format(new Date(timestamp));
System.out.println("It's commit time: "+date);
}
static List getChangedFileList(RevCommit revCommit, Repository repo) {
List returnDiffs = null;
try {
RevCommit previsouCommit=getPrevHash(revCommit,repo);
if(previsouCommit==null)
return null;
ObjectId head=revCommit.getTree().getId();
ObjectId oldHead=previsouCommit.getTree().getId();
System.out.println("Printing diff between the Revisions: " + revCommit.getName() + " and " + previsouCommit.getName());
// prepare the two iterators to compute the diff between
try (ObjectReader reader = repo.newObjectReader()) {
CanonicalTreeParser oldTreeIter = new CanonicalTreeParser();
oldTreeIter.reset(reader, oldHead);
CanonicalTreeParser newTreeIter = new CanonicalTreeParser();
newTreeIter.reset(reader, head);
// finally get the list of changed files
try (Git git = new Git(repo)) {
List diffs= git.diff()
.setNewTree(newTreeIter)
.setOldTree(oldTreeIter)
.call();
for (DiffEntry entry : diffs) {
// System.out.println("Entry: " + entry);
}
returnDiffs=diffs;
} catch (GitAPIException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return returnDiffs;
}
public static RevCommit getPrevHash(RevCommit commit, Repository repo) throws IOException {
try (RevWalk walk = new RevWalk(repo)) {
// Starting point
walk.markStart(commit);
int count = 0;
for (RevCommit rev : walk) {
// got the previous commit.
if (count == 1) {
return rev;
}
count++;
}
walk.dispose();
}
//Reached end and no previous commits.
return null;
}
public static void main(String[] args) {
// TODO Auto-generated method stub
// String versionTag="v2.6.19";//定位到某一次Commi,既可以使用Tag,也可以使用其hash
String versionTag="9f79b78ef74436c7507bac6bfb7b8b989263bccb";
String path="D:\\Projects\\Linux-Kernel\\linux-stable";
FileRepositoryBuilder builder = new FileRepositoryBuilder();
builder.setMustExist(true);
builder.addCeilingDirectory(new File(path));
builder.findGitDir(new File(path));
try {
Repo=builder.build();
RevWalk walk = new RevWalk(Repo);
ObjectId versionId=Repo.resolve(versionTag);
RevCommit verCommit=walk.parseCommit(versionId);
String commitName=verCommit.getName();
System.out.println("This version is: "+versionTag+", It hash: "+commitName);//如果通过Tag定位,可以获得其SHA-1 Hash Value
int verCommitTime=verCommit.getCommitTime();
printTime(verCommitTime);//打印出本次Commit的时间
System.out.println("The author is: "+verCommit.getAuthorIdent().getEmailAddress());//获得本次Commit Author的邮箱
List diffFix=getChangedFileList(verCommit,Repo);//获取变更的文件列表
ByteArrayOutputStream out = new ByteArrayOutputStream();
DiffFormatter df = new DiffFormatter(out);
// df.setDiffComparator(RawTextComparator.WS_IGNORE_ALL);//如果加上这句,就是在比较的时候不计算空格,WS的意思是White Space
df.setRepository(Repo);
for (DiffEntry entry : diffFix) {
df.format(entry);
String diffText = out.toString("UTF-8");
// System.out.println(diffText);
System.out.println(entry.getNewPath());//变更文件的路径
FileHeader fileHeader = df.toFileHeader(entry);
List hunks = (List) fileHeader.getHunks();
int addSize = 0;
int subSize = 0;
for(HunkHeader hunkHeader:hunks){
EditList editList = hunkHeader.toEditList();
for(Edit edit : editList){
subSize += edit.getEndA()-edit.getBeginA();
addSize += edit.getEndB()-edit.getBeginB();
}
}
System.out.println("addSize="+addSize);//增加和减少的代码行数统计,我和Git Log核对了一下,还挺准确的。
System.out.println("subSize="+subSize);
out.reset();
}
}
catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}