使用JGit API探索Git内部
您是否想過提交及??其內容如何存儲在Git中? 好吧,我有,在上一個下雨的周末,我有一些空閑時間,所以我做了一些研究。
因為我對Java的感覺比對Bash的感覺要多,所以我使用了JGit和一些學習測試來探索提交的Git內部。 這是我的發現:
Git –對象數據庫
Git的核心是簡單的內容可尋址數據存儲。 這意味著您可以在其中插入任何類型的內容,并且它將返回一個密鑰,您可以使用該密鑰在以后的某個時間點再次檢索數據。
對于Git,關鍵是從內容計算出的20字節SHA-1哈希。 內容也被稱為在GIT中術語的對象 ,因此數據存儲也被稱為對象數據庫 。
讓我們看看如何使用JGit來存儲和檢索內容。
斑點
在JGit中,ObjectInserter用于將內容存儲到對象數據庫中。 可以將其視為與Git中的git hash-object大致等效。
使用其insert()方法,您可以將對象寫入數據存儲,而其idFor()方法僅計算給定字節的SHA-1哈希。 因此,用于存儲字符串的代碼如下所示:
ObjectInserter objectInserter = repository.newObjectInserter(); byte[] bytes = "Hello World!".getBytes( "utf-8" ); ObjectId blobId = objectInserter.insert( Constants.OBJ_BLOB, bytes ); objectInserter.flush();所有代碼示例均假定存儲庫變量指向在代碼段外部創建的空存儲庫 。
第一參數表示要插入的對象的對象類型 ,在這種情況下,斑點的類型。 還有其他對象類型,我們將在后面學習。 Blob類型用于存儲任意內容。
有效載荷必須在第二個參數中給出,在這種情況下,必須作為字節數組。 也可以使用接受InputStream的重載方法。
最后,需要刷新ObjectInserter,以使其他訪問存儲庫的更改可見。
insert()方法返回根據類型,內容長度和內容字節計算得出的SHA-1哈希。 但是,在JGit中,SHA-1哈希通過ObjectId類表示,ObjectId類是一種不變的數據結構,可以在字節,整數和字符串之間來回轉換。
現在,您可以使用返回的blobId取回內容,從而確保上面的代碼實際寫入了內容。
ObjectReader objectReader = repository.newObjectReader(); ObjectLoader objectLoader = objectReader.open( blobId ); int type = objectLoader.getType(); // Constants.OBJ_BLOB byte[] bytes = objectLoader.getBytes(); String helloWorld = new String( bytes, "utf-8" ) // Hello World!ObjectReader的open()方法返回一個ObjectLoader,該對象可用于訪問由給定對象ID標識的對象。 借助ObjectLoader,您可以將對象的類型,大小以及內容作為字節數組或流獲取。
要驗證JGit編寫的對象與本機Git兼容,可以使用git cat-file檢索其內容。
$ git cat-file -p c57eff55ebc0c54973903af5f72bac72762cf4f4 Hello World! git cat-file -t c57eff55ebc0c54973903af5f72bac72762cf4f4 blob如果查看存儲庫的.git/objects目錄,則會發現一個名為'c5'的目錄,其中包含名為'7eff55ebc0c54973903af5f72bac72762cf4f4'的文件。 最初是這樣存儲內容的:每個對象作為一個文件,以內容的SHA-1哈希命名。 子目錄以SHA-1的前兩個字符命名,文件名由其余字符組成。
現在您可以存儲文件的內容,下一步就是存儲文件的名稱。 而且可能不僅僅是一個文件,因為提交通常由一組文件組成。 為了保存此類信息,Git使用了所謂的樹對象。
樹對象
樹對象可以看作是簡化的文件系統結構,其中包含有關文件和目錄的信息。
它包含任意數量的樹條目。 每個條目都有一個路徑名,一個文件模式,并指向一個文件(一個blob對象)或另一個(子)樹對象(如果它代表一個目錄)的內容。 指針當然是Blob對象或樹對象的SHA-1哈希。
首先,您可以創建一個樹,其中包含一個名為“ hello-world.txt”的文件的單個條目,該文件指向上面存儲的“ Hello World!”。 內容。
TreeFormatter treeFormatter = new TreeFormatter(); treeFormatter.append( "hello-world.txt", FileMode.REGULAR_FILE, blobId ); ObjectId treeId = objectInserter.insert( treeFormatter ); objectInserter.flush();TreeFormatter在這里用于構造內存中的樹對象。 通過調用append(),將添加具有給定路徑名,模式和ID(用于存儲其內容)的條目。
從根本上講,您可以自由選擇任何路徑名。 但是,Git希望路徑名是相對于工作目錄的,且不帶前導“ /”。
此處使用的文件模式表示正常文件。 其他模式是EXECUTABLE_FILE(它是一個可執行文件)和SYMLINK(它指定一個符號鏈接)。 對于目錄條目,文件模式始終為TREE。
同樣,您將需要一個ObjectInserter。 其重載的insert()方法之一接受TreeFormatter并將其寫入對象數據庫。
現在,您可以使用TreeWalk檢索和檢查樹對象:
TreeWalk treeWalk = new TreeWalk( repository ); treeWalk.addTree( treeId ); treeWalk.next(); String filename = treeWalk.getPathString(); // hello-world.txt實際上,TreeWalk旨在對添加的樹及其子樹進行迭代。 但是由于我們知道只有一個條目,因此只需調用next()就足夠了。
如果使用本地Git查看剛剛編寫的樹對象,則會看到以下內容:
$ git cat-file -p 44d52a975c793e5a4115e315b8d89369e2919e51 100644 blob c57eff55ebc0c54973903af5f72bac72762cf4f4 hello-world.txt現在您已經擁有提交的必要條件,讓我們創建提交對象本身。
提交對象
提交對象 (通過樹對象)引用構成提交的文件以及一些元數據。 詳細而言,提交包括:
- 指向樹對象的指針
- 指向零個或多個父提交的指針(稍后會詳細介紹)
- 提交消息
- 以及作者和提交者
由于提交對象只是對象數據庫中的另一個對象,因此它也被在其內容上計算出的SHA-1哈希密封。
為了形成提交對象,JGit提供了CommitBuilder實用程序類。
CommitBuilder commitBuilder = new CommitBuilder(); commitBuilder.setTreeId( treeId ); commitBuilder.setMessage( "My first commit!" ); PersonIdent person = new PersonIdent( "me", "me@example.com" ); commitBuilder.setAuthor( person ); commitBuilder.setCommitter( person ); ObjectInserter objectInserter = repository.newObjectInserter(); ObjectId commitId = objectInserter.insert( commitBuilder ); objectInserter.flush();使用它很簡單,它具有針對提交的所有屬性的setter方法。
作者和提交者通過PersonIdent類表示,該類包含名稱,電子郵件,時間戳和時區。 此處使用的構造函數應用給定的名稱和電子郵件,并采用當前時間和時區。
剩下的應該已經很熟悉了:ObjectInserter用于實際寫入提交對象并返回提交ID。
要從存儲庫中檢索提交對象,可以再次使用ObjectReader:
ObjectReader objectReader = repository.newObjectReader(); ObjectLoader objectLoader = objectReader.open( commitId ); RevCommit commit = RevCommit.parse( objectLoader.getBytes() );生成的RevCommit表示具有與CommitBuilder中指定的相同屬性的提交。
再一次-仔細檢查git cat-file的輸出:
$ git cat-file -p 783341299c95ddda51e6b2393c16deaf0c92d5a0 tree 4b825dc642cb6eb9a060e54bf8d69288fbee4904 author me <me@example.com> 1412872859 +0200 committer me <me@example.com> 1412872859 +0200My first commit!父母
父母鏈形成了Git倉庫的歷史,并建模了有向無環圖 。 這意味著提交“遵循”一個方向
一個提交可以有零個或多個父母。 存儲庫中的第一個提交沒有父提交(aka根提交)。 第二個提交又將第一個提交作為其父級,依此類推。
創建多個根提交完全合法。 如果使用git checkout --orphan new_branch將創建一個新的孤立分支并將其切換到。 在此分支上進行的第一次提交將沒有父項,并且將成為與所有其他提交斷開連接的新歷史記錄的根。
如果開始分支并最終合并變化的分歧線,通常會導致合并提交 。 并且這樣的提交具有分支的父分支作為其父分支。
為了構造父提交,需要在CommitBuilder中指定父提交的ID。
commitBuilder.setParents( parentId );還可以查詢RevCommit類,該類表示存儲庫中的提交,并且可以查詢其父級。 它的getParents()和getParent(int)方法返回全部或第n個父RevCommit。
請注意,盡管這些方法返回RevCommits,但這些方法尚未完全解決。 設置其ID屬性后,所有其他屬性(fullMessage,author,committer等)均未設置。 因此,例如,嘗試調用parent.getFullMessage()會引發NullPointerException。 為了實際使用父提交,您需要通過上面概述的ObjectReader檢索完整的RevCommit,或者使用RevWalk加載和解析提交標頭:
RevWalk revWalk = new RevWalk( repository ); revWalk.parseHeaders( parentCommit );總而言之,請記住將返回的父提交視為ObjectId而不是RevCommits。
有關樹對象的更多信息
如果要將文件存儲在子目錄中,則需要自己構造子樹。 假設您要將文件“ file.txt”的內容存儲在文件夾“文件夾”中。
首先,為子樹創建并存儲TreeFormatter,該子樹具有文件的條目:
TreeFormatter subtreeFormatter = new TreeFormatter(); subtreeFormatter.append( "file.txt", FileMode.REGULAR_FILE, blobId ); ObjectId subtreeId = objectInserter.insert( subtreeFormatter );然后,創建并存儲TreeFormatter以及一個表示文件夾的條目,并指向剛創建的子樹。
TreeFormatter treeFormatter = new TreeFormatter(); treeFormatter.append( "folder", FileMode.TREE, subtreeId ); ObjectId treeId = objectInserter.insert( treeFormatter );
條目的文件模式為TREE,表示目錄,其ID指向保存文件條目的子樹。 返回的treeId是將傳遞給CommitBuilder的那個。
Git對樹對象中的條目要求一定的排序順序。 我在這里找到的“ Git數據格式”文檔指出:
樹條目按包含條目名稱的字節序列排序。 但是,出于排序比較的目的,比較樹對象的條目,就好像條目名稱字節序列具有尾隨的ASCII'/'(0x2f)。
要讀取樹對象的內容,可以再次使用TreeWalk。 但是這一次,如果要訪問所有條目,則需要告訴它遞歸到子樹中。 而且,如果您希望看到指向樹的條目,請不要忘記將postOrderTraversal設置為true。 否則將被跳過。
最終,整個TreeWalk循環將如下所示:
…并將導致以下輸出:
100644 6b584e8ece562ebffc15d38808cd6b98fc3d97ea folder/file.txt 040000 541550ddcf8a29bcd80b0800a142a7d47890cfd6 folder盡管我發現API不是很直觀,但它可以完成工作并顯示樹對象的所有詳細信息。
總結Git內部
毫無疑問,對于常見用例,建議將高層Add-和CommitCommands提交到存儲庫中。 不過,我發現值得深入研究JGit和Git,希望您也這樣做。 而且,在您需要將文件提交到沒有工作目錄和/或索引的存儲庫的情況下(這種情況不太常見),這里提供的信息可能會有所幫助。
如果您想親自嘗試此處列出的示例,建議您設置JGit并對其源代碼和JavaDoc進行訪問,以便您擁有有意義的上下文信息,內容幫助,調試源等。
- 完整的源代碼托管在這里: https : //gist.github.com/rherrmann/02d8d4fe81bb60d9049e
為簡便起見,此處顯示的示例省略了用于釋放分配的資源的代碼。 請參考完整的源代碼以獲取所有詳細信息。
翻譯自: https://www.javacodegeeks.com/2014/10/explore-git-internals-with-the-jgit-api.html
總結
以上是生活随笔為你收集整理的使用JGit API探索Git内部的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: linux命令时间戳(linux 命令
- 下一篇: ddos智能防御(ddos防御 智新闻)