Hatena::ブログ(Diary)

ザリガニが見ていた...。 このページをアンテナに追加 RSSフィード

2012-10-01

シンプルなモデルで具体的に考えて理解する方法

新入社員として初めて配属された先は、とある巨大店舗の経理部だった。経理部はいくつかの課に分かれており、さらに課はいくつかの係に分かれている。自分が担当するのは、その中の一つの係である。

新入社員というのは、良くも悪くも前提知識がまったくない。自分が担当する係の仕事内容も分からないし、その係が経理全体の中でどのような役割を担っているのかさえ分かっていないのだ。

それにしても、巨大店舗の売り場では色々なことが起こる。伝票を処理するルールというのは決まっているのだけど、そのルールから外れてしまうのが常である。こちらの想像を超えた予想外のミスをどのように修正すれば良いのか、新入社員であっても電話をでたら、容赦なく問い合わせてくる。(電話の向こうでは、こちらが新入社員かどうかなんて、分からないのだから)

新入社員がそのような問い合わせを受けても答えられるはずもなく、必死で問い合わせの内容をメモして、保留あるいは折り返しにして、先輩社員に確認して、オウム返しに答えるしかないのだ...。

オウム返しと言えども何度も繰り返していれば、似たような質問に対しては過去の記憶を引き出して、答えられるようになる。ちょっと経験を積むと、何だか少しはできるようになった気になるが、現実はQ&Aを丸暗記して答えているに過ぎない。いい気になって知ったかぶって話していると、突然Q&Aにない状況を持ち出されて「少々お待ちください」あたふたと保留して、誰かに確認する羽目になる。結局、頼りの誰かがいないと何もできない社員、という状況はまったく変わっていないのだ。

いつまでも丸暗記ではダメなのだ。業務を理解する必要がある。一つの伝票が作成されて、それが各部署を巡る間にどのように処理され、そのデータがどのような影響を与えるのか、自分の係だけでなく、経理さらには店舗全体に与える影響を知っておく必要があるのだ。そこまで知って、初めて担当業務を理解したことになる。

しかし、巨大店舗の伝票処理は複雑怪奇である。様々な取引が複雑に絡み合う中で、それらのデータの流れを的確に理解するのは途方もなく困難なことのようの思えてくる。実際、新入社員の自分は相当混乱していた。未経験の対応についてはすべて上司に確認するしかなかった。上司が休みの日は、電話の問い合わせがちょっと怖かった。自分の知らない問い合わせがあったらどうしようか?と不安になるのだ。

そんな状況を見て、あるとき上司が面白い例え話で質問してきた。「○○くん、もし1ヶ月の売上が今問題になっている伝票しかなかったとしたら、どうなると思う?金額も細かいのがあると面倒だから、キリよく商品代金も10000円で考えてみて」

や、や、や、衝撃的な質問だった。日々の売上が1億円を超える店舗である。なのに1ヶ月の売上がこの伝票1枚しかないと考えると、どうなるのかと。そのようにして考えてみると、今まで複雑な処理に惑わされて混乱していた思考が、ウソのようにクリアになった。シンプルに具体的な数字を当てはめて考えることで、問題の本質が今までになく明確になってしまった!

それ以降、その上司と自分の会話は問題となる伝票にキリのよい金額を当てはめて、空想実験する話題がめっきり多くなった。そして、幾度かの空想実験を繰り返すうちに、いつの間にか未知の質問にも答えられるようになっていった。未知のことであっても上司に頼るのではなく、関連する部署に確認して、答えを導き出せるようになった。

今考えてみると、このように具体的な金額を当てはめ、シンプルなモデルで思考する方法は、決して特別な手法と言う訳ではなく、会計の世界ではよくやる手法であると思っている。しかし、新入社員の混乱している時期に知ったその思考方法は、とても画期的であり、素晴らしく分かりやすい手法に思えた。以降、自分が未知のことを理解しようとするときは、経理業務に限らず、あらゆることにこの方法を応用して考えるようになったのである。

世界がもし100人の村だったら

以前流行った「世界がもし100人の村だったら - Wikipedia」と思考して、「○○人がこんな状況です」と考える手法も、まさに自分が新入社員の時に知った手法と同じである。シンプルなモデルで具体的に考える手法の奥の深さを感じた。

Ruby on Rails

  • このブログの始まりは、Railsの学習記録であった。
  • ブログを始めた時点で、RailsさらにはRubyの知識も0であった。
  • しかし、Rubyの学習から始めるのではなく、Railsのscaffoldから始めだのだ。
  • Rubyが何かを知らなくても、scaffoldを実行すれば、動くWebアプリケーションができてしまう。
  • 動くアプリケーションのコードを読みながら、少しずつ、Rubyの世界を理解していった。
  • 結果的にその方が、学習するときの時間と労力が節約できたと思っている。
  • 未知の言語を学ぶ時のシンプルなモデルとは、分かりやすいサンプルコードと考えられる。
  • Railsを学習していた2006年当時、発売されていたRails本はすべて購入し、精読してみた。
  • すべての本が何らかの意図を持って、それぞれ独自の観点から、Railsを説明しようとしている。
  • 本を読むほどに、Railsを見る視野が広がり、Railsの理解は深まる。
  • 時間を惜しまなければ、すべての本を精読すれば良いのだけど、
  • 1日はすべての人に平等に24時間しかなく、その中で仕事・生活・睡眠などをやりくりする必要がある。
  • 少しでも効率良く学習しようとすれば、自分に合った本を選択する必要がある。
  • しかし、どうやって選択するべきなのか?
原典は外せない
  • その言語やフレームワークの開発者が書いている本(原典)があるなら、それは絶対に読むべきだと思っている。
    • あるいは開発者が書いていなくても、誰もが認めるその筋の良書と評価されている本が1冊くらいあるはず。
  • なぜなら、開発者の書いた本には、仕様だけでなく、その背後にある設計思想まで語られていることが多い。
  • 入門書として原典が最適かどうかは未知だが、その言語を正しく理解する過程では、原典は最も正確に学べる書である。
入門書から入る
  • 原典を読んですべてを理解できれば、それは理想である。学習効率も最高に良い。
  • しかし自分の場合、知識0の状態で、その筋の原典だけ読んですべてを理解できた試しがない。
  • 原典を読める知識さえないので、その前段階として、より噛み砕いた説明が欲しいのである。
  • では、より噛み砕いた説明とは、どんな説明が良いのか?
  • 実はこの部分こそが、原典以外の多くの本が試行錯誤している部分だと思っている。
  • そして、読者に評価される部分だと思っている。
  • では、評価が高い本を買えば間違いないかと言えば、決してそんなことはない。
  • 多くの本が、訴える対象を明確にイメージして書かれているはずである。
  • その本を読むときの読者が、訴える対象と重なれば、それは評価どおりの良書となるのである。
  • しかし、読者と訴える対象に差があると、読者は評価と違った印象を受けるはずである。
    • Railsをひと通り理解している読者が評価の高い「入門書」を読んでも、それは退屈な本にしか感じられないはず。
  • また、解説の手法が読者に合っているかどうかも重要である。
  • 人が新しいことを理解するとき、おそらくそれまでの経験を拡張させる思考を試すと思われる。
  • 読者がそれまでにどんな経験をしてきたかは千差万別である。
  • 多くの人が分かりやすいと評価していても、それが自分にとっても分かりやすいかどうかは、未知なのである。
実践をどうするか
  • 順調に入門書、原典、と読み進めて、Railsを理解したつもりになっても、
  • 結局はサンプルコードを修正するレベルの知識しか身に付いていなかったりする。
  • 実際にコードを書くときは、もっと現実的な問題や要求に応えなくてはならない。
  • 例えば、日本語(多言語)環境を扱うならRuby-GetText等が必要になる。
  • しかし原典を読んでも、Ruby-GetTextの取り扱い方法は記述していないのである。
    • Ruby-GetTextはRailsに含まれないので、記述がないのは当然である。
  • そう言った情報は、Ruby-GetTextの開発者のページで確認したり、日本語化を実践した先達のブログなどを読んで学ぶことになる。
    • Rails本の中には、Ruby-GetTextの使い方を解説したものもあった。
  • Ruby-GetTextのように日本語で情報発信されていれば、その使い方は効率的に学べる。
  • しかし、日本人以外の開発者が英語で情報発信していると、英語が苦手な自分はなかなか理解が進まない。
  • 2006年12月にさらに1冊のRails本が出版された。
  • ひたすら、問題提起と、それを解決するコードと解説がある実践的な本であった。
  • Railsの原典まで理解したけど、サンプルコードの改変より先になかなか進めない、と感じていた自分には素晴らしく参考になる本であった。
  • 非常に難解なコードもあり、部分的に何十回も読み直してボロボロになりつつある。
  • その頃の自分にとって、そこに出てくるコードを理解することは、宝を発掘している感覚であった。
  • そのRailsレシピを理解する過程で、すべてがオブジェクトであるというRubyの本質を感じ始めたと思う。
  • オブジェクト指向やメタプログラミングと言われる手法も理解し始めたのだ。
  • すると、難解なRailsのソースコードを読んでも、どんな処理をしているのか、分かるようになったのだ。
  • ソースコードを読めるようになると、理解は飛躍的に進む。
  • 分からないことは、ソースコードに当たって調べるようになる。
  • 英語が分からなくても、ソースコードが読めるので、そこから教えてもらった。
サンプルコードの重要性
  • Railsを学ぶ中で気付いたのが、サンプルコードの重要性である。
  • 自分の場合、こんな仕組みで動くと日本語で理論的に100回説明されるより、サンプルコードを示されてこんなふうに動く、と説明された方が自然と理解が進んだ。
  • これは中学・高校6年間も英語の文法を学んでも英語を話せないが、小学校入学までに多くの子供が文法を知らずに、日本語で会話できるようになることと似た感覚。
  • 何かを学ぶとき、理論が先か、実践が先かと考えたら、圧倒的に実践を先にした方が理解が進むのである。(自分の場合)
    • 実践を知って、あとで、なぜそうなるかを理論的に考える。
    • どちらかというと、学校での学習方法とは逆の順序である。
  • 素晴らしいサンプルコードは、そのコードを読んでいるだけで、その言語の使い方が理解できてしまう。
  • Railsを学ぶときには、そのような素晴らしいサンプルコードが数多く公開されていた気がする。

ソースコードリーディング

  • ソースコードが理解できたときの感動は、数学の図形問題が解けたときの感動に似ているかもしれない。
  • Railsの学習が一段落した後年、興味を持った仕組みが、その裏でどのように動いているか調べている。
  • 最初はまったく意味不明なソースコードが、コードを睨み続けて何日間も対峙することで、Web検索に助けられながら、少しずつその仕組みがひも解かれていくのだ。
  • このソースコードリーティングでも、実際にシンプルなモデルを考えて、引数や変数・レジスタに実際に数値を代入しながら、CPUの動きを追っていくのである。
    • (たぶん多くの方が実践している方法だと思う)

CocoaとObjective-C

  • OSX開発環境の原典と言えば、アーロン ヒレガス著・Mac OS X Cocoaプログラミング である。
  • 2002年6月発行の初版を購入した。その内容は非常に分かりやすいサンプルと言い回しで解説される、名著だと思っている。
  • しかし、その当時挫折していた。解説は分かりやすいのに、肝心な何かが理解できていない感覚。
  • その原因は、オブジェクト指向なプログラミングという手法の意味をちゃんと理解できなかったのだ。
  • オブジェクトとは何か、クラストは何か、Cocoaの中でどのように利用されているのか、正確に理解できていなかった。
  • どんな小さなプログラムコードでも、自分で一から書いてみることで理解は飛躍的に深まる。
  • そして、それが実際に役立つアプリケーションだとなお良い。
  • メンテナンスや改良を通じて理解をより深めることができる。
    • といいながら、2009年と2012年のコメントが、ほったらかしであったことが発覚。(しまった...。あとで返信しておかなければ)

Gitの学習

  • Gitを知ったのは、RailsがGitHubを使い始めたからであった。
    • GitHubはRailsで作られた、最高品質のWebアプリケーションかもしれない。
  • GitHubを使いこなすなら、Gitを知っておく必要がある。そんな流れで、Gitを学び始めた。
  • しかし、Subversionのインストールはしたが、コード履歴の確認ぐらいにしか使っていなかった身には、けっこう敷居が高かった。
  • 2008年当時にはまだGit本も出版されておらず、Webの情報だけが頼り。
  • Webの情報は決して分かりにくい訳ではなく、Gitチュートリアルの日本語訳は素晴らしく参考になった。
  • しかし、実際に使い始めてみると、現実のこの状況で何をどうやって操作するべきなのか、分からないのだ...。
    • インデックス・リポジトリ・作業フォルダの関係や役割が正確に分かっていない。
    • 間違った操作をしたときの修正の方法が分からない。
    • ファイル名の変更をすると、訳の分からない状態になる。
    • ブランチやマージが怖くてできない...。
  • 典型的な挫折症状である。
  • そこで、チュートリアルで出てくるアリスとボブの例に習って、徹底的にアリスとボブそれぞれの立場になって、試してみようとしたのである。
    • 自分で簡易なサンプルプロジェクトを立ち上げて、実際にやってみたのだ。
  • シンプルなモデルで具体的に考えるというこの試みは、新入社員の昔から慣れ親しんだ方法である。
  • この試みが想像以上にGitの理解を深めてくれた。ならば、忘れないうちにブログに書いておいた方がいい。
  • ブログに書くことで、もう一度書く内容を再確認する*1ので、勘違いに気付いたり、さらに理解を深めることになるのだ。
  • そのような経緯で公開した「アリスとボブのGitシリーズ」は想定外の嬉しい反響を頂いた。
  • そして、その反響は最大瞬間風速として終わることなく、何年もの間少しずつブックマークされ続けるという、息の長い記事となったのである。
  • アリスとボブのGit入門レッスンは、上記のブログ記事が元となり、さらにGitを詳細に分かり易く語ることを狙った本なのである。
  • 自分が何かを学習するときに使う「シンプルなモデルで具体的に考えて理解する」というお決まりの手法が本になっているのだ。
  • 書いている中で、分かり易く説明する、ということは実は相当難しいと感じた。
  • 単なる手順書なら詳細に手順を書けば済む。
  • しかし、シンプルなモデルで具体的に考える理由は、その本質を理解するためである。
  • 手順を知ると同時に、そこにある仕組みを理解してもらわなければ意味がないのだ。
  • また、本質を追究する姿勢は、自分が文章を書くときに心掛けていることでもある。
  • ブログや本でその姿勢を忘れてしまっては、自分が書く意味がなくなってしまう...。
  • どこまでも追求しなくてはならない、という使命感がある。
  • 目指したのは、丸暗記の理解ではなく、応用力の宿る理解である。
  • 分からないことは自ら調べて、理解を深めていく、そうゆう能力を高めていきたい。
  • 果たして、本の中でそれがどこまで成功しているかは、未知である。
  • 単なる初心者向けの入門書で終わらずに、本質を理解するきっかけとなりますように!

そんなことを願いながら、シンプルなモデルで具体的に考える軌跡に想いを馳せてみた。

*1:全世界に向けて、間違ったことを発信してはならない、と思っているので。

2012-09-21

アリスとボブのGitシリーズが本になりました!

サポート情報を追記しました。


f:id:zariganitosh:20120920094630p:image:h128:rightf:id:zariganitosh:20120919113737p:image:h128:right

アリスとボブのGitシリーズとは、4年前の2008年9月5日から始まる以下の一連の日記です。

  1. アリスとボブになりきってgitをちゃんと理解したい! - ザリガニが見ていた...。
  2. アリスとボブのコラボレーション、gitをちゃんと理解したい! - ザリガニが見ていた...。
  3. アリスとボブのサーバー、git pushをちゃんと理解したい! - ザリガニが見ていた...。
  4. アリスがチャレンジなコードを書く時、git branchをちゃんと理解したい! - ザリガニが見ていた...。
      • これらの日記は、最近になってもじわじわブックマークされ続けている隠れた人気記事です。(ありがたいことです、感謝)

上記の日記がベースとなって、書籍化のお話を頂き、半年かけて執筆しました。(超スローペースです)

  • ベースとなった日記は、アリスとボブを観察する第三者の目線で書いていますが、
  • 書籍ではアリスとボブの会話(台詞)がメインになります。(たまにナレーションが入ります)
  • バージョン管理システムの知識がまったくないアリスが、ボブと会話しながら、Gitを少しずつ学んでいきます。
  • コマンド操作の過程も可能な限り詳細に記載するようにしました。(ページ数・ページ割の制約があるので、全てではないですが)
  • 実際にアリスとボブになりきって体感していただくことで、Gitに対する理解をより深めていただきたい、という意図があります。
  • 基本的に2008年当時のGit初心者である自分自身が、アリスのモデルです。
  • ですから前半の章は、Gitをある程度知っている方には退屈かもしれません。
  • しかし、このブログのポリシーでもある 本質を追求する姿勢 は、本の中でも忘れていません。
  • 最後の章では、Gitの詳細な仕組みや内部構造まで迫ります。
  • 仕組みや内部構造が分かると、gitコマンドの動作の本質が見えてきます。
  • 本質を理解することで、数あるgitコマンドの意味を覚えやすくなります。
  • 失敗したときにも、対処方法を自ら考えられるようになると思っています。
  • Gitマニュアルを読む能力も格段に向上すると思っています。
  • 新たな知識もGitマニュアルやWeb上の情報を検索して、自ら習得できる考えています。

