前端利器躬行記(5)——Git

  Git是一款開源的分佈式版本控制系統,它的出現和Linux緊密相關。Linux內核項目組爲了能更好地管理和維護Linux內核開發,於2002年開始啓用商業的分佈式版本控制系統BitKeeper。雖然軟件開發商受權了Linux社區能無償使用,可是好景不長,到了2005年,BitKeeper的開發商因爲某些緣由終止了與Linux社區的合做關係。因而Linux的做者Linus Torvalds就決定開發一款能替代BitKeeper的分佈式版本控制系統(即Git),在花費十天的時間後發佈了Git的第一個版本。html

1、版本控制系統

  版本控制系統(Version Control System,VCS)能管理文件內容的變動記錄,便可追蹤文件的修訂歷史,確保不一樣的人在編輯同一文件時能保持同步。該系統不只能應用於保存源碼的文本文件,還能對圖像、Word文檔等各類類型的文件進行版本控制。有了版本控制系統以後,就能很方便的回退文件到某個狀態、比較文件變動先後的區別、查詢到修改文件的人等。目前市面上的版本控制系統大體可分爲兩種:集中式和分佈式,下面會對它們作單獨的講解。node

1)集中式git

  當須要多人協同工做時,就得讓集中式版本控制系統(Centralized Version Control Systems,CVCS)登場了。github

  這類系統包括CVS、Subversion等都是在一臺中央服務器中創建一個倉庫,專門用來管理文件的修訂版本,客戶端可經過網絡鏈接到這臺服務器,取出最新文件或提交變動,如圖3所示。正則表達式

圖3  集中式版本控制系統服務器

  雖然使用CVCS帶來了諸多便利,可是其缺點也很明顯,例如中央服務器一旦宕機,那麼客戶端將沒法提交變動和協同工做;或者中央服務器磁盤故障,沒有備份倉庫,那麼將丟失全部的變動記錄。網絡

2)分佈式app

  爲了解決CVCS所帶來的問題,又有人提出了分佈式版本控制系統(Distributed Version Control System,DVCS)。分佈式

  這類系統包括Git、BitKeeper等都會將服務器中的倉庫完整的克隆到本地,這麼一來,即便網絡斷開,也能繼續工做,而且受服務器故障的影響也能下降到最小。在圖4中,服務器和兩臺電腦三者之間都能相互推送各自的修訂記錄,而且每臺電腦上都保存了一個本地倉庫。工具

圖4  分佈式版本控制系統

  由此可知,DVCS剝奪了服務器的生殺大權,只讓它負責中轉你們的修訂記錄,即便缺了服務器,也不影響協同工做。

2、快照

  Git不會像CVS、Subversion等版本控制系統那樣存儲每一個文件所變動的內容以及修訂文件版本之間的關係。Git更像是一個文件系統,它存儲的是文件快照(Snapshot)。

  Git會先將那些變動的文件拷貝一份,而後把備份文件轉換成Blob對象,並對其進行壓縮,再把文件各自的內容經過SHA-1哈希運算出對應Blob的名稱(即版本號),以下所示,最後由這些哈希值做爲索引組成一個快照(即版本信息),而經過快照就能反推出該版本中全部發生變動的文件內容。

fbcceef922ce47253804cf00c72c2e955b8bc1b3

  每次提交都會生成一個新的快照,而對於未更改的文件,則直接連接到上一個版本。

3、工做區域

  Git的工做區域包含三部分:工做目錄、暫存區和倉庫,它們的說明以下所列:

  (1)工做目錄(Working Directory)就是去除項目版本信息後的目錄,即磁盤上實際操做的目錄。

  (2)暫存區(Stage)也叫索引區(Index),是一個記錄了變動信息的文件,即保存着變動文件的當前快照,爲提交到倉庫中作準備。

  (3)倉庫(Repository)也叫版本庫,是一個名爲.git的隱藏目錄,保存着項目的元數據、快照等信息,Git的倉庫可分爲遠程和本地兩種。

1)工做流程

  在圖5中,經過工做區域的三部分描繪出了Git基本的工做流程,簡單的將其分爲四步,並給出了相應的Git命令。

