稲枝の押入れ

いなえが適当なことを書いては、しまっておく場所

git初心者によるgit入門

前置き

対象

この記事は、「gitって何?」もしくは「名前だけ聞いたことはあるんだけど何となく手を出さないままでよく知らない」という人を主な対象としています。「コマンド普段から打つわけじゃないから抵抗がある or 面倒だ」という人にも向けています。また、僕自身の備忘録も兼ねています。あしからず。(おそらくこれらのユーザーの殆どはWindowsを使っているだろうという邪推から特に断りなくOSはWindowsを使っているものとして話を進めていきます)

目標

これを読んで、とりあえず最低限の機能については使える、知識としてではなく使えるくらいにはgitをわかってもらえたらなと思います。

注意

gitは一般には(というか原則は)CUI(文字だけの黒画面でコマンド打ち込んだり)で操作をするものなのですが、周りの人を見ていると、gitを使うことに抵抗を覚えている人がメインとしてGUIを使っている人(僕もその1人ですが)が多いので、SourceTreeというGUIフリーソフトを使ってとりあえずgitの便利さを知ってもらい、かつ難しい事をしている訳ではないという事を知ってもらうつもりでいます。コマンドをカタカタ打ってgitを使いたい!という方は沢山ドキュメントが転がっていますので適当なワードで検索することをおすすめします。とはいえ、SourceTreeについての説明部分以外についてはCUIで使う人にとっても有用な部分があるかもしれないので軽く見ていってください。

また、この記事では本当の本当に基本的な事を扱います。あくまでもgitに対するよくわからないという意識を拭うことのみを目的としています。

加えて、筆者も初心者なので、記述が正確であるかはわかりません。他のサイトと記述が矛盾している場合、自らでどちらが正しいか判断してください。そのような場合など、こちらにコメント等頂けると筆者も勉強になるばかりでなく、記述がより正確になるのでありがたいです。

本題

gitとは?

gitとは何か、と聞かれて一言で答えると「分散型バージョン管理システムの一つ」と僕ならば答えるのですが、人によっては何のことやら?という感じだと思います。順をおってみてみましょう。

バージョン管理システムの説明

バージョン管理システム」とは、そのまま「バージョン管理をするシステム」のことだろうというのは推測できるかと思いますが、ではその「バージョン管理」とは何でしょう?ゲームなどで(特にオンラインゲームで見る気がしますが)、「ver.1.2」みたいな表記を見ることが有りますよね。あれがバージョンだということは大体の人は知っていると思います。バージョン管理システムとはまさにあのバージョンを管理するシステムなのです。さて、バージョンはこれでわかったと思いますが、バージョン管理とはなんでしょうか。

それではここで、普段見るゲームのバージョンというものを、遊ぶ側からではなく作る側として考えながらバージョン管理とは何かを見ていきましょう。

例えば、

  • ゲームの基本システムが完成したものをver.1.0
  • 「モンスターA」を追加してモンスターの数を増やしたものをver.1.1
  • 「ボスA」を足したものをver.1.2
  • 「モンスターB」を追加してモンスターの数を増やしたものをver.1.3

としましょう。その上でこれをゲームを作る、開発者目線から見てみます。ver.1.2ではボスを追加しましたが、ボスの動きの実装が難しく、バグが出てしまったとします。バグが取り除ければそれに越したことは無いのですが、バグがなくプレイできる様なバージョンを用意して置かないと、ちょっとプレイして確認したいところがある、などといった時にバグのあるバージョンしかプレイが出来ずデバッグ的な観点からも不都合です。

ではどうすればいいかというと、それぞれのバージョンの状態でプロジェクトを何処かにコピーしてバックアップしておく方法が考えられます。そうすれば仮に最新バージョンでバグが出たとしても、ひとつ前のバージョンのバックアップがあるのでプレイ自体は問題なく行えます。こういったバージョン毎の管理がバージョン管理です。 しかし、多くの方がお気づきでしょうがこのような手動のバージョン管理は無駄が多いです。例えば

  • 新しいバージョンが完成する度に手動コピーをするので特にプロジェクトの規模が大きくなると時間がかかる
  • 同じく、プロジェクトの規模を増すほどに容量を食う
  • コピーした後、「ゲームver.1.0」「ゲームver1.1」とプロジェクトフォルダの名前を手動で書き換えないといけない
  • バージョンはわかってもそのバージョンでどのような変更がなされたか等はフォルダ名に書くわけには行かないので別途テキストを用意して管理しないといけない
  • モンスターAとモンスターBだけを実装したバージョン、つまりバグのあるボスAの実装を全て消したバージョンを作るのに手動でやるしか無い
  • 下のようにリネームの際の表記がブレてしまうこともあり、一貫性がなくなる

