Git Rebaseを使用してコミット履歴を整理します

  • Gitのリベース機能を使うと、履歴を書き換えて順序を変更し、クリーンで直線的なコミットのシーケンスを作成できます。
  • 対話型リベースを使用すると、コミットの削除、マージ、または並べ替えが簡単に行え、空のテストやコミットを排除できます。
  • 歴史を書き換えるには、チームとの連携と、他者の成果を失わないよう慎重な強制的な推進が必要となる。
  • git reflogやgit pull --rebaseのようなツールは、リベースを安全かつ可逆的に操作するのに役立ちます。

gitリベース

Gitをしばらく使っていると、おそらく、役に立たないコミット、「WIP」メッセージ、簡単なテスト、あるいはフックやパイプラインをトリガーするためだけに作成された空のコミットでいっぱいの履歴に出くわしたことがあるでしょう。そんな時、ログを見てこう思うはずです。 「誰もこれを理解できない」朗報です。あなたは一人ではありません。誰にでも起こりうることであり、だからこそ支援が必要なのです。 gitリベース.

興味深いのは、適切に使用すれば、 git rebase を使用すると、コミット履歴を「整理」できます。履歴を直線的でノイズのない、レビューしやすいものにしましょう。テストコミットを削除したり、複数のコミットを1つにマージしたり、コミットの順序を変更したり、コミットメッセージを修正したり、強制プッシュによってリモートリポジトリから変更を削除したりすることもできます。具体的な例を交えながら、リベースを使用して履歴を完全な混乱状態から整理された読みやすいものに変換する方法を見ていきましょう。

Git Rebaseとは具体的に何なのか、そしてなぜ履歴に影響を与えるのか?

追い越しについて話すとき、文字通り ブランチの開始点を変更するGit はあなたのブランチからコミットを取得し、それらを別のベース (通常はブランチ) に 1 つずつ「複製」します メイン (または更新されたリモートブランチ)。まるで時間を巻き戻して別のコミットから作業を開始し、変更内容を保持したかのようです。

あなたのプロジェクトのメインブランチに、次のようなストーリーがあると想像してみてください。 A – B – C機能のブランチを作成し、次に2つのコミットを行います。 D-E一方、メインブランチでは、誰かが新しいコミットを追加した。 Fリベースを行わない場合、ブランチは次のようになります。

A---B---C---F (main)
A---B---C---D---E (feature)

フィーチャーブランチをメインブランチに従来の方法でマージすると、余分なマージコミットが生成され、多くの場合、 複雑な歴史 複数の交差線があります。一方、リベースでは、Git はコミット D と E を取得し、F に再適用します。

A---B---C---F---D'---E' (feature reescrita)

それら D'とE' これらはDとEと全く同じコミットではありません。新しい識別子(ハッシュ)を持つ「コピー」です。だからこそ、リベースと言うのです。 歴史を書き換えるその結果、ノイズを増やすだけの中間マージコミットがなくなり、より直線的なログが得られる。

gitリベース

クリーンなコミット履歴を持つべき理由

整理された記録は、単に見た目の問題だけではない。 整理されたログは、日々の作業をはるかに容易にする。空のマージコミットや文脈のない「クイックフィックス」メッセージをいちいち確認しなくても、何が、いつ、なぜ行われたのかが一目でわかります。

ブランチがメインから継続的にマージされると、「Merge branch 'main' into feature-x」のようなコミットが大量に発生し、 コード変更に関する正確な情報は提供されないこれにより、次のような作業が複雑になります。

  • バグが導入されたコミットを特定するには、 ファイルを比較するためのツールなぜなら、その履歴には無関係な合併が多数含まれているからだ。
  • プルリクエストをレビューするなぜなら、実際に何が変更されたのかを確認するには、重複したコミット間を何度も行き来しなければならないからです。
  • 機能の進化を理解する特に、それが多数の小さなテストコミットを経て開発されてきたものである場合はなおさらです。

Git rebaseは、ブランチをチームの他のメンバーと共有する前に、そうした不要な要素をすべて「磨き上げる」ための理想的なツールです。 それはまるで、自分の過去を洗濯機にかけるようなものだ。重要な変更点を、分かりやすくグループ化し、明確なメッセージとともにまとめておく。

git mergeとgit rebaseの主な違い

rebase を使用する際に何をしているのかを完全に理解するには、その動作を次の動作と比較する価値があります。 git マージどちらも変化を統合する役割を果たすが、その方法は異なり、記録にとって重要な意味を持つ。

とともに マージGit は、2 つの親を持つ新しいマージ コミットを作成します。その親は、あなたのブランチの先端と、マージ先のブランチの先端です。結果として得られる履歴は次のようになります。 コミットの元のシーケンスを正確に保持しますしかし、結果として並列分岐やマージが大量に発生する可能性がある。

