第 1 回 Git

本日の内容


このドキュメントは http://edu.net.c.dendai.ac.jp/ 上で公開されています。

1-1. はじめに

この授業のねらい

この授業では、高度なプログラミング技術を演習を通して学ぶことを目的とし ています。

まず、ソースコードの取扱として、バージョンコントロールシステムを学ん だあと、 プログラミング環境であるUnityを取り扱います。

準備

Gitをインストールします(Mac は標準装備)

  1. Windows 用は gitforwindows からダウンロードする。 以下私家版 Git for Windows のインストール手順 を参考にした。
  2. 「Choosing the default editor used by git」 には、よくわからな い場合は Nano editor を選びましょう。 但し、歴史的な経緯から Vim が使われていました。癖はありますが、 Web などの文献はこちらを使っているものの方が多いです。
  3. Adjusting your PATH environment は Use Git from the Windows Command Prompt を選ぶと良いでしょう。 但し、コマンドプロンプトやPATHをカスタマイズしている人は、 一番上の Use Git from Git Bash only を選びましょう。
  4. 「Choosing HTTPS transport backend」はデフォルトの選択で 構いません
  5. 「Configuring the line ending conversions」はデフォルトは 自動変換ですが、無変換のCheck out as-is , comment as-is の 方がトラブルが少ないようですので、こちらを選びましょう。
  6. Configuring the terminal emulator to use with Git Bash はWindows の cmd.exe を気に入ってない限りは MinTTYを選びましょ う。
  7. Configuring extra options では、3つとも選びましょう。 Symbolic link にはチェックが入ってないですが、これは厳密に は互換性がないからですが、大抵はトラブルが起きにくいそうで す。

1-2. プログラムの管理

ソフトウェアは実行されるコード順や、定義の参照順に書くのではなく、書 く順番はプログラマーに任されています。 プログラムが単一のテキストファイルでなければならなかった Pascal 言語 では、プログラムの構成に関してもコンパイラの参照の順番を意識する必要 がありました。 一方 C 言語は、ヘッダファイルと関数ごとの分割コンパイルが可能になり、 様々なテククニックを要したものの、プログラムの作成は関数ごとに行えば 良くなりました。 Python はオブジェクト指向を導入し、さらにプログラムの部品をモジュール という形で管理できます。

プログラムは常に一気に書けるわけではないので、いくつかの工程ごとに作 業を分割する必要があります。 プログラムの特性上、最終的に全てが完璧でなければなりません。そのため には、プログラムを分割し、分割した部分部分でそれぞれが完璧であること を確認する必要があります。

プログラミングの自由度を減らさずに、ここに完璧を保証するようなプログ ラムの作成法を身につける必要があります。 その場合、常に大切なことは、部分的に作成したプログラムが、コンパイル可 能だったり、テスト可能だったりと、何らかの意味で正当性のチェックがで きることが必要となります。

ソフトウェア管理

プログラムを工程に分割して作る場合、改良、修正や、試作、設計変更など、 作成したプログラムの変更をする必要が多くあります。 さらに、修正自体が失敗する可能性もあります。

このため、保存したい時刻ごとに、すべてのファイルを保存することを考え ます。 すべてのファイルを入れたフォルダを時刻ごとに保存すれば、確かに目的が 果たせます。 但し、ファイル数が膨大になると、変更点などのドキュメント管理など、煩 瑣な作業があります。

このようなことをすべてコンピュータに管理させるのがバージョンコントロー ルシステムです。

バージョンコントロールシステム

バージョンコントロールシステムの歴史は古いですが、理想的なシステ ムができるまでに時間がかかりました。 しかし、歴史的経緯もあり、従来のものも未だに使われています。

バージョンコントロールシステムには、ネットワークを使うものと使わ ないものに分けられます。 ネットワークを使うものは複数人が共有できるようになっています。

バージョンコントロールシステムを使う際は、管理するフォルダを定めます。 それを記憶させるリポジトリと呼ばれる保管用の領域を用 意します。 リポジトリからはフォルダやファイルを取り出せるほか、バージョンを指定して 古いファイルも取り出すことができます。 一方、リポジトリへの登録方法ですが、ロック型とコピー/マージ型が あります。

ロック型
  1. リポジトリの内容をロックする
  2. ロックした内容を取得する
  3. 内容を変更する
  4. ロックした人だけが変更した内容をリポジトリに返す
  5. ロックを解除
