Git でやってしまった!損なときに変更を取り消す便利なツール “git-branchless”

Bash,Git

はじめに

Git で誤って git resetgit rebase をしてしまって冷や汗が出たことがある方は少なくないはずです。

僕も何度自分のローカルブランチでやってしまったことか。

焦りながらも git reflog で最後の変更を見つけ出し、なんとかもとに戻せた!

という場合もあれば、

うまく戻せずに結局最初から開発やり直し...

となったこともあります。

git reflog の操作がムスカ試飲ですよね。

変更がもっとかんたんにもとに戻せたら、と常々思っていましたが、そんな問題にぴったりのツールを見つけました。

検証環境

$ uname -moi
x86_64 MacBookPro16,1 Darwin

$ zsh --version | head -n 1
zsh 5.8 (x86_64-apple-darwin19.6.0)

$ git version
git version 2.32.0

git-branchless

見つけたのは arxanas/git-branchless というツールになります。

git-branchless はツールのセットになります。
コミット履歴を 視覚化ナビゲーション操作の支援修復 するのを手助けしてくれます。

"Google" や "Facebook" といった大企業で利用されている branchless Mercurial worlflows と呼ばれる開発フロー(?)に基づいているそうです。

インストール

Rust がインストールされていることが前提となっているようです。

$ brew install rust

Rust をインストールすると、Rust のビルド、パッケージマネージャーである cargo というコマンドが使えるうようになります。

cargo コマンドを使って git-branchless をインストールします。

$ cargo install git-branchless

パスを通しておきます。

$ export PATH="$HOME/.cargo/bin:$PATH"

gitのサブコマンドが利用できるようになっているか確認してみます。

$ git branchless help
git-branchless 0.3.3
Waleed Khan <me@waleedkhan.name>
Branchless workflow for Git.

See the documentation at https://github.com/arxanas/git-branchless/wiki.
...

利用できそうです。

修正機能

そんな中でも一番使いそうな便利な機能が 修正機能 です。

どんな機能か使いながら理解してみます。

作業用 Git ワークスペースを作成

検証のために作業用 Git ワークスペースを作成します。

$ mkdir work

$ cd work

$ git init
Initialized empty Git repository in /private/tmp/work20210701_161735/work/.git/

ローカルの Git ワークスペースに git-branchless の初期化設定を行う

Git ワークスペースに対して、 git branchless init コマンドで初期化を行います。
これをして初めて git branchless が使えるようになるようです。

作ったばかりのローカルリポジトリの場合は、「どのブランチをメインブランチにしますか?」と聞かれます。
今回は main ブランチを指定しました。

$ git branchless init
Your main branch name could not be auto-detected!
Examples of a main branch: master, main, trunk, etc.
See https://github.com/arxanas/git-branchless/wiki/Concepts#main-branch
Enter the name of your main branch: main
Setting config (non-global): branchless.core.mainBranch = main
Setting config (non-global): advice.detachedHead = false
Installing hook: post-commit
Installing hook: post-rewrite
Installing hook: post-checkout
Installing hook: pre-auto-gc
Installing hook: reference-transaction
Installing alias (non-global): git smartlog -> git branchless smartlog
Installing alias (non-global): git sl -> git branchless smartlog
Installing alias (non-global): git hide -> git branchless hide
Installing alias (non-global): git unhide -> git branchless unhide
Installing alias (non-global): git prev -> git branchless prev
Installing alias (non-global): git next -> git branchless next
Installing alias (non-global): git restack -> git branchless restack
Installing alias (non-global): git undo -> git branchless undo
Installing alias (non-global): git move -> git branchless move
Successfully installed git-branchless.
To uninstall, run: git branchless init --uninstall

コミットを重ねた後に取り消し( undo )してみる

それではコミットを重ねた後に取り消しして見ます。

まずは適当なコミットを何度か行ってみます。

# 1. fooというファイルを作成 ( "Hello, world!" と書かれている )
echo 'Hello, world!' >foo

# 2. git commmit
git add foo
git commit -m "first commit."

# 3. 内容を書き換える
echo 'Goodby, world!' >foo

# 4. git commmit (前のコミットに強制的に追加する)
git add foo
git commit --amend -m "Amend foo bad"

実はこの git commit --amend 、間違いだったことに気が付きました。
がときすでに遅し。

$ git log
commit 8eb37409d550bf4e7b5f039fd690b2d2fd95fb60 (HEAD -> main, refs/branchless/8eb37409d550bf4e7b5f039fd690b2d2fd95fb60)
Author: genzouw <genzouw@gmail.com>
Date:   Thu Jul 1 16:33:16 2021 +0900

    Amend foo bad

# git sl でもcommit historyが閲覧できる
$ git sl
◆ 8eb37409 34m (main) Amend foo bad

コミットがくっついてしまいましたとさ。。。

しかし落ち着きましょう。
こんなときには git undo を実行します。

$ git undo

すると以下のような画面になります。

n / p キーを押すと、変更履歴が表示されますので、戻したい履歴が表示されたら ENTER キーを押します。

完全に最初のコミットまで戻すこともできますし、

# コミット履歴 (まだ戻っていない)
$ git log
commit 29f637f8c89e3126271a8a67d90785987d6583f0 (HEAD, main, refs/branchless/29f637f8c89e3126271a8a67d90785987d6583f0)
Author: genzouw <genzouw@gmail.com>
Date:   Thu Jul 1 16:33:16 2021 +0900

    first commit.

$ git sl
◆ 29f637f8 46m (main) first commit.

# ファイルの中身を確認 (戻っている)
$ cat foo
Hello, world!

2 つ目のコミットを残したままにすることもできます。(修正を微調整する場合はこちらですね。)

# コミット履歴 (まだ戻っていない)
$ git log
commit 8eb37409d550bf4e7b5f039fd690b2d2fd95fb60 (HEAD, refs/branchless/8eb37409d550bf4e7b5f039fd690b2d2fd95fb60)
Author: genzouw <genzouw@gmail.com>
Date:   Thu Jul 1 16:33:16 2021 +0900

    Amend foo bad

$ git sl
◇ 29f637f8 48m (main) first commit.

⦻ 8eb37409 48m (rewritten as 29f637f8) Amend foo bad

# ファイルの中身を確認 (戻っていない)
$ cat foo
Goodby, world!

# コミット履歴を戻す
$ git reset --soft main
branchless: processing 1 update to a branch/ref

# ステージング状態まで戻せた!
$ git diff --cached
diff --git foo foo
index af5626b..c6e4c2b 100644
--- foo
+++ foo
@@ -1 +1 @@
-Hello, world!
+Goodby, world!

ひとこと

もう少し使ってみて、発見があればエントリを更新していきたいと思います。

Bash,Git