f:id:makiofinae:20170818164303p:plain

という感じです。欲を出すともう少しデメリットを出していけるのですが、ここではこれくらいにします。

ではどうすればいいのか、というところでバージョン管理システムの登場です。バージョン管理システムでは、上記のような手動のバージョン管理によるミスを防げる上に、より無駄のない管理が出来るようになっています。 例えばgitではフォルダをコピーしてバックアップしなくても、コミットという動作をすれば、バージョンを作ってくれる上に、その日時や変更内容、コメントなどの情報をまとめたものを作って管理してくれます

バージョン管理システムのイメージ

と、ここまで説明しましたが、イメージがつかめない人がいるかもしれません。その人はゲーム等のセーブをイメージしてくれればいいと思います。バージョン管理システムとは、開発(先程はゲーム開発の例を出しましたがそれ以外のソフトウェア開発についても)等をする際に、その開発内容のセーブやロード等を出来るように管理してくれるシステムなのです。 ゲームでの「あのアイテム取りそこねて先に進めないからあそこのセーブデータに戻ってアイテムを取り直そう」というのと同じように、開発でも「新しく実装したものがバグが有って取るのが難しいから前のバージョンに戻ってもう一度コードを書きなおしてみよう」というのを出来るようにするのがバージョン管理システムです。

gitの大まかなイメージ

「分散型バージョン管理システム」のうち、残りの「分散型」について説明をする前に、gitの簡単なイメージを掴んでおきましょう。 gitでは、ユーザーは「リポジトリ」というバージョン管理の情報等を入れる入れ物の様な物をつくり、そこに新しいバージョンについての情報を追加したり、そこにある過去のバージョンの情報を参照したりすることでバージョン管理をします。つまり、自分のPCの中には実際に開発中である様々なファイル(ソースコードやデータ等)とリポジトリがあり、開発中の様々なファイルの変更をセーブしたい時は、ローカルのリポジトリに対して、バージョンを保存してくれと言ったような命令を出して現状の情報を保存するわけですね。 また、gitではサーバ上などオンラインにリポジトリを作ることも出来、ここに自分のローカルのリポジトリの内容を反映させたり、逆にサーバ上のリポジトリの内容をローカルのリポジトリに反映させたりすることも出来ます。これを用いると、多人数開発等で非常に便利で、これこそがgitの便利な所ではあるのですが、ここについて詳しく書くのはこの記事の趣旨に沿わない上に、僕もそれほど詳しくないのでこの記事では書きません。もし機会があれば別の記事に起こしたいですね。気になる人はGitHub等について調べるといいかもしれません。

「分散型」の意味

ここではそれほど重要なことではないのですが、gitは「分散型バージョン管理システム」だと言ったので分散型の部分も説明しておこうと思います。どうでもいい人はここは読み飛ばしてもらって構いません。 まずそもそも、バージョン管理システムには、分散型でないもの、つまり集中型というものがあります。集中型バージョン管理システムとは、リポジトリはサーバーに1つであり、そこで直接バージョン管理をする、というシステムです。つまり、新しいバージョンが出来た時に、自分のPCにあるローカルなリポジトリにその情報を追加するのではなく、サーバ上のリポジトリに直接追加します。 これに対してgitでは、説明してきたとおりバージョン情報の追加は自分のPCにあるローカルなリポジトリに対して行います。サーバ上のリポジトリには、ローカルなリポジトリの内容を一部若しくは全部反映するという形でバージョン情報を追加したりするので、サーバ上のリポジトリに全てのバージョン情報があるわけではないという状態になります。このような管理の仕方をするものを分散型バージョン管理システムと言います。

gitの基本的な知識

はじめに