この本がGitの本質を理解する、きっかけになれば、と願っています。

目次

出版社の確認がとれましたので、目次を掲載させていただきます。

  • Chapter 01 はじめに
    • 01-01 バージョン管理システムの紹介
    • 01-02 アリスとボブの作業環境
    • 01-03 コマンドプロンプトの表記
    • 01-04 読み方
  • Chapter 02 ボブがGitを実演する
    • 02-01 Gitに管理する場所を教える
    • 02-02 変更を記録する
    • 02-03 変化を知る
    • 02-04 過去を復元する
  • Chapter 03 アリスがボブを真似してみる
    • 03-01 先にファイルを作っておく
    • 03-02 viエディタが起動する
    • 03-03 名前とメールアドレスを設定する
  • Chapter 04 ボブがGitの概念を説明する
    • 04-01 Gitの理解に必要な3つの領域と1つの目印とは?
    • 04-02 変更を記録するとき、何が起こっているのか?
  • Chapter 05 アリスがプロジェクトを始める
    • 05-01 もっと簡単にコミットしたい
    • 05-02 コミットを中止したい
    • 05-03 インデックスの余分な変更をなかったことにしたい
    • 05-04 ファイルを最新のコミットに戻したい
    • 05-05 最新のコミットのメッセージを修正したい
    • 05-06 最新のコミットにさらに変更を追加したい
    • 05-07 最新のコミットの余分な変更をなかったことにしたい
    • 05-08 ファイル名を変更したい
    • 05-09 HEAD~とHEAD^の意味を理解したい
    • 05-10 気付いたらコミットに含まれていた .DS_Storeを削除したい
    • 05-11 今後は.DS_Storeを無視したい
    • 05-12 無視するファイルの設定で注意すること
    • 05-13 いくつか前のコミットをなかったことにしたい
    • 05-14 いくつか前のコミットを削除したい
    • 05-15 複数のコミットを1つにまとめたい
    • 05-16 いくつか前のコミットを修正したい
  • Chapter 06 ボブがアリスのプロジェクトを手伝う
    • 06-01 ボブがプロジェクトをコピーする
    • 06-02 ボブの修正を取り込む
    • 06-03 ボブの修正を確認する
    • 06-04 2つの修正を重ね合わせる
    • 06-05 ボブもアリスの修正を取り込む
    • 06-06 リモート設定で簡潔に書く
    • 06-07 2人の意見が対立している
  • Chapter 07 アリスが別の歴史で新たな技術に挑戦する
    • 07-01 歴史を分岐させる
    • 07-02 masterブランチへ戻る
    • 07-03 ボブの修正を取り込む
    • 07-04 分岐した歴史を合流させる
    • 07-05 歴史を見やすい表示にしておく
  • Chapter 08 アリスとボブが共通のgitフォルダで作業する
    • 08-01 ボブが共通のgitフォルダを設定する
    • 08-02 ボブが共通のgitフォルダにアップロードする
    • 08-03 アリスが共通のgitフォルダからダウンロードする
    • 08-04 2人がアップロードした場合
  • Chapter 09 アリスのリポジトリにアクセスする方法
    • 09-01 リモートログインで接続
    • 09-02 ファイル共有で接続
    • 09-03 sshログイン認証で接続
    • 09-04 ssh公開鍵認証で接続
    • 09-05 httpプロトコルで接続
    • 09-06 gitプロトコルで接続
    • 09-07 プロトコルの比較
  • Chapter 10 アリスがバージョン0.1をリリースする
    • 10-01 ボブがバージョン0.1に目印を付ける
    • 10-02 ボブがコメント付きの目印を作る
    • 10-03 ボブの目印を共有する
  • Chapter 11 アリスが歴史のなかを自由自在にかけ回る
    • 11-01 リモート追跡ブランチを削除したい
    • 11-02 過去にさかのぼって歴史の流れを変えたい
    • 11-03 いまの作業は保留して別の作業を先にしたい
    • 11-04 名なしの歴史に分岐する場合
    • 11-05 名なしの歴史でしたコミットを取り戻したい
    • 11-06 やっぱりハードリセット実行前に戻したい
    • 11-07 過去から現在までのメールアドレスをすべて変更したい
  • Chapter 12 アリスがオープンソースとして公開する
    • 12-01 GitHubでプロジェクトを公開する
    • 12-02 GitHubのリポジトリを削除する
    • 12-03 READMEを書いてみる
    • 12-04 ボブが修正リクエストする
    • 12-05 アリスが修正リクエストを取り込む
  • Chapter 13 とっても便利な設定
    • 13-01 --global --systemのちがい
    • 13-02 設定を削除する
    • 13-03 好みのエディタを使う
    • 13-04 改行コードを変換する
    • 13-05 gitコマンドをTabキーで補完する
    • 13-06 コマンドエラー時に自動補完する
    • 13-07 リモートログインしたときにも日本語を正常に扱う
    • 13-08 日本語ファイル名の文字化けを直す
    • 13-09 日本語ファイル名をgit addする
    • 13-10 AppleScriptやリッチテキストなども管理する
    • 13-11 コマンドプロンプトにブランチ名を表示する
    • 13-12 テンプレートを活用する
  • Chapter 14 Gitの内部を探りに行く
    • 14-01 addしたときに起こること
    • 14-02 インデックスの中身
    • 14-03 SHA-1ハッシュ値の求め方
    • 14-04 オブジェクトの中身をのぞく
    • 14-05 コミットしたときに起こること
    • 14-06 タグやブランチ、HEAD、チェックアウトの実体とは?
    • 14-07 大切なオブジェクトを守るしくみ
    • 14-08 リポジトリを整理するしくみ
    • 14-09 まとめ
    • 14-10 資料

ギャラリー

内容と言うよりは、本の雰囲気を伝えるための写真です。

R0014414

R0014415

R0014416

R0014417

R0014462

アリスのGitHub

アリスのGitHubには現在、以下のプロジェクトが展開されています。

Support
  • 「アリスとボブのGit入門レッスン」のサポート情報を発信するプロジェクトです。
    • 正誤リスト(既に1件あります*1
    • 手入力するには長すぎるコマンド履歴
project
  • アリスが始めた開発プロジェクト「project」のリポジトリです。
  • アリスとボブの作業履歴のすべてが見られます。
Hello-World
  • 12章でGitHubの使い方を解説する時に使ったプロジェクトです。
  • GitHub独自のMarkdown記法の解説ページにもなっています。

*1:3回の校正をした中で見逃したケアレスミスです。悔やまれます。申し訳ないです。

2012-02-29

全部覚えたいviの使い方

前回、なるべく覚えないで使えるようになろう!と模索していたが、やっているうちに全部覚えたくなってしまった...。viおよびvimは、とてつもないポテンシャルを備えていることを、調べるほどにひしひしと感じる。viの魔力にハマった感じ。しかし、とてもじゃないが全部網羅できない...。

#### この表の用例
	esc		= escキー
	⏎ 		= enterキー
	space		= スペースキー
	tab		= tabキー
	delete		= deleteキー
	ctrl-x		= controlキーを押しながら x を押す
	option-x	= optionキーを押しながら x を押す
	command-x	= commandキーを押しながら x を押す
	fn-x		= fnキーを押しながら x を押す
	A・B		= 「AあるいはB」の意味。似た機能を1行で併記する時に使う
#### viの起動
	vi		名称未設定ファイルを開く
	vi ファイル	ファイルを開く
	vi -r ファイル	強制終了後のバックアップから、ファイルを復元して開く
#### モード
	i・a		カーソルの左・右に、挿入モードで入力する
	I・A		行頭・行末に移動して、挿入モードで入力する
	O・o		上・下に1行追加して、挿入モードで入力する
	s		1文字削除して、挿入モードへ移行する
	R		置換モードで入力する
	esc		コマンドモードへ移行する
#### 保存・終了
−-短縮----コマンド---------意味--
	:w⏎		保存する
	:w 別名⏎		別名で保存する(既存のファイルを上書きできない)
	:w! 別名⏎	別名で強制的に保存する(既存のファイルを上書きする)
	:q⏎		終了する(保存してない状態では終了できない)
ZQ	:q!⏎		強制的に終了する(保存してない状態でも終了する)
	:wq⏎		保存して、終了する(変更がなくても保存する)
ZZ	:x⏎		保存して、終了する(変更が無しなら保存せず、終了のみ)
#### カーソル移動
	h・j・k・l	←・↓・↑・→
	w・e・b		カーソルを単語単位で移動する(次の先頭・次の末尾・前の先頭)
	W・E・B		カーソルを空白(= spaceやtab)区切り単位で移動する(次の先頭・次の末尾・前の先頭)
	0・$		カーソルを行頭・行末へ移動する
	^・g_		カーソルを空白以外の行頭・行末へ移動する
	%		カーソルを対応する括弧に移動する(カーソルが括弧の上にある場合)
	-・↵		カーソルを空白以外の一つ上の行頭・一つ下の行頭へ移動する
	{・}		カーソルを「空行」区切りで移動する
	(・)		カーソルを「空行」か「ピリオド& スペース」区切りで移動する
	[[・]]		カーソルを「行頭の{」区切りで移動する
	ctrl-b・ctrl-f	カーソルを1ページ上・下に移動する
	ctrl-u・ctrl-d	カーソルを半ページ上・下に移動する
	H・M・L		カーソルを画面(表示領域内)の上・中央・下へ移動する
	nH・nL		カーソルを画面(表示領域内)の上からn行目・下からn行目へ移動する(n=数字)
	gg・G		カーソルをファイルの先頭・末尾へ移動する
	行番号G		カーソルを行番号へ移動する
	n%		カーソルをファイルの n% の位置へ移動する(n=数字)
	zt・zz・zb	現在のカーソル位置が画面の上・中央・下になるようにスクロールする
 
 ------- ファイル内・ページ内のカーソル移動のイメージ
 1g 0%      gg
 2g              ctrl-b
 3g       --H--          zt スクロール
 4g      | 2H  | ctrl-u 
 5g 50%  |  M  |         zz スクロール
 6g      | 2L  | ctrl-d
 7g       --L--          zb スクロール
 8g              ctrl-f
 9g 100%    G

 ------- w・e・b・W・E・B を体感する
 wXXXXe, wXXXXe. wXXXXe/ wXXXXe -wXXXXe
 bXXXXe (bXXXXe) bXXXXe [bXXXXe] bXXXXe
 日本語の場合は、入力システムの 単語 区切りとなる。
 W・E・B はあくまでも空白区切り単位
 WxxxxE WxxxxE WxxxxE	WxxxxE WxxxxE
 BxxxxE BxxxxE BxxxxE	BxxxxE BxxxxE
 
 ------- 0・^・g_・$ を体感する
         This is a pen.
 0       ^            g_        $
 
 ------- % を体感する
 { 2× (( 3+4 ) +6/3 ) } = [ 括弧のある計算 ]
 
 ------- }・)・]] を体感する
 { This is a pen.This is a pen.This is a pen.}
 ( This is a pen. This is a pen. This is a pen.)
 { This is a pen.This is a pen.This is a pen.}
 
 { This is a pen.This is a pen.This is a pen.}
 ( This is a pen. This is a pen. This is a pen.)
 { This is a pen.This is a pen.This is a pen.}
#### 削除・コピー・ペースト・インデント
	delete		コマンドモード:カーソルを移動(back move)、入力モード:1文字削除(back delete)
	fn-delete	コマンドモード:1文字削除(forward delete)、入力モード:1文字削除(forward delete)
	x・X		1文字削除 = d→・d←
	d カーソル移動	カーソル移動(←・↓・↑・→・w・e・b・0・^・$等)した部分の文字を削除
	dd		1行削除
	D		行末まで削除
	c カーソル移動	カーソル移動(←・↓・↑・→・w・e・b・0・^・$等)した部分の文字を削除、挿入モードへ移行
	cc		1行カット、挿入モードへ移行
	C		行末までカット、挿入モードへ移行
	y カーソル移動	カーソル移動(←・↓・↑・→・w・e・b・0・^・$等)した部分の文字をコピー
	yy		1行コピー
	Y		1行コピー
	P・p		カーソルの上左・下右にペースト(削除・コピー・カットしたテキストがペースト可能)
	>>・<<・==	カーソル行のインデントレベルを上げる・下げる・自動設定(行頭のタブを追加する・削除する)
#### 領域選択との組み合せ
	v		文字選択を開始する(カーソル移動して範囲を動かす、キャンセルは esc または もう一度v)
	V		行選択を開始する(カーソル移動して範囲を動かす、キャンセルは esc または もう一度V)
	ctrl-v		矩形(長方形)選択を開始する(カーソル移動して範囲を動かす、キャンセルは esc または もう一度ctrl-v)
	gv		前回選択した範囲をもう一度選択する
	d		選択した範囲を削除
	c		選択した範囲を削除、挿入モードへ移行
	y		選択した範囲をコピー
	>・<・=		選択範囲のインデントレベルを上げる・下げる・自動設定(行頭のタブを追加する・削除する)
	:s/A/B/g	選択した各行に対して、すべての A を B に置き換える(:後、'<,'>に続けて、s/A/B/gと入力する)
	:w 別名		選択範囲のテキストを、別名で保存する(:後、'<,'>に続けて、w 別名と入力する)
	"xy		選択範囲のテキストを、xレジスタに保存する
#### クォート・カッコ内の領域選択
     例 {(("['xAxx']"))} において、カーソル位置が A でvix・vaxした時の選択範囲
	vi' ・va'	xxxx		・'xxxx'
	vi] ・va]	'xxxx'		・['xxxx']
	vi" ・va"	['xxxx']	・"['xxxx']"
	vi) ・va)	"['xxxx']"	・("['xxxx']")
	v2i)・v2a)	("['xxxx']")	・(("['xxxx']"))
	vi} ・va}	(("['xxxx']"))	・{(("['xxxx']"))}
     つまり、囲い文字を viは含まず・vaは含む その内側を選択する
#### 選択領域の各行の先頭・末尾に挿入する
     例
	XXXX		ブロックエリア
	XXXX		ブロックエリア
	XXXX		ブロックエリア
     ctrl-v で ブロックエリア 部分を矩形(長方形)選択する
     I を入力する
     1行目の先頭に #### を入力して esc
     各行が ####ブロックエリア となる
	I・A		選択した領域の各行の先頭・末尾に挿入する
#### マウス
	ドラッグ		範囲選択
	option-ドラッグ	矩形選択
	command-c	コピー(コマンドモードのコピーは保持される)
	command-v	ペースト
	option-クリック	クリック行に移動する
#### 取り消し・繰り返し
−-短縮----コマンド---------意味--
	u		操作履歴を一つ前に戻す(undo)
ctrl-r	:redo⏎		操作履歴を一つ次に進める(redo)
	.		直前の操作を繰り返す
	数字 コマンド	数字を付加してコマンドを指定すると、その回数繰り返す
	例 10l		lを10回実行 = 10文字右へ移動 
	例 4dd		ddを4回実行 = 4行削除
	例 3x		xを3回実行 = 3文字削除
	例 2u		uを2回実行 = 操作履歴を2つ前に戻す
#### 検索
	*		カーソル位置の単語を、文章末尾へ向かって、検索する
	#		カーソル位置の単語を、文章先頭へ向かって、検索する
	/ キーワード	キーワード(正規表現も使える)を、文章末尾へ向かって、検索する
	? キーワード	キーワード(正規表現も使える)を、文章先頭へ向かって、検索する
	n・N		次の検索キーワードへ・前の検索キーワードへ
	fx・Fx		行内で、行末・行頭に向かって x 1文字を検索する、x 上にカーソルをセットする(行内1文字検索)
	tx・Tx		行内で、行末・行頭に向かって x 1文字を検索する、x の直前にカーソルをセットする(行内1文字検索)
	;・,		行内1文字検索で、次の検索文字へ・前の検索文字へ
#### 置き換え(Aには正規表現も使える)
	:s/A/B/⏎	カーソル行に対して、最初の A を B に置き換える
	:s/A/B/g⏎	カーソル行に対して、すべての A を B に置き換える
	:s/A/B/gc⏎	カーソル行に対して、すべての A を B に確認してから置き換える
	:%s/A/B/⏎	すべての各行に対して、最初の A を B に置き換える
	:%s/A/B/g⏎	すべての各行に対して、すべての A を B に置き換える
	:60,63s/A/B/g⏎	60から63行に対して、すべての A を B に置き換える
	:60,$s/A/B/g⏎	60から最終行に対して、すべての A を B に置き換える
	~		カーソル位置の大文字と小文字を変換する
	rx		カーソル位置を x に修正する(x = 1文字のテキスト)
	J・gj		次の行と連結する(スペース区切りあり・無し)
#### マーク
	mマーク		カーソル位置にマーク(A-Za-zの1文字のラベル)を付ける
	'マーク		マークした位置に移動する
	:marks⏎		マークの一覧を表示する
#### 折りたたみ
	zf		選択行を折りたたむ
	space		折りたたみを展開する
