お疲れさまです、trebyです。
もうだいぶ日付が変わりそうな勢いですが、Git Advent Calendar 2014の23日目を担当させていただきます。
Gitを業務で使い始めて早2年、だいぶ慣れてきた感じがありますが、それをアウトプットする機会があるかといえばなかなかありません。せいぜいたまに同僚に聞かれるくらいでなんかもったいない感じがあります。
そこで今日は私個人がgit
を使って仕事をする上でどういうフローしているかなーということを改めて文字にアウトプットしてみたいと思います。ご参考にしていただくなり、ツッコミしていただくなりしていただけますと幸いです。
なお、本投稿において想定するツールはGit、ホスティングサービスはGitHubですが、多分その他のサービスでもいけるのではないかと思います。
開発準備
「新しくチームに配属された!」等のシチュエーションを想定しています。
開発リソースの入手
あるディレクトリ以下をGitの管理下に置くためのコマンドとしては、git init
というものがありますが(今更)、既に開発が始まっているプロジェクトにジョインするような場面でこれを打つことはまずないですよね。
というわけでまずやるべきはgit clone
だと思います。
$ git clone git@github.com:reddit/reddit.git
こうすることで、GitHubのreddit/redditのコードを手元に持ってくることができます。
あ、もちろん各種ホスティングサービスへの公開鍵の登録みたいな作業は完了させておいてくださいね。
リモートリポジトリの整理
開発フローがgit-flowであれ、github-flowであれホスティングサービス上に自分用のリポジトリを持っておくことになるでしょう。開発に参加するならば、GitHub上でリポジトリをforkしますよね。
そこで開発マシン上で自分用のリモートリポジトリに名前をつけてあげます。
$ git remote add self git@github.com:treby/reddit.git
とすれば、先ほどのreddit/redditをforkした(という想定)treby/redditにself
という名前をつけたことになります。
最新のmasterブランチのコードを入手するならmasterブランチ上で、git pull origin master
すれば良いですし、featureブランチを切って、新機能を開発した際はgit push self feature/foobar
として、ホスティングサービス上などからPull Requestを作成することになります。
なお、余談ですがリモートの名前一覧はgit remote -v
で見ることができます。
$ 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
の事なのですが設定していないとコミットする際にデフォルトで、
- 名前: マシンに設定された名前
- メールアドレス: 【ユーザ名】@【ホスト名】
という情報が使われます。(コミット自体ができないかと思いきや、できるのはできるんですね)
$ 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情報などに使われる自分の情報を設定してあげます。
$ git config --global user.name "treby"
$ git config --global user.email "treby@example.net"
とまあ通常であれば、大抵のケースではこれ一回行えば良いのですが、プロジェクトによってAuthorを変えたいという場合が生じることがあります。一つのマシンで仕事のコードと趣味のコード、両方を書くようなケースですね。
git
がコミットの際に付与する情報はデフォルトでは上記で設定した情報ですが、もちろんリポジトリごとに設定することもできます。やり方は簡単で設定したいプロジェクトのコードがあるディレクトリで先ほどのコマンドから--global
を外せば良いです。
$ git config user.name "Hiroaki Ninomiya"
$ git config user.email "hiroaki.ninomiya@example.com"
ここで設定した情報はプロジェクトルート以下の.git/config
に記述されています。
$ tail .git/config
:
<略>
:
[user]
name = Hiroaki Ninomiya
email = hiroaki.ninomiya@example.com
ここまでで一通りの開発準備ができました。
よく使うテクニックあれこれ
次は実際に開発作業を行う上でよく使うコマンドをTips風に紹介します。
ブランチの作成と切り替え
ブランチを切るコマンドとしてはgit branch <ブランチ名>
ですが、git checkout
の-b
オプションを使用することが多いです(ブランチを新しく作成して、かつそのブランチに切り替えてくれる)
$ git checkout master
$ git pull origin master # 開発環境上のコードを最新にする
$ git checkout -b feature/sugoi_kinou
※2020年5月追記
なお、 git checkout
についてはそのコマンドが持つ役割が多すぎるという声もあったようです。そのフィードバックを反映して、Git 2.23(2019年8月リリース) で同等の機能を持つ git switch
(ブランチの切り替えに使用) コマンドと git restore
(ファイルの変更に使用) コマンドが実験的に導入されました。
上記の git checkout -b
を利用した「ブランチを新しく作成して、かつそのブランチに切り替える」操作を実行する git switch
コマンドは
$ git switch -c feature/sugoi_kinou
となります。 checkout
-> switch
のみならず -b
(--branch
) -> -c
(--create
)とオプションも変更になっているのがミソですね。その他の git checkout
と git switch
/ git restore
の書き換えについてはこちらの記事などを参照いただくと良いと思います。
当面は、従来の git checkout
の使い方でも問題はないですが、ツールのアップデートに追従するという点で知識として頭の片隅に置いておくと良いかもしれません。
git branch
自体はブランチを一覧するための用途に使用することが多いです。
$ git branch
* feature/foobar
master
git branch
は--merged
オプションで現在のブランチ(というかHEAD
というか)にマージされている(含まれている)ブランチの一覧、-d
オプションでブランチの削除ができます。
これらのオプションを利用して、開発が進んでブランチが多くなった時などmasterブランチ(最新にしておく)上などで以下のようにして、マージされたブランチを一括で削除したりします。
$ git branch --merged | grep -v '*' | xargs git branch -d
Deleted branch ...
Deleted branch ...
コミット前の準備
コードの修正がひと段落したらコードをステージングエリアにあげましょう。
ステージングエリアという考え方が独特でとっつきにくく感じていらっしゃる方も多いようですが、コミットの前の準備状態くらいの認識で良いのではないかと思います(15日目の記事では「手紙を封筒に入れる」というユニークな表現をされています)。
基本はプロジェクトディレクトリのルートで
$ git add .
と入力すれば、そこまで行った全てのコード修正がステージングエリアにあがります(封筒に詰められます)。
と、全てのコード修正をステージングエリアにあげて、コミットしたい場合はこれで良いのですが、コード修正を行ったけれど一部は別にしたいなぁとか、今はまだいいかなというケースではどうすれば良いのでしょうか。
例えば、私は移り気な性格をしていますので、機能を実装している途中に実装と全く関係のない部分のタイポ修正だとか、インデントの崩れの修正だとかを行ってしまいます。
そこで私は、
$ git add <パス>
で明示的にステージングエリアにあげるファイルを指定したり、
$ git add -p
で、どの部分の変更をステージングエリアにあげるのか指定することも割とあります。(-p
オプションは6日目の記事でも紹介されていますね!)
細かな修正は本当に軽微ならば一緒にコミットしちゃうこともありますが、また別の機会にある程度まとまった単位で修正Pull Requestしたほうが行儀が良い気がします。
ちなみにコード修正・追加ではなく、名前の変更やファイル削除をしたい場合はそれぞれ、git mv
、git rm
を使うと、これらの変更をステージングエリアにあげることができます。
コミットの作り方
ステージングエリアの準備ができたら、次はいよいよコミットです。行った修正を歴史に刻みましょう。
まず、git commit
を打つ前に、git diff --staged
とgit status
を使ってコミットしようとしている内容を本当にコミットして良いか確認しましょう。.DS_Store
やThumb.db
が含まれていると恥ずかしいですよ:)
実際のところ、普段は-m
オプションつけて一行でコメントを書くことが多いです。
$ git commit -m 'Redirect to root if user has something bad.'
チケット対応など、修正に対応するリソースがある場合は、あえてオプションをつけずエディタを起動させます。そしてエディタ上で2行目などにチケットへのURLを添付します。
$ git commit
〜エディタ起動〜
Fix for TICKET-XXXXXX
http://internal.example.com/TICKET-XXXXXX
本当に稀ですが、プロジェクトのルート上で、-a
オプション(git add .
)つけてコミットすることもあります。
$ git commit -am 'Fix typo.'
ちなみにコメントは現在形で書くのが良いらしいです。まあ、日本語/英語、現在形/過去形、自動詞/他動詞などのお作法はそれぞれのプロジェクトの文化によると思いますので、そちらに合わせてください。
コミットの作り直し
ちゃんと正しいかを確認してコミットしたつもりでも、やっぱりミスっていたり、そうでなくても後で関連する場所のtypoを直したくなったりすることはよくあります。そのようなケースでのコミットの作り直し(歴史の刻み直し)方を紹介します。
ちなみにエチケット上、公開されている環境下(チームの共用リポジトリなど)のコミットを書き換えることは厳禁です。事故の元になるので控えましょう。(まあ、そこの判断がまた難しいんですけどね……自分のリモートリポジトリなら良い気がしますが、Pull Request投げている状態でそれされるとーとか、いや、まだコメント付いてないならセーフだろうとかとか)
まずは、素直にゴミファイル削除のコミットや、Fix typoのコミットを作成してみて、それに慣れた段階で参考されてみてください。
git commit --amend
単純に直前のコミットに修正やファイル追加やファイル削除などを行いたい場合は、--amend
オプションを使います。
$ git add .
$ git commit --amend
git rebase
機能開発途中のコミットでtypoをしてしまった場合など、対話的rebaseを使います(-i
オプション)。
例えば3つ前のコミットで軽微なミスをした場合は以下のように入力します。
$ git rebase -i HEAD~3
するとエディタが起動して、下記のような画面になります。
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)にまとめられる形になります。
修正が完了したら、
$ git rebase --continue
でrebase作業を続行します。
なお、途中で訳がわからなくなった場合などは
$ git rebase --abort
でrebase処理そのものを中断することもできます。
git rebase -i
について、ここではコミット間に変更を加えるedit
を紹介しましたが、その他にもコミットを一つにまとめられるsquash
などを利用することができます。
さらに、pick
の記述をなくすことでコミットそのものをなかったことにしたり、前後を入れ替えることでコミットの順番を変更することもできます。
また、git rebase
自体は作成してからちょっと時間が経っちゃったなというブランチに対して行うことが多いです。
$ git checkout master
$ git pull origin master # 開発環境上のmasterブランチを最新にする
$ git checkout feature/foobar # 開発開始から時間が経ってしまったブランチ
$ git rebase master
git reset
実際上、ブランチのつじつま合わせに使っています。
例えば、本来であれば別途featureブランチを作成して行うべきコミットを誤ってmasterブランチに行ってしまった場合などです。以下に例を示します。
$ 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
します(一旦戻そう、的な)。
$ git reset --hard
まとめ
git
はあくまでツールの一つなので最低限使えればそれで十分だと思います。が、自由自在に使えるとドヤリングできますし、頼られますし、微かな全能感があって楽しいのも確かです。
使い始めはなんだかよくわからなくておっかなびっくりになってしまいがちですが(私の場合、開発環境上でコミットすら怖かったです)、コミットしていてリポジトリ(プロジェクトの.git
ディレクトリ)が無事ならばなんとかなります。master
ブランチ消してもなんとかなります。
Gitを使って、どんどんHappy Hacking!!していきましょう。
明日はshigemk2さんによる「GitHubEnterpriseを導入してみるにあたって気をつけたいことをいくつか」(既に記事がある……!)です。よろしくお願いします!