LoginSignup
62
70

More than 5 years have passed since last update.

仕組みから理解するgit入門 第一回

Last updated at Posted at 2019-04-12

gitを仕組みからきちんと理解するための入門記事です。全部で六回程度に分けて書いていきます。

一般的なgit入門よりもgitの本質的な部分の理解、他のバージョン管理ツールとの考え方の違いに重点を置いています。

特にSubversion/Perforceといった中央管理型バージョン管理ツール経験者の人向けに、ところどころその考え方の違いを説明しています。筆者がgitを利用し始めたときにその違いを理解するのに時間がかかった経験があったため、そういった人たちの助けにもなれたら良いと思っています。

他のバージョン管理ツール経験者でなくても、gitの仕組みの理解のために読めるように書いていますが、バージョン管理が何なのか、とかそのあたりはある程度分かっている前提で書いています。

手っ取り早くコマンドを知りたいという人には向けの記事ではないので、そういう場合は他を探してみてください。その後、詳しく知りたくなったら読んでみてください。

間違いの指摘や質問、感想などはコメントにお願いします。

環境と前提条件

  • Linux (コンソールのみ利用)
  • git version 2.20 (versionはシビアではないです。基本的なところはほとんど同じです。)
  • (Optional) SSH Server (チーム開発の部分で利用します。なくても大丈夫です。)

Linux上での基本操作(ディレクトリ/ファイルの作成、編集程度)ができることを前提としています。

Windowsを利用している方もできればLinuxで最初は練習したほうがいいのではないかと思います。gitをWindowsで使うこともできますが、一部変な動作をすることがあるので(個人的な経験のみで言ってますが)、ひとまずLinuxを使って理解してからでもいいんじゃないかと思います。

注意事項

gitを理解する上で最も良いドキュメントはPro Git book https://git-scm.com/book/en/v2 です。基本的にこれを読めばgitの使い方は確実に理解できます。ここで書いている内容もほぼ同じことを言っていますが、説明の順番や視点を変えて説明している部分もあるので、こっちの方が理解しやすいという人がいたら良いかなと思います。

また、この記事には様々な点で他のgit解説記事と利用している言葉が違っていたり、表現が違っていたりする部分が存在します。

全体的な説明はボトムアップ的になっています。つまり基礎やベースになる知識を説明して実際の利用方法という流れなので少し全体像がつかみにくいかもしれません。書いてあることは分かるけど、これが実際に使っているgitコマンドのどの部分の説明なのかよく分からないとか、なんか知ってるgitと違うとか。そういう場合はちょっと先まで読んでみることをおすすめします。

理解のポイント

最初にgitを理解していく上で重要なポイントを先にお伝えしようと思います。細かくは後から順に見ながら説明するので、さらっと読み飛ばしていただいても大丈夫です。

ポイント1 : gitはシンプル

gitは非常にシンプルです。しかし複雑そうに見えます。筆者も最初複雑だと思いました。コマンドはなんかたくさんあるし、それぞれの意味もいまいちよく分からない。しかし実際の中身(考え方)はとてもシンプルです。

ポイント2 : 中央サーバーというものは存在しない

私もそうでしたが、Subversionなどを利用していた人の場合、頭の中に以下のようなイメージ、つまり、中央サーバーからローカルコピーを取得し、それを修正、再度アップロードという流れ、があるかと思います(図左)。そして、GitHubを利用している人の場合、中央のサーバーがGitHubで、その周りでみんな開発しているというイメージを持ってしまったりします。そして、gitの世界に入ったとき、そのイメージをそのままgitに適用し右図のようなイメージを持つことがあります。

image.png

このイメージが全くの間違いということはないのですが、このイメージで操作を行おうとすると非常に混乱しますし、ときに非常に非効率です。もしできることならSubversionのときに持っていたイメージは完全に忘れてしまいましょう。

なお、念の為ですがgitとGitHubは別のものです。gitはツールでありGitHubはgitを便利に利用するためのサービスです。

ポイント3 : コミットグラフを中心に考える

