入門書を終えた人に捧げる、社会人のためのGit中級編

自分が実際に企業で働くうえでよく使ったコマンドや役に立った設定をまとめてみました。
Git入門系に関しては飽和していると思いますが、ちょっとした応用編としてご覧いただければ幸いです。

<b>自分の環境</b>

- ファイルの数や行数が<b>膨大</b>:dizzy_face:
- 複数の案件が同時進行することが多く、質問などに答えたりするために<b>ブランチ移動をすることが多い</b>
- プロジェクト内に複数文字コードが混在している（Shift-JISとUTF-8）


# コマンド編
## 基本のコマンド書きなぐり

```shell:コマンドライン上
$ git clone <ブランチ名> <ディレクトリ名> # clone先のディレクトリ名まで指定してcloneする
$ git pull # pullする。必要に応じて -u や、 remote名、ブランチ名を打ち込む
$ git diff # 差分見る
$ git diff master HEAD # 現在の状態とmasterを比較する
$ git checkout -b <ブランチ名> # 新規ブランチを作成して、チェックアウト
```

## 作業途中のファイルを退避させる

stashなど選択肢に入ってくると思うのですが、自分の場合は<b>commitして作業状況を退避</b>させます。
commitなので実際に作業状況が退避されているわけではないですが、ここでは退避という言葉を選択しております。
commit messageはなんでもいいのですが、「作業中」の意味を持つ「WIP」を。:dancers:

```shell:コマンドライン上
$ git add <退避させたいファイル>
$ git commit -m WIP
```

<b>ブランチ移動をすることが多い方</b>は、commitで作業を退避させることをお勧めします。
一回退避させて作業再開するのは1, 2週間後なんてこともざらなので、commitしてしまう方が整理しやすいです:fish_cake:

追加でちょっとした作業をしたときは、さらに追加でWIP commitをしていく感じです。

## 退避させたcommitを復元する

stashではないので復元というよりは、WIP commitを取り消してworkspaceに戻すイメージになります。
resetコマンドでcommitを取り消すのですが、、<b>stash代わりとして使う場合</b>には `--mixed` コマンドを使うと良いです。
`--mixed` はcommitを削除し、ファイルの変更はworkspaceに戻すといったオプションとなります。

```shell:コマンドライン上
$ git reset --mixed <コミットハッシュ>
# git reset --mixed HEAD^ (一個前に戻る)
# などでもオッケー
```

## addする

