home

Git/gitlabで共同作業をするための最小限の知識

(the page is encoded in UTF-8)

Gitとは

Gitは,「バージョン管理システム (Version Control System)」 と呼ばれるソフトウェアの一種で,同種のものとしては, CVS, Subversion, Mercurialなどがある. 要するに複数の人間で,ソースコードに関する変更を安全に行うシステムである. 複数の人間で同じソフトウェアをいじるときに, 変更点を他の人に伝えるのにまさか毎回手動でメールをやりとり, というわけにも行かない. かといって,本当に同じファイルを複数人が同時に更新したら, タイミング次第でお互いの更新をお互いにつぶしあうような事態が, 容易に発生してしまう. 手元である程度まとまった更新を行い, 最低でもコンパイルが通る程度までまとまったところで, 変更点を一括して他の人に伝える, というのがそれらのシステムの主な目的である.

このページでは,Git(とGitLab)の基本的な使い方として, ソースコードに対する変更をチーム内で共有する方法について解説する.

前提とする構成

下図に示しているのは,3人チームで作業を行う場合の基本的な構成である. 各メンバーの個人マシンにはディレクトリとファイルの集合(以下,作業ツリーと呼ぶ)があって, 「中間の置き場」であるサーバには 作業ツリーの変更内容が格納されている. この「中間の置き場」のことを,バージョン管理システムではリポジトリ (repository)と呼ぶ.

各自は個人マシンの作業ツリーで作業をして,切りのいいところで変更点をサーバに反映させる. 他のメンバーはその変更点を適当なタイミングでサーバから取り寄せて, 自分の作業ツリーに適用する. 以降ではこのモデルで作業するために必要な, 最低限のGitの概念と操作方法を説明する. また,サーバとしてはGitLabというソフトウェアを用いることにする. GitとGitLabの違いについては,用語の説明を参照されたい.

また,この課題ではこちらが 進捗把握やコードレビューをするために GitLabに上がっているソースを眺めたりもするので, こまめに途中経過でも上げてもらうことを推奨している.

gitlabサーバへのユーザ登録とログイン

上記のような構成で作業をするにあたっては, まずはgitlabサーバにアカウントが作られていなくてはならない. アカウントは,以下のURLにアクセスして,右の"Register"の項目から登録する.

https://doss-gitlab.eidos.ic.i.u-tokyo.ac.jp

なお,以降の作業をするにあたって必ずしも必須ではないが, 推奨される設定作業として,SSHキーの登録がある. その説明は後回しにして,その先の説明を行う.

授業用のGitLabに自分のアカウントを登録せよ.

プロジェクト開始時の作業

ここでは,チームで作業する練習として, チーム内の一人がGitLabにソースコードを追加し, 別のメンバーがそれを取り寄せることを体験してもらう. 練習のため,前回改造したgnuplotのソースをチーム内で共有してもらう ことにする.

以下の手順でGitLab上に新しいプロジェクトを作り, そこに(元々公式で配布されている)gnuplotのソースコードを送信せよ.

GitLabの「プロジェクト」というのは,今はGitのリポジトリと一対一対応すると覚えてもらえばよい. プロジェクト開始時に必要なセットアップ手順は,

  1. GitLab上でプロジェクトを作成.すると,内容が空のリポジトリができる(GUI 操作)
  2. GitLabからリポジトリを複製して,手元に作業ツリーを作成 (git clone).空の作業ツリーができる.
  3. 作業ツリーにソースコードを追加し, フォルダをGitに追加 (git add)
  4. 変更をコミットする (git commit)
  5. コミットした変更をGitLabサーバに送り込む (git push)
  6. 他のメンバーもGitLab上のリポジトリを複製 (git clone) すると, ソースコード一式が既に入った作業ツリーが取得できる
となる.以下で詳細を述べる.

GitLab上でプロジェクトを作る

GitLabの画面上のどこかにある "New Project" というボタンを押す. (以降,GitLabのバージョンによって画面に多少の差異があるかもしれない.)

そして,プロジェクト名を入力して確定する.

