Git的基本概念和用法


在日常使用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命令。 
  • 命令中”Changes to be committed“中所列的內容是在Index中的內容,commit之后進入Git Directory。
  • 命令中“Changed but not updated”中所列的內容是在Working Directory中的內容,add之后將進入Index。
  • 命令中“Untracked files”中所列的內容是尚未被Git跟蹤的內容,add之后進入Index。
哪些操作能夠改變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之間的差異, 
  • git diff [<path>...]:這個命令最常用,在每次add進入index前會運行這個命令,查看即將add進入index時所做的內容修改,即working directory和index的差異
  • git diff --cached [<path>...]:這個命令初學者不太常用,卻非常有用,它表示查看已經add進入index但是尚未commit的內容同最后一次commit時的內容的差異。即index和git directory的差異
  • git diff --cached [<commit>] [<path>...]:這個命令初學者用的更少,也非常有用,它表示查看已經add進入index但是尚未commit的內容同指定的<commit>之間的差異,和上面一條很相似,差別僅僅<commit>,即index和git directory中指定版本的差異
  • 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). --hard:重設(reset) index和working directory,自從<commit>以來在working directory中的任何改變都被丟棄,並把HEAD指向<commit>。 
    B). --softindex和working directory中的內容不作任何改變,僅僅把HEAD指向<commit>。這個模式的效果是,執行完畢后,自從<commit>以來的所有改變都會顯示在git status的"Changes to be committed"中。 
    C). --mixed僅reset index,但是不reset working directory。這個模式是默認模式,即當不顯示告知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: 
fa7dc3cd254c6fff683e2072 2284565b92d869ff HEAD 
14a62709ecadd11a266d234d 19955f4679fa95ab refs/heads/cpp-1.0 
34b38625bce0aa4d4a4e266e 20bba3e0ccd1b97e refs/heads/cpp-1.0.RC1 
3f40a21f20f51aaa74e2a695 4b64d82506cd4adf refs/heads/cpp-1.1 
2f795085d57b6784a6358d97 dbd0d1227891b01a refs/heads/distri 

#刪除遠程叫做diftri的分支 
git push idc :distri 
Password: 
To xxx@192.168.4.40:Project.git 
- [deleted]                distri 

#確認遠程分支被刪除 
git ls-remote  idc 
Password: 
fa7dc3cd254c6fff683e2072 2284565b92d869ff HEAD 
14a62709ecadd11a266d234d 19955f4679fa95ab refs/heads/cpp-1.0 
34b38625bce0aa4d4a4e266e 20bba3e0ccd1b97e refs/heads/cpp-1.0.RC1 
3f40a21f20f51aaa74e2a695 4b64d82506cd4adf 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 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM