バージョン管理(Git/GitHub)¶
バージョン管理とは、ファイルの更新履歴を記録する仕組みです。ソースコードのバックアップとして使えます。 ここではバージョン管理の基本の基本について紹介します。バージョン管理は現代のソフトウェア開発において必須の技術です。これから先、授業や研究でコーディングする際は、常にバージョン管理をすることを強くオススメします。バージョン管理は全ての学生が知っておいて良い概念ですが、比較的新しい技術なので、 これまでは必修の講義で教える機会がありませんでした。 なので、電気系学生必修のこのソフト1の講義で少し触れておくことになりました。
有益な資料:
- ドットインストールのGitの回: 短い動画でGitを紹介しています。これを眺めて雰囲気を知るといいかもしれません。
- Git/gitlabで共同作業をするための最小限の知識:田浦先生による3年生向けの選択実験の「大規模ソフトウェアを手探る」の講義資料です。特に多人数での協調作業について詳しく説明されており、非常に有益です。また、田浦先生の講義ではコードのホスト先としてGitLabという仕組みを使っています。一方、本講義(ソフトウェア1)ではGitHubを使います。このあたりの差が、本講義を読んだあと田浦先生の資料を読むと明らかになり、面白いと思います。
- ソフトウェア工学 GitHub:名工大の玉木徹先生による講義資料です。本講義の内容をさらに一段深く理解したいという方におすすめです。また、バージョン管理に限らず、このソフトウェア工学の講義資料はコンテナやCI/CDなど現代的な開発の基礎が丁寧に解説されており、必読です。
- GitHub演習: 慶應大学渡辺先生によるGitおよびGitHubに関する講義の資料です。Gitコマンドの裏側で何が起きているかを含めた非常にわかりやすく詳細で包括的な解説です。
- The Missing Semester of Your CS Education, MIT EECS [英語版][日本語版]:MITの講義のビデオと資料です。今日説明するGitをはじめ、情報系学生が知っておくべき現代的なtoolの使い方がまとめられています。こういった知識は先輩から伝授されたりしてだんだん学んでいくものですが、体系的に知っていれば効率的に物事を進められると思います。卒論を開始するまでにこれを全部見ておくと、卒論以降が効率的になると思います。情報系以外の学生にもむしろオススメです。日本語版も存在します。日本語版の翻訳は松井が取りまとめました。2020年のソフ1講義をとった学生さんも翻訳に手伝ってくれました。
コラム
ここでは基礎の基礎だけ教えます。「個人で自分のコードのバックアップをとる」ことを目指します。
Gitの根幹を成す重要な概念として「ブランチ」がありますが、本講義では触りしか説明しません。個人でバックアップする分には、全て「mainブランチ」というデフォルト設定で行います。
最後に少しだけ、ペアプログラミング(一つのリポジトリを複数人で編集する)を説明します。その際、少しだけブランチについて触れます。
バージョン管理とは何か?¶
バージョン管理とは、一言でいえばテキストファイルの更新履歴を記録してくれる仕組みです。
普通、main.c
を更新して上書き保存すると、その最新の状態しか残りません。過去のどの時点でどういう変更がされたかを記録するのがバージョン管理システムです。
歴史的にはsubversionやCVSなど色々あったのですが、近年はgit
という仕組みがデファクトスタンダードになっています。
git
はコマンド名であり、様々なgit
コマンドが準備されています。これにより、手元のファイルをバージョン管理できます。
そして、手元でgit
でバージョン管理されたファイルに対して、それをウェブ上にアップロードしてウェブサーバ上に保存してくれるサービスが存在します。これがGitHubなどです。
このGit + GitHubの仕組みを図示したものが下記です。
バージョン管理(+GitHub保存)が無い場合は、パソコンが壊れたら全てのデータは失われてしまいます。
また、3日前のコードと4日前のコードの差を見る、といったこともできません。
そして、現在のコードはそのままに新機能を増やしたいとき、しばしばmain_最新版.c
のようなファイルを作ってしまいます。これにより、ファイルがどんどん煩雑になります。
ここでバージョン管理をしたものが右図です。
ローカルの作業ディレクトリに対して、$ git init
コマンドを用いて.git
というディレクトリを作ります。このディレクトリの中にはgit
の作業データが入っています。
この中身は触らなくて大丈夫です。
この.git
があるディレクトリが、git
の管理対象になります。このディレクトリ中のファイルに対して、$ git add
や$ git commit
といったコマンド
を用いて変更履歴を保存します。
そして、その内容をGitHubのウェブ上のリポジトリに同期させます。これにより、コードのマスター情報はGitHub上に保存されます。これは安全なバックアップにもなります。
そして今後、ローカル側で作業してファイルを更新したときは、それを$ git push
という操作でGitHubのリポジトリに反映させます。
逆に、GitHubリポジトリ上で更新があった場合は、それを$ git pull
というコマンドで手元にもってこれます。
ここで重要なのはバージョン管理という概念を学ぶことです。 具体的なプログラム(Git/GitHub)の使い方を知ることは副次的なものです(とはいえ、今日の講義は実質ほとんどGit/GitHubの使い方の説明ですが)。 バージョン管理は情報の変更という概念をプログラマブルに扱う仕組みです。この考え方は非常に重要です。 近年の例だと、infrastructure as codeといって、 サーバなどの大規模システムの構成をGitのような「変更を追従出来る仕組み」で管理する概念が有効であることがわかってきました。 また、コードだけではなく、卒論などの文章を書くときに使うTeXファイルもバージョン管理します。 よって、バージョン管理は重要です。 これから先、授業や研究でコーディングする際は、常にバージョン管理をすることを強くオススメします。
また、よくある勘違いとして「最初はコードがグチャグチャだから、コーディングを進めて綺麗になってからバージョン管理しよう」というものがあります。
しかし、個人的には、最初のグチャグチャなときから始めることをオススメします。なぜなら、グチャグチャなときからバージョン管理をはじめても、デメリットがないからです。
そして、もし何かおかしいことになってしまったら、(1) ローカルの.git
を消す (2) GitHubのリポジトリを消す、とすれば全て綺麗サッパリなくなるので、
やり直せます。加えて、コードが綺麗になってから管理しようと思っても、どうせ永遠に綺麗になりません。恐れず初手からバージョン管理を始めましょう。
コラム
長年、GitHubはGitホストサービスで一番有名だったのですが、無料でプライベートリポジトリを作れないという問題がありました。 そのため、無料で作れるBitbucketというサービスをみんな使ったりしてました。 しかし2019年ごろからGitHubも無料でプライベートリポを作れるようになり、現在はとりあえずの初手としてはGitHubが人気だと思います。 また、自分のサーバでGitホストの仕組みを運営できるGitLabというものもあります。これは上記の田浦先生が使っているものです。 これらのウェブホストサービスの違いによって、仕組みに違いもあります。例えばこれから「Pull Request」という単語を教えるのですが、 これはGitHubの言い方であり、同様の操作はGitLabでは「Merge Request」です。
コラム
.git
に限らずですが、linux文化においては先頭にドットがつくファイルやフォルダは、「隠しファイル」として扱われます。ls
するだけでは表示されません。ls -a
すると表示されます。このようなドットつきファイルフォルダは、プログラムの設定に関する情報だったり、個人の秘密情報が入ったりします。これをgit
管理するかどうかは文脈によります。例えばみなさんは既に以下のようなものを見ています。
.git
: gitの設定ファイルが入るフォルダ。このフォルダ自身は明示的にgit管理しないし、普通は手動で編集しない(というか、gitプログラムが勝手に使う).gitignore
: gitが管理しないファイルを列挙するファイル。あとで説明します。.vscode
: みなさんはたまに作業フォルダ上に.vscode
というフォルダが出来ていることがあるかもしれません。これはvscodeの設定を何か変更した際に、その情報が明示的に記録されているというものです。
また、アプリケーションが設定ファイルを作る際は$HOME
(ls ~
で移動する位置)に(勝手に)作ることが多いので、cd ~
してls -a
すると、たくさんのドットファイルが見えると思います。
注意
情報教育棟のマックを使っている人は、今日の説明すべての「ローカル」とか「My PC」というのはそのマックのことです。
Google Cloud Shell Editorを使っている人は、「ローカル」とか「My PC」は、Google Cloud Shell Editor上での話だと思ってください。Google Cloud Shell Editorもウェブサービスなのでややこしいですが。。。
注意
データやコードの権利などには十分注意してください。例えば医療データを扱うときなどに、データやコードをクラウドサービスにアップロードしてはいけない、 という規約になっているとします。 そのような場合は、もちろんデータやコードをGitHubに上げることは規約違反となるので、注意してください。 研究室の方針としてクラウドサービスを使っておらず、自前のサーバでGitLabを運営している、といったところも多く存在すると思います。
やってみよう(5分)
まず準備として、「Personal Access Token (Classic)」を発行します。これはパスワードのようなものです。 近年のGitHubでは適切な権限をもったトークンを最初に発行し、それを使ってリポジトリの操作を行います。ここではリポジトリの編集権限だけを持つトークンを作ります。 このページ に従い、トークンを発行してください。ここで、
- トークンの名前は
repo_access
としましょう。 Expiration
というのは、トークンが有効な期限のことです。この期限を超えると、トークンが使えなくなります。ここではデフォルトの30日間としておきましょう。30日たつとこのトークンは使えなくなるので注意してください- スコープを選ぶときは、一番上の
repo
にだけチェックをいれましょう。これによりrepo:status
,repo_deployment
,public_repo
,repo:invite
,security_events
が自動的に選ばれます。 - これにより、
ghp_4qjmpor34prrwnrkuhfo8gupdsoirjt
のようなランダム文字列が表示されます。これがトークンです。これを大事に保存し、人に見せないようにしましょう。
ちなみに、「SSH設定」と言う設定をすでに済ませている人は上記の手続きは必要ないです。将来的には上記のトークンを使うよりも「SSH」を使うほうが良いので、調べてみることをオススメします。
リポジトリを作ってclone¶
それでは早速リポジトリを作ってみましょう。始め方として、(A) GitHubリポジトリを作り、それを手元に持ってくる、
(B) $ git init
コマンドで手元のディレクトリをgit管理下におき、それをGitHubに上げる、と二通りあるのですが、
ここでは簡単な(A)でいってみましょう。
- まず上図の①のように、GitHubのウェブ画面からリポジトリを作ります。これはポチポチクリックして情報を記入するだけです。
-
次に②のように、GitHubの内容を手元のPCにコピーします。この作業をcloneと言います。このcloneは最初に一回するだけです。
- cloneの作業は、具体的には次のようにします。ここでは
simple_calc
というリポジトリを持ってきます。cloneすべき作業ディレクトリの位置に移動したうえで、次のようにgit clone [取得したURL]
を実行しますすると下記のようにユーザ名とパスワードの入力を求められますので入力してください$ git clone https://github.com/hoge528/simple_calc.git
これでローカルにリポジトリをcloneできました。確認してみましょうCloning into 'simple_calc'... Username for 'https://github.com': hoge528 <- GitHubのユーザ名を打つ Password for 'https://hoge528@github.com': <- 先ほど取得した個人アクセストークンをコピペする remote: Enumerating objects: 3, done. remote: Counting objects: 100% (3/3), done. remote: Total 3 (delta 0), reused 0 (delta 0), pack-reused 0 Unpacking objects: 100% (3/3), done.
$ ls <- 確認 simple_calc <- git管理したに置かれたディレクトリが出来てる $ cd simple_calc $ ls -al total 16 drwxr-xr-x 3 matsui528 matsui528 4096 Nov 17 11:01 . drwxr-xr-x 12 matsui528 matsui528 4096 Nov 17 11:00 .. drwxr-xr-x 8 matsui528 matsui528 4096 Nov 17 11:01 .git <- .gitディレクトリ。触らない -rw-r--r-- 1 matsui528 matsui528 13 Nov 17 11:01 README.md <- 初期ファイルのREADME.md
- cloneの作業は、具体的には次のようにします。ここでは
-
そして③のように、手元で好きなようにコードを編集します。ここでは
main.c
というファイルを足しています。この内容をGitHub側に同期するために、pushを行います
コラム
Google Cloud Shell Editorでコピペがうまく行かない人は、以下のようにやってみてください。
- 前準備
- Google Cloud Shell Editor上にただのテキストファイル
hoge.txt
を準備しておく。 - GitHubの画面からトークンをコピーする
- コピーした内容を、
hoge.txt
上にペーストする。これは可能なはず。画面上(Cloud Shell Editor上で開かれているhoge.txt
上)にはトークンが表示される。これをAと呼ぶ。この「hoge.txt
上にA」を維持しておく。
- Google Cloud Shell Editor上にただのテキストファイル
- 実際のプログラミング
- プログラミングを行い、Git操作が必要になったとする。
hoge.txt
を開き、その中のAを、再度マウスで選択して、コピーする。- コピーしたものを、Aの次の行にでもペーストする。画面にはトークンが2つ(Aと、Aを再度張り付けたもの)見える
- この状態で、Gitコマンドのパスワードのところにペーストする(windowsの場合Ctrl+V)
上記だと出来る可能性が高いです。上記の手続きはナンセンスなのですが、どうやらコピーペーストの記憶領域の扱いがローカルとCloud Shell上で扱いが違い?ゴチャゴチャしている?ようで、Cloud Shell Editor上で実際にペーストを行った直後は少なくともコピー内容を記憶してくれているようです。
やってみよう(15分)
それでは早速上記をやってみましょう。GitHubにログインしましょう。
上図の緑色の「New」を押して、新しくリポジトリを作ります
上図のような画面になります。適当なリポジトリ名を打ちましょう。ここでは「simple_calc」としています。 今回は公開しないので「private」設定にします。公開する場合は「public」にします。publicにすると、世の中の人全てから見えるようになります。 皆さんは普段GitHubにアクセスして人のリポジトリを色々見ていると思いますが、それらはここでpublicに設定されたリポジトリです。 さらに、初期状態でREADME.mdを含んでくれるようにチェックします。そしてリポジトリを作ります。
さて、これで下記のようなリポジトリページが作られました。 ここが、ソースコードがおかれるマスター情報になります。パソコンが壊れても、ここが残っているので安心です。 また、ブラウザからビジュアルに中身を確認でき、履歴などもクリックを繰り返すことで確認できます。 さて、ここで右上の「Code」をクリックします。するとCloneのために必要なURLが表示されます。ちなみにここでもし「SSH」タブが選択されている場合は、「HTTPS」タブを選択してください。 ここでコピーマークをクリックすることで、URLがコピーされます。
あとは、上で述べたように、手元の作業ディレクトリでgit clone
を行ってください。手元でREADME.mdが見れればOKです。
ファイル変更履歴の記録:add, commit¶
さて、それでは手元のファイルに対して、git
コマンドを色々試してみましょう。
まずは更新の管理のための便利コマンドを紹介します。simple_calc
ディレクトリに移動したうえで、
git status
を実行してみましょう。これは、「現在のgit管理の状態を見せろ」という命令です。
実行すると以下のようになります。
$ git status
On branch main
Your branch is up to date with 'origin/main'.
nothing to commit, working tree clean
これは、「コードは何も編集されていないよ」という意味です。
git status
には副作用はないので、いつでも実行してOKです。
さて、それではコードを編集していきます。
ここでは、新しくmain.c
というファイルを作って、そこに適当に書き込んでみましょう。
ディレクトリ内は以下のようになります。
.
├── .git
├── README.md
└── main.c
コラム
main.c
を作ったあと、先ほど紹介した、更新管理の便利コマンドを実行してみましょう
$ git status
On branch main
Your branch is up to date with 'origin/main'.
Untracked files:
(use "git add <file>..." to include in what will be committed)
main.c
nothing added to commit but untracked files present (use "git add" to track)
Untracked files:
でmain.c
となっている(端末上では赤字になっています)のは、
「main.cという新しいファイルがいるが、そいつはgit管理の対象になっていないよ」と言っています。
さて、このmain.c
の更新をgit管理の対象にしましょう。
git add 更新を反映したいファイル
を実行します。
$ git add main.c
main.c
の更新をgit管理の対象にする」ことを意味します。
コラム
git status
で見てみましょう。
$ git status
On branch main
Your branch is up to date with 'origin/main'.
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
new file: main.c
main.c
はChanges to be comitted
でnew file
となっています。
これはターミナルから見ると、字の色が緑色に変わっているのでわかりやすいです。
これにより、「main.c
が新しく作られた。それはgit管理されている」を意味します。
次に、既存のファイルの更新もやってみましょう。README.md
の中身を適当に更新してください。
このとき、vscode / Google Cloud Shell Editor上ではmdファイルをクリックする表示モードになり、編集できないかもしれません。
そのときはREADME.md
を右クリックして「Open With」から「Code Editor」を選んでください。すると編集できます。
更新したあと、git add
でこの変更を反映させます。
$ git add README.md
コラム
git status
で見てみましょう。
$ git status
On branch main
Your branch is up to date with 'origin/main'.
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
modified: README.md
new file: main.c
README.md
がmodified
になっています。
これは「README.md
は更新されている。それはgit管理されている」を意味します。
次に、更新内容に「更新メッセージ」をつけて、「アップロード準備OK」の状態にします。 以下のコマンドを実行してください。
$ git commit -m "added main.c and updated readme"
ここで、-m
オプションは「更新メッセージをつける」という意味です。その後の"added main.c and updated readme"
は更新メッセージ
(コミットメッセージ)になります。
ここでは、編集した内容を簡単な文章で説明しておきます。この内容は自由です。"updated"
のように一言だけではなく、
行った変更をちゃんと説明する文章にしておくと良いです。
これにより、git add
したファイルが「アップロード準備OK」になりました。
以下のようなログが表示されるでしょう。
[main 07c2e59] added main.c and updated readme
2 files changed, 8 insertions(+), 1 deletion(-)
create mode 100644 main.c
注意として、メッセージの前後のダブルクオーテーションを忘れないようにしましょう。 つまり、次ではダメです。
# ダブルクオーテーションを忘れているのでダメ
$ git commit -m added main.c and updated readme
コラム
ここでgit status
を行うと、main.c
もREADME.md
も、アップロード準備OKになったので消えています。
$ git status
On branch main
Your branch is ahead of 'origin/main' by 1 commit.
(use "git push" to publish your local commits)
nothing to commit, working tree clean
Your branch is ahead of 'origin/main' by 1 commit.
となっているのは、
現在のディレクトリは、GitHubにある本体のリポジトリに比べ、
1コミットぶんだけ進んでいるという意味です。
ここまでが、ローカル側でのバージョン管理です。次にこれをGitHubのリポジトリにアップロードします
コラム
初めてgit commit
を実行した場合は、Google Cloud Shellの場合は以下のようなエラーメッセージが出てくると思います。
*** Please tell me who you are.
Run
git config --global user.email "you@example.com"
git config --global user.name "Your Name"
to set your account's default identity.
Omit --global to set the identity only in this repository.
fatal: unable to auto-detect email address (got 'matsui528@cs-961687541475-default-boost-sq5pd.(none)')
これは、「gitを使うときはメールアドレスと名前を教えろ」と言うことです。
マックの場合は、以下のようなメッセージが出ます。
Your name and email address were configured automatically based
on your username and hostname. Please check that they are accurate.
You can suppress this message by setting them explicitly. Run the
following command and follow the instructions in your editor to edit
your configuration file:
git config --global --edit
After doing this, you may fix the identity used for this commit with:
git commit --amend --reset-author
2 files changed, 4 insertions(+), 1 deletion(-)
create mode 100644 main.c
いずれにせよ、下記のように入力して設定してください。
$ git config --global user.email "hoge528@gmail.com"
$ git config --global user.name "Yusuke Matsui"
"hoge528@gmail.com"
と"Yusuke Matsui"
の部分には自分のものを入力してください。
ここのemailアドレスは、GitHubの自分のアカウントの登録に使ったものとそろえておくと便利です。
これらの情報はリポジトリを公開すると公開されるので注意してください。
Cloud Shell Editorの人は、そのうえで、git commit
をやり直してください。
ちなみに、ここで設定した内容は単なるテキストファイルとして/home/ユーザ名/.gitconfig
に保存されています。
更新のアップロード:push¶
さて、ここまでで、ローカル側での準備が整いました。この更新をGitHubのリポジトリに反映しましょう。
すなわち、git add
およびgit commit
で「アップロード準備OK」にした更新を、git push
のコマンドで実際にGitHub上のリポジトリにアップロードします。
$ git push origin main
origin
とは、GitHubウェブサイト上にあるリポジトリそのものを指します。main
とは、通常使っているブランチの名前です。
ここではブランチの説明はしないので、とりあえず全てmain
にしてください。
よって、このコマンドは、「手元の更新情報を、origin
(ウェブ上のリポジトリ)のmain
ブランチ(デフォルト)にアップロード(git push
)する」という意味です
これを実行すると、GitHubのアカウント名と個人アクセストークンを聞かれるので入力してください。
最終的にログは以下のようになります。
Username for 'https://github.com': hoge528
Password for 'https://hoge528@github.com':
Counting objects: 4, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (2/2), done.
Writing objects: 100% (4/4), 354 bytes | 354.00 KiB/s, done.
Total 4 (delta 0), reused 0 (delta 0)
To https://github.com/hoge528/simple_calc.git
4cd03fc..2cd5212 main -> main
ここで、GitHubのリポジトリ画面にアクセスしてみると、更新が反映されていることがわかります。これでアップロードが出来ました。
手元でgit status
をしてみると最初と同様のクリーンな結果が返ります。
このgit add
, git commit
, git push
が更新の1サイクルです。
いくらかコーディングをすすめたら、それらをadd
し、まとめてcommit
してメッセージをつけ、push
でアップロードします。
これを、少なくともと一日の最後には必ず行うようにしましょう。
問題が発生したら:
- エラーメッセージをググって調べてみましょう
- 調べてわからなかったら、誰か詳しい人に聞いてみましょう。
- どうしてもおかしいことになってしまったら、編集中のファイル(今回でいうと
main.c
やREADME.md
)を一旦どこかに退避させて、ディレクトリを完全に削除したうえで、clone
からやり直しましょう。そして、退避させたmain.c
を置きなおせばいいです。
コラム
実は数年前までgitのデフォルトのブランチ名はmaster
でした。なので、$ git push origin master
としていました。
近年のBLM運動により、master
という名称は相応しくないという結論になり、業界全体としてデフォルトブランチ名がmaster
からmain
に変更されたのです。今新しくリポジトリを作ると全てデフォルトでmain
が使われているので、これからはmain
で大丈夫です。一方、過去のリポジトリに触る場合は、メインのブランチ名がmaster
であることも多いと思いますので、注意してください。
また、GitHubにリポジトリを作るのではなく、手元のディレクトリに対してgit init
コマンドを作ってgit管理を始めることもできるのですが、
その場合は未だにデフォルトブランチ名がmaster
になってしまうので、注意が必要です。
更新のダウンロード: pull¶
さて、上記とは逆に、「リポジトリ上での更新を手元にもってくる」というコマンドがgit pull
です。
手元は変わらずにGitHubリポジトリのみが変更されることってあるのか?と思うでしょう。
今回のワークフローではそれは発生しないです。ですがそれでも、
複数台のPCから同じリポジトリを編集しているときなど、GitHubリポジトリ側が手元よりも進むことはありえます。
今回はそれをシミュレートします。
GitHubリポジトリのブラウザの画面からREADME.mdをクリックし、右側の鉛筆アイコン(Edit this file) をクリックし、README.mdの中身を適当に編集してみましょう。そして、一番下の緑色のアイコンの「Commit changes」を押してください。
これにより、GitHubリポジトリの中のREADME.mdが更新されました。ブラウザから確認してみてください。この情報を手元にもってくるには、次のコマンドを実行します
$ git pull origin main
結果は次のようになります。ユーザ名と個人アクセストークンを打ってください。
Username for 'https://github.com': hoge528
Password for 'https://hoge528@github.com':
remote: Enumerating objects: 5, done.
remote: Counting objects: 100% (5/5), done.
remote: Compressing objects: 100% (2/2), done.
remote: Total 3 (delta 0), reused 0 (delta 0), pack-reused 0
Unpacking objects: 100% (3/3), done.
From https://github.com/hoge528/simple_calc
* branch main -> FETCH_HEAD
2cd5212..9ea0035 main -> origin/main
Updating 2cd5212..9ea0035
Fast-forward
README.md | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
git pull
してからスタートすることをオススメします。
コラム
pullの実行時に以下のような警告が出るかもしれません。
hint: Pulling without specifying how to reconcile divergent branches is
hint: discouraged. You can squelch this message by running one of the following
hint: commands sometime before your next pull:
hint:
hint: git config pull.rebase false # merge (the default strategy)
hint: git config pull.rebase true # rebase
hint: git config pull.ff only # fast-forward only
hint:
hint: You can replace "git config" with "git config --global" to set a default
hint: preference for all repositories. You can also pass --rebase, --no-rebase,
hint: or --ff-only on the command line to override the configured default per
hint: invocation.
ここでは常にデフォルトの設定にしましょう。以下のコマンドを実行してください。この設定は必ず行ってください。この設定を行わない場合、コミットの衝突がおきたときにエラーになってしまいます。
$ git config --global pull.rebase false
/home/ユーザ名/.gitconfig
あるいは~/.gitconfig
の中に書き込まれます。これを確認すると、例えば私の場合は以下になります。
$ cat ~/.gitconfig
[user]
email = hoge528@gmail.com
name = Yusuke Matsui
[pull]
rebase = false
--global
を付け忘れると、自分の現在のプロジェクトの.git
の中のconfig
に書き込まれます。
変更履歴の確認:log¶
さて、手元において、ファイルの変更履歴を確認するには次のコマンドを実行します。
$ git log
結果は例えば次のようになります。
commit 9ea0035492bf3b718a7b41f219a5dbea8c60331c (HEAD ->
main, origin/main, origin/HEAD)
Author: hoge528 <70310899+hoge528@users.noreply.github.com>
Date: Wed Nov 11 00:04:23 2020 +0900
Update README.md
commit 2cd5212f9c9cf80814151a1f4ce6c194868c4195
Author: Yusuke Matsui <hoge528@gmail.com>
Date: Tue Nov 10 14:56:39 2020 +0000
added main.c and updated readme
commit 4cd03fc8923dc02a4ca1f83665223d3d419f6adf
Author: hoge528 <70310899+hoge528@users.noreply.github.com>
Date: Tue Nov 10 13:56:37 2020 +0900
Initial commit
コラム
履歴の確認はブラウザ上からリポジトリを見ても確認することができます。 上のように「X commits」となっているところをクリックします すると上のようにこれまでのコミット一覧が表示されます。これらをクリックすると、さらに詳しい履歴を確認できます。 みなさんは宿題の自動採点で既にこの部分によく触れているはずです。
使い方まとめ¶
さて、ここまでの使い方をまとめておきます。 なんらかのコーディングをしたいとします。
- 初期化:
- GitHub上でリポジトリを作り、それを
clone
で手元に持ってくる
- GitHub上でリポジトリを作り、それを
- コーディング:以下を繰り返す
- コーディングを開始するときに、まず
pull
でGitHubリポジトリの更新を取得 - コーディングする
- 区切りがいいところになったら、
add
,commit
,push
で更新をGitHubリポジトリに反映
- コーディングを開始するときに、まず
- おかしなことが発生したら:
- 全部消して、
clone
からやり直す
- 全部消して、
- 履歴を確認したい:
- ウェブ上からGitHubリポジトリを見て確認
- もっと複雑な操作をしたい:
- ググるか詳しい人に聞く。おかしなことが発生したら全部消して
clone
からやり直す
- ググるか詳しい人に聞く。おかしなことが発生したら全部消して
コラム
ここまではGitの操作をgitコマンドてCLIから行いました。一方で、GUIソフトウェアをインストールして、それらのコマンド操作をGUIで出来るようにする仕組みもあります。
そのようなソフトをGitクライアントといいます。Gitクライアントを使うと、add
やcommit
といったgit操作をポチポチクリックするだけで出来ます。
有名どころの一覧はこんな感じです。
SourceTreeなどは昔ながらの有名どころです。GitHub公式のGitHub Desktopもあります。
個人的にはGitKrakenがビューア機能が豊富で好きです。
ですが、実は最近はvscodeに標準でgitクライアントが入っているので、それで十分かもしれません。下は、古いファイルと新しいファイルの差分を可視化している例です。
これはCloud Shell Editorでも見れます。左側のメニューのSource Control: Git
というところをクリックすると見えます。
このような可視化はGitHubにアクセスしてブラウザを見ればできるのですが、クライアントをインストールしておけばローカルでも見れます。
git
コマンドを直接使うかクライアントを使うかは意見が分かれるところです。松井自身は、gitコマンドは直接叩き、必要に応じてビューアとしてクライアントを使っています。
また、「CLIの」クライアントというものもありそれをtigといいます。こちらはGUIが使えないサーバ上などでもクライアント相当のことができるので、 根強い人気があったりします。 tigの日本語解説サイト およびtigの公式
また、上記とは別に、GitHubにアクセスしなければ実行できない様々な操作(例えばissueを読むとか)は、 実はブラウザを経由しなくても、GitHub公式のCLIツールであるGitHub CLIで行うこともできます。 GitHub CLIは高機能なうえに痒い所に手が届くモダンなツールなので、興味がある人は使ってみることをおすすめします。
やってみよう(20分)
それでは上記のadd, commit, push, pull, logをやってみましょう。
無視するファイル:.gitignore¶
gitを使う上で重要な点として、gitが管理するのはあくまでテキストファイル(ソースコード等)であり、バイナリファイル(a.out、画像など)は扱うべきではないということです。 gitはテキストの行レベルで更新を検知します。 例えばjpg画像データはほんのちょっと画像が変わっただけでも内部表現は全く変わるため、 gitで変更を追跡するとものすごい量になります。 よって、バイナリファイルや、使い捨てるファイル(毎回値が変わるログファイルとか)、巨大すぎるファイル、などはgitで管理しないほうがいいです。
そういったファイルを追加してしまうと具体的にどうなるかというと、.git
フォルダのサイズが肥大化します。
これによりgit
操作が遅くなったり、エラーになったりします。
個人プロジェクトで、この.git
が数百MBぐらいになってしまう場合は、すべきでないファイルを管理してる可能性が高いです。
管理しないためには、git add
しなければいいだけです。しかし、それではgit status
するたびに
毎回それらの管理対象でないファイルが表示されてしまい不便です。また、いつか間違ってadd
してしまうかもしれません。
それを防ぐため、.gitignore
というファイルが使われます。
これは中身はただのテキストファイルです。
この中に、git管理したくないファイルのファイル名を書いておきます。
そうすると、gitはそれらを無視してくれるようになり、丸く収まります。
ちなみに、言語ごとの.gitignore
の雛形は公開されているので、これをまずコピペしてくればいいです。
このなかには、言語固有の、典型的な「無視すべきファイル」が最初からまとめられています。注意として、.gitignore
そのものはちゃんとadd
して追加してください。
コラム
じゃあ巨大なバイナリファイルのバックアップはどうすればいいのか?という疑問がわきます。実はこれに対する明確な答えは無いのです。 以下のような選択肢が考えられます
- DropboxやOneDriveなどの自動同期システムで管理
- ラボサーバや、AWS S3や、Google Cloud Storage (GCS)といったリモートストレージを用意してそこでバックアップ。必要に応じてそれらに直接アクセスしたり、ダウンロードしてくる
- Git Large File Storage (LFS)といった特殊な仕組みを利用
個人レベルで現実的な回答はDropbox / OneDriveでしょう。研究を始めると、ラボサーバやS3など、ネットワーク越しに巨大データを置いたりします。 また、GitにはLFSという巨大ファイルを扱う仕組みがあるのですが、これはクセが強いので慣れるまで難しいです。
リモートストレージをラップする形で、まるでデータもバージョン管理しているかのように扱うData Version Controlといった仕組みもあります(個人的にオススメ)
ところで、Dropboxなどの自動同期システムを使うならソースコードもDropboxで十分にバックアップとれるんじゃないの?と思うかもしれませんが、それは正しくないです。 まず、DropboxではGitのように厳密な変更履歴を保持できません。 そして、Dropboxのような自動同期システムは、「あるときにうっかり自分でも気付かずにファイルを消してしまった」という状況に弱いです。 なのでGitの代わりにはなりません。Dropboxを使う場合は、Dropbox管理したうえでさらにGitでも管理すると堅牢です。
バージョン管理はバックアップ¶
さて、長々と説明してきましたが、バージョン管理が実用上強力なバックアップであることを示します。 上の図にあるように、手元のPCは卒論提出直前に高い確率で壊れます。ここでソースコードをGit/GitHubでしっかりバージョン管理をしていたなら、 別PCにcloneすることですぐに研究を再開できます。 バージョン管理を知っている人間からすると、バージョン管理をしないでプログラミングすることは、シートベルトをしないで運転しているように見えます。
この場面ではDropboxも有効な手段ですが、Gitであればより完璧な履歴が保持できます(例えばDropboxだとうっかり容量制限に達していて記録できていなかったとか、 大きいファイルの共有に失敗して止まっていて保存できていなかった、なんてこともあり得ます) また、普段から、新しい環境でcloneしたときにちゃんと動くようにリポジトリ内容を整備しておくクセをつけておくといいです (作業手順や依存ライブラリをREADME.mdに書くなど)
コラム
ちなみに、これまでのGitHub Classroomによる宿題提出は何をしていたのでしょうか? 上図のように松井が1人1人のための宿題リポジトリを作り、みなさんに編集権限を与えていました。 みなさんは上図のhongoutarouさんのように、それを編集していたのでした。 そして、Codespacesという便利機能により直接リポジトリを編集していましたが、それは裏側ではgitコマンドを使っていました。 なので、下のkomabahanakoさんのように、ローカルPCにcloneしたうえでpull/pushで編集してもOKです。
ペアプログラミング入門:1つのリポジトリを複数人で編集¶
さて、それでは最後に、1つのリポジトリを複数人で編集する方式のうち、簡単なものを勉強しましょう。
みなさんは3年生以降の課題や実験で、複数人でプログラミングをすることがあります。 もしGit/GitHubを知らなければ、場当たり的にソースファイルを共有して編集することになり、大変です。 Git/GitHubの真価は複数人でのコード編集にあります。それをやっていきましょう。
また、以下の「やってみよう」にはチームで取り組んでもらいます。
- 知り合いとチームを組んだ場合はその知り合い同士で行ってください。チーム同士ではGitHubアカウント名がわかってしまうので注意してください。
- TAチームでもやってもらうので、TAチームと一緒にやりたい人は松井かTAまでDMしてください。TAチームのメンバー内ではアカウント名が見えるので注意してください。
- 松井も公開で同じことをやりますので、松井と同じチームでやりたければDMしてください(アカウント名はみんなに見えちゃいます)
- アカウント名を秘密にしておきたい場合は、以下の「やってみよう」はやらなくて大丈夫です。
素朴リポジトリ共有¶
最も簡単な方式は、これまで行ってきたことを単に複数人でやるというものです。ここではAさん、Bさん、Cさんが共同で開発するとします。 ここでAさんを代表としましょう。Aさんが作ったリポジトリを、A, B, Cさんみんなで編集するとします。
- Aがリポジトリを作る
- そのリポジトリの編集権限を、B, Cに付与
- A, B, Cともに、clone, add, commit, push, pullでリポジトリを編集
これが一番簡単な素朴リポジトリ共有方式であり、最初はこれでもOKです。しかし、このシンプルな方式の場合、衝突が発生することがあります。以下を考えましょう。
- Aさんが自分の手元で
main.c
を編集している - Bさんが自分の手元で
main.c
を編集し、push - その後Aさんもpushしようとするが、Bさんが既にGitHub上の
main.c
を更新してしまっており、その更新内容と衝突する。例えば以下のようなエラーが出る。$ git push origin main To https://github.com/hoge528/simple_printer ! [rejected] main -> main (fetch first) error: failed to push some refs to 'https://github.com/hoge528/simple_printer' hint: Updates were rejected because the remote contains work that you do not hint: have locally. This is usually caused by another repository pushing to hint: the same ref. If you want to integrate the remote changes, use hint: 'git pull' before pushing again. hint: See the 'Note about fast-forwards' in 'git push --help' for details.
このような衝突が発生したときの対処方法は、簡単にいうと
- まず、手元でadd, commitをちゃんと行っているか確認する。
git status
として赤や緑の字が出ないか確認してください。出る場合、適切にgit add
およびgit commit
を行ってください。 - 次に、pull漏れがある(GitHubの最新版を手元にマージできていない)かもしれないので、pullします。
git pull origin main
- ここで、以下のような感じのメッセージが表示される場合は、「衝突はしないので競合状態を自動的に解消できるが、現在の状態にコミットメッセージをつけてくれ」と言われています。
端末は以下のようになって待機中になっています
このとき、vscode画面には
$ git pull origin main From https://github.com/hoge528/simple_printer * branch main -> FETCH_HEAD hint: Waiting for your editor to close the file...
MERGE_MSG
という以下のようなファイルが表示されると思います(あるいは端末から実行した場合、このメッセージが端末に表示されています)ここではこのファイルを編集すればコミットメッセージを追加できるのですが、ここでは単にこのMerge branch 'main' of https://github.com/hoge528/simple_printer # Please enter a commit message to explain why this merge is necessary, # especially if it merges an updated upstream into a topic branch. # # Lines starting with '#' will be ignored, and an empty message aborts # the commit.
MERGE_MSG
ファイルをバツして閉じてしまってください。それでうまいことGitHubの最新情報を手元にマージできていることがわかります。 - もし上記のようにならず、衝突が発生してしまった場合は、以下のようになります。
- 衝突が発生するファイルについて、例えば以下のような謎の状態に勝手に変更されます。
ちょっとわかり辛いと思いますので画像も載せておくと、以下のような感じです。
<<<<<<< HEAD asdfswrwerwer 3423423423 45f ======= asdf dfs >>>>>>> b0cdd5d8d6fa2b61f947aaa833eb4a79f75a9c41
- ここでは、
というのが、GitHub上の最新版の内容であり、
asdf dfs
というのが、手元の内容です。ここでGitはその両方の可能性を提示しています。ここで、自分自身が、このどちらを残すのか、あるいは混ぜるのか、決めます。すなわち、上記のファイル内容を直接手動編集して、一番正しい形にします。「>>>>」とか全部消してOKです。asdfswrwerwer 3423423423 45f
- あとは、あらためて更新されたファイルを
git add
,git commit
,git push
すればOKです。
- 衝突が発生するファイルについて、例えば以下のような謎の状態に勝手に変更されます。
詳しくは田浦先生のGit/gitlabで共同作業をするための最小限の知識の 9. 衝突 (conflict) を理解する
を参照してください。エラーになった場合は、エラーメッセージをよく読んでください。
この簡単方式の別の弱点は、各自の更新が、「コミットレベル」でしかわからないことです。コミットというのは、とても小さい作業の単位であり、 コミットがたくさんあると一体最終的に何がおきたのかわかりません。例えば、Bさんが「calc関数に割り算機能を実装したい」としたときに、コミットは「割り算の出力部分を実装」「計算部分が間違っていたので修正」「さらに修正」「アルゴリズム部分を半分実装」・・といったように多岐になりえます。ここで、あとから振り返って知りたいのは、上記のコミットをたばねたものです。これを解決するために、Pull Requestという機能を紹介します。
また、この方式の場合は常にマスター情報である「AさんのGitHubのmainブランチ」を直接更新していく形になるので、不安という面があります。
また、本日は軽くしか述べませんが、Pull Requestを用いると、「レビュー」という、他のメンバーによるコード確認作業を行うこともできます。
やってみよう(15分)
それでは上記の素朴リポジトリ共有方式をやってみましょう。
- グループの代表者を決めてください。
- 代表者は
simple_printer
というリポジトリをprivateで作ってください - リポジトリ画面で、右上のSettingsを押し、左側のCollaboratorsを押します。すると右側がManage accessという画面になります。ここでAdd peopleを押し、他のメンバーのgithubアカウントを入力してください。すると、招待状を飛ばすことができます。
- 他のメンバーは、GitHubを登録したメールアドレスに、招待リンクが届いているはずです。そのメール中で「View invitation」を押し、「Accept invitation」を押してください。そうすると、代表者の作ったsimple_printerリポにアクセスできるようになります。
- あとは、各自で自由にclone/add/commit/pushを実行してみてください。また、意図的に、上で説明したconflictを発生させてみてください。
- リポジトリのコミット履歴をいろいろクリックしてみて、何が起こっているか確認してみてください。
素朴リポジトリ共有 + Pull Request¶
本講義で紹介するのは、上記の「素朴リポジトリ共有方式」に、Pull Request (PR)による更新を導入するというものです。 これは有名なGitHub Flowという方式の簡易版になっています。
ワークフローは以下になります。
- Aがリポジトリを作る
- そのリポジトリの編集権限を、B, Cに付与
- A, B, Cともに、
- clone, pull, add, commitを行い手元でコード編集
- PRを用いてリポジトリを更新
ここでは、ブランチと言う概念を触りだけ導入します。ブランチとは、「自分の並行世界の分身」のようなものです。
これまでの話は全て、手元のローカル環境もGitHubのリモート環境も、デフォルトであるmain
ブランチ上の話でした。ここからは、自分の並行世界の分身であるブランチを作成し、その上で作業をします。
概要は下図になります。
それでは、手元PC上で、前に作ったsimple_printer
リポジトリの中に移動します。
そして、以下を実行してみましょう。
$ git branch
* main
git branch
とだけ実行すると、現在のブランチの情報が見れます。ここではmain
ブランチしかなく、またそのブランチにいることがわかります。
まずはいつも通りpullをして手元のmainを最新にします。
$ git pull origin main
それではsay_hoge
ブランチを作ってみましょう。ここで、ブランチ名は固有のものにしましょう。今回の運用では、ブランチ名は使いまわしません。なので、例えばBさんが足し算機能を追加するために、feat_addition
ブランチを作る、といった形です。Bさんがfeat_addition
ブランチを作っている間に、Cさんもfeat_addition
ブランチを作る、という運用はここではやめておきましょう。Bさんがfeat_addition
ブランチを作って色々やったあと、三日後にまたBさんがfeat_addition
ブランチを作るというのは大丈夫です。
$ git branch say_hoge
say_hoge
ブランチが出来ました。これは上図でいう(2)に相当します。このブランチに変身してみましょう。
$ git switch say_hoge
Switched to branch 'say_hoge'
git switch
を使って変身ができます。確認しましょう。
$ git branch
main
* say_hoge
main
ブランチとsay_hoge
ブランチがあり、現在あなたはsay_hoge
ブランチです、ということがわかります。
コラム
少し前までは、git checkout
という便利コマンドがあり、それが色々な機能を担当していました。
ですが、同じコマンドで全然別の動作をするということで混乱を招いていました。
Git 2.23からは、git checkout
はgit restore
とgit switch
とうより直感的な名前の二つのコマンドに分割されました。
なので、皆さんはrestore
とswitch
を使っていくといいです。昔の記事ではcheckout
を使うものもたくさん残っていますので、注意してください。
ここでmain.c
を適当に作って編集し、git add
, git commit
しましょう。ここで、何度もコミットしてみてください。 これは(3)の編集作業に相当します。
そしてpush
します。(4)の部分です。これまではgit push origin main
としてきました。繰り返しますが、これは「リモート(origin
)の、main
ブランチにプッシュする」という意味です。GitHubのリモート環境側にもブランチという概念があるのです。ここでは、リモート側のsay_hoge
という同名ブランチを作りそこにプッシュします。以下を実行してください。
$ git push origin say_hoge
Username for 'https://github.com': hoge528
Password for 'https://hoge528@github.com':
Enumerating objects: 4, done.
Counting objects: 100% (4/4), done.
Delta compression using up to 12 threads
Compressing objects: 100% (2/2), done.
Writing objects: 100% (3/3), 280 bytes | 70.00 KiB/s, done.
Total 3 (delta 0), reused 0 (delta 0), pack-reused 0
remote:
remote: Create a pull request for 'say_hoge' on GitHub by visiting:
remote: https://github.com/hoge528/simple_printer/pull/new/say_hoge
remote:
To https://github.com/hoge528/simple_printer.git
* [new branch] say_hoge -> say_hoge
そしてGitHubの画面に戻ると、下図のように「新しくsay_hogeブランチが出来ています。PRを作りますか?」という表示が出ています。 ここで「Compare & pull request」を押します。
すると次のようなPR作成画面になります。ここではタイトルと本文を適当につけてPRを作成しましょう。 PRタイトルはわかりやすいものにしましょう。
すると、次の画面のように、PRが出来ます。これは上図の(5)にあたる部分です。
本来であれば、この時点で、「コードに対する更新をまとめたPRを作りました。これをチェックしてください」という状況になります。ここで、経験があるエンジニアや、チームの別の人が、 このPRをチェックすることになります。これをレビューと呼びます。それは以下のように表されます。
ここで、レビュアからの指摘に応じて、コードを編集してpushし直すことができます。これにより、PR内容が自動的に更新されます。
普通は上記のようにレビューを交えてすすめていくのですが、ここでは単に自分自身でマージしてみましょう。PR画面で「Merge pull request」を押し、さらに「Confirm merge」を押すと、この変更がmainブランチにマージされます。
そして、その画面の中で、「Delete branch」も押しておきましょう。こうすると、リモート側に作ったsay_hoge
ブランチを消すことができます。
GitHubのブランチ画面に戻ると、リポジトリが更新されていることがわかります。
ローカル側では、現在say_hoge
ブランチになっていますので、main
に戻り最新更新をpullしたうえで、say_hoge
ブランチを消しておきましょう。
$ git branch
main
* say_hoge <- 現在say_hogeブランチにいる
$ git switch main <- mainに戻る
Switched to branch 'main'
Your branch is up to date with 'origin/main'.
$ git branch
* main <- mainになった
say_hoge
$ git pull origin main <- リモートのmainをローカルのmainにもってくる
Username for 'https://github.com': hoge528
Password for 'https://hoge528@github.com':
remote: Enumerating objects: 1, done.
remote: Counting objects: 100% (1/1), done.
remote: Total 1 (delta 0), reused 0 (delta 0), pack-reused 0
Unpacking objects: 100% (1/1), 665 bytes | 95.00 KiB/s, done.
From https://github.com/hoge528/simple_printer
* branch main -> FETCH_HEAD
b28e440..6593ee4 main -> origin/main
Updating b28e440..6593ee4
Fast-forward
main.c | 1 +
1 file changed, 1 insertion(+)
create mode 100644 main.c
$ git branch --delete say_hoge <- say_hogeブランチを消す
Deleted branch say_hoge (was 5cb130a).
$ git branch
* main <- 分身は消えて初期状態に戻った
ブランチの使い方はいろいろあるのですが、一番簡単なのは使い捨てることです。 上記のように、機能を追加するために作り、使い終わったら消すようにするとよいです。
さて、ゴチャゴチャと色々行いましたが、なぜこのPRが楽なのでしょうか?まずは「複数のコミットをまとめて、意味のある更新単位を作れる」ということがあります。 そして、PRに対するレビュー作業の様子なども記録として残るので、あとから見返したときに「この機能は誰がどういう議論をもとに追加されたんだろう」ということが あとからハッキリとわかります。これは非常に重要です。
また、衝突の回避も楽です。以下を考えましょう。
- Aさんが手元で作業中に、Bさんがリポジトリを更新
- AさんがPRを作成
この場合、下図のように「衝突がおきているよ」ということをPR画面で教えてくれます。 ここをクリックすると、「素朴リポジトリ共有」で行っていた衝突解消画面に移行します。 ここでは内容を編集し、右上の「Mark as resolved」を押しそして「Commit merge」を押すことで、 衝突を解消しつつマージが可能になります。
このように、「一つのPR」の中で衝突を解消したり複数のコミットを入れることができます。
また、リモートのメインブランチは常に最新かつちゃんと動く状態に保っておき、それに対する更新だけをPRという形で受付ける、という状態にしておくと、健康です。
コラム
有名なGitHub Flowは、今日紹介した方式にさらに「Fork」という仕組みを導入したものになりますが、概念としてはほとんど同じです。 今日の方式は最初にリポジトリに対するアクセス権限を各ユーザに与える必要があります。よって、GitHub上で今日の方式でオープンに開発を進めることはできません。 オープンというのは、例えば「バグを直したよ」とか「新機能を考えたんだけどどう?」とかいう新たな提案を、知らないユーザから受け付ける、ということです。 GitHubやオープンソースの世界の真髄はそういったオープンな開発にあります。 そのようなオープンな開発を可能にするためには、Forkというものを使います。 Forkについては、2021のソフ1で触れていたので、気になる方は見てみてください。
やってみよう(20分)
それでは上記の素朴リポジトリ共有+PR方式をやってみましょう。色々失敗しつつ、慣れてみてください。
ここで、Aさん、Bさん、Cさんがそれぞれブランチを作る場合、ブランチ名は別のものを使うようにしましょう。