« 2010年02月 | メイン | 2010年04月 »

2010年03月 アーカイブ

2010年03月02日

「git commit --amend」と「git reset」の挙動

間違えてたらごめんなさい。

以下、現在のブランチの位置を「[]」で括って、到達不可能なコミットを「()」で括って表す。
「git commit --amend」の挙動
「git commit --amend」は、正確にはコミットを修正するのではなく修正したコミットを履歴ツリー上の「別の枝」にする作業らしい。例えば、以下のようなcが「あるブランチ」のHEADの時、
a---b--[c]
cをxに修正すると次のように見える。
a---b--[x]
でも、「--amend」を指定したときの実際の挙動は次のようになる模様。
     [x]
     /
a---b--(c)
この時、ブランチがx側に切り替わるために、cは到達不可能なコミットとなるため、「git log」等でも表示されないようだ。cのコミットIDが分かっている場合、チェックアウトでコミットcに切り替えられる。また、cにタグや別のブランチがあれば、cは参照可能なコミットなのでコミットIDを調べなくても切り替えることができる。
「git commit --amend」は過去のコミットも修正可能
最初に『「git commit --amend」では過去のコミットも修正できる』と聞いたときには次のような動作をすると思っていた。
修正前
a---b--[c]--d---e

修正後
a---b--[x]--d---e
しかし、実際には次のようになるらしい。
         [x]
         /
a---b---c--(d)-(e)
後はdとeを「git rebase」でxのHEADの位置に移動すれば良い。後からコミットIDを調べるのが面倒なので「git commit --amend」を実効する前に、eの位置に別のブランチやタグを作成しておくとリベースしやすい。
「git reset」の挙動
「git reset」も同様の動きを示す。
a---b--[c]
「git reset HEAD^」を実行すると次のようになる。
a--[b]-(c)
つまりresetはコミットを取り消すのではなく、「履歴を遡る」コマンドということらしい。この状態で新しいコミットxを作成すると分岐する。
     [x]
     /
a---b--(c)
Gitは全ての履歴を記録しているという意味がやっとわかった気がする。

「git rebase」はコミットの移動ではない

「git rebase」はコミットの移動だと思っていたがコミットの再適用だった。以下実験。
$ git init
$ touch aaaaa
$ git add aaaaa
$ git commit -m "a"
$ touch bbbbb
$ git add bbbbb
$ git commit -m "b"
$ touch ccccc
$ git add ccccc
$ git commit -m "c"
$ git branch branch1
$ git branch branch2 HEAD^^
$ git checkout branch2
$ touch ddddd
$ git add ddddd
$ git commit -m "d"
これで次のような状態になる。「[]」は現在のブランチ、「.」に続くのはブランチ名。
 [d.branch2]
 /
a---b---c.branch1.master
次にbranch1をbranch2の先端にリベースしてみる。
$ git checkout branch1
$ git rebase branch2
結果は次のようになる。
  d.branch2---b---c.branch1
 /
a---b---c.master
ここでそれぞれのブランチの履歴を見てみる。
$ git checkout master 
$ git log --pretty="oneline"
7693a9a91c96c7650de8115545e254b58c822b3d c
fe9f674ad0b724fe995ccc94240d673ea5bde02e b
5652af90e342454863003be1218a7cf7851ac9b8 a
$ git checkout branch1
$ git log --pretty="oneline"
3466d40431341822e9acaed3837b5c0b3f9fc478 c
de4ffa617d694be1b23de94b1062c0e97fe1e7eb b
6c7d5011324965a2ec5a198f089d30e6437aee38 d
5652af90e342454863003be1218a7cf7851ac9b8 a
$ git checkout branch2
$ git log --pretty="oneline"
6c7d5011324965a2ec5a198f089d30e6437aee38 d
5652af90e342454863003be1218a7cf7851ac9b8 a
注目したいのはbranch1とmasterそれぞれのコミットのbとcのコミットIDが違うこと。つまりリベースは、コミットを単純に移動するのではなく、コミット内に格納している変更内容を移動先に順番に適用していくということらしい。根本的な考え方は、前回のエントリーに書いた「git commit -amend」や「git reset」と同じようだ。

「git rebase --onto newbase upstream branch」の意味

やっと「git rebase」のちゃんとした動きを把握したと思うので書いておく。

