参考:
https://blog.csdn.net/Amy126/article/details/85335834
https://blog.csdn.net/yulin_Hu/article/details/81673446
https://github.com/smltq/spring-boot-demo/tree/master/jGit
java 使用jgit 操作 git
如果你想在一个 Java 程序中使用 Git ,有一个功能齐全的 Git 库,那就是 JGit 。 JGit 是一个用 Java 写成的功能相对健全的 Git 的实现,它在 Java 社区中被广泛使用。 JGit 项目由 Eclipse 维护,它的主页在 http://www.eclipse.org/jgit 。
1、在本地文件夹建立起与远程仓库的连接
2、根据主干master新建分支并同步到远程
3、提交commit文件到远程
4、从远程拉去代码到本地文件夹
maven依赖
<dependency> <groupId>org.eclipse.jgit</groupId> <artifactId>org.eclipse.jgit</artifactId> <version>3.7.0.201502260915-r</version> </dependency>
public class GitUtilClass { public static String localRepoPath = "D:/repo"; public static String localRepoGitConfig = "D:/repo/.git"; public static String remoteRepoURI = "git@gitlab.com:wilson/test.git"; public static String localCodeDir = "D:/platplat"; /** * 新建一个分支并同步到远程仓库 * @param branchName * @throws IOException * @throws GitAPIException */ public static String newBranch(String branchName){ String newBranchIndex = "refs/heads/"+branchName; String gitPathURI = ""; Git git; try { //检查新建的分支是否已经存在,如果存在则将已存在的分支强制删除并新建一个分支 List<Ref> refs = git.branchList().call(); for (Ref ref : refs) { if (ref.getName().equals(newBranchIndex)) { System.out.println("Removing branch before"); git.branchDelete().setBranchNames(branchName).setForce(true) .call(); break; } } //新建分支 Ref ref = git.branchCreate().setName(branchName).call(); //推送到远程 git.push().add(ref).call(); gitPathURI = remoteRepoURI + " " + "feature/" + branchName; } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (GitAPIException e) { // TODO Auto-generated catch block e.printStackTrace(); } return gitPathURI; } public static void commitFiles() throws IOException, GitAPIException{ String filePath = ""; Git git = Git.open( new File(localRepoGitConfig) ); //创建用户文件的过程 File myfile = new File(filePath); myfile.createNewFile(); git.add().addFilepattern("pets").call(); //提交 git.commit().setMessage("Added pets").call(); //推送到远程 git.push().call(); } public static boolean pullBranchToLocal(String cloneURL){ boolean resultFlag = false; String[] splitURL = cloneURL.split(" "); String branchName = splitURL[1]; String fileDir = localCodeDir+"/"+branchName; //检查目标文件夹是否存在 File file = new File(fileDir); if(file.exists()){ deleteFolder(file); } Git git; try { git = Git.open( new File(localRepoGitConfig) ); git.cloneRepository().setURI(cloneURL).setDirectory(file).call(); resultFlag = true; } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (GitAPIException e) { // TODO Auto-generated catch block e.printStackTrace(); } return resultFlag; } public static void deleteFolder(File file){ if(file.isFile() || file.list().length==0){ file.delete(); }else{ File[] files = file.listFiles(); for(int i=0;i<files.length;i++){ deleteFolder(files[i]); files[i].delete(); } } } public static void setupRepo() throws GitAPIException{ //建立与远程仓库的联系,仅需要执行一次 Git git = Git.cloneRepository().setURI(remoteRepoURI).setDirectory(new File(localRepoPath)).call(); }
JGit 使用说明
初步
- jdk 1.8
- 第一次我们需要clone这个git,这个git就是我们的schema集。
Git git=Git.cloneRepository()
.setURI("git号")
.setDirectory(new File("gitProject"))
.call();
这样的话,这个git工程就被clone到了我们指定的目录。
3. 当然第二次我们不能再clone了,我们只需要打开上次的git工程就可以进行操作。
Git git=Git.open(new File("gitProject"));
- 当我们新增或是修改一个文件的时候:
DirCache index=git.add().addFilepattern("schemas/test.md").call();
RevCommit commit=git.commit().setMessage("addFile").call();
git.push().call();
这个新增的文件需要位于我们拉下来的那个git工程里面。
- 查看一个文件所有的版本(也就是提交记录):在git的命令行中,我们是通过git log 或是git log –filename来实现。这个通过API的实现方式如下:
git.log().addPath(dir/filename.txt).setMaxCount(num).call();
setMaxCount可以指定返回最近num个版本,addPath则是指定查看文件.返回的是Iterable,我们可以通过其迭代器对其进行遍历。我们需要的是得到每一次变更的时间,message,提交的内部识别码,提交人
Iterable<RevCommit> iterable=git.log().call();
Iterator<RevCommit> iter=iterable.iterator();
while (iter.hasNext()){
RevCommit commit=iter.next();
String email=commit.getAuthorIdent().getEmailAddress();
String name=commit.getAuthorIdent().getName(); //作者
String commitEmail=commit.getCommitterIdent().getEmailAddress();//提交者
String commitName=commit.getCommitterIdent().getName();
int time=commit.getCommitTime();
String fullMessage=commit.getFullMessage();
String shortMessage=commit.getShortMessage(); //返回message的firstLine
String commitID=commit.getName(); //这个应该就是提交的版本号
System.out.println("authorEmail:"+email);
System.out.println("authorName:"+name);
System.out.println("commitEmail:"+commitEmail);
System.out.println("commitName:"+commitName);
System.out.println("time:"+time);
System.out.println("fullMessage:"+fullMessage);
System.out.println("shortMessage:"+shortMessage);
System.out.println("commitID:"+commitID);
}
结果:这个log我们并没有指定哪一个文件,也没有指定返回多少个,我们可以如前面提到那样指定文件,指定返回个数,但是从结果中我们确实得到我们想要的东西
authorEmail:yulin@DESKTOP-ALAIMHD
authorName:yulin
commitEmail:yulin@DESKTOP-ALAIMHD
commitName:yulin
time:1515468403
fullMessage:addFile
shortMessage:addFile
commitID:d22491b948e8013df552549a753dcafd4d9b3c4b
authorEmail:***
authorName:***
commitEmail:***
commitName:***
time:1515463064
fullMessage:[添加]gitignore文件
shortMessage:[添加]gitignore文件
commitID:be1be26068cd4fb5653c6efd3299f465d5863234
注意这里有这样一个问题,如果你使用了.addPath(dir/filename.txt),也就是你只想得到某个文件的提交。这种方式的确可以实现,如果某一次的提交,包含了多个文件,其中包含了这个文件,该次提交也会被包含到结果其中。(其实这个还是可以理解的。)
- 我们得到指定文件的所有版本后,需要去取得每一个版本的变化,这样我们才能显示出每一个版本不同的内容。git命令可以用diff实现,那么在JGit中的调用呢.
如我们比较一个特定文件最近两次的提交内容的不同。那么我们首先需要得到最近两次的commit。然后根据commit得到变化内容。如下:
Git git=Git.open(new File("gitProject"));
Repository repository=git.getRepository();
List<RevCommit> list=new ArrayList<RevCommit>();
Iterable<RevCommit> iterable=git.log().addPath("schemas/test1.md").setMaxCount(2).call();
for(RevCommit revCommit:iterable){
list.add(revCommit);
}
if(list.size()==2){
AbstractTreeIterator newCommit=getAbstractTreeIterator(list.get(0),repository);
AbstractTreeIterator oldCommit=getAbstractTreeIterator(list.get(1),repository);
List<DiffEntry> diff=git.diff().setOldTree(oldCommit).setNewTree(newCommit).call();
ByteArrayOutputStream outputStream=new ByteArrayOutputStream();
DiffFormatter diffFormatter=new DiffFormatter(outputStream);
//设置比较器为忽略空白字符对比(Ignores all whitespace)
diffFormatter.setDiffComparator(RawTextComparator.WS_IGNORE_ALL);
diffFormatter.setRepository(repository); // 这里为什么还要设置它
for(DiffEntry diffEntry:diff){
diffFormatter.format(diffEntry);
System.out.println(outputStream.toString("UTF-8"));
outputStream.reset();
}
}
git.close();
另外需要通过下面这个方法根据commit得到AbstractTreeIterator,如下:
public static AbstractTreeIterator getAbstractTreeIterator(RevCommit commit, Repository repository ){
RevWalk revWalk=new RevWalk(repository);
CanonicalTreeParser treeParser=null;
try {
RevTree revTree=revWalk.parseTree(commit.getTree().getId());
treeParser=new CanonicalTreeParser();
treeParser.reset(repository.newObjectReader(),revTree.getId());
revWalk.dispose();
} catch (IOException e) {
e.printStackTrace();
}
return treeParser;
}
通过以上代码,我们可以得到schemas/test1.md文件两次commit内容的不同,结果如下:
diff --git a/schemas/test.md b/schemas/test.md
index e8fce5c..c226794 100644
--- a/schemas/test.md
+++ b/schemas/test.md
@@ -1,4 +1,4 @@
-# JSON测试效率总结
+# JSON测试效率总结 test4
我们可以看到得到的结果的变化内容已经用 - +进行了标注,这与我们平常看到的diff命令结果是相符合的
但是这里就有这样的一个问题,我们虽然通过addPath来得到了某个文件的commit,但是我们得到diff内容是通过commit来的,如果一次commit包含多个文件,那么我们的diff内容自然也会所有更改文件的内容,那么这与我们说的得到某个文件的变化内容就有一定的出入了,但是这是因为我们的一次commit包含多个文件修改导致的。
那么我们能否对DiffEntry的内容进行筛选呢?通过前面的代码我们看到事实上我们的变化内容是通过DiffEntry来得到的,如果一次提交内容包含了多个文件的改变,那么我们也会得到对应数目的DiffEntry,我们需要对DiffEntry进行筛选,从而挑选出对应特定文件的DiffEntry,从而得到特定文件的变化内容,接下来试一试。
-
筛选DiffEntry
发现DiffEntry中有oldPath,newPath这样的属性。
/** File name of the old (pre-image). */
protected String oldPath;
/** File name of the new (post-image). */
protected String newPath;
那么如果我们得到了文件名,那就就可以根据文件名进行筛选了,如下:
for(DiffEntry diffEntry:diff){
diffFormatter.format(diffEntry);
System.out.println("new Path:____"+diffEntry.getNewPath());
System.out.println("old path:____"+diffEntry.getOldPath());
System.out.println(outputStream.toString("UTF-8"));
outputStream.reset();
}
结果:确实我们得到了文件名
new Path:____schemas/test.md
old path:____schemas/test.md
diff --git a/schemas/test.md b/schemas/test.md
index e8fce5c..c226794 100644
--- a/schemas/test.md
+++ b/schemas/test.md
@@ -1,4 +1,4 @@
-# JSON测试效率总结
+# JSON测试效率总结 test4
- 通过前面我们基本可以得到制指定文件版本之间的差异内容,接下来我们去获取指定文件指定版本的文件内容,以下为示例代码:
public static ByteArrayOutputStream read(String revision, Git git) {
ByteArrayOutputStream out = new ByteArrayOutputStream();
Repository repository = null;
try {
//gitDir表示git库目录
// Git git = Git.open(new File("gitProject"));
repository = git.getRepository();
RevWalk walk = new RevWalk(repository);
ObjectId objId = repository.resolve(revision);
RevCommit revCommit = walk.parseCommit(objId);
RevTree revTree = revCommit.getTree();
//child表示相对git库的文件路径
TreeWalk treeWalk = TreeWalk.forPath(repository, "schemas/test.md", revTree);
ObjectId blobId = treeWalk.getObjectId(0);
ObjectLoader loader = repository.open(blobId);
loader.copyTo(out);
} catch (IOException e) {
e.printStackTrace();
} catch (JGitInternalException e) {
e.printStackTrace();
} finally {
if (repository != null)
repository.close();
}
return out;
}
//调用
ByteArrayOutputStream outputStream=read("f532e63bac93f05345da1ff665687e69df9732dc",git);
System.out.println(outputStream.toString("UTF-8"));
我们仍然是通过commitID去获取,不过这里是直接给出了CommitID,我们同样可以像前面的代码那样先获取commit,结果我们确实拿到了这个版本文件的全部内容。(结果太多就不进行展示了)
JGit常用功能(提交、回滚、日志查询)
public class GitUtil { private final static String GIT = ".git"; private final static String REF_REMOTES = "refs/remotes/origin/"; /** * 将文件列表提交到git仓库中 * @param gitRoot git仓库目录 * @param files 需要提交的文件列表 * @param remark 备注 * @return 返回本次提交的版本号 * @throws IOException */ public static String commitToGitRepository(String gitRoot, List<String> files, String remark) throws Exception { if (StringUtils.isNotBlank(gitRoot) && files != null && files.size() > 0) { File rootDir = new File(gitRoot); //初始化git仓库 if (new File(gitRoot + File.separator + GIT).exists() == false) { Git.init().setDirectory(rootDir).call(); } //打开git仓库 Git git = Git.open(rootDir); //判断工作区与暂存区的文件内容是否有变更 List<DiffEntry> diffEntries = git.diff() .setPathFilter(PathFilterGroup.createFromStrings(files)) .setShowNameAndStatusOnly(true).call(); if (diffEntries == null || diffEntries.size() == 0) { throw new Exception("提交的文件内容都没有被修改,不能提交"); } //被修改过的文件 List<String> updateFiles = new ArrayList<String>(); ChangeType changeType; for (DiffEntry entry : diffEntries) { changeType = entry.getChangeType(); switch (changeType) { case ADD: case COPY: case RENAME: case MODIFY: updateFiles.add(entry.getNewPath()); break; case DELETE: updateFiles.add(entry.getOldPath()); break; } } //将文件提交到git仓库中,并返回本次提交的版本号 //1、将工作区的内容更新到暂存区 AddCommand addCmd = git.add(); for (String file : updateFiles) { addCmd.addFilepattern(file); } addCmd.call(); //2、commit CommitCommand commitCmd = git.commit(); for (String file : updateFiles) { commitCmd.setOnly(file); } RevCommit revCommit = commitCmd.setCommitter("yonge", "654166020@qq.com") .setMessage(remark).call(); return revCommit.getName(); } return null; } /** * 回滚到指定版本的上一个版本 * @param gitRoot git仓库目录 * @param diffEntries 需要回滚的文件 * @param revision 版本号 * @param remark 备注 * @return * @throws Exception */ public static boolean rollBackPreRevision(String gitRoot, List<DiffEntry> diffEntries, String revision, String remark) throws Exception { if (diffEntries == null || diffEntries.size() == 0) { throw new Exception("没有需要回滚的文件"); } Git git = Git.open(new File(gitRoot)); List<String> files = new ArrayList<String>(); //注意:下面的reset命令会将暂存区的内容恢复到指定(revesion)的状态,相当于取消add命令的操作 /*Repository repository = git.getRepository(); RevWalk walk = new RevWalk(repository); ObjectId objId = repository.resolve(revision); RevCommit revCommit = walk.parseCommit(objId); String preVision = revCommit.getParent(0).getName(); ResetCommand resetCmd = git.reset(); for (String file : files) { resetCmd.addPath(file); } resetCmd.setRef(preVision).call(); repository.close();*/ //取出需要回滚的文件,新增的文件不回滚 for (DiffEntry diffEntry : diffEntries) { if (diffEntry.getChangeType() == ChangeType.DELETE) { continue; } else { files.add(diffEntry.getNewPath()); } } if (files.size() == 0) { throw new Exception("没有需要回滚的文件"); } //checkout操作会丢失工作区的数据,暂存区和工作区的数据会恢复到指定(revision)的版本内容 CheckoutCommand checkoutCmd = git.checkout(); for (String file : files) { checkoutCmd.addPath(file); } //加了“^”表示指定版本的前一个版本,如果没有上一版本,在命令行中会报错,例如:error: pathspec '4.vm' did not match any file(s) known to git. checkoutCmd.setStartPoint(revision + "^"); checkoutCmd.call(); //重新提交一次 CommitCommand commitCmd = git.commit(); for (String file : files) { commitCmd.setOnly(file); } commitCmd.setCommitter("yonge", "654166020@qq.com").setMessage(remark).call(); return true; } /** * 获取上一版本的变更记录,如果是新增的文件,不会显示,因为做回滚时不需要回滚新增的文件 * @param gitRoot git仓库目录 * @param revision 版本号 * @return * @throws Exception */ public static List<DiffEntry> rollBackFile(String gitRoot, String revision) throws Exception { Git git = Git.open(new File(gitRoot)); Repository repository = git.getRepository(); ObjectId objId = repository.resolve(revision); Iterable<RevCommit> allCommitsLater = git.log().add(objId).call(); Iterator<RevCommit> iter = allCommitsLater.iterator(); RevCommit commit = iter.next(); TreeWalk tw = new TreeWalk(repository); tw.addTree(commit.getTree()); commit = iter.next(); if (commit != null) { tw.addTree(commit.getTree()); } else { throw new Exception("当前库只有一个版本,不能获取变更记录"); } tw.setRecursive(true); RenameDetector rd = new RenameDetector(repository); rd.addAll(DiffEntry.scan(tw)); List<DiffEntry> diffEntries = rd.compute(); if (diffEntries == null || diffEntries.size() == 0) { return diffEntries; } Iterator<DiffEntry> iterator = new ArrayList<DiffEntry>(diffEntries).iterator(); DiffEntry diffEntry = null; while (iterator.hasNext()) { diffEntry = iterator.next(); System.out.println("newPath:" + diffEntry.getNewPath() + " oldPath:" + diffEntry.getOldPath() + " changeType:" + diffEntry.getChangeType()); if (diffEntry.getChangeType() == ChangeType.DELETE) { iterator.remove(); } } return diffEntries; }
JGit----将 Git 嵌入你的应用
如果你想在一个 Java 程序中使用 Git ,有一个功能齐全的 Git 库,那就是 JGit 。 JGit 是一个用 Java 写成的功能相对健全的 Git 的实现,它在 Java 社区中被广泛使用。 JGit 项目由 Eclipse 维护,它的主页。
依赖添加
有很多种方式可以将 JGit 依赖加入到你的项目,并依靠它去写代码。 最简单的方式也许就是使用 Maven 。你可以通过在你的 pom.xml 文件里的 标签中增加像下面这样的片段来完成这个整合。
<dependency>
<groupId>org.eclipse.jgit</groupId>
<artifactId>org.eclipse.jgit</artifactId>
<version>5.5.1.201910021850-r</version>
</dependency>
在你读到这段文字时 version 很可能已经更新了,所以请浏览 http://mvnrepository.com/artifact/org.eclipse.jgit/org.eclipse.jgit 以获取最新的仓库信息。 当这一步完成之后, Maven 就会自动获取并使用你所需要的 JGit 库。
项目实践
在搭建我的博客的过程中,因为该博客是部署在自己的服务器上,需要在ci自动编译完成后,实现自动部署到我的服务器上(该步实现的方式很多,通过开放git接口,有编译部署的时候自动拉取到我的服务器就是其中的一个方法)
以下主要使用了pull拉取方法
package com.easy.jGit.controller;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.jgit.api.*;
import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.internal.storage.file.FileRepository;
import org.eclipse.jgit.lib.Repository;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.io.File;
@RestController
@Slf4j
public class JGitController {
/**
* git仓路径
*/
final String patch = "/opt/webapps/blog/.git";
/**
* 代码分支
*/
final String branch = "origin/gh-pages";
/**
* 拉取
*
* @return
*/
@RequestMapping("/pull")
public String pull() {
String result;
Repository repo = null;
try {
repo = new FileRepository(new File(patch));
Git git = new Git(repo);
log.info("开始重置");
//重置
git.reset()
.setMode(ResetCommand.ResetType.HARD)
.setRef(branch).call();
log.info("开始拉取");
//拉取
git.pull()
.setRemote("origin")
.setRemoteBranchName("gh-pages")
.call();
result = "拉取成功!";
log.info(result);
} catch (Exception e) {
result = e.getMessage();
} finally {
if (repo != null) {
repo.close();
}
}
return result;
}
/**
* 重置
*
* @return
*/
@RequestMapping("/reset")
public String reset() {
String result;
Repository repo = null;
try {
repo = new FileRepository(new File(patch));
Git git = new Git(repo);
git.reset().setMode(ResetCommand.ResetType.HARD).setRef(branch).call();
result = "重置成功!";
} catch (Exception e) {
result = e.getMessage();
} finally {
if (repo != null) {
repo.close();
}
}
return result;
}
/**
* 恢复
*/
@RequestMapping("/revert")
public String revert() {
String result;
Repository repo = null;
try {
repo = new FileRepository(new File(patch));
Git git = new Git(repo);
git.revert().call();
result = "恢复成功!";
} catch (Exception e) {
result = e.getMessage();
} finally {
if (repo != null) {
repo.close();
}
}
return result;
}
/**
* 克隆
*
* @return
*/
@RequestMapping("/clone")
public String clone() {
String result;
try {
Git.cloneRepository()
.setURI("https://github.com/smltq/blog.git")
.setDirectory(new File("/blog"))
.call();
result = "克隆成功了!";
} catch (GitAPIException e) {
result = e.getMessage();
e.printStackTrace();
}
return result;
}