Google Code Prettify

顯示具有 JGit 標籤的文章。 顯示所有文章
顯示具有 JGit 標籤的文章。 顯示所有文章

2015年10月23日 星期五

JGit / Gradle

項目日期
*
  JGit
1JGit - getting started
2015/10/21
2JGit - 基本命令
2015/10/22
3JGit - 走訪、差異比對、日誌
2015/10/23
*
  Gradle
1Gradle: task
2018/02/23
2Gradle: 編譯、測試 web 專案
2017/08/10
3Gradle: 編譯 spring boot application
2018/02/26


JGit - 走訪、差異比對、日誌



這是 git 各個階段版本控管的概念圖,非常重要! 雖然會想了解 JGit 的人,一定對這些都很熟了,但是還是貼出來大家看一下,因為這和接下來要談的差異比對有關。

首先看一下我的環境,如下圖所示,分了幾個 branch,故意弄的有點亂是為了測試。
要走訪 git repository,要先確定是要走訪那個 branch? 及要走訪的節點,一般來說,都是走訪 commit 或 tag 所在的節點。當然更詳細一點點的如下圖: (這些圖都是 TortoiseGit 產生的)。
這上面的每個節點的 name 和 object id,是非常重要的,要找到節點,就靠這些了! 下面是這篇要說明的程式,它會走訪被版控的檔案、目錄、並進行差異比對。 前兩篇說明過的。這篇不會重複,如果有疑問請翻閱前兩篇。
 1 public class DiffTest {
 2 
 3     @Before
 4     public void setUp() throws Exception {
 5     }
 6 
 7     @After
 8     public void tearDown() throws Exception {
 9     }
10 
11     //@Test
12     public void testDiff() throws Exception {
13         Repository repo = new FileRepositoryBuilder()
14         .setGitDir(new File("D:/TestJGit/local/.git"))
15         .build();
16         
17         RevTree headTree = getRevTree(repo, "refs/heads/master");
18         RevTree branchTree = getRevTree(repo, "refs/heads/branch_001");
19 
20         ObjectReader reader = repo.newObjectReader();
21         List<DiffEntry> listDiffs = compareTree(repo, reader, headTree,    branchTree);
22         
23         for (DiffEntry diff : listDiffs) {
24             System.out.println(diff);
25             FileMode mode = diff.getNewMode();
26             System.out.println(mode.toString());
27         }
28         
29         repo.close();
30     }
31     
32     @Test
33     public void testWalk() throws Exception {
34         Repository repo = new FileRepositoryBuilder()
35         .setGitDir(new File("D:/TestJGit/local/.git"))
36         .build();
37         
38         RevTree headTree = getRevTree(repo, "refs/heads/branch_003");
39         
40         try (TreeWalk treeWalk = new TreeWalk(repo)) {
41             treeWalk.addTree(headTree);
42             treeWalk.setRecursive(false);
43             
44             while (treeWalk.next()) {
45                 String rawPath = new String(treeWalk.getPathString());
46                 FileMode mode = treeWalk.getFileMode(0);
47                 System.out.print("rawPath = " + rawPath);
48                 if ((mode.getBits() & FileMode.TYPE_FILE) != 0) {
49                     System.out.println(" -- file");
50                 }
51                 if ((mode.getBits() & FileMode.TYPE_TREE) != 0) {
52                     System.out.println(" -- folder");
53                 }
54                 System.out.println(mode.toString());
55                 System.out.println(mode.getBits());
56                 System.out.println(treeWalk.getNameString());
57             }
58         }
59     }
60 
61     private List<DiffEntry> compareTree(Repository repo, ObjectReader reader,
62             RevTree newTree, RevTree oldTree)
63             throws IncorrectObjectTypeException, IOException, GitAPIException {
64         
65         CanonicalTreeParser newTreeIter = new CanonicalTreeParser();
66         newTreeIter.resetRoot(reader, newTree.getId());
67         
68         CanonicalTreeParser oldTreeIter = new CanonicalTreeParser();
69         oldTreeIter.resetRoot(reader, oldTree.getId());
70         
71         Git git = new Git(repo);
72         List<DiffEntry> listDiffs = git.diff().setOldTree(oldTreeIter).setNewTree(newTreeIter).call();
73         return listDiffs;
74     }
75 
76     private RevTree getRevTree(Repository repo, String objectName)
77             throws AmbiguousObjectException, IncorrectObjectTypeException,
78             IOException, MissingObjectException {
79         
80         RevTree tree = null;
81         try (RevWalk revWalk = new RevWalk(repo)) {
82             ObjectId objId = repo.resolve(objectName);
83             RevCommit commit = revWalk.parseCommit(objId);
84             tree = commit.getTree();
85         }
86         
87         return tree;
88     }
89 }
  • 先找到樹頭: getRevTree() method 就是用 object name (revision string) 去找到樹頭! 像是程式 17、18 行,我們要比對的是 master 和 branch_003 間的差異,就是先把這兩個節點的 object name 傳入 getRevTree() 以取得樹頭。JGit 的觀念多半是,要做什麼事,就會有個相對應的類別,這裡提供的就是 RevWalk 這個類別。簡單說明一下這個 method 的運作。
  1. 程式第 82 行,resolve() method 可以由 object name 取得 object id,因為很多 method 要的參數是 object id。 
  2. 程式第 83 行,RevWalk 的 parseCommit() 可以根據傳入的 object id 得到參考到 commit 那個節點的物件。 
  3. 由 RevCommit 的 getTree() 即可取得那個 commit 時的所有目錄、檔案,當然這些檔案的內容也是那個當下的內容。
  • 差異比對: 在 17、18 行取得檔案的樹狀結構後,在 21 行透過 compareTree() 比對,結果會以 List<DiffEntry> 傳回,這個 method 的寫法我還不太懂,先不解釋。
  • TreeWalk (第 40 行) 是 JGit 提供用來走訪檔案樹狀結構的類別,只要將檔案樹狀結構傳入 (第 41 行) 就可以開始走訪各個節點,setRecursive 傳入 false 就只會走訪最上層,要完整的走訪整棵樹就傳入 true。
  • 每個節點都可以取得它的 FileMode,這是記錄該節點基本屬性的物件,像是第 48、51 行所顯示的,呼叫 getBits() 後,傳回的整數其實是每個 bit 有它的意義,可以透過遮罩的方式取得各個 bit 的值,上面的程式就示範了判斷該節點是檔案或路徑的方法。
  • FileMode 的 toString() 在 API Document 裡雖然有說明,僅適合用在 debug 時,不過,這個 method 可傳回如 100644 這樣的字串,它是 getBits() 取得的數字以 8 進位表示成字串,後面三個數字是像 Unix 的權限表示法,644 就是 rw-r--r--。