コピー/マージ型
  1. リポジトリの内容をコピーする
  2. コピーした内容を変更する
  3. 変更した内容をリポジトリの内容へマージする。コピー時以降、 マージまでに他のユーザーがマージしたなど、ファイルに矛盾が発生 した場合、その旨を警告し、 矛盾が内容にマージさせる
名前運用登録方式管理対象リポジトリ
RCS集中型ロック型ファイル単位フォルダ
CVS集中型コピー/マージプロジェクトサーバー
subversion集中型
git分散型

1-3. Git

Git の基本的な仕組みは、編集領域、ステージ、リポジトリの三層構造で、編 集ファイルを git addコマンドでステージングし、 git commitコマンドでステージングされたファイルをリポジトリ へコミットします。従来のバージョンコントロールシステムと異なり、バージョ ン番号は基本的に はハッシュコードとなっており、v1.0 などの人間にとって意味のある名前は tag として任意につけます。

一方バージョンはブランチというコミットの列で管理します。 編集対象はブランチに属していて、コミットすると、そのブランチが更 新されます。 ブランチに対して、複数の矛盾したコミットがされた場合、ブランチが 矛盾しないようにファイルを修正する必要があります。

さらに、ローカルに設定するリポジトリの他に、複数のプロトコルに対 応したリモートリポジトリをもち、相互に同期させることができます。

これにより、複数人が一つのファイルの編集に参加でき、ネットワーク の接続の有無に関わらず、任意のタイミングで同期できることです。

なお、コミットの列をブランチと呼び、ブランチごとの管理を行います。 デフォルトのブランチの名前は master と呼び、これは通常は最新のコ ミットを指します。 ブランチごとの管理や統合などもできます。

用語

repository
git の保存領域。git initにより初期化する必要がある。 リモートリポジトリの場合 git init --bare --shareと する。
staging
リポジトリに登録するファイルの候補を登録する。 git add ファイル名
commit
リポジトリの登録。登録コマンドも commit になる。 commit は長い桁数の commit ID がつけられる(自分でつけない)。 指定する際はすべてを打ち込む必要はなく、上位数桁で指定が可能。
branch
コミットの列。ブランチ名はその列の最新のコミットを指す
master
デフォルトのブランチ名名
origin
リモートリポジトリのデフォルトのブランチ名
HEAD
作業領域中のリポジトリ

commit

git commit 登録時に必ず1行以上のコメントを入力する。 無駄なコミットも rebase であとでまとめられるので、こまめにコミッ トすべきである。

branch

ソフトウェアを複数の方針で修正する場合、それぞれを別に管理すべき である。

branch を作成すると branch ごとに異なる方針で修正できる

checkout

任意のブランチ、タグ、コミットを展開する。HEADを指定コミットに移 動するとも解釈できる。 なんの警告も出ないが、現在編集中でコミットしていない変更はす べて消える。 checkout を行う前に必ずコミットを行っておくこと。

merge

コミットに矛盾が生じた場合は、警告が出て、矛盾しているファイル には
>>>>>
=======
と矛盾している変更点が含まれているので、修正の後、staging する必要があ る。

rebase

2つのブランチに対して、相手のブランチの変更点を解析し、自分のブ ランチに施す。これにより、自分のブランチの分岐点を相手のブランチ の最新コミットまで進めることができる。

1-4. 演習

Git にはいくつかのシステム、用法がありますので以下の共通のシナリ オを使って、用法ごとの演習をします。

初期設定

  1. git CMD と git gui を起動します
  2. git CMD でgit config --global --listを打ち、環境 を確認します。
    user.name=自分の名前
    user.email=自分のメールアドレス
    core.quotentpath=false
    core.autocrlf=ture	  
    push.default=upstream
    
    などと表示される。user.name などを変更したい場合は、 git config --global user.name "Naoshi Sakamoto" などとし、再び上記のコマンドで設定内容を確認する。

演習1-1

前準備

  1. Git CMD を起動する
  2. (Git CMD において、カレントディレクトリの表示が(C:\Users\sakamoto)となっている前提で) cd Documents など、演習用のファイルを置くディレクトリ(フォルダ)に移動する
  3. mkdir sproで本講義用のディレクトリを作る
  4. cd sproで移動したあと、 mkdir 1 で本日 用のディレクトリを作る
  5. cd 1

