はじめに
今回6週間でチームビルディングからプロダクト公開までやるプロジェクトに参加させて頂きました。
プロジェクト全体に関しては以下のリンクでまとめておりますのでそちらを御覧ください。
私についてはその記事や過去記事等ご覧になっていただければと思いますが、端的に言うと未経験でエンジニアを目指して就職活動をしているものです。
この記事ではGit(GitHub)について初めてチームで本格的に使うことになったのでそれについてまとめたものになります。
Git(GitHub)に関しては以前こういうもの を作った際にリポジトリを作ってpushをしたことはありますが、今回本格的に使うにあたって慣れるのに苦労したので
そういうところを含めて少し、アウトプットをしておこうかなと思います。
そもそもGitHubとは?
難しいことは抜きにして簡単にいうとアプリやサービスのソースコードをネット上に公開できるサービスです。
例えばPythonのフレームワークであるDjangoも下の画像のようにまるまる公開されています。
開発者の人たちはこれをそのまま自分のローカル環境に引っ張ってきたりなんだりして開発を行ったり、そのアプリの環境を再現したりするわけですね。
そして、それを応用するとチームでの開発をより円滑にできるよというのがこのサービスの肝であるというわけです。
じゃあ具体的にどうやってチームでGitHubを使っていくの?
リポジトリを作る
まず、代表者(多分、設計を担当されている方なはす。今回のプロジェクトではそうでした)がマスターとなるディレクトリをGitHubに上げます。
すると先程の画像のような状態になります。
画像を見るとBranch:master
というところが見えると思います、ブランチとは日本語でいうと枝ですがまさにGitHubでの開発は枝分かれをしながら進めていくというイメージを持つとわかりやすいです。
Branch:master
は枝分かれ元かつオリジナルだと思ってください。
ちなみにこうやってGitHubにソースコードを公開している状態のものをリポジトリと呼称したりします。
リポジトリをクローンし、機能ごとに作業ブランチを切る
さて、Branch:master
にあたるブランチ(別にmasterに拘る必要はなくdevelopなどとつけてもいい)ができたら、コードを書いて機能を実装していく人達(語弊があるかもしれませんが以降PGと略します)は
このブランチを枝分かれさせて、作業をしていくことになります。
枝分かれのさせ方には色々ありますが、今回のプロジェクトでは認証なら認証で1ブランチと担当する機能ごとにその都度枝分かれをさせていきました。1機能1ブランチというわけです。
ちなみに枝分かれさせる行為のことは「ブランチを切る」と言われるみたいなので、以降そう呼ぶことにしますね。
ではブランチを切るというのはどういうことなのかというと
- 作業ディレクトリを用意して
Branch:master
にあたるブランチをクローンする。 - 作業ブランチを作成する
という手順の作業のことを指します。
クローンをするというのは簡単にいうとコピペをするということですね、やり方は後ほど。
そうなるとまずローカルにBranch:master
にあたるブランチのコピーができて、さらにそのコピーとして作業ブランチができるということになります。
ところで、ここでまず私と同じ初学者の方にここで聞きたいのですがこの時点でブランチの数はいくつだと思いますか?
おそらく2個または3個と答える方が多いと思います、私もプロジェクトの途中までそう思っていました。
ところがこれが違うわけで、ここがGitHubというかGitを理解する上での一つの壁になるのだと思いました。
では何を言っているのだ? ということになりますが、結論からいうとこの時点でのブランチの数は4つになります。
上位関係にあたる順から挙げていくと
-
Branch:master
にあたるブランチ(GitHub上のブランチ、リモートブランチと呼ばれます) - リモートブランチを追跡するブランチ(masterブランチに対してならorigin/masterなどと呼ばれたりします)
- クローンしてきた
Branch:master
にあたるブランチ - 作業ブランチ
ということになります。
2のブランチがいきなり出てきましたのでこれがどういうものなのかというとリモートブランチで変更があった場合にまず、その変更を直接ローカルに適用する前にこのブランチで預かるということができるわけです。
リモートブランチはチームで開発していると他の人の作業結果や設計の修正やバグや不具合の対応などで日に日に変わっていきます。
なので、随時その変更をローカルのブランチ(この例だと3のブランチですね)に反映していかないといけません。
さりとて、いきなりリモートブランチの内容をローカルに反映させるのは意外に危険です。なぜならそれを実行した瞬間ローカルで作業していた内容が吹き飛ぶ可能性がままあるからです。
Branch:master
にあたるブランチは元となるブランチであり、後々説明しますが設計などの責任者や管理者にあたる人が管理するのでPGが直接どうこうするということはあまりないため直接下ろしてきても問題ないですが、これが例えば後ほどまた触れることになるのですが、作業ブランチをpush
というコマンドでGitHubの方に上げて、作業ブランチのリモートブランチを作るといったことをする過程があるのですがその場合
- 作業ブランチのリモートブランチ
- 1のリモートブランチを追跡するブランチ
- ローカルの
Branch:master
にあたるブランチ - ローカルの作業ブランチ
という構成になるのはここまでやるとわかると思います。
厳密には3の部分では先述のBranch:master
にあたるブランチのリモートや追跡ブランチがあるのですが割愛します。
で、この状態でリモートブランチを一旦またローカルにおろしてくださいなんてことになったとき無遠慮にやってしまうとローカルの作業環境をふっとばしてしまう場合があります。
詳しくは以後のGitのコマンドについて
のところで触れますが、私はこのあたりの理解が曖昧だったために都合3回位環境をふっとばして大変な思いをしました。
この事故は初学者故の事故とも言えますが、それ以外にもトラブルが起こる可能性があるのでそれを回避するためにも2のようなブランチがあるわけです。
ちなみに、作業ブランチにBranch:master
にあたるブランチの変更を反映させるといったこともできますがそれは後ほど触れることにします。
作業ブランチをリポジトリにあげる
さて、作業ブランチを作って作業をしていると自分の担当する機能を書いて完全にできあがった! またはある程度は出来上がったけど進捗上聞きたいところや指示、指摘をもらいたいところがある……
といった場面が訪れると思います。
そういったときにGitHubにはPull request
という機能があります。
どういうものかというと、ローカルブランチをリポジトリに上げるとあるブランチとそのブランチとを比較して、その差分を表示してくれます。
その差分をもとにコメントを書いてBranch:master
にあたるブランチの管理者に指示を仰ぐことができるができるのがPull request
という機能です。
現場によりけりにはなるという話は聞きますが真っ当な現場、特に私のような経験の浅い人間がチームにいる場合はメンターと言われる謂わば教育担当だったりする人にコードレビューを受けることになります。
コードレビューというのは自分の書いたコードを他者に評価してもらうことですね。
個々の部分は処理をわけて~とかこの部分のコードの書き方は少し汚いからもう少しキレイに~とかその他不備があれば都度指摘を受けて改善をしていくわけです。
なので作業ブランチをリポジトリにあげるというこの工程は1度で完結するものではなく、何度か行われることになることもあります。
そのレビューの工程をチャット形式で残していくのもPull request
という機能の1つです。
作業ブランチをBranch:master
にあたるブランチに合流させる
レビューを繰り返し、レビュアーの人によしじゃあこの機能に関してはこのコードでOKだよと了承得たとします。
そうなるとこの作業ブランチでの作業は完了したことになるので、この作業ブランチで変更したファイルをBranch:master
にあたるブランチに適用させます。
枝分かれしていたものを取り込んで1本にするイメージがわかりやすいですね。
このように枝分かれさせたブランチの変更内容をオリジナルのブランチに取り込んでブランチを統合することを「ブランチをマージする」と呼ぶそうです。
この作業はチームで行う場合はレビュアーもしくは、設計の担当者が行います。
先述の通りブランチの変更を安易に適用させるのはトラブルの元になるので、チームでやる場合はオリジナルのブランチにマージできるのはこのように権限がある人のみだったり、
権限を持つ人に許可をもらった場合のみとなっているみたいです。
以上おおまかにはなりますが、この工程を機能の数など必要な回数行うことでアプリだったりシステムだったりが出来上がっていくというわけですね。
Gitの操作方法(コマンド)
さて、ここまででなんとなくチームでのGitを使った開発について流れは理解できたと思いますのでここからは操作方法を見ていきましょう。
実は今回このプロジェクトに参加してVScodeを使った環境構築を教えて頂いて、そのVScodeの拡張機能を使ったほうがわかりやすかったのですが正規のやり方をしっかり覚えることは大切なので
ここではターミナルでコマンドを打つやり方を紹介します。
なお、ターミナルでGitのコマンドを実行するにはGitをインストールしている必要があります。
やり方などは割愛しますので、インストールされていない方はgit for windows
などとググってインストールしてみてください。
あとは当然ですが、ここからのコマンドはGitHubでアカウントを作っていないとダメなので持ってない方は登録しておきましょう。
リポジトリをfork(フォーク)する
厳密にはコマンドではないのですがリポジトリをクローンではなくフォークするとという場合があります。
クローンが先述の通り、リポジトリをローカル環境に複製するという処理だったのに対して、フォークはリポジトリを自分のリモートリポジトリとして複製するという処理になります。
私も最初フォークをして作業をしようとしたのですが、メンターの方から
フォークはOSS(オープンソースソフトウェア)などに対して、追加機能の実装やバグの改修など、オリジナルのリポジトリへ貢献することが前提で行うもの
といったようなご指摘があってクローンでやり直したということがありました。
フォークするとオリジナルのリポジトリ管理者には通知が行くようになっているみたいです。
なので1から自分たちで開発というときにはクローン、既存のOSSなどを改修するといったときにはフォークと使い分けが必要なので一応覚えておきましょう。
ちなみにフォークするには下記の画像のForkの部分をクリックするだけでできます。
リポジトリをclone(クローン)する
では、改めてクローンについてです。
実はクローン自体は下記の画像のclone or download
の部分を押すとZipで落とせたりします。
ただ、それでは意味がないので以下の工程を覚えましょう。
- 作業用のディレクトリを用意する
- 1にリポジトリをクローンする
たった2つです、簡単ですね。
Gitを導入したけどGitHubのアカウントの情報を入れていないとかだと2のあとにユーザ名(メールアドレス)・パスワードの入力を求められる場合があります。
コマンドは2で行う。
# cloneの例(Djangoのリポジトリをクローンする場合)
git clone https://github.com/django/django.git
という形を覚えておきましょう。
git clone URL……
という形ですね。
URLの部分はclone or download
のボタンを押すと画像のように出てきますのでそれをコピペです。
最初のクローンの前には必ずgit init
というリポジトリを新たに作成・または既存のリポジトリの場合は再初期化するコマンドを打つことをおすすめします。
これをやっておかないと、例えば複数のリポジトリを管理していた場合、後ほど作業ブランチをリポジトリにあげるときに予期せぬものや別のリポジトリにまた別のリポジトリの作業をあげてしまうなど
トラブルの原因になるからです。
私は2回ほどやらかしてます。みなさんはやらないようにしましょう。
作業ブランチを切る
次はブランチを切っていきます。
クローンしたリポジトリのルートディレクトリにディレクトリを合わせたあと
# 現在のブランチを確認
git branch --contains=HEAD
# ブランチの作成
git branch feature/~
# ブランチの移動
git checkout feature/~
## 作成と移動を同時に行う場合
git checkout -b feature/~
とコマンドを打ってください。
feature/~
の部分はブランチ名を決める部分なのですが、
通例としてどの機能の作業ブランチなのかというのを表すために、feature/機能名
とかfeature/設計書で定義してある機能番号
などと定義するみたいです。
ブランチ名自体は自由に決めることができます。
ファイルの編集や追加などローカルでの作業内容をローカルリポジトリに反映させる
さて、クローンして作業ブランチを切りそこで実際にガリガリとコードを書いてCtrl+S
などで保存をしたとします。
しかし、このままでは厳密にはローカルのリポジトリ(feature/~ブランチ)には変更や追加は反映されていません。
そのために以下のコマンドを打ちます。
ちなみにローカルリポジトリに反映するべき変更や追加を行ったファイルを指定することをステージングするなんて言ったりするそうです。
# 現在のディレクトリ以下すべてをステージングする
git add .
# 特定のファイルをステージングする(例 ルート/test/test.py)
git add ./test/test.py
# 特定のディレクトリをステージングする(例 ルート/testにおいてtestフォルダすべてをステージングしたい)
git add ./test
ちなみに初めてのステージングの際はgit add .
で実行すると思います。
しかし、中には必ずadd
を適用したくないファイルがあるんだけども……という場合があるかもしれません。
Gitではクローンした段階でクローンしたファイルは追跡といって常に差分を確認されている状態になります。
この状態で今回の記事の工程を完了してしまうと、当然add
の対象外としたいファイルを含んだ状態で作業が完了してしまいます。
なので、そうしたことを防ぐために.gitignore
というファイルをルートディレクトリに作成し、そこで追跡の対象から外したいディレクトリやファイルを定義しておきましょう。
詳細は私が解説するよりも以下の参考記事を参照される方がわかりやすいので紹介させていただきます。
ちなみにチームで開発している場合、責任者の許可なしに.gitignore
を変更しないのがベターです。私はやってしまって余計なタスクを増やしてしまいました……
[Git] .gitignoreの仕様詳解
.gitignoreについて
ステージングで変更部分を指定したら、今度はそれをコミットしてローカルリポジトリに反映します。
コマンドは以下の通りです。
git commit -m "コメント"
-m
オプションの後にはこのコミットがどういうコミットなのかということを書くことができます。
例えば最初のコミットならばfirst commit
、レビューを受けてそれを修正したものをコミットするならレビュー1を受けて修正
といった感じです。
これでローカルリポジトリに変更が反映されて、あとはもう1つ工程を踏むとリモートリポジトリに作業ブランチをあげることができます。
しかし、ここで例えば間違えてadd
してたとかcommit
したら想定外のファイルがcommit
されていた……といったように、
ここまでのadd
やcommit
を取り消したい……といった状況が出てくるかもしれません。
その場合はgit reset
コマンドを使うのですがオプションによって挙動が違いますので注意してください。
今回はコミットした直後にadd
やcommit
を取り消したくなったということを例に説明します。
# commitを取り消したい
git reset --soft HEAD
# commitだけでなくaddも取り消したい
git reset HEAD
# add`やcommitどころかいっそのことファイルの変更や追加までなかったことにしたい
git reset --hard HEAD
--soft
や--hard
がオプションでどこまで取り消したいのかの指定、HEAD
の部分がどのコミットを取り消したいのかという指定になります。
つまり、オプションをつけなかった場合はそのコミット中のadd
及びcommit
までなかったことになるということですね。
ちなみにHEAD^
とすると一つ前のコミットを指定することができます、またそれらの代わりにコミットにはIDが振られるのでそのIDを入れると特定のコミットを指定できるみたいです。
作業ブランチ(ローカルリポジトリ)をリモートリポジトリへあげる
さていよいよ作業ブランチをリモートリポジトリにあげていきます。
まず、その前にgit status
コマンドで現在のリポジトリの状態を確認しましょう。
このコマンドを打つと例えばaddはしてるけど、commitはまだだよ?
といったように現在そのリポジトリがどういう状態なのか教えてくれます。
問題なさそうなら次の工程に進みます。
まずはBranch:master
にあたるブランチ、つまりオリジナルのリモートリポジトリの最新版を作業ブランチに反映させます。
チームで開発していると他の人の作業ブランチがマージされていたり、仕様の変更や細かい修正などなどBranch:master
にあたるブランチは日々更新されていきます。
この更新を無視してしまうとレビューのときに不都合がおきたり、いざ自分の作業ブランチのレビューでOKをもらいマージをしてもらう……というところで後述の競合が起きてしまうなんていうトラブルの原因になります。
なので、できるだけこの段階でBranch:master
にあたるブランチの更新をこちらに取り込んでおこうということになるわけです。
で、リポジトリをクローンし、機能ごとに作業ブランチを切る
の項で説明したとおり、ブランチは階層になっていて段階的に更新を受け取ったり、渡せたりできます。
渡し方は今やっているところですが、一旦ここで受け取り方を見てみましょう。
# `Branch:master`にあたるブランチ(リモートリポジトリのmasterブランチ)の変更をリモートリポジトリ追跡ブランチ(この場合はorigin/masterブランチ)に受け取る
git fetch origin master
# リモートリポジトリ追跡ブランチ(この場合はorigin/masterブランチ)に受け取った変更を現在のブランチに反映する
git merge origin/master
# 上記の2つの過程を同時に行う
git pull origin master
ちなみにmerge
コマンドはorigin/master
ブランチとローカルリポジトリとで競合があった場合はエラーが発生して実行できません。
ただし、pull
でやってしまうとmerge
まで一気にやってしまうためかたまに実行されてしまった先でエラーが発生してしまうこともあるみたいです。
なので、これら取り消し方も覚えておきましょう。
# fetchを取り消す
git reset --hard HEAD
# merge取り消す
git merge --abort
fetch
の取り消しのコマンドは先程出てきたので割愛します。
ちなみにpull
の取り消しはmergeの取り消し
→fetchの取り消し
という順でやることでできます。
競合とは? というところやその解消のやり方についてはやや例を挙げるには難しいので以下の参考記事がわかりやすかったのでそちらを参照してみてください。
では、ここまでで無事にmaster
ブランチの変更を現在のブランチに反映できたなら
git push origin feature/~
というコマンドを打てば作業ブランチ(ローカルリポジトリ)をリモートリポジトリにあげることができます。
このように作業ブランチのようにローカルで作ったブランチをリモートにも反映させることをプッシュする
と呼ぶそうです。
これでリモートの方でもmaster
ブランチからfeature/~
ブランチが枝分かれしている状態となるわけです。
Pull Requestを出す
先述の通り、チームで開発している場合はメンターや責任者・管理者・設計担当……etc からコードレビューをしてもらい、承認を得ないと作業は完了しません。
なので、初めてのプッシュの際は必ずPull Request
を出します。
といってもGitHubの場合は初めてプッシュした場合、必ずリモートリポジトリ上にガイドが出るのでその通りに進めていけば問題ありません。
詳しく知りたい方は以下の参考記事を参照してみてください。
トラブルシューティング
プッシュした後、レビューを受けてそれに基づいて修正をしていたらわけがわからなくなってしまった……etcの事情でプッシュ直後の環境に戻したい
→
-
.gitignore
を削除する。 - 未追跡のファイルをすべて削除する
-
git branch -D [ブランチ名]
でローカルの作業ブランチを削除する - もう一度、作業ブランチを切る。(その前に一応ローカルのmasterブランチを最新にしておくのがいいかも)
- 切ったブランチで
git pull
を行う
私の場合はプッシュした後に.gitignore
を削除するというアクロバットなことをやらかした結果、この工程を実行することになりました。
最後に
ここまで書いてきましたが、これでようやくGitの基本が理解できたというような状態です。
Gitはまだまだ奥が深いな……と思わざるをえないというのはもちろん、プロジェクトに今回参加してなかったらこれだけのことを身を以て理解することはできなかっただろうなと感じました。
参考サイト
君には1時間でGitについて知ってもらう(with VSCode)
図解! Gitのブランチ・ツリーをちゃんと読む
【やっとわかった!】gitのHEAD^とHEAD~の違い
Gitを使ってPull Requestを投げるまで
git-resetは結局何を戻すのか
【初心者向け】git fetch、git merge、git pullの違いについて
Gitでローカルブランチを削除する
[Git] .gitignoreの仕様詳解
.gitignoreについて
Gitプルリクエストでコンフリクトが発生した場合の対応
Git コンフリクト解消手順
[実践] はじめてのPull Requestをやってみよう