Git有三個工做區域,分別爲:工做目錄(Working Directory)、暫存區(Stage或Index)以及資源庫(Repository或Git Directory)。下圖是文件在這三個工做區域之間的關係:java
參考Pro Git一書,它給出了Git的幾個要點: * 直接快照,而非比較差別:Git與其餘版本管理系統的主要差異在於,Git只關心文件數據的總體是否發生了變化,而其餘多數版本管理系統則只關心文件內容的具體差別。Git並不保存文件先後變化的差別數據,更像是把變化的文件作一個快照,而後記錄在一個微型的文件系統中。每次提交更新時,會比較這個快照。若文件沒有變化,Git則只對上次保存的快照做一個連接。你能夠理解Git就是一個小型的文件系統。 * 近乎全部操做均可本地執行:無需多說,這自己就是分佈式版本管理系統的特徵。 * 時刻保持數據完整性:保存到Git前,全部數據都要進行內容的校驗和(checksum),並將該結果做爲數據的惟一標識。Git使用了SHA-1算法計算數據的校驗和,並將該結果做爲索引,而非文件名。git
* 多數操做僅添加數據算法
Pro Git一書認爲任何一個文件在Git內部能夠被分爲三種狀態:已提交(Committed)、已修改(Modified)和已暫存(Staged)。然而,這並不足以說明一個文件在不一樣的工做區域所展示的狀態。我認爲兩種狀態足以表達Git中的文件,即:未跟蹤(Untracked)和已跟蹤(Tracked)。而對於已跟蹤狀態,我又將其分爲:未修改的(Unmodified),Modified(已修改的),暫存的(Staged)和已提交的(committed)。下圖基本表達了個人思路:安全
這個圖表現了多種場景,知足了咱們在使用Git時耳濡目染的操做情形。服務器
場景1:暫存文件以及取消已暫存的文件分佈式
能夠參考上圖中上面部分黑色箭頭標示。當咱們經過git init在本地初始化了Git工做目錄後,新增了一個README.txt文件時,此時該文件處於Untracked狀態。接下來執行命令:fetch
- git add README.txt
add命令能夠暫存此文件,此時,狀態變動爲Staged狀態,被放到了Git暫存區中。若咱們要提交此文件到Git資源庫,就能夠執行git commit命令,文件狀態變爲committed。例如:3d
- git commit -m "first commit"
有時候,咱們但願取消已暫存的文件。例如說,我在工做目錄中增長了兩個文件,而後暫存了它們。後來發現其中一個文件並不須要在Git中管理,但願可以取消暫存。因爲此時的文件處於Staged狀態,咱們只須要刪掉Stage中對此文件的跟蹤便可。這時須要執行的命令是:指針
- git rm --cached README.txt
注意:此時取消暫存的文件歷來就未曾提交過,也便是說沒有在Git Repository留下過它的身影。這時的取消暫存實則是刪掉暫存的信息。與後面場景演示的取消暫存並不相同。code
場景2:修改已提交文件以及取消已暫存的內容
一旦文件被提交,就會在Git Repository造成提交記錄(以hash做爲鍵)。假若咱們此時push提交到遠程Git服務器,Git服務器應與本地庫保持一致。
如今,讓咱們看看圖中紅色箭頭展示的流程。咱們修改了已提交的README.txt文件,因而文件狀態就變動爲Modified。這部分修改的內容並無被放入暫存區,若要提交這次修改,就還須要再次執行git add命令,將此次新的修改放入到暫存區。這個流程包括後面的提交都與場景1類似。惟一不一樣的是「取消已暫存的內容」。
雖然一樣是取消暫存,但它與場景1是徹底不一樣的概念。場景1實則是要取消暫存區的文件,所以使用了git rm –cached,本質上講實際上是刪除。而這裏的取消,實際上是但願取消暫存區中已經被添加的修改內容,文件自己仍然保留在暫存區中。故而執行的命令爲:
- git reset HEAD README.txt
HEAD是何意呢?在Git中,HEAD是一個特別的指針,指向你正在工做的本地分支。當前分支就是master。以下圖所示:
而reset命令的意思是從新設置當前的HEAD指針到特定的狀態。因爲當前的README.txt尚未提交到master分支的Repository中。所以,這條命令實則就是將HEAD指向README.txt文件在當前master分支的Repository狀態,從而保證了對README.txt文件而言,暫存區與Repository的一致——取消了README.txt文件在暫存區的內容。
場景3:修改文件以及撤銷修改內容
再看圖中的綠色箭頭與藍色箭頭展示的流程。咱們不是初始化git工做目錄,而是經過git clone從遠程Repository克隆了項目,此時會在當前目錄創建git工做目錄。此時的文件所有處於Unmodified狀態。
如今,咱們修改文件,例如hello.java。一旦被修改,文件狀態就遷移到Modified狀態。假若須要暫存這次修改,甚至提交到Git Repository,則執行的流程與場景1相同(如藍色箭頭線所示)。
然而,咱們可能但願放棄這次修改,即不將修改的內容放入暫存區。這時,應執行checkout命令:
- git checkout -- hello.java
在執行checkout命令時要慎重。由於它要撤銷的內容並無被放入到暫存區或Repository。一旦撤銷,就一去不復返了。
概念區分:fetch vs. pull
fetch命令只是將遠端數據拉到本地倉庫,並不自動合併到當前工做分支。若要合併,還需手動合併。例如,執行git fetch origin,就會抓取自上次克隆以來別人上傳到此遠程倉庫中的全部更新。
pull命令則除了會抓取數據,還能將遠端分支自動合併到本地倉庫中當前分支。
場景4:撤銷提交
在Git中若要撤銷提交,可使用reset或者revert命令。但兩者有着顯著的區別:
revert命令能夠撤銷已經提交的快照,但它並不會將該提交從項目的提交歷史中移除,而是會判斷要撤銷的此次提交引入了哪些變化,而後將此變化撤銷(這次撤銷事實上仍是一種變化),再將此次撤銷做爲一個提交。所以,在執行revert命令後,若是經過git log查看提交歷史,能夠看到會新增一個revert提交。命令爲:
- git revert <commit>
這個commit能夠是指定提交對應的hash code。咱們也能夠用HEAD指針:
- git revert HEAD~n
若是是revert當前提交,則不須要HEAD後的~n。
reset命令就字面意義已經表達了該操做的含義爲「重置」。因爲Git的提交記錄是由HEAD指針指向當前分支。重置就是搬動這個指針到指定的snapshot。若是說revert是一種 安全的撤銷方式,則reset就是一種 危險的撤銷方式。默認狀況下,若是使用reset命令,會將當前的分支回退到指定commit,而後自指定commit到最新commit之間的內容會放在工做目錄下,使得咱們能夠再提交。這個命令爲:
- git reset <commit>
與前相同,這個commit就是提交對應的hash code。一樣,也可使用HEAD指針。不過若是是撤銷當前提交,與revert不一樣的是,須要指定爲:HEAD~1。這是由於HEAD指針指向了當前提交。reset與revert的意義不同。revert對應的commit爲目標提交,意思爲:「撤銷目標提交」,於是git revert HEAD,表明的就是「將當前提交撤銷」。而reset對應的commit表示將指針移向給定的Commit。若是執行git reset HEAD,表明的就是「將當前指針指向當前提交」,至關於沒作任何操做。因此應該執行git reset HEAD~1。
若是確實要撤銷操做,而前面的內容並不須要,在使用reset命令時,能夠添加–hard參數:
- git reset --hard <commit>
**注意:針對遠程的提交記錄,應儘可能避免使用git reset命令。假若在本地進行了reset以後,又進行了另外的修改並提交。此時,本地的提交記錄與遠程的提交記錄在reset的那個點產生了分叉。以下圖所示:
此時,若是執行git push,會在本地合併後提交,並同步遠程提交記錄。則團隊其餘成員會由於這個變化的提交記錄而困惑。因爲一部分變動消失,甚至可能致使一些數據被破壞。所以,使用reset命令要格外小心,一般狀況,應儘可能針對本地提交(未push到遠程)進行reset。優先考慮使用revert命令。