Google Code Prettify

2015年10月23日 星期五

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 需要的是毫秒。



沒有留言:

張貼留言