圖5  Git的工做流程

  首先在工做目錄中修改源文件,而後用add命令將變動記錄保存到暫存區,再用commit命令將暫存區中的文件提交給本地倉庫,最後用push命令將本地倉庫的信息推送給遠程倉庫。圖中的雲朵表示網絡,由於通常遠程倉庫都會被託管在某臺服務器中,須要經過網絡將本地信息傳送過去。

2)三種狀態

  Git中的文件有多種狀態,此處列出其中的三種:已修改(modified)、已暫存(staged)和已提交(committed)。

(1)已修改是還未提交的變動文件。

(2)已暫存是記錄在當前快照中的變動文件。

(3)已提交是保存在本地倉庫中的變動文件。

4、經常使用命令

  本節將列舉出Git經常使用的命令,包括建立倉庫、提交更新、查看信息等。

1)建立倉庫

  若是要在當前目錄中建立倉庫,那麼能夠用下面的初始化命令。

git init

  在執行該命令後,就會生成名爲.git的隱藏目錄。

2)克隆倉庫

  GitHub是一個提供Git倉庫託管服務的網站,若是要在本機獲取網站中已經存在的倉庫,那麼可使用克隆命令,以下所示。注意,這兩條命令的效果是相同的,由於Git支持HTTPS和SSH兩種數據傳輸協議。

git clone https://github.com/pwstrick/demo.git
git clone git@github.com:pwstrick/demo.git

  在執行該命令後,就會建立一個名爲demo的目錄。默認狀況下,遠程倉庫中的每一個文件的每一個版本都會被拉取下來。

3)提交更新

  工做目錄中的文件可分爲兩大類:已跟蹤(tracked)和未跟蹤(untracked)。已跟蹤的文件是指已經被歸入版本控制的文件,它們的狀態能夠是已修改、已暫存或已提交;而未跟蹤的文件正好與之相反,而且不會被記錄在以前的快照中。當初始化工做目錄時,其中的文件都是未跟蹤的;而當克隆倉庫時,其中的文件都是已跟蹤的。

  使用add命令,開始跟蹤文件,其後面跟的參數既能夠是文件路徑,也能夠是目錄路徑,下面命令中的點號表示跟蹤當前目錄中的全部文件。

git add .

  在執行add命令後,工做目錄中新增或變動的文件就會被記錄在暫存區,而且肯定了下次提交時的快照內容。

  當暫存區已準備完畢能夠提交時,就能執行commit命令了,以下所示,其中「-m」參數能爲這次提交添加備註說明,注意,得用引號包裹。

git commit -m "add files"

  commit命令能爲本地倉庫建立一個持久快照,其內容來自於暫存區的臨時快照。commit命令還有一個「-a」參數,可讓已經跟蹤過的文件直接到暫存區,而後一併提交給本地倉庫,從而就能跳過add命令,若是要與「-m」一塊兒使用,能夠像下面這樣。

git commit -am "add files"

  固然,對於未跟蹤的文件,沒法省略add命令,仍然得執行。

4)忽略文件

  在工做目錄中建立一個名爲.gitignore的文件,列出要忽略的匹配模式(以下所示),就能讓Git再也不管理相關的文件或目錄,例如忽略日誌、編譯生成的臨時文件、Node.js的安裝目錄等。

# system
*.docx
node_modules/

  第一行至關於註釋,第二行是告訴Git忽略後綴爲「.docx」的文件,第三行是忽略node_modules目錄。.gitignore文件支持Glob模式匹配(即簡化過的正則表達式),另外還有一些格式規範:

(1)全部空行或以「#」開頭的行都會被Git忽略。

(2)匹配模式能以「/」開頭防止遞歸。

(3)匹配模式能以「/」結尾指定目錄。

(4)只要在模式前加上「!」取反,就能忽略該模式之外的文件或目錄。

5)刪除文件

  Git提供了rm命令,可刪除工做目錄中的指定文件,而且能消除它在暫存區中的記錄,以下所示。

git rm demo.txt

  rm命令的後面既能夠跟文件或目錄的名稱,也能跟Glob模式的正則表達式,例如像下面這樣刪除後綴爲「.log」的文件。