差異比對不只可以比對不同 branch 間的差異,在文章一開頭顯示的那一張圖上的所有階段都可以比對。實際看過 API Documents 的人會發現,面的 FileMode 只記錄節點的基本屬性,那比我們想要了解的絕對少的多,要了解更多資訊,要從日誌查,下面的程式說明如何取得日誌 (git log)。
Repository repo = new FileRepositoryBuilder()
        .setGitDir(new File("D:/TestJGit/local/.git"))
        .build();
Git git = new Git(repo);
        
LogCommand logCmd = git.log();
ObjectId objId = repo.resolve("refs/remotes/origin/master");
Iterable<RevCommit> revCommit = logCmd.add(objId).call();
Iterator<RevCommit> commitIter = revCommit.iterator();
while (commitIter.hasNext()) {
    RevCommit commit = commitIter.next();
    System.out.println("commiter name = " + commit.getAuthorIdent().getName());
    System.out.println("commiter email = " + commit.getAuthorIdent().getEmailAddress());
    Date date = new Date(((long) commit.getCommitTime()) * 1000);
    System.out.println("commit time = " + date.toString());
}
透過 LogCommand 可以查得日誌,上面只簡單的舉幾個例子,像是 commit 的帳號、email、時間,詳細當然就請直接查 Documents。上面的時間有乘以 1000,是因為 getCommitTime() 傳回的值只到秒,而 Date 需要的是毫秒。



2015年10月22日 星期四

JGit - 基本命令

上一篇之後,這一篇說明一些 git 基本命令所對應的 API。在開始正式內容前,先說明一下我的環境,在我的電腦裡,有兩個目錄,分別為 D:/TestJGit/local 及 D:/TestJGit/remote,第一個目錄是 git repository 第二個是 bare repository,是第一個 repository 的遠端。
  • git init
File file = new File("D:/TestJGit/local");
Git git = Git.init().setDirectory(file).call();
git.close();
這個指令要注意,檔案系統中要已存在該目錄,才可以在該目錄建立 git repository。
  • git init --bare
Git git = Git.init().setDirectory(file).setBare(true).call();
setBare 預設是 false,所以第一個程式沒有呼叫這個 method,要建立 bare repository 只要如上呼叫 setBare 並傳入 true 即可。
  • git add
File file = new File("D:/TestJGit/local/.git");
Git git = Git.open(file);
AddCommand addCmd = git.add().addFilepattern("folder_1/local_d.txt");
addCmd.call();
git.close();
git 是分散式的版本控管,不管是要存取 local 或 remote 端,都先開啟 local 端的 git repository,要注意上面開始的是真正 git 的檔案所在的目錄 (.git),另一個要注意的是,要加入的檔案,是以 D:/TestJGit/local 目錄開始往下看,所以上面的檔案的完整路徑是 D:/TestJGit/local/folder_1/local_d.txt,不過,傳入時一定要如上傳相對路徑。
  • git commit
CommitCommand commitCmd = git.commit().setMessage("add a、b、d");
commitCmd.call();
  • git push
PushCommand pushCmd = git.push().setRemote("origin");
pushCmd.call();
只要牽涉到 remote 端,就有認證的問題,照理說要呼叫 setCredentialsProvider,這裡沒有呼叫這個 method,是因為我的 remote 端也在自己電腦的硬碟裡。(setCredentialsProvider 的用法請參考上一篇)
  • git clone
