コードリポジトリの履歴を書き換える、またはgit push-fを実行できる理由





若いパダワンがgitリポジトリにアクセスして受け取る最初の警告の1つは、「黄色い雪を絶対に食べないでgit push -fください」です。これは、初心者のソフトウェアエンジニアが学ぶ必要のある数百の格言の1つであるため、これを行うべきではない理由を明確にするために時間をかける人は誰もいません。それは赤ちゃんと火のようなものです:「試合は子供のためのおもちゃではありません」そしてそれはそれだけです。しかし、私たちは人として、そして専門家として成長し発展し、ある日、「なぜ、実際に?」という質問があります。完全な成長で上昇します。この記事は、「コミットの履歴をいつ書き直すことができ、いつ書き直す必要があるか」というトピックに関する社内ミーティングに基づいて書かれています。







一部の企業のインタビューでこの質問に答える能力は、上級職のインタビューの基準であると聞いています。しかし、その答えをよりよく理解するには、なぜ履歴の書き換えがまったく悪いのかを理解する必要がありますか?



これを行うには、次に、gitリポジトリの物理構造をすばやく確認する必要があります。レポデバイスについてすべて知っていると確信している場合は、この部分をスキップできますが、それを見つける過程でさえ、私は自分自身のためにかなり多くの新しいことを学び、古いもののいくつかはあまり関連性がないことがわかりました。



最下位レベルでは、gitリポジトリはオブジェクトとそれらへのポインタのコレクションです。各オブジェクトには、オブジェクトの内容に基づいて計算される独自の40桁のハッシュ(16進数の20バイト)があります。







Git Community Book から抜粋した



図主なオブジェクトタイプは、blob(ファイルの内容のみ)、tree(blobおよびその他のツリーへのポインターのコレクション)、およびcommitです。タイプcommitのオブジェクトは、ツリー、前のコミット、およびサービス情報(日付/時刻、作成者、コメント)へのポインターにすぎません。



私たちが操作に慣れているブランチとタグはどこにありますか?そして、それらはオブジェクトではなく、単なるポインターです。ブランチはその中の最後のコミットを指し、タグはリポジトリ内の任意のコミットを指します。つまり、IDEまたはGUIクライアントでコミットサークルが付いた美しく描かれたブランチを見ると、それらはオンザフライで構築され、ブランチの端から「ルート」までコミットチェーンに沿って実行されます。リポジトリの最初のコミットには前のコミットがなく、ポインタの代わりにnullがあります。



理解しておくべき重要なポイント:同じコミットが複数のブランチに同時に表示される可能性があります。新しいブランチが作成されたときにコミットはコピーされず、コマンドが発行された時点でHEADがあった場所から「成長」し始めgit checkout -b <branch-name>ます。



では、なぜリポジトリの履歴を書き換えることは有害なのでしょうか。







まず、これは明らかです。エンジニアリングチームが作業しているリポジトリに新しいストーリーをアップロードすると、他の人が変更を失う可能性があります。このコマンドgit push -f は、ローカルバージョンにないすべてのコミットをサーバー上のブランチから削除し、新しいコミットを書き込みます。



何らかの理由で、チームgit pushが「安全な」キーを長い間持っていることを知っている人はほとんどいません--force-with-leaseこれにより、他のユーザーによってリモートリポジトリにコミットが追加された場合、コマンドが失敗します。代わりに使用することを常にお勧めします-f/--force



コマンドgit push -fが有害であると見なされる2番目の理由は、書き換えられた履歴を持つブランチを、それが保存されたブランチとマージしようとすると(より正確には、書き換えられた履歴から削除されたコミットが保存された)、(数によって)多くの競合が発生することです。実際にコミットします)。これには簡単な答えがあります。GitflowまたはGitlabFlowに注意深く従えば、そのような状況はほとんど発生しません。



そして最後に、履歴の書き換えには不快な側面があります。いわばブランチから削除されたコミットは、実際にはどこにも消えず、単にリポジトリに永久に残ります。ささいなことですが、不快です。幸い、git開発者はこの問題にもgaveragecollectionコマンドで対処していますgit gc --prune。ほとんどのgitホスト、少なくともGitHubとGitLabは、これをバックグラウンドで時々実行します。



