LoginSignup
0
1

過去のcommitを修正するいくつかの方法について

Posted at

はじめに

この記事を読んでいるということは、止むに止まれぬ理由で過去のcommitを修正したいと考えているのでしょう。
私も様々な理由で毎週のように修正しているので、その気持ちはよくわかります...。

ただ修正するにしても方法がいくつかあり、どれを選ぶべきか迷ってしまいませんか?
そこでこの記事では過去のcommitを修正する方法と、使用するのに適した場面を合わせて解説します。

原則として過去のcommitの修正は、
リモートリポジトリにプッシュする前のcommitや、メインブランチにマージ前のcommit
個人的に使用しているリポジトリなど他の作業者に影響が発生しないcommitに限定してください。

1.commitに記録される内容について

そもそもcommitに何が記録されているか、そしてgit logでどのような情報が出力されるのか把握していますか?
これは決して馬鹿にしているわけではなく、標準的なオプションなしのgit logでは表示されない項目も存在するため見落としがちなポイントです。

まずcommitには以下の項目が含まれています。

image.png

項番 項目 内容
Commit Hash commitを一意に識別するための SHA-1 ハッシュ値
Author commitを作成したユーザー名/メールアドレス
AuthorDate commitが作成された日時
Commit (Commiter) commitをリポジトリに適用したユーザー名/メールアドレス
CommitDate commitをリポジトリに適用した日時
Commit Message commit時に作成したメッセージ
Changes commitで行われた変更内容

上記でcommit後に修正する可能性がある項目は、
AuthorAuthorDateCommitCommitDateCommit Messageのいずれかかと思います。

またCommitCommitDateをgit logで出力する場合、--prettyオプションでfullerを指定するか、
formatを用いてカスタムフォーマットを設定しない限り出力されません。
なおオプションを指定しない場合のgit logは--pretty=mediumオプションと同等です。

  • git log 出力例
commit b00a1c8d57ad878d156fca2989726a30d5059c05 (HEAD)
Author: example-user <email@example.com>
Date:   Mon Jan 1 12:00:00 2024 +0900

    :wrench: express upgrade
  • git log --pretty=fuller 出力例
commit b00a1c8d57ad878d156fca2989726a30d5059c05 (HEAD)
Author:     example-user <email@example.com>
AuthorDate: Mon Jan 1 12:00:00 2024 +0900
Commit:     example-user <example@example.com>
CommitDate: Mon Jan 1 12:00:00 2024 +0900

    :wrench: express upgrade

これらの点を理解していないと部分的な修正漏れを引き起こす可能性があるため、
上記の項目が存在することを前提に修正を行ってください。

なおgit logの仕様についてさらに深堀したい方は、以下公式ドキュメントをご参照ください。

2.過去commitの修正方法について

commitの修正は大きく分けて、「単一のcommitを修正したい場合」「複数のcommitを一括で修正したい場合」のどちらかと思いますので、
それぞれに適した方法を解説します。

(1) 単一のcommitを修正したい場合

直前のcommit過去の単一のcommitを修正したい場合が対象です。
ちょっとした修正でよく利用するので、普段使いはこちらかと思います。

① git commit --amend

直前のcommitを修正したい場合はとりあえずこのコマンドです。
ただし過去のcommitは直接変更できないため、後述のgit rebaseと合わせて利用する必要があります。

またgit commit --amendのみ実行するとCommit Messageの編集と、CommitDateが修正時の時刻に更新されるだけなので、
その他項目も修正したい場合はオプションや環境変数で指定が必要になります。

Author、AuthorDateを変更したい場合

Author--authorオプション、
AuthorDate--dateオプションで値を指定することで変更できます。

git commit --amend \
  --author="example-user <email@example.com>" \
  --date="2024-01-01 12:00:00"

Commit、CommitDateを変更したい場合

CommitCommitDateはオプションで指定できないため、
git config(~/.gitconfig)で設定するか、環境変数で設定する必要があります。

永続的に変更する場合は、git configで設定を更新してからgit commit --amendを実行してください。

git config --global user.name "new-user"
git config --global user.email "new-email@example.com"
git commit --amend

修正対象のcommitのみ変えたい場合は、git commit --amendの実行時に環境変数として設定する必要があります。
またCommitDateを特定の時間を指定したい場合も、同じく環境変数で設定が必要です。

GIT_COMMITTER_DATE="2024-01-01 12:00:00" \
GIT_COMMITTER_NAME="example-user" \
GIT_COMMITTER_EMAIL="<email@example.com>" \
git commit --amend

その他の環境変数は割愛しますが、詳しく知りたい方は以下ドキュメントでご確認ください。

② git rebase

一つ以上前の過去のcommitを修正したい場合はこちらを利用します。
git commit --amendと組み合わせて使う必要があり、以下の流れで修正できます。

1.git log --onelineなどで対象のcommit hashを確認

$ git log --oneline
b00a1c8 (HEAD) :wrench: express upgrade
fce919d :construction: add test page
b1d8db1 :construction: add react
4389323 :tada: first commit

2.git rebase -iで修正対象の一つ前のcommit hashを指定

