Gitリベースの使用を停止する理由

Gitを数年間使用した後、日々のワークフローの一環として、ますます高度なGitコマンドを使用するようになりました。 Gitのリベースを発見してすぐに、私はそれをすぐに毎日のワークフローに組み込みました。リベースに精通している人は、それがどれほど強力なツールであり、常に使用することの魅力を知っています。しかし、私はすぐに、リベースが最初にそれを始めたときに明らかでないいくつかの課題を提示することを発見しました。プレゼンテーションする前に、マージとリベースの違いを簡単に説明します。

まず、機能ブランチをmasterと統合する基本的な例を考えてみましょう。マージすることにより、2つのブランチ間のマージを表す新しいコミットgを作成します。コミットグラフは、何が起こったかを明確に示しており、大きなGitレポジトリでおなじみの「train track」グラフの輪郭を見ることができます。

マージの例

または、マージする前にリベースすることもできます。コミットが削除され、機能ブランチがマスターにリセットされます。その後、機能の上にコミットが再適用されます。これらの再適用されたコミットの差分は、通常、元のコミットと同じですが、親コミットが異なるため、SHA-1キーも異なります。

リベースの例

機能のベースコミットをbからcに変更し、文字通り再配置しました。フィーチャーに対するすべてのコミットはマスターの直接の子孫であるため、フィーチャーをマスターにマージすることは早送りマージになりました。

早送りマージの例

マージアプローチと比較すると、結果の履歴は分岐がなく線形です。読みやすさの向上は、マージする前にブランチのリベースを好む理由であり、他の開発者にも当てはまると予想しています。

ただし、このアプローチには、明らかでない可能性があるいくつかの課題があります。

機能でまだ使用されている依存関係がマスターで削除された場合を考えます。機能がマスターにリベースされている場合、最初に再適用されたコミットはビルドを中断しますが、マージの競合がない限り、リベースプロセスは中断されずに続行されます。最初のコミットからのエラーは、後続のすべてのコミットに存在するため、コミットのチェーンが破損します。

このエラーはリベースプロセスが完了した後にのみ発見され、通常は新しいバグ修正コミットgを上に適用することで修正されます。

失敗したリベースの例

ただし、リベース中に競合が発生した場合、Gitは競合するコミットで一時停止し、続行する前に競合を修正できます。コミットの長いチェーンのリベースの途中で競合を解決することは、しばしば混乱を招き、正しいものを見つけるのが難しく、潜在的なエラーの別の原因となります。

エラーの導入は、リベース中に発生する場合、さらに問題となります。このように、履歴を書き換えると新しいエラーが発生し、履歴が最初に書き込まれたときに導入された本物のバグを隠す可能性があります。特に、これにより、おそらくGitツールボックスで最も強力なデバッグツールであるGit bisectの使用が難しくなります。例として、次の機能ブランチを検討してください。ブランチの終わりに向かってバグを導入したとしましょう。

最後に向かってバグが導入されたブランチ

ブランチがmasterにマージされてから数週間後まで、このバグを発見できないかもしれません。バグを引き起こしたコミットを見つけるには、数十または数百のコミットを検索する必要がある場合があります。このプロセスは、バグの存在をテストするスクリプトを作成し、コマンドgit bisect run を使用してGit bisectで自動的に実行することで自動化できます。

Bisectは、履歴を介して2分検索を実行し、バグを導入したコミットを特定します。以下に示す例では、壊れたコミットにはすべて、探している実際のバグが含まれているため、最初の障害のあるコミットを見つけることができます。

Gitの二分法の成功例

一方、リベース中に追加の壊れたコミット(ここではdとe)を導入すると、二分法が問題になります。この場合、Gitがコミットfを悪いものとして識別することを期待していますが、テストを中断する他のエラーが含まれているため、誤ってdinを識別します。

Gitの二等分に失敗した例

この問題は、最初に思われるよりも大きいです。

なぜGitを使用するのですか?コード内のバグの原因を追跡するための最も重要なツールだからです。 Gitは私たちのセーフティネットです。リベースすることで、線形の履歴を達成したいという欲求を支持して、この優先順位を低くします。

しばらく前、システムのバグを追跡するために数百のコミットを二分する必要がありました。障害のあるコミットは、同僚が実行した障害のあるリベースのために、コンパイルされなかったコミットの長いチェーンの途中にありました。この不必要で完全に回避可能なエラーにより、コミットの追跡に1日近く費やすことになりました。

では、リベース中にこれらの壊れたコミットのチェーンをどのように回避できますか? 1つのアプローチは、リベースプロセスを終了させ、コードをテストしてバグを特定し、履歴に戻ってバグが発生したバグを修正することです。このために、インタラクティブなリベースを使用できます。

別のアプローチとしては、リベースプロセスのすべてのステップでGitを一時停止し、バグをテストして、すぐに修正することです。

これは面倒でエラーを起こしやすいプロセスであり、それを行う唯一の理由は線形の履歴を達成することです。もっと簡単で良い方法はありますか?

がある; Gitマージ。すべての競合が単一のコミットで解決される、シンプルなワンステッププロセスです。結果のマージコミットは、ブランチ間の統合ポイントを明確に示し、履歴は実際に何が起こったか、いつ起こったかを示します。

あなたの歴史を真実に保つことの重要性を過小評価すべきではありません。リベースすることにより、あなたは自分自身とあなたのチームに嘘をついています。コミットは、別のコミットに基づいて昨日実際に書き込まれたときに、今日書き込まれたふりをします。元のコンテキストからコミットを取り除いて、実際に起こったことを隠しています。コードがビルドされていることを確認できますか?コミットメッセージがまだ意味を成していると確信できますか?あなたは自分の歴史をきれいにし、はっきりさせていると信じているかもしれませんが、結果は正反対かもしれません。

将来のエラーや課題がコードベースにもたらすものを言うことは不可能です。ただし、実際の履歴は、書き換えられた(または偽の)履歴よりも有用であることを確信できます。

人々がブランチをリベースする動機は何ですか?

それは虚栄心だという結論に達しました。リベースは純粋に審美的な操作です。一見クリーンな歴史は開発者として私たちにアピールしますが、技術的または機能的な観点から正当化することはできません。

非線形履歴。ポール・ハンマンの図

非線形の履歴のグラフである「列車の軌跡」は、威圧的なものになります。彼らは確かにそもそも私にそのように感じましたが、彼らを怖がらせる理由はありません。 GUIベースとCLIベースの両方で、複雑なGit履歴を分析および視覚化できるすばらしいツールが多数あります。これらのグラフには、何が起こったのか、いつ起こったのかに関する貴重な情報が含まれていますが、線形化しても何も得られません。

Gitは、非線形の歴史のために作られ、奨励されています。それが気に入らない場合は、線形履歴のみをサポートするよりシンプルなVCSを使用する方が良いかもしれません。

歴史を真実に保つべきだと思います。それを分析するツールに慣れて、書き直そうという誘惑に負けないでください。書き換えの報酬は最小限ですが、リスクは大きいです。次回、卑劣なバグを追跡するために履歴を二分するときに感謝します。

この投稿の下書きに関する貴重なフィードバックを寄せてくれたPaul HammantとAslakHellesøyに感謝します。非線形の歴史の図についてポール・ハマントに感謝します。彼の優れたサイトは、強くお勧めします。最初にこの投稿を書くことを奨励してくれたAslakに感謝します。

この投稿は、JavaZone 2016でノルウェー語で行った講演に基づいています:https://vimeo.com/182068915