【59日目】今日から始めるGit/GitHubーローカルリポジトリ編

# 1 環境構築
今回は、AWS cloud9の無料プランを手元の開発環境とし、GitHubの無料プランをリモートリポジトリとして使用します。
cloud9のアカウント登録までは完了しているものとします。
以下、cloud9IDEのターミナルを使用します。

## 1.1 初期設定
### 1.1.1 ユーザ登録
ユーザ名とメールアドレスの登録をします

```
$ git config --global user.name "git_biginner"

$ git config --global user.email "***@***.com"
```

### 1.1.2テキストエディタの設定
ここではvimを指定します。
日本語が文字化けしないようにutf-8を指定します。

```
$ git config --global core.editor 'vim -c "set fenc=utf-8"'
```

ちなみに後からこれらの設定を確認する方法は下記のとおりです。

```
$ less ~/.gitconfig
```

lessはテキストファイルの中身を確認するコマンドです。

### 1.1.3ローカルリポジトリの作成
ディレクトリを作成してその中に移動します。

```
$ mkdir git_repositories
$ cd git_repositories
```

/git_repositories内にtutorialディレクトリを作成します。

```
アカウント名:~/environment/git_repositories$ mkdir tutorial
アカウント名:~/environment/git_repositories$ cd tutorial
```

リポジトリを初期化します

```
アカウント名:~/environment/git_repositories $ git init
```

これでtutorialディレクトリがローカルリポジトリとなります。
gitのバージョン管理はプロジェクトごとなので、tutorialディレクトリ配下のファイルが管理対象になります。
また、git initしたことによって、tutorialのなかに自動的にフォルダが作成されています。
これらのバージョン管理のためのファイルは隠しファイルとなっているため、「ls -a」で確認することができます。
/git_repositories/tutorial/.git/branches
と作成されている。

# 2 コミット
手元の開発状況（ワーキングツリー）をインデックスにaddして、そのインデックスをローカルリポジトリにcommitすることでバージョン履歴が管理されます。

## 2.1ステータスの把握
まずは、現在のローカルリポジトリの状況を確認します。

```ruby:tutorial
$ git status
```

するとこのように表示されます

```
On branch master
#現在のブランチを表示します。

No commits yet
# コミットされている変更履歴が表示されます。

Untracked files:
(use "git add <file>..." to include in what will be committed)
# インデックスに登録されていないワークツリーが表示されます。 
# ここでは「a.js」が未登録で存在することを表しています。

a.js

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

## 2.2ステージ（インデックス）への登録
まずはa.jsをステージ領域（インデックス）に登録します。

```ruby:tutorial
$ git add a.js
```

登録した後にもう一度 git status で確認するとこのように表示されます。

```
On branch master

No commits yet

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

                 new file: a.js
# commitはまだないが、commitされるべき変更としてnew gileが増えている。
# 登録したファイルをステージからおろす方法も書かれている。
```

さらにb.jsも同様にしてaddしておきます。

## 2.3 ローカルリポジトリへのcommit
ステージに登録した２つの変更履歴をローカルリポジトリにcommitして管理します。
ローカルリポジトリへcommitする際には、変更内容がすぐにわかるように「-m」オプションによってコメントをつけます。
日本語も使用可能です。

```ruby:tutorial
$ git commit -m "first commit"
```

commitすると、下記のように結果が表示されます。

```
[master (root-commit) 5817c8f] first commit
2 files changed, 2 insertions(+)
create mode 100644 a.js
create mode 100644 b.js
```

commit後にgit statusを確認してみます。

```
On branch master
nothing to commit, working tree clean
```

これはローカルリポジトリの内容と、ワーキングツリーの内容が一致しているという意味になりますので、コミットが不要です。


# 3 ローカルリポジトリでの変更履歴の確認
## 3.1 logの確認
これまでの変更履歴や、commitのコメントが確認できます
いくつかのオプションが選択できます。

```
git log
git log ファイル名 # 指定したファイルのログを確認
git log -p # 差分の表示
git log -p ファイル名 # 指定したファイルの差分を表示
```

## 3.2 差分の確認
ローカルリポジトリ配下のファイルを編集すると、ステータスが下記のように変わります。

```
On branch master
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git checkout -- <file>..." to discard changes in working directory)

modified: a.js # 変更が加わったファイル