#### ウィンドウ
	:split⏎		画面を水平に分割する
	:vsplit⏎	画面を垂直に分割する
	:new パス⏎	画面を水平分割して、パスのファイルを開く
	:vnew パス⏎	画面を垂直分割して、パスのファイルを開く
	:e パス⏎		現在の分割領域に、パスのファイルを開く
	:close⏎		分割領域を閉じる
	ctrl-w w	別の分割領域へ移動する
	ctrl-w +	分割領域を拡大する
	ctrl-w -	分割領域を縮小する
	ctrl-w r	分割領域を上下逆・左右逆にする
#### オプション設定
	:set all⏎	すべてのオプション設定を表示する(q=終了、space・b=1ページ移動、d・u=半ページ移動)
	:set tabstop=8⏎	タブ幅を8文字に設定する
	:set number⏎	行番号を表示する
	:set nonumber⏎	行番号を表示しない
#### 略記
	:ab mb MacBook⏎	mb tab で MacBook と入力するように設定する
	:unab mb⏎	略記 mb を解除する
	:ab⏎		略記設定を一覧表示する
#### 補完
	ctrl-p		入力モードにおいて、ファイル内で繰り返し使われる単語を補完する
     例 list1 list2 list3 日本国憲法 日本史
	li ctrl-p	list1 list2 list3 の選択候補が表示される
	日本ctrl-p	日本国憲法 日本史 の選択候補が表示される
#### マクロ(= キー操作の手順)
     例 
	# コメント行
	# コメント行
	# コメント行
	# コメント行
     1行目の # にカーソルを移動
     qa と入力して、マクロ記録を開始(aレジスタに保存される、レジスタはa-zの任意の1文字)
     xx と入力して、1行目の'# 'を削除
     ↓を押して2行目の # に移動
     q と入力して、マクロ記録を終了
     @a と入力して、aレジスタのマクロを実行
     2@a と入力すると、aレジスタのマクロを2回実行する
	qx・q		マクロ記録を開始(xレジスタに保存される)・もう一度 q を押してマクロ記録を終了		
	@x		xレジスタのマクロを実行する
	@@		直前に実行したマクロを実行する
#### レジスタ読み書き
	:reg⏎		レジスタ情報をリスト表示
	"xp		xレジスタの値をカーソル位置にペースト(レジスタ = a-zの1文字)
	"xyy		xレジスタにカーソル行のテキストをコピー(レジスタ = a-zの1文字)
	領域選択と"xy	xレジスタに選択範囲のテキストをコピー(レジスタ = a-zの1文字)
     レジスタにはマクロだけでなく、テキストの書込・読出が可能
     つまり、26個のクリップボードのように扱えるのだ
#### その他
−-短縮----コマンド---------意味--
	:r パス⏎		パスのファイル内容を挿入する
	:r! コマンド⏎	コマンドの実行結果を1行挿入する
!!	:.! コマンド⏎	コマンドの実行結果で1行置き換え
	:args⏎		ファイル名を一覧表示する
	:pwd⏎		カレントディレクトリを表示する
	ctrl-g		ファイル名・変更状態・カーソル位置などを表示する
	q:		:で始まるコマンド履歴を表示する(選択して実行可能)
	ctrl-a		カーソル下の数値に +1 する

経験とか、悩みとか、

  • GUI環境とショートカットが重複していると、viの操作が反応しないことがある。
    • viのcontrol-rが、Quicksilverの設定と重複していて反応してくれなかった。
    • Quicksilverの設定を変更したら、ちゃんと反応した。
  • 日本語使っていると、コマンドモードで「かな」のまま入力してしまったがっかり感は相当なもの。(どうにかしたいと悩む)

さらに、何を覚えるべきか?(きりがない)

参考ページ

素晴らしい情報に感謝です!

2012-02-27

なるべく覚えないviエディタの使い方

viというエディタがある。長い歴史のあるエディタである。ユーザー視点で見ると、コマンドモード・入力モードという二つのモードがあって、モードを切り替えながらテキスト編集するスタイルである。GUI全盛のモードレスなエディタに慣れきってしまった自分には、かなり面食らったエディタであった。初めて起動した時、文字も入力できず、終了の仕方も分からない...。何じゃこれは?

そう思って調べてみると、viには非常に多くのショートカット的コマンドがある。その多さにまず面食らう。とても覚えきれないと。以下は、自分で調べて理解できた使い方である。軽く100以上の操作がある。(これでもなるべく行を節約して書いたのに)しかも、以下の表でもすべてを網羅できている訳ではない。

どうにか、これらのコマンドをなるべく覚えないで、そこそこ使えるようになりたい。何を覚えて、何を覚えなくていいのか、探ってみた。

viエディタの使い方(多過ぎて覚えられない例)

1表記についてこの表で使われる書き方の意味
2esc= escキー
3= enterキー
4space= スペースキー
5tab= tabキー
6ctrl-x= controlキーを押しながら x を押す
7option-x= optionキーを押しながら x を押す
8command-x= commandキーを押しながら x を押す
9A・B= 「A あるいは B」の意味。似た機能を1行で併記する時に使う
10
11viの起動
12vi名称未設定ファイルを開く
13vi ファイルファイルを開く
14vi -r ファイル強制終了後のバックアップから、ファイルを復元して開く
15
16モード
17i・aカーソルの左・右に、挿入モードで入力する
18I・A行頭・行末に移動して、挿入モードで入力する
19O・o上・下に1行追加して、挿入モードで入力する
20s1文字削除して、挿入モードへ移行する
21R置換モードで入力する
22escコマンドモードへ移行する
23
24保存・終了
25:w⏎保存する
26:w 別名⏎別名で保存する
27:q⏎終了する(保存してない状態では終了できない)
28:q!⏎保存せずに終了する
29:wq⏎必ず、保存して終了する
30:x⏎変更があれば、保存して終了する(変更無しなら、保存せず終了のみ)
31ZZ= :x⏎
32
33カーソル移動
34h・j・k・l←・↓・↑・→
35w・e・bカーソルを単語単位で移動する(次の先頭・次の末尾・前の先頭)
36W・E・Bカーソルをスペース区切り単位で移動する(次の先頭・次の末尾・前の先頭)
370・^・$カーソルを行頭・空白以外の行頭・行末へ移動する
38次の行の先頭へ移動する
39{・}「空行」区切りで移動する
40(・)「空行」か「ピリオド& スペース」区切りで移動する
41[ [・] ]「{」区切りで移動する
42ctrl-f・ctrl-b1ページ上・下に移動する
43ctrl-u・ctrl-d半ページ上・下に移動する
44H・M・L画面(表示領域)の上・中央・下へ移動する
45gg・Gファイルの先頭・末尾へ移動する
46行番号 G行番号へ移動する
47zzカーソル位置が中央になるようにスクロールする
48
49削除・コピー
ペースト・インデント
50x・X= d→・d←
51d カーソル移動カーソル移動(←・↓・↑・→・w・e・b・0・^・$等)した部分の文字を削除
52dd1行削除
53D行末まで削除
54c カーソル移動カーソル移動(←・↓・↑・→・w・e・b・0・^・$等)した部分の文字を削除、挿入モードへ移行
55cc1行削除、挿入モードへ移行
56C行末まで削除、挿入モードへ移行
57y カーソル移動カーソル移動(←・↓・↑・→・w・e・b・0・^・$等)した部分の文字をコピー
58yy1行コピー
59Y1行コピー
60v範囲選択を開始する(カーソル移動して範囲を動かす、キャンセルはもう一度v or esc)
61V行選択を開始する(カーソル移動して範囲を動かす、キャンセルはもう一度V or esc)
62ctrl-v矩形選択を開始する(カーソル移動して範囲を動かす、キャンセルはもう一度ctrl-v or esc)
63gv前回選択した範囲をもう一度選択する
64d選択した範囲を削除
65c選択した範囲を削除、挿入モードへ移行
66y選択した範囲をコピー
67P・pカーソルの上左・下右にペースト(削除・コピー・カットしたテキストがペースト可能)
68選択範囲のインデントを上の行に合わせる
69
70マウス
71ドラッグ範囲選択
72option-ドラッグ矩形選択
73command-cマウスで選択した範囲をコピー(コマンドモードのコピーは保持される)
74command-vcommand-cに対するペースト
75option-クリッククリック行に移動する
76
77取り消し・繰り返し
78u操作履歴を一つ前に戻す(undo)
79:redo⏎・ctrl-r操作履歴を一つ次に進める(redo)
80.直前の操作を繰り返す
81数字 コマンド数字を付加してコマンドを指定すると、その回数繰り返す
82例 10llを10回実行 = 10文字右へ移動
83例 4ddddを4回実行 = 4行削除
84例 3xxを3回実行 = 3文字削除
85例 2uuを2回実行 = 操作履歴を2つ前に戻す
86
87検索
88カーソル位置の単語を、文章末尾へ向かって、検索する
89#カーソル位置の単語を、文章先頭へ向かって、検索する
90/ キーワードキーワード(正規表現も使える)を、文章末尾へ向かって、検索する
91? キーワードキーワード(正規表現も使える)を、文章先頭へ向かって、検索する
92n・N次の検索キーワードへ・前の検索キーワードへ
93
94置き換え(Aには正規表現も使える)
95:s/A/B/⏎カーソル行に対して、最初の A を B に置き換える
96:s/A/B/g⏎カーソル行に対して、すべての A を B に置き換える
97:s/A/B/gc⏎カーソル行に対して、すべての A を B に確認してから置き換える
98:%s/A/B/g⏎ファイル全体に対して、すべての A を B に置き換える
99:60,63s/A/B/g⏎60から63行に対して、すべての A を B に置き換える
100:60,$s/A/B/g⏎60から最終行に対して、すべての A を B に置き換える
101~カーソル位置の大文字と小文字を変換する
102rxカーソル位置を x に修正する(x = 1文字のテキスト)
103J次の行と連結する
104
105マーク
106mマークカーソル位置にマーク(A-Za-zの1文字のラベル)を付ける
107マークマークした位置に移動する
108:marksマークの一覧を表示する
109
110折りたたみ
111zf選択行を折りたたむ
112space折りたたみを展開する
113
114ウィンドウ
115:split⏎画面を水平に分割する
116:vsplit⏎画面を垂直に分割する
117:new パス画面を水平分割して、パスのファイルを開く
118:vnew パス画面を垂直分割して、パスのファイルを開く
119:e パス現在の分割領域に、パスのファイルを開く
120:close⏎分割領域を閉じる
121ctrl-w w別の分割領域へ移動する
122ctrl-w +分割領域を拡大する
123ctrl-w -分割領域を縮小する
124ctrl-w r分割領域を上下逆・左右逆にする
125
126環境設定
127:set all⏎すべての環境設定を表示する(q=終了、space・b=1ページ移動、d・u=半ページ移動)
128:set tabstop=8⏎タブ幅を8文字に設定する
129:set number⏎行番号を表示する
130:set nonumber⏎行番号を表示しない
131
132略記
133:ab mb MacBookmb tab で MacBook と入力するように設定する
134:unab mb⏎略記 mb を解除する
135:ab略記設定を一覧表示する
136
137その他
138:r パス⏎パスのファイル内容を挿入する
139:r! コマンド⏎コマンドの実行結果を1行挿入する
140:.! コマンド⏎コマンドの実行結果で1行置き換え
141!!= :.!
142:args⏎ファイル名を一覧表示する
143:pwd⏎カレントディレクトリを表示する
144ctrl-gファイル名・変更状態・カーソル位置などを表示する

必要最小限の7つの操作

今時のviは、矢印キーも使える。マウスで選択すればコピー・ペーストも可能である。日本語もデフォルト設定のまま問題なく入力できた。

ならば、必要最小の知識として、以下のことを知っていれば、どうにかなりそうな気がした。

  • viエディタには、コマンドモードと入力モードがあるよ。
  • vi起動直後は、コマンドモードになっているよ。
  • 文字入力は、入力モードの時しかできないよ。
  • コマンドモードで i を入力すると、入力モードに移行するよ。
  • escキーを押せば、コマンドモードに戻るよ。
  • コマンドモードで Z Z と押すと、ファイルを保存して終了するよ。
  • コマンドモードで : q ! enter と押すと、保存せずに終了するよ。

どのように覚えるか?

それにしても、必要最小の知識だけでは使いにくい。行全体をコピーしたいとか、行頭・行末に移動したいとか、文頭・文末に移動したいとか、そんな時でもカーソルが1文字1行ずつしか動かないのでイライラすること必至である。

ある程度のコマンドは覚える必要がある。しかし、丸暗記していると膨大なコマンド数に感じてしまう。コマンドの仕組みを理解して、少ない知識で最大の効果をあげるにはどのように覚えるべきなのか?考えてみた。

  • 入力モードに移行するのは、最初は i だけ覚えておけば十分。
    • 必要に応じて、その他の移行方法は覚えればいい。(個人的にはo・Oをよく使う)
  • 異なる操作だが、同じ機能を実現する場合がある。やり易い方法を一つだけ覚えておけば十分。
    • 大抵、短縮エイリアス的なコマンドの方が使い易いので、そちらを優先している。
    • 例 :x = ZZ = 変更があれば保存して、終了 ...... ZZが好みなので、これだけ覚えた。
    • 例 d→・d← = x・X = fn-delete・delete = 1文字削除 ...... つまり、忘れても入力モードでGUI環境と同等にdeleteキーが使えるので、覚えなくてもOK。
  • カーソル移動は矢印キーで十分。無理にhjklを使う必要はない。
    • 但し、hjkl以外のカーソル移動の方法は、できるだけ覚えた方がいい。
  • カーソル移動は重要!
    • カーソルを入力したい箇所に素早く動かせることは、そのままエディタの使い易さに繋がる。
    • 単語、行頭・行末、文頭・文末、ページ移動などは当然として、それ以上にきめ細かく制御できる。
    • カーソル移動だけは、頑張ってしっかり覚えた方がいい。

33カーソル移動
35w・e・bカーソルを単語単位で移動する(次の先頭・次の末尾・前の先頭)
36W・E・Bカーソルをスペース区切り単位で移動する(次の先頭・次の末尾・前の先頭)
370・^・$カーソルを行頭・空白以外の行頭・行末へ移動する
38次の行の先頭へ移動する
39{・}「空行」区切りで移動する
40(・)「空行」か「ピリオド& スペース」区切りで移動する
41[ [・] ]「{」区切りで移動する
42ctrl-f・ctrl-b1ページ上・下に移動する
43ctrl-u・ctrl-d半ページ上・下に移動する
44H・M・L画面(表示領域)の上・中央・下へ移動する
45gg・Gファイルの先頭・末尾へ移動する
46行番号 G行番号へ移動する
47zzカーソル位置が中央になるようにスクロールする

  • コマンドは連携する。
    • 例 :w = 保存、:q = 終了、:wq = 保存して終了
  • そして、カーソル移動もコマンド。よって連携する。
    • 例 w = 1単語ずつ移動、dw = 1単語ずつ削除する
    • 例 0(ゼロ) = 行頭へ移動、d0 = カーソル位置から行頭まで削除する
  • つまり、カーソル移動する範囲を○○する、というルールになっているのだ。
    • これを d、y、c等と連携させると、かなり強力に編集できる。
    • カーソル移動だけしっかり覚えておけば、あとは連携させるだけでOK。
  • c(=change=変更)とd(=delete=削除)の差は、入力モードに移行するか、しないかの差でしかない。
    • つまり、c=d+i である。
    • dだけ覚えておけば十分である。
    • 個人的には、モード移行しない d が使い易い。
  • c・d = OSX環境の command-x カット と同等。
    • つまり、コピー y と同じように、削除したテキストはペーストできるのだ。
  • 2連文字コマンド、大文字コマンド(つまりshiftと英字)は、マウス環境におけるダブルクリック、shift-クリックのような効果がある。
    • 1文字の時より作用する範囲が拡大したり、反対向きに作用したりする。
    • 例 d→ = 1文字削除、dd = 1行削除、D = カーソルから行末まで削除
    • 例 x = 右方向の1文字削除(=forward delete)、X = 左方向の1文字削除(=back delete)
    • 例 p = 下右方向にペースト、P = 上左方向にペースト
  • undo・redoは必ず覚える
    • 操作履歴を一つ前に戻せるu(= undo = OSX環境におけるcommand-z)は素晴らしく便利。
    • と同時に、紹介される機会が少ない :redo⏎ も覚えておくと、幸せ感が倍増される。
  • コマンドの前に数字を付けると、その回数分繰り返す。
    • 例 10→ →を10回実行 = 10文字右へ移動
    • 例 4dd ddを4回実行 = 4行削除
    • 例 3x xを3回実行 = 3文字削除
    • 例 2u uを2回実行 = 操作履歴を2つ前に戻す
  • 検索・置換まで覚えれば、一般的なGUI環境のエディタと同等に使える。
    • 正規表現*1を覚えれば、便利さが飛躍的に向上する。
  • 環境設定は:set all⏎で一覧表示されるので、設定の書式だけ覚えておけば、必要に応じて調べられる。
    • 値は = に続けて設定する。
      • 例 :set tabstop=8⏎(= タブ幅を8文字に設定する)
    • false・trueは、設定項目の先頭にnoを付加するか、しないか
      • 例 :set number⏎、:set nonumber⏎(= 行番号を表示する、しない)

便利に使うための操作

以上のことを考慮して、覚えるコマンドを厳選してみた。

11viの起動
13vi ファイルファイルを開く
14vi -r ファイル強制終了後のバックアップから、ファイルを復元して開く
15
16モード
17i挿入モードで入力する
19O・o上・下に1行追加して、挿入モードで入力する
22escコマンドモードへ移行する
23
24保存・終了
25:w⏎保存する
28:q!⏎保存せずに終了する
31ZZ変更があれば、保存して終了する(変更無しなら、保存せず終了のみ)
32
33カーソル移動
35w・e・bカーソルを単語単位で移動する(次の先頭・次の末尾・前の先頭)
36W・E・Bカーソルをスペース区切り単位で移動する(次の先頭・次の末尾・前の先頭)
370・^・$カーソルを行頭・空白以外の行頭・行末へ移動する
38次の行の先頭へ移動する
39{・}「空行」区切りで移動する
40(・)「空行」か「ピリオド& スペース」区切りで移動する
41[ [・] ]「{」区切りで移動する
42ctrl-f・ctrl-b1ページ上・下に移動する
43ctrl-u・ctrl-d半ページ上・下に移動する
44H・M・L画面(表示領域)の上・中央・下へ移動する
45gg・Gファイルの先頭・末尾へ移動する
46行番号 G行番号へ移動する
47zzカーソル位置が中央になるようにスクロールする
48
49削除・コピー
ペースト・インデント
54d カーソル移動カーソル移動(←・↓・↑・→・w・e・b・0・^・$等)した部分の文字を削除
55dd1行削除
56D行末まで削除
57y カーソル移動カーソル移動(←・↓・↑・→・w・e・b・0・^・$等)した部分の文字をコピー
58yy1行コピー
59Y1行コピー
60v範囲選択を開始する(カーソル移動して範囲を動かす、キャンセルはもう一度v or esc)
61V行選択を開始する(カーソル移動して範囲を動かす、キャンセルはもう一度V or esc)
62ctrl-v矩形選択を開始する(カーソル移動して範囲を動かす、キャンセルはもう一度ctrl-v or esc)
63gv前回選択した範囲をもう一度選択する
66y選択した範囲をコピー
67P・pカーソルの上左・下右にペースト(削除・コピー・カットしたテキストがペースト可能)
68選択範囲のインデントを上の行に合わせる
69
70マウス
71ドラッグ範囲選択
72option-ドラッグ矩形選択
73command-cマウスで選択した範囲をコピー(コマンドモードのコピーは保持される)
74command-vcommand-cに対するペースト
75option-クリッククリック行に移動する
76
77取り消し・繰り返し
78u操作履歴を一つ前に戻す(undo)
79ctrl-r操作履歴を一つ次に進める(redo)
80.直前の操作を繰り返す
81数字 コマンド数字を付加してコマンドを指定すると、その回数繰り返す
82例 10llを10回実行 = 10文字右へ移動
83例 4ddddを4回実行 = 4行削除
84例 3xxを3回実行 = 3文字削除
85例 2uuを2回実行 = 操作履歴を2つ前に戻す
86
87検索
88カーソル位置の単語を、文章末尾へ向かって、検索する
89#カーソル位置の単語を、文章先頭へ向かって、検索する
90/ キーワードキーワード(正規表現も使える)を、文章末尾へ向かって、検索する
91? キーワードキーワード(正規表現も使える)を、文章先頭へ向かって、検索する
92n・N次の検索キーワードへ・前の検索キーワードへ
93
94置き換え(Aには正規表現も使える)
98:%s/A/B/g⏎ファイル全体に対して、すべての A を B に置き換える
125
126環境設定
127:set all⏎すべての環境設定を表示する(q=終了、space・b=1ページ移動、d・u=半ページ移動)
128:set tabstop=8⏎タブ幅を8文字に設定する
129:set number⏎行番号を表示する
130:set nonumber⏎行番号を表示しない

全部で50コマンドくらい。これでも多いか...。

参考ページ

歴史

後にカリフォルニア大学バークレイ校にadm3a端末が導入されたのを機に、ビル・ジョイ自身により更なる改良を加えられたものが、現在のviと呼ばれるエディタである。

vi - Wikipedia

上記を読んで想像すると、おそらく、1978年頃からviエディタは活躍していたことになる。それは未だに廃れることなく、UNIXの中には必ず入っている。もちろん、OSXもUNIXなのでviは標準インストールされている。*2

*1:正規表現もまた相当奥の深い知識なのだが、ワイルドカード・行頭・行末ぐらい覚えておくだけでも、かなり便利だと思う。

*2:但し正確には、OSX 10.6のviは、vimへのハードリンクになっている。vimは、今ではviを拡張した上位互換のエディタとなっている。よって、viの操作性は30年以上たった今も、しっかり残っているのである。

2010-02-21

MacBookのターミナルの操作と設定

OSX 10.5以降では、何もしなくてもターミナルは日本語を表示してくれる。これまでデフォルト設定のまま問題なく使えていたので、無関心になりがちだった。でも、改めて操作方法や設定を調べ直すと、便利な操作や設定があることに気付かされる。そして、新たな操作や設定を知ることは、シェルやコマンドの仕組みを覚えることに繋がる。きっと、まだまだ自分の知らない多くの技が眠っているはず。

作業環境

$ bash --version
GNU bash, version 3.2.48(1)-release (x86_64-apple-darwin10.0)
Copyright (C) 2007 Free Software Foundation, Inc.

ターミナルの操作

クリア
  • command-K、あるいはcontrol-Lで、ターミナル画面のクリア。(ほぼ同等だが、微妙にクリアされる範囲が違う)
入力補完
  • コマンド入力中にtabキーを押すと、入力補完される。
    • 複数候補がある場合は警告音が鳴る。
    • もう一度tabキーを押すと、候補リストが表示される。
    • あるいは次の文字も入力して、tabキーを押してみる。(絞り込まれて補完されるかも)
  • escキーのダブル押しで、tabキー1回と同様の入力補完が期待できる。
    • もう一度escキーをダブル押しすると、候補リストが表示される。
全コマンド一覧
  • ターミナル画面を表示して esc キー長押しすると...(bashの場合)
  • Display all 1812 possibilities? (y or n)と出るので y を押すと、1812コマンドが一覧表示された!
$ 
Display all 1812 possibilities? (y or n)
!                         done                      join                      perlbug5.8.9              sqlite3
./                        dot_clean                 josusi_dic_find           perlcc                    sqlite3_3_4_0
2to3                      dprofpp                   jot                       perlcc5.8.9               srm
2to32.6                   dprofpp5.10.0             jpeg2swf                  perldoc                   ssh
:                         dprofpp5.8.9              jpegtran                  perldoc5.10.0             ssh-add
AppleFileServer           drutil                    jps                       perldoc5.8.9              ssh-agent
BMPtoRGB                  dscacheutil               jrunscript                perlivp                   ssh-keygen
BootCacheControl          dscl                      jsadebugd                 perlivp5.10.0             ssh-keyscan
BootstrapDump             dsconfigad                jstack                    perlivp5.8.9              sshd
BuildStrings              dsconfigldap              jstat                     perlthanks                sso_util
...(中略)...
矩形(長方形)領域のテキストを選択してコピーする
  • optionキーを押しながら、マウスでドラッグしてテキスト範囲を選択する。
コマンド履歴
  • ↑↓矢印キーでコマンド履歴を辿れる。
スクロール
Command-↑・↓上スクロール・下スクロール
fn-↑・↓ページアップ・ページダウン
fn-←・→ページ上端までスクロール・ページ下端までスクロール
コマンド入力中の操作
option-クリッククリック位置にマウスカーソルを移動する
control-A・Eカーソルを行頭へ移動・カーソルを行末へ移動
control-K・Uカーソル位置から行末まで削除する・カーソル位置から行頭まで削除する
control-W・option-Dカーソル左側の次のスペース手前まで削除する・カーソル右側の次のスペース手前まで削除する単語単位の削除
control-Y削除したテキストをペーストする
control-F・B1文字右へ移動・1文字左へ移動
option-F・B*11単語右へ移動・1単語左へ移動単語単位の移動
ディレクトリの移動
  • zariユーザでログイン
$ cd          # ホームへ移動(/Users/zari)
$ cd ~guest   # Guestユーザへ移動(/Users/Guest)
$ cd ~        # ホームへ移動(/Users/zari)
$ cd desktop  # デスクトップへ移動(/Users/zari/desktop)
$ cd -        # 一つ前のディレクトリへ戻る(/Users/zari)
$ cd ..       # 一つ上のディレクトリへ移動(/Users)
$ cd .        # 現在のディレクトリへ移動(/Users) 

bashの設定ファイル

  • ~/.bash_profile の例
# ~/.bashrcの内容を読み込むだけ
if [ -f ~/.bashrc ]; then
  . ~/.bashrc
fi

  • ~/bashrc の例
# コマンドプロンプトの設定
PS1='\[\033[40;1;32m\]\u\[\033[2;32m\]@\[\033[0m\]\[\033[40;32m\]\h \[\033[1;36m\]\w \[\033[0m\]\[\033[40;2;37m\]\d  \t\[\033[0m\]\n\\$ '

# コマンドサーチパスの追加
# MacPortsのパスを追加
PATH=/opt/local/bin:$PATH

# エイリアスの設定
alias ls='ls -aF'
alias rm='rm -i'
alias mv='mv -i'
alias cp='cp -i'
alias ..='cd ..'
alias ...='cd -'
alias cot='open -a CotEditor'
alias screensaver='/System/Library/Frameworks/ScreenSaver.framework/Resources/ScreenSaverEngine.app/Contents/MacOS/ScreenSaverEngine'

# 関数の読み込み
. /usr/local/bin/saykanji.sh

# シェルオプションの設定
# *ワイルドカードで、.不可視ファイルにもヒットする設定
#   $ ls *bash*
#   .bash_history  .bash_profile  .bashrc
shopt -s dotglob
コマンドプロンプトの設定

コマンドプロンプトは奥が深い...。

f:id:zariganitosh:20100220224616p:image

  • Visorスタイルのターミナルにすると、同色の黒い背景は淡いグレーで表現された。
# 半角¥は、半角\に置き換える。
$ echo "PS1='$PS1'";\
> echo "PS2='$PS2'";\
> echo "PS3='$PS3'";\
> echo "PS4='$PS4'";
PS1='\[\033[40;1;32m\]\u\[\033[2;32m\]@\[\033[0m\]\[\033[40;32m\]\h \[\033[1;36m\]\w \[\033[0m\]\[\033[40;2;37m\]\d  \t\[\033[0m\]\n\\$ '
PS2='> '
PS3=''
PS4='+ '

$ PS3='Number? '
$ select VALUE in A B C; do echo "'$VALUE' is selected.";break; done
1) A
2) B
3) C
Number? 1
'A' is selected.

