渡米生活。(日記)

渡米生活。本家から切り離しました。あまり渡米生活に関係のないプログラムネタや音楽ネタなど。

SVNユーザーのGitHub初心者メモ:他人のプロジェクトに手を加える場合(フォーク&プルリクエスト)

GitHub初心者でいろいろやらかしたので忘備録。
人様のプロジェクトに手を加えたいときにどうするか。

想定されるケースとして、

  • バグを見つけたのでバグフィックス依頼(プルリクエスト, pull request)を送りたい
  • 機能が足りないので自分用に新しい関数を足したい(オリジナルの作者へのマージ依頼は送らない)

の2種類があるけど、それぞれに別の手順を覚えておけるほどの脳味噌はないので、ほとんど同じにしてプルリクエストを送るかどうかだけが違う方法1つだけ覚えることにする。

※以下のサイトの方が図入りで判りやすいです。
Github で Fork してから Pull Request をするまでの流れ | けーこ in サンフランシスコ

最初に1度だけやればいい作業

もとにしたいプロジェクトをForkする。

もとにしたいプロジェクトをここでは本家と呼ぶ。

  1. GitHubのウェブサイトでコピーしたいプロジェクトのページへ行き、右上のForkボタンを押す。
  2. 自分のアカウント内にコピーしたプロジェクト名ができるので、端末でそっちの方をcloneする(こちらを分家とする。本家の方を間違ってcloneしないこと。)
$ cd <somewhere>
$ git clone https://github.com/アカウント名/プロジェクト名.git
$ cd プロジェクト名

本家プロジェクトとの関連づけを行う

本家プロジェクトのコピー用のリポジトリ名は、とりあえずupstreamという名前にしておく。
(別になんでも良い)

$ git remote add upstream git://github.com/本家アカウント名/プロジェクト名.git
$ git fetch upstream
$ git branch -a  
* master
  remotes/origin/HEAD -> origin/master
  remotes/origin/master
  remotes/upstream/master
  • git branch -a はプロジェクトの全てのリポジトリとブランチを見るコマンド。
  • remotesで始まるブランチは追跡ブランチ。GitHubサーバ上に保管されているリモートのソースのローカルコピー。ようするにスナップショットなので、時間がたつと、サーバー上の内容が更新されて最新ではなくなるかもしれない。追跡ブランチは直接いじることはできない。
  • originとかupstreamとかが「リポジトリ名」、masterが「ブランチ名」。
  • originリポジトリとその下のmasterブランチはデフォルトで勝手に作られる。

Developブランチを作る

(ブランチを切る、ともいう)

絶対、いきなりmasterブランチを触らないこと!
masterはマージ専用にとっておく。
とはいえ、絶対ついウッカリ変更してしまうので、意識的にmasterをcheckoutしない限り変更することがないように、とりあえずdevelopブランチを作ってcheckoutしておく。

$ git branch develop
$ git push origin develop   
$ git checkout develop
  • pushはリモートに情報を書き込むときに使う。origin developはリモートのoriginリポジトリの下にdevelopブランチの情報をコピーしろ、の意味。
  • 作業ブランチを変更するには、git checkout ブランチ名。

何か変更したい都度にやる作業

まず、本家のmasterと分家(自分がforkしたもの)のmasterを一致させる

これはスキップしてもいいけれど、なるべく差分を小さくするには最初にやっといた方がいいかも。

$ git fetch upstream  
$ git checkout master
$ git merge upstream/master
$ git push 
  1. git fetchでupstream、つまり本家を追跡するリポジトリの追跡ブランチmasterの内容を最新にする
  2. git checkoutでmasterの作業ブランチに移動
  3. git mergeで現在の作業ブランチであるmasterに、更新後の本家masterの内容(upstream/master)をマージする
  4. git pull upstream/master でもいいのかもしれないが、いまひとつこのコマンドがよくわかっていないので2段階に分けてみた。

その変更専用のブランチを切って、ファイルに変更を加える。または、既存のブランチに最新のマスターをマージ。

$ git branch add_constructor
$ git push origin add_constructor
$ git checkout add_constructor
  • add_constructorは新しく作成するブランチ名、なんでも良いけど何を変更したのかわかる名前にしとくとあとが楽かも。
  • ブランチの切り方は、developブランチを切るときと一緒。

すでにadd_constructorブランチがある場合は、add_constructorブランチをまずチェックアウト。

$ git checkout add_constructor
$ git merge master

※コンフリクトが出たら、手パッチ。

動作確認

$ git checkout add_constructor
$ make 
....

重要!!
コンパイルする前に目的のブランチをcheckoutしておくこと!

なぜ、ブランチを切り替えるのにcheckoutコマンドなのか?
subversionsvnコマンドに慣れているとなんか奇妙な感じなのだけど(だいたい最初の1回ソースを取ってくる時しか使わないよね)、なぜなのかようやく理解した。
gitでは、checkoutコマンドを発行すると、文字通りソースが書き換わるのだった。