とともに リベースマージコミットを作成する代わりに、Git はコミットを取得し、新しいデータベースに 1 つずつ適用します。これにより、 新しいハッシュを持つ新しいコミットコードの変更内容が同じであっても。

実際には:

  • マージ それは、すべての相互リンクや統合を含め、実際に起こったとおりの歴史を正確に保持します。
  • リベース それは、まるで全てが一直線に起こったかのように、直線的で整然とした履歴を生成する。

選択は「どちらかがもう一方より優れている」ということではなく、 それぞれ、どちらの状況により適しているでしょうか?既に共有され、かつ閉じられたブランチを結合するには、通常はマージの方が安全です。ローカルの作業ブランチを整理しておく場合や、プルリクエストを作成する前には、リベースが効果的です。

マージとリベース

リベースが真価を発揮するケース:ブランチの更新と洗練

ほとんどの開発者が日常的にリベースを使用するシナリオは2つあります。 作業用ブランチをメインブランチと常に最新の状態に保つ y コミットを共有する前に、それらを整理してください。それらをさらに詳しく見ていきましょう。

フィーチャーブランチをメインブランチの最新の変更で更新してください。

あなたは自分のブランチで新機能の開発に取り組んでいますが、その間、同僚はまだ別のブランチでコミットを行っています。 メイン変更内容を統合したい場合は、次の方法があります。

git checkout tu-rama-feature
git merge main

これは機能しますが、同期するたびにマージコミットが生成されるため、最終的には問題が発生します。 繰り返し合併を重ねてきた歴史しかし、もしあなたが以下のことをするなら:

git checkout tu-rama-feature
git rebase main

Git は、まるでそれらの変更後にブランチを開始したかのように、コミットを main の最後の状態の上に移動します。 中間マージコミットのない、直線的な履歴そしてレビューはより明確になるでしょう。

プルリクエストを開く前にコミットを整理してください

機能開発中に「タイプミス修正」「パイプラインテスト」「ログインの変更点追加」などのコミットが発生することはよくあることです。作業中はそれで問題ありませんが、 それは、あなたが示したい歴史ではない。 プルリクエストを開くとき。

インタラクティブなリベースにより それらのコミットを再編成してグループ化する もっと論理的なものに。例えば、これではなく:

- WIP: añadir login
- Más cambios login
- Corregir tests login
- Arreglar typo variable

結果として、明確に記述された単一のコミットが作成されます。

- Añadir funcionalidad completa de login de usuario con tests

そうした歴史の「美化」によって、批評家ははるかに理解しやすくなる。 各コミットが何に貢献するか そして、将来のトラブルシューティングを簡素化します。

インタラクティブなリベースで履歴を書き換える

リベースの基本バージョンは、コミットを別のベースに移動するだけです。しかし、その真髄は インタラクティブオーバーベースこれにより、最近のコミットの一覧が表示されたエディタが開き、それぞれのコミットに対してどのような処理を行うかを決定できます。

一般的な開始方法は、現在の HEAD から何コミット前まで「タッチ」するかを指定することです。例:

git rebase -i HEAD~6

このコマンドはGitに「このブランチから最新の6つのコミットを取得し、対話型リベースを準備してください」と指示します。Gitは、以下のような内容でデフォルトのエディタ(Vim、Nano、VS Codeなど)を開きます。

pick 7ed9c6e update version
pick ecb7ef3 empty commit 1
pick a323615 empty commit 2
pick 2c3d41d empty commit 3
pick d53c00f empty commit 4
pick 22dcc79 empty commit 5

# 549dd76..22dcc79 を 549dd76 にリベースします (6 コマンド)
#
# コマンド:
# p、選択= コミットを使用する
# r、言い換えコミットを使用するが、メッセージを編集する
# e、編集コミットとストップを使用して変更します。
# s、スカッシュ= 前のコミットをマージします
# f、修正= カボチャのように、ただしメッセージを破棄する
# x、exec = シェルコマンドを実行する
# b、break = ここでリベースを停止します
# d、ドロップ= 削除 コミット
#…

重要な点を一つ注目してください。 このファイル内の順序は、git log に表示される順序とは逆になっています。ファイル内では、最初にリストされているコミット(7ed9c6e)が6つの中で最も古く、最後にリストされているコミット(22dcc79)が最も新しいものです。これは、コミットを削除したり並べ替えたりする際に混乱を避けるための重要なポイントです。

ローカル履歴からテストまたは空のコミットを削除します

Gitフックをテストするために、オプションを使用して空のコミットを行っていたとします。 –空を許可する。 例えば、

git commit -m "commit with no changes" --allow-empty

このオプションを使用すると、ファイルに変更がない場合でもコミットを作成できます。これはテストに役立ちますが、 それは、実際には何の価値もないコミットで履歴を乱雑にする。ログが次のような状態になっていると想像してください。

