GitのMergeコンフリクトの簡単な解決

「Habrahabr」の読者に、「 Gitでの痛みのないマージ競合解決 」の翻訳を提供します。

ブログblog.wuwon.id.auから。







私の日々の仕事では、多くのgitブランチを頻繁に処理する必要があります。 これらは、中間リリースのブランチ、一部のクライアントでサポートされている古いAPIのブランチ、または実験的なプロパティのブランチです。 Gitモデルでのブランチ作成の容易さにより、開発者はますます多くのブランチを作成するようになり、原則として、これらすべてのブランチを維持し、他のブランチと定期的にマージする必要がある場合、多数のブランチの負担が非常に顕著になります。









マージはコードを最新の状態に保つために非常に重要であり、原則として、マージ中のミスは単純なコミットでのミスよりも多くの頭痛の種になります。 残念なことに、マージエラーは珍しくありません。そもそもマージにはいくつかの親ブランチがあるからです。 ブランチのマージの履歴を分析する場合でも、競合を解決するためにどのような変更が行われたかを理解することは非常に困難です。 第二に、失敗した合併をキャンセルすると 、大きな頭痛の種になります。 第三に、ブランチの概念そのものには多くのユーザーが関係しているため、マージの競合のほとんどは他の誰かのコードを操作するときに発生します 常に合併は、1つまたは別のブランチで働いていた同じ人によって行われます。 結論として、マージが非常に簡単な場合にミスを犯すと、修正するのが難しくなり、見つけるのが難しくなります。 したがって、ブランチをマージするプロセスの研究と理解に費やした時間は、興味を持って報われます。







驚くべきことに、マージに使用できるツールとインターフェイスの多くは、このプロセスを効率的に実行するための十分な装備がないことがわかりました。 多くの場合、プログラマは単にgit mergeチームが彼のためにすべての仕事をすることを望んでいます。 ただし、競合が発生した場合、マージ戦略は通常、競合する行の周りのコードをすばやく確認し、この特定のコードが他のコードよりも好ましいと直感的に推測します。







この記事では、競合解決のプロセスが段階的に正確であり、そこで何かを推測する必要がないことを実証したいと考えています。







青いバラ(バラは青)



あなたのチームが、これらの目的のために予約されたリポジトリに詩を書くよう依頼されたと仮定しましょう。 (なんて悪夢でしょう!)そして、最も重要なことはあなたに委ねられました-マスターブランチからの最後の修正をベータブランチにマージすることです。 したがって、ベータブランチに切り替えて、次のコマンドを実行します。







$ git merge master Auto-merging roses.txt CONFLICT (content): Merge conflict in roses.txt Automatic merge failed; fix conflicts and then commit the result.
      
      





うわー、これは矛盾です。 gitが参照するファイルを表示することにしました。







 $ cat roses.txt <<<<<<< HEAD roses are #ff0000 violets are #0000ff all my base are belong to you ======= Roses are red, Violets are blue, All of my base Are belong to you. >>>>>>> master (Listing 1)
      
      





いいね! リスト1に示すように、ファイル全体が競合状態にあります。 どのファイルオプションが正しいですか? 両方のオプションが正しく見えます。 上位バージョンは、HTMLスタイルの要素が色分けされ、小文字のみを使用したハッカースタイルで記述されています。 下のバージョンは、句読点と大文字を使用してより自然に見えます。







これがプロジェクトの場合は、1つのオプションを選択してこの合併を終了できます。 しかし問題は、これがあなたの詩ではないことです。あなたはこの詩を読んだことがなく、執筆や編集の責任を負いません。また、間違った決定の場合、誰かの苦労が忘れ去られる可能性があることを完全に理解しています。 ただし、これらのブランチをマージする責任はあなたにあります。 どうする?







ベースに戻る



秘Theは、リスト1が正しいマージを完了するために必要な完全な情報を提供しないことです。 実際、合併プロセスには4つの重要な情報(状態)が関係していますが、そのうち3つは紛争の解決に必要なものです。 リスト1の場合、Gitは2つの状態のみを提供しました。







次の図は、これらの4つの状態を示しています。







4つの州







