Git簡(jiǎn)介和使用說明

概述

什么是“版本控制”?我為什么要關(guān)心它呢? 版本控制是一種記錄一個(gè)或若干文件內(nèi)容變化,以便將來查閱特定版本修訂情況的系統(tǒng)。 在本書所展示的例子中,我們對(duì)保存著軟件源代碼的文件作版本控制,但實(shí)際上,你可以對(duì)任何類型的文件進(jìn)行版本控制。

三種分類(演化)

  • 本地版本控制系統(tǒng)
  • 集中化版本控制系統(tǒng)
  • 分布式版本控制系統(tǒng)

分布式版本控制系統(tǒng)(Distributed Version Control System,簡(jiǎn)稱 DVCS)。 在這類系統(tǒng)中,像 Git、Mercurial、Bazaar 以及 Darcs 等,客戶端并不只提取最新版本的文件快照,而是把代碼倉庫完整地鏡像下來。 這么一來,任何一處協(xié)同工作用的服務(wù)器發(fā)生故障,事后都可以用任何一個(gè)鏡像出來的本地倉庫恢復(fù)。 因?yàn)槊恳淮蔚目寺〔僮?,?shí)際上都是一次對(duì)代碼倉庫的完整備份。


分布式版本控制系統(tǒng)

更進(jìn)一步,許多這類系統(tǒng)都可以指定和若干不同的遠(yuǎn)端代碼倉庫進(jìn)行交互。籍此,你就可以在同一個(gè)項(xiàng)目中,分別和不同工作小組的人相互協(xié)作。 你可以根據(jù)需要設(shè)定不同的協(xié)作流程,比如層次模型式的工作流,而這在以前的集中式系統(tǒng)中是無法實(shí)現(xiàn)的。

Git基礎(chǔ)

1. 直接記錄快照,而非差異比較

Git 和其它版本控制系統(tǒng)(包括 Subversion 和近似工具)的主要差別在于 Git 對(duì)待數(shù)據(jù)的方法。概念上來區(qū)分,其它大部分系統(tǒng)以文件變更列表的方式存儲(chǔ)信息。這類系統(tǒng)(CVS、Subversion等等)將它們保存的信息看作是一組基本文件和每個(gè)文件隨時(shí)間逐步累積的差異。Git 更像是把數(shù)據(jù)看作是對(duì)小型文件系統(tǒng)的一組快照。 每次你提交更新,或在 Git 中保存項(xiàng)目狀態(tài)時(shí),它主要對(duì)當(dāng)時(shí)的全部文件制作一個(gè)快照并保存這個(gè)快照的索引。 為了高效,如果文件沒有修改,Git 不再重新存儲(chǔ)該文件,而是只保留一個(gè)鏈接指向之前存儲(chǔ)的文件。 Git 對(duì)待數(shù)據(jù)更像是一個(gè)快照流。

2. 近乎所有操作都是本地執(zhí)行

在 Git 中的絕大多數(shù)操作都只需要訪問本地文件和資源,一般不需要來自網(wǎng)絡(luò)上其它計(jì)算機(jī)的信息。 如果你習(xí)慣于所有操作都有網(wǎng)絡(luò)延時(shí)開銷的集中式版本控制系統(tǒng),Git 在這方面會(huì)讓你感到速度之神賜給了 Git 超凡的能量。 因?yàn)槟阍诒镜卮疟P上就有項(xiàng)目的完整歷史,所以大部分操作看起來瞬間完成。

3. Git 保證完整性

Git 中所有數(shù)據(jù)在存儲(chǔ)前都計(jì)算校驗(yàn)和,然后以校驗(yàn)和來引用。 這意味著不可能在 Git 不知情時(shí)更改任何文件內(nèi)容或目錄內(nèi)容。 這個(gè)功能建構(gòu)在 Git 底層,是構(gòu)成 Git 哲學(xué)不可或缺的部分。 若你在傳送過程中丟失信息或損壞文件,Git 就能發(fā)現(xiàn)。

