引子
如果要寫一篇論文,會經(jīng)歷初稿、修改版、再次修改版……
如果要設(shè)計一張海報,海報初版0510、修改版0511、修改版0512……
史前文明
復(fù)制整個項目目錄的方式來保存不同的版本,或許還會改名加上備份時間以示區(qū)別。
一、關(guān)于版本控制
版本控制系統(tǒng)(VCS, Version control System),版本控制是一種記錄一個或若干文件內(nèi)容變化,以便將來查閱特定版本修訂情況的系統(tǒng)。
有了它你就可以將某個文件回溯到之前的狀態(tài),甚至將整個項目都回退到過去某個時間點的狀態(tài)。
VCS初代—本地版本控制系統(tǒng)
采用某種簡單的數(shù)據(jù)庫來記錄文件的歷次更新差異。這代主要的特點提供本地代碼版本控制,比如SCCS(1972)、RCS(1982)等。這代主要實現(xiàn)了基本的代碼版本管理,但缺點是無法讓多人同時對一個版本庫進(jìn)行修改。
VCS二代—集中式版本控制系統(tǒng)
集中式的版本控制系統(tǒng)( Centralized Version Control Systems,簡稱 CVCS ),客戶端-服務(wù)器式,主要的特點是提供集中式服務(wù)器端代碼版本控制,比如 CVS(1990), SVN(2000) 等。主要解決的問題是讓在不同系統(tǒng)上的開發(fā)者協(xié)同工作,可以讓多人同時對一個代碼版本庫進(jìn)行同步和修改。
缺點:無法連接服務(wù)器的情況,無法查看日志以及提交和比較代碼版本。不支持local branch,管理branch比較復(fù)雜。發(fā)生災(zāi)難性問題,日志有丟失的危險。代碼量過大,速度比較緩慢。
VCS三代—分布式版本控制系統(tǒng)
分布式版本控制系統(tǒng)( Distributed Version Control System,簡稱 DVCS ),主要的特點是提供分布式代碼版本控制,比如BitKeeper(2000), Git(2005), Mercurial(2005)等。結(jié)合了第一代和第二代的優(yōu)點并實現(xiàn)了分布式的代碼版本管理。在沒有和服務(wù)器有連接的情況下仍然可以查看日志,提交代碼,創(chuàng)建分支;支持local branch,可以快速方便的實現(xiàn)各種分支管理……
二、一枝獨秀-Git
刀耕火種
1991-2002 — Linux內(nèi)核維護(hù)所使用的工具:diff和patch。
歡樂時光
在2002年至2005年期間,Linux內(nèi)核的代碼管理工具是商業(yè)版本控制系統(tǒng)BitKeeper。
艱難時刻
2005年發(fā)生的一件事最終導(dǎo)致Git的誕生。在2005年4月,Andrew Tridgell(即大名鼎鼎的Samba的作者)試圖對BitKeeper進(jìn)行反向工程,以開發(fā)一個能與BitKeeper交互的開源工具。這激怒了BitKeeper軟件的所有者BitKeeper公司,要求收回對Linux社區(qū)免費使用BitKeeper的授權(quán)。
Git誕生
迫不得已,Linus選擇了自己開發(fā)一個分布式版本控制工具以替代BitKeeper。十天之后,Git誕生。“git”,該詞源自英國俚語,意思大約是“混賬”。林納斯·托瓦茲自嘲地取了這個名字。
Git最早是根據(jù)Monotone(一個開放源碼的分散式版本控制軟件工具。C++寫的)改寫的。
GitHub助攻
2008年,GitHub平臺(全球最大同性交友網(wǎng)站)正式上線,通過Git進(jìn)行版本控制的軟件源代碼托管服務(wù)。截止到2015年,GitHub已經(jīng)有超過兩千八百萬注冊用戶和7900萬代碼庫。事實上已經(jīng)成為了世界上最大的代碼存放網(wǎng)站和開源社區(qū)。2018年6月4日晚上,美國科技公司微軟宣布以75億美元的股票收購GitHub。
一枝獨秀
截至 2018 年,全球知名的 IT 技術(shù)問答網(wǎng)站 Stack Overflow 調(diào)查的 74,298 開發(fā)人員中,87.2% 的人表示更喜歡使用 Git 進(jìn)行版本控制。(用SVN的為16.1%)
花絮:2016年5月11日,BitKeeper宣布以Apache 2.0許可證開源。托管在了GitHub上。
三、Git特點
1.直接記錄快照,而非差異比較。
- Git 更像是把數(shù)據(jù)看作是對小型文件系統(tǒng)的一組快照。 每次你提交 更新,或在 Git 中保存項目狀態(tài)時,它主要對當(dāng)時的全部文件制作一個快照并保存這個快照的索引。
2.近乎所有操作都是本地執(zhí)行。
- 在本地磁盤上就有項目的完整歷史。
3.Git 保證完整性。
- Git 中所有數(shù)據(jù)在存儲前都計算校驗和,然后以校驗和(由 40 個十六進(jìn)制字符組成字符串)來引用。
4.Git 一般只添加數(shù)據(jù)
- 一旦你提交快照到 Git 中,就不會丟失數(shù)據(jù)。
四、安裝&配置
安裝
1.Windows安裝
官網(wǎng)下載:https://git-scm.com/download/win
2.在 Mac 上安裝
1.最簡單的方法是安裝 Xcode Command Line Tools。在 Terminal 里嘗試首次運(yùn)行 git 命令即可。 如果沒有安裝過命令行開發(fā)者工具,將會提示你安裝。
2.官網(wǎng)下載:https://git-scm.com/download/mac
3.使用homebrew:https://brew.sh/
$ brew install git
配置
初次運(yùn)行 Git 前的配置
Git 自帶一個 git config 的工具來幫助設(shè)置控制 Git 外觀和行為的配置變量。 這些變量存儲在三個不同的位置:
-
/etc/gitconfig 文件
: 包含系統(tǒng)上每一個用戶及他們倉庫的通用配置。 如果使用帶有—system
選項的 git config 時,它會從此文件讀寫配置變量。 -
~/.gitconfig
或~/.config/git/config
文件:只針對當(dāng)前用戶。 可以傳遞—global
選項讓 Git 讀寫此文件。 - 前使用倉庫的 Git 目錄中的 config 文件(就是
.git/config
):針對該倉庫。
每一個級別覆蓋上一級別的配置。
用戶信息
設(shè)置你的用戶名稱與郵件地址,它會寫入到你的每一次提交中。
$ git config --global user.name "hony"
$ git config --global user.email hony@163.com
檢查配置信息
$ git config --list
user.name=hony
user.email=hony@163.com
...
列出系統(tǒng)中指定的配置信息
$ git config --system -l
$ git config --global -l
$ git config --local -l
檢查某一項的配置
$ git config user.name
hony
五、Git目錄結(jié)構(gòu)
Git 有三種狀態(tài):已提交(committed)、已修改(modified)和已暫存(staged)。
已提交表示數(shù)據(jù)已經(jīng)安全的保存在本地數(shù)據(jù)庫中。
已修改表示修改了文件,但還沒保存到數(shù)據(jù)庫中。
已暫存表示對一個已修改文件的當(dāng)前版本做了標(biāo)記,使之包含在下次提交的快照中。
由此引入 Git 項目的三個工作區(qū)域的概念:工作目錄、暫存區(qū)域(index)、Git 倉庫(HEAD)。
工作目錄是對項目的某個版本獨立提取出來的內(nèi)容,它持有實際文件。
暫存區(qū)域,它像個緩存區(qū)域,臨時保存你的改動。
Git 倉庫目錄,是 Git 用來保存項目的元數(shù)據(jù)和對象數(shù)據(jù)庫的地方。指向最近一次提交后的結(jié)果。
基本的 Git 工作流程如下:
- 在工作目錄中修改文件。
- 暫存文件,將文件的快照放入暫存區(qū)域。
- 提交更新,找到暫存區(qū)域的文件,將快照永久性存儲到 Git 倉庫目錄。
六、基礎(chǔ)命令
6.1獲取 Git 倉庫
有兩種取得 Git 項目倉庫的方法。
- 第一種是在現(xiàn)有項目或目錄下導(dǎo)入所有文件到 Git 中。
$ git init
該命令將創(chuàng)建一個名為 .git 的子目錄,這個子目錄含有你初始化的 Git 倉庫中所有的必須文件。
- 第二種是從一個服務(wù)器克隆一個現(xiàn)有的 Git 倉庫。
# clone已經(jīng)存在的項目
$ git clone https://gitee.com/coderhony/gittest.git
這會在當(dāng)前目錄下創(chuàng)建一個名為 “gittest” 的目錄,并在這個目錄下初始化一個 .git
文件夾,從遠(yuǎn)程倉庫拉取下所有數(shù)據(jù)放入 .git 文件夾,然后從中讀取最新版本的文件的拷貝。
6.2添加&提交
添加到暫存區(qū)git add <filename>
$ git add qin.md
提交到倉庫
$ git commit -m "創(chuàng)建了'秦國'的文件"
跳過使用暫存區(qū)域
$ git commit -a -m '創(chuàng)建了 wu.md'
檢查當(dāng)前文件狀態(tài)
$ git status
跟蹤新文件
$ git add <filename>
6.3.撤銷操作
取消暫存的文件
$ git reset HEAD wei.md
修改未暫存的狀態(tài)了。修改的內(nèi)容還在。
撤消對文件的修改
$ git checkout -- wei.md
在工作目錄已修改的文件(指的是未提交到暫存區(qū)域的修改)還原成上次提交時的樣子。
Tips: git
checkout -- [file] 是一個危險的命令,對文件做的任何修改都會消失
- 你只是拷貝了另一個文件來覆蓋它。
撤銷(重置)操作主要有3個命令:checkout、reset、revert
操作的范圍又可以分為 commits 和 files,根據(jù)后面的參數(shù)決定操作的范圍。
命令 | 范圍 | 使用場景 |
---|---|---|
git reset | commit級別 | 丟棄提交或扔掉未提交的修改 |
git reset | file級別 | Unstage 文件 |
git checkout | commit級別 | 切換分支或檢查舊的快照 |
git checkout | file級別 | 丟棄working directory中的修改 |
git revert | commit級別 | 撤銷在公共分支上的提交 |
git reset作用于commit
選項 | HEAD | Index | Working directory |
---|---|---|---|
--soft | 是 | 否 | 否 |
--mixed(默認(rèn)) | 是 | 是 | 否 |
--hard | 是 | 是 | 是 |
$ git reset --soft 7b055
$ git reset --mixed 7b055
$ git reset --hard 7b055
git reset作用于file
$ git reset HEAD <file>
修改只是添加到了暫存區(qū),還沒有提交時,可以有如下兩種方式理解:
1.拉取最近一次提交到版本庫的文件到暫存區(qū)
2.可以把暫存區(qū)的修改撤銷掉(unstage),重新放回工作區(qū)
git checkout作用于file
$ git checkout -- files
兩種情況:
1.修改后還沒有被放到暫存區(qū),撤銷修改后工作目錄就回到和版本庫最后一次commit一樣的狀態(tài);
2.已經(jīng)添加到暫存區(qū)后,又再次作了修改,撤銷修改就回到和暫存區(qū)一樣的狀態(tài)。
git checkout 作用于commit
$ git checkout branchname
切換分支,前提是該名稱的分支存在
Tips:區(qū)分作用于file還是commit方式是有無 --
6.4.查看不同
查看工作目錄中當(dāng)前文件和暫存區(qū)域快照之間的差異
git diff
查看暫存區(qū)域和本地倉庫之間的差異
git diff —cached
查看工作區(qū)與指定提交之間的區(qū)別git diff <gitreversion>
$ git diff HEAD
查看兩個commit之間的差別git diff commitId1 commitId2
$ git diff 78c515d8 1ff36534
HEAD 是最近一次 commit
6.5 忽略文件
在git所管理的目錄下,創(chuàng)建一個名為.gitignore
的文件,文件中列出要忽略的內(nèi)容。
.DS_Store
# 忽略'shanggu.md'文件
shanggu.md
# 忽略所有以 .o 或 .a 結(jié)尾的文件
*.[oa]
# 以 / 開頭防止遞歸
/sources
# 以 / 結(jié)尾指定目錄
lib/
# 不忽略 qin.md 文件
!qin.md
6.6.刪除已提交的文件
wu.md文件已經(jīng)被提交到了倉庫中,想要取消git對 wu.md
文件的跟蹤:
$ git rm --cached wu.md
注意:wu.md 一定要添加到 .gitignore中。
6.7.修改操作
有時候我們提交完了才發(fā)現(xiàn)漏掉了幾個文件沒有添加,或者提交信息寫錯了。 此時,可以運(yùn)行帶有 —amend
選 項的提交命令嘗試重新提交。
提交信息需要修改:
$ git commit --amend
忘記提交了文件:
$ git commit -m 'initial commit'
$ git add forgotten_file
$ git commit --amend
最終你只會有一個提交,第二次提交將代替第一次提交的結(jié)果。
6.8查看歷史
使用 git log
命令查看歷史提交記錄。如果不帶任何參數(shù),它會列出所有歷史記錄,最近的排在最上方,顯示提交對象的哈希值,作者、提交日期、和提交說明。
顯示參數(shù)
使用 -p
選項,顯示每次提交的內(nèi)容差異(信息較詳細(xì))。 也可以加上 -2
來僅顯示最近兩次提交:
$ git log -p -2
使用 —stat
選項,顯示每次提交的簡略的統(tǒng)計信息
$ git log --stat
使用 --name-only
選項,僅在已修改的提交信息后顯示文件列表
$ git log --name-only
使用 --name-status
選項,顯示新增、修改和刪除的文件清單
$ git log --name-status
使用 --abbrev-commit
僅顯示SHA-1的前幾個字符
$ git log --abbrev-commit
使用--graph
顯示ASCII圖形表示的分支合并歷史
$ git log --graph
選項 --pretty=
可以指定使用不同于默認(rèn)格式的方式展示提交歷史。
$ git log --pretty=oneline
# format 子選項,可以定制要顯示的記錄格式
$ git log --pretty=format:"%h - %an, %ar : %s"
-
%h
: 提交對象的簡短哈希字串 -
%H
:提交對象的完整哈希字串 -
%an
:作者(author)的名字 -
%ae
:作者的電子郵件地址 -
%ar
: 作者修訂日期,按多久以前的方式顯示 -
%s
: 提交說明 -
%cd
: 提交日期 -
%p
: 父對象的簡短哈希字串 -
%cn
:提交者(committer)的名字 -
%ce
:提交者的電子郵件地址
選項可以組合使用:
$ git log --pretty=format:"%h %s" --graph
帶顏色的輸出
$ git log --pretty=format:"%Cred%h%Creset -%C(yellow)%d%Cblue %s %Cgreen(%cd) %C(bold blue)<%an>"
斷句:[%Cred%h][%Creset -][%C(yellow)%d ][%Cblue%s][%Cgreen(%cd)][%C(bold blue)<%an>]
- 一個顏色+一個內(nèi)容
- 顏色以%C開頭,后邊接幾種顏色
- 能設(shè)置的顏色值包括:reset(默認(rèn)的灰色),normal, black, red, green, yellow, blue, magenta, cyan, white.
選項--date=
(relative|local|default|iso|rfc|short|raw):輸出指定格式日期
$ git log --date=short
定制日期格式
$ git log --date=format:'%Y-%m-%d %H:%M:%S'
篩選參數(shù)
1.按數(shù)量:-n
參數(shù)顯示前n條log
$ git log -n 1
2.按日期:--after=
和--before=
參數(shù),日期
$ git log --after="yesterday"
$ git log --before=="2019-5-7"
$ git log --after="2019-5-5" --before="2019-5-8"
3.按作者:--author
參數(shù)
$ git log --author="hony"
4.按文件:--
$ git log -- wu.md
5.按分支: branchname --
$ git log qinguo --
6.按內(nèi)容:-S"<string>"
、-G"<string>"
查找包含特定字符的某處代碼被修改的記錄,比如某個函數(shù)什么時候添加和修改的,等等。
查文本用-S
,查找的結(jié)果是匹配的 字符串出現(xiàn)的次數(shù)發(fā)生變化 的提交。比如,某次提交將abcdef
修改為abcd
, 使用git log -Sabc
將不會查找出此提交,使用git log -Sabcde
可以查找出。(-S
可以使用–pickaxe-regex
修改為接受正則表達(dá)式。)
$ git log -S"開始"
查正則用-G
,沒有次數(shù)變化的限制,只要變化就會查找出對應(yīng)的提交
$ git log -G"開始"
注意:-G默認(rèn)接受正則表達(dá)式,而-S通常接受一個字符串,但可以使用–pickaxe-regex修改為接受正則表達(dá)式。
7.按范圍:顯示2個branch之間的不同
..
包含所有qinguo有但是master沒有的commit
$ git log master..qinguo
...
查詢master或qinguo分支中的提交記錄
$ git log master...qinguo
8.按tag:
查詢tag之前的commit
$ git log v1.0
查詢tag之后的commit, 不包含tag所在的commit本身
$ git log v1.0..
9.按commit:
查詢commit之前的記錄,包含commit
$ git log commit
查詢commit1與commit2之間的記錄,包括commit1和commit2
$ git log commit1 commit2
查詢commit1與commit2之間的記錄,不包括commit1
$ git log commit1..commit2
10.組合使用:
$ git log --date=format:'%Y-%m-%d %H:%M:%S' --author='hony' --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Cblue %s %Cgreen(%cd) %C(bold blue)<%an>%Creset' --abbrev-commit
6.9搜索提交內(nèi)容
搜索代碼內(nèi)容
使用 grep
命令,可以很方便地從提交歷史或者工作目錄中查找一個字符串或者正則表達(dá)式。默認(rèn)情況下 Git 會查找你工作目錄的文件。
$ git grep "王"
傳入 -n
參數(shù)來輸出 Git 所找到的匹配行行號。
$ git grep -n "秦"
使用 -c
選項查看每個文件里有多少行匹配內(nèi)容。
$ git grep -c "秦"
使用 -p
選項查看匹配的行是屬于哪一個方法或者函數(shù):
$ git grep -p "王"
使用--name-only
只顯示文件名
$ git grep --name-only "王"
使用正則結(jié)合 --and
或--or
選項來查看復(fù)雜的字符串組合
$ git grep -e "^秦"
$ git grep -e '秦' --and -e "王"
$ git grep -e '秦' --or -e "王"
Git日志搜索
行日志搜索
搜索包含某個字符的提交說明
$ git log --grep="秦"
七、遠(yuǎn)程倉庫
查看遠(yuǎn)程倉庫
$ git remote
origin
origin
是 Git 給你克隆的倉庫服務(wù)器的默認(rèn)名字。
指定選項 -v
,會顯示需要讀寫遠(yuǎn)程倉庫使用的 Git 保存的簡寫與其對應(yīng)的 URL。
$ git remote -v
origin https://gitee.com/coderhony/gittest.git (fetch)
origin https://gitee.com/coderhony/gittest.git (push)
查看更多信息
$ git remote show origin
取消遠(yuǎn)程倉庫和本地關(guān)聯(lián)
$ git remote rm origin
添加遠(yuǎn)程倉庫
添加一個新的遠(yuǎn)程 Git 倉庫,同時指定一個你可以輕松引用的簡寫:
$ git remote add origin https://gitee.com/coderhony/gittest.git
其中origin
是倉庫的名字,https://gitee.com/coderhony/gittest.git
是遠(yuǎn)程倉庫的地址。
從遠(yuǎn)程拉取數(shù)據(jù)
注意:遠(yuǎn)程分支更新的情況。
$ git fetch [remote-name]
這個命令會訪問遠(yuǎn)程倉庫,從中拉取所有你還沒有的數(shù)據(jù)。
注意 git fetch 命令會將數(shù)據(jù)拉取到你的本地倉庫 - 它并不會自動合并或修改你當(dāng)前的工作。
如果你有一個分支設(shè)置為跟蹤一個遠(yuǎn)程分支,可以使用 git pull 命 令來自動的抓取然后合并遠(yuǎn)程分支到當(dāng)前分支。
$ git pull
推送數(shù)據(jù)到遠(yuǎn)程
將本地分支推送到遠(yuǎn)程 git push [remote-name] [branch-name]
$ git push origin master
注意:1.必須對遠(yuǎn)程有寫入權(quán)限,才可以進(jìn)行push操作。
2.必須先進(jìn)行pull操作,再進(jìn)行push操作。
遠(yuǎn)程倉庫的重命名
$ git remote rename origin pb
注意,這同樣也會修改你的遠(yuǎn)程分支名字。
現(xiàn)在可以用字符串 pb
指代對應(yīng)的倉庫地址了。
添加本地分支與遠(yuǎn)程倉庫的關(guān)聯(lián)
本地的master分支與遠(yuǎn)程的master分支相關(guān)聯(lián)
$ git branch --set-upstream-to=origin/master master
$ git branch -u origin/master
前面的 origin/master 指的是遠(yuǎn)程分支;后面的 master 指的是本地分支。
此命令的含義是指當(dāng)前分支的upstream為origin遠(yuǎn)程倉庫的master分支。
注意:如果提示
error: the requested upstream branch 'origin/master' does not exist
錯誤,則先進(jìn)行git fetch
操作,然后再進(jìn)行分支關(guān)聯(lián)操作。
取消本地分支與遠(yuǎn)程倉庫的關(guān)聯(lián)
$ git branch --unset-upstream
八、協(xié)議
Git 可以使用四種主要的協(xié)議來傳輸數(shù)據(jù):本地傳輸,SSH 協(xié)議,Git 協(xié)議和 HTTP 協(xié)議。
1.HTTPS協(xié)議
$ https://gitee.com/coderhony/gittest.git
可以使用用戶名/密碼授權(quán),和SSH相比,不用先在本地生成 SSH 密鑰對再把公鑰上傳到服務(wù)器。缺點是速度慢,每次推送都必須輸入口令。(有了keychain
的功能就不用每次都輸入密碼了。)
2.SSH協(xié)議
是一個同時便于讀和寫操作的網(wǎng)絡(luò)協(xié)議。通過 SSH 訪問是安全的,所有數(shù)據(jù)傳輸都是加密和授權(quán)的。SSH 很高效,會在傳輸之前盡可能的壓縮數(shù)據(jù)。SSH 唯一的限制就是不能匿名訪問。
生成 SSH Key:
$ ssh-keygen -t rsa -b 4096 -C "your_email@example.com"
$ cat ~/.ssh/id_rsa.pub
$ pbcopy < ~/.ssh/id_rsa.pub
clone代碼:
$ git clone ssh://git@gitee.com/<repo_owner>/<reponame>.git
$ git clone git@gitee.com:coderhony/gittest.git
3.本地協(xié)議
clone如下
$ git clone /Users/hony/Desktop/gittest/gittest
要添加一個本地倉庫作為現(xiàn)有 Git 項目的遠(yuǎn)程倉庫,可以這樣做:
$ git remote add local_proj /Users/hony/Desktop/gittest/gittest
難以控制不同位置的訪問權(quán)限。
4.Git協(xié)議
這是一個包含在 Git 軟件包中的特殊守護(hù)進(jìn)程; 它會監(jiān)聽一個提供類似于 SSH 服務(wù)的特定端口(9418),而無需任何授權(quán)。要么所有人都能克隆,要么都不能。所以沒有授權(quán)機(jī)制是一個缺點。
九、分支
Git 保存的不是文件的變化或者差異,而是一系列不同時刻的文件快照。
在進(jìn)行提交操作時,Git 會保存一個提交對象。該提交對象會包含一個指向暫存內(nèi)容快照的指針,還包含了作者的姓名和郵箱、提交時輸入的信息以及指向它的父對象的指針。
注意,首次提交產(chǎn)生的提交對象沒有父對象。普通提交操作產(chǎn)生的提交對象有一個父對象。由多個分支合并產(chǎn)生的提交對象有多個父對象。
暫存操作會為每一個文件計算校驗和(SHA-1 哈希算法),然后會把當(dāng)前版本的文件快照保存到 Git 倉庫中(Git 使用 blob 對象來保存它們),最終將校驗和加入到暫存區(qū)域等待提交:
$ git add wei.md zhao.md han.md
$ git commit -m '添加魏趙韓三個國家'
當(dāng)使用 git commit
進(jìn)行提交操作時,Git 會先計算每一個子目錄的校驗和,然后在 Git 倉庫中這些校驗和保存為樹對象。 隨后,Git 便會創(chuàng)建一個提交對象,它除了包含上面提到的那些信息外, 還包含指向這個樹對象的指針。
現(xiàn)在,Git 倉庫中有五個對象:三個 blob 對象(保存著文件快照)、一個樹對象(記錄著目錄結(jié)構(gòu)和 blob 對象索引)以及一個提交對象(包含著指向前述樹對象的指針和所有提交信息)。
做些修改后再次提交,那么這次產(chǎn)生的提交對象會包含一個指向上次提交對象(父對象)的指針。
Git 的分支,其實本質(zhì)上僅僅是指向提交對象的可變指針。 Git 的默認(rèn)分支名字是 master。它會在每次的提交操作中自動向前移動。
1.創(chuàng)建分支
創(chuàng)建分支就是創(chuàng)建了一個可以移動的新的指針。比如,創(chuàng)建一個 qinguo 分 支, 你需要使用 git branch
命令:
$ git branch qinguo
在 Git 中,有一個名為 HEAD 的特殊指針,指向當(dāng)前所在的本地分支。
可以簡單地使用 git log
命令搭配--decorate
參數(shù)查看各個分支當(dāng)前所指的對象。
$ git log --oneline --decorate
當(dāng)前 “master” 和 “qinguo” 分支均指向校驗和以 f30ab 開頭的提交對象。
切換分支
要切換到一個已存在的分支,使用 git checkout 命令。
$ git checkout qinguo
再修改內(nèi)容并提交一次:
$ echo "秦國在戰(zhàn)國史上是最輝煌的" >> qin.md
$ git commit -a -m '添加了qin.md'
HEAD 分支隨著提交操作自動向前移動:
qinguo
分支向前移動了,master 分支還停留在了checkout
時的位置。切回到master分支:
$ git checkout master
這條命令做了兩件事。 一是使 HEAD 指回 master 分支,二是將工作目錄恢復(fù)成 master 分支所指向的快照內(nèi)容。
注意,分支切換會改變你工作目錄中的文件。
再稍微做些修改并提交:
$ vim zhao.md
$ git commit -a -m '趙國的趙括出生了'
現(xiàn)在,這個項目的提交歷史產(chǎn)生了分叉。使用 git log 命令查看分叉歷史。 運(yùn)行 git log --oneline --decorate --graph —all
,它會輸出你的提交歷史、各個分支的指向以及項目的分支分叉情況。
$ git log --oneline --decorate --graph --all
2.分支管理
查看分支
$ git branch
分支前的 *
字符:表示當(dāng)前所在的分支(即當(dāng)前 HEAD 指針?biāo)赶虻姆种В?/p>
如果要查看每一個分支的最后一次提交,可以運(yùn)行以下命令:
$ git branch -v
添加--merged
選項可以查看哪些分支已經(jīng)合并到當(dāng)前分支:
$ git branch --merged
添加--no-merged
選項可以查看所有包含未合并當(dāng)前分支的分支:
$ git branch --no-merged
3.分支開發(fā)工作流
長期分支
在 master
分支上保留完全穩(wěn)定的代碼——有可能僅僅是已經(jīng)發(fā)布或即將發(fā)布的代碼。在短期分支(如develop
、topic
)上進(jìn)行開發(fā)測試,這些短期分支達(dá)到穩(wěn)定狀態(tài)后,就可以被合并入 master
分支了。
穩(wěn)定分支的指針總是在提交歷史中落后一大截,而前沿分支的指針往往比較靠前。
特性分支
特性分支是一種短期分支,它被用來實現(xiàn)單一特性或其相關(guān)工作。比如如develop
、topic
分支。
4.遠(yuǎn)程分支
遠(yuǎn)程引用是對遠(yuǎn)程倉庫的引用(指針),包括分支、標(biāo)簽等等。 你可以通過 git ls-remote (remote)
來顯式地獲得遠(yuǎn)程引用的完整列表,或者通過 git remote show (remote)
獲得遠(yuǎn)程分支的更多信息。 然而,一個更常見的做法是利用遠(yuǎn)程跟蹤分支。
遠(yuǎn)程跟蹤分支是遠(yuǎn)程分支狀態(tài)的引用。 它們是你不能移動的本地引用,當(dāng)你做任何網(wǎng)絡(luò)通信操作時,它們會自動移動。 遠(yuǎn)程跟蹤分支像是你上次連接到遠(yuǎn)程倉庫時,那些分支所處狀態(tài)的書簽。
它們以 (remote)/(branch)
形式命名。比如 origin/master
分支, origin/feature-v410
分支。
遠(yuǎn)程有一個https://gitee.com/coderhony/gittest
服務(wù)器,從這里克隆,Git 的 clone
命令會為你自動將其命名為 origin
,拉取它的所有數(shù)據(jù),創(chuàng)建一個指向它的 master
分支的指針,并且在本地將其命名為 origin/master
。Git 也會給你一個與 origin 的 master
分支在指向同一個地方的本地 master
分支,這樣在本地就可以基于master
分支進(jìn)行工作了。
“origin” 和“master”并無特殊含義。
“master” 是當(dāng)你運(yùn)行
git init
時默認(rèn)的起始分支名字,原因僅僅是它的廣泛使用。“origin” 是當(dāng)你運(yùn)行
git clone
時默認(rèn)的遠(yuǎn)程倉庫名字。如果你運(yùn)行git clone -o repository https://gitee.com/coderhony/gittest.git
,那么你默認(rèn)的遠(yuǎn)程分支名字將會是repository/master
。
如果你在本地的 master
分支做了一些工作,然而在同一時間,其他人推送提交到 git.ourcompany.com
并更新了它的 master
分支,那么你的提交歷史將向不同的方向前進(jìn)。 也許,只要你不與 origin 服務(wù)器連接,你的 origin/master
指針就不會移動。
如果要同步你的工作,運(yùn)行 git fetch origin
命令。這個命令查找 “origin” 是哪一個服務(wù)器(是 git.ourcompany.com
),從中抓取本地沒有的數(shù)據(jù),并且更新本地數(shù)據(jù)庫,移動 origin/master
指針指向新的、更新后的位置。
假定你有另一個內(nèi)部 Git 服務(wù)器,僅用于你的 sprint 小組的開發(fā)工作。 這個服務(wù)器位于 git.team1.ourcompany.com
。 你可以運(yùn)行 git remote add
命令添加一個新的遠(yuǎn)程倉庫引用到當(dāng)前的項目。 將這個遠(yuǎn)程倉庫命名為 teamone
,將其作為整個 URL 的縮寫。
現(xiàn)在,可以運(yùn)行 git fetch teamone
來抓取遠(yuǎn)程倉庫 teamone
有而本地沒有的數(shù)據(jù)。 因為那臺服務(wù)器上現(xiàn)有的數(shù)據(jù)是 origin
服務(wù)器上的一個子集,所以 Git 并不會抓取數(shù)據(jù)而是會設(shè)置遠(yuǎn)程跟蹤分支 teamone/master
指向 teamone
的 master
分支。
推送
當(dāng)你想要公開分享一個分支時,需要手動顯式地將其推送到有寫入權(quán)限的遠(yuǎn)程倉庫上。 使用 git push (remote) (branch)
命令:
比如serverfix
是共享分支
$ git push origin serverfix
Git 自動將 serverfix
分支名字展開為 refs/heads/serverfix:refs/heads/serverfix
,那意味著,“推送本地的 serverfix 分支來更新遠(yuǎn)程倉庫上的 serverfix 分支。” 我們將會詳細(xì)學(xué)習(xí) Git 內(nèi)部原理 的 refs/heads/
部分。
也可以運(yùn)行 git push origin serverfix:serverfix
,可以通過這種格式來推送本地分支到一個命名不相同的遠(yuǎn)程分支。 如果并不想讓遠(yuǎn)程倉庫上的分支叫做 serverfix
,可以運(yùn)行 git push origin serverfix:awesomebranch
來將本地的 serverfix
分支推送到遠(yuǎn)程倉庫上的 awesomebranch
分支。
下一次其他協(xié)作者從服務(wù)器上抓取數(shù)據(jù)時,他們會在本地生成一個遠(yuǎn)程分支 origin/serverfix
,指向服務(wù)器的 serverfix
分支的引用:
$ git fetch origin
當(dāng)抓取到新的遠(yuǎn)程跟蹤分支時,本地不會自動生成一個新的 serverfix
分支,只有一個不可以修改的 origin/serverfix
指針。如果想要在自己的 serverfix
分支上工作,可以將其建立在遠(yuǎn)程跟蹤分支之上:
$ git checkout -b serverfix origin/serverfix
這會創(chuàng)建一個用于工作的本地分支,并且起點位于 origin/serverfix
。
跟蹤分支
從一個遠(yuǎn)程跟蹤分支檢出一個本地分支會自動創(chuàng)建所謂的 “跟蹤分支”(它跟蹤的分支叫做 “上游分支”)。 跟蹤分支是與遠(yuǎn)程分支有直接關(guān)系的本地分支。 如果在一個跟蹤分支上輸入 git pull
,Git 能自動地識別去哪個服務(wù)器上抓取、合并到哪個分支。
當(dāng)克隆一個倉庫時,它通常會自動地創(chuàng)建一個跟蹤 origin/master
的 master
分支。
$ git checkout -b [branch] [remotename]/[branch]
$ git checkout -b serverfix origin/serverfix
$ git checkout -b sf origin/serverfix
創(chuàng)建跟蹤分支的快捷方式:
$ git checkout --track origin/serverfix
設(shè)置已有的本地分支跟蹤一個剛剛拉取下來的遠(yuǎn)程分支:
$ git branch --set-upstream-to origin/serverfix
$ git branch -u origin/serverfix
查看所有跟蹤分支,使用 git branch
的 -vv
選項
$ git branch -vv
需要重點注意的一點是這些數(shù)字的值來自于你從每個服務(wù)器上最后一次抓取的數(shù)據(jù)。 這個命令并沒有連接服務(wù)器,它只會告訴你關(guān)于本地緩存的服務(wù)器數(shù)據(jù)。 如果想要統(tǒng)計最新的領(lǐng)先與落后數(shù)字,需要在運(yùn)行此命令前抓取所有的遠(yuǎn)程倉庫。 可以像這樣做:
$ git fetch --all;
$ git branch -vv
拉取
當(dāng) git fetch
命令從服務(wù)器上抓取本地沒有的數(shù)據(jù)時,它并不會修改工作目錄中的內(nèi)容。 它只會獲取數(shù)據(jù)然后讓你自己合并。
git pull
命令是 git fetch
緊接著一個 git merge
命令。git pull
會查找當(dāng)前分支所跟蹤的服務(wù)器與分支,從服務(wù)器上抓取數(shù)據(jù)然后嘗試合并入那個遠(yuǎn)程分支。
刪除遠(yuǎn)程分支
運(yùn)行帶有 --delete
選項的 git push
命令來刪除一個遠(yuǎn)程分支。 如果想要從服務(wù)器上刪除 serverfix
分支,運(yùn)行下面的命令:
$ git push origin --delete serverfix
基本上這個命令做的只是從服務(wù)器上移除這個指針。
5.整合分支
整合來自不同分支的修改主要有兩種方法:merge
以及 rebase
。
Merge 快速前進(jìn)
此時在chuguo分支上,要想把chuguo合并到到master上
可以先檢出到master分支,然后執(zhí)行merge操作
$ git checkout master
$ git merge chuguo
Merge 三方合并
在這種情況下,開發(fā)歷史從C2的地方開始分叉開來。
因為,master 分支所在提交并不是 qinguo 分支所在提交的直接祖先,Git 不得不做一些額外的工作。 出現(xiàn)這種情況的時候,Git 會使用兩個分支的末端所指的快照(C4
和 C5 )以及這兩個分支的工作祖先(C2),做一個簡單的三方合并。
C2: master和qinguo的共同祖先
C4:將要接收別處merge進(jìn)來的快照
C5:將要被merge到別處的快照
Git將此次三方合并的結(jié)果做了一個新的快照并且自動創(chuàng)建一個新的提交指向它。這個被稱作一次合并提交,它的特別之處在于他有不止一個父提交。
Git會自行決定選取哪一個提交作為最優(yōu)的共同祖先,并以此作為合并的基礎(chǔ)。
Rebase的基本操作
開發(fā)任務(wù)分叉到兩個不同分支,又各自提交了更新。
可以提取在 C5 中引入的補(bǔ)丁和修改,然后在 C4 的基礎(chǔ)上應(yīng)用一次。 在 Git 中,這種操作就叫做 變基。可以使用 rebase 命令將提交到某一分支上的所有修改都移至另一分支上,就好像“重新播放”一樣。
$ git checkout qinguo
$ git rebase master
它的原理是首先找到這兩個分支(即當(dāng)前分支 qinguo、變基操作的目標(biāo)基底分支 master)的最近共同祖先 C2,然后對比當(dāng)前分支相對于該祖先的歷次提交,提取相應(yīng)的修改并存為臨時文件,然后將當(dāng)前分支指向目標(biāo)基底 C4,最后以此將之前另存為臨時文件的修改依序應(yīng)用。
現(xiàn)在回到 master 分支,進(jìn)行一次快進(jìn)合并。
$ git checkout master
$ git merge qinguo
此時,C5' 指向的快照就和上面使用 merge 命令的例子中 C6 指向的快照一模一樣了。 這兩種整合方法的最終結(jié)果沒有任何區(qū)別,但是變基使得提交歷史更加整潔。提交歷史是一條直線沒有分叉。
注意,無論是通過變基,還是通過三方合并,整合的最終結(jié)果所指向的快照始終是一樣的,只不過提交歷史不同罷了。 變基是將一系列提交按照原有次序依次應(yīng)用到另一分支上,而合并是把最終結(jié)果合在一起。
更復(fù)雜的Rebase舉例
需求:
希望將 hanguo 中的修改合并到主分支master并發(fā)布,但暫時并不想合并 weiguo 中的修改,因為它們還需要經(jīng)過更全面的測試。
這時,你就可以使用 git rebase 命令的 --onto 選項,選中在 hanguo 分支里但不在 weiguo 分支里的修改(即 C8 和 C9),將它們在 master 分支上重放:
$ git rebase --onto master weiguo hanguo
取出 hanguo 分支,找出處于 hanguo 分支和 weiguo 分支的共同祖先之后的修改,然后把它們在 master 分支上重放一遍。
現(xiàn)在可以快進(jìn)合并 master 分支了。
$ git checkout master
$ git merge hanguo
接下來你決定將 weiguo 分支中的修改也整合進(jìn)來。 使用 git rebase [basebranch] [topicbranch] 命令可以直接將特性分支(即本例中的 weiguo )變基到目標(biāo)分支(即 master)上。這樣做能省去你先切換到 weiguo 分支,再對其執(zhí)行變基命令的多個步驟。
$ git rebase master weiguo
此時, weiguo 中的代碼被“續(xù)”到了 master 后面。
然后就可以快進(jìn)合并主分支 master 了:
$ git checkout master
$ git merge weiguo
至此,hanguo 和 weiguo 分支中的修改都已經(jīng)整合到主分支里了,你可以刪除這兩個分支
$ git branch -d hanguo
$ git branch -d weiguo
Rebase的風(fēng)險
使用變基要遵守一條準(zhǔn)則:
不要對在你的倉庫外有副本的分支執(zhí)行變基。
變基操作的實質(zhì)是丟棄一些現(xiàn)有的提交,然后相應(yīng)地新建一些內(nèi)容一樣但實際上不同的提交。
merge 和 rebase的選擇
merge : 倉庫的提交歷史即是 記錄實際發(fā)生過什么。
rebase : 提交歷史是 項目過程中發(fā)生的事。
總的原則是,只對尚未推送的本地修改執(zhí)行變基操作清理歷史,不要對已推送至別處的提交執(zhí)行變基操作。
沖突解決
無論是使用merge 還是 rebase , 有時候合并操作不會如此順利。
如果你在兩個不同的分支中,對同一個文件的同一個部分進(jìn)行了不同的修改,Git
就沒法干凈的合并它們。
$ git merge qin
Auto-merging qin.md
CONFLICT (content): Merge conflict in qin.md
Automatic merge failed; fix conflicts and then commit the result.
此時Git 做了合并,但是沒有自動地創(chuàng)建一個新的合并提交。 Git 會暫停下來,等待你去解決合并產(chǎn)生的沖突。
任何因包含合并沖突而有待解決的文件,都會以未合并狀態(tài)標(biāo)識出來。
<<<<<<< HEAD
秦孝公任用商鞅進(jìn)行變法
=======
商鞅來到了秦國
>>>>>>> qinguo
HEAD和===
之間的部分表示當(dāng)前分支ours(master上)的內(nèi)容
===
和>>>qinguo
之間的部分表示其他分支theirs(qinguo)上的的內(nèi)容
解決方案可以僅保留其中一個分支的修改,并且 <<<<<<<
, =======
, 和 >>>>>>>
這些行要完全刪除。在你解決了所有文件里的沖突之后,對每個文件使用 git add 命令來將其標(biāo)記為沖突已解決。 一旦暫存這些原本有沖突的文件,Git 就會將它們標(biāo)記為沖突已解決。
接下來就可以繼續(xù)工作了。
如果是merge產(chǎn)生的沖突
$ git add .
$ git commit -m “合并代碼,解決沖突”
如果是rebase產(chǎn)生的沖突
$ git add .
$ git rebase --continue
…
如果要停止合并,可以使用
# merge的情況
$ git merge --abort
# rebase的情況
$ git rebase --abort
十、標(biāo)簽
查看標(biāo)簽
$ git tag
這個命令以字母順序列出標(biāo)簽。
也可以使用特定的模式查找標(biāo)簽。如果只對 1.8.5 系列感興趣,可以運(yùn)行:
$ git tag -l 'v1.8.5*’
創(chuàng)建標(biāo)簽
兩種主要類型的標(biāo)簽:
輕量標(biāo)簽(lightweight)與附注標(biāo)簽(annotated)。
?輕量標(biāo)簽只是一個特定提交的引用。
?附注標(biāo)簽是存儲在 Git 數(shù)據(jù)庫中的一個完整對象。它們是可以被校驗的;其中包含打標(biāo)簽者的名字、電子 郵件地址、日期時間;還有一個標(biāo)簽信息;并且可以使用 GNU Privacy Guard (GPG)簽名與驗證。 通常建議創(chuàng)建附注標(biāo)簽。
附注標(biāo)簽
在運(yùn)行 tag 命令時指定 -a 選項:
$ git tag -a v1.4 -m 'my version 1.4'
-m 選項指定了一條將會存儲在標(biāo)簽中的信息。
通過使用 git show 命令可以看到標(biāo)簽信息與對應(yīng)的提交信息:
$ git show v1.4
輕量標(biāo)簽
輕量標(biāo)簽本質(zhì)上是將提交校驗和存儲到一個文件中, 沒有保存任何其他信息。創(chuàng)建輕量標(biāo)簽,只需要提供標(biāo)簽名字:
$ git tag v1.4-lw
如果在標(biāo)簽上運(yùn)行 git show,只顯示提交信息。
后期打標(biāo)簽
在歷史提交上打標(biāo)簽,在某個提交上打標(biāo)簽,需要在命令的末尾指定提交的校驗和:
$ git log --pretty=oneline
$ git tag -a v1.2 9fceb02
推送標(biāo)簽
在創(chuàng)建完標(biāo)簽后你必須顯式地推送標(biāo)簽到 共享服務(wù)器上,使用git push origin [tagname]命令。
$ git push origin v1.4
如果要一次性推送很多標(biāo)簽,也可以使用帶有 —tags 選項。
$ git push origin --tags
檢出標(biāo)簽
如果你想要工作目錄與倉庫中特定 的標(biāo)簽版本完全一樣,可以使用 git checkout -b [branchname] [tagname] 在特定的標(biāo)簽上創(chuàng)建一個 新分支:
$ git checkout -b version2 v2.0.0
十一、儲藏
有時,當(dāng)你在項目的一部分上已經(jīng)工作一段時間后,所有東西都進(jìn)入了混亂的狀態(tài),而這時你想要切換到另一個分支做一點別的事情。此時可以使用 git stash (或 git stash save)命令。修改的跟蹤文件與暫存改動 - 然后將未完成的修改保存到一個棧上,而你 可以在任何時候重新應(yīng)用這些改動。
$ git stash
工作目錄是干凈的了。
要查看儲藏的東西,可以使用 git stash list:
$ git stash list
stash@{0}: WIP on master: 049d078 added the index file
stash@{1}: WIP on master: c264051 Revert "added file_size”
恢復(fù)儲藏的內(nèi)容到工作區(qū),使用 git stash apply 命令。如果想要更舊的儲藏,可以指定git stash apply stash@{2} 。如果未指定某一個儲藏,Git 認(rèn)為指定的是最近的儲藏:
$ git stash apply
git stash apply命令之后,在堆棧上還有記錄。使用 git stash drop 命令,恢復(fù)之后,堆棧的信息也就沒有了。
$ git stash list
stash@{0}: WIP on master: 049d078 added the index file
stash@{1}: WIP on master: c264051 Revert "added file_size"
stash@{2}: WIP on master: 21d80a5 added number to log
$ git stash drop stash@{0}
Dropped stash@{0} (364e91f3f268f0900bc3ee613f9f733e82aaed43)
十二、配置別名
如果不想每次都輸入完整的 Git 命令,可以通過 git config 文件來輕松地為每一個命令設(shè)置一個別名。
$ git config --global alias.co checkout
$ git config --global alias.br branch
$ git config --global alias.ci commit
$ git config --global alias.st status
$ git config --global alias.unstage 'reset HEAD --’
$ git config --global alias.last 'log -1 HEAD’
$ git config --global alias.lm "log --color --date=format:'%Y-%m-%d %H:%M:%S' --author='hony' --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Cblue %s %Cgreen(%cd) %C(bold blue)<%an>%Creset' --abbrev-commit"
當(dāng)要輸入 git commit 時,只需要輸入 git ci 即可。
使下面的兩個命令等價:
$ git unstage fileA
$ git reset HEAD -- fileA
Git 只是簡單地將別名替換為對應(yīng)的命令。
十三、核武器級選項:filter-branch
filter-branch 是一個歷史改寫的選項,可以改寫大量提交。
例如,全局修改你的郵箱地址或從每一個提交中移除一個文件。
全局修改郵箱地址
可以通過 filter-branch 來一 次性修改多個提交中的郵箱地址。 需要小心的是只修改你自己的郵箱地址,所以要使用 --commit-filter:
$ git filter-branch --commit-filter '
if [ "$GIT_AUTHOR_EMAIL" = "hony@163.com" ];
then
GIT_AUTHOR_NAME="hony";
GIT_AUTHOR_EMAIL="hony1220@163.com";
git commit-tree "$@";
else
git commit-tree "$@";
fi' HEAD
這會遍歷并重寫每一個提交來包含你的新郵箱地址。
從所有提交中刪除一個文件
意外地提交了一個巨大的二進(jìn)制文件,想將它從所有地方刪除。
$ git filter-branch --tree-filter 'rm -f passwords.txt' HEAD
--tree-filter
選項會在每次檢出項目時先執(zhí)行指定的命令然后重新提交結(jié)果。在這個例子中,你會在所有快照中刪除一個名叫
password.txt 的文件,無論它是否存在。
Git重寫目錄樹并且提交,使用時最好新建分支,測試沒有問題了,再reset到master上。
十四、清理工作目錄
可以使用git clean命令去除冗余文件或者清理工作目錄。 使用git clean -f -d命令來移除工作目錄中所有未追蹤的文件以及空的子目錄。 -f 意味著強(qiáng)制移除。
如果只是想要看看它會做什么,可以使用 -n 選項來運(yùn)行命令,這意味著 “做一次演習(xí)然后告訴你 將要 移除什么”。
$ git clean -d -n
Would remove test.o
Would remove tmp/
如果不知道 git clean 命令將會做什么,在將 -n 改為 -f 來真正做之前總是先用 -n 來運(yùn)行它做雙重檢查。
默認(rèn)情況下,git clean 命令只會移除沒有忽略的未跟蹤文件。任何與.gitignore 或其他忽略文件中的模式匹配的文件都不會被移除。
十五、簡短的SHA-1
Git 十分智能,你只需要提供 SHA-1 的前幾個字符就可以獲得對應(yīng)的那次提交,當(dāng)然你提供的 SHA-1 字符數(shù)量 不得少于 4 個。
例如查看一次指定的提交,假設(shè)你執(zhí)行 git log 命令來查看之前新增一個功能的那次提交:
$ git log
commit 1c002dd4b536e7479fe34593e72e6c6c1819e53b
假設(shè)這個提交是 1c002dd….,如果你想 git show 這個提交,下面的命令是等價的:
$ git show 1c002dd4b536e7479fe34593e72e6c6c1819e53b
$ git show 1c002dd4b
Git 可以為 SHA-1 值生成出簡短且唯一的縮寫。 如果你在 git log 后加上 --abbrev-commit 參數(shù),輸出結(jié) 果里就會顯示簡短且唯一的值;默認(rèn)使用七個字符:
$ git log --abbrev-commit --pretty=oneline
通常 8 到 10 個字符就已經(jīng)足夠在一個項目中避免 SHA-1 的歧義
如果地球上65 億的人類都在編程,每人每秒都在產(chǎn)生等價于整個 Linux 內(nèi)核歷史(一百萬個Git 對象)的代碼,并將之提交到一個巨大的 Git 倉庫里面,那將花費
5 年的時間才會產(chǎn)生足夠的對象,使其擁有 50% 的概率產(chǎn)生一次SHA-1對象沖突。
十六、引用日志
當(dāng)你在工作時,Git 會在后臺保存一個引用日志(reflog),引用日志記錄了最近幾個月你的 HEAD 和分支引用所指向的歷史。可以使用 git reflog 來查看引用日志:
$ git reflog
734713b HEAD@{0}: commit: fixed refs handling, added gc auto, updated
d921970 HEAD@{1}: merge phedders/rdocs: Merge made by recursive.
1c002dd HEAD@{2}: commit: added some blame and merge stuff
1c36188 HEAD@{3}: rebase -i (squash): updating HEAD
95df984 HEAD@{4}: commit: # This is a combination of two commits.
1c36188 HEAD@{5}: rebase -i (squash): updating HEAD
7e05da5 HEAD@{6}: rebase -i (pick): updating HEAD
每當(dāng)你的 HEAD 所指向的位置發(fā)生了變化,Git 就會將這個信息存儲到引用日志這個歷史記錄里。 通過這些數(shù)據(jù),你可以很方便地獲取之前的提交歷史。 如果你想查看倉庫中 HEAD 在五次前的所指向的提交,你可以使用 @{n} 來引用 reflog 中輸出的提交記錄。
$ git show HEAD@{5}
十七、祖先引用
祖先引用是另一種指明一個提交的方式。 如果你在引用的尾部加上一個 ^, Git 會將其解析為該引用的上一個提交。 假設(shè)你的提交歷史是:
$ git log --pretty=format:'%h %s' --graph --all
* 734713b fixed refs handling, added gc auto, updated tests
* d921970 Merge commit 'phedders/rdocs'
|\
| * 35cfb2b Some rdoc changes
* | 1c002dd added some blame and merge stuff
|/ * 1c36188 ignore *.gem
* 9b29157 add open3_detach to gemspec file list
你可以使用 HEAD^ 來查看上一個提交,也就是 “HEAD 的父提交”:
$ git show HEAD^
commit d921970aadf03b3cf0e71becdaab3147ba71cdef
$ git show HEAD~3
commit 1c3618887afb5fbcbea25b7c013f4e2114448b8d
$ git show HEAD^^^
commit 1c3618887afb5fbcbea25b7c013f4e2114448b8d
另一種指明祖先提交的方法是~。 同樣是指向第一父提交,因此 HEAD~
和 HEAD^
是等價的。而區(qū)別在于你在后面加數(shù)字的時候。 HEAD~2
代表 “第一父提交的第一父提交”,也就是 “祖父提交”。
在 ^
后面添加一個數(shù)字—例如d921970^2
代表 d921970 的第二父提交
這個語法只適用于合并 (merge)的提交,因為合并提交會有多個父提交。第一父提交是你合并時所在分支,而第二父提交是你所合并的分支。