現在作業しているブランチがdevelopで、他にhogeという名前のブランチがあるとする。
ここで、git checkout hoge とやると、今作業しているディレクトリのファイルは全て、hogeプロジェクトの最後に保存したスナップショットに書き換わってしまう。
svnでいうと、今ある作業ディレクトリに上書きする感じで、別のブランチをsvn checkoutする感じに近い。そう考えれば、なぜcheckoutなのか納得できる。

なので、今自分がどこのブランチにいるのかを確認してからコンパイルをかけないと、どのソースをコンパイルしたのかわからなくなって、実行時にハマる。

svnsvn switchがブランチ変更に相当すると思っていたけど、svnはswitchしてもsvn updateをかけない限り現在作業中のファイルが変更されることはないので油断した…。

ちなみに、git checkout hoge とやる前に、developブランチで何か変更しかけファイルがあったとして、そいつをgit commitしないままgit checkout hogeをやってしまった場合、コミットしてない変更がどうなるのかは、、まだ確かめてない(汗)。
これは、変更しかけのファイルがある場合、checkoutコマンドを打っても「変更したのに保存してないよ!」と怒られて、git commitするまで別ブランチをcheckoutさせてくれない、ということを学んだ。
svnの場合、リリースブランチでない限りはsvn switch & svn updateをかけてもローカルの変更は消えないので、当初この機能が大変不便に感じたのだけれど、まあ更新履歴の管理やブランチ管理を厳密にやるにはこの方がいいのかな、と思う。

変更したファイルをadd

$ git branch -a   
$ git diff #変更箇所の表示。ゴミがないかチェック
$ git add  変更ファイル名
  • しつこいが最初に自分がどのブランチにいるか確認しておく。この場合、add_constructorに*印がついていれば良い。もし違うところにいたら、git checkout add_constructorで移動。

変更のコミットとリモートサーバへの書き込み

$ git commit -m 'added a constructer'
$ git push origin add_constructor

変更を自分だけが使えればいい場合は、ここで終わり。


プルリクエストの送信

自分が加えた変更を、本家プロジェクトの所有者に頼んでマージしてもらうには、プルリクエストを送る。


githubのウェブサイトで、変更をリクエストしたいブランチのページを開ける。
プロッジェクトのページにいくと、左肩にbranchを変更するプルダウンリストがあるので、そこで変更するか、直接以下の書式のURLに移動。

https://github.com/githubアカウント名/プロジェクト名/tree/ブランチ名/

branch名のプルダウンリストの隣に”New pull request" ボタンがあるので、それを押す。

注意(というか自戒):

  • プルリクエスト は忙しい他の開発者に読んでいただくものなので、更新履歴はクリーンにしておく。gitでは手をとめたらコミットしろ、と言われることが多いけれど、そのような途中経過コミットや、単純なタイプミスの修正のためのログまで忙しい開発者様に読ませるのはよろしくないので、1プルリクエスト につき1変更を守り、コメントも完結にわかりやすくしておく。
  • プルリクエストは1送信ごとではなく、1ブランチごとに受理される。つまり、プルリクエストを送ったから、と安心して同じブランチにさらに別の変更を加えた場合、プルリクエスト送信後に加えた変更も全てプルリクエストとして本家プロジェクトの管理者に送られてしまう。
  • 一度送ってしまったプルリクエストは、リクエストをクローズすることはできても、サーバー上から痕跡を消す方法はないらしい。プルリクエスト はよくよく検証してから、全ての変更を完了した後に1度だけ送るようにし、プルリクエスト を送ったらもうそのブランチはいじらない。無事本家にマージされたらブランチごと消去。

で、現在はどうしているかというと、機能追加みたいな多少開発が必要なもののマージをお願いする場合は、プルリクエスト用のブランチは開発完了後に別に切り、そのプルリクエストに含めたい変更を手でコピーして、ちゃんと他人にもわかるようにコメントを入れてコミット&pushし、プルリクエストを送る、という形に落ち着いた。
まあ、ちゃんとしたプログラマーの更新履歴ならそのままプルリクエストに含めてもいいんでしょうが、typo直したとか、アホアホなbug fixの記録まで送るのは恥ずかしいので、、(汗)

プルリクエストを削除する(取り下げる)

極力こんな事態は避けるべきだが、リクエストをクローズにする方法はある。
githubのウェブサイトのプルリクエスト ページの一番下に、closeボタンがあるので、理由を書き込んでcloseするか、またはブランチそのものを消す。

$ git push --delete origin 削除したいリモートブランチ名

これで、プルリクエスト をクローズにすることはできる。
が、どうも一度githupホームページに上がってしまった情報は消せないっぽい。
どうしても全ての痕跡を消したければ、リポジトリごと全部消すしかない模様。

プルリクエストが反映されたあとの作業

本家プロジェクトにプルリクエスト が反映されたら、もうブランチはいらないので、消してもよい。

$ git branch -D add_constructor
$ git push --delete origin add_constructor