2013年1月8日火曜日

[vim] なんかvimがセグメンテーションフォルト吐いたんですけど。。

この記事はVim Advent Calendar 2012の39日目の記事です。
昨日は, @bool_fool さんの、「はじめてVimプラグインを書いた話」でした。

背景


いぜん、こんなのをやってみた。
[vim] 別のファイル開いた時、Ctrl+tで元のファイルに戻れたら便利だと思う

その時の心境はこんな感じ↓

① ctag使ってジャンプした後、元のファイルにCtrl+tで戻るの便利すぎて、ウケるw
② 別のファイル見たいけど、もう画面分割するには小さすぎるから、上に開いてしまう
③ 元、見てたファイルを見たくなる
④ 癖でCtrl+tって押したけど、「E73: タグスタックが空です」って出て、イラっとする
⑤ バッファから探そうとするけど、いっぱい開いて合って、ファイル名思い出すのもめんどくさい
⑥ ファイル開いたあと、Ctrl+tで、元開いてたファイルに戻れたらいいのにとおもう。

どうやったかは、上の記事みてください。。
すごくあれな方法だってるのはわかってます。わかってます。
でも方法が見つからなかったんです。。

まぁ、できるようになったし、いいかなって思ってたけど、その後、問題が発覚。
タグジャンプ、戻る、タグジャンプ、戻るを何度も繰り返していると、
「Vim: 致命的シグナル SEGV を検知しました449C セグメンテーション違反です」と出てしまう。。
途中で落ちるとかマジかんべん。でもこの機能あったらほんとに便利だし使いたい。。

ということで今回は、なぜこのような現象が起きるのか、調べて見ることにした。
この記事、どうしても中途半端に終わる気しかしない。

現象の再確認



とりあえず、どんな現象が起きているかを再確認する。
何度も繰り返していると、いかの2パターンがあることがわかった。

① セグメンテーションフォルト


「Vim: 致命的シグナル SEGV を検知しました」と言われる。

② 致命的なシグナル。


「Vim: 致命的シグナル ABRT を検知しました」と言われ、
「b771b000-b7736000 r-xp 00000000 08:01 193426 /lib/ld-2.11.3.so」とか、
「b7717000-b7718000 rw-p 00003000 08:01 1599098 /usr/lib/gconv/EUC-JP.so」とかが、
いっぱい吐かれて、ターミナルから操作不能になる。

どっちの現象が起きるかに規則性は確認できない。


どんな操作をした時に発生するかもまとめる。

① 2つのファイル間で、Ctrl+]、Ctrl+tを繰り返し続けた時、発生する。
② Ctrl+]でファイルをさかのぼり続けたときも、発生する。
③ ファイルを開き続けて、Ctrl+tで戻り続けても、発生しない。
④ ファイルひらいて、Ctrl+]でジャンプして、を繰り返すと発生。発生するタイミングは不定期。

Ctrl+]がきっかけとなっている模様。

原因を突き止める方法を探す



① gdb でデバッグ。


ここでやってるみたいに、gdbを使ってデバッグ出来ないものかな?。
gdb を用いたデバッグ方法

vimを開いて、そのpsをattachしてみたが、attatchすると「Cannot access memory at address 0x8508428f」と言われ、開いているvimは操作できなくなる。
gdbについて、何か根本的に勘違いしてるのかな。。

② 各操作で、タグスタックの中身がどうなっているか見てみる。



記事の設定をしなかった場合。

【Ctrl+]でタグジャンプ】
1 1 BaseActivityLog 17 lib/model/ActivityLog.php

【Ctrl+tで戻る】
1 1 BaseActivityLog 17 class ActivityLog extends BaseActivityLog {

【Ctrol+]でもう一度タグジャンプ】
1 1 BaseActivityLog 17 lib/model/ActivityLog.php
2 1 Persistent 10 ./lib/model/om/BaseActivityLog.php

【ファイルオープン】
なし

記事の設定をした場合。

【Ctrl+]でタグジャンプ】
1 1 tag_for_add_tagstack 17 lib/model/ActivityLog.php

【Ctrl+tで戻る】
1 1 tag_for_add_tagstack 17 lib/model/ActivityLog.php
2 1 tag_for_add_tagstack 10 ./lib/model/om/BaseActivityLog.php

【Ctrol+]でもう一度タグジャンプ】
1 1 tag_for_add_tagstack 17 lib/model/ActivityLog.php
2 1 tag_for_add_tagstack 10 ./lib/model/om/BaseActivityLog.php

【ファイルオープン】
1 1 tag_for_add_tagstack 2 lib/model/ActivityLog.php