git リポジトリ

  1. git init でカレントディレクトリをリポジトリにする
  2. Git GUIを起動する
  3. Git GUI の Open Existing Repository を選ぶ
  4. 上記で作成したリポジトリを指定する。 つまり、Browse を選び、 ドキュメント→spro を開き、 表示されている1 を選び「フォルダーの選択」を押す。 そして、 open を押す。

ファイルの作成、ステージング、コミット

  1. エディタなどで次のプログラム A.py を作成する
    	
    class A:
        def id(self):    
            return "99ec999"
        def str(self):
            return "{}".format(self.id())
    a=A()
    print(a.str())
    
  2. Git GUI でrescan を押すと、 A.py が Unstaged Changes に登録さ れていることを確認する。
  3. Git GUI で A.py を選んで「Commit」→「Stage to Commit」を選ぶ と Staged Changes に移動することを確認する
  4. Git GUI で A.py を選んで「Commit」→「Stage to Commit」を選ぶ と Staged Changes に移動することを確認する
  5. 同様の操作を Git CMD で行う。
    1. git status で、unstaged と staged されているファイ ルが表示されることを確認
    2. git add A.py で A.py が staged される
    3. git reset A.py で A.py が unstaged にな る
  6. A.py を staged にしたあとで
    Git CMD
    git commit を打つと、エディタが開く。 何をしたかを完結に書くことが求められている。 例えば「Initial commit」などとうち、 このエディタでは 「Esc」「Z(大文字)」「Z(大文字)」で終了する
    Git GUI
    Commit Message 欄に 何をしたかを完結に書く。 例えば「Initial commit」など書く。 そして、Commitボタンを押す
    これによりリポジトリにファイルが登録される

履歴など

  1. git logで今までの Commit の情報が表示できる。 ブランチの名前が master で、今編集中のファイル(HEAD)が 「initial commit」のコミットであることがわかる。
  2. git tag tag1 をすると、今のコミットにタグがつけられる
  3. git logで確認する

ファイルの修正

  1. 上記のプログラムを次のように変更する
    	
    class A:
        def id(self):    
            return "99ec999"
        def name(self):    
            return "坂本"
        def str(self):
            return "{} {}".format(self.id(),self.name())
    if __name__ == '__main__':
        a=A()
        print(a.str())
    
  2. git の状態を調べながら stage して、「Added name method」などの コメントを書いてcommit する。
  3. git tag tag2でタグをつける
  4. さらに次の B.py を新規に作成する。
    	
    from A import A
    class B(A):
        def id(self):    
            return "00ec000"
        def name(self):    
            return "電大"
    if __name__ == '__main__':
        b=B()
        print(b.str())
    
  5. git の状態を調べながら stage して、「Added B.py」などの コメントを書いてcommit する。
  6. git tag tag3でタグをつける

ヒストリの移動、ブランチ

  1. git logですべてのコミットにタグ tag1, tag2, tag3 が ついていることを確認する。
  2. git checkout tag1 をする。 dir コマンドや type コマンドでフォルダ内が initail commit 状態 になったことを確認する
  3. git checkout tag3 をすると、Added B.py の状態に戻 ることを 確認する。
  4. git logで見られる commit の横の16進数の最初の5桁 くらいを取り出して git checkout xxxxxとcheckout コマンドに指定すると tagと同様に扱えることを確認する。

なお、git checkout でブランチではなく、commit id や tag を指定す ると、「detached HEAD」という警告が出ることがあります。 これは、HEAD がブランチから外れていることを警告しています。 ブランチから外れている状況で commit すると、ブランチに属さない commit が生じます。履歴の単位として branchを使用する以上、ブラン チの処理である merge などができなくなる不都合が生じます。 そのため、既存のブランチに属さない commit が生じた場合、それ に git branch 新しブランチ名で新しブランチとして取り 扱うことで、解消できます。

ブランチ

  1. git checkout tag2 で中間のコミットに移動します。
  2. つぎのプログラム C.py を作成してcommit します。
    	
    from A import A
    class C(A):
        def id(self):    
            return "88ec888"
        def name(self):    
            return "太郎"
    if __name__ == '__main__':
        c=C()
        print(c.str())
    
  3. git tag tag4でタグをつける
  4. git branchとすると、master 以外のものができてる。 現在commit したものは master ではない。
  5. git branch b1 と現在のブランチに b1 という名前を つける。
  6. git checkout master とし、 dir コマンドで確認する と、 B.py が存在するが、C.pyが無い、以前の状態に戻る。
  7. git checkout b1とし、 dir コマンドで確認すると B.py が無く、 C.py がある状況に戻る。