$ unset PS3
$ select VALUE in A B C; do echo "'$VALUE' is selected.";break; done
1) A
2) B
3) C
#? 2
'B' is selected.
  • PS1は、コマンド待ちの状態で表示される、最も多く目にするプロンプト。
  • PS2は、コマンド入力中、入力完了まで複数行にわたる場合に表示されるプロンプト。
  • PS3は、コマンド実行中、selectコマンドで番号の選択を求める場合のプロンプト。
    • PS3のデフォルトは未設定だが、'#?'が表示される。
    • PS3=''と設定してしまうと、何も表示されない。
    • unset PS3とすることで、デフォルト設定に戻る。
  • PS4は、シェルスクリプトをトレースオプション-xで実行中に表示されるプロンプト。
    • サブシェルの深さに応じて、プロンプトの最初の文字が2重、3重に重なる。
# ---------- ファイル:~/desktop/sample.sh ----------
#!/bin/bash
echo this is sample.
echo `date`
$ bash -x ~/desktop/sample.sh
+ echo this is sample.
this is sample.
++ date
+ echo 2010年 $'2?\234\21021?\227?' $'?\227??\233\234?\227?' $'22?\231\20203?\210\20606?\222' JST
2010年 2月21日 日曜日 22時03分06秒 JST

  • エスケープシーケンス

\aASCII のベル文字 (07)
\d"曜日 月 日"のフォーマットによる日付け(例 "Tue May 26")
\eASCII のエスケープ文字 (033)
\h最初の"."のところまでのホスト名
\Hホスト名
\n改行
\r復帰
\sシェル名、$0 のベース名(最後のスラッシュの後ろの部分)
\t24時間制の HH:MM:SS のフォーマットによる時間
\T12時間制の HH:MM:SS のフォーマットによる時間
\@am/pm をつけた12時間制のフォーマットによる時間
\u現ユーザーのユーザー名
\vbash のバージョン(例 2.00)
\Vbash のリリース番号、バージョンとパッチレベル(例 2.00.0)
\w現在のディレクトリ
\W現在のディレクトリのベース名
\!現在のコマンドのヒストリー番号
\#現在のコマンドのコマンド番号
\$UIDが0なら#、そうでなければ$
\nnn8進数nnnに対応する文字
\\バックスラッシュ
\[表示されない文字列の開始。端末制御シーケンスをプロンプトに埋め込む。
\]表示されない文字列の終り。

      • 半角¥は、半角\に置き換える。

  • コマンドの実行結果をプロンプトにする。
# 半角¥は、半角\に置き換える。
$ PS1='$(date +%Y-%m-%d\ %H:%M:%S) $ '
2010-02-20 21:44:00 $

背景色文字色
4030
4131
4232
4333
4434
4535
4636
4737

# 半角¥は、半角\に置き換える。
$ PS1="\[\033[41;32m\] RED & GREEN \[\033[0m\] $ "

f:id:zariganitosh:20100220224614p:image


  • 文字スタイル

1:明、2:暗、4:下線、5:点滅、7:逆転、8:非表示、0:全設定クリア(背景色・文字色・スタイル)

# 半角¥は、半角\に置き換える。
$ PS1="\[\033[2;40;32m\] DARK \[\033[0;40;32m\] NORMAL \[\033[1;40;32m\] LIGHT \[\033[0;4;40;32m\] UNDERLINE \[\033[0;7;40;32m\] REVERSE \[\033[0m\] $ "

f:id:zariganitosh:20100220224615p:image

shopt(shell option、シェルオプション)コマンドによる設定
  • shoptコマンドを引数なしで実行すると一覧が表示される。
その他のシェル変数
# 環境変数の一覧
$ printenv

# 環境変数、シェル変数、関数の一覧
$ set

ログインシェルの変更

システム環境設定から
  1. システム環境設定 >> アカウント を開く。
  2. ウィンドウ左下の鍵アイコンをクリックして、変更可能な状態にする。(解錠されていればOK)
  3. マイアカウントをcontrol-クリックして、詳細オプション... を開く。
  4. ログインシェル:の項目でリストを選択する。(その他の項目を意味が分からずに変更してしまうと、アカウントを消失する可能性もあるので十分注意が必要)
リストにログインシェルを追加する
  • 自分でインストールしたシェルは、そのままではシステム環境設定 >> アカウントのログインシェル:の項目に表示されない。
  • root権限でテキストファイル/etc/shellsを開いて、そこにインストールしたシェルのパスを追加する。
$ sudo vi /etc/shells
# List of acceptable shells for chpass(1).
# Ftpd will not allow users to connect who are not using
# one of these shells.

/bin/bash
/bin/csh
/bin/ksh
/bin/sh
/bin/tcsh
/bin/zsh
# ここにインストールしたシェルのパスを追加する
chpassコマンド
  • ログインシェルは、chpassコマンドで変更することもできる。
  • ログインシェルとして指定できるのは、上記/etc/shellsに書かれているシェルだけ。
$ chpass -s /bin/zsh
      • ちなみに、引数なしでchpassコマンドを実行すると、エディタ*2が起動して、設定ファイルが開く。(その他の変更可能なユーザ情報を確認できる)
      • 変更して保存終了すれば、その内容にユーザ情報が変更される。

bash参考ページ

以下のページがたいへん参考になりました。感謝です!

マニュアル
設定例
コマンドプロンプト
端末とは

Visor

ターミナルの使い勝手を向上してくれる。使い始めると手放せない便利さを感じる。

  • controlキーを2度押しすると、ターミナルが下からスライドするように表示される。
    • ショートカットやターミナルの表示位置は、自分好みに設定できる。
    • 必要な時だけ、常に同じ位置に同じサイズで表示されるところが良い。
    • もちろんタブも使えて、ウィンドウに切り離したり、結合もできる。
  • Copy on Selectのチェックを入れると...
    • 選択したテキストを自動コピーしてくれる。(Command-V不要)
    • 選択したらCommand-Vで貼り付けるだけでOKになる。

TotalFinder

以前から、Finderのファイルをコピーすれば、ターミナルにはファイルパスとしてペーストできた。ターミナルはFinderと連携させることで、より快適になる。

  • Finderをタブ管理できるようになる。
  • optionキーを2度押しすると、Finderが下からスライドするように表示される。(Visorのように)
  • ウィンドウに切り離したり、結合もできる。

タブで管理できるFinderって素晴らしく便利だ。ターミナルさえタブを利用できるようになったのに、OSX標準のFinderで利用できないのは疑問を感じていた。しかも、Visorのような邪魔にならない使い勝手。ウィンドウやスペースを探し回らすに、欲しい時に常に同じ場所で直ぐ操作できる。Exposé・Spacesの上を行く。