GUIで使えるようになろうといっても流石にgitの事がわかっていないのにツールの話ばかりしてもわかるようにはならないので、まずはgitの基本的な事について話そうと思います。ただし、恐らく使いながら慣れたほうが良いと思うので、それなりに細かく書くつもりではいますが、イメージさえ掴めたらここで全てを理解しようとせず次に進んで構いません。 ここではGUIを想定しているので、コマンドという言い方よりも機能という言い方を採用します。

repository(リポジトリ)

先程軽く説明して、ここまでの説明でも使ってきましたが、念の為確認しておきましょう。リポジトリとはバージョン管理情報を入れておく入れ物の事です。もう少し具体的に言うと、ソースやファイルの状態について、保持しておくファイル(群)の事を指します。gitではまず最初にこの入れ物を作ってから、そこにデータを追加していったりする形でバージョン管理を行っていきます。

commit(コミット)

一番よく使うことになるであろう機能がこのコミットです。何をする機能かというと、ゲームで言うとセーブをする機能です。つまりはリポジトリにコードの変更について保存します。

どうやって管理してるの?

gitはセーブをしたり出来るといって、commitについて先程書きましたが、gitはその時のソースコード等の情報を保存しているのではありません。commitによって保存されるのは、commit時の情報ではなく、前回までのcommitから導かれる前回のソースコードと、現在のソースコードとの差分です。 ゲームに例えてみましょう。RPGポーションを初めから3つ持っているとして、その状態でまずセーブがされているとします。そのあと、ポーションの数が4つになったとして、そこでセーブします。ゲーム等であれば大体「ポーションの数が4つである」というのを元のセーブデータに上書き保存する形でセーブすると思います。 しかし、gitでは「ポーションの数が1つ増えた」という情報をコミットすることになります。これによって

という情報から辿っていって現在のポーションの数を4つだ、と認識するわけですね。これをソースコードでもやるわけです。例えば

#include <stdio.h>
int main() {
     printf("Hello World\n");
     return 0;
}

というデータが既にcommit等を済ませた状態であるとして、

#include <stdio.h>
int main() {
     printf("Good Night World\n");
     return 0;
}

と書き換えた場合、commitをすると、リポジトリにはこのコードが保存されるのではなく、 「3行目の『 printf(“Hello World\n”);』が『 printf(“Good Night World\n”);』に書き換えられた」 という情報がリポジトリに保存されるわけです。(厳密に言うと「書き換えられた」と言うのは違う気がしますが、ここでは分かりやすさを優先します)

これを繰り返していってバージョンを管理する訳なので、gitは変更情報(差分)を連ねて保持してくれるシステムとも捉えることが出来ます。

branch(ブランチ)

ブランチというのは、ゲームで例えるとセーブスロットと考えると良いかもしれません。もしくは、世界線と考えると少しわかりやすいかも知れないですね。gitは変更情報を連ねて保持してくれるシステムとさっき言いましたが、このブランチというのはその連なりの分岐を作る機能なのです。ただ、ここで注意しておかなければならないのは、ブランチは分岐を作成・管理するのに使われるものの、ブランチそのものは直接分岐を作成するというよりも、commitの連なりやその分岐の中で現在どこにいるかというのを示す機能でもあるという事です。 ゲームの例で見てみましょう。世にいうギャルゲー、つまり恋愛シミュレーションゲームを考えます。あの手のゲームは所謂攻略対象としてヒロインが設定されているのですが、1周のプレイでは誰か1人を攻略することになると思います(自分はギャルゲーに詳しくはないので、本当にそうなのか!?と詰め寄られると困りますが、ここではそう考えます)。例えば、クリスマスに誰をデートに誘うか、というベタベタの選択肢があったとしてAさんとBさんがその候補にあがったとしましょう。この場合、素直にやるのであれば、まずAさんを誘って攻略を進めた後、また最初からやり直してBさんを攻略します。しかし、このクリスマスイベントまでは同じ手順を辿るのであれば、わざわざ二度も行うのは面倒ですよね。そこで考えられるのが、セーブです。セーブスロット1ではAさんを誘って、セーブスロット2ではBさんを誘う、そして最後には、セーブスロット1ではAさんを、セーブスロット2ではBさんを攻略します。 さて、プログラミングの話に戻りましょう。ソフトウェアの開発では、複数のメンバーが同時に機能の追加や、バグ修正を行ったりすることがあります。また、複数のリリースバージョンが存在し、そのそれぞれを保守しなければならないといったこともあります。こういった場合に役に立つのがブランチです。上記のセーブスロットの例の様に、例えばブランチ「bug_fix」ではバグの修正を、ブランチ「add_enemy」では敵の追加を、と言った風に、依存性の無い機能をそれぞれ平行に管理することが可能になります。この機能のメリットは、次に紹介するmerge(マージ)を含めての部分が多いのですが、そうでなくとも、ブランチについた名前から、そのブランチに属するcommit達が何をする為にされたものなのかを推測する事ができるという利点があります。とある機能についてのコードしか書かない、と決めていたら良いのですが、バグに気付いたら修正したくなるものですよね。かといって敵の実装の一部→バグ修正→敵の実装の一部という風にコミットされているとあとで見直す時に見にくくなってしまいます。しかしブランチを分けて置くことによって、いつでも当該部分についてcommitをすることができるようになるのです。

