cp --parentsでパスごとコピーする

デプロイ用のファイルとかをコピーしてるとパス付きでまるっとコピーしたくなることがあると思うんだけど、そういう時は--parentsを使うとよい。


状況としてはこんな感じ。

$ tree hoge
hoge
└── fuga
    ├── bar.txt
    ├── foo.txt
    └── piyo.txt       ←これだけコピーしたい

1 directory, 3 files

piyo.txtが更新されたのでこのファイルだけデプロイ対象として持っていきたい。
hoge以下全部持っていくのならcp -R hoge rel-20110628とかでまるっとコピーすればいいけど、foo.txtとbar.txtは巻き込みたくない。


普通にコピーすると……

$ cp -v hoge/fuga/piyo.txt rel-20110628
`hoge/fuga/piyo.txt' -> `rel-20110628/piyo.txt'
$ tree rel-20110628
rel-20110628
└── piyo.txt

0 directories, 1 file

こんな感じでpiyo.txtがrel-20110628にコピーされる。ですよねー。
まぁ当たり前といえば当たり前。


で、--parentsを付けると

$ cp --parents -v hoge/fuga/piyo.txt rel-20110628
hoge -> rel-20110628/hoge
hoge/fuga -> rel-20110628/hoge/fuga
`hoge/fuga/piyo.txt' -> `rel-20110628/hoge/fuga/piyo.txt'
$ tree rel-20110628
rel-20110628
└── hoge
    └── fuga
        └── piyo.txt

2 directories, 1 file

こうなる。コピー元のパスがそのままコピーされる。

GNU coreutils

ちなみにこのオプションはBSD系にはないので、GNU coreutilsとか入れればいいと思うよ!

MacBookにPHP環境作ってみた

そういや自宅のMacBookにはPHP環境作ってないなーと思ったので作ってみた。
Mac OS Xには最初からApachePHPが入っているのだけど、モジュールとかどうなってるのかよくわからないし、せっかくなら最新版にしたいな、ということでソースからビルドしてみた。


ぐだぐだ書いても仕方ないので成功した内容しか書かないけど、PHPコンパイルがとにかくうまく行かなくて3日ぐらいはまってた。
1回10分ぐらいのコンパイルをトライ&エラーで両手でかぞえきれないぐらいやったのでもう疲れたよ……。

Apacheのインストール

まずはApacheのインストール。
ソースを落としてきてこんな感じにコンパイル&インストール。
付けるオプションはお好みで。とりあえずローカル開発用ってことで選り好みはhttpd.confですることにして共有ライブラリで全部入り*1にしてみた。

./configure \
--prefix=/usr/local/apache2 \
--enable-mods-shared="all ssl proxy dav-lock" \
--enable-static-support \
--with-mpm=prefork \
--with-included-apr
make
make install

httpd.confの設定とかは人それぞれ好みがあると思うので割愛。
標準のApacheは使う気がないので/usr/local/apache2/binにパス通して、sudo apachectl startで動作確認。
ポート1023以下はrootじゃないとbindできないので注意。


ここまではいつも通りで簡単な話だった……ここまでは。

PHPのインストール

さて、ここからが本番。
とりあえずつけたかったオプションに足りないライブラリを入れる。各自適当に。

  • libjpeg
  • libmcrypt
  • libxml2
  • icu4c

自分はhomebrewを使ってるのでbrewコマンドで入れた。
ただし他のライブラリはotool -Lで依存ライブラリを見るとフルパスになってるのに、icu4cだけは相対パスになってるようなので、DYLD_LIBRARY_PATHとかをいじらないとmake時や実行時にdylibが見つからない的なエラーが出る。

これは/usr/local/Cellar/icu4c/4.4.1/{lib,bin,sbin}あたりを片っぱしからotool -Lで確認しつつ、install_name_tool -idとか-changeで書き換えていくというしんどい作業で解決した。
なんか一括でやってくれるスクリプトとかどっかに転がってた。忘れたけど。


