思いついたネタだけど、作る時間がないので公開してみた

タイトル通りですが、ネタ帳で塩漬けされてたネタを列挙してみた。

もし面白いネタがあれば自由に使っても構わないです

駅情報の管理システム

確定申告の前に自分の行った駅を後から確認したり、交通費を簡単に算出できるサービス。

自分の移動した駅を出来るだけ簡単に登録できるのが前提。

考えてた実装方法は2通り

  • 4sq api 使って、チェックイン情報から移動した駅を見つける
  • Suica の履歴を利用する

週40時間を表示する砂時計

仕事開始前にwebページの[Start]を押すと砂時計の砂が落ちて、仕事終了時に[End]を押して砂時計を止められるwebページ

  • 出退勤時間→累計作業時間じゃなくて、残作業時間→作業可能時間にして、これを見える化したい
  • 週の残作業時間の初期値は可変にした方が良いかも
  • BTSプラグインとして作るのもあり?

FelicaSNSを連携した認証システム

FelicaカードとSNSのアカウントを紐付けて、カードだけで簡単にSNSアカウントを識別できるサービス

使い道

  • Felica カードでイベント参加者を識別
  • Felica 端末を自宅の玄関に置いて、カードかざすだけで「帰宅なう」って呟ける

あまり使い道が思い浮かばない...。
ただ、今はほとんどの人が1つ以上Felica(Suica, お財布携帯など)を持っているので、何かに使える気がする

自分の行きたい店、空いてる日を登録できるサービス

行きたいお店があった時、そのお店に行きたい他のユーザを簡単に検索できるサービス

  • 一緒にお店に行ける人を手軽に募集できる
  • 行きたいお店、空いてる日、参加条件、確定条件を入れておいて、確定したらメールがくる
    • 参加条件: 女性のみ、20〜30歳まで、相互フォローしてる相手のみとか
    • 確定条件: 4人以上、paypal支払い済み6人以上、男女比1:1とか

不動産の部屋の間取りを3Dで表示

8帖でも部屋の形とかで変わってくるので、間取り図(画像)、部屋の高さ(数値)、部屋の広さ(平米の数値)から3D化できたら便利かもと。

引越し時の思いつきネタ。

TDDネタ

TDDで書いてみようかなーと思ってたネタ

  • ダーツのクリケット
  • トランプのポーカー
  • 麻雀の役の判定

ある程度の複雑さ、追加仕様として他ユーザとの対戦(スレッド)とかのテストも出来るかもしれない。

TortoiseSVNの萌え化

擬人化した亀の絵をコミット時のウィンドウ背景に表示したい。

IDE、SCM、BTS、CIを全て萌え化して、コミケで販売してたらちょっと買いたい。

Trac 炎上Plugin

チケットの消化率や進捗によってWikiページの隅っこから徐々に焦げたり、煙が上がったり。
プロジェクトの炎上がWikiトップページで見える化

gitの作業ディレクトリでの移動が捗るgit_cdを作りました

11月19日にオラクルでSCMBootCamp in Tokyo 2というイベントがあり、そこでgitの講師してました。
周りの方のgitの質問に答えたり、コマンドの説明をしていたり。

濃い話が聞けたり、普段触らない他DVCSの話も聞けて楽しかったです。
あと、花映塚はやっぱり面白いですね!

そんな#scmbcの演習中

@1syoさんからの質問(質問だったと思う)で「gitの作業ディレクトリのrootに簡単に移動するコマンドとかないですか?」というものがありました。

具体的に書くと、こんな感じ。
例えば、下記のようなフォルダ構成だったとして。

~/rails_proj/.git
~/rails_proj/app/controllers

こういう時に

$ pwd
/home/sinsoku/rails_proj/app/controllers
$ git cd
$ pwd
/home/sinsoku/rails_proj/

こういう事ができないか?と。

言われるまで気づかなかったけど、便利そうなので調べてみましたが、標準には無いようです。
じゃあ、作りましょう!

