🌲

git prev & git next (git nextの改良)

2023/10/23に公開

はじめに

コードを読むために、前のコミットに戻ったり、先のコミットに進んだりすることがよくあります。移動を楽にするため今まではGit prev & nextのエイリアスを使っていました。git prevで1つ前のコミットに戻り、git nextで1つ先のコミットに進みます。

git prevは問題ありませんが、git nextはブランチがmaster(main)限定かつ、コミットヒストリが膨大なリポジトリだと遅くなります。本記事では任意のブランチに対してgit nextをできるようにし、最新のコミット付近で速くする方法について書きます。

結論

.gitconfigに以下のエイリアスを追加します。git prevは元記事と同じです。

.gitconfig
[alias]
	prev = checkout HEAD^
	next = !sh -c 'git checkout \"$(git log --format=%H \"$(git name-rev --name-only HEAD | sed 's/~.*$//')\" | grep -B1 -m1 \"$(git rev-parse HEAD)\" | head -1)\"'

もしくは以下のコマンドで追加できます。

git config --global alias.prev 'checkout HEAD^'
git config --global alias.next $'!sh -c \'git checkout "$(git log --format=%H "$(git name-rev --name-only HEAD | sed \'s/~.*$//\')" | grep -B1 -m1 "$(git rev-parse HEAD)" | head -1)"\''

ただし、以下の注意があります。

  • コミットヒストリは分岐するのでgit prevに対してgit nextは可逆ではありません。分岐地点でgit nextを使うと、git prevを使う前のコミットに戻るとは限りません。
  • git prevまたはgit nextを使うと、detached HEADになります[1]。例えば、ブランチの先頭でgit prevgit nextをちょうど1回ずつ使ってブランチの先頭に戻ってきたとしても、detached HEADのままです。

詳細

コマンドの説明

はじめに述べた通り、コミットヒストリが膨大なとき元記事のgit nextは遅いです。これはgit log--reverseオプションを付けているためです。

nextのエイリアスのスクリプト部分(!sh -c'...'の中身)を抜き出し、整形して以下に示します。!sh!は外部コマンドを呼び出すための記法です。

branch_name="$(git name-rev --name-only HEAD | sed "s/~.*$//")"
current_hash="$(git rev-parse HEAD)"
next_hash="$(                            \
    git log --format=%H "$branch_name"  \
        | grep -m1 -B1 "$current_hash"  \
        | head -1                       \
)"
git checkout "$next_hash"
  1. git name-revを使って現在のコミット(HEADが指し示すコミット)のブランチ名を取得します[2]。detached HEADのとき、ブランチ名とブランチの先頭からの世代数が出力されるので(master~4など)、sedで世代数を削ります。

  2. git rev-parseを使って現在のコミットのハッシュ値を取得し、現在のコミットから1つ先のコミットのハッシュ値を得ます。grep-mオプションで最大マッチ行数を指定するとコミットヒストリが膨大なときに速くなります。

  3. 得たハッシュ値を使って1つ先のコミットにチェックアウトして終わりです。

変更点は以下の3点です。

  • git name-revでブランチ名を取得し、master(main)以外のブランチでコミットを移動できるようにしました。
  • git log--reverseオプションを指定せず、最新のコミット付近での移動を速くしました。
  • grep-mオプションを指定し、ハッシュ値の検索時間を短くしました。

計測

速くした、という記事なのでtovalds/linux(コミット数は約112万)で簡単な計測をしました。

git next 元記事 本記事
最新コミット付近 0.137 10.215
最古コミット付近 10.031 10.483
脚注
  1. .git/HEADシンボリック参照ではなくハッシュ値を保持します。 ↩︎

  2. ブランチ名を取得する方法はいくつかありますが、detached HEADでブランチ名を取得できるコマンドはgit name-revのみでした。 ↩︎

Discussion