git rebase -i 4389323

3.リベースモードに入るので、修正対象のcommitのpickeditに書き換えて保存

edit b1d8db1 :construction: add react
pick fce919d :construction: add test page
pick b00a1c8 (HEAD) :wrench: express upgrade

4.git commit --amendで修正

※各項目の修正方法は前述のgit commit --amendを参照

git commit --amend

5.修正が完了したらgit rebase --continueでリベースモードを終了

git rebase --continue

(2) 複数のcommitを修正したい場合

過去の複数のcommit、または過去のすべてのcommitを修正したい場合が対象です。
影響範囲が大きいため、git cloneしたものに対して修正を実施することを推奨します。

① git filter-branch

gitに標準で含まれているため、追加のパッケージインストールや設定が不要なことが利点です。
ただし実行速度、安全性、使いやすさなどの観点で後述のgit filter-repoの利用が推奨されているため、
基本的にはそちらをご利用ください。

過去の複数のcommitを修正する場合

特定のcommitを複数指定して修正することはできないため、
条件式にてマッチしたものを置き換えるという方法になります。

例として以下であればGIT_COMMITTER_EMAIL、またはGIT_AUTHOR_EMAILOLD_EMAILに合致する場合は、
NEW_NAMENEW_EMAILに置き換えられます。

git filter-branch --env-filter '
OLD_EMAIL="old-email@example.com"
NEW_NAME="new-user"
NEW_EMAIL="new-email@example.com"
if [ "$GIT_COMMITTER_EMAIL" = "$OLD_EMAIL" ]
then
    export GIT_COMMITTER_NAME="$NEW_NAME"
    export GIT_COMMITTER_EMAIL="$NEW_EMAIL"
fi
if [ "$GIT_AUTHOR_EMAIL" = "$OLD_EMAIL" ]
then
    export GIT_AUTHOR_NAME="$NEW_NAME"
    export GIT_AUTHOR_EMAIL="$NEW_EMAIL"
fi
' -- --all

過去のすべてのcommitを修正する場合

すべてのcommitを修正する場合は、
以下の通り環境変数に値を指定するだけでOKです。

以下の場合は過去のcommitすべてのGIT_AUTHOR_NAMEGIT_AUTHOR_EMAILGIT_COMMITTER_NAMEGIT_COMMITTER_EMAILの値を置き換えています。

git filter-branch -f --env-filter \
  "GIT_AUTHOR_NAME='new-user'; \
   GIT_AUTHOR_EMAIL='new-email@example.com'; \
   GIT_COMMITTER_NAME='new-user'; \
   GIT_COMMITTER_EMAIL='new-email@example.com';"

② git filter-repo

git-filter-branchでも公式としてgit filter-repoの利用を推奨しており、
基本的にはこちらを利用します。

利用にあたりインストールする必要があるので、以下の方法で導入してください。

Linux (RedHat系)
sudo yum install git-filter-repo
Linux (Debian系)
sudo apt install git-filter-repo
Mac
brew install git-filter-repo
Windows (pip)
pip install git-filter-repo

過去の複数のcommitを修正する場合

特定のcommitを複数指定して修正することはできないため、
条件式にてマッチしたものを置き換えるという方法になります。

例として以下であればcommit.committer_name、またはcommit.author_nameold_nameに合致する場合は、
new_namenew_emailに置き換えられます。

git filter-repo --commit-callback '
old_name = b"old-user"
new_name = b"new-user"
new_email = b"new-email@example.com"

if commit.committer_name == old_name :
  commit.committer_name = new_name
  commit.committer_email = new_email
if commit.author_name == old_name :
  commit.author_name = new_name
  commit.author_email = new_email
'

またauthorとcommitterの情報が同じ場合は、
コールバックのオプションを利用することでより簡潔なコマンドで修正できます。

git-filter-repo \
  --name-callback 'return name.replace(b"old-user", b"new-user")' \
  --email-callback 'return email.replace(b"old-email@email.com", b"new@email.com")'

過去のすべてのcommitを修正する場合

すべてのcommitを修正する場合は、
以下の通り変数に値を指定するだけでOKです。

git filter-repo --commit-callback '
new_name = b"new-user"
new_email = b"new-email@example.com"

commit.committer_name = new_name
commit.committer_email = new_email
commit.author_name = new_name
commit.author_email = new_email
'

またauthorとcommitterの情報が同じ場合は、
コールバックのオプションを利用することでより簡潔なコマンドで修正できます。

git-filter-repo \
  --name-callback 'return b"new-user"' \
  --email-callback 'return b"new-email@email.com"'

3.最後に

以上、過去のcommitの修正方法についてでした。

しつこいようですが、
個人のリポジトリでない限り他の人とのコンフリクトが発生する可能性が高いため、実際に使用する際は細心の注意を払ってください。
ただし、いざというときに役立つ内容だと思いますので必要な時にはぜひご活用ください。

まあ、そもそも後から修正したくなるようなコミットをしないことが最善ではありますが...。

参考ドキュメント

本記事の作成に際して、以下のドキュメントを参照させていただきました。
この場を借りてお礼申し上げます。

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1