eyecatch
Sat, Nov 19, 2016

Bash on WindowsでWindows側からUbuntu側のファイルをいじると壊れることがあるので注意

Bash on WindowsでWindows側からUbuntu側のファイルをいじると危険という情報を見つけたので、試してみたら確かに困った状態になった話。 Bash on Windowsとは Bash on Windows (aka BoW)は、2016/8/3に公開されたWindows 10 Anniversary Updateで使えるようになった、Windows上でBashが使えるようになる機能。 POSIX APIのWindows実装を提供するCygwinなどとは違い、WindowsのサブシステムとしてUbuntuが動き、その上でBashが動き、そこからUbuntu用のバイナリをそのまま利用できるというもの。 2016/11/17現在でまだベータ版の機能。 Windows側からUbuntu側のファイルをいじると壊れる問題 Microsoftの中の人のブログに、BoWがセットアップされた環境で、Windows側からUbuntu側のファイル(i.e. %localappdata%\lxss\以下のファイル)をいじると壊れるという話があった。 いかにもやってしまいそうな操作で危険だし、実際このブログの人はこれに関する問い合わせに毎日1,2件対応しているそうな。 原因は上記ブログに詳しいが、簡単に言うと、Windows側のプロセスがUbuntu側のファイルを作ったり編集したりする際、パーミッションなどのメタデータを適切に設定しないため、Ubuntu側でファイルが壊れたと判断されてしまうから。 こうなると、結果としてファイルが消えてしまったり、壊れたデータで上書きされてしまったりするとのこと。 因みに、Ubuntu側からWindows側のファイルをいじるのは問題ないらしい。 再現確認 そういえばまだBoWをさわったことがなかったので、セットアップして件の問題を体験してみた。 環境は、VMware Player 7.1.0で作ったVMに評価版のWindows 10 Enterprise v1607をインストールしたもの。 セットアップは公式の手順に従うだけ。2ステップだけの簡単な手順。 セットアップ後、コマンドプロンプトでbashとうつとBoWが起動する。(初回はインストール処理が走り、十数分待たされる。) [コマンドプロンプト → Bash] 再現確認に使うのはhogeと書いたhoge.txt。 これをWindows側のC:\Users\kaitoy\Desktop\とUbuntu側の/home/kaitoy/に置く。 [コマンドプロンプト] [Bash] Windows側からは、Ubuntuのファイルシステムが%localappdata%\lxss\にマウントされているように見える。 (lxssはエクスプローラーのオプションから「保護されたオペレーティングシステムファイルを表示しない(推奨)」のチェックをはずさないと見えない。見えなくてもアドレスバーにパスを入力すればアクセスできるけど。) 一方Ubuntu側からは、WindowsのCドライブが/mnt/cにマウントされているように見える。 [Bash] ここで、コマンドプロンプトを開き、%localappdata%\lxss\hoge\kaitoy\(i.e. Ubuntu側の/home/kaitoy/)にcdし、hoge.txtをechoで編集してみた。 [コマンドプロンプト] したらBashから見えなくなった。アクセスしようとすると「Input/output error」というエラーになる。これが件の現象か。 [Bash] エクスプローラからは見えていたので、GUIで%localappdata%\lxss\hoge\kaitoy\hoge.txtを削除したら正常な状態に戻った。 再度同じhoge.txtを作り、今度はメモ帳で編集して内容をfooに変えてみた。 この場合は特に問題なし。なぜだ? [Bash] 例のブログをよく読むと、実際に問題になるのはファイルの作成だけのように読める。 編集しているようにみえても、アプリによっては新規ファイルを作って既存のを置き換えていることがあるから、編集もするなと言っている模様。 メモ帳は実際に編集しているから大丈夫だったということか。 今編集したhoge.txtを今度はエクスプローラから消してみる。 Ubuntu側からは消えてないように見えるが、アクセスしようとするとないと言われる。 [Bash] エクスプローラのビューをF5で更新したら、今消したはずのhoge.txtが復活した。 これをダブルクリックで開こうとしたら「Access is denied.」。 エクスプローラから何度消してもすぐ復活する。 Bashで消そうとしても「Permission denied」。詰んだ。 [Bash] ということで、むしろWindows側からUbuntu側のファイルを消すのがもっともやばいと言うことがわかった。 lxrun /uninstall /full、lxrun /installでUbuntuイメージをインストールしなおさないと直らない。 最後に、Ubuntu側(i.e.
eyecatch
Sat, Oct 8, 2016

git checkoutを図解する

この記事を読んだ、またはGitのオブジェクトモデルを理解していることを前提に、Gitの git checkout というコマンドについて説明する。 このコマンドは普通ブランチを切り替えるものと説明されるが、主たる機能は オブジェクト格納領域から指定されたファイルを取り出し、ワーキングディレクトリに配置する ものである。 つまりこれがGitにおけるチェックアウトで、チェックアウト=ブランチの切り替えではない。 コマンドに与える引数によっては HEAD の付け替え、つまりはブランチの切り替えもする、というだけ。 git checkout の動作を HEAD の付け替えの有無によって分けて考えると分かりやすく覚えやすいので、以下そのように説明する。 HEADを付け替えないgit checkout HEAD を付け替えない git checkout は、引数にワーキングディレクトリ内の ファイルまたはディレクトリへのパスを与えた場合 のもの。 ディレクトリを指定した場合はそれ以下の全ファイルが操作対象となる。 パスは絶対パスかカレントディレクトリからの相対パスで、複数指定できる。 つまりは以下の様なコマンド形式になる。 git checkout <パス(複数可)> これを実行すると、指定したファイルについて、インデックスが指しているブロブ をオブジェクト格納領域から取り出し、ワーキングディレクトリのファイルを置き変える。 上のスライドではインデックスが指しているブロブを取り出したが、任意のブロブを取り出すこともできる。 この場合、以下の様なコマンド形式を使う。 git checkout <コミット> <パス(複数可)> このコマンド形式だと、指定したコミットが指すツリー以下のブロブ が取り出される。 <コミット>の部分には、コミットオブジェクトのSHA1ハッシュ値、参照(i.e. ブランチかタグ)、シンボリック参照(e.g. HEAD)を指定できる。(実際にはこれらが全てではないが、実用的にはこの3種。) この形式だと、ワーキングディレクトリだけでなく、取り出すブロブを指すよう インデックスも更新される ことに注意。 HEADを付け替えるgit checkout HEAD を付け替える git checkout は、引数に パスを与えない場合 のもの。 代わりにコミットを与える。 つまりは以下の様なコマンド形式になる。 git checkout <コミット> <コミット>の部分には、コミットオブジェクトのSHA1ハッシュ値、参照(i.e. ブランチかタグ)、シンボリック参照(e.g. HEAD)を指定できる。(実際にはこれらが全てではないが、実用的にはこの3種。) これを実行すると、指定したコミットが指すツリー以下の全てのブロブ を指すようインデックスを更新し、それらのブロブをオブジェクト格納領域から取り出してワーキングディレクトリに配置する。 この上更にHEADを付け替えるわけだが、付け替え先は<コミット>の種類によって以下の三通りある。 <コミット>がブランチ: HEADはそのブランチを指すよう更新される。 <コミット>がSHA1ハッシュ値:
eyecatch
Thu, Oct 6, 2016

Gitの良さが分からない? ちょっとそこに座れ

Gitの良さがいまだに分からないという人がいるようなので、Git派の一人としてSubversion(以下SVN)と比較してのGitの良さ(メリット)について語りたい。 (GitとSVNの違いについては他の人の記事に詳しいのであまり書いていない一方、勢い余ってGitのデメリットも書いた。) 本題に入る前に、冒頭にリンクを貼った記事についてひとつだけつっこんでおく。 つっこみどころは他にも沢山あるけど。 ※話の前提としてgitとSVNを採用している現場に下記のような割と違いがあるとする。 git イシューごとにブランチを切り、ローカルでコミットして、リモートブランチにpushして、GitHub・GitLab・Bitbucket経由でマージリクエスト。コードレビューの後にマージ。 SVN リモートのtrunkに個々人が直接コミット。コードレビューはあまりない。ブランチを切ることもない。 このような違いが出る背景には次のものがある。 gitを採用する現場は、猫も杓子もgit-flowというプラクティスに従う傾向がある gitを採用する現場は、コードの品質もある程度管理する傾向がある SVNは集中型でありブランチ機能などが非常に使いにくい SVNを採用する現場はコードの品質よりも「リリースに含めるならさっさとコミット」と考える傾向がある この前提には無理がある。 Gitのところに書いてあるのが、Gitというツールの枠を大きくはみだしたGitHub Flowというブランチ戦略+開発プロセスに当たるものであり、 それでGitを批判するのはお門違いであろうという点については、Gitの流行がGitHubの人気によるところが大きく、GitHubを使えることがGitの大きなメリットであるので、目をつむることにする。(マージリクエストを使う羽目になるデメリットなんて言いがかりでしかないとだけ言っておく。) 看過できないのは、SVNを使った開発がコードレビューもブランチもないという点。 どこの世界の話をしているんだろうか。 Gitが世に出る前は世間にコードレビューもブランチもあまりなかったかのような前提だが、もちろんそんなことは全くない。 60万個以上のOSSプロジェクト情報を統括するOpen HUBによれば、OSSプロジェクトの46%がSVNを使っている。この中にはGitの誕生以降にSVNを使い始めたプロジェクトも多くある。270000余りのプロジェクトの大部分がブランチすら使っていないとでも? GitHub Flowと対比するために無理やりこじつけたんだろうけど、その無理のせいで議論のスタート地点からめちゃくちゃだ。 まともな開発にはコードレビューもブランチも必要だ。 品質管理もリリース管理もしないなら要らないのかもしれないが、そんないい加減な開発現場を前提にSVNかGitかなんて議論しても意味がない。 高品質なソフトウェアを効率よく開発するために則りたい素晴らしい開発フローがあるとして、そのフローをSVNやGitやその他のツールないしひょっとしたらアナクロな日付フォルダの内どれがもっとも上手く実現してくれるか、というのがあるべき議論だ。 この「素晴らしい開発フロー」には一般的に品質管理と並行開発が含まれていて、それらにはコードレビューとブランチの利用が含まれている。 Git(+GitHub)がこんなにも急速にSVNに取って代わって流行ったのは、分散リポジトリの仕組みとブランチの軽量な実装によって効率的な並行開発が実現でき、またプルリクエストなどの機能によりコードレビューを含む快適なソーシャルコーディングが実現できるからだ。 逆に言えば、Gitが流行ったことが、人々が効率的な並行開発やコードレビューを開発フローに取り入れたかった証拠と言えるかもしれない。 Gitのメリット 前置きが長くなったが、少なくともブランチとコードレビューを活用した高品質で高効率なソフトウェア開発をしたいという前提で、SVNに対するGitのメリットを挙げてみたい。 1. リポジトリ構造がシンプル Gitリポジトリはすごくシンプルに作られているそうな。 確かに、その構造を見ると、add、commit、log、resetくらいは自前ですぐに実装できそうだ。 このシンプルな構造のおかげで、Gitリポジトリは壊れにくい。ここで壊れにくいとは、リポジトリ内部で不整合が起こりにくいということで、コマンドミスでコミット履歴が一部消えたりとかいうトラブルは壊れるに入らない。 実のところSVNリポジトリの構造を知らないので経験的なことしか言えないが、SVNリポジトリ(というより作業ディレクトリの管理情報?)はちょくちょく変な状態になり、クリーンアップしたり、酷い時には.svn内のファイルを手動でいじったりしなければならなかった。 因みに、シンプルというのはリポジトリサイズがすごく小さいということにはならず、同等の履歴を含むGitリポジトリとSVNリポジトリはだいたい同サイズなんだそうな。 2. ブランチが軽い Gitのブランチは単一のコミットを指す参照で、リポジトリ内ではSHA-1ハッシュ値が書かれただけのたった一つのファイルに過ぎない。 その為ブランチは一瞬で作成できるし、ディスクも圧迫しないので、じゃんじゃん作ってじゃんじゃん消せる。 さらに、ローカルリポジトリに過去の全ファイルの全バージョンが入っているという分散リポジトリの特長のおかげで、ブランチの切り替えも軽快にできる。 ローカルから必要なファイルを作業ディレクトリに展開するだけなので。 一方SVNはそもそもブランチをサポートする直接的な機能がないため、ブランチはリビジョンのコピーという形で実装されている。 コピーと言ってもハードリンクみたいなものでディスク上に物理的なコピーが作られるわけではなく、軽量という点ではGitと大差ないが、集中リポジトリなせいでブランチの切り替えには差が出る。 svn switchにしろsvn checkoutにしろネットワークの向こうのサーバとの通信が必要なので、それなりの時間がかかるし、通信が途切れると切り替えられなくなる。 冒頭に貼った記事にはGitはブランチを切り替える際にstashとかしないといけなくて面倒とあったが、そんなのSVNだって同じだし、stashすればいいだけだし、stashという機能があるだけSVNよりまし。Gitならコミットはあとから書き変えられるので、stashの代わりに一時的にコミットしちゃってもいい。 それも嫌ならworktree使えばよろしい。 3. バージョン間の差分取得が速い Gitは全てのファイルについて全てのバージョンのコンテンツをまるまるリポジトリに持っている。 一方SVNのリポジトリにはバージョン間の変更が記録されている。 このため、あるファイルについて任意のバージョン間の差分を取るのに、Gitはシンプルにそれぞれのバージョンのファイルを取り出して比較するだけでよいが、SVNは隣り合ったバージョンでなければバージョン間の変更を足し合わせて差分を計算しなければいけない。 さらに、Gitは比較するファイルをローカルリポジトリから取り出すだけでよいが、SVNはサーバへのアクセスが必要なので、差分取得はGitの方が大分速い。 4. ログ取得が速い Gitのコミットは常にプロジェクトの全ファイルに対するものだ。 これは変更したファイルの一部だけを対象とするコミット操作ができないという意味ではない。 Gitがひとつのコミット操作をコミットオブジェクトと呼ばれる単一のファイルに記録し、そのファイルが常にプロジェクトの全ファイルの特定のバージョンを参照しているという意味だ。(正確に言うとこのファイル自身に全ての参照が記録されているわけではないが。) このためGitのコミット履歴は実にシンプルで、ログ一覧を取得するには単にコミットをたどりながらコミットオブジェクトに書かれたログを集めればいい。 一方SVNはファイル毎にバージョンを管理するので、もう少しややこしい。 さらに、Gitはコミットオブジェクトをローカルリポジトリから持ってこれるがSVNは(以下略)。 5. オフラインでだいたいなんでもできる と、ここまで書いて、Gitのいいところはオフライン作業が捗るところではないかと思い立った。 実際Gitは、clone、fetch、pull、pushといったあからさまな操作以外はオフラインでできる。 多くの操作にネットワーク通信コストを払わなくていい上、リモートリポジトリサーバが落ちたりネットワークが落ちたり山に籠ったりしていても作業が続けられる。 ノマドに最適。 一方SVNがネットワーク通信なしでできることは、…ベースバージョンとのdiffくらい?