Git 用以計(jì)算校驗(yàn)和的機(jī)制叫做 SHA-1 散列(hash,哈希)。 這是一個(gè)由 40 個(gè)十六進(jìn)制字符(0-9 和 a-f)組成的字符串,基于 Git 中文件的內(nèi)容或目錄結(jié)構(gòu)計(jì)算出來。

4. Git 一般只添加數(shù)據(jù)

你執(zhí)行的 Git 操作,幾乎只往 Git 數(shù)據(jù)庫中增加數(shù)據(jù)。 很難讓 Git 執(zhí)行任何不可逆操作,或者讓它以任何方式清除數(shù)據(jù)。 同別的 VCS 一樣,未提交更新時(shí)有可能丟失或弄亂修改的內(nèi)容;但是一旦你提交快照到 Git 中,就難以再丟失數(shù)據(jù),特別是如果你定期的推送數(shù)據(jù)庫到其它倉庫的話。

三種狀態(tài)

Git 有三種狀態(tài),你的文件可能處于其中之一:

  • 已提交(committed)表示數(shù)據(jù)已經(jīng)安全的保存在本地?cái)?shù)據(jù)庫中
  • 已修改(modified)表示修改了文件,但還沒保存到數(shù)據(jù)庫中
  • 已暫存(staged)表示對(duì)一個(gè)已修改文件的當(dāng)前版本做了標(biāo)記,使之包含在下次提交的快照中

三個(gè)工作區(qū)域


工作目錄、暫存區(qū)域以及 Git 倉庫
# 加上遠(yuǎn)程倉庫算四個(gè)
Workspace:工作區(qū)
Index / Stage:暫存區(qū)
Repository:倉庫區(qū)(或本地倉庫)
Remote:遠(yuǎn)程倉庫

基本的 Git 工作流程如下:

  • 在工作目錄中修改文件。
  • 暫存文件,將文件的快照放入暫存區(qū)域。
  • 提交更新,找到暫存區(qū)域的文件,將快照永久性存儲(chǔ)到 Git 倉庫目錄。

安裝和使用

安裝(略)

建議保留Git Bash

GUI工具

常用工具推薦

  • IntelliJ IDEA 自帶插件
  • Source Tree 號(hào)稱最好用的圖形工具
  • TortoiseGit 小烏龜,使用SVN的時(shí)候經(jīng)常用

SSH

1. 驗(yàn)證SSH key是否已存在

$ cd ~/.ssh
$ ls

看是否存在.pub文件,如果已存在可跳過第2步

2. 生成新的SSH key

$ ssh-keygen -t rsa -C "test@gmail.com"

參數(shù)說明:

  • -t 指定密鑰類型,默認(rèn)是 rsa ,可以省略
  • -C 設(shè)置注釋文字,比如郵箱
  • -f 指定密鑰文件存儲(chǔ)文件名

3. 添加SSH key到Git服務(wù)

$ cat ~/.ssh/id_rsa.pub | clip

也可以用文本編輯器打開pub文件后復(fù)制字符
打開Git的SSH配置頁,將復(fù)制的SSH key添加進(jìn)去

基本命令

常用的6個(gè)命令
# 1. 初始化本地倉庫
$ git init

# 2. 綁定一個(gè)遠(yuǎn)程倉庫,并將遠(yuǎn)程倉庫別名設(shè)置為origin 
$ git remote add origin '遠(yuǎn)程git倉庫地址' 

# 3. 從遠(yuǎn)程倉庫獲取最新代碼
$ git pull origin master 

# 4. 拉取遠(yuǎn)程倉庫到本地
$ git clone '遠(yuǎn)程git倉庫地址' 

# 5. 查看當(dāng)前改動(dòng)了哪些文件
$ git status 

# 6. 將改動(dòng)的這個(gè)文件提交到暫存區(qū)
$ git add '一個(gè)文件的路徑' 

# 7. 將文件夾下所有的改動(dòng)提交到暫存區(qū)
$ git add '一個(gè)文件夾' 