OS・シェル・ターミナルの関係について

MacBookのターミナルからコマンドを使い初めの頃、OS・シェル・ターミナルの区別が曖昧な自分がいた。


かつて、まだパソコンという考えもなく、コンピュータと言えば、メインフレームと呼ばれる大型のマシンが当り前だった時代の話。

  • 非常に高価なメインフレームは、企業や大学・研究所単位で数台しか所有できず、1台をみんなで共有して利用するのが当り前だった。
  • メインフレームを利用するには、ケーブルやネットワークを介して接続された「端末」と呼ばれるマシンを使っていた。
  • 端末はキー入力と画面出力の機能しか持たず、メインフレームに比べたら非常に安価。
  • 端末に入力した情報をメインフレームに送って、処理の結果を受け取って画面に表示していた。(メインフレームのファイルシステム側に出力することもあると思う)

メインフレームは、UNIXと呼ばれるOSで制御されていた。

  • OSは、必要最小限の機能しか提供してくれない。(適切なメモリを割り当て、適切なタイミングで、複数のプログラムを実行したり、ファイルへの読み書きを管理するなど)
  • OSが理解できるのは定義されたシステムコールだけであり、毎回プログラムを書いてシステムコール*3の連続で操作するのは、とても実用的とは言えない状態だ。

そこで、「シェル」と呼ばれるプログラムが、人間とOSの仲介をしてくれる仕組みになった。

  • シェルは、一般的な英単語や略語・記号を解釈して、ファイルの操作やプログラムの実行をOSに依頼する。
  • 利用頻度の高いプログラムは、コマンドとして、数文字のテキスト入力で実行できるようになった。
  • 処理結果は、画面に表示したり、ファイルに保存したり、次のコマンドに渡すことだってできる。
  • こうしてコンピュータは、シェルによって対話的*4に利用できるようになったのである。

時代は移り変わり、コンピュータは劇的に安価になり、一人1台、持ち運べるノートパソコンが当り前の環境になりつつある。もはや、端末は不要になった*5

  • ずいぶん時間が経過したにもかかわらず、今使っているMacBookさえも、当時のメインフレームから脈々と受け継がれてきたUNIXというOSで制御されている。
  • そして、シェルや端末もまた、MacBookの中で生き続けている。
  • かつての端末は「ターミナル」というアプリケーションによって、GUI環境のウィンドウの中でエミュレーションされるようになった。
  • シェルも改良され、いくつかに派生して進化した。MacBookには6種類(bash csh ksh sh tcsh zsh)のシェルがインストールされていた。
  • かつての端末と同じく、ターミナルもあらゆるUNIX環境に接続できる。
  • その操作感は、接続先のシェルが提供する機能によって変化する。
  • ターミナルは、あくまでキー入力と画面表示に特化した「端末」なのだ。

MacBookの見た目はだいぶ派手になったが、それはGUI*6の部分である。

  • 一方、昔ながらのシェル(bashなど)が提供するのは、すべてが文字による操作で、CUI*7と呼ばれる。
  • そう考えると、OSXが提供するFinderが作り出すデスクトップ環境も、シェルの一種と言える。

  • OSは、コンピュータを制御する最も基本的な仕組みを提供する。
  • シェルは、そのOSを対話的に利用できる操作環境を提供する。
  • ターミナルは、キー入力をシェルに届けて、処理結果をシェルから受け取り、画面に表示する。

*1:ターミナル >> 環境設定... >> 設定 >> キーボード >> メタキーとしてoptionキーを使用 チェックありの状態

*2:例:vi。保存しない終了方法:esc、:q!、return。

*3:実際にはプログラミング言語の標準ライブラリの関数を利用することになると思うので、その関数の中からシステムコールされることになる。

*4:コマンドを手順に沿って書き出し、テキストファイルとして保存しておき、そのファイルをシェルが実行する非対話的な利用方法もある。

*5:別の視点から見ると、かつてのメインフレームの性能を軽く超えるノートパソコンが、インターネット端末になりつつある。端末は不要になったのではなく、パソコンが端末になってしまったのである。

*6:グラフィカル・ユーザー・インタフェース。マウスでメニューやボタンなどの現実感のある画像を操作して、コンピューターを利用する仕組み

*7:キャラクタ・ユーザー・インタフェース

2008-09-12

アリスがチャレンジなコードを書く時、git branchをちゃんと理解したい!

f:id:zariganitosh:20120919113737p:image:h128:leftf:id:zariganitosh:20120920094630p:image:h128:left


アリスとボブのGitシリーズが本になりました!

  アリスとボブのGit入門レッスン



アリスは迷っていた。現状のshowメソッドは固定されたメッセージしか出力しないが、理想的にはユーザーの条件によって変化させたいと。

しかし、その機能を実装するためには結構な大改修になってしまう。果たして今の自分の技術でちゃんと完了させることが出来るだろうか?この機能追加をやるべきか、このままにするか...。

  • アリスはこの修正が失敗に終わった時のことを考えて、ボブに連絡しておくことにした。「失敗したらごめんね。」と。(なんて無責任なアリス...。)
  • 連絡を受けたボブは、アリスの機能追加には大賛成。ボブ:「ただし、新しいブランチを追加して、そこで作業くれ。」と。アリス:「ブランチ???」
  • アリスはブランチを理解できていないが、とりあえず、ボブに説明された手順をそのままやってみることにした。アリス:「習うより、慣れろだわ」
alice/project$ git branch challenge #ブランチchallengeを作成
alice/project$ git branch           #ブランチの一覧を確認
   challenge
 * master
  • ブランチの一覧を確認すると、今作成した「challenge」と、「master」が確認できる。
  • ブランチ「master」はgitがデフォルトで作成するブランチ。最初はみんなmasterから始まる。
  • ブランチ名の先頭に「*」マークが付いているので、現在作業中のブランチは「master」ということになる。
alice/project$ git checkout challenge #ブランチを「challenge」に変更
Switched to branch "challenge"
alice/project$ git branch             #ブランチの一覧を確認
 * challenge
   master
  • ブランチの一覧を確認すると、ブランチは「challenge」に変更された。

challengeブランチでのアリスの修正

  • ブランチが切り替わったことを確認して、アリスはコードの修正を始めた。
# About git
Class WhatIsGit
  def show
    puts 'Do you understand the basis of git? [yes/no]'
    input = gets.chomp.downcase
    case input
    when 'yes', 'y'
      puts 'Git is easy.'
    else
      puts 'Git is difficult...'
    end
  end
  
  def about(lang = 'en')
    puts "http://#{lang}.wikipedia.org/wiki/Git"
  end
end
  • ひとまず完成したので、コミット。
alice/project$ git commit -a -m 'challenge commit 1'
Created commit 472085b: challenge commit 1
 1 files changed, 8 insertions(+), 1 deletions(-)

  • アリスがじっくり考え直すと、もっと簡潔に書くべきと感じたので、さらに修正。
# About git
Class WhatIsGit
  def show
    case input('Do you understand the basis of git? [yes/no]')
    when 'yes', 'y'
      puts 'Git is easy.'
    else
      puts 'Git is difficult...'
    end
  end
  
  def about(lang = 'en')
    puts "http://#{lang}.wikipedia.org/wiki/Git"
  end
  
  private
    def input(message)
      puts message
      gets.chomp.downcase
    end
end
  • これで良し、コミット。
alice/project$ git commit -a -m 'challenge commit 2'
Created commit 31ce330: challenge commit 2
 1 files changed, 7 insertions(+), 3 deletions(-)

ブランチを切り替えてみる

  • アリスは、challengeブランチでの作業が一段落したので、masterブランチに切り替えてみた。
alice/project$ git checkout master
Switched to branch "master"
  • すると、what_is_git.rbの内容が、以前の状態に戻ってしまった...。

  • アリスは一瞬焦ったが、もう一度challengeブランチに切り替えてみると、さっき書いたコードが復元された!
alice/project$ git checkout challenge
Switched to branch "challenge"
  • つまりgitに管理されたディレクトリにおいては、what_is_git.rbというファイルは、編集作業をするための一時的なコピーでしかないのだ。
  • gitがコミット時点のスナップショットを脈々と保存している.gitフォルダの履歴がすべてであり、
  • 今やその履歴は、masterと、そこから分岐するchallengeという二つの系統に分かれているのだ。
イメージ図
過去 <----------------------> 未来

           o--o <-- challengeブランチ
          /
...o--o--o <------- masterブランチ

oはコミット、またはマージを表す

ボブの修正

  • ちょうどその頃、ボブは重大なバグを見つけていた。ボブ:「クラス宣言のclassが大文字で始まっていたとは...」
bob/myrepo$ git diff
 diff --git a/what_is_git.rb b/what_is_git.rb
 index ac3f723..bec373b 100644
 --- a/what_is_git.rb
 +++ b/what_is_git.rb
 @@ -1,5 +1,5 @@
  # About git
 -Class WhatIsGit
 +class WhatIsGit
    def show
      puts 'Git is difficult, if you understand the basis.'
    end
bob/myrepo$ git commit -a -m 'class downcase'
Created commit 87f0ad7: class downcase
 1 files changed, 1 insertions(+), 1 deletions(-)
bob/myrepo$ git push shared master
Counting objects: 5, done.
Compressing objects: 100% (2/2), done.
Writing objects: 100% (3/3), 281 bytes, done.
Total 3 (delta 1), reused 0 (delta 0)
Unpacking objects: 100% (3/3), done.
To /Users/Shared/project.git
   16150ca..87f0ad7  master -> master
  • すぐさま修正して、コミット、プッシュ。

challengeブランチでのアリスの迷走

  • 一方、アリスはchallengeブランチのコードをテストしていた。
alice/project$ git checkout challenge
Switched to branch "challenge"
alice/project$ irb
 >> require 'what_is_git'
 SyntaxError: ./what_is_git.rb:21: syntax error, unexpected kEND, expecting $end
 	from /Library/Ruby/Site/1.8/rubygems/custom_require.rb:27:in `gem_original_require'
 	from /Library/Ruby/Site/1.8/rubygems/custom_require.rb:27:in `require'
 	from (irb):1
 >> 
  • しかし、requireからして実行できない...。やはり私(アリス)には無理だったのか?アリスは落胆していた。

  • その時、ボブから連絡があった。重大なバグを修正したと。これを取り込まないとすべてのテストがエラーになると。
  • アリスは早速、共有リポジトリからプルした。なんと、クラス定義の先頭が大文字になっていたとは。ダメだこりゃ。
alice/project$ git checkout challenge
Switched to branch "challenge"
alice/project$ git pull shared master
From /Users/Shared/project
 * branch            master     -> FETCH_HEAD
Auto-merged what_is_git.rb
Merge made by recursive.
 what_is_git.rb |    2 +-
 1 files changed, 1 insertions(+), 1 deletions(-)
alice/project$ git diff HEAD^
 diff --git a/what_is_git.rb b/what_is_git.rb
 index 8fe919a..559d6cb 100644
 --- a/what_is_git.rb
 +++ b/what_is_git.rb
 @@ -1,5 +1,5 @@
  # About git
 -Class WhatIsGit
 +class WhatIsGit
    def show
      case input('Do you understand the basis of git? [yes/no]')
      when 'yes', 'y'
  • すぐさま修正を取り込んで、irbでテストしてみた。
alice/project$ irb
>> require 'what_is_git'
=> true
>> git = WhatIsGit.new
=> #<WhatIsGit:0x365b8c>
>> git.show
Do you understand the basis of git? [yes/no]
y
Git is easy.
=> nil
>> git.show
Do you understand the basis of git? [yes/no]
n 
Git is difficult...
=> nil
>> git.about
http://en.wikipedia.org/wiki/Git
=> nil
  • うまく動いているようだ!さすがボブ、ありがとう!

  • アリスはmasterブランチに戻って、challengeブランチをマージした。
alice/project$ git checkout master
Switched to branch "master"
alice/project$ git merge challenge
Updating 16150ca..2d04f6c
Fast forward
 what_is_git.rb |   15 +++++++++++++--
 1 files changed, 13 insertions(+), 2 deletions(-)
alice/project$ git show
 commit 2d04f6c21384ece8e0a14c5c18b179aa43b90f7f
 Merge: 1689263... 87f0ad7...
 Author: zarigani <zariganigamiteita@gmail.com>
 Date:   Fri Sep 12 10:59:02 2008 +0900

     Merge branch 'master' of /Users/Shared/project into challenge

 diff --cc what_is_git.rb
 index 8fe919a,bec373b..559d6cb
 --- a/what_is_git.rb
 +++ b/what_is_git.rb
 @@@ -1,12 -1,7 +1,12 @@@
   # About git
 - Class WhatIsGit
 + class WhatIsGit
     def show
  -    puts 'Git is difficult, if you understand the basis.'
  +    case input('Do you understand the basis of git? [yes/no]')
  +    when 'yes', 'y'
  +      puts 'Git is easy.'
  +    else
  +      puts 'Git is difficult...'
  +    end
     end
    
     def about(lang = 'en')

  • 最後にアリスは、共有gitリポジトリにプッシュしておいた。良い仕事ができた。
alice/project$ git push shared master
Counting objects: 13, done.
Compressing objects: 100% (6/6), done.
Writing objects: 100% (9/9), 1.07 KiB, done.
Total 9 (delta 2), reused 0 (delta 0)
Unpacking objects: 100% (9/9), done.
To /Users/Shared/project.git
   d0688af..7c03a98  master -> master

所感

  • コミットを繰り返すと、その時点のファイルの状態を記録したスナップショットが順に連なって保存されていく。(Time Machineや連凧をイメージ)
  • スナップショットの連続した集合をブランチと呼んでいる。
  • ブランチは途中で分岐させることができるし、再び合流させることもできる。
  • ブランチが分岐すると、分岐先のブランチは新たなブランチとして区別される。
リモートブランチについて
  • ボブは元々アリスのプロジェクトをgit cloneでコピーしている。
  • すると、ボブのプロジェクトにはアリスのプロジェクトを追跡するためのキャッシュが準備される。
  • そのキャッシュにはつまり、アリスのプロジェクトのブランチが保存されることになるので、これをリモートブランチと呼んでいる。(ブランチの一種なのだ)
  • リモートブランチは、-rオプションを指定してgit branch -rで確認できる。
bob/myrepo$ git branch -r
  origin/master
  shared/master
  • origin/masterリモートブランチは、git cloneによってgitが自動的に用意してくれた。
  • shared/masterリモートブランチは、git remote add shared /Users/Shared/project.gitによって、パスにエイリアス名を設定した時に用意されるようだ。
  • git pullを使ってしまうとその存在は見えにくいが、git fetchとgit mergeでその存在を感じられる。
bob/myrepo$ git fetch shared        #共有gitリポジトリと同期させて、キャッシュに保存する
bob/myrepo$ git merge shared/master #キャッシュ内のshared/masterリモートブランチとマージする
どちらのブランチでgit pullするべきか?
  • 上記の例では、作業中のchallengeブランチで、git pull shared masterを実行してしまった。
イメージ図
過去 <----------------------> 未来


...1--2--3--6         <--- 共有gitリポジトリのmasterブランチ(6はボブの修正Class -> class)

           4--5--M6   <--- アリスのchallengeブランチ
          /       \
...1--2--3---------M7 <--- アリスのmasterブランチ


M6: アリスのchallengeブランチでgit pull shared master
M7: アリスのmasterブランチでgit merge challenge

数字は、コミットを表す
数字の先頭にMが付く場合はマージを表す
数字の順にコミットまたはマージされている
alice/project$ git log
commit d4d076320c1fe7a3f9bee447148aefe28530289a
Merge: 6912d3f... b48a40e...
Author: zarigani <zariganigamiteita@gmail.com>
Date:   Fri Sep 12 15:29:44 2008 +0900

    Merge branch 'master' into challenge

commit 6912d3ff8f82d869240daa05db89655d271ded7c
Author: zarigani <zariganigamiteita@gmail.com>
Date:   Fri Sep 12 15:26:11 2008 +0900

    challenge commit 2

commit cd12c7fff1e9320042612409a1833c38de79d0b9
Author: zarigani <zariganigamiteita@gmail.com>
Date:   Fri Sep 12 15:25:42 2008 +0900

    challenge commit 1

commit b48a40ed3c38dfce1e4c33f733c85733cedac43d
Author: bob <bob@example.com>
Date:   Fri Sep 12 15:21:31 2008 +0900

    class downcase

commit 16150ca79dc873b972edb6570bf750a99ddf80fe
Author: bob <bob@example.com>
Date:   Wed Sep 10 16:36:59 2008 +0900

    changed to quotation marks

  • masterブランチでgit pull shared masterを実行する手順も考えられる。
  • 最終的な結果は同じ。ただし、マージの際に生成されるメッセージが若干異なる。
イメージ図
過去 <----------------------> 未来


...1--2--3--6         <--- 共有gitリポジトリのmasterブランチ(6はボブの修正Class -> class)


           4--5--M7   <--- アリスのchallengeブランチ
          /     / \
...1--2--3-----6---M8 <--- アリスのmasterブランチ


 6: アリスのmasterブランチでgit pull shared master(コミットが追加されただけなのでマージは発生しない)
