最近使用Git時感到有些生疏,所以利用晚上空余時間將廖雪峰Git教程重新系統的學習一邊并做了筆記,溫故知新。
Git簡介
Git是目前世界上最先進的分布式版本控制系統。
集中式vs分布式
CVS和SVN是集中式的版本控制系統,Git是分布式版本控制系統。
集中式:
版本庫是集中存放在中央服務器的,在工作室都用自己的電腦,要先從中央服務器取得最新的版本,然后開始干活,干完活再把自己的或推送到中央服務器。集中式版本控制系統最大毛病是必須聯網才能工作。
分布式:
分布式版本控制系統根本沒有“中央服務器”,每個人的電腦上都是一個完整的版本庫,這樣你工作時就不須聯網了,因為版本庫就在你電腦中。既然每個人電腦上都有完整的版本庫,那多人何如協作呢?比方你在電腦修改了文件A,你同事也在他電腦中修改了文件A,這時你們只需把各自修改推送給對方就可以互相看到對方修改了。
相比集中式,分布式安全性要更高些。Git優勢不單是不必聯網這么簡單,Git極其強大的分支管理,把SVN遠遠拋在后面。
版本庫版本管理
版本庫(repository)又名倉庫,可以簡單理解為一個目錄,這個目錄里面所有文件都可以被Git管理起來,每個文件的修改、刪除,Git都能跟蹤,一遍任何時刻都能追蹤歷史,或者在將來某時刻可以還原。
- git init
通過git init
命令可以吧當前目標變成Git可以管理的倉庫。瞬間Git就把倉庫建好并告訴你這是個空的倉庫,且目錄下多一個.git目錄,這個目錄是Git來跟蹤管理版本庫的,沒事不要手動修改這個目錄的文件。
- git add 與git commit
在該倉庫目錄下先用命令git add readme.txt
告訴Git,把文件添加到倉庫。執行上面命令,沒有任何顯示則表明成功。Unix的哲學就是“沒有消息就是好消息”。第二步,用命令git commit
告訴Git,把文件提交到倉庫。git commit -m "wrote a readme file"
,其中-m
后面輸入的是本提交的說明,可以輸入任意內容,方便在歷史記錄中查找改動。git commit
命令成功后會告訴你,1個文件被改動(新添加的readme文件),插入了兩行內容。
為什么Git添加文件需要add
,commit
兩步呢?
因為
commit
可以一次提交很多文件,所以你可以多次add
不同的文件。
- git status
當修改了文件后,運行git status
命令。
$ git status
# On branch master
# Changes not staged for commit:
# (use "git add <file>..." to update what will be committed)
# (use "git checkout -- <file>..." to discard changes in working directory)
#
# modified: readme.txt
#
no changes added to commit (use "git add" and/or "git commit -a")
可以讓我們時刻掌握倉庫當前的狀態,命令告訴我們readme被修改過但還沒準備提交修改。
- git diff
git diff可以查看文件具體修改了什么內容。輸入git diff readme.txt
$ git diff readme.txt
diff --git a/readme.txt b/readme.txt
index 46d49bf..9247db6 100644
--- a/readme.txt
+++ b/readme.txt
@@ -1,2 +1,2 @@
-Git is a version control system.
+Git is a distributed version control system.
Git is free software.
- git log
當commit
了數次版本后,可以使用git log
命令顯示從最近到最遠的提交日志。如果覺得輸出信息太多,可以加上--pretty=oneline
參數。
- git reset
我們準備把readme文件會退到上一個版本,首先Git必須知道當前版本是哪個版本,在Git中,用HEAD
表示當前版本,上個版本就是HEAD^
,上上個版本就是HEAD^^
,往上100個版本可寫成HEAD~100
。
輸入git reset -- hard HEAD^
即可回到上個版本,此時用git log
再看版本庫狀態發現之前版本消失了。如果想再回去,只要順著命令行串口向上找,找到那版本的commit id
然后再運行``git reset -- hard 95d933`就又可以回到未來的那版本了。
Git版本回退速度非常快,因為Git在內部有個指向當前版本的HEAD
指針。HEAD
指向版本就是當前版本,因此Git允許我們在版本歷史之間穿梭。穿梭前用git log
可以查看提交歷史,以便確定要退回哪個版本。要重返未來,用git reflog
查看命令歷史,以便確定要會帶未來的哪個版本。
工作區和暫存區
工作區(Working Directory):就是你在電腦里能看到的目錄。
版本庫(Repository):工作區有一個隱藏目錄.git,這個不算工作區,而是Git的版本庫。Git的版本庫里存了很多東西,其中最重要的是stage(或叫index)的暫存區。還有Git為我們自動創建了第一個分支master
,以及指向master
的一個指針叫HEAD
。
前面講吧文件往Git版本庫里添加時分兩步執行:git add
,實際就是把文件修改添加到暫存區。git commit
,實際上是把暫存區的所有內容提交到當前分支。因為我們創建Git版本庫時,Git自動為我們創建了唯一一個master
分支,所以現在git commit
就是往這個分支上提交修改。
- git checkout
使用git checkout --file
可以把file文件在工作區的修改全部撤銷,即讓這個文件回到最近一次git add
或git commit
時的狀態。這里必須在checkout
后寫明文件,否則就變成“切換到另一個分支”的命令了。
如果想撤銷掉已添加到暫存區的修改,用命令git reset HEAD file
可以把暫存區的修改撤銷掉,重新放回工作區。git reset
命令既可以回退版本,也可以把暫存區的修改回退到工作區。然后再按上面步驟撤銷工作區。
- git rm
在Git中,刪除也是一個修改操作。一般情況下會在終端調用命令rm file
刪除文件,Git知道你刪除了文件,因此工作區和版本庫就不一致了。此時可以從版本庫中刪除這個文件,使用git rm
刪掉并且git commit
,文件就從版本庫中刪除了。如果是刪錯了,因為版本庫中還有,則使用git checkout --file
就可把文件恢復了。git checkout
其實是用版本庫里的版本替換工作區的版本,無論工作區是修改還是刪除,都可以還原。
遠程倉庫
Git是分布式版本控制系統,同一個Git倉庫可以分布到不同的機器上。最早肯定是只有一臺機器有原始版本庫,伺候別的機器可以“克隆”這個原始版本庫,而且每臺機器的版本庫都一樣不分主次。實際情況常是找一臺電腦充當服務器的角色,其他每個人都從這個“服務器”倉庫克隆一份到自己電腦上并且各自把各自的提交推送到服務器倉庫里,也從服務器倉庫中拉取別人的提交。
GitHub就是提供Git倉庫托管服務的,只要注冊一個GitHub賬號就可以免費獲得Git遠程倉庫。假設我們從零開發,最好的方式是先創建遠程庫,然后再從遠程庫克隆。
使用命令git clone
克隆一個本地庫,如git clone git@github.com:orwater/cleargit.git
分支管理
在版本回退時我們已經知道,每次提交Git都辦它們穿成一條時間線,這條時間線就是分支。截至目前只有一條時間線,在Git里這個分支叫主分支,即master
分支。HEAD
嚴格來說不是指向提交,而是指向master
,master
才是指向提交的,所以,HEAD
指向的就是當前分支。
一開始時,master
分支是一條線,Git用master
指向最新的提交,再用HEAD
指向master
,就能確定當前分支,以及當前分支的提交點。每次提交,master
分支都會向前移動一部,這樣隨著不斷提交,master
分支線也越來越長。
當我們創建新的分支,例如dev
時,Git新建一個指針叫dev
,指向master
相同的提交,再把HEAD
指向dev
,就表示當前分支在dev
上了。
從現在開始,對工作區的修改和提交就是針對dev
分支了,比如新的提交一次后,dev
指針往前移動一步,而master
指針不變。
假如我們在dev
上的工作完成了,就可以把dev
合并到master
上,最簡單的方法是直接把master
指向dev
的當前提交完成合并。
合并完分支后,甚至可以刪除dev
分支。刪除dev
分支就是刪除dev
指針,最后就剩下一條主分支了。
-
創建分支
首先我們創建dev
分支,然后切換到上面,git checkout -b dev
,-b
參數表示創建并且切換。
然后可以用git branch
命令查看當前分支。
可以使用命令git checkout master
切換回主分支。
如果要把dev
分支的工作結果合并到master
上,使用git merge
命令用于合并指定分支到當前分支。git merge dev
合并完成后,就可以使用git branch -d dev
刪除分支了。
-
解決沖突
創建個feature1
新分支,修改readme文件并提交。然后切換到主分支再對readme文件做不同的修改并提交。
這種情況下,Git無法執行“快速合并”,只能試圖把各自的修改合并起來,但這種合并可能會有沖突。
$ git merge feature1
Auto-merging readme.txt
CONFLICT (content): Merge conflict in readme.txt
Automatic merge failed; fix conflicts and then commit the result.
Git提示readme文件存在沖突,必須手動解決沖突后再提交。這時我們查看readme文件:
Git is a distributed version control system.
Git is free software distributed under the GPL.
Git has a mutable index called stage.
Git tracks changes of files.
<<<<<<< HEAD
Creating a new branch is quick & simple.
=======
Creating a new branch is quick AND simple.
>>>>>>> feature1
Git用<<<<<<<
,=======
,>>>>>>>>
標記出不同分支的內容。這樣進行修改后再提交就可以了。
-
分支合并策略
通常,合并分支時,如果可能,Git會用Fast forward
模式,但這種模式下刪除分支后會丟掉分支信息。如果要強制禁用Fast forward
模式,Git就會在merge
時生成一個新的commit
,這樣從分支歷史上就可以看出分支信息了,知道曾經做個合并。
使用--no-ff
參數的git merge
表示禁用Fast forward
模式。因為本次合并要創建一個新的commit
,所以加上-m
參數。
$ git merge --no-ff -m "merge with no-ff" dev
Merge made by the 'recursive' strategy.
readme.txt | 1 +
1 file changed, 1 insertion(+)
在實際開發中,我們應該按照幾個基本原則進行分支管理:
首先。master
分支應該是非常穩定的,也就是僅用來發布新版本,平時不能在上面干活。干活都在dev
分支上,其分支不穩定,到摸個時候再把分支合并到主分支上,在主分支發布版本。團隊成員都在dev
上干活,每個人都有自己的分支,是不是往dev
分支上合并就可以了。
- git stash
軟件開發中,修復bug時,在Git中由于分支是如此強大,所以每個bug都可以通過一個新的臨時分支來修復,修復后合并分支再將臨時分支刪除。
如果正在工作時要修復一個bug,就要先創建一個臨時分支來修復。但手頭工作還沒完成不能提交。這時應使用Git提供的stash
功能,可以把當前工作現場存儲起來,等以后恢復現場后繼續工作。
$ git stash
Saved working directory and index state WIP on dev: 6224937 add merge
HEAD is now at 6224937 add merge
現在再用git status
查看工作區就是干凈的了。
當修復好bug后再切換回dev
分支要恢復之前內容,有兩個方法:
一種是用git stash apply
恢復,但恢復后,stash
內容并不刪除,須再調用git stash drop
來刪除。
另一種方法是用git stash pop
,恢復的同事把stash
內容也刪除。再用git stash list
查看就看不到任何stash
內容了。你可以多次stash
,恢復時先用git stash list
查看,然后再恢復指定的stash
,git stash apply stash@{0}
。
-
多人協作
當你從遠程倉庫克隆時,實際上Git自動把本地的master
分支和遠程的master
分支對應起來了,并且遠程倉庫默認名稱是origin
。要查看遠程庫的信息,用git remote
,添加-v
參數顯示更詳細信息。
$ git remote -v
origin git@github.com:michaelliao/learngit.git (fetch)
origin git@github.com:michaelliao/learngit.git (push)
上面顯示了可以抓取和推送的origin
地址,如果沒有推送權限就看不到push地址。
- git push
推送分支,就是把該分支上的所有本地提交推送到遠程庫。推送時,要制定本地分支,Git就會把該分支推送到遠程庫對應的遠程分支上。git push origin master
。
但是并不是一定要把本地分支往遠程推送,那么哪些分支需要推送?
-
master
分支是主分支,因此要時刻與遠程同步。 -
dev
分支是開發分支,團隊所有成員都需要在上面工作,所以要與遠程同步。 - bug分支只用于在本地修復bug,就沒必要推到遠程了。
總之Git中分支可以再本地自己藏著玩,是否推送視情況而定。
多人協作的工作模式通常是這樣:
- 首先,可以試圖用
git push origin branch -name
推送自己的修改 - 如果推送失敗,則因為遠程分支比你本地更新,需要先用
git pull
試圖合并 - 如果合并有沖突,則解決沖突,并在本地提交;
- 沒有沖突或解決沖突后,再用
git push origin branch -name
推送即可。
如果git pull
提示“no tracking information”,則說明本地分支和遠程分支的鏈接關系沒有創建,用命令git branch --set-upstream branch -name origin/branch -name
。
標簽管理
在發布一個版本時,通常先在版本庫中打一個標簽(tag),這樣就唯一確定了打標簽時刻的版本。將來無論什么時候,取某個標簽的版本就是把那個打標簽時刻的歷史版本取出來。所以,標簽也是版本庫的一個快照。
Git的標簽歲談事版本庫的快照,但其實他就是指向某個commit的指針,所以創建和刪除標簽都是瞬間完成的。相比于難記的commit id
,tag是一個讓人容易記住的名字,它跟某個commit綁定在一起。
- git tag
使用命令git tag<name>
可以打一個新標簽,使用命令git tag
可查看所有標簽。默認標簽是打在最新提交的commit上的,如果想之前的commit打標簽可以先找到是歷史提交的commit id
,然后打命令git tag v0.9 6224937
即可。
最后附一張Git Cheat Sheet
Git分支最佳實踐
- 主要分支:
中央倉庫有兩個長期的分支:
- master
- develop
master
用作生產分支,里面的代碼是準備部署到生產環境的。develop
是可交付的開發代碼,也可以看作用于集成分支,每晚構建從`develop獲取代碼。當
develop分支中的代碼足夠穩定時,就將改動合并到
master``分支,同時打上一個標簽,標簽名稱為發布的版本號。
- 輔助分支
通過輔助分支來幫助并行開發,和主要分支不同,這些分支的生命周期是有限的:
- 特性分支
- 發布分支
- 緊急修復分支
特性分支:
特性分支可能從develop
分支分出,最終必須合并回develop
。特性分支用于開發新特性。每個新特性開一個新分支,最終會合并會develop
。特性分支只存在于開發者的倉庫中。
創建新的特性分支:
git checkout -b myfeature develop
合并回develop
:
git checkout develop
git merge --no-ff myfeature
git branch -d myfeature
git push origin develop
使用--no-ff
確保總是新生成一個提交,避免丟失曾經存在一個特性分支的歷史信息,也能方便地看出那些提交屬于同一個特性。
發布分支:
發布分支可能從develop
分出,最終必須合并回develop
和master
。發布分支以release-*
的方式命名。
發布分支為新的發部分版本做準備,包括一些小bug修正和發布的元信息(版本號、發布日期等)的添加。這樣develop
分支就可以接受針對以后的發布的新特征。
在代碼基本可以發布的時候從develop
分支分出發布分支。這是要確保此次發布包括的特性都已經合并到develop
分支了。
創建發布分支:
git checkout -b release-1.2 develop
./bump-version.sh 1.2
git commit -a -m "Bumped version number to 1.2"
完成發布分支:
git checkout master
git merge --no-ff release-1.2
git tag -a 1.2
git checkout develop
git merge --no-ff release-1.2
緊急修復分支:
可能從master
分出,必須合并回develop
和master
。分支名以hotfix-*
開頭。如果生產系統里面有個緊急bug要馬上修復的話,我們就從master
里分出一個緊急修復分支。這樣某人修復緊急bug的同時,團隊其他成員可繼續在develop
上開發。修復完bug之后,需要合并回master
,同時也需要合并回develop
。
參考文章 git分支最佳實踐;