各用語
- リポジトリ:コードを管理する単位。管理するソースコードが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を入れよう。常に状態がわかる。
最後の手段として
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から持ってくるだけ。ブランチは移動しない。