仕様

仕様は上述した通りなのですが、もう一つ便利そうなのでこんな仕様を加えてみました。

$ pwd
/home/sinsoku/rails_proj/app/controllers
$ git cd spec
$ pwd
/home/sinsoku/rails_proj/spec

こんな感じのスクリプトの作り方を考えてみました。

迷走

少し調べると、gitのリポジトリは簡単に取得できましたが、ディレクトリの移動が難しい。

の2種類の方法を調べて試してみましたが、上手くcdできない。

bashrcに関数作った

出来れば"git cd"にしたかったけど、難しそうなので諦めて"git_cd"にして.bashrcに関数作りました。

git_cd() {
    path="`git rev-parse --git-dir`/../$1"
    ret=$?
    if [ ${ret} -eq 0 ]; then cd ${path}; fi
}
alias gcd="git_cd"

これでGitの作業ディレクトリでの移動が楽になって、作業が捗りますね!

何、勘違いしているんだ…まだ、俺のバトルフェイズは終了してないぜ…!!

追加スクリプト発動!

#!bash
#
# version: 0.2
# license: New BSD License
#
# how to install
# ===============
#
# * Ubuntu
# $ cp git_cd.bash /etc/bash_completion.d/git_cd

_git_cd()
{
    if [ ${COMP_CWORD} -lt 2 ]; then
        git_repo="`git rev-parse --git-dir 2>/dev/null`"
        ret=$?
        if [ ${ret} -ne 0 ]; then return 0; fi
        if [ "${git_repo}" == ".git" ]; then
            git_root="`pwd`/"
        else
            git_root="${git_repo%.git}"
        fi

        cur="${COMP_WORDS[COMP_CWORD]}"
        if [ -n "${cur##*/}" ]; then
            cur_d="${cur}/"
        else
            cur_d="${cur}"
        fi

        dist="${git_root}${cur_d}"
        if [ -d "${dist}" ]; then
            subs=`compgen -d "${dist}"`
            opts=`for s in ${subs}; do echo ${s#${git_root}}; done`
        else
            subs=`compgen -d "${dist%/[^/]*}/"`
            opts=`for s in ${subs}; do echo ${s#${git_root}}; done`
        fi

        COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
    fi
}
complete -F _git_cd git_cd
complete -F _git_cd gcd

このスクリプトを入れておくと、bash-completionでディレクトリの補完が効くようになります。
たぶんgit_cdの関数本体よりもこの補完スクリプトの方が時間かかってる。

git submodule を100倍便利にするpost-commit hook

追記[2011/11/20]

  • submodule のコミット書き換えは上手く動いていないので、該当箇所を取り消し線で削除

100倍は釣りです。
深夜にgit helpを読みながら構築した黒魔術の構築式がこちらです。

git submoduleについて

gitで外部リポジトリを扱うコマンドです。
詳しくはgit submodule --help、もしくは参考ページを読んでください。

これは何?

親プロジェクトのGitリポジトリで変更をコミットした時、追加している全てのサブモジュールでも同時にコミットを行います。
コミットメッセージは親プロジェクトのコミットのコミットメッセージとsha-1になり、下記のようになります。

commit 5cd4716a7bb8b38557e11886256b878d004bd82e
Author: sinsoku <sinsoku.listy[at]gmail.com>
Date:   Thu Oct 27 02:28:27 2011 +0900

    親プロジェクトのコミットメッセージ
    
    This related commit ec7fa14290d651c02b27b6d151d7aac86b3ba2d4

注) 同時に行うのはコミットだけなので、submoduleのpushは手動です。

post-commit.for_submodule

コードはGitHubに置いてあります。
post-commitにリネーム&実行権限を追加して、使用したいリポジトリの.git/hooksにコピーして下さい。

sinsoku/dotfiles - GitHub post-commit.for_submodule.sample

