版本控制是什么已不用在說(shuō)了,就是記錄我們對(duì)文件、目錄或工程等的修改歷史,方便查看更改歷史,備份以便恢復(fù)以前的版本,多人協(xié)作。。。
最原始的版本控制是純手工的版本控制:修改文件,保存文件副本。有時(shí)候偷懶省事,保存副本時(shí)命名比較隨意,時(shí)間長(zhǎng)了就不知道哪個(gè)是新的,哪個(gè)是老的了,即使知道新舊,可能也不知道每個(gè)版本是什么內(nèi)容,相對(duì)上一版作了什么修改了,當(dāng)幾個(gè)版本過(guò)去后,很可能就是下面的樣子了:
手工管理比較麻煩且混亂,所以出現(xiàn)了本地版本控制系統(tǒng),記錄文件每次的更新,可以對(duì)每個(gè)版本做一個(gè)快照,或是記錄補(bǔ)丁文件。比如RCS。
但是本地版本控制系統(tǒng)偏向于個(gè)人使用,或者多個(gè)使用的人必須要使用相同的設(shè)備,如果需要多人協(xié)作就不好辦了,于是,集中化的版本控制系統(tǒng)( Centralized Version Control Systems,簡(jiǎn)稱 CVCS )應(yīng)運(yùn)而生,比如Subversion,Perforce。
在CVCS中,所有的版本數(shù)據(jù)都保存在服務(wù)器上,一起工作的人從服務(wù)器上同步更新或上傳自己的修改。
但是,所有的版本數(shù)據(jù)都存在服務(wù)器上,用戶的本地設(shè)備就只有自己以前所同步的版本,如果不連網(wǎng)的話,用戶就看不到歷史版本,也無(wú)法切換版本驗(yàn)證問(wèn)題,或在不同分支工作。。
而且,所有數(shù)據(jù)都保存在單一的服務(wù)器上,有很大的風(fēng)險(xiǎn)這個(gè)服務(wù)器會(huì)損壞,這樣就會(huì)丟失所有的數(shù)據(jù),當(dāng)然可以定期備份。
針對(duì)CVCS的以上缺點(diǎn),出現(xiàn)了分布式版本控制系統(tǒng)( Distributed Version Control System,簡(jiǎn)稱 DVCS ),如GIT,Mercurial。
DVCS不是復(fù)制指定版本的快照,而是把所有的版本信息倉(cāng)庫(kù)全部同步到本地,這樣就可以在本地查看所有版本歷史,可以離線在本地提交,只需在連網(wǎng)時(shí)push到相應(yīng)的服務(wù)器或其他用戶那里。由于每個(gè)用戶那里保存的都是所有的版本數(shù)據(jù),所以,只要有一個(gè)用戶的設(shè)備沒(méi)有問(wèn)題就可以恢復(fù)所有的數(shù)據(jù)。
當(dāng)然,這增加了本地存儲(chǔ)空間的占用。
必須要了解GIT的原理,才能知道每個(gè)操作的意義是什么,才能更容易地理解在什么情況下用什么操作,而不是死記命令。當(dāng)然,第一步是要獲得一個(gè)GIT倉(cāng)庫(kù)。
有兩種獲得GIT倉(cāng)庫(kù)的方法,一是在需要用GIT管理的項(xiàng)目的根目錄執(zhí)行:
git init
執(zhí)行后可以看到,僅僅在項(xiàng)目目錄多出了一個(gè).git目錄,關(guān)于版本等的所有信息都在這個(gè)目錄里面。
另一種方式是克隆遠(yuǎn)程目錄,由于是將遠(yuǎn)程服務(wù)器上的倉(cāng)庫(kù)完全鏡像一份至本地,而不是取某一個(gè)特定版本,所以用clone而不是checkout:
git clone <url>
記錄版本信息的方式主要有兩種:
GIT采用第一種方式。像Subversion和Perforce等版本控制系統(tǒng)都是記錄文件每個(gè)版本之間的差異,這就需要對(duì)比文件兩版本之間的具體差異,但是GIT不關(guān)心文件兩個(gè)版本之間的具體差別,而是關(guān)心文件的整體是否有改變,若文件被改變,在添加提交時(shí)就生成文件新版本的快照,而判斷文件整體是否改變的方法就是用SHA-1算法計(jì)算文件的校驗(yàn)和。
GIT能正常工作完全信賴于這種SHA-1校驗(yàn)和,當(dāng)一個(gè)文件的某一個(gè)版本被記錄之后會(huì)生成這個(gè)版本的一個(gè)快照,但是一樣要能引用到這個(gè)快照,GIT中對(duì)快照的引用,對(duì)每個(gè)版本的記錄標(biāo)識(shí)全是通過(guò)SHA-1校驗(yàn)和來(lái)實(shí)現(xiàn)的。
當(dāng)一個(gè)文件被改變時(shí),它的校驗(yàn)和一定會(huì)被改變(理論上存在兩個(gè)文件校驗(yàn)和相同,但機(jī)率小到可以忽略不計(jì)),GIT就以此判斷文件是否被修改,及以些記錄不同版本。
在工作目錄的文件可以處于不同的狀態(tài),比如說(shuō)新添加了一個(gè)文件,GIT發(fā)覺(jué)了這個(gè)文件,但這個(gè)文件是否要納入GIT的版本控制還是要由我們自己決定,比如編譯生成的中間文件,我們肯定不想納入版本控制。下面就來(lái)看下文件狀態(tài)。
版本控制就是對(duì)文件的版本控制,對(duì)于Linux來(lái)說(shuō),設(shè)備,目錄等全是文件,要對(duì)文件進(jìn)行修改、提交等操作,首先要知道文件當(dāng)前在什么狀態(tài),不然可能會(huì)提交了現(xiàn)在還不想提交的文件,或者要提交的文件沒(méi)提交上。
GIT倉(cāng)庫(kù)所在的目錄稱為工作目錄,這個(gè)很好理解,我們的工程就在這里,工作時(shí)也是在這里做修改。
在工作目錄中的文件被分為兩種狀態(tài),一種是已跟蹤狀態(tài)(tracked),另一種是未跟蹤狀態(tài)(untracked)。只有處于已跟蹤狀態(tài)的文件才被納入GIT的版本控制。如下圖:
當(dāng)我們往工作目錄添加一個(gè)文件的時(shí)候,這個(gè)文件默認(rèn)是未跟蹤狀態(tài)的,我們肯定不希望編譯生成的一大堆臨時(shí)文件默認(rèn)被跟蹤還要我們每次手動(dòng)將這些文件清除出去。用以下命令可以跟蹤文件:
git add <file>
上圖中右邊3個(gè)狀態(tài)都是已跟蹤狀態(tài),其中的灰色箭頭只表示untracked<-->tracked的轉(zhuǎn)換而不是untracked<-->unmodified的轉(zhuǎn)換,新添加的文件肯定算是被修改過(guò)的。那么,staged狀態(tài)又是什么呢?這就要搞清楚GIT的三個(gè)工作區(qū)域:本地?cái)?shù)據(jù)(倉(cāng)庫(kù))目錄,工作目錄,暫存區(qū),如下圖所示:
git directory就是我們的本地倉(cāng)庫(kù).git目錄,里面保存了所有的版本信息等內(nèi)容。
working driectory,工作目錄,就是我們的工作目錄,其中包括未跟蹤文件及已跟蹤文件,而已跟蹤文件都是從git directory取出來(lái)的文件的某一個(gè)版本或新跟蹤的文件。
staging area,暫存區(qū),不對(duì)應(yīng)一個(gè)具體目錄,其時(shí)只是git directory中的一個(gè)特殊文件。
當(dāng)我們修改了一些文件后,要將其放入暫存區(qū)然后才能提交,每次提交時(shí)其實(shí)都是提交暫存區(qū)的文件到git倉(cāng)庫(kù),然后清除暫存區(qū)。而checkout某一版本時(shí),這一版本的文件就從git倉(cāng)庫(kù)取出來(lái)放到了我們的工作目錄。
那么,我們?cè)趺粗喇?dāng)前工作目錄的狀態(tài)呢?哪些文件已被暫存?有哪些未跟蹤的文件?哪些文件被修改了?所有這些只需要一個(gè)命令,git status,如下圖所示:
GIT在這一點(diǎn)做得很好,在輸出每個(gè)文件狀態(tài)的同時(shí)還說(shuō)明了怎么操作,像上圖就有怎么暫存、怎么跟蹤文件、怎么取消暫存的說(shuō)明。
在上圖中我們可以很清楚地看到,filea未跟蹤,fileb已被暫存(changes to be committed),但是怎么還有一個(gè)fileb是modified但unstaged呢?這是因?yàn)楫?dāng)我們暫存一從此文件時(shí),暫存的是那一文件當(dāng)時(shí)的版本,當(dāng)暫存后再次修改了這個(gè)文件后就會(huì)提示這個(gè)文件暫存后的修改是未被暫存的。
接下來(lái)我們就看怎么暫存文件,其實(shí)也很簡(jiǎn)單,從上圖中可以看到GIT已經(jīng)提示我們了:use "git add <file>..." to update what will be committed,通過(guò)
git add <file>...
就可以暫存文件,跟蹤文件同樣是這一個(gè)命令。在這個(gè)命令中可以使用glob模式匹配,比如"file[ab]",也可以使用"git add ."添加當(dāng)前目錄下的所有文件。
取消暫存文件是
git reset HEAD <file>...
若修改了一個(gè)文件想還原修改可用
git checkout -- <file>...
當(dāng)我們修改過(guò)一些文件之后,我們可能想查看我們都修改了什么東西,用"git status"只能查看對(duì)哪些文件做了改動(dòng),如果要看改動(dòng)了什么,可以用:
git diff
比如下圖:
---a表示修改之前的文件,+++b表示修改后的文件,上圖表示在fileb的第一行后添加了一行"bb",原來(lái)文件的第一行擴(kuò)展為了修改后的1、2行。
但是,前面我們明明用"git status"看到filesb做了一些修改后暫存了,然后又修改了fileb,理應(yīng)有兩次修改的,怎么只有一個(gè)?
因?yàn)?git diff"顯示的是文件修改后還沒(méi)有暫存起來(lái)的內(nèi)容,那如果要比較暫存區(qū)的文件與之前已經(jīng)提交過(guò)的文件呢,畢竟實(shí)際提交的是暫存區(qū)的內(nèi)容,可以用以下命令:
/dev/null表示之前沒(méi)有提交過(guò)這一個(gè)文件,這是將是第一次提交,用:
git diff --staged
是等效的,但GIT的版本要大于1.6.1。
再次執(zhí)行"git add"將覆蓋暫存區(qū)的內(nèi)容。
如果有一些部件我們不想納入版本控制,也不想在每次"git status"時(shí)看到這些文件的提示,或者很多時(shí)候我們?yōu)榱朔奖銜?huì)使用"git add ."添加所有修改的文件,這時(shí)就會(huì)添加上一些我們不想添加的文件,怎么忽略這些文件呢?
GIT當(dāng)然提供了方法,只需在主目錄下建立".gitignore"文件,此文件有如下規(guī)則:
比如:
# 此為注釋 – 將被 Git 忽略
*.a # 忽略所有 .a 結(jié)尾的文件
!lib.a # 但 lib.a 除外
/TODO # 僅僅忽略項(xiàng)目根目錄下的 TODO 文件,不包括 subdir/TODO
build/ # 忽略 build/ 目錄下的所有文件
doc/*.txt # 會(huì)忽略 doc/notes.txt 但不包括 doc/server/arch.txt
當(dāng)我們要?jiǎng)h除一個(gè)文件時(shí),我們可能就直接用GUI刪除或者直接rm [file]了,但是看圖:
我們需要將文件添加到暫存區(qū)才能提交,而移除文件后是無(wú)法添加到暫存區(qū)的,那么怎么移除一個(gè)文件讓GIT不再將其納入版本控制呢?上圖中GIT已經(jīng)給出了說(shuō)明:
git rm <file>...
執(zhí)行以上命令后提交就可以了,有時(shí)我們只是想將一些文件從版本控制中剔除出去,但仍保留這些文件在工作目錄中,比如我們一不小心將編譯生成的中間文件納入了版本控制,想將其從版本控制中剔除出去但在工作目錄中保留這些文件(不然再次編譯可要花費(fèi)更多時(shí)間了),這時(shí)只需要添加"--cached"參數(shù)。
如果我們之前不是通過(guò)"git rm"刪除了很多文件呢?比如說(shuō)通過(guò)patch或者通過(guò)GUI,如果這些文件命名沒(méi)有規(guī)則,一個(gè)一個(gè)地執(zhí)行"git rm"會(huì)搞死人的,這時(shí)可以用以下命令:
和移除文件一樣,移動(dòng)文件不可以通過(guò)GUI直接重命令或用"mv"命令,而是要用"git mv",不然同移除文件一樣你會(huì)得到如下結(jié)果:
如果要重命名文件可以使用
git mv old_name new_name
這個(gè)命令等效于
mv old_name new_name
git rm old_name
git add new_name
使用git add -i可以開(kāi)啟交互式暫存,如圖所示,系統(tǒng)會(huì)列出一個(gè)功能菜單讓選擇將要執(zhí)行的操作。
git clean [options] 一般會(huì)加上參數(shù)-df,-d表示包含目錄,-f表示強(qiáng)制清除。
可能會(huì)遇到這樣的情況,你正在一個(gè)分支上進(jìn)行一個(gè)特性的開(kāi)發(fā),或者一個(gè)Bug的修正,但是這時(shí)突然有其他的事情急需處理,這時(shí)該怎么辦?不可能就在這個(gè)工作進(jìn)行到一半的分支上一起處理,先把修改的Copy出去?太麻煩了。這種情況下就要用到Stashing了。假如我們現(xiàn)在的工作目錄是這樣子的
$ git status# On branch master# Changes to be committed:#(use "git reset HEAD <file>..." to unstage)##modified:index.html## Changed but not updated:#(use "git add <file>..." to update what will be committed)##modified:lib/simplegit.rb
此時(shí)如果想切換分支就可以執(zhí)行以下命令
$ git stashSaved working directory and index state "WIP on master: 049d078 added the index file"HEAD is now at 049d078 added the index file(To restore them type "git stash apply")
這時(shí)你會(huì)發(fā)現(xiàn)你的工作目錄變得很干凈了,就可以隨意切分支進(jìn)行其他事情的處理了。
我們可能不只一次進(jìn)行"git stash",通過(guò)以下命令可以查看所有stash列表
$ git stash liststash@{0}: WIP on master: 049d078 added the index filestash@{1}: WIP on master: c264051... Revert "added file_size"
當(dāng)緊急事情處理完了,需要重新回來(lái)這里進(jìn)行原來(lái)的工作時(shí),只需把Stash區(qū)域的內(nèi)容取出來(lái)應(yīng)用到當(dāng)前工作目錄就行,命令就是
git stash apply
如果不基參數(shù)就應(yīng)用最新的stash,或者可以指定stash的名字,如:stash@{1},可能通過(guò)
git stash show
顯示stash的內(nèi)容具體是什么,同git stash apply一樣,可以選擇指定stash的名字。
git stash apply之后再git stash list會(huì)發(fā)現(xiàn),apply后的stash還在stash列表中,如果要將其從stash列表中刪除可以用
git stash drop
丟棄這個(gè)stash,stash的命令參數(shù)都可選擇指定stash名字,否則就是最新的stash。
一般情況下apply stash后應(yīng)該就可以把它從stash列表刪除了,先apply再drop還是比較繁瑣的,使用以下一條命令就可以同時(shí)完成這兩個(gè)操作
git stash pop
如果我們執(zhí)行g(shù)it stash時(shí)工作目錄的狀態(tài)是部分文件已經(jīng)加入了暫存區(qū),部分文件沒(méi)有,當(dāng)我們執(zhí)行g(shù)it stash apply之后會(huì)發(fā)現(xiàn)所有文件都變成了未暫存的,如果想維持原來(lái)的樣子操持原來(lái)暫存的文件仍然是暫存狀態(tài),可以加上--index參數(shù)
git stash apply --index
還有這么一種情況,我們把原來(lái)的修改stash了,然后修復(fù)了其他一些東西并進(jìn)行了提交,但是,這些提交的文件有些在之前已經(jīng)被stash了,那么git stash apply時(shí)就很可能會(huì)遇到?jīng)_突,這種情況下就可以在stash時(shí)所以提交的基礎(chǔ)上新建一個(gè)分支,然后再apply stash,當(dāng)然,這兩個(gè)步驟有一人簡(jiǎn)單的完成方法
git stash branch <branch name>
了解了文件的狀態(tài),我們對(duì)文件進(jìn)行了必要的修改后,就要把我們所做的修改放入版本庫(kù)了,這樣以后我們就可以在需要的時(shí)候恢復(fù)到現(xiàn)在的版本,而要恢復(fù)到某一版,一般需要查看版本的歷史。
提交很簡(jiǎn)單,直接執(zhí)行"git commit"。執(zhí)行g(shù)it commit后會(huì)調(diào)用默認(rèn)的或我們?cè)O(shè)置的編譯器要我們填寫(xiě)提示說(shuō)明,但是提交說(shuō)明最好按GIT要求填寫(xiě):第一行填簡(jiǎn)單說(shuō)明,隔一行填寫(xiě)詳細(xì)說(shuō)明。因?yàn)榈谝恍性谝恍┣闆r下會(huì)被提取使用,比如查看簡(jiǎn)短提交歷史或向別人提交補(bǔ)丁,所以字符數(shù)不應(yīng)太多,40為好。下面看一下查看提交歷史。
查看提交歷史使用如下圖的命令
如圖所示,顯示了作者,作者郵箱,提交說(shuō)明與提交時(shí)間,"git log"可以使用放多參數(shù),比如:
僅顯示最新的1個(gè)log,用"-n"表示。
顯示簡(jiǎn)單的SHA-1值與簡(jiǎn)單提交說(shuō)明,oneline僅顯示提交說(shuō)明的第一行,所以第一行說(shuō)明最好簡(jiǎn)單點(diǎn)方便在一行顯示。
"git log --graph"以圖形化的方式顯示提交歷史的關(guān)系,這就可以方便地查看提交歷史的分支信息,當(dāng)然是控制臺(tái)用字符畫(huà)出來(lái)的圖形。
"git log"的更多參數(shù)可以查看命令幫助。
如果我們想跳過(guò)暫存區(qū)直接提交修改的文件,可以使用"-a"參數(shù),但要慎重,別一不小心提交了不想提交的文件
git commit -a
如果需要快捷地填寫(xiě)提交說(shuō)明可使用"-m"參數(shù)
git commit -m 'commit message'
如果我們提交過(guò)后發(fā)現(xiàn)有個(gè)文件改錯(cuò)了,或者只是想修改提交說(shuō)明,這時(shí)可以對(duì)相應(yīng)文件做出修改,將修改過(guò)的文件通過(guò)"git add"添加到暫存區(qū),然后執(zhí)行以下命令:
git commit --amend
然后修改提交說(shuō)明覆蓋上次提交,但只能重寫(xiě)最后一次提交。
通過(guò)衍合(rebase)可以修改多個(gè)提交的說(shuō)明,并可以重排提交歷史,拆分、合并提交,關(guān)于rebase在講到分支時(shí)再說(shuō),這里先看一下重排提交。
假設(shè)我們的提交歷史是這樣的:
如果我們想重排最后兩個(gè)提交的提交歷史,可以借助交互式rebase命令:
git rebase -i HEAD~2 或 git rebase -i 3366e1123010e7d67620ff86040a061ae76de0c8
HEAD~2表示倒數(shù)第三個(gè)提交,這條命令要指定要重排的最舊的提交的父提交,此處要重排Second commit與Third commit,所以要指定Initial commit的Commit ID。如圖所示:
注釋部分詳細(xì)說(shuō)明了每個(gè)選項(xiàng)的作用,如果我們想交互這兩個(gè)提交,只需把開(kāi)頭的這兩行交換下位置就OK了,交換位置后保存,然后看下提交歷史:
可以看到提交歷史已經(jīng)變了,而且最新的兩個(gè)提交的Commit ID變了,如果這些提交已經(jīng)push到了遠(yuǎn)程服務(wù)器,就不要用這個(gè)命令了。
如果要?jiǎng)h除某個(gè)提交,只需要?jiǎng)h除相應(yīng)的行就可以了,而要修改某個(gè)提交的提交說(shuō)明的話,只需要把相應(yīng)行的pick改為reward。
合并提交也很簡(jiǎn)單,從注釋中的說(shuō)明看,只需要把相應(yīng)的行的pick改為squash就可以把這個(gè)提交全并到它上一行的提交中。
至于拆分提交,由于Git不可能知道你要做哪里把某一提交拆分開(kāi),把以我們就需要讓Git在需要拆分的提交處停下來(lái),由我們手動(dòng)修改提交,這時(shí)要把pick改為edit,這樣Git在處理到這個(gè)提交時(shí)會(huì)停下來(lái),此時(shí)我們就可以進(jìn)行相應(yīng)的修改并多次提交來(lái)拆分提交。
前面說(shuō)了刪除提交的方法,但是如果是多人合作的話,如果某個(gè)提交已經(jīng)Push到遠(yuǎn)程倉(cāng)庫(kù),是不可以用那種方法刪除提交的,這時(shí)就要撤銷(xiāo)提交
git revert <commit-id>
這條命令會(huì)把指定的提交的所有修改回滾,并同時(shí)生成一個(gè)新的提交。
git reset會(huì)修改HEAD到指定的狀態(tài),用法為
git reset [options] <commit>
這條命令會(huì)使HEAD提向指定的Commit,一般會(huì)用到3個(gè)參數(shù),這3個(gè)參數(shù)會(huì)影響到工作區(qū)與暫存區(qū)中的修改:
當(dāng)與別人和作開(kāi)發(fā)時(shí),會(huì)向別人貢獻(xiàn)代碼或者接收別人貢獻(xiàn)的代碼,有時(shí)候可能不想完全Merge別人貢獻(xiàn)的代碼,只想要其中的某一個(gè)提交,這時(shí)就可以使用cherry-pick了。就一個(gè)命令
git cherry-pick <commit-id>
這條命令可以修改整個(gè)歷史,如從所有歷史中刪除某個(gè)文件相關(guān)的信息,全局性地更換電子郵件地址。
分支被稱之為GIT最強(qiáng)大的特性,因?yàn)樗浅5剌p量級(jí),如果用Perforce等工具應(yīng)該知道,創(chuàng)建分支就是克隆原目錄的一個(gè)完整副本,對(duì)于大型工程來(lái)說(shuō),太費(fèi)時(shí)費(fèi)力了,而對(duì)于GIT來(lái)說(shuō),可以在瞬間生成一個(gè)新的分支,無(wú)論工程的規(guī)模有多大,因?yàn)镚IT的分支其實(shí)就是一指針而已。在了解GIT分支之前,應(yīng)該先了解GIT是如何存儲(chǔ)數(shù)據(jù)的。
前面說(shuō)過(guò),GIT存儲(chǔ)的不是文件各個(gè)版本的差異,而是文件的每一個(gè)版本存儲(chǔ)一個(gè)快照對(duì)象,然后通過(guò)SHA-1索引,不只是文件,包換每個(gè)提交都是一個(gè)對(duì)象并通過(guò)SHA-1索引。無(wú)論是文本文件,二進(jìn)制文件還是提交,都是GIT對(duì)象。
每個(gè)對(duì)象(object) 包括三個(gè)部分:類型,大小和內(nèi)容。大小就是指內(nèi)容的大小,內(nèi)容取決于對(duì)象的類型,有四種類型的對(duì)象:"blob"、"tree"、 "commit" 和"tag"。
比如說(shuō)我們執(zhí)行了以下代碼進(jìn)行了一次提交:
$ git add README test.rb LICENSE2
$ git commit -m 'initial commit of my project'
現(xiàn)在,Git 倉(cāng)庫(kù)中有五個(gè)對(duì)象:三個(gè)表示文件快照內(nèi)容的 blob 對(duì)象;一個(gè)記錄著目錄樹(shù)內(nèi)容及其中各個(gè)文件對(duì)應(yīng) blob 對(duì)象索引的 tree 對(duì)象;以及一個(gè)包含指向 tree 對(duì)象(根目錄)的索引和其他提交信息元數(shù)據(jù)的 commit 對(duì)象。概念上來(lái)說(shuō),倉(cāng)庫(kù)中的各個(gè)對(duì)象保存的數(shù)據(jù)和相互關(guān)系看起來(lái)如下圖:
如果進(jìn)行多次提交,倉(cāng)庫(kù)的歷史會(huì)像這樣:
所謂的GIT分支,其實(shí)就是一個(gè)指向某一個(gè)Commit對(duì)象的指針,像下面這樣,有兩個(gè)分支,master與testing:
而我們?cè)趺粗喇?dāng)前在哪一個(gè)分支呢?其實(shí)就是很簡(jiǎn)單地使用了一個(gè)名叫HEAD的指針,如上圖所示。HEAD指針的值可以為一個(gè)SHA-1值或是一個(gè)引用,看以下例子:
git的所有版本信息都保存了Working Directory下的.git目錄,而HEAD指針就保存在.git目錄下,如上圖所有,目前為止已經(jīng)有3個(gè)提交,通過(guò)查看HEAD的值可以看到我們當(dāng)前在master分支:refs/heads/master,當(dāng)我們通過(guò)git checkout取出某一特定提交后,HEAD的值就是成了我們checkout的提交的SHA-1值。
記錄我們當(dāng)前的位置很簡(jiǎn)單,就是能過(guò)HEAD指針,HEAD指向某一提交的SHA-1值或是某一分支的引用。
git branch <branch-name>
有時(shí)需要在新建分支后直接切換到新建的分支,可以直接用checkout的-b選項(xiàng)
git checkout -b <branch-name>
git branch -d <branch-name>
如果在指定的分支有一些unmerged的提交,刪除分支會(huì)失敗,這里可以使用-D參數(shù)強(qiáng)制刪除分支。
git branch -D <branch-name>
檢出某一分支或某一提交是同一個(gè)命令
git checkout <branch-name> | <commit>
當(dāng)我們新建一個(gè)分支進(jìn)行開(kāi)發(fā),并提交了幾次更新后,感覺(jué)是時(shí)候?qū)⑦@個(gè)分支的內(nèi)容合回主線了,這是就可以取出主線分支,然后把分支的更新merge回來(lái):
git checkout master
git merge testing
如果master分支是testing分支的直接上游,即從master延著testing分支的提交歷史往前走可以直接走到testing分支的最新提交,那么系統(tǒng)什么也不需要做,只需要改變master分支的指針即可,這被稱之為"Fast Forward"。
但是,一般情況是這樣的,你取出了最新的master分支,比如說(shuō)master分支最新的提交是C2(假設(shè)共3次提交C0<-C1<-C2),在此基礎(chǔ)上你新建了分支,當(dāng)你在分支上提交了C3、C5后想將br1時(shí)merge回master時(shí),你發(fā)現(xiàn)已經(jīng)有其他人提交了C4,這時(shí)候就不能直接修改master的指針了,不然會(huì)丟失別人的提交,這個(gè)時(shí)候就需要將你新建分支時(shí)master所在的提交(C2)后的修改(C4),與你新建分支后在分支上的修改(C3、C5)做合并,將合并后的結(jié)果作為一個(gè)新的提交提交到master,GIT可以自動(dòng)推導(dǎo)出應(yīng)該基于哪個(gè)提交進(jìn)行合并(C2),如果沒(méi)有沖突,系統(tǒng)會(huì)自動(dòng)提交新的提交,如果有沖突,系統(tǒng)會(huì)提示你解決沖突,當(dāng)沖突解決后,你就可以將修改加入暫存區(qū)并提交。提交歷史類似下面這樣(圖來(lái)自Pro-Git):
merge后的提交是按時(shí)間排序的,比如下圖,我們?cè)趓ename提交處新建分支test,在test上提交Commit from branch test,然后回到master提交commit in master after committing in branch,再將test分支merge進(jìn)master,這時(shí)看提交提交歷史,Commit from branch test是在commit in master...之前的,盡管在master上我們是在rename的基礎(chǔ)上提交的commit in master...而GIT會(huì)在最后添加一個(gè)新的提交(Merge branch 'test')表示我們?cè)诖颂帉⒁粋€(gè)分支merge進(jìn)來(lái)了。這種情況會(huì)有一個(gè)問(wèn)題,比如說(shuō)在rename提交處某人A從你這里Copy了一個(gè)GIT倉(cāng)庫(kù),然后你release了一個(gè)patch(通過(guò)git format-patch)給A,這時(shí)候test分支還沒(méi)有merge進(jìn)來(lái),所以patch中只包含提交:commit in master...然后你把test分支merge了進(jìn)來(lái)又給了A一個(gè)patch,這個(gè)patch會(huì)包含提交:Commit from branch test,而這個(gè)patch是以rename為base的,如果commit in master...和Commit from branch test修改了相同的文件,則第二次的patch可能會(huì)打不上去,因?yàn)橐詒ename為base的patch可能在新的Code上找不到在哪個(gè)位置應(yīng)用修改。
有兩種方法將一個(gè)分支的改動(dòng)合并進(jìn)另一個(gè)分支,一個(gè)就是前面所說(shuō)的分支合并,另一個(gè)就是分支衍合,這兩種方式有什么區(qū)別呢?
分支合并(merge)是將兩個(gè)分支的改動(dòng)合并到一起,并生成一個(gè)新的提交,提交歷史是按時(shí)間排序的,即我們實(shí)際提交的順序,通過(guò)git log --graph或一些圖形化工具,可能很明顯地看到分支的合并歷史,如果分支比較多就很混亂,而且如果以功能點(diǎn)新建分支,等功能點(diǎn)完成后合回主線,由于merge后提交是按提交時(shí)間排序的,提交歷史就比較亂,各個(gè)功能點(diǎn)的提交混雜在一起,還可能遇到上面提到的patch問(wèn)題。
而分支衍合(rebase)是找到兩個(gè)分支的共同祖先提交,將要被rebase進(jìn)來(lái)的分支的提交依次在要被rebase到的分支上重演一遍,即回到兩個(gè)分支的共同祖先,將branch(假如叫experiment)的每次提交的差異保存到臨時(shí)文件里,然后切換到要衍合入的分支(假如是master),依次應(yīng)用補(bǔ)丁文件。experiment上有幾次提交,在master就生成幾次新的提交,而且是連在一起的,這樣合進(jìn)主線后每個(gè)功能點(diǎn)的提交就都在一起,而且提交歷史是線性的
對(duì)比merge與rebase的提交歷史會(huì)是下圖這樣的(圖來(lái)自Pro-GIt):
(merge)
(rebase)
rebase后C3提交就不存在了,取而代之的是C3',而master也成為了experiment的直接上游,只需一次Fast Forward(git merge)后master就指向了最新的提交,就可以刪除experiment分支了。
git rebase --onto master server client
這條命令的意思是:檢出server分支與client分支共同祖先之后client上的變化,然后在master上重演一遍。
HEAD表示當(dāng)前所在的提交,如果要查看當(dāng)前提交父提交呢?git log查看提交歷史,顯然太麻煩了,而且輸入一長(zhǎng)串的Commit-ID也不是一個(gè)令人愉悅的事。這時(shí)可借助兩個(gè)特殊的符號(hào):~與^。
^ 表示指定提交的父提交,這個(gè)提交可能由多個(gè)交提交,^之后跟上數(shù)字表示第幾個(gè)父提交,不跟數(shù)字等同于^1。
~n相當(dāng)于n個(gè)^,比如~3=^^^,表示第一個(gè)父提交的第一個(gè)父提交的第一個(gè)父提交。
遠(yuǎn)程分支以(遠(yuǎn)程倉(cāng)庫(kù)名)/(分支名)命令,遠(yuǎn)程分支在本地?zé)o法移動(dòng)修改,當(dāng)我們clone一個(gè)遠(yuǎn)程倉(cāng)庫(kù)時(shí)會(huì)自動(dòng)在本地生成一個(gè)名叫original的遠(yuǎn)程倉(cāng)庫(kù),下載遠(yuǎn)程倉(cāng)庫(kù)的所有數(shù)據(jù),并新建一個(gè)指向它的分支original/master,但這個(gè)分支我們是無(wú)法修改的,所以需要在本地重新一個(gè)分支,比如叫master,并跟蹤遠(yuǎn)程分支。
Clone了遠(yuǎn)程倉(cāng)庫(kù)后,我們還會(huì)在本地新建其他分支,并且可能也想跟蹤遠(yuǎn)程分支,這時(shí)可以用以下命令:
git checkout -b [branch_name] --track|-t <remote>/<remote-banch>
和新建分支的方法一樣,只是加了一個(gè)參數(shù)--track或其縮寫(xiě)形式-t,可以指定本地分支的名字,如果不指定就會(huì)被命名為remote-branch。
要拉取某個(gè)遠(yuǎn)程倉(cāng)庫(kù)的數(shù)據(jù),可以用git fetch:
git fetch <remote>
當(dāng)拉取到了遠(yuǎn)程倉(cāng)庫(kù)的數(shù)據(jù)后只是把數(shù)據(jù)保存到了一個(gè)遠(yuǎn)程分支中,如original/master,而這個(gè)分支的數(shù)據(jù)是無(wú)法修改的,此時(shí)我們可以把這個(gè)遠(yuǎn)程分支的數(shù)據(jù)合并到我們當(dāng)前分支
git merge <remote>/<remote-branch>
如果當(dāng)前分支已經(jīng)跟蹤了遠(yuǎn)程分支,那么上述兩個(gè)部分就可以合并為一個(gè)
git pull
當(dāng)在本地修改提交后,我們可能需要把這些本地的提交推送到遠(yuǎn)程倉(cāng)庫(kù),這里就可以用git push命令,由于本地可以由多個(gè)遠(yuǎn)程倉(cāng)庫(kù),所以需要指定遠(yuǎn)程倉(cāng)庫(kù)的名字,并同時(shí)指定需要推的本地分支及需要推送到遠(yuǎn)程倉(cāng)庫(kù)的哪一個(gè)分支
git push <remote> <local-branch>:<remote-branch>
如果本地分支與遠(yuǎn)程分支同名,命令可以更簡(jiǎn)單
git push <remote> <branch-name> 等價(jià)于 git push <remote> refs/heads/<branch-name>:refs/for/<branch-name>
如果本地分支的名字為空,可以刪除遠(yuǎn)程分支。
前面說(shuō)過(guò)可以有不止一個(gè)遠(yuǎn)程分支f,添加遠(yuǎn)程分支的方法為
git remote add <short-name> <url>
作為一個(gè)版本控制工具,針對(duì)某一時(shí)間點(diǎn)的某一版本打tag的功能是必不可少的,要查看tag也非常簡(jiǎn)單,查看tag使用如下命令
git tag
參數(shù)"-l"可以對(duì)tag進(jìn)行過(guò)濾
git tag -l "v1.1.*"
Git 使用的標(biāo)簽有兩種類型:輕量級(jí)的(lightweight)和含附注的(annotated)。輕量級(jí)標(biāo)簽就像是個(gè)不會(huì)變化的分支,實(shí)際上它就是個(gè)指向特定提交對(duì)象的引用。而含附注標(biāo)簽,實(shí)際上是存儲(chǔ)在倉(cāng)庫(kù)中的一個(gè)獨(dú)立對(duì)象,它有自身的校驗(yàn)和信息,包含著標(biāo)簽的名字,電子郵件地址和日期,以及標(biāo)簽說(shuō)明,標(biāo)簽本身也允許使用 GNU Privacy Guard (GPG) 來(lái)簽署或驗(yàn)證。
輕量級(jí)標(biāo)簽只需在git tag后加上tag的名字,如果tag名字
git tag <tag_name>
含附注的標(biāo)簽需要加上參數(shù)-a(annotated),同時(shí)加上-m跟上標(biāo)簽的說(shuō)明
git tag -a <tag_name> -m "<tag_description>"
如果你有自己的私鑰,還可以用 GPG 來(lái)簽署標(biāo)簽,只需要把之前的 -a
改為 -s(signed)
查看標(biāo)簽的內(nèi)容用
git show <tag_name>
驗(yàn)證已簽署的標(biāo)簽用-v(verify)
git tag -v <tag_name>
有時(shí)在某一個(gè)版本忘記打tag了,可以在后期再補(bǔ)上,只需在打tag時(shí)加上commit-id
要將tag推送到遠(yuǎn)程服務(wù)器上,可以用
git push <remote> <tag_name>
或者可以用下面的命令推送所有的tag
git push <remote> --tags
使用"git config"可以配置Git的環(huán)境變量,這些變量可以存放在以下三個(gè)不同的地方:
/etc/gitconfig
文件:系統(tǒng)中對(duì)所有用戶都普遍適用的配置。若使用 git config
時(shí)用 --system
選項(xiàng),讀寫(xiě)的就是這個(gè)文件。~/.gitconfig
文件:用戶目錄下的配置文件只適用于該用戶。若使用 git config
時(shí)用 --global
選項(xiàng),讀寫(xiě)的就是這個(gè)文件。.git/config
文件):這里的配置僅僅針對(duì)當(dāng)前項(xiàng)目有效。每一個(gè)級(jí)別的配置都會(huì)覆蓋上層的相同配置,所以 .git/config
里的配置會(huì)覆蓋/etc/gitconfig
中的同名變量。 在 Windows 系統(tǒng)上,Git 會(huì)找尋用戶主目錄下的 .gitconfig
文件。主目錄即 $HOME
變量指定的目錄,一般都是 C:\Documents and Settings\$USER
。此外,Git 還會(huì)嘗試找尋 /etc/gitconfig
文件,只不過(guò)看當(dāng)初 Git 裝在什么目錄,就以此作為根目錄來(lái)定位。
最基礎(chǔ)的配置是配置git的用戶,用來(lái)標(biāo)識(shí)作者的身份
git config --global user.name <name>
git config --global user.email <email>
文本編輯器也可以配置,比如在git commit的時(shí)候就會(huì)調(diào)用我們?cè)O(shè)置的文本編輯器
git config --global core.editor vim
另一個(gè)常用的是diff工具,比如我們想用可視化的對(duì)比工具
git config --global merge.tool meld
要查看所有的配置,可以用
git config --list
或者可以在git config后加上配置項(xiàng)的名字查看具體項(xiàng)的配置
git config user.name
作為一個(gè)懶人,雖然checkout、status等命令只是一個(gè)單詞,但是還是嫌太長(zhǎng)了,我們還可以給命令設(shè)置別名如
git config --global alias.co checkout
這樣git co就等于git checkout
前面說(shuō)地,git配置項(xiàng)都保存在那3個(gè)文件里,可以直接打開(kāi)相應(yīng)的配置文件查看配置,也可以直接修改這些配置文件來(lái)配置git,想刪除某一個(gè)配置,直接刪除相應(yīng)的行就行了
關(guān)于GIT各命令的說(shuō)明可以查看相關(guān)幫助文檔,通過(guò)以下方法:
git help <command>或git <command> --help
repo start <topic_name>
開(kāi)啟一個(gè)新的主題,其實(shí)就是每個(gè)Project都新建一個(gè)分支。
repo init -u <url> [OPTIONS]
在當(dāng)前目錄下初始化repo,會(huì)在當(dāng)前目錄生生成一個(gè).repo目錄,像Git Project下的.git一樣,-u指定url,可以加參數(shù)-m指定manifest文件,默認(rèn)是default.xml,.repo/manifests保存manifest文件。.repo/projects下有所有的project的數(shù)據(jù)信息,repo是一系列g(shù)it project的集合,每個(gè)git project下的.git目錄中的refs等目錄都是鏈接到.repo/manifests下的。
repo manifest
可以根據(jù)當(dāng)前各Project的版本信息生成一個(gè)manifest文件
repo sync [PROJECT1...PROJECTN]
同步Code。
repo status
查看本地所有Project的修改,在每個(gè)修改的文件前有兩個(gè)字符,第一個(gè)字符表示暫存區(qū)的狀態(tài)。
- | no change | same in HEAD and index |
A | added | not in HEAD, in index |
M | modified | in HEAD, modified in index |
D | deleted | in HEAD, not in index |
R | renamed | not in HEAD, path changed in index |
C | copied | not in HEAD, copied from another in index |
T | mode changed | same content in HEAD and index, mode changed |
U | unmerged | conflict between HEAD and index; resolution required |
每二個(gè)字符表示工作區(qū)的狀態(tài)
letter | meaning | description |
---|---|---|
- | new/unknown | not in index, in work tree |
m | modified | in index, in work tree, modified |
d | deleted | in index, not in work tree |
repo prune <topic>
刪除已經(jīng)merge的分支
repo abandon <topic>
刪除分支,無(wú)論是否merged
repo branch或repo branches
查看所有分支
repo diff
查看修改
repo upload
上傳本地提交至服務(wù)器
repo forall [PROJECT_LIST]-c COMMAND
對(duì)指定的Project列表或所有Project執(zhí)行命令COMMAND,加上-p參數(shù)可打印出Project的路徑。
repo forall -c 'git reset --hard HEAD;git clean -df;git rebase --abort'
這個(gè)命令可以撤銷(xiāo)整個(gè)工程的本地修改。
說(shuō)明:文中關(guān)于Git的知識(shí)大多來(lái)自Pro-GIt,這本書(shū)感覺(jué)不錯(cuò),想學(xué)習(xí)的可以找來(lái)看:Pro-Git
版本控制是什么已不用在說(shuō)了,就是記錄我們對(duì)文件、目錄或工程等的修改歷史,方便查看更改歷史,備份以便恢復(fù)以前的版本,多人協(xié)作。。。
最原始的版本控制是純手工的版本控制:修改文件,保存文件副本。有時(shí)候偷懶省事,保存副本時(shí)命名比較隨意,時(shí)間長(zhǎng)了就不知道哪個(gè)是新的,哪個(gè)是老的了,即使知道新舊,可能也不知道每個(gè)版本是什么內(nèi)容,相對(duì)上一版作了什么修改了,當(dāng)幾個(gè)版本過(guò)去后,很可能就是下面的樣子了:
手工管理比較麻煩且混亂,所以出現(xiàn)了本地版本控制系統(tǒng),記錄文件每次的更新,可以對(duì)每個(gè)版本做一個(gè)快照,或是記錄補(bǔ)丁文件。比如RCS。
但是本地版本控制系統(tǒng)偏向于個(gè)人使用,或者多個(gè)使用的人必須要使用相同的設(shè)備,如果需要多人協(xié)作就不好辦了,于是,集中化的版本控制系統(tǒng)( Centralized Version Control Systems,簡(jiǎn)稱 CVCS )應(yīng)運(yùn)而生,比如Subversion,Perforce。
在CVCS中,所有的版本數(shù)據(jù)都保存在服務(wù)器上,一起工作的人從服務(wù)器上同步更新或上傳自己的修改。
但是,所有的版本數(shù)據(jù)都存在服務(wù)器上,用戶的本地設(shè)備就只有自己以前所同步的版本,如果不連網(wǎng)的話,用戶就看不到歷史版本,也無(wú)法切換版本驗(yàn)證問(wèn)題,或在不同分支工作。。
而且,所有數(shù)據(jù)都保存在單一的服務(wù)器上,有很大的風(fēng)險(xiǎn)這個(gè)服務(wù)器會(huì)損壞,這樣就會(huì)丟失所有的數(shù)據(jù),當(dāng)然可以定期備份。
針對(duì)CVCS的以上缺點(diǎn),出現(xiàn)了分布式版本控制系統(tǒng)( Distributed Version Control System,簡(jiǎn)稱 DVCS ),如GIT,Mercurial。
DVCS不是復(fù)制指定版本的快照,而是把所有的版本信息倉(cāng)庫(kù)全部同步到本地,這樣就可以在本地查看所有版本歷史,可以離線在本地提交,只需在連網(wǎng)時(shí)push到相應(yīng)的服務(wù)器或其他用戶那里。由于每個(gè)用戶那里保存的都是所有的版本數(shù)據(jù),所以,只要有一個(gè)用戶的設(shè)備沒(méi)有問(wèn)題就可以恢復(fù)所有的數(shù)據(jù)。
當(dāng)然,這增加了本地存儲(chǔ)空間的占用。
必須要了解GIT的原理,才能知道每個(gè)操作的意義是什么,才能更容易地理解在什么情況下用什么操作,而不是死記命令。當(dāng)然,第一步是要獲得一個(gè)GIT倉(cāng)庫(kù)。
有兩種獲得GIT倉(cāng)庫(kù)的方法,一是在需要用GIT管理的項(xiàng)目的根目錄執(zhí)行:
git init
執(zhí)行后可以看到,僅僅在項(xiàng)目目錄多出了一個(gè).git目錄,關(guān)于版本等的所有信息都在這個(gè)目錄里面。
另一種方式是克隆遠(yuǎn)程目錄,由于是將遠(yuǎn)程服務(wù)器上的倉(cāng)庫(kù)完全鏡像一份至本地,而不是取某一個(gè)特定版本,所以用clone而不是checkout:
git clone <url>
記錄版本信息的方式主要有兩種:
GIT采用第一種方式。像Subversion和Perforce等版本控制系統(tǒng)都是記錄文件每個(gè)版本之間的差異,這就需要對(duì)比文件兩版本之間的具體差異,但是GIT不關(guān)心文件兩個(gè)版本之間的具體差別,而是關(guān)心文件的整體是否有改變,若文件被改變,在添加提交時(shí)就生成文件新版本的快照,而判斷文件整體是否改變的方法就是用SHA-1算法計(jì)算文件的校驗(yàn)和。
GIT能正常工作完全信賴于這種SHA-1校驗(yàn)和,當(dāng)一個(gè)文件的某一個(gè)版本被記錄之后會(huì)生成這個(gè)版本的一個(gè)快照,但是一樣要能引用到這個(gè)快照,GIT中對(duì)快照的引用,對(duì)每個(gè)版本的記錄標(biāo)識(shí)全是通過(guò)SHA-1校驗(yàn)和來(lái)實(shí)現(xiàn)的。
當(dāng)一個(gè)文件被改變時(shí),它的校驗(yàn)和一定會(huì)被改變(理論上存在兩個(gè)文件校驗(yàn)和相同,但機(jī)率小到可以忽略不計(jì)),GIT就以此判斷文件是否被修改,及以些記錄不同版本。
在工作目錄的文件可以處于不同的狀態(tài),比如說(shuō)新添加了一個(gè)文件,GIT發(fā)覺(jué)了這個(gè)文件,但這個(gè)文件是否要納入GIT的版本控制還是要由我們自己決定,比如編譯生成的中間文件,我們肯定不想納入版本控制。下面就來(lái)看下文件狀態(tài)。
版本控制就是對(duì)文件的版本控制,對(duì)于Linux來(lái)說(shuō),設(shè)備,目錄等全是文件,要對(duì)文件進(jìn)行修改、提交等操作,首先要知道文件當(dāng)前在什么狀態(tài),不然可能會(huì)提交了現(xiàn)在還不想提交的文件,或者要提交的文件沒(méi)提交上。
GIT倉(cāng)庫(kù)所在的目錄稱為工作目錄,這個(gè)很好理解,我們的工程就在這里,工作時(shí)也是在這里做修改。
在工作目錄中的文件被分為兩種狀態(tài),一種是已跟蹤狀態(tài)(tracked),另一種是未跟蹤狀態(tài)(untracked)。只有處于已跟蹤狀態(tài)的文件才被納入GIT的版本控制。如下圖:
當(dāng)我們往工作目錄添加一個(gè)文件的時(shí)候,這個(gè)文件默認(rèn)是未跟蹤狀態(tài)的,我們肯定不希望編譯生成的一大堆臨時(shí)文件默認(rèn)被跟蹤還要我們每次手動(dòng)將這些文件清除出去。用以下命令可以跟蹤文件:
git add <file>
上圖中右邊3個(gè)狀態(tài)都是已跟蹤狀態(tài),其中的灰色箭頭只表示untracked<-->tracked的轉(zhuǎn)換而不是untracked<-->unmodified的轉(zhuǎn)換,新添加的文件肯定算是被修改過(guò)的。那么,staged狀態(tài)又是什么呢?這就要搞清楚GIT的三個(gè)工作區(qū)域:本地?cái)?shù)據(jù)(倉(cāng)庫(kù))目錄,工作目錄,暫存區(qū),如下圖所示:
git directory就是我們的本地倉(cāng)庫(kù).git目錄,里面保存了所有的版本信息等內(nèi)容。
working driectory,工作目錄,就是我們的工作目錄,其中包括未跟蹤文件及已跟蹤文件,而已跟蹤文件都是從git directory取出來(lái)的文件的某一個(gè)版本或新跟蹤的文件。
staging area,暫存區(qū),不對(duì)應(yīng)一個(gè)具體目錄,其時(shí)只是git directory中的一個(gè)特殊文件。
當(dāng)我們修改了一些文件后,要將其放入暫存區(qū)然后才能提交,每次提交時(shí)其實(shí)都是提交暫存區(qū)的文件到git倉(cāng)庫(kù),然后清除暫存區(qū)。而checkout某一版本時(shí),這一版本的文件就從git倉(cāng)庫(kù)取出來(lái)放到了我們的工作目錄。
那么,我們?cè)趺粗喇?dāng)前工作目錄的狀態(tài)呢?哪些文件已被暫存?有哪些未跟蹤的文件?哪些文件被修改了?所有這些只需要一個(gè)命令,git status,如下圖所示:
GIT在這一點(diǎn)做得很好,在輸出每個(gè)文件狀態(tài)的同時(shí)還說(shuō)明了怎么操作,像上圖就有怎么暫存、怎么跟蹤文件、怎么取消暫存的說(shuō)明。
在上圖中我們可以很清楚地看到,filea未跟蹤,fileb已被暫存(changes to be committed),但是怎么還有一個(gè)fileb是modified但unstaged呢?這是因?yàn)楫?dāng)我們暫存一從此文件時(shí),暫存的是那一文件當(dāng)時(shí)的版本,當(dāng)暫存后再次修改了這個(gè)文件后就會(huì)提示這個(gè)文件暫存后的修改是未被暫存的。
接下來(lái)我們就看怎么暫存文件,其實(shí)也很簡(jiǎn)單,從上圖中可以看到GIT已經(jīng)提示我們了:use "git add <file>..." to update what will be committed,通過(guò)
git add <file>...
就可以暫存文件,跟蹤文件同樣是這一個(gè)命令。在這個(gè)命令中可以使用glob模式匹配,比如"file[ab]",也可以使用"git add ."添加當(dāng)前目錄下的所有文件。
取消暫存文件是
git reset HEAD <file>...
若修改了一個(gè)文件想還原修改可用
git checkout -- <file>...
當(dāng)我們修改過(guò)一些文件之后,我們可能想查看我們都修改了什么東西,用"git status"只能查看對(duì)哪些文件做了改動(dòng),如果要看改動(dòng)了什么,可以用:
git diff
比如下圖:
---a表示修改之前的文件,+++b表示修改后的文件,上圖表示在fileb的第一行后添加了一行"bb",原來(lái)文件的第一行擴(kuò)展為了修改后的1、2行。
但是,前面我們明明用"git status"看到filesb做了一些修改后暫存了,然后又修改了fileb,理應(yīng)有兩次修改的,怎么只有一個(gè)?
因?yàn)?git diff"顯示的是文件修改后還沒(méi)有暫存起來(lái)的內(nèi)容,那如果要比較暫存區(qū)的文件與之前已經(jīng)提交過(guò)的文件呢,畢竟實(shí)際提交的是暫存區(qū)的內(nèi)容,可以用以下命令:
/dev/null表示之前沒(méi)有提交過(guò)這一個(gè)文件,這是將是第一次提交,用:
git diff --staged
是等效的,但GIT的版本要大于1.6.1。
再次執(zhí)行"git add"將覆蓋暫存區(qū)的內(nèi)容。
如果有一些部件我們不想納入版本控制,也不想在每次"git status"時(shí)看到這些文件的提示,或者很多時(shí)候我們?yōu)榱朔奖銜?huì)使用"git add ."添加所有修改的文件,這時(shí)就會(huì)添加上一些我們不想添加的文件,怎么忽略這些文件呢?
GIT當(dāng)然提供了方法,只需在主目錄下建立".gitignore"文件,此文件有如下規(guī)則:
比如:
# 此為注釋 – 將被 Git 忽略
*.a # 忽略所有 .a 結(jié)尾的文件
!lib.a # 但 lib.a 除外
/TODO # 僅僅忽略項(xiàng)目根目錄下的 TODO 文件,不包括 subdir/TODO
build/ # 忽略 build/ 目錄下的所有文件
doc/*.txt # 會(huì)忽略 doc/notes.txt 但不包括 doc/server/arch.txt
當(dāng)我們要?jiǎng)h除一個(gè)文件時(shí),我們可能就直接用GUI刪除或者直接rm [file]了,但是看圖:
我們需要將文件添加到暫存區(qū)才能提交,而移除文件后是無(wú)法添加到暫存區(qū)的,那么怎么移除一個(gè)文件讓GIT不再將其納入版本控制呢?上圖中GIT已經(jīng)給出了說(shuō)明:
git rm <file>...
執(zhí)行以上命令后提交就可以了,有時(shí)我們只是想將一些文件從版本控制中剔除出去,但仍保留這些文件在工作目錄中,比如我們一不小心將編譯生成的中間文件納入了版本控制,想將其從版本控制中剔除出去但在工作目錄中保留這些文件(不然再次編譯可要花費(fèi)更多時(shí)間了),這時(shí)只需要添加"--cached"參數(shù)。
如果我們之前不是通過(guò)"git rm"刪除了很多文件呢?比如說(shuō)通過(guò)patch或者通過(guò)GUI,如果這些文件命名沒(méi)有規(guī)則,一個(gè)一個(gè)地執(zhí)行"git rm"會(huì)搞死人的,這時(shí)可以用以下命令:
和移除文件一樣,移動(dòng)文件不可以通過(guò)GUI直接重命令或用"mv"命令,而是要用"git mv",不然同移除文件一樣你會(huì)得到如下結(jié)果:
如果要重命名文件可以使用
git mv old_name new_name
這個(gè)命令等效于
mv old_name new_name
git rm old_name
git add new_name
使用git add -i可以開(kāi)啟交互式暫存,如圖所示,系統(tǒng)會(huì)列出一個(gè)功能菜單讓選擇將要執(zhí)行的操作。
git clean [options] 一般會(huì)加上參數(shù)-df,-d表示包含目錄,-f表示強(qiáng)制清除。
可能會(huì)遇到這樣的情況,你正在一個(gè)分支上進(jìn)行一個(gè)特性的開(kāi)發(fā),或者一個(gè)Bug的修正,但是這時(shí)突然有其他的事情急需處理,這時(shí)該怎么辦?不可能就在這個(gè)工作進(jìn)行到一半的分支上一起處理,先把修改的Copy出去?太麻煩了。這種情況下就要用到Stashing了。假如我們現(xiàn)在的工作目錄是這樣子的
$ git status# On branch master# Changes to be committed:#(use "git reset HEAD <file>..." to unstage)##modified:index.html## Changed but not updated:#(use "git add <file>..." to update what will be committed)##modified:lib/simplegit.rb
此時(shí)如果想切換分支就可以執(zhí)行以下命令
$ git stashSaved working directory and index state "WIP on master: 049d078 added the index file"HEAD is now at 049d078 added the index file(To restore them type "git stash apply")
這時(shí)你會(huì)發(fā)現(xiàn)你的工作目錄變得很干凈了,就可以隨意切分支進(jìn)行其他事情的處理了。
我們可能不只一次進(jìn)行"git stash",通過(guò)以下命令可以查看所有stash列表
$ git stash liststash@{0}: WIP on master: 049d078 added the index filestash@{1}: WIP on master: c264051... Revert "added file_size"
當(dāng)緊急事情處理完了,需要重新回來(lái)這里進(jìn)行原來(lái)的工作時(shí),只需把Stash區(qū)域的內(nèi)容取出來(lái)應(yīng)用到當(dāng)前工作目錄就行,命令就是
git stash apply
如果不基參數(shù)就應(yīng)用最新的stash,或者可以指定stash的名字,如:stash@{1},可能通過(guò)
git stash show
顯示stash的內(nèi)容具體是什么,同git stash apply一樣,可以選擇指定stash的名字。
git stash apply之后再git stash list會(huì)發(fā)現(xiàn),apply后的stash還在stash列表中,如果要將其從stash列表中刪除可以用
git stash drop
丟棄這個(gè)stash,stash的命令參數(shù)都可選擇指定stash名字,否則就是最新的stash。
一般情況下apply stash后應(yīng)該就可以把它從stash列表刪除了,先apply再drop還是比較繁瑣的,使用以下一條命令就可以同時(shí)完成這兩個(gè)操作
git stash pop
如果我們執(zhí)行g(shù)it stash時(shí)工作目錄的狀態(tài)是部分文件已經(jīng)加入了暫存區(qū),部分文件沒(méi)有,當(dāng)我們執(zhí)行g(shù)it stash apply之后會(huì)發(fā)現(xiàn)所有文件都變成了未暫存的,如果想維持原來(lái)的樣子操持原來(lái)暫存的文件仍然是暫存狀態(tài),可以加上--index參數(shù)
git stash apply --index
還有這么一種情況,我們把原來(lái)的修改stash了,然后修復(fù)了其他一些東西并進(jìn)行了提交,但是,這些提交的文件有些在之前已經(jīng)被stash了,那么git stash apply時(shí)就很可能會(huì)遇到?jīng)_突,這種情況下就可以在stash時(shí)所以提交的基礎(chǔ)上新建一個(gè)分支,然后再apply stash,當(dāng)然,這兩個(gè)步驟有一人簡(jiǎn)單的完成方法
git stash branch <branch name>
了解了文件的狀態(tài),我們對(duì)文件進(jìn)行了必要的修改后,就要把我們所做的修改放入版本庫(kù)了,這樣以后我們就可以在需要的時(shí)候恢復(fù)到現(xiàn)在的版本,而要恢復(fù)到某一版,一般需要查看版本的歷史。
提交很簡(jiǎn)單,直接執(zhí)行"git commit"。執(zhí)行g(shù)it commit后會(huì)調(diào)用默認(rèn)的或我們?cè)O(shè)置的編譯器要我們填寫(xiě)提示說(shuō)明,但是提交說(shuō)明最好按GIT要求填寫(xiě):第一行填簡(jiǎn)單說(shuō)明,隔一行填寫(xiě)詳細(xì)說(shuō)明。因?yàn)榈谝恍性谝恍┣闆r下會(huì)被提取使用,比如查看簡(jiǎn)短提交歷史或向別人提交補(bǔ)丁,所以字符數(shù)不應(yīng)太多,40為好。下面看一下查看提交歷史。
查看提交歷史使用如下圖的命令
如圖所示,顯示了作者,作者郵箱,提交說(shuō)明與提交時(shí)間,"git log"可以使用放多參數(shù),比如:
僅顯示最新的1個(gè)log,用"-n"表示。
顯示簡(jiǎn)單的SHA-1值與簡(jiǎn)單提交說(shuō)明,oneline僅顯示提交說(shuō)明的第一行,所以第一行說(shuō)明最好簡(jiǎn)單點(diǎn)方便在一行顯示。
"git log --graph"以圖形化的方式顯示提交歷史的關(guān)系,這就可以方便地查看提交歷史的分支信息,當(dāng)然是控制臺(tái)用字符畫(huà)出來(lái)的圖形。
"git log"的更多參數(shù)可以查看命令幫助。
如果我們想跳過(guò)暫存區(qū)直接提交修改的文件,可以使用"-a"參數(shù),但要慎重,別一不小心提交了不想提交的文件
git commit -a
如果需要快捷地填寫(xiě)提交說(shuō)明可使用"-m"參數(shù)
git commit -m 'commit message'
如果我們提交過(guò)后發(fā)現(xiàn)有個(gè)文件改錯(cuò)了,或者只是想修改提交說(shuō)明,這時(shí)可以對(duì)相應(yīng)文件做出修改,將修改過(guò)的文件通過(guò)"git add"添加到暫存區(qū),然后執(zhí)行以下命令:
git commit --amend
然后修改提交說(shuō)明覆蓋上次提交,但只能重寫(xiě)最后一次提交。
通過(guò)衍合(rebase)可以修改多個(gè)提交的說(shuō)明,并可以重排提交歷史,拆分、合并提交,關(guān)于rebase在講到分支時(shí)再說(shuō),這里先看一下重排提交。
假設(shè)我們的提交歷史是這樣的:
如果我們想重排最后兩個(gè)提交的提交歷史,可以借助交互式rebase命令:
git rebase -i HEAD~2 或 git rebase -i 3366e1123010e7d67620ff86040a061ae76de0c8
HEAD~2表示倒數(shù)第三個(gè)提交,這條命令要指定要重排的最舊的提交的父提交,此處要重排Second commit與Third commit,所以要指定Initial commit的Commit ID。如圖所示:
注釋部分詳細(xì)說(shuō)明了每個(gè)選項(xiàng)的作用,如果我們想交互這兩個(gè)提交,只需把開(kāi)頭的這兩行交換下位置就OK了,交換位置后保存,然后看下提交歷史:
可以看到提交歷史已經(jīng)變了,而且最新的兩個(gè)提交的Commit ID變了,如果這些提交已經(jīng)push到了遠(yuǎn)程服務(wù)器,就不要用這個(gè)命令了。
如果要?jiǎng)h除某個(gè)提交,只需要?jiǎng)h除相應(yīng)的行就可以了,而要修改某個(gè)提交的提交說(shuō)明的話,只需要把相應(yīng)行的pick改為reward。
合并提交也很簡(jiǎn)單,從注釋中的說(shuō)明看,只需要把相應(yīng)的行的pick改為squash就可以把這個(gè)提交全并到它上一行的提交中。
至于拆分提交,由于Git不可能知道你要做哪里把某一提交拆分開(kāi),把以我們就需要讓Git在需要拆分的提交處停下來(lái),由我們手動(dòng)修改提交,這時(shí)要把pick改為edit,這樣Git在處理到這個(gè)提交時(shí)會(huì)停下來(lái),此時(shí)我們就可以進(jìn)行相應(yīng)的修改并多次提交來(lái)拆分提交。
前面說(shuō)了刪除提交的方法,但是如果是多人合作的話,如果某個(gè)提交已經(jīng)Push到遠(yuǎn)程倉(cāng)庫(kù),是不可以用那種方法刪除提交的,這時(shí)就要撤銷(xiāo)提交
git revert <commit-id>
這條命令會(huì)把指定的提交的所有修改回滾,并同時(shí)生成一個(gè)新的提交。
git reset會(huì)修改HEAD到指定的狀態(tài),用法為
git reset [options] <commit>
這條命令會(huì)使HEAD提向指定的Commit,一般會(huì)用到3個(gè)參數(shù),這3個(gè)參數(shù)會(huì)影響到工作區(qū)與暫存區(qū)中的修改:
當(dāng)與別人和作開(kāi)發(fā)時(shí),會(huì)向別人貢獻(xiàn)代碼或者接收別人貢獻(xiàn)的代碼,有時(shí)候可能不想完全Merge別人貢獻(xiàn)的代碼,只想要其中的某一個(gè)提交,這時(shí)就可以使用cherry-pick了。就一個(gè)命令
git cherry-pick <commit-id>
這條命令可以修改整個(gè)歷史,如從所有歷史中刪除某個(gè)文件相關(guān)的信息,全局性地更換電子郵件地址。
分支被稱之為GIT最強(qiáng)大的特性,因?yàn)樗浅5剌p量級(jí),如果用Perforce等工具應(yīng)該知道,創(chuàng)建分支就是克隆原目錄的一個(gè)完整副本,對(duì)于大型工程來(lái)說(shuō),太費(fèi)時(shí)費(fèi)力了,而對(duì)于GIT來(lái)說(shuō),可以在瞬間生成一個(gè)新的分支,無(wú)論工程的規(guī)模有多大,因?yàn)镚IT的分支其實(shí)就是一指針而已。在了解GIT分支之前,應(yīng)該先了解GIT是如何存儲(chǔ)數(shù)據(jù)的。
前面說(shuō)過(guò),GIT存儲(chǔ)的不是文件各個(gè)版本的差異,而是文件的每一個(gè)版本存儲(chǔ)一個(gè)快照對(duì)象,然后通過(guò)SHA-1索引,不只是文件,包換每個(gè)提交都是一個(gè)對(duì)象并通過(guò)SHA-1索引。無(wú)論是文本文件,二進(jìn)制文件還是提交,都是GIT對(duì)象。
每個(gè)對(duì)象(object) 包括三個(gè)部分:類型,大小和內(nèi)容。大小就是指內(nèi)容的大小,內(nèi)容取決于對(duì)象的類型,有四種類型的對(duì)象:"blob"、"tree"、 "commit" 和"tag"。
比如說(shuō)我們執(zhí)行了以下代碼進(jìn)行了一次提交:
$ git add README test.rb LICENSE2
$ git commit -m 'initial commit of my project'
現(xiàn)在,Git 倉(cāng)庫(kù)中有五個(gè)對(duì)象:三個(gè)表示文件快照內(nèi)容的 blob 對(duì)象;一個(gè)記錄著目錄樹(shù)內(nèi)容及其中各個(gè)文件對(duì)應(yīng) blob 對(duì)象索引的 tree 對(duì)象;以及一個(gè)包含指向 tree 對(duì)象(根目錄)的索引和其他提交信息元數(shù)據(jù)的 commit 對(duì)象。概念上來(lái)說(shuō),倉(cāng)庫(kù)中的各個(gè)對(duì)象保存的數(shù)據(jù)和相互關(guān)系看起來(lái)如下圖:
如果進(jìn)行多次提交,倉(cāng)庫(kù)的歷史會(huì)像這樣:
所謂的GIT分支,其實(shí)就是一個(gè)指向某一個(gè)Commit對(duì)象的指針,像下面這樣,有兩個(gè)分支,master與testing:
而我們?cè)趺粗喇?dāng)前在哪一個(gè)分支呢?其實(shí)就是很簡(jiǎn)單地使用了一個(gè)名叫HEAD的指針,如上圖所示。HEAD指針的值可以為一個(gè)SHA-1值或是一個(gè)引用,看以下例子:
git的所有版本信息都保存了Working Directory下的.git目錄,而HEAD指針就保存在.git目錄下,如上圖所有,目前為止已經(jīng)有3個(gè)提交,通過(guò)查看HEAD的值可以看到我們當(dāng)前在master分支:refs/heads/master,當(dāng)我們通過(guò)git checkout取出某一特定提交后,HEAD的值就是成了我們checkout的提交的SHA-1值。
記錄我們當(dāng)前的位置很簡(jiǎn)單,就是能過(guò)HEAD指針,HEAD指向某一提交的SHA-1值或是某一分支的引用。
git branch <branch-name>
有時(shí)需要在新建分支后直接切換到新建的分支,可以直接用checkout的-b選項(xiàng)
git checkout -b <branch-name>
git branch -d <branch-name>
如果在指定的分支有一些unmerged的提交,刪除分支會(huì)失敗,這里可以使用-D參數(shù)強(qiáng)制刪除分支。
git branch -D <branch-name>
檢出某一分支或某一提交是同一個(gè)命令
git checkout <branch-name> | <commit>
當(dāng)我們新建一個(gè)分支進(jìn)行開(kāi)發(fā),并提交了幾次更新后,感覺(jué)是時(shí)候?qū)⑦@個(gè)分支的內(nèi)容合回主線了,這是就可以取出主線分支,然后把分支的更新merge回來(lái):
git checkout master
git merge testing
如果master分支是testing分支的直接上游,即從master延著testing分支的提交歷史往前走可以直接走到testing分支的最新提交,那么系統(tǒng)什么也不需要做,只需要改變master分支的指針即可,這被稱之為"Fast Forward"。
但是,一般情況是這樣的,你取出了最新的master分支,比如說(shuō)master分支最新的提交是C2(假設(shè)共3次提交C0<-C1<-C2),在此基礎(chǔ)上你新建了分支,當(dāng)你在分支上提交了C3、C5后想將br1時(shí)merge回master時(shí),你發(fā)現(xiàn)已經(jīng)有其他人提交了C4,這時(shí)候就不能直接修改master的指針了,不然會(huì)丟失別人的提交,這個(gè)時(shí)候就需要將你新建分支時(shí)master所在的提交(C2)后的修改(C4),與你新建分支后在分支上的修改(C3、C5)做合并,將合并后的結(jié)果作為一個(gè)新的提交提交到master,GIT可以自動(dòng)推導(dǎo)出應(yīng)該基于哪個(gè)提交進(jìn)行合并(C2),如果沒(méi)有沖突,系統(tǒng)會(huì)自動(dòng)提交新的提交,如果有沖突,系統(tǒng)會(huì)提示你解決沖突,當(dāng)沖突解決后,你就可以將修改加入暫存區(qū)并提交。提交歷史類似下面這樣(圖來(lái)自Pro-Git):
merge后的提交是按時(shí)間排序的,比如下圖,我們?cè)趓ename提交處新建分支test,在test上提交Commit from branch test,然后回到master提交commit in master after committing in branch,再將test分支merge進(jìn)master,這時(shí)看提交提交歷史,Commit from branch test是在commit in master...之前的,盡管在master上我們是在rename的基礎(chǔ)上提交的commit in master...而GIT會(huì)在最后添加一個(gè)新的提交(Merge branch 'test')表示我們?cè)诖颂帉⒁粋€(gè)分支merge進(jìn)來(lái)了。這種情況會(huì)有一個(gè)問(wèn)題,比如說(shuō)在rename提交處某人A從你這里Copy了一個(gè)GIT倉(cāng)庫(kù),然后你release了一個(gè)patch(通過(guò)git format-patch)給A,這時(shí)候test分支還沒(méi)有merge進(jìn)來(lái),所以patch中只包含提交:commit in master...然后你把test分支merge了進(jìn)來(lái)又給了A一個(gè)patch,這個(gè)patch會(huì)包含提交:Commit from branch test,而這個(gè)patch是以rename為base的,如果commit in master...和Commit from branch test修改了相同的文件,則第二次的patch可能會(huì)打不上去,因?yàn)橐詒ename為base的patch可能在新的Code上找不到在哪個(gè)位置應(yīng)用修改。
有兩種方法將一個(gè)分支的改動(dòng)合并進(jìn)另一個(gè)分支,一個(gè)就是前面所說(shuō)的分支合并,另一個(gè)就是分支衍合,這兩種方式有什么區(qū)別呢?
分支合并(merge)是將兩個(gè)分支的改動(dòng)合并到一起,并生成一個(gè)新的提交,提交歷史是按時(shí)間排序的,即我們實(shí)際提交的順序,通過(guò)git log --graph或一些圖形化工具,可能很明顯地看到分支的合并歷史,如果分支比較多就很混亂,而且如果以功能點(diǎn)新建分支,等功能點(diǎn)完成后合回主線,由于merge后提交是按提交時(shí)間排序的,提交歷史就比較亂,各個(gè)功能點(diǎn)的提交混雜在一起,還可能遇到上面提到的patch問(wèn)題。
而分支衍合(rebase)是找到兩個(gè)分支的共同祖先提交,將要被rebase進(jìn)來(lái)的分支的提交依次在要被rebase到的分支上重演一遍,即回到兩個(gè)分支的共同祖先,將branch(假如叫experiment)的每次提交的差異保存到臨時(shí)文件里,然后切換到要衍合入的分支(假如是master),依次應(yīng)用補(bǔ)丁文件。experiment上有幾次提交,在master就生成幾次新的提交,而且是連在一起的,這樣合進(jìn)主線后每個(gè)功能點(diǎn)的提交就都在一起,而且提交歷史是線性的
對(duì)比merge與rebase的提交歷史會(huì)是下圖這樣的(圖來(lái)自Pro-GIt):
(merge)
(rebase)
rebase后C3提交就不存在了,取而代之的是C3',而master也成為了experiment的直接上游,只需一次Fast Forward(git merge)后master就指向了最新的提交,就可以刪除experiment分支了。
git rebase --onto master server client
這條命令的意思是:檢出server分支與client分支共同祖先之后client上的變化,然后在master上重演一遍。
HEAD表示當(dāng)前所在的提交,如果要查看當(dāng)前提交父提交呢?git log查看提交歷史,顯然太麻煩了,而且輸入一長(zhǎng)串的Commit-ID也不是一個(gè)令人愉悅的事。這時(shí)可借助兩個(gè)特殊的符號(hào):~與^。
^ 表示指定提交的父提交,這個(gè)提交可能由多個(gè)交提交,^之后跟上數(shù)字表示第幾個(gè)父提交,不跟數(shù)字等同于^1。
~n相當(dāng)于n個(gè)^,比如~3=^^^,表示第一個(gè)父提交的第一個(gè)父提交的第一個(gè)父提交。
遠(yuǎn)程分支以(遠(yuǎn)程倉(cāng)庫(kù)名)/(分支名)命令,遠(yuǎn)程分支在本地?zé)o法移動(dòng)修改,當(dāng)我們clone一個(gè)遠(yuǎn)程倉(cāng)庫(kù)時(shí)會(huì)自動(dòng)在本地生成一個(gè)名叫original的遠(yuǎn)程倉(cāng)庫(kù),下載遠(yuǎn)程倉(cāng)庫(kù)的所有數(shù)據(jù),并新建一個(gè)指向它的分支original/master,但這個(gè)分支我們是無(wú)法修改的,所以需要在本地重新一個(gè)分支,比如叫master,并跟蹤遠(yuǎn)程分支。
Clone了遠(yuǎn)程倉(cāng)庫(kù)后,我們還會(huì)在本地新建其他分支,并且可能也想跟蹤遠(yuǎn)程分支,這時(shí)可以用以下命令:
git checkout -b [branch_name] --track|-t <remote>/<remote-banch>
和新建分支的方法一樣,只是加了一個(gè)參數(shù)--track或其縮寫(xiě)形式-t,可以指定本地分支的名字,如果不指定就會(huì)被命名為remote-branch。
要拉取某個(gè)遠(yuǎn)程倉(cāng)庫(kù)的數(shù)據(jù),可以用git fetch:
git fetch <remote>
當(dāng)拉取到了遠(yuǎn)程倉(cāng)庫(kù)的數(shù)據(jù)后只是把數(shù)據(jù)保存到了一個(gè)遠(yuǎn)程分支中,如original/master,而這個(gè)分支的數(shù)據(jù)是無(wú)法修改的,此時(shí)我們可以把這個(gè)遠(yuǎn)程分支的數(shù)據(jù)合并到我們當(dāng)前分支
git merge <remote>/<remote-branch>
如果當(dāng)前分支已經(jīng)跟蹤了遠(yuǎn)程分支,那么上述兩個(gè)部分就可以合并為一個(gè)
git pull
當(dāng)在本地修改提交后,我們可能需要把這些本地的提交推送到遠(yuǎn)程倉(cāng)庫(kù),這里就可以用git push命令,由于本地可以由多個(gè)遠(yuǎn)程倉(cāng)庫(kù),所以需要指定遠(yuǎn)程倉(cāng)庫(kù)的名字,并同時(shí)指定需要推的本地分支及需要推送到遠(yuǎn)程倉(cāng)庫(kù)的哪一個(gè)分支
git push <remote> <local-branch>:<remote-branch>
如果本地分支與遠(yuǎn)程分支同名,命令可以更簡(jiǎn)單
git push <remote> <branch-name> 等價(jià)于 git push <remote> refs/heads/<branch-name>:refs/for/<branch-name>
如果本地分支的名字為空,可以刪除遠(yuǎn)程分支。
前面說(shuō)過(guò)可以有不止一個(gè)遠(yuǎn)程分支f,添加遠(yuǎn)程分支的方法為
git remote add <short-name> <url>
作為一個(gè)版本控制工具,針對(duì)某一時(shí)間點(diǎn)的某一版本打tag的功能是必不可少的,要查看tag也非常簡(jiǎn)單,查看tag使用如下命令
git tag
參數(shù)"-l"可以對(duì)tag進(jìn)行過(guò)濾
git tag -l "v1.1.*"
Git 使用的標(biāo)簽有兩種類型:輕量級(jí)的(lightweight)和含附注的(annotated)。輕量級(jí)標(biāo)簽就像是個(gè)不會(huì)變化的分支,實(shí)際上它就是個(gè)指向特定提交對(duì)象的引用。而含附注標(biāo)簽,實(shí)際上是存儲(chǔ)在倉(cāng)庫(kù)中的一個(gè)獨(dú)立對(duì)象,它有自身的校驗(yàn)和信息,包含著標(biāo)簽的名字,電子郵件地址和日期,以及標(biāo)簽說(shuō)明,標(biāo)簽本身也允許使用 GNU Privacy Guard (GPG) 來(lái)簽署或驗(yàn)證。
輕量級(jí)標(biāo)簽只需在git tag后加上tag的名字,如果tag名字
git tag <tag_name>
含附注的標(biāo)簽需要加上參數(shù)-a(annotated),同時(shí)加上-m跟上標(biāo)簽的說(shuō)明
git tag -a <tag_name> -m "<tag_description>"
如果你有自己的私鑰,還可以用 GPG 來(lái)簽署標(biāo)簽,只需要把之前的 -a
改為 -s(signed)
查看標(biāo)簽的內(nèi)容用
git show <tag_name>
驗(yàn)證已簽署的標(biāo)簽用-v(verify)
git tag -v <tag_name>
有時(shí)在某一個(gè)版本忘記打tag了,可以在后期再補(bǔ)上,只需在打tag時(shí)加上commit-id
要將tag推送到遠(yuǎn)程服務(wù)器上,可以用
git push <remote> <tag_name>
或者可以用下面的命令推送所有的tag
git push <remote> --tags
使用"git config"可以配置Git的環(huán)境變量,這些變量可以存放在以下三個(gè)不同的地方:
/etc/gitconfig
文件:系統(tǒng)中對(duì)所有用戶都普遍適用的配置。若使用 git config
時(shí)用 --system
選項(xiàng),讀寫(xiě)的就是這個(gè)文件。~/.gitconfig
文件:用戶目錄下的配置文件只適用于該用戶。若使用 git config
時(shí)用 --global
選項(xiàng),讀寫(xiě)的就是這個(gè)文件。.git/config
文件):這里的配置僅僅針對(duì)當(dāng)前項(xiàng)目有效。每一個(gè)級(jí)別的配置都會(huì)覆蓋上層的相同配置,所以 .git/config
里的配置會(huì)覆蓋/etc/gitconfig
中的同名變量。 在 Windows 系統(tǒng)上,Git 會(huì)找尋用戶主目錄下的 .gitconfig
文件。主目錄即 $HOME
變量指定的目錄,一般都是 C:\Documents and Settings\$USER
。此外,Git 還會(huì)嘗試找尋 /etc/gitconfig
文件,只不過(guò)看當(dāng)初 Git 裝在什么目錄,就以此作為根目錄來(lái)定位。
最基礎(chǔ)的配置是配置git的用戶,用來(lái)標(biāo)識(shí)作者的身份
git config --global user.name <name>
git config --global user.email <email>
文本編輯器也可以配置,比如在git commit的時(shí)候就會(huì)調(diào)用我們?cè)O(shè)置的文本編輯器
git config --global core.editor vim
另一個(gè)常用的是diff工具,比如我們想用可視化的對(duì)比工具
git config --global merge.tool meld
要查看所有的配置,可以用
git config --list
或者可以在git config后加上配置項(xiàng)的名字查看具體項(xiàng)的配置
git config user.name
作為一個(gè)懶人,雖然checkout、status等命令只是一個(gè)單詞,但是還是嫌太長(zhǎng)了,我們還可以給命令設(shè)置別名如
git config --global alias.co checkout
這樣git co就等于git checkout
前面說(shuō)地,git配置項(xiàng)都保存在那3個(gè)文件里,可以直接打開(kāi)相應(yīng)的配置文件查看配置,也可以直接修改這些配置文件來(lái)配置git,想刪除某一個(gè)配置,直接刪除相應(yīng)的行就行了
關(guān)于GIT各命令的說(shuō)明可以查看相關(guān)幫助文檔,通過(guò)以下方法:
git help <command>或git <command> --help
repo start <topic_name>
開(kāi)啟一個(gè)新的主題,其實(shí)就是每個(gè)Project都新建一個(gè)分支。
repo init -u <url> [OPTIONS]
在當(dāng)前目錄下初始化repo,會(huì)在當(dāng)前目錄生生成一個(gè).repo目錄,像Git Project下的.git一樣,-u指定url,可以加參數(shù)-m指定manifest文件,默認(rèn)是default.xml,.repo/manifests保存manifest文件。.repo/projects下有所有的project的數(shù)據(jù)信息,repo是一系列g(shù)it project的集合,每個(gè)git project下的.git目錄中的refs等目錄都是鏈接到.repo/manifests下的。
repo manifest
可以根據(jù)當(dāng)前各Project的版本信息生成一個(gè)manifest文件
repo sync [PROJECT1...PROJECTN]
同步Code。
repo status
查看本地所有Project的修改,在每個(gè)修改的文件前有兩個(gè)字符,第一個(gè)字符表示暫存區(qū)的狀態(tài)。
- | no change | same in HEAD and index |
A | added | not in HEAD, in index |
M | modified | in HEAD, modified in index |
D | deleted | in HEAD, not in index |
R | renamed | not in HEAD, path changed in index |
C | copied | not in HEAD, copied from another in index |
T | mode changed | same content in HEAD and index, mode changed |
U | unmerged | conflict between HEAD and index; resolution required |
每二個(gè)字符表示工作區(qū)的狀態(tài)
letter | meaning | description |
---|---|---|
- | new/unknown | not in index, in work tree |
m | modified | in index, in work tree, modified |
d | deleted | in index, not in work tree |
repo prune <topic>
刪除已經(jīng)merge的分支
repo abandon <topic>
刪除分支,無(wú)論是否merged
repo branch或repo branches
查看所有分支
repo diff
查看修改
repo upload
上傳本地提交至服務(wù)器
repo forall [PROJECT_LIST]-c COMMAND
對(duì)指定的Project列表或所有Project執(zhí)行命令COMMAND,加上-p參數(shù)可打印出Project的路徑。
repo forall -c 'git reset --hard HEAD;git clean -df;git rebase --abort'
這個(gè)命令可以撤銷(xiāo)整個(gè)工程的本地修改。
說(shuō)明:文中關(guān)于Git的知識(shí)大多來(lái)自Pro-GIt,這本書(shū)感覺(jué)不錯(cuò),想學(xué)習(xí)的可以找來(lái)看:Pro-Git
聯(lián)系客服