マージ1

  1. git checkout master で元のmasterブランチに戻る。
  2. git merge b1 でブランチ b1 とマージする。

この場合何の矛盾もなかったので、自動的にすべてが合わさるようにな ります。

マージ2

矛盾した状況を作ります。

  1. git checkout tag3 で B.py が追加された状況に戻りま す。
  2. git branch b2とブランチ名 b2 をつけます
  3. 次のプログラムを main.py として保存します。
    	
    from A import A
    from B import B
    if __name__ == '__main__':
        a=A()
        b=B()
        print(a.str())
        print(B.str())
    
  4. 「Added main.py」などとコミットします
  5. git tag tag4でタグをつけます。
  6. git checkout b1 でC.py が追加された状況に移行しま す。
  7. 次のプログラムを main.py として保存します。
    	
    from A import A
    from C import C
    if __name__ == '__main__':
        a=A()
        C=C()
        print(a.str())
        print(C.str())
    
  8. 「Added main.py」などとコミットします
  9. ここで、tag4 をマージします。 git merge tag4 エラーメッセージが出ます。
  10. git statusで状態をみると、矛盾の発生したファイル は unstaged になっています。この場合は main.py ファイルが矛盾し ます。 さらに、 type main.pyとすると、ファイルの相違点がす べて取り込まれた形で保存されていることがわかります。
  11. この矛盾は手動で解決する必要があります。 エディタなどできちんとファイルを修正します。 そして、stage して、すべての矛盾が解決したところで commitしま す

リベース

二つのブランチを足し算のように混ぜるのが mergeでした。 一方で、一つに統合してしまうのが rebase です。

先程、tag4 と merge した b1 を master に rebase します。

  1. git checkout master
  2. git rebase b1で master と b1 が統合されます。 git logで見ると、 masterとb1が同じコミットに表示さ れるようになります。

.gitignore

プログラム開発などで、同一ディレクトリ内にソースコードの他に、バッ クアップファイルやオブジェクトファイルなど、ソースコードから派生 したファイルが含まれることがあります。 これらはソースコードから生成されるため、個別にバージョンを管理す る必要はありません。 そのため、git で管理することはふさわしくありません。

.gitignore ファイルはテキスト形式で、無視すべきファイルを指定で きます。 自分で作成することもできますし、一般に処理系ごとに固有のファイル が生成されるため、 「処理系 gitignore」で検索すると作られたものをダウンロードするこ ともできます。

代表的な書式は以下のとおりです。

リモートリポジトリ

git は外部にリポジトリを持ちます。 これは、push, pull するための領域なので、通常のリポジトリは異な るものです。 アクセス方法として GitHUub, ssh でログインできる端末、Webサーバー、USBメモリーなど なんでもできます。

演習1-2

作成

ここでは、USBファイルやローカルファイルシステムとやり取りします。 自分でリモートリポジトリを作成します。 GitCMD でリモートリポジトリを作るフォルダを作ります。

  1. USBメモリーを挿して、それが E: ドライブだとすると 次のようにリポジトリ用のディレクトリ(フォルダ)を作ります。
    1. e:
    2. mkdir \git
    3. mkdir \git\spro1
    4. cd \git\spro1
  2. リポジトリを作りますgit init --bare --share
操作
  1. 演習1-1のフォルダに行きます cd ~/Documents/spro/1
  2. リモートリポジトリのブランチ名を指定して接続します。 git remote add origin file:/e/git/spro1 など、リモートリポジトリを作ったフォ ルダを指定する
  3. master ブランチのすべてを origin に送る git push
  4. プログラムを修正した後、commit してから、また push するとリモート ブランチも更新される。
  5. USBメモリのリポジトリを共有して、他からpushされたとき、 git pullで変更がマージされる

リモートリポジトリを元に、ローカルリポジトリを作ります。

  1. ローカルリポジトリを作るディレクトリを作る場所へ移動します。 cd \Users\sakamoto\Documents\spro
  2. ディレクトリを作成 mkdir 1copy
  3. 移動 cd 1copy
  4. リモートブランチを指定して、クローンを作ります。 git clone file:///e/git/spro1