これで,GitLabサーバ上に,空のプロジェクトが作られる. なお,この状態でプロジェクトのページへ行くと, 以下のような画面で以降の手順が案内される. 以下で説明するのはこの内の上の方(Create a new repository) に沿ったやり方である.

GitLabサーバ上の空のリポジトリを複製(clone)する

cloneするには https 経由で行う方法と,SSH経由で行う方法がある. 後者を用いるには, SSHキーの登録作業を事前に行っておく必要がある. 本格的作業が始まったら後者にすることを推奨するが, もしSSHキーの登録にハマったら後回しにして https 経由でも良い.

URLは,わざわざ打ち込まなくてもGitLabの画面から簡単にコピーできる.

どちらにしても手元に,「プロジェクト名」という,空のディレクトリができるはずで, これが作業ツリーとなる. 一見すると何も入っていないように見えるが,実際には 変更を管理するためのディレクトリ「.git」が存在している. (「.」で始まるファイルは隠しファイルとみなされて,普段は表示されないが, lsに-aをつけると表示される.)

$ ls -a プロジェクト名
.    ..   .git

ちなみに,なぜこの作業を複製 (clone)と呼ぶかというと, 実は「作業ツリーもリポジトリの一種」だからで, これはGitが採用している分散リポジトリという仕組みの特徴である. 興味があれば調べてみるとよい.

ソースファイルを配置する

できた作業ツリーに早速ソースコードを追加したいところだが, ここで一つ注意点がある. ソースコード管理のお作法として, リポジトリ内には人間が直接編集するファイル(広い意味でのソース)だけを入れ, configureやmakeの結果各マシン上で生成されるもの (Makefile, config.h, .o ファイルや実行可能ファイル)は入れない, というのが普通である.各マシンの 上で,configure, make, make install とすることで ビルドできるようなものを共有したいのだから, これは当然のことである.だから, ソースコードを追加する時は常に,解凍したばかりの, まだ何も作業をしていない状態で加えることを推奨する.

前回の演習でtar ballをダウンロードしてそれを展開する, というところまで済んだはずなので, もう一度そのtar ballを展開して 変更前のソースコードを追加することにする.

$ cd プロジェクト名
$ tar xvf /home/denjo/gnuplot-5.0.1.tar.gz
ディレクトリ構成は以下のようになる.
プロジェクト名/
  +- gnuplot-5.0.1/
     +- config/
     +- demo/
     +- ...

ファイル・ディレクトリをリポジトリに加える(add)

$ git add gnuplot-5.0.1

これで,gnuplot-5.0.1というディレクトリおよびその下の ファイルが全て追加される.ただし,追加されるとはいっても, その内容がすでにGitLabサーバに反映されている訳ではない.

追加されたファイルをコミット(commit)

$ git commit -m "コメント" 
とする.コメントのところは適切に書く.例えば今なら, "Add source code" とか.
このとき以下のような文句を言われたら
$ git commit -m "コメント"

*** 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.

言われたとおりにする(上記の下線部を実行する. もちろんEmailアドレスと名前の部分は自分のものに置き換える). 初めてgitを使った人はこう言われるはず. コミットした記録に誰がやったのかを記録しないといけない.
ところで,この段階に至っても,GitLabサーバにデータが 送られているわけではない (それをやるのは次の,push).

コミットした変更を別のマシン(GitLabサーバ)へ送り込む(push)

$ git push

ここに至って,先ほどまでに行われ,コミットされた変更 (つまり,ソースコードの追加)が,GitLabサーバへ送り込まれる. pushに至って初めて変更が送り込まれるんだったら, では,その前段のcommitはいったい,何をしていると思えばよいのか.

要するに,ここまで行った色々な変更を一つのパッケージにまとめて, あとで別のマシン(GitLabサーバ)に送り込む際の単位を作っている, と理解してもらえれば良いのではないかと思う.

他のメンバーがリポジトリを複製する

ここまででGitLabサーバにコミットが無事送り込まれた. GitLabをブラウザで見れば,ファイルが見られるはずである. 他の人が行った変更やそれらの履歴などを見ることも出来, GitLabを使うことのメリットが感じられる.