M7: アリスのchallengeブランチでgit merge master
M8: アリスのmasterブランチでgit merge challenge

数字は、コミットを表す
数字の先頭にMが付く場合はマージを表す
数字の順にコミットまたはマージされている
alice/project$ git log
commit 7c03a98a2225ff55a00341b9a146263bc2a8131b
Merge: 4aac071... d0688af...
Author: zarigani <zariganigamiteita@gmail.com>
Date:   Fri Sep 12 21:21:04 2008 +0900

    Merge branch 'master' of /Users/Shared/project into challenge

commit d0688af70c18996a57de04055c1011162f070427
Author: bob <bob@example.com>
Date:   Fri Sep 12 21:07:13 2008 +0900

    class downcase

commit 4aac071139e4407b1ae731408ffe61138dfc9aaa
Author: zarigani <zariganigamiteita@gmail.com>
Date:   Fri Sep 12 20:53:07 2008 +0900

    challenge commit 2

commit 872622077a0bb0af50462044a0143207e38c56c7
Author: zarigani <zariganigamiteita@gmail.com>
Date:   Fri Sep 12 20:52:23 2008 +0900

    challenge commit 1

commit 16150ca79dc873b972edb6570bf750a99ddf80fe
Author: bob <bob@example.com>
Date:   Wed Sep 10 16:36:59 2008 +0900

    changed to quotation marks

推奨されるのは、どちらの手順なのだろう?

もう一度、日本語訳されたマニュアルを読み直す

全4回、実際にアリスとボブになりきって、gitを操作して、それをひたすら記録してみた。

その後,以下のマニュアルを読むと、以前と違って知りたいことが頭にどんどん入ってくる!実際に操作してみることで、ある程度gitの理解が深まったのが良いのかもしれない。自分は身体を使わないと理解できないタイプなのかもしれない...。読んでるだけでは眠くなってしまって。

2008-09-10

アリスとボブのサーバー、git pushをちゃんと理解したい!

f:id:zariganitosh:20120919113737p:image:h128:leftf:id:zariganitosh:20120920094630p:image:h128:left


アリスとボブのGitシリーズが本になりました!

  アリスとボブのGit入門レッスン



上記の日記から続く、アリスとボブの記録。

前提条件

  • アリスとボブは同じマシンにログインする異なるユーザー。
  • ファイルシステムからアクセスする分には、サーバーの設定は不要になるので、これで話がシンプルになる。

共通gitリポジトリの準備

最近、アリスにはちょっとした悩みがあった。

  • 現在、このプロジェクトはアリスとボブの二人で、修正したら連絡を取り合って、お互いの変更をダウンロードする(git pullする)ことで同期をとっていた。
  • しかし、プロジェクトメンバーが増えた場合、このやり方では同期する手間が煩雑になってしまう...。
  • 理想は、サーバーとなるgitリポジトリを決めて、作業前にそこからダウンロード、修正したらそこにアップロードを繰り返す方式にしたい。

アリスがボブに相談すると、ボブはアリスのマシンで操作して、あっという間に実現してくれた。

alice/project$ git clone --bare /Users/alice/project /Users/Shared/project.git
Initialized empty Git repository in /Users/Shared/project.git/
  • 上記コマンドで作成されたフォルダのアクセス権を、everyone読み/書きOKにして、内包している項目すべてに適用した。(Finderから操作した。)
  • git cloneはgit管理のフォルダをコピーするコマンドなのだが、--bareオプション*1を指定することで...
    • /Users/alice/project/.gitフォルダを/Users/Shared/project.gitフォルダへコピーしてくれる。(gitが管理するスナップショットと設定だけがコピーされる。完全に同じではなく、サーバーとして必要な設定になるようだ)
    • gitにとって重要なのは、変化の履歴をスナップショットとして保存したデータとその設定、つまり.gitフォルダであり、
    • ユーザーが操作するテキストファイルは、作業結果を記録する一時的なファイルでしかない。(...と思う。)

これで、/Users/Shared/project.gitという共通のgitリポジトリが出来た。

  • アリスとボブは今後、作業の前後に/Users/Shared/project.gitと同期することによって、お互いの作業を確認し合うことになる。*2
  • 具体的には以下の操作。(今はコピーした直後なので、同期された状態だ。)
alice/project$ git pull /Users/Shared/project.git master
From /Users/Shared/project
 * branch            master     -> FETCH_HEAD
Already up-to-date.
alice/project$ git push /Users/Shared/project.git master
Everything up-to-date
  • git pushによって、/Users/Shared/project.gitのmasterブランチへアップロードしている。

ボブの作業

  • ボブは前回修正したaboutメソッドにバグを見つけた。早速、新しい運用方法でgitを操作してみる。
  • まず最初にボブは、/Users/Shared/project.gitにsharedという略称を設定した。(この方が簡潔に入力できる)
  • ボブはその後、共通リポジトリsharedのmasterブランチからプル。
bob/myrepo$ git remote add shared /Users/Shared/project.git
bob/myrepo$ git pull shared master
From /Users/Shared/project
 * branch            master     -> FETCH_HEAD
Already up-to-date.
  • 式展開を含む文字列をシングルクォートで囲っていたので、ダブルクォートに変更した。
# About git
Class WhatIsGit
  def show
    puts 'Git is easy, if you understand the basis.'
  end
  
  def about(lang = 'en')
    puts "http://#{lang}.wikipedia.org/wiki/Git"
  end
end
  • ボブはその後、コミット
bob/myrepo$ git commit -a -m "changed to double quotation marks"
Created commit f85e8dc: about URL changed3
 1 files changed, 1 insertions(+), 1 deletions(-)
  • ボブはその後、プッシュ
bob/myrepo$ git push shared master
Counting objects: 5, done.
Compressing objects: 100% (2/2), done.
Writing objects: 100% (3/3), 291 bytes, done.
Total 3 (delta 1), reused 0 (delta 0)
Unpacking objects: 100% (3/3), done.
To /Users/Shared/project.git
   52cfe0d..16150ca  master -> master

無事、共通gitリポジトリに反映されたようだ。

アリスの作業

  • ところで、アリスが作業する前にプルすると...
alice/project$ git pull /Users/Shared/project.git master
remote: Counting objects: 5, done.
remote: Compressing objects: 100% (2/2), done.
remote: Total 3 (delta 1), reused 0 (delta 0)
Unpacking objects: 100% (3/3), done.
From /Users/Shared/project
 * branch            master     -> FETCH_HEAD
Updating 52cfe0d..16150ca
Fast forward
 what_is_git.rb |    2 +-
 1 files changed, 1 insertions(+), 1 deletions(-)
  • 何らかの変更がダウンロードされたようだ。アリスは履歴を確認してみた。
alice/project$ git log
commit 16150ca79dc873b972edb6570bf750a99ddf80fe
Author: bob <bob@example.com>
Date:   Wed Sep 10 16:36:59 2008 +0900

    changed to double quotation marks

commit 52cfe0de979425dbf1dcef3d081e06ea40a68965
Merge: 616c5cb... fb7c82f...
Author: alice <alice@ example.com>
Date:   Mon Sep 8 17:44:48 2008 +0900

    Merge /Users/bob/myrepo
    
    Conflicts:
        what_is_git.rb

commit fb7c82fda542762554792fc9b198428a3e6ae5b3
Author: bob <bob@example.com>
Date:   Mon Sep 8 16:11:45 2008 +0900

    git is easy!

...(中略)...
  • アリスはさらに詳細を確認すると...
$ git show
 commit 16150ca79dc873b972edb6570bf750a99ddf80fe
 Author: bob <bob@example.com>
 Date:   Wed Sep 10 16:36:59 2008 +0900

     changed to double quotation marks

 diff --git a/what_is_git.rb b/what_is_git.rb
 index 103a58e..ac3f723 100644
 --- a/what_is_git.rb
 +++ b/what_is_git.rb
 @@ -5,6 +5,6 @@ Class WhatIsGit
    end
   
    def about(lang = 'en')
 -    puts 'http://#{lang}.wikipedia.org/wiki/Git'
 +    puts "http://#{lang}.wikipedia.org/wiki/Git"
    end
  end

アリスは「ボブもたまには間違うのね!」と呟いた。

所感

  • 当初、git pushはgit pullとはデータの流れが反対の動きをするだけで、あとは同じと考えていた。
  • しかし、実際に使ってみると、それ以外に大きな違いがあることに気付く。
    • git pullは履歴データを受け取る人が、自分の好きなタイミングで実行することが出来る。
    • git pushでは、データを受け取る人は、外部からいつのタイミングで送信されてくるのか予想できない。
  • そのため、git pullは自分の作業ディレクトリ*3でそのまま実行し、同期された内容に作業ファイルの状態も即更新されるが、
  • git pushは、git push専用の.gitフォルダを用意して、その専用フォルダに対してのみgit pushする仕様となっているようだ。
    • もし、ファイルを修正中に外部から次々プッシュされ最終のコミットが変化てしまったら、そのユーザーは混乱してしまうことになるだろう。
    • /Users/alice/projectフォルダに対してgit psuhすることも可能なようだが、運用としてgit pushしないようにするのが一般的なようだ。
    • フォルダにアクセス権を設定しておけば、勘違いのgit pushも防止できるはず。

*1:bare=裸の、むき出しの、という意味らしい

*2:もちろん、今までのgit pullでも同期できるが、/Users/Shared/project.gitが最新の状態になっていないと、せっかくボブが設定した意味がない。

*3:.gitフォルダ以外の、実際に編集するファイルを含むフォルダ

2008-09-08

アリスとボブのコラボレーション、gitをちゃんと理解したい!

f:id:zariganitosh:20120919113737p:image:h128:leftf:id:zariganitosh:20120920094630p:image:h128:left


アリスとボブのGitシリーズが本になりました!

  アリスとボブのGit入門レッスン



前回からの続き。ひたすらアリスとボブの操作の記録。

ボブがアリスのプロジェクトを手伝う

  • アリスは自分のプロジェクトが全く進んでいないことに気付いて、愕然とした...。そこで同僚のボブにも手伝ってもらうことにした。
  • 「ボブ、お願い!」アリスはボブに頼んでおきながら、今までの作業に相当疲れたので、すぐに休憩に出てしまった。
  • アリスとボブは同じマシン上にホームディレクトリを持っている。ボブは早速以下の操作をした。(ボブは優しい。)
  • まずはgitに自分の名前とメールアドレスを設定
bob$ git config --global user.name "bob"
bob$ git config --global user.email bob@example.com
  • アリスに教えてもらったパスを指定して、アリスのプロジェクトをコピーした。
bob$ git clone /Users/alice/project myrepo
Initialized empty Git repository in /Users/Guest/myrepo/.git/
bob$ cd myrepo
bob/myrepo$ git log
commit 9e0c7cdc0de938ede09432849a952e8b1e4c6f9d
Author: alice <alice@ example.com>
Date:   Mon Sep 8 11:20:59 2008 +0900

    Revert "show message changed"
    
    This reverts commit ea192b7aa11fc5ebae988d1f063705a629d8acff.

commit ea192b7aa11fc5ebae988d1f063705a629d8acff
Author: alice <alice@ example.com>
Date:   Fri Sep 5 18:19:24 2008 +0900

    show message changed

commit cb83d4fae763b66f647f217cb3e0373ea065fe94
Author: alice <alice@ example.com>
Date:   Fri Sep 5 17:37:22 2008 +0900

    first commit
  • この作業でボブは、アリスのプロジェクトをボブのホーム直下のmyrepoフォルダにコピーしたことになる。
  • /Users/alice/projectと/Users/bob/myrepoは、どちらも同等なgit管理されたフォルダになる。
    • git logで確認すると、今までアリスが行った修正もちゃんと残っている。
    • git config -lで確認すると、その設定に若干の違いはある。

  • ボブは早速what_is_git.rbを以下のように修正した。(aboutメソッドが出力するURLをウィキペディア英語版にした。)
# About git
Class WhatIsGit
  def show
    puts 'Git is difficult...'
  end
  
  def about
    puts 'http://en.wikipedia.org/wiki/Git'
  end
end
  • 変更を確認し、ファイルに保存後、ボブはコミットした。
bob/myrepo$ git commit -a

  1 about URL changed
  2
  3 # Please enter the commit message for your changes. Lines starting
  4 # with '#' will be ignored, and an empty message aborts the commit.
  5 # On branch master
  6 # Changes to be committed:
  7 #   (use "git reset HEAD <file>..." to unstage)
  8 #
  9 #       modified:   what_is_git.rb
 10 #
~
~
".git/COMMIT_EDITMSG" 10 lines, 281 characters written
Created commit 941e293: about URL changed
 1 files changed, 1 insertions(+), 1 deletions(-)
  • ボブはコミットの履歴を確認した。
bob/myrepo$ git log
commit 941e2939cb5645f5f352d414b9bdb86108c006af
Author: bob <bob@example.com>
Date:   Mon Sep 8 12:15:19 2008 +0900

    about URL changed

commit 9e0c7cdc0de938ede09432849a952e8b1e4c6f9d
Author: alice <alice@example.com>
Date:   Mon Sep 8 11:20:59 2008 +0900

    Revert "show message changed"
    
    This reverts commit ea192b7aa11fc5ebae988d1f063705a629d8acff.

commit ea192b7aa11fc5ebae988d1f063705a629d8acff
Author: alice <alice@example.com>
Date:   Fri Sep 5 18:19:24 2008 +0900

    show message changed

commit cb83d4fae763b66f647f217cb3e0373ea065fe94
Author: alice <alice@example.com>
Date:   Fri Sep 5 17:37:22 2008 +0900

    first commit
bebe-MacBook:myrepo Guest$ 

アリスがボブの変更をプロジェクトに取り込む

  • アリスが休憩から戻ってくると、ボブから例のプロジェクトを修正したと伝えられた。相変わらずボブは仕事が早い。頼りになる。
  • 早速アリスはボブに教えてもらったパスを指定して、プロジェクトの変更を取り込んだ。
alice/project$ git pull /Users/Guest/myrepo master
remote: Counting objects: 5, done.
remote: Compressing objects: 100% (2/2), done.
remote: Total 3 (delta 1), reused 0 (delta 0)
Unpacking objects: 100% (3/3), done.
From /Users/bob/myrepo
 * branch            master     -> FETCH_HEAD
Updating 9e0c7cd..941e293
Fast forward
 what_is_git.rb |    2 +-
 1 files changed, 1 insertions(+), 1 deletions(-)
  • この操作でボブの変更は、アリスのプロジェクトに取り込まれたようだ。

  • アリスはコミットの履歴を確認してみた。ボブがabout URL changedをコミットしてくれたようだ。
alice/project$ git log
commit 941e2939cb5645f5f352d414b9bdb86108c006af
Author: bob <bob@example.com>
Date:   Mon Sep 8 12:15:19 2008 +0900

    about URL changed

commit 9e0c7cdc0de938ede09432849a952e8b1e4c6f9d
Author: alice <alice@example.com>
Date:   Mon Sep 8 11:20:59 2008 +0900

    Revert "show message changed"
    
    This reverts commit ea192b7aa11fc5ebae988d1f063705a629d8acff.

commit ea192b7aa11fc5ebae988d1f063705a629d8acff
Author: alice <alice@example.com>
Date:   Fri Sep 5 18:19:24 2008 +0900

    show message changed

commit cb83d4fae763b66f647f217cb3e0373ea065fe94
Author: alice <alice@example.com>
Date:   Fri Sep 5 17:37:22 2008 +0900

    first commit
  • アリスはさらに変更点を確認してみた。「なるほど、aboutの出力をウィキペディア英語版のURLにしてくれたのね。さすがボブ。」
alice/project$ git diff 9e0c7cdc0de938e..941e2939cb5645f5
diff --git a/what_is_git.rb b/what_is_git.rb
index b126408..12f415a 100644
 --- a/what_is_git.rb
 +++ b/what_is_git.rb
 @@ -5,6 +5,6 @@ Class WhatIsGit
    end
   
    def about
 -    puts 'http://ja.wikipedia.org/wiki/Git’
 +    puts 'http://en.wikipedia.org/wiki/Git’
    end
  end

  • たまたま、アリスの後ろを通りかかったボブは、コミットの詳細を見るならもっと便利なコマンドがあることを教えてくれた。
alice/project$ git show 941e2939cb5645f5
# または...
alice/project$ git show HEAD
 commit 941e2939cb5645f5f352d414b9bdb86108c006af
 Author: bob <bob@example.com>
 Date:   Mon Sep 8 12:15:19 2008 +0900

     about URL changed

 diff --git a/what_is_git.rb b/what_is_git.rb
 index b126408..12f415a 100644
 --- a/what_is_git.rb
 +++ b/what_is_git.rb
 @@ -5,6 +5,6 @@ Class WhatIsGit
    end
   
    def about
 -    puts 'http://ja.wikipedia.org/wiki/Git’
 +    puts 'http://en.wikipedia.org/wiki/Git’
    end
  end
  • git show HEADで最新のコミットの詳細を表示してくれる。(diffの部分は直前のコミットとの差分)
  • ちなみに、以下のような便利な引数指定ができる。
