gitにハマらないために

## 各用語

- リポジトリ：コードを管理する単位。管理するソースコードが1つのディレクトリにまとまっており、そのディレクトリ内のファイルの変更履歴が管理される。
- コミット：コードの変更をまとめて確定したもの。変更は複数のファイルにまたがるときもある。
- ステージ：「次にこれをコミットします」と選ばれた変更のひとまとまり。
- ブランチ：コードの変更の流れが分岐した枝のこと。複数の開発者で同時に開発するための機能。
- HEAD：各ブランチの最後のコミット。
- masterブランチ：一番メインのブランチ。一般的に、共同開発している場合、masterブランチに直接変更を加えるのはよくない。
- トピックブランチ：master以外のブランチ。masterから分岐して造られる。bug_fixとかrefactorとかadd_new_featureとか（さらにはもっと詳しく）開発トピックごとに作られるのが正しい。
- マージ：他のブランチの変更をいまいるブランチに取り込む操作。共通の分岐点（ブランチベース）以後の変更が取り込まれる。取り込まれる側（参照側）には何も変更はない。マージも1つのコミットになる。
- プルリクエスト（これはgithub用語）：（主にmasterに）自分のブランチを取り込んで(マージして)くださいと依頼する。この時のマージはレビューが入り、web上で行われることが多い。



# 途中でgitをやめたくなったら

git管理にあるディレクトリの最上位（ルートディレクトリと呼ぶ）に`.git/`というディレクトリがある（通常、隠れている）。
この中に全ての履歴や情報が入っており、gitはこれを認識している。
この`.git/`ディレクトリを削除してしまえば、そこはgit管理化からはずれる。（もちろん、変更履歴も何もかも全て消える。手元のファイルはそのまま。）


## 初めてのgit

### 自分用のgitの設定 (git config)
ユーザ名とeメール

```
git config --global user.name {myusername}
git config --global user.email {myemail@address.com}
```

UIの設定（オススメ）

```
git config --global color.ui auto
git config --global color.diff auto
git config --global color.status auto
git config --global color.branch auto
git config --global core.quotepath false
```

便利系エイリアス（これもオススメ）

```
git config --global alias.loga "log --oneline --tags --graph --decorate --all"
```

これを入れておくと、`git loga`でgranchのグラフが見える。

### リポジトリの作成

`git init`   もしくは   `git clone {URL}`

- 手元のディレクトリをgitリポジトリにする場合には`git init`
- すでにあるリポジトリをコピーするなら`git clone`
- githubで新しく空リポジトリを作り、手元のディレクトリに`git clone`するのも良い（空っぽのリポジトリを今のディレクトリにcloneしても、手元のファイルは消されない）

### 最初のコミット

```touch test.c```　ファイルを作成
```git add test.c```   ステージング：これからコミットにのせる変更を選択。
```git commit -m "first commit"```　　　メッセージ付きでコミット

なおここまで順にやってくると、masterブランチにコミットしているはず。


## 共同開発（リモートにpushするまで）

### リモートの追加

git remoteで追加する。
というか追加しなくても使えるので、「エイリアスを登録するコマンド」と捉えた方が正確。
なお、originというリモートだけは特別で、省略したときのデフォルト名になる。
どうせリモートリポジトリは一つだろうから、とりあえず登録しておこう。

```
git remote add origin git@github.com:yourname/your_project.git
```

これで、 ```git@github.com:yourname/your_project.git``` と　```origin```が等価。


### 一般的な開発の流儀

これは基本とされる割にはどこにも書いていない。

まず、masterからbranchを作成

```
git checkout -b new_feature
```

次に、作成したbranch上で開発、commitする。

```
echo "p 'hello world'" >> hello.rb
git add hello.rb
git commit -m "added hello.rb"
```

自分がつくったbranchなので遠慮なくpushする（最初は上手くいかない。すぐ後のupstreamで説明する）。

```
git push
```

(githubなど)webインタフェースから、masterにマージしてもらうための申請をする（プルリクエスト）。


### upstreamとは

各ローカルブランチに、リモート側として対応付いたブランチをupstreamブランチという。
upstreamブランチにしかpushできない。push時に対応付けする。

```
git push --set-upstream origin new_feature
```

ローカルとブランチ名が一緒である必要はない（一緒の方がわかりやすいが）

```
git push --set-upstream origin my_new_feature
```

などでも可能。

なお、とりあえず`git push`を叩くと、上記コマンドを提示してくれる。
面倒な人はそれをコピペするとよい。
（`--set-upstream`のショートオプションは`-u`だが、ゆえにロングを表記した）


