Git 簡介及常用命令

一、Git簡介

Git是Linux之父Linus與2005年用C語言編寫的分布式控制系統(tǒng)。 Git的分布式區(qū)別于SVN等集中式版本控制系統(tǒng)的地方有哪些呢?

  • 集中式:有一個(gè)集中管理的“中央服務(wù)器”,保存所有的修訂版本。
  1. 缺點(diǎn)是版本控制器是放在服務(wù)器中,必須聯(lián)網(wǎng)才能協(xié)同工作,保存數(shù)據(jù)。
  2. 如果服務(wù)器損壞,將丟失所有數(shù)據(jù)。
  • 分布式:分布式?jīng)]有“中央服務(wù)器”。每一個(gè)人電腦上都有一個(gè)獨(dú)立且完整的版本庫。
  1. 工作不一定需要聯(lián)網(wǎng),每次可以提交到本地版本庫,需要的時(shí)候在同步到網(wǎng)絡(luò)或其他人的版本庫中
  2. 不擔(dān)心數(shù)據(jù)的丟失,數(shù)據(jù)丟失了,可以隨時(shí)在其他人電腦中拷貝數(shù)據(jù)
  3. 工作中很少有相互之間推送版本庫,往往還是會(huì)有一臺電腦充當(dāng)“中央服務(wù)器”,方便大家交換數(shù)據(jù),或者使用github等網(wǎng)絡(luò)服務(wù)器
  4. git還擁有分支管理等技術(shù)
  5. 幾個(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è)不同的位置:

  1. /etc/gitconfig 文件:包含系統(tǒng)上每一個(gè)用戶及他們倉庫的通用配置。如果使用帶有 --system 選項(xiàng)的 git config 時(shí), 它會(huì)從文件讀寫配置變量
  2. ~/.gitconfig~/.config/git/config 文件: 只針對當(dāng)前用戶。可以傳遞 --global 選項(xiàng)讓Git讀寫此文件
  3. 當(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)目倉庫的方法。

  1. 從服務(wù)器克隆一個(gè)現(xiàn)有的Git倉庫
  2. 創(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倉庫

  1. 創(chuàng)建空文件目錄(可省略,自己手動(dòng)創(chuàng)建一個(gè)空文件也可以)
$ mkdir NewFile //在當(dāng)前目錄下創(chuàng)建NewFile文件夾
$ cd NewFile //進(jìn)入到空文件目錄
$ pwd //查看當(dāng)前具體路徑
  1. 初始化git
$ git init //將當(dāng)前目錄變成Git可以管理的倉庫
  1. 添加文件
$ 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. 撤銷

  1. 漏掉文件沒有添加,或提交信息寫錯(cuò)可以使用帶有--amend的提交命令
$ git commit -m "init"
$ git add xxx
$ git commit -amend

第二次提交將會(huì)代替第一次提交的結(jié)果。使用這個(gè)技巧需要注意,它會(huì)修改 SHA1 的值,類似于一個(gè)小小的變基。如果已經(jīng)推送了最后一次提交,就不要用這個(gè)方法了。

  1. 將已經(jīng)加入暫存區(qū)撤回工作區(qū)
$ git reset HEAD <file>
  1. 撤銷對文件的修改

如果修改的文件還沒有到暫存區(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. 搜索

  1. 內(nèi)容搜索

Git提供了一個(gè) git grep 搜索命令,你可以很方便的在提交歷史或工作目錄中查到一個(gè)字符串或者正則。

$ git grep // 搜索 xx,
$ git grep -n zcy // 顯示所在行數(shù)

------
fileD:5:zcy
fileD:7:zcyzcy
fileD:12:zcysdjfkldsj
------
  1. 日志搜索

如果想查看某個(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)程倉庫移除與重命名

  1. 重命名引用名字可以執(zhí)行命令:
$ git remote rename current_name new_name // 此命令同時(shí)會(huì)修改遠(yuǎn)程分支名
  1. 移除遠(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)簽

  1. 輕量標(biāo)簽

輕量只做一個(gè)特定的引用作用,輕量標(biāo)簽本質(zhì)上是將提交校驗(yàn)和存儲(chǔ)到一個(gè)文件夾中,沒有其他任何信息。

$ git tag v1.4
$ git tag v1.5 -ll
  1. 附屬標(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)的提交信息。

  1. 后期添加標(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í),newVersion2v1.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)分支(主分支)名字是 mastermaster 分支會(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ā)工作流

  1. 長期分支:master 分支只保留完全穩(wěn)定的代碼,可能只是已經(jīng)發(fā)布或即將發(fā)布的代碼。開發(fā)工作在 develop / next 或其他平行分支上進(jìn)行,在開發(fā)完成后在合并到 master 分支。

  2. 特性分支:特性分支是一種短期分支,它被用來實(shí)現(xiàn)某一單一特性或相關(guān)工作。在工作完成,并合并到需要的分支上后,再將其刪除掉。這樣既不用擔(dān)心其破壞工程的穩(wěn)定性,又不會(huì)耽誤開發(fā),如果發(fā)現(xiàn)有問題或停止更新該功能,可以隨時(shí)廢棄。每個(gè)人的每項(xiàng)工作都可以創(chuàng)建一個(gè)特性分支,在開發(fā)完成后合并到自己的分支上。

  3. 遠(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/mastermaster 分支。如果你想設(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

  1. 使用合并:分叉了歷史,它會(huì)把兩個(gè)分支的最新快照及最近的共同祖先進(jìn)行三方合并成一個(gè)最新的快照并提交。
  2. 使用變基:在當(dāng)前開發(fā)分支引入提交分支的更新,再開發(fā)。再將開發(fā)分支并入提交分支。變基的實(shí)質(zhì)是丟棄一些現(xiàn)有的提交,然后相對應(yīng)的創(chuàng)建一些內(nèi)容一樣但是實(shí)際上不同的提交。

舉個(gè)栗子對比下:

  1. 合并
// 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
------
  1. 變基
// 重復(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è)。
輸入 2u,腳本會(huì)提示要暫存那個(gè)文件。輸入前面的數(shù)字,文件面前會(huì)帶 *

What now> 2
          staged    unstaged path
* 1:    unchanged        +1/-0 fileD
Update>>

如果此時(shí)不輸入任何東西,直接回車,將會(huì)暫存選中的文件并提示 updated 1 path。輸入 1s 可以看到已被暫存:

          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>

要取消暫存文件,輸入 3revert,執(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 stashgit 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》吧或者以后再慢慢添加。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲(chǔ)服務(wù)。