一、Git簡介
Git是Linux之父Linus與2005年用C語言編寫的分布式控制系統(tǒng)。 Git的分布式區(qū)別于SVN等集中式版本控制系統(tǒng)的地方有哪些呢?
- 集中式:有一個(gè)集中管理的“中央服務(wù)器”,保存所有的修訂版本。
- 缺點(diǎn)是版本控制器是放在服務(wù)器中,必須聯(lián)網(wǎng)才能協(xié)同工作,保存數(shù)據(jù)。
- 如果服務(wù)器損壞,將丟失所有數(shù)據(jù)。
- 分布式:分布式?jīng)]有“中央服務(wù)器”。每一個(gè)人電腦上都有一個(gè)獨(dú)立且完整的版本庫。
- 工作不一定需要聯(lián)網(wǎng),每次可以提交到本地版本庫,需要的時(shí)候在同步到網(wǎng)絡(luò)或其他人的版本庫中
- 不擔(dān)心數(shù)據(jù)的丟失,數(shù)據(jù)丟失了,可以隨時(shí)在其他人電腦中拷貝數(shù)據(jù)
- 工作中很少有相互之間推送版本庫,往往還是會(huì)有一臺電腦充當(dāng)“中央服務(wù)器”,方便大家交換數(shù)據(jù),或者使用github等網(wǎng)絡(luò)服務(wù)器
- git還擁有分支管理等技術(shù)
- 幾個(gè)專用名詞
Workspace 工作區(qū):數(shù)據(jù)所在的目錄
Reponsitory 版本庫:本地版本庫,隱藏文件.git
Index/Stage 暫存區(qū): 版本庫中緩存修改的數(shù)據(jù)
Remote 遠(yuǎn)程倉庫: 服務(wù)器中的版本庫,一般為GitHub/BitBucket/GitLab
等
二、配置 Git
Git安裝完成后需要配置Git環(huán)境。Git自帶 git config
工具幫助設(shè)置控制Git倉庫的外觀和行為的配置變量。這些配置變量分別存儲(chǔ)在三個(gè)不同的位置:
/etc/gitconfig
文件:包含系統(tǒng)上每一個(gè)用戶及他們倉庫的通用配置。如果使用帶有--system
選項(xiàng)的git config
時(shí), 它會(huì)從文件讀寫配置變量~/.gitconfig
或~/.config/git/config
文件: 只針對當(dāng)前用戶。可以傳遞--global
選項(xiàng)讓Git讀寫此文件- 當(dāng)前使用倉庫的Git目錄中的
config
文件(就是.git/config
): 針對該倉庫。
每一個(gè)界別覆蓋上一級別的配置,所以 .git/config
的配置變量會(huì)覆蓋 /etc/gitconfig
中的配置變量。
用戶信息:
當(dāng)安裝完Git應(yīng)該做的第一件事就是配置用戶名與郵件地址。這很重要,因?yàn)槊恳粋€(gè)Git的提交都會(huì)使用這些信息,并且它會(huì)寫到你的每一次提交中,不可更改
$ git config --global user.name "zcy"
$ git config --global user.email "zcy@xx.com"
如果使用了 --global
選項(xiàng),那么該名字只需要運(yùn)行一次,因?yàn)橹鬅o論你在該系統(tǒng)上做任何事,Git都會(huì)使用那些信息。如果你想針對特定的項(xiàng)目使用不同的用戶名稱和郵箱,可以在該項(xiàng)目目錄下運(yùn)行沒有 --global
選項(xiàng)的命令。
檢查配置信息:
$ git config --list
user.name = xx
user.email = xx@xx.com
color.xx = xx
......
如果看到重復(fù)的變量名,是因?yàn)镚it會(huì)從不同的文件中讀取同一個(gè)配置(例如 /etc/gitconfig
與 ~/.gitconfig
)。這種情況下,Git會(huì)使用它找到每一個(gè)變量的最后一個(gè)配置。
也可以通過輸入 git config <key>
來檢查某一項(xiàng)配置:
$ git config user.name
獲取幫助
$ git help
$ git help <key> //例如:$ git help config
三、獲取Git倉庫
有兩種獲取Git項(xiàng)目倉庫的方法。
- 從服務(wù)器克隆一個(gè)現(xiàn)有的Git倉庫
- 創(chuàng)建一個(gè)Git倉庫,將現(xiàn)有項(xiàng)目或目錄導(dǎo)入所有的文件到Git中
1. 克隆現(xiàn)有的倉庫
$ git clone https://xxx.com/A //克隆A到A
$ git clone https://xxx.com/A FileA //克隆A到FileA中
2. 創(chuàng)建Git倉庫
- 創(chuàng)建空文件目錄(可省略,自己手動(dòng)創(chuàng)建一個(gè)空文件也可以)
$ mkdir NewFile //在當(dāng)前目錄下創(chuàng)建NewFile文件夾
$ cd NewFile //進(jìn)入到空文件目錄
$ pwd //查看當(dāng)前具體路徑
- 初始化git
$ git init //將當(dāng)前目錄變成Git可以管理的倉庫
- 添加文件
$ git add *.* //添加具體文件,注意,可反復(fù)多次使用,添加多個(gè)文件;
$ git add . //也可以使用這句,添加全部修改文件
$ git commit -m "提交說明" //把修改提交到倉庫
git add
是將工作區(qū)數(shù)據(jù)添加到暫存區(qū)
git commit
是將暫存區(qū)數(shù)據(jù)提交到分支
四、Git常用命令
1. 狀態(tài)檢查
$ git status // 查看當(dāng)前版本庫的狀態(tài)
Untracked files: 下面的文件代表未跟蹤的文件,Git不會(huì)自動(dòng)跟蹤,如果需要跟蹤,需要
git add xx
Changes to be commited: 下面的文件代表已暫存狀態(tài)。如果提交,那么該文件的版本將會(huì)被保存。
Changes not staged for commit: 說明已跟蹤文件內(nèi)容發(fā)生了變化,但是沒有放到暫存區(qū)。
如果要暫存這次更新,需要運(yùn)行 git add xx
。
2. 狀態(tài)簡覽
git status
命令的輸出十分詳細(xì),如果需要查看簡單格式,可以使用
$ git status -s
$ git status -short
其中,未跟蹤的文件前面有 ??
標(biāo)記;新添加到暫存區(qū)的文件面前有 A
的標(biāo);修改過的文件前面有 M
的標(biāo)記。
出現(xiàn)在右面的話表示該文件被修改了,但是還沒有放到暫存區(qū)。
出現(xiàn)在左面的話表示該文件修改了,并放入了暫存區(qū)。
如果出現(xiàn)了兩個(gè)則表示,在工作區(qū)中修改了并提交到了暫存區(qū)后又被修改了,還沒有存到暫存區(qū)。
3. 查看當(dāng)前相對修改的內(nèi)容
如果覺得 git status
不夠詳細(xì),想查看尚未暫存的文件具體修改的地方,可以用
$ git diff
$ git diff HEAD --xxx
此命令比較的是工作區(qū)和版本庫中文件的差異。
如果要查看已暫存未提交的內(nèi)容,可以用
$ git diff -cached
$ git diff -staged // Git 1.6.1 及更高版本可以用
4. 提交更新
如果覺得使用暫存區(qū)較為繁瑣,Git 提供了一個(gè)跳過使用暫存區(qū)的方式,只要在提交的時(shí)候給 git commit
加上 -a
選項(xiàng),Git就會(huì)自動(dòng)把所有已經(jīng)跟蹤過的文件暫存起來并一起提交,從而跳過 git add
步驟:
$ git commit -a -m "commit describe"
5. 撤銷
- 漏掉文件沒有添加,或提交信息寫錯(cuò)可以使用帶有
--amend
的提交命令
$ git commit -m "init"
$ git add xxx
$ git commit -amend
第二次提交將會(huì)代替第一次提交的結(jié)果。使用這個(gè)技巧需要注意,它會(huì)修改 SHA1
的值,類似于一個(gè)小小的變基。如果已經(jīng)推送了最后一次提交,就不要用這個(gè)方法了。
- 將已經(jīng)加入暫存區(qū)撤回工作區(qū)
$ git reset HEAD <file>
- 撤銷對文件的修改
如果修改的文件還沒有到暫存區(qū),想還原成上次提交的樣子,即放棄此次對工作區(qū)內(nèi)容的修改。可以使用
$ git checkout -- <file>
注意:一定要帶著 --
, 如果沒有 --
, 就變成了 ++切換另一個(gè)分支++ 命令。
6. 查看提交歷史
在提交了若干更新后,可以回顧提交歷史,使用
$ git log // 按照由近到遠(yuǎn)的提交時(shí)間列出所有更新
$ git log -p // 按補(bǔ)丁格式顯示每次提交內(nèi)容的差異
$ git log --graph // 顯示ASCII圖形表示分支合并歷史(很有用)
$ git log --stat // 額外查看每次提交的簡略統(tǒng)計(jì)信息
$ git log --shortstat // 僅顯示--stat中最后行數(shù)的修改統(tǒng)計(jì)
$ git log --name-only // 僅顯示已修改的文件清單
$ git log --name-status // 顯示新增、修改、刪除的文件清單
$ git log --abbrev-commit // 顯示簡短且唯一的哈希字串(默認(rèn)7個(gè)字符,有時(shí)會(huì)增加到8-10個(gè))
$ git log --relative-date // 使用較短的時(shí)間顯示
$ git log --pretty=oneline // 當(dāng)行顯示簡易log, 不顯示很多凌亂的信息(online 可替換成 short,full和fuller)
$ q // 如果顯示log版本信息有很多,使用q鍵停止查看
--pretty
后面還可以接 format
,可以定制要顯示的格式。
$ git log --pretty=format:"%h - %an, %ar: %s"
git log --pretty=format:xx
常用選項(xiàng)
選項(xiàng) | 說明
%H | 提交對象(commit)的完成哈希字串
%h | 提交對象的簡短哈希字串
%T | 樹對象(tree)的完整哈希字串
%t | 樹對象的簡短哈希字串
%P | 父對象(parent)的完整哈希字串
%p | 父對象的簡短哈希字串
%an | 作者名字
%ae | 作者電子郵件
%ad | 作者修訂日期(可以用 --date= 選項(xiàng)定制格式)
%ar | 作者修訂日期,按多久前顯示
%cn | 提交者(commiter)的名字
%ce | 提交者電子郵件地址
%cd | 提交日期
%cr | 提交日期,按多久前顯示
%s | 提交說明
限制輸出長度
除了定制輸出格式選項(xiàng)外,git log
還有其他非常使用的限制輸出長度的選項(xiàng)。比如
$ git log -2 // 顯示最新兩條提交
// 顯示指定時(shí)間點(diǎn)之后
$ git log --since=2.weeks
$ git log --since=2008-01-15
$ git log --since="2 years 1 day 3 minutes ago"
$ git log --after=2.weeks
// 顯示指定時(shí)間點(diǎn)之前
$ git log --until=3
$ git log --before=3
$ git log --author xx // 指定作者
$ git log --committer xx // 指定提交者
$ git log --grep xx // 搜索提交說明中的關(guān)鍵字
$ git log -s xxx // 檢索關(guān)于增加或移除xxx字符串的提交(方法、函數(shù)名、實(shí)例等)
$ git log -g master // 查看引用日志(引用日志的概念查看本節(jié)末尾)
// 查看在 `develop` 中而不在 `master` 中的提交,即 develop 尚未推送的提交,也可以反過來查未拉取的提交。以下命令是等價(jià)的
$ git log master..develop // 兩點(diǎn)
$ git log ^master develop
$ git log develop --not master
// 查看 `master` 中沒有的提交
$ git log developA developB ^master
$ git log developA developB --not ^master
// 查看包含但不是兩者共有的提交
$ git log master...develop // 三點(diǎn)
$ git log --left-right master...develop // 顯示分別歸屬哪一個(gè)分支
注意: 如果是多條件組合搜索,需要同時(shí)滿足的話,需要添加 --all-match
, 否則,滿足任意一個(gè)條件的提交都會(huì)被匹配出來
綜合例子:檢查Git倉庫中,2008年10月,ZCY提交的但是未合并的測試文件
$ git log --pretty="%h - %s" --author=zcy --since="2008-10-01" \ --befor="2008-11-01" --no-merges -- t/
7. 移動(dòng)文件(或者說是重命名文件)
$ git mv file_from file_to
8. 刪除文件
$ rm xxx // 刪除本地文件
$ git rm xxx // 刪除暫存區(qū)文件
$ git rm --cached xx // 停止追蹤指定文件,但該文件會(huì)保留在工作區(qū)
$ git rm *.* // 刪除文件也可以用glob模式
刪除本地文件后,需要執(zhí)行 git rm
命令也同時(shí)刪除掉緩存區(qū)中的文件。如果誤刪了,執(zhí)行 git checkout
撤銷命令,即可恢復(fù)。
注意:無法恢復(fù)從來沒有被添加到版本庫就被刪除的文件
9. 版本回退
Git中如果想要版本回退,必須要知道回退的版本。在Git中 HEAD
表示當(dāng)前版本,上個(gè)版本是 HEAD^
,上上個(gè)版本是 HEAD^^
,前100個(gè)版本是 HEAD~100
。
$ git reset --hard HEAD^ // 項(xiàng)目回退第一父提交(合并時(shí)所在的分支或非合并時(shí)上一版本)
$ git reset --hard HEAD^2 // 項(xiàng)目回退第二父提交(僅適用于合并的提交)
$ git reset --hard HEAD~2 // 項(xiàng)目回退到第一父提交的第一父提交(與 `HEAD^^` 是等價(jià)的,與 `HEAD^2` 不一樣)
$ git reset --hard SHA1_id // 指定版本號的回退
HEAD
指向哪個(gè)版本號,你就把當(dāng)前版本定位在哪。
如果回退后悔了,并且已經(jīng)關(guān)掉了終端,可以使用命令 git reflog
查看引用日志(reflog)。引用日志記錄了最近幾個(gè)月你的 HEAD
和分支引用所指向的歷史。每次 HEAD
所指向的位置發(fā)生了變化,引用日志都會(huì)記錄下來。通過這些數(shù)據(jù),你可以很輕松的獲取之前的提交歷史。然后可以使用 git show HEAD@{2}
來引用 reflog
中輸出的記錄。
10. 搜索
- 內(nèi)容搜索
Git提供了一個(gè) git grep
搜索命令,你可以很方便的在提交歷史或工作目錄中查到一個(gè)字符串或者正則。
$ git grep // 搜索 xx,
$ git grep -n zcy // 顯示所在行數(shù)
------
fileD:5:zcy
fileD:7:zcyzcy
fileD:12:zcysdjfkldsj
------
- 日志搜索
如果想查看某個(gè)文件什么時(shí)候引入的及修改的可以調(diào)用命令
$ git log -s fileD // 顯示提交信息
$ git log -s --oneline fileD // 單行顯示簡要信息
------
b8db0f4 (HEAD -> master) C6 // 有修改
9f9a151 (develop) C4 // 有修改
bf7bb79 C2 // 這里引入
------
五、遠(yuǎn)程倉庫
遠(yuǎn)程倉庫指的是托管在其他網(wǎng)絡(luò)中你的項(xiàng)目的版本庫。(GitHub/BitBucket/GitLab/私有服務(wù)器等)
1. 查看遠(yuǎn)程倉庫
$ git remote // 查看已經(jīng)配置的遠(yuǎn)程倉庫服務(wù)器
$ git remote -v // 會(huì)顯示對應(yīng)的URL
$ git remote show [remote-name] // 顯示更多信息
2. 添加遠(yuǎn)程倉庫
// 先有遠(yuǎn)程倉庫
$ git clone <遠(yuǎn)程倉庫地址/url> // 從服務(wù)器拉取項(xiàng)目到本地 命名與遠(yuǎn)程一樣
$ git clone <url> xxx // 將項(xiàng)目放到xxx文件中(如果沒有,創(chuàng)建xxx)
// 先有本地倉
$ git remote add origin <遠(yuǎn)程倉庫地址> // 關(guān)聯(lián)一個(gè)新的遠(yuǎn)程Git倉庫
$ git push -u origin master // 第一次推送master分支的所有內(nèi)容到遠(yuǎn)程倉庫
$ git push origin master // 本地推送到遠(yuǎn)程主分支,`master` 可以切換分支名
$ git push // 本地推送到遠(yuǎn)程(不考慮分支等情況或明確自動(dòng)推送的分支)
第一次推送master分支時(shí),加上了 -u
參數(shù),Git不但會(huì)把本地的master
分支內(nèi)容推送的遠(yuǎn)程的 master
分支,還會(huì)關(guān)聯(lián)本地的 master
分支和遠(yuǎn)程的 master
分支,在以后的推送或者拉取時(shí)就可以簡化命令。
3. 拉取更新的內(nèi)容
$ git fetch [remote-name] // 拉取還沒有的數(shù)據(jù)(該命令不會(huì)自動(dòng)合并并修改當(dāng)前的工作,需要手動(dòng)合并)
$ git pull origin master // 拉取遠(yuǎn)程分支未同步的數(shù)據(jù)
$ git pull // 拉取還沒有的數(shù)據(jù)(自動(dòng)合并并修改當(dāng)前的工作)
4. 遠(yuǎn)程倉庫移除與重命名
- 重命名引用名字可以執(zhí)行命令:
$ git remote rename current_name new_name // 此命令同時(shí)會(huì)修改遠(yuǎn)程分支名
- 移除遠(yuǎn)程倉庫可以執(zhí)行命令:
$ git remote rm <分支>
六、標(biāo)簽管理
Git可以給歷史中的某一個(gè)提交上打標(biāo)簽。比如標(biāo)記發(fā)布版本(v1.0)或重大更新等。
1. 列出標(biāo)簽
$ git tag // 顯示所有標(biāo)簽,以字母順序排列
$ git tag -l 'v1.8*' // 只列出 1.8 版本相關(guān)的標(biāo)簽
2. 添加標(biāo)簽
Git標(biāo)簽分兩種:輕量標(biāo)簽、附屬標(biāo)簽
- 輕量標(biāo)簽
輕量只做一個(gè)特定的引用作用,輕量標(biāo)簽本質(zhì)上是將提交校驗(yàn)和存儲(chǔ)到一個(gè)文件夾中,沒有其他任何信息。
$ git tag v1.4
$ git tag v1.5 -ll
- 附屬標(biāo)簽
存儲(chǔ)在Git中的一個(gè)完整對象。它們是可以被校驗(yàn)的,包括打標(biāo)簽者的名字、郵箱、日期時(shí)間等。并且可以使用 GNU Privacy Guard(GPG)
簽名與驗(yàn)證。要添加附屬標(biāo)簽的只要在運(yùn)行 tag
命令時(shí)執(zhí)行 -a
選項(xiàng):
$ git tag -a v1.4 -m 'add version v1.4'
-m
指定了一條會(huì)存儲(chǔ)在標(biāo)簽中的信息。
通過 git show <tag>
可以查看到標(biāo)簽信息與對應(yīng)的提交信息。
- 后期添加標(biāo)簽
如果之前提交的版本需要添加標(biāo)簽,或者忘記給剛剛提交的版本添加標(biāo)簽,我們也可以后期提交:
$ git tag -a v1.5 1234567 // 其中1234567是commit_id SHA1的前幾位
3. 共享標(biāo)簽
默認(rèn)情況下,git push
不會(huì)將標(biāo)簽推送到遠(yuǎn)程服務(wù)器上。如果要推送到遠(yuǎn)程,需要運(yùn)行:
$ git push origin v1.4 // 推送v1.4的標(biāo)簽到遠(yuǎn)程
$ git push --tags // 將遠(yuǎn)程沒有的標(biāo)簽全部推送到遠(yuǎn)程
4. 刪除標(biāo)簽
如果標(biāo)簽只在本地的話,要?jiǎng)h除某個(gè)標(biāo)簽只需要執(zhí)行:
$ git tag -d v1.4
如果標(biāo)簽已在遠(yuǎn)程,要執(zhí)行:
$ git tag -d v1.4 // 刪除本地標(biāo)簽
$ git push origin:refs/tags/v1.4 // 刪除遠(yuǎn)程的標(biāo)簽
5. 根據(jù)標(biāo)簽檢出數(shù)據(jù)
在特定的標(biāo)簽上創(chuàng)建一個(gè)新分支,可以用:
$ git checkout -b newVersion2 v1.9.0
再次修改并提交,newVersion2
分支會(huì)因?yàn)楦膭?dòng)向前移動(dòng),此時(shí),newVersion2
與v1.9.0
開始不同,這點(diǎn)需要注意。
七、分支管理
1. 簡介
使用分支意味著你可以把你的工作從開發(fā)主線上分離出來,即使開發(fā)過程中出現(xiàn)問題也不會(huì)影響到主線。在開發(fā)完成后,將支線上完成的功能合并到主線即可。
在進(jìn)行提交操作時(shí),Git會(huì)保存一個(gè)提交對象。該提交對象會(huì)包含一個(gè)指向暫存內(nèi)容快照的指針,但不僅僅是這樣,還包括作者的姓名、郵箱和提交時(shí)輸入的信息以及指向它父對象的指針。首次提交時(shí)沒有父對象,普通提交操作產(chǎn)生的提交對象有一個(gè)父對象,由多個(gè)分支合并產(chǎn)生的提交對象有多個(gè)父對象。
假設(shè)有一個(gè)工作目錄,里面有三個(gè)將要暫存和提交的文件。當(dāng)使用 git commit
進(jìn)行提交時(shí),Git會(huì)先校驗(yàn),然后將將校驗(yàn)和保存為樹對象。隨后,Git會(huì)創(chuàng)建一個(gè)提交對象,它除了上面說到的信息外,還會(huì)包括樹對象的指針。
現(xiàn)在Git中包含五個(gè)對象:三個(gè)blob對象(保存文件快照),一個(gè)數(shù)對象(記錄目錄結(jié)構(gòu)和對象索引),一個(gè)提交對象(包含指向前述樹對象的指針和所有提交信息)
Git的分支是包含所指對象校驗(yàn)和(長度為40的SHA-1值字符串)的文件,本質(zhì)上是指向提交對象的可變指針。 它的創(chuàng)建、合并和銷毀都只是切換一個(gè)41字節(jié)(包含換行符)的指針的指向,所以異常高效。
Git默認(rèn)分支(主分支)名字是 master
。master
分支會(huì)在每次 git commit
操作中自動(dòng)向前移動(dòng),形成一條主分支時(shí)間線。master
分支并不是一條特殊的分支,跟其他的分支沒有區(qū)別。只是因?yàn)?git init
默認(rèn)創(chuàng)建 master
分支,絕大多數(shù)人不會(huì)去改動(dòng)它,所以基本每個(gè)倉庫都有,并默認(rèn)作為主分支。
HEAD
是當(dāng)前分支引用的指針,它默認(rèn)總是指向該分支最后一次提交。嚴(yán)格來說 HEAD
指針并不是直接指向提交,而是默認(rèn)指向 master
分支, 再用 master
指向最新提交,就能確定當(dāng)前分支及當(dāng)前分支的提交點(diǎn)。
2. 創(chuàng)建分支
查看當(dāng)前已有分支
$ git branch // 顯示所有分支,并在當(dāng)前分支前加*號
創(chuàng)建Git分支就是創(chuàng)了一個(gè)可以移動(dòng)的新的指針。創(chuàng)建分支執(zhí)行以下命令:
$ git branch testing // testing 為新分支名
當(dāng)我們創(chuàng)了新的分支 testing
,這會(huì)在當(dāng)前所提交的對象上創(chuàng)建一個(gè) testing
指針。此時(shí),HEAD
仍然指向 master
分支。因?yàn)?git branch xx
命令僅僅是創(chuàng)建了一個(gè)新的分支,不會(huì)自動(dòng)切換到新分支上。
可以使用 git log --oneline --decorate
查看當(dāng)前各個(gè)分支當(dāng)前所指向的對象。
3. 切換分支
要切換到一個(gè)已存在的分支,需要執(zhí)行以下命令:
$ git checkout testing // testing 為已存在的分支
$ git checkout -b testing // 上述如果加上 -b 表示創(chuàng)建并切換分支,等價(jià)于下面兩條命令
$ git branch testing
$ git checkout testing
這樣 HEAD
分支就會(huì)指向新創(chuàng)建的 testing
分支。
假設(shè)此時(shí)最新提交對象為 a
。再次提交,testing
分支會(huì)向前移動(dòng),指向新對象 b
。
此時(shí)再次切換回 master
分支,會(huì)發(fā)現(xiàn) master
分支內(nèi)容沒有改變,分支仍然是指向?qū)ο?a
。
如果再次修改并提交,那么這個(gè)項(xiàng)目的提交歷史就會(huì)產(chǎn)生分叉。master
分支會(huì)指向 c
,而 testing
分支會(huì)指向 b
。
使用 git log --oneline --decorate --graph --all
輸入提交歷史、各個(gè)分支的指向及項(xiàng)目的分叉情況。
3. 分支的創(chuàng)建與合并
合并分支命令
$ git merge <分支名> // 合并分支,指定分支名的分支合并到當(dāng)前分支
當(dāng)需要合并時(shí)的整個(gè)流程是怎么樣的呢?舉個(gè)栗子:
1. 假設(shè)正在開發(fā)一個(gè)版本,為了實(shí)現(xiàn)一個(gè)新需求,創(chuàng)建了一個(gè)分支 `A`, 在該分支上工作。
2. 此時(shí)來了一個(gè)問題需要緊急修復(fù),切換到線上分支。
3. 為緊急任務(wù)創(chuàng)建新分支 `B`, 并修復(fù)問題。
4. 測試通過后,切換回線上分支,合并 `B` 分支,將改動(dòng)推動(dòng)到線上分支。
5. 切換回 `A` 分支,繼續(xù)開發(fā)工作。
整體流程如下:
// 1
$ git checkout -b A // 創(chuàng)建分支 `A`
update
// 2
$ git commit -a -m "update" // 更新工作內(nèi)容,并且提交到分支 `A`
$ git checkout master // 切換到線上主分支
// 3
$ git checkout -b B // 創(chuàng)建新分支 `B`
// 4
update
$ git commit -a -m "fix bug" // 修復(fù)完成,提交修改
$ git checkout master // 切換到線上主分支
$ git merge B // 合并分支 `B` 中修改的內(nèi)容到 `master` 分支
$ git branch -d B // 刪除分支 `B`,因?yàn)榇藭r(shí)已經(jīng)不需要它了
// 5
$ git checkout A // 切換到分支 `A`,要注意此時(shí)不帶 `-b`
在合并的時(shí)候,會(huì)看到“快進(jìn)(fast-forward)”這個(gè)詞。由于當(dāng)前
master
分支所指向的當(dāng)前提交(有關(guān)B
提交)的直接上游。所以Git只是簡單的將指針向前移動(dòng)。當(dāng)試圖合并兩個(gè)分支時(shí),如果順著分支走下去就能到達(dá)另一個(gè)分支,那么Git在合并時(shí)只會(huì)簡單的將指針向前推進(jìn),因?yàn)檫@種情況下合并操作沒有需要解決的分歧——這就叫快進(jìn)。
此時(shí)在分支 B
中的修改并沒有包含到分支 A
中。如果需要拉取 B
分支中修改的內(nèi)容,可以先執(zhí)行 git merge master
合并 master
分支中的內(nèi)容到分支 A
中。
假設(shè)此時(shí)已經(jīng)完成 A
的開發(fā)并且已經(jīng)提交 commit
,并且打算合并到 master
分支。需要執(zhí)行:
$ git checkout master
$ git merge A
此時(shí),合并的時(shí)候并沒有看到 fast-forward
。因?yàn)檫@種情況下,開發(fā)歷史從一個(gè)更早的地方有了分叉。master
分支所在的提交不是 A
的直接祖先,Git需要使用兩個(gè)分支的末端和兩個(gè)分支的共同的一個(gè)祖先分支做一個(gè)三方合并,創(chuàng)建一個(gè)新的提交并且指向它。這個(gè)被稱為一次合并提交。Git會(huì)自行決定選取哪個(gè)提交作為共同祖先,并以此為合并的基礎(chǔ)。
此時(shí),已經(jīng)不再需要 A
分支了,可以刪除這個(gè)分支了:
$ git branch -d A
刪除包含未合并工作的代碼,刪除會(huì)失敗并報(bào)錯(cuò)。如果還是想要?jiǎng)h除分支,并放棄那些已修改工作,可以使用 -D
強(qiáng)制刪除。
通常在合并分支時(shí),Git會(huì)采用 fast-forward
模式,在這種模式下,刪除分支后,會(huì)一起刪除掉分支的信息。如果禁用掉該模式,在合并的時(shí)候使用 --no-ff
,Git在合并的時(shí)候會(huì)生成一個(gè)新的 commit
。這樣在歷史分支流程就能看到該分支的信息。
$ git merge --no-ff -m "merge A without fast-forward" A
4. 遇到?jīng)_突的分支合并
合并操作并不是總是能順利完成的。如果在兩個(gè)不同分支內(nèi)或者多人協(xié)作對同一文件的同一地方進(jìn)行了不同的修改,修改就會(huì)發(fā)生沖突。Git做了合并,但是沒有自動(dòng)的創(chuàng)建一個(gè)新的合并提交。Git會(huì)暫停,等待解決沖突。你可以使用 git status
查看未合并(unmerged)的文件。
任何因包含合并沖突而有待解決的文件,都會(huì)以未合并狀態(tài)表示出來。Git會(huì)在有沖突的文件中加入標(biāo)準(zhǔn)的沖突解決標(biāo)記。
<<<<<<<< HEAD
master code
========
A code
>>>>>>>>
這表示 HEAD
所示的版本(當(dāng)前分支 master
)在這個(gè)區(qū)段的上半部分 (<<<
到 ===
),而 A
分支所指示的版本在這個(gè)區(qū)段的下半部分(===
到 >>>
)。為了解決沖突,需要選擇其中一個(gè)版本的內(nèi)容,或者自行合并這些內(nèi)容。修改完成后刪除掉 <<<
, ===
, >>>
。
在修改完成后,使用 git add
來標(biāo)記文件沖突以解決。暫存這些原本有沖突的文件,Git會(huì)將他們標(biāo)記為沖突已解決。你可以再次運(yùn)行 git status
來確認(rèn)所有的沖突已解決。
如果遇到?jīng)_突,但是不想處理沖突這種情況,你可以使用 git merge --abort
來退出合并。該命令會(huì)嘗試恢復(fù)到你運(yùn)行合并之前的狀態(tài)。但是,當(dāng)運(yùn)行命令前,在工作目錄有未儲(chǔ)藏、未提交的修改時(shí),它不能完美的處理,即有可能有代碼丟失的風(fēng)險(xiǎn)。
如果發(fā)現(xiàn)合并后出現(xiàn)了沖突,因?yàn)橐恍┨厥馇闆r弄的你很混亂,而本地的代碼如果修改不是很多,很重要。那么可以嘗試 git reset --hard HEAD
回到初始狀態(tài)。這個(gè)命令會(huì)清空工作目錄中的所有修改。
5. 分支列表
$ git branch -v // 查看每一個(gè)分支的最后一次提交
$ git branch --megred // 過濾這個(gè)列表中已經(jīng)合并的分支
$ git branch --no-megred // 過濾這個(gè)列表中有未合并工作的分支
6. 分支開發(fā)工作流
長期分支:
master
分支只保留完全穩(wěn)定的代碼,可能只是已經(jīng)發(fā)布或即將發(fā)布的代碼。開發(fā)工作在develop
/next
或其他平行分支上進(jìn)行,在開發(fā)完成后在合并到master
分支。特性分支:特性分支是一種短期分支,它被用來實(shí)現(xiàn)某一單一特性或相關(guān)工作。在工作完成,并合并到需要的分支上后,再將其刪除掉。這樣既不用擔(dān)心其破壞工程的穩(wěn)定性,又不會(huì)耽誤開發(fā),如果發(fā)現(xiàn)有問題或停止更新該功能,可以隨時(shí)廢棄。每個(gè)人的每項(xiàng)工作都可以創(chuàng)建一個(gè)特性分支,在開發(fā)完成后合并到自己的分支上。
遠(yuǎn)程分支:遠(yuǎn)程引用是對遠(yuǎn)程倉庫的引用(指針),包括分支、標(biāo)簽等。可以通過
git ls-remote
來顯示的獲取遠(yuǎn)程引用的完整列表。或者git remote show
獲取遠(yuǎn)程分支的更多信息。一個(gè)更常見的做法是利用遠(yuǎn)程跟蹤分支。在你做任何網(wǎng)絡(luò)通信操作時(shí),它們會(huì)自動(dòng)移動(dòng)。
遠(yuǎn)程倉庫的
origin
并沒有特殊含義,和master
一樣都是默認(rèn)創(chuàng)建的名字而已。只不過,master
是在git init
時(shí)創(chuàng)建的默認(rèn)分支名;而origin
是在git clone
時(shí)默認(rèn)的遠(yuǎn)程倉庫名字。如果在git clone
時(shí)運(yùn)行git clone -o zzz
, 那么你默認(rèn)遠(yuǎn)程分支名就是zzz/master
。
7. 跟蹤分支
從一個(gè)遠(yuǎn)程跟蹤分支檢出一個(gè)本地分支會(huì)自動(dòng)創(chuàng)建一個(gè)叫做跟蹤分支(有時(shí)候也叫做上游分支)。跟蹤分支是與遠(yuǎn)程分支有直接關(guān)系的本地分支。如果再跟蹤分支上輸入 git pull
,Git能自動(dòng)識別到哪個(gè)服務(wù)器上拉、合并到哪個(gè)分支。
當(dāng)克隆一個(gè)倉庫時(shí),它通常會(huì)自動(dòng)創(chuàng)建個(gè)跟蹤 origin/master
的 master
分支。如果你想設(shè)置其他遠(yuǎn)程倉庫的跟蹤分支,運(yùn)行:
$ git checkout --track origin/serverfix // 創(chuàng)建跟蹤分支
或
$ git checkout -b sf origin/serverfix // 將本地分支與遠(yuǎn)程分支設(shè)置不同的名字
設(shè)置已有的本地分支跟蹤一個(gè)剛剛拉取下來的遠(yuǎn)程分支,或者想要修改正在跟蹤的上游分支。可以添加 -u
或 --set-upstream-to
來執(zhí)行
$ git branch -u origin/serverfix
查看所有的跟蹤分支可以執(zhí)行命令
$ git fetch --all // 抓取所有的遠(yuǎn)程服務(wù)器
$ git branch -vv // 查看所有的跟蹤分支
反饋結(jié)果中,
ahead
表示本地還有提交未推送到服務(wù)器,hebind
表示本地有未并入的服務(wù)器內(nèi)容。需要注意的是,查看所有跟蹤分支的名利置灰告訴你關(guān)于本地緩存的服務(wù)器數(shù)據(jù)。所以在統(tǒng)計(jì)最新的數(shù)據(jù)之前,需要先抓取所有的遠(yuǎn)程倉庫。
8. 刪除遠(yuǎn)程分支
如果已經(jīng)通過遠(yuǎn)程分支完成了所有的工作(完成了一個(gè)特性并合并到了 master
分支)。可以將遠(yuǎn)程分支刪除。運(yùn)行命令:
$ git push origin --delete serverfix
9. 變基
寫在前頭:不要對在你的倉庫外有副本的分支執(zhí)行變基!(個(gè)人理解:不要變基提交到 master
或多人共同提交工作的分支,不要變基已經(jīng)提交過的歷史。只用于完全屬于個(gè)人的分支或者尚未推送到公用倉庫的提交上)
在Git中整合不同分支的修改主要有兩種方法合并 merge
與變基 rebase
。
首先,對比下合并與變基的流程差異。開發(fā)分支 develop
與提交分支 master
。
- 使用合并:分叉了歷史,它會(huì)把兩個(gè)分支的最新快照及最近的共同祖先進(jìn)行三方合并成一個(gè)最新的快照并提交。
- 使用變基:在當(dāng)前開發(fā)分支引入提交分支的更新,再開發(fā)。再將開發(fā)分支并入提交分支。變基的實(shí)質(zhì)是丟棄一些現(xiàn)有的提交,然后相對應(yīng)的創(chuàng)建一些內(nèi)容一樣但是實(shí)際上不同的提交。
舉個(gè)栗子對比下:
- 合并
// 1 創(chuàng)建合并測試git
$ cd /Users/zcy/Documents/Project
$ mkdir mergeTest // 也可以手動(dòng)創(chuàng)建
$ cd mergeTest // 也可以手動(dòng)創(chuàng)建
$ git init
// 2 master 提交兩次
$ vim fileM
update
$ git add .
$ git commit -m "C0"
$ vim fileM
update
$ git add .
$ git commit -m "C1"
// 3 創(chuàng)建 develop 分支并提交
$ git checkout -b develop
$ vim fileD
update
$ git add .
$ git commit -m "C2"
// 4 切換 master 并提交
$ git checkout master
$ vim fileM
update
$ git add .
$ git commit -m "C3"
// 5 切換 develop 分支并提交
$ git checkout -b develop
$ vim fileD
update
$ git add .
$ git commit -m "C4"
// 6 切換 master 合并,輸出結(jié)果
$ git checkout master
$ git merge develop
commit "C5"
$ git log --graph --pretty=oneline --abbrev-commit
------
* dffc0cc (HEAD -> master) C5
|\
| * 9f9a151 (develop) C4
| * bf7bb79 C2
* | 7012753 C3
|/
* e453181 C1
* 1c27cc4 C0
------
- 變基
// 重復(fù)合并前五步創(chuàng)建新倉庫 rebaseTest
// 6. 變基,輸出結(jié)果
// 當(dāng)前在 develop 分支
$ git rebase master
$ git log --graph --pretty=oneline --abbrev-commit
------
* 7f9e86d (HEAD -> develop) C4
* b088e32 C2
* 2a4a7b5 (master) C3
* 4397a6b C1
* ab9683f C0
------
// 7. 將 develop 分支上的代碼合并到 master 輸出結(jié)果
$ git checkout master
$ git merge develop
$ git log --graph --pretty=oneline --abbrev-commit
------
* 7f9e86d (HEAD -> master, develop) C4
* b088e32 C2
* 2a4a7b5 C3
* 4397a6b C1
* ab9683f C0
------
對比合并與變基的輸出結(jié)果,可以得到總結(jié):
1. 變基把分叉的提交歷史整理成為一條直線,相對于合并可以得到一個(gè)更加簡潔的提交歷史。
2. 變基比合并少一個(gè)合并提交節(jié)點(diǎn)。
3. 合并的分支是按照時(shí)間倒序排列的。而變基中 `master` 分支的時(shí)間線是錯(cuò)亂的,可能會(huì)影響到提交歷史的查看。
4. 無論是變基還是合并,整合的最終指向提交都是一樣的。只是提交歷史不同而已。
5. 個(gè)人建議:倉庫的提交歷史是對實(shí)際發(fā)生過什么的歷史記錄。它是針對歷史的文檔,本身就非常的有價(jià)值且很重要。它可以供開發(fā)者或者后來者查閱,盡可能的不要亂改。如果一定要改動(dòng)的話,相對于重要的分支或長期分支也不建議使用變基,對提交歷史沒有影響或特性分支可以采用變基,用來使提交歷史更簡潔。
補(bǔ)充:萬一真遇到有人對 master
下手了,執(zhí)行了變基操作,可以讓團(tuán)隊(duì)中的每個(gè)人都執(zhí)行 git pull --rebase
嘗試稍微挽救一下。
10. 暫存文件
Git自帶一些腳本可以使部分工作更簡單。如果想將文件的特定組合部分組合成提交可以暫存對應(yīng)文件。在調(diào)用 git add
命令時(shí)添加 -i
或 --interactive
,進(jìn)入終端模式。
$ git add -i
$ git add --interactive
-----
$
staged unstaged path
1: unchanged +1/-0 fileD
*** Commands ***
1: status 2: update 3: revert 4: add untracked
5: patch 6: diff 7: quit 8: help
What now>
------
這個(gè)命令展示了與 git status
類似的信息,但是更簡明。暫存的在左側(cè),未暫存的在右側(cè)。
輸入 2
或 u
,腳本會(huì)提示要暫存那個(gè)文件。輸入前面的數(shù)字,文件面前會(huì)帶 *
。
What now> 2
staged unstaged path
* 1: unchanged +1/-0 fileD
Update>>
如果此時(shí)不輸入任何東西,直接回車,將會(huì)暫存選中的文件并提示 updated 1 path
。輸入 1
或 s
可以看到已被暫存:
staged unstaged path
1: +1/-0 nothing fileD
*** Commands ***
1: status 2: update 3: revert 4: add untracked
5: patch 6: diff 7: quit 8: help
What now>
要取消暫存文件,輸入 3
或 revert
,執(zhí)行與暫存相同操作。再次查看,會(huì)發(fā)現(xiàn)已取消暫存。
11. 儲(chǔ)藏
當(dāng)開發(fā)中突然遇到問題需要緊急修復(fù),又不能放棄當(dāng)前的更改,當(dāng)前的修改還在報(bào)錯(cuò)又不能提交。
這種情況經(jīng)常會(huì)發(fā)生,在這種情況下我們可以先將手里的工作儲(chǔ)藏起來,存儲(chǔ)在棧上。執(zhí)行命令 git stash
或 git stash save
。待其他工作完成后,回來再繼續(xù)。
$ git status
------
On branch master
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
modified: fileD
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: fileM
------
$ git stash // 或 `git stash save`
$ git status
------
On branch master
nothing to commit, working tree clean
------
可以看到,工作目錄清空了,未暫存的和已暫存的都消失了。這時(shí)我們就可以去搞別的事情了。
那么,當(dāng)別的工作完成了回到了原來的開發(fā)分支,該怎么繼續(xù)呢?使用 git stash list
查看儲(chǔ)藏列表(可以儲(chǔ)藏很多)。
$ git stash list
------
stash@{0}: WIP on master: 7f9e86d C4
------
獲取到儲(chǔ)藏列表以后,就可以取出來繼續(xù)使用了。
$ git stash apply // 獲取最新的儲(chǔ)藏
$ git stash apply stash@{0} // 獲取指定的儲(chǔ)藏
可以不再原來的開發(fā)分支獲取儲(chǔ)藏,即如果在 A
分支儲(chǔ)藏的工作內(nèi)容,在 B
分支也可以獲取,但是要注意有可能會(huì)合并沖突。
使用上述獲取儲(chǔ)藏命令獲取完成后,儲(chǔ)藏列表不會(huì)自動(dòng)刪除已儲(chǔ)藏內(nèi)容,需要執(zhí)行命令刪除:
$ git stash drop // 刪除最新儲(chǔ)藏
$ git stash drop stash@{0} // 刪除執(zhí)行儲(chǔ)藏
或者在獲取到儲(chǔ)藏列表后直接運(yùn)行 git stash pop
來獲取儲(chǔ)藏并自動(dòng)刪除。
補(bǔ)充
1. 忽略文件
總有一些文件不需要納入到Git的管理中,也不希望它們總是出現(xiàn)在未跟蹤文件列表。一般都是自動(dòng)生成的文件,比如日志文件。在這種情況下,可以創(chuàng)建一個(gè)名為.gitignore
的文件,列出要忽略的文件模式。
$ touch .gitignore
$ vim .gitignore
*.[oa]
*~
第一行告訴Git忽略所有.o或.a結(jié)尾的文件。
第二行告訴Git忽略所有以波浪符~結(jié)尾的文件。
文件 .gitignore
的格式規(guī)范如下:
- 所有空行或者以#開頭的行都會(huì)被Git忽略
- 可以使用標(biāo)準(zhǔn)的glob模式匹配
- 匹配模式可以以
/
開頭防止遞歸 - 匹配模式可以以
/
結(jié)尾指定目錄 - 要忽略指定模式以外的文件或目錄,可以在模式前加上
!
取反
所謂的glob模式是指shell所使用的簡化了的正則表達(dá)式。
*
匹配另個(gè)或多個(gè)任意字符
**
表示匹配任意中間目錄,如a/**/z
可以匹配a/z
,a/b/z
,a/b/c/d/z
[abc]
匹配任何一個(gè)列在方括號中的字符(要么匹配a
,要么b
,要么c
)
?
只匹配一個(gè)任意字符
[0-9]
表示所有0到9的數(shù)字;
舉個(gè)栗子:
# no .a file
*.a
# but lib.a
!lib.a
# only ignore the TODO file in the current directory, not subdir /TODO
/TODO
# ignore all files in the build/ directory
build/
# ignore doc/notes.txt, but not doc/server/arch.txt
doc/*.txt
# ignore all .pdf file in the doc/directory
doc/**/*.pdf
.gitignore
在修改完成并保存后后,也需要提交到Git。
有一些特殊的文件,雖然是被 .gitignore
文件忽略掉了,但是我們需要把這個(gè)特殊文件添加到Git,又不希望改 .gitignore
的話,可以執(zhí)行命令:
$ git add -f filename.class
如果想修改 .gitignore
文件來取消篩選,而又不知道該取消哪一行時(shí),可以執(zhí)行以下命令來查找相關(guān)信息:
$ git check-ignore -v filename.class
2. Git命令別名
Git并不會(huì)在你輸入部分命令時(shí)自動(dòng)推斷出你想要的命令。
如果不想每次都輸入完成的Git命令的話,可以通過 config
文件來輕松的為每一個(gè)命令設(shè)置一個(gè)別名。
比如:
$ git config --global alias.co checkout
$ git config --global alias.br branch
$ git config --global alias.ci commit
$ git config --global alias.st status
這意味著當(dāng)想要出入 git commit
時(shí),只要輸入 git ci
即可。
為了解決取消暫存文件的易用性問題,可以添加取消暫存別名:
$ git config --global alias.unstage 'reset HEAD'
這會(huì)使下面的兩個(gè)命令等價(jià):
$ git unstage fileA // 實(shí)際執(zhí)行下面一步
$ git reset HEAD fileA
通常也會(huì)加一個(gè) last
命令:
$ git config --global alias.last 'log -1 HEAD'
這樣就可以使用命令 git last
查看最后一次提交。
如果想直接修改文件,也可以到當(dāng)前用戶的目錄下的 ~/.gitconfig
或者項(xiàng)目目錄下的 .git/config
中修改。
參考網(wǎng)友提供的很6的一個(gè)方法,命令 git lg
:
git config --global alias.lg "log --color --graph --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset' --abbrev-commit"
Git的內(nèi)容太多了,還有很多未能記錄的東西(服務(wù)器上的Git、分布式Git、內(nèi)部原理等)。如果有需要,查看《Pro Git》吧或者以后再慢慢添加。