如何修剪git reflog歷史

2023-02-07 18:00:40

背景:

vscode外掛git-graph可以方便檢視git-commit-graph,效果很好,關鍵是互動性很好。
點選任意commit即可預覽提交內容,實在是太方便了,比我之前用命令列上git log --graph --oneline強太多了。
但同時帶來的困擾是能看到的資訊(commit歷史)太多了,讓我眼花繚繞。
例如,為了修復一個issue,前後進行了10次git commit --amend。也就是一共11次提及歷史記錄。
git graph大概長這樣

 

初衷:

實際上當我合併這次的修改之後,我只想保留最後一次的記錄在reflog裡,其他的10次提交歷史都不要了。這就涉及到了git reflog修剪了。

實現:

首先,git 是通過HEAD找commit hash ID,然後每個commit都有parent commit,如此組成一條鏈式結構。
commit是描繪git-graph的主要依據,其實只要刪掉一個commit就能改變git-graph的結果。
每一次提交都會在.git/objects目錄下生成至少一個commit型別的檔案,其完整的檔案路徑為.git/objects/12/34567xx... (這裡假設這個commit hash id 是1234567xx...)
git cat-file -t可以檢視.git/objects目錄下的檔案是tree、commit還是blob型別。
例如:

注意:不能刪除當前分支上可達的commit,不然鏈就斷了,git就無法正常工作了。前面提到的"其他的10次提交歷史" 因為在當前分支已經不可達,所以可以刪除

例如我想從git-graph刪除一個hash為 1234567的commit
那麼步驟為:

  1. 找到.git/objects/12/34567xx...
  2. 刪除或者移動它 (建議移動到一個目錄下,萬一想要檢視的時候還能還原)

因為git commit hash有縮寫形式、參照形式、完整形式,但是.git/objects/下的檔名都是完整形式,這種事情當然要寫個指令碼來一勞永逸了。

#!/bin/bash

function zlipd() {
    printf "\x1f\x8b\x08\x00\x00\x00\x00\x00" | cat - $@ | gzip - dc 2>  /dev/null
}

function move_intermediate_obj() {
    local dst_path
    dst_path=$1;  shift
    while [ -n  "$1" ];  do
        if [ -f .git /objects/ "${1:0:2}" / "${1:2}" ];  then
            mv - v .git /objects/ "${1:0:2}" / "${1:2}" "$dst_path/$1"
        fi
        shift
    done
}
# function migrate_intermediate_obj() {
#     [ ! -d ./.git ] && { echo ".git dir not exist"; return; }
#     [ ! -d ./intermediate_obj ] && mkdir -p intermediate_obj
#     for f in $(git rev-list -n "${2:-1}" "${1:?params not enough}")
#     do
#         # echo "$f"
#         move_intermediate_obj "$f"
#     done
# }

migrate_intermediate_obj ()
{
    [ ! -d ./.git ] && {
        echo ".git dir not exist" ;
        return
    };

    local dst_dir
    [ -d . /output ] && dst_dir=. /output/intermediate_obj ;
    [ -z $dst_dir ] && dst_dir=. /intermediate_obj ;
    mkdir -p $dst_dir

    if [ $ # -eq 1 ]
    then
        move_intermediate_obj $dst_dir  "$1" ;
    elif expr $2 + 0 >  /dev/null 2>&1  # test $2 whether is number otherwise $? neq 0/1 if $2 non-integer argument
    then
        for f  in $(git rev-list -n  "${2:-1}" "${1:?params not enough}" );
        do
            move_intermediate_obj $dst_dir  "$f" ;
        done
    else
        move_intermediate_obj $dst_dir  "$@" ;
    fi
}

指令碼使用方法:
  source script.sh
  migrate_intermediate_obj 1234567

指令碼將會在當前目錄下建立一個檔案intermediate_obj,並將commit檔案移動進去。
PS:在git gc的時候有些commit會被打包到.git/objects/pack資料夾下的pack字尾的檔案裡,這樣的話在.git/objects/下就找不到這些commit檔案了。
    解決辦法是使用git unpack-objects < .git/*.pack檔案解壓出來。(pack檔案和index檔案要事先從.git/objects/pack移出去該命令才會有效果)
PS2:git verify-pack -v .git/objects/pack/pack-xx.pack可以檢視哪個pack檔案包含你要的commit