Git 學(xué)習(xí)筆記(CheatSheet)(二)

Git 思維導(dǎo)圖

高級(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.ago 1分鐘之前
    1.hour.ago 1小時(shí)之前
    yesterday 昨天
    1.week.ago 一周之前
    1.month.ago 一月之前
    1.year.ago 1小時(shí)之前
    2020-05-18.09:00:00 2020-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 gcgit 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模式合并分支效果:

    1. 首先現(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
    2. 然后將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è)分支(即devmaster分支)的最近共同祖先提交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è)不存在沖突)。

    3. 此時(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 mastermaster分支就會(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)在我們想將C2C3壓縮成一個(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í)包含C2C3的提交信息,我們可以手動(dòng)編輯壓縮合并生成的新提交信息:

      # 默認(rèn)采用上一個(gè)提交信息,這里我將其修改為如下
      squash C2 and C3
      

      此時(shí),我們就完成了C2C3的合并,效果如下所示:

      $ git log --oneline
      cfed515 (HEAD -> master) C4
      fb5e7f3 squash C2 and C3    # 合并成功
      c830b70 C1
      
    • fixup:該操作也squash作用一樣,也是用于壓縮提交。但與squash不同的是,該操作會(huì)丟棄被壓縮提交(即被fixup標(biāo)注的提交)的提交信息。

      舉個(gè)例子:比如我們還是壓縮C2C3,但是直接將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ù)提交C2C3壓縮到一起,如下所示:

      $ git log --oneline
      cfed515 (HEAD -> master) C4
      fb5e7f3 squash C2 and C3
      c830b70 C1
      

      現(xiàn)在如果我們想重新分離開(kāi)C2C3,則可以借助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è)小提交C2C3

      # 暫存區(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除C2C3這兩個(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)成功刪除了C2C3

    • 更換提交順序:更換提交順序只需在編輯窗口中直接更換提交的順序即可。

      舉個(gè)例子:比如我們當(dāng)前本地倉(cāng)庫(kù)提交歷史記錄如下所示:

      $ git log --oneline
      45fcf79 (HEAD -> master) C3
      6992e50 C2
      c830b70 C1
      

      假設(shè)現(xiàn)在我們想更換C2C3的順序,其步驟如下:

      # 啟動(dòng)交互式變基
      $ git rebase -i HEAD~2
      

      此時(shí)的編輯窗口如下所示:

      pick 6992e50 C2
      pick 45fcf79 C3
      

      要更換C2C3提交順序,只需在編輯窗口中更換兩者順序即可:

      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ù)存在masterdev分支,現(xiàn)在突然發(fā)現(xiàn)線上版本出現(xiàn)漏洞,因此緊急從master分支上創(chuàng)建一個(gè)hotfix/add_file分支,然后做了兩個(gè)提交,如下圖所示:

cherrypick - initial

簡(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í)的示意圖如下所示:

cherrypick - master merge hotfix

同樣的,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分支的所有提交,即C4C5直接摘取到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)可以看到成功摘取C4C5dev分支上了,此時(shí)的示意圖如下所示:

    cherrypick - dev pick hotfix
  • cherry-pick合并提交:第二種方法是摘取合并提交,即摘取C6應(yīng)用到dev分支上。具體步驟如下:

    1. 首先我們將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
    2. 此時(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_pointend_point的中間提交,如果未指定start_pointend_point,則進(jìn)入二分搜索時(shí),還需手動(dòng)使用git bisect bad <commit>git bisect good <commit>手動(dòng)指定end_pointstart_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 revertgit 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_commitparent-number就是1,第二個(gè)parent2_commitparetn-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)指定只顯示文件startend之間的行:

    # 只顯示 .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.requireForcefalse的話,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ì)象文件的哈希字符串。比如:dae86e1950b1277e545cee180551750029cfe735dae86e。
    • <refspec>:表示符號(hào)引用。比如:master、heads/masterrefs/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>}功能一致,只是refspecHEAD,即表示當(dāng)前分支的最新第number個(gè)提交。
    • <rev>^[n]:表示提交rev的前n個(gè)父提交,n表示字符^的重復(fù)個(gè)數(shù)。比如:a95fabe^表示提交a95fabe的前一個(gè)提交,HEAD^^表示HEAD的前第二個(gè)提交...
      n也可以為數(shù)字,但只能為01。比如a95fabe^0等同于a95fabeHEAD^1等同于HEAD^。
    • <rev>~<number>:表示提交rev的第number祖先提交。比如:a95fabe~0等同于a95fabea95fabe~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í)包含rev1rev2,即[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"

參考

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

相關(guān)閱讀更多精彩內(nèi)容

  • [TOC] Git 內(nèi)部實(shí)現(xiàn)原理剖析[http://m.itdecent.cn/p/8154ac47d406...
    Whyn閱讀 1,008評(píng)論 0 0
  • 一、Git是什么? 是一個(gè)開(kāi)源的分布式版本控制系統(tǒng),可以有效、高速的處理從很小到非常 大的項(xiàng)目版本管理。 Git ...
    名字誰(shuí)不會(huì)取閱讀 1,216評(píng)論 0 0
  • 1.直接進(jìn)入沙盒 2. 本地操作相關(guān)篇節(jié)2.1 基礎(chǔ)篇2.2 處理復(fù)雜問(wèn)題2.2.1 修改提交樹(shù)2.3 雜項(xiàng) 3....
    徽先生閱讀 526評(píng)論 0 0
  • 久違的晴天,家長(zhǎng)會(huì)。 家長(zhǎng)大會(huì)開(kāi)好到教室時(shí),離放學(xué)已經(jīng)沒(méi)多少時(shí)間了。班主任說(shuō)已經(jīng)安排了三個(gè)家長(zhǎng)分享經(jīng)驗(yàn)。 放學(xué)鈴聲...
    飄雪兒5閱讀 7,868評(píng)論 16 22
  • 今天感恩節(jié)哎,感謝一直在我身邊的親朋好友。感恩相遇!感恩不離不棄。 中午開(kāi)了第一次的黨會(huì),身份的轉(zhuǎn)變要...
    余生動(dòng)聽(tīng)閱讀 10,918評(píng)論 0 11

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