# 8. 將所有改動(dòng)的文件提交到暫存區(qū)
$ git add .

# 9. 對(duì)本次要提交的的文件進(jìn)行說明
$ git commit -m '提交說明'

# 10. 將修改的文件提交到遠(yuǎn)程倉庫
$ git push origin master 

# 11. 創(chuàng)建一個(gè)名為dev的新分支,并切換到dev分支,dev的內(nèi)容取決于你再哪個(gè)分支上執(zhí)行該命令
$ git checkout -b dev 

# 12. 創(chuàng)建一個(gè)名為dev的新分支,但是不做切換動(dòng)作。dev的內(nèi)容取決于你在哪個(gè)分支上執(zhí)行該命令
$ git branch dev 

# 13. 切換到dev分支,切換分支要保證工作區(qū)純凈
$ git checkout dev 

# 14. 將dev分支的改動(dòng)合并到目標(biāo)分支。目標(biāo)分支為你當(dāng)前工作區(qū)所在的分支
$ git merge dev 

# 15. 查看所有的分支
$ git branch -a 

# 16. 刪除本地dev分支
$ git branch -d dev 

# 17. 刪除origin對(duì)應(yīng)的遠(yuǎn)程倉庫的dev分支
$ git push --delete origin dev 

# 18. 撤銷一個(gè)文件的改動(dòng)
$ git checkout -- '文件路徑' 

# 19. 將一個(gè)文件撤出暫存區(qū),保留修改
$ git reset '文件路徑' 

# 20. 未push到遠(yuǎn)程分支時(shí)修改commit message的方法
$ git commit --amend -m '消息內(nèi)容'

# 21. 刪除遠(yuǎn)程標(biāo)簽
$ git push origin :refs/tags/標(biāo)簽名

高級(jí)用法

命令和工作區(qū)
  1. 需求做了一半告訴你需求有變化,不再做了,如果你此時(shí)還沒有進(jìn)行commit
$ git reset --hard
  1. 代碼已經(jīng)commit ,還未進(jìn)行push
$ git reset --hard '上一次提交的版本號(hào)'
  1. 代碼已經(jīng)commit,但是發(fā)現(xiàn)不該提交的文件也在里面
$ git reset '上一次提交的版本號(hào)' 

將提交撤銷,把所有文件從本地倉庫退出保持自己修改的狀態(tài),重置暫存區(qū),但工作區(qū)不變

  1. 代碼已經(jīng)commit ,提交說明寫的不太好,想重新修改下提交說明
$ git reset --soft '上一次提交的版本號(hào)'

不涉及暫存區(qū)和工作區(qū),可以直接執(zhí)行git commit -m '說明'

  1. 代碼改了半天,發(fā)現(xiàn)自己改錯(cuò)分支了。

沒有沖突的情況下可以試著直接執(zhí)行git checkout '對(duì)的分支',如果提示有錯(cuò),再使用以下方法

$ git add .
$ git stash
$ git checkout '對(duì)的分支'
$ git stash pop
  1. 代碼不但做錯(cuò)分支了,而且還提交到遠(yuǎn)程了。
$ git checkcout '錯(cuò)誤分支'
$ git reset --soft '上一次提交的版本號(hào)'
$ git stash
$ git checkout '對(duì)的分支'
$ git stash pop
$ git commit -m "版本說明"
$ git push origin '對(duì)的分支'
$ git checkout 錯(cuò)的分支
$ git push origin 錯(cuò)的分支 --force
  1. 已經(jīng)發(fā)布一期,二期內(nèi)容還未完全開發(fā)完??蛻艏敝胍诘囊粋€(gè)功能。該功能二期已經(jīng)開發(fā)完。可在一期的分支上執(zhí)行:
$ git cherry-pick '該功能所形成的版本號(hào)'

