2019/09/21

git clean を使って untracked なファイルを削除する

Mercurial には hg purge というコマンドがあって、 hg で control していないファイルを一括削除できます。
具体的には add してないファイルとか、.c から作った .o とか、間違って作った Rails の migration とか。
それを Git でもできないかなー、と調べたメモ。


環境

  • OS: macOS Mojave 10.14.6
  • Git: 2.23.0


git clean

hg purge のようなコマンドは Git にも default でも存在していて、git clean だそうです。

untracked なファイルを消す際は
  • $ git clean -fd
とかすると add してないファイルが消えます。

あとは、すぐに消すのではなく、消す対象が何かを教えてくれる -n (--dry-run) option が便利です。
  • $ git clean -fdn
で削除対象一覧を出してくれます。ちなみに
  • git clean -f -d -n
と書いても良いみたいです。

他には 
  • -X option: .gitignore で指定されたファイルのみを消す
    • .o とかが消えてくれる。
    • あと untracked なファイルは消えない
    • 新規にファイルを作った上で rebuild したい時に便利だよ、とのこと
  • -x option: .gitignore で指定されたファイルも削除対象 + untracked のファイルも削除対象
    • かなり危険なので事前に dry-run を付けた方が良いです
    • git clean -fdx とかすると clone 直後くらい綺麗な状態になります
とかですかね。
さらに詳細を知りたい場合は man git-clean を読んで頂ければ。


参考

2019/09/16

Terminal.app を使って mac を sleep させる

Terminal から mac を sleep させる方法無いかなー、と思ったのでそのメモ。


経緯

私は普段 MacBookPro + Display + Keyboard で作業をしています。
この状態からそのまま MBP を閉じると clamshell mode になってしまいます。
なので MBP を sleep -> MBP を閉める という流れで全体を sleep させています。
MBP を sleep させるには Shift + Control + 電源ボタン の shortcut を使うことが多いです。

しかし、たまに MBP を sleep させずに閉じてしまう時があります。
外付け keyboard には電源ボタンが無い。ので  sleep させられない。
そのせいで一旦閉じた MBP を再度開いて sleep させて閉じる、とかやっていたのですがちょっと面倒。

ということで、外付け Keyboard だけで clamshell mode の MBP を sleep させる方法た無いかな、と思って調べました。


環境

  • OS: macOS mojave 10.14.6 Version (18G95)


コマンド

  • $ pmset sleepnow
sleep させることができます。思ったより楽だった。


2019/09/01

neovim で plugin を利用せずに sudo を使う

root 権限が必要なファイルを neovim や vim で編集する事があると思います。
その際、 sudoers で許可されているユーザならば
  • :write ! sudo tee % > /dev/null
で sudo を使って root として書き込むことができます。

ですが、 neovim では
  • :write ! sudo tee % > /dev/null
    • sudo: no tty present and no askpass program specified
    • shell returned 1
と言われて sudo が使えません。


解決策

以下のようなコマンドを定義することで対応できます。
function! s:sudo_write_current_buffer() abort
if has('nvim')
let s:askpass_path = '/tmp/askpass'
let s:password = inputsecret("Enter Password: ")
let $SUDO_ASKPASS = s:askpass_path
try
call delete(s:askpass_path)
call writefile(['#!/bin/sh'], s:askpass_path, 'a')
call writefile(["echo '" . s:password . "'"], s:askpass_path, 'a')
call setfperm(s:askpass_path, "rwx------")
write ! sudo -A tee % > /dev/null
finally
unlet s:password
call delete(s:askpass_path)
endtry
else
write ! sudo tee % > /dev/null
endif
endfunction
command! SudoWriteCurrentBuffer call s:sudo_write_current_buffer()
このコマンドは vim と neovim の両方で利用できます。

vim で利用する場合は ~/.vimrc に追記します。
neovim で利用する場合は ~/.cofig/nvim/init.vim に追記します。


使い方

  • $ nvim /etc/hosts
    • root 権限が必要なファイルを開いて編集します。保存の際は
  • :SudoWriteCurrentBuffer
    • を実行します。
    • パスワードを入力すれば root で書き込む事ができます。


動作確認をした環境