前回のエントリーで「git rebase」は正確にはコミットの履歴の移動でないと書いたが、ここでは説明のしやすさから、『移動』という言葉を使用する。
履歴ツリー上でリベースするためには、「『どこから』『どこまで』を『どこに』移動するのか」を知る必要がある。もう少し整理すると、「移動する上流側(祖先側)の位置」「移動する下流側(子孫側)の位置」「移動先(新しい親)の位置」となる。

これら全てを指定するのが「git rebase [--onto <newbase>] <upstream> [<branch>]」という書き方になる。

定義は以下の通りのはず。

「移動先の位置」
<newbase>が使用される。
ただし、省略された場合は、<upstream>が使用される。
ブランチでなくてもよい。

「移動する上流側の位置」
<upstream>が使用される。
ただし、<upstream>が「移動する下流側の位置」の直接の祖先でない場合、<upstream>と「移動する下流側の位置」の共通の祖先の位置となる。
ブランチでなくてもよい。

「移動する下流側の位置」
<branch>が使用される。
ただし、省略された場合は、現在のブランチが使用される。
ブランチでなくてはならない。

また、移動するのは「移動する下流側の位置」から「移動する下流側の位置」の一本だけであり、履歴の枝が生えていてもそれらは無視される。これはリベースが実際にはコミットの履歴の移動ではなく、コミット内に記録されている変更内容を移動先に順番に適用していくため。
「ブランチのHEADをチェックアウト」し、「<newbase>と<branch>を省略」して「git rebase <ustream>」を実行した場合の動きは、履歴を「木」のメタファーで説明していると「接ぎ木をしている」ように見え、そう説明する人もいる。しかし、内部の動作はそうはなっておらず、同じ変更内容を持った別のコミットを生成している。Gitを学習する時に、表面上な動きだけではなく、内部構造がどうなっているのか把握しておいた方が良いと言われるのは、このようなところに理由があるのだろうと思った。

2010年03月03日

Firefox限定 加速度センサーとCSSのTransform2Dをあわせて使う

Firefox3.6からJavaScriptから加速度センサーの値を取得できるようになりました。この手のセンサーは、移動方向というよりも筐体の傾き検出の方が向いているはずなのですが、そのようなデモがあまりないようなので簡単に実装してみました。Firefox3.6から加速度センサーを利用できる環境が必須です。MacBook系であれば大丈夫なはずです。
バージョン3.6のFirefoxは、加速度センサーに対応していますが、Transform3Dには対応していません。逆に、最新のSafariでは既にTransform3Dに対応しているのですが加速度センサーには対応していません。iPhone用のSafariはTransform3Dに対応していますが、加速度センサーは縦横の方向しか取得できません。加速度センサーとTransform3Dの両方に対応したブラウザが登場し、面白いアイディアのものが色々出てくるのが楽しみですね。
関連エントリー
iPhone/iPod touchで、Transform3Dと加速度センサーの両方を使った例。

2010年03月06日

Gitのユーザマニュアルを読んでのメモ

Gitのユーザマニュアルの非公式日本語訳を読んでの個人的メモ。訳してくれた人に感謝。以下、個人的に実験した内容も含む。
gitkの起動時のオプション
gitkを起動するとき、「--all」をつけると、現在のブランチの履歴だけでなく、全てのブランチの履歴を見ることができる。gitkを起動するとき、「--not <ブランチ名>」とすると、指定されたブランチは表示されない。「gitk --all --not master」で、masterブランチ以外の全てのコミットを表示する。
強制pushと強制fetch
強制pushする時、「-f」の代わりに「+」でもいけるらしい。
git push -f ssh://(略) master
git push ssh://(略) +master