gitでは履歴をコミットという形で保存します。gitのほとんどの重要な操作はこのコミットを操作することにあります。そして、コミットは以下のようなグラフ構造(DAG)で管理されています。(今は意味分からなくてもいいです。)

image.png

あるコマンドがコミットおよびコミットグラフをどう変更するのかをイメージしながら作業すると期待通りの結果が得られるようになります。

ポイント4 : gitの細かいコマンドを覚えようとしない

gitのコマンドは結構複雑です。これは特定の結果を得るための一連のコマンドが複数存在するためだと筆者は思っています。

決まりきった手順としてコマンドを覚えようとするのはやめたほうが良いと思います。
どう操作したいかを考えて、それを実現するためのコマンドを探すほうがいいです。行いたい操作をきちんと理解していれば、自然とgitに与えなくては行けない情報が何なのか分かるようになります。使っているうちにコマンドは自然に覚えてしまいます。

理解の全体の流れ

ここで、gitを理解する全体像を示します。大きく分けると開発の流れ沿って3つの段階に分かれます。

1. コミット(履歴)の作成
これはコードを修正して履歴、つまりコミットとして残す部分の作業です。gitの作業としてベースとなる部分です。
2. コミットグラフの操作
これは開発時に過去の状態に戻したり、一部だけ別ブランチで作業、マージするなどといった作業の部分に相当します。
3. コミットの共有
作成したコミットをチーム内で共有します。これによってチーム連携開発ができるようになります。

image.png

第一回はこのコミットの作成部分を見ていきます。

(ローカル環境での)基本操作

前置きがだいぶ長くなりましたが、ここからgitの基本操作を学んでいきます。

gitはバージョン管理ツールであって、通常はチームで一つのコードを作り上げていくために利用されますが、gitを理解する第一歩は「gitを一人で利用してバージョン管理をする」ことから始まります。

Subversionを利用していた私からするとこのことは非常に理解し難かったです。そもそもバージョン管理ツールを利用する目的の一つは、複数人で一つのソフトをうまく作り上げていくためであり、そのために中央サーバーを立てて、そこにそれぞれの開発した分をUpdateしながら進んでいくという方が考え方としてはストレートです。

一人で孤独にバージョン管理してどうするんだ?という感じもしますが、gitではスタート地点は一人開発で一人でバージョン管理です。というか、基本gitには「一人でバージョン管理」しか存在しません。なんか、言い切ると後で文句言われそうですが、そう考えたほうが分かりやすいと思います。

バージョン管理の主目的は、「ファイルの履歴を記録し、あとから取り出せるようにすること」なので、まずはそれをできるようにしていきます。

リポジトリの作成

さっそく一人でバージョン管理をしていきましょう。そのために必要な環境は・・・なんとgitコマンドのみです。サーバーとか必要ないの?と思うかもしれませんが必要ありません。まずはバージョン管理を開始するための環境を作りましょう。

  $ mkdir MyProject     // MyProjectディレクトリを作って
  $ cd MyProject
  $ git init            // その中でバージョン管理することにする

これでMyProjectディレクトリ内でバージョン管理をしていく準備が整いました。ディレクトリの中を見ると

  $ ls -al
  total 12
  drwxrwxr-x. 3 shu shu 4096 Jan 23 01:03 .
  drwxrwxr-x. 5 shu shu 4096 Jan 23 01:03 ..
  drwxrwxr-x. 7 shu shu 4096 Jan 23 01:03 .git

となっており、.gitというディレクトリが作られています。これがSubversionでいうリポジトリに相当し、実際にこれがgitのリポジトリです。つまりこの中にバージョン管理のための情報や設定が保存されていきます。この中のファイルはgitが管理しているものであり直接操作することはほとんどありません。

そして、もう一つ重要なことがあります。ここで.gitの外側(つまりMyProjectの直下)はワーキングツリー(またはワークツリー)と呼ばれます。SVNのワークエリア(ローカルにチェックアウトする部分)と同じ意味合いです。gitの場合リポジトリとワーキングツリーがすぐ隣にあるイメージです。

image.png