大元の環境(vim と neovim で動作確認済)
  • OS: macOS Mojave 10.14.6 (18G95)
  • Homebrew: 2.1.11
    • Homebrew/homebrew-core: (git revision 883ee; last commit 2019-09-01)
  • neovim: v0.3.8
  • vim: 8.0.1365
  • sudo: version 1.8.17p1
    • Sudoers policy plugin version 1.8.17p1
    • Sudoers file grammar version 45
    • Sudoers I/O plugin version 1.8.17p1
  • docker: 19.03.1, build 74b1e89

    docker を使っていくつかの distribution でも動作を確認
    • Alpine linux: 3.10.2
      • neovim: v0.3.7
      • vim: 8.1.1365
      • sudo: version 1.8.27
        • Sudoers policy plugin version 1.8.27
        • Sudoers file grammar version 46
        • Sudoers I/O plugin version 1.8.27
    • CentOS: release 7.6.1810 (Core)
      • vim: 7.4.1099
      • sudo: version 1.8.23
        • Sudoers policy plugin version 1.8.23
        • Sudoers file grammar version 46
        • Sudoers I/O plugin version 1.8.23
    • Fedora: release 30
      • vim: 8.1.1912
      • sudo: version 1.8.27
        • Sudoers policy plugin version 1.8.27
        • Sudoers file grammar version 46
        • Sudoers I/O plugin version 1.8.27
    • Ubuntu: 18.04.3
      • vim: 8.0.1453
      • sudo: version 1.8.21p2
        • Sudoers policy plugin version 1.8.21p2
        • Sudoers file grammar version 46
        • Sudoers I/O plugin version 1.8.21p2
    • Debian: 10.0 (buster-20190812)
      • vim: 8.1.1401
      • sudo: version 1.8.27
        • Sudoers policy plugin version 1.8.27
        • Sudoers file grammar version 46
        • Sudoers I/O plugin version 1.8.27


    何故 neovim では sudo が使えないのか

    ここから先は先程の Vimscript を作るに至った経緯を書きます。
    興味のある方はご覧ください。

    まず sudo の error message
    • sudo: no tty present and no askpass program specified
    を見ると、 tty と askpass が無いと言っています。


    tty と askpass

    実際、vim では
    • : ! tty
      • /dev/ttys004 
    などが得られますが、 neovim では
    • : ! tty
      • not a tty
      • shell returned 1
    と言われてしまいます。

    askpass は使ったことが無いのですが、fedora の container で dnf search すると
    [root@9015bf44beee /]# dnf search askpass
    Fedora Modular 30 - x86_64 1.5 MB/s | 1.9 MB 00:01
    Fedora Modular 30 - x86_64 - Updates 2.0 MB/s | 2.8 MB 00:01
    Fedora 30 - x86_64 - Updates 5.0 MB/s | 23 MB 00:04
    Fedora 30 - x86_64 5.2 MB/s | 61 MB 00:11
    Last metadata expiration check: 0:00:01 ago on Sat Aug 31 00:41:37 2019.
    =============================== Name & Summary Matched: askpass ================================
    lxqt-openssh-askpass-l10n.x86_64 : Translations for lxqt-openssh-askpass
    lxqt-openssh-askpass.x86_64 : Askpass openssh transition dialog for LXQt desktop suite
    ==================================== Name Matched: askpass =====================================
    R-askpass.x86_64 : Safe Password Entry for R, Git, and SSH
    ksshaskpass.x86_64 : A ssh-add helper that uses kwallet and kpassworddialog
    openssh-askpass.x86_64 : A passphrase dialog for OpenSSH and X
    x11-ssh-askpass.x86_64 : A passphrase dialog for X and not only for OpenSSH
    の様に、いくつかの askpass が確認できます。のでちょっと試してみます。
    • x11-ssh-askpass
    • # dnf install -y x11-ssh-askpass
      # find . -name '*askpass*' -type f
      ./etc/profile.d/x11-ssh-askpass.sh
      ./etc/profile.d/x11-ssh-askpass.csh
      ./usr/libexec/openssh/x11-ssh-askpass
      # /usr/libexec/openssh/x11-ssh-askpass
      Error: Can't open display:
      # /usr/libexec/openssh/x11-ssh-askpass --help
      Error: Can't open display:
      # dnf remove -y x11-ssh-askpass
    • openssh-askpass
    • # dnf install -y openssh-askpass
      # find . -name '*askpass*' -type f
      ./etc/profile.d/gnome-ssh-askpass.csh
      ./etc/profile.d/gnome-ssh-askpass.sh
      ./usr/libexec/openssh/gnome-ssh-askpass
      # /usr/libexec/openssh/gnome-ssh-askpass
      (gnome-ssh-askpass:112): Gtk-WARNING **: 00:51:47.072: cannot open display:
      # /usr/libexec/openssh/gnome-ssh-askpass --help
      (gnome-ssh-askpass:113): Gtk-WARNING **: 00:52:10.596: cannot open display:
      # dnf remove -y openssh-askpass
    • ksshaskpass
    • # dnf install -y ksshaskpass
      # find . -name '*askpass*' -type f
      ./etc/xdg/plasma-workspace/env/ksshaskpass.sh
      ./usr/share/locale/en_GB/LC_MESSAGES/ksshaskpass.mo
      ./usr/bin/ksshaskpass
      /usr/bin/ksshaskpass
      # /usr/bin/ksshaskpass --help
      qt.qpa.xcb: could not connect to display
      qt.qpa.plugin: Could not load the Qt platform plugin "xcb" in "" even though it was found.
      This application failed to start because no Qt platform plugin could be initialized. Reinstalling the application may fix this problem.
      Available platform plugins are: eglfs, linuxfb, minimal, minimalegl, offscreen, vnc, xcb.
      Aborted
      # dnf remove -y ksshaskpass
    という感じで全然起動してくれません。
    ですがエラーの内容的に display を開こうとしているのが分かります。
    なので、おそらく GUI で password を聞くような物だと思われます。


    askpass を作る

    askpass について調べていると、どうやら
    • password を stdout に出力するもの
    であることが分かりました

    よって
    • neovim 内で password を取得
    • password を stdout に出力する
    • password の履歴はどこにも残さない
    ようなものを作れば解決できそうです。

    それで完成したのが /tmp/askpass を作るこの Vimscript です。

    本当はファイル名は固定でなく tempfile 的な物で対応するなど、改善点はいくつも存在するかと思います。
    ですが私は Vimscript に明るくないので、"3つの条件を満たして動けばOK" という条件で作成しました。
    他により良い対応方法があればご指摘頂けると幸いです。


    その他の解決策

    調べた結果、 suda.vim というプラグインがあるようです。
    また、他にも sudo.vim など、似たようなプラグインはいくつかあるらしいです。

    これを使わなかった理由としては
    • docker のコンテナ内で作業をする事が多々ある
    • init.vim を配置するスクリプトは既にある
    • plugin を install するスクリプトはまだ無い
    • 全コンテナに plugin を入れるのも面倒だし結構サイズがある
      • $ du -sh ~/.config/nvim/repos
        • 100M    /Users/atton/.config/nvim/repos
    などがあります。

    また、"sudo が使えるならこの際 root になってしまう" という解決策もあるのですが
    • 前述した理由の後半3つ
    • 複数人数のユーザが使う環境だと、 /root 配下に自分専用の設定を置くことになる
    のでこちらも無しで。

    他には、 "sudoers で NOPASSWD を設定する" という解決策もあります。
    が、sudo を no password でガンガン使えば実質 root で作業しているのと変わらず、危険すぎるのでこちらも無し。

    ということでこの Vimscript を書くに至った、という訳なのです。


    おまけ: 環境変数の unlet

    スクリプト内部では askpass の場所を示す SUDO_ASKPASS を設定している部分があります。
    具体的には
    • let $SUDO_ASKPASS  = s:askpass_path
    ですね。

    この環境変数を定義していると、 sudo -A した際に指定したファイルが実行されます。
    一応、 :SudoWriteCurrentBuffer の実行後にはなるべく元の環境に戻すために
    • unlet $SUDO_ASKPASS
    を書いていた時もありました。

    ですが動作確認中、 Vim 8.0.1365 で unlet 時にエラーが出てしまいました。
    また 、-A を指定しなければ sudo の動作は変わらないので 、残っていても無害と判断して unlet しない形になりました。


    おまけ: shebang を指定しないとどうなるのか

    ちなみに askpass の shebang を消すと
    • sudo: unable to run /tmp/askpass: Exec format error
    • sudo: no password was provided
    と言われる為、きちんと書いておく必要があります。


    おまけ: x flag を付けないとどうなるのか

    setfperm(s:askpass_path, "rwxr-xr-x")  をせずに 644 のままだと
    • sudo: unable to run /tmp/askpass: Permission denied
      • sudo: no password was provided
      と言われるので、こちらもきちんと指定しておく必要があります。


      参考