git log --pretty=oneline --abbrev-commit

そして、あなたは以下を得ます。

22dcc79 (HEAD -> main, origin/main, origin/HEAD) empty commit 5
d53c00f empty commit 4
2c3d41d empty commit 3
a323615 empty commit 2
ecb7ef3 empty commit 1
7ed9c6e update version

このシナリオでは、有用な「バージョン更新」コミットのみを残し、他のすべてのコミットを削除したいとします。 空のコミット これらは単なるテストでした。これを行うには、最後の6つのコミットに対して対話型リベースを実行します。

git rebase -i HEAD~6

エディタを開くと、6つのコミットが表示されます。あなたの目標は、空のコミットに対応する行を削除することです。つまり、次のような行だけを残します。

pick 7ed9c6e update version

# 549dd76..22dcc79 を 549dd76 にリベースします (6 コマンド)
# …その他のコメント…

ファイル自体のヘルプセクションに示されているように、 行を削除すると、そのコミットは履歴から消えます。 新しく書き直されたバージョンでは、エディタを保存して閉じると、Gitは「バージョン更新」コミットのみを再現し、他のコミットは破棄します。

Originに新しいストーリーをアップロード中: 強制プッシュを使用

これまでのところ、すべてのリベース変更はリポジトリのローカルコピーに対して行われています。このクリーンアップをリモートリポジトリにも反映させたい場合は(たとえば、 起源/主な) リモート履歴を新しい履歴で上書きする必要があります。

これは 強制的な押し付けこれを示す一般的な方法は、プッシュ時にブランチ名の前に「+」記号を使用することです。

git push origin +main

そのプラス記号はGitに 履歴の分岐を無視してリモートブランチを上書きする 書き換えたバージョンで。もしあなたが単に次のようにしようとしただけなら:

git push origin main

Gitは、ローカルの履歴がリモートの履歴の直接の子孫ではない(リベースでコミットを書き換えたため)ことを警告し、データ損失を避けるためにプッシュを拒否します。

この強制的な後、 削除されたコミットは、origin/main からアクセスできなくなります。それらは他の同僚のreflogやローカルコピーに残っている可能性がありますが、実際にはリモート履歴は削除されています。

重大な警告:共有ブランチの書き換えはゲームではありません

履歴を書き換えることは、すべてを整理するのに素晴らしいように聞こえますが、重要な結果が伴います。新しいハッシュで新しいコミットを作成すると、 以前のコミットに基づいて作業を進めていた人は、全員混乱してしまうでしょう。だからこそ、有名な黄金律が存在するのです。

既に他のユーザーによって共有され、積極的に使用されているブランチはリベースしないでください。

実際には、これは以下の場合にリベースを使用しても安全であることを意味します。

  • ローカル機能ブランチ まだ公開していないもの、またはあなただけが使用しているもの。
  • 最近の取り組み まだ誰も頼っていないと分かっている人たち。

そして、それについて行うのはかなり悪い考えだ。

  • メイン、開発、または「公式」の支店 複数の人々の基盤となっているリポジトリから。
  • 共有作業ブランチ 複数の開発者がコミットを行っている場合。

共有ブランチの履歴を書き換えてから強制プッシュを実行すると、他のチームメイトは次のような結果になります。 そのブランチは、リモートリポジトリにはもはや存在しないコミットを指し示している。この混乱を解消するには、より高度な操作(新しい履歴へのリベースやリセットなど)を実行する必要があるだろう。

強制的に押す場合:シートベルトを着用した方が安全です

多くの場合、リベース後には強制的にプッシュする必要があります。一般的な方法は次のとおりです。

git push --force origin tu-rama

しかし、このバージョンでは、許可なくリモート上のあらゆるデータを上書きしてしまいます。他人の作業を誤って上書きしてしまうことを避けるため、Gitにはもっと優れたオプションが用意されています。 –リース契約による強制.

あなたがそうするとき:

git push --force-with-lease origin tu-rama

Gitはまず、 リモコンは、あなたが思っていた通りの状態のままです。 最後にプルまたはフェッチを行ったとき、それ以降に誰かがそのブランチに新しいコミットをプッシュしたことが検出されると、プッシュは拒否され、強制されません。これにより、他の人の変更を上書きすることを防ぎます。

要は、冷静さを保ちながら、リベースと強制プッシュを組み合わせるということだ。 あなたが管理する支店のみ また、可能な限り、追加のセキュリティ層として –force-with-lease を使用します。

リベースが失敗した場合の対処法:reflogが救世主となる

誰にでも起こりうることです。インタラクティブなリベースを開始し、誤って何かを削除または変更し、競合を誤って解決し、突然 あなたは仕事の一部を失ったようですね慌てる前に、Gitには切り札があることを思い出してください。 git reflog.

