git建立一個自己的本地倉庫
如果我們要把一個專案加入到Git的版本管理中,可以在專案所在的目錄用git init命令建立一個空的本地倉庫,然後再用git add命令把它們都加入到Git本地倉庫的暫存區(stage or index)中,最後再用git commit命令提交到本地倉庫裡。
建立一個新的專案目錄,並生成一些簡單的檔案內容:
$ mkdir test_proj $ cd test_proj $ echo “hello,world” > readme.txt
在專案目錄建立新的本地倉庫,並把專案裡的所有檔案全部新增、提交到本地倉庫中去:
$ git init #在當前的目錄下建立一個新的空的本地倉庫 Initialized empty Git repository in /home/user/test_proj/.git/ $ git add . #把前目錄下的所有檔案全部新增到暫存區 $ git commit -m 'project init' #建立提交 [master (root-commit) b36a785] project init 1 files changed, 1 insertions(+), 0 deletions(-) create mode 100644 readme.txt
Git目錄的結構
git init命令在專案的頂層目錄中建了一個名為:“.git”的目錄,它的別名是 “Git目錄”(Git directory)。這時”Git目錄”中雖然有一些檔案,但是沒有任何提交(commit)在裡面,所以我們叫它是空倉庫(empty Git repository)。
和 SVN不同,一個Git專案一般只在專案的根目錄下建一個“.git”目錄,而SVN則會在專案的每一個目錄下建一個”.svn”目錄;這也我喜歡Git的原因之一:)
Git把所有的歷史提交資訊全部儲存在“Git目錄”裡,它就是一個Git專案的倉庫;你對本地的原始碼進行編輯修改後建立的提交也都會先儲存在這裡面,然後再推送到遠端的伺服器。當我們我把專案目錄和“Git目錄”一起拷到其它電腦裡,它能馬上正常的工作(所有的提交資訊全都儲存在Git目錄裡);甚至可以只把“Git目錄”拷走也行,但是要再簽出(checkout)一次。
Git為了 除錯的方便,它可以指定專案的Git目錄的位置。有兩種辦法:一是設定“GIT_DIR”環境變數,二是在命令列裡設定“--git-dir--git-dir”引數指定它的位置,大家可以看一下這裡(git(1) Manual Page)。
庖丁解牛
前面的這些東東我在第一篇裡也大概的講過一些,但是今天我們想不但要開動這輛叫“Git”的跑車,還想看看它裡面有些什麼樣的零件,是怎麼構成的。
OK,我們來看看“test_proj”專案裡的“Git目錄”的結構:
$cd test_proj/.git $ ls | more branches/ # 新版的Git已經不再使用這個目錄,所以大家看到它 #一般會是空的 COMMIT_EDITMSG # 儲存著上一次提交時的註釋資訊 config # 專案的配置資訊 description # 專案的描述資訊 HEAD # 專案當前在哪個分支的資訊 hooks/ # 預設的“hooks” 指令碼檔案 index # 索引檔案,git add 後把要新增的項暫存到這裡 info/ # 裡面有一個exclude檔案,指定本專案要忽略的檔案 #,看一下這裡 logs/ # 各個refs的歷史資訊 objects/ # 這個目錄非常重要,裡面儲存都是Git的資料物件 # 包括:提交(commits), 樹物件(trees),二進位制物件 #(blobs),標籤物件(tags)。 #不明白沒有關係,後面會講的。 refs/ # 標識著你的每個分支指向哪個提交(commit)。
我先用git log命令來看一下這個Git專案裡有哪些提交:
$ git log commit 58b53cfe12a9625865159b6fcf2738b2f6774844 Author: liuhui998 <[email protected]> Date: Sat Feb 19 18:10:08 2011 +0800 project init
大家可以看到目前只有一個提交(commit)物件,而它的名字就是:”58b53cfe12a9625865159b6fcf2738b2f6774844”。這個名字就是物件內容的一個SHA簽名串值,只要物件裡面的內容不同,那麼我們就可以認為物件的名字不會相同,反之也成立。我在使用時一般不用把這個40個字元輸全,只要把前面的5~8個字元輸完就可以(前提是和其它的物件名不衝突)。為了方便表示,在不影響表達的情況下,我會只寫SHA串值的前6個字元。
我們可以用git cat-file來看一下這個提交裡的內容是什麼:
$ git cat-file -p 58b53c tree 2bb9f0c9dc5caa1fb10f9e0ccbb3a7003c8a0e13 author liuhui998 <[email protected]> 1298110208 +0800 committer liuhui998 <[email protected]> 1298110208 +0800 project init
大家可以看到:提交“58b53c” 是引用一個名為“2bb9f0”的樹物件(tree)。一個樹物件(tree)可以引用一個或多個二進位制物件(blob), 每個二進位制物件都對應一個檔案。 更進一步, 樹物件也可以引用其他的樹物件,從而構成一個目錄層次結構。我們再看一下這個樹物件(tree)裡面有什麼東東:
$ git cat-file -p 2bb9f0
100644 blob 2d832d9044c698081e59c322d5a2a459da546469 readme.txt
不難看出,2bb9f0”這個樹物件(tree)包括了了一個二進位制物件(blob),對應於我們在前面建立的那個叫 ”readme.txt”的檔案。現在我們來看看這個”blob”裡的資料是不是和前面的提交的內容一致:
$ git cat-file -p 2d832d hello,world
哈哈,熟悉的“hello,world”又回來了。
想不想看看提交物件、樹物件和二進位制物件是怎麼在”Git目錄“中儲存的;沒有問題,執行下面的命令,看看”.git/objects”目錄裡的內容:
$ find .git/objects .git/objects .git/objects/2b .git/objects/2b/b9f0c9dc5caa1fb10f9e0ccbb3a7003c8a0e13 .git/objects/2d .git/objects/2d/832d9044c698081e59c322d5a2a459da546469 .git/objects/58 .git/objects/58/b53cfe12a9625865159b6fcf2738b2f6774844 .git/objects/info .git/objects/pack
如果大家仔細看上面命令執行結果中的粗體字,所有的物件都使用SHA簽名串值作為索引儲存在”.git/objects”目錄之下;SHA串的前兩個字元作為目錄名,後面的38個字元作為檔名。
這些檔案的內容其實是壓縮的資料外加一個標註型別和長度的頭。型別可以是提交物件(commit)、二進位制物件(blob)、 樹物件(tree)或者標籤物件(tag)。
如何clone一個遠端專案
我身邊的很多朋友是因為要得到某個開源專案的程式碼,所以才開始學習使用Git。而獲取一個專案的程式碼的一般的做法就是用git clone命令進行直接複製。
例如,有些朋友可能想看一下最新的linux核心原始碼,當我們開啟它的網站時,發現有如下面的一段提示:
URL git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux-2.6.git http://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux-2.6.git https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux-2.6.git
URL下面的三行字串表示三個地址,我們可以通過這三個地址得到同樣的一份Linux核心原始碼。
也就是說下面這三條命令最終得到的是同一份原始碼:
git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux-2.6.git git clone http://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux-2.6.git git cone https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux-2.6.git
我們先來看一下URL,git://、http://、https://這些代表是傳輸git倉庫的協議形式,而“git.kernel.org“則代表了Git倉庫儲存的伺服器名字(域名),“/pub/scm/linux/kernel/git/torvalds/linux-2.6.git” 則代表了Git倉庫在伺服器上位置。
Git 倉庫除了可以通過上面的git、http、https協議傳輸外還可以通過ssh、ftp(s)、rsync等協議來傳輸。git clone的本質就是把“Git目錄”裡面的內容拷貝過來,大家想想看,一般的“Git目錄”裡有成千上萬的各種物件(提交物件,樹物件,二進位制物件......),如果逐一複製的話,其效率就可想而知。
如果通過git、ssh協議傳輸,伺服器端會在傳輸前把需要傳輸的各種物件先打好包再進行傳輸;而http(s)協議則會反覆請求要傳輸的不同物件。如果倉庫裡面的提交不多的話,前者和後者的效率相差不多;但是若倉庫裡有很多提交的話,git、ssh協議進行傳輸則會更有效率。
不過現在Git對http(s)協議傳輸Git倉庫做了一定的優化,http(s)傳輸現在也能達到ssh協議的效率,有興趣的朋友可以看一下這裡(Smart HTTP Transport)。
好的,現在我們執行了下面這條命令,把linux-2.6的最新版原始碼clone下來:
$cd ~/ $mkdir temp $git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux-2.6.git Initialized empty Git repository in /home/liuhui/temp/linux-2.6/.git/ remote: Counting objects: 1889189, done. remote: Compressing objects: 100% (303141/303141), done. Receiving objects: 100% (1889189/1889189), 385.03 MiB | 1.64 MiB/s, done. remote: Total 1889189 (delta 1570491), reused 1887756 (delta 1569178) Resolving deltas: 100% (1570491/1570491), done. Checking out files: 100% (35867/35867), done.
當我們執行了“git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux-2.6.git”這條命令後大家可以看到這條輸出:
Initialized empty Git repository in /home/user/temp/linux-2.6/.git/
這就是意味著我們在本地先建了一個“linux-2.6”目錄,然後在這個目錄建了一個空的Git本地倉庫(Git目錄);裡面將會儲存從網上拉下來的歷史提交。
下面兩條輸入代表伺服器現在呼叫 git-pack-objects 對它的倉庫進行打包和壓縮:
remote: Counting objects: 1888686, done. remote: Compressing objects: 100% (302932/302932), done.
然後客戶端接收伺服器端發過送過來的資料:
Receiving objects: 100% (1889189/1889189), 385.03 MiB | 1.64 MiB/s, done.
在我們執行完上面的clone linux-2.6程式碼的的操作後,Git會從“Git目錄”裡把最新的程式碼到簽出(checkout)到“linux-2.6”這個目錄裡面。我們一般把本地的“linux-2.6”這個目錄叫做”工作目錄“(work directory),它裡面儲存著你從其它地方clone(or checkout)過來的程式碼。當你在專案的不同分支間切換時,“工作目錄”中的檔案可能會被替換或者刪除;“工作目錄”只是儲存著當前的工作,你可以修改裡面檔案的內容直到下次提交為止。
大家還記得前面的“庖丁解牛”嗎,是不是覺得只殺一頭叫“hello,world”的小牛太不過癮了。沒有問題,拿起前面的那把小刀,來剖析一下現在躺在你硬盤裡這頭叫“linux-2.6”大牛看看,我想一定很好玩。