それで、リポジトリの履歴を変更することへの恐れを払拭したので、最終的に主要な質問に移ることができます:なぜそれが必要であり、いつそれが正当化されるのですか?



実際、ほぼすべてのアクティブなgitユーザーが少なくとも1回は履歴を変更したと確信しています。最後のコミットで問題が発生したことが突然判明したとき、コードに迷惑なタイプミスが忍び込み、それからではなくコミットを行いました。ユーザー(仕事ではなく個人の電子メールから、またはその逆)、新しいファイルを追加するのを忘れた(私のように、使用したい場合git commit -a)。コミットの説明を変更しても、ハッシュは説明からカウントされるため、コミットを書き直す必要があります。



しかし、これは些細なケースです。もっと面白いものを見てみましょう。



数日間見た大きな機能を作成し、毎日の作業結果をサーバー上のリポジトリに送信し(4〜5回のコミット)、レビューのために変更を送信したとします。 2、3人のたゆまぬレビュアーが、編集に関する大小の推奨事項を提示したり、わき柱を見つけたりしました(さらに4〜5回のコミット)。次に、QAは、修正が必要ないくつかのエッジケースを見つけました(さらに2〜3回のコミット)。そして最後に、統合中に、いくつかの非互換性が発見されたか、自動テストが開始されました。これも修正する必要があります。



見ずにマージボタンを押すと、「マイ機能、1日目」、「2日目」、「テストの修正」、「レビューの修正」などのコミットがメインブランチに追加されます(多くの場合、昔ながらの方法でマスターと呼ばれます)。等これはもちろん、現在GitHubとGitLabの両方にあるスカッシュモードに役立ちますが、注意する必要があります。まず、コミットの説明を予測できないものに置き換えることができ、次に、機能の作成者を置き換えることができます。マージボタンを押した人(私たちの国では、これは通常、リリースエンジニアが今日の展開を構築するのを助けるロボットです)。したがって、最も簡単なことは、リリースへの最終的な統合の前に、を使用してブランチのすべてのコミットを1つにまとめることgit rebaseです。



しかし、オリビエサラダを彷彿とさせるレポの歴史ですでにコードレビューにアプローチしていることもあります。これは、機能が十分に分解されていないために数週間ソーイングされている場合、または適切なチームがこのためのカンデラブラムで殴打されているにもかかわらず、開発プロセス中に要件が変更された場合に発生します。たとえば、2週間前にレビューのために私に届いた実際のマージリクエストは次のとおり







です。「不正使用の報告」ボタンが自動的に届きました。これは、約2000行が変更された50回のコミットでリクエストを特徴付けることができるためです。そして、どのように、それをレビューするのだろうか?



正直なところ、このレビューを開始するように強制するのに2日かかりました。そして、これはエンジニアにとっては正常な反応です。同様の状況にある誰かが、見ずに承認を押すと、妥当な時間内に、十分な品質でこの変更を確認する作業を実行できないことに気付きます。



しかし、友人の生活を楽にする方法があります。問題のより良い分解に関する予備作業に加えて、メインコードの記述が完了した後、その記述の履歴をより論理的な形式に変換し、それぞれにグリーンテストを使用してアトミックコミットに分割できます:「新しいサービスとそのトランスポートレイヤーを作成しました」、「モデルを構築して記述不変条件のチェック "、"検証と例外処理の追加 "、"テストの作成 "。

これらのコミットはそれぞれ個別に確認でき(GitHubとGitLabの両方でこれを実行できます)、タスクを切り替えるときや休憩中にレイドで実行できます。キー



git rebase持つ同じものは、これを行うのに役立ちます--interactive。パラメータとして、コミットのハッシュを渡す必要があり、そこから履歴を書き換える必要があります。写真の例のように、最後の50のコミットについて話している場合は、次のように書くことができますgit rebase --interactive HEAD~50(「50」の代わりに番号を使用してください)。



ちなみに、タスクの作業中にマスターブランチを自分に追加した場合は、マージコミットとマスターからのコミットが足元で混乱しないように、最初にこのブランチをリベースする必要があります。