Ubuntu 11.04で動作確認しており、Windowsでの動作は未検証です。

  • post-commit.for_submodule.sample (c877bb5)
#!/bin/sh
#
# An example hook script that is called after a successful
# commit is made.
#
# To enable this hook, rename this file to "post-commit".

export supermsg="`git log -n 1 --format="%B" HEAD`"
export pre_submsg="`git log -n 1 --format="%B%nThis related commit %H" HEAD`"
git submodule foreach 'if [ -n "`git status -s`" ]; then git add -A; git commit -m "${pre_submsg}"; fi'

submodules=`cat .gitmodules | grep "\[submodule" | awk 'match($0,/\"[-0-9a-zA-Z_\.\/]*\"/) { print substr($0,RSTART+1,RLENGTH-2) }'`
for m in $submodules; do
if [ -n "`git status -s $m`" ]; then
git add $m
  fi
done

treeobj=`git write-tree`
commitobj=`git commit-tree ${treeobj} -p HEAD^ <<EOF
${supermsg}
EOF`
git reset ${commitobj}

export fix_submsg="`git log -n 1 --format="%B%nThis related commit %H" HEAD`"
git submodule foreach 'if [ "`git log -n 1 --format="%B"`" = "${pre_submsg}" ]; then git commit --amend -m "${fix_submsg}"; fi'

hookの解説

簡単にpost-commit.for_submodule の作り方の説明。

post-commit

  • Pro Gitから引用
コミットプロセスが全て完了した後に、post-commitフックが実行されます。パラメータは必要無く、git log -1
HEADを実行することで直前のコミットを簡単に取り出すことができます。一般的にこのスクリプトは何かしらの通
知といった目的に使用されます。

実行タイミングとしては、ユーザがコミットメッセージを作成し終わった時になります。
後述しますが、post-commit.for_submodule は親コミットを書き換える必要があるため、post-commitにしてあります。

post-commit-hook.for_submodule がやっている事

post-commit-hook.for_submodule は下記のような事をやってます。

  • 親プロジェクトでコミットする。(この後にフックが起動する)
  • 親プロジェクトのコミットメッセージを取得する。
  • 各サブモジュールで変更をコミットする。
  • 親プロジェクトのコミットを書き換え、サブモジュールのsha-1の変更をコミットに含める。
  • 各サブモジュールのコミットを書き換え、親プロジェクトの書き換え後のsha-1をコミットメッセージに追加する。

一回のcommit-hookで、最低でも2回コミットが書き換わるお得なhookです。

post-commit-hook での注意点

git commit

post-commit-hookの中で、git commit を実行すると無限ループに陥るため、使えません。*1
そこで、コミットしたい場合は、git write-treeとgit commit-treeを使ってコミットオブジェクトを作る必要があります。

git submodule foreach

git submodule foreach の中でbashの変数を使う場合、exportにしておく必要があります。

*1:git submodule foreach内のgit commitは別リポジトリになるので、大丈夫です

図で分かるgit-mergeの--ff, --no-ff, --squashの違い

git-merge の--ff, --no-ff, --squashの違いをまとめてみた。

git helpから引用

まずは、git helpを読みましょう

  • git merge --helpから引用(抜粋)
NAME
       git-merge - Join two or more development histories together

SYNOPSIS
       git merge [-n] [--stat] [--no-commit] [--squash]
               [-s <strategy>] [-X <strategy-option>]
               [--[no-]rerere-autoupdate] [-m <msg>] <commit>...
       git merge <msg> HEAD <commit>...
       git merge --abort
OPTIONS
       --ff, --no-ff
           Do not generate a merge commit if the merge resolved as a fast-forward, only update the branch pointer. This is the default behavior of git-merge.

           With --no-ff Generate a merge commit even if the merge resolved as a fast-forward.
       --squash, --no-squash
           Produce the working tree and index state as if a real merge happened (except for the merge information), but do not actually make a commit or move the HEAD, nor record
           $GIT_DIR/MERGE_HEAD to cause the next git commit command to create a merge commit. This allows you to create a single commit on top of the current branch whose effect is the same as
           merging another branch (or more in case of an octopus).

           With --no-squash perform the merge and commit the result. This option can be used to override --squash.
       --ff-only
           Refuse to merge and exit with a non-zero status unless the current HEAD is already up-to-date or the merge can be resolved as a fast-forward.