gitもこのワーキングツリーにコードをcheckoutしたり、そこに加えた修正をリポジトリに保存することでバージョン管理をしてきます。
ここまでの説明でgitのサーバーが必要ない理由が分かると思います。

gitではリポジトリは常にローカルに存在します。

gitの設定

さっさとバージョン管理の説明に移りたいのですが、初めてgitを使う場合に必要な設定が2つあるので、それを行っておきます。以下のようにユーザー名とメールアドレスを設定してください(すでに利用してる環境で設定している場合には必要ありません)。これから作成するコミットの情報として利用される設定値です。

 $ git config --global user.name "Your Name"
 $ git config --global user.email your@email.address

コミットの作成

gitではバージョン管理をコミットと呼ばれるファイル群のスナップショットを作成して行います。コミットを作成する = あるファイル群のスナップショットの記録を取るという意味になります。
現在このリポジトリには一つもコミットがありません。当然ですね。では、記念すべき1つ目のコミットを作成してみます。

  $ echo hello > text1.txt             // ワーキングツリーにファイルを作成
  $ git add text1.txt                  // text1.txtをこれから作成するコミットに追加
  $ git commit                         // コミットを作成

ここで、以下のような画面でコミット作成のメッセージを書くように言われるので適当に書いて保存します。通常はこの変更がどういう理由で行われたものか、どのような変更が含まれているのかを完結に記述します。

  First commit                        // 自分でコミットメッセージを書く
  # Please enter the commit message for your changes. Lines starting
  # with '#' will be ignored, and an empty message aborts the commit.
  #
  # On branch orphan
  #
  # Initial commit
  #
  # Changes to be committed:
  #       new file:   test1.txt
  #

これで1つ目のコミットができました。このコミットには一つのファイルが入っている状態です。コミットを作成したので今後いつでもこの記録を取り出せます(取り出す部分は少し後に説明します。)。

ここで行った操作をgitが管理しているデータの観点から図解してみます。

image.png

図の上部は.gitディレクトリの内部、下部はワーキングツリーを表しています。

順に説明すると

  1. echo hello > text1.txtでワーキングツリーにファイルを作成します
  2. git add text1.txtではgitがtext1.txtをステージングエリアにコピーします。
  3. git commitでステージングエリアの内容のスナップショットをコミットとして記録します

ステージングエリア(indexやcacheと呼ばれることもある)はリポジトリ内にあるコミット準備エリアと考えてください。gitではワーキングツリーの状態を直接コミットにするのではなく、一度ステージングエリアにコピーしてから、最終的に出来上がったステージングエリアの内容をコミットとして作成します。(ワーキングツリーの一部だけコミットに含めたい場合に利用できます。最初のうちは変更したファイルはすべてステージングにコピーすると考えてしまって良いと思います。)

図の最上段の円はコミットを表します。コミットには、ファイルのスナップショットとそれに付けたメッセージが一緒に記録されてます。詳しくは後のほど説明しますが、今はステージングエリアの内容のスナップショットが記録として取られることを理解しておいてください。

これでまず一つ目のコミットができました。

バージョン管理を体験するために更に変更を加えてコミットを作成しましょう。

  $ echo hello world > text1.txt       // ワーキングツリーのファイルを変更
  $ git add text1.txt                  // text1.txtをステージングエリアにコピー
  $ git commit                         // コミットを作成 (ここではSecond Commitと入力)

ここでも、ワーキングツリーとステージングエリアがどう変わっていったかを図示しておきます。

image.png

2つ目のコミットがリポジトリ内に作成されました。ステージングエリアの内容がコミットになるという点に注意してください。

さらに、もう一つファイルを追加しましょう。

  $ echo my 2nd file > text2.txt       // ワーキングツリーにファイルを追加
  $ git add text2.txt                  // text2.txtをステージングエリアにコピー
  $ git commit                         // コミットを作成 (ここではSecond Fileと入力)

ここでも、ワーキングツリーとステージングエリアがどう変わっていったかを図示しておきます。先程の続きからの操作になるのでステージングエリアにはすでにtext1.txtが存在していることに注意してください。

image.png

ここまでで3つのコミットを作成しました。コミットの作成における重要な点を説明しておきます。