で、ようやくconfigureに入るんだけど、なんかこんな環境変数を設定しないとうまくいかないっぽい。

MACOSX_DEPLOYMENT_TARGET=10.6
CFLAGS="-arch x86_64 -g -Os -pipe -no-cpp-precomp"
CCFLAGS="-arch x86_64 -g -Os -pipe"
CXXFLAGS="-arch x86_64 -g -Os -pipe"
LDFLAGS="-arch x86_64 -bind_at_load"
EXTRA_CFLAGS="-lresolv"
EXTRA_LIBS="-lstdc++"

実際に叩いたコマンドはこんな感じ。
例によってオプションはお好みで。自分はとりあえずめぼしいのを共有ライブラリとして指定。一部うまくいかなかったのは静的に組み込んだ。
homebrewじゃない場合は$(brew --prefix foo)の部分は各自でパスを指定してね。

MACOSX_DEPLOYMENT_TARGET=10.6 \
CFLAGS="-arch x86_64 -g -Os -pipe -no-cpp-precomp" \
CCFLAGS="-arch x86_64 -g -Os -pipe" \
CXXFLAGS="-arch x86_64 -g -Os -pipe" \
LDFLAGS="-arch x86_64 -bind_at_load" \
EXTRA_CFLAGS="-lresolv" \
EXTRA_LIBS="-lstdc++" \
./configure \
--prefix=/usr/local/php-5.3.6 \
--with-config-file-path=/usr/local/php-5.3.6/etc \
--with-config-file-scan-dir=/usr/local/php-5.3.6/etc/php.d \
--with-apxs2=/usr/local/apache2/bin/apxs \
--enable-mbstring \
--enable-mbregex \
--enable-zend-multibyte \
--with-pdo-mysql=shared,mysqlnd \
--with-mysql=shared,mysqlnd \
--with-mysqli=shared,mysqlnd \
--with-mysql-sock=/var/mysql/mysql.sock \
--with-gd=shared \
--with-jpeg-dir=$(brew --prefix libjpeg) \
--enable-exif=shared \
--with-png-dir=/usr/X11 \
--with-zlib=shared \
--with-bz2=shared \
--enable-zip=shared \
--with-openssl=shared \
--enable-sockets=shared \
--with-curl=shared \
--enable-ftp=shared \
--with-mcrypt=shared,$(brew --prefix libmcrypt) \
--enable-bcmath=shared \
--with-libxml-dir=$(brew --prefix libxml2) \
--with-iconv=shared,/usr \
--enable-intl \
--with-icu-dir=$(brew --prefix icu4c)

でmake時にもこんなのがいるっぽい。

EXTRA_CFLAGS="-lresolv"

実際に叩いたコマンドはこんな感じ。

EXTRA_CFLAGS="-lresolv" make
EXTRA_CFLAGS="-lresolv" make install

ちなみにOSデフォルトのApacheに入れる場合は

  • /usr/libexec/apache2/libphp5.so

あたりが上書きされると思うので注意。やったことないからわからないけど。


無事インストールできたら、やっぱり標準の方は使う気がないので/usr/local/phpとかにリンク張って、こっちにパス通して、php.ini書いて、動作確認。

php -i

httpd.confに追加してphpinfo()とか書いて動作確認。

LoadModule php5_module        modules/libphp5.so
<FilesMatch \.php$>
    SetHandler application/x-httpd-php
</FilesMatch>
DirectoryIndex index.php index.html

*1:といいつつ、ldapとかは入れてないけど。

日に何度もある更新作業にpatchを使う

テストフェーズでの不具合修正のリリースがとにかく面倒くさくなってきたので、patchコマンドを活用してみためもめも。

日々の作業

日々の作業はこんな感じ。

  1. 不具合を修正する。
  2. 修正したファイルをアーカイブしてサーバに持っていく。
  3. 更新ファイルのバックアップを取る。
  4. 念のため差分を確認してから上書き反映。
  5. (ここで待ったがかかる or 戻してって言われる。)
  6. (前のファイルに戻す。)


