See the Elephant

1992生まれのプログラマが書くエンジニアブログ

vimのキーバインド<C-j>がinsertモード移行からmapできない時の解決方法

背景

vimとTmuxをシームレスに移動するためのプラグインを真似したかった.

vimの<C-j>をremapできなかったので, その対策を記す.

やりたかったこと

vim の normalモードで<C-j>に他のキーバインドを当てたい.

できなかったこと

normalモードで<C-j>を入力するとinsertモードに移行する.
:map <C-j><Plug>IMAP_JumpForwardが出力される.

解決方法

$HOME/.vim/plugin/imaps.vimの480行目あたりを編集する.
imap, nmap, vmapコメントアウトする.

" Default maps for IMAP_Jumpfunc {{{
" map only if there is no mapping already. allows for user customization.
" NOTE: Default mappings for jumping to the previous placeholder are not
"       provided. It is assumed that if the user will create such mappings
"       hself if e so desires.
if !hasmapto('<Plug>IMAP_JumpForward', 'i')
  " imap <C-J> <Plug>IMAP_JumpForward コメントアウト
 endif
if !hasmapto('<Plug>IMAP_JumpForward', 'n')
  " nmap <C-J> <Plug>IMAP_JumpForward コメントアウト
endif
if exists('g:Imap_StickyPlaceHolders') && g:Imap_StickyPlaceHolders
 if !hasmapto('<Plug>IMAP_JumpForward', 'v')
  " vmap <C-J> <Plug>IMAP_JumpForward コメントアウト
 endif
else
 if !hasmapto('<Plug>IMAP_DeleteAndJumpForward', 'v')
  " vmap <C-J> <Plug>IMAP_DeleteAndJumpForward コメントアウト
 endif
endif
" }}}

上記でできた.

git revertについて

git revertを理解する上で必要な知識

前回, 前々回でgit reset, git rebaseについて触れた.
ここからはgit resetを理解してることを前提に話を進めていく.
参考ページ Git チュートリアル: 変更を元に戻す | アトラシアン

git revertの概要

git revertは, コミットを打ち消すような動作をする.
コミットの編集履歴を辿り, 前回コミットで変更された部分(差分)のみを"打ち消す"ようなコミットを行う.

git reset と git revertの違い

例として, 前回コミットした内容に間違いが含まれていたため,
前回のコミットを取り消して, 改めてコミットしたい場合を考える.

# コミット A,B,Cがあり, Cに間違いが含まれている. 
# そこで, Cを取り消して, 改めてコミットを行いたい. 

A -> B -> C (間違いを含むコミット) 
          ↑
         HEAD 

これを実現する方法は, 2つある.
それがgit resetgit revertである.

git resetを使った方法

git reset怖くないgit reset - namu_r21の日記でも触れたように, working tree, index, headを過去コミットの状態に完全に戻すコマンドである.
つまり, そもそも"コミットなんてなかった"という状態にすることができる.

# git resetでCをなかったことにする
 A -> B -> C (間違いを含むコミット) 
           ↑
          HEAD 
$ git reset --hard HEAD~1
 A -> B # Cなんてなかった 
      ↑ 
     HEAD 

この状態で改めて変更をコミットすると以下のような状態になる.
ここで, 注意すべきは改めてコミットしたものは新しいコミットとして見なされることである.

# git resetでCをなかったことにした後 
A -> B
     ↑
    HEAD 

# 改めてcommit
$ git commit 
A -> B -> D (Cとは異なるコミットとして扱われる.)
          ↑
         HEAD 

git revertを使った方法

対して, git revertは, コミットを打ち消すような動作をする.
前回コミットで変更された部分(差分)のみを"打ち消す".
コミットCはそのまま残し, コミットCによって行われた変更部分を打ち消すコミットを発行する.

# git revertでCで行われた変更を打ち消す
A -> B -> C (間違いを含むコミット)
          ↑
          HEAD 

$ git revert
A -> B -> C -> C' ( Cによって行われた変更を打ち消すコミット) 
               ↑ 
              HEAD 

この時, working treeはコミットBと同じ状態になっている. 改めて, コミットを行う場合は以下のような状態になる.

# git revertでCで行われた変更を打ち消した後, 
A -> B -> C -> C' 
               ↑ 
              HEAD 

# 改めてcommit 
$ git commit 
A -> B -> C -> C' -> D 
                     ↑ 
                    HEAD 

このようにすることで,
git revertによってログを残しながらコミットCを"なかったこと"にすることができる. ここがgit resetgit revertの違いである.

なぜgit revertが必要なのか

個人でgitを利用し, ローカルリポジトリだけで開発を行っている場合はこの2つを混同しても特に問題はない. しかし, リモートリポジトリが関係すると話は別になる.

例えば, ローカルでコミットCまで開発しリモート(origin)にpushしてしまった場合を考える.

# 間違いが含むコミットCをoriginにpushしてしまった. 
A -> B -> C (間違いを含むコミット) 
          ↑ 
         HEAD 
       origin/HEAD 