no changes added to commit (use "git add" and/or "git commit -a")
```

この変更内容を確認するには

```ruby:tutorial
$ git diff
```

で確認できます。

```
diff --git a/a.js b/a.js
index c35697f..b1ac65e 100644
--- a/a.js
+++ b/a.js
@@ -1 +1,2 @@
-console.log('A');   #「-」で表示されているものが変更前の状態です。
\ No newline at end of file
+console.log('A');　#「+」で表示されているものが変更後の状態です。
+console.log('AA');
\ No newline at end of file
```

このように変更されたファイルがあった場合、改めてインデックスにaddします。
addした段階で、git diff では何も表示されなくなります。
ただし、commitする前に必ず

```ruby:tutorial
$ git diff HEAD　　# HEADとは前回のコミットのこと
```

によって、前回のコミットとの差を確認してください。

```
diff --git a/a.js b/a.js
index c35697f..b1ac65e 100644
--- a/a.js
+++ b/a.js
@@ -1 +1,2 @@
-console.log('A');
\ No newline at end of file
+console.log('A');   # 修正内容です。想定どおりの修正となっていることを確認します。
+console.log('AA');
\ No newline at end of file
```

修正内容が想定どおりであれば、git commitします。

```
$ git commit -m "add AA"
```

これによってlogを見ると

```
commit 19c0de03c69e70a3aa9ccb6a6f481145b6e61d3d (HEAD -> master)
Author: git_biginner <mindrendel999@gmail.com>
Date: Fri Oct 18 15:33:18 2019 +0000

add AA

commit 5817c8f6d78a4455d29870f792672126742c8e08
Author: git_biginner <mindrendel999@gmail.com>
Date: Fri Oct 18 14:49:06 2019 +0000

first commit
```

のように、最初のコミットと、2回目のコミットの内容とが連続して表示されます。

# 4コミットの取り消し
コミットを取り消す方法は２つあります。

## 4.1 revert 
- コミットを打ち消すコミットを新しく作成することで、特定のコミットに戻します。
- コミットを取り消した記録が残ります。
- リモートリポジトリで行われた変更を取り消すにはrevertが必須です。

### 4.1.1 revertの方法
まず git log -pによって削除したい変更を示すハッシュをコピーします。
ただし、最新の更新をrevertしたい場合は、git revert の引数にHEADを指定すれば十分です。

```
#     ↓このcommitの横にある長い文字です
commit 6262d09135b571b6cc128dce664b36dc1cc0dc12 (HEAD -> master)
Author: git_biginner <mindrendel999@gmail.com>
Date: Sat Oct 19 02:12:19 2019 +0000

add R

diff --git a/a.js b/a.js
index a1cf418..4f5fcf9 100644
--- a/a.js
+++ b/a.js
@@ -1 +1,2 @@
-　console.log('AA');
\ No newline at end of file
+console.log('AA');
+console.log('R');
\ No newline at end of file
```

次にrevertコマンドを実行します。

```
$ git revert コピーしたハッシュ
```

すると設定したエディタが立ち上がります。
ここではコメントが編集できるので保存して終了するとrevertが完了します。初期では
正常に動作している場合、ログにrevertの履歴が残りますので確認します。

```ruby:git log -p
commit c1468ce015108871d361068fdd9fafe72be97a7d (HEAD -> master)
Author: git_biginner <mindrendel999@gmail.com>
Date: Sat Oct 19 02:19:19 2019 +0000

Revert "add R"

This reverts commit 6262d09135b571b6cc128dce664b36dc1cc0dc12.

diff --git a/a.js b/a.js
index 4f5fcf9..a1cf418 100644
--- a/a.js
+++ b/a.js
@@ -1,2 +1 @@
-console.log('AA');
-console.log('R');
\ No newline at end of file
+　console.log('AA');
\ No newline at end of file
```

## 4.2 reset
- コミットをなかったことにします。
- ローカルリポジトリで行なった変更を取り消す場合に用います。
- ログが残らないので、みやすく保つことができます。
- 誤操作したときの影響が大きいので要注意

### 4.2.1 resetの方法
resetでは、revertの時とは異なり、戻りたいコミットのハッシュをコピーします。
例えば、first commitの状態まで戻りたいとしたら、下記のハッシュをコピーします。
ここで指定したコミットより新しいコミットは全てなかったことになるので、取り返しがつきません。
操作をミスると全て消えてしまいます。

```
#  　　  ↓今回はこの値をコピーします。
commit 5817c8f6d78a4455d29870f792672126742c8e08
Author: git_biginner <mindrendel999@gmail.com>
Date: Fri Oct 18 14:49:06 2019 +0000

