Git 全過去コミットの Author と Committer を上書きする（GPG 署名対応）

まとまった情報がなさそうだったので。歴史修正していきましょう 🥺

:::note warn
`filter-branch` や `rebase` のような歴史修正コマンドは危険なので、バックアップを取るなど安全を確保した上で自己責任での作業をお願いします 🙇
:::

## 環境

Git v2.36.0

## 共通の準備

```sh
git config --global user.name "Alice"
git config --global user.email alice@example.com
git config --global user.signingkey 0123456789ABCDEF # コミットに署名する場合のみ
git config --global commit.gpgsign true # コミットにGPG署名する場合のみ
```

## 歴史修正

いくつかやり方の候補があります。それぞれ実行結果が少しずつ異なるので、状況に合う方法を選択してください。

### A. filter-branch 署名あり（全コミット）

```sh
git filter-branch -f --commit-filter '
GIT_AUTHOR_NAME="Alice";
GIT_AUTHOR_EMAIL="alice@example.com";
GIT_COMMITTER_NAME="Alice";
GIT_COMMITTER_EMAIL="alice@example.com";
git commit-tree -S "$@";
' HEAD
```

- 歴史修正前の AuthorDate と CommitDate の両方を維持
- Author と Committer を指定したもので上書き
- filter-branch は何でもできてしまう[最強のオプション](https://git-scm.com/book/ja/v2/Git-%E3%81%AE%E3%81%95%E3%81%BE%E3%81%96%E3%81%BE%E3%81%AA%E3%83%84%E3%83%BC%E3%83%AB-%E6%AD%B4%E5%8F%B2%E3%81%AE%E6%9B%B8%E3%81%8D%E6%8F%9B%E3%81%88)なので、rebase より気分的にちょっと怖いかも

### B. filter-branch  署名なし（全コミット）

```sh
git filter-branch -f --env-filter '
GIT_AUTHOR_NAME="Alice";
GIT_AUTHOR_EMAIL="alice@example.com";
GIT_COMMITTER_NAME="Alice";
GIT_COMMITTER_EMAIL="alice@example.com";
' HEAD
```

- 歴史修正 A の署名なしバージョン
- 歴史修正前のコミットに署名があってもその署名は消える

### C. filter-branch  署名あり（条件付き）

```sh
git filter-branch -f --commit-filter '
  if [ "$GIT_AUTHOR_EMAIL" = "alice@localhost" ];
  then
    GIT_AUTHOR_NAME="Alice";
    GIT_AUTHOR_EMAIL="alice@example.com";
    GIT_COMMITTER_NAME="Alice";
    GIT_COMMITTER_EMAIL="alice@example.com";
    git commit-tree -S "$@";
  else
    git commit-tree -S "$@";
  fi' HEAD
```

- if の `test` 条件に一致するコミットのみ上書き
- 署名なしの場合も歴史修正 B と同様の考え方でできると思います

### D. rebase & amend author（全コミット）

```sh
git rebase -i --root -x 'git commit --amend --author="Alice <alice@example.com>" -C HEAD'

# エディタが起動するので何も編集せずに保存して閉じる
```

- 歴史修正前の AuthorDate を維持
- Author を指定したもので上書き
- 現在の git config に従って Committer と CommitDate を更新

### E. rebase & amend reset-author（全コミット）

```sh
git rebase -i --root -x "git commit --amend --reset-author -C HEAD"

# エディタが起動するので何も編集せずに保存して閉じる
```

- 歴史修正前のメタデータは原則すべて消える
- 現在の git config に従って Author と AuthorDate を更新
- 現在の git config に従って Committer と CommitDate を更新

### F. rebase & amend reset-author（一部のコミット）

```sh
git rebase -i <上書きしない中で最新のコミットハッシュ> -x "git commit --amend --reset-author -C HEAD"

# エディタが起動するので何も編集せずに保存して閉じる
```

- `author` オプションを使う場合も同様

### 備考

- いずれの方法でもオリジナルのコミット署名は消えます。デジタル署名が歴史修正に負けたらまずいので当然ですが
- オプションなしの `$ git log` で表示されるタイムスタンプは AuthorDate ですが、GitHub上で表示されるタイムスタンプは CommitDate です（2022-04-21現在）

## 確認

```sh
git log --pretty=fuller --show-signature
```

## リモートリポジトリへの反映

必要な場合のみ。

```sh
git push --force-with-lease [origin] [branch]
```

## 参考

- [Git - 歴史の書き換え](https://git-scm.com/book/ja/v2/Git-%E3%81%AE%E3%81%95%E3%81%BE%E3%81%96%E3%81%BE%E3%81%AA%E3%83%84%E3%83%BC%E3%83%AB-%E6%AD%B4%E5%8F%B2%E3%81%AE%E6%9B%B8%E3%81%8D%E6%8F%9B%E3%81%88)
- [Git の Commit Author と Commiter を変更する - Qiita](https://qiita.com/sea_mountain/items/d70216a5bc16a88ed932)
- [Git - git-filter-branch Documentation](https://git-scm.com/docs/git-filter-branch)
- [Git - git-commit-tree Documentation](https://git-scm.com/docs/git-commit-tree)
- [git の author を最初の commit から修正する - めっちゃいいタイトル思いついたんだけど風呂入ったら忘れちゃった](https://ry-2718.hatenablog.com/entry/2021/05/26/172058)
- [How to amend several commits in Git to change author - Stack Overflow](https://stackoverflow.com/questions/4981126/how-to-amend-several-commits-in-git-to-change-author)
- [Git - git-commit Documentation](https://git-scm.com/docs/git-commit)