イケてる職場だとデプロイツールとかで一発だったりするんだろうけど、サーバ上で操作できることが制限されてるのでそうはいかない。
ファイルを固めては多段sshを乗り越えて持っていって差分を確認しつつ反映、という実際に何度もやるとなるとクソ面倒くさい作業をしている。
ファイル数が多いとバックアップを取ったり差分を確認するだけでも大変だしミスも増えてくる。

patchコマンドを使った改善

みんなこういう作業をやってるんだけど、自分は3回ぐらいでもう気が狂いそうなほど耐えられなくなったので、なんとか上手いことやれないか、と考えたのが以下の方法。

  1. 修正内容のパッチファイルを作る(git diffとかで)。
  2. パッチファイルをサーバに持っていく。
  3. patch --dry-runで適用確認。
  4. patchを適用。
  5. 戻せって言われたらpatch -Rで戻す。


別に画期的な方法でもなんでもなくてむしろ昔ながらの方法なんだけど、これだと必要なファイルも1つで済むし、コマンド一発で適用/逆適用ができるので作業が格段に楽になる。ミスも減る。
サーバだけ設定ファイルの一部が書き換えてある、みたいな状況でもうっかりファイル全体を上書きしたりせず該当箇所だけパッチできるのでトラブルも減る。


具体的にはこんな感じ。

git checkout -b issue_123

    ... edit ... commit ...

git diff --no-prefix master >issue_123.patch

こうしてパッチを作り*1、サーバに持っていって、

cd 反映先のルート
patch -p0 --dry-run <issue_123.patch    # --dry-runで確認
patch -p0 <issue_123.patch              # 問題なさそうなら反映

こんな感じで反映する。
戻せーって言われたら、

cd 反映先のルート
patch -p0 -R --dry-run <issue_123.patch    # --dry-runで確認
patch -p0 -R <issue_123.patch              # 問題なさそうなら反映

こんな感じで戻す。
-Rは(間違って作って)新旧が逆になってるパッチを当てるためにあるっぽいんだけど、まぁ元に戻したいのでこれでいいかなと。


不具合の調査とかで一時的にログを増やしたりコードを変更したい場合なんかも、こうやってpatch一発で切り替えられるようにするとよい。
あ、gitのところはお使いのVCSツールに読み替えてください。diff -Naurとかでもいいけど。


一点注意が必要なのはpatchをどこで適用するか、patchファイル内のパス、-pとは何か、というのをちゃんと知ってないと上手くパッチを当てられなくてハマるところかな。
このへん初めてだと難しいよね。

まとめ

とにかくこの方法にすると、

  • 対象ファイルが複数でも1ファイルで管理できる。
  • コマンド一発で全部反映できる。
  • バックアップしてなくてもパッチの逆適用で一発で戻せる。
  • 少しぐらいファイルの内容が違ってても空気を読んでくれる。

と作業が格段に楽になる。
もうcp -rとかdiffとかガチャガチャやって作業ミスすることもなくなる。

ところで自分の場合は

実は上の通りのコマンドは使ってなくて普段はこんな感じ。

git diff master >issue_123.patch

patch -d 反映先ルート -p1 -i issue_123.patch --dry-run
patch -d 反映先ルート -p1 -i issue_123.patch

patch -d 反映先ルート -p1 -i issue_123.patch -R --dry-run
patch -d 反映先ルート -p1 -i issue_123.patch -R

理由は--no-prefixとかつけるの面倒くさいし、-p1のほうが慣れてるから。
-dは場所を間違わないために明示的に指定しておく。
--dry-runとか-Rを付けたり削ったりはコマンドラインの最後のほうが編集しやすいから、リダイレクトではなく-iを使う感じ。

あわせて学びたい

  • diffコマンド
  • patchutils

*1:パッチの作り方はキミ次第!

tig(gitのほう)でGを押してもgcが走らないようにする

tigといえば一部のvimmerの間では事あるごとにGを押してコーヒーブレイクに突入するというのが流行ってるらしいんだけど、実はこのキーバインドは.tigrcで変更できるのだった。