fast-forward(ファーストフォワード)って何?

マージの説明の前に、まずはGitのfast-forwardについて。

  • 入門Git p.230 から引用
  2つのコミットAとBとがあるときに、コミットAにいたる歴史がコミットBにいたる歴
史にすべて含まれている場合、2つのコミットはファーストフォワードの関係にある、と
か、コミットAはコミットBにファーストフォワードする、といいます。

少し分かりにくいので、絵にしてみた。

fast-forwardな関係

  • コミットAの歴史: X -> A
  • コミットBの歴史: X -> A -> Y -> B

コミットBの歴史に、コミットAの歴史が全て含まれている。

fast-forwardでない関係

  • コミットAの歴史: X -> Y -> A
  • コミットBの歴史: X -> Y -> Z -> B

コミットBの歴史に、コミットAの歴史が含まれていない(Aのコミットが含まれていない)

各マージの解説

本題の各マージについて。

0. マージ前の状態

マージ前は下記のようなコミットグラフだったとします。

この時、各オプションを指定した場合のマージ結果が1〜3です。

1. --ff

デフォルトの設定です。
もしマージ対象のブランチとfast-forwardの関係にある場合、マージコミットは作られず、ブランチの参照先の更新だけを行います。
(fast-forwardの関係でない場合、マージコミットが作られます)

$ git merge topic


2. --no-ff

fast-forwardの関係であっても、必ずマージコミットを作ります。

$ git merge --no-ff topic


3. --squash

ワークツリーとインデックスの状態はマージ後の状態になりますが、マージコミットは作りません。

$ git merge --squash topic

マージコミットが作られないため、--squashの場合は自分でコミットする必要があります。

$ git commit

各オプションの使い分けの前に

まずはプロジェクトのリポジトリの運用について決める必要があります。

  • コミットの粒度
  • rebase は必須であるか?
  • トピックブランチのマージの時にマージコミットを作るか?

などを決め、プロジェクトのポリシーに沿ったコミットログを作るのが良いと思います。

オプションの使い分け

私はこうやって使っている・・・という例を紹介します。
他の便利な例や突っ込みがあれば、コメントで。

--ff

ログが一本になり、トピックブランチがあったという情報がなくなる。
トピックブランチの存在をログに残す必要がなく、そのままマージしてもコミット粒度が問題無い場合に使う。

--no-ff

--ffと異なり、トピックブランチがあったという情報が残る。
基本的には「A successful Git branching model」を参考に--no-ffを使うのが良い。

--squash

トピックブランチがあった情報も、コミットログも全て残らない。
トピックブランチで行った作業が少なく、1コミットにまとめてしまっても問題ない場合に使う。

git mergeで常に--no-ffにする

ついでに、常に--no-ffにする設定を紹介します。
この方法は2つあります。

方法1. エイリアスを使う

エイリアスを設定し、--no-ffのマージを行う時はgit nffmerge を使うようにする。

$ git config --global alias.nffmerge "merge --no-ff"

方法2. mergeoptions

マージオプションを設定すると、指定したブランチのマージでは設定値がデフォルトとして使用されます。はブランチ名。

git config branch.<name>.mergeoptions "--no-ff"

ただし、この設定を行うとgit pull の実行時にもマージコミットが出来てしまい、マージコミットが大量に出来てしまうため、リモートの変更を取得したい時はgit pull --rebase を使う必要があります。

特定のブランチで必ずpull --rebase を有効にする

次のコマンドで設定できる。

$ git config branch.<name>.rebase true
全てのブランチでpull --rebase を有効にする

