<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ??一站式輕松地調用各大LLM模型接口,支持GPT4、智譜、豆包、星火、月之暗面及文生圖、文生視頻 廣告
                ## Git詳解之五 分布式Git **分布式 Git** 為了便于項目中的所有開發者分享代碼,我們準備好了一臺服務器存放遠程 Git倉庫。經過前面幾章的學習,我們已經學會了一些基本的本地工作流程中所需用到的命令。接下來,我們要學習下如何利用 Git來組織和完成分布式工作流程。 特別是,當作為項目貢獻者時,我們該怎么做才能方便維護者采納更新;或者作為項目維護者時,又該怎樣有效管理大量貢獻者的提交。 ? ? **5.1?分布式工作流程** 同傳統的集中式版本控制系統(CVCS)不同,開發者之間的協作方式因著 Git的分布式特性而變得更為靈活多樣。在集中式系統上,每個開發者就像是連接在集線器上的節點,彼此的工作方式大體相像。而在 Git網絡中,每個開發者同時扮演著節點和集線器的角色,這就是說,每一個開發者都可以將自己的代碼貢獻到另外一個開發者的倉庫中,或者建立自己的公共倉庫,讓其他開發者基于自己的工作開始,為自己的倉庫貢獻代碼。于是,Git的分布式協作便可以衍生出種種不同的工作流程,我會在接下來的章節介紹幾種常見的應用方式,并分別討論各自的優缺點。你可以選擇其中的一種,或者結合起來,應用到你自己的項目中。 **集中式工作流** 通常,集中式工作流程使用的都是單點協作模型。一個存放代碼倉庫的中心服務器,可以接受所有開發者提交的代碼。所有的開發者都是普通的節點,作為中心集線器的消費者,平時的工作就是和中心倉庫同步數據(見圖 5-1)。 圖 5-1. 集中式工作流 如果兩個開發者從中心倉庫克隆代碼下來,同時作了一些修訂,那么只有第一個開發者可以順利地把數據推送到共享服務器。第二個開發者在提交他的修訂之前,必須先下載合并服務器上的數據,解決沖突之后才能推送數據到共享服務器上。在 Git中這么用也決無問題,這就好比是在用 Subversion(或其他 CVCS)一樣,可以很好地工作。 如果你的團隊不是很大,或者大家都已經習慣了使用集中式工作流程,完全可以采用這種簡單的模式。只需要配置好一臺中心服務器,并給每個人推送數據的權限,就可以開展工作了。但如果提交代碼時有沖突, Git根本就不會讓用戶覆蓋他人代碼,它直接駁回第二個人的提交操作。這就等于告訴提交者,你所作的修訂無法通過快近(fast-forward)來合并,你必須先拉取最新數據下來,手工解決沖突合并后,才能繼續推送新的提交。絕大多數人都熟悉和了解這種模式的工作方式,所以使用也非常廣泛。 **集成管理員工作流** 由于 Git允許使用多個遠程倉庫,開發者便可以建立自己的公共倉庫,往里面寫數據并共享給他人,而同時又可以從別人的倉庫中提取他們的更新過來。這種情形通常都會有個代表著官方發布的項目倉庫(blessed repository),開發者們由此倉庫克隆出一個自己的公共倉庫(developer public),然后將自己的提交推送上去,請求官方倉庫的維護者拉取更新合并到主項目。維護者在自己的本地也有個克隆倉庫(integration manager),他可以將你的公共倉庫作為遠程倉庫添加進來,經過測試無誤后合并到主干分支,然后再推送到官方倉庫。工作流程看起來就像圖 5-2 所示: 1.項目維護者可以推送數據到公共倉庫blessed repository。 2.貢獻者克隆此倉庫,修訂或編寫新代碼。 2.貢獻者推送數據到自己的公共倉庫developer public。 4.貢獻者給維護者發送郵件,請求拉取自己的最新修訂。 3.維護者在自己本地的integration manger倉庫中,將貢獻者的倉庫加為遠程倉庫,合并更新并做測試。 4.維護者將合并后的更新推送到主倉庫blessed repository。 圖 5-2. 集成管理員工作流 在 GitHub網站上使用得最多的就是這種工作流。人們可以復制(fork亦即克隆)某個項目到自己的列表中,成為自己的公共倉庫。隨后將自己的更新提交到這個倉庫,所有人都可以看到你的每次更新。這么做最主要的優點在于,你可以按照自己的節奏繼續工作,而不必等待維護者處理你提交的更新;而維護者也可以按照自己的節奏,任何時候都可以過來處理接納你的貢獻。 **司令官與副官工作流** 這其實是上一種工作流的變體。一般超大型的項目才會用到這樣的工作方式,像是擁有數百協作開發者的 Linux內核項目就是如此。各個集成管理員分別負責集成項目中的特定部分,所以稱為副官(lieutenant)。而所有這些集成管理員頭上還有一位負責統籌的總集成管理員,稱為司令官(dictator)。司令官維護的倉庫用于提供所有協作者拉取最新集成的項目代碼。整個流程看起來如圖 5-3 所示: 1.一般的開發者在自己的特性分支上工作,并不定期地根據主干分支(dictator上的 master)衍合。 2.副官(lieutenant)將普通開發者的特性分支合并到自己的 master分支中。 3.司令官(dictator)將所有副官的master分支并入自己的master分支。 4.司令官(dictator)將集成后的master分支推送到共享倉庫blessed repository中,以便所有其他開發者以此為基礎進行衍合。 圖 5-3. 司令官與副官工作流 這種工作流程并不常用,只有當項目極為龐雜,或者需要多級別管理時,才會體現出優勢。利用這種方式,項目總負責人(即司令官)可以把大量分散的集成工作委托給不同的小組負責人分別處理,最后再統籌起來,如此各人的職責清晰明確,也不易出錯(譯注:此乃分而治之)。 以上介紹的是常見的分布式系統可以應用的工作流程,當然不止于 Git。在實際的開發工作中,你可能會遇到各種為了滿足特定需求而有所變化的工作方式。我想現在你應該已經清楚,接下來自己需要用哪種方式開展工作了。下節我還會再舉些例子,看看各式工作流中的每個角色具體應該如何操作。 ? **5.2?為項目作貢獻** 接下來,我們來學習一下作為項目貢獻者,會有哪些常見的工作模式。 不過要說清楚整個協作過程真的很難,Git如此靈活,人們的協作方式便可以各式各樣,沒有固定不變的范式可循,而每個項目的具體情況又多少會有些不同,比如說參與者的規模,所選擇的工作流程,每個人的提交權限,以及 Git以外貢獻等等,都會影響到具體操作的細節。 首當其沖的是參與者規模。項目中有多少開發者是經常提交代碼的?經常又是多久呢?大多數兩至三人的小團隊,一天大約只有幾次提交,如果不是什么熱門項目的話就更少了。可要是在大公司里,或者大項目中,參與者可以多到上千,每天都會有十幾個上百個補丁提交上來。這種差異帶來的影響是顯著的,越是多的人參與進來,就越難保證每次合并正確無誤。你正在工作的代碼,可能會因為合并進來其他人的更新而變得過時,甚至受創無法運行。而已經提交上去的更新,也可能在等著審核合并的過程中變得過時。那么,我們該怎樣做才能確保代碼是最新的,提交的補丁也是可用的呢? 接下來便是項目所采用的工作流。是集中式的,每個開發者都具有等同的寫權限?項目是否有專人負責檢查所有補丁?是不是所有補丁都做過同行復閱(peer-review)再通過審核的?你是否參與審核過程?如果使用副官系統,那你是不是限定于只能向此副官提交? 還有你的提交權限。有或沒有向主項目提交更新的權限,結果完全不同,直接決定最終采用怎樣的工作流。如果不能直接提交更新,那該如何貢獻自己的代碼呢?是不是該有個什么策略?你每次貢獻代碼會有多少量?提交頻率呢? 所有以上這些問題都會或多或少影響到最終采用的工作流。接下來,我會在一系列由簡入繁的具體用例中,逐一闡述。此后在實踐時,應該可以借鑒這里的例子,略作調整,以滿足實際需要構建自己的工作流。 **提交指南** 開始分析特定用例之前,先來了解下如何撰寫提交說明。一份好的提交指南可以幫助協作者更輕松更有效地配合。Git項目本身就提供了一份文檔(Git項目源代碼目錄中Documentation/SubmittingPatches),列數了大量提示,從如何編撰提交說明到提交補丁,不一而足。 首先,請不要在更新中提交多余的白字符(whitespace)。Git有種檢查此類問題的方法,在提交之前,先運行?git diff --check,會把可能的多余白字符修正列出來。下面的示例,我已經把終端中顯示為紅色的白字符用X?替換掉: ~~~ $ git diff --check lib/simplegit.rb:5: trailing whitespace. +??? @git_dir =File.expand_path(git_dir)XX lib/simplegit.rb:7: trailing whitespace. + XXXXXXXXXXX lib/simplegit.rb:26: trailing whitespace. +??? defcommand(git_cmd)XXXX ~~~ 這樣在提交之前你就可以看到這類問題,及時解決以免困擾其他開發者。 接下來,請將每次提交限定于完成一次邏輯功能。并且可能的話,適當地分解為多次小更新,以便每次小型提交都更易于理解。請不要在周末窮追猛打一次性解決五個問題,而最后拖到周一再提交。就算是這樣也請盡可能利用暫存區域,將之前的改動分解為每次修復一個問題,再分別提交和加注說明。如果針對兩個問題改動的是同一個文件,可以試試看git add --patch?的方式將部分內容置入暫存區域(我們會在第六章再詳細介紹)。無論是五次小提交還是混雜在一起的大提交,最終分支末端的項目快照應該還是一樣的,但分解開來之后,更便于其他開發者復閱。這么做也方便自己將來取消某個特定問題的修復。我們將在第六章介紹一些重寫提交歷史,同暫存區域交互的技巧和工具,以便最終得到一個干凈有意義,且易于理解的提交歷史。 最后需要謹記的是提交說明的撰寫。寫得好可以讓大家協作起來更輕松。一般來說,提交說明最好限制在一行以內,50個字符以下,簡明扼要地描述更新內容,空開一行后,再展開詳細注解。Git項目本身需要開發者撰寫詳盡注解,包括本次修訂的因由,以及前后不同實現之間的比較,我們也該借鑒這種做法。另外,提交說明應該用祈使現在式語態,比如,不要說成 “I added tests for”或 “Addingtests for”而應該用 “Add tests for”。下面是來自 tpope.net的 Tim Pope 原創的提交說明格式模版,供參考: 本次更新的簡要描述(50個字符以內) ? 如果必要,此處展開詳盡闡述。段落寬度限定在 72個字符以內。 某些情況下,第一行的簡要描述將用作郵件標題,其余部分作為郵件正文。 其間的空行是必要的,以區分兩者(當然沒有正文另當別論)。 如果并在一起,rebase這樣的工具就可能會迷惑。 ? 另起空行后,再進一步補充其他說明。 ? ?- 可以使用這樣的條目列舉式。 ? ?- 一般以單個空格緊跟短劃線或者星號作為每項條目的起始符。每個條目間用一空行隔開。 ?? 不過這里按自己項目的約定,可以略作變化。 如果你的提交說明都用這樣的格式來書寫,好多事情就可以變得十分簡單。Git項目本身就是這樣要求的,我強烈建議你到 Git項目倉庫下運行?git log --no-merges?看看,所有提交歷史的說明是怎樣撰寫的。(譯注:如果現在還沒有克隆 git 項目源代碼,是時候git clonegit://git.kernel.org/pub/scm/git/git.git?了。) 為簡單起見,在接下來的例子(及本書隨后的所有演示)中,我都不會用這種格式,而使用?-m?選項提交?git commit。不過請還是按照我之前講的做,別學我這里偷懶的方式。 **私有的小型團隊** 我們從最簡單的情況開始,一個私有項目,與你一起協作的還有另外一到兩位開發者。這里說私有,是指源代碼不公開,其他人無法訪問項目倉庫。而你和其他開發者則都具有推送數據到倉庫的權限。 這種情況下,你們可以用 Subversion或其他集中式版本控制系統類似的工作流來協作。你仍然可以得到 Git帶來的其他好處:離線提交,快速分支與合并等等,但工作流程還是差不多的。主要區別在于,合并操作發生在客戶端而非服務器上。讓我們來看看,兩個開發者一起使用同一個共享倉庫,會發生些什么。第一個人,John,克隆了倉庫,作了些更新,在本地提交。(下面的例子中省略了常規提示,用...?代替以節約版面。) ~~~ # John's Machine $ git clone john@githost:simplegit.git Initialized empty Git repository in/home/john/simplegit/.git/ ... $ cd simplegit/ $ vim lib/simplegit.rb $ git commit -am 'removed invalid default value' [master 738ee87] removed invalid default value ?1 files changed, 1insertions(+), 1 deletions(-) ~~~ 第二個開發者,Jessica,一樣這么做:克隆倉庫,提交更新: # Jessica's Machine ~~~ $ git clone jessica@githost:simplegit.git Initialized empty Git repository in /home/jessica/simplegit/.git/ ... $ cd simplegit/ $ vim TODO $ git commit -am 'add reset task' [master fbff5bc] add reset task ?1 files changed, 1insertions(+), 0 deletions(-) ~~~ 現在,Jessica將她的工作推送到服務器上: ~~~ # Jessica's Machine $ git push origin master ... To jessica@githost:simplegit.git ??1edee6b..fbff5bc? master ->master ~~~ John 也嘗試推送自己的工作上去: ~~~ # John's Machine $ git push origin master To john@githost:simplegit.git ?! [rejected]??????? master -> master (non-fast forward) error: failed to push some refs to 'john@githost:simplegit.git' ~~~ John 的推送操作被駁回,因為Jessica已經推送了新的數據上去。請注意,特別是你用慣了 Subversion的話,這里其實修改的是兩個文件,而不是同一個文件的同一個地方。Subversion會在服務器端自動合并提交上來的更新,而 Git則必須先在本地合并后才能推送。于是,John不得不先把 Jessica的更新拉下來: ~~~ $ git fetch origin ... From john@githost:simplegit ?+049d078...fbff5bc master???? ->origin/master ~~~ 此刻,John的本地倉庫如圖 5-4所示: 圖 5-4. John的倉庫歷史 雖然 John下載了 Jessica推送到服務器的最近更新(fbff5),但目前只是?origin/master?指針指向它,而當前的本地分支master?仍然指向自己的更新(738ee),所以需要先把她的提交合并過來,才能繼續推送數據: ~~~ $ git merge origin/master Merge made by recursive. ?TODO |??? 1 + ?1 files changed, 1insertions(+), 0 deletions(-) ~~~ 還好,合并過程非常順利,沒有沖突,現在 John的提交歷史如圖 5-5所示: 圖 5-5. 合并origin/master 后 John 的倉庫歷史 現在,John應該再測試一下代碼是否仍然正常工作,然后將合并結果(72bbc)推送到服務器上: ~~~ $ git push origin master ... To john@githost:simplegit.git ?? fbff5bc..72bbc59? master -> master ~~~ 最終,John的提交歷史變為圖 5-6所示: 圖 5-6. 推送后John 的倉庫歷史 而在這段時間,Jessica已經開始在另一個特性分支工作了。她創建了?issue54?并提交了三次更新。她還沒有下載John提交的合并結果,所以提交歷史如圖5-7所示: 圖 5-7.Jessica的提交歷史 Jessica 想要先和服務器上的數據同步,所以先下載數據: ~~~ # Jessica's Machine $ git fetch origin ... From jessica@githost:simplegit ??fbff5bc..72bbc59? master???? -> origin/master ~~~ 于是 Jessica的本地倉庫歷史多出了 John的兩次提交(738ee和 72bbc),如圖 5-8所示: 圖 5-8. 獲取 John 的更新之后Jessica的提交歷史 此時,Jessica在特性分支上的工作已經完成,但她想在推送數據之前,先確認下要并進來的數據究竟是什么,于是運行git log?查看: ~~~ $ git log --no-merges origin/master ^issue54 commit 738ee872852dfaa9d6634e0dea7a324040193016 Author: John Smith Date:?? Fri May 2916:01:27 2009 -0700 ??? removed invaliddefault value ~~~ 現在,Jessica可以將特性分支上的工作并到?master?分支,然后再并入John的工作(origin/master)到自己的master?分支,最后再推送回服務器。當然,得先切回主分支才能集成所有數據: ~~~ $ git checkout master Switched to branch "master" Your branch is behind 'origin/master' by 2 commits, andcan be fast-forwarded. ~~~ 要合并?origin/master?或?issue54?分支,誰先誰后都沒有關系,因為它們都在上游(upstream)(譯注:想像分叉的更新像是匯流成河的源頭,所以上游 upstream 是指最新的提交),所以無所謂先后順序,最終合并后的內容快照都是一樣的,而僅是提交歷史看起來會有些先后差別。Jessica選擇先合并issue54: ~~~ $ git merge issue54 Updating fbff5bc..4af4298 Fast forward ?README?????????? |???1 + ?lib/simplegit.rb |???6 +++++- ?2 files changed, 6insertions(+), 1 deletions(-) ~~~ 正如所見,沒有沖突發生,僅是一次簡單快進。現在 Jessica開始合并 John 的工作(origin/master): ~~~ $ git merge origin/master Auto-merging lib/simplegit.rb Merge made by recursive. ?lib/simplegit.rb|??? 2 +- ?1 files changed, 1insertions(+), 1 deletions(-) ~~~ 所有的合并都非常干凈。現在 Jessica的提交歷史如圖 5-9所示: 圖 5-9. 合并 John 的更新后Jessica的提交歷史 現在 Jessica已經可以在自己的?master?分支中訪問?origin/master?的最新改動了,所以她應該可以成功推送最后的合并結果到服務器上(假設 John 此時沒再推送新數據上來): ~~~ $ git push origin master ... To jessica@githost:simplegit.git ??72bbc59..8059c15? master ->master ~~~ 至此,每個開發者都提交了若干次,且成功合并了對方的工作成果,最新的提交歷史如圖 5-10所示: 圖 5-10.Jessica推送數據后的提交歷史 以上就是最簡單的協作方式之一:先在自己的特性分支中工作一段時間,完成后合并到自己的?master?分支;然后下載合并origin/master?上的更新(如果有的話),再推回遠程服務器。一般的協作流程如圖 5-11 所示: 圖 5-11. 多用戶共享倉庫協作方式的一般工作流程時序 **私有團隊間協作** 現在我們來看更大一點規模的私有團隊協作。如果有幾個小組分頭負責若干特性的開發和集成,那他們之間的協作過程是怎樣的。 假設 John和 Jessica 一起負責開發某項特性 A,而同時 Jessica和 Josie 一起負責開發另一項功能 B。公司使用典型的集成管理員式工作流,每個組都有一名管理員負責集成本組代碼,及更新項目主倉庫的master?分支。所有開發都在代表小組的分支上進行。 讓我們跟隨 Jessica的視角看看她的工作流程。她參與開發兩項特性,同時和不同小組的開發者一起協作。克隆生成本地倉庫后,她打算先著手開發特性 A。于是創建了新的featureA?分支,繼而編寫代碼: ~~~ # Jessica's Machine $ git checkout -b featureA Switched to a new branch "featureA" $ vim lib/simplegit.rb $ git commit -am 'add limit to log function' [featureA 3300904] add limit to log function ?1 files changed, 1insertions(+), 1 deletions(-) ~~~ 此刻,她需要分享目前的進展給 John,于是她將自己的?featureA?分支提交到服務器。由于Jessica沒有權限推送數據到主倉庫的master?分支(只有集成管理員有此權限),所以只能將此分支推上去同 John共享協作: ~~~ $ git push origin featureA ... To jessica@githost:simplegit.git ?* [newbranch]????? featureA -> featureA ~~~ Jessica 發郵件給John 讓他上來看看?featureA?分支上的進展。在等待他的反饋之前,Jessica決定繼續工作,和 Josie一起開發featureB?上的特性 B。當然,先創建此分支,分叉點以服務器上的?master?為起點: ~~~ # Jessica's Machine $ git fetch origin $ git checkout -b featureB origin/master Switched to a new branch "featureB" ~~~ 隨后,Jessica在?featureB?上提交了若干更新: ~~~ $ vim lib/simplegit.rb $ git commit -am 'made the ls-tree function recursive' [featureB e5b0fdc] made the ls-tree function recursive ?1 files changed, 1insertions(+), 1 deletions(-) $ vim lib/simplegit.rb $ git commit -am 'add ls-files' [featureB 8512791] add ls-files ?1 files changed, 5insertions(+), 0 deletions(-) ~~~ 現在 Jessica的更新歷史如圖 5-12所示: 圖 5-12.Jessica的更新歷史 Jessica 正準備推送自己的進展上去,卻收到Josie的來信,說是她已經將自己的工作推到服務器上的?featureBee?分支了。這樣,Jessica就必須先將 Josie的代碼合并到自己本地分支中,才能再一起推送回服務器。她用git fetch?下載 Josie的最新代碼: ~~~ $ git fetch origin ... From jessica@githost:simplegit ?* [newbranch]????? featureBee ->origin/featureBee ~~~ 然后 Jessica使用?git merge?將此分支合并到自己分支中: ~~~ $ git merge origin/featureBee Auto-merging lib/simplegit.rb Merge made by recursive. ?lib/simplegit.rb|??? 4 ++++ ?1 files changed, 4insertions(+), 0 deletions(-) ~~~ 合并很順利,但另外有個小問題:她要推送自己的?featureB?分支到服務器上的?featureBee?分支上去。當然,她可以使用冒號(:)格式指定目標分支: ~~~ $ git push origin featureB:featureBee ... To jessica@githost:simplegit.git ? ?fba9af8..cd685d1? featureB -> featureBee ~~~ 我們稱此為_refspec_。更多有關于 Git refspec的討論和使用方式會在第九章作詳細闡述。 接下來,John發郵件給 Jessica告訴她,他看了之后作了些修改,已經推回服務器?featureA?分支,請她過目下。于是Jessica運行git fetch?下載最新數據: ~~~ $ git fetch origin ... From jessica@githost:simplegit ?? 3300904..aad881d? featureA??-> origin/featureA ~~~ 接下來便可以用?gitlog?查看更新了些什么: ~~~ $ git log origin/featureA ^featureA commit aad881d154acdaeb2b6b18ea0e827ed8a6d671e6 Author: John Smith Date:?? Fri May 2919:57:33 2009 -0700 ??? changed logoutput to 30 from 25 ~~~ 最后,她將 John的工作合并到自己的?featureA?分支中: ~~~ $ git checkout featureA Switched to branch "featureA" $ git merge origin/featureA Updating 3300904..aad881d Fast forward ?lib/simplegit.rb|?? 10 +++++++++- 1 files changed, 9 insertions(+), 1 deletions(-) ~~~ Jessica 稍做一番修整后同步到服務器: ~~~ $ git commit -am 'small tweak' [featureA ed774b3] small tweak ?1 files changed, 1insertions(+), 1 deletions(-) $ git push origin featureA ... To jessica@githost:simplegit.git ?? 3300904..ed774b3? featureA -> featureA ~~~ 現在的 Jessica提交歷史如圖 5-13所示: 圖 5-13. 在特性分支中提交更新后的提交歷史 現在,Jessica,Josie和 John 通知集成管理員服務器上的?featureA?及?featureBee?分支已經準備好,可以并入主線了。在管理員完成集成工作后,主分支上便多出一個新的合并提交(5399e),用 fetch 命令更新到本地后,提交歷史如圖 5-14所示: 圖 5-14. 合并特性分支后的Jessica 提交歷史 許多開發小組改用 Git就是因為它允許多個小組間并行工作,而在稍后恰當時機再行合并。通過共享遠程分支的方式,無需干擾整體項目代碼便可以開展工作,因此使用 Git的小型團隊間協作可以變得非常靈活自由。以上工作流程的時序如圖 5-15所示: 圖 5-15. 團隊間協作工作流程基本時序 **公開的小型項目** 上面說的是私有項目協作,但要給公開項目作貢獻,情況就有些不同了。因為你沒有直接更新主倉庫分支的權限,得尋求其它方式把工作成果交給項目維護人。下面會介紹兩種方法,第一種使用 git托管服務商提供的倉庫復制功能,一般稱作 fork,比如 repo.or.cz和 GitHub 都支持這樣的操作,而且許多項目管理員都希望大家使用這樣的方式。另一種方法是通過電子郵件寄送文件補丁。 但不管哪種方式,起先我們總需要克隆原始倉庫,而后創建特性分支開展工作。基本工作流程如下: ~~~ $ git clone (url) $ cd project $ git checkout -b featureA $ (work) $ git commit $ (work) $ git commit ~~~ 你可能想到用?rebase-i?將所有更新先變作單個提交,又或者想重新安排提交之間的差異補丁,以方便項目維護者審閱 – 有關交互式衍合操作的細節見第六章。 在完成了特性分支開發,提交給項目維護者之前,先到原始項目的頁面上點擊“Fork”按鈕,創建一個自己可寫的公共倉庫(譯注:即下面的 url部分,參照后續的例子,應該是git://githost/simplegit.git)。然后將此倉庫添加為本地的第二個遠端倉庫,姑且稱為?myfork: ~~~ $ git remote add myfork (url) ~~~ 你需要將本地更新推送到這個倉庫。要是將遠端 master合并到本地再推回去,還不如把整個特性分支推上去來得干脆直接。而且,假若項目維護者未采納你的貢獻的話(不管是直接合并還是 cherry pick),都不用回退(rewind)自己的 master 分支。但若維護者合并或 cherry-pick了你的工作,最后總還可以從他們的更新中同步這些代碼。好吧,現在先把 featureA分支整個推上去: ~~~ $ git push myfork featureA ~~~ 然后通知項目管理員,讓他來抓取你的代碼。通常我們把這件事叫做 pull request。可以直接用 GitHub等網站提供的 “pull request”按鈕自動發送請求通知;或手工把git request-pull?命令輸出結果電郵給項目管理員。 request-pull?命令接受兩個參數,第一個是本地特性分支開始前的原始分支,第二個是請求對方來抓取的 Git倉庫 URL(譯注:即下面myfork?所指的,自己可寫的公共倉庫)。比如現在Jessica準備要給 John 發一個 pull requst,她之前在自己的特性分支上提交了兩次更新,并把分支整個推到了服務器上,所以運行該命令會看到: ~~~ $ git request-pull origin/master myfork The following changes since commit1edee6b1d61823a2de3b09c160d7080b8d1b3a40: ? John Smith (1): ??????? added a newfunction are available in the git repository at: ?git://githost/simplegit.git featureA Jessica Smith (2): ????? add limit tolog function ????? change logoutput to 30 from 25 ?lib/simplegit.rb|?? 10 +++++++++- ?1 files changed, 9insertions(+), 1 deletions(-) ~~~ 輸出的內容可以直接發郵件給管理者,他們就會明白這是從哪次提交開始旁支出去的,該到哪里去抓取新的代碼,以及新的代碼增加了哪些功能等等。 像這樣隨時保持自己的?master?分支和官方?origin/master?同步,并將自己的工作限制在特性分支上的做法,既方便又靈活,采納和丟棄都輕而易舉。就算原始主干發生變化,我們也能重新衍合提供新的補丁。比如現在要開始第二項特性的開發,不要在原來已推送的特性分支上繼續,還是按原始master?開始: ~~~ $ git checkout -b featureB origin/master $ (work) $ git commit $ git push myfork featureB $ (email maintainer) $ git fetch origin ~~~ 現在,A、B兩個特性分支各不相擾,如同竹筒里的兩顆豆子,隊列中的兩個補丁,你隨時都可以分別從頭寫過,或者衍合,或者修改,而不用擔心特性代碼的交叉混雜。如圖 5-16所示: 圖 5-16.featureB以后的提交歷史 假設項目管理員接納了許多別人提交的補丁后,準備要采納你提交的第一個分支,卻發現因為代碼基準不一致,合并工作無法正確干凈地完成。這就需要你再次衍合到最新的?origin/master,解決相關沖突,然后重新提交你的修改: ~~~ $ git checkout featureA $ git rebase origin/master $ git push -f myfork featureA ~~~ 自然,這會重寫提交歷史,如圖 5-17所示: 圖 5-17.featureA重新衍合后的提交歷史 注意,此時推送分支必須使用?-f?選項(譯注:表示 force,不作檢查強制重寫)替換遠程已有的?featureA?分支,因為新的commit并非原來的后續更新。當然你也可以直接推送到另一個新的分支上去,比如稱作featureAv2。 再考慮另一種情形:管理員看過第二個分支后覺得思路新穎,但想請你改下具體實現。我們只需以當前?origin/master?分支為基準,開始一個新的特性分支featureBv2,然后把原來的?featureB?的更新拿過來,解決沖突,按要求重新實現部分代碼,然后將此特性分支推送上去: ~~~ $ git checkout -b featureBv2 origin/master $ git merge --no-commit --squash featureB $ (change implementation) $ git commit $ git push myfork featureBv2 ~~~ 這里的?--squash?選項將目標分支上的所有更改全拿來應用到當前分支上,而?--no-commit?選項告訴Git此時無需自動生成和記錄(合并)提交。這樣,你就可以在原來代碼基礎上,繼續工作,直到最后一起提交。 好了,現在可以請管理員抓取?featureBv2?上的最新代碼了,如圖 5-18 所示: 圖 5-18.featureBv2之后的提交歷史 **公開的大型項目** 許多大型項目都會立有一套自己的接受補丁流程,你應該注意下其中細節。但多數項目都允許通過開發者郵件列表接受補丁,現在我們來看具體例子。 整個工作流程類似上面的情形:為每個補丁創建獨立的特性分支,而不同之處在于如何提交這些補丁。不需要創建自己可寫的公共倉庫,也不用將自己的更新推送到自己的服務器,你只需將每次提交的差異內容以電子郵件的方式依次發送到郵件列表中即可。 ~~~ $ git checkout -b topicA $ (work) $ git commit $ (work) $ git commit ~~~ 如此一番后,有了兩個提交要發到郵件列表。我們可以用?git format-patch?命令來生成mbox格式的文件然后作為附件發送。每個提交都會封裝為一個.patch?后綴的mbox文件,但其中只包含一封郵件,郵件標題就是提交消息(譯注:額外有前綴,看例子),郵件內容包含補丁正文和 Git版本號。這種方式的妙處在于接受補丁時仍可保留原來的提交消息,請看接下來的例子: ~~~ $ git format-patch -M origin/master 0001-add-limit-to-log-function.patch 0002-changed-log-output-to-30-from-25.patch ~~~ format-patch?命令依次創建補丁文件,并輸出文件名。上面的?-M?選項允許Git檢查是否有對文件重命名的提交。我們來看看補丁文件的內容: ~~~ $ cat 0001-add-limit-to-log-function.patch From 330090432754092d704da8e76ca5c05c198e71a8 Mon Sep 1700:00:00 2001 From: Jessica Smith Date: Sun, 6 Apr 2008 10:17:23 -0700 Subject: [PATCH 1/2] add limit to log function Limit log functionality to the first 20 --- ?lib/simplegit.rb|??? 2 +- ?1 files changed, 1insertions(+), 1 deletions(-) diff --git a/lib/simplegit.rb b/lib/simplegit.rb index 76f47bc..f9815f1 100644 --- a/lib/simplegit.rb +++ b/lib/simplegit.rb @@ -14,7 +14,7 @@ class SimpleGit ?? end ?? def log(treeish= 'master') -???command("git log #{treeish}") +???command("git log -n 20 #{treeish}") ?? end ? ?def ls_tree(treeish = 'master') -- 1.6.2.rc1.20.g8c5b.dirty ~~~ 如果有額外信息需要補充,但又不想放在提交消息中說明,可以編輯這些補丁文件,在第一個?---?行之前添加說明,但不要修改下面的補丁正文,比如例子中的Limit log functionality to the first 20?部分。這樣,其它開發者能閱讀,但在采納補丁時不會將此合并進來。 你可以用郵件客戶端軟件發送這些補丁文件,也可以直接在命令行發送。有些所謂智能的郵件客戶端軟件會自作主張幫你調整格式,所以粘貼補丁到郵件正文時,有可能會丟失換行符和若干空格。Git提供了一個通過 IMAP發送補丁文件的工具。接下來我會演示如何通過 Gmail的 IMAP 服務器發送。另外,在 Git 源代碼中有個Documentation/SubmittingPatches?文件,可以仔細讀讀,看看其它郵件程序的相關導引。 首先在?~/.gitconfig?文件中配置 imap 項。每個選項都可用?git config?命令分別設置,當然直接編輯文件添加以下內容更便捷: ~~~ [imap] ? folder ="[Gmail]/Drafts" ? host =imaps://imap.gmail.com ? user =user@gmail.com ? pass = p4ssw0rd ? port = 993 ? sslverify = false ~~~ 如果你的 IMAP服務器沒有啟用 SSL,就無需配置最后那兩行,并且 host應該以?imap://?開頭而不再是有?s?的?imaps://。保存配置文件后,就能用?git send-email?命令把補丁作為郵件依次發送到指定的 IMAP服務器上的文件夾中(譯注:這里就是 Gmail的[Gmail]/Drafts?文件夾。但如果你的語言設置不是英文,此處的文件夾 Drafts字樣會變為對應的語言。): ~~~ $ git send-email *.patch 0001-added-limit-to-log-function.patch 0002-changed-log-output-to-30-from-25.patch Who should the emails appear to be from? [Jessica Smith ???? ] Emails will be sent from: Jessica Smith Who should the emails be sent to? jessica@example.com Message-ID to be used as In-Reply-To for the first email?y ~~~ 接下來,Git會根據每個補丁依次輸出類似下面的日志: ~~~ (mbox) Adding cc: Jessica Smith ????? from ? \line 'From:Jessica Smith ' OK. Log says: Sendmail: /usr/sbin/sendmail -i jessica@example.com From: Jessica Smith To: jessica@example.com Subject: [PATCH 1/2] added limit to log function Date: Sat, 30 May 2009 13:29:15 -0700 Message-Id:<1243715356-61726-1-git-send-email-jessica@example.com> X-Mailer: git-send-email 1.6.2.rc1.20.g8c5b.dirty In-Reply-To: ????????References: ????????? Result:OK ~~~ 最后,到 Gmail上打開 Drafts 文件夾,編輯這些郵件,修改收件人地址為郵件列表地址,另外給要抄送的人也加到 Cc列表中,最后發送。 **小結** 本節主要介紹了常見 Git項目協作的工作流程,還有一些幫助處理這些工作的命令和工具。接下來我們要看看如何維護 Git項目,并成為一個合格的項目管理員,或是集成經理。 ? **5.3?項目的管理** 既然是相互協作,在貢獻代碼的同時,也免不了要維護管理自己的項目。像是怎么處理別人用?format-patch?生成的補丁,或是集成遠端倉庫上某個分支上的變化等等。但無論是管理代碼倉庫,還是幫忙審核收到的補丁,都需要同貢獻者約定某種長期可持續的工作方式。 **使用特性分支進行工作** 如果想要集成新的代碼進來,最好局限在特性分支上做。臨時的特性分支可以讓你隨意嘗試,進退自如。比如碰上無法正常工作的補丁,可以先擱在那邊,直到有時間仔細核查修復為止。創建的分支可以用相關的主題關鍵字命名,比如ruby_client?或者其它類似的描述性詞語,以幫助將來回憶。Git項目本身還時常把分支名稱分置于不同命名空間下,比如?sc/ruby_client?就說明這是?sc?這個人貢獻的。現在從當前主干分支為基礎,新建臨時分支: ~~~ $ git branch sc/ruby_client master ~~~ 另外,如果你希望立即轉到分支上去工作,可以用?checkout -b: ~~~ $ git checkout -b sc/ruby_client master ~~~ 好了,現在已經準備妥當,可以試著將別人貢獻的代碼合并進來了。之后評估一下有沒有問題,最后再決定是不是真的要并入主干。 **采納來自郵件的補丁** 如果收到一個通過電郵發來的補丁,你應該先把它應用到特性分支上進行評估。有兩種應用補丁的方法:git apply?或者git am。 **使用 apply命令應用補丁** 如果收到的補丁文件是用?gitdiff?或由其它 Unix 的?diff?命令生成,就該用?git apply?命令來應用補丁。假設補丁文件存在?/tmp/patch-ruby-client.patch,可以這樣運行: ~~~ $ git apply /tmp/patch-ruby-client.patch ~~~ 這會修改當前工作目錄下的文件,效果基本與運行?patch -p1?打補丁一樣,但它更為嚴格,且不會出現混亂。如果是?gitdiff?格式描述的補丁,此命令還會相應地添加,刪除,重命名文件。當然,普通的patch?命令是不會這么做的。另外請注意,git apply?是一個事務性操作的命令,也就是說,要么所有補丁都打上去,要么全部放棄。所以不會出現patch?命令那樣,一部分文件打上了補丁而另一部分卻沒有,這樣一種不上不下的修訂狀態。所以總的來說,git apply?要比patch?嚴謹許多。因為僅僅是更新當前的文件,所以此命令不會自動生成提交對象,你得手工緩存相應文件的更新狀態并執行提交命令。 在實際打補丁之前,可以先用?gitapply --check?查看補丁是否能夠干凈順利地應用到當前分支中: ~~~ $ git apply --check0001-seeing-if-this-helps-the-gem.patch error: patch failed: ticgit.gemspec:1 error: ticgit.gemspec: patch does not apply ~~~ 如果沒有任何輸出,表示我們可以順利采納該補丁。如果有問題,除了報告錯誤信息之外,該命令還會返回一個非零的狀態,所以在 shell腳本里可用于檢測狀態。 **使用 am命令應用補丁** 如果貢獻者也用 Git,且擅于制作?format-patch?補丁,那你的合并工作將會非常輕松。因為這些補丁中除了文件內容差異外,還包含了作者信息和提交消息。所以請鼓勵貢獻者用format-patch?生成補丁。對于傳統的?diff?命令生成的補丁,則只能用?git apply?處理。 對于?format-patch?制作的新式補丁,應當使用?git am?命令。從技術上來說,gitam?能夠讀取 mbox格式的文件。這是種簡單的純文本文件,可以包含多封電郵,格式上用 From加空格以及隨便什么輔助信息所組成的行作為分隔行,以區分每封郵件,就像這樣: ~~~ From 330090432754092d704da8e76ca5c05c198e71a8 Mon Sep 1700:00:00 2001 From: Jessica Smith Date: Sun, 6 Apr 2008 10:17:23 -0700 Subject: [PATCH 1/2] add limit to log function Limit log functionality to the first 20 ~~~ 這是?format-patch?命令輸出的開頭幾行,也是一個有效的 mbox 文件格式。如果有人用?git send-email?給你發了一個補丁,你可以將此郵件下載到本地,然后運行git am?命令來應用這個補丁。如果你的郵件客戶端能將多封電郵導出為 mbox格式的文件,就可以用?git am?一次性應用所有導出的補丁。 如果貢獻者將?format-patch?生成的補丁文件上傳到類似 Request Ticket 一樣的任務處理系統,那么可以先下載到本地,繼而使用git am?應用該補丁: ~~~ $ git am 0001-limit-log-function.patch Applying: add limit to log function ~~~ 你會看到它被干凈地應用到本地分支,并自動創建了新的提交對象。作者信息取自郵件頭?From?和?Date,提交消息則取自Subject?以及正文中補丁之前的內容。來看具體實例,采納之前展示的那個 mbox 電郵補丁后,最新的提交對象為: ~~~ $ git log --pretty=fuller -1 commit 6c5e70b984a60b3cecd395edd5b48a7575bf58e0 Author:???? JessicaSmith AuthorDate: Sun Apr 6 10:17:23 2008 -0700 Commit:???? ScottChacon CommitDate: Thu Apr 9 09:19:06 2009 -0700 ?? add limit to logfunction ?? Limit logfunctionality to the first 20 ~~~ Commit?部分顯示的是采納補丁的人,以及采納的時間。而?Author?部分則顯示的是原作者,以及創建補丁的時間。 有時,我們也會遇到打不上補丁的情況。這多半是因為主干分支和補丁的基礎分支相差太遠,但也可能是因為某些依賴補丁還未應用。這種情況下,git am?會報錯并詢問該怎么做: ~~~ $ git am 0001-seeing-if-this-helps-the-gem.patch Applying: seeing if this helps the gem error: patch failed: ticgit.gemspec:1 error: ticgit.gemspec: patch does not apply Patch failed at 0001. When you have resolved this problem run "git am--resolved". If you would prefer to skip this patch, instead run"git am --skip". To restore the original branch and stop patching run"git am --abort". ~~~ Git 會在有沖突的文件里加入沖突解決標記,這同合并或衍合操作一樣。解決的辦法也一樣,先編輯文件消除沖突,然后暫存文件,最后運行?git am --resolved?提交修正結果: ~~~ $ (fix the file) $ git add ticgit.gemspec $ git am --resolved Applying: seeing if this helps the gem ~~~ 如果想讓 Git更智能地處理沖突,可以用?-3?選項進行三方合并。如果當前分支未包含該補丁的基礎代碼或其祖先,那么三方合并就會失敗,所以該選項默認為關閉狀態。一般來說,如果該補丁是基于某個公開的提交制作而成的話,你總是可以通過同步來獲取這個共同祖先,所以用三方合并選項可以解決很多麻煩: ~~~ $ git am -3 0001-seeing-if-this-helps-the-gem.patch Applying: seeing if this helps the gem error: patch failed: ticgit.gemspec:1 error: ticgit.gemspec: patch does not apply Using index info to reconstruct a base tree... Falling back to patching base and 3-way merge... No changes -- Patch already applied. ~~~ 像上面的例子,對于打過的補丁我又再打一遍,自然會產生沖突,但因為加上了?-3?選項,所以它很聰明地告訴我,無需更新,原有的補丁已經應用。 對于一次應用多個補丁時所用的 mbox格式文件,可以用?am?命令的交互模式選項?-i,這樣就會在打每個補丁前停住,詢問該如何操作: ~~~ $ git am -3 -i mbox Commit Body is: -------------------------- seeing if this helps the gem -------------------------- Apply? [y]es/[n]o/[e]dit/[v]iew patch/[a]ccept all ~~~ 在多個補丁要打的情況下,這是個非常好的辦法,一方面可以預覽下補丁內容,同時也可以有選擇性的接納或跳過某些補丁。 打完所有補丁后,如果測試下來新特性可以正常工作,那就可以安心地將當前特性分支合并到長期分支中去了。 **檢出遠程分支** 如果貢獻者有自己的 Git倉庫,并將修改推送到此倉庫中,那么當你拿到倉庫的訪問地址和對應分支的名稱后,就可以加為遠程分支,然后在本地進行合并。 比如,Jessica發來一封郵件,說在她代碼庫中的?ruby-client?分支上已經實現了某個非常棒的新功能,希望我們能幫忙測試一下。我們可以先把她的倉庫加為遠程倉庫,然后抓取數據,完了再將她所說的分支檢出到本地來測試: ~~~ $ git remote add jessicagit://github.com/jessica/myproject.git $ git fetch jessica $ git checkout -b rubyclient jessica/ruby-client ~~~ 若是不久她又發來郵件,說還有個很棒的功能實現在另一分支上,那我們只需重新抓取下最新數據,然后檢出那個分支到本地就可以了,無需重復設置遠程倉庫。 這種做法便于同別人保持長期的合作關系。但前提是要求貢獻者有自己的服務器,而我們也需要為每個人建一個遠程分支。有些貢獻者提交代碼補丁并不是很頻繁,所以通過郵件接收補丁效率會更高。同時我們自己也不會希望建上百來個分支,卻只從每個分支取一兩個補丁。但若是用腳本程序來管理,或直接使用代碼倉庫托管服務,就可以簡化此過程。當然,選擇何種方式取決于你和貢獻者的喜好。 使用遠程分支的另外一個好處是能夠得到提交歷史。不管代碼合并是不是會有問題,至少我們知道該分支的歷史分叉點,所以默認會從共同祖先開始自動進行三方合并,無需?-3?選項,也不用像打補丁那樣祈禱存在共同的基準點。 如果只是臨時合作,只需用?gitpull?命令抓取遠程倉庫上的數據,合并到本地臨時分支就可以了。一次性的抓取動作自然不會把該倉庫地址加為遠程倉庫。 ~~~ $ git pull git://github.com/onetimeguy/project.git From git://github.com/onetimeguy/project ?* branch??????????? HEAD?????? -> FETCH_HEAD Merge made by recursive. ~~~ **決斷代碼取舍** 現在特性分支上已合并好了貢獻者的代碼,是時候決斷取舍了。本節將回顧一些之前學過的命令,以看清將要合并到主干的是哪些代碼,從而理解它們到底做了些什么,是否真的要并入。 一般我們會先看下,特性分支上都有哪些新增的提交。比如在?contrib?特性分支上打了兩個補丁,僅查看這兩個補丁的提交信息,可以用--not?選項指定要屏蔽的分支?master,這樣就會剔除重復的提交歷史: ~~~ $ git log contrib --not master commit 5b6235bd297351589efc4d73316f0a68d484f118 Author: Scott Chacon Date:?? Fri Oct 2409:53:59 2008 -0700 ??? seeing if thishelps the gem commit 7482e0d16d04bea79d0dba8988cc78df655f16a0 Author: Scott Chacon Date:?? Mon Oct 2219:38:36 2008 -0700 ??? updated thegemspec to hopefully work better ~~~ 還可以查看每次提交的具體修改。請牢記,在?git log?后加?-p?選項將展示每次提交的內容差異。 如果想看當前分支同其他分支合并時的完整內容差異,有個小竅門: ~~~ $ git diff master ~~~ 雖然能得到差異內容,但請記住,結果有可能和我們的預期不同。一旦主干?master?在特性分支創建之后有所修改,那么通過?diff?命令來比較的,是最新主干上的提交快照。顯然,這不是我們所要的。比方在?master?分支中某個文件里添了一行,然后運行上面的命令,簡單的比較最新快照所得到的結論只能是,特性分支中刪除了這一行。 這個很好理解:如果?master?是特性分支的直接祖先,不會產生任何問題;如果它們的提交歷史在不同的分叉上,那么產生的內容差異,看起來就像是增加了特性分支上的新代碼,同時刪除了master?分支上的新代碼。 實際上我們真正想要看的,是新加入到特性分支的代碼,也就是合并時會并入主干的代碼。所以,準確地講,我們應該比較特性分支和它同?master?分支的共同祖先之間的差異。 我們可以手工定位它們的共同祖先,然后與之比較: ~~~ $ git merge-base contrib master 36c7dba2c95e6bbb78dfa822519ecfec6e1ca649 $ git diff 36c7db ~~~ 但這么做很麻煩,所以 Git提供了便捷的?...?語法。對于?diff?命令,可以把?...?加在原始分支(擁有共同祖先)和當前分支之間: ~~~ $ git diff master...contrib ~~~ 現在看到的,就是實際將要引入的新代碼。這是一個非常有用的命令,應該牢記。 **代碼集成** 一旦特性分支準備停當,接下來的問題就是如何集成到更靠近主線的分支中。此外還要考慮維護項目的總體步驟是什么。雖然有很多選擇,不過我們這里只介紹其中一部分。 **合并流程** 一般最簡單的情形,是在?master?分支中維護穩定代碼,然后在特性分支上開發新功能,或是審核測試別人貢獻的代碼,接著將它并入主干,最后刪除這個特性分支,如此反復。來看示例,假設當前代碼庫中有兩個分支,分別為ruby_client?和php_client,如圖 5-19 所示。然后先把?ruby_client?合并進主干,再合并php_client,最后的提交歷史如圖5-20所示。 圖 5-19. 多個特性分支 圖 5-20. 合并特性分支之后 這是最簡單的流程,所以在處理大一些的項目時可能會有問題。 對于大型項目,至少需要維護兩個長期分支?master?和?develop。新代碼(圖 5-21 中的?ruby_client)將首先并入?develop分支(圖 5-22 中的?C8),經過一個階段,確認develop?中的代碼已穩定到可發行時,再將?master?分支快進到穩定點(圖5-23中的?C8)。而平時這兩個分支都會被推送到公開的代碼庫。 圖 5-21. 特性分支合并前 圖 5-22. 特性分支合并后 圖 5-23. 特性分支發布后 這樣,在人們克隆倉庫時就有兩種選擇:既可檢出最新穩定版本,確保正常使用;也能檢出開發版本,試用最前沿的新特性。你也可以擴展這個概念,先將所有新代碼合并到臨時特性分支,等到該分支穩定下來并通過測試后,再并入develop?分支。然后,讓時間檢驗一切,如果這些代碼確實可以正常工作相當長一段時間,那就有理由相信它已經足夠穩定,可以放心并入主干分支發布。 **大項目的合并流程** Git 項目本身有四個長期分支:用于發布的?master?分支、用于合并基本穩定特性的?next?分支、用于合并仍需改進特性的pu分支(pu是 proposedupdates的縮寫),以及用于除錯維護的?maint?分支(maint取自maintenance)。維護者可以按照之前介紹的方法,將貢獻者的代碼引入為不同的特性分支(如圖 5-24 所示),然后測試評估,看哪些特性能穩定工作,哪些還需改進。穩定的特性可以并入next?分支,然后再推送到公共倉庫,以供其他人試用。 圖 5-24. 管理復雜的并行貢獻 仍需改進的特性可以先并入?pu?分支。直到它們完全穩定后再并入?master。同時一并檢查下?next?分支,將足夠穩定的特性也并入?master。所以一般來說,master?始終是在快進,next?偶爾做下衍合,而pu?則是頻繁衍合,如圖5-25所示: 圖 5-25. 將特性并入長期分支 并入?master?后的特性分支,已經無需保留分支索引,放心刪除好了。Git項目還有一個?maint?分支,它是以最近一次發行版為基礎分化而來的,用于維護除錯補丁。所以克隆 Git 項目倉庫后會得到這四個分支,通過檢出不同分支可以了解各自進展,或是試用前沿特性,或是貢獻代碼。而維護者則通過管理這些分支,逐步有序地并入第三方貢獻。 **衍合與挑揀(cherry-pick)的流程** 一些維護者更喜歡衍合或者挑揀貢獻者的代碼,而不是簡單的合并,因為這樣能夠保持線性的提交歷史。如果你完成了一個特性的開發,并決定將它引入到主干代碼中,你可以轉到那個特性分支然后執行衍合命令,好在你的主干分支上(也可能是develop分支之類的)重新提交這些修改。如果這些代碼工作得很好,你就可以快進master分支,得到一個線性的提交歷史。 另一個引入代碼的方法是挑揀。挑揀類似于針對某次特定提交的衍合。它首先提取某次提交的補丁,然后試著應用在當前分支上。如果某個特性分支上有多個 commits,但你只想引入其中之一就可以使用這種方法。也可能僅僅是因為你喜歡用挑揀,討厭衍合。假設你有一個類似圖 5-26 的工程。 圖 5-26. 挑揀(cherry-pick)之前的歷史 如果你希望拉取e43a6到你的主干分支,可以這樣: ~~~ $ git cherry-picke43a6fd3e94888d76779ad79fb568ed180e5fcdf Finished one cherry-pick. [master]: created a0a41a9: "More friendly messagewhen locking the index fails." ?3 files changed,17 insertions(+), 3 deletions(-) ~~~ 這將會引入e43a6的代碼,但是會得到不同的SHA-1值,因為應用日期不同。現在你的歷史看起來像圖 5-27. 圖 5-27. 挑揀(cherry-pick)之后的歷史 現在,你可以刪除這個特性分支并丟棄你不想引入的那些commit。 **給發行版簽名** 你可以刪除上次發布的版本并重新打標簽,也可以像第二章所說的那樣建立一個新的標簽。如果你決定以維護者的身份給發行版簽名,應該這樣做: ~~~ $ git tag -s v1.5 -m 'my signed 1.5 tag' You need a passphrase to unlock the secret key for user: "Scott Chacon ???? " 1024-bit DSA key, ID F721C45A, created 2009-02-09 ~~~ 完成簽名之后,如何分發PGP公鑰(public key)是個問題。(譯者注:分發公鑰是為了驗證標簽)。還好,Git的設計者想到了解決辦法:可以把key(既公鑰)作為blob變量寫入Git庫,然后把它的內容直接寫在標簽里。gpg --list-keys命令可以顯示出你所擁有的key: ~~~ $ gpg --list-keys /Users/schacon/.gnupg/pubring.gpg --------------------------------- pub??1024D/F721C45A 2009-02-09 [expires: 2010-02-09] uid?????????????????Scott Chacon sub??2048g/45D02282 2009-02-09 [expires: 2010-02-09] ~~~ 然后,導出key的內容并經由管道符傳遞給git hash-object,之后鑰匙會以blob類型寫入Git中,最后返回這個blob量的SHA-1值: ~~~ $ gpg -a --export F721C45A | git hash-object -w --stdin 659ef797d181633c87ec71ac3f9ba29fe5775b92 ~~~ 現在你的Git已經包含了這個key的內容了,可以通過不同的SHA-1值指定不同的key來創建標簽。 ~~~ $ git tag -a maintainer-pgp-pub659ef797d181633c87ec71ac3f9ba29fe5775b92 ~~~ 在運行git push --tags命令之后,maintainer-pgp-pub標簽就會公布給所有人。如果有人想要校驗標簽,他可以使用如下命令導入你的key: ~~~ $ git show maintainer-pgp-pub | gpg --import ~~~ 人們可以用這個key校驗你簽名的所有標簽。另外,你也可以在標簽信息里寫入一個操作向導,用戶只需要運行git show?查看標簽信息,然后按照你的向導就能完成校驗。 **生成內部版本號** 因為Git不會為每次提交自動附加類似’v123’的遞增序列,所以如果你想要得到一個便于理解的提交號可以運行git describe命令。Git將會返回一個字符串,由三部分組成:最近一次標定的版本號,加上自那次標定之后的提交次數,再加上一段SHA-1值of the commit you’re describing: ~~~ $ git describe master v1.6.2-rc1-20-g8c5b85c ~~~ 這個字符串可以作為快照的名字,方便人們理解。如果你的Git是你自己下載源碼然后編譯安裝的,你會發現git --version命令的輸出和這個字符串差不多。如果在一個剛剛打完標簽的提交上運行describe命令,只會得到這次標定的版本號,而沒有后面兩項信息。 gitdescribe命令只適用于有標注的標簽(通過-a或者-s選項創建的標簽),所以發行版的標簽都應該是帶有標注的,以保證git describe能夠正確的執行。你也可以把這個字符串作為checkout或者show命令的目標,因為他們最終都依賴于一個簡短的SHA-1值,當然如果這個SHA-1值失效他們也跟著失效。最近Linux內核為了保證SHA-1值的唯一性,將位數由8位擴展到10位,這就導致擴展之前的git describe輸出完全失效了。 **準備發布** 現在可以發布一個新的版本了。首先要將代碼的壓縮包歸檔,方便那些可憐的還沒有使用Git的人們。可以使用git archive: ~~~ $ git archive master --prefix='project/' | gzip > `gitdescribe master`.tar.gz $ ls *.tar.gz v1.6.2-rc1-20-g8c5b85c.tar.gz ~~~ 這個壓縮包解壓出來的是一個文件夾,里面是你項目的最新代碼快照。你也可以用類似的方法建立一個zip壓縮包,在git archive加上--format=zip選項: ~~~ $ git archive master --prefix='project/' --format=zip> `git describe master`.zip ~~~ 現在你有了一個tar.gz壓縮包和一個zip壓縮包,可以把他們上傳到你網站上或者用e-mail發給別人。 **制作簡報** 是時候通知郵件列表里的朋友們來檢驗你的成果了。使用git shortlog命令可以方便快捷的制作一份修改日志(changelog),告訴大家上次發布之后又增加了哪些特性和修復了哪些bug。實際上這個命令能夠統計給定范圍內的所有提交;假如你上一次發布的版本是v1.0.1,下面的命令將給出自從上次發布之后的所有提交的簡介: ~~~ $ git shortlog --no-merges master --not v1.0.1 Chris Wanstrath (8): ????? Add supportfor annotated tags to Grit::Tag ????? Addpacked-refs annotated tag support. ????? AddGrit::Commit#to_patch ????? Updateversion and History.txt ????? Remove stray`puts` ????? Make ls_treeignore nils Tom Preston-Werner (4): ????? fix dates inhistory ????? dynamicversion method ????? Version bumpto 1.0.2 ????? Regeneratedgemspec for version 1.0.2 ~~~ 這就是自從v1.0.1版本以來的所有提交的簡介,內容按照作者分組,以便你能快速的發e-mail給他們。 **5.4?小結** 你學會了如何使用Git為項目做貢獻,也學會了如何使用Git維護你的項目。恭喜!你已經成為一名高效的開發者。在下一篇你將學到更強大的工具來處理更加復雜的問題,之后你會變成一位Git大師。
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看