同様に、強制fetchする時、「-f」の代わりに「+」でもいけるらしい。
git fetch -f git://(略) master:ref/(略)
git fetch git://(略) +master:ref/(略)
リベースでコンフリクトした時の作業手順
1.「git status」でコンフリクトしているファイルを確認
2.「git mergetool」やその他のマージツール、または手作業で修正
3.「git add [修正したファイル名]」でコンフリクトが解消した事を通知
4.「git rebase --continue」でリベースを継続
※どの時点でも「git rebase --abort」でリベース前に戻せる
「.git/objects/」内のファイルの格納ルール
「.git/objects/」の下にはサブフォルダがあり、そのフォルダ名はオブジェクトIDの最初の2文字となっている。ファイル名はオブジェクトIDの最初の2文字を削除したものになっている。つまり、オブジェクトIDが「123456...」のファイルは、「.git/objects/12/3456...」に格納される。
リポジトリが破損した時用のメモ
「git fsck」でリポジトリの検証。「git fsck --full」で完全な検証を行える。
破損していた場合、破損したBLOBオブジェクトのIDやそれを参照しているツリーオブジェクト等の情報が表示される。リポジトリのクローン先やクローン元に破損したオブジェクトを入手し、「.git/objects/」配下に格納する。
破損したファイルが見つからなかった場合、「git ls-tree <ツリーID>」で破損したBLOBオブジェクトのファイル名を確認する。作業ディレクトリ内に運良く元のファイルらしきものが残っている場合、「git hash-object <ファイルパス>」でオブジェクトID確認できる。「git hash-object -w <ファイルパス>」でオブジェクトを生成し「.git/objects」配下に格納する。
元のファイルが見つからなかった場合、「git log --raw --all --full-history -- <ファイルパス>」でコミットの履歴を表示する。これにより、履歴の前後のコミットIDやコミット時のコミットメッセージが確認できるので、破損したファイルを再現できる可能性がある。再現できた場合、「git hash-object <ファイル>」でオブジェクトIDの確認、「git hash-object -w <ファイル>」でオブジェクトの登録を行う。
復旧できなかったとしても、紛失したオブジェクトは、特定のバージョンの特定のBLOBオブジェクトであるため、状況により「git fsck」の警告を無視することも可能らしい。
破損したBLOBオブジェクトの子孫にあたるファイルが残っているのであれば、履歴が分断する事にはなるが、リポジトリから一度ファイルを削除した後に子孫ファイルを再度登録したほうが安全ではないかと思った。
雑多なメモ
「git show」や「git log」に「--pretty=raw」をつけると、ツリーID、親コミットID等の詳細な情報を見ることができる。

「git mv」等でファイル名の変更があった場合、「git diff」は異なるオブジェクトとして認識するが、「git diff -M」とすると名前の変更を認識し、同一ファイルとして扱う。

Gitで管理するファイルのパーミッションは755か644のいずれかになる。Gitは実行権限だけを管理する。

「git ls-tree <コミットID>」でtreeの情報を表示できる。コミットIDを省略すると現在のコミット。
「git ls-show <レビジョン:パス>」でtreeやblobの中身を確認できる。

「git cat-file [blob|tree|commit|tag] <id>」でそれぞれの詳細な情報を表示できる。

「git tag」では、「軽量タグ」というものを作成できるらしい。詳細は不明。

「git count-objects」で、オブジェクト数を確認できる。

「git gc」は、「git repack」と「git prune」を実行する高レベルのコマンド。

「git ls-files --stage」で索引(index,ドキュメントによってはステージ(stage)と呼ぶこともある)の中を確認できる。

「git log branch1..branch2」branch1には含まれないがbranch2に含まれるコミットを一覧する。
「git log branch1...branch2」branch1かbranch2のどちらかにのみ含まれるコミットを一覧する。

「git diff branch1 branch2」と「git diff branch1..branch2」と「git diff branch1...branch2」の出力の違いがわからない。そのうち調べる。
その他
「Linuxのサブシステム管理者がtopicブランチを管理する方法」の部分が難しい。
サブモジュールは運用方法を気をつけないとまずそう。「Chapter 8. サブモジュール」をよく読むこと。
「Chapter. 9」以降は未整理。

2010年03月14日

ATNDで参加者を募集しているイベントの参加者層を調べるGreasemonkey

ATNDはイベント開催を支援するウェブサービスです。
参加申込は、OpenIDを使用します。最近気付いたのですが、どこのOpenIDを使用しているかによって参加者層が若干違うようです。そこで、使用されているOpenIDの割合を調べるGreasemonkeyを作成しました。
インストールすると、参加募集のページに次のようなグラフが表示されます。

2010年03月18日

Firefoxのブックマークバーを折り返して表示する

Firefoxのブックマークバーを複数行で表示する方法は「FIREFOX 3 HACKS」にも書いてあったが、自分でも実装してみた。
ブックマークやブックマークレットをブックマークバーに沢山登録していて、溢れかえってしまっている人にお勧め。
以下のソースをコピーして、アドオンのStylishに登録するだけでOK。
@namespace xul url(http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul);

#bookmarksBarContent>xul|hbox>xul|hbox,
#bookmarksBarContent>xul|hbox>.chevron{
    display:none;
}

#bookmarksBarContent>xul|hbox,
#bookmarksBarContent>xul|hbox>.bookmarks-toolbar-items,
#bookmarksBarContent>xul|hbox>.bookmarks-toolbar-items>.scrollbox-innerbox{
    display:block;
    max-height:12.5em;
}

