履歴の書き換え

履歴の書き換え

はじめに

このチュートリアルでは、Git リポジトリの書き換えおよび置き換えのさまざまな方法について説明します。Git では、複数の方法を使用して変更を記録します。さまざまな方法の長所と短所について説明し、それらの使用方法の例を示します。このチュートリアルでは、コミット済みのスナップショットを書き換える必要が生じる理由のうち一般的によく起こるものについて説明し、それを実行する際の事故をいかにして防止するかを示します。

Git の主要目的は、コミット済みの変更を失うことなく記録していくことです。しかし、Git は開発ワークフローを完全に作り変える機能も備えています。これらの機能には、プロジェクトの履歴の外観を書き換える機能も含まれますが、これによりコミット情報を失う可能性も生じます。Git では、履歴書き換えを行うコマンドを、プロジェクトの内容を失う可能性があるとの警告文付きで提供しています。

Git には、履歴の保存および変更の保存のための、複数のメカニズムがあります。これらのメカニズムにはコミット --amendgit rebase および git reflog などがあります。これらのオプションにより、強力なワークフロー カスタマイズ オプションできるようになりますを利用することができます。このチュートリアルが終わるころには、Git コマンドを再構築できるコマンドを習熟し、履歴の書き換え時に一般的に遭遇する落とし穴を回避できるようになります。

直前のコミットを変更する: git commit --amend

git commit --amend コマンドは、直前のコミットを変更する最も便利な方法です。これを使用することで全く新しいコミットを作成する代わりに、ステージングされた変更を前のコミットと組み合わせることができます。また、スナップショットを変更せずに、前のコミット メッセージを単純に変更する際にも使用されます。しかし、改変では、直前のコミットが変更されるだけでなく、全体が置き換えられます。つまり、修正されたコミットは独自の ref を持つ新しいエンティティとなります。Git にとっては全く新しいコミットのように見えます (以下の図ではアスタリスク (*) を使用して視覚化されています)。git commit --amend の使用には、いくつかの一般的なシナリオがあります。次のセクションでは、使用例を紹介します。

Git コマンドの修正

直前の Git コミット メッセージを変更する

Git commit --amend

コミットを行ったばかりで、コミット ログ メッセージに間違いがあるとします。ステージ領域に何もない状態でこのコマンドを実行すると、スナップショットを書き換えることなく直前のコミットメッセージの編集を行うことができます。

開発現場では不完全なコミットが実行されることが日常的に起こります。ファイルのステージを忘れたり、コミットメッセージのフォーマッティングを間違えたりすることはよくあるのです。--amend フラグは、このような軽度の誤操作を修正する場合に便利です。

git commit --amend -m "an updated commit message"

-m オプションを追加すると、エディターを開くプロンプトを表示せずに、コマンド ラインから新しいメッセージに渡すことができます。

コミット済みのファイルを変更する

次の例では、Git ベースの開発における一般的なシナリオを示します。いくつかのファイルを編集してひとつのスナップショットとしてコミットする予定であったが、最初にコミットしたときに片方のファイルを追加し忘れたとします。この修正を行うためには、単にそのファイをステージして、--amend フラグを指定してコミットすればよいのです:

# Edit hello.py and main.py
git add hello.py
git commit

# Realize you forgot to add the changes from main.py
git add main.py
git commit --amend --no-edit

--no-edit フラグを使用すると、コミット メッセージを変更せずに、コミットに変更を加えることができます。結果のコミットは不完全なコミットに置き換えられ、hello.pymain.py への変更が 1 つのスナップショットでコミットされたようになります・

公開済みコミットの修正は厳禁

修正されたコミットは実際には全く新しいコミットであり、以前のコミットは現在のブランチから削除されます。これにより、公開済みのスナップショットを取り消す場合と同様の問題が生じます。他の開発者が既に作業のベースとして使用しているコミットを修正しないようにしてください。これは開発者に混乱をもたらし、回復は面倒です。

要約

レビューを行うには、git commit --amend を使用すると、直前のコミットへ移動して、そこに新しいステージングされた変更を追加します。You can add or remove changes from the Git --amend コミットを使用して、適用対象の Git ステージング領域から変更を追加または削除できます。変更がステージングされていない場合でも、--amend を使用すると、直前のコミット メッセージ ログを変更するよう求めるメッセージが表示されます。他のチーム メンバーと共有しているコミットで --amend を使用する際には注意が必要です。他のユーザーと共有しているコミットを変更すると、複雑で時間のかかる競合解決が必要となる可能性があります。

古いコミットや複数のコミットの変更

古いコミットは複数のコミットを変更するには、git rebase を使用してコミットのシーケンスを新しいベース コミットと組み合わせることができます。標準モードでは、git rebase を使用すると、文字通り履歴を書き換え、現在作業中のブランチのコミットを、渡されたブランチ ヘッドへ自動適用することができます。古いコミットは新しいコミットに置き換えられてしまうため、公開済みのコミットでは git rebase を使用しないことが重要です。そうでない場合、プロジェクト履歴が消失したかのように見えてしまいます。

クリーンなプロジェクト履歴を維持することが重要となるtこれらのインスタンスや童謡のインスタンスでは、-i オプションを git rebase に追加することで、rebase interactive を実行できます。これにより、すべてのコミットを移動させるのではなく、プロセス内の個別のコミットを変更できます。インタラクティブなリベースおよびその他の rebase コマンドの詳細は、git rebase のページを参照してください。