git rm *.log

  結合commit命令就能將刪除的操做告知本地倉庫。

6)撤銷操做

  若是要撤銷工做目錄中的文件變動,可使用checkout命令,以下所示,撤銷了demo.txt文件中的變動。注意,命令中的「--」不能省略,不然就會執行切換分支的操做。

git checkout -- demo.txt

  若是要撤銷暫存區中的文件變動,可使用reset命令,以下所示,其中HEAD是一個指針,指向當前分支,經過它能獲得該分支上最後一次提交的快照。

git reset HEAD demo.txt

  注意,上述兩種撤銷都是在commit命令以前執行的。有些狀況下的撤銷須要依次執行上面兩條命令才能完成,例如demo.txt修改了屢次,而且執行了屢次「git add .」命令,而後要撤銷全部的更改。

7)回退版本

  若是要實現版本回退,那麼首先得知道版本號,而經過log命令就能獲取到當前分支的歷史版本,以下所示,包括版本號、做者、備註等信息。

$ git log
commit 00ef86de2fe382c5e1a9185a182a35bbbd34c0fd
Author: strick <pwstrick@163.com>
Date:   Tue Jul 2 14:26:31 2019 +0800
    test

commit 9a701365eeb47cf876c726cf5e321af5ce9cabbf
Author: strick <pwstrick@163.com>
Date:   Tue Jul 2 14:13:19 2019 +0800
    add demo.txt

  如今就能經過reset命令,指定版本號,實現回退,以下所示。因爲HEAD指針的存在,Git的版本回退其實就是移動指向,所以其速度是很是快的。

git reset --hard 9a701365eeb47cf876c726cf5e321af5ce9cabbf

8)查看信息

  在Git中用於查看信息的命令包括log、status、diff和reflog等,其中log命令已在前文介紹過,用來查看提交的版本歷史,按提交時間倒序排列。接下來會先修改demo.txt文件,而後再執行相關的查看命令,其中命令可選擇的參數因爲篇幅緣由都沒有給出。

  status命令用來查看工做目錄的狀態,顯示變動信息以及提示可用命令,以下所示。

$ git status
On branch master
Your branch is up-to-date with 'origin/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:   demo.txt
no changes added to commit (use "git add" and/or "git commit -a")

  diff命令用來查看工做目錄和暫存區之間的差別,以下所示,其中a和b表示的是同一個文件的不一樣版本。

$ git diff
diff --git a/demo.txt b/demo.txt
index d28d40b..30d74d2 100644
--- a/demo.txt
+++ b/demo.txt
@@ -1 +1 @@
-add
\ No newline at end of file
+test
\ No newline at end of file

  diff命令還能比較暫存區和本地倉庫、兩次commit提交、文件修改先後等之間的差別。

  reflog命令用來查看當前分支的全部操做記錄,包括commit、reset、pull等,以下所示。

e1ce188 HEAD@{0}: pull origin master: Fast-forward
9a70136 HEAD@{1}: reset: moving to 9a701365eeb47cf876c726cf5e321af5ce9cabbf
......
fb8e5df HEAD@{13}: commit: demo
f1f30d6 HEAD@{14}: clone: from https://github.com/pwstrick/demo.git

9)遠程倉庫

  與遠程倉庫相關的命令有remote、fetch、pull和push,其中remote可與其它命令(例如add、show、rename等)配合實現擴展功能。

  remote命令可列出全部遠程倉庫的簡稱,在執行克隆命令時,Git會默認爲其添加一個名爲origin的遠程倉庫。若是爲remote命令添加「-v」參數,那麼會顯示遠程倉庫的簡稱和其讀寫的URL。

$ git remote -v
origin  https://github.com/pwstrick/demo.git (fetch)
origin  https://github.com/pwstrick/demo.git (push)

  fetch命令可從遠程倉庫中拉取本地沒有的數據,例如缺失的分支。

  pull命令可抓取遠程分支的最新變動,並將其自動合併到當前分支中,在pull後面會緊跟遠程倉庫的簡稱和分支名稱,以下所示。

git pull origin master

  push命令可將本地快照、變動的文件等信息推送到遠程倉庫中,其格式與pull命令相似,以下所示。