こうしておけば,他のメンバーがcloneコマンドを実行しても ソースコード一式がダウンロードできる.

今度は「プロジェクト名」というフォルダの中にソースコード一式が入った リポジトリが複製されることになる.以上で,プロジェクトごと に必要なセットアップは終わりである.

自分に見えるプロジェクトはgitlabの画面の, "Projects" -> "Explore Projects" -> "All" から見ることが出来る.

他の人が作ったリポジトリを見て, cloneできることを確認せよ.

グループの作成

これは2回目以降, チームが出来た時点で行う. まだチームが出来ていなければ飛ばして良い.

GitLabでは,各ユーザアカウントが所属できる「グループ」を作ることができる. チームごとにグループを作っておくと,後々便利なので,予め作っておくことにする. 下図のボタンを辿ると,グループを作れる.

グループができたら,他のメンバーをグループに招待する. 下図のように追加する.

おもしろいチーム名前を考え, チームの誰かが代表してグループを作成し, チームメンバー全員をGitLabグループのメンバーとして追加せよ.
プロジェクトのリスト("Projects" -> "Explore Projects" -> "All") から doss20xx (20xxの部分は年度) チームリストのプロジェクト を見つけ, そのREADME.md に自分のチーム情報(決まっているところまで)を書き込め (もちろんこれには git clone, 修正, git commit, git push の作業が必要).

日常繰り返される作業

いったん全員が同一のリポジトリの複製を手にしたら, 以降の作業の流れは以下のようになる.
これらを変更点があるたびにこまめに繰り返す,というサイクルである. もっとも今回の演習に限って言えば, 他の人に渡す変更内容ができるよりも, 試行錯誤をする時間のほうが中心となり, そこまでpushが細かく発生するわけではないかも知れない.
Gitに追加したgnuplotのソースファイルに,初回の演習で行った変更を適用し, その変更をGitLabに送信せよ.

手元の作業ツリー内で普通にファイルを編集する

これは通常通りエディタで行えば良い. なお,新しいファイルやディレクトリを追加する場合は, 上でソースツリーのディレクトリを追加した時と同じように, git addコマンドを使って追加する.
前回の演習で改造したgnuplotであれば, tables.cやcommand.cを同じように直接編集するか, 別ディレクトリにまだ残っていればそこからコピーしてくればよい.
$ cp /home/denjo/gnuplot-5.0.1/src/tables.c プロジェクト名/gnuplot-5.0.1/src/tables.c

切りのいいところでコミットする

$ git commit -m "コメント" 
既に説明したcommitをここでも使うのだが,ひとつだけ注意がある. それは, commitはそれに先立ってaddコマンドで指定されたファイルしか, 変更されたとはみなしてくれない,ということである. つまり,既存のファイルをただ変更するだけでは, commitはそれに気づいてくれない.それを勝手に発見してくれるようにするために, -aオプションを指定する.もしくは変更したいファイルをいちいち git addで指定しても良いのだが,普通は余りやりたくないのではないか.

コミット結果をgitlabサーバに送り込む

$ git push
ここは先程と同じ.新たに説明することはない.
コミットを新たにpushすると,GitLabで履歴が確認できるようになっているはずである. 余力があればこれも確認するとよい.

他のメンバーがpushした変更を取り込む

他のメンバーのリポジトリをcloneした後, そのメンバーに変更をpushしてもらい, その変更が取り込めることを確認せよ.
$ git pull
これは,他の人がpushした変更を取り込む手段である. 自分が変更をしていようといまいと,時折実行するのが良い. もしくはとなりに座って作業しているのであれば, 他の人がpushしたら自分はpullをするようにすればよい.

これだけは,というコマンドまとめ

今手元の作業ツリーがどうなっているかを調べるのに, 以下の3つのコマンドも知っておくと便利.

元になるソースがgithub上にある場合

衝突 (conflict) を理解する

