パスワード受け渡し用の Heroku アプリ、 Keydrop というのを作りました
Keydrop は、遠隔にいる方からパスワードを (比較的) 安全に受け取るための Heroku アプリケーションです。
必要な時だけ起動して、使い終わったら消す、という使い方を想定しています。
Keydrop がやること
Keydrop はパスワードの
- 暗号化
- バリデーション
を行います。実装は PHP です。
暗号化
以下を使います。
- 送信時: HTTPS
https://*.herokuapp.com/...
- 保存時: htpasswd apr1 形式でハッシュ化、もしくは RSA 公開鍵で暗号化
- 受信時: PostgreSQL SSL 接続
バリデーション
デフォルトでは、パスワードに以下の制約を設けています。
- 10 文字以上
- 最低 1 文字の数字 (
0 - 9
) - 最低 1 文字の半角大文字アルファベット (
A - Z
) - 最低 1 文字の半角記号 (
#$%@&*!...
)
準備
- Heroku のアカウント作成。2段階認証を忘れずに
- Heroku のコマンドラインアプリケーションをインストール
- PostgreSQL をインストール。Heroku のデータベースに接続するためクライアントだけを使用します。サーバのセットアップは不要です
- 例:
$ brew install postgresql
- 例:
- 公開鍵で暗号化する場合は OpenSSL をインストール
セットアップ
以下のボタンをクリックしてください。
以下の設定画面が表示されます。
設定: App name
空のままがおすすめです。ランダムなドメイン名が設定されます。
設定: ACCEPT_PATHS
ランダムなパス名を設定してください。コンマで区切って複数のパスを設定できます。
例:
/K7FElrHrPmUnmBdw,/Mfj2Wl2YF6rFcMQ6
URL は以下のような形式になります。
https://${app-name}.herokuapp.com${ACCEPT_PATH}
ACCEPT_PATHS に合致しない URL は 404 になります。
ユーザグループを区別したい場合などに、複数のパス名を設定して、それぞれの URL を伝えてください。
設定: ENCRYPTION_TYPE
暗号化の方式。デフォルトは htpasswd
です。
公開鍵で暗号化したい場合は、publickey
に設定してください。
設定: PUBLICKEY
ENCRYPTION_TYPE に htpasswd
を設定している場合は、変更する必要はありません。
デフォルト値は、GitHub にコミットされている鍵でテスト用です。公開鍵暗号を使う場合は、必ず修正してください。
公開鍵の生成
bin/genkey.sh
を使うか、以下のようなコマンドで生成してください。
$ openssl genrsa -aes256 -out private.pem 2048 $ openssl rsa -pubout -in private.pem -out public.pem
URL を伝える
パスワードを送ってほしい相手に、以下を伝えます。
以下の URL にアクセスし、
xxxxxxx
という ID でパスワードを入力してご連絡ください。
https://${app-name}.herokuapp.com${ACCEPT_PATH}
実際のログイン ID ではなく、受け渡し用の仮の ID を使っても構いません。
同じ ID で複数のパスワードがポストされた場合は、2度送信したかどうかを確認してください。他人が別のパスワードを送っている可能性を検討してください。ID が重複しても、ユーザには何も知らせません。
結果を受け取る
./bin/dump.sh
を使用します。結果をブラウザで表示する方法はありません。
$ ./bin/dump.sh random-appname-12345 --> Connecting to postgresql-dbname-54321 1 2017-08-30 03:06:57+00 htpasswd /vEswu2ech4ta test-1 test-1:$apr1$4.Pz0U/W$TppMU9SjE/W0yg1FAPiMS/ 2 2017-08-30 03:08:19+00 publickey /vEswu2ech4ta test-2 nJ97fbhHIkZAMISp/zXVETvitUl8Qlbi1pyOTtoF3ybI9EDrqenPFb4WMOISrTn8sW+Qu5xvNsjaMEIC3j0Md+hmtEzlLmVK+Nb9bq989I9TnmjgdtFE9klyKkhb5J7r+7SKqBgzfmu7kAoREYBtg05hvNb3mJXGbAruybElbZlxNgf06b5f6W/kkHtGcJaV49oNHKBEmg03ceMip2wP5H6tk/BS6O4FTrEKvpYsn4+Kh6+7JMioCVQEXz3NvpH0BIkmnGncXBZTdtPihju7srb0uEHe0sys66PPBZGZQWbisBdr9knJ5WTfnh2iWLOGv2NgOwfgXQZyMdizINALDw==
公開鍵暗号の復号もできます。
$ ./bin/dump.sh random-appname-12345 ./keys/insecure-private.pem --> Connecting to postgresql-dbname-54321 1 2017-08-30 03:06:57+00 htpasswd /vEswu2ech4ta test-1 test-1:$apr1$4.Pz0U/W$TppMU9SjE/W0yg1FAPiMS/ 2 2017-08-30 03:08:19+00 publickey /vEswu2ech4ta test-2 testTEST1234!!!
または、 heroku pg:sql
で直接 DB にアクセスしてください。
$ heroku pg:psql --app random-appname-12345 --> Connecting to postgresql-dbname-54321 psql (9.6.1, server 9.6.4) SSL connection (protocol: TLSv1.2, cipher: ECDHE-RSA-AES256-GCM-SHA384, bits: 256, compression: off) Type "help" for help. random-appname-12345::DATABASE=> select * from keys; id | type | path | username | key | created_at ----+-----------+---------------+----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+------------------------ 1 | htpasswd | /vEswu2ech4ta | test-1 | test-1:$apr1$4.Pz0U/W$TppMU9SjE/W0yg1FAPiMS/ | 2017-08-30 03:06:57+00 2 | publickey | /vEswu2ech4ta | test-2 | nJ97fbhHIkZAMISp/zXVETvitUl8Qlbi1pyOTtoF3ybI9EDrqenPFb4WMOISrTn8sW+Qu5xvNsjaMEIC3j0Md+hmtEzlLmVK+Nb9bq989I9TnmjgdtFE9klyKkhb5J7r+7SKqBgzfmu7kAoREYBtg05hvNb3mJXGbAruybElbZlxNgf06b5f6W/kkHtGcJaV49oNHKBEmg03ceMip2wP5H6tk/BS6O4FTrEKvpYsn4+Kh6+7JMioCVQEXz3NvpH0BIkmnGncXBZTdtPihju7srb0uEHe0sys66PPBZGZQWbisBdr9knJ5WTfnh2iWLOGv2NgOwfgXQZyMdizINALDw== | 2017-08-30 03:08:19+00 (2 rows)
公開鍵暗号を手動で復号する場合は、以下を行います。
- Base64 デコード
$ base64 -D input_file
- OpenSSL で復号
$ openssl rsautl -decrypt -inkey privatekey_file -in base64decoded_file
片付け
受け渡しが終わったら、Heroku の管理画面からアプリを削除します。
Heroku の料金体系
Keydrop を使っていて、無料の範囲を超えることは普通はないと思います。
無料のプランでは、30 分アクセスがないとアプリがスリープ状態になり、アクセスが来ると起動します(表示に少し時間がかかります)。月合計で 550 時間 (22日分) まで、クレジットカードを登録すると 1,000 時間 (41日分) まで稼働させることができます。
(2017年9月現在)
マインクラフト内に記憶の宮殿を建てる
マインクラフトを使って、記憶の宮殿を作る試みを行っています。
いまは主に英単語を覚えようとしています。
動画
記憶の宮殿とは
記憶の宮殿は、非常に古くからある記憶術です。
2,500 年ほど前、古代ギリシャの詩人シモニデスが、記憶の宮殿を発明したと言われています。
ある宴会で天井が崩れ落ち、たった一人生き延びたシモニデスは、出席者がどこに座っていたかを完璧に思い出せることに気づきます。
シモニデスは目を閉じて、記憶の中で崩壊した建物を再び組み立てた。驚異的な再現力により、彼は悲劇の晩餐でどの客人がどこに座っていたのかも思い出すことが出来た。部屋の見取り図を意識して憶えようとしたわけではなかったのに、記憶の中にずっと残っていたのだ。このシンプルな発見から、いわゆる「記憶術」の基盤となるテクニックが編み出されたと言われている。
エドは、宴席に並んでいるのが客人ではなく別の人や物であったとしても、例えばギリシャの偉大な劇作家が生まれた順番に座っていたとしても、自分の詩の言葉だったとしても、あるいはその日にやらなければならない仕事の内容だったとしても、空間記憶を使って憶えれば、たいていのことは、並んでいる順番のまま脳に刻み込むことができると確信していた。(中略)
「ジョシュ、わかってほしいのは、人間は本当に場所を憶えることに長けているってことだ」ごく平凡な記憶力の私が1年で全米記憶力チャンピオンになれた理由
- 作者: ジョシュア・フォア,梶浦真美
- 出版社/メーカー: エクスナレッジ
- 発売日: 2011/07/29
- メディア: 単行本(ソフトカバー)
- 購入: 29人 クリック: 1,964回
- この商品を含むブログ (27件) を見る
詳しくは Wikipedia 記憶術 場所法を見てください。下のプレゼンテーション動画もおすすめです。
これをマインクラフトを使ってやると楽しいので試してみませんか、というお誘いが、本記事の内容です。
既存の試みとの比較
こんなナイスなアイディアを思いつくのは世界で自分だけだろう、などと一瞬思いましたが、ググったら全くそんなことはなくて、何年も前からたくさんの人がこのアイディアについて書いたり動画を作ったりしていました。
- 2010/12 Minecraft beta リリース
- 2010/12 Using Minecraft to build a Memory Palace : Minecraft フォーラム
- 2011/04 Using a Video Game for a Memory Palace - Art of Memory 記事
- 2012/03 Memory Palace Demonstration video コンセプト動画?
- 2012/12 Minecraft Memory Palace フォーラム
- 2012/12 My Memory Palace 動画
- 2013/06 Minecraft Memory Palace 記事
- 2014/12 Has anyone used Minecraft to create a Memory Palace? : Mnemonics フォーラム
- 2016/02 Minecraft Memory Palace for learning Geography 動画
- https://www.google.co.jp/?gws_rd=ssl#safe=off&q=minecraft+%22memory+palace%22
自分の試している方法に、多少なりともオリジナリティがあるとしたら、
- 屋外を使う
- 看板を使う
- パターンを使う
とかかなと思います。
大建築を作るのも楽しそうですが、基本的には屋外を使っています。 屋外のいいところは、いくらでも自動で生成されるところです。
PC版のマインクラフトは地球の約8倍の広さがあるそうなので、実質、無限に土地を使えます。 マインクラフトが生成する風景は、壁に囲まれた屋内よりもダイナミックで記憶に残りやすいように思います。夕焼けや雲の流れ、天候の変化、歩き回る動物が、単調になるのを防いでくれます。
パターン
試行錯誤してみた中で、特にうまく機能しそうだった、いくつかのパターンを紹介します。
Circle
Circle パターンは、12個の単語のグループを作り、円形に配置します。
アルファベット順、時計回りに、看板で円を作ります。
例えば、conf* で始まる12個の単語。
confection 砂糖菓子 / confederate 連合国・共犯者・南部連合 / confer 相談する
confide 打ち明ける / confine 閉じ込める / confiscate 没収する
conflagration 大火事 / conflate まとめる / confluence 川の合流点
conform 適合する / confound 混乱させる / confrontation 対立
看板の後ろに、単語を思い出す手がかりになるようなオブジェクトを配置します。ちょうど、ストーンヘンジのような見た目になります。
単語はアルファベット順に並んでいるので、一部の単語が思い出せれば、間にある単語のスペルの範囲がわかります。 東西南北4つの単語を憶えていれば、間2つを思い出すのは、そんなに難しくありません。
寝る前や入浴時に、頭の中でサークルを歩き回ります。
12個の単語でストーリーを作るのも、いいのではないかと思います。上の conf*
の単語だったら、砂糖菓子職人が共犯者と一緒に金庫破りを計画し仲間割れしていく話とか。
8個、16 個、20 個のサークルも作っていますが、12 個 〜 16 個がちょうどいい感じでした。
12個
16個
20個
12 + 8 の二重のサークル。外側の円と内側の円で関連を出せるのはいいんですが、ちょっと複雑かもしれません。
サークルに単語が入りきらない場合は、あまり大事でない単語をサークルの外側に、放射状に配置します。関連する単語も外側に置いたりしてみています。
blunt と同じ「ぶっきらぼうな」という意味の単語 curt をサークルの外側に足したら、隣の blurt と韻を踏んでいて嬉しかったの図です。
- blunt なまくらな、ぶっきらぼうな
- curt ぶっきらぼうな
- blurt 口を滑らせる
こういう小さい発見をつないで言葉のネットワークを広げていくところが、ゲームっぽい。
Line
Line パターンは、本や映像などの、ある程度長いコンテンツから、知らない単語を含むフレーズを抜き出して一列に並べます。
語られている内容のあらすじが、意味を思い出す助けになります。
TVドラマ glee
小説
- 作者: Paul Auster
- 出版社/メーカー: Faber & Faber
- 発売日: 1990/04/01
- メディア: マスマーケット
- 購入: 1人
- この商品を含むブログ (3件) を見る
TED
全てのフレーズを完全に暗記するというよりは(そんなことできる気がしないので)、フレーズを見たときに意味が思い浮かべばいいかというつもりでやっています。
また、副次的な利点として、内容が頭に入る、というのがあります。 自分は小説を読んでもぼんやりとした印象しか残っていないことがほとんどですが、 Line を歩いていると、あらすじを説明できる程度には物語が頭に入ります。普通に読むよりもずっと、物語の構造が把握できるような気がします (気のせいかもしれませんが)。
Scene
Scene パターンは、複数の単語を組み合わせて、ドラマチックな場面を作ります。
preach 説教する / plead 嘆願する / heed 傾聴する
clout 権威・一撃 / conceal 隠し持つ / clot (血などの)どろっとした固まり
repulsion revulsion 嫌悪感
impulsion 衝動 / compulsion 衝動・上からの強制力 / convulsion 痙攣
complaint 抗議・不平 / composure 沈着・平静
expulsion 排除・駆逐 / expel 追い出す・発射する / compel 強制する・強要する
plunge into 飛び込む
Circle よりも自由で、その分、想像力が必要になります。あまりうまくはやれていませんが。
Circle で粗筋を作り、周囲に放射状に Scene を配置し、物語のディテールを埋めていく、みたいなこともできるかもしれません。
辞書について
Spotlight 辞書
単語の意味は、看板に書いていません。
最初は、裏側にもう1枚看板を貼って意味を書いてみたりしたのですが、面倒くさいのと、Mac 版のマインクラフトは日本語が書けず、また、簡単に Spotlight 辞書で検索できるのでやめてしまいました。
Spotlight にはショートカットキーを割り当てて(デフォルトは コントロール+スペース
)、ゲーム中にいつでもショートカットキーで呼び出せるようにしています。
Spotlight に単語を入力した後、辞書で開くショートカットは Command + L です。
マインクラフトがフルスクリーンだとショートカットが効かないので、ウィンドウを最大にして実行しています。
Wisdom2 (iOS)
他に、物書堂の Wisdom2 (iOS)を使っています。2,900 円。
素直なインタフェースで使いやすいアプリです。
Circle パターンで単語のグループを作るために、パターン検索を使います。
例えば「bl で始まって t で終わる単語」(blast, bleat, bleast, blight...) を bl*t
で検索します。知らない単語がだいたい12個くらいに収まりそうなパターンを探すのが、楽しみになります。
オンラインだと、OneLook dictionary があります。ただ、これだと(自分程度の語彙力では)ちょっとレベルが高すぎでした。
ゲームの話
元々マインクラフトは、地下の「ゲームらしさ」に比べて、地上は、好きなように使えば?と放置されている感じがあって、そこに記憶の宮殿がうまくはまる気がしています。
語彙のネットワークを広げていく作業は、地下の洞窟探検によく似ています。コツコツと掘り進めていくと、突然視界が開けて、よく知っている場所に出ることがあります。あ、この洞窟はここにつながってるのか!という発見があります。
例えば、tin*
で始まる単語を調べていて、tinker 鋳掛屋(いかけや)という言葉を初めて知りました。
大阪では夫婦が仲良く出かけることを鋳掛屋と言った、とか、これだけでも十分面白いんですが、鋳掛屋について調べてから何日か経って、朝ふとんの中でぼんやり tin* のサークルを思い出していて、突然 tinker ってティンカーベルの tinker なのか!と思ったんですね。
そういえば子供の頃に、ティンカーベルが鍋や釜を直す仕事というのが、なんか妖精のイメージと合わなくてヘンなの、と思ってたのも思い出した。
これを思い出した瞬間、 tin*
のサークルに血が通う感じがしました。tinker の隣は、tinkle 鈴がチリンチリン鳴る、などの意味で、Tinker Bell のネーミングに tinkle がイメージされていたのかは調べてもわからないのですが、もうこの2つの単語は死ぬまで忘れないだろうなと思いました。
今この記事を書いていて、ディズニーのティンカーベルには双子の姉妹、ペリウィンクルというのがいるのを知ったんですが、periwinkle といえば glee シーズン1第2話冒頭の "Look, we much, periwinkle" (青紫でお揃いだわ) → "Get a room" (部屋とったら?/いちゃついてんじゃねーよ) です。
2つの単語の位置関係を憶えているので、tinker から periwinkle に向かって伸びるアーチを思い描くことができます。ある単語を空間内の位置とセットで思い出せる、というのは、いままで自分の脳には無かった機能で、不思議な気はしますが、特に無理をしている感じはしません。
上で書いた blunt - curt -blurt の話もそうですが、単にマインクラフトが学習にも使えますね、という話ではなく、ゲームとして有機的に結合している感覚があって、それが、今やっていることの一番の面白さだと思います。
ストラクチャーブロックによるパターンの共有
自分で作る方が楽しいし効果的な気はしますが、量をこなすことを考えると、人が作ったパターンを取り込めたほうがいい気もします。
マインクラフトは最近のリリースで、範囲を指定して構造物をファイルに書き出せるようになりました。ストラクチャーブロックという機能です。
ストラクチャーブロックについては、こちらの記事がわかりやすかったです。
試しにサークルをストラクチャーファイルに書き出して、ダウンロードできるようにしてみようかなと思います。(準備できたら、ここにリンクをはります)
おまけの Tips
以下蛇足です。
エンダーマンに運ばれてしまうので、土に額縁を貼るのはやめた方がいいです。 いつの間にか希少なアイテムが消えていたことがあって、土をサンドストーンに切り替えました。
雷雨で火が付くので、TNT を地面に直に置くのも避けた方が良さそうです。額縁に入れています。 まあ大抵大丈夫だと思いますが、、、雷が鳴ったらさっさと寝てしまえばいいのかもしれません。
テレポートコマンドブロックを作るために、チートコマンドをオンにしています。テレポートは、世界をブックマークする機能として必要です。ゲームをスポイルするつもりはないんですが、無意味に長距離を移動するのはさすがに苦痛なので……。
すっきりまとまった記事が見つからなかったので、以下、テレポートコマンドブロックの作り方。
- F3 キーでワープしたい場所の座標を調べる。間違えるとしぬので慎重に
/give @p minecraft:command_block
でコマンドブロックを得る- コマンドブロックを適当な場所に設置
/gamemode 1
でクリエイティブモードに切り替え- コマンドブロックにテレポートコマンド設定
/tp @p 123 -234 345
/gamemode 0
でサバイバルモードに戻る- コマンドブロックにレバー等を付ける
バグだと思うんですが、手元の Mac 版では、テレポート後にベッドに入ると、起きたときに金縛り状態になってゲームを一旦終了させるしかなくなる、という現象が起きています。目が覚めているのに何もできない、というのは、なかなかぞっとする体験で面白いんですが直してほしい。
自分はマルチプレイをやったことないんですが、サーバ上で各人の記憶の宮殿を混ぜるのも、きっと面白い体験になると思います。いずれ、1つの VR 空間に全世界の人々の記憶の宮殿を構築するサービスなんかが生まれて、死語になりつつある「サイバースペース」の復興が起こるかも、などと想像しています。
Rails, Django, Laravel 開発サーバをダブルクリックで起動する vagrant-layout プラグインというのを作った
vagrant-layout というのを作りました。
この記事で書いていた環境を生成する Vagrant プラグイン&設定サンプル集です。
インストール
$ vagrant plugin install vagrant-layout
でプラグインをインストールします。
Quickstart
$ mkdir my-project $ cd my-project $ vagrant layout init php
で各種ファイルを生成します。生成された、
をダブルクリックしてしばらく待つと、CentOS に
が入った VM が http://localhost:8080/ で起動します。
開発は手元のファイルを編集することで行います。ローカルのフォルダが /vagrant
にマウントされており、 VM に即反映されます。
開発プロジェクトが一段落したら、
で環境をアンインストールできます。
vagrant layout init
の後ろは vagrant-layout リポジトリのブランチ名です。
vagrant layout init ruby
(ruby
ブランチ)vagrant layout init python
(python
ブランチ)vagrant layout init php
(php
ブランチ)vagrant layout init
(デフォルト:master
ブランチ)
を指定できます。
Gist からインストールする
Rails
$ mkdir my-project $ cd my-project $ vagrant layout init https://gist.github.com/koseki/37f61d9a02b9a48e6651
で生成された start.{command|bat}
をダブルクリックしてしばらく待つと、 http://localhost:8080/ で Rails が起動します。
Django
$ mkdir my-project $ cd my-project $ vagrant layout init https://gist.github.com/koseki/b7670fc1576a3129e4f7
で生成された start.{command|bat}
をダブルクリックしてしばらく待つと、 http://localhost:8080/ で Django が起動します。
Laravel
$ mkdir my-project $ cd my-project $ vagrant layout init https://gist.github.com/koseki/efbd631472c932ff2153
で生成された start.{command|bat}
をダブルクリックしてしばらく待つと、 http://localhost:8080/ で Laravel が起動します。
Gist の中身
Gist は、
- ベースになるコミットの URL が書かれた
README.md
- パッチファイル
patch
でできています。
- Rails - https://gist.github.com/koseki/37f61d9a02b9a48e6651
- Django - https://gist.github.com/koseki/b7670fc1576a3129e4f7
- Laravel - https://gist.github.com/koseki/efbd631472c932ff2153
vagrant-layout
プラグインは、Gist に書かれたコミットをダウンロードし、git apply
でパッチをあてます。(実行には Git が必要になります)
Gist の作り方はこの記事の最後で説明します。
ファイル構成
プロジェクトのルートディレクトリに sandbox
を作成し、そこに開発環境用の全設定をいれます。
. ├── sandbox/ │ ├── Procfile │ ├── Vagrantfile │ ├── bin/ │ │ ├── boot.sh …… サーバ起動スクリプト本体 │ │ ├── nginx.sh …… Nginx 起動スクリプト │ │ ├── provision.sh …… プロビジョニング │ │ ├── shoreman …… foreman の shell 版 │ │ └── start.sh …… サーバ起動スクリプト boot.sh を呼ぶだけ │ ├── config/ │ │ ├── mime.types …… Ngixn MIME-Type 設定 │ │ └── nginx.conf.src …… Nginx 設定 │ ├── logs/ …… ログディレクトリ │ ├── osx/ …… OSX 用スクリプト │ │ ├── manage/ │ │ │ ├── init …… 設定 │ │ │ ├── provision.command …… プロビジョニング実行 │ │ │ ├── ssh.command …… SSH ログイン │ │ │ ├── status.command …… VM ステータス確認 │ │ │ ├── uninstall.command …… アンインストール │ │ │ ├── up.command …… VM 起動のみ実行 │ │ │ └── use-proxy.command …… プロキシ使用開始 │ │ ├── start.command …… 起動 │ │ └── stop.command …… 終了 │ └── win/ …… Windows 用スクリプト │ ├── manage/ …… OSX と同様なので省略 │ ├── start.bat …… 起動 │ └── stop.bat …… 終了 └── static/ └── htdocs/ └── index.html …… ドキュメントルートサンプル
sandbox
は開発環境専用のディレクトリです。プロジェクトは sandbox
を消しても動くようにします。
sandbox とステージング・本番環境の設定について
ステージング・本番環境の設定と、ローカル開発環境の設定は分けるようにしています。認証、外部サービスとの連携、デバッグログ、SSL、ロードバランサや CDN などなど、多くの場合、環境に差がありすぎるので。
プロジェクトのルートディレクトリに config
のようなディレクトリを作って、そこにステージング・本番用の Chef レシピを入れたりしています。
開発環境では開発者の作業しやすさを優先し、ステージング・本番はコストが許す限り同じ構成にします。
プロビジョニング
サーバ設定を変えたときは、チームメンバーに
をダブルクリックしてもらうように依頼します。このスクリプトは、vagrant provision
を実行します。
Vagrantfile
でプロビジョニングに sandbox/bin/provisioning.sh
を使うように設定されており、これは、VM 上のファイル /root/.provisioning_version
に書かれたバージョンよりも新しい処理を実行します。
VERSION=`expr $VERSION + 1` if [ $CURRENT_VERSION -lt $VERSION ]; then echo "--- $VERSION: nginx ---" yum install -y -v nginx --enablerepo=epel # To stop 'could not open error log file' alert when starting nginx. chmod 777 /var/log/nginx echo $VERSION > $VERSION_FILE fi
provisioning.sh
に、上記のような if ブロックを追加していくことで、追加の構成をメンバー全員の VM に導入します。
シェルスクリプトを使ったプロビジョニングは冪等とは言えませんが、開発環境用としては、大概これで十分なのではと思います。プロビジョニングのために Ruby や Python が必須になると、導入のハードルが上がってしまいます。
開発メンバー向け環境構築手順
以下は開発メンバー (特に非エンジニア) 向けのセットアップ手順です。sandbox
入りのプロジェクトリポジトリを手元に clone してある前提です。開発メンバーが vagrant-layout
プラグインを入れる必要はありません。
Windows の日本語ユーザディレクトリの問題
Windows を普通にセットアップすると、日本語のホームディレクトリができてしまいます。
Vagrant は日本語のホームディレクトリでは動きません。また ASCII でも特殊な文字が文字がパスに入っていると動きません。以下の issue を参照してください。
以下の手順で問題を回避します。
VAGRANT_HOME
はC:\vagrant
に設定するstart.bat
から呼ばれるinit.bat
が設定するので気にする必要はありません。ディスク容量が足りなくなった時などに思い出してください
- プロジェクトは
C:\projects\my-project-name
など、安全なパスに clone する - VirtualBox の
machinefolder
をC:\vms
など、安全なパスに変更する
最後のは、VirtualBox のインストール後、VirtualBox マネージャを起動して、設定 > 一般
の デフォルトの仮想マシンフォルダー
を書き換えます。
この設定は VirtualBox 上の全 VM で共通ですが、既にインストール済みの VM があっても特に影響しない(はず)です。古い VM は、そのまま同じディレクトリを参照し続けます。参考まで、最新の VirtualBox がどこに何を保存するかについてのメモが以下にあります。
Vagrant / VirtualBox / Git のインストール
Mac
Mac はインストーラを実行するだけです。何か聞かれたらデフォルトのままにします。
Windows
Windows は上の2つに加えて、Git をインストールしてください。Git パッケージに含まれる ssh.exe
が必要です。
Windows で Git を通常と異なるパスにインストールしたい場合は (おすすめしませんが)、init.bat
を修正するとかしてください。
Windows の Git はコンテキストメニューに大量の項目を追加します。普通いらないと思うので、
こちらの記事を参考に削除してください。
Windows の改行コード設定
VM にマウントされたシェルスクリプトを実行するには、改行コードに LF を使う必要があります。
全ファイルがコミットされた状態でsandbox/win/convert-eol.bat
をダブルクリックします。
このスクリプトは、
git config core.eol lf git config core.autocrlf input
を設定し、作業ディレクトリをリセットします。
git rm --cached -r . git reset --hard
ちなみに、*.bat
は CRLF でないと動きません。.gitattributes
で改行コードを設定しています。
*.bat text eol=crlf
start.command で起動する
上記が終わったら、
で VM を起動します。成功すると、
にアクセスできます。途中、
default: Warning: Connection timeout. Retrying...
みたいな表示が出ますが、これで正常です。VM が起動し終わるまで待っている状態です。この後、プロビジョニングと呼ばれるセットアップ処理が実行されます。
プロビジョニング開始前に止まってしまう場合
default: Warning: Connection timeout. Retrying... default: Warning: Connection timeout. Retrying... default: Warning: Connection timeout. Retrying... Timed out while waiting for the machine to boot. This means that Vagrant was unable to communicate with the guest machine within the configured ("config.vm.boot_timeout" value) time period.
のように止まってしまう場合は、VM が起動していません。以下をお試し下さい。
1. 一度アンインストールしてもう一回実行してみる
sanbox/osx/manage/uninstall.command
または uninstall.bat
でアンインストールしてから start.command
を実行してみてください。
2. DHCP のバグ対応を試す
VirtualBox の初期設定が原因で、Vagrant の DHCP 設定がうまく動かない問題がありました。(最新版は自動で修正するようです)
この問題を回避するには、以下の手順を試します。
- VirtualBox マネージャーで、全仮想環境の電源が切れているのを確認。
- ターミナルから次のコマンドを実行し、表示される内容をどこかにメモする。(念のため)
$ VBoxManage list hostonlyifs $ VBoxManage list dhcpservers
- ターミナルから次のコマンドを実行。
$ VBoxManage dhcpserver remove --netname HostInterfaceNetworking-vboxnet0
3. GUI で OS が起動しているか確認する
Vagrantfile を開いて、vb.gui = true
のコメントアウトを外すと、GUI ウィンドウが開くようになります。
これで OS が起動していない場合は、何か根本的に問題があります。Vagrant、VirtualBox が正常にインストールできているかどうかを確認してください。
4. プロキシ設定が必要な場合
プロキシを設定しないと LAN から外に出られない場合。
Mac は sandbox/osx/manage/use-proxy.command
をダブルクリックで実行してください。プロキシ設定ファイルのサンプルが sandbox/config/vagrant_proxy.conf
に生成されます。
export http_proxy="http://user@passwd:www.example.com:8080" export https_proxy="http://user@passwd:www.example.com:8080" export no_proxy="localhost,127.0.0.1"
Windows は、use-proxy.bat
を実行すると sandbox/config/vagrant_proxy.bat
に同様のファイルが生成されます。
生成された vagrant_proxy.{conf|bat}
をエディタで開き、各行を編集してください。
編集後、Mac は、最初に実行した use-proxy.command
を再度ダブルクリックで実行します。設定がうまくいっていれば、プロキシ経由で vagrant-proxyconf
プラグインがインストールされます。
Windows は、最初のウィンドウで何かキーを押すと、プラグインのインストールを開始します。ウィンドウを閉じた場合は、もう一度 use-proxy.bat
をダブルクリックしてください。
プラグインのインストールが終わったら、 start.{command|bat}
でセットアップを開始します。VM に自動的に
http_proxy
https_proxy
no_proxy
環境変数が設定されます。また、/etc/yum.conf
等にプロキシ設定が書き込まれます。
5. DHCP ではなく固定 IP を使いたい場合
sandbox/config/vagrant_ip.conf
ファイルを作成し、1行目に
192.168.33.10
のように IP を書いてください。デフォルトでは DHCP を使います。
通常は DHCP がおすすめです。同じネットワーク内で複数のメンバーが作業する場合に面倒なことになります。
プロビジョニングが途中で止まってしまう場合
--- 1: nginx ---
のようなインストール処理中に止まってしまった場合は、以下を試します。
- 一度
stop.command
またはstop.bat
で VM を止める manage/provision.{command|bat}
をダブルクリックで実行- 無事終了したら
start.{command|bat}
をダブルクリックで実行
レイアウトのカスタマイズ方法
vagrant-layout
リポジトリは CentOS や Nginx、MySQL を固定で使っています。
設定を変えるには、 vagrant layout init
コマンド実行後に、生成したファイルを編集すれば良いのですが、変更した環境を再利用したり共有したい場合もあるかもしれません。(あればいいなと思っています)
独自のレイアウトを共有するには、Gist を使う方法とリポジトリを fork する方法があります。
Gist を使う方法
ちょっとした変更であれば Gist を使うのが手軽です。
1. patch を作る
以下の手順で patch
ファイルを作成します。
vagrant-layout
リポジトリを clone する- ベースになるブランチをチェックアウトして、新しいブランチを作る
- 好きなだけ編集してコミット
- ベースになるコミットとの
diff
をpatch
ファイルに書き出す
例えば、Rails だと以下のような手順になります。
$ git clone https://github.com/koseki/vagrant-layout.git $ cd vagrant-layout $ git checkout ruby $ git checkout -b rails : ファイルを編集 $ git add -A . $ git commit -m 'Rails 用レイアウト' $ git diff 'HEAD^' > ../patch
更にコミットを加える場合は、'HEAD^' が正しいコミットを指すように調整してください。git commit --amend
するなどして、ベースからの変更を1つのコミットにまとめておくのも良いと思います。
ベースコミットを変更したい時は、リポジトリから pull
したあと変更分のコミットを rebase
してください。
2. README.md を作る
README.md
の末尾に、
---
base: ${ベースとなるコミットのURL}
を書きます。patch
を生成するときに指定した比較元のコミットの URL を base
に指定します。この URL をダウンロードしてパッチをあてます。
README.md
は、サンプルの Gist をコピーするか fork してください。
パッチのベースになるのは特定のコミットなので、元のブランチが更新されたせいでパッチに失敗したり、動作が変わってしまうようなことはありません。
3. Gist に登録して確認
README.md
と patch
を Gist に登録して、
$ vagrant layout init ${Gist の URL}
でうまく動くか確認します。
vagrant-layout リポジトリを fork する方法
大きな修正を行いたい場合や、URL ではなく名前で init
できるようにしたい場合は、vagrant-layout
リポジトリをフォークしてください。(どうぞお気軽に)
フォークしたレイアウトは、
$ vagrant layout init ${ユーザ名}/${ブランチ名} $ vagrant layout init ${ユーザ名}/${リポジトリ名}/${ブランチ名}
でインストールできます。例えば、
$ vagrant layout init koseki/python
のようにします。また、特定のコミットを URL で指定できます。
$ vagrant layout init https://github.com/koseki/vagrant-layout/commit/80c34e5
といった具合です。確実に同じバージョンを使いたい場合などに、こちらをご利用ください。
Git の仕組み (2) - コミット・ブランチ・タグ
Git の仕組みシリーズの2回目です。目次がここにあります。
前回の記事では、Git オブジェクトとリファレンスが大きなツリー構造になっていることを説明しました。
また、Git オブジェクトがどのように記録されているか、
ファイルツリーの変更がルート tree オブジェクトの ID に反映される仕組みなどを見てきました。
今回は commit
オブジェクト、ブランチ、タグ、stash の仕組みについて説明します。
実際のデータが見たいときは、Git Object Browser にアクセスしてみてください。
5. commit オブジェクト
先に説明した通り、Git オブジェクトデータベースには、複数のファイルツリーを保存できます。
個々のファイルツリーは、最上位 (ルート) にある tree
オブジェクトの ID で区別することができます。ファイルツリーは、大抵の場合、過去のファイルツリーと tree
blob
オブジェクトを共有しています。
けれども、それらのファイルツリーがどんな順番で作られたかは、ファイルツリーを見てもわかりません。
例えば A と B の2つのファイルツリーがあったとして、A にファイルを追加して B になったのか、B からファイルを削除して A になったのかは、ファイルツリーからでは判別が付きません。
「いつ、だれが、なぜファイルツリーを変えたか」を管理するのは、 commit
オブジェクトの役割です。commit
オブジェクトは、次のようにテキストで書かれています。
tree 3db13f863b338b2dbaa12f564a2a63841396e59c parent 44c68b1afc3ae211b248a9385f89b5061cbf2f94 author KOSEKI Kengo <koseki@gmail.com> 1370766628 +0900 committer KOSEKI Kengo <koseki@gmail.com> 1370766628 +0900 ビスケットが増える重大なバグを修正した
commit
オブジェクトには、
tree
…… 1つのtree
オブジェクトの IDparent
…… 0個以上の親commit
オブジェクトの IDauthor, committer
…… 変更・コミットしたユーザの名前、メールアドレス、変更日時- コミットメッセージ
などが書いてあります。
commit
オブジェクトは、parent
に書かれた ID でコミットツリーを形成します。
tree
オブジェクトとは逆に、commit
オブジェクトは子が親を参照します。親は自分の子を知りません。
Git は、新しいコミットから古いコミットへとさかのぼり易く、その逆は難しい仕組みになっています。子が親を見つけるのは簡単ですが、全オブジェクトを読まない限り、親が全ての子を見つけることはできません。
Github を見ると、「前のコミット (parent)」にはリンクしていても、「次のコミット」へのリンクは見当たらないことがわかります。
6. リファレンスとブランチ
Git を理解する上で、commit
オブジェクトとリファレンスを区別することは、とても大切です。
commit
オブジェクトは、コミットツリーを形成し、一度作られたら決して変化せず、そう簡単には消えません。
一方、リファレンスは、コミットツリーの上を動き回る動的なデータで、頻繁に書き換えられ、不要になったら削除されます。
ブランチ
Git のブランチは、ブランチの先頭を指すリファレンスと、そのリファレンスから辿れる Git オブジェクトの集合で出来ています。
リファレンスが指すのは、必ずしもコミットツリーの枝の先端とは限りません。リファレンスは、コミットツリーのどこか特定の位置を指します。ツリーの途中や根元が枝分かれしていることもあります。
ブランチ先頭を指すリファレンス
ブランチの先頭を指すリファレンスは .git/refs/heads
ディレクトリに保存されます。
たとえば master
ブランチの先頭を指すリファレンスは、.git/refs/heads/master
ファイルに保存されます。リファレンスには commit
オブジェクトの ID が、テキストで書いてあります。
20baf26a647734601c3a02e20ee128bc26600c29
ブランチ先頭のリファレンスは、commit
や pull
コマンドで書きかわります。また、reset
コマンドを使って、自分で好きな位置に書き換えることができます。
リファレンスからたどって、
- リファレンス →
commit
commit
→ 親commit
commit
→ ルートtree
- ルート
tree
→tree
blob
といった具合に、芋づる式にオブジェクトを引き出せます。
リファレンスがないと、糸口になる commit
を見つけるのは難しくなってしまいます。Git オブジェクトは、外見からは区別が付かないからです。
リファレンスや他のオブジェクトから参照されなくなったオブジェクトは、2週間程度で削除候補になります。*1
参照されなくなったオブジェクトを削除したり、オブジェクトを `Pack` 形式にまとめ直す処理を gc、ガベージコレクション (ゴミ回収) と呼びます。`gc` は、リポジトリが乱雑になってくると自動で実行されます。また、$ git gc
コマンドを使って、手動で実行することもできます。
HEAD リファレンス
今チェックアウトしているブランチは .git/HEAD
という特別なリファレンスに記録されています。HEAD
リファレンスは git checkout
コマンドで書き換わります。
HEAD
は、通常は、リファレンスを参照するリファレンスです。
ref: refs/heads/master
ただし例外もあります。HEAD
が、他のリファレンスではなく、直接 commit
オブジェクトを参照している状態です。これを、detached HEAD (分離ヘッド) と呼びます。
detached HEAD
detached HEAD は、git checkout
時に commit
オブジェクトの ID を指定したりすると発生します。例えば、
$ git checkout 20baf26
とすると、.git/HEAD
ファイルには
20baf26a647734601c3a02e20ee128bc26600c29
と ID が書かれた状態になります。これが detached HEAD です。detached HEAD に入ると、
Note: checking out '20baf26'. You are in 'detached HEAD' state. You can look around, make experimental changes and commit them, and you can discard any commits you make in this state without impacting any branches by performing another checkout. If you want to create a new branch to retain commits you create, you may do so (now or later) by using -b with the checkout command again. Example: git checkout -b new_branch_name
といった警告がでます。
detached HEAD 状態でもコミットはできます。ただし、HEAD 以外にそのコミットを参照するリファレンスが無いので、別のブランチをチェックアウトして HEAD を書き換えると、コミットはどこからも参照されなくなります。
どこからも参照されないオブジェクトは、放っておくと、いずれガベージコレクションで消されてしまいます。
detached HEAD 中のコミットを残すには、他のブランチにマージしたり、新たにブランチを作成する必要があります。
7. 2種類のタグ
Git には2種類のタグがあります。軽量タグとアノテートタグです。
軽量タグは、コミットを直接参照するリファレンスです。ブランチと同じ、ただのリファレンスなので、名前以外の情報を持ちません。
アノテートタグは、tag
オブジェクトを指すリファレンスです。tag
オブジェクトは commit
オブジェクトを参照します。tag
オブジェクトには、tag
を作った日付やユーザ名、コメントなどを含めることができます。
8. 一時待避 (stash)
Git には、まだコミットしていないファイルを一時的に退避させる git stash
というコマンドがあります。
git stash
は毎回 2つの commit
オブジェクトを作成します。
- 現在の
index
を保存するcommit
オブジェクト (親は HEAD) - 作業ディレクトリを保存する
commit
オブジェクト (親は上のコミット)
の2つです。
stash を index
に反映するには git stash pop --index
のように --index
オプションを指定します。
2つ作られた commit
オブジェクトの内、後者を指すリファレンスが .git/refs/stash
ファイルに保存されます。
.git/refs/stash
に記録されるのは、一番最近の stash だけです。昔の stash は、ログ .git/logs/refs/stash
に記録されています。
(続きます)
GitHub for Mac と Git コマンドで .gitignore の挙動が異なる件について (聞いてみた)
Git は、1.8.4 から .gitignore に /**/ のようなパターンを書けるようになっています (正式にサポートされたようです)。
Uses of the platform fnmatch(3) function (many places in the code,
https://github.com/git/git/blob/maint/Documentation/RelNotes/1.8.4.txt
matching pathspec, .gitignore and .gitattributes to name a few)
have been replaced with wildmatch, allowing "foo/**/bar" that would
match foo/bar, foo/a/bar, foo/a/b/bar, etc.
foo/**/bar
は、
foo/bar
(←注目)foo/a/bar
foo/a/b/bar
にマッチします。
/**/
の部分が無い場合 (foo 直下に bar がある場合) にもマッチする仕様です。
ところが GitHub for Mac (2014/04/22 リリース版 Your Hogward Eyes) だと、
foo/a/bar
foo/a/b/bar
にしかマッチしません。GitHub for Mac にバンドルされている git コマンドのバージョンは、
$ /Applications/GitHub.app/Contents/Resources/git/bin/git --version git version 1.8.4
で 1.8.4 と互換性がありそうなのですが…。
issue トラッカーがないので、メールフォームで聞いてみたところ、内部で使っている libgit2 が未対応とのことでした。
そのうち直りそう。
Git の仕組み (1)
目次
- はじめに
- Git の全体像
- Git オブジェクトの ID と 中身
- tree と blob オブジェクト
- commit オブジェクト
- リファレンスとブランチ
- 2種類のタグ
- 一時待避 (stash)
- インデックス
- キャッシュとしての役割
- マージ
- Fast-Forward マージ
- non Fast-Forward マージ
- rebase
- reset
- 2種類のブランチ
- 各リポジトリが自分のブランチを持っている
- 「リモートブランチ」について
- 追跡ブランチ
- ブランチの大三角形
- リモートと関係するコマンド、しないコマンド
- リモートリポジトリと refspec
- リモートリポジトリ指定
- refspec
- fetch の refspec は remotes がローカル
- コミットツリーの森、孤児のコミット
- 関係ない2つのリポジトリを混ぜる
- 混ぜたリポジトリを分離する
- bareリポジトリ
- 非 bare リポジトリに push すると何が起きるか
- 分散リポジトリ
1. はじめに
Git の難しさは独特です。
Git は、洗練されたオブジェクトモデルを、雑多なコマンド群が包み込む構造になっています。
オブジェクトモデルというのは、「Git オブジェクト」を単位としたデータ構造のことです。どのようにデータを記録してあるかが、 Git の最も魅力的な部分です。Git の内部を見ていくと、実にシンプルな仕組みで動いていることに驚きます。
一方、Git コマンドには現実の複雑さをそのまま反映しているような部分もあって、学んでいてもあまり楽しくありません。
Git は、核となるオブジェクトモデルに、これこれの操作を加えたい、という順序で発展してきており、コマンドから学び始めるのは、そもそも方向が逆なのです。
オブジェクトモデルがスタート地点で、ゴールとして雑多なコマンドがあるのに、使い始めるときはどうしてもコマンドから入門せざるを得ない。コマンドは泥臭い部分もあって、あまり楽しくない。
そこで、基本的な使い方がある程度わかったら、内部のオブジェクトモデルから入門し直すことをおすすめします。オブジェクトモデルがわかると、自然な流れでコマンドを理解できるようになります。
内部動作と実装を学ぶことは、Git がどうしてこんなに便利で有効なのかを根本的に理解するのに重要です。しかし初心者にとっては不必要に複雑で混乱を招いてしまうという人もいました。そのため、遅かれ早かれ学習の仕方に合わせて読めるように、この話題を最後の章に配置しました。いつ読むかって? それは読者の判断にお任せします。
Git - Gitの内側
私が Git を理解しようと調査した時、高級なコマンドの視点から眺めるよりボトムアップ式に理解することが役立った。そしてボトムアップ視点で見る Git がこんなにも美しくシンプルであるなら、私が調べたことを他の人も興味を持って読んでくれるのではないか、そうして私が経験した苦労を避けられるのではと考えた。
見えないチカラ: 【翻訳】Gitをボトムアップから理解する
これらの優れた記事を頼りに、この記事もまた、ボトムアップの入り口から行けるところまで行くことを目標としています。
技術的な前提知識が必要な話はほとんどありません。エンジニアでなくても十分に理解できる内容だと思います (難しかったら自分の文章力のせいです)。
Git を使ったことがない方へ
Git を全く使ったことがない方は、一度使ってみることをおすすめします。すぐれたチュートリアルや入門記事がたくさんあります。
生のデータが見たい方へ
エンジニアの方の中には、日本語の説明よりデータを見た方が早い、という方もいるかもしれません。
この記事とは別に Git Object Browser という Git の内部をブラウズするアプリケーションを作成しました。さらにそのアプリから書き出したデータに注釈を加えたサイトを以下で公開しています。
- Part1 http://koseki.github.io/git-object-browser/ja/#/step1/.git/
- Part2 http://koseki.github.io/git-object-browser/ja-bare/#/step1/.git/
Part1 は、どの Git コマンドで .git
ディレクトリのどこが変わるかについて、30のステップで説明しました。Part2 は複数のリポジトリを行き来しながら、リポジトリがお互いにオブジェクトをやり取りする様子を観察できるようにしてあります。
このサイトは、本記事とは独立した内容です。記事を読んでいてよくわからなくなったら、実際のデータを見るとイメージがわくかもしれません。
2. Git の全体像
Git リポジトリは、大きく分けると、
- 作業ディレクトリ
.git
ディレクトリ
の 2 つのディレクトリでできています。
作業ディレクトリには、Git で管理されるファイルが入っています。私たちが直接編集するのは、作業ディレクトリのファイルです。
作業ディレクトリの一番上の階層に .git
ディレクトリ があります。.git
ディレクトリがリポジトリの本体です。また、.git
ディレクトリは、そこから下が Git で管理されていることの目印にもなっています。
リポジトリから別のリポジトリを作るとき、普通は git clone
コマンドを使いますが、単に .git
ディレクトリを別のディレクトリにコピーするだけでも、新しいリポジトリを作ることができます。
.git の中身
.git
に含まれる要素で大切なのは、
- Git オブジェクト
- リファレンス
- インデックス
の3つです。ほかに補助的な要素として、
- ログ
- 設定
があります。
- Gitオブジェクトは
.git/objects
ディレクトリに、 - リファレンスは
.git/refs
ディレクトリに、 - インデックスは
.git/index
ファイルに、
それぞれ格納されています。
Git オブジェクトデータベース
Git オブジェクトの集合を Git オブジェクトデータベースと呼びます。
最初のうち、1 個の Git オブジェクトは 1 個のファイルに書いてあります。たとえば、
.git/objects/30/3ff981c488b812b6215f7db7920dedb3b59d9a
というファイルに Git オブジェクトが格納されています。
オブジェクトの数が増えてくると、複数のオブジェクトが pack 形式と呼ばれるファイルにまとめられることもあります。
1 オブジェクト 1 ファイルの形式でも pack 形式でも、 Git オブジェクトの中身は一緒です。記録方式が違うだけです。
4種類のオブジェクト
Git オブジェクトは 4 種類あります。tree
、 blob
、 commit
、 tag
の4種類です。
tree
はディレクトリ、blob
はファイルに相当します。commit
はコミット、tag
はタグです。
Git オブジェクトは、ID (オブジェクト名) で区別できます。ID は 40 文字の 16 進数 (0〜9 と a〜f) で表現されます。例えば、
0b48b1325567ae55dbfaefb250e7460e4fbb7d6d
といった具合です。
ID だけ見ても、オブジェクトの種類 (tree
、 blob
、 commit
、 tag
のどれか) は区別がつきません。4種類のオブジェクトがごちゃ混ぜに .git/objects
ディレクトリに入っています。
リファレンス
オブジェクトの他に、リファレンスと呼ばれる重要なファイルがあります。
リファレンスは、特定の commit
オブジェクト (または tag
オブジェクト) を指し示すブックマークのようなものです。
リファレンスが指し示す commit
オブジェクトからたどって、芋づる式に他のオブジェクトを引き出すことができます。
リファレンスは .git/refs
ディレクトリ以下に保存されています。
リファレンスには Git オブジェクトの ID がテキストで書いてあります。たとえば .git/refs/heads/master
というリファレンスに、
0b48b1325567ae55dbfaefb250e7460e4fbb7d6d
と書いてあったとします。この 40 文字が commit
オブジェクトの ID です。この ID を持つオブジェクトは、
.git/objects/0b/48b1325567ae55dbfaefb250e7460e4fbb7d6d
というファイルに保存されています。ID の先頭 2 文字分のディレクトリがあって、のこり 38 文字がファイル名です。
リファレンスのリファレンス
また、リファレンスには、もう一つ別の形式があります。他のリファレンスを指し示すリファレンスです。
たとえば、.git/HEAD
リファレンスには、
ref: refs/heads/master
のように書いてあります。この形式のリファレンスは、 commit
オブジェクトを指し示すのではなく、別のリファレンス .git/refs/heads/master
を指し示しています。
HEAD は特別なリファレンスファイルで、いまチェックアウトしているブランチを示します。HEAD に ref: refs/heads/master
と書いてある場合、 master ブランチがチェックアウトされています。
別の名前のブランチ (例えば example) をチェックアウトすると、HEAD は ref: refs/heads/example
と書き換わります。
大きなツリー
リファレンスと Git オブジェクトの参照関係は、全体で大きなツリー構造になっています。
この大きなツリー構造は、4層に整理することができます (各層の名前は勝手につけたものです。公式な呼び方ではありません)。
Git にコミットしたファイルやディレクトリは、① ファイルツリーに保存されます。ファイルツリーには tree
オブジェクトと blob
オブジェクトでできています。
コミットのたび、ファイルツリーを1つぶら下げた commit
オブジェクトが ② コミットツリー に追加されます。commit
オブジェクトも、ファイルツリーと同様にツリー構造を形成します。
コミットツリーの特定の位置を ③ リファレンスが指し示します。また、④ リファレンスのリファレンス が特定のリファレンスを指し示します。
MacOSX のタイムマシンの UI がイメージしやすいかもしれません。
Mac OS X 10.5 Leopard feature video - Time ...
ファイルツリーが垂直方向に成長するとしたら、コミットツリーは奥から手前に成長します。コミットツリーのある地点を指すのがリファレンスです。
歴史が一直線にならぶ Mac のタイムマシンとは異なり、Git のコミットツリーは枝分かれしたり合流しながら成長していきます。
3. Git オブジェクトの ID と 中身
Git オブジェクトの ID は、SHA1 というハッシュ関数で生成されます。
Git オブジェクトの中身は、 Zlib という圧縮ライブラリで生成されます。
下図のように、オブジェクトの種類 + (スペース) + データのサイズ + (\0) + データ
を SHA1 関数に入力した結果が ID になり、Zlib に入力した結果が中身になります。
ハッシュ関数は、同じ入力には同じハッシュ値、違う入力には違うハッシュ値を返します。Git オブジェクトの中身が同じなら ID も同じ、中身が違えば ID も異なります。
世界中、誰が作ったどのリポジトリでも、同じオブジェクトには同じ ID が付いています。
たとえば、空の blob
の ID を Google で検索すると、こんな風に たくさんみつかります。
空の .gitignore ファイルが入った tree
オブジェクトの ID を検索すると、こんな感じです。
Git オブジェクトの ID と中身は不可分です。ある ID が付いたオブジェクトの中身だけ書き換える、というような操作はあり得ません。中身を変えると、別の ID が付いた別のオブジェクトになります。Git オブジェクトには「変更」に当たる操作は無く、「新規作成」と「削除」だけが可能です。
- 参考: Git - Gitオブジェクト
ハッシュ関数 SHA1 の簡単な説明
Git オブジェクトの ID を作るハッシュ関数について、ごく簡単に見ておきましょう。
Git で使われるハッシュ関数 SHA1 は、入力されたデータを 40 文字にして返します。 *1
この 40 文字をハッシュ値と呼びます。
ハッシュ関数は、同じ入力に対して常に同じハッシュ値を返します。例えば、
入力 | SHA1 ハッシュ値 |
---|---|
(空文字列) | da39a3ee5e6b4b0d3255bfef95601890afd80709 |
a | 86f7e437faa5a7fce15d1ddcb9eaeaea377667b8 |
b | e9d71f5ee7c92d6dc9e92ffdad17b8bd49418f98 |
c | 84a516841ba77a5b4648de2cd0dfcb30ea46dbb4 |
git | 46f1a0bd5592a2f9244ca321b129902a06b53e03 |
夏目漱石 坊ちゃん (青空文庫 XHTML版) | adf63acaf3cff383174e95477b9a37b7296afccb |
といった具合です。何百テラもあるような巨大なデータでも、SHA1 のハッシュ値は 40 文字になります。 *2
そして、このハッシュ値は、非常に重複しにくくなっています。あなたと私が同姓同名でなければ、あなたの氏名の SHA1 と、私の氏名の SHA1 は確実に異なります。
仮に、 2 つの異なる blob
オブジェクトに同じ SHA1 ハッシュ値が割り当てられた場合、2つのファイルの内 1 つが消失してしまいます。けれども、そのようなことを心配する必要はありません。
SHA1 ハッシュの重複 (衝突) しにくさについて、『Pro Git』は次のように説明しています。
たった一つの衝突が 50% の確率で発生するために必要なオブジェクトの数は約 2^80 となります (中略)。つまり一兆二千億のそのまた一兆倍です。これは、地球上にあるすべての砂粒の数の千二百倍にあたります。
地球上の人類 65 億人が全員プログラムを書いていたとします。そしてその全員が、Linux カーネルのこれまでの開発履歴 (100 万の Git オブジェクト) と同等のコードを一秒で書き上げ、馬鹿でかい単一の Git リポジトリにプッシュしていくとします。これを五年間続けたとして、SHA-1 オブジェクトの衝突がひとつでも発生する可能性がやっと 50% になります。それよりも「あなたの所属する開発チームの全メンバーが、同じ夜にそれぞれまったく無関係の事件で全員オオカミに殺されてしまう」可能性のほうがよっぽど高いことでしょう。
Git - リビジョンの選択
という 3 点だけ覚えておいてください。
4. tree と blob オブジェクト
Git に追加したディレクトリやファイルは、tree
オブジェクトや blob
オブジェクトとして、オブジェクトデータベースに保存されます。
tree
はディレクトリ、blob
はファイルに相当します。
ただし、tree
は自分のディレクトリ名を知りません。blob
は自分のファイル名を知りません。
blob
オブジェクトには、ファイルの中身がそのまま書いてあります。
tree
オブジェクトには、次のように、あるディレクトリに含まれるファイル名・サブディレクトリ名と、そのオブジェクト ID が一覧で書かれています。
100644 aaa.txt \0 72943a16fb2c8f38f9dde202b7a70ccc19c52f34 100644 bbb.txt \0 f761ec192d9f0dca3329044b96ebdb12839dbff6 100644 ccc.txt \0 b2a7546679fdf79ca0eb7bfbee1e1bb342487380 40000 ddd \0 0d546c66a969f7b205bf2fd450ef5e4b9efb70bf 100755 eee.sh \0 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 120000 fff-symlink \0 35a6b12323dd0e2f9a2a3e5abe291920151ede4a
先頭の数字は、
- ファイルの種類
- 40:ディレクトリ
- 100:ファイル
- 120:シンボリックリンク
- 160:サブモジュール
- 実行可能ファイルかどうか
- 644 実行不可
- 755 実行可
を示しています。
空白の後にファイル名があり、ヌルバイト(\0
)が挟まった後、最後に blob
または tree
オブジェクトの ID が (本当はバイナリで) 書いてあります。
tree
に記録されている情報はこれだけです。ファイルのタイムスタンプや所有者のような情報は、blob
にも tree
にも書かれていません。あるファイルを誰がいつ作ったかは、tree
や blob
を見てもわかりません。
blob
はファイルを git add
したときに作成されます。一方、tree
は git commit
した時に作成されます。このタイミングの違いについては、後でインデックスについて説明する時に詳しく見ます。
tree と blob の参照関係
あるディレクトリに同じ中身のファイル A.txt と B.txt があった場合、blob
オブジェクトは1個だけ作成されます。
tree
オブジェクトは以下のようなデータになります。ファイルの中身が同じなので blob は同じ 72943a1...
を参照しています。
100644 A.txt \0 72943a16fb2c8f38f9dde202b7a70ccc19c52f34 100644 B.txt \0 72943a16fb2c8f38f9dde202b7a70ccc19c52f34
ファイル B.txt を別のディレクトリに移動すると、移動元と移動先の tree
が更新されます。blob
は元のままです。
tree 1
100644 A.txt \0 72943a16fb2c8f38f9dde202b7a70ccc19c52f34
tree 2
100644 B.txt \0 72943a16fb2c8f38f9dde202b7a70ccc19c52f34
ルートツリーの ID でツリー全体を識別する
tree
や blob
を変更すると、新しいオブジェクトが作られます。
例えば、あるファイルを書き換えて、git add
すると、新しい blob
オブジェクトが、Git オブジェクトデータベースに追加されます。
さらに、blob
が更新されると、そのオブジェクトを含む親 tree
オブジェクトも更新されます。tree オブジェクトには、子の ID が書き込まれているからです。
例えば、次のような tree オブジェクトがあったとして、
100644 A.txt \0 72943a16fb2c8f38f9dde202b7a70ccc19c52f34 100644 B.txt \0 72943a16fb2c8f38f9dde202b7a70ccc19c52f34
A.txt の内容が書かれた blob
が入れ替わると、
100644 A.txt \0 f761ec192d9f0dca3329044b96ebdb12839dbff6 100644 B.txt \0 72943a16fb2c8f38f9dde202b7a70ccc19c52f34
と tree
が変化します。同様に、子の tree
が変わると、親 tree
も変わります。
変化は親へ親へと伝播して、最終的に、一番上のルート tree
オブジェクトが入れ替わります。
どれほど巨大なファイルツリーであっても、末端のどこか一箇所ファイルを書き換えると、変化は親をさかのぼってルートの tree
ID に反映されます。つまり、ルート tree
ID は、ファイルツリー全体を一意に識別する ID になっています。
ルート tree
オブジェクトの ID さえあれば、他のファイルツリーと混同することなくファイルツリー全体を取り出すことができます。
新しいファイルツリーによって、古いファイルツリーが影響を受けることはありません。新しいファイルツリーが追加された後でも、古いルート tree
オブジェクトからたどって、古いファイルツリーを取り出すことができます。
ファイルツリーは、過去のファイルツリーと tree
blob
オブジェクトの大半を共有しつつ、差分だけを新しいオブジェクトで置き換えて、増えていく仕組みになっています。
(続きます)
*1:正確には 16 進数文字列で40文字、160ビットの値です。
*2: SHA1 は最大で 2 ^ 64 - 1 = 約 16 エクサバイトまでの入力を計算できるそうです。 http://ja.wikipedia.org/wiki/Secure_Hash_Algorithm
Vagrant を使って Mac からダブルクリックで操作できる仮想環境を作る
この記事を公開する直前に Vagrant Cloud がはてブに上がってるのを見て、今あわてて追記しています。すごい楽しいことになってますねー。
ブログ記事をざっと読んだ感じだと、Vagrant 1.5 の新機能は、
- VagrantShare - まるで公開されてるサイトみたいに VM の URL をチームメンバーに見せる
- Box を koseki/centos みたいな短い名前で指定できるように。バージョン管理
- rsync と SMB でフォルダ同期
- Hyper-V - MS の仮想環境に対応
- insecure-private-key の代わりに SSH でパスワードが使えるように
- プラグイン管理
- Funtoo, NetBSD, TinyCore Linux のゲストに対応。ネットワーク設定などができるように
特にウェブ開発をやっていると、VagrantShare が大きそう。使ってみるのが楽しみです。
VagrantCloud は、Box のリポジトリと VagrantShare の仲介をやってくれるみたい。コミュニティ向けには無料、ということなので、パブリックな Box のホスティングは無料ということなんでしょうか。すばらしい。
というわけで本題。メジャーリリースの 1.5 でも後方互換は保たれているということなので、書いてた記事をそのまま公開です。ぽちり。
マウス操作で仮想環境
サーバ上で実行したい各種タスクを Mac からダブルクリックで起動できる、仮想環境のサンプルを作りました。VirtualBox + Vagrant を使います。
デザイナやフロントエンドエンジニアが、本番と同様のレスポンスを確認しながら開発できるように、手軽な仮想環境のセットアップ方法がほしい、というのがそもそもの動機です。
Vagrant で、やりたい事はほとんど実現できていましたが、ターミナルでコマンドを打たずに済むように、いくつか工夫しています。
デモ
- VirtualBox と Vagrant をインストール
(インストーラで簡単に入ります。Ruby を入れたりする必要はありません) - リポジトリを
git clone
またはダウンロード start.command
をダブルクリックしてしばらく待つと、VirtualBox の CentOS 上で Nginx, Rails, MySQL が起動します- http://192.168.33.10:8080/ にアクセスして Rails が起動しているのを確認します
VM が、app/sample
以下に Rails アプリケーションを生成します。Mac でファイルを書き換えて、VM で確認できます。DB は VM 上で動いています。
構成
こんな感じになっています。
サーバの詳細を知る必要がない開発メンバーには、以下のように見えてほしい。VM が、プロジェクトのファイルを読んでブラウザに応答する一個のアプリケーションとして振る舞うようにします。
プロジェクトの動作環境を VM にカプセル化すると、別のプロジェクトに干渉する心配がなくなります。開発が終わったら VM を捨てれば元通りです。
また、VM と同じ設定でローカルでもサーバが動くようにします。エンジニアは Mac で直接サーバを動かした方が便利そうです。
エンジニアが自分が使っていない設定はメンテされなくなってしまうので、VM でもローカルでもなるべく同じ設定を使います。
ディレクトリ構成
. ├── LICENSE ├── README.md ├── app │ └── sample …… VM が Rails を生成する空のディレクトリ ├── htdocs │ ├── index.html │ └── static │ └── index.html └── sandbox …… 設定の本体。プロジェクトのルートに、この sandbox を足して使うイメージです ├── Procfile ……foreman の設定。Nginx と Rails をまとめて起動 ├── bin │ ├── halt.command …… VM をシャットダウンします │ ├── reset_db.command …… VM に SSH して local/reset_db.command を起動 │ ├── sample.command …… VM に SSH して local/sample.command を起動 │ ├── ssh.command …… VM に SSH します │ ├── start.command …… VM に SSH して local/start.command を起動 │ ├── status.command …… VM の起動状態を表示します │ ├── up.command …… VM を起動します │ ├── local …… VM またはローカルで実行するシェルスクリプト │ │ ├── reset_db.command …… DB をリセットします。rake db:setup を実行 │ │ ├── sample.command …… rails server を実行 │ │ └── start.command …… foreman start を実行 │ └── provision.command …… プロビジョニングを実行 ├── conf …… サーバ設定。このサンプルでは Nginx の設定が入っています │ ├── mime.types │ └── nginx.conf ├── logs ├── tmp └── vm ├── Vagrantfile …… Vagrant 設定 ├── provision.sh …… プロビジョニングスクリプト。Mac 上で実行されることはないので .sh です └── uninstall.command …… VM をアンインストールします
コマンドファイル
OSX で、シェルスクリプトの拡張子を .command
にして実行権限を付けると、ダブルクリックで実行できるようになります。
*.command
ファイルを使って、vagrant
コマンドを実行します。たとえば VM を起動する up.command
は、
#! /bin/sh DIR=`dirname $0` cd $DIR/../vm vagrant up
のようになっています。
VM に SSH 接続してシェルスクリプトを起動するコマンド
vagrant ssh -c 'コマンド'
で、VM に SSH してコマンドを実行することができます。Vagrant Box がパスフレーズ無しでログインできる sshd を起動してくれるおかげで、SSH 経由で簡単に VM を操作できます。*1
この仕組みを使って、ダブルクリックで VM 上のシェルスクリプトを起動するコマンドを作ります。
たとえば、
は、全て同じ中身になっています。
#! /bin/sh DIR=`dirname $0` NAME=`basename $0` echo "--- VM ---" cd $DIR/../vm vagrant up vagrant ssh -c "/vagrant/sandbox/bin/local/$NAME"
vagrant up
で VM を(起動していなかったら)起動し、local ディレクトリ以下の同名のスクリプトを実行します。
最後の行で SSH してシェルスクリプトを実行しています。プロジェクトのルートを /vagrant にマウントしているので、/vagrant/sandbox/bin/local
は、このディレクトリが見えています。
local
以下の *.command
ファイルがスクリプトの本体です。たとえば local/start.command
は、
#! /bin/sh set -e DIR=`dirname $0` cd $DIR/../.. foreman start
のようになっています。
Foreman でサーバをまとめて起動・終了する
Foreman は最近教えてもらって知ったのですが、簡潔ですごくいいアプリケーションでした。OSに標準でついててほしい。
Foreman が Procfile を読んで、Nginx と Rails を起動します。
nginx: nginx -p . -c conf/nginx.conf rails_sample: bin/local/sample.command
start.command
をダブルクリックすると、Nginx と Rails が起動し、ターミナルにログが流れます。Ctrl-C
で起動したサーバをまとめて停止します。
サーバの設定を読み込み直すときは、Ctrl-C
で止めてダブルクリックで再起動です。
Procfile
は、Mac 上でサーバを起動する時にも使えます。
$ cd sandbox $ foreman start
プロビジョニング
サーバの構成は、開発が進むにつれて変化していきます。環境が変わるたびに VM をセットアップし直しになっては困ります。
このサンプルでは、シェルスクリプトでプロビジョニングする Vagrant の機能を使っています。
config.vm.provision :shell, :path => 'provision.sh'
これで、初回の起動時と、provisioning.command
をダブルクリックした際に、VM で provision.sh が実行されます。
Vagrant は path で指定されたファイルを VM にアップロードして実行するだけのようです。
プロビジョニングのたびに毎回同じスクリプトが実行されるのでは使いにくいので、自前で簡単なバージョン管理を実装します。
# バージョン番号を記録するファイル VERSION_FILE=/root/.provisioning_version # バージョンファイルがあったら CURRENT_VERSION に読み込む。なかったら 0 if [ -f $VERSION_FILE ]; then CURRENT_VERSION=`cat $VERSION_FILE` else CURRENT_VERSION='0' fi
のようにして、最後に実行したプロビジョニングのバージョンを読み込みます。あとは以下のようにして、バージョン番号が付いたブロックを足していきます。
VERSION=1 if [ $CURRENT_VERSION -lt $VERSION ]; then echo "--- $VERSION ---" # 何かする。 # バージョンファイルに $VERSION を書き込み echo $VERSION > $VERSION_FILE fi
ファイルに記録された $CURRENT_VERSION より $VERSION の方が数字が大きかったら処理を実行します。
原始的ですが、開発サーバ向けにはこの程度でも良いのかなという気がしました。セキュリティの要件などが全然違うので、Chef などを使って本番と同様のプロビジョニングを実行するのは大変そうです。
あるいは、一般的な構成なら Vagrant Box を公開してもいいかもしれません。プロジェクトを /vagrant
にマウントして使うため、VM にはプロジェクトに関わるファイルを一切含めないことが可能です。
Nginx
Nginx は sandbox/conf/nginx.conf を直接読んで起動します。
nginx -p . -c conf/nginx.conf
daemon off;
でデーモン化せず、ターミナルにログを書きながら実行します。
sendfile off;
を設定しないと、共有フォルダのファイルが壊れて見える場合があるようです。
- http://httpd.apache.org/docs/2.2/mod/core.html#EnableSendfile
- http://docs-v1.vagrantup.com/v1/docs/config/vm/share_folder.html
Nginx に限らず、サーバはできるだけプロジェクトに入っている設定ファイルを直接読んで起動した方がいいと思います。/etc
にコピーなどしてしまうと、設定をかえるたびに毎回プロビジョニングの実行が必要になってしまいます。設定ファイルがプロジェクトに入っていれば、再起動するだけで済みます。
VM を使わずに Mac 上でサーバを起動する
$ cd sandbox $ foreman start
でサーバ一式を起動することができます。また、sandbox/bin/local/*command
ファイルをダブルクリックしてローカルで実行できます。
Docker
この構成でやる前は Docker を試していました。0.8 から Mac がサポートされていますが、まだファイル共有がスムーズにいかないようです。
VirtualBox (boot2docker) + Docker の2階建ては、まだちょっとローカルから直に触れていない印象でした。将来的には Docker に切り替えるのもありかと思います。