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}