ここまでの話で作業は始められる. 以降は時間を見つけて好きなときに読んで下さい. トラブルに遭遇してから読んでも良いが, 事前に読んでおけば心の平穏は保ちやすい.

授業時間中は皆で相談しながら コーディングは一人で,というスタイルを推奨するが, 時には並行して作業をすることもあるだろう (そもそもリビジョン管理システムの本来の目的は, それを可能にすることである).すると, 複数のメンバーが同じファイルを変更し, commit, pushをするという事態が避けられない. これが,変更の「衝突(conflict)」という現象で, このようなときにgitが何を言ってくるか, 自分は何をすればよいかを知っておくことは, 安心して作業をするために重要である.

衝突はいつ「判明」するか

今,A, B二人が編集をしているとする. 当然のことながら衝突は二つの, 別の場所で行われた変更が「出会う」ところで判明する. それには少なくとも, A, Bのどちらかが, pushを行っていることが前提である. つまり,リポジトリ内でのファイルの編集や, commitだけでは衝突は発生しない. より具体的には以下の時点で衝突が, 明るみに出る.

その語感から受ける印象とは異なり,commitだけでは衝突は発生しないことに注意. git特有の言葉遣いだが,commitはあくまでそのリポジトリ内で, 変更点を「パッケージ化」するだけの,ローカルな操作である. データベースを始めとして,「コミット」という言葉は普通, 他と衝突したら失敗する操作,逆に言うと,コミットが成功すれば その変更はグローバルに反映されたとみなせるような操作を 意味するのだが,gitではそうではない.したがって,commitが 成功したあと,いざpushをしようとして衝突が判明する.

それぞれの場合のgitの挙動を説明しておく.

ケース1: push時の衝突

これは操作自身が失敗する.出るのは以下のようなメッセージ:
nanamomo:gnuplot-5.0.1% git push
To git@doss-gitlab.eidos.ic.i.u-tokyo.ac.jp:tau/my-awful-project.git
 ! [rejected]        master -> master (fetch first)
error: failed to push some refs to 'git@doss-gitlab.eidos.ic.i.u-tokyo.ac.jp:ta\
u/my-awful-project.git'
ヒント: Updates were rejected because the remote contains work that you do
ヒント: not have locally. This is usually caused by another repository pushing
ヒント: to the same ref. You may want to first integrate the remote changes
ヒント: (e.g., 'git pull ...') before pushing again.
ヒント: See the 'Note about fast-forwards' in 'git push --help' for details.
つまり,Bに「まずAの変更を取り込むためにpullせよ」と言っている. ので,Bは素直にこれに従う.
$ git pull
よって,結局ケース2の場合に帰着される.

ケース2: pull時の衝突, かつBがcommitする以前の変更と衝突

つまりBはまだファイルの変数作業をしている最中で,まだそれを commitはしていなうちに,pullを行った場合. この場合のメッセージがこれ.
nanamomo:gnuplot-5.0.1% git pull
remote: Counting objects: 7, done.        
remote: Compressing objects: 100% (5/5), done.        
remote: Total 7 (delta 3), reused 0 (delta 0)        
Unpacking objects: 100% (7/7), done.
From doss-gitlab.eidos.ic.i.u-tokyo.ac.jp:tau/my-awful-project
   b672bcb..a6f0c56  master     -> origin/master
Updating b672bcb..a6f0c56
error: Your local changes to the following files would be overwritten by merge:
        gnuplot-5.0.1/CodeStyle
Please, commit your changes or stash them before you can merge.
Aborting
stashというのは,コミット以前の変更を一時保存する操作で,大概の場合これがやりたい操作ではないだろう. なのでまずは自分の変更を(自分のリポジトリ内に)commitすることになる.
$ git commit -m "..." -a
$ git pull
そこで結局すべては,ケース2-2の場合に帰着する.

ケース2: pull時の衝突, かつBがcommitした変更と衝突

