gitの使用方法

gitを使用するようになってから、数年。自分なりの使用方法が定着してから、結構経つが、最近、その使用方法の改善について、停滞気味。
gitは、あることをするのに色々な手段が取れる。だから、より簡単で安全で効率の良い方法をいつも探している。
それ故に、初心者やSubversionからの移行者が非常に取っつきにくく、調べても、基本的な手法すら記載されていない。調べても出てこないので、それが正しいのか、どうすると効率が良いのかなど、私も未だ悩むことも多い。
大体、gitの使用方法を調べていると、下記の2段階で出てくる。

1. gitの基本的なコマンド
2. 運用方法（GitHubフローを使うとか）

しかし、Subverionと違い、色々な方法が取れるgitであるからこそ、１と２の間にあたるユーザー操作の方法が無限に存在するように思っている。
私がSubversionから移行するときも、その部分には悩まされたし、プロジェクトメンバーに教えていても、コマンドは似通った部分があるから何となく分かるけど、何が正解か分からない、ベストプラクティスがないどころか、基本的な手法すら出てこないとよく聞く。
状況によって、ベストな方法があるから、とも言えるかもしれないが、それでもとりあえずは操作の指標は欲しいところ。
つまり、実際に書いたコードを上げたり、他人のコードを取り込んだりする時、どのやり方（pull、fetch & merge、rebase）をすれば正解か、実際にやった時にどんな挙動になるかもよくわからないし、それがあってるかどうかも分からないから、困ってるということ。

## まずは、ブランチ

Subversionからgitへ移行する際、ブランチの取り扱いの差異に最初は戸惑った。Subversionのブランチは運用手法だが、gitでは、システムだ。
gitのブランチは他人へ影響が無いから、とにかく簡単にブランチが切れる。影響が無いし、簡単に戻せる。
gitは、ブランチを操作するものだから、まずはブランチを理解する必要がある。

### 二種類のブランチ

ともかく、まず、ローカルリポジトリ内には、リモートブランチとローカルのブランチがあることを理解すること。
コマンドで見てみると、こんな感じになってる。

```bash
$ git branch -a
* master
  remotes/origin/HEAD -> origin/master
  remotes/origin/master
```

これは、全部ローカル内にある。先頭にremotesと付いてるのが、外部のリポジトリと連携をとるためのもので、外から持ってきたものが入ってる。そして外に出す際に一緒に更新される。どの順番で更新されるのかは知らない。要は、外部のコピーもローカルに入っていると思っておけば良い。
詳しくは、下記を見ていただいた方が良いかと思う。
http://qiita.com/uasi/items/69368c17c79e99aaddbf

### 操作方法

で、それぞれの操作方法は下記の通り。remotesの中身は、勝手に更新されないので、fetchで手動更新する。ある程度操作したことがあるなら、これで何となくデータの流れがわかってくると思う。

|操作内容|ローカルブランチ|リモートブランチ|
|---|---|---|
|作成|checkout -b|push|
|更新（自分の）|commit|push|
|更新（取り込み）|merge|fetch|
|削除|branch -d|push --delete|

### 自動連係

push、pullする時はupstreamが設定してあれば、push先は指定しなくても良い。masterしか使用していないと、最初に自動設定されただけで触らないので分からないと思うが、要はローカルブランチとリモートブランチの関連付けを行っておく。ブランチ名は同じである必要はないので、自分で好きなように関連付けられる。
下記のようになっている場合、hogeは連携先がないので、pullやpushする際に宛先を指定する必要がある。宛先は、origin/hogeにも、origin/masterにもできる。masterをorigin/hogeに関連づけることもできる。

```bash
$ git branch -vv
* master aaaaaa [origin/master] test
  hoge   aaaaaa test
```

### ローカルにあるものは自分の自由にしてよい

ローカルにあるものは、壊してしまっても戻してしまえば良い。使わないのなら、ローカルのmasterブランチも消しても全く問題ない。所詮、`master`と名のついた只のブランチでしかないから。
ただし、remotes/のブランチは、コマンド操作すると相手側と連動されるので、ローカルから消したくなったら`git remote remove origin`で、連携を外すか、.gitディレクトリごと消そうｗ