コミット済みのファイルを変更する

rebase の間、edit または e コマンドによってそのコマンドにおける rebase の再生が一時停止され、git commit --amend で追加の変更を加えることができます。Git は再生を中断し、メッセージを表示します。

Stopped at 5d025d1... formatting
You can amend the commit now, with
git commit --amend
Once you are satisfied with your changes, run
git rebase --continue

複数のメッセージ

通常の Git コミットにはそれぞれ、コミットで何が起こったかを説明するログ メッセージがあります。これらのメッセージは、プロジェクト履歴への貴重な洞察を提供します。rebase の際、コミットでいくつかのコミットを実行してコミット メッセージを変更することができます。

  • reword または「r」によって rebase の再生が停止し、その間に個別のコミット メッセージを書き換えることができます。
  • 再生中に squash または「s」を使用すると、s とマークされたコミットはすべて一時停止し、別のコミット メッセージを結合メッセージへと編集するよう求めるプロンプトが表示されます。詳細は、以下の squash コミットセクションを参照してください。
  • fixup または「f」は、squash と同じ効果があります。squash とは異なり、fixup では、コミット メッセージを組み合わせるためにエディターを開く際、rebase の再生が中断されません。「f」とマークされたコミットはメッセージを破棄して、以前のコミットのメッセージを選びます。

squash コミットで履歴をクリーンにする

s 「squash」コマンドでは、rebase の真の有用性を確認できます。squash を使用すると、度のコミットを以前のコミットとマージさせるかを指定できます。これにより、「クリーンな履歴」を実現します。rebase を再生する間、Git は各コミットに対して指定した rebase コマンドを実行します。squash コミットの場合は、Git によって構成済テキスト エディターまたはプロンプトが開き、指定したコミット メッセージを組み合わせることができます。これにより、プロセス全体を次のように視覚化できます。

Git チュートリアル:git rebase -i の例

rebase コマンドで変更されたコマンドには、元のコミットとは異なる ID が付きます。以前のコミットで書き換えられている場合、pick とマークされたコマンドには新しい名前が付きます。

Bitbucket などの近代的な Git ホスティング ソリューションでは、マージの際に「自動スカッシュ」機能が提供されるようになりました。これらは、ホストされているソリューション UI を活用している場合、ブランチのコミットを自動的にリベースまたはスカッシュします。詳細については、「Git ブランチをを Bitbucket とマージする際にコミットを squash する」を参照してください。

要約

Git rebase を使用すると履歴を変更できます。インタラクティブ リベースにを使用すれば、「乱雑な」痕跡を残さずにこの操作を行うことができます。これにより、クリーンで、直線状のプロジェクト履歴を維持しながら、自由にエラーを発生させたり、エラーを修正、および履歴を調整することができます。

セーフティ ネット: git reflog

参照ログ、または「reflogs」は、Git を使用してブランチのヒントやその他のコミット参照に適用される更新を記録するメカニズムです。ブランチやタグのいずれからも参照されていない場合でも、この reflog によってそのコミットに戻ることができます。履歴を書き換えた後であっても reflog にはブランチの過去の状態が記録されており、必要な場合にはそこに戻ることができます。何らかの理由でブランチ ヒントにが更新 (ブランチの切り替え、新たに加えられた変更のプル、履歴の書き換え、あるいは単なる新規コミットの実行など) が加えられるたびに reflog に新たな項目が追加されます。このセクションでは、git reflog コマンドを高度に確認し、一部の一般的な使用法を探ります。

使用法

git reflog

ローカルリポジトリの reflog を表示するコマンドです。

git reflog --relative-date

相対形式の日付 (例: 2 週間前) で reflog を表示します。

To understand git reflog, let's run through an example.

0a2e358 HEAD@{0}: reset: moving to HEAD~2
0254ea7 HEAD@{1}: checkout: moving from 2.2 to master
c10f740 HEAD@{2}: checkout: moving from master to 2.2

The reflog above shows a checkout from master to the 2.2 branch and back. From there, there's a hard reset to an older commit. The latest activity is represented at the top labeled HEAD@{0}.

取り消しが意図しないものであった場合でも、2 つのコミットを取り消す前に (0254ea7) をポイントしていたコミットの元情報が reflog に残っています。

git reset --hard 0254ea7

Git リセットを使用して master を過去に存在したコミットに戻すことができるようになりました。これには、履歴の誤書き換えに対するセーフティネットの役割があります。

なお、変更がローカルリポジトリにコミット済みである場合は reflog が唯一のセーフティネットであること、また reflog はリポジトリ ブランチ チップの移動を記録しているのみであることに留意してください。さらに、reflog エントリには有効期限があります。reflog エントリの既定の有効期限は 90 日間です。

詳細については、git reflog ページを参照してください。 

Summary

この記事では、git 履歴を変更し、git の変更を元に戻すいくつかの方法について説明します。git rebase プロセスについて高いレベルで説明します。重要な成果をいくつか紹介します。

  • git を使用して履歴を書き換える方法は多数あります。
  • git commit --amend を使用して最新のログ メッセージを変更する。
  • git commit --amend を使用して最新のコミットに変更を加える。
  • git rebase を使用してコミットを組み合わせ、ブランチの履歴を変更する。
  • git rebase -i: 標準の git rebase よりもきめ細かく履歴改変を制御できます。

コマンドの詳細は、個別ページを参照してください。