違いといえば、「Ctrl+tで戻った時の中身」と「タグが全部同じになっている」ことの2つか。

結論


だめだ。よくわからん。タイムアップ・・・。

結局、なにも解決せずに、この記事を締めくくることに、痛惜の念を禁じ得ない。
そして、絶対もっといい方法あるはずなんだ。

明日は、@c0hamaさんです。

2012年11月24日土曜日

[vim] 別のファイル開いた時、Ctrl+tで元のファイルに戻れたら便利だと思う

背景


ソース読むときなり、プログラム書くときなり、タグジャンプはよく使ってるな。というか使わんとやってられん。今のこの環境、結構満足できてるんだけど、ひとつ不満なことがある。それは、別のファイル開いたら、さっきまで開いてたファイルがどれだったか、わかんなくなってしまうこと。そこまで深くないソースなら、それほど開くバッファ多くないから不便は無いが、深いソースになってくるとbuffer内に10個くらい溜まってきて、さっき見てた奴どれかわからなくなる。

うまく定着するかはわからんが、対策を一つ思いついた。今開いてるwindowからバッファが離れるタイミングで、タグスタックにそのバッファを登録しておけば、別のファイル開いてもタグでジャンプしたみたいな感覚で、Ctrl+t押せば元のファイルに戻れるのではないか?。これやってみるか。

しかし、タグスタックに対象を追加する方法が見つからない。そんなコマンドヘルプにも無いし、ぐぐってもいいの出てこない。いやでも絶対やり方あるでしょ、と思って調べ続けるけどない。。仕方ない、力技でいく。

方法は、「BufWinLeaveするときに、どっか適当なファイルにジャンプして、そのあと別のバッファを開く」というやりかた。ジャンプすれば、タグスタックに追加されるってなら、どこでもいいからジャンプしちゃえばいいじゃんってやりかた。・・・絶対、もっと良いやり方あるよな。。でも、できないよりはマシか。。

やってみた感



vimrcには以下の感じで設定する。
"----------------------------------------------------------+
" add currnet file to tagstack
"----------------------------------------------------------+

autocmd BufWinLeave .vimrc call AddTagstack()
autocmd BufWinLeave *.vim call AddTagstack()
autocmd BufWinLeave *.py call AddTagstack()
autocmd BufWinLeave *.php call AddTagstack()
autocmd BufWinLeave *.markdown call AddTagstack()

set tags+=/home/karino/.foraddtagstack
function! AddTagstack()
    execute "tag tag_for_add_tagstack"
endfunction
そして、/home/karino/.foraddtagstackは以下の感じ。
tag_for_add_tagstack ~/.foraddtagstack /^tag_for_add_tagstack/;" f
とりあえず、できるようにはなった。。
なかなか便利だ。
でも、なんていうか。絶対もっといい方法あるはず。。

2012年11月12日月曜日

[git] コンフリクトしたところだけ、vimdiffで分割して表示してほしい

背景


よくコンフリクトする気がする。

そんなとき普通、ファイル開いて、コンフリクトしてるところ検索して、解消する。でも結構めんどくさい。コンフリクトしてる箇所が1行とか2行程度ならいいけど、数十行にわたってコンフリクトしてるのを解消するのは結構しんどい。そんなときはやっぱりvimdiffとかで開いて、コンフリクトを解消したいと思うところ。

調べてみると,こんなやり方が見つかる。
Three-way merging for git using vim
なんかそれっぽいけど、このやり方だと「変更のもとになるファイル」(BASE)と、「LOCALの変更ファイル」(LOCAL)、「REMOTEの変更ファイル」(REMOTE)を一緒に表示しているだけ。つまり、「コンフリクトが発生している状態のファイル」(MERGED)で、コンフリクト箇所調べて、解消するのは変わりない。

やりたいのは、自動でマージできたところはそのままで、コンフリクトしたところだけ分割して、vimdiffで表示すること。ということで書いてみた。


やりかた


基本的なやり方は参考リンクと同じ。mergetool設定して、mergeする時それを呼び出す。違うところは、単純にvimdiffで開くのではなく、MERGEDファイルのコンフリクトしてる箇所を分割したファイルを作り、そのファイルとdiffをとっているところ。

mergetoolはこんな感じのを準備する。
#!/bin/bash