merge(マージ)

mergeというのは、分岐を再び統合する処理です。gitはその時のコードではなく変更についての情報を保持しているとさっき言いました。そして、その変更情報の連なったものは、分岐してbranch等を通して意味のあるかたまりとして捉えることができました(例えばバグ修正、など)。つまりは、元の情報に変更情報を1つずつ適用していけば、現在の情報が導ける訳です。となると、とある所で分岐したブランチについて、その元は同じであるがゆえに、ある変更情報の連なりに、別の変更情報の連なりを反映する、といったことも可能になります。このようにして、一度分岐した変更のかたまりを、再度統合する処理がmergeです。(以下、便宜上変更情報の連なりをブランチと表現します) しかし、このmergeはいつでもうまくいくとは限りません。あるブランチに別のブランチをマージしようとしたとき、それぞれの変更内容が矛盾してしまった場合、gitはそのどちらを採用すれば良いのかわからないのです。これをconflict(コンフリクト/衝突)と言います。コンフリクトが起きた場合は、その矛盾を解消してやらなければなりません。 具体的にどの様な時にコンフリクトするのでしょう。例えば、

#include <stdio.h>
int main() {
     printf("Hello World\n");
     return 0;
}

というコードに対して、ブランチ「night」では

#include <stdio.h>
int main() {
     printf("Good Night World\n");
     return 0;
}

と、3行目を書き換えてcommitしているのに対して、ブランチ「leave」では

#include <stdio.h>
int main() {
     printf("See You World\n");
     return 0;
}

と3行目を書き換えている事を考えましょう。 この時、3行目について、それぞれのブランチでは「 printf(“Good Night World\n”);」と「 printf(“See You World\n”);」という変更がなされています。この状態でどちらかのブランチにもう一方を統合することを考えてみたらどうでしょう。gitは3行目をどちらにすればいいかわかりませんね。ここでコンフリクトが生じます。 では、「wanna_know_name」というブランチがあって

#include <stdio.h>
int main() {
     printf("Hello World\n");
     printf("What's your name?\n");
     return 0;
}

こういった風になっているとします。元のHello Worldと比較すると、変更分は3行目と4行目の間に「 printf(“What’s your name?\n”);」が追加されたになります。これを「night」ブランチにマージしたらどうなるでしょうか? この場合変更分は、

  • 3行目と4行目の間に「 printf(“What’s your name?\n”);」を追加
  • 「 printf(“Good Night World\n”);」を追加

の2つなので、矛盾することなく、マージ後のコードは

#include <stdio.h>
int main() {
     printf("Good Night World\n");
     printf("What's your name?\n");
     return 0;
}

になります。

checkout(チェックアウト)