.tigrcに

bind generic G move-last-line

みたいに書けば変更できる。


割り当てられる機能は他にもあるし、キーバインド以外にも設定項目が色々ある(詳しくはtigrc(5))。
マニュアルによるとこの設定はgitの設定ファイルの[tig "bind"]セクションでも設定できるらしい。

pushし忘れないようにプロンプトに表示するようにした

SubversionからGitに乗り替えてからというものコミット漏れ/忘れが激減(Changed but not updatedとかUntracked filesって出るし)したんだけど、今度はpush忘れをするように……。
なんかローカルでコミットした時点で満足しちゃうんだよね。で、帰宅してから同期しようと思ったら何も流れてこなくてうわぁぁぁぁぁ!!て。


コミットしたらすぐpushっていうのはrebaseとかしづらくなっちゃうからちょっとやだし、実はgit-svnで運用してるのでイライラで爆発しちゃいそう。
なので帰宅前に忘れずにpush(dcommit)できればそれでいいんだけど、毎日定時で帰れるほど平和な生活してないのでアラームを仕掛けるという案もいまいち。


で、色々考えた結果、プロンプトにpush済みかそうでないか表示できたらいいかも!と思ったので早速やってみた。
git bash completionについてくる__git_ps1()に付け加える形でこんな風にしてみた。


※2011-07-22 remotesがない場合や初期化直後でHEADがない場合の判定を追加。

__git_not_pushed()
{
  if [[ "$(git rev-parse --is-inside-work-tree 2>/dev/null)" = "true" ]]; then
    head="$(git rev-parse --verify -q HEAD 2>/dev/null)"
    if [[ $? -eq 0 ]]; then
      remotes=($(git rev-parse --remotes))
      if [[ -n "${remotes[@]}" ]]; then
        for x in ${remotes[@]}
        do
          if [[ "$head" = "$x" ]]; then
            return 0
          fi
        done
        echo -n " NOT PUSHED"
      fi
    fi
  fi
  return 0
}

# git bash completion
if [ -f ~/git-completion.bash ] ; then
  source /Users/pasela/git-completion.bash

# 前のやつ
#  PS1='\[\033]0;$MSYSTEM:\w\007
#\033[32m\]\u@\h \[\033[33m\w$(__git_ps1)\033[0m\]
#$ '

# __git_not_pushedを追加したやつ
  PS1='\[\033]0;$MSYSTEM:\w\007