alice/project$ git show HEAD^   # HEAD の1つ前を表示
alice/project$ git show HEAD^^  # HEAD の2つ前を表示
alice/project$ git show HEAD^^^ # HEAD の3つ前を表示
...
alice/project$ git show HEAD~1  # HEAD の1つ前を表示
alice/project$ git show HEAD~2  # HEAD の2つ前を表示
alice/project$ git show HEAD~3  # HEAD の3つ前を表示
...

アリスとボブが同時に作業する

アリスの修正
  • ボブの素早い仕事を見て、アリスも俄然やる気になった。早速、前回の無駄な打ち消しを修正した。
# About git
Class WhatIsGit
  def show
    puts 'Git is easy, if you understand the basis'
  end
  
  def about
    puts 'http://en.wikipedia.org/wiki/Git’
  end
end
  • 変更を確認し、ファイルに保存後、アリスはコミットした。
alice/project$ git commit -a

  1 show message changed2
  2 
  3 # Please enter the commit message for your changes. Lines starting
  4 # with '#' will be ignored, and an empty message aborts the commit.
  5 # On branch master
  6 # Changes to be committed:
  7 #   (use "git reset HEAD <file>..." to unstage)
  8 #       
  9 #       modified:   what_is_git.rb
 10 #
~
~
".git/COMMIT_EDITMSG" 10L, 285C written
Created commit 4117bcb: show message changed2
 1 files changed, 1 insertions(+), 1 deletions(-)
  • アリスはコミットの履歴を確認した。
alice/project$ git log
commit 3ce0713a5cf938397f6fe4695b11857d43b63314
Author: alice <alice@example.com>
Date:   Mon Sep 8 14:50:53 2008 +0900

    show message changed2

commit 941e2939cb5645f5f352d414b9bdb86108c006af
Author: bob <bob@example.com>
Date:   Mon Sep 8 12:15:19 2008 +0900

    about URL changed

commit 9e0c7cdc0de938ede09432849a952e8b1e4c6f9d
Author: alice <alice@example.com>
Date:   Mon Sep 8 11:20:59 2008 +0900

    Revert "show message changed"
    
    This reverts commit ea192b7aa11fc5ebae988d1f063705a629d8acff.

commit ea192b7aa11fc5ebae988d1f063705a629d8acff
Author: alice <alice@example.com>
Date:   Fri Sep 5 18:19:24 2008 +0900

    show message changed

commit cb83d4fae763b66f647f217cb3e0373ea065fe94
Author: alice <alice@example.com>
Date:   Fri Sep 5 17:37:22 2008 +0900

    first commit
gitが自動的に取り込んでくれる場合
  • アリスは無事コミットが完了してホッとしたのも束の間、ボブから「さらに修正が入ったのでプロジェクトに取り込んで欲しい」と伝えられた。
  • アリスはすぐにgit pullしようと考えたが、それに不安を覚えた。
    • ボブは今、私(アリス)が行った修正を知っているのだろうか?(たぶん知らない)取り込む前にボブの修正を確認したい。
  • このような状況で、アリスは以下の手順でボブの変更をプロジェクトに取り込む前に確認することができる。
  • コマンドを簡潔に表現するために、アリスはボブの作業ディレクトリにエイリアス名を付けた。(bob = /Users/bob/myrepo)
alice/project$ git remote add bob /Users/bob/myrepo
  • git fetchによって、ボブの変更を、アリスのプロジェクトのキャッシュ(一時的な保存領域)に取り込む。
alice/project$ git fetch bob
remote: Counting objects: 5, done.
remote: Compressing objects: 100% (2/2), done.
remote: Total 3 (delta 0), remote: reused 0 (delta 0)
Unpacking objects: 100% (3/3), done.
From /Users/Guest/myrepo
 * [new branch]      master     -> bob/master
  • その後アリスは、git logを-pオプション付きで実行して、ボブの変更箇所を確認できた。
    • -pオプションを指定することで、コミットの履歴を変更箇所(直前のコミットとのdiff)も含めて確認できる。
alice/project$ git log -p master..bob/master
 commit 12508a3561d41f3393b93c598026ad5057735f97
 Author: bob <bob@example.com>
 Date:   Mon Sep 8 14:55:35 2008 +0900

     about URL changed2

 diff --git a/what_is_git.rb b/what_is_git.rb
 index 12f415a..f5760c9 100644
 --- a/what_is_git.rb
 +++ b/what_is_git.rb
 @@ -4,7 +4,7 @@ Class WhatIsGit
      puts 'Git is difficult...'
    end
   
 -  def about
 -    puts 'http://en.wikipedia.org/wiki/Git’
 +  def about(lang = 'en')
 +    puts 'http://#{lang}.wikipedia.org/wiki/Git'
    end
  end
  • ボブはaboutメソッドに引数を設定して、多言語対応にしてくれていたのだった。
  • しかも、引数が無い場合はウィキペディア英語版のURLが出力されるデフォルト。素晴らしい、ボブ!
  • アリスは安心して、ボブの修正を取り込んだ。
alice/project$ git merge bob/master
Auto-merged what_is_git.rb
Merge made by recursive.
 what_is_git.rb |    4 ++--
 1 files changed, 2 insertions(+), 2 deletions(-)
  • アリスの修正とボブの修正は、別々の箇所でコードが対立しないので、そのような場合gitは自動的に取り込んでくれる。
  • これでアリスのプロジェクトに、ボブの修正は反映された。
二つの意見が対立する場合
  • アリスがgitに四苦八苦しているうちに、ボブは更なる修正をしていた。
  • アリスは再び修正を取り込むようにボブから言われた。
  • アリスは今回エイリアス名は使わずに、ボブの作業ディレクトリのパスを指定して、git fetchしてみた。
    • ボブの変更は、アリスのプロジェクトのFETCH_HEADというキャッシュに取り込まれたようだ。
alice/project$ git fetch /Users/bob/myrepo
remote: Counting objects: 5, done.
remote: Compressing objects: 100% (2/2), done.
remote: Total 3 (delta 1), reused 0 (delta 0)
Unpacking objects: 100% (3/3), done.
From /Users/Guest/myrepo
 * branch            HEAD       -> FETCH_HEAD
  • git diffで、今のアリスとボブの変更箇所を確認
alice/project$ git diff HEAD..FETCH_HEAD
 diff --git a/what_is_git.rb b/what_is_git.rb
 index 6c645b8..652973c 100644
 --- a/what_is_git.rb
 +++ b/what_is_git.rb
 @@ -1,7 +1,7 @@
  # About git
  Class WhatIsGit
    def show
 -    puts 'Git is easy, if you understand the basis'
 +    puts 'Git is easy!'
    end
   
    def about(lang = 'en')

  • git mergeで取り込んでみると...コンフリクトしていて自動的にマージできないと警告された。なぜだろう?
    1. ボブは'Git is difficult...'時点のアリスのプロジェクトをコピーしている。
    2. その後、アリスは'Git is easy, if you understand the basis'に修正した。
    3. 上記を知らずに、ボブも'Git is easy!'に修正した。
  • よって、アリス'Git is easy, if you understand the basis' vs ボブ'Git is easy!'という、二つの意見が対立していることになる。
alice/project$ git merge FETCH_HEAD
Auto-merged what_is_git.rb
CONFLICT (content): Merge conflict in what_is_git.rb
Automatic merge failed; fix conflicts and then commit the result.

  • この時点でgit diffを実行してみると...以下のように表示された。
alice/project$ git diff
 diff --cc what_is_git.rb
 index 6c645b8,652973c..0000000
 --- a/what_is_git.rb
 +++ b/what_is_git.rb
 @@@ -1,7 -1,7 +1,11 @@@
   # About git
   Class WhatIsGit
     def show
 ++<<<<<<< HEAD:what_is_git.rb
  +    puts 'Git is easy, if you understand the basis'
 ++=======
 +     puts 'Git is easy!'
 ++>>>>>>> FETCH_HEAD:what_is_git.rb
     end
    
     def about(lang = 'en')
  • アリスが、ファイルwhat_is_git.rbを確認してみると...何だか変なコードになっている。大変!
# About git
Class WhatIsGit
  def show
<<<<<<< HEAD:what_is_git.rb
    puts 'Git is easy, if you understand the basis'
=======
    puts 'Git is easy!'
>>>>>>> FETCH_HEAD:what_is_git.rb
  end
  
  def about(lang = 'en')
    puts 'http://#{lang}.wikipedia.org/wiki/Git'
  end
end
  • この状態を解消するためには、自分で「<<<<<<< HEAD:what_is_git.rb」と「>>>>>>> FETCH_HEAD:what_is_git.rb」ブロック内のコードを修正する必要がある。
    • 「=======」を挟んで上下が対立しているコードらしい。
    • どちらかを残しても良いし、全く別のコードに変更してしまっても良いようだ。

  • アリスはボブに状況を説明して、今回は自分のコードを優先させることで了解してもらった。
    • ただし、ピリオド「.」が抜けていることに気付いたので、最後に追加しておいた。
# About git
Class WhatIsGit
  def show
    puts 'Git is easy, if you understand the basis.'
  end
  
  def about(lang = 'en')
    puts 'http://#{lang}.wikipedia.org/wiki/Git'
  end
end
  • 修正したらgit addする。
    • git add .より、地道にgit add ファイル名を指定した方が良いかもしれない。(修正漏れ防止のため)
    • git addすることで、対立しているコードの差分は修正されたものと見なされ、git diffから除外されるようだ。
alice/project$ git add what_is_git.rb
  • その後、git commitを実行すると、コミットのメッセージにはコンフリクトの情報も最初から含まれて表示された。
alice/project$ git commit

  1 Merge /Users/bob/myrepo
  2 
  3 Conflicts:
  4         what_is_git.rb
  5 #
  6 # It looks like you may be committing a MERGE.
  7 # If this is not correct, please remove the file
  8 #       .git/MERGE_HEAD
  9 # and try again.
 10 #
 11 
 12 # Please enter the commit message for your changes. Lines starting
 13 # with '#' will be ignored, and an empty message aborts the commit.
 14 # On branch master
 15 # Changes to be committed:
 16 #   (use "git reset HEAD <file>..." to unstage)
 17 #
 18 #       modified:   what_is_git.rb
 19 #
~
~
".git/COMMIT_EDITMSG" 19L, 450C written
Created commit 52cfe0d: Merge /Users/bob/myrepo
  • このまま保存して、コミットは完了した。

  • アリスがコミットの履歴を確認すると、以下のように表示された。
    • ボブのコミットgit is easy!と、アリスのコミットMerge /Users/bob/myrepoの二つの履歴が追加されていた。
alice/project$ git log
commit 52cfe0de979425dbf1dcef3d081e06ea40a68965
Merge: 616c5cb... fb7c82f...
Author: alice <alice@example.com>
Date:   Mon Sep 8 17:44:48 2008 +0900

    Merge /Users/bob/myrepo
    
    Conflicts:
        what_is_git.rb

commit fb7c82fda542762554792fc9b198428a3e6ae5b3
Author: bob <bob@example.com>
Date:   Mon Sep 8 16:11:45 2008 +0900

    git is easy!

commit 616c5cbd40587a31cd20dd8ed9b30d282a77a970
Merge: 3ce0713... 12508a3...
Author: alice <alice@example.com>
Date:   Mon Sep 8 15:10:43 2008 +0900

    Merge commit 'bob/master'

commit 12508a3561d41f3393b93c598026ad5057735f97
Author: bob <bob@example.com>
Date:   Mon Sep 8 14:55:35 2008 +0900

    about URL changed2

commit 3ce0713a5cf938397f6fe4695b11857d43b63314
Author: alice <alice@example.com>
Date:   Mon Sep 8 14:50:53 2008 +0900

    show message changed2

commit 941e2939cb5645f5f352d414b9bdb86108c006af
Author: bob <bob@example.com>
Date:   Mon Sep 8 12:15:19 2008 +0900

    about URL changed

...(中略)...
  • 直前のコミットをgit show HEADを確認してみる
    • ボブのコミットと、アリスのコミットをマージした時の様子が読み取れる。
alice/project$ git show HEAD
 commit 52cfe0de979425dbf1dcef3d081e06ea40a68965
 Merge: 616c5cb... fb7c82f...
 Author: alice <alice@example.com>
 Date:   Mon Sep 8 17:44:48 2008 +0900

     Merge /Users/bob/myrepo
    
     Conflicts:
         what_is_git.rb

 diff --cc what_is_git.rb
 index 6c645b8,652973c..103a58e
 --- a/what_is_git.rb
 +++ b/what_is_git.rb
 @@@ -1,7 -1,7 +1,7 @@@
   # About git
   Class WhatIsGit
     def show
 -     puts 'Git is easy, if you understand the basis'
  -    puts 'Git is easy!'
 ++    puts 'Git is easy, if you understand the basis.'
     end
    
     def about(lang = 'en')
  • さらに一つ前のコミットを確認してみると、コミットMerge commit 'bob/master'が表示された。
    • このコミットはgit logで表示される順番では、最新の3番目だ。
    • このことから、マージされたコミットMerge /Users/bob/myrepoとgit is easy!は、同じタイミングの一回のコミットとして扱われているようだ。(履歴の表示は分かれているが)
alice/project$ git show HEAD^
 commit 616c5cbd40587a31cd20dd8ed9b30d282a77a970
 Merge: 3ce0713... 12508a3...
 Author: alice <alice@example.com>
 Date:   Mon Sep 8 15:10:43 2008 +0900

     Merge commit 'bob/master'

 diff --cc what_is_git.rb
 index 453239c,f5760c9..6c645b8
 --- a/what_is_git.rb
 +++ b/what_is_git.rb
 @@@ -1,10 -1,10 +1,10 @@@
   # About git
   Class WhatIsGit
     def show
  -    puts 'Git is difficult...'
  +    puts 'Git is easy, if you understand the basis'
     end
    
 -   def about
 -     puts 'http://en.wikipedia.org/wiki/Git’
 +   def about(lang = 'en')
 +     puts 'http://#{lang}.wikipedia.org/wiki/Git'
     end

アリスの最新の状態をボブに取り込んでもらう

  • 最後にアリスは、プロジェクトの最新状態をボブにも更新しておいて欲しいと伝えた。
  • ボブは最小の手順で素早く更新した。
bob/myrepo$ git pull
remote: Counting objects: 15, done.
remote: Compressing objects: 100% (6/6), done.
remote: Total 9 (delta 3), reused 0 (delta 0)
Unpacking objects: 100% (9/9), done.
From /Users/bebe/alice/project/
   9e0c7cd..52cfe0d  master     -> origin/master
Updating fb7c82f..52cfe0d
Fast forward
 what_is_git.rb |    2 +-
 1 files changed, 1 insertions(+), 1 deletions(-)

  • 何故、ボブはgit pullのみでOKなのか?
    • ボブはアリスのgitをコピーしており、gitはその時アリスのパスを記憶していたのだ。賢い!
  • git config -lを実行すると以下のように表示された。
