チャチャチャおもちゃの抹茶っちゃ

ゲームのこととかプログラミングのこととか。気が向いたら書く。ブログタイトルは友人が考えました。

【Git入門】Gitを使い始めたい人へ「その他細かいこと」 ~A Beginner to Beginners~ 【その7】

【Git入門】Gitを使い始めたい人へ「リモートリポジトリを操作する」 ~A Beginner to Beginners~ 【その6】 : 前

目次

ここまででリポジトリの様々な操作をできるようになった。ここではその操作の細かなところを見ていく。

マージに関するあれこれ

Gitの機能の中でも特徴的な機能がマージだ。通常バージョンを分岐させてしまうと合流させるのは容易ではないがGitはそれを簡単にしてくれる。
マージは実は結構奥深いので、ここで少し掘り下げておく。

fast-forward

プルを行ったときや、分岐したブランチから元のブランチにマージしようとしたとき、「fast- forward」と出たことがあるのを見覚えあるだろうか。まずはこの「fast-forward」がどのような状態なのか説明する。次の状態を考えてほしい。
f:id:mattyan1053:20180722224300j:plain
この状態でmasterにbranch-1をマージをしたとき、つまり

$ git checkout master
$ git merge branch-1

としたとき、何が起こるかと言うとmasterとbranch-1をマージした結果できあがるのはbranch-1と同じ状態なので、新しいコミットは作成されず、masterがbranch-1と同じ場所に移動する。つまり枝分かれしない。
f:id:mattyan1053:20180722224706j:plain
このような状態になる。
どういうことか整理すると「branch-1のコミットの履歴」に「masterのコミットの履歴」が完全に含まれている時、branch-1をmasterにマージするとmasterはbranch-1の指し示すコミットまでfast-forwardするということだ。
ちなみにfast-forwardできないとき、つまり
f:id:mattyan1053:20180722225314j:plain
のように、branch-1のコミットの履歴にmasterのコミットの履歴が含まれない時(masterの指しているものが含まれていないため)は、新しくコミットが作成される。
f:id:mattyan1053:20180722225425j:plain
逆に、fast-forwardできるときでも、

$ git merge --no-ff branch-1

とすると、fast-forwardせずに新しくコミットをつくってくれるので、分岐があったことが履歴でわかりやすくなる。
f:id:mattyan1053:20180722225647j:plain

[--squash]オプション

マージには[--squash]オプションというものがある。これは、ワークツリーとインデックスを別ブランチの根本からたどっていき書き換えるが、コミットまではしない状態にもっていく操作である。
f:id:mattyan1053:20180722230745j:plain
この状態ではまだコミットは作られていないので変更飲みされている状態である。

$ git merge --squash branch-1

とするとワークツリーとインデックスが書き換えられ

$ git status
On branch master

Changes to be committed:
    modified : sample.c

のような状態になる。したがって、

$ git merge --squash branch-1
$ git commit -m "squash merged"

とすれば
f:id:mattyan1053:20180722231035j:plain
のような状態にすることができる(履歴がスッキリ)。

プルの仕様

プルでリモートリポジトリからローカルリポジトリに変更点を写すことができるが、実はこれは二段階の工程を踏んでいる。どういうことかというと

$ git pull origin master

$ git fetch origin master
$ git merge origin/master

は同義である。
git fetch ではローカルリポジトリにまだないコミットデータをダウンロードしてくる。今回masterをリモートリポジトリからとってきたので「origin/master」というところに記憶される。
f:id:mattyan1053:20180722233030j:plain
次にgit mergeすると、origin/masterをmasterにマージする(この場合fast-forwardになる)。
f:id:mattyan1053:20180722233420j:plain
これで実質pullと同じ動きをすることになった。
前回、プルリクエストの競合を直すときにpullで競合を起こした。これはorigin/masterブランチをfetchしたあとmergeする先のブランチをmasterではなくclone-branchにしていたため競合が発生したということである。
f:id:mattyan1053:20180722234017j:plain

リベース

これまでやってきたGit操作の履歴を見てみると、分岐が多くて見るのが少し大変である。
f:id:mattyan1053:20180722234544p:plain
これを少しきれいにするのに便利な操作がリベースである。リベースをすると、複数に分岐していたコミット履歴を直線的な履歴に書き換えることができる(これが良いか悪いかという話はおいておいて)。

git rebase

リベースの流れを図を用いて確認する。
f:id:mattyan1053:20180722234950j:plain
上記の状態を普通にマージすると
親コミットを2つ持つ新しいコミットが生成される。これを履歴で見ると分岐して合流するというあまり見やすくはない履歴になるだろう。
f:id:mattyan1053:20180722235253j:plain
ところが

$ git checkout branch-1
$ git rebase master

とすると
f:id:mattyan1053:20180722235626j:plain
というふうに、branch-1で行った変更を追いかけるように順番に、masterの位置から変更していってくれる。競合するたびにとまるので、止まったときは競合を解決したあと、

$ git add <filename>
$ git rebase --continue

とすればrebaseの続きから再開してくれる。 こうしてbranch-1の先端の変更と同じ変更まで追いつくと、masterブランチの先のほうにもbranch-1の変更と同じ変更が適用されたコミットが完成する。当然branch-1のコミット履歴を見れば一直線の見やすい履歴になっていることだろう。あとは「master」に「branch-1」をmergeしてあげれば良い(fast-forwardになる)。
mergeとrebaseでそれぞれ分岐したブランチを統合したときの図は次のようになる。
f:id:mattyan1053:20180723001440p:plain
分岐していた部分がmasterの上にそのままくっついた形になったいるのがおわかりいただけるだろうか。

git rebase -i

このコマンドを使うと、複数のコミットを一つにまとめることができる。
たとえば
f:id:mattyan1053:20180723002211j:plain
branch-1において2回コミットし、それぞれ別の言葉を追加していたとする。しかしこの2つのコミットは似たような作業をしただけなので、一つにまとめてしまいたい。こういうときに

$ git rebase -i HEAD~~

とすると、

$ git rebase -i HEAD~~
pick eaa773d <commit message1>
pick fba24fd <commit message2>
(略)

テキストエディタが起動して表示されたはずだ。ここで、二つ目の「pick」を「squash」または「s」に書き換えてエディタを終了すると、再度エディタが起動し、今度はコミットメッセージの編集ができる。

# This is a combination of 2 commits.
# The first commit's message is;

add hello

#This is the 2nd commit message:

add good evening

(略)

これを書き換えて

add hello and good evening

とすれば、前2つのコミットをまとめることができる。
まとめるコミットは2つでなくてもいいので、例えば

$ git rebase -i <branchname> HEAD~4

とすれば4つのコミットをまとめることもできたりする。当然ログも短くなっている。
f:id:mattyan1053:20180723003510p:plain
このように、ログが短くなりコミットメッセージは更新されている。
また、git rebase -iはコミットをまとめるだけでなく編集も可能である。

$ git rebase -i HEAD~~

としたあとに表示されるエディタで、「pick」のところを先ほどは「squash」に書き換えたが今度は「edit」に書き換えてみると

Stopped at fba24fd...  add good evening
You can amend the commit now, with

  git commit --amend

Once you are satisfied with your changes, run

  git rebase --continue

と表示されるはずだ。これであとは好きなように編集して、満足したらcommit --amendする(commitはしなくて良い、すでにあるコミットを書き換えるため)。まだrebaseは終わっていないので「git rebase --continue」で続きを促すと

$ git add sample.c
$ git commit --amend
$git rebase --continue

これで指定の内容のコミットを修正することができた。ログで変更されていることを確認できる。ちなみに、rebaseを途中でやっぱりやめたくなった場合は

$ git rebase --abort

とすればそれまでの作業を取り消して中断できる。
f:id:mattyan1053:20180723004541p:plain
上図の場合はコミットメッセージが修正できていることが確認できる。

リベースの怖いところとお約束

リベースは履歴をきれいにしたり、間違ってコミットしたものを修正したり余計にコミットしたものをまとめたりできる便利な道具である。しかし同時に、行っていることはコミット履歴の改ざんにほかならない。バグの生まれた箇所を見つけにくくなるし、誰がどこでどう分岐して変更したのかが正確にはわからなくなる(コミットメッセージなどでだいたいはわかるが・・・・・・)。使い所をよく考える必要があるだろう。

一度コミットしたものをリベースする

一度コミットしたものをリベースすると、すでにリモートリポジトリに保存されているコミットの履歴と内容があわなくなってしまう。したがって一度コミットしたものをリベースして書き換えた後再度pushすると、コミット履歴が途中まですら一致しなくなり失敗してしまう。ここで、強制的にpush(ローカルリポジトリのコミット履歴をリモートリポジトリに反映する)には

$ git push -f <リモートリポジトリ> <ブランチ名>

とすること可能だ。
とはいえ、一度コミットしたものを書き換えるのはあまり推奨されない。なぜなら、コミットを編集することでコミット名(ハッシュ値)が変わり、リモートリポジトリとローカルリポジトリのコミット履歴の間で大きな不一致が発生してしまうからだ。一度コミットしたものはrebaseせず、自分の作業ブランチを管理者が見やすくするために予めrebaseでコミットをまとめたり分岐をなくしたりしてからコミットする程度にrebaseの利用は留めるのがベターかもしれない(あるいはrebaseの利用基準をチームで明確に決めておく)。

まとめ

今回はマージやリベースなど、コミット操作について少し詳しく説明した。
これらの作業は更新履歴をいじる危険な作業であるから慎重に行うべきである。仕様をしっかり理解しておかしなバグを産まないようにしたい。

最後に

長かったGit入門もここまで。何分自分もGit初心者なのでいろいろ整理しながら書くことができてよかった。アウトプットの大切さがよくわかる。ここまでわかればGitで結構な操作ができるんじゃないだろうか。一応自分なりにジャンル分けしたコマンド一覧を作ったのでそちらのURLも貼っておく。

qiita.com

それでは皆さん良い開発ライフを。

【Git入門】Gitを使い始めたい人へ「リモートリポジトリを操作する」 ~A Beginner to Beginners~ 【その6】 : 前

【Git入門】Gitを使い始めたい人へ「リモートリポジトリを操作する」 ~A Beginner to Beginners~ 【その6】

 【Git入門】Gitを使い始めたい人へ「リモートリポジトリをつくる(GitHub)」 ~A Beginner to Beginners~ 【その5】 : 前 次 : 【Git入門】Gitを使い始めたい人へ「その他細かいこと」 ~A Beginner to Beginners~ 【その7】

目次

前回はGitHubでリモートリポジトリを作成した。今回はこれを操作していく。

ローカルリポジトリにリモートリポジトリを設定する

GitHubで作ったリポジトリのページを開いてCodeのところを開くと次のように出るだろう。
f:id:mattyan1053:20180722164857p:plain
SSHのところをクリックしたときに横に表示される「git@github.com:/git-test.git」の部分を表示する。
できたら、次はコマンドでローカルリポジトリのところに移動(今回は「git-test」)して、次のように入力してほしい。

