Evil を eval-last-sexp に対応させる

Emacs では通常、Lisp の S 式の最後 (閉じ括弧の次) にカーソルを置いて C-x C-e を押すとその S 式が eval-last-sexp により評価される。しかし、Evil の normal 状態では、S 式が行末にある場合にカーソルが閉じ括弧をポイントするため、 eval-last-sexp により正しく S 式を指定して評価することができない。insert 状態では正しく指定できるが、評価するたびに insert 状態に入るのは勝手が悪すぎる。というわけで、調べてみると eval-last-sexp が内部で使っている preceding-sexp に defadvice する方法で解決できることがわかった。

normal-state interacts poorly with eval-last-sexp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
(defadvice preceding-sexp (around evil activate)
"In normal-state, last sexp ends at point."
(if (evil-normal-state-p)
(save-excursion
(unless (or (eobp) (eolp))
(forward-char))
ad-do-it)
ad-do-it))
(defadvice pp-last-sexp (around evil activate)
"In normal-state, last sexp ends at point."
(if (evil-normal-state-p)
(save-excursion
(unless (or (eobp) (eolp))
(forward-char))
ad-do-it)
ad-do-it))

Slime でも同様に slime-last-expression に対して defadvice することで対応できた。

1
2
3
4
5
6
7
8
(defadvice slime-last-expression (around evil activate)
"In normal-state, last sexp ends at point."
(if (evil-normal-state-p)
(save-excursion
(unless (or (eobp) (eolp))
(forward-char))
ad-do-it)
ad-do-it))

初期の Git コマンドのソースコード

Linus さんによる Git の最初のコミットとなる e83c5163 のソースコードを読んでみた。

init-db

.dircache という名前でディレクトリを作成し、その中に 00 ~ FF のディレクトリを作成する。Git はこのディレクトリをコンテンツ管理用の DB として扱い、ファイルのコンテンツから求めた SHA-1 ハッシュ値をファイル名としてこのディレクトリに保存する。保存するディレクトリは SHA-1 ハッシュ値の先頭 1 バイトから保存すべき 00 ~ FF のディレクトリを識別し、そのディレクトリの中に SHA-1 ハッシュ値の残りの 19 バイトからなるファイル名で保存する。このようにインデクシングする理由は、.dircache はそれが置かれているディレクトリ以下のすべてのファイルを管理するため、ファイル数が多くなった場合に対処しているのだろう (という予想)。

データの基本的なフォーマットは以下の通り。

  • blob データサイズ\0データ
  • tree データサイズ\0データ
  • commit データサイズ\0データ

“データ” の部分はそれぞれの形式毎に変わる。

update-cache

指定したファイルを blob データサイズ\0データ という形式に変換し、それを zlib で deflate した結果を .dircache/objects 以下の DB に保存する。このときのファイル名は deflate 後の結果から SHA-1 ハッシュ値を求めたものになる。

さらに、登録するファイル情報を .dircache/index にキャッシュエントリとして保存する。.dirchache/index の構造は単純で、1 つのキャッシュヘッダとそれに続くキャッシュエントリのリストから構成される。キャッシュエントリは fstat から求めたファイル情報と deflate 結果から求めた SHA-1 ハッシュ値を含んでおり、ファイル名でソートされている。また、キャッシュヘッダにもすべてのキャッシュエントリのリストから算出した SHA-1 ハッシュ値を保存する。

write-tree

キャッシュエントリを走査し、tree データサイズ\0ファイルモード ファイル名\0SHA-1... という形式のデータを生成する。update-cache でファイルを blob 形式で DB に保存したのと同様に、この tree 形式を zlib で deflate して、その結果から求めた SHA-1 ハッシュ値をファイル名として DB に保存する。このときのハッシュ値は commit-tree で使う。

commit-tree

引数で指定した SHA-1 ハッシュ値の tree を commit する。コミットメッセージは標準入力から取得する。形式は以下の通り。

commit データサイズ\0tree SHA-1\nparent SHA-1\nauthor 本名 <ユーザ名@email> 日時\ncommitter 本名 <ユーザ名@email> 日時\n\nコミットメッセージ

write-tree と同様に上記のデータを zlib で deflate して SHA-1 ハッシュ値からなるファイル名で DB に登録する。-p で親となる tree を指定することができ、通常は前回 commit したときの tree の SHA-1 ハッシュ値を指定する(初回 commit 時は指定しない)。この tree を逆順にたどっていくことにより履歴を巡ることが可能。

cat-file

DB に保存されているデータはすべて zlib で deflate されているためそのままでは読めない。cat-tree はこれを読める形式に変換するコマンドで、引数に指定した SHA-1 ハッシュ値の DB ファイルを inflate して一時ファイル (temp_git_file_XXXXXX) に書き出す。

read-tree

blob と commit 形式のファイルは cat-file で inflate すれば読めるが、tree 形式は SHA-1 ハッシュ値がバイナリ形式で含まれるためそのままでは読めない。read-tree は tree 形式のファイルをテキストとして読める形に変換して一時ファイルに書き出す。

show-diff

