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