1、能夠將git簡單的分爲三個區域
一、工做區(working directory)
二、暫緩區(stage index)
三、歷史記錄區(history)
如圖: git
其中git add files 把當前工做目錄中的文件放入暫存區域 緩存
這其實作了兩件事:
一、將本地文件的時間戳、長度,當前文檔對象的id等信息保存到一個樹形目錄中去(index,即平時說的暫存區)
二、將本地文件的內容作快照並保存到Git 的對象庫 。
服務器
綜上2點來講,暫存區實際上就是一個包含文件索引的目錄樹,像是一個虛擬的工做區。在這個虛擬工做區的目錄樹中,記錄了文件名、文件的狀態信息(時間戳、文件長度等),文件的內容並不存儲其中,而是保存在 Git 對象庫(.git/objects)中,文件索引創建了文件和對象庫中對象實體之間的對應。
如圖:工具
在這個圖中,咱們能夠看到部分 Git 命令是如何影響工做區和暫存區(stage, index)的:
圖中左側爲工做區,右側爲版本庫。在版本庫中標記爲 "index" 的區域是暫存區(stage, index),標記爲 "master" 的是 master 分支所表明的目錄樹。
圖中咱們能夠看出此時 "HEAD" 實際是指向 master 分支的一個「遊標」。因此圖示的命令中出現 HEAD 的地方能夠用 master 來替換。
圖中的 objects 標識的區域爲 Git 的對象庫,實際位於 ".git/objects" 目錄下
當對工做區修改(或新增)的文件執行 "git add" 命令時,暫存區的目錄樹被更新,同時工做區修改(或新增)的文件內容被寫入到對象庫中的一個新的對象中,而該對象的ID 被記錄在暫存區的文件索引中
當執行提交操做(git commit)時,暫存區的目錄樹寫到版本庫(對象庫)中,master 分支會作相應的更新。即 master 指向的目錄樹就是提交時暫存區的目錄樹 。指針
當執行 "git status" 命令掃描工做區改動的時候,先依據 .git/index 文件中記錄的(工做區跟蹤文件的)時間戳、長度等信息判斷工做區文件是否改變。若是工做區的文件時間戳改變,說明文件的內容可能被改變了,須要打開文件,讀取文件內容,和更改前的原始文件相比較(本地文件和與之對應的object庫中的文件的內容進行對比),判斷文件內容是否被更改。若是文件內容沒有改變,則將該文件新的時間戳記錄到 .git/index 文件中。由於判斷文件是否更改,使用時間戳、文件長度等信息進行比較要比經過文件內容比較要快的多,因此 Git 這樣的實現方式可讓工做區狀態掃描更快速的執行,這也是 Git 高效的因素之一。 對象
git diff files用來進行具體文件的變更對比,一般用來進行工做區與暫存區之間的對比,實質上是用 git objects 庫中的快照與工做區文件的內容的對比。 blog
另外,Git中提供了幾個相關的撤銷操做的命令,如git reset、git revert、git checkout,這幾者之間的用法各有不一樣。索引
2、git reset的用法文檔
從上圖可知:git reset -- files 用來撤銷最後一次的git add files(由於每git add file一次,暫存區的文件都會被更改一次),你也能夠用git reset 撤銷全部暫存區域文件。 同步
另外:
1、git reset的用法:git reset + commit號
一、git reset命令後面須要加2種參數:"--hard"和"--soft",若是不加,默認狀況下是"--soft"。
二、--soft表示該條commit號以後(時間做爲參考點)的全部commit的修改都會退回到git緩衝區中。因此使用git status命令能夠在緩衝區中看到這些修改。
三、"--hard"則表示緩衝區中不會存儲這些修改,git會直接丟棄這部份內容,但須要注意的一個問題是:這樣的重置是直接在本地的修改,沒法提交到遠程服務器,若是直接丟棄的內容已經被推到遠程服務器上了,則會形成本地和服務器沒法同步的問題,即git reset --hard只能針對本地操做,不能針對遠程服務器進行一樣操做。若是從本地刪掉的內容沒有推到服務器上,則不會有反作用,若是被推到服務器,則下次本地和服務器進行同步時,這
部分刪掉的內容仍然會回來。
(其實這個問題則能夠很好的被git revert 命令解決,使用git revert + commit號,該命令撤銷對某個commit的提交,這一撤銷動做會做爲一個新的修改存儲起來,這樣,當你和服務器同步時,就不會產生什麼反作用。)
其實在merge的時候,也有可能會用到git reset.
若是咱們當前使用git pull的時候,可能會出現merge衝突,在衝突狀態下,須要解決衝突的文件會從index暫存區打回到工做區。
若是有衝突的時候,通常用以下步驟解決衝突:
一、用工具或者手工解決衝突
二、git add 命令來代表衝突已經解決。
三、再次commit已解決衝突的文件。
這當中,可使用git reset --hard ORIG_HEAD用來撤銷已經commit的merge.
使用git reset --hard HEAD 用來撤銷還沒commit 的merge,其實原理就是放棄index和工做區的改動。
也可使用git reset --merge ORIG_HEAD,注意其中的--hard 換成了 --merge,這樣就能夠避免在回滾時清除working tree。
3、git checkout的用法
從上圖可知,git checkout -- files 把文件從暫存區域複製到工做目錄,用來丟棄本地修改。 須要另外注意的是:
一、當執行 "git rm --cached <file>" 命令時,會直接從暫存區刪除文件,工做區則不作出改變。
二、當執行 "git checkout ." 或者 "git checkout -- <file>" 命令時,會用暫存區所有或指定的文件替換工做區的文件。這個操做很危險,會清除工做區中未添加到暫存區的改動
三、當執行 "git checkout HEAD ." 或者 "git checkout HEAD <file>" 命令時,會用 HEAD 指向的 master 分支中的所有或者部分文件替換暫存區以及工做區中的文件。這個命令也是極具危險性的,由於不但會清除工做區中未提交的改動,也會清除暫存區中未提交的改動 。
4、git revert的用法
git revert 也是撤銷命令,區別在於reset是指向原地或者向前移動指針,git revert是建立一個commit來覆蓋當前的commit,指針向後移動。
那麼二者的具體區別有:
1)git revert 是撤銷某次操做,這次操做以前的commit都會被保留,而git reset 是撤銷某次提交,可是這次以後的修改都會被退回到暫存區中。
具體一個例子,假設有三個commit(commit1,commit2,commit3),使用 git status:
commit3: add test3.c
commit2: add test2.c
commit1: add test1.c
當執行git revert HEAD~1時(撤銷倒數第二個操做),第二個操做即commit2這個操做被撤銷了,使用git log能夠看到:
commit1:add test1.c
commit3:add test3.c
因爲git revert不會回退到暫存區中,因此使用git status 沒有任何變化
若是換作執行git reset --soft(默認) HEAD~1後,運行git log能夠看到
commit2: add test2.c
commit1: add test1.c
運行git status,能夠看到test3.c處於暫存區了,準備提交。
但若是換作執行git revert後,
顯示:HEAD is now at commit2,運行git log能夠看到
commit2: add test2.c
commit1: add test1.c
運行git status, 則沒有任何變化
因此,git revert與git reset最大的不一樣是,git revert 僅僅是撤銷某次提交,而git reset會將撤銷點以後的操做
都回退到暫存區中。
一、git revert是用一次新的commit來回滾以前的commit,git reset是直接刪除指定的commit。
二、在回滾這一操做上看,效果差很少。可是在往後繼續merge之前的老版本時有區別。
由於git revert是用一次逆向的commit「中和」以前的提交,所以往後合併老的branch時,致使這部分改變不會再次出現,可是git reset是之間把某些commit在某個branch上刪除,於是和老的branch再次merge時,這些被回滾的commit應該還會被引入。
三、git reset 是把HEAD向後移動了一下,而git revert是HEAD繼續前進,只是新的commit的內容和要revert的內容正好相反,可以抵消要被revert的內容。
6、不得不提的git的其它刪除命令(相似於Linux的命令):
git rm --cached readme.txt 只從緩存區中刪除readme.txt,保留物理文件
git rm readme.txt 不但從緩存區中刪除,同時刪除物理文件
git mv a.txt b.txt 把a.txt更名爲b.txt
綜上因此咱們有時若是是想撤銷對單個文件的更改:
<->、若是已經用add命令把文件加入stage暫存區了:
一、就先須要從stage暫存區中撤銷:
"git reset HEAD <file>..."
二、而後再從工做區撤銷,即將暫存區的文件替換工做區當前的文件
"git checkout -- <file>..." to discard changes in working directory
如:git checkout a.txt 撤銷a.txt的變更(工做區上的文件)
若是是多個文件:git chenkout .
<二>、若是已經commit了,則須要
一、使用git commit --amend來進行修改,這個只能修改最近一次的,也就是用一個新的提交來覆蓋上一次的提交。所以若是push之後再作這個動做就會有危險。
二、git reset --hard HEAD 放棄工做區和index的改動,HEAD指針仍然指向當前的commit.(參照第一幅圖)
這條命令同時還能夠用來撤銷還沒commit的merge,其實原理就是放棄index暫存區和工做區的改動,由於沒commit的改動只存在於index暫存區和工做區中。
而git reset --hard HEAD^ 用來撤銷已經commit的內容(等價於 git reset --hard HEAD~1) 。原理就是放棄工做區和index的改動,同時HEAD指針指向前一個commit對象。