### pushできない場合

（すでにupstreamブランチが存在する場合）upstreamブランチ上の変更が今いるローカルブランチに含まれていない場合、pushできない。

自分だけがpushしてるはずなのに、そんなことあるもんかと思うが、upstreamブランチは、作った人専用というわけではない。
うっかり他の人と同じ名前を使ってしまうと、衝突の危険性がある（なので、bug_fixのような単純な名前はやめよう）。
逆に、あえて同じリモートブランチで一緒に作業することもある。

ともあれ、pushしようとしている先のupstreamブランチ上のすべての変更を取り込んだ状態(fast-forwardと呼ぶ)にしないと、pushできないので、これを修正する必要がある。



## 共同開発（他の人が編集してpushしていた時の解決）

### マージ

マージそのものはリモート関係ない。ローカルブランチ同士でもできる。
あくまで、相手側（指定したブランチ）の変更を、自分側（今いるブランチ）に取り込むこと。

マージをすると
- 手元のファイルが変更される
- コミットされる（マージコミットと呼ばれる）
- マージされたということがgitの記録に残る（これがとても大事。）

なお、ここでの「変更」というのは、「共通の分岐点」からの変更である。

共通の分岐点に対して、相手側も自分側も変更を加えていた場合、コンフリクトが起こる。
コンフリクトは自動で解決される場合もあるが、手動で解決しなければならない場合もある。


### fetchとpull

- `fetch`はリモート(upstream)ブランチからデータを取ってくる。
- `pull`は`fetch`と`merge`を同時に行う。


### リモートブランチとトラッキングブランチ

- `fetch`でupstreamブランチのデータをダウンロードする先がトラッキングブランチ。
- つまりトラッキング（追跡）ブランチは、ローカルにある、リモートブランチのコピー（のようなもの）。
- トラッキングブランチに対してコミットすることはできない。
- `fetch`コマンドを叩くと、リモートブランチをトラッキングブランチに持ってくる（トラッキングをリモートと同期する）。
- これを自分のところにmergeする。
- `pull`コマンドを叩くと、fetchとmergeを同時にやってくれる。
- `pull`がうまくいく（コンフリクトも解決される）と、今度は`push`できるようになる。
- マージはトラッキング →　ローカル　→　リモート　の三角形の形で行われる。



## トラブルシューティング

### pushができない場合

とにかく、`git log --oneline --tags --graph --decorate --all`　（先ほど追加した場合は、`git loga`）を頻繁に確認しよう。

- `git reset HEAD^`でうっかりHEADを戻していたりしないか？　→　pushでは葉の先にコミットを積む方向にしか進められない

- うっかりミス[git-prompt](https://qiita.com/varmil/items/9b0aeafa85975474e9b6)を入れよう。常に状態がわかる。

- 最後の手段として`git push -f`というのがある。今の自分が正しいとして強制的にpushする。他の人が同じリモートブランチを参照している場合、一貫性が取れず状態がおかしくなるので、めっちゃ怒られるだろう。（push -fを禁止しているレポジトリもある。それくらいのご法度である）



## コミットに関してよくある混乱

### コミットは「差分」なのか、コミット時点での「スナップショット」なのか。

- 内部処理では、「スナップショット」を保存している。
- けれども、たいていの操作(merge, rebase, cherry-pick)では差分を扱っていると捉えた方が理解しやすい。
- checkoutで別のコミットからファイルを持ってくるときだけは、「スナップショット」と考える必要がある。


# 間違えやすいコマンド

### git reset

「ステージングを解除する」機能と、「HEADを過去のcommitまで移動する」（つまり、コミットを取り消す）という機能が混ざったように見える。
（実際は、ステージングとHEADを同時に移動する機能らしい。引数がないと、両者を現在のHEADに移動する（つまりHEADそのものは移動せず、ステージングだけ移動＝解除となる））

両者とも、--hardをつけると実際のファイルまで変更するので、間違えると危険。

```
git add test.c   # test.cの変更がステージングされる。
git reset        # ステージングが解除される
```

```
git reset HEAD^        # HEADを一つ前のコミットに移動する。
```

### git checkout
branchの切り替えと、別のbranchからのファイルを持ってくるのが混ざっている。
ファイルパスをつけるとファイルを持ってくる。

```
git checkout master   # ブランチがmasterに移動する
```

```
git checkout master hello.rb        # hello.rbというファイルをmasterから持ってくるだけ。ブランチは移動しない。
```