$ git remote add origin git@github.com:<username>/git-test.git

originの後ろは先程表示したものをそのまま入力する。これでGitHubのリモートリポジトリと自分のローカルリポジトリを結びつけることが成功した(間違えて入力してしまった人は「git remote remove <リポジトリの名前(うち間違えてなければorigin)>」としたあとやり直せばOK)。このコマンドについて解説しておくと、現在いるローカルリポジトリを保存するリモートリポジトリを追加するコマンドである。「add」の後ろはリモートリポジトリの名前(デフォルトは「origin」)、その後ろがリポジトリの場所である。

プッシュする

次に、今の自分のリポジトリ(ローカルリポジトリ)の「master」ブランチの内容をリモートリポジトリにアップロードしよう。このアップロードすることを「プッシュ」と呼び、

$ git push origin master

とすると、リモートリポジトリにローカルリポジトリの「master」に現在のブランチの内容をプッシュ(アップロード)できる。
実際、ここでGitHubのほうのリポジトリを開いてCodeのところをクリックすると
f:id:mattyan1053:20180722170204p:plain
ちゃんと同期がとれていることがわかる。今回は更に「test-branch」も同期しておこう。

$ git checkout test-branch
$ git push origin test-branch

今回のpush先は「test-branch」なのでこちらを指定。かならずpush元に「checkout」してからプッシュする。これでブランチが2つとも同期できた。コミットログなどもちゃんと同期されていることをGitHub上で確認できる。
f:id:mattyan1053:20180722170819p:plain
ここで更に「local/test-branch」上で変更を行って、再度プッシュしてみよう。まず「master」が変更されているかもしれないので、「master」の内容を「test-branch」にマージする(今回は自分しか作業していないので変更されているわけもないが・・・・・・)。

$ git merge master
Fast-forward
  sample.c | 2 ++
  1 file changed, 2 insertion(+)

となっただろう。Fast-forwardについてはあとで説明するのでまだわからなくても良いが、これが表示されたときは特にmasterが変更されてないときである。
さて準備が整ったので作業をしていこう。
まずsample.cを書き換える。

$ vim sample.c
#include<stdio.h>

int main(){
    printf("Gitコマンド一覧\n");
    printf("git init : リポジトリを作成\n");
    printf("git status : Gitの状態を確認\n");
    printf("git add : ファイルをステージ\n");
    printf("git commit : コミットする\n");
    printf("git log : コミット履歴を見る\n");
    printf("git push : プッシュする\n");
    branch_name();
    return 0;
}

一行追加した。これをコミットする。

$ git add sample.c
$ git commit -m "add description of push"

これをプッシュ(アップロード)する。

$ git push origin test-branch

実際にGitHubでコミット履歴でブランチを「test-branch」に設定すると
f:id:mattyan1053:20180722172736p:plain
ちゃんとコミット履歴に追加されていることがわかる。
プッシュは先程からアップロードと説明していたが、実は少し違う。アップロードときくとディレクトリ全体をリポジトリに保存するようだが、プッシュしてるのはあくまで「コミット」なので、リモートリポジトリにもやはりコミットが追加されて、かつリモートリポジトリのブランチの指す位置が変化する。あくまで指定したブランチ上のコミットをローカルリポジトリとリモートリポジトリで同期しているに過ぎないということである。

クローンする

今の所作業しているのは一人だが、新たに作業員が加わったとしよう。その人は自分のPC上にこのリモートリポジトリの内容をもったローカルリポジトリを作る必要がある。けれども。「git init」して、このリモートリポジトリのコミットをたどるように自分のPC上にローカルリポジトリを作成するのはとてもバカバカしい作業である。そこで、このリモートリポジトリの内容をコピーしてリモートリポジトリに反映するのが「クローン」だ。クローンという言葉は聞き覚えのある人もいるだろう、同じ遺伝子の子を作り出すアレと同じ意味である。
今回は仮想的にもうひとりの作業員を用意して、自分がそれになりきることにしよう。まずはGitHubのほうでクローンを作るのに必要なローカルリポジトリの場所を確認しなければならない。GitHubでコードのところを見ると、右の方に「Clone or download」というものがある。ここをクリックして表示される場所をコマンドで入力することになる。
f:id:mattyan1053:20180722174002p:plain
新しくローカルリポジトリを作りたい場所に移動し、次のように入力する。

$ git clone <先ほど表示したリポジトリの場所> <クローンをいれるディレクトリ名>

今回は

$ git clone git@github.com:mattyan1053/git-test.git git-test-clone

とした。ただし、クローンでできたローカルリポジトリに存在するブランチはデフォルトブランチであるmasterのみである。あとは今までと同様に作業できるので、とりあえず自分用のブランチを作成して編集しておこう。

$ git branch clone-branch
$ git checkout clone-branch
$ vim sample.c
#include<stdio.h>

int main(){
    printf("Gitコマンド一覧\n");
    printf("git init : リポジトリを作成\n");
    printf("git status : Gitの状態を確認\n");
    printf("git add : ファイルをステージ\n");
    printf("git commit : コミットする\n");
    printf("git log : コミット履歴を見る\n");
    printf("git clone : クローンをつくる\n");
    branch_name();
    return 0;
}

クローンを作るコマンドを追加した「sample.c」を作成した。こちらからも当然pushすることができ

$ git add sample.c
$ git commit -m "add description of clone"
$ git push origin clone-branch

とすれば「sample.c」の変更がプッシュ(アップロード)できる。

プルリクエス

一旦ここで現在のリモートリポジトリの状況を図で確認してみよう。
f:id:mattyan1053:20180722180029j:plain
ここでtest-branchで作業していた人が「master」にマージしたいとする。通常、ローカルリポジトリ

$ git checkout master
$ git merge test-branch

とすればマージできるのだが、複数人で開発しているときは、勝手に本流である「master」ブランチにマージするのは好ましくない。このリモートリポジトリの管理者に頼んで、チェックしてもらってからマージをする必要がある。そこで登場するのが「プルリクエスト」だ。
ここでGitHubでCodeを確認してみると右の方に「Compare & pull request」と出ているのが見つかるだろう。
f:id:mattyan1053:20180722180543p:plain
今回は「test-branch」のほうをプルリクエストしてみる。
すると
f:id:mattyan1053:20180722180843p:plain
のように表示されるだろう。赤枠で囲った部分はどこからどこへマージするのか、下はリクエストのタイトルとコメントを書く場所である。必要なことが記入できたら「Create pull request」をクリックしよう。
すると、プルリクエストが作成される。GitHubリポジトリページのタブの「Pull requests」の部分が「1」と表示され、クリックすればリクエストが表示されるようになる。管理者は、ココからプルリクエストをチェックし、マージするか、修正させるかを決めることができる。

修正してもらう

内容を確認するには「File changed」の部分をクリックする。
f:id:mattyan1053:20180722181545p:plain
するとファイルの中身が表示される。そこでファイルの内容にカーソルをあわせると「+」ボタンが表示されるので、クリックすると、メッセージが残せる。必要なことを書いたら「Start a review」をクリックすれば良い。すると会話の履歴が残るので、また「test-branch」で作業している人が修正してプッシュ、同じようにコメントしてくれるのを待つ。
f:id:mattyan1053:20180722210733p:plain

開発者側はレビューに従って修正して再度同じブランチにプッシュ。これを問題がなくなるまで繰り返す。

マージする

すべての問題がなくなったら、「Conversation」タブの下の方にある「Merge pull request」をクリックする。問題なければ緑色のボタンが出ているはずである。
f:id:mattyan1053:20180722182314p:plain
確認のように「Confirm Merge」とでるのでこれもクリック。これで無事「master」にマージすることができた。上の方のプルリクエストタイトルの左下に「Merged」と表示されているはずだ。
f:id:mattyan1053:20180722182537p:plain
さて、これで管理者は開発者の作ったコミットを本流にマージすることができるようになった。しかし当然マージしようとしたら競合してしまうこともありうる。そこで、競合したときの対処について話たいところだが、「プル」という操作を知らなければ対処できないので、先にプルについて説明する。

プルする

現在のリモートリポジトリの状況はこう。
f:id:mattyan1053:20180722201633j:plain
masterが書き換わっている。このように、寝ている間とか、自分が作業していなくても他の人が作業を進めてリモートリポジトリに変更が加えられている場合がある。この変更を次作業するときはじめにローカルリポジトリのほうにも反映しておきたい。そこで「プル」というものを行う。プルはプッシュとは逆の操作で、ローカルリポジトリに行われた変更をローカルリポジトリにも反映するということだ。ローカルリポジトリに追加されたコミットをローカルリポジトリに追加する操作とも取ることができる。プルするには次のようにする。
今回は「remote/master」ブランチをそのままローカルの「local/master」ブランチに反映したいので、まず現在のブランチを「master」に変更する。その後プルを行う。

$ git checkout master
$ git pull origin master

これで「remote/master」の内容が「local/master」にプル(適用)された。このように、プルは、

$ git pull <リモートリポジトリの名前> <リモートのブランチ名>

というふうに使う。これで実際にログを確認すると

$ git log --oneline --graph

f:id:mattyan1053:20180722202410p:plain
とプルリクエストでマージされた「master」のコミットをローカルリポジトリに反映できていることがわかる。

プルの正体

実は、プルというのは「remote/<branchname>」から「local/<branchname>」へのマージである。基本的には同じブランチをマージしていることになるので、競合は起きないが、リモートのブランチからローカルの異なるブランチへ(例えばmasterからclone-branchへ)プルすると競合が起きることがある。

プルリクエストの競合

現在のリモートリポジトリの状態はこんな感じ
f:id:mattyan1053:20180722201633j:plain
この絵でわかるかもしれないが、ここで「clone-branch」がプルリクエストを作成し、「master」にマージしようとすると競合が発生する(sample.cが同時に書き換わってしまっている)。
そこで、管理者側でこの競合をどう解決するかという話だ。
先ほどと同様の手順で「clone-branch」のプルリクエストを作成する。
途中、どこからどこにマージするか表示される部分の右に「Can't automatically merge.」と表示されるが気にしないで良い。
さて管理者になりきってみてみる。Pull requestを開くと次のように表示される。
f:id:mattyan1053:20180722195125p:plain
当然「Resolve conflicts」をクリックといきたいところだが(こちらでも解決できる)、ブラウザ上で解決するよりローカルリポジトリで作業したほうが怖くないので今回はこちらでやってみる。
まずは現在リモートリポジトリに存在する「remote/master」を、「local/clone-branch」にプルしてみると、自動的に「remote/master」から「local/clone-branch」へのマージを試みる。当然、前のプルリクエストによってリモートの「master」は書き換わっているので、競合が発生する。