\033[32m\]\u@\h \[\033[33m\w$(__git_ps1)\033[31m\]$(__git_not_pushed)\033[0m\]
$ '
fi

これで


pasela@pasela.org ~/repos/foo (master) NOT PUSHED
$

pushしてない時はこんな感じにNOT PUSHEDって出る。
検出方法は見ての通りものすごく適当だけど、とりあえず今の自分には十分。
あまり複雑なことやりすぎるとプロンプトが激重になって発狂しそうだし。




実はシャットダウンを検出して(WindowsならWM_QUERYENDSESSIONとか)そのタイミングで調べてはどうかと考えたこともあったんだけど、ちょっとどうなの?って感じがしたのでやめた。

git-osx-installerでMacにgitを入れる

実は最初はMacPortsでgitを入れてたんだけど、perl5に依存するのが嫌で別の方法を探ってgit-osx-installerに乗り換えた。
perl5に依存するのが嫌だった理由はMacPortsのperl5が5.8系で、もっと新しいバージョンを使いたかったからというものなんだけど、あとから調べてみたらvariantsで+perl5_10とか+perl5_12を指定すれば5.10や5.12を入れられることがわかったので、今となってはgit-osx-installerでなくてもいいかもしれない。
けど、せっかくなのでgit-osx-installerでの環境構築手順を残しておくことにする。

git-osx-installerを落としてきて入れる

git-osx-installerのサイトからインストーラを落としてきてインストールする。それだけw
名前からしてわかるとおり普通のインストーラになっているのでボタンをぽちぽち押していくだけでインストールできる。git-svnも使える。

git-completion.bashを読み込む

拍子抜けするほど簡単にインストールが終わったものの、bash用のcompletionは何故かついてこないみたいなので、本家リポジトリあたりからcontrib/completion/git-completion.bashをパクってきて読み込む。

.profile.bashrcにこんな感じで追加してみた。
(2010-11-20追記:.profileだとscreen起動したときに戻っちゃうので.bashrcにした)

# git bash completion
if [ -f ~/git-completion.bash ] ; then
  source /Users/pasela/git-completion.bash
  PS1='\[\033]0;$MSYSTEM:\w\007
\033[32m\]\u@\h \[\033[33m\w$(__git_ps1)\033[0m\]
$ '
fi

実は補完よりもこのプロンプトが目当てだったりする。

tigを入れる

MacPortsならtigもあるのだけど、git-osx-installerで入れてしまったのでtigも自前で入れることにする。
だってMacPortsでtig入れたらgitが入ってperl5が(ry


入れ方はCygwinの時とほぼ同じでこんな感じ。
ncurseswだけはちゃっかりMacPortsのお世話になっておくw

sudo port install ncursesw

cd ~/tmp
wget tigのtarball ... gitで最新版持ってきてもいいかも
tar zxf tigのtarball
cd tig-x.x.x
LIBS=-lncursesw LDFLAGS=-L/opt/local/lib CPPFLAGS=-I/opt/local/include ./configure
make
make install

ncurseswだけMacPortsというのは気持ち悪いという人は別途入れてそっちを指定してね。

tigを入れる(homebrew編)

2010-11-20追記

brew install ncursesw
brew install tig

先にncurseswを入れておいたら大丈夫だった。

Cygwinのtigは日本語が文字化けするので自分でコンパイルした

git(に限らないけど)でリポジトリの履歴をさかのぼりながらdiffを見たりするにはgitkを使えばいいんだけど、こいつはTk製のGUIアプリなのでssh接続してるリモートサーバ上では使うのが難しい。*1
そんな時に便利なのがncursesベースのtigというツールで、これならCUI環境でも実行できるのでssh越しでも使えるし、そうでなくても普段からターミナルで作業しててマウスとか持ち替えたくねーという不満を解消してくれる。


というわけで、丁度TL上で話題になってたんでCygwinにも入れてみたんだけど、パッケージのやつだとどうも日本語が文字化けしてしまう。
普通にgitコマンド使う分には文字化けなんてしないしgit自体には問題はないと思うんだけど、git show | tigとかでも文字化けしちゃうのでncurses周りかなーと思ってlddしたらやっぱりncursesにリンクしてたので、ソース落としてきてncurseswにリンクし直すようにしたら解決した。



※2011/11/11 久しぶりに新しいの(tig-0.18)落としてきてビルドしようと思ったら、なんかLIBS=-lncurseswじゃダメだったので修正。


配布元の説明書きにはLDLIBS=-lncurseswをつけるんだぜみたいに書いてあるけど、つけてもCygwinだとだめっぽかったのでこんな感じでいけた。

cd ~/tmp
wget tigのtarball ... gitで最新版持ってきてもいいかも
tar zxf tigのtarball
cd tig-x.x.x
LDFLAGS=-L/usr/lib/ncursesw CPPFLAGS=-I/usr/include/ncursesw ./configure
make
make install

これで日本語でも文字化けしないようになった。Cygwinでも使えるようになった!
ちなみにUbuntuでは普通にパッケージで入れたやつで使えてます。



※以下はなんか古いバージョンでは通ったけどいつの間にかダメになってたケース。


配布元からソースを落としてきて、説明書きにあったように-lncurseswを付けてビルドしてみる。*2

cd ~/tmp
wget tigのtarball ... gitで最新版持ってきてもいいかも
tar zxf tigのtarball
cd tig-x.x.x
LIBS=-lncursesw ./configure
make
make install

*1:X11 ForwardingとかCygwin/XとかXmingとか使えば……

*2:付けずにビルドしてみたらやっぱり文字化けした。