場合により,gitが両者の変更を行単位で勝手に統合してくれる場合と, そうでなくさじを投げられる(例えば同じ行を二人が編集した)場合がある. さじを投げられた場合は以下のメッセージが出る.
nanamomo:gnuplot-5.0.1% git pull
remote: Counting objects: 4, done.        
remote: Compressing objects: 100% (3/3), done.        
remote: Total 4 (delta 2), reused 0 (delta 0)        
Unpacking objects: 100% (4/4), done.
From doss-gitlab.eidos.ic.i.u-tokyo.ac.jp:tau/my-awful-project
   76bcaeb..bbbec8a  master     -> origin/master
Auto-merging gnuplot-5.0.1/ChangeLog
CONFLICT (content): Merge conflict in gnuplot-5.0.1/ChangeLog
Automatic merge failed; fix conflicts and then commit the result.
後者の場合はファイル中に両者の相違点が織り込まれたファイルができる.
<<<<<<<< HEAD
Gitはクソだ
=======
Gitはクールだ
>>>>>>>> 72616890b37a0fb7b2f8cadf83524ed591430d5f
これを見ながら手動で変更する. どちらの場合でも,Bはその後改めて,commitをする.その後push をすれば,めでたく,A, B両者の変更点を統合したバージョンが gitlabサーバにとどく.
 ... 手動で変更をマージして, <<<<<<<< とかがないファイルを作る ...
$ git commit -m "..." -a
$ git push

用語の説明

"Git"というのはオープンソースソフトウェアの一つのことで, 各々のコンピュータごとにインストールして使うものである. Gitには標準のGraphical User Interface (GUI) が存在しないので, 基本的には端末エミュレータからコマンドで操作することになる. 「Gitの初心者はGUIから始めるべき」という主張をする人もいるが, ツールの演習も兼ねて,今回はコマンドのみを使ってもらうことにする.

Gitを便利に使えるようにしたウェブサービスは複数存在し, 代表的なものにGitHubがあり, 現在のオープンソースプロジェクトの多くがこうしたサービスを使って 運用されている. GitHubはウェブブラウザから各種操作ができて便利ではあるが, 一企業の商用サービスなので非公開のソースコードを置くと料金がかかる. そこで,GitHubと同じような利便性を提供しつつ, 自前のサーバ上で管理するようなソフトウェアとして GitLabがあり, 本授業でもこれを使っている.

紛らわしいことに,GitLabの開発元では「GitLabの公開サーバ」を gitlab.com 上で運用しているが, 本授業で使うのは授業用に立てた以下のURLにあるサーバである.

https://doss-gitlab.eidos.ic.i.u-tokyo.ac.jp

SSHキーの登録方法

リポジトリとのやりとりはhttpsを利用して行う方法と, SSHを利用して行う方法がある.httpsの場合,pushやpullをするたびに, gitlabサーバ上のユーザ名とパスワードを入力する必要がある. SSHを利用するためには事前に公開鍵を登録する必要がある. 以下はその手順について述べる.

  1. sign in直後の画面で,左のメニューから,"Profile Settings"をクリック.
  2. Profile Settingsの画面から,"SSH Keys"をクリック.
  3. "ADD SSH KEY"ボタンをクリック
  4. キーを貼り付けるボックスに,SSHの公開鍵の中身をペーストし, "ADD KEY"ボタンをクリック.

    公開鍵は,すでに他の目的で生成したことがある人は, ~/.ssh/id_rsa.pub (または ~/.ssh/id_dsa.pub)という名前で生成されている (~/ はホームディレクトリの意味). 生成したことがない人は,
    $ ssh-keygen
    
    というコマンドで生成する.すでに存在する場合上書きしてよいか, という警告が出るので,その場合は上書きせずにコマンドを中断し, 既にあるものを使えば良い.
なお,SSHキーを登録していない状態では,プロジェクトのトップページに 以下のようなオレンジのバナーが表示される. ここから "add an SSH key"をクリックしても同じ登録画面に到達できる.

もう少し進んだ使い方

授業では詳しく解説していないが, Gitに限らずバージョン管理システムには数多くの機能があり, それに慣れることで生産性を高めることができる. 余裕のある人は,チーム開発の練習と思って, 以下のような機能の使い方に慣れてみるとよい.