### まずはブランチを理解すること

* ブランチ同士にどのような関連性があるか（ローカルとremotes）
* どうやって関連づいているか（upstreamの設定）
* どのようなデータフローになっているか（fetch、merge、pushした時にどこが更新されるか）

gitを操作しようとすると、このブランチが理解できずに行き詰まる。使用するだけなら、それほど難しくないはずなので、あんまり深く考えず、感じてみよう。

## 私が使用しているgit操作手順

で、色々彷徨いつつ、自分なりに確立した方法が下記になる。

1. ローカルリポジトリを共有リポジトリからcloneする（`git clone URL`）
1. 作業ブランチを切る（`git checkout -b feature_featurename_myname`）
1. 作業する
1. 一日の終わり、または気が向いたときにcommitする（`git add . && git commit -m 'f'`）
1. 一区切りついたら、commitを整理する（`git rebase -i HEAD^^^`）
1. リモートのcommitを取り込む（`git remote update && git fetch -p && git rebase origin/master`）
 1. conflictが起きたら、解消する（`git mergetool`）
 1. よくわからなくなったら、戻す（`git rebase --abort`）
 1. merge失敗した時用にバックアップを取っておく（`git checkout -b backup`）
 1. マージできたら次を処理する（`git rebase --continue`）
 1. 全体のdiffを見て、きちんとmergeできたか判断する（`git diff backup feature_featurename_myname`） 
1. バックアップも兼ねて、リモートへブランチをpushする（`git push -f origin feature_featurename_myname`）
1. 3 - 7 を繰り返す
1. 機能が完成したら、masterにマージする（`git checkout master && git fetch -p && git pull && git merge --no-ff feature_featurename_myname`）
1. マージしたmasterをremoteに送る（`git push origin master`）
1. 不要になったブランチを消す（`git branch -d feature_featurename_myname && git push --delete origin feature_featurename_myname`）

プロジェクトメンバーのgit練度の兼ね合いもあって、GitHubフローなどは使用していないが、とりあえず、この操作内容の意味が分かるのであれば、運用方法が何になっても困らないと思う。
ちょっと補足

* `git remote update`は、リモートが複数ある時だけで良い
* ローカルmasterの更新は、pullでも、mergeでも良い
* 変更点取り込みのタイミングは、固定ではない、commitしてすぐにしておいても良い。

### 利点

