GitのUntracked cacheという機能
執筆時点のGitのバージョン
Git 2.8.2
Untracked cache
GitのUntracked cacheとは、おおざっぱに言うと、ワーキングツリーのファイル構成が変化したかどうかを識別する方法を簡略化させて、Gitの動作を高速化させる機能。これをオンにすると例えばgit status
が速くなるらしい。
具体的には「ディレクトリの」mtime(最終更新日時)だけを見てファイル構成の変化を察知することで高速化を図る(ということは普段は違うのか)。ドキュメントの「Untracked cache」の節を読むと「この機能は、ワーキングツリーのディレクトリのmtimeを記録しておき、mtimeに変化がないディレクトリは読み飛ばして、その中のファイルに対するstat callを省略する」と書かれている。
ディレクトリのmtimeの変化
一応整理しておくと、*nixやOS Xのファイルシステムでは、ディレクトリのmtimeは以下の時に変化する。
- そのディレクトリの中にファイルが追加される。
- そのディレクトリの中のファイルが削除される。
- そのディレクトリの中のファイルがリネームされる。
けれど、
- そのディレクトリの中のファイルの中身が変更される。
の場合は、ディレクトリのmtimeは書き変わらない。だからこの機能は恐らくファイル内容の変化を識別することについては関係がない(ハズ)。あくまでファイルの追加/削除を調べるのが高速化される。
Untracked cacheを有効化できるか
速くなるならとりあえず有効化してみるか、と思いきや実は罠があり、OSやファイルシステムによってはディレクトリのmtimeがGitが想定した動作をしない場合がある。そういう場合はUntracked cacheを有効化するとマズイ(例えばファイル構成の変化を誤判断する)ので、有効化しても大丈夫なのか事前にテストしなければならない。テストにはgit update-index --test-untracked-cache
を使う。
自分の所(OS X 10.11.4 El Capitan、HFS+)で実行してみると、
$ git update-index --test-untracked-cache Testing mtime in '/path/to/repo' ...... OK
となり有効化できるようだ。
どういう環境でこのテストが失敗するのかがよくわからないけれど、HackerNewsの記事のコメントではParallelsでのホスト・ゲスト間の共有フォルダなんかでは失敗するとか。
Untracked cacheを有効化する
ではテストも成功したので、有効化してみよう。以下のようにする。
$ git update-index --untracked-cache
オフにするには以下を使う。
$ git update-index --no-untracked-cache
単発のコマンドで機能のオンオフを切り替えるのってなんかわかりづらいですね。新しい機能だからとりあえずこうしましたって感じなのだろうか。
git configで設定する方法(2.8.0から)
と思ったら、Git 2.8.0からはgit config
でオンオフできるようになっていたようだ。
$ git config --global core.untrackedCache true
さきほど「有効化できない環境もあるかもしれない」と述べたが、それが割と重要で、例えばUntracked cacheを有効化したリポジトリが後からUntracked cacheのテストが失敗するようなファイルシステム上にcloneされるという状況も考えられる。となるとこのconfigは個々のリポジトリに対して設定するのはあまりよくないかも。危険性は十分に理解して使ってください。
Gitのindexという領域について
いままで、Gitのindexのことを「リポジトリとワーキングディレクトリの間にあり、コミットの準備に使うステージング領域」という風にしか捉えていなかったのだが、git update-index
のドキュメントなんかを読むともっと他にも意味が持たせられているような気がする。Gitがリポジトリ側からワーキングディレクトリを見る際のフィルタというかそんな感じ?
Git v2.8.0からgit grepをリポジトリ外で実行した時の挙動が変わった
Git v2.8.0
Git v2.8.0が出た。そのリリースノートの中で少し気になった箇所があった。
- "git grep" by default does not fall back to its "--no-index" behavior outside a directory under Git's control (otherwise the user may by mistake end up running a huge recursive search); with a new configuration (set in $HOME/.gitconfig--by definition this cannot be set in the config file per project), this safety can be disabled.
適当訳: "git grep" をGit管理下でないディレクトリで実行した時に、"--no-index"を付けた振る舞いにフォールバックしないのがデフォルトになりました。でないと、誤って(Gitリポジトリ外で)git grepした際に、長ったらしい再帰的な検索が走ってしまうはめになるからです。新しく用意した設定項目で、この安全装置を解除できます(=以前の挙動に戻せます)。なお、動作の性質上、プロジェクトディレクトリ毎の.gitconfigにこの設定は適用できません。$HOME/.gitconfigに設定してください。
git grep --no-index
git grep
はそもそもGitで管理しているファイルのみを対象にgrepしてくれるコマンドだが、--no-index
を使うことでその制約を無視して全ファイルを対象にgrepするようになる。例えばGitリポジトリ内でgit grep --no-index
すると、.gitignore
で無視しているファイルや、まだgit add
していないファイルも含めてgrepしてくれる。
で、Git v2.7.4までは、Gitリポジトリ外でgit grepした際に、自動で--no-index
をつけてくれる仕様になっていた。つまり「今いる場所はどうやらGit管理下ではないようだけど、せっかくだからgrepするぜ〜」という動作になっていた。でもgit grep
は再帰的にファイルをサーチするため、上位のディレクトリにいる時に間違って実行した場合に凄く重たい動作になってしまう可能性がある。だからやめたんだろう。
Git v2.8.0でGitリポジトリ外でgit grep
を実行すると、以下のようにメッセージが出てgrepされない。
$ git grep hoge fatal: Not a git repository (or any of the parent directories): .git
grep.fallbackToNoIndex = true
でも自分は、git grepをGitリポジトリ外でも使ってたりしてたんですよね。。。ということで、以前の挙動に戻せる設定が作られたらしいので、 globalな.gitconfigに設定するために以下を実行する。
$ git config --global grep.fallbackToNoIndex true
これでv2.7.4以前の挙動に戻すことが出来た。
おまけ Git v2.8.0
Git v2.8.0で新しく入った要素については、GitHubの公式ブログで少し解説されている(全変更点の網羅ではない)。
Git 2.8 has been released · GitHub
GitHub公式ブログは、Gitの新バージョンがリリースされた時にたまにこうやって新機能などを解説してくれる。ありがたい。
RubyでPhysical Webを簡単に体験してみる
前提
ハードウェア
- MacBook Pro (Retina, 13-inch, Early 2013)
- Bluetooth 4.0 に対応しているマシンなら可
- iPhone 5s
ソフトウェア
Physical Webとは
「事前の準備なしに、近くにあるスマートなオブジェクト(例えば自販機、ポスター、おもちゃ、バス停、レンタカー等)から簡単に情報を受け取ったり、手元のデバイスでそれらとインタラクションできたら嬉しいよね?」という動機でGoogleが提唱しているオープン規格。具体的には、スマートなオブジェクト側はビーコンのように無線でURLを飛ばし、手元のデバイス(スマホ)はそれを察知してURLを説明付きで提示、利用者がURLを選択するとそれをブラウザで開く。そこから先はWebの技術を用いて情報提示なりインタラクションなりをするという形。まだまだexperimentalな段階らしい。
ここらへんの動画を見ると何をしたいか分かるかと思います。 https://google.github.io/physical-web/examples
現状はBLE(Bluetooth Low Energy)での利用を想定しているが別の規格も使えるようにすることも考えているとのこと。
こういうビーコン的なものというとiBeaconがあるが、iBeaconではサービス提供側が変わればまたそれ専用のアプリをダウンロードしなければならないのに比べて、Physical Webではどんなスマートオブジェクトに対してもURL送信を察知するアプリが1つあれば良い(あるいはスマホに最初から組み込んである?)という状態を目指している(現状はAndroidにしろiPhoneにしろChromeアプリが必要)。といっても、iBeaconを置き換えるためのものではないと書かれている。
Eddystone
このPhysical WebはEddystoneというBLEのプロトコルを採用している。Physical Webを試してみるには送信側としてわざわざビーコンを買う必要はなく、Bluetooth通信ができるPC上にEddystoneの作法にしたがって通信できるプログラムを用意すればよいようだ。あとは受信側としてChromeアプリがインストールされたAndroidかiPhoneがあればいい。自分の場合はMacbook+iPhoneでやってみた。
scan_beacon gem
で、調べてみると、送信側にnode.jsのEddystoneライブラリを使って試してみたという記事がいくつか見つかる。
iOSとnode.jsでEddystone入門 - Qiita
Physical Web / Eddystone によるO2Oのお手軽実装 | CHANGE-MAKERS
RubyではEddystoneライブラリはないのだろうかと調べてみると、scan_beaconというgemが名前に反してadvertise(送信)も出来るようだったので、これを使ってみた。Mac以外にもLinuxでも動くようだ。
コード
Gemfile
source "https://rubygems.org" gem "scan_beacon"
beacon.rb
require 'scan_beacon' beacon = ScanBeacon::EddystoneUrlBeacon.new( url: "https://goo.gl/VPfbp1", power: -20 ) advertiser = ScanBeacon::DefaultAdvertiser.new(beacon: beacon) advertiser.start puts "Advertising started." puts "Press Enter key to stop advertising." gets # 入力待ちしたいだけ advertiser.stop puts "Advertising stopped."
下の方がごちゃっとしているが、単にEnterキーを押したら送信終わりにしたかっただけ。advertiserがstartしたあとはstopするまでは毎秒URLを周囲に送信し続ける。
最初の方のScanBeacon::EddystoneUrlBeacon.new
でスマホに送るURLが指定できるが、気をつけないといけないのはURLは17バイト以内じゃないとダメだということ(Eddystone-URLの仕様)。「http://」のようなschemeや「.com」のようなTLDの部分はそれぞれ予め定められた1バイトのデータにエンコードされるらしいので、それを含めて17バイトに収める必要がある。なのでここではGoogle URL Shortenerを使った。Eddystoneの公式の説明でもURL短縮サービスの使用が推奨されているようだ。
他の注意点
- MacbookのBluetoothをオンにしておく
- iPhone側もBluetoothをオンにしておく
- iPhone側にChromeアプリを入れたうえで、Today ExtensionにChromeアプリを追加しておく
Today Extensionというのは、iPhoneの上の方からぐいーっとひっぱって出てくるメニューの「今日」と書かれた方のタブのこと。
実行
この状態で上記のプログラムを実行してから手元のiPhoneのToday Extensionを見ると、「フィジカルウェブ対応オブジェクトをスキャンしています…」と出たあとにRubyコードで指定したURLが表示される。タップすればChromeで開いてくれる。
上記コードではWikipediaのページが表示される。これはスマートオブジェクトからスマホに対して単に情報を提示しているだけということになるが、もしスマホと目の前のスマートオブジェクトをインタラクションさせたいとなれば、URLでアクセスした先のサーバがスマートオブジェクトとなんらかの経路で通信をして、手元のスマホの操作をスマートオブジェクトに反映させる必要があるということになる(ということだよね?)。
あるいはブラウザからJavaScriptでスマートフォンのBluetooth機能にアクセスできるAPI(Web Bluetooth)が策定され普及すれば、外部のサーバを経由せずスマートオブジェクトに直接やって欲しいことを伝えられるかもしれない。と、先ほど示した動画がいっぱい貼ってあるページに書いてあった。
HomebrewでVimを再ビルド
HomebrewでVimが更新されていたのでbrew upgrade vim
でアップデート。その後、新しいRubyが出ている事に気づき、rbenvを使って新しいruby(2.1.3)を入れた。
この時、Vimにリンクされている?(vim --version
とやった時に出てくる)rubyのバージョンをあげたいと思った。今までLinux等ではVimを再ビルドしていたのだが、Homebrewではどうするのか。たった今Vimをupgradeしてしまったから、新しいバージョンは無いためbrew upgrade vim
は拒否される。では一度vimを削除してからbrew install
するのか。えー。
以下で良かったらしい。
$ brew reinstall vim
これで、Vimが使っているRubyのバージョンを新しくすることが出来た。
【疑問1】rbenvで入れたRubyでは+rubyが付かない?(2016-05-01)
最近brew reinstall vim
で+ruby
にならないことに気づいた。この節を書いている時点のHomebrewのバージョンは以下。
$ brew --version Homebrew 0.9.9 (git revision f54f2; last commit 2016-04-30) Homebrew/homebrew-core (git revision 0f8b; last commit 2016-04-30)
rbenv
を使ってrubyを入れているのだが、例えばrbenv global 2.3.1
としてrbenvで入れたRuby2.3.1が有効になっている状態でbrew reinstall vim
をしても+ruby
が付かない。
なぜなのだろうと思い色々試してみたが、brew install ruby
でHomebrewのformulaになっている方のrubyを入れ、rbenv global system
でHomebrewで入れたrubyを有効化した後、brew reinstall vim
をすれば+ruby
になった。
HomebrewのvimはHomebrewで入れたrubyじゃないと使ってくれないのだろうか?
【疑問2】brew reinstall時にインストールオプションを忘れてしまう(2016-05-01)
neocompleteというvimのプラグインを使っているのでvimに+lua
が必要であり、vimをビルドする時に--with-lua
のオプションを付けたいのだが、これはbrew reinstall vim
する時に容易に忘れてしまう。
「このformulaでビルドを行う時は常にこのオプションを使います」みたいな機能はないのだろうか?(Bundlerのconfigがgemのインストールオプションを覚えてくれるみたいなやつ)
Watirを使ってChromeをRubyで動かす(自分用メモ)
http://chromedriver.storage.googleapis.com/index.html から最新のドライバー(ブラウザとの間を取り持ってくれるもの、単一の実行ファイル)を落としてPATHの通ったところに置いておく。
$ bundle init $ echo "gem 'watir-webdriver'" >> Gemfile $ bundle install --path vendor/bundle $ vim a.rb
a.rb
の中身↓
require 'watir-webdriver' browser = Watir::Browser.new :chrome browser.goto "http://www.google.com/" browser.text_field(:name => "q").set "るるきゃん" browser.button.click
$ bundle exec ruby a.rb
chromeが勝手に開いて、検索までする。
RVM使いのための rbenv&ruby-build コマンド簡易対応表
前提
執筆時点(2013/12/26)での話。Ruby 2.1.0 が出たばかり。
rbenv&ruby-buildを使い始めた
自分は今までずっとRVMでRubyを管理していて、最近rbenv+ruby-buildを使いはじめたので簡単にコマンド対応を書き出してみる。
インストールは各公式サイトを参考のこと。以下ではrbenvは ~/.rbenv
、ruby-build は ~/.rbenv/plugins/ruby-build
にインストールされているとします。
なお、RVMからrbenvに移行するなら、rbenvを使いはじめる前に ~/.rvm
ディレクトリを削除するか.bashrc等の設定を削除するなどして、rvmが読み込まれないようにすること。
本体のアップデート
RubyのバージョンアップではなくRVMやrbenv自体のアップデート。
RVM
$ rvm get stable
最新の最新を使いたい場合はこちら:
$ rvm get head
rbenv & ruby-build
$ cd ~/.rbenv $ git pull $ cd ~/.rbenv/plugins/ruby-build $ git pull
インストール可能なRubyのバージョンの列挙
RVM
$ rvm list known
rbenv & ruby-build
$ rbenv install -l
Rubyのインストール
RVM
$ rvm install Rubyのバージョン
rbenv & ruby-build
$ rbenv install Rubyのバージョン $ rbenv rehash
rbenvではインストール中に何も出力しないので、インストールの進行状況を見たい場合は、rbenv install -v
を使った方がよいです。
インストールされているRubyのバージョンを列挙
RVM
$ rvm list
rbenv & ruby-build
$ rbenv versions
使用するRubyのバージョンの切り替え
RVM
$ rvm use Rubyのバージョン
rvm use --default
を使うと、指定バージョンがデフォルトになります。
rbenv & ruby-build
$ rbenv global Rubyのバージョン
この方法以外だと、RBENV_VERSION
という環境変数や .ruby-version
というファイルで使用するバージョンを指定するようです。
その他
RVMでRubyを入れると初めからBundlerが入っているけど、rbenvだとそうじゃないので自分で入れる必要あり。
Method#to_proc の動作(Ruby)
前提
ruby-2.0.0-p353
Method#to_proc の動作に納得したい
(1..5).each do |i| puts i end
(1..5).each(&method(:puts))
上記2つのコードは同じ動作をする。パッと見で納得できなかったので動作を1つずつ追ってみる。
まず method(:puts)
。 Object#method
を使って Method オブジェクトを作っているわけだけど、Method オブジェクトはレシーバとメソッドの組み合わせで保存される。この場合ではレシーバが「Ruby のトップレベルの Object オブジェクト」、メソッドが「puts メソッド」の組み合わせで Method オブジェクト化している。
この Method オブジェクトを &
記法を使って、each メソッドにブロックとして渡している。Method オブジェクトのままブロックとして渡せないので、ここで暗黙的に Method#to_proc
が呼ばれ、Proc オブジェクトへ変換される。
では、どういう Proc オブジェクトになるのか。
Method#to_proc
はるりまを見ると「self を call する Proc オブジェクトを生成して返します。」と説明されている。ここでの self
とは to_proc
を呼ばれる Method オブジェクト自身のことだ。
ということは単純に考えると以下の a_proc の様になる。
m = method(:puts) a_proc = Proc.new { m.call }
しかしこれは正解ではない。引数が考慮されていないから、この a_proc を each メソッドに &
記法で渡すと、何も引数を受け取らずにただ改行だけ出力するブロックが繰り返し実行されてしまう。
puts
は必須引数がなく、可変長引数を取る。Method オブジェクト化した puts
に Method#arity
を使うと -1 が返ってくる。それは Proc オブジェクト化した後も変わらない。
よって、きちんと動かすにはこういう Proc オブジェクトになっているはずだ。
m = method(:puts) a_proc = Proc.new {|*i| m.call(*i) }
実際に Method#to_proc
で変換された Proc オブジェクトがこういう形をしているかどうかは分からないけど、多分間違っていないはず。
試してみるとちゃんと動きました。
m = method(:puts) a_proc = Proc.new {|*i| m.call(*i) } (1..5).each(&a_proc) 1 2 3 4 5 #=> (1..5)
(この記事は最初、変数 a_proc を proc にしてたんだけど、Kernel.procがあるからバッティングしていた…。)