git push origin master

5、分支

  Git之因此能高性能的處理分支,主要得益於其不同凡響的存儲設計:棄文件變化,取文件快照。每次commit提交,Git都會建立一個提交對象(Commit Object),該對象包含本次快照的指針、父對象的指針(即上一個提交對象)、當前工做目錄的結構和相關附屬信息(例如做者、備註等),注意,首次提交產生的提交對象沒有父對象。

  Git的分支本質上就是指向提交對象的可變指針,其默認分支叫master,如圖6所示,用圓表明提交對象,圓內的字符串表示版本號的前5位。

圖6  分支和其提交歷史

1)建立

  建立分支其實就是新建一個新的可移動指針,若是要建立一條develop分支,那麼執行的命令能夠像下面這樣。

git branch develop

  如今就會變成兩條分支,如圖7所示。

圖7  兩條分支

  執行「git branch」命令可以查看已有的分支,在當前分支的名稱前會加「*」標記,以下所示。

$ git branch
  develop
* master

2)切換

  在Git中,有一個特殊的HEAD指針,以前曾提到過它能指向當前分支,至關於當前分支的別名,如圖8所示。

圖8  HEAD指向當前分支

  切換分支其實就是改變HEAD的指向,例如切換到develop分支,其命令以下所示,內部操做如圖9所示。

git checkout develop

圖9  切換分支

  有一條快捷命令,能夠建立一個分支並自動切換到那個分支,以下所示,給checkout命令添加了「-b」參數。

git checkout -b develop

3)合併

  有了分支後,就能在各自的分支工做,而且互不干擾,例如在develop分支提交了一個版本,如圖10所示,develop分支向前移動了,而master分支仍在原地。

圖10  HEAD自動向前移動

  如今用「git checkout master」命令將分支切換回master,HEAD會指向master分支,而且工做目錄將恢復成master分支中的快照。若是要將develop分支中的變動記錄合併(Merge)到master分支中,那麼可使用merge命令,以下所示。

$ git merge develop
Updating 9a9636b..64ea4b2
Fast-forward
 demo.txt | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

  注意上面的Fast-forward,說明此次合併使用了快進模式,即直接將指針向前移動。舉例來講,當前開發處在develop分支,在提交一系列變動後,切換到master分支,而master分支沒有提交新的變動,此時將develop分支合併到master分支就會採用快進模式。

4)衝突

  若是兩個分支對同一文件的同一行都作了不一樣的修改,那麼在合併時就會發生衝突(Conflict),以下所示,都修改了demo.txt文件的第一行。

$ git merge develop
Auto-merging demo.txt
CONFLICT (content): Merge conflict in demo.txt
Automatic merge failed; fix conflicts and then commit the result.

  此時,Git就會中止工做,等待衝突的解決。使用status命令能夠查看全部衝突的文件,以下所示。

$ git status
On branch master
Your branch is up-to-date with 'origin/master'.
You have unmerged paths.
  (fix conflicts and run "git commit")
Unmerged paths:
  (use "git add <file>..." to mark resolution)
        both modified:   demo.txt
no changes added to commit (use "git add" and/or "git commit -a")

  查看demo.txt文件,能夠看到下面的特殊區段。

<<<<<<< HEAD
test
=======
minus
>>>>>>> develop

  Git用<<<<<<<、=======和>>>>>>>標記出不一樣分支的變動,其中上半部分是HEAD所指的master分支,下半部分是develop分支。要解決衝突,就得手動去掉Git生成的多餘字符,以及保留其中一個分支的修改,在完成這一系列的操做後,demo.txt文件中的內容就會變成下面這樣。

minus

  如今只要將衝突文件的變動保存到暫存區(以下所示),就能將它們標記成已解決,繼續下面的工做了。

git add .
git commit -am "conflict"

  當衝突的文件特別多時,一個個的查找不免會費時費力,改用Git客戶端工具會便捷不少,例如TortoiseGit,它不只能列出衝突的文件,還能提供圖11中的可視化編輯界面。

圖11  TortoiseGit衝突解決