1.ステージングエリアには変更されたファイルだけでなく、コミットに含めるべきすべてのファイルが入っている

Commit2を作成した後、ステージングエリアにはtext1.txtが存在しています。そのため、Commit3にはtext1.txtに関する操作はしていませんが、自動的にtext1.txtが含まれています。

2. コミットはスナップショットであり差分ではない

作成したCommitにはその時点でステージングエリアに存在したすべてのファイルが記録として残されています(2番目と3番目のコミットに同じ内容のtext1.txtが入っています。)。

コミット情報の確認

ここまでで3つのコミットが出来上がりました。ここで出来上がった3つのコミットに関する情報を確認してみます。git logコマンドを使います。

 $ git log
 commit 9a5249bf9f2b961b2a92be3e0208a182ad9bcb07 (HEAD -> master)  // 3つ目のコミットハッシュ
 Author: JugglerShu <shu@jugglershu.net>                           // 3つ目のコミットの作者
 Date:   Tue Jan 29 00:47:45 2019 -0800                            // 3つ目のコミットの作成日時

 Second File                                                       // 3つ目のコミットメッセージ

 commit 5622427b51d97aea22046d1fa516b652b68f6524                   // 2つ目のコミットハッシュ
 Author: JugglerShu <shu@jugglershu.net>                           // 2つ目の...
 Date:   Tue Jan 29 00:47:45 2019 -0800

 Second Commit

 commit 2171ed0a148bfdf948ab6100893069f651727b28
 Author: JugglerShu <shu@jugglershu.net>
 Date:   Tue Jan 29 00:47:45 2019 -0800

 First Commit

説明に入る前に、みなさんの画面で出てきたこの部分の表示を全部コピーしておいてください。後で使います。

3つのコミットの情報が新しい順に表示されます。いくつか情報が表示されますが、gitを理解する上で最も重要な情報、それはコミットハッシュです。commit 9a5249bf9f2b961b2a92be3e0208a182ad9bcb07の部分です。これはそのコミットを識別するIDで、そのコミットに含まれる内容(ファイルやメッセージ、Author、作成時刻など)をまとめてSHA1を計算したものになります。これを利用していつでもこのコミットに含まれる内容を取り出すことができます。そして、重要な性質として、

このコミットハッシュが同じであればそのコミットに含まれる内容は同一であることが保証されています

つまり今作成したコミットについているコミットハッシュと同じIDを持つコミットは世界中にここにしか存在しません。逆にもし存在しているとすれば、その中身は全く同じ内容(ファイルの内容だけでなく、上に出てくるAuthorや作成時刻なども含めて)ということになります。

皆さんの作成したコミットとこのサンプルのコミットではハッシュは異なっているはずです。以下ではみなさんのコミットに合わせてサンプルを変更しながらコマンドを実行してください。

コミットの取り出し (checkout)

ここまでで3つのコミットを作成したので、ここで最初のコミットを取り出してみたいと思います。

過去のコミットの状態を取り出す = ワーキングツリーをコミットの内容にする

ということになります。git checkoutというコマンドに取り出したいコミットハッシュを渡します。

 $ git checkout 2171ed0a148bfdf948ab6100893069f651727b28            // 最初に作ったコミットのコミットハッシュ
 Note: checking out '2171ed0a148bfdf948ab6100893069f651727b28'.     // なんか注意がでるけど今は無視

 You are in 'detached HEAD' state. You can look around, make experimental
 changes and commit them, and you can discard any commits you make in this
 state without impacting any branches by performing another checkout.

 If you want to create a new branch to retain commits you create, you may
 do so (now or later) by using -b with the checkout command again. Example:

   git checkout -b <new-branch-name>

 HEAD is now at 2171ed0 First Commit

 $ cat text1.txt                                       // ちゃんと戻ったか確認
   hello

注意書きみたいなのが表示されますが、今は無視してください。

このときのリポジトリ、及び、ワーキングツリーの状態を図解すると以下のようになります。(円で表したコミットの下の部分にあるのはコミットハッシュの先頭部分です)