状態(B)と©はそれぞれ、マスターブランチとベータブランチの現在の位置(ヘッド)に関連しています。これら2つの状態はリスト1とまったく同じです。状態(D)はマージの結果で、最終的に取得/生成するもの(ほとんどの場合、Gitは自動的に状態(D)を生成します)。 一番上の状態(A)は、マスターブランチとベータブランチをマージするためのベース(基本)です。 マージベース(A)は、マスターブランチとベータブランチの最後の共通の祖先であり、現時点では、このマージベースが一意であると仮定します。 後で見るように、状態(A)は紛争解決において重要な役割を果たします。 図では、それぞれ状態(A)-(B)と(A)-©の間の変化を表すデルタ1と2も反映しています。 状態(A)、(B)および©デルタ1および2を知ることは簡単に取得(計算)できます。 デルタ1と2は、複数のコミットで構成できることに注意してください。 ただし、ここでは、すべてのデルタがモノリシックであると想定しています。







状態(D)を取得する方法を理解するには、マージ操作が何をしようとしているかを理解する必要があります。 状態(D)は、それぞれmasterブランチとbetaブランチに加えられた変更の組み合わせである必要があります。 つまり 言い換えれば、デルタ1と2の組み合わせです。この考え方は表面上は単純であり、デルタがファイルの階層化(重複)部分に影響する特別な場合を除き、ほとんどの場合、人間の介入は必要ありません。 この状況では、デルタ1と2を比較して、マシンが結果(D)を生成するのを支援する必要があります。







違いを特定する



各ブランチに加えられた変更を見つけるには、状態(A)のマージベースがどのように見えるかを知る必要があります。 マージデータベースに関する情報を取得する最も簡単な方法は、merge.conflictstyleオプションをdiff3に設定することです







 $ git config merge.conflictstyle diff3
      
      





このオプションを有効にした後、再度マージを試み(git reset --hard; git merge master)、競合するファイルを再度検査します。







 $ cat roses.txt <<<<<<< HEAD roses are #ff0000 violets are #0000ff all my base are belong to you ||||||| roses are red violets are blue all my base are belong to you ======= Roses are red, Violets are blue, All of my base Are belong to you. >>>>>>> master (Listing 2)
      
      





中央に3番目のフラグメントがあります。これは、合併または状態の基礎です(A)。 変更ははっきりと見えます:ベータ(HEAD)ブランチでは、人間の色の名前がHTMLコードに置き換えられ、大文字と句読点がマスターブランチに追加されました。 この知識に基づいて、結果に大文字、句読点、HTMLカラーコードが含まれることがわかりました。







原理的には、結果が達成されたため、これは完了できたはずです。 しかし、より良い解決策があります。







グラフィカルなマージ(GUIマージ)



マージ競合の単純なテキスト表現は、単純な場合にはその役割を果たしますが、実際には競合はより過激で複雑になる可能性があります。 このような場合、グラフィカルツールが役立ちます。 私が選んだのは、 meldと呼ばれるPythonで書かれたシンプルなツールでしたが、3列形式でマージを表示できる他のグラフィカルツールが登場します。







グラフィカルツール(インストールする必要があります)を使用するには、gitが競合があることを訴えた後、次のコマンドを入力します。







 $ git mergetool
      
      





これにより、使用するマージプログラムが尋ねられます。meldと入力してEnterキーを押します。 プログラムウィンドウの外観は次のとおりです(暗黙的なオプションmerge.conflictstyleは有効になっていません)。













情報が並んで表示されているという事実にもかかわらず、リスト2にあった必要なフラグメントは表示されません。ここでは、このファイルroses.txt.LOCAL.2760.txtが表示されるマージベースのフラグメント(状態(A))は表示されません。左の列と右の列のファイルroses.txt.REMOTE.2760.txtと中央のファイルは失敗したマージです。 つまり 実際、状態(B)、©、および失敗した状態(D)が表示されましたが、状態(A)はありません...







本当に行方不明ですか? 古き良き端末をチェックインしましょう:







 $ ls -1 roses.txt roses.txt.BACKUP.2760.txt roses.txt.BASE.2760.txt roses.txt.LOCAL.2760.txt roses.txt.REMOTE.2760.txt
      
      





関心のあるファイル:roses.txt.BASE.2760.txtが表示されます。 これはマージデータベースファイルです。 ここで、データベースに関連してmasterブランチとbetaブランチに加えられた変更を見つける必要があります。 これを行うには、meldへの2つの別個の呼び出しを使用します。







 $ meld roses.txt.LOCAL.2760.txt roses.txt.BASE.2760 & $ meld roses.txt.BASE.2760 roses.txt.REMOTE.2760.txt &
      
      





