
高級(jí)技巧
下面介紹幾個(gè) Git 中非常強(qiáng)大的命令,借助這些命令我們可以完成一些非常有用的操作。
git reflog
Git 對(duì)于本地倉(cāng)庫(kù)的操作都進(jìn)行了日志記錄,日志文件存儲(chǔ)在目錄.git/logs:
$ tree .git/logs
.git/logs
├── HEAD # HEAD 指針變更記錄
└── refs
├── heads # 本地分支記錄
│ ├── master
├── remotes # 遠(yuǎn)程分支記錄
│ └── origin
│ └── HEAD
│ └── master
└── stash # stash 記錄
從 Git 的日志目錄中可以看出,Git 主要對(duì)一些引用文件進(jìn)行了日志記錄,包括HEAD指針、分支和儲(chǔ)藏更改記錄。當(dāng)本地更改了這些指針指向時(shí),該操作就會(huì)被記錄到對(duì)應(yīng)的日志文件中。比如,切換分支會(huì)導(dǎo)致HEAD指針指向變化,則該操作會(huì)被記錄到日志文件.git/logs/HEAD中,比如,master分支添加或刪除commit時(shí)會(huì)同時(shí)導(dǎo)致master指針和HEAD指針指向變化,則該操作會(huì)同時(shí)被記錄到.git/logs/refs/heads/master和.git/logs/HEAD日志文件中...
由于日志記錄了所有指針、分支和儲(chǔ)藏的變更操作,因此,本地倉(cāng)庫(kù)的所有提交都不會(huì)丟失,可隨時(shí)從這些日志文件中索取得回。比如,假設(shè)我們?cè)诋?dāng)前分支指向了回退操作,那么當(dāng)前分支的日志記錄git log會(huì)丟失被回退的那些提交,如果想找回這些提交,搜索該分支日志文件即可。當(dāng)然,實(shí)際操作中,我們無(wú)需手動(dòng)查詢這些日志文件(雖然這些日志文件都是文本文件),因?yàn)?Git 提供了相關(guān)命令可以讓我們直接查詢相應(yīng)日志記錄,該命令為git reflog,其具體語(yǔ)法如下所示:
# 查詢?nèi)罩?git reflog [show] [<ref>]
# 刪除過(guò)期日志
git reflog expire [<ref>]
# 刪除日志條目
git reflog delete ref@{specifier}
# 檢測(cè)引用是否存在日志文件
git reflog exists <ref>
大多數(shù)情況下我們都是使用git reflog [show]來(lái)查看日志記錄,因此下面我們具體介紹常用的一些查詢操作:
-
查詢
HEAD指針變更操作:查詢HEAD指針變更日志,如下所示:# 日志呈棧結(jié)構(gòu),即最近的操作顯示在前(棧頂),初始操作顯示在最后面(棧底) $ git reflog show HEAD 5279645 (HEAD -> master, dev) HEAD@{0}: checkout: moving from dev to master 5279645 (HEAD -> master, dev) HEAD@{1}: reset: moving to HEAD db8348b HEAD@{4}: commit: feat: 222 5279645 (HEAD -> master, dev) HEAD@{5}: commit (initial): feat: 111注:由于查詢
HEAD指針變更日志是最常使用的操作,因此,默認(rèn)情況下,執(zhí)行不帶參數(shù)的git reflog就相當(dāng)于執(zhí)行git reflog show HEAD。 -
查詢分支提交變更:分支增加提交或回退提交等操作都會(huì)被記錄到日志中,因此我們查詢分支的變更記錄:
# 查詢 master 指針變更記錄 $ git reflog show master 5279645 (HEAD -> master, dev) master@{0}: reset: moving to HEAD~1 # 回退操作 db8348b master@{1}: commit: feat: 222 # 增加提交 5279645 (HEAD -> master, dev) master@{2}: commit (initial): feat: 111 # 初始提交 -
查詢儲(chǔ)藏變更操作:當(dāng)我們執(zhí)行
git stash命令時(shí),stash指針會(huì)被更改,因此這些操作也都會(huì)被記錄到日志中。查詢儲(chǔ)藏變更命令如下:$ git reflog show stash 0ba206e (refs/stash) stash@{0}: WIP on master: 5279645 feat: 111 54044f5 stash@{1}: WIP on dev: 5279645 feat: 111 -
日志時(shí)間過(guò)濾:日志文件每條記錄都帶有一個(gè)時(shí)間戳,因此我們可以對(duì)日志記錄進(jìn)行時(shí)間過(guò)濾,只顯示某個(gè)時(shí)間范圍內(nèi)的記錄。
Git 提供了一些時(shí)間標(biāo)識(shí)符方便我們進(jìn)行時(shí)間過(guò)濾,常見(jiàn)的時(shí)間標(biāo)識(shí)符如下表所示:
時(shí)間標(biāo)識(shí)符 含義 1.minute.ago1分鐘之前 1.hour.ago1小時(shí)之前 yesterday昨天 1.week.ago一周之前 1.month.ago一月之前 1.year.ago1小時(shí)之前 2020-05-18.09:00:002020-05-18 09:00:00 注:時(shí)間標(biāo)識(shí)符也支持復(fù)數(shù)形式,比如:
5.hours.ago表示 5 小時(shí)之前。
注:時(shí)間標(biāo)識(shí)符支持聯(lián)合使用,比如:1.day.2.hours.ago表示 1 天 2 小時(shí)之前。舉個(gè)例子:比如查詢
master分支 1 小時(shí) 17 分鐘之前的操作:$ git reflog master@{1.hour.17.minutes.ago} 5279645 (HEAD -> master, dev) master@{Sat Jan 9 23:18:05 2021 +0800}: commit (initial): feat: 111
最后,日志文件只存在于本地倉(cāng)庫(kù)中,不會(huì)上傳到遠(yuǎn)程倉(cāng)庫(kù),并且,日志條目默認(rèn)只有 90 天有效期限,過(guò)期條目可能會(huì)被自動(dòng)刪除(因?yàn)槟承┟顣?huì)觸發(fā)git gc操作),也可以通過(guò)手動(dòng)執(zhí)行git gc或git reflog expire刪除過(guò)期條目。如果想更改日志條目有效期限,可以設(shè)置gc.reflogExpire配置或執(zhí)行git reflog expire --expire=<time>手動(dòng)指定時(shí)間。比如,下面的命令將刪除 1 分鐘之前的所有日志條目:
# --all 表示清除所有日志,也可以指定清除特定日志文件,比如 HEAD、master...
$ git reflog expire --expire=1.minute.ago --all
git rebase
git rebase是 Git 提供的一個(gè)具備非常強(qiáng)大功能的命令,rebase中文翻譯為『變基』,見(jiàn)名知意,即git rebase可以更改基準(zhǔn)點(diǎn),比如,一個(gè)分支從另一個(gè)分支某個(gè)提交上創(chuàng)建,使用git rebase可以更改該分支基準(zhǔn)點(diǎn),使新分支從另一個(gè)提交上創(chuàng)建延伸,擴(kuò)展來(lái)說(shuō),git rebase不僅僅可以用于修改分支基準(zhǔn)點(diǎn),它還具備修改分支提交歷史記錄,比如刪除、合并、更換提交...
git rebase的具體語(yǔ)法如下所示:
git rebase [-i | --interactive] [<options>] [--exec <cmd>] [--onto <newbase> | --keep-base] [<upstream> [<branch>]]
git rebase [-i | --interactive] [<options>] [--exec <cmd>] [--onto <newbase>] --root [<branch>]
git rebase (--continue | --skip | --abort | --quit | --edit-todo | --show-current-patch)
上述命令可以簡(jiǎn)化為如下格式:
# 將 topic_branch 變基到 base_branch 的最新提交,
# 當(dāng)未指定 topic_branch 時(shí),則默認(rèn)變基當(dāng)前分支
git rebase [base_branch] [topic_branch]
# 交互式變基
git rebase -i [branch]
# 變基(繼續(xù) | 跳過(guò) | 停止 | 退出 | 繼續(xù)編輯 | 顯示當(dāng)前差異包)
git rebase (--continue | --skip | --abort | --quit | --edit-todo | --show-current-patch)
下面我們主要對(duì)git rebase的兩個(gè)主要功能進(jìn)行講解:
-
分支合并:可以使用
rebase模式進(jìn)行分支合并。默認(rèn)情況下,Git 使用
merge模式進(jìn)行分支合并,該方式會(huì)基于當(dāng)前合并的兩個(gè)分支最新提交以及兩者之間的公共提交做一個(gè)簡(jiǎn)單三路合并,生成一個(gè)合并提交點(diǎn),這種情況下,當(dāng)我們查看歷史記錄時(shí),會(huì)看到有向無(wú)環(huán)圖存在,且各分支結(jié)點(diǎn)以提交時(shí)間順序進(jìn)行排列。比如對(duì)于如下示意圖:separate branch to be merged如果我們想
master分支合并dev分支,執(zhí)行如下命令:$ git switch master Switched to branch 'master' $ git merge dev -m 'C5: merge branch dev' Merge made by the 'recursive' strategy. 3.txt | 1 + 1 file changed, 1 insertion(+) create mode 100644 3.txt $ git log --format='%h - %cd : %s' --date=format:'%Y-%m-%d %H:%M:%S' --graph * 1069fb9 - 2021-01-11 01:10:01 : C5: merge branch dev |\ | * 7ee751d - 2021-01-11 01:09:28 : dev: C3 * | 3c29c30 - 2021-01-11 01:09:50 : master: C4 |/ * 44b275c - 2021-01-11 01:08:47 : master: C2 * 5454078 - 2021-01-11 01:08:29 : master: C1注:如果我們使用
--graph進(jìn)行查看,可以看到,不同分支之間的提交不一定按時(shí)間順序進(jìn)行排列(比如上述代碼中C3早于C4,但卻排在C4后面),這是因?yàn)榉种创蚱剑燥@示效果有點(diǎn)異常,如果不使用--graph,則可以以時(shí)間順序正常排列各個(gè)提交。此時(shí)的示意圖如下所示:
git merge以上就是
merge模式合并結(jié)果,接下來(lái)我們來(lái)看下rebase模式合并分支效果:-
首先現(xiàn)在我們將倉(cāng)庫(kù)回退到未合并之前的狀態(tài):
$ git reset --hard HEAD~1 HEAD is now at 3c29c30 master: C4此時(shí)倉(cāng)庫(kù)的示意圖如下所示:
separate branch to be rebase -
然后將
dev變基,將基準(zhǔn)點(diǎn)移動(dòng)到master分支最新提交點(diǎn)上:$ git switch dev Switched to branch 'dev' # 變基前的分支提交記錄 $ git log --format='%h - %cd : %s' --date=format:'%Y-%m-%d %H:%M:%S' 7ee751d - 2021-01-11 01:09:28 : dev: C3 44b275c - 2021-01-11 01:08:47 : master: C2 5454078 - 2021-01-11 01:08:29 : master: C1 # 變基 $ git rebase master Successfully rebased and updated refs/heads/dev. # 變基后的分支提交記錄 $ git log --format='%h - %cd : %s' --date=format:'%Y-%m-%d %H:%M:%S' ca22a79 - 2021-01-11 01:12:55 : dev: C3 # 注意 C3 的哈希值被更改了 3c29c30 - 2021-01-11 01:09:50 : master: C4 44b275c - 2021-01-11 01:08:47 : master: C2 5454078 - 2021-01-11 01:08:29 : master: C1注:
git rebase時(shí)可能會(huì)存在沖突,此時(shí)解決完沖突后,只需執(zhí)行git add .,然后執(zhí)行git rebase --continue繼續(xù)變基過(guò)程即可。通過(guò)查看變基前和變基后的
dev分支提交歷史,我們可以看到,變基前,dev分支是基于C2提交點(diǎn)的,而變基后,dev分支是基于C4提交點(diǎn)的,也就是git rebase master會(huì)將dev分支基準(zhǔn)點(diǎn)移動(dòng)到master分支最新提交處。此時(shí)倉(cāng)庫(kù)的示意圖如下所示:
git rebase這里簡(jiǎn)單介紹下
git rebase的實(shí)現(xiàn)原理:以本例子進(jìn)行闡述,當(dāng)執(zhí)行git rebase master時(shí),Git 首先會(huì)找到這兩個(gè)分支(即dev和master分支)的最近共同祖先提交C2,然后將當(dāng)前分支(即dev分支)超前共同祖先C2的所有提交一一打散并提取出相應(yīng)的修改保存為臨時(shí)patch文件,文件存儲(chǔ)在.git/rebase目錄下;然后將當(dāng)前分支指向master分支最新提交點(diǎn)C4上;最后,依序?qū)?code>patch文件應(yīng)用到dev分支上,這個(gè)步驟相當(dāng)于重新播放之前dev分支的各個(gè)提交點(diǎn)進(jìn)行的修改操作,這樣就保證了生成新的提交的內(nèi)容是一致的(假設(shè)不存在沖突)。 -
此時(shí)
master分支就可以合并dev分支:# 切換到 master 分支 $ git switch master Switched to branch 'master' # 合并 dev 分支 $ git merge dev -m 'C5: merge branch dev with rebase' Updating 3c29c30..ca22a79 Fast-forward (no commit created; -m option ignored) # 采用 Fast-forward 模式合并 3.txt | 1 + 1 file changed, 1 insertion(+) create mode 100644 3.txt # 查看提交歷史,沒(méi)有分支合并信息 $ git log --oneline --graph * ca22a79 (HEAD -> master, dev) dev: C3 * 3c29c30 master: C4 * 44b275c master: C2 * 5454078 master: C1由于變基后,
dev分支和master分支位于同一條時(shí)間線上,因此,git merge默認(rèn)采用Fast-forward模式合并分支,這樣分支合并信息就被消除了,提交歷史記錄呈現(xiàn)一條線,非常簡(jiǎn)潔。注:前面我們說(shuō)過(guò),盡量禁用
Fast-forward模式,以保留合并分支信息,而rebase模式卻是打平分支,消除分支合并信息,與我們的建議截然相反。其實(shí),分支合并需要依據(jù)具體場(chǎng)景進(jìn)行選擇,當(dāng)我們?cè)诒镜剡M(jìn)行開(kāi)發(fā)時(shí),我們最好保留自己的分支合并信息,而在協(xié)同工作時(shí),如果我們直接git pull origin master,master分支就會(huì)前進(jìn)(假設(shè)拉取到新提交),這樣當(dāng)我們合并分支到master時(shí),其他人的提交就交織到我們的分支中,默認(rèn)以merge模式生成一個(gè)合并提交,通常來(lái)說(shuō),我們不希望合并其他人的提交,因?yàn)檫@樣會(huì)污染我們的分支,并且隨著合并次數(shù)增多,提交歷史會(huì)非常混亂,在這種情況下,采用rebase模式進(jìn)行分支合并就是一個(gè)不錯(cuò)的選擇。具體來(lái)說(shuō),
rebase模式常用于協(xié)同開(kāi)發(fā),常見(jiàn)的場(chǎng)景有如下兩種:變基本地私有分支:當(dāng)我們基于
master分支創(chuàng)建一個(gè)私有分支,并做了一些提交后,此時(shí)通常會(huì)先執(zhí)行git pull origin master拉取新提交,這樣本地master分支就前進(jìn)了,然后對(duì)私有分支執(zhí)行git rebase master,將私有分支變基到master分支最新提交點(diǎn)處,最后再合并到master分支,這樣就能消除其他人的提交對(duì)我們分支的污染,提交記錄呈一條線,沒(méi)有分叉,并且在一個(gè)區(qū)間內(nèi)都是我們自己的提交,不會(huì)在中間夾雜其他人的提交,這樣歷史記錄就非常清晰。-
變基追蹤分支:當(dāng)我們對(duì)本地追蹤分支修改并進(jìn)行了多個(gè)提交后,如果要這些提交上傳到遠(yuǎn)程倉(cāng)庫(kù),首先都會(huì)
git pull拉取遠(yuǎn)程倉(cāng)庫(kù)更新,但這樣做遠(yuǎn)程倉(cāng)庫(kù)該分支的新提交(假設(shè)成功拉取到新提交)需要與我們的提交進(jìn)行一個(gè)merge操作,如下圖所示:track branch to be merged當(dāng)我們執(zhí)行
git pull時(shí),上述示意圖中,本地追蹤分支dev就會(huì)與遠(yuǎn)程分支origin/dev進(jìn)行一個(gè)分支合并,生成一個(gè)新的合并提交,如下圖所示:track branch merged我們并不想讓自己的提交與其他的提交交織在一起,因?yàn)檫@樣相當(dāng)于自己的提交被污染了,因此,對(duì)于追蹤分支的上傳,推薦使用
git pull --rebase方式拉取更新,這種方式相當(dāng)于執(zhí)行git fetch && git rebase origin/dev,Git 會(huì)將我們的提交變基到遠(yuǎn)程分支origin/dev的最新提交點(diǎn),如下圖所示:track branch rebased這樣,所有人的提交都不會(huì)互相污染,分支提交歷史呈一條線展示,且每個(gè)人的多個(gè)提交都集中在一個(gè)連續(xù)區(qū)間內(nèi),方便查閱。
注:變基追蹤分支指的是更改本地已提交但未上傳到服務(wù)器的提交,千萬(wàn)不要變基已存在于服務(wù)器上的分支提交,因?yàn)檫@相當(dāng)于修改了遠(yuǎn)程倉(cāng)庫(kù)分支歷史,會(huì)導(dǎo)致協(xié)同開(kāi)發(fā)產(chǎn)生問(wèn)題。
注:如果項(xiàng)目比較大,協(xié)同開(kāi)發(fā)的人比較多,這種情況下,可能每天遠(yuǎn)程分支都有很多次新提交,此時(shí)如果使用變基追蹤分支,可能存在過(guò)多沖突,解決這些沖突會(huì)非常耗時(shí)耗力,這種情況下,也許直接合并會(huì)更加簡(jiǎn)單。
-
-
交互式變基:交互式變基主要的作用就是用于更改分支提交歷史記錄。其語(yǔ)法如下所示:
# 編輯 commit 之后的所有提交(不包含 commit) git rebase { -i | --interactive } <commit>交互式變基提供了一系列操作可以讓我們修改分支提交歷史記錄,具體操作如下表所示:
操作 描述 pick保留該提交 reword修改該提交信息 edit編輯該提交 squash將該提交合并到前一個(gè)提交中(即合并到當(dāng)前提交的上一個(gè)舊提交中) fixup與 squash作用一致,但直接丟棄該提交信息exec(該行剩余部分)使用 shell 運(yùn)行命令 break到該提交處暫停變基(后續(xù)使用 git rebase --contine從此處繼續(xù)開(kāi)始變基)drop移除該提交 label為當(dāng)前 HEAD打一個(gè)標(biāo)記reset重置 HEAD到該標(biāo)記merge合并提交 git rebase -i <commit>需要指定一個(gè)提交commit,然后會(huì)彈出vi編輯模式以提交時(shí)間順序展示該commit之后的所有提交(即展示比該commit新的提交),比如,倒數(shù)第三個(gè)提交可以使用HEAD~2表示,則命令git rebase -i HEAD~2會(huì)展示最新的兩個(gè)提交,不包含HEAD~2。在編輯模式中,可以移動(dòng)提交行來(lái)更改提交順序,也可以刪除某個(gè)提交行,這樣改提交就會(huì)從提交歷史中進(jìn)行移除(但是不支持刪除全部提交,此時(shí) Git 會(huì)自動(dòng)停止變基)。下面列舉一些常用的分支歷史記錄修改操作:
-
reword:該操作可以修改提交信息。舉個(gè)例子:比如本地倉(cāng)庫(kù)存在如下提交記錄:
$ git log --oneline a75e892 (HEAD -> master) C3 8837671 c2 c830b70 C1可以看到,
8837671提交的信息使用了小寫(xiě)字母,如果我們希望將其修改為大寫(xiě),則可以如下進(jìn)行操作:# 首先啟動(dòng)交互式變基,展示前兩條提交 $ git rebase -i HEAD~2上面命令執(zhí)行完后,會(huì)彈出
vi編輯模式,其內(nèi)容如下所示:pick 8837671 c2 pick a75e892 C3此時(shí),我們將
c2對(duì)應(yīng)的提交8837671修改為reword,然后保存退出:reword 8837671 c2 # 修改提交信息 pick a75e892 C3 # 保留該提交此時(shí),另一個(gè)
vi編輯模式窗口會(huì)自動(dòng)彈出,我們可在此修改提交信息,此處我們將c2修改為C2,保存并退出。到此,我們就成功修改了
8837671的提交信息,如下所示:$ git log --oneline 2de4c14 (HEAD -> master) C3 35e3b4c C2 # 已成功修改 c830b70 C1 -
squash:該操作可以壓縮提交,也即將多個(gè)提交壓縮到前一個(gè)提交中。舉個(gè)例子:比如對(duì)于本地倉(cāng)庫(kù),其提交歷史記錄如下所示:
$ git log --oneline 7b8e084 (HEAD -> master) C4 2de4c14 C3 35e3b4c C2 c830b70 C1假設(shè)現(xiàn)在我們想將
C2和C3壓縮成一個(gè)提交,此時(shí)可以如下操作:# 啟動(dòng)交互式變基,展示 C1 之后的所有提交 $ git rebase -i c380b70此時(shí)會(huì)彈出編輯窗口,我們將
C3設(shè)置為squash,表示它會(huì)壓縮到C2中:pick 35e3b4c C2 # 保留 C2 squash 2de4c14 C3 # 壓縮 C3(壓縮到上一個(gè)提交,即 C2) pick 7b8e084 C4 # 保留 C4此時(shí)會(huì)彈出另一個(gè)窗口,該窗口同時(shí)包含
C2和C3的提交信息,我們可以手動(dòng)編輯壓縮合并生成的新提交信息:# 默認(rèn)采用上一個(gè)提交信息,這里我將其修改為如下 squash C2 and C3此時(shí),我們就完成了
C2和C3的合并,效果如下所示:$ git log --oneline cfed515 (HEAD -> master) C4 fb5e7f3 squash C2 and C3 # 合并成功 c830b70 C1 -
fixup:該操作也squash作用一樣,也是用于壓縮提交。但與squash不同的是,該操作會(huì)丟棄被壓縮提交(即被fixup標(biāo)注的提交)的提交信息。舉個(gè)例子:比如我們還是壓縮
C2和C3,但是直接將C3壓縮到C2:# 倉(cāng)庫(kù)初始狀態(tài) $ git log --oneline 3ba4ca0 (HEAD -> master) C4 31b3716 C3 b32f587 C2 c830b70 C1 # 啟動(dòng)變基 $ git rebase -i HEAD~3此時(shí)將
C3標(biāo)注為fixup,表示將C3直接壓入到上一個(gè)提交(即C2)中:pick b32f587 C2 # 保留 fixup 31b3716 C3 # 壓入到上一個(gè) pick 3ba4ca0 C4 # 保留由于是直接壓縮,因此直接就返回了,不會(huì)像
squash需要再?gòu)棾鲆粋€(gè)窗口合并提交信息,此時(shí)的效果如下所示:$ git log --oneline 1a32d3c (HEAD -> master) C4 fd46dfc C2 c830b70 C1 -
edit:該操作可以用于編輯提交。該操作具體的執(zhí)行邏輯為:當(dāng)提交被標(biāo)注為
edit時(shí),Git 會(huì)自動(dòng)變基到該提交上,然后我們就可以編輯該提交,比如增加、刪除一些內(nèi)容,然后使用git comit --amend修改此次提交,也可以在該提交上增添新提交,這樣就相當(dāng)于在之前的提交歷史記錄中插入一些新提交...舉個(gè)例子:除了重置、增加提交外,
edit操作也常常用來(lái)將提交切分為多個(gè)小提交,其實(shí)就是重置+增加提交的操作,比如,前面我們將倉(cāng)庫(kù)提交C2和C3壓縮到一起,如下所示:$ git log --oneline cfed515 (HEAD -> master) C4 fb5e7f3 squash C2 and C3 c830b70 C1現(xiàn)在如果我們想重新分離開(kāi)
C2和C3,則可以借助edit操作,如下所示:# 啟動(dòng)變基 $ git rebase -i HEAD~2我們將
fb5e7f3標(biāo)注為edit:edit fb5e7f3 squash C2 and C3 pick cfed515 C4保存退出編輯窗口后,Git 會(huì)自動(dòng)變基到
fb5e7f3中:$ git show HEAD --oneline -s fb5e7f3 (HEAD) squash C2 and C3我們將該提交重新拆分為兩個(gè)小提交
C2和C3:# 暫存區(qū)移除 C3 內(nèi)容 $ git rm --cached 3.txt rm '3.txt' # 拆分出 C2 內(nèi)容并重新提交 $ git commit --amend -m 'C2' [detached HEAD b32f587] C2 Date: Tue Jan 12 11:37:16 2021 +0800 1 file changed, 1 insertion(+) create mode 100644 2.txt # 添加 C3 內(nèi)容 $ git add 3.txt # 提交 C3 內(nèi)容 $ git commit -m 'C3' [detached HEAD 31b3716] C3 1 file changed, 1 insertion(+) create mode 100644 3.txt # 拆分完成后,繼續(xù)變基過(guò)程 $ git rebase --continue Successfully rebased and updated refs/heads/master.此時(shí),變基結(jié)束,我們成功將一個(gè)大提交拆分為多個(gè)小提交,如下所示:
$ git log --oneline 3ba4ca0 (HEAD -> master) C4 31b3716 C3 # 拆分 b32f587 C2 # 拆分 c830b70 C1 -
drop:如果要?jiǎng)h除某個(gè)提交,只需將該提交標(biāo)注為drop,或者直接在編輯窗口中刪除該提交即可。舉個(gè)例子:假設(shè)我們當(dāng)前倉(cāng)庫(kù)本地分支提交歷史記錄如下所示:
$ git log --oneline 45fcf79 (HEAD -> master) C3 6992e50 C2 c830b70 C1假設(shè)現(xiàn)在我們想要?jiǎng)h除
C2和C3這兩個(gè)提交,其步驟如下所示:# 啟動(dòng)交互式變基 $ git rebase -i HEAD~2此時(shí)彈出的編輯窗口內(nèi)容如下:
pick 6992e50 C2 pick 45fcf79 C3這里我們將
C2標(biāo)注為drop,然后直接刪除C3,同時(shí)驗(yàn)證這兩種刪除方法:drop 6992e50 C2保存并退出編輯窗口,此時(shí)查看倉(cāng)庫(kù)提交歷史記錄:
$ git log --oneline c830b70 (HEAD -> master) C1可以看到,我們已經(jīng)成功刪除了
C2和C3。 -
更換提交順序:更換提交順序只需在編輯窗口中直接更換提交的順序即可。
舉個(gè)例子:比如我們當(dāng)前本地倉(cāng)庫(kù)提交歷史記錄如下所示:
$ git log --oneline 45fcf79 (HEAD -> master) C3 6992e50 C2 c830b70 C1假設(shè)現(xiàn)在我們想更換
C2和C3的順序,其步驟如下:# 啟動(dòng)交互式變基 $ git rebase -i HEAD~2此時(shí)的編輯窗口如下所示:
pick 6992e50 C2 pick 45fcf79 C3要更換
C2和C3提交順序,只需在編輯窗口中更換兩者順序即可:pick 45fcf79 C3 pick 6992e50 C2如此我們就已經(jīng)完成提交順序更換,此時(shí)的提交歷史記錄如下所示:
$ git log --oneline 0486bd0 (HEAD -> master) C2 afa4a24 C3 c830b70 C1
-
git cherypick
在多分支工作流中,當(dāng)我們需要獲取另一個(gè)分支的所有變動(dòng)時(shí),通常采用的都是分支合并(git merge)策略,但是如果我們只對(duì)分支的一個(gè)或某幾個(gè)提交感興趣,那么也可以只摘取這幾個(gè)提交,將他們各自的修改一一應(yīng)用到我們當(dāng)前分支上,Git 中,具備提交摘取的命令為git cherry-pick,其具體語(yǔ)法如下所示:
# 支持摘取多個(gè) commit
git cherry-pick [<options>] <commit-ish>...
git cherry-pick (--continue | --skip | --abort | --quit)
git cherry-pick的本質(zhì)是摘取提交,將其修改應(yīng)用到當(dāng)前分支上。
git cherry-pick支持摘取一個(gè)或多個(gè)提交,每一個(gè)提交應(yīng)用到當(dāng)前分支,都會(huì)生成一個(gè)新的提交,該提價(jià)的修改完全與摘取的提交一致。其實(shí)git cherry-pick就是將摘取的提交在當(dāng)前分支上進(jìn)行重復(fù)播放。
git cherry-pick常用的命令選項(xiàng)有如下:
-n, --no-commit:應(yīng)用摘取提交時(shí),只進(jìn)行更新,但不提交。
注:默認(rèn)情況下,git cherry-pick在應(yīng)用摘取提交完成時(shí),會(huì)自動(dòng)進(jìn)行提交,生成一個(gè)新提交。-e, --edit:如果想更改提交信息,可以添加-e, --edit。
注:默認(rèn)情況下,git cherry-pick直接將摘取的提交信息作為新生成提交的提交信息。-m parent-number, --mainline parent-number:當(dāng)摘取的提交是一個(gè)合并提交時(shí),此時(shí)git cherry-pick無(wú)法區(qū)分應(yīng)當(dāng)使用哪個(gè)分支上進(jìn)行的修改,因此默認(rèn)失敗處理。此時(shí)必須指定一個(gè)parent-number,表示要摘取的變動(dòng)分支。parent-number取值由1開(kāi)始,具體查找方法可參考:高級(jí)技巧 - 查看合并提交的parent-number
舉個(gè)例子:假設(shè)本地倉(cāng)庫(kù)存在master和dev分支,現(xiàn)在突然發(fā)現(xiàn)線上版本出現(xiàn)漏洞,因此緊急從master分支上創(chuàng)建一個(gè)hotfix/add_file分支,然后做了兩個(gè)提交,如下圖所示:

簡(jiǎn)單起見(jiàn),每個(gè)提交都只是增加了相應(yīng)數(shù)字的文件。
當(dāng)漏洞修改完成后,就需要將hotfix/add_file分支合并到master分支中:
$ git switch master
Switched to branch 'master'
# 合并 hotfix 分支
$ git merge --no-ff hotfix/add_file -m 'fix: C6 => merge branch hotfix/add_file'
Merge made by the 'recursive' strategy.
4.txt | 1 +
5.txt | 1 +
2 files changed, 2 insertions(+)
create mode 100644 4.txt
create mode 100644 5.txt
此時(shí)的示意圖如下所示:

同樣的,hotfix/add_file分支上的修改也要合并到dev分支上,此時(shí),dev分支可以git cherry-pick或直接合并hotfix/add_file分支,也可以git cherry-pick主分支master上的合并提交C6,下面對(duì)這兩種方法分別進(jìn)行講解:
-
git cherry-pick摘取hotfix/add_file分支所有提交:這里我們不采用合并方式,而是將hotfix/add_file分支的所有提交,即C4和C5直接摘取到dev分支中:# 切換到 dev 分支 $ git switch dev Switched to branch 'dev' # 查看 hotfix/add_file 分支所有提交 $ git log --oneline hotfix/add_file 35a10e5 (hotfix/add_file) fix: C5 # 目標(biāo)提交 f79b3b1 fix: C4 # 目標(biāo)提交 d0b972f feat: C2 537ba3c feat: C1 # cherry-pick C4 和 C5 $ git cherry-pick f79b3b1 35a10e5 [dev 8bf6c28] fix: C4 # 應(yīng)用 C4 Date: Tue Jan 12 21:40:29 2021 +0800 1 file changed, 1 insertion(+) create mode 100644 4.txt [dev 6e3d3ef] fix: C5 # 應(yīng)用 C5 Date: Tue Jan 12 21:40:46 2021 +0800 1 file changed, 1 insertion(+) create mode 100644 5.txt # 合并成功 $ git log --oneline 6e3d3ef (HEAD -> dev) fix: C5 8bf6c28 fix: C4 12b54a1 feat: C3 d0b972f feat: C2 537ba3c feat: C1從提交歷史中,我們已經(jīng)可以看到成功摘取
C4和C5到dev分支上了,此時(shí)的示意圖如下所示:cherrypick - dev pick hotfix -
cherry-pick合并提交:第二種方法是摘取合并提交,即摘取C6應(yīng)用到dev分支上。具體步驟如下:-
首先我們將
dev分支重置到C3提交:# 回退到 C3 $ git reset --hard 12b54a1 HEAD is now at 12b54a1 feat: C3 # 回退成功 $ git log --oneline 12b54a1 (HEAD -> dev) feat: C3 d0b972f feat: C2 537ba3c feat: C1此時(shí)的倉(cāng)庫(kù)示意圖如下所示:
cherrypick - reset dev -
此時(shí)
dev分支可以摘取C6,需要注意的是,由于C6是一個(gè)合并提交,因此需要指定摘取分支,對(duì)于C6而言,其是由master分支合并hotfix/add_file分支生成的合并提交,這里我們應(yīng)當(dāng)選擇摘取分支為hotfix/add_file:# 查找 C6 的哈希值 $ git log --oneline master | grep C6 02d311d fix: C6 => merge branch hotfix/add_file # 查看 C6 的 parent-number $ git cat-file -p 02d311d | grep -i parent parent d0b972f09705aaf330c59be6eedbd69a1e49ccbc # parent-number = 1 parent 35a10e51533ae17c42ecdf3ad9598334cdaeca08 # parent-number = 2 # 比對(duì) C6 和 parent_commit1 的差異,可以看到,parent_commit1 就是 hotfix/add_file, # 因此 parent-number = 1 $ git diff --stat d0b972f 02d311d 4.txt | 1 + 5.txt | 1 + 2 files changed, 2 insertions(+) # 比對(duì) C6 和 parent_commit2 的差異(此步可忽略,因?yàn)樯弦徊揭颜页?parent-number) $ git diff --stat 35a10e5 02d311d # 摘取 parent-number = 1 的提交 $ git cherry-pick -m 1 02d311d [dev fdfc2c9] fix: C6 => merge branch hotfix/add_file Date: Tue Jan 12 21:55:29 2021 +0800 2 files changed, 2 insertions(+) create mode 100644 4.txt create mode 100644 5.txt # 查看摘取合并結(jié)果 $ git log --oneline fdfc2c9 (HEAD -> dev) fix: C6 => merge branch hotfix/add_file 12b54a1 feat: C3 d0b972f feat: C2 537ba3c feat: C1可以看到,我們成功將
C6的修改應(yīng)用到了dev分支,此時(shí)的示意圖如下所示:cherrypick - pick merge commit注:可以看到,
git cherry-pick合并提交的操作還是相對(duì)麻煩的,建議盡量避免對(duì)合并提交進(jìn)行摘取。
-
git bisect
git bisect命令可以讓我們很方便快速查找到出現(xiàn) bug 的提交,它的原理是對(duì)給定范圍的提交進(jìn)行二分查找,由用戶判斷當(dāng)前提交是否存在 bug,依次迭代不斷縮小規(guī)模進(jìn)行二分查找,這樣我們就可以很快從一個(gè)大范圍提交區(qū)間找到引入 bug 的那個(gè)提交。
git bisect命令的具體語(yǔ)法如下所示:
# 啟動(dòng)二分查找
git bisect start [<paths>...]
# 當(dāng)前提交存在 bug
git bisect bad [<rev>]
# 當(dāng)前提價(jià)良好(即不存在 bug)
git bisect good [<rev>...]
# 退出二分查找
git bisect reset [<commit>]
git bisect terms [--term-good | --term-bad]
git bisect skip [(<rev>|<range>)...]
git bisect (visualize|view)
git bisect replay <logfile>
git bisect log
git bisect run <cmd>...
git bisect help
可以看到,git bisect攜帶了很多的子命令選項(xiàng),但是通常我們只會(huì)使用git bisect { start | good | bad | reset }這四個(gè)子命令來(lái)進(jìn)行二分查找。其中:
-
git bisect start:該命令會(huì)啟動(dòng)二分查找過(guò)程。其具體語(yǔ)法如下所示:git bisect start [end_point] [start_point]其中,
end_point表示最近的提交,start_point表示最早之前的提交,如果兩者都指定了,那么二分查找第一個(gè)提交就是start_point和end_point的中間提交,如果未指定start_point和end_point,則進(jìn)入二分搜索時(shí),還需手動(dòng)使用git bisect bad <commit>和git bisect good <commit>手動(dòng)指定end_point和start_point。 -
git bisect good:執(zhí)行該命令會(huì)將當(dāng)前提交設(shè)置為良好狀態(tài),即表示當(dāng)前提交不存在 bug。其語(yǔ)法如下所示:git bisect good [<rev>...]我們也可以在啟動(dòng)二分查找后,手動(dòng)指定一個(gè)提交設(shè)置為良好狀態(tài)(比如
git bisect good 31af8d,表示提交31af8d未引入 bug),這樣可以人為縮短搜索范圍。 -
git bisect bad:執(zhí)行該命令會(huì)將當(dāng)前提交設(shè)置為出錯(cuò)狀態(tài),即表示當(dāng)前提交存在 bug。其語(yǔ)法如下所示:git bisect bad [<rev>]我們也可以在啟動(dòng)二分查找后,手動(dòng)指定一個(gè)提交設(shè)置為出錯(cuò)狀態(tài)(比如
git bisect bad 31af8d,表示提交31af8d存在 bug),這樣可以人為縮短搜索范圍。 -
git bisect reset:該命令會(huì)結(jié)束二分查找過(guò)程。其語(yǔ)法如下所示:git bisect reset [<commit>]默認(rèn)情況下,該命令會(huì)退出二分查找過(guò)程,然后回到先前的提交,即執(zhí)行
git bisect start時(shí)的提交。如果想退出時(shí)回到其他提交,直接在后面添加目標(biāo)提交<commit>即可。
舉個(gè)例子:假設(shè)我們當(dāng)前倉(cāng)庫(kù)存在 5 個(gè)提交歷史,為了方便演示,我們假設(shè)某個(gè)提交刪除了ReadMe.md文件,現(xiàn)在想找出刪除該文件的提交,操作過(guò)程如下:
# 所有提交
$ git log --oneline
15c3cdb (HEAD -> master) feat: 555
7b93852 feat: 444
c40d88f feat: 333
b9ce941 docs: add ReadMe.md
58bdc1d feat: 111
# HEAD 58bdc1d 表示對(duì)所有提交進(jìn)行二分查找,這里也可以忽略不寫(xiě)
$ git bisect start HEAD 58bdc1d
Bisecting: 1 revision left to test after this (roughly 1 step)
[c40d88fc907538e7392509c7b221ebf78ae42516] feat: 333 # 表示當(dāng)前處于 c40d88f,也就第三個(gè)提交
# 查看當(dāng)前提交,可以看到,確實(shí)是處于第三個(gè)提交
$ git show HEAD --oneline --stat -s
c40d88f (HEAD) feat: 333
# 當(dāng)前提交存在 ReadMe.md
$ ls
1.txt 3.txt ReadMe.md
# 由于當(dāng)前提交存在 ReadMe.md,故設(shè)置為良好狀態(tài)
$ git bisect good
Bisecting: 0 revisions left to test after this (roughly 0 steps)
[7b9385209361db20ce6b1d4e1e7c81d999ae84b5] feat: 444 # 此時(shí)處于 7b93852,即第四個(gè)提交
# 當(dāng)前提交不存在 ReadMe.md
$ ls
1.txt 3.txt 4.txt
# 由于當(dāng)前提交不存在 ReadMe.md,故將其設(shè)置為 bad 狀態(tài)
$ git bisect bad
7b9385209361db20ce6b1d4e1e7c81d999ae84b5 is the first bad commit # 這里表示當(dāng)前提交就是第一個(gè)引入 bug 的提交
commit 7b9385209361db20ce6b1d4e1e7c81d999ae84b5
Author: Why8n <Why8n@gmail.com>
Date: Sun Jan 10 18:18:08 2021 +0800
feat: 444
4.txt | 1 +
ReadMe.md | 1 -
2 files changed, 1 insertion(+), 1 deletion(-)
create mode 100644 4.txt
delete mode 100644 ReadMe.md # 刪除了文件 ReadMe.md
# 找到引入 bug 的提交后,就可以退出二分查找了
$ git bisect reset
Previous HEAD position was 7b93852 feat: 444
Switched to branch 'master'
# 因?yàn)閯h除了 ReadMe.md 的提交還進(jìn)行了其他修改,因此這里不能直接使用 git revert
# 但是既然找到了刪除 ReadMe.md 的提交,那么我們只需從該提交之前的提交獲取 ReadMe.md 文件,進(jìn)行恢復(fù)即可
# 7b93852 提交刪除了 ReadMe.md,該提交之前的提交為 c40d88f
$ git log --oneline | grep 7b93852 -A 1
7b93852 feat: 444
c40d88f feat: 333
# 查詢 c40d88f 所有文件,獲取 ReadMe.md 的對(duì)象文件
$ git ls-tree c40d88f
100644 blob 58c9bdf9d017fcd178dc8c073cbfcbb7ff240d6c 1.txt
100644 blob 55bd0ac4c42e46cd751eb7405e12a35e61425550 3.txt
100644 blob c200906efd24ec5e783bee7f23b5d7c941b0c12c ReadMe.md # 目標(biāo)文件
# 將目標(biāo)文件內(nèi)容寫(xiě)到當(dāng)前工作目錄
$ git cat-file -p c20090 > ReadMe.md
以上,就是git bisect的整個(gè)基本操作過(guò)程。
查看合并提交的parent-number
前面我們介紹過(guò)的git revert和git cherry-pick等命令,在遇到合并提交時(shí),都需要指定主線分支,也即parent-number。Git 似乎并沒(méi)有直接提供查詢parent-number的命令,因此我們只能手動(dòng)進(jìn)行查找。
我們以一個(gè)例子來(lái)驅(qū)動(dòng)講解查找合并提交的parent-number,假設(shè)現(xiàn)在我們有一個(gè)合并提交02d311d,可以通過(guò)如下命令查看其內(nèi)容:
# 法一
git show --pretty=raw <merge_commit>
# 法二
git cat-file -p <merge_commit>
比如,查看合并提交02d311d,結(jié)果如下:
$ git show --pretty=raw 02d311d
commit 02d311d5c9bb0989c1285e068fc3c2a4de02b027
tree 03c45eebbff1c1fa9f9152d9800d4e65f4602052
parent d0b972f09705aaf330c59be6eedbd69a1e49ccbc # parent1_commit,其 parent-number = 1
parent 35a10e51533ae17c42ecdf3ad9598334cdaeca08 # parent2_commit,其 parent-number = 2
author Why8n <Why8n@gmail.com> 1610459729 +0800
committer Why8n <Why8n@gmail.com> 1610459729 +0800
fix: C6 => merge branch hotfix/add_file
該命令會(huì)顯示合并提交的父提交,第一個(gè)parent1_commit的parent-number就是1,第二個(gè)parent2_commit的paretn-number就是2,依次類推...
然后,我們只需一一比對(duì)parent_commit與合并提交之間的差別,就可以判斷得出應(yīng)當(dāng)使用哪個(gè)parent_commit了:
git diff --stat <parent_commit> <merge_commit>
更多詳細(xì)內(nèi)容,請(qǐng)參考:Git cherry-pick syntax and merge branches
其他
git blame
如果我們想查看文件每一行對(duì)應(yīng)的版本以及最后修改的作者時(shí),則可以使用git blame命令。其具體語(yǔ)法如下所示:
git blame [<options>] [<rev-opts>] [<rev>] [--] <file>
下面列舉幾種常用的git blame操作:
-
指定顯示行數(shù):可以通過(guò)添加
-L <start>,<end>選項(xiàng)來(lái)指定只顯示文件start到end之間的行:# 只顯示 .gitignnore 文件第 1 到 3 行的內(nèi)容 $ git blame -L 1,3 .gitignore aa658574bfc (Josh Steadmon 2019-01-15 14:25:50 -0800 1) /fuzz-commit-grap 5e472150800 (Josh Steadmon 2018-10-12 17:58:40 -0700 2) /fuzz_corpora 5e472150800 (Josh Steadmon 2018-10-12 17:58:40 -0700 3) /fuzz-pack-header # 只顯示 .gitignnore 文件第 3 行修改信息 $ git blame -L 3,+1 HEAD~1 .gitignore 5e472150800 (Josh Steadmon 2018-10-12 17:58:40 -0700 3) /fuzz-pack-headers -
顯示特定版本的文件修改:
git blame默認(rèn)只顯示文件最后一個(gè)版本的修改(當(dāng)然文件中每一行內(nèi)容都可能處于不同的版本中),如果想顯示某個(gè)提交該文件的信息時(shí),可以指定該提交版本:# 顯示 .gitignore 文件倒數(shù)第二次提交的第一行修改信息 $ git blame -L 1,+1 HEAD~1 .gitignore aa658574bfc (Josh Steadmon 2019-01-15 14:25:50 -0800 1) /fuzz-commit-graph
git gc
前文說(shuō)過(guò),Git 本質(zhì)是一個(gè)全量快照的文件系統(tǒng),因此當(dāng)我們暫存次數(shù)過(guò)多時(shí),Git 對(duì)象數(shù)據(jù)庫(kù)會(huì)存儲(chǔ)很多對(duì)象文件,有些對(duì)象文件實(shí)際上沒(méi)有被任何提交對(duì)象直接或間接進(jìn)行引用,這些對(duì)象稱為『松散對(duì)象(loose objects)』。
我們使用命令git gc來(lái)垃圾回收這些松散對(duì)象,減小倉(cāng)庫(kù)大小。簡(jiǎn)單來(lái)說(shuō),當(dāng)運(yùn)行git gc時(shí),Git 會(huì)收集所有松散對(duì)象并將它們存入一個(gè)packfile文件中,并將多個(gè)packfile文件合并成一個(gè)大的packfile文件,然后移除不被任何提交引用且超過(guò)一定期限的對(duì)象文件。除此之外,git gc還會(huì)將所有的引用文件(即.git/refs)打包到另一個(gè)單獨(dú)的文件中。
更多 Git 垃圾回收相關(guān)內(nèi)容,可參考如下文章:
分頁(yè)器
Git 中幾乎所有命令都提供了分頁(yè)器(Pager)功能,當(dāng)命令輸出超出一頁(yè)時(shí),會(huì)自動(dòng)啟動(dòng)分頁(yè)器。
分頁(yè)器的交互方式并不人性化,可通過(guò)如下幾種方法進(jìn)行分頁(yè):
-
--no-pager:手動(dòng)為 Git 添加--no-pager選項(xiàng),可禁止啟動(dòng)分頁(yè)器:$ git --no-pager log -n 10 --oneline -
全局配置分頁(yè)器,使用
less命令進(jìn)行翻頁(yè):$ git config --global core.pager "less -FRSX"
別名
可以通過(guò)git config alias來(lái)為其他命令設(shè)置一個(gè)簡(jiǎn)短的別名,方便使用,比如:
$ git config --global alias.br branch
$ git br # ==> 擴(kuò)展為:git branch
上述命令為branch設(shè)置了一個(gè)別名br,此時(shí)使用git br就相當(dāng)于使用git branch。
注:Git 的別名就是一個(gè)字符串,使用時(shí)會(huì)自動(dòng)擴(kuò)展為設(shè)置的內(nèi)容,自動(dòng)拼接到git指令后面。
Git 也可以為外部命令指定別名,只需在命令前面添加!即可:
$ git config --global alias.ls '!ls -alrt'
$ git ls # ==> ls -alrt
舉個(gè)例子:這里我們?cè)O(shè)置一個(gè)別名(命令),來(lái)顯示本地倉(cāng)庫(kù)對(duì)象數(shù)據(jù)庫(kù)所有對(duì)象文件及其類型:
# 由于命令太長(zhǎng),我們選擇直接在全局配置文件中進(jìn)行修改,方便很多
$ git config --global --edit
然后在標(biāo)簽[alias]下設(shè)置如下內(nèi)容:
[alias]
; 注釋:sdo represent show data objects
sdo = "!find .git/objects -type f | awk -F '/' '{ hash=$3$4; cmd = sprintf(\"git cat-file -t %s\", hash); printf(\"%s\t\", hash); system(cmd); }'"
此時(shí)使用命令git sdo就可以顯示對(duì)象數(shù)據(jù)庫(kù)中所有的對(duì)象文件及其類型:
# sdo represents show data objects
$ git sdo
0767f3e206a0a431633b2063bbda680026c33f70 commit
10f86d6b803b8962653f16a9967a4578215dcb22 tree
778d49177a4b6da0e967ac3e9308076ad500e6e7 blob
git clean
如果需要移除工作區(qū)中未被追蹤的文件或文件夾,可以使用git clean命令,其語(yǔ)法如下所示:
git clean [-d] [-f] [-i] [-n] [-q] [-e <pattern>] [-x | -X] [--] [<paths>]...
其中,常用的選項(xiàng)有:
-n, --dry-run:表示只顯示將要被移除的文件/文件夾,而不進(jìn)行真正的刪除。-d:表示移除文件和文件夾。
注:默認(rèn)情況下,為了盡可能減少文件刪除,git clean不會(huì)刪除未被追蹤的文件夾。-f, --force:表示強(qiáng)制執(zhí)行刪除操作。
注:如果配置了選項(xiàng)clean.requireForce為false的話,git clean默認(rèn)不進(jìn)行刪除動(dòng)作,此時(shí)可通過(guò)添加-f選項(xiàng)真正執(zhí)行刪除操作。
版本及范圍表示法
大多數(shù) Git 命令都會(huì)攜帶一個(gè)revision(修訂版本)作為參數(shù),因此 Git 也內(nèi)置了一些版本及其范圍的簡(jiǎn)便引用方法,大致有如下:
-
版本指定:版本指定可引用一個(gè)提交或多個(gè)提交:
-
<sha1>:表示對(duì)象文件的哈希字符串。比如:dae86e1950b1277e545cee180551750029cfe735或dae86e。 -
<refspec>:表示符號(hào)引用。比如:master、heads/master和refs/heads/master都表示master分支,比如HEAD表示HEAD引用文件... -
@:一個(gè)單獨(dú)的@等同于HEAD。 -
<refspec>@{<date>}:表示指定時(shí)間段的引用。比如:master@{yesterday},HEAD@{5 minutes ago}。 -
<refspec>@{<number>}:表示引用refspec之前的第number個(gè)提交。比如:master@{0}等同于master,master@{1}為master分支第二個(gè)最新提交。 -
@{<number>}:與<refspec>@{<number>}功能一致,只是refspec為HEAD,即表示當(dāng)前分支的最新第number個(gè)提交。 -
<rev>^[n]:表示提交rev的前n個(gè)父提交,n表示字符^的重復(fù)個(gè)數(shù)。比如:a95fabe^表示提交a95fabe的前一個(gè)提交,HEAD^^表示HEAD的前第二個(gè)提交...
注:n也可以為數(shù)字,但只能為0和1。比如a95fabe^0等同于a95fabe,HEAD^1等同于HEAD^。 -
<rev>~<number>:表示提交rev的第number祖先提交。比如:a95fabe~0等同于a95fabe,a95fabe~1表示a95fabe的前一個(gè)提交,HEAD~5表示當(dāng)前分支的最新第 5 個(gè)提交。
注:<rev>~<number>也支持<rev>~<n>操作,比如:HEAD~等同于HEAD~1,HEAD~~等同于HEAD~2...
注:該模式也支持<rev>^{tree},表示獲取提交rev的樹(shù)對(duì)象。 -
<rev>:<path>:表示版本rev下的文件。比如:HEAD:1.txt表示當(dāng)前版本下的1.txt文件內(nèi)容,a95fabe:1.txt表示版本a95fabe下的1.txt文件內(nèi)容。
注:該模式可以讓我們很方便對(duì)不同版本的同一文件進(jìn)行比對(duì):# HEAD:1.txt 相對(duì)于 HEAD~1:1.txt 的文件差異 $ git diff HEAD~1:1.txt HEAD:1.txt
-
-
范圍指定:當(dāng)分支提交歷史記錄包含多個(gè)提交時(shí),可以指定提交范圍:
-
^<rev>:表示不包含rev`的提交。 -
<rev1>..<rev2>:表示包含rev2,但是不包含rev1,即(rev1,rev2]。 -
<rev1>...<rev2>:表示同時(shí)包含rev1和rev2,即[rev1,rev2]。
-
更多其他版本與版本范圍表示法,可參考:Git 版本及版本范圍表示法
顯示引用哈希值
可以通過(guò)git rev-parse查看引用或?qū)ο笪募V担?/p>
# 顯示 master 最新提交哈希值
$ git rev-parse master
378a269ba0c11542ade35eef1df88e094d935548
# 顯示 HEAD 樹(shù)對(duì)象簡(jiǎn)短哈希值
$ git rev-parse --short HEAD^{tree}
5466733
顯示所有對(duì)象文件
其命令為:
git rev-list [OPTION] <commit-id>... [ -- paths... ]
舉個(gè)例子:
-
顯示最新版本所有的對(duì)象文件
$ git rev-list --objects HEAD -
顯示倉(cāng)庫(kù)所有對(duì)象文件
$ git rev-list --objects --all
附錄
本人配置
以下是本人的 Git 配置選項(xiàng):
# 用戶名
git config --global user.name Why8n
# 郵箱
git config --global user.email whyncai@gmail.com
# 默認(rèn)編輯器
git config --global core.editor nvim
# 解決 git status 中文亂碼
git config --global core.quotepath false
# 設(shè)置 git gui 界面編碼
git config --global gui.encoding utf-8
# 設(shè)置 git log 提交內(nèi)容編碼
git config --global i18n.commitencoding utf-8
# 分頁(yè)器替換
git config --global core.pager "less -FRSX"








