?
本文檔使用 php中文網(wǎng)手冊 發(fā)布
gitcore-tutorial - 面向開發(fā)人員的Git核心教程
git *
本教程介紹了如何使用“core”Git命令來設(shè)置和使用Git存儲庫。
如果你只需要使用Git作為修訂控制系統(tǒng),你可能更喜歡從“Git教程簡介”(gittutorial[7])或Git用戶手冊開始。
但是,如果您想了解Git的內(nèi)部組件,那么對這些低級工具的理解會很有幫助。
核心Git通常被稱為“plumbing”,其中更漂亮的用戶界面稱為“porcelain”。您可能不想經(jīng)常直接使用管道,但可以很好地知道porcelain沒有沖洗時plumbing的功能。
本文最初編寫時,很多瓷器命令都是shell腳本。為了簡單起見,它仍然用它們作為例子來說明管道是如何配合在一起形成瓷器命令的。源代碼樹包含一些在contrib/examples/中的腳本以供參考。雖然這些不再作為shell腳本實現(xiàn),但對管道層命令做什么的描述仍然有效。
注意 | 更深入的技術(shù)細(xì)節(jié)通常被標(biāo)記為Notes,您可以在第一次閱讀時跳過。 |
---|
創(chuàng)建一個新的Git倉庫不是一件容易的事情:所有的Git倉庫都是空的,你唯一需要做的就是找到一個你想用作工作樹的子目錄 - 對于一個全新的項目來說是空的,或者您想要導(dǎo)入到Git中的現(xiàn)有工作樹。
對于我們的第一個例子,我們將從頭開始一個全新的存儲庫,沒有預(yù)先存在的文件,我們將調(diào)用它git-tutorial
。首先,為它創(chuàng)建一個子目錄,切換到該子目錄,并使用以下命令初始化Git基礎(chǔ)結(jié)構(gòu)git init
:
$ mkdir git-tutorial $ cd git-tutorial $ git init
Git會回復(fù)
Initialized empty Git repository in .git/
這只是Git說你沒有做過任何奇怪事情的方式,并且它會.git
為你的新項目創(chuàng)建一個本地目錄設(shè)置。你現(xiàn)在會有一個.git
目錄,你可以用它來檢查ls
。對于您的新空項目,它應(yīng)該顯示三個條目,其中包括:
一個叫做HEAD
的文件,里面有ref: refs/heads/master
。這類似于符號鏈接并指向refs/heads/master
相對于該HEAD
文件。不要擔(dān)心HEAD
鏈接指向的文件甚至不存在 - 您尚未創(chuàng)建將啟動您的HEAD
開發(fā)分支的提交。
一個名為objects
的子目錄,其中將包含項目的所有對象。你永遠(yuǎn)不應(yīng)該有任何真正的理由直接看對象,但你可能想知道這些對象是什么包含data
你的存儲庫中的所有實體。
一個名為refs
的子目錄,其中包含對對象的引用。
特別是,refs
子目錄將包含另外兩個子目錄,分別命名heads
和tags
分別。他們完全按照他們的名字暗示:它們包含對任意數(shù)量不同heads
開發(fā)(aka branches
)的引用,以及為tags
創(chuàng)建存儲庫中特定版本而創(chuàng)建的任何引用。
注意:特殊的master
頭是默認(rèn)分支,這就是為什么.git/HEAD
文件被創(chuàng)建指向它,即使它尚不存在。基本上,這個HEAD
鏈接應(yīng)該始終指向你現(xiàn)在正在工作的分支,并且你總是開始期待在master
分支上工作。
然而,這僅僅是一個約定,你可以隨意命名你的分支什么,不必連過have
一個master
分支。不過,許多Git工具會認(rèn)為.git/HEAD
是有效的。
注意 | 一個對象由其160位SHA-1散列(又名對象名稱)標(biāo)識,而對象的引用始終是該SHA-1名稱的40字節(jié)十六進(jìn)制表示。預(yù)計refs子目錄中的文件將包含這些十六進(jìn)制引用(通常在最后有一個最后的\n),因此當(dāng)您實際啟動時,您應(yīng)該期望在這些refs子目錄中看到許多包含這些引用的41字節(jié)文件填充你的樹。 |
---|
注意 | 完成本教程后,高級用戶可能想看看gitrepository-layout5。 |
---|
你現(xiàn)在已經(jīng)創(chuàng)建了你的第一個Git倉庫。當(dāng)然,由于它是空的,這不是很有用,所以讓我們開始用數(shù)據(jù)填充它。
我們會保持這種簡單和愚蠢的,所以我們將開始填充一些簡單的文件,以獲得它的感覺。
首先創(chuàng)建你想在你的Git倉庫中維護(hù)的隨機文件。我們將從一些不好的例子開始,以了解它的工作原理:
$ echo "Hello World" >hello $ echo "Silly example" >example
你現(xiàn)在在你的工作樹(又名working directory
)中創(chuàng)建了兩個文件,但要真正檢查你的努力工作,你必須經(jīng)歷兩個步驟:
用有關(guān)工作樹狀態(tài)的信息填寫index
文件(又名cache
)。
將該索引文件作為對象提交。
第一步很簡單:當(dāng)你想告訴Git關(guān)于工作樹的任何修改時,你就可以使用git update-index
程序。該程序通常只需要一個你想更新的文件名列表,但為了避免微不足道的錯誤,它拒絕向索引添加新的條目(或刪除現(xiàn)有的條目),除非你明確地告訴它你正在添加一個新的條目--add
標(biāo)志(或移除--remove
標(biāo)志)。
因此,要使用剛剛創(chuàng)建的兩個文件填充索引,可以這樣做
$ git update-index --add hello example
你現(xiàn)在已經(jīng)告訴Git跟蹤這兩個文件。
事實上,正如你所做的那樣,如果你現(xiàn)在查看你的對象目錄,你會注意到Git會在對象數(shù)據(jù)庫中添加兩個新對象。如果你完成上述步驟,你現(xiàn)在應(yīng)該可以做到
$ ls .git/objects/??/*
并看兩個文件:
.git/objects/55/7db03de997c86a4a028e1ebd3a1ceb225be238.git/objects/f2/4c74a2e500f5ee1332c86b94199f52b1d1d962
它們分別與名稱為557db...
和的對象相對應(yīng)f24c7...
。
如果你愿意,你可以用git cat-file
來查看這些對象,但是你必須使用對象名稱,而不是對象的文件名:
$ git cat-file -t 557db03de997c86a4a028e1ebd3a1ceb225be238
當(dāng)-t
告訴git cat-file
告訴你對象的“類型”是什么。Git會告訴你,你有一個“blob”對象(即,只是一個普通文件),你可以看到內(nèi)容
$ git cat-file blob 557db03
這將打印出“Hello World”。該對象557db03
只不過是文件的內(nèi)容hello
。
注意 | 不要將該對象與文件hello本身混淆。該對象實際上只是文件的特定內(nèi)容,不管您稍后更改文件hello中的內(nèi)容,我們剛剛查看的對象都不會改變。對象是不可變的。 |
---|
注意 | 第二個例子說明,可以在大部分地方將對象名縮寫為僅前幾個十六進(jìn)制數(shù)字。 |
---|
無論如何,正如我們前面提到的,通常你永遠(yuǎn)都不會真正看到對象本身,而輸入長40個字符的十六進(jìn)制名稱并不是你通常想要做的事情。上面的題目只是為了表明git update-index
做了一些神奇的事情,并且實際上將文件的內(nèi)容保存到了Git對象數(shù)據(jù)庫中。
更新索引也做了其他事情:它創(chuàng)建了一個.git/index
文件。這是描述您當(dāng)前工作樹的索引,以及您應(yīng)該非常清楚的內(nèi)容。同樣,你通常不會擔(dān)心索引文件本身,但是你應(yīng)該知道這樣一個事實,即到目前為止你還沒有真正“檢查”你的文件到Git中,你只是告訴Git。
但是,由于Git知道它們,現(xiàn)在可以開始使用一些最基本的Git命令來操縱文件或查看它們的狀態(tài)。
特別是,我們甚至不會將兩個文件檢入到Git中,然后我們將首先添加另一行hello
:
$ echo "It's a new day for git" >>hello
現(xiàn)在你可以,因為你告訴Git關(guān)于以前的狀態(tài)hello
,請問使用下面的git diff-files
命令Git樹中的變化與舊索引相比:
$ git diff-files
噢不,這不是很可讀。它只是說出它自己的內(nèi)部版本diff
,但是這個內(nèi)部版本實際上只是告訴你,它已經(jīng)注意到“hello”已被修改,并且它的舊對象內(nèi)容已被替換為其他東西。
為了使它可讀,我們可以git diff-files
通過使用-p
標(biāo)志來告訴輸出差異作為補丁:
$ git diff-files -p diff --git a/hello b/hello index 557db03..263414f 100644--- a/hello+++ b/hello @@ -1 +1,2 @@ Hello World+It's a new day for git
即我們通過添加另一條線所導(dǎo)致的變化的差異hello
。
換句話說,git diff-files
總是向我們顯示索引中記錄的內(nèi)容與工作樹中當(dāng)前內(nèi)容之間的區(qū)別。這非常有用。
一個通用的簡寫git diff-files -p
是只寫git diff
,它會做同樣的事情。
$ git diff diff --git a/hello b/hello index 557db03..263414f 100644--- a/hello+++ b/hello @@ -1 +1,2 @@ Hello World+It's a new day for git
現(xiàn)在,我們要進(jìn)入Git的下一個階段,即在索引中獲取Git知道的文件,并將它們作為真正的樹進(jìn)行提交。我們分兩個階段來完成:創(chuàng)建一個tree
對象,并將該tree
對象作為commit
對象提交,同時解釋樹的全部內(nèi)容以及我們?nèi)绾芜M(jìn)入該狀態(tài)的信息。
創(chuàng)建一個樹對象是微不足道的,并且完成git write-tree
。沒有選項或其他輸入:git write-tree
將采用當(dāng)前索引狀態(tài),并編寫描述整個索引的對象。換句話說,我們現(xiàn)在將所有不同的文件名與他們的內(nèi)容(以及他們的權(quán)限)結(jié)合在一起,我們創(chuàng)建了一個Git“目錄”對象的等價物:
$ git write-tree
并且這只會輸出結(jié)果樹的名稱,在這種情況下(如果您完全按照我所描述的那樣完成)它應(yīng)該是
8988da15d077d4829fc51d8544c097def6644dbb
這是另一個難以理解的對象名稱。再一次,如果你愿意的話,你可以使用git cat-file -t 8988d...
看到這次對象不是一個“blob”對象,而是一個“樹”對象(你也可以用它git cat-file
來實際輸出原始對象的內(nèi)容,但你會看到主要二進(jìn)制混亂,所以這不那么有趣)。
但是 - 通常你不會git write-tree
自己使用它,因為通常你總是使用該git commit-tree
命令將一棵樹提交到一個提交對象中。事實上,根本不能實際使用git write-tree
它,但只是將其結(jié)果作為參數(shù)傳遞給它git commit-tree
。
git commit-tree
通常需要幾個參數(shù) - 它想知道parent
提交的內(nèi)容是什么,但由于這是該新存儲庫中的第一次提交,并且沒有父母,我們只需要傳入樹的對象名稱。但是,git commit-tree
也希望在其標(biāo)準(zhǔn)輸入上獲得提交消息,并將提交的結(jié)果對象名稱寫入其標(biāo)準(zhǔn)輸出。
這就是我們創(chuàng)建.git/refs/heads/master
指向的文件的地方HEAD
。這個文件應(yīng)該包含對主分支樹頂端的引用,因為這正是git commit-tree
吐出來的東西,所以我們可以用一系列簡單的shell命令來完成這一切:
$ tree=$(git write-tree)$ commit=$(echo 'Initial commit' | git commit-tree $tree)$ git update-ref HEAD $commit
在這種情況下,這會創(chuàng)建一個與其他任何內(nèi)容都無關(guān)的全新提交。通常情況下,你這樣做只是一次為一個項目永遠(yuǎn),其隨后所有的提交將在較早提交頂部父。
再次,通常你永遠(yuǎn)不會親自去做這件事。有一個有用的腳本git commit
,它會為你做所有這些。所以你可以寫下來git commit
,而且它會為你完成上面的magic腳本。
請記住我們是如何完成git update-index
on文件的hello
,然后再進(jìn)行更改hello
,并且可以將新狀態(tài)hello
與我們保存在索引文件中的狀態(tài)進(jìn)行比較?
此外,請記住我是如何git write-tree
將索引文件的內(nèi)容寫入樹中的,因此我們剛剛承諾的實際上是文件的原始內(nèi)容hello
,而不是新的內(nèi)容。我們這樣做的目的是為了顯示索引狀態(tài)和工作樹中的狀態(tài)之間的區(qū)別,以及它們?nèi)绾尾黄ヅ?,即使我們犯了錯誤。
和以前一樣,如果我們git diff-files -p
在我們的git-tutorial項目中做,我們?nèi)匀粫吹轿覀兩洗慰吹降耐瑯拥膮^(qū)別:索引文件并沒有因提交任何內(nèi)容而改變。然而,現(xiàn)在我們已經(jīng)犯了一些事件,我們也可以學(xué)習(xí)使用一個新的命令:git diff-index
。
與git diff-files
顯示索引文件和工作樹git diff-index
之間的區(qū)別不同,顯示了提交樹與索引文件或工作樹之間的區(qū)別。換句話說,我們git diff-index
希望一棵樹能夠與之相抗衡,而在我們提交之前,我們不能這樣做,因為我們沒有任何可以相互區(qū)分的東西。
但現(xiàn)在我們可以做到
$ git diff-index -p HEAD
(這里-p
的含義與它的意思相同git diff-files
),它會向我們展示同樣的差異,但是卻出于完全不同的原因?,F(xiàn)在我們將工作樹與索引文件進(jìn)行比較,但是對照我們剛剛編寫的樹。恰恰相反,這兩個顯然是相同的,所以我們得到了相同的結(jié)果。
再次,因為這是一種常見的操作,您也可以簡單地使用
$ git diff HEAD
最終為你做上述事情。
換句話說,git diff-index
通常將一棵樹與工作樹進(jìn)行比較,但是在給定該--cached
標(biāo)志時,會告訴它僅僅比較索引緩存內(nèi)容,并完全忽略當(dāng)前工作樹狀態(tài)。由于我們只是將索引文件寫入HEAD,git diff-index --cached -p HEAD
因此應(yīng)該返回一組空白的差異,而這正是它所做的。
注意 | 因此,git diff-index確實總是使用索引進(jìn)行比較,并且說它將樹與工作樹進(jìn)行比較因此不是嚴(yán)格準(zhǔn)確的。特別是,無論是否使用--cached標(biāo)志,要比較的文件列表(“元數(shù)據(jù)”)總是來自索引文件。--cached標(biāo)志只確定要比較的文件內(nèi)容是否來自工作樹。這并不難理解,只要您意識到Git根本就不知道(或關(guān)心)文件,而不會明確地告知它。Git永遠(yuǎn)不會去查找要比較的文件,它希望你告訴它文件是什么,這就是索引的用途。 |
---|
但是,我們下一步要承諾我們所做的改變,并且再次了解發(fā)生了什么,請牢記“工作樹內(nèi)容”,“索引文件”和“承諾樹”之間的差異。我們在工作樹中進(jìn)行了更改,我們需要處理索引文件,因此我們需要做的第一件事是更新索引緩存:
$ git update-index hello
(注意--add
這次我們不需要這個標(biāo)志,因為Git已經(jīng)知道這個文件了)。
注意git diff-*
這里的不同版本會發(fā)生什么。我們更新后hello
的指數(shù),git diff-files -p
現(xiàn)在顯示無顯著差異,但git diff-index -p HEAD
仍然沒有顯示當(dāng)前狀態(tài)是我們犯下的狀態(tài)不同。實際上,git diff-index
無論我們是否使用該--cached
標(biāo)志,現(xiàn)在都顯示出同樣的差異,因為現(xiàn)在索引與工作樹是一致的。
現(xiàn)在,由于我們已經(jīng)更新hello
了索引,我們可以提交新版本。我們可以通過再次手工編寫樹并提交樹來完成此操作(這次我們必須使用-p HEAD
標(biāo)志來告訴提交HEAD是新提交的父代,并且這不是初始提交任何更多),但你已經(jīng)完成了,所以我們這次只使用有用的腳本:
$ git commit
它會啟動一個編輯器來編寫提交消息,并告訴你一些關(guān)于你已經(jīng)完成的事情。
寫下你想要的任何消息,所有#
以此開始的行將被刪除,其余的將被用作改變的提交消息。如果您決定此時不想提交任何內(nèi)容(您可以繼續(xù)編輯內(nèi)容并更新索引),則可以留下一條空的消息。否則git commit
會為你做出改變。
你現(xiàn)在已經(jīng)做出了你的第一個真正的Git提交。如果你有興趣研究git commit
真正的功能,請隨時調(diào)查:這是一些非常簡單的shell腳本,用于生成有用的(?)提交消息頭文件,以及一些實際執(zhí)行提交本身的單行程序(git commit
) 。
雖然創(chuàng)建更改很有用,但如果以后可以告訴更改了哪些內(nèi)容,則更有用。對此的最有用的命令是另一個diff
家庭,即git diff-tree
。
git diff-tree
可以給兩棵任意的樹,它會告訴你它們之間的區(qū)別。也許更普遍的是,你可以給它一個單一的提交對象,它會找出那個提交本身的父對象,并直接顯示它們之間的區(qū)別。因此,為了獲得我們已經(jīng)多次看到的差異,我們現(xiàn)在可以做
$ git diff-tree -p HEAD
(再次,-p
意味著將差異顯示為人類可讀的補?。?,并且它將顯示上次提交(in HEAD
)實際上發(fā)生了什么變化。
Note | Here is an ASCII art by Jon Loeliger that illustrates how various diff-* commands compare things. diff-tree +----+ | | | | V V +-----------+ | Object DB | | Backing | | Store | +-----------+ ^ ^ | | | | diff-index --cached | | diff-index | V | +-----------+ | | Index | | | "cache" | | +-----------+ | ^ | | | | diff-files | | V V +-----------+ | Working | | Directory | +-----------+ |
---|
更有意思的是,你也可以給git diff-tree
這個--pretty
標(biāo)志,告訴它也顯示提交信息和作者以及提交日期,你可以告訴它顯示一系列的差異?;蛘?,你可以告訴它是“沉默”,并且根本不顯示差異,但只顯示實際的提交信息。
實際上,與git rev-list
程序(產(chǎn)生修訂列表)一起,git diff-tree
最終成為變化的真正來源。你可以模仿git log
,git log -p
等用一個簡單的腳本,管道輸出git rev-list
到git diff-tree --stdin
,這是究竟如何早期版本git log
中實現(xiàn)。
在Git中,有兩種標(biāo)簽,一種是“輕”標(biāo)簽,另一種是“帶標(biāo)簽的標(biāo)簽”。
一個“l(fā)ight”標(biāo)簽在技術(shù)上只不過是一個分支,除了我們把它放在.git/refs/tags/
子目錄中而不是調(diào)用它head
。所以最簡單的標(biāo)簽形式僅僅涉及
$ git tag my-first-tag
它只是把當(dāng)前HEAD
的.git/refs/tags/my-first-tag
文件寫入文件,然后你可以在這個特定的狀態(tài)下使用這個符號名稱。例如,你可以做
$ git diff my-first-tag
將當(dāng)前狀態(tài)與當(dāng)前顯然是空白區(qū)別的標(biāo)簽進(jìn)行比較,但如果您繼續(xù)開發(fā)和提交內(nèi)容,則可以使用標(biāo)簽作為“定位點”來查看標(biāo)記后發(fā)生了哪些變化。
一個“帶注釋的標(biāo)簽”實際上是一個真正的Git對象,它不僅包含一個指向你想要標(biāo)記的狀態(tài)的指針,而且還包含一個小標(biāo)簽名稱和消息,還有一個可選的PGP簽名,表示是的,你確實是這樣做的標(biāo)簽。您可以使用-a
或-s
標(biāo)志創(chuàng)建這些帶注釋的標(biāo)簽git tag
:
$ git tag -s <tagname>
這將簽署當(dāng)前HEAD
(但您也可以給它另一個參數(shù),指定要標(biāo)記的東西,例如,您可以使用標(biāo)記當(dāng)前mybranch
點git tag <tagname> mybranch
)。
通常,您只會為主要版本或類似的東西做簽名標(biāo)記,而輕量級標(biāo)記對于您想要執(zhí)行的任何標(biāo)記都很有用 - 無論何時您決定要記住某個特定點,只需為其創(chuàng)建一個專用標(biāo)記,并且在那個時候你有一個很好的符號名稱。
Git倉庫通常是完全自給自足和可重新定位的。例如,與CVS不同,“存儲庫”和“工作樹”沒有單獨的概念。Git倉庫通常是工作樹,本地Git信息隱藏在.git
子目錄中。沒有別的。你看到的是你得到的。
注意 | 您可以告訴Git將Git內(nèi)部信息從它跟蹤的目錄中分離出來,但現(xiàn)在我們將忽略它:這不是普通項目的工作方式,而只是用于特殊用途。因此,“Git信息始終與其描述的工作樹直接相關(guān)”的心智模型在技術(shù)上可能不是100%準(zhǔn)確的,但它對于所有正常使用來說都是一個很好的模型。 |
---|
這有兩個含義:
如果您對創(chuàng)建的教程庫感到厭倦(或者您犯了一個錯誤并想要重新開始),那么您可以執(zhí)行簡單的$ rm -rf git-tutorial
它會消失。沒有外部存儲庫,并且您創(chuàng)建的項目之外沒有任何歷史記錄。
如果你想移動或復(fù)制一個Git倉庫,你可以這樣做。有git clone
命令,但如果你想要做的只是創(chuàng)建一個你的倉庫的副本(附帶所有完整的歷史記錄),你可以用常規(guī)的方式來完成cp -a git-tutorial new-git-tutorial
。請注意,當(dāng)您移動或復(fù)制Git存儲庫時,您的Git索引文件(緩存各種信息,特別是所涉及文件的一些“統(tǒng)計”信息)可能需要刷新。所以在你cp -a
創(chuàng)建一個新副本之后,你會想要做$ git update-index --refresh
在新的存儲庫中確保索引文件是最新的。
請注意,第二點即使在機器上也是如此。您可以復(fù)制一個遠(yuǎn)程的Git倉庫與任何常規(guī)復(fù)制機制,是它scp
,rsync
或wget
。
在復(fù)制遠(yuǎn)程存儲庫時,您至少需要更新索引緩存,尤其是在其他人的存儲庫中,您通常希望確保索引緩存處于某種已知狀態(tài)(您不需要知道他們做了什么,且還沒有檢查),所以通常你會先于git update-index
用
$ git read-tree --reset HEAD $ git update-index --refresh
這將強制從指向的樹中重新構(gòu)建索引HEAD
。它將索引內(nèi)容重置為HEAD
,然后git update-index
確保將所有索引條目與檢出文件進(jìn)行匹配。如果原始存儲庫在其工作樹中有未提交的更改,則git update-index --refresh
通知它們并告訴您需要更新它們。
以上內(nèi)容也可以簡單寫成
$ git reset
實際上很多常見的Git命令組合都可以通過git xyz
接口編寫腳本。您可以通過查看各種git腳本所做的工作來了解情況。例如,git reset
用于實現(xiàn)在上述的兩行git reset
,但像一些git status
和git commit
稍微圍繞基本Git命令更復(fù)雜的腳本。
許多(大多數(shù)?)公共遠(yuǎn)程存儲庫不會包含任何檢出的文件或甚至索引文件,并且只包含實際的核心Git文件。這樣的存儲庫通常甚至沒有該.git
子目錄,但直接在存儲庫中包含所有Git文件。
要創(chuàng)建自己的“原始”Git存儲庫的本地活動副本,首先要為項目創(chuàng)建自己的子目錄,然后將原始存儲庫內(nèi)容復(fù)制到.git
目錄中。例如,要創(chuàng)建自己的Git存儲庫副本,您需要執(zhí)行以下操作
$ mkdir my-git $ cd my-git $ rsync -rL rsync://rsync.kernel.org/pub/scm/git/git.git/ .git
其次是
$ git read-tree HEAD
來填充索引。然而,現(xiàn)在你已經(jīng)填充了索引,并且你擁有所有的Git內(nèi)部文件,但是你會注意到你實際上沒有任何工作樹文件可以工作。為了得到這些,你會檢查出來
$ git checkout-index -u -a
其中-u
標(biāo)志意味著你要檢出,以保持指數(shù)最高最新(這樣你就不必事后刷新),-a
標(biāo)志的意思是“簽出的所有文件”(如果你有一個陳舊的副本或簽出樹的舊版本,你可能還需要添加-f
第一個標(biāo)志,告訴git checkout-index
來強制覆蓋任何舊文件)。
再次,這可以全部簡化
$ git clone git://git.kernel.org/pub/scm/git/git.git/ my-git $ cd my-git $ git checkout
這將最終為你做上述所有的事情。
您現(xiàn)在已經(jīng)成功復(fù)制了其他人的(我的)遠(yuǎn)程存儲庫,并將其簽出。
Git中的分支實際上只不過是從.git/refs/
子目錄中進(jìn)入Git對象數(shù)據(jù)庫的指針,正如我們已經(jīng)討論的那樣,HEAD
分支只不過是這些對象指針之一的符號鏈接。
您可以隨時通過在項目歷史記錄中選擇一個任意點來創(chuàng)建一個新分支,然后將該對象的SHA-1名稱寫入一個文件中.git/refs/heads/
。你可以使用你想要的任何文件名(實際上是子目錄),但是慣例是調(diào)用“普通”分支master
。盡管如此,這只是一個慣例,沒有什么可以強制它。
舉個例子,讓我們回到我們之前使用的git-tutorial存儲庫,并在其中創(chuàng)建一個分支。你只需說出你想要簽出一個新的分支就可以做到這一點:
$ git checkout -b mybranch
將在當(dāng)前HEAD
位置創(chuàng)建一個新的分支,并切換到它。
注意 | 如果您決定在歷史中的某個其他位置啟動新分支,而不是當(dāng)前的HEAD,那么您可以通過告訴git checkout檢出結(jié)果的基礎(chǔ)來做到這一點。換句話說,如果你有一個更早的標(biāo)簽或分支,你只需要做$ git checkout -b mybranch early-commit,它會在之前的提交中創(chuàng)建新的分支mybranch,然后檢查當(dāng)時的狀態(tài)。 |
---|
你可以隨時跳回原來的master
分支
$ git checkout master
(或者任何其他分支名稱),如果你忘記了你碰巧在哪個分支上,那么簡單
$ cat .git/HEAD
會告訴你它指向的地方。要獲得您擁有的分支名單,您可以說
$ git branch
它曾經(jīng)只是一個簡單的腳本而已ls .git/refs/heads
。目前分支前面會有一個星號。
有時您可能希望創(chuàng)建一個新的分支,without
實際檢查并切換到該分支。如果是這樣,只需使用該命令
$ git branch <branchname> [startingpoint]
這將簡單create
分支,但不會做任何進(jìn)一步的事情。然后,你可以稍后 - 一旦你決定要在該分支上進(jìn)行實際開發(fā),就可以git checkout
使用branchname作為參數(shù)切換到該分支。
擁有一個分支的想法之一是你做了一些(可能是實驗性的)工作,并最終將它合并回主分支。因此,假設(shè)您創(chuàng)建了mybranch
與原始master
分支相同的上述內(nèi)容,那么讓我們確保我們在該分支中,并在那里做一些工作。
$ git checkout mybranch $ echo "Work, work, work" >>hello $ git commit -m "Some work." -i hello
在這里,我們只是添加了另一行hello
,并且我們使用了一個簡寫來完成這兩個操作,git update-index hello
并git commit
通過直接給文件名直接git commit
添加一個-i
標(biāo)志(include
除了索引文件到目前為止您做了什么之外,它還告訴Git 該文件)提交)。該-m
標(biāo)志是從命令行提供提交日志消息。
現(xiàn)在,為了讓它更有趣一些,讓我們假設(shè)別人在原始分支中做了一些工作,并通過回到主分支來模擬它,并在那里編輯相同的文件:
$ git checkout master
在這里,花一些時間看看內(nèi)容hello
,注意它們不包含我們剛才所做的工作mybranch
- 因為這項工作在master
分支中根本沒有發(fā)生。然后做
$ echo "Play, play, play" >>hello $ echo "Lots of fun" >>example $ git commit -m "Some fun." -i hello example
因為主分支顯然心情更好。
現(xiàn)在,你有兩個分支,并且你決定要合并完成的工作。在我們這樣做之前,讓我們介紹一個很酷的圖形工具,幫助您查看正在發(fā)生的事情:
$ gitk --all
會以圖形方式向你展示你的兩個分支(這--all
就是說:通常它會向你顯示你當(dāng)前的HEAD
)和他們的歷史。你也可以看到他們是如何來自一個共同的來源。
無論如何,讓我們退出gitk
(^Q
或文件菜單),并決定我們要將我們在mybranch
分支上所做的工作合并到master
分支中(這也是我們的工作HEAD
)。要做到這一點,有一個很好的腳本調(diào)用git merge
,它想知道你想要解決哪些分支以及合并的內(nèi)容:
$ git merge -m "Merge work in mybranch" mybranch
如果可以自動解析合并,則第一個參數(shù)將用作提交消息。
現(xiàn)在,在這種情況下,我們故意創(chuàng)建了需要手動修正合并的情況,因此,Git會自動完成它的功能(在這種情況下,它只是合并example
文件,在mybranch
分支中沒有差異),并說:
Auto-merging hello CONFLICT (content): Merge conflict in hello Automatic merge failed; fix conflicts and then commit the result.
它告訴你它做了一個“自動合并”,由于hello
沖突導(dǎo)致失敗。
不用擔(dān)心。如果你曾經(jīng)使用過CVS,那么它就會以相同的形式在hello
留下(小小的)沖突,所以讓我們在我們的編輯器中打開hello
(不管怎么說),然后以某種方式修復(fù)它。我建議讓hello
包含所有四行:
Hello World It's a new day for git Play, play, play Work, work, work
一旦你對你的手動合并感到滿意,就執(zhí)行
$ git commit -i hello
這會非常大聲地警告你,你現(xiàn)在正在進(jìn)行合并(這是正確的,所以不要介意),并且你可以寫一個關(guān)于你的冒險的小型合并信息git merge
。
完成后,啟動gitk --all
以圖形方式查看歷史記錄的樣子。注意,mybranch
仍然存在,你可以切換到它,并繼續(xù)使用它,如果你想。該mybranch
分支不包含合并,但下一次您從master
分支中合并它時,Git會知道您如何合并它,因此您不必再次合并。
另一個有用的工具,特別是如果你不總是在X-Window環(huán)境下工作,是git show-branch
。
$ git show-branch --topo-order --more=1 master mybranch* [master] Merge work in mybranch ! [mybranch] Some work.--- [master] Merge work in mybranch*+ [mybranch] Some work.* [master^] Some fun.
前兩行表示它顯示了兩個分支,它們的樹頂部提交的標(biāo)題,您當(dāng)前在master
分支上(注意星號*
字符),后面的輸出行的第一列用于顯示master
分支中包含的提交以及分支的第二列mybranch
。顯示了三個提交以及他們的標(biāo)題。它們都在第一列中有空白字符(*
顯示當(dāng)前分支上的普通提交,-
是合并提交),這意味著它們現(xiàn)在是master
分支的一部分。只有“某些工作”提交+
在第二列中具有加號字符,因為mybranch
尚未合并到主分支的這些提交中。提交日志消息之前的括號內(nèi)的字符串是一個短名稱,可用于命名提交。在上面的例子中,master
并且mybranch
是分支頭。master^
是master
分支頭的第一位家長。如果您想查看更復(fù)雜的案例,請參閱gitrevisions[7]。
注意 | 如果沒有--more = 1選項,git show-branch將不會輸出master ^ commit,因為mybranch提交是master和mybranch提示的共同祖先。有關(guān)詳細(xì)信息,請參閱git-show-branch1。 |
---|
注意 | 如果合并后主分支上有更多的提交,默認(rèn)情況下合并提交本身不會由git show-branch顯示。在這種情況下,您需要提供--sparse選項以使合并提交可見。 |
---|
現(xiàn)在,讓我們假裝你是一個完成所有工作的人mybranch
,并且你辛勤工作的成果最終被合并到master
分支中。讓我們回到mybranch
,并運行git merge
以獲取“上游變更”回到您的分支。
$ git checkout mybranch $ git merge -m "Merge upstream changes." master
這會輸出這樣的內(nèi)容(實際的提交對象名稱會有所不同)
Updating from ae3a2da... to a80b4aa....Fast-forward (no commit created; -m option ignored) example | 1 + hello | 1 + 2 files changed, 2 insertions(+)
因為你的分支沒有包含任何已經(jīng)合并到master
分支中的東西,合并操作實際上并沒有進(jìn)行合并。相反,它只是將分支樹的頂部更新為分支樹的頂部master
。這通常被稱為fast-forward
合并。
您可以再次運行gitk --all
以查看commit ancestry的外觀,或者運行show-branch
,這可以告訴您這一點。
$ git show-branch master mybranch! [master] Merge work in mybranch * [mybranch] Merge work in mybranch---- [master] Merge work in mybranch
通常情況下,與其他人合并比合并你自己的分支要普遍得多,所以值得指出的是,Git也使得它非常簡單,事實上,與做一個沒有什么不同git merge
。事實上,遠(yuǎn)程合并最終不過是“將工作從遠(yuǎn)程存儲庫獲取到臨時標(biāo)記中”,然后是一個git merge
。
不出所料,從遠(yuǎn)程存儲庫中獲取git fetch
:
$ git fetch <remote-repository>
可以使用下列其中一個傳輸命令來從以下位置下載存儲庫:
SSH
remote.machine:/path/to/repo.git/
or
ssh://remote.machine/path/to/repo.git/
此傳輸可用于上傳和下載,并要求您擁有ssh
遠(yuǎn)程計算機的登錄權(quán)限。它通過交換兩端提交的頭部提交并轉(zhuǎn)移(接近)最小的一組對象來找出對方缺乏的對象集合。這是在庫之間交換Git對象的最有效方式。
本地目錄
/path/to/repo.git/
此傳輸與SSH傳輸相同,但用于sh
在本地計算機上運行兩端,而不是在遠(yuǎn)程計算機上運行另一端ssh
。
Git Native
git://remote.machine/path/to/repo.git/
此傳輸設(shè)計用于匿名下載。與SSH傳輸一樣,它可以找到下游端缺少的對象集合,并將其轉(zhuǎn)移(接近)最小的一組對象。
HTTP(S)
http://remote.machine/path/to/repo.git/
Downloader從http和https URL首先通過查看repo.git/refs/
目錄下指定的refname從遠(yuǎn)程站點獲取最高的提交對象名稱,然后嘗試通過repo.git/objects/xx/xxx...
使用該提交對象的對象名稱進(jìn)行下載來獲取提交對象。然后它讀取提交對象以找出其父提交和關(guān)聯(lián)樹對象;它會重復(fù)這個過程,直到它獲得所有必需的對象。由于這種行為,他們有時也被稱為commit walkers
。
commit walkers
有時也被稱為dumb transports
,因為它們不需要任何的Git知道智能服務(wù)器如Git機傳輸一樣。任何股票甚至不支持目錄索引的HTTP服務(wù)器就足夠了。但是您必須準(zhǔn)備好您的存儲庫git update-server-info
來幫助傳輸下載者。
一旦你從遠(yuǎn)程倉庫獲取,merge
當(dāng)前分支。
然而 - fetch
是一件很常見的事情,然后立即merge
就被調(diào)用git pull
,你可以簡單地做
$ git pull <remote-repository>
并且可選地給遠(yuǎn)端的分支名稱作為第二個參數(shù)。
注意 | 根本不需要使用任何分支,通過保留盡可能多的本地存儲庫,因為您希望擁有分支,并使用git pull合并它們,就像在分支之間合并一樣。這種方法的優(yōu)點是,它可以讓你為每個分支保存一組文件,并且如果你同時處理多行開發(fā),你可能會發(fā)現(xiàn)來回切換更容易。當(dāng)然,您將支付更多磁盤使用的代價來保存多個工作樹,但是現(xiàn)在磁盤空間很便宜。 |
---|
您可能會不時從同一個遠(yuǎn)程存儲庫中獲取數(shù)據(jù)。簡而言之,您可以將遠(yuǎn)程存儲庫URL存儲在本地存儲庫的配置文件中,如下所示:
$ git config remote.linus.url http://www.kernel.org/pub/scm/git/git.git/
并使用“l(fā)inus”關(guān)鍵字git pull
而不是完整的URL。
示例:
git pull linus
git pull linus tag v0.99.1
以上相當(dāng)于:
git pull http://www.kernel.org/pub/scm/git/git.git/ HEAD
git pull http://www.kernel.org/pub/scm/git/git.git/ tag v0.99.1
我們說這個教程展示了什么管道可以幫助你應(yīng)對不沖水的瓷器,但我們迄今沒有談到合并的真正效果。如果您是第一次使用本教程,我會建議跳至“發(fā)布您的作品”部分,稍后再回來。
好,還跟得上嗎?為了讓我們看一個示例,讓我們回到先前的存儲庫,帶有“hello”和“example”文件,并讓我們回到預(yù)合并狀態(tài):
$ git show-branch --more=2 master mybranch! [master] Merge work in mybranch * [mybranch] Merge work in mybranch---- [master] Merge work in mybranch+* [master^2] Some work.+* [master^] Some fun.
請記住,在運行git merge
之前,我們的master
head在“享受些樂趣”。承諾,而我們的mybranch
head在“做些工作”。commit.
$ git checkout mybranch $ git reset --hard master^2$ git checkout master $ git reset --hard master^
倒回后,提交結(jié)構(gòu)應(yīng)如下所示:
$ git show-branch* [master] Some fun. ! [mybranch] Some work.--* [master] Some fun. + [mybranch] Some work.*+ [master^] Initial commit
現(xiàn)在我們已經(jīng)準(zhǔn)備好嘗試手動合并了。
git merge
命令,當(dāng)合并兩個分支時,使用3-way合并算法。首先,它找到它們之間的共同祖先。它使用的命令是git merge-base
:
$ mb=$(git merge-base HEAD mybranch)
該命令將公共祖先的提交對象名稱寫入標(biāo)準(zhǔn)輸出,因此我們將其輸出捕獲到一個變量中,因為我們將在下一步中使用它。順便說一下,在這種情況下,共同的祖先提交是“初始提交”提交。你可以告訴它:
$ git name-rev --name-only --tags $mb my-first-tag
找到一個共同的祖先提交后,第二步是這樣的:
$ git read-tree -m -u $mb HEAD mybranch
這與git read-tree
我們已經(jīng)看到的命令是一樣的,但是與以前的例子不同,它需要三棵樹。這將每棵樹的內(nèi)容讀入stage
索引文件中的不同內(nèi)容(第一棵樹進(jìn)入第一階段,第二棵樹進(jìn)入第二階段,等等)。在將三棵樹讀入三個階段之后,所有三個階段中相同的路徑都collapsed
進(jìn)入階段0.在三個階段中的兩個階段中相同的路徑折疊到階段0,從階段2獲取SHA-1或者階段3,與第一階段不同(即只有一側(cè)從共同祖先改變)。
collapsing
操作之后,三棵樹中不同的路徑將保留在非零階段。此時,您可以使用以下命令檢查索引文件:
$ git ls-files --stage100644 7f8b141b65fdcee47321e399a2598a235a032422 0 example100644 557db03de997c86a4a028e1ebd3a1ceb225be238 1 hello100644 ba42a2a96e3027f3333e13ede4ccf4498c3ae942 2 hello100644 cc44c73eb783565da5831b4d820c962954019b69 3 hello
在我們僅有兩個文件的示例中,我們沒有沒有更改的文件,因此只能example
導(dǎo)致崩潰。但是在現(xiàn)實生活中的大型項目中,當(dāng)一次提交中只有少量文件發(fā)生變化時,這collapsing
往往會使相當(dāng)快速的大部分路徑輕松合并,從而在非零階段只發(fā)生少量實際變化。
要僅查看非零階段,請使用--unmerged
標(biāo)志:
$ git ls-files --unmerged100644 557db03de997c86a4a028e1ebd3a1ceb225be238 1 hello100644 ba42a2a96e3027f3333e13ede4ccf4498c3ae942 2 hello100644 cc44c73eb783565da5831b4d820c962954019b69 3 hello
合并的下一步是合并這三個版本的文件,使用3-way合并。這是通過將git merge-one-file
命令作為命令的參數(shù)之一來完成的git merge-index
:
$ git merge-index git-merge-one-file hello Auto-merging hello ERROR: Merge conflict in hello fatal: merge program failed
git merge-one-file
用參數(shù)調(diào)用腳本來描述這三個版本,并負(fù)責(zé)將合并結(jié)果留在工作樹中。這是一個相當(dāng)簡單的shell腳本,最終調(diào)用merge
RCS套件中的程序執(zhí)行文件級3路合并。在這種情況下,merge
檢測到?jīng)_突,并將帶有沖突標(biāo)記的合并結(jié)果留在工作樹中。如果ls-files --stage
在此時再次運行,可以看到這一點:
$ git ls-files --stage100644 7f8b141b65fdcee47321e399a2598a235a032422 0 example100644 557db03de997c86a4a028e1ebd3a1ceb225be238 1 hello100644 ba42a2a96e3027f3333e13ede4ccf4498c3ae942 2 hello100644 cc44c73eb783565da5831b4d820c962954019b69 3 hello
這是索引文件的狀態(tài),并且工作文件git merge
將控制權(quán)返回給您,并將沖突合并留給您解決。請注意,路徑hello
尚未被合并,此時您看到的git diff
是自第2階段(即您的版本)以來的差異。
所以,我們可以使用別人的作品從遠(yuǎn)程倉庫,但你如何能準(zhǔn)備一個資料庫,讓其他人撤出?
您在工作樹中執(zhí)行真正的工作,并將主存儲庫掛在其.git
子目錄下。您可以遠(yuǎn)程訪問該存儲庫,并要求人們從中獲取信息,但實際上這并不是通常的做法。推薦的方法是擁有一個公共存儲庫,讓其他人可以訪問該存儲庫,并且當(dāng)您在主工作樹中所做的更改狀態(tài)良好時,請從中更新公共存儲庫。這通常被稱為pushing
。
注意 | 這個公共存儲庫可以進(jìn)一步被鏡像,這就是kernel.org上的Git存儲庫的管理方式。 |
---|
將本地(專用)存儲庫中的更改發(fā)布到遠(yuǎn)程(公用)存儲庫需要在遠(yuǎn)程計算機上具有寫權(quán)限。您需要有一個SSH帳戶才能運行單個命令,git-receive-pack
。
首先,您需要在將存放公共存儲庫的遠(yuǎn)程機器上創(chuàng)建一個空的存儲庫。這個空的存儲庫將被填充并隨后推入,以保持最新狀態(tài)。顯然,這個存儲庫的創(chuàng)建只需要完成一次。
注意 | git push使用一對命令,本地機器上的git send-pack和遠(yuǎn)程機器上的git-receive-pack。兩者之間的通信通過網(wǎng)絡(luò)在內(nèi)部使用SSH連接。 |
---|
您的私有存儲庫的Git目錄通常是.git
,但您的公共存儲庫通常以項目名稱命名,即<project>.git
。讓我們?yōu)轫椖縿?chuàng)建一個這樣的公共存儲庫my-git
。登錄到遠(yuǎn)程機器后,創(chuàng)建一個空目錄:
$ mkdir my-git.git
然后,通過運行將該目錄設(shè)置為Git存儲庫git init
,但這次由于其名稱.git
并不常見,所以我們的做法略有不同:
$ GIT_DIR=my-git.git git init
確保此目錄可用于您希望通過您選擇的交通工具提取您的更改的其他人。你也需要確保你在$PATH
有這個git-receive-pack
程序。
注意 | 當(dāng)你直接運行程序時,許多sshd安裝不會將你的shell作為登錄shell調(diào)用; 這意味著如果您的登錄shell是bash,則只讀取.bashrc而不是.bash_profile。作為一種解決方法,確保.bashrc設(shè)置$ PATH,以便您可以運行g(shù)it-receive-pack程序。 |
---|
注意 | 如果你打算發(fā)布這個通過http訪問的倉庫,你應(yīng)該在這個時候執(zhí)行mv my-git.git/hooks/post-update.sample my-git.git/hooks/post-update。這可以確保每次你進(jìn)入這個倉庫時,都會運行g(shù)it update-server-info。 |
---|
您的“公共存儲庫”現(xiàn)在已準(zhǔn)備好接受您的更改。回到你有你的私人存儲庫的機器。從那里運行這個命令:
$ git push <public-host>:/path/to/my-git.git master
這會使您的公共存儲庫與master
您的當(dāng)前存儲庫中與指定的分支頭(即本例中)和它們可訪問的對象相匹配。
作為一個真實的例子,這是我更新公共Git存儲庫的方式。Kernel.org鏡像網(wǎng)絡(luò)負(fù)責(zé)傳播給其他公開可見的機器:
$ git push master.kernel.org:/pub/scm/git/git.git/
之前,我們看到.git/objects/??/
為目錄創(chuàng)建的每個Git對象都存儲目錄下的一個文件。這種表示對于原子級和安全地創(chuàng)建是有效的,但在網(wǎng)絡(luò)上傳輸并不方便。由于Git對象一旦創(chuàng)建就不可變,因此可以通過“將它們組合在一起”來優(yōu)化存儲。命令
$ git repack
會為你完成。如果你按照教程的例子,你現(xiàn)在已經(jīng)在.git/objects/??/
目錄中累積了約17個對象。git repack
告訴您打包了多少個對象,并將打包文件存儲在.git/objects/pack
目錄中。
注意 | 您將在.git/objects/pack目錄中看到兩個文件,即pack - *。pack和pack - *。idx。它們彼此密切相關(guān),如果您出于任何原因手動將它們復(fù)制到不同的存儲庫,則應(yīng)確保將它們復(fù)制在一起。前者保存包中對象的所有數(shù)據(jù),后者保存隨機訪問的索引。 |
---|
如果你偏執(zhí),運行git verify-pack
命令會檢測你是否有腐敗的包裝,但不要太擔(dān)心。我們的項目總是完美的;-)。
一旦你打包了對象,你就不需要保留包文件中包含的解壓對象了。
$ git prune-packed
會為你刪除它們。
如果您好奇,您可以在跑步find .git/objects -type f
前后嘗試跑步git prune-packed
。此外git count-objects
還會告訴您存儲庫中有多少個未打包對象以及它們占用了多少空間。
注意 | 對于HTTP傳輸來說,git pull稍微麻煩,因為打包的存儲庫可能包含相對較少包中的相對較少的對象。如果你期望從你的公共倉庫獲取很多HTTP請求,你可能需要經(jīng)常重新打包和修剪,或者永遠(yuǎn)不要修剪。 |
---|
如果此時再次運行git repack
,則會顯示“沒有新包裝”。一旦繼續(xù)開發(fā)并累積更改,git repack
再次運行將創(chuàng)建一個新包,其中包含自上次打包存儲庫后創(chuàng)建的對象。我們建議您在初次導(dǎo)入后盡快打包項目(除非您從頭開始項目),然后git repack
每隔一段時間運行一次,具體取決于項目的活躍程度。
當(dāng)一個儲存庫是通過同步git push
和git pull
填充在源存儲庫對象通常存儲在目的地解壓。雖然這允許您在兩端使用不同的打包策略,但這也意味著您可能需要每隔一段時間重新打包兩個存儲庫。
盡管Git是一個真正的分布式系統(tǒng),但通過非正式的開發(fā)人員層次來組織項目通常很方便。Linux內(nèi)核開發(fā)就是這樣運行的。在Randy Dunlap的演講中有一個很好的例子(第17頁,“合并到Mainline”)。
應(yīng)該強調(diào)的是,這個層次純粹是非正式的。在Git中沒有任何基礎(chǔ)強制執(zhí)行這個層次結(jié)構(gòu)所暗示的“補丁流程鏈”。您不必僅從一個遠(yuǎn)程存儲庫中獲取數(shù)據(jù)。
“項目領(lǐng)導(dǎo)”的推薦工作流程如下所示:
在本地計算機上準(zhǔn)備主要存儲庫。你的工作在那里完成。
準(zhǔn)備一個可供他人訪問的公共存儲庫。如果其他人通過啞傳輸協(xié)議(HTTP)從您的存儲庫中提取數(shù)據(jù),則需要保留此存儲庫dumb transport friendly
。之后git init
,$GIT_DIR/hooks/post-update.sample
從標(biāo)準(zhǔn)模板復(fù)制將包含呼叫,git update-server-info
但您需要手動啟用掛鉤mv post-update.sample post-update
。這確保git update-server-info
了必要的文件保持最新。
從主存儲庫推入公共存儲庫。
git repack
公共存儲庫。這會建立一個包含初始對象集合作為基準(zhǔn)的大包,并且可能git prune
用于從存儲庫中提取的傳輸支持打包存儲庫。
繼續(xù)在主存儲庫中工作。您的更改包括修改您自己的修改,通過電子郵件收到的修補程序以及從您的“子系統(tǒng)維護(hù)者”的“公共”存儲庫中拉取合并。只要你愿意,你可以重新包裝這個私人存儲庫。
將更改推送到公共存儲庫,并向公眾發(fā)布。
每過一段時間,git repack
公共存儲庫。返回步驟5.繼續(xù)工作。
為該項目工作并擁有自己的“公共存儲庫”的“子系統(tǒng)維護(hù)人員”推薦的工作周期如下所示:
通過git clone
在“項目負(fù)責(zé)人”的公共存儲庫上運行,準(zhǔn)備工作存儲庫。用于初始克隆的URL存儲在remote.origin.url配置變量中。
準(zhǔn)備一個可供他人訪問的公共存儲庫,就像“項目負(fù)責(zé)人”一樣。
將“項目負(fù)責(zé)人”公共存儲庫中的打包文件復(fù)制到公共存儲庫,除非“項目負(fù)責(zé)人”存儲庫與您的計算機位于同一臺計算機上。在后一種情況下,您可以使用objects/info/alternates
文件指向您從中借用的存儲庫。
從主存儲庫推入公共存儲庫。運行git repack
,并且可能git prune
用于從存儲庫中提取的傳輸支持打包的存儲庫。
繼續(xù)在主存儲庫中工作。您的更改包括修改您自己的修改,通過電子郵件收到的修補程序,以及拉動“項目負(fù)責(zé)人”的“公共”存儲庫和可能的“子子系統(tǒng)維護(hù)人員”所產(chǎn)生的合并。只要你愿意,你可以重新包裝這個私人存儲庫。
將更改推送到公共存儲庫,并請求您的“項目負(fù)責(zé)人”和可能的“子子系統(tǒng)維護(hù)人員”從中抽取。
每過一段時間,git repack
公共存儲庫。返回步驟5.繼續(xù)工作。
沒有“公共”存儲庫的“個人開發(fā)人員”的建議工作周期稍有不同。它是這樣的:
通過git clone
“項目負(fù)責(zé)人”(或“子系統(tǒng)維護(hù)人員”,如果您在子系統(tǒng)上工作)的公共存儲庫準(zhǔn)備工作存儲庫。用于初始克隆的URL存儲在remote.origin.url配置變量中。
在master
分支機構(gòu)的倉庫中工作。
git fetch origin
每隔一段時間從上游的公共存儲庫運行。這只有前半部分,git pull
但不合并。公共存儲庫的頭部存儲在.git/refs/remotes/origin/master
。
使用git cherry origin
查看哪些補丁那些被接受,和/或使用git rebase origin
端口的未合并的變化著更新的上游。
使用git format-patch origin
準(zhǔn)備用于電子郵件提交補丁,你的上游,并發(fā)送出去。返回第2步并繼續(xù)。
如果您來自CVS背景,上一節(jié)中提出的合作風(fēng)格對您來說可能是新的。你不必?fù)?dān)心。Git支持您可能更熟悉的“共享公共存儲庫”合作風(fēng)格。
有關(guān)詳細(xì)信息,請參閱gitcvs-migration[7]。
您可能一次只能處理一件以上的事情。使用Git分支來管理那些或多或少的獨立任務(wù)是很容易的。
我們已經(jīng)看到了分支機構(gòu)以前的工作方式,以兩個分支機構(gòu)的“樂趣和工作”為例。如果有兩個以上的分支,這個想法是一樣的。假設(shè)你從“主”頭開始,并在“主”分支中有一些新代碼,并在“提交 - 修復(fù)”和“差異修復(fù)”分支中有兩個獨立修復(fù):
$ git show-branch! [commit-fix] Fix commit message normalization. ! [diff-fix] Fix rename detection. * [master] Release candidate #1--- + [diff-fix] Fix rename detection. + [diff-fix~1] Better common substring algorithm.+ [commit-fix] Fix commit message normalization. * [master] Release candidate #1++* [diff-fix~2] Pretty-print messages.
這兩個修補程序都經(jīng)過了很好的測試,在這一點上,您想要在它們兩個中進(jìn)行合并。你可以先合并diff-fix
然后再合并commit-fix
,如下所示:
$ git merge -m "Merge fix in diff-fix" diff-fix $ git merge -m "Merge fix in commit-fix" commit-fix
這將導(dǎo)致:
$ git show-branch! [commit-fix] Fix commit message normalization. ! [diff-fix] Fix rename detection. * [master] Merge fix in commit-fix--- - [master] Merge fix in commit-fix+ * [commit-fix] Fix commit message normalization. - [master~1] Merge fix in diff-fix +* [diff-fix] Fix rename detection. +* [diff-fix~1] Better common substring algorithm. * [master~2] Release candidate #1++* [master~3] Pretty-print messages.
但是,沒有什么特別的理由要先合并一個分支,然后再合并,當(dāng)你有一系列真正獨立的變化時(如果順序重要,那么它們就不是定義上的獨立)。您可以將這兩個分支同時合并到當(dāng)前分支中。首先讓我們撤銷我們剛剛做的并重新開始。我們希望在這兩次合并之前將主分支重置為master~2
:
$ git reset --hard master~2
你可以確保git show-branch
在git merge
你剛剛做的那兩個之前匹配狀態(tài)。然后,不是連續(xù)運行兩個git merge
命令,而是合并這兩個分支頭(這被稱為making an Octopus
):
$ git merge commit-fix diff-fix $ git show-branch! [commit-fix] Fix commit message normalization. ! [diff-fix] Fix rename detection. * [master] Octopus merge of branches 'diff-fix' and 'commit-fix'--- - [master] Octopus merge of branches 'diff-fix' and 'commit-fix'+ * [commit-fix] Fix commit message normalization. +* [diff-fix] Fix rename detection. +* [diff-fix~1] Better common substring algorithm. * [master~1] Release candidate #1++* [master~2] Pretty-print messages.
請注意,你不應(yīng)該因為你可以做Octopus。如果要同時合并兩個以上的獨立更改,章魚是一件有效的事情,并且通常可以更容易地查看提交歷史記錄。但是,如果您與正在合并且需要手工解決的任何分支合并沖突,則表示發(fā)生在這些分支中的發(fā)展畢竟不是獨立的,并且您應(yīng)該一次合并兩個分支,記錄如何你解決了沖突,以及你偏好一方的變化。否則,它會使項目的歷史難以跟上,并不容易。
gittutorial[7], gittutorial-2[7], gitcvs-migration[7], git-help[1], giteveryday[7], The Git User’s Manual