git resetを使って, コミットCをなかったことにして
改めてコミットを行うと以下のような状態になる.

# 間違いが含むコミットCを``git reset``によってなかったことにして 
# 改めてコミットを行う. 
A -> B ->  ->  D 
         ↑     ↑ 
  origin/HEAD HEAD 

$ git push origin
CONFLICT!

この状態でgit push originを行うと, コンフリクトが起きる.
origin側からすると,

  • Bと繋がっているはずのCコミットがなくなっている
  • Bと繋がっていないはずのコミットDが送られてくる

という状態になり, エラーを吐く.

これをgit revertを使うことで繋がりを守ったまま
コミットCを処理できるため, エラーを吐かない.

# 間違いが含むコミットCを``git revert``によって"打ち消し"て 
# 改めてコミットを行う. 
A -> B -> C -> C' -> D 
          ↑          ↑ 
     origin/HEAD    HEAD 

$ git push origin
A -> B -> C -> C' -> D 
                     ↑ 
                   HEAD 
                origin/HEAD

チーム開発における git revertの有効性と git resetのヤバさ

チームで開発しているリポジトリのログをgit resetで汚してし,それを共有すると全員のリポジトリでコンフリクトが起きる.

なおかつ, logが残らないので原因追及ができなくて死ぬ.

チーム開発するときは,

  • リモートにpush後のコミットを訂正する際は, git revertを使う( 不用意にgit resetしない)
  • 間違っても, git resetでログをめちゃくちゃにした後にリモートリポジトリに反映しない
  • pushする前に本当にpushしても問題ないかを確認する.

まとめ

  • git revertは,コミットを残しつつコミットの変更を打ち消す.
  • git resetは, コミットそのものを無かったことにする.

git rebaseってなによ?

git rebaseってなに?

前回, 前々回に続いて
今回は, git rebaseについて書く.

git rebase コミットログを綺麗にする 機能である.
git rebaseを使えば,

  • 複数のコミットを1つにまとめる
  • コミットのノードを変える

ことができる.

なにも考えずにgit commitしまくって,
後からgit rebaseでログを綺麗にまとめる使い方が主流みたい.

どうやって使うの?

liginc.co.jp

このサイトがとってもわかりやすい.
わかりやすすぎて正直書くことがない...

今後, 使いそうなものをpickupして書こう.

複数コミットをまとめる方法

基本的には, HEADからの距離対象コミットのhash値 を使う.

# -i はinteractiveモード
$ git rebase -i <hash値> or <HEAD~n>

例えば, 2つのコミットを1つにまとめる場合.

$ git log --oneline
...  
fl32k49 commit C
r43ng9c commit B
2Ofncfb commit A

$ git rebase -i HEAD~1
         or 
$ git rebase -i r43ng9c

rebase すると, editorが開く.
ここで, 各コミットの扱いを設定する.

# rebase すると開くファイル  

pick r43ng9c commit B
pick fl32k49 commit C

ちなみに, rebaseファイルで使うコマンドは以下の通り.

  • pick(コミットを採用)
  • reword(コミットを採用するが、コミットメッセージを変更)
  • edit(コミットを採用するが、ファイルを修正する)
  • squash(一個前のコミットと合体させる)
  • fixup(コミットメッセージを変更しない点以外、squashと同じ)
  • exec(shellでコマンドを実行する)

ここで, コミットBにコミットCを結合する場合を考える.

pick   r43ng9c commit B
squash fl32k49 commit C   # squashは s だけでもいい

このファイルを上書き保存すると, コメント編集用のファイルが開く. コメントを書き換えて保存するとrebase完了.

コンフリクト発生!!!

コミット同士を無理やり結合しているので, 衝突する場合がある.
その時, rebaseは一時中断されるのだが, その時に使うコマンドを記す.

  • git rebase --continue(衝突などを解決した後に実行して、rebaseを続行する)
  • git rebase --skip(エラーを無視する)
  • git rebase --abort(rebaseをやめる)

注意すべきこと

rebaseはコミットを書き換えるわけではなく, 新しくコミット を行う

このため, リモートリポジトリのコミットを
git rebaseで書き換えてしまうと大量のconflictを引き起こす.
だから, リモートリポジトリに対しgit rebaseによる編集は控えた方が良い.

リモートリポジトリが関係する場合は, git revertでノードの編集を行うことが慣習になってる.

簡単ながら, git rebaseについてまとめた.

怖くないgit reset

git reset について

git reset... 怖い!!!

どのサイトに行っても, git resetは操作がログに残らないので, 慎重に!! といった書き込みが多く, 少しgit reset恐怖症になっていた.

そこで改めてgit resetについて再勉強した.

参考サイトから大事だと思う部分をpickupさせていただきました. 先人様様です.

git-resetは結局何を戻すのか - Qiita

git reset についてもまとめてみる - murankの日記

resetは何を操作しているのか.

それは, working treeindexheadである.

