GIT基本概念和用法總結
guibin.beijing@gmail.com
在平常使用GIT過程當中,常常會出錯,好比無心間丟失了未提交的數據,回退版本時丟失了工做目錄,等等。通過思考發現,全部這些錯誤都是由於對 GIT中一些基本的概念模糊而致使,由於對一些基本概念不清晰,致使對GIT每一條命令將會產生的結果不符合預期。下面我就梳理如下我常常碰到的問題相關 的基本概念。
1. Working Directory(工做目錄)
Git的工做目錄是保存當前正在工做的文件所在的目錄,和working tree是相同的意思。在這個目錄中的文件可能會在切換branch時被GIT刪除或者替換。這個目錄是個臨時目錄,臨時存儲你從GIT庫中取出的文件,這些文件一直會被保存,直到下次提交。
2. GIT Directory(GIT庫目錄)
項目的全部歷史提交都被保存在了GIT庫目錄中,只要你不做回滾操做,它應該不會丟失。
3. GIT Index(Git索引)
Git index 能夠看做是工做目錄和Git庫目錄之間的暫存區,和staging area是相同的意思。可使用Git index構建一組你準備一塊兒提交的改變。Git Index和Git Staging area是同一個意思,都是指已經被add的但還沒有commit的那些內容所在的區域。最簡單的查看目前什麼內容在index中的方法是使用git status命令。
html
命令中」Changes to be committed「中所列的內容是在Index中的內容,commit以後進入Git Directory。git
命令中「Changed but not updated」中所列的內容是在Working Directory中的內容,add以後將進入Index。安全
命令中「Untracked files」中所列的內容是還沒有被Git跟蹤的內容,add以後進入Index。ui
哪些操做可以改變git index中的內容?
A). git add <path>...會將working directory中的內容添加進入git index。
B). git reset HEAD <path>...會將git index中path內容刪除,從新放回working directory中。
4. git diff
git diff能夠比較working tree同index之間,index和git directory之間,working tree和git directory之間,git directory中不一樣commit之間的差別,
spa
git diff [<path>...]:這個命令最經常使用,在每次add進入index前會運行這個命令,查看即將add進入index時所作的內容修改,即working directory和index的差別。命令行
git diff --cached [<path>...]:這個命令初學者不太經常使用,卻很是有用,它表示查看已經add進入index可是還沒有commit的內容同最後一次commit時的內容的差別。即index和git directory的差別。scala
git diff --cached [<commit>] [<path>...]:這個命令初學者用的更少,也很是有用,它表示查看已經add進入index可是還沒有commit的內容同指定的<commit>之間的差別,和上面一條很類似,差異僅僅<commit>,即index和git directory中指定版本的差別。htm
git diff <commit> [<path>...]:這個命令用來查看工做目錄和指定<commit>的commit之間的差異,若是要和Git directory中最新版比較差異,則<commit>=HEAD。若是要和某一個branch比較差異,<commit>=分支名字對象
git diff <commit> <commit> [<path>...]:這個命令用來比較git directory中任意兩個<commit>之間的差異,若是想比較任意一個<commit>和最新版的差異,把其中一個<commit>換成HEAD便可。索引
5. 如何merge不一樣的分支
在git中,在執行任何命令時你必定要清楚,你在哪?對誰執行這個命令?
好比在建立新的branch時,執行命令:git branch 1.0-beta,這個命令是說在當前branch上,以當前branch爲基準,建立一個新的branch,名叫1.0-beta。
在好比,當merge不一樣的branch時:
引用
git checkout 1.0-beta
git merge master
首先切換到1.0-beta branch上,而後將主幹(master)上的代碼合併到當前1.0-beta分支上。
merge完後,可能會由衝突,按照git的提示,編輯標識爲"CONFLICT (content)"的文件,解決衝突後再次將衝突的文件add,commit後,merge完畢。
6. git reset
在通常使用中,若是發現錯誤的將不想staging的文件add進入index以後,想回退取消,則可使用命令:git reset HEAD <file>...,同時git add完畢以後,git也會作相應的提示,好比:
引用
# Changes to be committed:
# (use "git reset HEAD <file>..." to unstage)
#
# new file: Test.scala
git reset [--hard|soft|mixed|merge|keep] [<commit>或HEAD]: 將當前的分支重設(reset)到指定的<commit>或者HEAD(默認,若是不顯示指定commit,默認是HEAD,即最新的一次提 交),而且根據[mode]有可能更新index和working directory。mode的取值能夠是hard、soft、mixed、merged、keep。下面來詳細說明每種模式的意義和效果。
A).
:重設(reset)
,自從<commit>以來在working directory中的任何改變都被丟棄,並把HEAD指向<commit>。
B).
:
,僅僅把HEAD指向<commit>。這個模式的效果是,執行完畢後,自從<commit>以來的全部改變都會顯示在git status的
中。
C).
:
。 這個模式是默認模式,即當不顯示告知git reset模式時,會使用mixed模式。這個模式的效果是,working directory中文件的修改都會被保留,不會丟棄,可是也不會被標記成"Changes to be committed",可是會打出什麼還未被更新的報告。報告以下:
引用
Unstaged changes after reset:
M Test.Scala
M test.txt
D). --merge和--keep用的很少,在下面的例子中說明。
下面列出一些git reset的典型的應用場景:
A) 回滾add操縱
引用
$ edit (1)
$ git add frotz.c filfre.c
$ mailx (2)
$ git reset (3)
$ git pull git://info.example.com/ nitfol (4)
(1) 編輯文件frotz.c, filfre.c,作了些更改,並把更改添加到了index
(2) 查看郵件,發現某人要你pull,有一些改變須要你merge下來
(3) 然而,你已經把index搞亂了,由於index同HEAD commit不匹配了,可是你知道,即將pull的東西不會影響已經修改的frotz.c和filfre.c,所以你能夠revert這兩個文件的改變。 revert後,那些改變應該依舊在working directory中,所以執行git reset。
(4) 而後,執行了pull以後,自動merge,frotz.c和filfre.c這些改變依然在working directory中。
B) 回滾最近一次commit
引用
$ git commit ...
$ git reset --soft HEAD^ (1)
$ edit (2)
$ git commit -a -c ORIG_HEAD (3)
(1) 當提交了以後,你又發現代碼沒有提交完整,或者你想從新編輯一下提交的comment,執行git reset --soft HEAD^,讓working tree還跟reset以前同樣,不做任何改變。
HEAD^指向HEAD以前最近的一次commit。
(2) 對working tree下的文件作修改
(3) 而後使用reset以前那次commit的註釋、做者、日期等信息從新提交。注意,當執行git reset命令時,git會把老的HEAD拷貝到文件.git/ORIG_HEAD中,在命令中可使用ORIG_HEAD引用這個commit。commit 命令中 -a 參數的意思是告訴git,自動把全部修改的和刪除的文件都放進stage area,未被git跟蹤的新建的文件不受影響。commit命令中-c <commit> 或者 -C <commit>意思是拿已經提交的commit對象中的信息(做者,提交者,註釋,時間戳等)提交,那麼這條commit命令的意思就很是清晰了,把全部更改的文件加入stage area,並使用上次的提交信息從新提交。
C) 回滾最近幾回commit,並把這幾回commit放到叫作topic的branch上去。
引用
$ git branch topic/wip (1)
$ git reset --hard HEAD~3 (2)
$ git checkout topic/wip (3)
(1) 你已經提交了一些commit,可是此時發現這些commit還不夠成熟,不能進入master分支,但你但願在新的branch上潤色這些commit改動。所以執行了git branch命令在當前的HEAD上創建了新的叫作 topic/wip的分支。
(2) 而後回滾master branch上的最近三次提交。HEAD~3指向當前HEAD-3個commit的commit,git reset --hard HEAD~3即刪除最近的三個commit(刪除HEAD, HEAD^, HEAD~2),將HEAD指向HEAD~3。
D) 永久刪除最後幾個commit
引用
$ git commit ...
$ git reset --hard HEAD~3 (1)
(1) 最後三個commit(即HEAD, HEAD^和HEAD~2)提交有問題,你想永久刪除這三個commit。
E) 回滾merge和pull操做
引用
$ git pull (1)
Auto-merging nitfol
CONFLICT (content): Merge conflict in nitfol
Automatic merge failed; fix conflicts and then commit the result.
$ git reset --hard (2)
$ git pull . topic/branch (3)
Updating from 41223... to 13134...
Fast-forward
$ git reset --hard ORIG_HEAD (4)
(1) 從origin拉下來一些更新,可是產生了不少衝突,你暫時沒有這麼多時間去解決這些衝突,所以你決定稍候有空的時候再從新pull。
(2) 因爲pull操做產生了衝突,所以全部pull下來的改變還沒有提交,仍然再stage area中,這種狀況下git reset --hard 與 git reset --hard HEAD意思相同,即都是清除index和working tree中被搞亂的東西。
(3) 將topic/branch合併到當前的branch,此次沒有產生衝突,而且合併後的更改自動提交。
(4) 可是此時你又發現將topic/branch合併過來爲時尚早,所以決定退滾merge,執行git reset --hard ORIG_HEAD回 滾剛纔的pull/merge操做。說明:前面講過,執行git reset時,git會把reset以前的HEAD放入.git/ORIG_HEAD文件中,命令行中使用ORIG_HEAD引用這個commit。一樣 的,執行pull和merge操做時,git都會把執行操做前的HEAD放入ORIG_HEAD中,以防回滾操做。
F) 在被污染的working tree中回滾merge或者pull
引用
$ git pull (1)
Auto-merging nitfol
Merge made by recursive.
nitfol | 20 +++++----
...
$ git reset --merge ORIG_HEAD (2)
(1) 即使你已經在本地更改了一些你的working tree,你也可安全的git pull,前提是你知道將要pull的內容不會覆蓋你的working tree中的內容。
(2) git pull完後,你發現此次pull下來的修改不滿意,想要回滾到pull以前的狀態,從前面的介紹知道,咱們能夠執行git reset --hard ORIG_HEAD,可是這個命令有個反作用就是清空你的working tree,即丟棄你的本地未add的那些改變。爲了不丟棄working tree中的內容,可使用git reset --merge ORIG_HEAD,注意其中的--hard 換成了 --merge,這樣就能夠避免在回滾時清除working tree。
G) 被中斷的工做流程
在實際開發中常常出現這樣的情形:你正在開發一個大的feature,此時來了一個緊急的bug須要修復,可是目前在working tree中的內容尚未成型,還不足以commit,可是你又必須切換的另外的branch去fix bug。請看下面的例子
引用
$ git checkout feature ;# you were working in "feature" branch and
$ work work work ;# got interrupted
$ git commit -a -m "snapshot WIP" (1)
$ git checkout master
$ fix fix fix
$ git commit ;# commit with real log
$ git checkout feature
$ git reset --soft HEAD^ ;# go back to WIP state (2)
$ git reset (3)
(1) 此次屬於臨時提交,所以隨便添加一個臨時註釋便可。
(2) 此次reset刪除了WIP commit,而且把working tree設置成提交WIP快照以前的狀態。
(3) 此時,在index中依然遺留着「snapshot WIP」提交時所作的uncommit changes,git reset將會清理index成爲還沒有提交"snapshot WIP"時的狀態便於接下來繼續工做。
(H) Reset單獨的一個文件
假設你已經添加了一個文件進入index,可是然後又不打算把這個文件提交,此時可使用git reset把這個文件從index中去除。
引用
$ git reset -- frotz.c (1)
$ git commit -m "Commit files in index" (2)
$ git add frotz.c (3)
(1) 把文件frotz.c從index中去除,
(2) 把index中的文件提交
(3) 再次把frotz.c加入index
(I) 保留working tree並丟棄一些以前的commit
假設你正在編輯一些文件,而且已經提交,接着繼續工做,可是如今你發現當前在working tree中的內容應該屬於另外一個branch,與這以前的commit沒有什麼關係。此時,你能夠開啓一個新的branch,而且保留着working tree中的內容。
引用
$ git tag start
$ git checkout -b branch1
$ edit
$ git commit ... (1)
$ edit
$ git checkout -b branch2 (2)
$ git reset --keep start (3)
(1) 此次是把在branch1中的改變提交了。
(2) 此時發現,以前的提交不屬於這個branch,此時你新建了branch2,並切換到了branch2上。
(3) 此時你能夠用reset --keep把在start以後的commit清除掉,可是保持working tree不變。
7. git revert
git revert用於回滾一些commit。對於一個或者多個已經存在的commit,去除由這些commit引入的改變,而且用一個新的commit來記錄這個回滾操做。這個命令要求working tree必須是乾淨的。
git revert和git reset的功能很類似,可是有區別,具體以下。
git revert用於用一個commit來記錄並回滾早前的commit,常常是一些錯誤的提交。若是你想幹脆扔掉working tree中的東西,可使用git reset --hard
好比
A) git revert HEAD~3:丟棄最近的三個commit,把狀態恢復到最近的第四個commit,而且提交一個新的commit來記錄此次改變。
B) git revert -n master~5..master~2:丟棄從最近的第五個commit(包含)到第二個(不包含),可是不自動生成commit,這個revert僅僅修改working tree和index。
8. git revert 和 git reset的區別
1. git revert是用一次新的commit來回滾以前的commit,git reset是直接刪除指定的commit。
2. 在回滾這一操做上看,效果差很少。可是在往後繼續merge之前的老版本時有區別。由於git revert是用一次逆向的commit「中和」以前的提交,所以往後合併老的branch時,致使這部分改變不會再次出現,可是git reset是之間把某些commit在某個branch上刪除,於是和老的branch再次merge時,這些被回滾的commit應該還會被引入。
3. git reset 是把HEAD向後移動了一下,而git revert是HEAD繼續前進,只是新的commit的內容和要revert的內容正好相反,可以抵消要被revert的內容。
9. 如何刪除遠程分支
刪除遠程分支就是將本地的空分支push到遠程便可。
引用
#查看遠程分支
$ git ls-remote idc
Password:
fa7dc3cd254c6fff683e20722284565b92d869ff HEAD
14a62709ecadd11a266d234d19955f4679fa95ab refs/heads/cpp-1.0
34b38625bce0aa4d4a4e266e20bba3e0ccd1b97e refs/heads/cpp-1.0.RC1
3f40a21f20f51aaa74e2a6954b64d82506cd4adf refs/heads/cpp-1.1
2f795085d57b6784a6358d97dbd0d1227891b01a refs/heads/distri
#刪除遠程叫作diftri的分支
$ git push idc :distri
Password:
To xxx@192.168.4.40:Project.git
- [deleted] distri
#確認遠程分支被刪除
$ git ls-remote idc
Password:
fa7dc3cd254c6fff683e20722284565b92d869ff HEAD
14a62709ecadd11a266d234d19955f4679fa95ab refs/heads/cpp-1.0
34b38625bce0aa4d4a4e266e20bba3e0ccd1b97e refs/heads/cpp-1.0.RC1
3f40a21f20f51aaa74e2a6954b64d82506cd4adf refs/heads/cpp-1.1
9. 如何刪除本地分支
使用git branch命令就能夠刪除本地分支,好比
引用
git branch -d toBeDelBranch
10. 如何clone(克隆)遠程倉庫中的指定分支,而非默認的master分支
在git clone 命令中使用-b參數指定分支名字便可,好比將遠端aiotrade.git上的levelIISZ-1.1分支克隆下來:
引用
git clone -b levelIISZ-1.1 username@192.168.4.40:aiotrade.git
參考文獻:1. http://book.git-scm.com/3_basic_branching_and_merging.html2. git reset --help3. git revert --help