背景:
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
那麼步驟為:
- 找到.git/objects/12/34567xx...
- 刪除或者移動它 (建議移動到一個目錄下,萬一想要檢視的時候還能還原)
因為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