対象 説明
head 最新コミットを表す代名詞
index Addされたファイルを記憶する領域
working tree 作業中のファイルを記憶する領域

操作対象を決める --soft と --hard オプション

resetには--soft--hardというオプションがある. これは, どの操作対象をresetするかを決めるオプションである.

オプション HEAD index working tree
--soft
no option
--hard

つまり,

git reset <何を戻す?> <どこまで戻す?>

というコマンドである.

qiita.com

git resetの動作

何となく, resetの機能は掴めてきた.
とは言っても, 実際にどのように動作しているのかイメージしづらい.
ここを見ると, head, index, working treeの動きが視覚的に見てわかりやすい.

d.hatena.ne.jp

git resetを使う場面をケース別に考える.

1 : 間違えて必要のないファイルをindexに追加してしまったのでindexを消去したい. しかし, working treeを消したくない場合.

# indexをHEADの状態に戻す(headは同じ場所を指す).
$ git reset HEAD

2 : 今のコミット上で編集済みのworking tree, indexがある.この2つを保持したまま, 直前のコミットに戻りたい.

# headだけを直前のコミットに戻す.
$ git reset --soft HEAD^

3 : 一度全てを今のコミットの初期状態に戻したい!

# head, index, working treeをHEADの状態に戻す
$ git reset --hard HEAD

4 : 直近のコミットは致命的なバグを含んでいる! n個前のコミットに全てを戻そう!!

# head, index, working treeをn個前のコミットに戻す

$ git reset --hard n個前のコミットのhash値

例えば, コミットした後に直前のコミットにミスが含まれていることに気づいた場合,ケース2で対処できる.

# index, working treeを保持したまま, 直前のコミットに移動
$ git reset --soft HEAD^

# 新しくコミットする
$ git add .
$ git commit -m "replace commit"

しかし, これをやると ログに編集作業が残らない .

git では編集ログはわかりやすく残すことが流儀とされているので, ログが残らない ことはタブーらしい.

だから, git resetは云々... って言われているみたい.

まとめ

理解すれば, git resetもそれほど怖くないとわかった.
あと, git reset でやらかした場合は, git reflogで全ての編集作業を辿れるので,このコマンドは覚えておいたほうがいいと思う.

以上, git resetについて簡単にまとめた.

gitのreset / rebase / revertがいまいち理解できてなかった件について

git reset / rebase / revert... 何が違うの?

gitは過去のコミットを自由に編集できることは知っていたけども,
いまいち理解できていなかった.

ちょっと触ってなんとなく感覚をつかんだのでメモ.

今後詰まったときのために, コマンド早見表でも書いておこう.

やりたいこと 適正コマンド
ある地点のコミットまで完全に歴史を消して戻りたい! reset
あるコミットだけをなかったことにしたい!(ログは残す) revert
複数のコミットを1つのコミットとしてまとめたい!(ログ残らない) rebase

なぜかgemが環境変数http_proxyを読んでくれない理由

問題発生!!!

$echo $http_proxy
http://my.proxy:port/

$sudo gem install ***
...(don't work)

がプロキシを通らない!!!

この理由がどっかのサイトに書いてあった.

sudoすると, bashrc(zshrc)を読まない.  

つまり, bashrc(zshrc)で設定している環境変数は読み込まれない。
と, いうことはhttp_proxyが設定されない!
つまりプロキシ設定がなされない!

ということらしい。
sudoするときはこれに気を付けたい。

mint17にbrewを入れる


パッケージ管理ソフトの話

世の中にはいろいろパッケージ管理ソフトがあるよねー。

パッケージ管理ソフトっていうとちょっとわかりづらい。 語弊や誤植を恐れずに言えば、google playappleapp store的に色んなソフトウェアを配信するためのソフトウェアととらえて問題ないと思う(少なくとも僕はそれくらいの理解で留まっている)。

細かく見ると, ソフトウェア開発にはバージョンが大きくかかわるのでかなり細かく管理されている。だから一緒というにはかなり無理があると思うが...

バージョン管理ソフトは 例えば, linuxならば yum, apt-getあたりが有名だし

macならば macportとかhomebrewが有名。

ぼくはmacユーザでなおかつミーハーなのでhomebrewが大好き。 依存関係あたりの解決が楽だし最近はbrew関連のエントリが多いので管理も楽。

この前先輩に聞いた話によるとlinuxにもbrewがあるらしい。。。

その名もlinux brew。 まんまだよね。

linux brew

これはapt-getでインストールする。 linuxbrewを入れるエントリはここがわかりやすかった.

qiita.com

ここで少し躓く。
mintを入れたばっかりの僕はapt-get update, apt-get upgradeを繰り返してから上記エントリのapt-get installを実行したら通った。

...あと, このエントリのコマンド, ちょっと綴りがちがうっぽい... だから, コマンドは公式を参照しよう。

Linuxbrew

って感じで簡単にlinuxbrewが入りました。 linuxでもmacでもbrewが使えると移行が簡単になりそうだし、 胸が高鳴るね。