コミットは変更の積み重ね、ブランチは変更について分岐したもの、というのは話しましたが、となると今自分がどこに居るのでしょう?「そりゃ最新のコミットでしょ」と思うかもしれませんが、ブランチがいくつもあったら?また、今までゲームで例えた場合のセーブについて話してきましたが、ロードはどうするのでしょう?あのブランチの最新のコミットの状態をロードしたい、となったらどうすれば良いのでしょうか。 それはともかく、gitは差分で情報を保持するといいました。となると、常に現在の最新コミットがどこで、その最新コミットに比べた差分を見るわけです。この最新コミットがどこにあるかというのは何を基準に見て居るのでしょうか。 この二つの問いに対する答えはcheckout(チェックアウト)という機能を見ることで見えてきます。 チェックアウトとは、現在の作業ブランチを変更する機能です。またゲームの喩えをすると、選択しているセーブスロットを変更する機能になります。もっというと、別のセーブスロットをロードする機能、になります。 ブランチAからブランチBに移る場合「ブランチBにチェックアウトする」といいます。「ブランチAからチェックアウトする」といいたくなりますが、こうは言わない様です。

導入

実践

gitを使うメリットと、基本的な機能などについては何となく掴んでもらえたと思うので、そろそろその使う準備をしようかと思います。まず、この記事においては上記の通りGUIでgitを使えるようになる事を目的としているので、そのツールの紹介からしましょう

SourceTree

使うツールはこちら、SourceTreeです。もしかしたらもっと良いツールがあるのかもしれませんが、僕が教えてもらって非常に簡単に使うことが出来るようになったという事や、使い続けてきて充分使えるレベルのソフトウェアだという事を僕がわかっているのでこちらのツールを使っていこうと思います。

インストール

こちらのサイトからSourceTreeをダウンロード、インストールしてください。基本的に無料で使えると思うのでその辺は心配しなくて良いと思います。インストールする際等にアカウントを作ることを求められるかもしれません。もし将来オンラインでソースコードを管理することを考えているのであればGitHubのアカウントを取得してそれを登録しておいても良いかもしれません。(この記事ではSourceTree Version 1.9.13.7を前提として話を進めます)

不親切

色々ともしかすると選択肢等が出てくるかと思いますが、その辺は読めばだいたいわかると思うのでがんばってください…

リポジトリの作成

gitを使うにはリポジトリをまず作る必要がありますのでリポジトリを作ってみましょう。とはいっても難しいことはありません。左上の「新規/クローンを作成する」をクリックします。するとウィンドウ内に小窓が開くので、そのタブの中で「リポジトリを作成」を押し、「保存先のパス」にプロジェクトのパスを指定して「作成」を押します。これでリポジトリが作れました。

コミットしてみる

さて、次はコミットです。本当はgitにはaddという機能があって、gitで管理するファイルをこのコマンドで追加するのですが、Sourcetreeでは、これを自動で認識してくれるので、何も考えずコミットをすれば良いです。 コミットの仕方も簡単で、上の「コミット」ボタンを押して、コミット画面を開きます。変更点があるファイルが下側に表示されるので、コミットしたいものを選んで「Stage selected」を押すか、「Stage All」を押して、変更をステージします。変更をステージする、とは、変更を今回のコミットに含むと言うことを意味します。変更をステージできたら、下のメッセージボックスにコミットに関するコメントを書いて、右下のボタンを押すか、ctrl+Enterを押すだけでコミットは終了です。

ブランチを切る

ブランチを切る、とはブランチを作ることです。ブランチを作る、つまり新しい世界線・セーブスロットを作るのさえ、難しいことではありません。上にある「ブランチ」ボタンを押して、出てきた小窓に新しいブランチの名前を入力し、決定ボタンを押すだけです。

マージする

マージをしたい場合にも、またも上にあるマージボタンを押すのですが、この時に少し注意があります。それは、統合したい先のブランチにチェックアウトした状態でマージを押す必要があるということです。統合元ではないのです。 つまり、ブランチAにブランチBの変更を適用する(統合する=マージする)場合、ブランチAにチェックアウトした上でマージボタンを押す必要があります。 マージボタンを押すと、マージするコミットを選ぶよう言われます。マージしたいブランチの最新コミットを選択して右下の決定を押せば、自動でマージが行われます。 コンフリクトが発生した場合はダイアログが開くので、説明に従いながら衝突を解決してください。

現在のブランチを指定したコミットに移動させる

