1. 程式人生 > >JGit----將 Git 嵌入你的應用

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;
    }

    /**
     * 狀態
     */
    @RequestMapping("/status")
    public static void status() {
        File RepoGitDir = new File("/blog/.git");
        Repository repo = null;
        try {
            repo = new FileRepository(RepoGitDir.getAbsolutePath());
            Git git = new Git(repo);
            Status status = git.status().call();
            log.info("Git Change: " + status.getChanged());
            log.info("Git Modified: " + status.getModified());
            log.info("Git UncommittedChanges: " + status.getUncommittedChanges());
            log.info("Git Untracked: " + status.getUntracked());
        } catch (Exception e) {
            log.info(e.getMessage());
        } finally {
            if (repo != null) {
                repo.close();
            }
        }
    }
}

.travis.yml 原始檔

language: node_js # 設定語言
node_js: stable   # 設定相應版本
cache:
  apt: true
  directories:
    - node_modules # 快取不經常更改的內容
before_install:
  - export TZ='Asia/Shanghai' # 更改時區
  - npm install hexo-cli -g
  #- chmod +x ./publish-to-gh-pages.sh  # 為shell檔案新增可執行許可權
install:
  - npm install # 安裝hexo及外掛
script:
  - hexo clean  # 清除
  - hexo g      # 生成
after_script:
  - git clone https://${GH_REF} .deploy_git
  - cd .deploy_git
  - git checkout master:gh-pages
  - cd ../
  - mv .deploy_git/.git/ ./public/
  - cd ./public
  - git config user.name  "tqlin"
  - git config user.email "[email protected]"
  # add commit timestamp
  - git add .
  - git commit -m "Travis CI Auto Builder at `date +"%Y-%m-%d %H:%M"`"
  - git push --force --quiet "https://${GH_TOKEN}@${GH_REF}" master:gh-pages && curl http://49.235.170.100:8787/pull
  - curl http://49.235.170.100:8787/pull   #這裡呼叫上面實現的拉取介面
branches:
  only:
    - master # 只監測master分支
env:
  global:
    - GH_REF: github.com/smltq/blog.git #設定GH_REF

基本概念

  • Repository 包括所有的物件和引用,用來管理原始碼

  • AnyObjectId 表示SHA1物件,可以獲得SHA1的值,進而可以獲得git物件

  • Ref 引用物件,表示.git/refs下面的檔案引用 Ref HEAD = repository.getRef("refs/heads/master");

  • RevWalk 可以遍歷提交物件,並按照順序返回提交物件

  • RevCommit 代表一個提交物件

  • RevTag 代表標籤物件

  • RevTree 代表樹物件

其它常用命令

大多數 JGit 會話會以 Repository 類作為起點,你首先要做的事就是建立一個它的例項。 對於一個基於檔案系統的倉庫來說(JGit 允許其它的儲存模型),用 FileRepositoryBuilder 完成它。

// 建立一個新倉庫
Repository newlyCreatedRepo = FileRepositoryBuilder.create(
    new File("/tmp/new_repo/.git"));
newlyCreatedRepo.create();

// 開啟一個存在的倉庫
Repository existingRepo = new FileRepositoryBuilder()
    .setGitDir(new File("my_repo/.git"))
    .build();

當你擁有一個 Repository 例項後,你就能對它做各種各樣的事。比如:

// 獲取引用
Ref master = repo.getRef("master");

// 獲取該引用所指向的物件
ObjectId masterTip = master.getObjectId();

// Rev-parse
ObjectId obj = repo.resolve("HEAD^{tree}");

// 裝載物件原始內容
ObjectLoader loader = repo.open(masterTip);
loader.copyTo(System.out);

// 建立分支
RefUpdate createBranch1 = repo.updateRef("refs/heads/branch1");
createBranch1.setNewObjectId(masterTip);
createBranch1.update();

// 刪除分支
RefUpdate deleteBranch1 = repo.updateRef("refs/heads/branch1");
deleteBranch1.setForceUpdate(true);
deleteBranch1.delete();

// 配置
Config cfg = repo.getConfig();
String name = cfg.getString("user", null, "name");

提交命令

AddCommand可以把工作區的內容新增到暫存區。

Git git = Git.open(new File("D:\\source-code\\temp\\.git"));
git.add().addFilepattern(".").call(); // 相當與git add -A新增所有的變更檔案git.add().addFilepattern("*.java")這種形式是不支援的
git.add().addFilepattern("src/main/java/").call(); // 新增目錄,可以把目錄下的檔案都新增到暫存區
//jgit當前還不支援模式匹配的方式,例如*.java

CommitCommand用於提交操作

