2023 年度 OSS リテラシー 3 : Git, GitHub 入門 (2)
開発フロー: GitHub flow
多人数が同じブランチで作業をすると, コンフリクトが起きやすくなる. 特に開発者全員が master ブランチを使う状況では, 常に他の開発者のことを意識せねばならず, かえって開発の効率が悪くなることがある.
このような状況が起きにくくなるように, Git ではいくつかの標準的な使い方 (開発フロー) が提案されている. ここでは GitHub 社が実践しているシンプルなワークフローである GitHub flow をごく限定的に説明する. GitHub flow の実践は来週行う. また, 全容や詳細を知りたい場合は <URL:https://gist.github.com/Gab-km/3705015> (日本語) を参照されたい.
Git flow の鉄則は,
- 永続的なブランチは master のみ.
- 新作業は master ブランチから新ブランチを作成してから行う.
- 作成した新ブランチに修正/追加をコミットする.
- 新ブランチは最終的には master ブランチにマージする (pull request する).
ことである. 新機能の追加でもバグ修正でも必ず作業用のブランチ (「feature ブランチ」や「トピックスブランチ」と呼ぶ) を作り, そのブランチで作業する. ブランチ名は行う具体的な作業名になっていることが望ましい. ここで注意すべきは, 1 つの作業を 1 つのブランチで行うことである.
例えば,
1) コードのインデントが崩れていたので修正
2) 英単語にスペルミスがあったので修正
3) 新たなメソッドを追加
という 3 つの作業を行いたい場合は, これらをまとめて 1 つの ブランチで行うのではなく, それぞれを別々のブランチで行うべきである. 他の開発者のためにも, 修正・追加の意図が伝わりやすいように, コミットの粒度に気をつけるべきである.
この開発フローではマスターブランチ以外は作業中のブランチとなるため, 気軽に作業中のブランチを push することができる. 定期的に GitHub などのリモートリポジトリに push すると良い.
Github の利用 (1) : ユーザとして
GitHub で公開されているリポジトリを利用するだけなら git clone コマンドでリポジトリをローカル環境に clone するだけで十分である.
例えばお馴染みの smalruby のソースファイルをローカル環境に clone するには, GitHub の smalruby の Web ページを開き, 右側の「Clone or download」を クリックすると, clone するために使う URL が表示される.
それをコピー & ペーストして, 以下のように実行すれば良い.
$ git clone --depth=1 https://github.com/smalruby/smalruby3-gui.git
GitHub の利用 (2) : オープンソースへ Pull Request
GitHub の Pull Request (プルリクエスト, プルリク) 機能は, 自分で変更したコードをオリジナルのリポジトリに取り込んでもらえるよう 依頼するための機能であり, GitHub の機能の中心をなすものである. オリジナルのリポジトリの開発者 (contributors) でなくても Pull Request を簡単に行うことができ, この機能のおかげでオープンソース開発に多くの人が関わりやすくなった.
開発者 (contributors) でないリポジトリに対して Pull Request を行うためには, いったん自分の GitHub に fork するという手順が必要となる.
以下の演習では教員の GitHub にあるファイルを修正し, Pull Request することにする. 修正対象の HTML ファイルは以下でも公開されている. <URL:https://sugiymkc.github.io/firststep2018/>
Fork と git clone
Pull Request を行うためには, まず始めに相手の GitHub のリポジトリを自分の GitHub に Fork することが必要となる. 試しに GitHub に自分のアカウントでログインし, <URL:https://github.com/sugiymkc/firststep2018> にアクセスせよ. 右上の Fork ボタンを押すと, 自分の GitHub に相手のリポジトリを Fork することができる.
自分の GitHub を開くと, sugiymkc の firststep2018 リポジトリを fork したことが確認できる.
次に自分のローカル環境に GitHub のリポジトリを clone する. 本演習では自分の使っているラズパイにおいて git clone することになる. git clone コマンドを実行すると git pull と同様に, ローカル環境に GitHub のリポジトリのコピーを作ることができる. なお, GitHub のリポジトリは全世界に公開されているので, git clone する際にユーザ名やパスワードの入力は必要ない.
$ git clone https://github.com/<自分のユーザ名>/firststep2018.git Cloning into 'firststep2018'... remote: Counting objects: 4, done. remote: Compressing objects: 100% (3/3), done. remote: Total 4 (delta 0), reused 0 (delta 0), pack-reused 0 Unpacking objects: 100% (4/4), done.
ブランチの作成とファイルの編集
まずは clone したリポジトリに cd し, リポジトリの user.name と user.email を GitHub に登録したものに変更する.
$ cd firststep2018/ $ git config --global user.name "名前" (GitHub のアカウント) $ git config --global user.email "メールアドレス" (GitHub に登録したメールアドレス)
後に確認できるが, このような設定をしておくことで GitHub にコミットした際, Web ページに自分のアイコンが表示されるようになる.
Pull request はブランチ単位で行うので, clone した Git リポジトリで feature ブランチ (トピックスブランチ) を作成する. git branch -a を実行してリモートリポジトリも含めた全ブランチを確認し, 名前が重複しないように新たなブランチの名前を決める. ここでは add_学生番号 とする.
$ git branch -a * master remotes/origin/HEAD -> origin/master remotes/origin/master $ git checkout -b add_jxxxx (jxxxx は学生番号に直すこと) Switched to a new branch 'add_jxxxx'
git branch を実行すると, 新たに作った add_学生番号のブランチに移動したことが確認できる.
$ git branch * add_jxxxx master
ディレクトリ内を ls すると LICENSE と README.md の 2 つのファイルと docs ディレクトリ が存在することがわかる. docs ディレクトリには index.html ファイルが存在することがわかる. 今回は docs/index.html を編集することにする.
$ ls LICENSE README.md docs $ ls docs index.html $ vi docs/index.html <p>テストコメント 2 です @sugiymki</p> (<body> から </body> の間にコメントを書くこと. p タグを使うこと.)
編集が終わったらいつも通り git add と git commit を行う.
$ git add docs/index.html $ git commit -m "add my impression by sugiymki" (コメントは適宜修正すること) 1 file changed, 1 insertion(+)
新規ブランチを GitHub へ push
新規に作成したブランチ (add_学生番号) を自分の GitHub のリポジトリへ push する.
$ git push origin add_jxxxx (学生番号にすること) Username for 'https://github.com': <ユーザ名> <-- GitHub のユーザ名を入れる Password for 'https://[email protected]': <-- GitHub の token を入れる Counting objects: 12, done. Delta compression using up to 4 threads. Compressing objects: 100% (10/10), done. Writing objects: 100% (12/12), 2.37 KiB | 0 bytes/s, done. Total 12 (delta 4), reused 0 (delta 0) remote: Resolving deltas: 100% (4/4), done. To https://github.com/sugiymki/firststep2018.git * [new branch] add_jxxxx -> add_jxxxx
リポジトリを push したので, git branch -a コマンドを行うと, リモートにブランチが追加されたことがわかる.
$ git branch -a * add_jxxxx master remotes/origin/HEAD -> origin/master remotes/origin/add_jxxxx <--- 追加 remotes/origin/master
GitHub の「ユーザ名/firststep2018」のサイトを Web ブラウザで表示し, 「add_学生番号」のブランチが追加されたことを確認せよ.
Pull Request (プルリクエスト, プルリク) を送る
Web ブラウザで自分の GitHub の firststep2018 リポジトリを開く. 左上のプルダウンメニューから git push した add_学生番号 (feature ブランチ, トッピックブランチ) を選択する.
Pull Request を送るには, push したリポジトリの表示画面の横の "Compare & pull request" を押せばよい.
相手先のリポジトリが間違っていないか慎重に確認した後, Write タグを選択してコメントを書く.
送信すると, 以下のように Pull Request が全世界に公開されることになる.
Pull Requset を受け取ったら
上記までの作業で, 一度に 40 人の Pull Request が教員の元に来ることになる. コンフリクトが生じる可能性が高いので, Pull Request 後の様子は Web 画面の スナップショットで示すに留める.
オリジナルのリポジトリの開発者は, 以下のように相手からの Pull Request を受け取る. 相手から送られてきたソースをコードレビューしてコメントを返す. オリジナルの開発者は相手からのコードをすぐにマージする必要はない. コメントを何度かやりとりして, 相手からの修正依頼が納得できるものとなってから, マージを行えばよい. 以下の例では「おっけー」とコメントし, master ブランチに マージを行った.
以下は Pull Request を送信したユーザの Pull Requset 画面である. 送ったソースがマージされた後には, 赤で示す「Delete branch」が表示される. もはやブランチが必要なければこのボタンを押してトピックブランチを消すと良い.
参考: リポジトリのメンテナンス
fork や clone してきたリポジトリはそのまま放置すると最新のソースコードから 離れていってしまう. 最新コードを取り込んでそれをベースに開発を行わないと, せっかく書いたコードが無駄になってしまうこともある.
clone してきたリポジトリは, その時点でオリジナルのリポジトリとは 全く別のものとして扱われる. 自分のリポジトリを最新版に保つには, リモートリポジトリとしてオリジナルのリポジトリを設定し, リポジトリの データを取り込んで (fetch), 自分のリポジトリにマージ (merge) することが必要となる.
まずは自分のローカル環境のリポジトリに移動する.
$ cd ~/firststep2018/
オリジナルのリポジトリに upstream という名前をつける.
$ git remote add upstream https://github.com/sugiymkc/firststep2018.git
最新のデータを取得 (fetch) しマージ (merge) する.
$ git fetch upstream remote: Counting objects: 8, done. remote: Compressing objects: 100% (7/7), done. remote: Total 8 (delta 2), reused 0 (delta 0), pack-reused 0 Unpacking objects: 100% (8/8), done. From https://github.com/sugiymkc/firststep2018 * [new branch] master -> upstream/master $ git merge upstream/master Updating e2ab1dc..cb28d40 Fast-forward docs/index.html | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 docs/index.html
上記の例はアップデートするファイルがあった場合のものだが, すでに 最新版となっている場合には git merge コマンドを実行すると "Already up-to-date" と出力される.
GitHub の利用 (3): 共同開発
開発フロー: GitHub Flow
GitHub 社が実践しているシンプルなワークフローである GitHub Flow に従って共同開発を行う. 全容や詳細を知りたい場合は <URL:https://gist.github.com/Gab-km/3705015> (日本語) を参照されたい.
Git Flow の鉄則は,
- 永続的なブランチは master のみ.
- 新作業は master ブランチから新ブランチを作成してから行う.
- 作成した新ブランチに修正/追加をコミットする.
- 新ブランチは最終的には master ブランチにマージする (pull request する).
ことである. 新機能の追加でもバグ修正でも必ず作業用のブランチ (「feature ブランチ」や「トピックブランチ」と呼ぶ) を作り, そのブランチで作業する. ブランチ名は行う具体的な作業名になっていることが望ましい. ここで注意すべきは, 1 つの作業を 1 つのブランチで行うことである.
例えば,
1) コードのインデントが崩れていたので修正
2) 英単語にスペルミスがあったので修正
3) 新たなメソッドを追加
という 3 つの作業を行いたい場合は, これらをまとめて 1 つの ブランチで行うのではなく, それぞれを別々のブランチで行うべきである. 他の開発者のためにも, 修正・追加の意図が伝わりやすいように, コミットの粒度に気をつけるべきである.
準備
GitHub Flow に基づいた共同開発を練習する. ここでは 2 人 1 組で作業を行う. 今回は A さんと B さんの 2 人で作業をすることにし, A さんの GitHub 上のリポジトリの 共同開発者 (collaborator) として B さんを登録する.
なお,チームの人数が 3 人以上となってしまった場合には,B さんの役割をする人を増やすことで対応して欲しい.
自分の GitHub に新たなレポジトリを作成する. second-step2023 という名前にする.
作成したら, Settings > Manage access から友人を collaborators に加える.
"Add people" ボタンを押すと, リストされる.
Collaborator になるかどうか確認するメールが届く. メール上のリンクをクリックすると以下の Web が開くので, 承諾か否かを答える.
承諾すると A さんのリポジトリが自動で Web ブラウザ上に表示される. 上部に "You now have push access to the sugiymkc/second-step2023 repository." と書かれていることに気づくことだろう.
(注: 携帯でメールを受け取る場合は, 承認する前に,
- 携帯のブラウザで GitHub にログインしておく,
- ブラウザが「プライベートウィンドウモード」になっていないこと,
の 2 つを確認すること. そうしないと "404 NOT FOUND" となってしまう.
git clone
前回作成したテスト用のディレクトリで作業を行う.
$ cd ~/test-remote
リモートリポジトリからデータを取得する.
$ git clone https://github.com/<Aさんのユーザ名>/second-step2023.git Cloning into 'second-step2023'... remote: Counting objects: 4, done. remote: Compressing objects: 100% (3/3), done. remote: Total 4 (delta 0), reused 0 (delta 0), pack-reused 0 Unpacking objects: 100% (4/4), done. $ cd second-step2023
リポジトリの user.name と user.email を GitHub に登録したものに変更する.
$ git config --global user.name "名前" (GitHub のアカウント) $ git config --global user.email "メールアドレス" (GitHub に登録したメールアドレス)
git status や git branch を実行すると, git clone を行った直後には master ブランチにいることがわかる. また, git status のメッセージ中に 'origin/master' とあるように, clone 元のリモートリポジトリは origin という名前で参照できるように自動的に設定されている.
$ git status On branch master Your branch is up-to-date with 'origin/master'. nothing to commit, working tree clean $ git branch * master
git branch に -a オプションをつけて実行すると, リモートリポジトリの 中に存在するブランチも表示することができる. 今は master のみが存在する.
$ git branch -a * master remotes/origin/HEAD -> origin/master remotes/origin/master
ローカルリポジトリの feature ブランチ (トピックブランチ) での作業
feature ブランチの作成を行う. A さんと B さんの役割はそれぞれ以下の通りとする.
- A さん : C のプログラムを追加する.
- B さん : ruby のプログラムを追加する.
なお, git push の際に -u origin <ブランチ名> というオプションを与える必要がある. これにより, orgin という名前のリモートリポジトリ (GitHub の second-step2023 リポジトリが自動的に設定されている) に 新たなブランチを追加することができる.
以下のように作業してみよ.
$ git checkout -b feature-A Switched to a new branch 'feature-A' $ vi hello.c (プログラムの中身は何でもよい. ここでは Hello World に) #include <stdio.h> int main() { printf("Hello, world!\n"); return 0; } $ gcc hello.c ; ./a.out (動作チェック) Hello, World! $ git add hello.c $ git commit -m "add program (C)" [feature-A a5b877f] add program (C) 1 file changed, 8 insertions(+) create mode 100644 hello.c $ git push -u origin feature-A Username for 'https://github.com': <--- ユーザ名入力 Password for 'https://[email protected]': <--- token 入力 Counting objects: 3, done. Delta compression using up to 4 threads. Compressing objects: 100% (3/3), done. Writing objects: 100% (3/3), 384 bytes | 0 bytes/s, done. Total 3 (delta 0), reused 0 (delta 0) To https://github.com/sugiymkc/second-step2023.git * [new branch] feature-A -> feature-A Branch feature-A set up to track remote branch feature-A from origin.
以下のように作業してみよ.
$ git checkout -b feature-B Switched to a new branch 'feature-B' $ vi hello.rb (プログラムの中身は何でもよい. ここでは Hello World に) print "Hello world! \n" $ ruby hell.rb (動作チェック) Hello World! $ git add hello.rb $ git commit -m "add program (Ruby)" [feature-B 2beed6b] add program (Ruby) 1 file changed, 1 insertion(+) create mode 100644 hello.rb $ git push -u origin feature-B Username for 'https://github.com': <--- ユーザ名入力 Password for 'https://[email protected]': <--- token 入力 Counting objects: 3, done. Delta compression using up to 4 threads. Compressing objects: 100% (2/2), done. Writing objects: 100% (3/3), 334 bytes | 0 bytes/s, done. Total 3 (delta 0), reused 0 (delta 0) To https://github.com/sugiymkc/second-step2023.git * [new branch] feature-B -> feature-B Branch feature-B set up to track remote branch feature-B from origin.
GitHub 上で Pull Request の送信
A さんと B さんが共にブランチを git push すると, GitHub 上で新たに 2 つのブランチが加わっていることが確認できる.
A さんの Web ブラウザには, "Your recently pushed branches:" として feature-A が, B さんの Web ブラウザには, "Your recently pushed branches:" として feature-B が, 表示される. 以下は A さんの Web 画面の例である.
Pull Request を送るには, ブランチを選択して "Compare & pull request" ボタンを押す.
コメントを入れる.
pull request は全ての開発者に送られる. "pull request" タブを押すと内容を確認できる.
何度か開発者の間でコードについてコードレビューやコメントのやり取りをした後, "merge pull request" を押してマージする. リンクを押せばソースコードの修正点が簡単に確認できる.
マージが成功すると以下のようなメッセージが表示される.
同様にして, もう 1 つの feature ブランチを pull request し, master ブランチにマージする. ここで着目してほしいのは, feature-A のブランチを作った後に master ブランチを変更している (feature-B ブランチをマージした結果として) にもかかわらず, 「コンフリクトしていない (This branch has no conflicts with the base branch)」ことである. コミットの粒度に気をつけ, 個々の作業単位で feature ブランチを作ったおかげで, 個々の開発者の作業が被らなくて済んだのである.
GitHub とローカルの同期
GitHub のリポジトリの master ブランチが変更されたので, その変更を ローカル環境へ git pull する必要がある. まずはリポジトリに移動する.
$ cd ~/second-step2023
git status を実行し, ブランチを確認する. master ブランチでなければ, master ブランチに移動する.
$ git status On branch feature-B Your branch is up-to-date with 'origin/feature-B'. nothing to commit, working tree clean $ git checkout master Switched to branch 'master' Your branch is up-to-date with 'origin/master'. $ git status On branch master Your branch is up-to-date with 'origin/master'. nothing to commit, working tree clean $ git pull origin master remote: Counting objects: 5, done. remote: Compressing objects: 100% (5/5), done. remote: Total 5 (delta 1), reused 2 (delta 0), pack-reused 0 Unpacking objects: 100% (5/5), done. From https://github.com/sugiymkc/second-step2023 * branch master -> FETCH_HEAD b490dbd..892a8fd master -> origin/master Updating b490dbd..892a8fd Fast-forward hello.rb | 4 ++++ hello.c | 8 ++++++++ 2 file changed, 12 insertions(+) create mode 100644 hello.c
ls コマンドを実行すると, 新たに master ブランチに加わった hello.c と hello.rb が表示される.
$ ls LICENSE README.md hello.c hello.rb
課題
準備 (Ruby で統計量)
準備として, NArray をインストールする.
$ sudo -s # apt-get update # apt-get install ruby-dev make g++ ruby-narray (g++ は C++ コンパイラ. 必要なら入れる) # exit $
NArray を使って平均値を計算してみる.NArray メソッドは SPEC.ja.txt を参照されたい.
$ vi test.rb <以下のように書く> require "narray" # narray を呼ぶ #################################### #数字を配列化 score1 = [65, 80, 67, 35, 57] # 平均値の計算. Ruby の通常の配列には mean メソッドは無い # .to_f (整数から実数の変換) しないと,少数点以下が切り捨てされるので注意 p score1.sum.to_f / score1.size.to_f #################################### # NArray を使う場合は,NArray.new や NArray.to_na する. score2 = NArray.to_na( [65, 80, 67, 35, 57] ) # 平均値の計算 p score2.mean
実行して同じ数字になることを確認.
$ ruby test.rb 60.8 60.8
課題内容
- GitHub flow に沿った共同開発を行う. 上記の演習を終えた後, グループの中でソースファイルの追加/修正をさらに行い, 最終的に master ブランチに登録せよ.
- 作業の証拠として, (1) Pull request の画面, (2) 作成したプログラムを GitHub 上で表示した画面, の 2 つをキャプチャして wbt に登録せよ. 但し,ブラウザの URL 欄を必ず含めるようにスナップショットを取得せよ.
- 手順
- 上記の A さん・B さんの役割分担を交換する.
- 新たにレポジトリ (third-step) を作成すること.
- リポジトリ third-step を git clone する.
ローカルリポジトリに feature ブランチ (トピックスブランチ) を作成し, 以下の問題を解く Ruby のプログラムと C 言語のプログラムを作成する (A さんは C 言語で, B さんは Ruby で書くこと.3 人目以降は何でも良いが C, Ruby では無い言語で書くこと).
- Ruby で平均値や標準偏差などの統計量を求める場合は,NArray を使うと良い.NArray のメソッドは SPEC.ja.txt を参照されたい.
笑介くんのクラス20人のテストの点数は, 理科が 65 80 67 35 58 60 72 75 68 92 36 50 25 85 46 42 78 62 84 70 英語が 44 87 100 63 52 60 58 73 55 86 29 56 89 23 65 84 64 27 86 84 でした. 理科と英語のそれぞれについて, 平均点, 標準偏差, 合計点を求めよ. それぞれの人について, 理科と英語の偏差値を求めよ. さらに, 理科と英語のそれぞれについて点数の高い順に並べかえなさい.
- ローカルリポジトリで作成したブランチを GitHub に git push する.
- GitHub で Pull Request する.
- 相方のプログラムをコードレビューし,1 つのプログラムに対して 2 回以上は意見のやりとりをする. コードレビューの結果を踏まえてソースファイルの修正を行うこと.
- 最終的にお互いのプログラムを "Merge pull request" する.