function fixpatch(){
    basefile=${1}
    tmp="tmp.php"
    tmp2="tmp2.php"

    cp $basefile $tmp
    if [ "${2}" = "local" ]; then
        sed "/>>>>>>>/d" $basefile > $tmp
        start_array=($(cat $tmp  | grep -n "<<<<<<<" | cut -d ':' -f 1))
        end_array=($(cat $tmp | grep -n "=======" | cut -d ':' -f 1))
    elif [ "${2}" = "remote"  ]; then
        sed "/<<<<<<</d" $basefile > $tmp
        start_array=($(cat $tmp  | grep -n "=======" | cut -d ':' -f 1))
        end_array=($(cat $tmp | grep -n ">>>>>>>" | cut -d ':' -f 1))
    fi

    cat $tmp > $tmp2

    # コンフリクト箇所を省く.
    for (( i=0; i<${#start_array[@]}; i++ ))
    do
        cp $tmp2 $tmp
        num=$((${#start_array[@]} - ${i} - 1))
        sed -e "${start_array[$num]},${end_array[$num]}d" $tmp > $tmp2
    done

    cat $tmp2
    rm $tmp
    rm $tmp2
    return 0
}

# ブランチ名を取得
BRANCH=$(git branch --no-color | sed -n -e 's/^\* //p')
gitroot="$(git rev-parse --show-toplevel)/"

# mergeに関するファイルを取得
MERGED=${1}
LOCAL=${2}
BASE=${3}
REMOTE=${4}

# MERGEDファイルのコンフリクト箇所を除外
fixedpatch_local="fixedpatch_local.php"
fixedpatch_remote="fixedpatch_remote.php"
fixpatch ${MERGED} "local" > $fixedpatch_local
fixpatch ${MERGED} "remote" > $fixedpatch_remote

echo ${fixedpatch_local}
echo ${fixedpatch_remote}

cp ${fixedpatch_local} ${MERGED}


# vimdiffで開く
vim -f \
    -c 'set nofoldenable' \
    -c 'wincmd p'         \
    -c 'set nofoldenable' \
    -c 'wincmd p'         \
    -c 'noremap J ]c'     \
    -c 'noremap K [c'     \
    -c "set tags+=.tags;${gitroot}"  \
    -d $MERGED $fixedpatch_remote

# ゴミファイルを削除
rm $fixedpatch_local
rm $fixedpatch_remote


そして、mergetoolに設定する。これは、参考リンクとほぼ同じ。
git config --global mergetool.vimdiff3.cmd 'git_merge_wrapper3 "$MERGED" "$LOCAL" "$BASE" "$REMOTE"'
git config --global mergetool.keepBackup false
git config --global merge.tool vimdiff3

使い方


コンフリクトしてるときに、git mergetoolを叩くと、コンフリクトしているファイルを開き、コンフリクトしている箇所を分割して、vimdiffで表示する。


karino@goldfish ~/src/trunk $ git st
# Not currently on any branch.
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
#       modified:   lib/model/PersonGuildMember.php
#
# Unmerged paths:
#   (use "git reset HEAD <file>..." to unstage)
#   (use "git add/rm <file>..." as appropriate to mark resolution)
#
#       both modified:      apps/main/config/template.yml
#       both modified:      apps/main/modules/guild/actions/actions.class.php
#       both modified:      config/schema.yml
#       both modified:      lib/util/GuildUtil.php
#
karino@goldfish ~/src/trunk $ git mergetool 
Merging the files: apps/main/config/template.yml
apps/main/modules/guild/actions/actions.class.php
config/schema.yml
lib/util/GuildUtil.php

Normal merge conflict for 'apps/main/config/template.yml':
  {local}: modified
  {remote}: modified
Hit return to start merge resolution tool (vimdiff3): 

ここでenter押せばvimdiffが表示される。左側がworkingtreeのファイルで、右側がREMOTE側のファイル。diffの時と同じ用に、Shift-j、Shift-kで差分にジャンプできるようにしている。僕の好み。これで、コンフリクトなんか怖くない。


2012年11月10日土曜日

[git] diffはvimdiffで開こうよ

背景


diffはvimdiffで開きたい。


やり方


やりかたは、ほとんど以下のまんま。
vimdiffの開き方だけちょっと変えた。
Git Diff with Vimdiff


① .gitconfigに設定

以下を.gitconfigに書いておけば、
git diffした時、git_diff_wrapperが呼ばれることになる。

[diff]
    external = git_diff_wrapper
[pager]
    diff =

もしくは、以下をコマンドラインで叩いても可。
git config --global diff.external git_diff_wrapper
git config --global pager.diff ""


② git_diff_wrapperを準備


以下を、git_diff_wrapperという名前で、PATH通っているところに置いておく。


変更点1:折りたたまれないようにする。

ただ、普通にvimdiff開くと、どうしても折りたたまれてしまう。
.vimrcにnofoldable書いても効果なし。
無理やり開くとき設定するようにした。


変更点2:タグファイルを読み込む

プロジェクト毎の読み込むtagファイル変更しているが、
その際、開くファイルのパスを見て、どのtagファイルを読むか切り替えている。
でも、vimdiffでtmpファイル開くとtagが読み込まれないため、
tmp側ではジャンプできなくなってしまう。

コレまた無理やりだけど、gitrootの取得して、その下にあるtagファイルを読み込む用にした。
これでtmpで開いてる側でもジャンプできるようになった。


#!/bin/sh

BRANCH=$(git branch --no-color | sed -n -e 's/^\* //p')
gitroot="$(git rev-parse --show-toplevel)/"

vim -c 'set nofoldenable' \
    -c 'wincmd p'         \
    -c 'set nofoldenable' \
    -c 'wincmd p'         \
    -c 'noremap J ]c'     \
    -c 'noremap K [c'     \
    -c "set tags+=.tags;${gitroot}"  \
    -d "${2}" "${5}"

2012年11月3日土曜日

[git] masterブランチにpushできる人を制限したい

背景



今の開発環境を軽く説明。
テストサーバに共有レポジトリがおかれていて、開発メンバはそこから、cloneしてきたローカルレポジトリで開発をおこなう。ブランチの構成は、masterブランチ1本と、機能ブランチが複数本。

開発を始める際、機能ブランチを切ってもらい、そこにガンガンコミットしていく。テスト環境で動作を確認する場合は、その機能ブランチを共有レポジトリにpushすれば、自動でdeployされて、確認できるようになる。

また、進捗状況の確認とかにも使えるかなとも思うので、メンバには定期的に共有レポジトリにpushしてもらっている。もし、masterに何かしらの更新がかかった場合、機能ブランチをrebaseしてもらっている。そして、開発が完了し、動作確認できたら、masterにmergeしていく。


このように、開発メンバはちょくちょく共有レポジトリにpushする。
しかし、gitに慣れていないメンバもいるので、間違って、ぶっ壊れた履歴を、masterにpushしちゃうこともある。

まぁ機能ブランチであれば、もし間違ったコミット達をpushしたとしても、gitなら割と簡単に履歴修正できるし、取り返しのつかない履歴上げられたとしても、最悪、masterからブランチ切り直して、必要なコミットだけ、cherry-pickで持っていく事もできる.

しかし、masterを壊されると結構厄介な事になりそう。
機能ブランチは、適度なタイミング(メンバが気づいた時)、masterの変更を取り込む作業をしてもらっているので、もし壊れたmasterを共有された場合、その影響が各機能ブランチまで波及してしまう可能性がある。

そのため、機能ブランチはいいけど、masterはpushされたくない。ということで、masterへのpushができるユーザを制限したかった。


方法


似たようなことやってるところ。
[Git] Git でサーバに認証したユーザと Author が同じであることを強制するフックスクリプト
やり方としては、pushされた時に走るhookを仕込んで置いて、
masterブランチへのpushの時だけ、
接続してきたユーザが許可されたユーザであればpushを実行、それ以外なら失敗させる。


pushを止めるhookとしては、「pre-receive」と「update」がある模様。
「pre-receive」は、push毎に実行される。
「update」は、pushが更新するリファレンス毎に実行される。
今回やりたいのは、masterブランチへのpushだけ止めて、
その他のブランチへのpushは許可したいので「update」を使ってみる。


ソース


① ユーザの取得

参考にしたところでは、environmentを使って環境変数を設定していたが、
今の僕の仕事上rootを持っていないので、この方法は使えない。
が、他の案件の人が、command使ってやっているのをみて、丸パクりした。

.ssh/authorized_keysには以下。
command="if test -n \"$SSH_ORIGINAL_COMMAND\"; then SSH_CLIENT_USER=karino-t eval $SSH_ORIGINAL_COMMAND; else SSH_CLIENT_USER=karino-t  exec /bin/bash -l ; fi" ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDCMi+sgejbfTsc8FR2fL+yIP2vbVCcwE5th0cI8HB9NG5NEe6VwH8VfbxVq7I/VOST09Ww+sWm6BgZCewNyJfV9GNsfEOVOC93VEeTDLudKzB2I+21ybvVa1A+/PtFdMY4trOeRMc6HwTbt0uwpVhZLJjGJxISoAoH9clF8I4x85qlXZeOsePoyW55uwHpyoojB+UhIxuzG60+mQutmMwgAQ5OF1tSBqAjB7fKVOievjKq/AZ8hGW3HuR5Yj6XE8RaUPWzu91wEqF/FSXrS0i8nUp2+ZZ2Loapu9xVhFxRASacVBLrwV+BnNhcC5lIV0/lClttVxY2eLCXxeVr4EFx karino@goldfish


② update hook

これで、masterにpushする時のみ止められることになる。
多分問題無いはず。


#!/bin/sh

BRANCH=${0}

echo
echo "SSH_CLIENT_USER : ${SSH_CLIENT_USER}"
echo "target branch   : ${1}"

                                           
PERMIT_USER=("karino-k" "karino-n" "karino-k")
STATUS=1                                   
                                           
# master以外はpush OK
if [ ! `echo ${1} | grep 'master'` ]; then
    STATUS=0
fi

# 特定ユーザはpush OK
for USER in ${PERMIT_USER[@]}
do
    if [ ${SSH_CLIENT_USER} = ${USER} ]; then
        STATUS=0
    fi
done

# エラーメッセージ
if [ $STATUS = 1 ]; then
    echo
    echo "You don't allow to push master"
    echo "Please contact to karino-t"
    echo
fi

exit ${STATUS}

2012年11月2日金曜日

[git] bashでプロンプトにブランチ名表示したら、git branch叩く回数がめっちゃ減った

背景


gitでブランチをいっぱい切って運用してる場合、ブランチの移動を頻繁に行う。
そして、ちゃんと自分が今何処のブランチで作業しているかを把握していないと、
機能ブランチだと思って、コミットしたらmasterブランチだったとかになってしまう。
結果、git branchを叩きまくることになる

と言っても、やっぱりめんどくさい。
そこで、よくやられていることかもだけど、
プロンプトにブランチ名表示した。
めっちゃ便利。これ使う用になってから、git branch叩く回数が極端に減った。
もう、これないと死ぬ。

参考URL
zsh で Git の作業コピーに変更があるかどうかをプロンプトに表示する方法
Gitとプロンプト変数PS1とbash_completionと

ソース


2つ目の参考URLのやり方だと上手く行かなくて、
いろいろ試行錯誤した結果、下の形に落ち着いた.
やらなきゃいけないことは、.bashrcに以下を書き込めば使えるはず。
ちなみに、indexに変更がある場合は赤、
working treeに変更がある場合は黄色、
なんの変更もない場合は青いろになるようになっている。

#!/bin/bash
# プロンプトにブランチ名表示
function parse_git_branch {
  RED="\e[0;31m"
  YELLOW="\e[33m"
  GREEN="\e[0;32m"
  BLUE="\e[0;34m"
  NONE="\e[0m"

  NOW_BRANCH=$(git symbolic-ref HEAD 2> /dev/null | cut -d/ -f3)
  IND_STAT=$(git status --porcelain 2> /dev/null | grep -v "?" | cut -d" " -f1 )
  WTR_STAT=$(git status --porcelain 2> /dev/null | grep -v "?" | cut -d" " -f2 )

  if [ "${IND_STAT}" != "" ]; then
    [[ ${NOW_BRANCH} ]] && echo -e "${RED}[${NOW_BRANCH}]${NONE}"

  elif [ "${WTR_STAT}" != "" ]; then
    [[ ${NOW_BRANCH} ]] && echo -e "${YELLOW}[${NOW_BRANCH}]${NONE}"

  else
    [[ ${NOW_BRANCH} ]] && echo -e "${BLUE}[${NOW_BRANCH}]${NONE}"

  fi

}
export PS1='\u@\h \w $(parse_git_branch)$ '

2012年11月1日木曜日

[git] pushしたらdeployしてほしいよね

背景

pushしたとき、自動でdeployしてくれるhookスクリプトが欲しかった。


post-update hook


git push した際、push先のレポジトリで実行される。
設定してあるhookスクリプトには、リファレンス名が引数として渡ってくる。
同じようなものでpost-receiveフックがあるが、
post-receiveは、更新されるリファレンスの数にかかわらず一回実行。
post-updateは、対象になるリファレンス毎に実行される模様。



ソース


すごく単純。いま使っているデプロイコマンドは引数にブランチ名を取るので、
pushされた時渡ってくる引数から、ブランチ名を取得して、引き渡している。
これをサーバ側レポジトリに、.git/hooks/post-updateという名前で保存して、
実行権限持たせておけば、OK
pushしたら自動でdeployしてくれる。

BRANCH=$(git rev-parse --symbolic --abbrev-ref $1)
echo ${BRANCH}
deploy ${BRANCH}