2010年03月20日

Gitのcore.editorにEmacsを指定する試行錯誤(Macの場合)

これまで、.gitconfigのeditorには次のような設定をしていた。
[core]
    editor = emacs
これで「COMMIT_EDITMSG」というファイルがEmacsで開く。
しかしこれだと、Emacs.app(Carbon Emacs)が起動していても、ターミナル上でMac OS X標準のEmacsが起動する。
これまでは気にもしていなかったのだけど、Emacsに色々な拡張をした結果起動時間が遅くなってしまった。
「git commit」を実行する度にEmacsが起動するまで待つのが嫌になって、コミットが億劫になってきていた。

Macの場合、次の様にすると「/Applications」ディレクトリ内の「Emacs.app」を探し出して起動してくれる。
[core]
    editor = open -a emacs
詳細は「man open」を参照のこと。
Tiger(Mac OS X 10.4)時代の書籍だけど、「入門 Unix for Mac OS X 第4版」にも少し解説されている。
Mac OS X固有のコマンドをきちんと解説している書籍は少ないので、この本の改訂版を出版して欲しい...。

話を元に戻す。
この方法だと確かに「git commit」実行時にEmacs.appが開きコミットメッセージの入力ができる。
しかし、コミットすることができない。
Emacs.appが起動した直後に、ターミナル側のGitが次のエラーを吐いて終了してしまう。
% git commit
Aborting commit due to empty commit message.
%
Emacs.appが別プロセルで立ち上がっているのが原因だと思う。

そこで、次の様に設定した。
[core]
    editor = open -W -a emacs
これだと、Git側はEmacs.appが終了するまで待ってくれる。
しかし、この方法にも問題がある。
「COMMIT_EDITMSG」を閉じてもGitはコミットを継続してくれず、Emacs.appの終了まで待ってしまう。
他のファイルをEmacs.appで開いていた場合、それも終了させないといけなくなる。
これじゃあ、本末転倒。

emacssclientというのを使うことにした。
emacssclientの詳細は割愛。
以前、gnuclientというのを使ったことがあったけど、これはemacssclient/emacsserverの機能拡張版らしい。
まず、次のelispをEmacsの設定に追加。
;;;サーバ起動
(server-start)
;;;クライアントを終了するとき終了するかどうかを聞かない
(remove-hook 'kill-buffer-query-functions 'server-kill-buffer-query-function)
次にcore.editorにemacssclientを指定する。
「open -a」はMac OS X向けのアプリケーション(Aqua対応のみ?)しか対応していないので、フルパスで指定する必要がある。
Carbon Emacs版のemacsclientはEmacs.appのパッケージ内にある。
[core]
    editor = /Applications/Emacs.app/Contents/MacOS/bin/emacsclient
まだ、問題がある。
Emacs.appが既に起動している場合(正確にはserver-startが実行された場合)はこれでいいけど、起動していなかった場合は、以下の様なエラーとなってしまう。
% git commit
/Applications/Emacs.app/Contents/MacOS/bin/emacsclient: connect: Connection refused
/Applications/Emacs.app/Contents/MacOS/bin/emacsclient: No socket or alternate editor.  Please use:

    --socket-name
    --server-file      (or environment variable EMACS_SERVER_FILE)
    --alternate-editor (or environment variable ALTERNATE_EDITOR)
error: There was a problem with the editor '/Applications/Emacs.app/Contents/MacOS/bin/emacsclient'.
Please supply the message using either -m or -F option.
emacsclientが起動指定かなった時のために代わりのエディタを「--alternate-editor」で指定する。
ここではEmacs.appパッケージ内のCarcon Emacsの本体を指定してる。
[core]
    editor = /Applications/Emacs.app/Contents/MacOS/bin/emacsclient --alternate-editor /Applications/Emacs.app/Contents/MacOS/Emacs
本当は、テキストエディット等の軽量なものを指定したいところだけど、TextEdit.appを直接指定しても起動せず、TextEdit本体を指定するとEmacs同様にアプリケーションを終了しないとコミットを継続してくれない。
しかも「--alternate-editor」にオプション付のopenコマンドが何故か指定できないため、「--new」による新規インスタンスでの起動や、「--wait-apps」によるブロックができなかった。
普段はEmacsを使用しているけど、viも使える人は、次の様にしておくと良いと思う。
[core]
    editor = /Applications/Emacs.app/Contents/MacOS/bin/emacsclient --alternate-editor vi
Google

タグ クラウド