image.png

checkoutコマンドは指定されたコミットに含まれたファイルの状態を取り出しステージングエリアとワーキングツリーをそれと同じ状態にします。ステージングエリアも最初のコミットの内容でリセットされることに注意してください。1

もう一度、3番目のコミットに戻ります。(先程コピーしておいたgit logの結果から3番目のコミットハッシュを調べてください。)

 $ git checkout 9a5249bf9f2b961b2a92be3e0208a182ad9bcb07
 Previous HEAD position was 2171ed0 First Commit
 HEAD is now at 9a5249b Second File

ワーキングツリーの内容を確認すると、3番目のコミットと同じになっていることが分かると思います。

このようにこれまで作成したコミットの状態を行ったり来たりできます。

まとめると、

1. コミットを作成して記録をする

2. コミットハッシュを調べて、その状態を後から取り出す。

これで基本的なバージョン管理の機能が実現できていることは分かります。これがgitにおけるバージョン管理の最もベースとなる操作になります。

HEAD

最後にコミットに関するもう一つ重要な点を説明したいと思います。コミットには順番があります。当たり前のように思うかもしれませんが、今回作成した3つのコミットうち2番目のコミットは1番目の後となっていて、これはリポジトリ内にもきちんと記録されています。具体的には

コミットの中にその前のコミット(Parentという)のコミットハッシュを含める

という形で実現されています。図解すると以下のようになります。プログラマ的に言うならコミットのリンクリスト構造ができている感じになります。イメージとしてはどんどんコミットがつながっていくというイメージがあればよいです。

image.png

(じゃあ、1番目の最初のコミットのParentはなんなのか、という疑問が出てきますが、1番目のコミットは特別なコミットでParentを持ちません。)

では、今新しいコミットを作成したとき、そのParentはどれに設定されるのでしょうか。ここまで3つのコミットができていますが、単純に考えれば最後のコミットをParentとしてコミットが作成されるはずです。それはそれで正しいのですが、重要なのはどのようにgitがこれから作成するコミットのParentを決めているかという点です。時間的に最も新しいものになるわけではありません。gitはrepository内にHEADと呼ばれるポインタを持っています。ここに最後にワークツリーにcheckoutしたコミットの番号が記録されています。

普段、あまり使うことはないですが、現在のHEADがどのコミットを指しているか確認してみます。

 $ cat .git/HEAD            // .gitディレクトリ内にあるHEADというファイルの中身を確認
 9a5249bf9f2b961b2a92be3e0208a182ad9bcb07

意外ににシンプルにgitがこの情報を管理していることが分かります。ファイルにテキストでコミットハッシュが記録されているだけです。

コミットの作成時には

  • HEADのコミットをParentとしてコミットが作成される
  • HEADは新たなコミットを指すように更新される

ということが起こります。これによりどんどんコミットを作成していくと数珠つなぎにコミットが作成されていくことになります。

また、HEADはcheckoutを行うとcheckoutしたコミットを指すように移動します。

最終的な全体像

最後にここまでの作業の結果を一つの図にまとめてみます。

image.png

コミットが3つあり、順につながっています。それぞれにはコミットハッシュが振られていて、HEADは最後のコミットを指しています。ワーキングツリーとステージングエリアの内容は最後にcheckoutした3つ目のコミットの内容になっています。

この図の中にgitの操作で利用するほとんどの概念が含まれています。まだ少し足りない部分がありますが、それは今後追加していきます。
まずは、ワーキングツリー、ステージングエリア、コミットグラフ、の3つの関係性を理解しておきましょう。

課題

ここまでの操作の理解を深めるため、各自以下の課題をこなしてください。コミットのリストがどのようになっているか、ステージングエリアの内容が何であるかをイメージしながら操作してみてください。

  1. さらに4つ目のコミットとしてtext1.txtのファイルを削除したものを作成しましょう。ワーキングツリーとステージングエリアの状態をイメージすることが重要です。ステージングエリア内のファイルを消すには、git rm --cached [path]を利用します。コミットを作り終わったらtext1.txtを削除しましょう。
  2. git logで4つすべてのコミットハッシュを確認しましょう
  3. git checkoutで3つ目のコミットにワーキングツリーを戻しましょう。text1.txtが存在することを確認しましょう。
  4. 3つ目のコミットの後にコミットを作成するとコミットグラフがどうなるか考えてみましょう