first commit# ←この状態まで戻ろうとしています。

diff --git a/a.js b/a.js
new file mode 100644
index 0000000..c35697f
--- /dev/null
+++ b/a.js
@@ -0,0 +1 @@
+console.log('A');
\ No newline at end of file
diff --git a/b.js b/b.js
new file mode 100644
index 0000000..44bccb0
--- /dev/null
+++ b/b.js
@@ -0,0 +1 @@
+console.log('B');
\ No newline at end of file
(END) 
```

コピーしたら次のようにresetコマンドを実行します。

```
$ git reset --hard 戻りたいコミットのハッシュ
```

すると

```
HEAD is now at 5817c8f first commit
```

と表示され、resetが完了します。

## 4.3 ファイル、ディレクトリの削除
 コミットではなく、ファイルやディレクトリの削除を行いたい場合はgit rmコマンドを用います。

### 4.3.1 ファイルの削除

```
$ git rm 削除したいファイル名
```

で削除ができます。
削除自体も変更ですから、削除後にコミットするようにします。
コミットしたら、削除したことは次のようにログで確認ができます。

```ruby:git log -p
commit a4af7ffbd5857c929d2ee580c39c4c5dadb46900 (HEAD -> master)
Author: git_biginner <mindrendel999@gmail.com>
Date: Sat Oct 19 02:40:20 2019 +0000

remove.js

diff --git a/remove.js b/remove.js
deleted file mode 100644
index e69de29..0000000
```

コミットしておくことで、削除のコミットも取り消して、削除前に戻ることができます。

### 4.3.2 ディレクトリの削除
ディレクトリの削除は下記のようにして行います。
削除後は忘れずにコミットするようにします。

```
$ git rm -r 削除したいディレクトリ名
```

# 5 ブランチ
 実務上、デプロイしているアプリケーションを直接編集することは行いません。
ブランチと呼ばれるものをトピック別に作成して、そちらを編集し、最後にマージ（合体）します。
この、常時デプロイ可能にしているブランチをマスターブランチと呼び、編集したい項目ごとのブランチをトピックブランチと呼びます。
トピックブランチは幾つでも自由に作成できるので、チームのルールにしたがって複数作成し、各種機能の編集を並行して進められます。

## 5.1 ブランチの操作
### 5.1.1ブランチの確認
既存のブランチは

```
$ git branch -a
```

を実行することで、

```
* master
```

のように確認できます。アスタリスクは、現在使用しているブランチを意味します。

### 5.1.2 ブランチの移動と作成
まずブランチを作成したいディレクトリに移動します

```
$ cd /git_repositories/tutorial
```

次に、ブランチの元としたいブランチに移動します。
まず、branch -a コマンドでブランチ一覧を確認し、git checkout コマンドでブランチを移動します。
今回はマスターブランチからfeature-Aというブランチを作成したいと思います。

```ruby:tutorial
$ git checkout  元にしたいブランチ名(今回はmaster)
```
ちなみに直前にいたブランチに移動する場合は

```
git checkout -
```
で十分です。

ブランチの元となるブランチに移動したら、次のコマンドを実行するとブランチが作成できます。

```ruby:tutorial
$ git branch 作成したいブランチ名(今回はfeature-A)
```

作成したブランチに移動すれば、新しいブランチで並行作業を開始できます。

```tuby:tutorial
$ git checkout feature-A
``

ちなみに、このブランチの作成と移動を同時に実行することができます。

```
$ git checkout -b 作成したいブランチ名
```

### 5.1.3 ブランチでのコミット
例えばこのトピックブランチ「feature-A」で ファイルを編集し、ステージにaddしてローカルリポジトリにコミットしたとします。
その後masterブランチに移動すると、feature-Aでなされたはずの編集は反映されていないはずです。
このように、各ブランチでの作業はマージするまで互いに他のブランチには反映されません。

## 5.2 ブランチのマージ
ブランチのマージはマスターブランチで行いますので、マスターブランチに移動します。

```
$ git checkout master
```

マージは下記のコマンドを実行します。

