參考:
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; } /**