File file = new File("D:/TestJGit/remote");
Git git = Git.open(file);
CloneCommand cloneCmd = git.cloneRepository()
    .setBare(false)
    .setBranch("refs/remotes/origin/master")
    .setURI("D:/TestJGit/remote")
    .setDirectory(new File("D:/TestJGit/local3"));
cloneCmd.call();
git.close();
這裡要注意的是,開啟 remote 端的 bare repository,目錄名稱不是 .git 子目錄,事實上 bare repository 也沒有這個子目錄,這裡傳入 local repository 也可以。另外,setURI 是傳入 remote 端的路徑 (網址),setDirectory 則傳入 local repository 的路徑。
  • git pull
File file = new File("D:/TestJGit/local3/.git");
Git git = Git.open(file);
PullCommand pullCmd = git.pull();
pullCmd.setRemote("origin");
pullCmd.call();
git.close();
  • git rm
RmCommand rmCmd = git.rm().addFilepattern("local_a.txt");
rmCmd.call();
  • git diff
DiffCommand diffCmd = git.diff();
List<DiffEntry> diffEntry = diffCmd.call();
for(DiffEntry en:diffEntry) {
    System.out.println(en.toString());
}
這裡比對的是 working directory 和 最後一次 commit 間的差異,更深入的待下一篇說明。



2015年10月21日 星期三

JGit - getting started

目前使用 Java 要操作 git,似乎只有 JGit 這組 API,從這篇開始,簡要的說明 JGit 的使用方法。在 JGit 中,最重要的兩個類別是 Repository 及 Git,Repository 類別可以用來存取 Git Repository (Git 容器) 中的屬性,Git 類別則提供了 Git 相關的指令。接下來舉一些實例:
  • 創建 Repository、Git
Repository repo = new FileRepositoryBuilder()
   .setGitDir(new File("D:/BitBucket/VersionControl/.git"))
   .build();
        
Git git = new Git(repo);
我的電腦中有一個目錄 D:/BitBucket/VersionControl,這是一個 git repository,在 BitBucket 這個網站上,有相對應的的一個 git bare repository,在創建 Repository 物件時,路徑指到本機的路徑,至於怎麼存取遠端的 bare repository 內的資料稍後說明。不管是 Repository 類別或 Git 類別,使用完都要呼叫 close() method,以關閉 I/O 連線。
  • 取得 remote name
如上圖所示,我的 git repository 設定了兩個 remote repository,要如何取得名稱呢? 如下:
Set<String> remoteNames = repo.getRemoteNames();




  •  取得本機 branch
除了預設的 master 外,我又建立了一個 TEST branch,以下是取得 branch 參考物件的方法。
ListBranchCommand branchCmd = git.branchList();
List<Ref> branch = branchCmd.call();
使用 Git 類別時,只要想想 git 有提供那些指令,大至上就可以找到相對的 method,上面的程式會取得 branch 的參考物件 (Ref),如果用 toString() 把它輸出,會如下:
Ref[refs/heads/TEST=e5dc4bc8a01cca3c74a3554f87857d468815b58b]
Ref[refs/heads/master=977a0b74fe3e49c166119d473f2415663e158260]
輸出的格式為 Ref[name=objectId],這表示如果我們要操作 branch,就可以透過這個方式取得 name 或 objectId 帶入其它 method 來操作 branch。
  • 取得遠端 branch
CredentialsProvider cp = new UsernamePasswordCredentialsProvider("username", "password");
Collection<Ref> lsRemote = git.lsRemote()
    .setCredentialsProvider(cp)
    .call();
        
for(Ref remote:lsRemote) {
    String objName = remote.getName();
            
    Ref localRef = repo.getRef(objName);
    System.out.println("local ref = " + localRef.toString());
            
    Ref remoteRef = remote.getTarget();
    System.out.println("remote ref = " + remoteRef.toString());
}
要取得遠端的物件,當然要認證,這裡採用帳號、密碼登入的方式,輸出會如下:
local ref = SymbolicRef[HEAD -> refs/heads/TEST=e5dc4bc8a01cca3c74a3554f87857d468815b58b]
remote ref = Ref[HEAD=6c248c9ae67bdf1fc65e3d5cc8110a5c017aba53]
local ref = Ref[refs/heads/master=977a0b74fe3e49c166119d473f2415663e158260]
remote ref = Ref[refs/heads/master=6c248c9ae67bdf1fc65e3d5cc8110a5c017aba53]
local ref = Ref[refs/heads/TEST=e5dc4bc8a01cca3c74a3554f87857d468815b58b]
remote ref = Ref[refs/heads/TEST=b6c238f6a641b4272dc0728a9db0ddd93690ce64]
可以看到,local 和 remote 的 object id 不同,對照直接下 git ls-remote 的輸出,就可以發現,上面的 remote ref 與 ls-remote 的輸出是一樣的。