Gitで軽率に歴史改変するためのエイリアス powered by fzf

## 歴史は一方通行ではない

今更言うまでもありませんが、Gitは便利ですよね。
Gitの基本的な使い方は歴史を重ね・分岐し・まとめていくことですが、歴史を逆行し・書き換え・削除したくなる場合もあります。
そんなときに使えるエイリアス（というかサブコマンド）をいくつか作ったので解説します。
みなさんにも軽率に歴史改変していただけると幸いです。

なお、実際に使用している`.gitconfig`等は以下のリポジトリで管理しています。

[kawarimidoll/dotfiles](https://github.com/kawarimidoll/dotfiles)

### dependencies
本記事で紹介するエイリアスでは、`git`以外に下記のコマンドを使用します。

- `xargs`
- `grep`
- `head`
- `fzf`

`fzf`は改行区切りのテキストからfuzzy findできるCUIアプリです。本当に便利なので知らない方は使ってみてください。

> fzf is a general-purpose command-line fuzzy finder.
> [junegunn/fzf](https://github.com/junegunn/fzf)

その他のコマンドは普通の環境には入っていると思いますが、`xargs`のバージョンが古い場合、以下で使用しているオプションが対応していない可能性があります。

## 直前のコミットの修正

### 歴史をひとつ戻る `git uncommit`

```git:.gitconfig
[alias]
  uncommit = reset --soft HEAD^
```

直前のコミットをなかったことにし、変更をステージングエリアへ戻します。
~~ウンコミットではない~~

### 歴史を修正する `git amend`

```git:.gitconfig
[alias]
  amend = commit --amend
```

現在ステージングされている変更を直前のコミットにまとめる形で再度コミットします。
コミットメッセージの修正もできます。

## 2つ以上前のコミットの修正
### 歴史を思い出す `git remember`

```git:.gitconfig
[alias]
  remember = __remember
```

```bash:git-__remember
#!/bin/bash

git log --graph --color=always --format="%C(auto)%h%d %C(black bold)%cr %C(auto)%s" "$@" | \
  fzf --ansi --exit-0 --no-sort --no-multi --tiebreak=index --height=100% \
  --preview="grep -o '[a-f0-9]\{7\}' <<< {} | head -1 | xargs --no-run-if-empty git show --color=always" \
  --header="Ctrl-y to toggle preview, Ctrl-u to preview down, Ctrl-i to preview up" \
  --bind="ctrl-y:toggle-preview,ctrl-u:preview-down,ctrl-i:preview-up" \
  --preview-window=down:60% | grep -oE '[a-f0-9]{7}' | head -1
```
![git-remember.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/932385/b303bf53-b8c2-2970-bda5-7e6a220987dd.gif)

コミットグラフからfzfでコミットハッシュを取り出すコマンドです[^1]。変更内容を見つつコミットを選択できます。
[^1]: タグが正規表現にマッチする場合があるため、`head -1`を入れています
[fzf公式サンプル](https://github.com/junegunn/fzf/wiki/examples)のfshowを改変しました。
`.gitconfig`内に書くこともできますが、長い上にエスケープが面倒なのでファイルを分けています。
これ自体は歴史改変操作を行いませんが、引数にコミットハッシュを取るコマンドを簡単に利用するために必要なので、歴史改変の要です。

Gitは`git-xxx`という名前の実行可能ファイルをPATHの通ったディレクトリに置くことでサブコマンドを追加することができます。今回のもので言うと、`git-remember`という実行可能ファイルを上記の内容で作成し、PATHの通ったディレクトリに配置すれば、自動的に`git remember`というコマンドが使えるようになります。
ただ、私は自分の追加したコマンド群を`.gitconfig`内で一覧したいため、`git-__remember`というファイルを追加し、さらに`.gitconfig`にてエイリアスを定義して呼び出すという形をとっています。

本質的には以下のワンライナーです。調整していくうちに、プレビューや色付けなどのオプションが増えて長くなりました。

```bash
git log --oneline | fzf --no-multi | grep -oE '[a-f0-9]{7}' | head -1
```

### 歴史を巻き戻す `git rewind`

```git:.gitconfig
[alias]
  rewind = !git remember | xargs --no-run-if-empty git reset --soft
```

最初に示した`git uncommit`の増強版です。2つ以上前の段階へ戻りたいとき、`git uncommit`を何度も打つよりはこちらを使ったほうが楽です。
前出の`git remember`でコミットを選択し、そこまで一気に戻り、以降の作業をなかったこと[^2]にします。
[^2]: `--soft`を指定しているため、選択したコミット以降の変更はステージングエリアに残りますが、`--hard`にすれば本当にすべて消し去ることができます

### 歴史を書き換える `git allohistory`

```git:.gitconfig
[alias]
  allohistory = !git remember | xargs --no-run-if-empty --open-tty git rebase -i
```

`git rebase -i`をカジュアルに使うためのコマンドです。
前出の`git rewind`と同様に過去のコミットまで戻りますが、こちらは戻ったコミット以降の歴史を壊さないため、過去の1点のみを修正するということが出来ます。
ここでは`rebase`の使い方の説明は省略しますが、修正の他にも、歴史の削除や整理が可能です。

### 未来へ帰る `git btf`

```git:.gitconfig
[alias]
  btf = rebase --continue
```

前出の`git allohistory`で過去に戻った後、~~1.21ジゴワット使って~~ 未来へ帰るためのコマンドです。
`git delorean`でも良かったかもしれません。
なお、未来へ帰ってくる途中の歴史の流れによってはコンフリクトが発生します。

### 歴史を整理する `git fixup`

```git:.gitconfig
[alias]
  fixup = !git remember | xargs --no-run-if-empty -I_ git commit --fixup _ && git rebase -i --autosquash
```

`git allohistory`なら柔軟に歴史を編集できますが、過去のコミットひとつに変更を加えたいときにはこちらのほうが便利です。
選択したコミットへ現在ステージングされている変更をねじ込みます。
こちらも変更内容によりコンフリクトが発生し得ます。

## さあ歴史を変えよう

簡単に歴史を移動できるようになると、自分がどの段階にいるのかわからなくなるので、簡単に確認できるツールがあるといいと思います。私はstarshipを使っています。

[starship/starship](https://github.com/starship/starship)

歴史改変コマンドは用法・用量を守って正しくお使いください。