この方法だと、commitが綺麗になる。masterの取り込みをrebaseで行って（`git rebase origin/master`）、ブランチの適用をnon fast-forward（`git merge --no-ff`）で行うと、他の人の変更点を取り込んだ後で、自分がまとめて変更点を適用したことになる。
時系列的には違うが、コードの正当性としては（conflictをきちんと解消していれば）間違っていない。
また、ブランチでの作業分が、まとまってきっちり分離され、概要がmergeコメントに載っているので、非常に分かりやすい。
![git_commit.gif](https://qiita-image-store.s3.amazonaws.com/0/54822/9b80455b-1894-c2b8-f400-8ab168b1ec3b.gif)

### 欠点

git rebaseでconflictが起きた場合、解消が難しくなることがある。rebaseの間、ずっとconflictが起きることがあるため。

### それぞれの意味

#### commitする

無駄にcommitしてるけど、大丈夫か。問題ない。
後にも書くが、commit時点では確定ではない。むしろ、訳の分からない変更点が大量に貯まる前に、適当な位置でcommitしておいた方が、前回作業とのdiffも取れて便利。
commitが増えてきたら、とりあえず、fixupでまとめてしまえば良い。

#### git rebase origin/master、git mergetool

利点にも書いたが、rebaseすると、**最新の状態から、自分が変更を行った**ことになる。
その作業で、conflictをきちんと解消するはずなので、それで問題ないはず。問題になったら、きちんとconflictを取り込めなかった自分が問題。それは、相手の変更点を理解していなかったと同義だ。
また、mergeで取り込むと、自分のコミットログと他人のコミットログが混ざるが、rebaseは、当たり前だが、それがない。
`rebase origin/master`は、下記のようにイメージすると分かりやすい。

1. 最新のorigin/masterの先頭に自分のcommitを順番に先頭にくっつけていく
1. conflictが起きたら、その時点の変更ファイルをワーキングに展開して止まる
1. conflictを解消したら、`git rebase --continue`すると、そこからまた、自分のcommitを順番にくっつけていく

変更ファイルを展開する時は、また、`git branch`を見てみると分かるが、一時ブランチで行われるので、`git rebase --abort`で、rebase実行直前の状態にすぐに戻せる。

#### git push -f origin feature_featurename_myname

`push -f`はいけないんじゃ。。と思うかもしれないが、自分専用のブランチで問題なんて起きるの？
間違って、masterに`push -f`する可能性が問題かもね。。まあ、それをやらかすのは、masterの過去のcommitを変更するような使用方法に問題があるからだけどね。普通は無いね。

#### git pull && git merge --no-ff feature_featurename_myname

masterは、リモートと同期をとっているだけなので、作業ブランチと違い`git pull`してもコミットログが崩れない。
利点にも書いたが、`--no-ff`をつけて、ブランチを取り込むことで、自分の作業分のみを分離したログを出せて、見やすくなる。

### Subversionとの主な違い

* とりあえず、commitしまくる。途中でもする。Subversionのようにcommitを神聖視するな。（masterにpushしなければ）何も問題にならない。日常のcommitは、らくがき程度の扱いなので、pushする前に、他人に見せるようにまとめれば良い。
* ローカルのリポジトリや自分専用のブランチは、他人が触る余地がないので、都合が良いように改変しまくれ。誰も見てないんだから（masterにpushしなければ）何も問題ない。そして、ブランチも切りまくれ。困ったら切れ、不安なら切れ、ブランチは、超低コストの`cp -r`＋`vimdiff`だ。ブランチを切った（所謂`cp -r`した）先は、壊しても問題ない。元のブランチを使うだけだ。うまくいったらmergeで取り込む（vimdiff）なり、そっちをメイン作業スペースにするだけだ。

Subversionからすると、commitやブランチの扱いがだいぶ違うと思う。Subversionからの認識を改めるべき。
Subversionとgitの対応コマンドを調べるとよく見つかる。確かにその通り間違っていないのだが、そのまま使用しようとするとgitの利点を全く生かせない。それどころか、無理にSubversionに合わせようとするせいで混乱が生じて、余計に難しくなってしまう。
特に、commit、`svn commit = git commit && git push`とは思うな、`svn commit = git push origin master`と思うべき。ここでの`origin master`は重要。
`git commit`は、確定された作業内容ではなく、個人の作業ログ程度の扱いで良い。
そして、Subversionでは、手動で行っていた操作をgitで簡単に行える。
例えば、Subversionでは、mergeするとワーキングツリーが壊れるから。。とかで、ファイルやワーキングツリー丸ごと、一時的な退避を行っていたが、それもgitでは、reflogで大体戻せるし、バックアップ用ブランチを切ってしまえば、それらを手動でやる必要すらない。
対応表に表すとこんな感じだと思えば良い。とにかく、目的は、大体gitで解決できるはず。

|Subversion|Git|意味|
|---|---|---|
|svn commit |git push origin master|変更確定|
||git commit|今日の作業、気が向いた時の変更ログ、変更確定の準備|
||git checkout -b feature|作業スペース確保|
|cp -r ../project{,.bak}|git checkout -b backup|バックアップ、変更テスト用スペース|
|cp file{,.bak}|git commit -m 'f'|確度の高い変更箇所を残す|
|cp file{,.bak}|git stash|確度の低い変更点をバックアップ|
|svn up|git rebase origin/master|変更点の取り込み|

## 最後に

gitシステム自体が、ブランチを使用しているところが多々あるように、gitは、ブランチを操作するものである。だから、ブランチの扱いに慣れることが重要。
それと、Subversionからの移行者は、commitやブランチの認識を改める必要がある。