```ruby:tutorial
$ git merge --no-ff マージしたいブランチ名（ここではfeature-A）
```

マージしたら、エディタが立ち上がるのでマージコミットのメッセージ内容を編集してマージ完了です。
ログを確認すると、マージの流れが確認できます。

```
commit 7cd89f8f416d8e2b6c48a360e641d6778c3a6ec9 (HEAD -> master)
Merge: a4af7ff 2006e95
Author: git_biginner <mindrendel999@gmail.com>
Date: Sat Oct 19 03:29:36 2019 +0000

Merge branch 'feature-A'


commit 2006e95014f583fc3bb615335505decacb9976cb (feature-B)
Author: git_biginner <mindrendel999@gmail.com>
Date: Sat Oct 19 03:12:12 2019 +0000

update to feature-A

diff --git a/b.js b/b.js
index 44bccb0..e83683b 100644
--- a/a.js
+++ b/a.js
@@ -1 +1,2 @@
-console.log('A');
\ No newline at end of file
+console.log('A');
+console.log('feature-Aブランチで追加');
\ No newline at end of file
```

### 5.2.1 --no-ffオプション
オプションとして--no-ffを指定しています。
gitの初期設定においては、マージコミットが必要ない場合、コミットせずにマージするように設定されています。
これをfast-forwardと言いますが、これをキャンセルするので--no-ffです。

#### 5.2.1.1 --no-ffのメリット
--no-ffオプションによって確実にマージコミットを残すことで次のようなメリットがあります。
- マージしたブランチでの作業内容をdiffを見ることですぐに参照できる。
- マージを取り消したい場合に、コミットの取り消しの手順で取り消すことができる。

#### 5.2.1.2 --no-ffをデフォルト設定にする。
次のコマンドでデフォルトでfast-fowardしないように設定できます。

```
git config --global --add merge.ff false
```

### 5.2.2 競合（コンフリクト）の解決
マージしようとした２つのブランチで、同じ箇所を編集していた場合に発生します。
どちらの変更を優先すればいいかgitには判断ができないので、手作業で修正します。

例えば今、/git_repositories/tutorial配下にてmasterブランチから作成されたブランチの状態が下記のとおりだったとします。
- master

```javascript:a.js
console.log('A');
console.log('feature-Aブランチで追加');
```

-feature-C

```javascript:a.js
console.log('C');
console.log('feature-Cブランチで追加');
```

そしてfeature-Cをマージすると、

```
Auto-merging a.js
CONFLICT (content): Merge conflict in a.js
Automatic merge failed; fix conflicts and then commit the result.
```

のようにコンフリクトが表示され、a.jsには

```javascript:a.js
<<<<<<< HEAD
console.log('A');
console.log('feature-Aブランチで追加');
=======
console.log('C');
console.log('feature-Cブランチで追加');
>>>>>>> feature-C
```

と表示されます。
これは、<<<<<と=====の間にマージ元（HEAD）での編集内容が、　>>>>との間にマージしようとしているブランチ（feature-C）での編集内容が表示されています。

これを直接修正して、コンフリクトを解決します。
今回は

```javascript:a.js
console.log('C');
console.log('feature-Aブランチで追加');
```

としてみました。
バグが起きやすいポイントなので注意深く編集します。

コンフリクトが解決したら、編集し直したワークツリーをマージ元のブランチのステージ（ここではmaster）でaddしてコミットします。
今いるブランチの表示が(master|MERGING)と表示され、コンフリクト解決中であることが確認できます。

```
:~/environment/git_repositories/tutorial (master|MERGING) $ git add a.js
:~/environment/git_repositories/tutorial (master|MERGING) $ git commit -m "fixed conflict"
```

これでコンフリクトの解決は完了です。
ログで確認するとこのように通常のマージと同じように表示されています。
競合があった場合には、メッセージで記録しておくことが望ましいです。

``
commit 0167e160cb7484a5c5378a76202645094a84b723 (HEAD -> master)
Merge: b62c91b 1188172
Author: git_biginner <mindrendel999@gmail.com>
Date: Sat Oct 19 04:13:39 2019 +0000

fixed conflict
```

###  5.2.3コミットグラフオプションでコミットの履歴を追う
git logコマンドを実行する際に--graphオプションを使用することで、コミットの履歴を色分けがされた簡単なグラフにしてくれる。