Git git =Git.open(new File("D:\\source-code\\temp\\user1\\.git"));
CommitCommand commitCommand = git.commit().setMessage("master 23 commit").setAllowEmpty(true);
commitCommand.call();

status命令

    Git git = Git.open(new File("D:\\source-code\\temp-1\\.git"));
    Status status = git.status().call();        //返回的值都是相對工作區的路徑,而不是絕對路徑
    status.getAdded().forEach(it -> System.out.println("Add File :" + it));      //git add命令後會看到變化
    status.getRemoved().forEach(it -> System.out.println("Remove File :" + it));  ///git rm命令會看到變化,從暫存區刪除的檔案列表
    status.getModified().forEach(it -> System.out.println("Modified File :" + it));  //修改的檔案列表
    status.getUntracked().forEach(it -> System.out.println("Untracked File :" + it)); //工作區新增的檔案列表
    status.getConflicting().forEach(it -> System.out.println("Conflicting File :" + it)); //衝突的檔案列表
    status.getMissing().forEach(it -> System.out.println("Missing File :" + it));    //工作區刪除的檔案列表

log命令

LogCommand相當於git log命令

//提取某個作者的提交,並列印相關資訊
Git git = Git.open(new File("D:\\source-code\\temp-1\\.git"));
DateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Iterable<RevCommit> results = git.log().setRevFilter(new RevFilter() {
    @Override
    public boolean include(RevWalk walker, RevCommit cmit)
       throws StopWalkException, MissingObjectException, IncorrectObjectTypeException, IOException {
        return cmit.getAuthorIdent().getName().equals("xxxxx dsd");
    }

    @Override
    public RevFilter clone() {
    return this;
            }
        }).call();
results.forEach(commit -> {
    PersonIdent authoIdent = commit.getAuthorIdent();
    System.out.println("提交人:  " + authoIdent.getName() + "     <" + authoIdent.getEmailAddress() + ">");
    System.out.println("提交SHA1:  " + commit.getId().name());
    System.out.println("提交資訊:  " + commit.getShortMessage());
    System.out.println("提交時間:  " + format.format(authoIdent.getWhen()));
});

fetch命令

fetch命令

Repository rep = new FileRepository("D:\\source-code\\temp-1\\.git");
Git git = new Git(rep);
git.pull().setRemote("origin").call();
//fetch命令提供了setRefSpecs方法,而pull命令並沒有提供,所有pull命令只能fetch所有的分支
git.fetch().setRefSpecs("refs/heads/*:refs/heads/*").call();

push命令

而PushCommand和git push相同,一般都需要我們提供使用者名稱和密碼,需要用到CredentialsProvider類

Repository rep = new FileRepository("D:\\source-code\\temp-1\\.git");
Git git = new Git(rep);
git.push().setCredentialsProvider(new UsernamePasswordCredentialsProvider("myname", "password")).call();

clone命令

CloneCommand等價與git clone命令

Git.cloneRepository().setURI("https://admin@localhost:8443/r/game-of-life.git")
                .setDirectory(new File("D:\\source-code\\temp-1")).call();

RevWalk API

以下程式碼實現這樣一個功能,查詢某個檔案的歷史記錄,並把每個提交的檔案內容打印出來。

 DateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
 Repository repository = new RepositoryBuilder().setGitDir(new File("D:\\source-code\\temp-1\\.git")).build();
 try (RevWalk walk = new RevWalk(repository)) {
     Ref head = repository.findRef("HEAD");
     walk.markStart(walk.parseCommit(head.getObjectId())); // 從HEAD開始遍歷,
     for (RevCommit commit : walk) {
         RevTree tree = commit.getTree();

         TreeWalk treeWalk = new TreeWalk(repository, repository.newObjectReader());
         PathFilter f = PathFilter.create("pom.xml");
         treeWalk.setFilter(f);
         treeWalk.reset(tree);
         treeWalk.setRecursive(false);
         while (treeWalk.next()) {
             PersonIdent authoIdent = commit.getAuthorIdent();
             System.out.println("提交人: " + authoIdent.getName() + " <" + authoIdent.getEmailAddress() + ">");
             System.out.println("提交SHA1: " + commit.getId().name());
             System.out.println("提交資訊: " + commit.getShortMessage());
             System.out.println("提交時間: " + format.format(authoIdent.getWhen()));

             ObjectId objectId = treeWalk.getObjectId(0);
             ObjectLoader loader = repository.open(objectId);
             loader.copyTo(System.out);              //提取blob物件的內容
         }
     }
 }

其它更多命令參考官網

資料

  • JGit 示例原始碼
  • JGit官方文件

    Spring Boot、Cloud 學習專案