reflogは、新しいコミット、リセット、リベース、マージなど、すべてのHEADの動きをローカルに記録したものです。これのおかげで、ブランチが混乱する前の状態を特定し、ハードリセットによってその状態に戻ることができます。

典型的な流れは次のようになります。

git reflog

そこには、次のようなエントリのリストが表示されます。

abc1234 HEAD@{0}: rebase terminado
def5678 HEAD@{1}: checkout: moving from main to main
...

リベースを開始する前のコミットを特定します(例: def5678)そしてあなたは彼にこう答える:

git reset --hard def5678

このようにして、 オーバーレイを完全に解除します そして、以前の状態に戻ります。進行中のリベースを中止することもできます(処理の途中で完了していない場合)。その方法は簡単です。

git rebase --abort

このコマンドは、現在のリベースを開始する直前のブランチの状態を維持します。これは、望ましくない競合が発生した場合や、作業を続行するのが適切でないと判断した場合に最適です。

Git pull と git pull –rebase: わずかな違いが大きな影響をもたらす

もう一つよく疑問に思う点は、 git pull 通常および git pull --rebaseどちらもリモートからローカルブランチを更新しますが、その方法は異なります。

実行すると:

git pull

Gitは2つのステップで動作します。 git フェッチ リモートから変更を取り込んで、 git マージ それらを現在のブランチと結合します。リモートよりも上位にローカルコミットがある場合、これにより追加のマージコミットが作成される可能性があります。 不要なマージを含む履歴.

ただし、以下を実行すると:

git pull --rebase

Gitはフェッチを行い、 ローカルのコミット内容を、リモート上の更新されたバージョンに複製してください。結果は、あなたが次のようにした場合と同様です。 git fetch 続いて git rebase origin/mainそして、より簡潔で直線的な歴史を維持する。

そのため、多くのチームはプルを行う際にデフォルトでリベースを使用するようにGitを設定しています。これは、 自動マージコミットを回避する それらはあまり貢献しない。

履歴からファイルや画像を削除する:概要

問題は醜いコミットではなく、 リポジトリに決して到達するべきではなかったファイル: 間違った画像、間違った認証情報、巨大なファイルなど。GitHub にアクセスしてゴミ箱アイコンを探したくなるかもしれませんが、無効になっていて「ブランチにいる必要があります」のようなメッセージが表示される場合は、 GitHubでは、そのような方法で履歴を削除することはできません。.

正しい方法は通常、ローカルマシンからの履歴書き換え(対話型リベースやツールなど)を使用することです。 gitfilter-リポジトリ)そして強制プッシュを実行します。GitHub Desktop では、ブランチとコミットも管理できますが、 すべてのコミットからファイルを完全に削除する これは、ここで見られるような方法で歴史を書き換えることを意味します。

つまり、間違った画像や機密ファイルをアップロードしてしまい、それを履歴から「消去」したい場合、最後のコミットで削除するだけでは不十分です。 それが導入されたコミットを書き直す必要がある。 そして、力を入れて押し出す。これは繊細な作業なので、冷静に、チームと連携して行う必要がある。

何も壊さずに追い越しを練習するための実践的な流れ

追い越しを上達させる最良の方法は、他人の作業を妨害する心配のない、管理された環境で練習することです。簡単な流れは次のようになります。

  1. main から新しいブランチを作成します。 git checkout -b my-branch-tests.
  2. 小さなコミットをいくつか作成します(これらは些細な変更やテストファイルでも構いません)。
  3. メインブランチの進行状況をシミュレートするには、そこに新しいコミットを作成するか(または他の人に依頼する)。
  4. テストブランチに戻って実行してください git rebase main コミットがどのように再配置されるかを確認する。
  5. 試す git rebase -i HEAD~3 最新のコミットを結合、名前変更、または削除する。

この演習では、 履歴の変更前と変更後Gitが競合にどのように対応するか、また必要に応じてreflogを使用して以前の状態に戻す方法について解説します。ローカル環境で練習を重ねるほど、これらのテクニックを実際のプロジェクトブランチに適用する際に自信が持てるようになります。

Gitのリベースをマスターすれば、空のコミット、不要なマージ、意味不明なメッセージで溢れた混沌とした履歴を、重要な変更を明確かつ読みやすく整理したシーケンスに変換できます。対話型リベース、テストコミットの削除、散在する作業のグループ化、ブランチの線形更新、reflogや`--force-with-lease`による強制プッシュなどのツールを活用することで、リポジトリをより健全でプロフェッショナルな状態に保つことができます。ただし、これらの手法は自分の管理下にあるブランチにのみ適用し、共有履歴を書き換える前にチームに通知することを忘れないでください。

ドライブのバージョン履歴: 使い方とベストプラクティス
関連記事
ドライブのバージョン履歴: 高度な使用方法とベスト プラクティス

優先ソースとして追加