$ git checkout clone-branch
$ git pull origin master
remote: Counting objects: 1, done,
remote: Total 1 (delta 0), reused 0 (delta 0), pack-reused 0
Unpacking objects: 100% (1/1), done.
From github.com:mattyan1053/git-test
 * branch            master     -> FETCH_HEAD
   5463036..b39b03e  master     -> origin/master
Auto-merging sample.c
CONFLICT (content): Merge conflict in sample.c
Automatic merge failed; fix conflicts and then commit the result.

プルのところで正体はマージであるという話をしたが、ここでよくわかるだろう。マージの競合を解決するのと同じ手順で競合を解決する。

$ vim sample.c
#include<stdio.h>

int main(){
    printf("Gitコマンド一覧\n");
    printf("git init : リポジトリを作成\n");
    printf("git status : Gitの状態を確認\n");
    printf("git add : ファイルをステージ\n");
    printf("git commit : コミットする\n");
    printf("git log : コミット履歴を見る\n");
//<<<<<<< HEAD
    printf("git clone : クローンをつくる\n");
//=======
    printf("git push : プッシュする\n");
//>>>>>>> b39b03e8e0d5e0908abe4475a679aa8d33723bcf
    branch_name();
    return 0;
}

消すべき部分の文頭を「//」としてみた。この部分を削除すれば良い。
これで競合が解決したので、「add」「commit」していく。

$ git add sample.c
$ git commit -m "Merged master to clone-branch"

あとは再度プッシュする。

$ git push origin clone-branch

これでGitHubに戻ってみてみると
f:id:mattyan1053:20180722204339p:plain
競合が解決したので、マージができるようになっている。
プルリクエストの競合を解決するために行った操作を図に表すと次のようになる。
f:id:mattyan1053:20180722204758j:plain
これでプルリクエストの競合は解決された。

まとめ

今回はローカルリポジトリとリモートリポジトリのデータを同期する様々な方法について触れたが、結局のとこ概要は次のとおりである。

これらのコマンドを用いてチーム開発をより便利にすすめていくことができる。
次回はラスト。履歴をコントロールしてみたりマージの仕様だったりといくつか後回しにしていたところを考える。

 【Git入門】Gitを使い始めたい人へ「リモートリポジトリをつくる(GitHub)」 ~A Beginner to Beginners~ 【その5】 : 前 次 : 【Git入門】Gitを使い始めたい人へ「その他細かいこと」 ~A Beginner to Beginners~ 【その7】

【Git入門】Gitを使い始めたい人へ「リモートリポジトリをつくる(GitHub)」 ~A Beginner to Beginners~ 【その5】

【Git入門】Gitを使い始めたい人へ「ローカルリポジトリの操作3」 ~A Beginner to Beginners~ 【その4】 : 前 次 : 【Git入門】Gitを使い始めたい人へ「リモートリポジトリを操作する」 ~A Beginner to Beginners~ 【その6】

目次

前回までの内容では、自分のPC内で作業を行ってバージョン管理をしてきた。ここからはチームで管理をするためのリモートリポジトリについて考える。

ローカルリポジトリとリモートリポジトリ

リモートリポジトリを説明するにあたり、まずは今までいじってきたリポジトリについて少し説明する。

ローカルリポジトリ

今までGit管理してきたリポジトリは「ローカルリポジトリ」と呼ばれるものである。
ある個人が自分のPC上につくるリポジトリで、使用するのは操作している自分だけである(複数人で使用しているPCの場合はその限りではないが・・・・・・)。

リモートリポジトリ

これから扱っていこうとしているリポジトリが「リモートリポジトリ」と呼ばれるものである。
これはオンライン上に保存されているリポジトリで、複数人がそこにアクセスすることにより共有することができるものである。開発者は自分のローカルリポジトリでつくったコミットをここに加えていく(アップロードしていく)ことになる。
これもリポジトリであるので、当然コミットやブランチなどを持っている。
通常、みんなが好き勝手にリモートリポジトリの内容を変更していくと訳がわからないことになってしまうので、管理者を誰かしら一人または複数人決めておくことになるだろう。

ローカルリポジトリとリモートリポジトリの関係

図で表してみよう。たとえば、ローカルリポジトリとリモートリポジトリの「master」ブランチだけが同じ状態だと次のようになる。
f:id:mattyan1053:20180722143752j:plain
ブランチがリモートリポジトリのものなのかローカルリポジトリのものなのか明確にするため、ブランチ名の手前に「remote/」や「local/」をつけてみた。ここで、「remote/master」の内容と「local/master」の内容は等しくなっている。
このように、共有していくブランチを決めておいて、みんなでそこを書き換えるように変更点をアップロードしていけば、チームで開発を進めることができるということだ。

実際の開発の流れ

メインとなるブランチ(ここでは「master」)を変更するというのは重要な行為である。したがって、次のようにリモートブランチを利用してみる。
あるプロジェクトが始動したとしよう。初期状態は次のようになっている。
f:id:mattyan1053:20180722150240j:plain

ここで、Aさんがリモートリポジトリから「master」の内容をコピーし、「local/test-branch-A」で作業を始めたとしよう。このブランチを「Aさんのブランチ」として設定する。さらにAさんはここから分岐して「local/dev-A」ブランチをつくり、そこで作業を行った。最後にAさんは「local/dev-A」を「local/test-branch-A」にマージして、Aさんのブランチである「local/test-branch-A」を「remote/test-branch-A」にアップロードした。このとき、「local/dev-A」ブランチはアップロードされていないのでリモートリポジトリには存在していないことに注意したい。
f:id:mattyan1053:20180722145705j:plain
ここで、Aさんはリモートリポジトリの管理者に作業が終わったのでmasterにマージしてほしい旨を伝える。そこで、管理者は「remote/test-branch-A」を確認し、問題がなければマージする。
f:id:mattyan1053:20180722152230j:plain
更にAさんが開発を続ける場合はAさんが「remote/master」ブランチをダウンロードして、「local/test-branch-A」にマージ。そしてまたAさんのブランチである「local/test-branch-A」で作業を続けることができる。
f:id:mattyan1053:20180722152538j:plain
また、Bさんも開発に参加しているときは、Aさんのブランチ「remote/test-branch-A」と同様に「Bさんのブランチ」となる「remote/branch-B」を作成して、そちらで作業を進めていけば良い。「master」にマージしてほしいときは管理者に報告する。
f:id:mattyan1053:20180722153210j:plain
こうすることで他人のブランチ、作業にふれることなく別々の人物で並行して作業ができる。本流(ここでは「master」)に合流させるときだけ管理者がチェックするようにすればよい。

