對比SVN學(xué)習(xí)GIT版本管理工具
作者:劉旭暉 Raymond轉(zhuǎn)載請注明出處
Email:colorant@163.com
BLOG:http://blog.csdn.net/colorant/
主頁:http://sites.google.com/site/rgbbones/
因為近期工作需要,要掌握git的使用方法,所以決心花點時間學(xué)習(xí)一下它的各種使用方法,就當(dāng)是花點時間磨刀吧。所以寫這篇文檔的目的主要還是為了自己能夠系統(tǒng)的學(xué)習(xí)和理解GIT應(yīng)用的方方面面,因為之前對SVN算是比較熟悉,所以決定以概念對比的方式來整理這篇文章,盡管,有些地方兩者無法直接比較 8 )此外,主要的目的還是為了方便自己積累相關(guān)的git使用技巧。文中理解有偏差的地方,還請大家指正。
http://www.kernel.org/pub/software/scm/git/docs/
這里面包括了 tutorial 基本的操作 / core-tutorial 底層命令的使用 / user’s manual 完整的用戶手冊以及其它各種資料,如果你看完了,我的這篇文檔你也就不用看了。
http://svnbook.red-bean.com/ 這里是一份online的svn book
或者也可以看tortoiseSVN的幫助文檔
倉庫的組織管理形式這部分,應(yīng)該說是版本管理工具設(shè)計上最核心的內(nèi)容。對于倉庫的內(nèi)部管理機制,我了解得很少,只能從外部的表象上做一些簡單的比較。
SVN屬于中心式的倉庫管理,完整的倉庫數(shù)據(jù),統(tǒng)一維護在服務(wù)器端的(當(dāng)然,服務(wù)器也可以就是你的本機了)倉庫中,對于客戶端來說,本地取得的數(shù)據(jù)不是完整的倉庫,只是倉庫中特定版本的部分或全部數(shù)據(jù),同時,客戶端還負(fù)責(zé)維護本地數(shù)據(jù)的變更情況,在客戶端并不擁有倉庫完整的歷史數(shù)據(jù)。本地的工作樹和倉庫是相對獨立的。
對于Git來說,應(yīng)該屬于分布式的倉庫管理,倒不是說倉庫的內(nèi)容分散在不同的server上,只是對倉庫而言,沒有中心倉庫之說,所有的倉庫都是平等的。對于一個倉庫的不同工作拷貝,每個都擁有完整的歷史數(shù)據(jù),工作樹和倉庫基本是合二為一的。
在SVN中,從倉庫checkout的一個工作樹,每個子目錄下都維護著自己的.svn目錄,記錄著該目錄中文件的修改情況以及和服務(wù)器端倉庫的對應(yīng)關(guān)系。所以SVN可以局部checkout部分路徑下的內(nèi)容,而不用checkout整個分支。
Git倉庫中,項目根目錄下的.git目錄統(tǒng)一管理了所有的倉庫數(shù)據(jù)和當(dāng)前工作樹的相關(guān)信息。
在SVN中,默認(rèn)采用FSFS的數(shù)據(jù)庫格式,任何提交都是一個版本的遞增,所謂分支,tag等概念都只是倉庫中不同路徑上的一個對象或索引而已,和普通的路徑并沒有本質(zhì)的區(qū)別。在工作樹中,可以同時checkout多個分支的內(nèi)容。
在Git中,其內(nèi)部的對象層級依賴關(guān)系或許和SVN類似,但是其工作樹的視圖表現(xiàn)形式和SVN完全不同。工作樹永遠(yuǎn)是一個完整的分支,不同的分支由不同的head索引去構(gòu)建,你不可能在工作樹中同時獲得多個分支的內(nèi)容。
在SVN中,倉庫本身的管理和日常應(yīng)用,使用的是兩套不同的命令。倉庫的創(chuàng)建和備份維護等使用的命令是 svnadmin, 使用svnadmin create來創(chuàng)建一個新的倉庫
在git中,創(chuàng)建一個新的倉庫,可以在一個空目錄下,使用git init來實現(xiàn),它將創(chuàng)建一個.git目錄用來維護倉庫數(shù)據(jù)。
在SVN中,創(chuàng)建倉庫的地方并不是你日常使用的倉庫的地方,你需要在別的地方checkout出特定的倉庫路徑作為你的日常工作的目錄。在git中,倉庫所在的目錄也就是你的日常工作目錄,沒有服務(wù)器端和客戶端之分。(嚴(yán)格的說 .git目錄才是倉庫,.git目錄外的地方是你的工作目錄,對于bare project來說,只有git目錄下的內(nèi)容,工作目錄離得內(nèi)容還是要checkout出來的)
在SVN中,使用SVN checkout(co)來checkout本地或遠(yuǎn)程倉庫的代碼
而對于git來說,盡管也有checkout命令,但是由于你需要在本地?fù)碛袀}庫,所以通常從服務(wù)器上checkout代碼的第一步是使用git clone來獲取一個倉庫的拷貝,默認(rèn)的git clone操作同時還會checkout一份遠(yuǎn)程倉庫上當(dāng)前active的分支
在SVN中,其倉庫的管理形式?jīng)Q定了你可以只checkout倉庫中特定路徑/分支下的子目錄,而不是整個倉庫,而git只能checkout整個分支。
在SVN中,使用SVN add,這樣在以后的commit過程中,每次在提交數(shù)據(jù)之前,svn都會自動根據(jù)這些add過的對象的修改情況,構(gòu)建一個commit tree。
在git中,因為存在index的概念,要將一個文件納入版本管理的范疇,首先是要用git-update-index –-add將文件納入index的監(jiān)控范圍,只有更新到index中的內(nèi)容才會在commit的時候被提交。另外,文件本身的改動并不會自動更新到index中,每次的任何修改都必須重新更新到index中去才會被提交。 當(dāng)然,通常會用git add這樣的封裝腳本來調(diào)用git-update-index
SVN Status 可以顯示當(dāng)前working tree的文件修改狀態(tài)
在git中 git status 命令顯示當(dāng)前index的狀態(tài)和working tree的狀態(tài)。
Git commit操作在git命令中屬于相對簡單的,需要注意的一點就是上面提到的,只有index中的內(nèi)容才會比提交。
在使用Svn rm刪除一個目錄的時候,因為每個目錄下都存在.svn目錄,記錄了這個目錄于服務(wù)器端倉庫相關(guān)的信息,所以在commit之前,目錄里的其它文件會被刪除,但是目錄及其子目錄并不會被真正刪除,只有commit以后,目錄才會被刪除。
在git中,同樣,使用git rm 刪除文件。但是git對目錄的處理有些奇怪,如果某個目錄下的所有文件都被刪除以后,該目錄就會被自動刪除,也就是說你無法保留一個空的目錄。你也無法添加一個空目錄到倉庫里。也就是說git 自動忽略空目錄,不知道這樣做的目的是什么?
svn log命令基本上就是用來查看版本提交時的所填寫的log信息
git log可以做的事情會多很多,畢竟git log是對底層核心命令的再包裝,通過它,不僅可以查看log信息,還可以輸出特定版本的具體變更內(nèi)容等等信息。
在SVN中,不提供任何從倉庫中刪除對象的機制,任何的修改都會導(dǎo)致版本的遞增,所以,如果想丟棄一個修改,你需要做的事是反向diff你的修改,再提交一個新的版本。
在git中提供了重置committed tree對象索引的機制,所以,你可以通過例如git-reset這樣的操作將當(dāng)前分支的版本恢復(fù)到以前的某個狀態(tài)。經(jīng)??匆姷睦泳褪腔厮菀粋€版本,然后修改內(nèi)容,再次提交。不過這樣做搞不好很容易出問題。包括在git-push之類的操作時會被reject,需要強行push之類的。
如果只是想放棄一個修改,git的文檔推薦使用git-revert操作,這個操作基本上和SVN的思路是一樣的了,就是提交一個新的版本將需要revert的版本的內(nèi)容再反向修改回去,版本會遞增,不影響之前提交的內(nèi)容。
在SVN中,使用SVN revert對目錄或文件操作都可以將當(dāng)前工作樹上特定路徑的修改恢復(fù)到服務(wù)器上的版本,放棄當(dāng)前的修改。
Git中,對特定文件使用不帶其它參數(shù)的git checkout命令可以將文件恢復(fù)到index中的狀態(tài),如果你想恢復(fù)的特定的版本,那么類似: git checkout HEAD file這樣的操作,將文件恢復(fù)到HEAD tree即最近一次提交的狀態(tài)。
不過git checkout有個問題,不知道是否是故意這樣設(shè)計的,就是即使用git rm刪除的內(nèi)容,如果沒有提交,git checkout以后也會恢復(fù),包括它在index中的狀態(tài)。這點有些不理解。 理論上index上已經(jīng)記錄這個刪除操作,不應(yīng)該恢復(fù)才對。
Git中還有一種辦法,可以快速徹底的放棄自從上次commit以來的所有變更,git reset –hard HEAD
git merge能夠自動記住以前merge過的位置和狀態(tài),這個比較容易理解,因為通過每個分支的head commit可以跟蹤它的對象索引關(guān)系。另外,因為其對象管理機制的原因,只能以commit為單位,merge整個分支的所有修改。不能有選擇的merge部分路徑下的修改。Merge的時候要求index和HEAD是一致的,如果merge成功,內(nèi)容會直接commit,而工作樹上的修改仍會保持。(如果失敗,會在工作樹上將需要merge的內(nèi)容和你已有的修改合并,大概不是你所希望的,所以最好不要這樣做)
merge特定分支的特定版本之前的所有修改,可以通過merge那個版本對應(yīng)的rev來實現(xiàn),merge某一段版本區(qū)間的修改,考慮到commit需要完整的代碼樹關(guān)系,估計靠git merge來做是沒有辦法了,需要自己diff / patch代碼來實現(xiàn)
SVN的Merge操作不會記住它的merge歷史,換句話說,你可以多次merge同一份代碼,但是他的好處是你可以自由的選擇merge哪一部分、哪一段版本之間的代碼,應(yīng)該說他基本等同于是diff和patch的組合。不過因為SVN沒有index的概念,所以merge的操作會和當(dāng)前working tree上的修改合并在一起。
關(guān)于歷史信息方面,因為svn的merge實際是patch文件內(nèi)容本身,所以,不同分支上的歷史信息不會在merge以后的主干上體現(xiàn)出來,而git的merge,如果沒有沖突的話,實際是merge commit樹的繼承關(guān)系,所以,所有的歷史信息在merge以后的commit中都能夠被索引到。
在svn中,如果不需要任何歷史信息,只想要某個版本純粹的代碼(經(jīng)常會有這種需求,這樣做本地數(shù)據(jù)比較?。?/span> 那么,使用svn export命令即可以實現(xiàn)。
在git中,似乎沒有這樣的命令,不過,由于git的本地倉庫信息完全維護在project根目錄的.git目錄下,(不像svn一樣,每個子目錄下都有單獨的.svn目錄)。所以,只要clone,checkout然后刪除.git目錄就可以了。
對于SVN來說,由于是中心式的倉庫管理形式,所以并不存在特殊的遠(yuǎn)程提交的概念,所有的commit操作都可以認(rèn)為是對遠(yuǎn)程倉庫的更新動作。
在git中,因為有本地倉庫和remote倉庫之分,所以也就區(qū)別于commit 操作,存在額外的push命令,用于將本地倉庫的數(shù)據(jù)更新到遠(yuǎn)程倉庫中去。這種工作模式應(yīng)該是大多數(shù)開源項目的維護者的工作模式之一。
git push 可以選擇需要提交的更新的分支以及制定該分支在遠(yuǎn)程倉庫上的名字。
在SVN中,因為只有一個中心倉庫,所以所謂的遠(yuǎn)程更新,也就是svn update
對于git來說,別人的改動是存在于遠(yuǎn)程倉庫上的,所以git checkout命令盡管在某些功能上和svn中的update類似(例如取倉庫特定版本的內(nèi)容),但是在遠(yuǎn)程更新這一點上,還是不同的,不屬于git checkout的功能涵蓋范圍
Git使用git fetch和git pull來完成遠(yuǎn)程更新任務(wù),fetch操作只是將遠(yuǎn)程數(shù)據(jù)庫的object拷貝到本地,然后更新remotes head的refs,git pull 的操作則是在git fetch的基礎(chǔ)上對當(dāng)前分支外加merge操作。
SVN中,我很喜歡的一個功能就是switch,使用Switch可以在同一個工作樹上,對不同的模塊checkout不同分支上的代碼。 舉個例子: 我從主干上checkout了整個內(nèi)核樹,然后使用switch命令將其中一個或幾個驅(qū)動的目錄或文件切換到我的個人分支或其它人的分支上去,這樣,我可以使用一個update命令同時從幾個不同的來源更新特定的文件,而我在工作樹上對switch過的文件做的修改會自動提交到我的個人分支上,而不是主干的路徑上。這樣我的修改不會影響主干的內(nèi)容,而同時又能隨時更新主干上的最新內(nèi)容。不僅方便工作,也有利于權(quán)限控制。一切都是自動的,方便!
在Git中,盡管也可以使用checkout命令checkout 特定分支的特定文件到當(dāng)前分支的工作樹上, 但是,這只是簡單的更新當(dāng)前工作樹的文件內(nèi)容而已,這些文件并不會被關(guān)聯(lián)到他的來源上去,也就是說你做的任何修改,還是針對當(dāng)前分支的。
對于多分枝協(xié)同工作,我所見到的常見的工作模式是fetch遠(yuǎn)程更新,然后merge到當(dāng)前分支。這對于維護會沖突的不同版本和快速切換局部分支顯然還是有所不足的。
這種情況或許和git的分布式倉庫結(jié)構(gòu)和整體設(shè)計思路有關(guān),或許這樣有利于保持所有開發(fā)者之間的代碼的同步,但是總覺得這是個遺憾,這方面沒有深入的再去研究,或許通過borrow object的方式可以部分實現(xiàn)類似SVN的switch的功能?(也或許要實現(xiàn)多分枝協(xié)同工作,在Git中還有其它不同思路的更巧妙的辦法?) 哪位高手知道解決辦法的還請不吝賜教!
git submodules 看起來是為了解決類似多個有依賴關(guān)系的模塊的協(xié)同工作問題。不過用起來似乎有不少限制和麻煩。
對于git協(xié)同工作時的權(quán)限控制,還沒有仔細(xì)研究,不知道能否像SVN那樣,通過Apache的用戶賬號形式,對每一個用戶精確控制到文件級別的讀寫權(quán)限。 目前初步查找了一下,看來似乎沒有這樣的功能,不知道設(shè)計的初衷是什么,對于小組開發(fā)來說或許會比較麻煩?
這個與開源精神應(yīng)該沒有太直接的關(guān)系才對,因為很多時候,其實權(quán)限控制的目的倒不是純粹為了限制對代碼的Access,主要還是為了減少代碼沖突,減少誤操作等情況的發(fā)生。
丟失版本最常見的問題就是 比如使用了 git reset –hard HEAD^之類的操作,結(jié)果發(fā)現(xiàn)丟棄的版本還想恢復(fù)回來,但是已經(jīng)沒有任何分支能夠reference到這個commit了。幸運的是,git 對各個分支的head還有一份log記錄叫做reflog,你可以在.git/logs/refs/head/ 目錄下看到他們。 通過 git reflog可以顯示變更歷史。使用類似 master@{1} master@{“2 days ago”}之類的格式,就能索引到你想要的commit。例如對應(yīng)于git reset –hard HEAD^ 使用 git reset --hard HEAD@{1}即可恢復(fù)到reset之前的commit上。
如果只是想對最近一次的提交做一些變更,但是不想在commit tree上遞增版本的話,可以使用git commit –amend來實現(xiàn),在現(xiàn)有的基礎(chǔ)上做任何你想做的變更,然后用帶–amend參數(shù)的commit命令提交即可?;旧?,這個操作近似等同于于以下操作:
$ git reset --soft HEAD^
$ ... do something else to come up with the right tree ...
$ git commit -c ORIG_HEAD
和git reset之類的操作類似,對于已經(jīng)push的內(nèi)容,最好是不要做這些回滾的操作,因為實際上,原先commit的head還是存在的,新的head和以前的head沒有繼承關(guān)系,在協(xié)同工作的時候容易產(chǎn)生一些問題(我猜想主要是merge相關(guān)的操作吧,因為merge是根據(jù)對象的繼承關(guān)系來自動判斷需要merge的內(nèi)容的,對于已經(jīng)merge過的分支被回滾以后,可能無法自動區(qū)別識別出這部分內(nèi)容應(yīng)該如何處理)
聯(lián)系客服