gitのコンフリクト解消
githubの使い方に慣れてきていろいろなPC経由で pushするようになったら、pullが必要だと言われ、pullしたら今度はコンフリクト(競合)が発生しました。今回はこれに対処します。
gitの基本操作のおさらい
コンフリクトはリモートの FETCH_HEAD とローカルブランチ間で起きる現象なので、まずはブランチについておさらいしておきます。まずは、ブランチの生成と選択方法です。「 git branch ブランチ名 」でブランチを生成します。このときブランチ名を指定しなければ既存のブランチの一覧が表示されます。ブランチを変更したい場合は checkout コマンドを利用します。
$ git branch branch1 ... $ git branch branch1 * main $ git checkout branch1 ... * branch1 main
checkout コマンドの内容を言い換えると「ワークツリー」とよばれる作業ディレクトリの内容を変更するコマンドです。この時、「 HEAD 」とよばれる最新コミットへのポインタも合わせて変更されます。
マージしたりして役目を終えたブランチは -dオプションで削除することができます。
$ git checkout main ... $ git branch -d branch1 ... $git branch * main
ちなみにHEADポインタは HEAD~ や HEAD~2 というふうにN世代前の指定の仕方があり、コミットを取り消すような場合にも利用します。
$ git reset --hard HEAD~
pullとfetch
pull作業は、fetchというリモートレポジトリの変更を取得する過程と、リモートレポジトリのHEADをローカルレポジトリに取り込むmergeのふたつの作業に分解されます。
これは別々に行うことができます。切り離してみてみると仕組みがよく理解できます。
まず、fetchです。データを取得するだけなので、ここでは競合は表示されません。fetchしたリモートポジジトリは無名のブランチの扱いとなりますが、これを確認したい場合は git branch -r を使います。
$ git fetch ... $ git branch -r origin/HEAD -> origin/main origin/main
ローカルレポジトリから fetchで取得したブランチをマージするのですが、この際mergeに渡す引数として「 FETCH_HEAD 」というリモート側のHEADを意味する特殊なポインタを指定します。
merge はローカルレポジトリをコミットした状態でないと使えませんが、stash コマンドで一旦変更を退避させておくこともできます。
$ git merge FETCH_HEAD CONFLICT (add/add): Merge conflict in js/lib/focus-order.js Auto-merging js/lib/focus-order.js ... Automatic merge failed; fix conflicts and then commit the result.
通常はマージは自動で行われます。ただ自動で処理できずにマージに失敗する(コンフリクトが発生する)と人的な措置を求められます。
ブランチを作成してから(cloneしてから)ローカル側で変更した箇所と同じ箇所でリモート側も変更されていたりするとこのような状況になります。
コンフリクトの状況は「 $ git status 」コマンドにより状況を確認できます。
$ git status On branch main Your branch and 'origin/main' have diverged, and have 2 and 1 different commits each, respectively. (use "git pull" to merge the remote branch into yours) You have unmerged paths. (fix conflicts and run "git commit") (use "git merge --abort" to abort the merge) Changes to be committed: new file: test.php Unmerged paths: (use "git add <file>..." to mark resolution) both modified: sample.php both added: QandA.pdf
Unmerget pathsにリストされた項目に対して修正が必要となります。
コンフリクトの確認と修正
コンフリクトが発生しているファイルがテキストファイルの場合は、任意のエディタからも確認や修正ができます。
その際、ファイル内のどこでコンフリクトが発生して修正が必要なのかは「マーカー」によって記されています。
「 <<<<<<< HEAD 」 から 「 ======= 」までの前半部が、ローカルレポジトリのデータで、「 ======= 」から「 >>>>>>> 」 までが後半でリモート側のデータです。
これは git diff コマンドによっても出力されます。
$ git diff diff --cc sample.php index 0267580,47ee72e..0000000 mode 100755,100644..100755 --- a/sample.php +++ b/sample.php @@@ -276,7 -276,7 +276,11 @@@ <button id="btn-addFile" type="button" class="" onclick="addFileFromBotton(event);" style="margin-bottom: 20px; background:#ccf; color: #333;"> 追加 </button> ++<<<<<<< HEAD + <!-- <button type="button">dummy</button> --> ++======= + <button type="button">dummy</button> ++>>>>>>> 572219c4e257fd135470c9898cf6cd5bbb490002 ... diff --cc QandA.pdf index e8fe411,e8fe411..0000000 mode 100755,100644..100755 Binary files differ ...
バイナリファイルの場合はマーカーが挿入できす、「 Binary files differ 」という表示のみにとどまります。これはエディタを使っての手動での修正も困難です。そのような場合は、特定のバージョンのファイルで上書きするなどの方法をとります。
変更後 git add を経て git commit することでマージが完了します。
修正不可能な競合を見つけたりして、マージを取り消したい場合は、「 git merge --abort 」を実行します。これによりマーカーは除去されます。
$ git merge --abort
コンフリクトをまとめて解決
ローカル側かリモート側のどちらかを選択してまとめてコンフリクトを解決させることができます。これは多くのデータでコンフリクトが発生しているような場合などに有効です。
git checkout --ours コマンドを使うとすべての競合をローカル側のデータで解決します。逆の場合(マージの引数で指定する側ここではFETCH_HEAD)で更新したい場合はは --theirs とします。
$ git checkout --ours または $ git checkout --theirs ... $ git add -A ... $ git commit -m "All conflicts resolved." ... $ git push ...