如果有多次提交可按 git cherry-pick 功能版本號(hào)1 功能版本號(hào)2
注意,抽取功能過程中可能會(huì)產(chǎn)生沖突。需要解決沖突后再繼續(xù)cherry-pick

  1. 合并代碼某個(gè)沖突文件已經(jīng)搞不太清楚是保留誰的版本合適。
$ git checkout --conflict=diff3 '已經(jīng)merge沖突的某個(gè)文件'。

此命令每個(gè)沖突的文件會(huì)產(chǎn)生三段標(biāo)志,第一段為自己修改的。第二段是自己修改前的。第三段是別人修改的。

  1. 撤銷已提交的某個(gè)commit
$ git revert '提交的版本號(hào)'

此命令會(huì)新建一個(gè)反轉(zhuǎn)的commit,一般用于已提交遠(yuǎn)程倉庫的回退。

  1. 將某個(gè)文件回退到指定版本
$ git checkout '版本號(hào)' -- '文件路徑'
  1. 之前重置了一個(gè)不想保留的提交,但是現(xiàn)在又想要回滾。
# 獲取所有操作歷史
$ git reflog
# 重置到相應(yīng)提交
$ git reset HEAD@{4}
# ……或者……
$ git reset --hard <提交的版本號(hào)>

常用工作流

這三種工作流程,有一個(gè)共同點(diǎn):都采用"功能驅(qū)動(dòng)式開發(fā)"(Feature-driven development,簡(jiǎn)稱FDD)。

它指的是,需求是開發(fā)的起點(diǎn),先有需求再有功能分支(feature branch)或者補(bǔ)丁分支(hotfix branch)。完成開發(fā)后,該分支就合并到主分支,然后被刪除。

1. Git flow

Git Flow

1.1 兩個(gè)特點(diǎn)

首先,項(xiàng)目存在兩個(gè)長(zhǎng)期分支。

  • 主分支master
  • 開發(fā)分支develop

前者用于存放對(duì)外發(fā)布的版本,任何時(shí)候在這個(gè)分支拿到的,都是穩(wěn)定的分布版;后者用于日常開發(fā),存放最新的開發(fā)版。

其次,項(xiàng)目存在三種短期分支。

  • 功能分支(feature branch)
  • 補(bǔ)丁分支(hotfix branch)
  • 預(yù)發(fā)分支(release branch)

一旦完成開發(fā),它們就會(huì)被合并進(jìn)developmaster,然后被刪除。

1.2 優(yōu)缺點(diǎn)

Git flow的優(yōu)點(diǎn)是清晰可控,缺點(diǎn)是相對(duì)復(fù)雜,需要同時(shí)維護(hù)兩個(gè)長(zhǎng)期分支。大多數(shù)工具都將master當(dāng)作默認(rèn)分支,可是開發(fā)是在develop分支進(jìn)行的,這導(dǎo)致經(jīng)常要切換分支,非常煩人。

更大問題在于,這個(gè)模式是基于"版本發(fā)布"的,目標(biāo)是一段時(shí)間以后產(chǎn)出一個(gè)新版本。但是,很多網(wǎng)站項(xiàng)目是"持續(xù)發(fā)布",代碼一有變動(dòng),就部署一次。這時(shí),master分支和develop分支的差別不大,沒必要維護(hù)兩個(gè)長(zhǎng)期分支。

2. Github flow

Github flow是Git flow的簡(jiǎn)化版,專門配合"持續(xù)發(fā)布"。它是 Github.com 使用的工作流程

2.1 流程

它只有一個(gè)長(zhǎng)期分支,就是master,因此用起來非常簡(jiǎn)單。官方推薦的流程如下:

Github官方流程

第一步:根據(jù)需求,從master拉出新分支,不區(qū)分功能分支或補(bǔ)丁分支。
第二步:新分支開發(fā)完成后,或者需要討論的時(shí)候,就向master發(fā)起一個(gè)pull request(簡(jiǎn)稱PR)。
第三步:Pull Request既是一個(gè)通知,讓別人注意到你的請(qǐng)求,又是一種對(duì)話機(jī)制,大家一起評(píng)審和討論你的代碼。對(duì)話過程中,你還可以不斷提交代碼。
第四步:你的Pull Request被接受,合并進(jìn)master,重新部署后,原來你拉出來的那個(gè)分支就被刪除。(先部署再合并也可。)

