具体代码详见:https://gitee.com/RainyGao/DocSys
JGIT是非常好用的Java库,通过JGIT的API可以新建、克隆仓库、CheckOut和Commit文件,但使用JGIT时需要关注一些地方,否则会出现很多异常。
1. 关于仓库的遍历
JGIT通过treeWalk来实现对仓库的遍历,原理上是首先根据revision来获取这个revision对应的revTree,换句话说就是这个版本上的文件节点树(这些文件在该版本一定是存在的),然后获取该revTree相应的入口文件(treeWalk)来实现遍历。
默认情况下获取的treeWalk是递归的(换句话说,就是会自动进入子目录进行遍历),所以如果只是遍历所有文件那么没有问题,但实际上的应用场景而言,通常只需要获取该目录下的文件列表即可(尤其对于需要展示目录结构的场景),如果这个时候需要将treeWalk设置为不可递归。
treeWalk.setRecursive(true)
(1)获取GIT根目录的revTree的入口的方法如下:
treeWalk = new TreeWalk( repository )
treeWalk.reset(revTree)
需要注意的是如果是根目录的treeWalk,那么此时treeWalk指向的是根目录下的第一个文件,而不是根目录,换句话说根目录本身是不会有treeWalk的,所以如果在这个revision上没有文件的话,那么treeWalk将会是Null值。
由于指向的是根目录下的第一个文件,那么设置非递归的话,调用treeWalk.next就可以遍历根目录下的文件列表。
(2)获取指定的文件的treeWalk入口:
treeWalk = TreeWalk.forPath(repository, entryPath, revTree);
网上还有利用更底层的PathFilter接口来实现treeWalk的获取,我也尝试过,但似乎并不能达到我想要的效果,个人建议直接使用forPath接口即可,forPath返回的treeWalk的递归设置默认是不递归。
forPath返回的treeWalk可能是文件也可能是目录,通过treeWalk.isSubTree可以判断是不是目录,该接口是通过FileMode的值来实现的,但某些情况下treeWalk的FileMode是个Null值(例如根目录的treeWalk),所以直接对treeWalk进行判断会导致异常,因此对于treeWalk的判断需要区分是不是根目录的treeWalk。
另外treeWalk指向的是revTree的某个节点,在不递归的情况下,要遍历其子目录需要调用 treeWalk.enterSubtree()来进入子目录(treeWalk将指向该目录下的第一个文件)
2、关于CheckOut
只要treeWalk搞定了,CheckOut是最简单的,找到指定的文件节点的入口(即treeWalk),用如下代码即可将文件下载下来:
out = new FileOutputStream(localParentPath + targetName);
ObjectId blobId = treeWalk.getObjectId(0);
ObjectLoader loader = repository.open(blobId);
loader.copyTo(out);
3、关于Commit
Commit目前的实现还是依赖于jgit提供的commit接口,原理根本地的git命令没什么区别,但是对本地WorkingCopy是有依赖的(也就是说必须本地CheckOut一个branch才能Commit),这并不是我想要的(我更倾向于直接利用文件数据流直接向Stash中写入想要Commit的文件),但似乎还有点复制,所以会放在后面来实现。
以下是Commit和Push的实现代码。
RevCommit ret = null;
try {
ret = git.commit().setCommitter(commitUser, "").setMessage(commitMsg).call();
System.out.println("doAutoCommmit() commitId:" + ret.getName());
} catch (Exception e) {
System.out.println("doAutoCommmit() commit error");
e.printStackTrace();
return null;
}
if(isRemote)
{
try {
git.push().call();
} catch (Exception e) {
System.out.println("doAutoCommmit() Push Error");
e.printStackTrace();
//Do roll back commit
rollBackCommit(git, null);
return null;
}
}
rollBackCommit是用来将本地仓库还原掉,因为push失败的原因,通常是远程仓库已经有更新了,那么需要对本地仓库先进行rebase以保证本地仓库与远程仓库的同步。
4. 关于删除
实际上删除也是一个Commit操作,但有有些不同,因为删除实际上并没有内容需要commit到仓库中去,只是删除了节点,那么这时候的操作就需要一点点技巧了。