よんちゅBlog

― このブログは自分用のメモや日々の問題などを共有するためのものです ―

20121005185841 お知らせ:  2013/07/17 ブログデザインをリニューアルしました。

gitで変更のあったファイルを簡単に開ける git-edit について

とにかく目的のファイルにすぐにアクセスしたかった。
ただそれだけ。

コードはGithubにあります。
github - yonchu/git-edit

インストール方法:

  • git-edit をPATHの通った所に置くだけ
  • zshを使用している場合は _git-edit をfpathの通った所に置いて下さい。

 (※ fpath は echo "$fpath" などで確認して下さい)

どういうコマンドなのか

変更のあったファイルをエディタで開くための gitサブコマンドです。

コマンド形式は以下

$ git edit [commit] [file ...]

こんな感じで使います。

$ git edit
$ git edit HEAD
$ git edit hoge
$ git edit *
$ git edit *.txt
$ git edit HEAD^ dir/*

コミットの指定はハッシュ値でも構いません。

zshの補完機能との連携

今回一番やりたかったこと。

git-edit単体でも、まあそれなりに使えますがzshと併用することで格段に使い勝手がよくなる…と思います。たぶん

こんな感じで変更のあったファイル名が補完できます。

$ git edit <TAB>
Editable Files
xxxx.txt    yyyy.sh    zzzz.c
Deleted Files
hoge.txt

$ git edit HEAD <TAB>
Commit Hash:62c91ef1f56d89b3328fa4aeae4bb52b579ad679
Editable Files
aaaa.txt    bbbb.sh    cccc.c
Deleted Files
fuga.txt
zshの設定について

zshの設定によっては上記とは違った表示になるかも。

補完候補の一覧表示はデフォルトでも有効になっているはずだけど、説明項目(ラベル)はデフォルトでは表示されないはず。

説明項目(ラベル)を表示させるには "~/.zshrc" 等に以下のように設定します。

zstyle ':completion:*' group-name ''
zstyle ':completion:*:descriptions' format '%B%d%b'

%B はボールド(太字)の有効、%b はボールドの無効 (不要なら消して下さい)
%F{color name} と %f で色を変更することもできます。
%d が説明項目(ラベル)の指定

注意事項

  • submodule の変更は無視されます。そこまではちょっと…
  • ファイル名をリネームした場合はリネーム前のファイル名が Deleted FIles に表示されます
  • HEADなどのコミットオブジェクトは補完されません。これはそのうち対応するかも

以上

ちょっと説明がざっくりし過ぎているが、そんな需要があるとも思えないのでよしとしよう。

bash/zshで16色(ANSI カラーコード)と256色のカラーパレットを表示

bashzshでカラーパレットを表示するスクリプト

なんだか定期的に色を変えたくなる…

プロンプトの色やtmux/screenのステータスバーの色変更時のお共に。

yonchu/shell-color-pallet · GitHub

256色カラーパレットを表示

こんな感じに表示されます。
f:id:yonchu:20121020045036p:plain

ANSI のカラーコード(16色)を表示

こんな感じに表示されます。
f:id:yonchu:20121019042417p:plain

スクリプトのコメントにエスケープシーケンスの説明を記載しているので、設定するときに参照すると良いかと思います。

【Mac】zshをサブシェルで起動するとPATHがおかしくなる

どういう状態かというと。

★サブシェル起動前
$ printpath    ← $PATHを表示
/Users/yonchu/.pythonbrew/bin
/Users/yonchu/.rvm/gems/ruby-1.9.3-p286/bin
/Users/yonchu/.rvm/gems/ruby-1.9.3-p286@global/bin
/Users/yonchu/.rvm/rubies/ruby-1.9.3-p286/bin
/Users/yonchu/.rvm/bin
/usr/local/share/python
/usr/local/mysql/bin
/Library/Java/Home/bin
/Users/yonchu/bin
/Users/yonchu/dotfiles/bin
/usr/local/bin
/usr/local/sbin
/usr/local/share
/usr/bin
/bin
/usr/sbin
/sbin
/usr/X11/bin
$
$ zsh    ← サブシェルとして zsh を起動
$
★サブシェル起動後
$ printpath    ← $PATHを表示
/Users/yonchu/.pythonbrew/bin
/Users/yonchu/.rvm/gems/ruby-1.9.3-p286/bin
/Users/yonchu/.rvm/gems/ruby-1.9.3-p286@global/bin
/Users/yonchu/.rvm/rubies/ruby-1.9.3-p286/bin
/Users/yonchu/.rvm/bin
/usr/bin
/bin
/usr/sbin
/sbin
/usr/local/bin
/usr/X11/bin
/usr/local/share/python
/usr/local/mysql/bin
/Library/Java/Home/bin
/Users/yonchu/bin
/Users/yonchu/dotfiles/bin
/usr/local/sbin
/usr/local/share

(※ printpathはPATHを整形して出力してるだけです)

という感じです。
意味分かりませんね。

原因

何が問題なのかというと、/etc/zshenv に致命的な誤りがありました。
普段ユーザが触れるようなファイルではないので、え?と思うかもしれません。

この問題の原因に対して、homebrewのzshで以下のような注意書きがありました。

$ brew info zsh
zsh: stable 5.0.0
http://www.zsh.org/
Depends on: gdbm, pcre
/usr/local/Cellar/zsh/5.0.0 (956 files, 9.1M) *
https://github.com/mxcl/homebrew/commits/master/Library/Formula/zsh.rb
==> Options
--disable-etcdir
        Disable the reading of Zsh rc files in /etc
==> Caveats
To use this build of Zsh as your login shell, add it to /etc/shells.

If you have administrator privileges, you must fix an Apple miss
configuration in Mac OS X 10.7 Lion by renaming /etc/zshenv to
/etc/zprofile, or Zsh will have the wrong PATH when executed
non-interactively by scripts.

Alternatively, install Zsh with /etc disabled:

  brew install --disable-etcdir zsh

homebrewでzshを入れている方はこの注意書きに気づいたでしょうか。
自分は全然気づきませんでした…

対処方法

説明に書かれている通り、対処方法は2種類

  • /etc/zshenv を /etc/zprofile にリネームする
  • brew install --disable-etcdir zsh で/etc 無効版をインストールする

今回私は1を採用しました。
(1の方が今後気にすることが少なそうなので)

$ ls -l /etc/zshenv
-r--r--r--   1 root wheel  126 2012-04-06 03:56 zshenv
$ sudo mv /etc/zshenv /etc/zprofile
$ ls -l /etc/zprofile
-r--r--r--   1 root wheel  126 2012-04-06 03:56 zprofile
以下、自分用のメモを兼ねて説明

まず、zsh起動時に読み込まれる設定ファイルの読み込み順序についてです。
zshの設定ファイルは種類が多いので非常に分かりづらいです。

zshを起動する方法によって以下のように分けられます。

ログインシェルとして起動した場合

 /etc/zshenv
 $ZDOTDIR/.zshenv
 /etc/zprofile
 $ZDOTDIR/.zprofile
 /etc/zshrc
 $ZDOTDIR/.zshrc
 /etc/zlogin
 $ZDOTDIRA/.zlogin

インタラクティブシェル(対話シェル)として起動した場合
(GUIターミナル/ssh/サブシェルなど)
(※ MacのGUIターミナルはログインシェル扱いみたいです)

 /etc/zshenv
 $ZDOTDIR/.zshenv
 /etc/zshrc
 $ZDOTDIR/.zshrc

ノンインタラクティブシェル(非対話シェル)として起動した場合
(シェルスクリプト/リモートシェルなど)

 /etc/zshenv
 $ZDOTDIR/.zshenv

※ $ZDOTDIRを設定していない場合は、$HOMEになります。

今回問題となったのはサブシェルなので2番目のケースですね。

続いて問題の /etc/zshenv の中身です。

$ cat /etc/zshenv
# system-wide environment settings for zsh(1)
if [ -x /usr/libexec/path_helper ]; then
        eval `/usr/libexec/path_helper -s`
fi

path_helper というコマンドを起動しているだけのようです。

path_helper というのは man によると、

DESCRIPTION
The path_helper utility reads the contents of the files in the directories /etc/paths.d and /etc/manpaths.d and appends their contents to the PATH and MANPATH
     environment variables respectively.  (The MANPATH environment variable will not be modified unless it is already set in the environment.)

     Files in these directories should contain one path element per line.

     Prior to reading these directories, default PATH and MANPATH values are obtained from the files /etc/paths and /etc/manpaths respectively.

     Options:

     -c      Generate C-shell commands on stdout.  This is the default if SHELL ends with "csh".

     -s      Generate Bourne shell commands on stdout.  This is the default if SHELL does not end with "csh".

ということだそうです。

要するにデフォルトのPATHを設定しますってことですね。
デフォルトで設定されるパスは、/etc/paths に書かれているパスと /etc/paths.d 配下にあるファイルの中に書かれているパスです。

私のMac(OS X 10.7)では以下のようなパスが書かれていました。

$ cat /etc/paths
/usr/bin
/bin
/usr/sbin
/sbin
/usr/local/bin

$ cat /etc/paths.d/*
/usr/X11/bin

ここで私のPATH設定環境について補足しておくと、
基本的に、PATHは ZDOTDIR/.zprofile で設定しています。
しかし、一部 pythonbrew や rvm などのPATH設定処理を含んでいるスクリプトを $ZDOTDIR/.zshrc で読み込んでいます。

サブシェル起動時のPATH設定の流れ

以上を踏まえて、サブシェル起動時にどのようにPATHが設定されているかを考えてみます。
おそらく以下のような流れになっていると思われます。

  1. /etc/zshenv がデフォルトパスを設定
  2. 親シェルで元々設定されていたPATHを設定(環境変数の引き継ぎ)
  3. ZDOTDIR/.zshrc に書かれているPATHを設定

ここで1つ注意すべき点があります。
PATHを設定する際、重複したパスが存在すると"左"に書かれているパスが優先して設定され、"右"にあるパスは削除されます。
この重複パスを削除する機能は、通常 "typeset -U path" を行なっていないと有効になりませんが、2番目の親シェルから引き継いだパスを設定する際は、"typeset -U" を行なっていなくても重複パスが削除されるようになっています。

この重複削除によって、一見なぜあんな並び方になったのかが分かりづらい状態になってしまったわけです。

また、.zprofile や .zlogin を使用せず、PATH設定を全て .zshrc で行なっている場合は、サブシェル起動時でも .zshrc でPATHを上書きできるため問題が表面化しづらくなっています。
しかし、/etc/zshenv はサブシェルなどのインタラクティブシェルだけでなく、通常のスクリプトをzshで起動した場合にも読み込まれてしまうため、問題になる可能性があります。
(シェバングでzshを指定している場合や、zshを指定してスクリプトを起動する場合など)

Macでzshを使用している方はお気をつけ下さい。

zshのchpwdの設定

今回もzshネタです。

zshではchpwd関数を使用してcd後に自動でlsを実行するというのはもはやお馴染みなわけですが、移動先のディレクトリ配下のファイル数があまりにも多いと、画面から溢れて残念な結果になったりします。

そこでchpwdを以下のようにすることでファイル数が多い場合にも良い感で表示されるようになります。

これでファイル数が多い場合は以下のように表示されるようになります。

[yonchu@macbook:~]$ cd /usr/bin
./                      dprofpp5.12*                       jcmd@                         pg_config*         snmpdf*
../                     dropdb*                            jconsole@                     pg_dump*           snmpget*
2to3*                   droplang*                          jdb@                          pg_dumpall*        snmpgetnext*
2to3-*                  dropuser*                          jhat@                         pg_restore*        snmpinform*
2to3-2.7@               drutil*                            jinfo@                        pg_upgrade*        snmpnetstat*
...
dispqlen.d*             javadoc@                           perlthanks*                   snmp-bridge-mib*   zipsplit*
ditto*                  javah@                             perlthanks5.10*               snmpbulkget*       zless*
dns-sd*                 javap@                             perlthanks5.12*               snmpbulkwalk*      zmore*
dprofpp*                javatool*                          pfbtops*                      snmpconf*          znew*
dprofpp5.10*            javaws@                            pg_basebackup*                snmpdelta*         zprint*
1098 files exist
[yonchu@macbook:/usr/bin]$

OSの種類に応じて実行するlsコマンドを変更したりもしています。
また、Macの場合はglsをインストールすることをオススメします。*1

―――
上記の設定は私のgithub(yonchu/dotfiles)にて公開しています。
良かったら参考にして下さい。

*1:homebrewを使用している場合はcoreutilsをインストールするとglsが使用できるようになります

zshのプロンプトにgitの状態表示 - 未add/未commit/未push/untracked(未追跡)に対応

zshのプロンプトにgitの状態(add/commit/push)を表示する方法に関しては、ここここを参考にしています。

今回はそこにプラスしてUntracked(未追跡)なファイルが存在しているかどうかも分かるようにzshのプロンプトに表示させます。
さらに状態を表す記号は全てブランチ名の”前”に付けるようにしました。
その他、git init直後などでHEADがまだ存在しない場合にも正常に表示されるようにするための対応、リモートリポジトリが存在しない場合に未pushと認識しないように対応したました。

っでこんな感じになりました。



.zshrcに設定する内容はこんな感じ

### zshプロンプト設定
# カラーの設定を$fg[red]のように人がわかるような書き方ができる
autoload -Uz colors
colors

#
# Color定義(あとで変更しやすいように)
#
DEFAULT=$'%{\e[0;0m%}'
RESET="%{${reset_color}%}"
GREEN="%{${fg[green]}%}"
BOLD_GREEN="%{${fg_bold[green]}%}"
BLUE="%{${fg[blue]}%}"
BOLD_BLUE="%{${fg_bold[blue]}%}"
RED="%{${fg[red]}%}"
BOLD_RED="%{${fg_bold[red]}%}"
CYAN="%{${fg[cyan]}%}"
BOLD_CYAN="%{${fg_bold[cyan]}%}"
YELLOW="%{${fg[yellow]}%}"
BOLD_YELLOW="%{${fg_bold[yellow]}%}"
MAGENTA="%{${fg[magenta]}%}"
BOLD_MAGENTA="%{${fg_bold[magenta]}%}"
WHITE="%{${fg[white]}%}"


setopt prompt_subst
autoload -Uz add-zsh-hook

#
# Gitの状態表示
#
# 記号について
#   - : WorkingTreeに変更がある場合(Indexにaddしていない変更がある場合)
#   + : Indexに変更がある場合(commitしていない変更がIndexにある場合)
#   ? : Untrackedなファイルがある場合
#   * : remoteにpushしていない場合
#
autoload -Uz vcs_info

zstyle ':vcs_info:*' enable git svn hg bzr
zstyle ':vcs_info:*' formats '(%s)-[%b]'
zstyle ':vcs_info:*' actionformats '(%s)-[%b|%a]'
zstyle ':vcs_info:(svn|bzr):*' branchformat '%b:r%r'
zstyle ':vcs_info:bzr:*' use-simple true

autoload -Uz is-at-least
if is-at-least 4.3.10; then
    # zshが4.3.10以上の場合
    zstyle ':vcs_info:git:*' check-for-changes true
    zstyle ':vcs_info:git:*' stagedstr "+"
    zstyle ':vcs_info:git:*' unstagedstr "-"
    zstyle ':vcs_info:git:*' formats '%s,%u%c,%b'
    zstyle ':vcs_info:git:*' actionformats '%s,%u%c,%b|%a'
fi

function _update_vcs_info_msg() {
    psvar=()
    LANG=en_US.UTF-8 vcs_info
    local _vcs_name _status  _branch_action
    if [ -n "$vcs_info_msg_0_" ]; then
        _vcs_name=$(echo "$vcs_info_msg_0_" | cut -d , -f 1)
        _status=$(_git_untracked_or_not_pushed $(echo "$vcs_info_msg_0_" | cut -d , -f 2))
        _branch_action=$(echo "$vcs_info_msg_0_" | cut -d , -f 3)
        psvar[1]="(${_vcs_name})-[${_status}${_branch_action}]"
    fi
    # 右側プロンプト
    RPROMPT="${RESET}%1(v|${RED}%1v|)${RESET}${BOLD_YELLOW}${VIRTUAL_ENV:+($(basename "$VIRTUAL_ENV"))}${RESET}[${MAGENTA}%D{%Y/%m/%d %H:%M:%S}${RESET}]${RESET}"
}
add-zsh-hook precmd _update_vcs_info_msg

#
# Untrackedなファイルが存在するか未PUSHなら記号を出力
#   Untracked: ?
#   未PUSH: *
#
function _git_untracked_or_not_pushed() {
    local git_status head remotes stagedstr
    local  staged_unstaged=$1 not_pushed="*" untracked="?"
    # カレントがgitレポジトリ下かどうか判定
    if [ "$(git rev-parse --is-inside-work-tree 2> /dev/null)" = "true" ]; then
        # statusをシンプル表示で取得
        git_status=$(git status -s 2> /dev/null)
        # git status -s の先頭が??で始まる行がない場合、Untrackedなファイルは存在しない
        if ! echo "$git_status" | grep "^??" > /dev/null 2>&1; then
            untracked=""
        fi

        # stagedstrを取得
        zstyle -s ":vcs_info:git:*" stagedstr stagedstr
        # git status -s の先頭がAで始まる行があればstagedと判断
        if [ -n "$stagedstr" ] && ! printf "$staged_unstaged" | grep "$stagedstr" > /dev/null 2>&1 \
            && echo "$git_status" | grep "^A" > /dev/null 2>&1; then
            staged_unstaged=${staged_unstaged}${stagedstr}
        fi

        # HEADのハッシュ値を取得
        #  --verify 有効なオブジェクト名として使用できるかを検証
        #  --quiet  --verifyと共に使用し、無効なオブジェクトが指定された場合でもエラーメッセージを出さない
        #           そのかわり終了ステータスを0以外にする
        head=$(git rev-parse --verify -q HEAD 2> /dev/null)
        if [ $? -eq 0 ]; then
            # HEADのハッシュ値取得に成功
            # リモートのハッシュ値を配列で取得
            remotes=($(git rev-parse --remotes 2> /dev/null))
            if [ "$remotes[*]" ]; then
                # リモートのハッシュ値取得に成功(リモートが存在する)
                for x in ${remotes[@]}; do
                    # リモートとHEADのハッシュ値が一致するか判定
                    if [ "$head" = "$x" ]; then
                        # 一致した場合はPUSH済み
                        not_pushed=""
                        break
                    fi
                done
            else
                # リモートが存在しない場合
                not_pushed=""
            fi
        else
            # HEADが存在しない場合(init直後など)
            not_pushed=""
        fi

        # Untrackedなファイルが存在するか未PUSHなら記号を出力
        if [ -n "$staged_unstaged" -o -n "$untracked" -o -n "$not_pushed" ]; then
            printf "${staged_unstaged}${untracked}${not_pushed}"
        fi
    fi
    return 0
}


状態を表す記号は以下のようになっています。

- : WorkingTree変更がある場合(Indexにaddしていない変更がある場合)
+ : Indexに変更がある場合(commitしていない変更がIndexにある場合)
? : Untrackedなファイルがある場合
* : remoteにpushしていない場合(リモートが設定されている場合のみ)


記号を変更したい方は

zstyle ':vcs_info:git:*' stagedstr "+"
zstyle ':vcs_info:git:*' unstagedstr "-"

local  staged_unstaged=$1 not_pushed="*" untracked="?"

の行の記号を変更して下さい。

記号の出力位置とかが気に食わないよって方は

psvar[1]="(${_vcs_name})-[${_status}${_branch_action}]"

の部分を修正して下さい。

上記の設定は私のgithub(yonchu/dotfiles)にて公開しています。
良かったら見てみたください。