2.2 優(yōu)缺點(diǎn)

Github flow 的最大優(yōu)點(diǎn)就是簡(jiǎn)單,對(duì)于"持續(xù)發(fā)布"的產(chǎn)品,可以說是最合適的流程。

問題在于它的假設(shè):master分支的更新與產(chǎn)品的發(fā)布是一致的。也就是說,master分支的最新代碼,默認(rèn)就是當(dāng)前的線上代碼。

可是,有些時(shí)候并非如此,代碼合并進(jìn)入master分支,并不代表它就能立刻發(fā)布。比如,蘋果商店的APP提交審核以后,等一段時(shí)間才能上架。這時(shí),如果還有新的代碼提交,master分支就會(huì)與剛發(fā)布的版本不一致。另一個(gè)例子是,有些公司有發(fā)布窗口,只有指定時(shí)間才能發(fā)布,這也會(huì)導(dǎo)致線上版本落后于master分支。

上面這種情況,只有master一個(gè)主分支就不夠用了。通常,你不得不在master分支以外,另外新建一個(gè)production分支跟蹤線上版本。

3. Gitlab flow

Gitlab flow是 Git flow 與 Github flow 的綜合。它吸取了兩者的優(yōu)點(diǎn),既有適應(yīng)不同開發(fā)環(huán)境的彈性,又有單一主分支的簡(jiǎn)單和便利。它是 Gitlab.com 推薦的做法。

3.1 上游優(yōu)先

Gitlab flow 的最大原則叫做"上游優(yōu)先"(upsteam first),即只存在一個(gè)主分支master,它是所有其他分支的"上游"。只有上游分支采納的代碼變化,才能應(yīng)用到其他分支。

Chromium項(xiàng)目就是一個(gè)例子,它明確規(guī)定,上游分支依次為:

  1. Linus Torvalds的分支
  2. 子系統(tǒng)(比如netdev)的分支
  3. 設(shè)備廠商(比如三星)的分支

3.2 持續(xù)發(fā)布

Gitlab flow 分成兩種情況,適應(yīng)不同的開發(fā)流程。

持續(xù)發(fā)布

對(duì)于"持續(xù)發(fā)布"的項(xiàng)目,它建議在master分支以外,再建立不同的環(huán)境分支。比如,"開發(fā)環(huán)境"的分支是master,"預(yù)發(fā)環(huán)境"的分支是pre-production,"生產(chǎn)環(huán)境"的分支是production

開發(fā)分支是預(yù)發(fā)分支的"上游",預(yù)發(fā)分支又是生產(chǎn)分支的"上游"。代碼的變化,必須由"上游"向"下游"發(fā)展。比如,生產(chǎn)環(huán)境出現(xiàn)了bug,這時(shí)就要新建一個(gè)功能分支,先把它合并到master,確認(rèn)沒有問題,再cherry-pickpre-production,這一步也沒有問題,才進(jìn)入production

只有緊急情況,才允許跳過上游,直接合并到下游分支。

3.3 版本發(fā)布

版本發(fā)布

對(duì)于"版本發(fā)布"的項(xiàng)目,建議的做法是每一個(gè)穩(wěn)定版本,都要從master分支拉出一個(gè)分支,比如2-3-stable、2-4-stable等等。

以后,只有修補(bǔ)bug,才允許將代碼合并到這些分支,并且此時(shí)要更新小版本號(hào)。

4. AoneFlow

另辟蹊徑的 AoneFlow

在 AoneFlow 上你能看到許多其他分支模式的影子。它基本上兼顧了 TrunkBased 的“易于持續(xù)集成”和 GitFlow 的“易于管理需求”特點(diǎn),同時(shí)規(guī)避掉 GitFlow 的那些繁文縟節(jié)。