もう大体話したのですが、たまに便利なのを紹介しておくと、ブランチの現在位置を変更する機能があります。後から「うまくいかない原因を比較検証するためにあの状態からこの機能を実装してみたい」と言った時に過去のコミットに戻れる機能になります。これもいわばゲームでいうロードに近いですね。 やり方は、戻りたい先のコミットを指定して右クリックから「現在のブランチをこのコミットまでリセット」を選択するだけです。選択すると「Mixed」「Hard」など、どの設定で移動するかを聞かれますが、大体Mixedで問題ないでしょう。こいつらがなにかというと、ブランチの現在のコミットに合わせて、コードの方も変更するかという選択肢です。 オンラインリポジトリを使うようになるとこいつのせいで変に整合性が取れなくなることもあるので注意して使いましょう。

gitignore

地味に大事なのがこのgitignoreです。何かというと、変更を検知しないファイルをリスト形式で定義できるものになります。 例えば、コンパイルするたびに生成される.exeファイルや、VisualStudioを使っているのであれば、ソリューションについて更新があるたびに変更される.slnファイル等、定期的に変更が入り、かつ別にそれをコミットしたくはないというオブジェクトがあったりすると思います。これを検知せず、つまりはコミットの対象から外す様に設定できるのがgitignoreです。実際にはこれはgitignore.txt(ここでは名前が違うかもしれませんが気にしないでください)を編集することで行います。どこからそのgitignore.txtを開けるのかというと、まずSourceTreeのウィンドウメニューのツール->オプションを選択してオプションウィンドウを開きます。ここでGitのタブを選択し、「グローバル無視リスト」とある行の一番右の「ファイルの編集」をクリックすることで開けます。これは名前の通り全体に効果のある無視リストになります。ローカル環境でのみ無視するには.git/info/excludeを編集するという手がありますが、これは入門の範囲で扱う必要はないと思うので、必要であれば調べてみてください。
さて、gitignoreの中にどうやって更新を無視するファイルを定義するかですが例えばこのように行います。

#ignore thumbnails created by windows
Thumbs.db
#Ignore files build by Visual Studio
*.obj
*.exe
*.pdb
*.user
*.aps
*.pch
*.vspscc
*_i.c
*_p.c
*.ncb
*.suo
*.tlb
*.tlh
*.bak
*.cache
*.ilk
*.log
*.dll
*.lib
*.sbr

簡単に書くと

  • #で始まる行はコメント
  • *.[拡張子]の場合、その拡張子のファイルすべてが無視される
  • 特定のフォルダ以下を無視したい場合は"folder_name/"

という感じです。

詳しくはこことかここあたりを参考にするか、「gitignore 書き方」とかでググれば出てくると思います。

総括

gitについて

さて、グダグダな説明になってしまいましたが、みなさんgitのイメージは掴めたでしょうか。もし、この有用性にまでイメージを膨らませることが出来たのであれば、あなたは今日からでもgitを使うべきでしょう。 gitにはここで紹介していない機能や、その本領を発揮するオンラインリポジトリについて等、まだまだ強い機能が沢山あります。それらについて皆さんの興味をひくことが出来たなら幸いです。

その他ページ

gitの学習をするにあたって、役に立ちそうなページを紹介しておきます

  • サルでもわかるGit入門
    有名どころ。なんだかんだ忘れた時にみるのはここな気がする。一通り書いてある気がするのでここを見てやるのは普通にアリ。

  • LearnGitBranching
    めっちゃ便利なサイト実戦形式でブラウザ上でコマンドを打ちながらgitについて学べる。CUIも気になる人には特におすすめ

  • こわくないGit
    スライドでGitの原理等から色んな機能に触れてくれる。非常に分かりやすい。スライドなのでとっかかりやすい。

  • チーム開発に必要なGitコマンドを神速で習得しよう! 
    そういえば殆どここにまとまってたななんて今更思い出した。個人使用に慣れてきて、オンラインリポジトリでの多人数開発したいってなったら見るといいかも。

最後に

gitも他の技術同様、実際に使ってみるのがその理解には早いと思います。しかし、バージョン管理という大枠の重要な位置を占める機能であるが故、少しのミスでデータをすべて飛ばす可能性も否定はできません。バージョン管理システムを導入しようとしているのに皮肉な話ではあるのですが、最初の慣れないうちは、プロジェクトのバックアップを手動でとっておくことをおすすめします。ただし、gitはそんなにこわいものではないので、恐れずに使って見てほしいなと思います。 それでは、これを読んだ方がgitの恩恵を受けられることを願っています。