| 項目 | 日期 | |
|---|---|---|
| * |
JGit
| |
| 1 | JGit - getting started |
2015/10/21
|
| 2 | JGit - 基本命令 |
2015/10/22
|
| 3 | JGit - 走訪、差異比對、日誌 |
2015/10/23
|
| * |
Gradle
| |
| 1 | Gradle: task |
2018/02/23
|
| 2 | Gradle: 編譯、測試 web 專案 |
2017/08/10
|
| 3 | Gradle: 編譯 spring boot application |
2018/02/26
|
Google Code Prettify
2015年10月23日 星期五
JGit / Gradle
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 的運作。
- 程式第 82 行,resolve() method 可以由 object name 取得 object id,因為很多 method 要的參數是 object id。
- 程式第 83 行,RevWalk 的 parseCommit() 可以根據傳入的 object id 得到參考到 commit 那個節點的物件。
- 由 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--。
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()); }
2015年10月22日 星期四
JGit - 基本命令
繼上一篇之後,這一篇說明一些 git 基本命令所對應的 API。在開始正式內容前,先說明一下我的環境,在我的電腦裡,有兩個目錄,分別為 D:/TestJGit/local 及 D:/TestJGit/remote,第一個目錄是 git repository 第二個是 bare repository,是第一個 repository 的遠端。
只要牽涉到 remote 端,就有認證的問題,照理說要呼叫 setCredentialsProvider,這裡沒有呼叫這個 method,是因為我的 remote 端也在自己電腦的硬碟裡。(setCredentialsProvider 的用法請參考上一篇)
這裡比對的是 working directory 和 最後一次 commit 間的差異,更深入的待下一篇說明。
- 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();
- git clone
這裡要注意的是,開啟 remote 端的 bare repository,目錄名稱不是 .git 子目錄,事實上 bare repository 也沒有這個子目錄,這裡傳入 local repository 也可以。另外,setURI 是傳入 remote 端的路徑 (網址),setDirectory 則傳入 local repository 的路徑。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();
- 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());
}
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
Set<String> remoteNames = repo.getRemoteNames();
- 取得本機 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 的輸出是一樣的。
訂閱:
意見 (Atom)





