共有リポジトリの変更内容を取り込む仕組みとしては、「プル」と「フェッチ」があるんだよ。
ふ〜ん、プルとフェッチって何が違うの?
プルは、共有リポジトリのコミットをローカルリポジトリと作業ツリーに取り込むの。作業ツリーの状態は、ほかのユーザーの変更(コミット)が反映され状態になるの。フェッチは共有リポジトリの変更をローカルリポジトリにのみ取り込み、作業ツリーには反映しないの……。作業ツリー(編集するファイル)に、反映するには、ローカルリポジトリに取り込んだ修正をマージする必要があるんだよ。
う〜ん、動きは何とな〜く分かったんだけど、やっぱり、どう違うのかよく分かんないなぁー。どういうときにプルを使って、どういうときにフェッチを使うのかなぁ?
そうね。簡単に長所と短所をまとめると、下記のような感じかな。
プル | フェッチ | |
---|---|---|
長所 | 即座に作業ツリーに反映されるので、手間が掛からない | 共有リポジトリの変更を取り込む前に変更点を確認できる |
短所 | 取り込む前に、作業ツリーにどのような変更を及ぼすのか確認しづらい | マージの手間が掛かる |
うーん、難しいなぁ。神経質なA型の人は他人の変更を細かく確認したいだろうからフェッチを使って、大ざっぱなO型の人は「取りあえず取り込んじゃえ、えい!!」って感じでプルを使えばいいのかな。あ、でもB型の人はどうすればいいんだろう……。
まぁ、血液型でどっちを使うのか決めるのは悪くない選択肢ね。実際のコマンドの説明は亜琉美ちゃんお願いね。
承知しました。プルは、下記コマンドで行うことができます。
$ git pull
フェッチは、下記コマンドで行います。
$ git fetch
ただし、フェッチはローカルリポジトリへ更新を取り込んだだけで、作業ツリーや作業中のブランチには変更は反映されません。「git fetch」でローカルリポジトリに取り込んだ変更内容を確認するには、下記のようにします。
$ git diff FETCH_HEAD
確認した変更内容を作業ツリー(正確には現在作業しているブランチ)へ取り込むには、下記のようにします。
$ git merge FETCH_HEAD
この変更を取り込む作業を「マージ」って呼ぶんだよ。
プルを行うときに、下記のようなエラーが出ることがあります。
$ git pull remote: Counting objects: 55, done. remote: Compressing objects: 100% (13/13), done. remote: Total 31 (delta 22), reused 27 (delta 18) Unpacking objects: 100% (31/31), done. From https://github.com/alminium/alminium b3c9e63..ee81015 master -> origin/master Updating b3c9e63..ee81015 error: Your local changes to 'README.txt' would be overwritten by merge. Aborting. Please, commit your changes or stash them before you can merge.
これは、README.txtが作業ツリー上で変更されているので、変更の取り込みに失敗したことを示しています。このメッセージが出た場合、下記コマンドで作業ツリー上の変更をコミットするか、
$ git commit -a
下記コマンドで作業ツリーの変更を取り消して(注:コミットしていない変更は破棄されるので注意)、
$ git reset --hard
下記コマンドで変更点の取り込めます。
$ git merge FETCH_HEAD
「マージ」っていう言葉が出てきたけど、「僕と契約して、ほにゃらら」っていうやつ?
それは多分魔法使いの英語の「メイジ(Mage)」……。いや、そもそもメイジは男の魔法使いだし、読者は魔法少女にしか興味ないと思うけど……。
話を戻すと、ほかの人、あるいはほかのブランチの変更点を取り込む操作を「マージ(統合)」って呼ぶんだよ。
例えば、さっきのプル/フェッチの図10だと、ほかのユーザーのコミットをローカルリポジトリに一回取り込んでから、作業ツリー(正確には作業ツリーで作業をしているブランチ)に取り込んでいるけど、このローカルリポジトリから作業ツリー、正確には作業中のブランチに取り込む操作がマージと呼ばれる作業なんだよ。
マージは、例えば作業ツリー上での変更点とほかのユーザーの変更点がバッティングすると失敗して、「コンフリクト(競合)」と呼ばれる状態になることがあるの。
例えば、図12の例で説明すると、Talkクラスを重複して実装し、亜琉美ちゃんが共有リポジトリにコミットしたときにおねーちゃんがプルすると、編集個所が重複しているから、競合した状態が発生するのよ。この状態で、ローカルリポジトリでプルを実行すると、下記のようにエラーになるの。
$ git pull remote: Counting objects: 13, done. remote: Compressing objects: 100% (7/7), done. remote: Total 7 (delta 4), reused 0 (delta 0) Unpacking objects: 100% (7/7), done. From https://192.168.56.101/git/test e2a59bc..aedc9d1 master -> origin/master Auto-merging java/websocket-chat/app/models/ChatRoom.java CONFLICT (content): Merge conflict in java/websocket-chat/app/models/ChatRoom.java Automatic merge failed; fix conflicts and then commit the result.
ここで、「git status」を実行すると、下記のように表示されます。
$ git status -s UU java/websocket-chat/app/modiels/ChatRoom.java
「UU」っていうのは、そのファイルがコンフリクトを起こしていることを示しているの。ここで、コンフリクトを起こしたChatRoom.javaをエディタで開くと、図13のようになっているよ。
コンフリクトしたファイルを開くと、編集が競合した場所が「<<<<」と「>>>>」に囲まれ、図13のようになっているよ。「====」を区切りとして、上側にローカルリポジトリの内容が、下側にpullで取り込もうとした共有リポジトリ上の変更の内容が記述されているの。
競合を起こした場所を編集し、どちらの変更を取るか選択しましょう。例えば、図13の例だと赤の変更はユーザーとメッセージだけをパラメータとして取っているけど、青の変更はユーザーとメッセージに加え、写真の扱いも追加してあるよ。写真も使える方がいいから、青の変更を選択する場合、競合が発生している部分を編集し、下記のようにしてね(<<<<、====、>>>>などのセパレータも削除)。
…… } public static class Talk { final String user; final String text; final String picture; public Talk(String user, String text, String picture) { this.user = user; this.text = text; this.picture = picture; } } public static class Quit { ……
下記のようにadd、コミットすると、マージは完了です。
$ git add java/websocket-chat/app/modiels/ChatRoom.java $ git commit -m "競合を解決し、会話クラス(Talk)で写真を扱えるようにする"
マージするのに、いちいちコンフリクトが起こったところを編集するのは、面倒くさいよー。一括して、ローカルリポジトリの編集と、共有リポジトリの編集を選択できないかなぁ。
下記のようにすると、一括してどちらかのファイルの変更を採用できます。
$ git checkout MERGE_HEAD - java/websocket-chat/app/modiels/ChatRoom.java $ git commit
$ git checkout ORIG_HEAD - java/websocket-chat/app/modiels/ChatRoom.java $ git commit
コンフリクトになったときは、競合した変更をコミットした人と相談して修正するようにしましょう。意識合わせをせずに勝手に他の人の編集を上書きすると、トラブルの基になるんだよ。
確かに、こっちのリソースの競合とか考えずに仕事を取ってくる課長とかって超ムカつくよね。競合が発生したときは、話し合いによる解決は重要だよね〜。
コンフリクトした場所を誰が編集したか確認するには、ログを確認します。まず、コンフリクトを起こした場所の最後にある、以下の部分に競合したコミットのコミットIDが記述されています。
<<<<<<< HEAD …… ======== …… >>>>>> aedc9d1106f2e68888c4ac82b2
このコミットIDを指定して下記のようにログを確認すると誰が編集したのか一目で確認できます。
$ git log -1 aedc9d commit aedc9d1106f2e68888c4ac82b2cf03d549f42bfd Author: 亜琉美 <alminium@example.com> Date: Thu Jun 21 20:33:57 2012 +0900 会話機能の実装
上記のログからは、亜琉美が競合するコミットを行ったことが分かります。
コンフリクトの解決しようとしてファイルを編集してたら、ファイルがグチャグチャになっちゃんだけど、そういうときはどうすればいーの?
競合を解決していて、ファイルがグチャグチャになって、最初からマージをやり直したい場合は、下記コマンドのようにすると、マージ前の状態――ここでは共有リポジトリの変更を取り込む前の状態――に戻るよ。
$ git reset --hard
resetで元に戻してから、下記コマンドのようにしてマージすると、もう一度共有リポジトリ上のコミットをマージできます。
$ git merge FETCH_HEAD
まあコミットは、「ソースコードの年輪」みたいなものかな。コミットを見ていけば、どのようにソースコードが変更していったか分かるよ。例えば、コミットによる変更履歴は「git log」コマンドで確認できるの。
$ git log commit b89a8befe5b4da63fd356609220ef074192c8ae1 Author: mikoto20000 <mikoto20000@gmail.com> Date: Wed Jun 6 08:21:09 2012 +0900 Redmine 2.0.2にアップデート commit da2e10fab985ed7969404455d47a436bf22b93d8 Author: mikoto20000 <mikoto20000@gmail.com> Date: Fri Jun 1 02:06:03 2012 +0900 smeltに実行権限を付与 commit ada129bfde3adf11fa338d53adfbed867beeea3c Author: mikoto20000 <mikoto20000@gmail.com> Date: Fri Jun 1 18:59:30 2012 +0900 Backlogsプラグインをサポート。 commit dce70618e2a96b1ac751449db299c233e13c66ba Author: mikoto20000 <mikoto20000@gmail.com> Date: Thu May 31 00:38:49 2012 +0900 リポジトリの不正な上書きとブランチの削除を禁止
開発中のあるコミットからプログラムが正しく動かなかった場合や、過去のプログラムの変更を参考にしたい場合など、コミットによる変更内容を確認したい場合は、次のように「git show」コマンドを使いましょう。
$ git show ada129df
さて、今日はGitリポジトリの作り方とGitのキホンについて紹介したよ。次回はGitとRedmineとの連携についてと、ブランチについてのお話しだよ。ホームワークで確認して待っててね!
今日の宿題を出します。Gitに限らずですが、本当に理解するには実践あるのみですよ。
ALMiniumを利用してGitリポジトリ付きのプロジェクトを作成してください。
問1で作成したプロジェクトにユーザーを追加してください。
問2で追加したユーザーでリポジトリをクローンしてください。
クローンしたリポジトリの作業ツリーにファイルを作成し、コミットしてください。
コミットした変更を共有リポジトリへプッシュしてください。
git logコマンドやgit showコマンドでコミットメッセージと変更点を確認してください。
■本稿の図版について本稿の図版について
本稿の図版は京風子(Kyoko)氏作成の「あんずもじ」フォントを利用しています。
Copyright © ITmedia, Inc. All Rights Reserved.