灰いじり

This blog's posts are licensed under CC-BY 4.0.

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を簡単に体験してみる

前提

ハードウェア
ソフトウェア

Physical Webとは

The 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アプリがインストールされたAndroidiPhoneがあればいい。自分の場合は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とRubyスクリプトを用意する。

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短縮サービスの使用が推奨されているようだ。

他の注意点

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は ~/.rbenvruby-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 オブジェクト化した putsMethod#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があるからバッティングしていた…。)