リモートリポジトリを作成する(GitHub

リモートリポジトリがなんなのかわかったところで実際にリモートリポジトリを作成しよう。オンライン上においておくものなのでサーバーが必要だ。今回は無料で利用することのできるリモートリポジトリサービス、GitHubを使ってみよう。英語になってしまうがなんとか耐えて。

アカウントを作成する

まずはGitHub(github.com)にアクセスする。
次に右上のSign Upをクリックする。
f:id:mattyan1053:20180722153937p:plain
次に、ユーザー名、Eメールアドレス、パスワードを入力して「Create an account」をクリック。
f:id:mattyan1053:20180722154114p:plain
プランの選択だが今回はフリープランを使用するので上をチェック。下の2つのチェックボックスは任意。メール欲しい人は一番下にチェックを入れよう。「Continue」をクリック。
f:id:mattyan1053:20180722154413p:plain
最後にアンケートのようなものがある。面倒な人は下の方の「skip this step」。アンケートに答えてあげる優しい人はぜひ。上からプログラミング経験、GitHubの使用目的、あなたの立場、あなたの興味(自由記述)である。答え追えたら「Submit」。
f:id:mattyan1053:20180722154719p:plain
あとは記入したメールアドレスに登録確認メールがきてるはずなので、「Verify email address」をクリック。
これでアカウント作成は完了だ。

公開鍵の設定

さて、アカウントが作成できたところでいくつか設定をしよう。
f:id:mattyan1053:20180722155159p:plain
画像とかBio、URLなどはお好みで。今回設定したいのはココ。
f:id:mattyan1053:20180722155350p:plain
ここで一旦コマンドに戻ろう。
次のように入力してみる。

$ ssh-keygen -t rsa

すると次のようになるだろう。

$ ssh-keygen -t rsa
Generating public/private rsa key pair.
Enter file in which to save the key (/Users/(username)/.ssh/id_rsa):

ここは何も記入しなくてOKなのでEnter(むしろ入力すると少し面倒な設定が増える)。すると、

$ ssh-keygen -t rsa
Generating public/private rsa key pair.
Enter file in which to save the key (/Users/(username)/.ssh/id_rsa):
Enter passphrase (empty for no passphrase):

と出るはずなので好きなパスワードを入れる。パスワードを指定しない場合は何もしないでEnter。初回はなしで大丈夫だろう。すると

$ ssh-keygen -t rsa
Generating public/private rsa key pair.
Enter file in which to save the key (/Users/(username)/.ssh/id_rsa):
Enter passphrase (empty for no passphrase):
Enter same passphrase agein:

と言われるのでもう一度同じパスワードを入力(あるいは未入力)してEnter。 すると、上の「Enter file in which to save the key」の後ろに表示されたところにRSA暗号鍵が作成される。よくわからない人はGitHubと通信するのに必要なものだと思おう。「cd」で表示されたところに移動してディレクトリの中身を表示してみると、

> cd <表示された場所>
> dir
$ cd <表示された場所>
$ ls

で「id_rsa」と「id_rsa.pub」の2つの鍵が生成できていることがわかる。
このうちGitHubには公開鍵のほうを登録したいので

> clip < ~/.ssh/id_rsa.pub
  • Mac、Lunuxの場合
$ pbcopy < ~/.ssh/id_rsa.pub

として鍵の内容をクリップボードにコピーしておき、GitHubの「New SSH key」のところから
f:id:mattyan1053:20180722161202p:plain
鍵の名前と内容を貼り付けして「Add SSH key」をクリックする。
f:id:mattyan1053:20180722161532p:plain
これで通信の準備は完了。「ssh -T git@github.com」と入力して

$ ssh -T git@github.com
Hi <username>! You've successfully authenticated, but GitHub does not provide shell access.

と表示されていればOK。

リモートリポジトリを作成する

ここまでできたら、右上の「+」をクリックしてメニューを開く。そして「New repository」を選択。
f:id:mattyan1053:20180722162314p:plain
リポジトリーの名前を入力する。今回は「git-test」。下のチェックボックスは「Public」。フリープランではこれしか選択できない。最後に「Create repository」を選択すれば終わりだ。READMEはチェックしなくてOK。 f:id:mattyan1053:20180722163130p:plain
これでリモートリポジトリの作成は完了した。

まとめ

今回はリモートリポジトリはなにかということと、GitHubでの作り方を説明した。
リモートリポジトリはチームでプロジェクトを共有するためのリポジトリであり、ローカルリポジトリと適宜同期を取りながら作業を進める。
GitHubではアカウントを取得後、鍵を指定、リポジトリを作成した。鍵は一度作っておけばそのアカウントのリポジトリはどれでも使えるので以後新しくリポジトリを作成するときは不要である(パスワードをつけるなど変更を加えたい場合は再度設定する必要がある)。

次回は作ったリモートリポジトリにデータをアップロードしたりと操作を加えていくことになる。

【Git入門】Gitを使い始めたい人へ「ローカルリポジトリの操作3」 ~A Beginner to Beginners~ 【その4】 : 前 次 : 【Git入門】Gitを使い始めたい人へ「リモートリポジトリを操作する」 ~A Beginner to Beginners~ 【その6】

【Git入門】Gitを使い始めたい人へ「ローカルリポジトリの操作3」 ~A Beginner to Beginners~ 【その4】

【Git入門】Gitを使い始めたい人へ「ローカルリポジトリの操作2」 ~A Beginner to Beginners~ 【その3】 : 前 次 : 【Git入門】Gitを使い始めたい人へ「リモートリポジトリをつくる(GitHub)」 ~A Beginner to Beginners~ 【その5】

目次

Gitの操作もだいぶ学んできた。このへんで頭の中を一旦整理しつつ、細かい話をしていきたい。

HEADとブランチ

前回、HEADとブランチと呼ばれているものを移動させてワークツリー(作業ディレクトリ)の内容をコントロールしてきた。
復習がてら確認しておくと、ブランチを切り替えればそのブランチが指し示すコミット(記憶のかたまり)の状態に変更してくれる、というものだった。HEADを前の状態に戻していけば、その前の状態がワークツリー(作業ディレクトリ)に復元された。
今までコミットをどちらも指し示している曖昧な状態で進めてきたが、これらは一体どういうものなんだろうか。

コミットの正体

これまで作成してきた「コミット(記憶のかたまり)」に含まれる情報は次のようなものである。

  • ワークツリーの状態
  • コミットを作成したユーザーの情報(ユーザー名、E-mailアドレス)
  • コミットメッセージ
  • 親コミットの名前

図で表すとこう
f:id:mattyan1053:20180721224606j:plain
コミットには自分の一つ手前のコミット(親コミット)の名前が記録されている。つまり、今いるコミットから親コミットへ順番にたどっていけば最初のコミットまでたどり着けるというわけだ。逆に、自分の後ろのコミットについての情報は全く含まれていない。したがって、親コミットから子コミットのほうは認識できないということである。
今まで

$ git log

としたとき、今いるコミットより下のコミットの情報は表示されなかったはずだ。
たとえば
f:id:mattyan1053:20180721003349j:plain
この状態でみると

$ git log --oneline
0d5bf59 Third commit
393f3a1 Second commit
f0c1e0e first commit

と表示されたが、一つもどってログを見ると

$ git reset --hard HEAD~
$ git log --oneline
393f3a1 Second commit
f0c1e0e first commit

f:id:mattyan1053:20180721005314j:plain

となり、Third commitは表示してくれない。この原因はコミットに含まれる情報にあったわけだ。

現在の状態について

Gitにおける現在の状態について整理しよう。
現在の状態は、

  • 現在のコミット
  • 現在のインデックス
  • 現在のワークツリー

の3つに分けることができる。
「現在のコミット」は問題ないだろう。通常、最後にコミットしたものを指し示し、「git reset」などを使って移動した場合はその移動先のコミットを表す。
「現在のインデックス」はステージ(インデックス(変更したものリスト)に追加)されたもののことだ。「git add」で追加したりできる。
「現在のワークツリー」とは作業ディレクトリの状態である。普段ファイルを開いたり編集したりできるあれ。コミットしたあとにまた変更してたりしてなかったり。 コミットの中身は変更されることは基本ないがこちらは随時変更されていくだろう。 図で表してみるとこんな感じ。
f:id:mattyan1053:20180721231840j:plain
これを把握した上で次からを読み進めてもらいたい。

ブランチの正体

前回、ブランチについて説明した。ブランチは枝のことだと説明してきたが、実はあまり正しくない。
ブランチとは、コミットを指し示すものである。次の図を見てみよう。
f:id:mattyan1053:20180721232751j:plain
この図では「master」がコミット「0d5bf59」を、「test-branch」がコミット「84507d3」を指し示している。
通常、ブランチは枝一つにつきブランチを一つ用意する。そしてそのブランチが枝の先端を指し示すように動かすものなのだ。
逆に言えば「master」や「test-branch」を別の枝を指し示すように移動することもできるということだが、無意味なのでやらない。

HEADの正体

最後にHEADの正体について考える。ブランチの流れから察している人もいるかもしれないが、HEADもなにかを指し示すものだ。HEADは指し示すものが複数存在し、同時に指し示している。指し示しているのは次の3つだ。

  • 現在のブランチ
  • コミット直後のインデックス
  • コミット直後のワークツリー

「現在のブランチ」を指し示すとはどういうことか。現在「master」ブランチにいればHEADは「master」を指し示し、現在「test-branch」にいればHEADは「test-branch」を指し示す。これを「HEAD->master」などと表していて、「git log」などで見たことがあるのではないだろうか。図で表すと、
f:id:mattyan1053:20180721234105j:plain
この状態ではHEADは「master」を指し示しているが、

$ git checkout test-branch

とするとヘッドの指し示すブランチは「test-branch」に変わり
f:id:mattyan1053:20180721234204j:plain
となる。
ブランチを指し示していれば実質コミットも指し示すことができているということになる。

「コミット直後のインデックス」はそのままの意味であり、ステージされているものすべてを表す。
「コミット直後のワークツリー」も同様そのままの意味を示し、最後のコミットから変更されている部分も含むことになる。

C言語などの「ポインタ」がわかる人へ

C言語などの「ポインタ」がなにか知らない人は飛ばして次へ Gitのリポジトリを自在に操る
実は、これらの「指し示す」という考えはC言語の「ポインタ」の概念と照らし合わせるとよく理解できる。HEADはブランチ、空のインデックス、元のワークツリーへのポインタをメンバにもつ構造体のように捉えるとわかりやすいかもしれない。「HEAD->master」のような表現にアロー演算子やん!って反応した人はある意味正しい。ブランチはコミットへのポインタであり、コミットは親コミットへのポインタ、ワークツリーへのポインタなどを保持していることになる。つまるところポインタのオンパレード

Gitのリポジトリを自在に操る

ここまでの説明をしたことで、リポジトリをうまく操るために準備が整った。今までの知識をフル活用して読んでいきたい。

revert

はじめてみるコマンドかもしれないが、便利なので掲載。
最終コミットの内容を取り消したコミットを作成するのに使う。
一度コミットしたものを取り消すのだが、その取り消したこともログに残したいことがあるだろう。そういうときのコマンドである。
具体的には直近のコミットを取り消したコミットを作り出すことになる(一つ前のコミットができあがる)。
f:id:mattyan1053:20180722013104j:plain

$ git revert HEAD

git reset

さて、ここまできて今まで幾度とみてきたgit resetについて考える。とても重要なコマンドなので詳しくしっておきたい。
「git reset」の使い方は

$ git reset [オプション] <コミットを指すもの>

という形だ。「コミットを指すもの」としては「HEAD」、「ブランチ」、「コミットの名前(ハッシュ値)」とその相対位置(HEADの一つ前、ブランチの一つ前など)を指定できる。一つ前を指し示すときは「~」をつけるなどする。例えばHEADが指し示すコミットの一つ前のコミットを指定したいときは

$ git reset HEAD~

というようにすれば良い。
オプションはとりあえず「--hard」「--soft」「--mixed(つけなくても良い、デフォルト)」の3つを把握しておけばよい。

[--mixed]オプション

まず「--mixed」について説明する。これはデフォルトなので、オプションを何もつけないで「git reset」した場合は自動的に「--mixed」オプションをつけたときと同じ扱いになる。
このオプションをつけると、現在のインデックス、及びコミットが現在の状態から指定したコミットの作成直後までリセットされる。ワークツリーはリセットされないので、変更したファイルはそのまま残っている。どういうことか、図で表してみると次のようになる。
f:id:mattyan1053:20180722004610j:plain
コミットとインデックスはHEADの状態に書き換えられているが、ワークツリーは現在の状態のままなことがわかる。

次の例を見てみよう。

【その2】で説明したように、

$ git add <filename>

で現在のインデックス(変更したものリスト)に変更したファイルを追加できた。
ここで、変更したけどまだコミットしたくないものを間違って「add」してしまったときのコマンドとして

$ git reset HEAD <filename>

を紹介したとおもう。これはどういうことかというと「<filename>で指定したファイルを、HEADの指し示す状態にリセットする」ということだ。コミット直後は当然インデックスは空であるから、指定したファイルをコミット直後の状態に戻すとまだステージされる前の状態に戻る。けれどもワークツリーは何も変化しないので、書き換えた部分は取り消されることはない。したがって、結果的には「add」を取り消すことができるようになる。同様にして、すべてのインデックスを取り消す場合は

$ git reset HEAD

とすればいいことがわかる。

[--soft]オプション

次に「--soft」についてだ。このオプションでは、コミットのみ後ろに書いたコミットの状態にリセットする。インデックスやワークツリーは書き換えられない。つまりコミットだけリセットされるということだ。
図で表してみよう。
f:id:mattyan1053:20180722005121j:plain
コミットの部分だけHEAD~の状態にリセットされ、ほかはそのままなことがわかる。図の状態をコマンドで打つなら

$ git reset --soft HEAD~

となる。これはファイルの変更を維持したまま直前のコミットを取り消す動作を意味する(コミット1は消えてしまうが、sample.cの変化は生きていて、ステージされたまま)。
この操作に関しては、実は後述する

$ git add sample.c
$ git commit --amend

のほうが便利だったりする。

[--hard]オプション

今までかなり多用してきたように見える[--hard]オプションについてだ。「--hard」オプションでは後ろに書いたコミットの状態にリセットし、なおかつインデックス、ワークツリーも元の状態にリセットする。図で表すと
f:id:mattyan1053:20180722005752j:plain
この場合、一つ前のコミットの状態を完全に上書きしていることになる。これまで、一つ前のコミットに戻る時や指定のコミットに戻る時

$ git reset --hard HEAD~

$ git reset --hard 0d5bf59

などとしてきたのは、現在の状態を指定の状態に上書きしたということになる。結果的には指定のコミットに移動したのと同じになるが、コミットしていないワークツリーの内容やインデックスの内容は完全に消えてしまうので注意が必要である(コミットは残るがハッシュ値を直接入力しないと戻れない)。
もっとも、現在のコミットを別のブランチにマージして、そのあと一つ前のコミットに戻って作業を進めた後再度マージすると競合が発生することになりかねないので、「コミットの内容を取り消す」のならば「git reset [--hard] HEAD~」をするよりも前述した「git revert」を利用することで「コミットの内容を取り消した履歴」を残しておくほうがベターであろう。

$ git reset --hard HEAD~

の場合
f:id:mattyan1053:20180722141151j:plain

$ git revert HEAD

の場合
f:id:mattyan1053:20180722223307j:plain
競合を回避するためには変化の過程が一意的でなければならない。

ブランチの移動

さて、ここまでやってきたところで注意したいところなのだが、「git reset [option] HEAD~」などとHEADの位置をリセットしてきた。このとき、HEADの位置変更と同時にブランチの位置も変わっていることに注意したい。例えば現在「master」ブランチをHEADが指しているのなら

$ git reset --hard HEAD~

としたときはHEADのみでなく「master」も一つ手前のコミットを指すように変化していることになる。また、

$ git reset --hard master~

などと指定することもでき、この場合は「master」ブランチの一つ手前にHEADとHEADの指し示すブランチが移動することになる。つまり、現在「master」ブランチにいるときに強引に

$ git reset --hard test-branch~

とすれば、HEADと「master」は「test-branch」が指し示しているコミットの一つ手前に移動することになる。
f:id:mattyan1053:20180722152014j:plain
まあこんなこと決してやってはいけないのだが・・・・・・。

git commit

今までさんざん行ってきたgit commitだが、コミットを修正したいことがある。図で表すと
f:id:mattyan1053:20180722011847j:plain
こんな感じ。「sample.c」をコミットしたあと再度変更したが、その変更も前のコミットにいれてしまいたくなったときの方法。やり方は単純で、変更したものをインデックスに追加したあと、もう一度コミットするのだが、オプションで[--amend]をつけることである。こうすることで変更したものリストの内容を新規のコミットではなく直前のコミットの内容に上書きしてくれる。

$ git add sample.c
$ git commit --amend

使わなくなったブランチ

分岐して使っていたブランチをマージし、そのブランチが不要になったときはブランチを破棄することができる。

$ git branch -d <ブランチの名前>

実際の開発とブランチの使い分け

これで自分の操作しているリポジトリはほぼ自由に操ることができるようになった。では実際の開発ではどのようにするのがいいのだろうか。
f:id:mattyan1053:20180722014615j:plain
基本的にはmasterで作業をせずに、別のブランチ(develop)で作業をして、良いところでmasterとマージさせていく。途中の機能追加などはdevelopから更に別のブランチに分岐して作業をしてdevelopにマージしていく、という流れが開発としてはやりやすいだろう。こういったルールはチームで決めていきたい。
上記のものは「A successful Git branching model」というのを参考にした。ググれば日本語になっているやつも出てくると思う。

まとめ

コミットやHEAD、ブランチの正体について学習し、それを踏まえていくつかコマンド(特にgit reset)について説明した。「git reset」は結局

$ git reset [オプション(どこをリセットするか)] <リセット元のデータはどこにあるか>

というふうにまとめられる。他のコマンドも同様、現在のHEADがどうなっているかに注視していれば良い。

次回からリモートリポジトリについて進めていく。

【Git入門】Gitを使い始めたい人へ「ローカルリポジトリの操作2」 ~A Beginner to Beginners~ 【その3】 : 前 次 : 【Git入門】Gitを使い始めたい人へ「リモートリポジトリをつくる(GitHub)」 ~A Beginner to Beginners~ 【その5】

【Git入門】Gitを使い始めたい人へ「ローカルリポジトリの操作2」 ~A Beginner to Beginners~ 【その3】

【Git入門】Gitを使い始めたい人へ「ローカルリポジトリの操作1」 ~A Beginner to Beginners~ 【その2】 : 前 次 : 【Git入門】Gitを使い始めたい人へ「ローカルリポジトリの操作3」 ~A Beginner to Beginners~ 【その4】

目次

前回の続き

前回は変更したファイルを「変更したリスト」に追加(ステージ)して、それを記憶(コミット)するまでの流れをやった。実はこのコミットしたことで「記憶のかたまり」がつくられる。
今回はこのコミットしたものたちをどう扱っていくか説明していく。
今後、この「コミットすることでできた記憶のかたまり」を「コミット」と呼んでいく。

リポジトリに記憶された指定のコミットに移動する

前回、作ったファイルをコミットする(記憶する)ことでバックアップのようなものをとることができた。今回は、それをどのように利用していくか考える。
まず、準備として追加で2回ほど前回のsample.cを編集してステージ(変更リストに追加)してコミット(記憶)する。(例ではVimを使用)
sample.cを編集して

$ vim sample.c
#include<stdio.h>
int main(){
    printf("Gitコマンド一覧\n");
    printf("git init : リポジトリを作成\n");
    printf("git status : Gitの状態を確認\n");
    printf("git add : ファイルのステージ\n");
    printf("git commit : コミットする\n");
    return 0;
}

コミットしたあと、もう一度sample.cを編集する

$ git add sample.c
$ git commit -m "Second commit"
$ vim sample.c
#include<stdio.h>
int main(){
    printf("Gitコマンド一覧\n");
    printf("git init : リポジトリを作成\n");
    printf("git status : Gitの状態を確認\n");
    printf("git add : ファイルのステージ\n");
    printf("git commit : コミットする\n");
    printf("git log : コミット履歴を見る\n");
    return 0;
}

コミットする。

$ git add sample.c
$ git commit -m "Third commit"

こうしてコミット(記憶)を続けていくと、次の図のようになる。四角形一つ一つがコミットであると捉えてほしい。
f:id:mattyan1053:20180721003349j:plain
コマンドで見るとこんな感じ

$ git log --oneline --graph
* 0d5bf59 (HEAD -> master) Third commit
* 393f3a1 Second commit
* f0c1e0e first commit

HEADというものが最新のコミット(ver3)のところに書いてあるが、詳しいことはおいておいて今は現在どの状態かを指し示しているものだと考えよう(正確には正しくないが今はこの考え方で進める)。
このヘッドの位置をコントロールすることで現在の作業ディレクトリの状態をコントロールすることができる。
現在の状態でsample.cの中身を見てみると(catコマンドで中身が見られる)

$ cat sample.c
#include<stdio.h>
int main(){
    printf("Gitコマンド一覧\n");
    printf("git init : リポジトリを作成\n");
    printf("git status : Gitの状態を確認\n");
    printf("git add : ファイルのステージ\n");
    printf("git commit : コミットする\n");
    printf("git log : コミット履歴を見る\n");
    return 0;
}

当然こうなっているはずである。しかしここで

$ git reset --hard HEAD~

とすると(HEADの後ろについているのはチルダ「~」)

$ cat sample.c
#include<stdio.h>
int main(){
    printf("Gitコマンド一覧\n");
    printf("git init : リポジトリを作成\n");
    printf("git status : Gitの状態を確認\n");
    printf("git add : ファイルのステージ\n");
    printf("git commit : コミットする\n");
    return 0;
}

となり、Third commitする前の、ver2のsample.cになっているはずだ。つまり何が起きているかと言うと

f:id:mattyan1053:20180721005314j:plain
HEADが一つ前のものに移動しているということになる。一つ前のところに戻せるというわけだ。
ちなみに、「HEAD」のところをうまいことやれば他の場所に移動もできる。「HEAD」にすればver3の部分を指し示すことになるので、最後にコミットしたときの状態に一致することになる。また「HEAD~」の「~」の数を増やしたり、「~2」としてみると戻る数を指定できる。たとえば

$ git reset --hard HEAD~2

とすればHEADはver3からver1のところに移動する。
この「git reset」の注意しなければならないところはHEADを交代させるとその先のver3のところは「git log」では表示できず、現在位置からの相対位置(HEADをいくつずらすか)ではもとに戻ることができない(理由はあとで詳しく説明する)。もし一度resetしたものを取り消してもとのresetする前の状態に戻したいときは

$ git reset --hard ORIG_HEAD

とすると一度前のresetの操作を取り消せる。
他に、直接ある指定のコミットへHEADを移動する方法があり、HEADの移動ログをたどる

$ git reflog

などからHEADの移動履歴を見ることができ、コミットのハッシュ値(先頭のほうに表示されるごちゃごちゃの英数字)を直接入力すれば移動できる。ハッシュ値とは、どのコミットを指し示すかわかるようにするための名前のようなものと捉えて良い。(下図参照) f:id:mattyan1053:20180721011229j:plain
ここまでの操作をしてみると、Gitがバージョン管理ソフトとして機能する過程の片鱗を見ることができたのではないだろうか。「git log」で見たコミットメッセージ(変更内容)をみて、戻りたい位置に任意で戻ることができるわけだ。
これでコミットさえしておけばその時点まで巻き戻したりもとに戻したりすることが容易にできるようになった。

コミットの分岐

先程までのコミットは一直線型に変更を続けていった。
ところが実際に開発を進めていると、バグの修正だったり、他の人と協力してやるためにバージョンが少し分岐したりすることがあるだろう。そういうときの対処法について学ぶ。

先程と同様に、コミットを3回した状態を考える。(ヘッドはVer3を指している状態)
f:id:mattyan1053:20180721164601j:plain
現在それぞれのコミット(記憶したもの)は一直線上に並んでいるが、実はこれを分岐させることができる。そのために必要な考え方が「ブランチ」というものだ。

ブランチを扱う

いつもどおりブランチがなんなのかということはさておき、「branch」という文字は見たことがあるかもしれない。そう、「git status」と入力したときに一番上に出てきたアレだ。

$ git status
On branch master
nothing to commit, working tree clean

一番上の「On branch master」である。
とりあえず「git branch」と入力してみてほしい。すると次のようになるだろう。

$ git branch
* master

「git status」でみた「master」という文字が表示されたはずだ。これは現在いる「ブランチ」が「master」であることを示している。

ブランチって何

それでは後回しにしていたブランチについて説明する。コミット(記憶したもの)を分岐させる、ということは次の図のようになる。
f:id:mattyan1053:20180721165850j:plain
さて、コミットを枝分かれさせたはいいものの、自分が今どの枝にいるのかわからないと困ってしまう。これを解決するのが「ブランチ」である。それぞれの枝に次のように名前をつけてみる。
f:id:mattyan1053:20180721170353j:plain
これで枝を区別できるようになった。この枝の名前を「ブランチ」と呼び、細かく言えば枝全体でなく、枝の先端を指し示すものとなる。ちなみにGitのデフォルトのブランチ名(真ん中の枝といってよい)は「master」である。サーヴァントは召喚しません。

実際にブランチをつくる

ブランチがなんとなくイメージできたところで実際に操作を行ってみよう。
ヘッドの移動をやったときの状態
f:id:mattyan1053:20180721003349j:plain
からスタートしてみる。今回はVer3のところから分岐したいとしよう。まずヘッドをVer2のところに移動する。「git log」でコミット履歴を確認してみると、Ver3のところがなかったことのようになっているだろう。

$ git reset --hard HEAD~

f:id:mattyan1053:20180721005314j:plain
ここから分岐させていく。ちなみにこの時、ブランチ(枝)「master」の指し示す位置はVer2であることに注意したい(ヘッドと一緒に移動する)。まずは新しくブランチ(枝)を作る。

$ git branch test-branch

これで新しく枝を生やすことができた。図で表すと
f:id:mattyan1053:20180721172415j:plain
コマンドで実際に今ある枝を確認してみると

$ git branch
* master
  test-branch

と表示されるだろう。ここで、「*」がついているブランチ(枝)は今伸ばそうとしている(今現在いる)ブランチである。分岐させて「test-branch」を伸ばすには伸ばす枝を変更せねばならない。そこで次のようにコマンドを入力する。

$ git checkout test-branch

これで現在いるブランチ(枝)を確認すると

$ git branch
  master
* test-branch

と表示されたことだろう。次に、分岐させる前に作業ディレクトリに何かしら変化がなければ分岐させていく意味がないので新しくファイルを作ってみる。

$ vim branch.c
#include<stdio.h>

int branch_name(){
    char branch[] = {"test-branch"};
    printf("追加したブランチ : %s\n", branch);
    return 0;
}

と追加したブランチも表示するコードを書いた。
この結果をコミット(記憶していく)。このとき「add」するのは「branch.c」のみで問題ない。なぜなら「sample.c」には変更を加えていないからだ。

$ git add branch.c
$ git commit -m "add new file to print branch name"

現在の状態を図で表すと f:id:mattyan1053:20180721181058j:plain
これで枝分かれすることができた。
次に、分岐することができてももとのブランチ(枝)に戻ることができなければ意味がないので戻ってみる。操作はブランチを「test-branch」に切り替えたときと同様に「master」に切り替えるので

$ git checkout master

とすれば「master」ブランチに移動できる。なので実際にワークツリー(作業ディレクトリ)の中身を見てみると

> dir
(略)
2018/7/20 23:05                     .gitignore
2018/7/21 18:13                     sample.c
(略)
$ ls
sample.c     .gitignore

というようにVer2のときのワークツリー(作業ディレクトリ)の内容になっているだろう。更にVer3に移動したいので「git reflog」でVer3のハッシュ値を確認する。

$ git reflog
<ハッシュ値> (HEAD-> master) HEAD@{0}: checkout moving from test-branch to master
 (略)  
<ハッシュ値>HEAD@[n]: commit Third commit   
 (略)

このときnは何回前のヘッド移動かを表す。
これでVer3のコミットのハッシュ値が確認できたので移動してみる。

$ git reset --hard <3回目のハッシュ値>
HEAD is now at <3回目のハッシュ値> Third commit

と表示されるだろう。当然ワークツリーの中身はsample.cと.gitignoreのままであり、sample.cの中身は

$ cat sample.c
#include<stdio.h>

int main(){
    printf("Gitコマンド一覧\n");
    printf("git init : リポジトリを作成\n");
    printf("git status : Gitの状態を確認\n");
    printf("git add : ファイルをステージ\n");
    printf("git commit : コミットする\n");
    printf("git log : コミット履歴を見る\n");
    return 0;
}

となっている。図であらわすと
f:id:mattyan1053:20180721181701j:plain
となっている。
逆にブランチ「test-branch」に戻れば、ワークツリー(作業ディレクトリ)にはbranch.cが現れて

$ git checkout test-branch
$ cat branch.c
#include<stdio.h>

int branch_name(){
    char branch[] = {"test-branch"};
    printf("現在のブランチ : %s\n", branch);
    return 0;
}

となる。 つまり、ちょっと分岐して作業したい(バグ修正や機能追加など)ときには「git checkout <ブランチ名>」で別の枝に移動して作業していけばもとの方(master)には影響が出ないわけだ。

ブランチをマージする

さてバグ修正など分岐して作業を進めていて、その作業が完了したとする。この完了した分岐データを「master」のほうに統合していかなければ意味がない。この修正内容を大元の「master」でも反映するということだ。このブランチ(枝)を合流させることを「マージ(merge)する」という。
図で言うと
f:id:mattyan1053:20180721190655j:plain
「master」のほうで変更した「sample.c」を保持したまま「test-branch」の「branch.c」を追加したver4を作るという流れである。
「test-branch」のほうの「sample.c」はもともと「master」のver2の「sample.c」と同じであるので、「変更なし」ということになり、ver3のほうの「sample.c」がそのまま残る(「master」と「test-branch」の両方で「sample.c」に異なる変更を行った場合「競合」が起きてしまうがこれはあとで説明する)。
さて実際にマージをしてみよう。「master」のほうに「test-branch」を合わせる形なので、まず現在のブランチ(枝)を「master」に変更する(「master」に「test-branch」を統合するのと、「test-branch」に「master」を統合するのでは意味合いが異なる。どちら側に合流させるのか気をつけよう)。

$ git checkout master

次に、「master」に「test-branch」を合流(マージ)させる。

$ git merge test-branch

これで「dir」や「ls」をして実際のファイルを見てみると、「branch.c」が追加されていることがわかるだろう。ログを見てみると

$ git log --oneline

「Merge branch 'test-branch'」というコミットが追加されているのがわかる(上の図で言うver4)。更に面白いことができて、「--graph」オプションをつけてみると、

$ git log --oneline --graph

としてみると、図を頑張って文字と棒線使って表したようなものを見ることができる。
f:id:mattyan1053:20180721185632p:plain
一応GitのGUI(グラフィカルな感じのやつ)もあるので

$ gitk

と起動してみてみると
f:id:mattyan1053:20180721185754p:plain
とうまく合流している図を見ることができる。これで分岐させて作業していたものを本流に合流させて反映することができるようになった。

マージの競合

さて、これで実は基本的なバージョン管理はできるようになったのだが、いくつか問題が発生する。その一つが「マージの競合」だ。実例を見てみよう。現在「test-branch」で作業中だ。

$ git checkout test-branch

先程マージしてみる前の状態を見てみる。
f:id:mattyan1053:20180721181701j:plain
このとき「test-branch」にあるsample.cを更に書き換えたとしよう。

vim sample.c
#include<stdio.h>

int main(){
    printf("Gitコマンド一覧\n");
    printf("git init : リポジトリを作成\n");
    printf("git status : Gitの状態を確認\n");
    printf("git add : ファイルをステージ\n");
    branch_name();
    return 0;
}

これをコミットする。

$ git add sample.c
$ git commit -m "add branch_name() in sample.c"

このとき、「master」にある「sample.c」と「test-branch」にある「sample.c」では内容が異なる。
この状態で「master」に「test-branch」をマージしてみる。

$ git checkout master
$ git merge test-branch
Auto-merging sample.c
CONFLICT (content): Merge conflict in sample.c
Automatic merge failed; fix conflicts and then commit the result.

受験でみたような気がする英単語「conflict」。音ゲーでみたような気がする「conflict」。要は競合あったので失敗しましたという意。修正してコミットし直してねって言われるので修正する。

$ vim sample.c

お好みのテキストエディタで「sample.c」を開く。すると
f:id:mattyan1053:20180721203202p:plain
というように差分(変化した部分)を挿入してくれている。後はうまい具合になるように修正。今回はどちらの内容も残したいので単純に

<<<<<<< HEAD
=======
>>>>>>> test-branch

の部分を削除して

#include<stdio.h>

int main(){
    printf("Gitコマンド一覧\n");
    printf("git init : リポジトリを作成\n");
    printf("git status : Gitの状態を確認\n");
    printf("git add : ファイルをステージ\n");
    printf("git commit : コミットする\n");
    printf("git log : コミット履歴を見る\n");
    branch_name();
    return 0;
}

というコードに直す。修正がおわったらいつもコミットするように

$ git add sample.c
$ git commit -m "Merged sample.c"

とすればOK。つまりどういうことかというと、コミットは作らずに、「master」の「sample.c」を書き換えた状態にしてくれるので、あとは適宜書き換えた後普通にコミット(記憶)すればマージ(合流)完了、という流れである。
実質、「git merge test-branch」を実行したことによって「ver3のコミットの「sample.c」を書き換えて、新しく「branch.c」がステージ(変更リスト登録)された状態にした」ということがわかる。ためしに「git merge test-branch」で警告が出された後に「git status」してみると

$ git merge test-branch
Auto-merging sample.c
CONFLICT (content): Merge conflict in sample.c
Automatic merge failed; fix conflicts and then commit the result.

$ git status
On branch master
You have unmerged paths.
  (fix conflicts and run "git commit")
  (use "git merge --abort" to abort the merge)

Changes to be committed:

        new file:   branch.c

Unmerged paths:
  (use "git add <file>..." to mark resolution)

        both modified:   sample.c

と表示され、「branch.c」がすでにステージ(変更リストに登録)されていて、「sample.c」が書き換わったけどまだステージされていない状態であることが確認できる。

まとめ

今回はブランチについて詳しく説明した。
流れとしては

  1. ブランチをつくる
  2. 作業を進める
  3. マージする

の3ステップを踏むことになる。
これでGitでバージョン管理を柔軟にすることができるようになった。
次回はこれまでやってきたことをもう少し詳しく見ていく。具体的にはHEADやブランチの詳しい話をしつつこれらとコミットやステージとの結びつきを解説していくことになる。

【Git入門】Gitを使い始めたい人へ「ローカルリポジトリの操作1」 ~A Beginner to Beginners~ 【その2】 : 前 次 : 【Git入門】Gitを使い始めたい人へ「ローカルリポジトリの操作3」 ~A Beginner to Beginners~ 【その4】

【Git入門】Gitを使い始めたい人へ「ローカルリポジトリの操作1」 ~A Beginner to Beginners~ 【その2】

【Git入門】Gitを使い始めたい人へ「Gitの導入」 ~A Beginner to Beginners~ 【その1】 : 前 次 : 【Git入門】Gitを使い始めたい人へ「ローカルリポジトリの操作2」 ~A Beginner to Beginners~ 【その3】

目次

実際にGitの操作をしてみる

インストールも終わったので実際にGitを操作してみよう、と行きたいところだがその前に少し準備をしなければならない。ついでにせっかくなのでコマンド操作に慣れる練習もしていこう

Gitの初期設定

Gitはバージョン管理ソフトなので誰が更新したかを覚えておく必要がある。そこで、最初にユーザー情報を設定しておく。これは一度設定しておけば今後変える必要はない。登録するのはユーザ名とメールアドレスなのであとから変更は可能。
Gitの設定をするにあたって、コマンドを入力できるものを起動する必要がある。
まずはコマンドプロンプト(端末、ターミナル)を起動しよう。Windowsなら「Windowsキー+R」で「ファイル名を指定して実行」がでるのでそこに「cmd」と打ち込めば出る。
f:id:mattyan1053:20180720174238p:plain
Macはよくわからない。左上の方からターミナル起動してるのを他人ので見たことがある。 Linuxはデスクトップで「右クリック」→「端末」とかメニューから「アクセサリ」→「端末」とかで開けたと思う。
開くことができたら次のように入力しよう。

$ git config --global user.name "username"
$ git config --global user.email "example@example.com"

と入力すればよい。「username」「example@example.com」のところは自分のものにあわせて入力しよう。
ちなみに、色がないとちょっとさみしいので、

$ git --global color.ui auto

と入力すると少しだけ色がついて賑やかになる(ほんの気持ちだけ)。

Gitを使いたい場所を作る

バージョン管理したい、ソースコードをおいたりするフォルダーを作っておかねばならない。今回練習用のフォルダーとして好きなところに「git-test」というフォルダーを作ることにする。
通常、マウスとか使って新しくフォルダーを作るときは作りたい場所までダブルクリックとかして移動したあと、「右クリック」→「新規作成」→「フォルダー」とかやるだろう(Windowsならこれ)。これと同じことをコマンドでやる。わざわざ説明されなくてもできるぜ!って方はスキップスキップ。Gitを使ってみる

コマンドプロンプト(ターミナル、端末)を起動することができたら、次のように入力してみてほしい。

> dir
$ ls

これをすることで現在のディレクトリの中身が表示される。ディレクトリとはフォルダーとか言われてたりするあれのこと。 以後ディレクトリというとおもうので覚えてほしい。面倒だったらフォルダーとかわかりやすいように読み替えて構わない。 次に、自分が今いるディレクトリを移動するコマンドを入力してみる

$ cd <現在の位置からみた移動先のディレクトリ>

とすることで移動できる。たとえば、現在ドキュメント(Documents)にいて、その中の「test」の中の、更に「in_test」ディレクトリに移動したいときは

$ cd test\in_test

とすることで移動できる。また、今の場所より上の場所に移動したいとき、例えば先程の流れで「in_test」ディレクトリから「test」ディレクトリに戻りたいときは

$ cd ../

とすれば移動できる。
この2つのコマンドを駆使して、自分が使いたいディレクトリの場所に移動しよう。今回の例ではDドライブ直下にディレクトリを作成した(スクショしてもユーザー名が写らないとか楽できる事情があった)
次に、ここに新しくディレクトリを作成する。次のように入力してみる。

$ mkdir git-test

これで新しく「git-test」というディレクトリが作成された。「dir」や「ls」といったコマンドを利用してうまく作れていることを確認してほしい。確認できたら、「cd」コマンドで「git-test」の中に移動しよう。

-Windows,Mac,Linux共通

$ cd git-test

これで新しく練習用のフォルダを作成してそのディレクトリに移動することができた。うまくいっていれば次のような感じになるはずである。
f:id:mattyan1053:20180720182320p:plain
今回利用したコマンドはごくごく一部だし、使ったコマンドですらもっと便利な使い方があったりする。気になる人は「<OS名> コマンド」とかでググればたくさん出てくるので調べてみるとよい。

Gitを使ってみる

さて準備も整ったのでGitを使っていこう。

作ったディレクトリにローカルリポジトリを設定する

Git操作

リポジトリとはなんなのかというのは後回しにして、とりあえず次のように入力してみてほしい

  • OS共通
$ git init

これでディレクトリにリポジトリが作成された。実際に確かめてみよう。

> dir /a
$ ls -a

「/a」や「-a」が後ろについているのは、「隠しファイル」を表示するためだ。作られるディレクトリが隠しファイルなので、単純に「dir」や「ls」しただけでは表示してくれない。
さて、先程のように入力すると、「.git」というディレクトリが作成されているはずだ。
f:id:mattyan1053:20180720183442p:plain
この「.git」というのが先程スルーした「リポジトリ」、特に「ローカルリポジトリ」というものになる。
次に、Gitの状態を確認できるコマンドがある。次のように入力する。

git status

これから頻繁に入力することになるコマンドなので絶対に覚えよう。すると、次のように表示されるはずである。

$ git status
On branch master  
  
No commits yet  
  
nothing to commit (create/copy files and use "git add" to track)

一文目から「今のブランチはmasterです」、二文目「まだ何もcommitしていません」、三文目「commitするものがありません。(作成またはコピーしてgit addコマンドを使ってtrackに追加してください)」という文章になる。英語にふるえてはいけない。まだこれらの意味はわからなくて構わない。

リポジトリって何よ?

リポジトリとは、ファイルやディレクトリの状態を保存する場所である。ファイルを変更したりしても、変更前の状態をリポジトリが覚えてくれるわけだ。操作のできるバックアップに近い感覚。
f:id:mattyan1053:20180720184734j:plain
新しくプロジェクトを始めたりしてGitを使うときはまずバージョン管理したいディレクトリでリポジトリを作ることになる。「git init」コマンドは覚えよう。

ローカルリポジトリ
今回作成したのはリポジトリ、その中でも特に「ローカルリポジトリ」と呼ばれているものだ。詳しいことはローカルでないリポジトリを扱うときに説明するが、簡単に言うとそのPCだけ、そのディレクトリだけで有効なリポジトリということになる。

ファイルをコミットする

ファイルのコミットとは

例のごとくコミットという言葉は一旦スルー。Gitはこういう覚える言葉が多くていけない。コミットなんてRIZAPくらいでしか普段聞かない。
リポジトリでバージョンを管理する過程を説明する。工程は2段階で、まず「バージョン管理したいファイルをリストアップ」したあと、「現在の状態をリポジトリに保存する(覚えさせる)」ということを行う。
f:id:mattyan1053:20180720201304j:plain
上記の図でいうと、今の状態を保存しておきたいもの(バージョン管理したいファイル)として「sample.c」と「sample2.c」を「バージョン管理に追加したいものリスト(変更したものリスト)」に追加して、そのあと「追加したいものリスト」にあるファイルを「リポジトリに覚えさせる」ということになる。 このとき、「test.c」には何もしていないので記憶されない。 ここでの「作業しているディレクトリ」のことをワークツリー、「変更したものリスト」のことをインデックスと呼ぶ。
さて、Gitはバージョン管理ソフトである。なので、このあとなにか作業をしたあとどうするのかにも触れておく。 上の図のように「sample.c」と「sample2.c」を記憶したあと、「sample.c」だけ変更を行って新しくなった。そこで、今の変更点をリポジトリに保存したい。
f:id:mattyan1053:20180720201909j:plain
そのときは「変更したものリスト」に書き換えた「sample.c」だけを追加して、リポジトリに記憶してもらえば良い。変更してない部分は考えなくていいからだ。更に、リポジトリは一回目の保存も破棄しないので、両方のバージョンを記憶しておくことができるというわけだ。そして、実はここでの「変更したものリストに追加」することをステージすると言い、「リポジトリにリストに追加されたものの変更点を保存」することをコミットするという。  つまり「ファイルをコミットする」とは「変更したファイルをリポジトリに記憶する」ということを言うわけだ。

さて、カタカナ語リポジトリ、ワークツリー、インデックス・・・・・・と立て続けに出てきて混乱してきた。ここで一旦まとめて簡単に確認しておこう。

  • リポジトリ : 作業の状態を記憶するところ
  • ワークツリー : 作業しているディレクト
  • インデックス : 変更したものリスト
  • ステージ : 変更したものリストに加える
  • コミット : 変更したものリストにあるファイルの変更点をリポジトリに記憶する

これらの用語はこれから頻出し、一度「ん?」となるとどんどんわからなくなっていく。本記事ではできるだけ後者の言葉を使ったあと括弧書き()をしてその中に用語を書いておこうとおもう。タイトルはあとから振り返りやすいようにあえて用語を使っている。

実際のGit操作

何はともあれ一つファイルを作ってみなければならない。適当なテキストファイルでもいいのだが、なんだか雰囲気がでないのでそれっぽいファイルを用意してみる。お好みのテキストエディタを使って以下の内容のファイルを作って作業しているディレクトリ(ワークツリー)に保存してほしい。

#include<stdio.h>
int main(){
    printf("Gitコマンド一覧\n");
    printf("git init : リポジトリを作成\n");
    printf("git status : Gitの状態を確認\n");
    return 0;
}

C言語Hello World的ファイル。コンパイルとかは別にしなくていいです。何も中身がないのも寂しいので使ったコマンドをprintfしていってみる。そんなのコマンドを羅列したテキストファイルをcatすればいいじゃないかとおもったそこのアナタ。そのとおりです。ほら、雰囲気大事だよ雰囲気。ソースコードをGitで管理したらそれっぽいじゃん? ちなみにPATH設定してあるテキストエディタならコマンドから起動できる。例えば

$ vi sample.c

とか

> notepad sample.c

とかで。
ここで

$ git status

とすると

$ git status
On branch master

No commits yet

Untracked files:
 (use "git add <file>..." to include in what will be commited)

    sample.c

nothing added to commit but untracked files present (use "git add" to track)

と表示されたのではないだろうか。上2つの文は前回の「git status」と同じだが、その次が異なる。内容は「ステージされていないファイルがあります。git add ...を入力してコミットしたいものをステージしてください。そのファイルは「sample.c」です。コミットすべきファイルはありませんがステージされていないファイルがあります。」頭がいたくなりそうな話だが、要は「変更したのに「変更したものリスト」に追加(ステージ)されてないものがあるよ、追加してね」ってことである。つまり、「git status」コマンドを使えば変更したのに「変更したものリスト」に加え(ステージし)てないものがあるときは教えてくれるということである。

ステージする

さて、では言われたとおり作った「sample.c」を「変更したものリスト」に追加(ステージ)をしてみよう。次のようにコマンドで入力する。

$ git add sample.c

「git add 」はファイルを「変更したものリスト」に追加(ステージ)するコマンドだ。
ちなみに

$ git add sample.c
warning: CRLF will be replaced by LF in sample.c.
The file will have its original line endings in your working directory.

って出た人は改行コードの違いで勝手に改行コード書き換わって保存されただけなのでよくわからなければ気にしないでOK。元ファイルのほうは何も書き換わっていない。
そのあと、「git status」コマンドを使うことで今どうなってくれるか教えてくれる。 やってみると

$ git status
On branch master

No commits yet

Changes to be committed:
 (use  "git rm --cached <file>..." to unstage)
    newfile : sample.c

と表示されると思う。下に「Untracked~」で始まる文字が表示されているときは、エディタの一時ファイルかなんかだと思うので気にしないで問題ない。

複数のファイルをまとめてステージしたい

通常、一つのファイルだけ書き換えることもあれば、複数のファイルを一気に書き換えることもあるだろう。そういうときは書き換えたものをすべて「変更したものリスト」に追加(ステージ)しなければならない。一つ一つのファイルをいちいち「git add」していくのは面倒であるが、「git add」は次のように一気に複数のファイルを指定できる。

$ git add <filename1> <filename2> <filename3>...

こうすることで一度に複数のファイルをステージすることができる。更に

$ git add .

と最後をドットにすることでそのディレクトリ内のすべてのファイルをステージする(リストに追加する)ことができる。またこのコマンドを使ったりしてもステージしない方法として「.gitignore」ファイルを作り同じディレクトリにいれておく方法がある。テキストエディタ

*~
test.c

などこのファイルにかかれているファイルは無視されるようになる。
f:id:mattyan1053:20180720234437p:plain

もし間違えてリストに追加(ステージ)してしまったときは

ここでバージョン管理するつもりのないファイルを間違って追加してしまったときも慌てなくてOK。
新しく作成したファイルをaddしたときは

$ git rm --cached <filename>...

と入力すればリストから取り消すことができる。
また、もともとあったファイルを変更してaddしたときは

$ git reset HEAD <filename>...

とすればリストから取り消すことができる。

コミットする

これで「変更したものリスト」に追加(ステージ)が完了した。あとはこれをリポジトリに記憶してもらう(コミット)だけである。そのためには次のコマンドを入力する。

$ git commit

これを入力すると何かしらのテキストエディタが起動するだろう。そこにはコミットの内容を少なくとも1行記入しておく必要がある。通常

コミットの内容

変更した理由など (なくても可)

を記入する。日本語が通常のままだと入力できないので頑張って英語で。要約でいいので簡単な英語で問題ない。「Fix A in B」とかそんな感じ。今回は

First commit

で良いかと思う。いちいちテキストエディタで書くのが面倒、というときは

$ git commit -m "First commit"

というように、「-m」オプションをつけて後ろにコメントを書くことでも代用できる。
これでリポジトリに記憶が完了した。

なんで最初からコミットしないで一回ステージしてからコミットするの?

ここまで読んだ人で疑問に思った人もいるだろう。「変更したところをコミット(記憶)するに決まっているんだからステージ(リストに追加)はしなくていいんではないか」と。基本的にはそれで問題ないかもしれない。実際にaddとcommitをまとめてしまう方法がある。

$ git commit -a -m "Commit collectively"

「-a」オプションをつけることでコミットまで一気にやってくれる。しかしコミットしたくないものまでコミットしてしまったり、一気に変更を行ったけれどコミットメッセージを分けて別々にコミットしたいときがある。そういうときに、「add」と「commit」を分けて行うようにしておけば融通が効くということである。

コミット履歴をみる

自分の記憶履歴(コミット履歴)を見るには次のコマンドがある。

$ git log

こうすることでcommit履歴が見られる。入力してみるとよくわからんIDと「HEAD->master」、コミット(記憶)した人とそのメールアドレス、コミットの日時、コミットメッセージが表示されるだろう。これからはこのコマンドを使うことで履歴を確認することができる。

$ git log --oneline

とすれば一回のコミットにつき1行で履歴が表示される。

$ git log --graph

とするとツリー状に履歴が表示される。詳しくは次回。大抵の場合「--graph」オプションは「--oneline」オプションと一緒に使うことになると思う。

まとめ

今回はGitでコミットするまでの流れとやり方を説明した。 まずGitを使うために作業しているディレクトリにリポジトリを作る必要があり、そのリポジトリにデータをコミット(記憶)していく。リポジトリを作るのは作業ディレクトリを作った初回だけで良い。

結局コミットとは変更点を保存するだけである。
そのためには

  1. ファイルを変更する
  2. ファイルをステージする(変更したものリストに加える)
  3. ファイルをコミットする(変更点を保存する)

の3手順(変更する部分を抜けば2手順)を行うことになり、バージョンアップするたびに繰り返すことになる。

まだここまでではデータのバックアップがとれるようになっただけでGitの便利なところはわからないだろう。次回はリポジトリにコミットを繰り返していったあとのデータの扱いについて説明する。

【Git入門】Gitを使い始めたい人へ「Gitの導入」 ~A Beginner to Beginners~ 【その1】 : 前 次 : 【Git入門】Gitを使い始めたい人へ「ローカルリポジトリの操作2」 ~A Beginner to Beginners~ 【その3】

【Git入門】Gitを使い始めたい人へ「Gitの導入」 ~A Beginner to Beginners~ 【その1】

次: 【Git入門】Gitを使い始めたい人へ「ローカルリポジトリの操作」 ~A Beginner to Beginners~ 【その2】

目次

この記事の生まれた経緯と目的

Gitというものを知っているだろうか?正直に言います、ぼくはこの前まで名前知ってるくらいだった。
ちょっと友人とコード書いたりした時に簡単に同期できたりしたら楽だよなーって思ったのでGitについて調べてみたのが始まりで、自分の学習ついでにその過程を残して後学者の助けに少しでもなればなという。 つまりどういうことかっていうと某「サルでもわかる」とかQiitaの記事とか読んでも「???」ってなってしまった(カタカナ語がおおくて毎回意味を確認したりしてたら頭にはいってこなかった)、躓いたところを書いて残しておこうというアレ。更にいざやってみると英語ばっかりで「うっ」となる割に自力で解読する元気がない人の参考になればいいなと。
最終的にはこれを読んだ人なら一緒にGit使って開発をうまく連携していくことができるようになることを目指していく。チーム開発を捗らせたい。
サークルのほうでもチーム開発する人いるだろうし役に立てば嬉しい。

Gitって何?

そもそもGit(ギット)ってなんなのか。Wikipediaをみると

Git(ギット)は、プログラムのソースコードなどの変更履歴を記録・追跡するための分散型バージョン管理システムである。Linuxカーネルソースコード管理に用いるためにリーナス・トーバルズによって開発され、それ以降ほかの多くのプロジェクトで採用されている。Linuxカーネルのような巨大プロジェクトにも対応できるように、動作速度に重点が置かれている。現在のメンテナンスは濱野純 (Junio C Hamano) が担当している。(Wikipediaより)

「分散型バージョン管理システム」とは一体。   分散型とかいろいろついてるけど根本はバージョンを管理するシステムである。たとえば f:id:mattyan1053:20180720002452p:plain 自分で作ったものをどんどん更新していくとき、途中までの状態を別のフォルダーなどに保存しておいたりすることがあるだろう。バージョンを上げるたびにフォルダを複製して複製したものを書き直したりして作業してという作業を繰り返すのは面倒だし、量が増えてくれば管理がしにくい。バージョンが一つ変化したところでどこのところが変更されたのか探すだけでも一苦労である。更にバージョン情報のみでなく誰が編集したかという情報をいれたりすれば「sample_v3_mattyan1053」とか「sample_v3_name」みたいな同じバージョンを他の人が編集したもののフォルダーなどができてしまいわけが分からなくなりかねない。そこで登場するのがGitだ。 f:id:mattyan1053:20180720003825j:plain バージョンをツリー構造のように管理することができ(途中で分岐させたり、合流させたりできる)、常に作業する場所は同じでありながら、Gitを操作するだけで過去のバージョンに戻してそこから作業をやりなおしたりできる。また、フォルダーを複製するのと異なり変更点を保存するので、変更してない部分もすべて保存するような無駄もなく、どこが変わったのか、何を変更したのかだけを残しておくことができる。 f:id:mattyan1053:20180720005220j:plain 例えば上記のように、Version1からVersion2にするときに書き換えた時、「またVersion1に戻したいな」と思ったら通常一度Version2のwhileのところなどを消してVersion1と書き直さないといけないが、書き直すときにミスが発生するかもしれないし、たくさん書き換えてたら大変だ。もとのファイルを残してあればいいが、毎回別ファイルをつくるのはいただけない。ところがGitにVersion1を登録しておけば、そのファイルを複製することなくそのまま書き換えてVersion2にしても、すぐにVersion1の状態に変更分だけ戻すことが可能になるわけだ。 こうしたファイルの変更点の管理は、一つのファイルに限ったことではなくGit管理している複数のファイルで行うことができる。多くのファイルの変更を一括管理することができるということになる。 Gitの便利さがおわかりただけただろうか?

Gitのインストール

さてすぐにGitを始めたいところだが、当然Gitを使うにはまずGitをインストールしなければならない。次のURLから自分のOSにあったものをインストールしよう。
「Git - Downloads」 https://git-scm.com/downloads
f:id:mattyan1053:20180720011159p:plain
ぼくの端末がWindowsなのでWindowsで説明。てかたぶんWindowsが一番面倒。Linuxだとコマンドでインストールかな。yumとかそういう(あまりよくしらない)。macは同じようにGUIインストーラあるらしい? インストールするのはこれだけで構わない。下に「GUI Clients」とかいってGUI(マウスとかの操作)でGitを使えるようにするものもあるが、ここではCUI(キーボード入力での操作)でGitの操作をしていくつもりなので関係はない(お好みでインストールしてもかまわない) なぜグラフィカルな、わかりやすい操作方法のほうを利用しないのかというと、OS間で操作の違いが出てしまうことと、CUI(キーボード入力での操作)に慣れておいたほうがいいと思うので。実際慣れるとマウスでカチカチやるより早く作業が進む。最初は抵抗あるかもしれないが、手をしっかり動かすことで覚えていきたい。

さて、Gitインストーラがダウンロードできたら実行しよう。 f:id:mattyan1053:20180720012335p:plain
残念ながら全部英語です。諦めましょう。Nextをクリック。
f:id:mattyan1053:20180720012411p:plain
何もしないでNext押してもいいけどショートカットをデスクトップに作りたい人は上2つにチェックを、アップデートを日頃確認させたい場合は一番したチェックいれる。そしたらNext。
f:id:mattyan1053:20180720012541p:plain
Gitで使うエディタを設定。ファイル変更して更新したときに「何を更新したのか」書いたりするのに使うエディタになる。好きなの使えばよろし。
f:id:mattyan1053:20180720012648p:plain
環境変数ってやつ。わからなかったら素直に真ん中。Windowsでlsコマンドとか使いたいやつは一番下。一番上でやるとコマンドプロンプトでやらせてもらえない。
f:id:mattyan1053:20180720013244p:plain
HTTPS接続にOpenSSLライブラリ使うので上。わからなくても問題ない。
f:id:mattyan1053:20180720013518p:plain
改行コードの話。LFでおねがいします。Unix Style。わからなくてもOK。
f:id:mattyan1053:20180720013653p:plain
標準コンソールつかうよ。下。Next。
f:id:mattyan1053:20180720013736p:plain
追加オプション。全チェックで良いと思う。
これでInstallでOK。Git使えるようになりました!おめでとう!!

インストールできてるか確かめる

スタートメニューからGit Bashって奴見つけて起動するか、普通にコマンドプロンプトMacだとターミナル、Lunuxだと端末?)を起動する。そしたらコマンドで

$ git --version

と入力したらGitのバージョンが表示されればOK。
f:id:mattyan1053:20180720014910p:plain
こんな感じ。

お疲れ様でした!次から自分のPC内での操作かなー

次: 【Git入門】Gitを使い始めたい人へ「ローカルリポジトリの操作」 ~A Beginner to Beginners~ 【その2】