分からなくなったらディレクトリ全部消して最初からやってもさほど時間かからないはずなので、作り直しちゃってください。

少し応用的な部分も含まれるので分からなければ次回の解説を読んでください。

まとめ

ひとまずここまでで、コミットの作成とコミットのcheckoutの操作イメージができたかと思います。
ポイントとしては

  • コミットの作成には3段階(ワーキングツリーの編集、ステージングエリアへの反映、コミットの作成)を踏む
  • コミットはファイル群のスナップショットでありコミットハッシュで識別される
  • checkoutはあるコミットの状態にワーキングツリーを戻すことができる(ステージングエリアもその状態になる)
  • これから作成するコミットのParentはHEADによって示されている

といった部分です。

いろいろ説明を飛ばしている部分も多々あるので、???がたくさん頭の中にあるかもしれません。

  • これじゃ一人で個別にバージョン管理ができてるだけで、チーム開発ができないじゃないか
  • ブランチとかは?
  • masterとかよく聞くけど、これはどこいった?

などなど

今回、gitの仕組みの理解を優先するためあえて「ブランチ」やコマンド上にも現れる「master」などの言葉を一切使わずにここまでやってきました。gitでは確実に使うことになるので早く解説したいですが、それは次回以降にしたいと思います。

第二回はこちら

Q&A

Q. 3つのコミットを作成した後、git checkoutで1つ目のコミットに戻るとgit logを見ても1つ目のコミットの情報しか出てこない。3つ目に戻りたくてもコミットハッシュが分からなくなってしまうのではないか。

A. これについては、その通りです。今回の例では最も基礎となる情報「コミットハッシュ」のみを用いているため、1つ目に戻った時点で3番目のコミットを確認する方法がなくなってしまいます。通常の開発では次回説明するブランチなどのマークをコミットに付けておくことで、コミットを識別し、前後にコミットを移動しながら作業していきます。
また、時折利用するコマンドとしてgit reflogというコマンドがあります。このコマンドは、HEADなどのポインタが過去どのコミットを移動してきたかというログの一覧が見られます。1つ目のコミットをcheckoutした直後にこのコマンドを実行すると、以下のように、直前にHEADが参照しているコミットハッシュを確認できます。

$ git reflog
e32f21b (HEAD) HEAD@{0}: checkout: moving from master to e32f21bfc1fb4226fb91c2841b59f9158d7d20f3  //1つ目のコミットのcheckout
e7befe8 (master) HEAD@{1}: commit: Second File  // その直前に HEADのコミット。 e7befe8 が3番目のコミット
383362b HEAD@{2}: commit: Second Commit
e32f21b (HEAD) HEAD@{3}: commit (initial): First Commit

(コミットハッシュの値は上の例と異なっています。)

Q. 課題でファイルを削除するとき、git rm --cached [path][path]には何を指定すればよいのか?

A. gitでファイルパスを指定する場合、現在のワーキングディレクトリからの相対パスを指定します。ワーキングツリーとステージングエリアには同じディレクトリツリー構造がコピーされていると考えてください。例えば、以下のようにファイルを作成、追加、コミットの作成、その後、そのファイルを削除したコミットを作成します。

  $ mkdir subdir                                       // gitのワーキングツリー内に一つディレクトリを作成
  $ echo file in subdir > subdir/newfile.txt
  $ git add subdir/newfile.txt                         // 相対パスで指定してステージングエリアにコピー
  $ git commit                                         // コミットを作成

  $ git rm --cached subdir/newfile.txt                 // ステージングエリアから先程追加したファイルを削除
  $ git commit                                         // newfile.txtが削除されたコミットを作成

  1. まだgitに記録されていない新しいファイルやファイルの編集がある場合、git checkoutはそれらのファイルを消したり上書きしたりはしません。 

62
70
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
62
70