.dircache/index で管理しているすべてのキャッシュエントリについて、DB 内のファイルと現在のファイルの stat 情報に差分があれば diff を表示する。

使い方

1
2
$ git clone github.com/gitster/git.git
$ git checkout e83c5163

そのままでは make できなかったので以下のパッチをあてる。

次の実行例は DB を作成して README と Makefile を tree に登録して commit するまでの例。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
$ ./init-db
defaulting to private storage area
$ ./update-cache README
$ ./update-cache Makefile
$ find .dircache -type f
.dircache/index
.dircache/objects/66/5025b11ce8fb16fadb7daebf77cb54a2ae39a1
.dircache/objects/90/9a87257113dd11a2c2749c059b4aa6d55ed9f7
$ ./write-tree
8f4d3dbaec34d144bfcf5a8f2d7e0573abc00230
$ ./commit-tree 8f4d3dbaec34d144bfcf5a8f2d7e0573abc00230
Committing initial tree 8f4d3dbaec34d144bfcf5a8f2d7e0573abc00230
first commit
1f3b88e43904b187069aa7a8b6ff8006743969c9
$ ./read-tree 8f4d3dbaec34d144bfcf5a8f2d7e0573abc00230
100644 Makefile (909a87257113dd11a2c2749c059b4aa6d55ed9f7)
100644 README (665025b11ce8fb16fadb7daebf77cb54a2ae39a1)
$ ./cat-file 665025b11ce8fb16fadb7daebf77cb54a2ae39a1
temp_git_file_OfINdU: blob
$ head temp_git_file_OfINdU
GIT - the stupid content tracker
"git" can mean anything, depending on your mood.
- random three-letter combination that is pronounceable, and not
actually used by any common UNIX command. The fact that it is a
mispronounciation of "get" may or may not be relevant.
- stupid. contemptible and despicable. simple. Take your pick from the
dictionary of slang.

Git コマンドの使い方は以下の記事が参考にさせていただきました。

Git の履歴を削除して新しいブランチを作成する方法

Git の履歴をすべて削除して新しいブランチからリポジトリ管理を始めたい場合、まずは以下のように git checkout コマンドに —orphan オプションをつけて空のブランチを作成する。

1
$ git checkout --orphan tmp

この状態で、いつものように Git 管理下におきたいファイルをステージングして commit する。何も commit していない状態だと git branch を実行しても tmp ブランチは表示されないようだ。

1
2
$ git add .
$ git commit -m "first commmit"

この時点で master と履歴が 1 つの tmp というブランチが存在することになる。あとは master ブランチを tmp ブランチで push して履歴をすべて削除した master ブランチに改変する。

1
$ push -f . tmp:master

別のリモートリポジトリに push

GitHub から clone したリポジトリを履歴を削除した上で BitBucket で新たに管理を開始したい場合などは、まず上記の手順で履歴を削除したリポジトリを作成した後、以下のように origin の url を変更する。

1
2
$ git checkout master # カレントブランチを master ブランチに戻す
$ git remote set_url origin git@bitbecket.com:username/repository.git

あとはリモートリポジトリに対して push すればよい。

1
$ git push origin master

最初に作成した orphan なリポジトリはもはや不要なので削除してよい。

1
$ git branch -d tmp

Atom Shell で Hello world

Atom Shell を使っておそらく本来の使われ方ではないプログラムを作成してみる。以下のような Node.js プログラムを普通に実行することができる模様。

hello.js
1
2
console.log("hello, world");
process.exit(0);

最後の process.exit(0) を書かないとプロセスが終了しない。 実行するときは引数に JavaScript ファイルを指定すればよい。

実行結果
1
2
$ ./out/Release/Atom.app/Contents/MacOS/Atom hello.js
hello, world

require でロードしたモジュールも普通に使うことができる。

cpus.js
1
2
3
var os = require('os');
console.log(os.cpus());
process.exit(0);
実行結果
1
2
3
4
5
6
7
8
9
10
11
12
13
$ ./out/Release/Atom.app/Contents/MacOS/Atom cpus.js
[ { model: 'Intel(R) Core(TM) i5-3317U CPU @ 1.70GHz',
speed: 1700,
times: { user: 4692350, nice: 0, sys: 3321620, idle: 38701300, irq: 0 } },
{ model: 'Intel(R) Core(TM) i5-3317U CPU @ 1.70GHz',
speed: 1700,
times: { user: 2480960, nice: 0, sys: 1154690, idle: 43077500, irq: 0 } },
{ model: 'Intel(R) Core(TM) i5-3317U CPU @ 1.70GHz',
speed: 1700,
times: { user: 4196740, nice: 0, sys: 2200760, idle: 40315740, irq: 0 } },
{ model: 'Intel(R) Core(TM) i5-3317U CPU @ 1.70GHz',
speed: 1700,
times: { user: 2333780, nice: 0, sys: 1036880, idle: 43342400, irq: 0 } } ]

本来のデスクトップアプリケーションを目的としたプログラムのチュートリアルは下記ドキュメントにある。

ちょっと試してみたけど、main.js から HTML の DOM を操作することはできないんだろうか。よくわからない…。