もし全てのブランチで設定したい場合、次のコマンドで設定する。

$ git config branch.autosetuprebase always

RSpecでArelのメソッドチェインをスタブにする

テストでメソッドチャインできるメソッドをスタブにする。

describe BlogsController do
  before do(:all)
    u = User.new
    User.stub_chain(:where, :limit, :order).and_return(u)
  end
end

こういう書き方も出来る

describe BlogsController do
  before do(:all)
    u = User.new
    User.stub_chain([:where, :limit, :order] => u)
  end
end

テスト対象のControllerの親クラスのfilterをskipする

テスト対象のControllerのActionのみをテストする時に便利。

describe BlogsController do
  before do
    superclass = controller.class.superclass
    super_filters = superclass._process_action_callbacks.map(&:filter)
    controller.class.skip_filter *super_filters
  end
end

少しだけキモいコードになってしまいました。
もっと素敵な書き方があれば、コメントやブコメで教えて頂けるとありがたいです。

RubyでFizzBuzzを書いてみた

ネタは前回の記事のデコレータ式と同じだけど、Rubyだとこんな感じになる。
余計なメソッドが多いのは、なんとなくRubyっぽいからです。特に意味はない。

仕様

前回と同じ

1から100までの数をプリントするプログラムを書け。ただし3の倍数のときは数の代わりに「Fizz」と、5の倍数のときは「Buzz」とプリントし、3と5両方の倍数の場合には「FizzBuzz」とプリントすること。

テストコード

#!/usr/bin/env ruby
# -*- coding: utf-8 -*-
require './fizzbuzz'

  
describe "FizzBuzz" do
  describe "one" do
    subject { 1 }
    it { should_not be_fizz }
    it { should_not be_buzz }
    it { should_not be_fizzbuzz }
    its(:fizzbuzz) { should == '1' }
  end
  describe "three" do
    subject { 3 }
    it { should be_fizz }
    it { should_not be_buzz }
    it { should_not be_fizzbuzz }
    its(:fizzbuzz) { should == 'Fizz' }
  end 
  describe "five" do
    subject { 5 }
    it { should_not be_fizz }
    it { should be_buzz }
    it { should_not be_fizzbuzz }
    its(:fizzbuzz) { should == 'Buzz' }
  end
  describe "fifteen" do
    subject { 15 }
    it { should_not be_fizz }
    it { should_not be_buzz }
    it { should be_fizzbuzz }
    its(:fizzbuzz) { should == 'FizzBuzz' }
  end
end

テスト実行結果

FizzBuzz
  one
    should not be fizz
    should not be buzz
    should not be fizzbuzz
    fizzbuzz
      should == "1"
  three
    should be fizz
    should not be buzz
    should not be fizzbuzz
    fizzbuzz
      should == "Fizz"
  five
    should not be fizz
    should be buzz
    should not be fizzbuzz
    fizzbuzz
      should == "Buzz"
  fifteen
    should not be fizz
    should not be buzz
    should be fizzbuzz
    fizzbuzz
      should == "FizzBuzz"

Finished in 0.00265 seconds
16 examples, 0 failures

FizzBuzzの実装

#!/usr/bin/env ruby
# -*- coding: utf-8 -*-

class Numeric
  def fizz?
    not fizzbuzz? and self % 3 == 0
  end
  def buzz? 
    not fizzbuzz? and self % 5 == 0
  end
  def fizzbuzz?
    self % 3 == 0 and self % 5 == 0
  end
  def fizzbuzz
    if fizz?
      'Fizz'
    elsif buzz?
      'Buzz'
    elsif fizzbuzz?
      'FizzBuzz'
    else
      self.to_s 
    end
  end
end

if __FILE__ == $0
  (1..100).each { |x| puts x.fizzbuzz }
end

fizzbuzz.rb を実行すると、「1, 2, Fizz, ... , 98, Fizz, Buzz」が標準出力に表示される。