現場で使うGitのテクニック

お疲れさまです、trebyです。
もうだいぶ日付が変わりそうな勢いですが、[Git Advent Calendar 2014](http://qiita.com/advent-calendar/2014/git)の23日目を担当させていただきます。

Gitを業務で使い始めて早2年、だいぶ慣れてきた感じがありますが、それをアウトプットする機会があるかといえばなかなかありません。せいぜいたまに同僚に聞かれるくらいでなんかもったいない感じがあります。

そこで今日は私個人が`git`を使って仕事をする上でどういうフローしているかなーということを改めて文字にアウトプットしてみたいと思います。ご参考にしていただくなり、ツッコミしていただくなりしていただけますと幸いです。

なお、本投稿において想定するツールはGit、ホスティングサービスはGitHubですが、多分その他のサービスでもいけるのではないかと思います。

# 開発準備

「新しくチームに配属された！」等のシチュエーションを想定しています。

## 開発リソースの入手
あるディレクトリ以下をGitの管理下に置くためのコマンドとしては、`git init`というものがありますが（今更）、既に開発が始まっているプロジェクトにジョインするような場面でこれを打つことはまずないですよね。

というわけでまずやるべきは`git clone`だと思います。

```shell
$ git clone git@github.com:reddit/reddit.git
```

こうすることで、GitHubの[reddit/reddit](https://github.com/reddit/reddit)のコードを手元に持ってくることができます。
あ、もちろん各種ホスティングサービスへの[公開鍵の登録](https://help.github.com/articles/generating-ssh-keys/)みたいな作業は完了させておいてくださいね。

## リモートリポジトリの整理

開発フローが[git-flow](http://nvie.com/posts/a-successful-git-branching-model/)であれ、[github-flow](https://gist.github.com/Gab-km/3705015)であれホスティングサービス上に自分用のリポジトリを持っておくことになるでしょう。開発に参加するならば、GitHub上でリポジトリをforkしますよね。

そこで開発マシン上で自分用のリモートリポジトリに名前をつけてあげます。

```shell
$ git remote add self git@github.com:treby/reddit.git
```

とすれば、先ほどの[reddit/reddit](https://github.com/reddit/reddit)をforkした(という想定)treby/redditに`self`という名前をつけたことになります。

最新のmasterブランチのコードを入手するならmasterブランチ上で、`git pull origin master`すれば良いですし、featureブランチを切って、新機能を開発した際は`git push self feature/foobar`として、ホスティングサービス上などからPull Requestを作成することになります。

![各リポジトリのイメージ図](https://qiita-image-store.s3.amazonaws.com/0/35353/3b6dd93c-b66b-ccb6-d00f-01a64fef94c7.png)

なお、余談ですがリモートの名前一覧は`git remote -v`で見ることができます。

```shell
$ git remote -v
origin	git@github.com:reddit/reddit.git (fetch)
origin	git@github.com:reddit/reddit.git (push)
self	git@github.com:treby/reddit.git (fetch)
self	git@github.com:treby/reddit.git (push)
```

## 履歴に残す自分の情報の設定

要するに`git config`の事なのですが設定していないとコミットする際にデフォルトで、

- 名前: _マシンに設定された名前_
- メールアドレス: _【ユーザ名】@【ホスト名】_

という情報が使われます。(コミット自体ができないかと思いきや、できるのはできるんですね)

```shell
$ git commit -m 'Test Commit' 
[master 0acfdcb] Test Commit
 Committer: Hiroaki Ninomiya <treby@yourhost>
Your name and email address were configured automatically based
on your username and hostname. Please check that they are accurate.
You can suppress this message by setting them explicitly:

    git config --global user.name "Your Name"
    git config --global user.email you@example.com

After doing this, you may fix the identity used for this commit with:

    git commit --amend --reset-author
```

そこで、コミットのAuthor情報などに使われる自分の情報を設定してあげます。

```shell
$ git config --global user.name "treby"
$ git config --global user.email "treby@example.net"
```

とまあ通常であれば、大抵のケースではこれ一回行えば良いのですが、プロジェクトによってAuthorを変えたいという場合が生じることがあります。一つのマシンで仕事のコードと趣味のコード、両方を書くようなケースですね。

`git`がコミットの際に付与する情報はデフォルトでは上記で設定した情報ですが、もちろんリポジトリごとに設定することもできます。やり方は簡単で設定したいプロジェクトのコードがあるディレクトリで先ほどのコマンドから`--global`を外せば良いです。

```shell
$ git config user.name "Hiroaki Ninomiya"
$ git config user.email "hiroaki.ninomiya@example.com"
```
ここで設定した情報はプロジェクトルート以下の`.git/config`に記述されています。

```shell
$ tail .git/config 
:
<略>
:
[user]
	name = Hiroaki Ninomiya
	email = hiroaki.ninomiya@example.com
```

ここまでで一通りの開発準備ができました。

# よく使うテクニックあれこれ
次は実際に開発作業を行う上でよく使うコマンドをTips風に紹介します。

## ブランチの作成と切り替え

ブランチを切るコマンドとしては`git branch <ブランチ名>`ですが、`git checkout`の`-b`オプションを使用することが多いです(ブランチを新しく作成して、かつそのブランチに切り替えてくれる)

```shell
$ git checkout master
$ git pull origin master           # 開発環境上のコードを最新にする
$ git checkout -b feature/sugoi_kinou
```

---
**※2020年5月追記**

なお、 `git checkout` についてはそのコマンドが持つ役割が多すぎるという声もあったようです。そのフィードバックを反映して、[Git 2.23(2019年8月リリース)](https://github.blog/2019-08-16-highlights-from-git-2-23/) で同等の機能を持つ `git switch` (ブランチの切り替えに使用) コマンドと `git restore` (ファイルの変更に使用) コマンドが実験的に導入されました。
上記の `git checkout -b` を利用した「ブランチを新しく作成して、かつそのブランチに切り替える」操作を実行する `git switch` コマンドは

```shell
$ git switch -c feature/sugoi_kinou
```

となります。 `checkout` -> `switch` のみならず `-b` (`--branch`) -> `-c` (`--create`)とオプションも変更になっているのがミソですね。その他の `git checkout` と `git switch` / `git restore` の書き換えについては[こちらの記事](https://qiita.com/yukibear/items/4f88a5c0e4b1801ee952)などを参照いただくと良いと思います。

当面は、従来の `git checkout` の使い方でも問題はないですが、ツールのアップデートに追従するという点で知識として頭の片隅に置いておくと良いかもしれません。

---

`git branch`自体はブランチを一覧するための用途に使用することが多いです。

```shell
$ git branch
* feature/foobar
  master
```

`git branch`は`--merged`オプションで現在のブランチ(というか`HEAD`というか)にマージされている(含まれている)ブランチの一覧、`-d`オプションでブランチの削除ができます。
これらのオプションを利用して、開発が進んでブランチが多くなった時などmasterブランチ(最新にしておく)上などで以下のようにして、マージされたブランチを一括で削除したりします。

```shell
$ git branch --merged | grep -v '*' | xargs git branch -d
Deleted branch ...
Deleted branch ...
```

## コミット前の準備

コードの修正がひと段落したらコードをステージングエリアにあげましょう。
ステージングエリアという考え方が独特でとっつきにくく感じていらっしゃる方も多いようですが、コミットの前の準備状態くらいの認識で良いのではないかと思います([15日目の記事](http://qiita.com/yunico-jp/items/87bdd13971e82833f6bb)では「手紙を封筒に入れる」というユニークな表現をされています)。

基本はプロジェクトディレクトリのルートで

```shell
$ git add .
```

と入力すれば、そこまで行った全てのコード修正がステージングエリアにあがります(封筒に詰められます)。

と、全てのコード修正をステージングエリアにあげて、コミットしたい場合はこれで良いのですが、コード修正を行ったけれど一部は別にしたいなぁとか、今はまだいいかなというケースではどうすれば良いのでしょうか。

例えば、私は移り気な性格をしていますので、機能を実装している途中に実装と全く関係のない部分のタイポ修正だとか、インデントの崩れの修正だとかを行ってしまいます。

そこで私は、

```shell
$ git add <パス>
```

で明示的にステージングエリアにあげるファイルを指定したり、

```shell
$ git add -p
```

で、どの部分の変更をステージングエリアにあげるのか指定することも割とあります。(`-p`オプションは[6日目の記事](http://yoshiko.hatenablog.jp/entry/2014/12/06/Git%E3%81%AE%E4%BE%BF%E5%88%A9%E3%81%AA-p%E3%82%AA%E3%83%97%E3%82%B7%E3%83%A7%E3%83%B3%E5%9B%9B%E5%85%84%E5%BC%9F)でも紹介されていますね！)

細かな修正は本当に軽微ならば一緒にコミットしちゃうこともありますが、また別の機会にある程度まとまった単位で修正Pull Requestしたほうが行儀が良い気がします。

ちなみにコード修正・追加ではなく、名前の変更やファイル削除をしたい場合はそれぞれ、`git mv`、`git rm`を使うと、これらの変更をステージングエリアにあげることができます。

## コミットの作り方
ステージングエリアの準備ができたら、次はいよいよコミットです。行った修正を歴史に刻みましょう。

まず、`git commit`を打つ前に、`git diff --staged`と`git status`を使ってコミットしようとしている内容を本当にコミットして良いか確認しましょう。`.DS_Store`や`Thumb.db`が含まれていると恥ずかしいですよ:)

実際のところ、普段は`-m`オプションつけて一行でコメントを書くことが多いです。

```shell
$ git commit -m 'Redirect to root if user has something bad.'
```

チケット対応など、修正に対応するリソースがある場合は、あえてオプションをつけずエディタを起動させます。そしてエディタ上で2行目などにチケットへのURLを添付します。

```shell
$ git commit

〜エディタ起動〜

Fix for TICKET-XXXXXX
http://internal.example.com/TICKET-XXXXXX

```

本当に稀ですが、プロジェクトのルート上で、`-a`オプション(`git add .`)つけてコミットすることもあります。

```shell
$ git commit -am 'Fix typo.'
```

ちなみにコメントは[現在形で書くのが良いらしい](http://minus9d.hatenablog.com/entry/2014/02/11/125222)です。まあ、日本語／英語、現在形／過去形、自動詞／他動詞などのお作法はそれぞれのプロジェクトの文化によると思いますので、そちらに合わせてください。

## コミットの作り直し

ちゃんと正しいかを確認してコミットしたつもりでも、やっぱりミスっていたり、そうでなくても後で関連する場所のtypoを直したくなったりすることはよくあります。そのようなケースでのコミットの作り直し(歴史の刻み直し)方を紹介します。

ちなみにエチケット上、公開されている環境下(チームの共用リポジトリなど)のコミットを書き換えることは厳禁です。事故の元になるので控えましょう。（まあ、そこの判断がまた難しいんですけどね……自分のリモートリポジトリなら良い気がしますが、Pull Request投げている状態でそれされるとーとか、いや、まだコメント付いてないならセーフだろうとかとか）

まずは、素直にゴミファイル削除のコミットや、Fix typoのコミットを作成してみて、それに慣れた段階で参考されてみてください。

### git commit --amend

単純に直前のコミットに修正やファイル追加やファイル削除などを行いたい場合は、`--amend`オプションを使います。

```shell
$ git add .
$ git commit --amend
```

### git rebase

機能開発途中のコミットでtypoをしてしまった場合など、対話的rebaseを使います(`-i`オプション)。
例えば3つ前のコミットで軽微なミスをした場合は以下のように入力します。

```shell
$ git rebase -i HEAD~3
```

するとエディタが起動して、下記のような画面になります。

```shell
pick c7b8f45 commit 1                 # 3つ前のコミット
pick e218500 commit 2                 # 2つ前のコミット
pick f408755 commit 3                 # 1つ前のコミット

:
<略>
:
```

ここで、3つ前のコミットの「pick」の部分を「edit」に変更して保存、エディタを終了するとGitは3つ前のコミット(commit 1)を行った状態で一時停止してくれます。

この状態でミスを修正して、前述した`git commit --amend`でコミットをまとめます。すると3つ前のコミット(commit 1)にまとめられる形になります。

修正が完了したら、

```shell
$ git rebase --continue
```

でrebase作業を続行します。

なお、途中で訳がわからなくなった場合などは

```shell
$ git rebase --abort
```

でrebase処理そのものを中断することもできます。

`git rebase -i`について、ここではコミット間に変更を加える`edit`を紹介しましたが、その他にもコミットを一つにまとめられる`squash`などを利用することができます。

さらに、`pick`の記述をなくすことでコミットそのものをなかったことにしたり、前後を入れ替えることでコミットの順番を変更することもできます。

また、`git rebase`自体は作成してからちょっと時間が経っちゃったなというブランチに対して行うことが多いです。

```shell
$ git checkout master
$ git pull origin master           # 開発環境上のmasterブランチを最新にする
$ git checkout feature/foobar      # 開発開始から時間が経ってしまったブランチ
$ git rebase master
```

### git reset

実際上、ブランチのつじつま合わせに使っています。

例えば、本来であれば別途featureブランチを作成して行うべきコミットを誤ってmasterブランチに行ってしまった場合などです。以下に例を示します。

```shell
$ git checkout master

# 本当であればこの間で git checkout -b feature/foobar とすべきだった

$ edit
$ git add .
$ git commit -m 'foobar.'
$ git branch feature/foobar       # とりあえず今の位置に本来のブランチ名をつける
$ git reset --hard HEAD^          # masterブランチのHEADを一個前に戻す

$ git checkout feature/foobar     # 改めて開発を再開できる
```

また、マージしようとしたらコンフリクトして失敗した！というような場合はひとまず`git reset --hard`します(一旦戻そう、的な)。

```shell
$ git reset --hard
```

# まとめ

`git`はあくまでツールの一つなので最低限使えればそれで十分だと思います。が、自由自在に使えるとドヤリングできますし、頼られますし、微かな全能感があって楽しいのも確かです。

使い始めはなんだかよくわからなくておっかなびっくりになってしまいがちですが(私の場合、開発環境上でコミットすら怖かったです)、コミットしていてリポジトリ(プロジェクトの`.git`ディレクトリ)が無事ならばなんとかなります。`master`ブランチ消してもなんとかなります。

Gitを使って、どんどんHappy Hacking!!していきましょう。

明日は[shigemk2](http://qiita.com/shigemk2)さんによる[「GitHubEnterpriseを導入してみるにあたって気をつけたいことをいくつか」](http://shigemk2.hatenablog.com/entry/2014/12/21/GitHubEnterprise%E3%82%92%E5%B0%8E%E5%85%A5%E3%81%97%E3%81%A6%E3%81%BF%E3%82%8B%E3%81%AB%E3%81%82%E3%81%9F%E3%81%A3%E3%81%A6%E6%B0%97%E3%82%92%E3%81%A4%E3%81%91%E3%81%9F%E3%81%84%E3%81%93%E3%81%A8)(既に記事がある……！)です。よろしくお願いします！