bob/myrepo$ git config -l
user.name=bob
user.email=bob@example.com
core.repositoryformatversion=0
core.filemode=true
core.bare=false
core.logallrefupdates=true
core.ignorecase=true
remote.origin.url=/Users/alice/project/.git
remote.origin.fetch=+refs/heads/*:refs/remotes/origin/*
branch.master.remote=origin
branch.master.merge=refs/heads/master
  • remote.origin.url=/Users/alice/project/.gitがアリスのプロジェクトのパスなのかもしれない。

  • git pull完了後、ボブのファイルwhat_is_git.rbも以下の状態に更新された。
# About git
Class WhatIsGit
  def show
    puts 'Git is easy, if you understand the basis.'
  end
  
  def about(lang = 'en')
    puts 'http://#{lang}.wikipedia.org/wiki/Git'
  end
end

所感

合っているかどうか不安だが、アリスとボブの操作を実際にやってみて、今はgitの仕組みを以下のように考えている。(git pushやブランチについてはまた後日。また理解が変わるかもしれない。)

  • git add .とgit commitで自分のホームディレクトリに履歴を保存する。
  • gitリポジトリは、git cloneで任意の場所にコピーすることが出来る。
  • gitリポジトリ同士を同期させるためには、git pull、git fetch、git mergeを利用する。
    • git fetchで外部のgitリポジトリをキャッシュに取り込む。
    • git mergeで合算されてコードが修正される。
      • 変更箇所が重複していなければ、自動的に取り込まれる。
      • コードが対立していれば、対立の状況がファイルに保存される。
    • git pullはgit fetch + git merge。(git commit -aのような感覚だ)
  • git remote add エイリアス名 リポジトリのパスで、外部リポジトリにエイリアス名を設定することが出来る。

Time Machineのスナップショットの集合がコピーされ、変更されたスナップショットの集合を再びキャッシュに取り込み、それを混ぜ込む様子を想像している。

2008-09-05

アリスとボブになりきってgitをちゃんと理解したい!

f:id:zariganitosh:20120919113737p:image:h128:leftf:id:zariganitosh:20120920094630p:image:h128:left


ここから始まるアリスとボブのGitシリーズが本になりました!

  アリスとボブのGit入門レッスン



gitの解説には素晴らしいページがある。

こんなに親切に説明されているのに、読んでいるだけではgitの仕組みが見えてこない...。(そうです。自分の理解が悪いのです。)ちゃんと理解したいので、チュートリアルに出てくるアリスとボブになりきって、実際に作業してみることにする。以下は淡々としたその作業記録と自分の理解のイメージ。

環境

  • MacBook OSX 10.5.4
  • Xcode3.0以上インストール済

インストール

gitのページから最新版をダンロードしてみた。現在のバージョンは1.6.0.1のようだ。

cd ~/Downloads
curl http://kernel.org/pub/software/scm/git/git-1.6.0.1.tar.gz > git-1.6.0.1.tar.gz
tar zxvf git-1.6.0.1.tar.gz
cd git-1.6.0.1
./configure
make
sudo make install

名前と連絡先

  • まず最初に、自分がどこの誰だかを設定しておく。
  • この設定はcommitする時に履歴として記録される。
    • 名前は、アリス
    • 連絡先は、alice@example.com
alice$ git config --global user.name "alice"
alice$ git config --global user.email alice@example.com

プロジェクトの開始

  • アリスは、とあるプロジェクトを開始した。
alice$ cd
alice$ mkdir project
alice$ cd project
alice/project$ git init
Initialized empty Git repository in /Users/alice/project/.git/
  • これで、このprojectフォルダ以下はgitで管理されることとなった。

ファイルの追加

  • アリスは、WhatIsGitクラスを作成して保存した。
# About git
Class WhatIsGit
  def show
    puts 'Git is difficult...'
  end
  
  def about
    puts 'http://ja.wikipedia.org/wiki/Git'
  end
end

  • ファイルはwhat_is_git.rbで保存されている。
alice/project$ ls
what_is_git.rb

  • 「git add」で、gitに引数に指定したファイルのスナップショット(その時点の内容?)を取得してもらう。
  • スナップショットのイメージが今イチ理解できない場合...自分はMacOSX 10.5 LeopardのTime Machineに入った時のアニメーションを見て一発で理解できた。
  • 「git add」は次に「git commit」するまで、同じシートにスナップショットを書き込んでいく。
    • git addを繰り返せば、スナップショットに取得された同じファイルは上書きされる。
    • git addした時点のファイルの内容がスナップショットとして取得される。
    • だから一度git addしたファイルも、その後変更されたら、もう一度git addしない限りスナップショットには反映されない。
alice/project$ git add what_is_git.rb
# または
alice/project$ git add .
      • 「git add .」は、gitに現在のディレクトリ以下のすべてのファイルのスナップショットを取得するように依頼する。(「.」重要。)

  • 「git commit」で、その時点のスナップショットのシートにメッセージを添付して、この場合はmasterキャビネットに保存する。
    • viが起動して、そのコミットのメッセージの入力を求められる。
    • 「#」で始まるコメント行はこのコミットの情報を教えてくれているようだ。(「---文---」は自分で追加した日本語訳)
      • メッセージは複数行の入力可能で、「#」で始まるコメント行はメッセージにはならない。
      • 最初はデフォルトではmasterというブランチ(gitが管理するディレクトリ?)に追加される。
      • その下に新規追加、変更、削除されるファイル名が表示されるようだ。
alice/project$ git commit

  1 first commit
  2 # Please enter the commit message for your changes.---変更に関するメッセージを入力してください---
  3 # (Comment lines starting with '#' will not be included)---「#」で始まるコメント行はメッセージには含まれません。---
  4 # On branch master---ここはmasterブランチです。---
  5 #
  6 # Initial commit
  7 #
  8 # Changes to be committed:
  9 #   (use "git rm --cached <file>..." to unstage)
 10 #
 11 #       new file: what_is_git.rb
 12 #
~
~

  • viの1行目にfirst commitと入力して保存するとコミットは完了した。
".git/COMMIT_EDITMSG" 12L, 269C written
Created initial commit 119674b: first commit
 1 files changed, 11 insertions(+), 0 deletions(-)
 create mode 100644 what_is_git.rb

  • ちなみに-mオプションでメッセージを指定してコミットすれば、viは起動せず、即完了する。
alice/project$ git commit -m 'first commit'
  • 分かりきったシンプルな変更ならこっちの方が簡単。
  • でも、いろいろな変更が絡んでいると、何がコミットされるか確認したくもなる。その場合は、-mオプションは無しの方が良さそう。

ファイルの変更

  • アリスは考え直した。本当はgitは簡単なんじゃないかと...。そしてコードを以下のように修正した。
# About git
Class WhatIsGit
  def show
    puts 'Git is easy!'
  end
  
  def about
    puts 'http://ja.wikipedia.org/wiki/Git'
  end
end

  • 変更したらgitにスナップショットを取得してもらい、それをmasterキャビネットに保存する。
  • だから以下のコマンドは、ペアで使うことになる。
alice/project$ git add .
alice/project$ git commit

  • もしgit addしないでgit commitだけでは、以下のように注意される。(スナップショットがないのでキャビネットに保存できない)
alice/project$ git commit
# On branch master
# Changed but not updated:
#   (use "git add <file>..." to update what will be committed)
#
#	modified:   what_is_git.rb
#
no changes added to commit (use "git add" and/or "git commit -a")

  • 注意の最後には、とても素敵なことが書いてある。
  • なんと!-aオプションを付けてコミットすれば、この場合git addは不要になる。
alice/project$ git commit -a
  • しかし、「git commit -a」は「git add .」+「git commit」と同等ではない。
    • 「git commit -a」は現在までに履歴管理しているファイルのみ、変更または削除を検知してコミットしてくれる。
    • 新規追加したファイルは、勝手にコミットしてくれない。注意が必要!
    • コミットを中止した場合、キャビネットに保存する予定のスナップショットも残らない。
      • スナップショットのシートは透明で、過去のスナップショットは、その時点のスナップショットに記録されなくても透けて見える、と理解している。

  • git commit -aを実行したら、viで以下のように表示された。
alice/project$ git commit -a

  1 
  2 # Please enter the commit message for your changes.
  3 # (Comment lines starting with '#' will not be included)
  4 # On branch master
  5 # Changes to be committed:
  6 #   (use "git reset HEAD <file>..." to unstage)
  7 #
  8 #       modified:   what_is_git.rb
  9 #
~
~
  • ここでアリスは不安になる。変更した文字列のスペルが間違ってないかしら?そこで、このコミットを中止することにした。
  • コミットはメッセージが空欄だと中止された。だから、viならescキーを押してから:q!returnキーで終了すればOK。
fatal: no commit message?  aborting commit.
  • これで、このコミットは中止された。

  • その後、アリスはwhat_is_git.rbを開いて、スペルには問題がなかったことを確認した。(ひと安心)
  • しかし、ここでさらに考え直して、以下のように修正した。
# About git
Class WhatIsGit
  def show
    puts 'Git is easy, if you understand the basis'
  end
  
  def about
    puts 'http://ja.wikipedia.org/wiki/Git'
  end
end

  • アリスは修正を繰り返して混乱してきたので、変更箇所を確認したくなった。
  • git add .したスナップショットと、前回コミットのスナップショットを比較してみた。
 alice/project$ git add .
 alice/project$ git diff --cached
 diff --git a/what_is_git.rb b/what_is_git.rb
 index 5864c55..2288d7f 100644
 --- a/what_is_git.rb
 +++ b/what_is_git.rb
 @@ -1,11 +1,10 @@
  # About git
  Class WhatIsGit
    def show
 -    puts 'Git is difficult...'
 +    puts 'Git is easy, if you understand the basis'
    end
    
    def about
      puts 'http://ja.wikipedia.org/wiki/Git’
    end
  end
 -    
 \ No newline at end of file

  • ちなみにgit diffのオプション指定で以下のような違いがあるようだ。
alice/project$ git diff --cached # 前回コミットのスナップショットと、git add .したスナップショットを比較
alice/project$ git diff          # git add .したスナップショットと、現在のファイルディレクトリを比較
alice/project$ git diff HEAD     # 前回コミットのスナップショットと、現在のファイルディレクトリを比較
      • HEADは、コミットした最新のスナップショットを指し示すタグのようだ。

  • さらに、git statusでgit commitした時にviに表示される情報も確認できるようだ。(ファイルの変更がどのような状態かを表示してくれるようだ)
$ git status
# On branch master
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
#	modified:   what_is_git.rb
#
  • これを確認して、アリスは安心してコミットした。
alice/project$ git commit -m 'show message changed'
Created commit d249d65: show message changed
 1 files changed, 1 insertions(+), 2 deletions(-)
 create mode 100644 test.rb

履歴の確認

  • アリスは「git log」で今までのコミットの履歴を一覧できる。
alice/project$ git log
commit d249d65e7d0d626e4b695d594237a7b3d73532fa
Author: alice <alice@ example.com>
Date:   Fri Sep 5 15:25:51 2008 +0900

    show message changed

commit 119674b5ddbac4a220df0851257a457a7623612e
Author: alice <alice@ example.com>
Date:   Fri Sep 5 08:40:25 2008 +0900

    first commit

いろいろな取り消し

上記のshow message changedをコミットする過程で、以下のような失敗をしてしまい修正したいという状況。

コミット前の場合
  • 実験でtest.rbを作ったが、それを削除するのを忘れて「git add .」してしまった。まだ、コミットはしていない。
    • test.rbをスナップショットから取り除きたい。
alice/project$ ls
test.rb        what_is_git.rb
alice/project$ git add .
alice/project$ git status
# On branch master
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
#	new file:   test.rb
#	modified:   what_is_git.rb
#

すべてのファイルをスナップショットから取り除く方法
  • 上記コメント欄にも、その方法が"git reset HEAD <file>..."と書いてある。
alice/project$ git reset HEAD
what_is_git.rb: locally modified
  • その後、不要なファイルを削除して、改めて「git add .」する。
alice/project$ ls
test.rb		what_is_git.rb
alice/project$ rm test.rb
alice/project$ git add .
alice/project$ git status
# On branch master
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
#	modified:   what_is_git.rb
#

test.rbだけ、スナップショットから取り除く方法
alice/project$ git reset HEAD :test.rb
  • その後、不要なファイルを削除する。
alice/project$ ls
test.rb		what_is_git.rb
alice/project$ rm test.rb
alice/project$ git status
# On branch master
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
#	modified:   what_is_git.rb
#
コミットしてしまった直後、まだ公開されていない場合
  • 実験でtest.rbを作ったが、それを削除するのを忘れて「git add .」そして「git commit -m 'test'」まで完了してしまった。
    • test.rbをコミットから取り除きたい。
    • コミットのメッセージも変更したい。
alice/project$ ls
test.rb        what_is_git.rb
alice/project$ git add .
alice/project$ git commit -m 'test'
Created commit fb237cf: test
 1 files changed, 1 insertions(+), 1 deletions(-)
 create mode 100644 test.rb
alice/project $ git log
commit fb237cf09be3461d54be20298c027c8ccd1802b3
Author: alice <alice@ example.com>
Date:   Fri Sep 5 18:19:24 2008 +0900

    test

commit cb83d4fae763b66f647f217cb3e0373ea065fe94
Author: alice <alice@ example.com>
Date:   Fri Sep 5 17:37:22 2008 +0900

    first commit

そのコミットを無かったことにする方法
alice/project$ git reset --hard ORIG_HEAD
alice/project$ git log
commit cb83d4fae763b66f647f217cb3e0373ea065fe94
Author: alice <alice@ example.com>
Date:   Fri Sep 5 17:37:22 2008 +0900

    first commit
  • 「git reset --hard ORIG_HEAD」を実行すると、ファイルディレクトリの状態まで一つ前のコミットの状態に戻ってしまうようだ。
  • 追加したファイルはファイルディレクトリからも削除され、変更したファイルは一つ前のコミットに戻ってしまった。注意が必要!
$ ls
what_is_git.rb
# About git
Class WhatIsGit
  def show
    puts 'Git is difficult...'
  end
  
  def about
    puts 'http://ja.wikipedia.org/wiki/Git'
  end
end

直前のコミットにスナップショットを上書きする方法
  • 一旦git add .してコミットしてしまったファイルは、git rmで取り除く。
alice/project$ git rm test.rb
rm 'test.rb'
alice/project$ ls
what_is_git.rb
alice/project$ git add .
  • コミットする時に--amendオプションを指定して、testと表示されているコミットのメッセージもshow message changedに修正する。
alice/project$ git commit --amend

  1 show message changed
  2 # Please enter the commit message for your changes.
  3 # (Comment lines starting with '#' will not be included)
  4 # On branch master
  5 # Changes to be committed:
  6 #   (use "git reset HEAD^1 <file>..." to unstage)
  7 #   
  8 #       new file:   test.rb
  9 #       modified:   what_is_git.rb
 10 #       
 11 # Changed but not updated:
 12 #   (use "git add/rm <file>..." to update what will be committed)
 13 #   
 14 #       deleted:    test.rb
 15 #       
~
~
".git/COMMIT_EDITMSG" 15L, 400C written
Created commit 8a17866: show message changed
 1 files changed, 1 insertions(+), 1 deletions(-)
 create mode 100644 test.rb
  • 履歴を確認すると、直前のコミットのメッセージtestは、show message changedに変更されたようだ。(新たに履歴が追加されていないので)
alice/project$ git log
commit 920e5c44da8fa75c3ece7e0fed3c1f66d05c6778
Author: alice <alice@ example.com>
Date:   Fri Sep 5 20:21:47 2008 +0900

    show message changed

commit cb83d4fae763b66f647f217cb3e0373ea065fe94
Author: alice <alice@ example.com>
Date:   Fri Sep 5 17:37:22 2008 +0900

    first commit
  • first commitと、show message changedの差分を確認してみる。
  • git logで確認できるcommit コードの最初の数桁をコピーする。(他のコミットと重複しない最低の長さでOKだと思う)
  • 上記コードをドット二つ「..」で繋いで、git diffの引数に指定すると、その間の差分を確認できる。
 alice/project$ git diff cb83d4fae763b6..920e5c44da8fa75c3e
 diff --git a/what_is_git.rb b/what_is_git.rb
 index b126408..2288d7f 100644
 --- a/what_is_git.rb
 +++ b/what_is_git.rb
 @@ -1,7 +1,7 @@
  # About git
  Class WhatIsGit
    def show
 -    puts 'Git is difficult...'
 +    puts 'Git is easy, if you understand the basis'
    end
    
    def about
  • 上記コミットの差分には、test.rbは表示されていない。
  • 内部的にはgit addがgit rmで上書きされ、何も無かったことになっているようだ。
コミットして既に公開されている場合
  • コミット後、誰かがそれを元に作業をしている可能性がある場合は、そのコミット自体を取り消すのではなく、変更を打ち消す新たなコミットを実行するのが望ましいようだ。
  • そうしないと、既に作業している誰かは、元になるコミットが変化してしまい混乱することになる。
  • 現在のアリスの状況は、上記までの作業が完了して、現状は正しいコミット状態なのだが、実験として以下の方法で打ち消してみた。
直前のコミットの変更を打ち消す、新たなコミットを実行する方法
  • 「git revert HEAD」を実行すると、直前のコミットの変更を打ち消すファイルの修正とスナップショットの取り込みを、gitが自動で処理してくれる。
    • コミットのメッセージも、どのコミットの修正であるかを明記してくれている。
    • もちろん、git revertに頼らず、自分で変更を打ち消す修正をしてファイルを保存し、その後git addでも良いはず。
alice/project$ git revert HEAD
Finished one revert.

  1 Revert "show message changed"
  2 
  3 This reverts commit 920e5c44da8fa75c3ece7e0fed3c1f66d05c6778.
  4 
  5 # Please enter the commit message for your changes. Lines starting
  6 # with '#' will be ignored, and an empty message aborts the commit.
  7 # On branch master
  8 # Changes to be committed:
  9 #   (use "git reset HEAD <file>..." to unstage)
 10 #
 11 #       modified:   what_is_git.rb
 12 #
~
~
Created commit 127e51f: Revert "show message changed"
 1 files changed, 1 insertions(+), 1 deletions(-)
  • git revertが完了後、履歴を確認すると、Revert "show message changed"が新たにコミットされている。
alice/project$ git log
commit 127e51f5d9ea2f0c744918bfcce88a0774c98fd2
Author: alice <alice@ example.com>
Date:   Sat Sep 6 06:49:17 2008 +0900

    Revert "show message changed"
    
    This reverts commit 920e5c44da8fa75c3ece7e0fed3c1f66d05c6778.

commit 920e5c44da8fa75c3ece7e0fed3c1f66d05c6778
Author: alice <alice@ example.com>
Date:   Fri Sep 5 20:21:47 2008 +0900

    show message changed

commit cb83d4fae763b66f647f217cb3e0373ea065fe94
Author: alice <alice@ example.com>
Date:   Fri Sep 5 17:37:22 2008 +0900

    first commit
  • what_is_git.rbを確認すると、first commitの状態に戻っている。
# About git
Class WhatIsGit
  def show
    puts 'Git is difficult...'
  end
  
  def about
    puts 'http://ja.wikipedia.org/wiki/Git'
  end
end

本当は、アリスとボブのコミットのやり取りまで試したかったが、本日はここで力尽きたので、また後日...。

  • これじゃ、アリスが登場した意味がない...。
  • 自分の場合、git addとgit commitが、スナップショット(MacBookのTime Machineを想像)をどのように操作するのかイメージすると、gitのいろんなことが理解し易くなる気がした。