こちらはどんな方法でもいいし、普通のaddで十分なのですが、自分のやっている方法を紹介させていただきます。
本当は[tig（説明はこちら）](https://qiita.com/suino/items/b0dae7e00bd7165f79ea "tigでgitをもっと便利に！ addやcommitも")など使っていきたいところなのですが、私は古典学者なので今回はデフォルト機能のみで紹介させていただきます。

addコマンドでは、オプション `-i` を用いると対話式でaddすることができるようになります。
詳しい使い方については長くなってしまうため、こちらの [公式サイト](https://git-scm.com/book/ja/v2/Git-%E3%81%AE%E3%81%95%E3%81%BE%E3%81%96%E3%81%BE%E3%81%AA%E3%83%84%E3%83%BC%E3%83%AB-%E5%AF%BE%E8%A9%B1%E7%9A%84%E3%81%AA%E3%82%B9%E3%83%86%E3%83%BC%E3%82%B8%E3%83%B3%E3%82%B0) をご確認ください。

```shell:コマンドライン上
$ git add -i
```

# 設定編
## fast forwardの設定

<b>割と重要です。</b>:sushi:

```shell:~/.gitconfig
[merge]
	ff = false
[pull]
	ff = only
```

ff = falseにしておくと、mergeする時に必ずmerge commitを作ってくれるようになります。
github上で行うmergeにはデフォルトでついています。

逆に、pullの時は絶対にfast forwardしか受け入れないようにします。
fast forwardじゃなくていいことなんてほとんどないと思います。
普通に運用していればこっちは必要ないと思われるかもしれないですが、そうです。心理的な安全のためです。

## よく使うコマンドのalias

こちらは好みです。よくみるコマンドにはすぐにアクセスできるように。
特に使うものを紹介させていただきます。

```shell:.bashrc
# 省略版status
alias s='git status -s'
# 直近20行をグラフ表示
alias l='git log --pretty=oneline -n 20 --graph --abbrev-commit'
# conv設定を尊重してgrep
alias gg='git grep --textconv'
```

```shell:~/.gitconfig
[alias]
	di = !"d() { git diff --patch-with-stat HEAD~$1; }; git diff-index --quiet HEAD -- || clear; d"

	# 最新のcommitと現在の状態の差分を表示する
	d = !"git diff-index --quiet HEAD -- || clear; git --no-pager diff --patch-with-stat"

	# 全部addしてcommit
	ca = !git add -A && git commit -av

	# 特定のcommitを含むブランチの検索(find branch)
	fb = "!f() { git branch -a --contains $1; }; f"

	# ソースコードでlogを検索(find by code)
	fc = "!f() { git log --pretty=format:'%C(yellow)%h  %Cblue%ad  %Creset%s%Cgreen  [%cn] %Cred%d' --decorate --date=short -S$1; }; f"

	# commit messageでlogを検索(find by message)
	fm = "!f() { git log --pretty=format:'%C(yellow)%h  %Cblue%ad  %Creset%s%Cgreen  [%cn] %Cred%d' --decorate --date=short --grep=$1; }; f"

	# masterにmerge済みのブランチ削除(delete merged)
	dm = "!git branch --merged | grep -v '\\*' | xargs -n 1 git branch -d"

```


## 文字化けしないための設定

自分の担当でShift-JISとUTF-8が混在しているプロジェクトがあったため、このような設定をしています。
設定をするとdiffなどの<b>コマンドを文字化けを起こさずに利用できる</b>ようになります。実行速度とトレードオフです。

```shell:~/.gitconfig
[diff "mixed"] # 混在用のネームスペースを独自に用意する
	textconv = nkf -w8
```

```shell:プロジェクトルート/.gitattributes
# 対象の拡張子を選択する
*.html diff=mixed
*.js diff=mixed
*.css diff=mixed
```

今回は扱っておりませんが、textconvでExcelやバイナリファイルなどの扱いについても設定できます。（[参考](https://qiita.com/takedakn/items/660d17fe10ede8441ee6)）
この設定をしたうえで、以下のようオプションを付けるとtextconvを尊重してコマンドを実行できます。


```shell:コマンドライン上
$ git diff --textconv
$ git show --textconv
$ git grep --textconv <検索したい語句> <検索をかけたいディレクトリ>
#プロジェクトが巨大なため、検索速度を速めるためにディレクトリまで指定しています。
```



## 署名付きコミット

コミットするとこのようなマークがGitHubなどでつくようになる。
![8a45d10e6f38d1a36034b571c98e74a3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/308284/cb080d0f-ae0b-0bef-47cc-bd5bbfe74837.png)

必須ではないが、公式マークみたいでかっこいい。

```shell:~/.gitconfig
[user]
	signingkey = <それぞれのkey>
[commit]
	gpgsign = true
```

この設定のほかに、環境に合わせたGPGクライアントをインストールする必要があります。
github上で行うcommitにはデフォルトで署名がついています。

# 事例集
## レビューお願いします（依頼を受けたとき）
レビューをお願いされたときにどのような手順で対象のブランチを見に行くのかを書かせていただきました。

```shell
# まずは自分の作業の退避
$ git add -i # -iを使いインタラクティブにaddする
$ git commit -m WIP

# originの状態を取得（1度目は必須、2度目以降ならスキップ可）
$ git checkout master
$ git pull origin master

# レビュー対象のブランチを見に行く
$ git checkout <レビューをお願いされているブランチ名>
$ git pull origin <ブランチ名> # 同一ブランチで2度目以降のみ

# テストしたりdiff確認したり。
```

## いつの間にかエラーが発生してるんですが原因がわからないです:disappointed_relieved:

ほとんどの不具合は目視や標準出力で解決できちゃいますが、<b>どうしてもわからないとき</b>などに使います。
基本はresetとcheckoutをちまちま繰り返して特定し、
履歴が多すぎて時間がかかりそうなときは `bisect` というコマンドを使います。

### resetとcheckoutでちまちま
```shell
# あるあるなようであまりない。でも自分の中で絶対にエラーを特定できる方法を持ってると安心ですよね。
# commitが細かく分かれているほどgitで詳細に特定しやすくなります。
# 工夫すれば楽になりますが、混乱を避けるためにシンプルなコマンドのみで実践してみます。
# 下記コマンドの組み合わせで色々なことが可能になります。

# ※※※作業途中のものは消えてしまうため退避させてください。※※※

# 1: 正常であろう時代を探す
$ git log
$ git reset --hard <commit hash>

# まだエラーが続いていれば1に戻る。
# 2: 正常であれば、一旦元の状態に戻るために元のcommitハッシュを探す
$ git reflog
# （出力例）
# 955f116 (HEAD -> master, origin/master, origin/HEAD) HEAD@{0}: reset: moving to HEAD^
# 55ed0ee HEAD@{1}: commit: aaaa

# 3: 元の状態に戻す
$ git reset --hard <commit hash> # 上の出力例でいうと commit hash は 55ed0ee

# 4: 1に戻り、エラーcommitを特定。

# 5: 特定できたら、どのファイルが原因か探る（reset --mixedや、checkout <ファイル名> を駆使して探す。）
```

### bisectで二分探査を行いエラーcommitの特定

[こちら](https://qiita.com/usamik26/items/cce867b3b139ea5568a6)が分かり易いです