看一下具體套路。AoneFlow 只使用三種分支類型:主干分支、特性分支、發(fā)布分支,以及三條基本規(guī)則。

4.1 規(guī)則一,開始工作前,從主干創(chuàng)建特性分支。

AoneFlow 的特性分支基本借鑒 GitFlow,沒有什么特別之處。每當(dāng)開始一件新的工作項(xiàng)(比如新的功能或是待解決的問題)的時(shí)候,從代表最新已發(fā)布版本的主干上創(chuàng)建一個(gè)通常以feature/前綴命名的特性分支,然后在這個(gè)分支上提交代碼修改。也就是說,每個(gè)工作項(xiàng)(可以是一個(gè)人完成,或是多個(gè)人協(xié)作完成)對(duì)應(yīng)一個(gè)特性分支,所有的修改都不允許直接提交到主干。

從主干創(chuàng)建特性分支

4.2 規(guī)則二,通過合并特性分支,形成發(fā)布分支。

GitFlow 先將已經(jīng)完成的特性分支合并回公共主線(即開發(fā)分支),然后從公共主線拉出發(fā)布分支。TrunkBased 同樣是等所有需要的特性都在主干分支上開發(fā)完成,然后從主干分支的特定位置拉出發(fā)布分支。而 AoneFlow 的思路是,從主干上拉出一條新分支,將所有本次要集成或發(fā)布的特性分支依次合并過去,從而得到發(fā)布分支。發(fā)布分支通常以release/前綴命名。

通過合并特性分支,形成發(fā)布分支

4.3 規(guī)則三,發(fā)布到線上正式環(huán)境后,合并相應(yīng)的發(fā)布分支到主干,在主干添加標(biāo)簽,同時(shí)刪除該發(fā)布分支關(guān)聯(lián)的特性分支。

當(dāng)一條發(fā)布分支上的流水線完成了一次線上正式環(huán)境的部署,就意味著相應(yīng)的功能真正的發(fā)布了,此時(shí)應(yīng)該將這條發(fā)布分支合并到主干。為了避免在代碼倉庫里堆積大量歷史上的特性分支,還應(yīng)該清理掉已經(jīng)上線部分特性分支。與 GitFlow 相似,主干分支上的最新版本始終與線上版本一致,如果要回溯歷史版本,只需在主干分支上找到相應(yīng)的版本標(biāo)簽即可。

發(fā)布到線上正式環(huán)境后,合并相應(yīng)的發(fā)布分支到主干,在主干添加標(biāo)簽,同時(shí)刪除該發(fā)布分支關(guān)聯(lián)的特性分支

除了基本規(guī)則,還有一些實(shí)際操作中不成文的技巧。比如上線后的 Hotfix,正常的處理方法應(yīng)該是,創(chuàng)建一條新的發(fā)布分支,對(duì)應(yīng)線上環(huán)境(相當(dāng)于 Hotfix 分支),同時(shí)為這個(gè)分支創(chuàng)建臨時(shí)流水線,以保障必要的發(fā)布前檢查和冒煙測(cè)試能夠自動(dòng)執(zhí)行。但其實(shí)還有一種簡(jiǎn)便方法是,將線上正式環(huán)境對(duì)應(yīng)的發(fā)布分支上關(guān)聯(lián)的特性分支全部清退掉,在這個(gè)發(fā)布分支上直接進(jìn)行修改,改完利用現(xiàn)成的流水線自動(dòng)發(fā)布。如果非得修一個(gè)歷史版本的 Bug 怎么辦呢?那就老老實(shí)實(shí)的在主干分支找到版本標(biāo)簽位置,然后從那個(gè)位置創(chuàng)建 Hotfix 分支吧。

分支開發(fā)規(guī)范

  • 分支的定義(master、develop、release、hotfix、feature)
  • 分支命名規(guī)范
  • checkout、merge request流程
  • 提測(cè)流程
  • 上線流程
  • Hotfix流程

參考資料

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容