Gitはウェブディベロッパーであれば誰しも日々の業務で使っている、親しみのあるツールではありますが、そのユーザーフレンドリーな数々の機能ゆえに、私は構造について理解をしないまま使っていました。その結果として、イレギュラーな操作やエラーに対して、全く対処ができない、という状態に陥っていたため、改めてGitについて学習しなおし、この記事を書きました。
Gitに関する優れたドキュメント、解説書は既にたくさんありますが、本記事の特異な点は面積において7割以上を図が占めていることです。結果として、そんな事知ってます!という部分は図を見ればわかるので、読み飛ばすことができますし、 復習する際にも図だけをみればいいので楽です。
それ以外の点では、次の資料のほうが圧倒的に優れています。特に1の資料を読む事でGitの構造への理解が非常に深まりました。ありがとうございます。本記事は基本的には1の資料を図示したものです。
- https://github.com/Shinpeim/introduction-to-git
- https://www.atlassian.com/ja/git
- https://git-scm.com/book/ja/v2/
本記事の対象者像は次のような方です。
業務でGitを使用しており、コンソールでGitコマンドをうったことはあるが、いつもはGUIで操作しているため、Gitコマンドで全ての操作をすることはできない。コンフリクトへの対処に自信がなく、ブランチを作ったりマージするなんてことは絶対にしたくない。
私です。
Contents
Working Directory, Stage, Local and Remote Repository の関係
次の図は Working Directory, Stage, Local Repository, Remote Repository の4つの領域がどのように関連しているかを示したものです。また各矢印は各Gitコマンド実行時にどの領域からどの領域に「*情報」が移動するのかということを示しています。
*情報 適切な言葉がみつかりませんでした
ファイルの新規作成や、ファイル内容の変更といった「変更」を$ git add <ファイル名>のコマンドによってステージに移動させます。次にステージに配置されたもの全てを、$ git commit -m <コミットメッセージ>によってローカルリポジトリに移動させます。最後に$ git push origin <ブランチ名>によってリモートリポジトリに移動させます。
Gitリポジトリの作成からコミットまで
Git環境の作成
(Gitがインストールされている前提です)
まずプロジェクトのルートに移動します。このディレクトリをワーキングディレクトリと呼ぶことにします。このディレクトリで git コマンドを過去に一度も実行していなければ、ここにはまだリポジトリが関連付けられていません。そのため当然ですが、$ git add といった git コマンドは動作しません。
まずはワーキングディレクトリにリポジトリを作成し、関連付けるために以下のコマンドを実行します。
すると次の図のように、ワーキングディレクトリとリポジトリが関連付けられます。
リポジトリの状況を確認する
次に下図のコマンドでワーキングディレクトリの状態を確認します。すると、Untracked = 追跡されていないファイルがあることがわかります。
変更をステージに追加する
追跡されていないファイルをステージに上げるために、次のコマンドを実行します。
その結果どうなったかを次のコマンドで確認します。すると、Changes to be committed = コミットされる予定の変更に、先程追加したファイルが配置されています。つまりこれはステージに配置されているファイルです。
ステージから取り消す
ステージにある変更は、次の2つのコマンドのどちらかでステージから取り消すことができます。これはあくまでステージから取り消すだけで、ワーキングディレクトリの内容には一切影響を与えません。
次のコマンドで確認するとステージには何もなく、ワーキングディレクトリに変更があるという状態に戻っていることがわかります。
ファイル内容の変更をステージにあげる
既に追跡されているファイルの中で変更があった場合にも、$ git add <ファイル名>でステージに上げることができます。
コミットをする
では次にのように、新しいファイルが作られたという変更と、追跡済のファイルの内容が変更されたという、2つの変更をステージに上げた状態からスタートして、コミットをします。
ステージに変更が追加された状態で、次のコマンドを実行します。
するとステージからなくなり、ローカルリポジトリにコミットが追加されます。
コミットを続けていくと、それぞれのコミットは別々のコミットとして管理され、蓄積されていきます。
コミットした内容は、どこに、どのように保存されていて、どのようにしてツリーを形成するのか
コミットをするとコミットオブジェクトが形成され、ローカルリポジトリに蓄積される。またParentの情報を持ち、これによりツリーを形成する。
コミットをすると、コミットオブジェクトが形成されます。この中にはコミットしたファイルの全ての内容が含まれています。差分ではありません。
コミットオブジェクトにはコミットしたファイルの内容に加えて、誰が parent =親 なのかという情報も持っています。親情報を辿っていくことができるので、コミットオブジェクトは結果としてツリーを形成します。
ツリーはなぜ2つ以上にわかれるのか
実際のプロジェクトで使用されているコミットログをみると、ツリーがあり、枝分かれしたり、結合したりしています。単に一つ前のコミットが親になるだけであれば、このような枝分かれは発生しえません。
ツリーの枝分かれには、どのようにしてコミットオブジェクトが親を決めているのか、ということが大きく関わってきます。そしてその決定には「ブランチ」という概念が関係します。
ブランチの確認
次のコマンドを実行するとブランチがいくつあるのか、どのコミットにそのブランチがアサインされているのか、そしてコミットがどのようなツリーを形成しているのか確認することができます。
ブランチは、ある特定のコミットを指し示すための「タグ」のようなものです。ここではmasterとHEADの2つがブランチです。masterの先頭についている「*」は、今選択されているブランチです。
(HEADは特殊なブランチで、現在選択しているブランチが指し示すオブジェクトと同じオブジェクトに配置されるブランチです。*マークだけでは見えづらいので用意されているのでしょうか?わかりません)
ブランチを作成する
次のコマンドでブランチを作成することができます。このコマンドでは、自分が今いるブランチが指し示すオブジェクトに対して、新たなブランチを作成します。
コミットオブジェクトの親はどのように決まるのか
コミットオブジェクトの親は、コミットを宣言した時に選択していた「ブランチ」が「指ししめしているコミットオブジェクト」が指定されます。
次の図のように、masterが選択されている状態でコミットをして、新しいコミットを「作ろうとした」場合には、masterが指し示しているコミットが親になります。(この際、選択されていない他のブランチは親の決定には全く関与しません。)
新しいコミットが作成されると、選択されているブランチは、新しく作成されたコミットに対象を変更します。結果として、ブランチは植物の成長点のように動作します。
ブランチを変更する
ブランチを変更するには次のコマンドを実行します。
すると現在選択されているブランチを意味する「*」が移動します。
ツリーの途中にあるブランチでコミットをするとどうなるか
次にツリーの途中にあるブランチでコミットをするとどうなるでしょうか。
新しく作成されるコミットの親は、その時に選択されていたブランチですから、ここではツリーの途中から枝のように新しいコミットが生えたような形になります。そして現在選択されているmy_branchが新しいコミットに移動します。
マージによって枝分かれしていたツリーの先端が、再度1本に結合される
先程の状態からブランチをmasterに移動させてから、次のコマンドを実行します。これは現在選択されているブランチ(ここではmaster)と対象のブランチ(ここではmy_branch)をmerge = 結合させるコマンドです。
すると、新しくできるコミットの親は次の2つになります。
- 現在選択されているmasterブランチが先程まで指し示していた1個前のコミット
- マージの対象となったmy_branchが指し示しているコミット
新しくできたコミットの内容は、上記リストで説明している2つの親コミットの内容を結合したものになります。(結合については本記事とは別の資料を御覧ください)
Merge後のツリーは、次のように枝分かれした後に、再度繋がったような形になります。
まとめ
Working Directory-Stage-Local Repository-Remote Repositoryの四層になっている
コミットオブジェクトはコミットされたファイルと親の情報を持っており、親を遡ることでツリーを形成する
親は選択しているブランチが先程まで指し示していたオブジェクトが指定される
マージをすると、現在のブランチと結合対象のブランチの2つの親を持つオブジェクトが生成される
ローカル環境での運用は大体説明できたと思うので、次回はリモートリポジトリの運用について記事を書きたいと思います。