- Gitの基本的な操作をCLIから行うことができる
- GitHub Flowに則った開発が行えるようになる
ls,cdなどのコマンドを用いてファイルを操作できる人- C++ソースコードが多少なりとも読める人(高度な知識は必要ありません)
以下の5点を済ませておいてください。
Gitとは、バージョン管理システムの一つです。本教材で理解を目指すGitHubとは、Gitのホスティングサービス(事業者のサーバを一部貸し出すサービス)の一つです。 OSやディストリビューションに合わせてインストールしてください。
-
Windows
Downloading Gitから「Git Bash」というアプリをダウンロード・インストールしてください。
-
MacOS
標準で使うことができます。明示的にインストールしたい場合は以下のコマンドを利用してください。
$ brew install git
-
Linux
Download for Linux and Unixを参考にディストリビューションに合わせた方法でインストールしてください。
gitがインストールできたか確認するためには、以下のコマンドを利用してください。表示のような出力となればOKです。
$ git --version git version 2.30.1
GitHubからアカウントを作成してください。
また、GitHubは2021年8月からプッシュなどの操作時でのパスワード認証を廃止しています。パーソナルアクセストークンを発行していない方は、こちらを参考に発行しておいてください。
各自、テキストエディタを準備してください。
VSCodeを推奨しますが、宗教戦争案件なのでお好きなものをお使いください。
以下のコマンドを実行してください。表示のような出力となればOKです。
$ make --version
GNU Make 3.81
Copyright (C) 2006 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.
There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE.
This program built for i386-apple-darwin11.3.0以下に類する表示の場合、makeがインストールされていません。インストールしてください。
$ make --version
zsh: command not found: make本教材は2人1組となって活動します。以下、Aさん,Bさんという表現をするため、どちらがどちらの役を行うか決めておいてください。
前述の通り、Gitとはバージョン管理システムの一つです。
Gitの挙動を把握する上で、理解が必要になる用語がいくつか存在します。本節では、その中から以下の10個を概説します。
- リポジトリ
- クローン
- プル
- ワークツリー
- ステージング
- インデックス
- コミット
- プッシュ
- ブランチ
- マージ
-
リポジトリ(Repository)
リポジトリとは、資産(ファイルやディレクトリ)を格納している貯蔵庫のことです。
リポジトリはリモートリポジトリとローカルリポジトリの2種類に分けられます。
リモートリポジトリとは、Gitサーバ内に保管されているリポジトリです。
ローカルリポジトリとは、ユーザごとに保有するリポジトリです。
ユーザは、ローカルリポジトリ内で作業を行い、その変更をリモートリポジトリに反映させることで変更点を共有します。
-
クローン(Clone)
クローンとは、リモートリポジトリをローカルリポジトリに複製する操作です。
通常、最初のみ行います。
-
プル(Pull)
プルとは、リモートリポジトリにある、持っていないファイルや他の誰かが更新したファイルをローカルリポジトリに反映させる操作です。すなわち、「自分のローカル環境を共有されているものの最新版に揃える」というような認識です。
先述のクローンはリモートリポジトリの内容を丸ごと複製するのに対し、プルは差分のみを更新します。
topic
これに比べ、リモートリポジトリの最新情報の取得のみを行い、ファイルの更新を行わない操作をフェッチ(Fetch)と呼びます。 本当にプルして良い状態か調べるときなどに使用します。
-
ワークツリー(Working tree)
ワークツリーとは、作業しているディレクトリのことです。
-
ステージング(Staged)
ステージングを理解する前に、Gitは変更を管理するファイルと管理しないファイルを指定できるということを知っておきましょう。
Gitで変更を管理することを
track(追跡)、しないことをuntrackと呼びます。追跡されているファイルは以下の3つの状態のいずれかとなります。
状態 内容 Unmodified 変更されていないファイル Modified 変更されたファイル Staged Commit対象のファイル ModifiedのファイルをStagedの状態にすることをステージングと呼びます。ステージングされたファイルは、後述のインデックスに登録されます。 -
インデックス(Index)
ワークツリーとは、作業しているディレクトリのことでした。それに対しインデックスは、ワークツリーとローカルリポジトリの中間に位置する場所です。ローカルリポジトリに反映させるファイルが格納されます。
このインデックスが存在することにより、余分なファイルをコミットせずに済んだり、ファイルの一部だけの変更を記録してコミットすることができます。
-
コミット(Commit)
インデックスの状態をローカルリポジトリに反映させる操作です。すなわち、施した編集をローカルリポジトリに記録する操作です。
コミットを実行すると、編集した日時が記されたファイルやコミットごとのIDが生成されます。これらは時系列順に格納され、管理されます。
topic
もし「以前の状態に戻したい」という状況になった際には、このIDを指定することで、そのコミット時点の状態まで戻すことができます。
コミットを実行する際に編集が記録されるファイルは、インデックスのファイルのみです。したがって、ステージングとはコミットするファイルを指定する操作であると言い換えることができます。
-
プッシュ(Push)
プッシュとは、ローカルリポジトリにあるファイルをリモートリポジトリに反映する操作です。
ワークツリーで作業した内容をステージングによってインデックスに仮登録し、その後コミットでローカルリポジトリに反映した後、他の人へ変更を共有するためにプッシュするという流れが一般的です。
topic
ここまでで、図の左のユーザは無事変更点を共有することができました。右のユーザがその変更点を取得するためには、前述のプルをすればよいということになります。
-
ブランチ(Branch)
ブランチとは、編集履歴を分岐させる機能です。これによりあるブランチの編集が他のブランチに影響を及ぼさないという状況を作ることができます。
topic
ブランチは、あるブランチのある時点から切り出して作成(分岐)するため、ブランチを作ることを俗に「ブランチを切る」と表現します。
図は、
mainという名前のブランチからfix/aという名前のブランチを作成している例です。ブランチを切ると他ブランチに編集の影響を及ぼさないため、mainブランチはfix/aブランチに加えた変更がなされません。これにより、
fix/aブランチはmainブランチから独立して開発を進めることができます。
-
マージ(Merge)
ブランチを分岐元となった他のブランチに併合する操作をマージと呼びます。
図では、マージを行なったことにより、
mainブランチにfix/aブランチの変更が反映されました。
GitHubは、Gitを利用するホスティングサービスの一つです。
GitHub Flowとは、GitHubを用いた効率的なチーム開発のために策定されたワークフローの一つです。ワークフローってなに?という方はひとまず、業務活動をパターン化するためのルールや規約だと思ってください。
GitHub Flowでは、常に遵守されなければならない6つのルールとして、以下を定めています。
mainブランチのものは何であれデプロイ可能である- 新しい何かに取り組む際は、説明的な名前のブランチを
mainから作成する(例: new-oauth2-scopes) - 作成したブランチにローカルでコミットし、サーバー上の同じ名前のブランチにも定期的に作業内容をpushする
- フィードバックや助言が欲しい時、ブランチをマージしてもよいと思ったときは、プルリクエストを作成する
- 他の誰かがレビューをして機能にOKを出してくれたら、あなたはコードを
mainへマージすることができる - マージをして
mainへpushしたら、直ちにデプロイをする
note
GitHub Flowの規約を調べると、デフォルトのブランチ名は
masterと表記されています。 しかしGitHubは、IT業界の標準に合わせて2020年頃にデフォルトブランチの名前をmasterからmainに変更しました。その後、この変更はGitHubに限らずGitLabなどのサービスでも行われ、現在主流のデフォルトブランチ名はmainとなっています。 この流れを受け、本教材はデフォルトブランチをmainとして話を進めています。
それぞれ見ていきましょう。
-
mainブランチのものは何であれデプロイ可能であるGitHub Flowで、最も重要なルールです。
mainブランチとは、リポジトリが作られた最初の段階から存在する大元のブランチです。また、デプロイとは「本番環境でプログラムを動作させ、ユーザに使ってもらえる状態にする」などを含めた、ソフトウェアを利用できるようにすること全般を指す言葉です。
すなわち、このルールは、
mainブランチは常にバグを含まないものにしろというような意味合いとなります。 -
新しい何かに取り組む際は、説明的な名前のブランチを
mainから作成する(例: new-oauth2-scopes)ここでいう新しい何かとは、新機能追加やバグ修正などの開発作業です。
このルールにより、開発者は「
mainブランチから作成したブランチにて作業を行い、mainブランチにマージする」というシンプルな活動をとることになります。また、作業用ブランチを作成する際には、そのブランチがどのような作業を目的としているか分かりやすいブランチ名をつけるよう決められています。
topic
作業用ブランチ名に関して、「説明的な名前をつけること」とだけ記されており、命名規則は設けられていません。
-
作成したブランチにローカルでコミットし、サーバー上の同じ名前のブランチにも定期的に作業内容をpushする
簡単に言えば「作業用ブランチは定期的にプッシュしなさい」というものです。
-
フィードバックや助言が欲しい時、ブランチをマージしてもよいと思ったときは、プルリクエストを作成する
プルリクエストとは、GitHub上で「このプログラムで大丈夫そうですか?」「誰か動作確認してくれませんか?」というように、他者にアドバイスやレビューを依頼する機能です。
-
他の誰かがレビューをして機能にOKを出してくれたら、あなたはコードを
mainへマージすることができるここでいうレビューとは、ルール4で作成したプルリクエストに集まった評価です。
このルールにより、全ての変更点(新機能やバグ修正)は第3者の確認があった後に反映(マージ)されます。
topic
このとき、「〇〇人OKを出したらマージしてよい」といったような規定は設けられていません。 何人以上や、どういった役職の人が承認したらマージできるのかといったようなルールは組織ごとに策定してください。
-
マージをして
mainへpushしたら、直ちにデプロイをする全ての作業用ブランチは
mainブランチから作成されるため(ルール2)、mainブランチは常に最新の状態でなければなりません。
note
より詳しく理解したい人は、 https://gist.github.com/Gab-km/3705015を読むことをお勧めします。
note
以降、
<>で囲まれた情報は適宜置き換えてください。例えば$ git config --global user.name '<あなたのユーザ名>'は、筆者の場合
$ git config --global user.name 'Lium1126'となります。
Aさんはこのリポジトリをフォークしてください。
下図の「Fork」をクリックします。
note
フォークとは、Gitサーバ上のリポジトリを自分のリモートリポジトリに複製する操作です。よって、
<AさんのGitHubアカウント名>/github-practiceというリモートリポジトリが作成されます。以降は、このリポジトリに対して操作を行うことになります。
フォークを行ったら、Aさんのgithub-practiceリポジトリにBさんがアクセスできるよう設定します。
Aさんはhttps://github.com/<AさんのGitHubアカウント名>/github-practice/にアクセスしてください。
次に、下図の赤く囲んでいる「Settings」をクリックしてください。
移った先のページで、「Manage access」をクリックしてください。認証を求められた場合は、パスワードを用いてログインしてください。
さらに、下図で囲った「Add people」から、BさんのGitHubアカウント名で検索し、このリポジトリに対してアクセス権を持つユーザとして追加してください。
上記の操作が行われると、Bさんのメールアドレスに招待メールが届きます。メールの「View invitation」をクリックしてください。これで、BさんがAさんのgithub-practiceリポジトリに対するアクセス権限を得られます。
以下のコマンドを実行して、出力に下記の表示が含まれているか確認してください。
$ git config -l
user.email=<あなたのメールアドレス>
user.name=<あなたのGitHubアカウント名>上記の表示が得られない場合、お使いの環境にGitアカウントの設定がされていません。
その場合、以下のコマンドで設定してください。
$ git config --global user.name '<自分の名前>'
$ git config --global user.email '<自分のメールアドレス>'例えば、筆者の場合は以下のようにします。
$ git config --global user.name 'Lium1126'
$ git config --global user.email 'yosi.4sya@gmail.com'topic
--globalオプションを指定することで、あらゆるリポジトリでこの設定が反映されるようにします。
任意のディレクトリに、フォークしたリポジトリをクローンします。
以下のコマンドを実行してください。
$ mkdir github_flow_handson
$ cd github_flow_handson
$ git clone https://github.com/<AさんのGitHubアカウント名>/github-practice.git
Cloning into 'github-practice'...
remote: Enumerating objects: 50, done.
remote: Counting objects: 100% (50/50), done.
remote: Compressing objects: 100% (47/47), done.
remote: Total 50 (delta 21), reused 17 (delta 2), pack-reused 0
Receiving objects: 100% (50/50), 17.81 KiB | 1.48 MiB/s, done.
Resolving deltas: 100% (21/21), done.
$ ls
github-practice
$ cd github-practiceクローンが完了すると、リモートリポジトリにあるファイルが複製され、ローカルリポジトリが作成されます。
note
空のローカルリポジトリを作成したい場合は、
$ git initコマンドを利用します。これを使用した場合、プッシュ時にリモートリポジトリを作成します。
ファイルが正しく複製されたか確認してください。
$ ls
Makefile README.md doc main.cpp search.cpp search.hpp sort.cpp sort.hppクローンしたリポジトリには、既にプログラムを作成してあります。
少し中身を見てみましょう。
main.cpp内のmain関数は以下のようになっています。
#define FIRST_TARGET 38
#define SECOND_TARGET 75
int main(const int argc, const char *argv[])
{
vector<int> data{29, 48, 70, 34, 92, 64, 26, 100, 15, 20, 82, 24, 79, 99, 87, 38, 14, 45, 94, 8};
cout << endl
<< "Before sort" << endl;
cout << "---------------------------------------------------------------" << endl;
printData(data);
cout << endl;
data = github_practice::sort(data);
cout << "After sort" << endl;
cout << "---------------------------------------------------------------" << endl;
printData(data);
cout << endl;
cout << "Search for " << FIRST_TARGET << endl;
cout << "---------------------------------------------------------------" << endl;
cout << FIRST_TARGET << (github_practice::search(data, FIRST_TARGET) ? " is found!" : " is not found!") << endl;
cout << endl;
cout << "Search for " << SECOND_TARGET << endl;
cout << "---------------------------------------------------------------" << endl;
cout << SECOND_TARGET << (github_practice::search(data, SECOND_TARGET) ? " is found!" : " is not found!") << endl;
return 0;
}main.cppでは、配列dataを準備し一度内容を表示、その後ソートして同様に表示しています。その後、「38」と「75」がdataの中に含まれているか探索しています(プログラムが読めなくても、「そういった処理をしているのか」程度の理解で問題ありません)。
ソートしているsort関数はsort.cppに記述されており、バブルソートが実装されています。
std::vector<int> sort(std::vector<int> data)
{
for (int i = 0; i < data.size() - 1; i++)
{
for (int j = data.size() - 1; j > i; j--)
{
if (data[j] < data[j - 1])
{
int tmp = data[j];
data[j] = data[j - 1];
data[j - 1] = tmp;
}
}
}
return data;
}また、指定した値を探索するsearch関数はsearch.cppに実装されており、線形探索で実装されています。
bool search(std::vector<int> data, int target)
{
for (int x : data)
{
if (x == target)
return true;
}
return false;
}プログラムを実行してみましょう。
以下のコマンドを実行し、表示のような出力が得られれば正しくプログラムが動作しています。
$ make
Before sort
---------------------------------------------------------------
29 48 70 34 92 64 26 100 15 20 82 24 79 99 87 38 14 45 94 8
After sort
---------------------------------------------------------------
8 14 15 20 24 26 29 34 38 45 48 64 70 79 82 87 92 94 99 100
Search for 38
---------------------------------------------------------------
38 is found!
Search for 75
---------------------------------------------------------------
75 is not found!ここから、Aさんがこのプログラムに対して改修作業を行うという想定でハンズオンを行います。
GitHub Flowでは、まずmainブランチから作業用ブランチを作成することから改修作業が始まります。
まずは、今どのブランチにいるのか確認しましょう。
$ git branch
* maingit branchは、ローカルリポジトリ内のブランチ一覧と、今いるブランチを表示します。*の付いているブランチが現在いるブランチです。
ブランチを作成するには、以下のコマンドを使用します。このコマンドを実行すると、今いるブランチから分岐した新しいブランチが作成されます。
$ git branch <新しいブランチ名>ここでは、以下のようなブランチ名で新たなブランチを作成しましょう。
$ git branch fix-bubble-sorttopic
GitHub Flow以外のワークフローには、ブランチ名を規定しているものもあります。 詳しくは、Git FlowやIssueドリブン開発について学習してください。
ブランチが作成されたことを確認します。
$ git branch
fix-bubble-sort
* mainfix-bubble-sortブランチが作成されたことは確認できましたが、ユーザがいるブランチはmainブランチのままです。ブランチの切り替えは以下のコマンドを利用します。
$ git checkout <ブランチ名>作業を行うのはfix-bubble-sortブランチですから、以下のコマンドを実行してブランチを切り替えてください。
$ git checkout fix-bubble-sort
Switched to branch 'fix-bubble-sort'
$ git branch
* fix-bubble-sort
maingit branchコマンドの表示にて、fix-bubble-sortブランチに*が付されていることが確認できたら成功です。
作業用ブランチを作成することができたため、ここからAさんにプログラムを改修してもらいます。しかし、プログラミングは本教材の本質ではないため、具体的な編集作業はコピー&ペーストのみとします。
ソートアルゴリズム集にいくつかのソートアルゴリズムの例を示しています。
エディタを使って、sort.cppのsort関数を、ソートアルゴリズム集のバケットソートに書き換えてください(コピー&上書きペーストで構いません)。
プログラムの変更ができたら、正しく動作することを確認してください。
$ make
Before sort
---------------------------------------------------------------
29 48 70 34 92 64 26 100 15 20 82 24 79 99 87 38 14 45 94 8
After sort
---------------------------------------------------------------
8 14 15 20 24 26 29 34 38 45 48 64 70 79 82 87 92 94 99 100
Search for 38
---------------------------------------------------------------
38 is found!
Search for 75
---------------------------------------------------------------
75 is not found!topic
このように、関数の外部仕様と内部仕様を分離することで、変更の影響が他に及ばないようにすることがチーム開発では重要です。今回の例では、main.cppに影響を及ぼさず、sort.cppを改修することができました。
変更を加えたファイルの状態を確認してみましょう。
$ git status
On branch fix-bubble-sort
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: sort.cpp
no changes added to commit (use "git add" and/or "git commit -a")すると、sort.cppがModified(編集済み)となっていることがわかります。「Changes not staged for commit(変更はコミットのためのステージングがなされていません)」という表記から分かるように、sort.cppはStagedではありません。(赤い字であることからも、Stagedでないことを判断できます)
変更をコミットするためには、ステージングをしてインデックスに登録しなければなりませんでした。そこで、以下のコマンドでsort.cppをステージングします。
$ git add sort.cppここでは何も表示されませんが、再度確認するとsort.cppがStagedになっていることが確認できます。
$ git status
On branch fix-bubble-sort
Changes to be committed:
(use "git restore --staged <file>..." to unstage)
modified: sort.cpptopic
git addコマンドは、git add -Aとすると「UntrackやUnmodifiedの全てのファイルを一括でステージングする」ということができます。 しかし、不要なファイルまでステージングしてしまうといったリスクがあるため、乱用に注意してください。
ここまでで、sort.cppがインデックスに登録され、コミットの準備が整いました。コミットしてみましょう。
$ git commit上記コマンドを実行すると、エディタが起動してコミットメッセージの入力が求められます。
<任意のコミットメッセージ>
# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
#
# Date: Thu Nov 18 17:30:35 2021 +0900
#
# On branch fix-bubble-sort
#
# Initial commit
#
# Changes to be committed:
# new file: sort.cpp
#コミットメッセージを入力し、ファイルを保存すればコミット完了です。
note
コミットメッセージとは、そのコミットがどのような変更を加えたのか、なぜその変更を加えたのかを表すコメントです。 変更履歴を辿ったり、以前のバージョンに戻したりするときの目印として活用します。
topic
git commitコマンドは、-mオプションを利用することでエディタを起動せずにコミットすることができます。書式は以下の通りです。$ git commit -m "<任意のコミットメッセージ>"
コミットの履歴が残されていることを確認します。
$ git log
commit <コミットID> (HEAD -> fix-bubble-sort)
Author: <AさんのGitHubアカウント名> <<Aさんのメールアドレス>>
Date: Thu Nov 18 17:30:35 2021 +0900
<コミットメッセージ>git showコマンドを実行すると、直前の変更の差分を確認することができます。+の部分が追加された箇所、-の部分が削除された箇所です。
$ git showローカルリポジトリにコミットできたので、リモートリポジトリに変更を反映しましょう。
プッシュと同時に現在のブランチをリモートリポジトリにも作成します。
$ git push --set-upstream origin <ブランチ名>変更を加えたブランチはfix-bubble-sortであったので、以下のコマンドを実行することになります。
$ git push --set-upstream origin fix-bubble-sortnote
すでにリモートリポジトリにブランチが作成されている場合は
$ git pushを利用することができます。
https://github.com/<AさんのGitHubアカウント名>/github-practice/にアクセスし、下図に示すような「Compare & pull request」をクリックします。
上記のような表記が見られない場合は、下図の「Pull Requests」タブから「New pull request」をクリックしてください。
差分比較画面で、マージ先(「←」の左側)を、必ず「Lium1126/github-practice・main」から「<AさんのGitHubアカウント名>/github-practice・main」に変更してください。
その後、マージ元(「←」の右側)が「<AさんのGitHubアカウント名>/github-practice・fix-bubble-sort」となっていることを確認してください。上記2項目を確認したら、「Create pull request」をクリックします。
下図のようにコメントを入力し、プルリクエストを作成します。
リモートリポジトリのfix-bubble-sortブランチからプルして、動作確認を行います。
リモートリポジトリにfix-bubble-sortブランチが作られたという変更をローカルブランチに反映するため、フェッチします。
$ git fetchリモートリポジトリのブランチから、ローカルブランチを作成するには以下のコマンドを実行します。
$ git branch <ローカルブランチ名> <リモートブランチ名>よって、以下のコマンドを実行します。
$ git branch fix-bubble-sort origin/fix-bubble-sortブランチを移動し、動作確認を行います。
$ git branch
fix-bubble-sort
* main
$ git checkout fix-bubble-sort
$ git branch
* fix-bubble-sort
main
$ make
Before sort
---------------------------------------------------------------
29 48 70 34 92 64 26 100 15 20 82 24 79 99 87 38 14 45 94 8
After sort
---------------------------------------------------------------
8 14 15 20 24 26 29 34 38 45 48 64 70 79 82 87 92 94 99 100
Search for 38
---------------------------------------------------------------
38 is found!
Search for 75
---------------------------------------------------------------
75 is not found!動作確認ができたら、コードレビューをしましょう。sort.cppのsort関数が正しく変更されているか確認してください。
確認ができたら、下図のようなコメントをつけて承認の意思を表しましょう。
topic
LGTMとは、「Looks Good To Mee(私目線OKよ)」という意味です。プルリクエスト承認時の慣習となっている掛け声です。 しかし、「とりあえずこれ言っておけばいいだろ」という思考停止に陥りやすいとして、この言葉を問題視する声も多くあります。
Bさんからの承認を得ることができたら、マージすることができます。
下図のボタンをクリックし、mainブランチにマージしましょう。
ここまでで、リモートリポジトリのmainブランチに変更を加えることができました。
最新のmainリモートブランチを追跡するために、ローカルブランチにプルしましょう。
まず、mainブランチにいない場合は、mainブランチに切り替えます。
$ git branch
* fix-bubble-sort
main
$ git checkout main
$ git branch
fix-bubble-sort
* mainnote
不要になったローカルブランチを削除しても構いません。ブランチを削除するコマンドは以下の通りです。
$ git branch -d <ブランチ名>よって、
fix-bubble-sortブランチを削除する場合は$ git branch -d fix-bubble-sortを実行します。
mainブランチに切り替えたら、リモートリポジトリの最新状態をプルしましょう。
$ git pullsort.cppのsort関数が、バブルソートから以下のように変更されていれば、Aさんの編集が正しく共有されています。
std::vector<int> sort(std::vector<int> data)
{
// calculate bucket size
int bucket_size = data[0];
for (int i = 1; i < data.size(); i++)
{
if (data[i] > bucket_size)
{
bucket_size = data[i];
}
}
bucket_size += 1;
// create empty buckets
std::vector<int> bucket[bucket_size];
// put data elements into buckets depending on the value
for (int i = 0; i < data.size(); i++)
{
bucket[data[i]].push_back(data[i]);
}
// concatenate all buckets into data
int id = 0;
for (int i = 0; i < bucket_size; i++)
{
for (int j = 0; j < bucket[i].size(); j++)
{
data[id++] = bucket[i][j];
}
}
return data;
}続いて、Bさんも同様に改修作業を行います。
まずは作業用ブランチを作成しましょう。本節では、作業用ブランチをfix-bucket-sortとします。
$ git branch
* main
$ git branch fix-bucket-sort
$ git branch
fix-bucket-sort
* main
$ git checkout fix-bucket-sort
Switched to branch 'fix-bucket-sort'エディタを使って、sort.cppのsort関数を、ソートアルゴリズム集のシェルソートにコピー&上書きペーストしてください。
ソートアルゴリズムを変更したら、動作確認してください。
$ make
Before sort
---------------------------------------------------------------
29 48 70 34 92 64 26 100 15 20 82 24 79 99 87 38 14 45 94 8
After sort
---------------------------------------------------------------
8 14 15 20 24 26 29 34 38 45 48 64 70 79 82 87 92 94 99 100
Search for 38
---------------------------------------------------------------
38 is found!
Search for 75
---------------------------------------------------------------
75 is not found!バケットソートからシェルソートへの変更をローカルリポジトリに登録するため、ステージングします。
まずはsort.cppの状態を確認します。
$ git status
On branch fix-bucket-sort
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: sort.cpp
no changes added to commit (use "git add" and/or "git commit -a")sort.cppがModifiedであることが確認できたら、ステージングを行います。
$ git add sort.cpp
$ git status
On branch fix-bucket-sort
Changes to be committed:
(use "git restore --staged <file>..." to unstage)
modified: sort.cppsort.cppがStagedになったら、コミットします。
$ git commit -m "<コミットメッセージ>"
[fix-bucket-sort fa826f1] <コミットメッセージ>
1 file changed, 10 insertions(+), 6 deletions(-)
コミットの履歴が残されていることを確認します。
$ git log
commit <コミットID> (HEAD -> fix-bucket-sort)
Author: <BさんのGitHubアカウント名> <<Bさんのメールアドレス>>
Date: Thu Nov 18 17:30:35 2021 +0900
<コミットメッセージ>直近のコミットの変更点を確認します。
$ git showsort関数が書き換えられていることが確認できましたか?
正しく編集・コミットできたら、リモートリポジトリに対してプッシュしましょう。
fix-bucket-sortブランチをリモートリポジトリにも作成し、そこにプッシュします。
$ git push --set-upstream origin fix-bucket-sorthttps://github.com/<AさんのGitHubアカウント名>/github-practice/にアクセスし、プルリクエストを作成してください。
この際も、マージ先(「←」の左側)を「Lium1126/github-practice・main」から「<AさんのGitHubアカウント名>/github-practice・main」に変更することを忘れないでください。
また、マージ元(「←」の右側)が「<AさんのGitHubアカウント名>/github-practice・fix-bucket-sort」となっていることを確認してください。
Bさんがプッシュした変更点をプルし、動作確認を行いましょう。
$ git fetch
$ git branch fix-bucket-sort origin/fix-bucket-sort
$ git branch
fix-bucket-sort
* main
$ git checkout fix-bucket-sort
$ make
Before sort
---------------------------------------------------------------
29 48 70 34 92 64 26 100 15 20 82 24 79 99 87 38 14 45 94 8
After sort
---------------------------------------------------------------
8 14 15 20 24 26 29 34 38 45 48 64 70 79 82 87 92 94 99 100
Search for 38
---------------------------------------------------------------
38 is found!
Search for 75
---------------------------------------------------------------
75 is not found!動作確認が完了したら、sort.cppのsort関数が正しく変更されているか確認してください。
確認できたら、プルリクエストにレビューコメントをつけましょう。
Aさんから承認されたら、マージしましょう。
両者プルして、ローカルmainブランチを最新のリモートmainブランチで更新します。
$ git branch
* fix-bucket-sort
main
$ git checkout main
Switched to branch 'main'
$ git branch
fix-bucket-sort
* main
$ git pullsort.cppのsort関数が以下のように変更されているか確認してください。
std::vector<int> sort(std::vector<int> data)
{
for (int h = data.size() / 2; h > 0; h /= 2)
{
for (int i = h; i < data.size(); i += 1)
{
int k = data[i];
int j;
for (j = i; j >= h && data[j - h] > k; j -= h)
{
data[j] = data[j - h];
}
data[j] = k;
}
}
return data;
}チーム開発では、コンフリクト(競合)という事象が起こることがあります。コンフリクトとは、複数人の変更箇所が重複してしまい、マージすると誰かしらの変更が失われてしまうという状態です。
コンフリクトが起きた場合、人力で対処しなくてはなりません。以下、実際に解消する活動を行います。
Aさんはfix-search-aブランチ、Bさんはfix-search-bブランチを作成してください。
$ git branch fix-search-a
or
$ git branch fix-search-bまずはAさんにプログラムの変更を行ってもらいます。その後、Bさんがその変更点と重複するような箇所を編集し、コンフリクトを発生させます。
Aさんは作成した作業用ブランチに移動してください。
$ git checkout fix-search-aその後、search.cppのsearch関数を、探索アルゴリズム集の番兵に変更してください。
変更作業が完了したら、動作確認を行ってください。
$ make正しく動作したら、状態を確認し、変更をリモートリポジトリに反映してください。
$ git status
$ git add search.cpp
$ git commit -m "<コミットメッセージ>"
$ git push --set-upstream origin fix-search-aプッシュしたら、https://github.com/<AさんのGitHubアカウント名>/github-practiceにアクセスし、下図の「Compare & pull request」をクリックしてプルリクエストを作成します。図のような表示が無い場合、「Pull request」タブの「New pull request」から作成します。
この際も、差分比較画面で、マージ先(「←」の左側)を、必ず「Lium1126/github-practice・main」から「<AさんのGitHubアカウント名>/github-practice・main」に変更してください。
その後、マージ元(「←」の右側)が「<AさんのGitHubアカウント名>/github-practice・fix-search-a」となっていることを確認してください。上記2項目を確認したら、「Create pull request」をクリックします。
コメントを入力し、プルリクエストを作成します。
本来なら、マージするためにはBさんがレビューを行いますが、ここでは「Bさんの知らないところでレビューが行われ、認知外でマージされてしまっていた」という想定とします。Aさんは、先ほど作成したプルリクエストをマージしてください。
現状、前節のAさんのマージによって、search関数が番兵に変更されています。Bさんの作業用ブランチはその変更以前に作成されているため、search関数が変更されていることを追跡できていません。その状態で、Bさんもsearch関数を変更し、コンフリクトを発生させてみましょう。
まずは、ブランチを移動してください。
$ git checkout fix-search-bBさんは、search.cppのsearch関数を、探索アルゴリズム集の二分探索に変更してください。
変更できたら、Bさんのローカル環境で動作確認しましょう。
$ make正しく動作することが確認できたら、状態を確認し、ローカルリポジトリにコミットしましょう。
$ git status
$ git add search.cpp
$ git commit -m "<コミットメッセージ>"変更をリモートリポジトリにプッシュしてください。
$ git push --set-upstream origin fix-search-bここまでで、探索アルゴリズムを二分探索に変更したという更新が、リモートリポジトリのfix-search-bブランチに反映されました。これまで通り、プルリクエストを作成しましょう。https://<AさんのGitHubアカウント名>/github-practice/にアクセスし、下図の「Compare & pull request」をクリックします。表示が無い場合は、「Pull request」タブから「New pull request」をクリックします。
これまでと同様に、差分比較画面で、マージ先(「←」の左側)を、必ず「Lium1126/github-practice・main」から「<AさんのGitHubアカウント名>/github-practice・main」に変更してください。
その後、マージ元(「←」の右側)が「<AさんのGitHubアカウント名>/github-practice・fix-search-b」となっていることを確認してください。上記2項目を確認したら、「Create pull request」をクリックします。
すると、下図のように「Can't automatically merge.」と表示されます。コンフリクトが発生しているマージを行おうとする際に見られる表示です。今回は、このままプルリクエストを作成します。
プルリクエストが作成されると下図に示すように、コンフリクトが発生している旨のメッセージが確認できます。
コンフリクトを解消するにあたって、2種類の方法を示します。
mainブランチに現在のリモートブランチの状態をプルします。プルした後、作業用ブランチに切り替えます。
$ git checkout main
$ git pull origin main
$ git checkout fix-search-b以下のコマンドで、最新版(Aさんが施した番兵への変更が反映された状態)のmainブランチへfix-search-bブランチをマージしようと試みてください。すると、「コンフリクトが発生していて自動的にマージすることができない」と表示されます。
$ git merge main
Auto-merging search.cpp
CONFLICT (content): Merge conflict in search.cpp
Automatic merge failed; fix conflicts and then commit the result.現在の状態を確認してみましょう。
$ git status
On branch fix-search-b
Your branch is up to date with 'origin/fix-search-b'.
You have unmerged paths.
(fix conflicts and run "git commit")
(use "git merge --abort" to abort the merge)
Unmerged paths:
(use "git add <file>..." to mark resolution)
both modified: search.cpp
no changes added to commit (use "git add" and/or "git commit -a")「コンフリクトを解消してコミットし直してください」という旨が表示されています。
上記の表示で、both modifiedと表示されているファイルが、コンフリクトが発生しているファイルです。
topic
本教材では発生しませんが、状況によっては
both addedと表示される場合などがあります。
この表示からコンフリクトが発生しているファイルがsearch.cppであることがわかるので、実際に開いて確認してみましょう。
すると、コンフリクトしている箇所が下図のように示されます。HEADと示されている部分は現在のmainブランチ、下の囲まれている部分がfix-search-bブランチでBさんが施した変更です。
上図のソースコードを、下図のように編集してください(コンフリクトを解消してください)。具体的には、<<<<<<<と>>>>>>>で囲まれた範囲を編集します。また、赤く囲んだ部分の変更(return false;)も忘れないでください。
コンフリクト解消のために加えた変更でプログラムの正当性が損なわれていないか、動作確認をしてください。
$ makeコンフリクトを解消したファイルをステージングしましょう。この操作で、コンフリクトを解消したことがGitに知らされます。
$ git add search.cpp状態を確認してみましょう。
$ git status
On branch fix-search-b
Your branch is up to date with 'origin/fix-search-b'.
All conflicts fixed but you are still merging.
(use "git commit" to conclude merge)コンフリクトが解消されたという表示がなされています。コミットおよびプッシュしてください。
$ git commit -m "<コミットメッセージ>"
$ git pushここまでで、ローカルリポジトリのfix-search-bブランチでコンフリクトを解消し、リモートリポジトリのfix-search-bブランチにプッシュできました。
プルリクエストのWebページをリロードすると、「This branch has conflicts that must be resolved.」という表示から「This branch has no conflicts with the base branch」という表示へ変化します。これで、コンフリクトを解消し、mainブランチへマージする準備が整いました。
「This branch has conflicts that must be resolved.」という表示の横にある「Resolve conflicts」ボタンをクリックします。すると、コンフリクトが発生しているファイルをWebページ上で編集することができます。上のfix-search-bで囲まれた範囲はfix-search-bブランチでの変更、下の部分は競合しているmainブランチの箇所です。
note
画像ではベースブランチが
masterと表示されていますが、mainと読み替えてください。
コンフリクトを解消するため、以下のコードになるように<<<<<<<と>>>>>>>で囲まれた範囲、およびreturn文を編集しましょう。
bool search(std::vector<int> data, int target)
{
int left = 0, right = data.size(), mid;
while (left < right)
{
mid = (left + right) / 2;
if (data[mid] == target)
return true;
else if (target < data[mid])
right = mid;
else if (data[mid] < target)
left = mid + 1;
}
return false;
}編集後は下図のようになっているかと思います。その後、右上の「Mark as resoved」をクリックしてください。
最後に、「Commit changes」をクリックしてください。プルリクエストの画面で「This branch has no conflicts with the base branch」という表記があればコンフリクト解消の成功です。
note
Web上でコンフリクトを解消する方法はCLI+エディタより単純でわかりやすいですが、以下のような不都合があります。
- コンフリクト解消後の動作確認をするためには、どちらにせよ変更をプルしなければなりません
- 正しく編集されなかった場合に、間違ったコードのまま「Mark as resoved」されたプルリクエストが残ってしまいます
コンフリクトが解消されたプルリクエストのレビューをしてください。
$ git fetch
$ git branch fix-search-b origin/fix-search-b
$ git branch
fix-search-b
* main
$ git checkout fix-search-b
$ make
Before sort
---------------------------------------------------------------
29 48 70 34 92 64 26 100 15 20 82 24 79 99 87 38 14 45 94 8
After sort
---------------------------------------------------------------
8 14 15 20 24 26 29 34 38 45 48 64 70 79 82 87 92 94 99 100
Search for 38
---------------------------------------------------------------
38 is found!
Search for 75
---------------------------------------------------------------
75 is not found!動作確認後、search.cppのsearch関数が、下に示すコードのようになっているかも確認してください。
bool search(std::vector<int> data, int target)
{
int left = 0, right = data.size(), mid;
while (left < right)
{
mid = (left + right) / 2;
if (data[mid] == target)
return true;
else if (target < data[mid])
right = mid;
else if (data[mid] < target)
left = mid + 1;
}
return false;
}どちらも正しいと確認できたら、プルリクエストに承認コメントを送りましょう。
Aさんから承認されたら、プルリクエストをマージしてください。
note
ここまでで体感したとおり、コンフリクトは手動で解消せねばならないことから、生産性が著しく低下します。極力コンフリクトを発生させないよう、適切に分担するなどの工夫を施しましょう。
以上で、ハンズオンでのGitHub Flowの練習は終了です。
topic
動作確認の中で生成されたバイナリファイルが邪魔だと思う方は、
make cleanを実行してください。
今回取り上げたGitHub Flowの他にも、ワークフローはたくさんあります。また、これらのワークフローを基にした開発フローが数多く存在します。
色々な方法を身につけ、効率良いチーム開発ライフを送れることを願っております。





