(誰かが最初の呼び出しで引数の順序を変更して、どちらの場合でもデータベースファイルが左の列になるようにする方が合理的であることに気付くかもしれませんが、この順序は、ベースが中央に残る3列のビューの類似性を保持します。 )実行の結果は、次のように2つのウィンドウになります。













最初のウィンドウを右から左に、2番目のウィンドウを左から右に読むと、各ブランチでどのような変更が発生したかが1日として明確になります。 meldはすべての変更を親切に強調しているので、細かく目立つ編集をスキップすることはほとんど不可能です(競合解決リスト1またはリスト2のテキスト表現を表示するときに、前置詞「の」の追加に気づいた人はいますか?)







この知識を武器に、3列表示に戻って変更を加えることができます。 手動マージの私の戦略は、より重要な変更(この場合はmaster / REMOTEつまりベータ)からブランチからすべてのテキストを取得し、その上でステップバイステップの編集を行うことです。 別のブランチ(マスター)で変更を行います。 起こったことは次のとおりです。













そして今、すべて一緒(All Together Now)



この3ウィンドウの競合解決方法が、私が見つけたのと同じくらい便利であることを願っています。 ただし、競合を解決するたびに新しいmeld呼び出しを手動で起動することはあまり便利ではありません。 方法は、git mergetoolコマンドが呼び出されたときに3つのウィンドウすべてが自動的に開くようにgitを構成することです。 これを行うには、PATH環境変数(たとえば、$ HOME / bin / gitmerge)に配置する必要がある実行可能スクリプトを、次の内容で作成できます。







 #!/bin/sh meld $2 $1 & sleep 0.5 meld $1 $3 & sleep 0.5 meld $2 $4 $3
      
      





そして、以下を〜/ .gitconfigファイルに追加します:







 [merge] tool = mymeld [mergetool "mymeld"] cmd = $HOME/bin/gitmerge $BASE $LOCAL $REMOTE $MERGED
      
      





これで、次回git mergetoolコマンドを実行して競合を解決すると、3つのウィンドウがすべて開きます。







    BASE  LOCAL    BASE  REMOTE  - 
      
      





前述の3つのウィンドウを使用してこのような競合の解決に慣れると、プロセスがより系統的かつ機械的になっていることがわかります。 ほとんどの場合、マージに使用するオプションを理解するために、各ブランチのコードを読んで理解する必要さえありません。 コミットの正確さにはるかに自信を持つため、推測する必要がなくなります。 この自信のために、紛争解決はエキサイティングな活動になっていると感じるでしょう。







翻訳者からのボーナス



tmuxおよびn?Vimを使用している場合は、次のgitmergeスクリプトをお勧めします。







 #!/bin/sh sn=gitmerge tmux new-session -d -s "$sn" -n "diff3" "nvim -d $2 $4 $3" tmux split-window -t "$sn:1" -v "nvim -d $2 $1" tmux split-window -t "$sn:1" -h "nvim -d $1 $3"
      
      





:〜/ .tmux.confでこのオプションを使用しない場合、最後の2行で"$sn:1"



"$sn:0"



に変更する必要があります







したがって、以下を〜/ .gitconfigに追加します







 [mergetool "gitmerge"] cmd = $HOME/bin/gitmerge \"$BASE\" \"$LOCAL\" \"$REMOTE\" \"$MERGED\" [merge] tool = gitmerge
      
      





ワークフローの競合解決は次のようになります。







git merge masterワークフロー







今のところ、質問を無視し(マージは[y / n]に成功しましたか?)、gitmergeと呼ばれるセッションに切り替えます(TMUXPREFIX + sの組み合わせ):







sessiowスイッチ







1つの画面に3つのウィンドウが表示されます。 数字は、tmuxの分割(ペイン)、状態に対応する文字を示します。 競合を解決するための編集、つまり 状態(D)を編集して保存します。 その後、元のtmux'aセッションに戻り、合併が成功したことを確認します。







git merge master







git rebase master



個人的には、最初にベータブランチでマスターをリベースし、その後マスターに切り替えてからGitマージベータを実行する方が正しいと考えています。 原則として、3ウィンドウビューを除いて、ワークフローはそれほど変わりません。







git merge masterワークフロー







gitmergeセッションに切り替えます







sessiowスイッチ







状態(B)と©が入れ替わっていることに注意してください。







git merge master







リポジトリ例を少なくとも1回試して、上記のスキームに従って競合の解決を行うことをお勧めします 。 個人的には、「彼らを受け入れる」または「あなたを受け入れる」を選択するのはもう不思議ではありません。








All Articles