5)遠程

  以前經過push命令將本地分支的信息推送到了遠程倉庫中(以下所示),與此同時,若是沒有遠程的同名分支,那麼Git會自動爲其建立一個,而且還會爲它們兩個創建跟蹤關係。

git push origin master

  利用branch命令可在本地查看遠程倉庫(以下所示),分支會以「遠程倉庫/分支名稱」的形式顯示,例如origin/master。

$ git branch -r
  origin/develop
  origin/master

  若是當前分支與遠程分支存在跟蹤關係,那麼在push或pull時可省略分支名稱,以下所示。

git pull origin
git push origin

  Git容許手動創建跟蹤關係,只要爲branch命令添加「-u」或「--set-upstream-to」參數便可(以下所示),從而就能讓一個本地分支跟蹤多個遠程分支。

$ git branch -u origin/strick
Branch develop set up to track remote branch strick from origin.

  若是當前分支只有一個可跟蹤的遠程分支,那麼在push或pull時連倉庫名稱都能省略,以下所示。

git pull
git push

6)刪除

  若是要刪除分支,那麼有兩條命令可供選擇,下面的第一條可刪除本地分支,第二條可刪除遠程分支,本質上它們作的只是移除相應的指針。

git branch -d develop
git push origin --delete develop

7)變基

  變基(Rebase)能將一條分支上的變動轉移到另外一條分支上,以圖12中的master和develop兩條分支爲例。

圖12  變基前的兩條分支

  假設當前分支是develop,變基的目標分支是master,執行rebase命令後的分支狀況如圖13所示。

圖13  變基後的兩條分支

   接下來會經過一個例子來說解變基的用法,首先經過log命令查看master分支的提交歷史,以下所示,其中「number」是修改demo.txt文件時所提交的備註。

$ git log
commit 7c27be31904221f3bb4556ca39dd7c8dea071178
Author: strick <pwstrick@163.com>
Date:   Wed Jul 3 17:48:47 2019 +0800
    number

  而後切換到develop分支,並執行rebase命令,以下所示。注意,變基目標是master而不是develop。

$ git checkout develop
$ git rebase master
First, rewinding head to replay your work on top of it...
Applying: digit
Using index info to reconstruct a base tree...
M       demo.txt
Falling back to patching base and 3-way merge...
Auto-merging demo.txt
CONFLICT (content): Merge conflict in demo.txt
Failed to merge in the changes.
Patch failed at 0001 digit
The copy of the patch that failed is found in:
   D:/demo/.git/rebase-apply/patch

When you have resolved this problem, run "git rebase --continue".
If you prefer to skip this patch, run "git rebase --skip" instead.
To check out the original branch and stop rebasing, run "git rebase --abort".

  因爲兩條分支同時修改了demo.txt文件中的內容,所以產生了衝突,必須先將其解決,才能繼續後面的操做。注意,develop分支的修改記錄先於master分支提交。

  當解決了全部衝突以後,先執行add命令,注意,此時不須要commit命令。而後再爲rebase命令添加「--continue」(以下所示),就能完成變基,其中「digit」也是修改demo.txt文件時所提交的備註。

$ git add .
$ git rebase --continue
Applying: digit

  接着切換回master分支,並將develop分支中的修改合併進來,注意,此時開啓了快進模式。

$ git checkout master
$ git merge develop
Updating 7c27be3..24c4b5a
Fast-forward
 demo.txt | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

  最後執行log命令,就能看到develop分支所提交的版本添加到了master分支的後面,注意,它沒有根據提交時間按順序插入。

$ git log
commit 24c4b5adb332f8c4f2e5ec39a7c77e0fc224b065
Author: strick <pwstrick@163.com>
Date:   Wed Jul 3 17:48:00 2019 +0800
    digit

commit 7c27be31904221f3bb4556ca39dd7c8dea071178
Author: strick <pwstrick@163.com>
Date:   Wed Jul 3 17:48:47 2019 +0800
    number

  變基的一大特色就是能將分叉的提交歷史梳理成一條直線,另外一個特色是它會改變提交歷史,這與合併徹底不一樣。變基還有一條基本原則,即只對還沒有推送和分享給他人的本地修改容許執行變基操做。

相關文章
相關標籤/搜索