各用語
- リポジトリ:コードを管理する単位。管理するソースコードが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から持ってくるだけ。ブランチは移動しない。