gitリポジトリの内部に関する知識があれば、マスターでリベースがどのように機能するかを簡単に理解できるはずです。このコマンドは、ブランチ内のすべてのコミットを取得し、最初のコミットの親をマスターブランチ内の最後のコミットに変更します。図を参照してください:図









本Pro Gitから取られてい



ますC4とC3の変更が競合する場合、競合が解決された後、コミットC4はその内容を変更するため、2番目の図では名前がC4 'に変更されます。



このようにして、変更のみで構成され、マスターのトップから成長するブランチになります。もちろん、マスターは最新でなければなりません。サーバーからのバージョンを使用することができます:(git pull --rebase origin/masterご存知のように、git pull同等git fetch && git mergeであり、キー--rebaseはgitにマージではなくリベースを強制します)。



いよいよに戻りましょうgit rebase --interactive..。プログラマーのためにプログラマーが作成したもので、その過程で人々がどのようなストレスを感じるかを理解し、ユーザーの神経をできるだけ節約し、過度の負担をかけないようにしました。これが画面に表示されるものです。





これは、人気のあるGuzzleパッケージのリポジトリです。リベースは彼にとって役立つようです...



生成されたファイルがテキストエディタで開きます。以下に、ここで行うことの詳細情報を示します。次に、簡単な編集モードで、ブランチのコミットをどうするかを決定します。すべてがスティックのように単純です:ピック-そのまま、言い換え-コミットの説明を変更、スカッシュ-前のものとマージ(プロセスは下から上に動作します、つまり、前のものは下の行です)、ドロップ-完全に削除、編集-そしてこれは興味深いのは、停止してフリーズすることです。 gitが編集コマンドを検出すると、コミットの変更がすでにステージモードに追加されている位置になります。このコミットで何でも変更し、その上にさらにいくつかを追加してgit rebase --continueから、リベースプロセスを続行するようにコマンドを実行できます。



ああ、ちなみに、コミットを交換することができます。これにより競合が発生する可能性がありますが、一般に、リベースプロセスで完全に競合が発生しないことはめったにありません。彼らが言うように、彼らは頭を脱いだ後、髪を求めて泣きません。



混乱してすべてがなくなったように見える場合は、git rebase --abortすべてをすぐに戻す緊急イジェクトボタンがあります



リベースを数回繰り返して、ストーリーの一部だけに触れ、残りの部分はピックで触れないままにして、陶芸家の水差しのように、ストーリーをますます完成させておくことができます。上で書いたように、各コミットのテストが緑色になるようにすることをお勧めします(このため、編集は完全に役立ち、次のパスでスカッシュします)。



同じファイル内のいくつかの変更を異なるコミットに分解する必要がある場合に役立つ別のエアロバティックス- git add --patch。それ自体は便利ですが、editディレクティブと組み合わせると、1つのコミットを複数に分割し、個々の行のレベルで実行できます。これは、私が間違っていなければ、GUIクライアントもIDEも許可しません。



もう一度、すべてが正常であることを確認して、このチュートリアルを開始した理由で、最終的に安心して何かを行うことができますgit push --force。ああ、それはもちろん--force-with-leaseです!







最初は、このプロセス(マスターでの最初のリベースを含む)に1時間、または機能が実際に普及している場合は2時間かかる可能性があります。しかし、これでも、レビュー担当者が最終的にリクエストを受け入れるように強制するのを2日間待ち、それを通過するまでさらに2、3日待つよりもはるかに優れています。将来的には、30〜40分で収まる可能性があります。競合解決ツールが組み込まれたIntelliJ製品(完全な開示:FunCorpはこれらの製品の代金を従業員に支払います)は、これに特に役立ちます。



最後に警告したいのは、コードレビュー中にブランチ履歴を書き直さないことです。良心的なレビュー担当者は、IDEを介してコードを確認し、テストを実行できるようにするために、コードをローカルで複製する場合があることに注意してください。



最後まで読んでくださった皆様、ありがとうございました!この記事があなただけでなく、レビューのためにあなたのコードを受け取る同僚にも役立つことを願っています。あなたがいくつかのクールなgitハックを持っているなら-コメントでそれらを共有してください!



All Articles