<samp id="0l9hf"></samp>
<td id="0l9hf"><option id="0l9hf"></option></td>

  • Git經典教程Cap12-將Git嵌入你的應用

    ★ 附錄B. 將 Git 嵌入你的應用

    假設你的應用程序的目標人群是開發者,如果它能夠被整合進一些源碼控制的功能,那真真是極好的。甚至對于一個例如文檔編輯器之類的不是為開發者而設計的應用程序,它們也可能從版本控制系統中受益,并且 Git 的實現方式在很多情況下都表現得非常出色。

    如果你想將 Git 整合進你的應用程序的話,一般來說你有三種可能的選擇:啟動一個 shell 來使用 Git 的命令行工具;使用 Libgit2;或者使用 JGit。

    ? 命令行 Git 方式

    一種方式就是啟動一個 shell 進程并在里面使用 Git 的命令行工具來完成任務。 這種方式看起來很循規蹈矩,但是它的優點也因此而來,就是支持所有的 Git 的特性。 它也碰巧相當簡單,因為幾乎所有運行時環境都有一個相對簡單的方式來調用一個帶有命令行參數的進程。 然而,這種方式也有一些固有的缺點。

    一個就是所有的輸出都是純文本格式。 這意味著你將被迫解析 Git 的有時會改變的輸出格式,以隨時了解它工作的進度和結果。更糟糕的是,這可能是無效率并且容易出錯的。

    另外一個就是令人捉急的錯誤修復能力。 如果一個版本庫被莫名其妙地損毀,或者用戶使用了一個奇奇怪怪的配置, Git 只會簡單地拒絕表現自己的強大能力。

    還有一個就是進程的管理。 Git 會要求你在一個獨立的進程中維護一個 shell 環境,這可能會無謂地增加復雜性。 試圖協調許許多多的類似的進程(尤其是在某些情況下,當不同的進程在訪問相同的版本庫時)是對你的能力的極大的挑戰。

    ? Libgit2

    另外一種可以供你使用的是 Libgit2。 Libgit2 是一個 Git 的非依賴性的工具,它致力于為其他程序使用 Git 提供更好的 API。 你可以在 http://libgit2.github.com 找到它。

    首先,讓我們來看一下 C API 長啥樣。 這是一個旋風式旅行。

    // 打開一個版本庫
    git_repository *repo;
    int error = git_repository_open(&repo, "/path/to/repository");
    
    // 逆向引用 HEAD 到一個提交
    git_object *head_commit;
    error = git_revparse_single(&head_commit, repo, "HEAD^{commit}");
    git_commit *commit = (git_commit*)head_commit;
    
    // 顯示這個提交的一些詳情
    printf("%s", git_commit_message(commit));
    const git_signature *author = git_commit_author(commit);
    printf("%s <%s>\n", author->name, author->email);
    const git_oid *tree_id = git_commit_tree_id(commit);
    
    // 清理現場
    git_commit_free(commit);
    git_repository_free(repo);

    前兩行打開一個 Git 版本庫。 這個 git_repository 類型代表了一個在內存中帶有緩存的指向一個版本庫的句柄。 這是最簡單的方法,只是你必須知道一個版本庫的工作目錄或者一個 .git 文件夾的精確路徑。 另外還有 git_repository_open_ext ,它包括了帶選項的搜索,git_clone 及其同類可以用來做遠程版本庫的本地克隆, git_repository_init 則可以創建一個全新的版本庫。

    第二段代碼使用了一種 rev-parse 語法(要了解更多,請看 分支引用 )來得到 HEAD 真正指向的提交。 返回類型是一個 git_object 指針,它指代位于版本庫里的 Git 對象數據庫中的某個東西。git_object 實際上是幾種不同的對象的 “父” 類型,每個 “子” 類型的內存布局和git_object 是一樣的,所以你能安全地把它們轉換為正確的類型。 在上面的例子中,git_object_type(commit) 會返回 GIT_OBJ_COMMIT ,所以轉換成 git_commit 指針是安全的。

    下一段展示了如何訪問一個提交的詳情。 最后一行使用了 git_oid 類型,這是 Libgit2 用來表示一個 SHA-1 哈希的方法。

    從這個例子中,我們可以看到一些模式:

    》如果你聲明了一個指針,并在一個 Libgit2 調用中傳遞一個引用,那么這個調用可能返回一個 int 類型的錯誤碼。 值 0 表示成功,比它小的則是一個錯誤。
    》如果 Libgit2 為你填入一個指針,那么你有責任釋放它。
    》如果 Libgit2 在一個調用中返回一個 const 指針,你不需要釋放它,但是當它所指向的對象被釋放時它將不可用。
    》用 C 來寫有點蛋疼。

    最后一點意味著你應該不會在使用 Libgit2 時編寫 C 語言程序。 但幸運的是,有許多可用的各種語言的綁定,能讓你在特定的語言和環境中更加容易的操作 Git 版本庫。 我們來看一下下面這個用 Libgit2 的 Ruby 綁定寫成的例子,它叫 Rugged,你可以在https://github.com/libgit2/rugged 找到它。

    repo = Rugged::Repository.new('path/to/repository')
    commit = repo.head.target
    puts commit.message
    puts "#{commit.author[:name]} <#{commit.author[:email]}>"
    tree = commit.tree

    你可以發現,代碼看起來更加清晰了。 首先, Rugged 使用異常機制,它可以拋出類似于ConfigError 或者 ObjectError 之類的東西來告知錯誤的情況。 其次,不需要明確資源釋放,因為 Ruby 是支持垃圾回收的。 我們來看一個稍微復雜一點的例子:從頭開始制作一個提交。

    blob_id = repo.write("Blob contents", :blob) 
    
    index = repo.index
    index.read_tree(repo.head.target.tree)
    index.add(:path => 'newfile.txt', :oid => blob_id) 
    
    sig = {
        :email => "bob@example.com",
        :name => "Bob User",
        :time => Time.now,
    }
    
    commit_id = Rugged::Commit.create(repo,
        :tree => index.write_tree(repo), 
        :author => sig,
        :committer => sig, 
        :message => "Add newfile.txt", 
        :parents => repo.empty? ? [] : [ repo.head.target ].compact, 
        :update_ref => 'HEAD', 
    )
    commit = repo.lookup(commit_id)

    創建一個新的 blob ,它包含了一個新文件的內容。

    將 HEAD 提交樹填入索引,并在路徑 newfile.txt 增加新文件。

    這就在 ODB 中創建了一個新的樹,并在一個新的提交中使用它。

    我們在 author 欄和 committer 欄使用相同的簽名。

    提交的信息。

    當創建一個提交時,你必須指定這個新提交的父提交。 這里使用了 HEAD 的末尾作為單一的父提交。

    在做一個提交的過程中, Rugged (和 Libgit2 )能在需要時更新引用。

    返回值是一個新提交對象的 SHA-1 哈希,你可以用它來獲得一個 Commit 對象。

    Ruby 的代碼很好很簡潔,另一方面因為 Libgit2 做了大量工作,所以代碼運行起來其實速度也不賴。 如果你不是一個 Ruby 程序員,我們在 其它綁定 有提到其它的一些綁定。

    ■ 高級功能

    Libgit2 有幾個超過核心 Git 的能力。 例如它的可定制性:Libgit2 允許你為一些不同類型的操作自定義的“后端”,讓你得以使用與原生 Git 不同的方式存儲東西。 Libgit2 允許為自定義后端指定配置、引用的存儲以及對象數據庫,

    我們來看一下它究竟是怎么工作的。 下面的例子借用自 Libgit2 團隊提供的后端樣本集 (可以在https://github.com/libgit2/libgit2-backends 上找到)。 一個對象數據庫的自定義后端是這樣建立的:

    git_odb *odb;
    int error = git_odb_new(&odb); 
    
    git_odb_backend *my_backend;
    error = git_odb_backend_mine(&my_backend, /*…*/); 
    
    error = git_odb_add_backend(odb, my_backend, 1); 
    
    git_repository *repo;
    error = git_repository_open(&repo, "some-path");
    error = git_repository_set_odb(odb);

    (注意:這個錯誤被捕獲了,但是沒有被處理。我們希望你的代碼比我們的更好。)

    初始化一個空的對象數據庫( ODB ) “前端”,它將被作為一個用來做真正的工作的 “后端” 的容器。

    初始化一個自定義 ODB 后端。

    為這個前端增加一個后端。

    打開一個版本庫,并讓它使用我們的 ODB 來尋找對象。

    但是 git_odb_backend_mine 是個什么東西呢? 嗯,那是一個你自己的 ODB 實現的構造器,并且你能在那里做任何你想做的事,前提是你能正確地填寫 git_odb_backend 結構。 它看起來應該是這樣的:

    typedef struct {
        git_odb_backend parent;
    
        // 其它的一些東西
        void *custom_context;
    } my_backend_struct;
    
    int git_odb_backend_mine(git_odb_backend **backend_out, /*…*/)
    {
        my_backend_struct *backend;
    
        backend = calloc(1, sizeof (my_backend_struct));
    
        backend->custom_context = …;
    
        backend->parent.read = &my_backend__read;
        backend->parent.read_prefix = &my_backend__read_prefix;
        backend->parent.read_header = &my_backend__read_header;
        // ……
    
        *backend_out = (git_odb_backend *) backend;
    
        return GIT_SUCCESS;
    }

    my_backend_struct 的第一個成員必須是一個 git_odb_backend 結構,這是一個微妙的限制:這樣就能確保內存布局是 Libgit2 的代碼所期望的樣子。 其余都是隨意的,這個結構的大小可以隨心所欲。

    這個初始化函數為該結構分配內存,設置自定義的上下文,然后填寫它支持的 parent 結構的成員。 閱讀 Libgit2 的 include/git2/sys/odb_backend.h 源碼以了解全部調用簽名,你特定的使用環境會幫你決定使用哪一種調用簽名。

    ■ 其它綁定

    Libgit2 有很多種語言的綁定。 在這篇文章中,我們展現了一個使用了幾個更加完整的綁定包的小例子,這些庫存在于許多種語言中,包括 C++、Go、Node.js、Erlang 以及 JVM ,它們的成熟度各不相同。 官方的綁定集合可以通過瀏覽這個版本庫得到:https://github.com/libgit2[] 。 我們寫的代碼將返回當前 HEAD 指向的提交的提交信息(就像 git log -1 那樣)。

    LibGit2Sharp

    如果你在編寫一個 .NET 或者 Mono 應用,那么 LibGit2Sharp (https://github.com/libgit2/libgit2sharp) 就是你所需要的。 這個綁定是用 C# 寫成的,并且已經采取許多措施來用令人感到自然的 CLR API 包裝原始的 Libgit2 的調用。 我們的例子看起來就像這樣:

    new Repository(@"C:\path\to\repo").Head.Tip.Message;

    對于 Windows 桌面應用,一個叫做 NuGet 的包會讓你快速上手。

    objective-git

    如果你的應用運行在一個 Apple 平臺上,你很有可能使用 Objective-C 作為實現語言。 Objective-Git (https://github.com/libgit2/objective-git) 是這個環境下的 Libgit2 綁定。 一個例子看起來類似這樣:

    GTRepository *repo =
        [[GTRepository alloc] initWithURL:[NSURL fileURLWithPath: @"/path/to/repo"] error:NULL];
    NSString *msg = [[[repo headReferenceWithError:NULL] resolvedTarget] message];

    Objective-git 與 Swift 完美兼容,所以你把 Objective-C 落在一邊的時候不用恐懼。

    pygit2

    Python 的 Libgit2 綁定叫做 Pygit2 ,你可以在 http://www.pygit2.org/ 找到它。 我們的示例程序:

    pygit2.Repository("/path/to/repo") # 打開版本庫
        .head                          # get the current branch
        .peel(pygit2.Commit)           # walk down to the commit
        .message                       # read the message

    ■ 擴展閱讀

    當然,完全闡述 Libgit2 的能力已超出本書范圍。 如果你想了解更多關于 Libgit2 的信息,可以瀏覽它的 API 文檔: https://libgit2.github.com/libgit2, 以及一系列的指南:https://libgit2.github.com/docs. 對于其它的綁定,檢查附帶的 README 和測試文件,那里通常有簡易教程,以及指向拓展閱讀的鏈接。

    ★ JGit

    如果你想在一個 Java 程序中使用 Git ,有一個功能齊全的 Git 庫,那就是 JGit 。 JGit 是一個用 Java 寫成的功能相對健全的 Git 的實現,它在 Java 社區中被廣泛使用。 JGit 項目由 Eclipse 維護,它的主頁在 http://www.eclipse.org/jgit 。

    ? 起步

    有很多種方式可以讓 JGit 連接你的項目,并依靠它去寫代碼。 最簡單的方式也許就是使用 Maven 。你可以通過在你的 pom.xml 文件里的 <dependencies> 標簽中增加像下面這樣的片段來完成這個整合。

    <dependency>
        <groupId>org.eclipse.jgit</groupId>
        <artifactId>org.eclipse.jgit</artifactId>
        <version>3.5.0.201409260305-r</version>
    </dependency>

    在你讀到這段文字時 version 很可能已經更新了,所以請瀏覽http://mvnrepository.com/artifact/org.eclipse.jgit/org.eclipse.jgit 以獲取最新的倉庫信息。 當這一步完成之后, Maven 就會自動獲取并使用你所需要的 JGit 庫。

    如果你想自己管理二進制的依賴包,那么你可以從 http://www.eclipse.org/jgit/download 獲得預構建的 JGit 二進制文件。 你可以像下面這樣執行一個命令來將它們構建進你的項目。

    javac -cp .:org.eclipse.jgit-3.5.0.201409260305-r.jar App.java

    ? 底層命令

    JGit 的 API 有兩種基本的層次:底層命令和高層命令。 這個兩個術語都來自 Git ,并且 JGit 也被按照相同的方式粗略地劃分:高層 API 是一個面向普通用戶級別功能的友好的前端(一系列普通用戶使用 Git 命令行工具時可能用到的東西),底層 API 則直接作用于低級的倉庫對象。

    大多數 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();

    無論你的程序是否知道倉庫的確切位置,builder 中的那個流暢的 API 都可以提供給它尋找倉庫所需所有信息。 它可以使用環境變量 (.readEnvironment()) ,從工作目錄的某處開始并搜索 (.setWorkTree(…).findGitDir()) , 或者僅僅只是像上面那樣打開一個已知的 .git 目錄。

    當你擁有一個 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");

    這里完成了一大堆事情,所以我們還是一次理解一段的好。

    第一行獲取一個指向 master 引用的指針。 JGit 自動抓取位于 refs/heads/master 的 真正的 master 引用,并返回一個允許你獲取該引用的信息的對象。 你可以獲取它的名字 (.getName()) ,或者一個直接引用的目標對象 (.getObjectId()) ,或者一個指向該引用的符號指針 (.getTarget()) 。 引用對象也經常被用來表示標簽的引用和對象,所以你可以詢問某個標簽是否被 “削除” 了,或者說它指向一個標簽對象的(也許很長的)字符串的最終目標。

    第二行獲得以 master 引用的目標,它返回一個 ObjectId 實例。 不管是否存在于一個 Git 對象的數據庫,ObjectId 都會代表一個對象的 SHA-1 哈希。 第三行與此相似,但是它展示了 JGit 如何處理 rev-parse 語法(要了解更多,請看 分支引用 ),你可以傳入任何 Git 了解的對象說明符,然后 JGit 會返回該對象的一個有效的 ObjectId ,或者 null 。

    接下來兩行展示了如何裝載一個對象的原始內容。 在這個例子中,我們調用ObjectLoader.copyTo() 直接向標準輸出流輸出對象的內容,除此之外 ObjectLoader 還帶有讀取對象的類型和長度并將它以字節數組返回的方法。 對于一個( .isLarge() 返回 true的)大的對象,你可以調用 .openStream() 來獲得一個類似 InputStream 的對象,它可以在沒有一次性將所有數據拉到內存的前提下讀取對象的原始數據。

    接下來幾行展現了如何創建一個新的分支。 我們創建一個 RefUpdate 實例,配置一些參數,然后調用 .update() 來確認這個更改。 刪除相同分支的代碼就在這行下面。 記住必須先.setForceUpdate(true) 才能讓它工作,否則調用 .delete() 只會返回 REJECTED ,然后什么都沒有發生。

    最后一個例子展示了如何從 Git 配置文件中獲取 user.name 的值。 這個 Config 實例使用我們先前打開的倉庫做本地配置,但是它也會自動地檢測并讀取全局和系統的配置文件。

    這只是底層 API 的冰山一角,另外還有許多可以使用的方法和類。 還有一個沒有放在這里說明的,就是 JGit 是用異常機制來處理錯誤的。 JGit API 有時使用標準的 Java 異常(例如IOException ),但是它也提供了大量 JGit 自己定義的異常類型(例如NoRemoteRepositoryException、 CorruptObjectException 和NoMergeBaseException)。

    ? 高層命令

    底層 API 更加完善,但是有時將它們串起來以實現普通的目的非常困難,例如將一個文件添加到索引,或者創建一個新的提交。 為了解決這個問題, JGit 提供了一系列高層 API ,使用這些 API 的入口點就是 Git 類:

    Repository repo;
    // 構建倉庫。。。
    Git git = new Git(repo);

    Git 類有一系列非常好的 構建器 風格的高層方法,它可以用來構造一些復雜的行為。 我們來看一個例子——做一件類似 git ls-remote 的事。

    CredentialsProvider cp = new UsernamePasswordCredentialsProvider("username", "p4ssw0rd");
    Collection<Ref> remoteRefs = git.lsRemote()
        .setCredentialsProvider(cp)
        .setRemote("origin")
        .setTags(true)
        .setHeads(false)
        .call();
    for (Ref ref : remoteRefs) {
        System.out.println(ref.getName() + " -> " + ref.getObjectId().name());

    這是一個 Git 類的公共樣式,這個方法返回一個可以讓你串連若干方法調用來設置參數的命令對象,當你調用 .call() 時它們就會被執行。 在這情況下,我們只是請求了 origin 遠程的標簽,而不是頭部。 還要注意用于驗證的 CredentialsProvider 對象的使用。

    在 Git 類中還可以使用許多其它的命令,包括但不限于add、blame、commit、clean、push、rebase、revert 和 reset。

    ? 拓展閱讀

    這只是 JGit 的全部能力的冰山一角。 如果你對這有興趣并且想深入學習,在下面可以找到一些信息和靈感。

    》JGit API 在線官方文檔: http://download.eclipse.org/jgit/docs/latest/apidocs 。 這是基本的 Javadoc ,所以你也可以在你最喜歡的 JVM IDE 上將它們安裝它們到本地。
    》JGit Cookbook : https://github.com/centic9/jgit-cookbook 擁有許多如何利用 JGit 實現特定任務的例子。
    http://stackoverflow.com/questions/6861881 指出了幾個好的資源。

    鄭重聲明:

    1 本資源來源于互聯網,資源的版權歸資源原作者所持有,受《中華人民共和國著作權法》等相關法律保護。

    2 由于無法和原作者取得聯系,所以上傳的部分資源無法先通過原作者的同意就分享給大家了,如本資源侵犯了您(原作者)的權益,請聯系我們(微信號 wenyan90s),我們會立馬刪除您的資源,并向您表達誠摯的歉意!

    3 本站是一個公益型網站,分享資源的目的在于傳播知識,分享知識,收取一點點打賞的辛苦費是用于網站的日常運營開支,并非用于商業用途。

    4 本站資源只提供學習和參考研究使用,使用過后請在第一時間內刪除。本站不承擔資源被單位或個人商用帶來的法律責任。

    發表評論

    国产精品午夜福利在线观看,av色片在线观看,男生看的黄色网站在线,黄片a片视频黄色
    <samp id="0l9hf"></samp>
    <td id="0l9hf"><option id="0l9hf"></option></td>