読者です 読者をやめる 読者になる 読者になる

なぜ、ああたの仕事は終わらないのか? 中島聡

読書

著者はマイクロソフト本社で働いていたこともある日本人プログラマの中島さん。
中島さんが培ってきた仕事術を紹介している。

・(仕事が終わらない理由は)大きくまとめると、次の3点に集約されます。(p.48)
 ① 安請け合いをしてしまう
 ② ギリギリまでやらない
 ③ 計画の見積もりをしない

・言葉で説明するのが難しいときは形にして見せてしまうのが一番いい(p.123)
・どんな仕事でも、企画をアイデアのままではなく形にした人がその企画の推進者になることができます。
 私のような日本から渡米したばかりのいちエンジニアがマイクロソフトで活躍できたのは、
 まさに限られた時間を濃密に使いこなし、プロトタイプを先に作ったからなのです。(p.134)

・まずは「締切は絶対に守るもの」と考える(p.152)
 ① 「まずはどのくらいかかるかやってみるので、スケジュールの割り出しのために2日ください」と
   答えて仕事に取り掛かる(見積もりをするための調査期間をもらう)。
 ② その2日をロケットスタート期間として使い、2日で「ほぼ完成」まで持っていく
 ③ 万が一、その2日で「ほぼ完成」まで持っていけなかった場合、これを「危機的な状況」と認識して
  スケジュールの見直しを交渉する

・期間が10日だとしたら、最初の2日は「ロケットスタート」の期間。ここで、「ほぼ完成」に持っていく。残り8日は「流し」の期間。(p.165)
ロケットスタート期間のまま全て終わらせないこと。
 ・いつも全力を出していると、真の実力を発揮できなくなる(p.164)

Bundlerとは

Ruby

Bundlerとは

Bundlerとはアプリケーションで利用するgemパッケージを定義し、依存関係を解決するための仕組み。
Bundlerはアプリケーションごとにgemパッケージの依存関係を閉じ込めることで、特定のアプリケーションだけで利用するgemパッケージを簡単に管理することができる。

Gemfile

Bundlerはアプリケーションで利用するgemパッケージをGemfileというファイルに列挙する。Gemfileは「bundle init」コマンドを実行することで生成される。
Gemfileの中身は以下のようになっている。

# frozen_string_literal: true
# A sample Gemfile
source "https://rubygems.org"

# gem "rails"

sourceがgemパッケージの取得先、デフォルトではrubygems.orgが指定されているが、ローカルネット内に独自に立てているリポジトリサーバーに向けることも出来る。

インストール

Gemfileに使用するgemパッケージを記載して「bundle install」コマンドを実行するとgemパッケージがインストールされる。またインストール対象のパッケージ内で使われているgemパッケージも芋づる式にインストールされる。
「bundle install」を実行すると、Gemfileと同じディレクトリにGemfile.lockファイルが作成される。このファイルには、Gemfilleに記述されたgemパッケージの依存関係を解決した結果が書き出される。
Gemfile.lock内にはインストールされたgemパッケージの具体的なバージョンも記載される。
Gemfile.lockも更新したい場合は、「bundle update」コマンドを実効する。

「bundle install」でインストールしたコマンドの実行

「bundle install」でインストールしたGemは「bundle exec コマンド名」で実行する。

$ bundle exec [コマンド]

開発用、テスト用

Gemfileは開発環境の場合とテスト環境の場合にインストールするものを設定可能。

source "http://rubygems.org"

gem 'sinatra'

# 開発用のGem
group :development do
    gem 'awesome_print'
end

# テスト用のGem

group :test do
    gem 'rspec'
end

特定の環境のGemだけインストールしない場合は、--withoutオプションを使う。

# developmentとtest以外のGemをインストールする
% bundle install --without development test

binstubsオプション

「bundle install」でインストールしたコマンドは、通常「bundle exec コマンド名」でなければ実行できない。
しかし、「bundle install」時に「--binstubs」オプションを指定すれば、直下にbinディレクトリが作成されその中にGemのコマンドが配置されるので、コマンド名だけでコマンドが実行可能になる。

RSpecのまとめ

Ruby

概要

BDDを行うためのテストフレームワーク

準備

gemのインストール。

$ gem install rspec

初期化

$ rspec -init

これを実行すると spec というディレクトリが直下に出来て、その中に spec_helper.rb ファイルが出来る。rspecのテストファイルを作成する際は必ずこれを require する必要がある。

チュートリアル

(1) テスト対象を決める
今回は、/lib/dog.rb を対象とする。

(2) 失敗するテストを書く
下記のテストファイルを作る。
spec/lib/dog_spec.rb

require "spec_helper"
require "dog"

describe Dog do
  it "is named 'Pochi'" do
    dog = Dog.new
    expect(dog.name).to eq 'Pochi'
  end
end

またテスト対象である Dog クラスもファイルだけ作成しておく。 (でないとテスト実行時にエラーがとなってしまうので)

テストを実行してみます。実行結果は・・

$ rspec spec/lib/dog_spec.rb
F

Failures:

  1) Dog is named 'Pochi'
     Failure/Error: expect(dog.name).to eq 'Pochi'

     NoMethodError:
       undefined method `name' for #<Dog:0x007fcc4f97a8d8>
     # ./spec/lib/dog_spec.rb:7:in `block (2 levels) in <top (required)>'

Finished in 0.00061 seconds (files took 0.55266 seconds to load)
1 example, 1 failure

Failed examples:

rspec ./spec/lib/dog_spec.rb:5 # Dog is named 'Pochi'

当然、dogの中身がないので失敗します。
テストが成功するように、dog.rbを修正します。

class Dog
  attr_accessor :name

  def initialize(name="Pochi")
    @name = name
  end
end

再度テストを実行してみる。

$ rspec spec/lib/dog_spec.rb
.

Finished in 0.0074 seconds (files took 0.53777 seconds to load)
1 example, 0 failures

今度は無事成功したのでOK! このテスト作成→失敗→修正→成功までの一連のサイクルがBDD(Behavior Driven Development)の基本的な流れになる。

その他のテスト例

describe User do
  describe '#greet' do
    before do
      @params = { name: 'たろう' }
    end
    context '12歳以下の場合' do
      before do
        @params.merge!(age: 12)
      end
      it 'ひらがなで答えること' do
        user = User.new(@params)
        expect(user.greet).to eq 'ぼくはたろうだよ。'
      end
    end
    context '13歳以上の場合' do
      before do
        @params.merge!(age: 13)
      end
      it '漢字で答えること' do
        user = User.new(@params)
        expect(user.greet).to eq '僕はたろうです。'
      end
    end
  end
end

describe '#greet' doは、Userクラスのgreetメソッドをテストするということ。
また、beforeは、テスト実行前に必要な共通処理を記載する。
contextは、直訳すると「文脈」や「状況」という意味。つまり、なにかしら条件が異なる場合はcontextを使ってテストを分割する。

インスタンス変数の代わりにletを使う

インスタンス変数の代わりに以下のようにletを使うこともできる。

require 'spec_helper'
require 'user'

describe User do
  describe '#greet' do
    let(:params) {{name: 'たろう'}}
    context '12歳以下の場合' do
      before do
        params.merge!(age: 12)
      end
      it 'ひらがなで答えること' do
        user = User.new(params)
        expect(user.greet).to eq 'ぼくはたろうだよ。'
      end
    end
    context '13以上の場合' do
      before do
        params.merge!(age: 13)
      end
      it '漢字で答えること' do
        user = User.new(params)
        expect(user.greet).to eq '僕はたろうです。'
      end
    end
  end
end

let(:params) {{name: 'たろう'}}がそう。{}が二重になってちょっとわかりにくいが、外側がRubyのブロックの{}で、内側がハッシュの{}だ。
let は遅延評価されるので、それが使われる時に初めて初期化される。つまり、上のテストコードを以下のように書き直すことが出来てしまう。

require 'spec_helper'
require 'user'

describe User do
  describe '#greet' do
    let(:user) { User.new(params) }
    let(:params) {{name: 'たろう', age: age}}
    context '12歳以下の場合' do
      let(:age) {12}
      it 'ひらがなで答えること' do
        expect(user.greet).to eq 'ぼくはたろうだよ。'
      end
    end
    context '13以上の場合' do
      let(:age) {13}
      it '漢字で答えること' do
        expect(user.greet).to eq '僕はたろうです。'
      end
    end
  end
end

age は params ハッシュの定義時に使われているが、各context内ではじめて実際の値が代入されている。

gemパッケージの作り方

Ruby

Rubyのgemパッケージは個人でも作って公開することが出来るらしい。

雛形を生成する

Bundlerと呼ばれるgemパッケージを利用して雛形を生成し、それを土台にして作るのが一般的らしい。
Bundlerはgem installコマンドでインストール可能。

$ gem install bundler
$ bundle --version

さっそく雛形を作る。以下のコマンドを実行する。

$ bundle gem [gem名]

テストはrspecかminitestどっち使うか?とか、コードをGemに含めるかとかの質問に答えれば、指定したgem名のディレクトリが直下に生成される。

生成されたディレクトリ内には以下のようなファイルが含まれる。

ファイル 説明
Gemfile Bundlerがgemの依存関係を解決するために利用するファイル
Rakefile gemパッケージのビルドやリリースのためのタスクが定義されている
LICENSE.txt gemパッケージのライセンスについて記述するファイル。生成時はMITライセンス。
.gitignore Gitのソースコード管理に含めないファイルの一覧を記述するファイル。
[gem名].gemspec gemパッケージの構成や詳細情報を記述するファイル
lib/[gem名].rb gemパッケージのメインとなるソースコード
lib/[gem名]/version.rb gemパッケージのバージョンを記述しておくソースコード

gemspecファイル

生成されたファイルの中で最も重要なファイル。gemspecファイルはgemパッケージの作者に関する情報やgemパッケージを構成するために必要な情報が含まれている。

# coding: utf-8
lib = File.expand_path('../lib', __FILE__)
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
require 'gem_sample_pruby/version'

Gem::Specification.new do |spec|
  spec.name          = "gem_sample_pruby"
  spec.version       = GemSamplePruby::VERSION
  spec.authors       = ["TODO: Write your name"]
  spec.email         = ["TODO: Write your email address"]

  spec.summary       = %q{TODO: Write a short summary, because Rubygems requires one.}
  spec.description   = %q{TODO: Write a longer description or delete this line.}
  spec.homepage      = "TODO: Put your gem's website or public repo URL here."

  # Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
  # to allow pushing to a single host or delete this section to allow pushing to any host.
  if spec.respond_to?(:metadata)
    spec.metadata['allowed_push_host'] = "TODO: Set to 'http://mygemserver.com'"
  else
    raise "RubyGems 2.0 or newer is required to protect against public gem pushes."
  end

  spec.files         = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
  spec.bindir        = "exe"
  spec.executables   = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
  spec.require_paths = ["lib"]

  spec.add_development_dependency "bundler", "~> 1.12"
  spec.add_development_dependency "rake", "~> 10.0"
  spec.add_development_dependency "rspec", "~> 3.0"
end

nameはgemの名前、この値がrubygems.orgで公開される時の名称として使われる。versionも同様にここで設定した値がrubygems.orgで公開される時のバージョン番号になる。雛形作成時には定数「GemSamplePruby::VERSION」が設定されているが、これはversion.rb内で定義されているもの。descriptionにはgemの詳細な説明を記載する。

filesはgemパッケージに含む対象となるファイルのリストを表している。ここではgit ls-filesから取得した値がベースになっているということ、つまりgitの管理対象のファイルがそのままgemパッケージに含まれるファイルということ。

依存関係の解決

依存関係には「実行時に使うgemへの依存」と「開発時に使うgemへの依存」の2種類がある。どちらもgemspecファイル内に記述する。

  # 実行時に使うGem
  spec.add_runtime_dependency "json", ">= 1.7.3"

  # 開発時に使うGem
  spec.add_development_dependency "bundler", "~> 1.12"

Gemパッケージの開発ディレクトリ内でBundlerを利用して依存対象のGemパッケージをインストールしたときには、開発時に使うGemもインストールされるが、リリース後のパッケージをインストールすれば実行時に使うGemしかインストールされない。

作成したGemのビルドとインストール

作成したGemはRakeでビルドしてインストールする。

% rake build
gem_sample_pruby 0.1.0 built to pkg/gem_sample_pruby-0.1.0.gem.

直下にpkgディレクトリが生成され、その下にgemパッケージのバイナリファイルが生成される。
生成されたgemをインストールするには以下のコマンドを実行。

$ gem install pkg/gem_sample_pruby-0.1.0.gem
Successfully installed gem_sample_pruby-0.1.0
Parsing documentation for gem_sample_pruby-0.1.0
Installing ri documentation for gem_sample_pruby-0.1.0
Done installing documentation for gem_sample_pruby after 0 seconds
1 gem installed

ビルドとインストールを一つのコマンドで実行することもできる。

$ rake install

Gemパッケージを公開する

作成したGemパッケージを公開するには、rubygems.orgを利用する。アカウントが必要になるので事前にユーザー登録しておく。
アカウントを作成するとAPI KEYが発行される。このAPIキーを「~/.gem/credentials」というファイルに記述する必要があるが、以下のコマンドを使ってAPIキーの取得と同ファイルへの記載をまとめて行うことが出来る。

$ curl -u ryopeko https://rubygems.org/api/v1/api_key.yaml > ~/.gem.credentials
Enter host password for user 'ryopeko':
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100    27    0    27    0     0     23      0 --:--:--  0:00:01 --:--:--    23

あとは「rake release」コマンドを実行するだけで簡単にgemパッケージを公開することができる。

ドットインストールのHaml動画のまとめ

Ruby

次の職場でHamlっていうのを使うらしいのでまとめてみました。

Hamlとは?

htmlを簡単に記述するための記法、またはそこからHtmlを自動生成するための gem のこと。
例えば以下のようなHamlは・・・

!!!
%html
    %body
        hello world!

次のようなHtmlに変換される。

<!DOCTYPE html>
<html>
  <body>
    hello world!
  </body>
</html>

準備

hamlをインストールする。

$ gem install haml

基本的な使い方

haml記法で書いたファイルを保存して、以下のコマンドを実行しhtmlに変換する。

$ haml index.haml index.html

またはヘルプは以下のコマンドで確認できる。

$ haml -help

HTML5形式で出力する

hamlは -f オプションで出力形式を指定できる。
-f ・・・出力形式を指定する。
-q・・・属性の指定時にシングル(既定値)ではなくダブルクォーテーションを使う。

以下のコマンドを実行すれば、hamlHTML5形式で出力する。

$ haml -q -f html5 index.haml index.html

書き方

コメント

コメントの入力には「/」を使う。

/ コメント

また、htmlに変換しないコメントは「-#」 を使う。

/コメント
-# comment

これをHTMLにコンバートすると・・

<!- コメント ->

「-#」で書いたコメントはHTMLに変換されていないのでOK。

属性

Ruby風、Html風、ショートカット的な書き方の3つがあるとです。
まずはRuby風な書き方。

%div{:id=>"main", :class=>"myClass"}

次に、HTML風な書き方。

%div(id="main" class="myClass")

属性はよく使うので、ショートカット的な書き方も。

%div#main.myClass

さらにはdivもよく使うので省略可能。

#main.myClass

全部以下のようなHTMLに変換されまーす。

<div class="myClass" id="main"></div>

cssとかjavascriptを埋め込む(フィルター)

cssとかjavascriptHaml記法で簡単に埋め込める。

%body
    hello world!
    :css
      .myStyle {
        color: red;
      }
    :javascript
      alert(1);
      if(1) {
        alert(2);
      }
<body>
    hello world!
    <style>
      .myStyle {
        color: red;
      }
    </style>
    <script>
      alert(1);
      if(1) {
        alert(2);
      }
    </script>
</body>

「:css」みたいに:の後にしているするのがフィルター。他にも escaped(文字列をエスケープした形式で出力してくれる) とかがある。

Rubyコードを埋め込む

RubyのgemなのでRubyコードを埋め込める。

%p total is #{5 * 3}  -#出力の中に埋め込む
%p= Time.now         -#タグの中に出力する
- x = 5                       -#式を評価するだけ
%p= x
- (1..10).each  do |i| -#ループする
    %p= i
<p>total is 15</p>
<p>2016-12-13 21:00:41 +0900</p>
<p>5</p>
<p>1</p>
<p>2</p>
<p>3</p>
<p>4</p>
<p>5</p>
<p>6</p>
<p>7</p>
<p>8</p>
<p>9</p>
<p>10</p>

RubyGemsの使い方まとめ

Ruby

gemコマンドの調べ方

# コマンドの一覧を表示する
$ gem help commands
# コマンドの詳細を確認する
$ gem help [コマンド名]

gemのインストール

下記のコマンドでGemをインストールする。

$ gem install [gem名]

依存するgemパッケージも自動的にインストールされる。またバージョンを指定していない場合は、最新バージョンがインストールされる。

バージョンを指定する場合

$ gem install [gem名] -v '= 2.2.2'   # 2.2.2
$ gem install [gem名] -v '< 2.2'      # 2.2より1つ古いもの
$ gem install [gem名] -v '>= 2.3'    # 2.3かそれよりも新しいもの
$ gem install [gem名] -v '~> 0.8'    # 0.x系で一番新しいもの
$ gem install [gem名] -v '~> 0.8.0' # 0.8系で一番新しいもの

サーバー側(rubygems.org)のリストを得る

$ gem list [gem名] remote

gemパッケージを探す

$ rubygems.orgのgemパッケージを検索する
$ gem search [gem名] --remote

正規表現も使える。。

$ gem query -r --name-matches="^active*"

gemパッケージのアップデート

$ gem update [gem名]

RubyGems自体のアップデート

$ gem update --system

gemのアンインストール

$ gem uninstall [gem名]

バージョンを指定してアンインストールする場合は、

$ gem uninstall [gem名] -v 'X.X.X'

全てのバージョンをアンインストールする場合は、

$ gem uninstall [gem名] -a 

gemのrdocを確認する

gemインストール時にはそのrdocも同時に生成される。なので、インターネット日接続時にもドキュメントを参照することが出来る。

% gem server

後はlocalhost:8808にアクセスし、参照したいGemのリンクをクリックする。

インストール時にrdocを生成しない場合は、「--no-rdoc --no-ri」オプションを指定する。

RubyGemsの環境や設定の確認

$ gem env

Rubyのバージョン、Gemのあージョン、Gemのインストール場所なんかの一覧が表示される。

古いGemパッケージを削除する

$ gem cleanup -d [gem名]

-d はドライランオプション。削除されるバージョンを確認することができる。

インストールしたgemパッケージの状態を元に戻す

インストールしたGemをなんらかの理由で編集した後に、インストール直後に戻すためのコマンド。

$ gem pristine [gem名]
$ gem pristine [gem名] -v 1.2.0 # バージョン指定も可能
$ gem pristine --all                     # すべてを元の状態に戻す

サルでもわかるGit入門(入門編&発展編)をざっくりと呼んだメモ

Git

サルでもわかるGit入門の入門編と発展編を読んだ時のメモです。

リポジトリとインデックスとワークツリー

Gitには3つの階層がある。

ワークツリー

作業場。変更中のファイルがある場所。

インデックス

git add されたファイルが置かれる場所。 git commitでコミットされたファイルはリポジトリに移動する。

リポジトリ

コミットされたファイルが保存されている場所。

ブランチ

統合ブランチとトピックブランチ

ブランチは、統合ブランチとトピックブランチに分かれる。
統合ブランチは、いつでもリリースできる状態のブランチで通常、masterブランチを用いる。masterブランチはCIツールによる自動ビルドやテストを定期的に行うことで常に明らかなバグが無いようにしておくことが大切。
トピックブランチは、機能追加やバグの修正時に作る一時的なブランチのこと。作業が終われば統合ブランチにマージして、トピックブランチは削除する。

stash

コミットしていない変更内容や追加したファイルがインデックスやワークツリーに残ったままで、他のブランチへのチェックアウトを行うと、その変更内容はもとのブランチから移動先にブランチに対して移動する。
ただし、移動先のブランチで同じファイルが既に何らかの変更が行われている場合はチェックアウトに失敗する。
このような場合には変更内容を一度コミットするか、stashを使って一時的に変更内容を退避させてからチェックアウトする必要がある。

具体的なコマンドは以下の通り。

# ワークツリーの変更を保存する。stashに保存された変更分は取り消される。
$git stash save
# 以下のコマンドで保存しているstashの一覧を確認できる。
$ git stash list
stash@{0}: WIP on localize: 67f7992 localized
# 以下のコマンドでそのstash内の変更内容を確認できる。
$ git stash show stash@{0}
 sample1.txt | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
# 以下のコマンドでstashを適用できる
$ git stash apply stash@{0}
Auto-merging sample1.txt
CONFLICT (content): Merge conflict in sample1.txt 
# stashを削除するにはapplyをdropに変えればよい。
# applyとdropを同時にしたい場合は、popとする。

ブランチの統合

ブランチの統合にはmergeを使う方法と、rebaseを使う方法がある。

merge

mergeを使うと2つのブランチを1つのブランチに統合することができる。通常、マージ先のブランチは統合(master)ブランチでマージ元がトピックブランチとなる。
具体的なコマンドは以下の通り。

$ git merge <ブランチ名>

もし、マージ先にも変更がある場合は、コミットメッセージを入力する画面がvimで表示される。コミットしない場合は :q でvimを終了する。
マージが完了したら、以下のコマンドで用済みのトピックブランチを削除する。

$ git branch -d <ブランチ名>

rebase

rebaseを使うと、統合元ブランチの履歴が統合先ブランチ(通常master)ブランチの後ろに付け替えられれる。統合元のブランチは削除され、統合先ブランチに一本化される。
うーん。難しいので具体的な手順は省略ー。

リモートリポジトリ

pull

リモートリポジトリの履歴を取得することが出来る。リモートリポジトリが変更されていて、ローカルリポジトリが変更されていない場合は、fast-forwardマージが行われる。
ローカルリポジトリも変更されている場合は、競合の有無を確認し、競合が無い場合は自動的にマージコミットされる。競合がある場合は、やっぱり手動解決&コミットが必要。

fetch

pullを実行すると、リモートリポジトリの内容が自動的にマージされてしまう。しかし、単にリモートリポジトリの内容を確認したいだけのときはマージしたくない場合もある。そんなときに、fetchを使う。
もしmergeしたくなれば、

$ git merge origin/master

でマージする。 なので、pull = fetch + mergeであるとも言える。

push

リモートリポジトリにローカルリポジトリの変更を反映したい場合は、pushを実行する。ただし、競合が発生する場合は拒否されるので、事前にリモートリポジトリをpullして競合を解決しておく必要がある。

タグ

# コミットに注釈なしタグを付ける。今回はappleというタグを付ける。
$ git tag apple
# 付けたタグを確認する
$ git tag
apple
# 今度は注釈ありのタグを付ける。タグ名は banana。
$ git tag -am "サルでも分かるGit" banana
# タグの一覧を注釈付きで確認する
$ git tag -n
apple           first commit
banana          サルでも分かるGit
# タグを削除する
$ git tag -d <タグ名>

コミットの書き換え

commit --amend を使って直前のコミットを編集する

まずは、ログを確認する。

$ git log
commit 326fc9f70d022afdd31b0072dbbae003783d77ed
Author: yourname <yourname@yourmail.com>
Date:   Mon Jul 16 23:17:56 2012 +0900

    addの説明を追加

次に、ファイルを編集する。ここでは、sample.txtにcommitに関する説明を追記する。編集が終わったら commit --amend コマンドで直前のコミットを修正する

$ git commit --amend
[master 0f911e9] addとcommitの説明を追加
 Date: Mon Jul 16 23:17:56 2012 +0900
 1 file changed, 3 insertions(+), 1 deletion(-)

再度ログを確認すると・・・。

$ git log
commit 0f911e9ebc8b373eeb62b56b999c02280440f737
Author: yourname <yourname@yourmail.com>
Date:   Mon Jul 16 23:17:56 2012 +0900

    addとcommitの説明を追加

無事、直前のコミットが編集されているのでOK。

revert を使って直前のコミットを打ち消す

まずはログを確認。

$ git log
commit 0d4a808c26908cd5fe4b6294a00150342d1a58be
Author: yourname <yourname@yourmail.com>
Date:   Mon Jul 16 23:19:26 2012 +0900

    pullの説明を追加

commit 9a54fd4dd22dbe22dd966581bc78e83f16cee1d7
Author: yourname <yourname@yourmail.com>
Date:   Mon Jul 16 23:19:01 2012 +0900

    commitの説明を追加

git revert コマンドで直前のコミットを取り消す。

$ git revert HEAD
[master d47bb1d] Revert "pullの説明を追加"
 1 files changed, 1 insertions(+), 2 deletions(-)

ログを確認してみる。

$ git log
commit 7bcf5e3b6fc47e875ec226ce2b13a53df73cf626
Author: yourname <yourname@yourmail.com>
Date:   Wed Jul 18 15:46:28 2012 +0900

    Revert "pullの説明を追加"

    This reverts commit 0d4a808c26908cd5fe4b6294a00150342d1a58be.

commit 0d4a808c26908cd5fe4b6294a00150342d1a58be
Author: yourname <yourname@yourmail.com>
Date:   Mon Jul 16 23:19:26 2012 +0900

    pullの説明を追加

直前のコミットを打ち消すコミットが追加されているのが分かる。つまり、直前のコミットとその一つ前のコミットの差分を自動的に求め、その差分を打ち消すようなコミットがされているということ。

reset を使ってコミットを削除する

revertと違ってこっちは物理的にコミットを削除するコマンド?(正直あんまよくわかってません。。)

$ git reset --hard HEAD~
HEAD is now at 9a54fd4 commitの説明を追加

これで直前の履歴が削除される。
ただし、GitHubのパブリックリポジトリなど既に公開されているものに対してこれをやるのは絶対に駄目!

chrry-pick で別ブランチのコミットを取り込む

masterブランチとトピックブランチがある時、トピックブランチの1つ前のコミットをmasterブランチに取り込みたい場合などに使う。

$ git branch
* issue1
  master
# issue1の履歴を確認
$ git log
commit 08084a5c58e9ca3672292c3883c44e623f817b72
Author: yourname <yourname@yourmail.com>
Date:   Mon Jul 16 23:22:17 2012 +0900

    pullの説明を追加

commit 99daed25b45fcae2ce9d707a3434951cf69f253a
Author: yourname <yourname@yourmail.com>
Date:   Mon Jul 16 23:21:57 2012 +0900

    commitの説明を追加

この issue1 の 99daed25.. のコミットをmasterブランチに取り込む。

# まずmasterブランチをチェックアウト
$ git checkout master
Switched to branch 'master'
# あとはトピックブランチのコミットをmasterブランチの先頭に持ってくる
$ git cherry-pick 99daed2
error: could not apply 99daed2... commit
hint: after resolving the conflicts, mark the corrected paths
hint: with 'git add <paths>' or 'git rm <paths>'
hint: and commit the result with 'git commit'

競合が発生しているので、ファイルを開いて競合箇所を修正してからコミットする。

rebase -i でコミットをまとめる

後からコミットをまとめることも出来る。例えば以下のような履歴があった時

$ git log --oneline
0d4a808 pullの説明を追加
9a54fd4 commitの説明を追加
326fc9f addの説明を追加
48eec1d first commit

直前2つのコミットをrebase -iを使ってまとめる。

$ git rebase -i HEAD~~

rebase -iコマンドを実行すると、以下のようなvimが開く。

pick 9a54fd4 commitの説明を追加
pick 0d4a808 pullの説明を追加

# Rebase 326fc9f..0d4a808 onto 326fc9f (2 command(s))
#
# Commands:
# p, pick = use commit
# r, reword = use commit, but edit the commit message
# e, edit = use commit, but stop for amending
# s, squash = use commit, but meld into previous commit
# f, fixup = like "squash", but discard this commit's log message
# x, exec = run command (the rest of the line) using shell
# d, drop = remove commit
#
# These lines can be re-ordered; they are executed from top to bottom.
#
# If you remove a line here THAT COMMIT WILL BE LOST.
#
# However, if you remove everything, the rebase will be aborted.
#
# Note that empty commits are commented out

2行目の pick を squash に変更して保存・終了する(squash は押しつぶすという意味らしい)。すると今度はまとめたコミットのメッセージを入力するためのエディタが開くので適切なメッセージを入力して保存・終了する。今回は、「pullとcommitを追加」とする。
再び履歴を確認してみると・・。

$ git log --oneline
866d3e3 pullとcommitの説明を追加
326fc9f addの説明を追加
48eec1d first commit

直前2つのコミットが1つのコミットになっているのでOK。

rebase -i でコミットを修正する

rebase -iを使ってコミットを修正することも出来る・・。
こんな履歴があった時

$ git log --oneline
0d4a808 pullの説明を追加
9a54fd4 commitの説明を追加
326fc9f addの説明を追加
48eec1d first commit

2つ前の 9a54fd4 コミットを修正するとする。

$ git rebase -i HEAD~~

するとvimが開く。

pick 9a54fd4 commitの説明を追加
pick 0d4a808 pullの説明を追加

# Rebase 326fc9f..0d4a808 onto d286baa
#
# Commands:
#  p, pick = use commit
#  r, reword = use commit, but edit the commit message
#  e, edit = use commit, but stop for amending
#  s, squash = use commit, but meld into previous commit
#  f, fixup = like "squash", but discard this commit's log message
#  x, exec = run command (the rest of the line) using shell
#
# If you remove a line here THAT COMMIT WILL BE LOST.
# However, if you remove everything, the rebase will be aborted.
#

編集対象(ここでは0d4a808)のpickを edit に変更して保存・終了する。
すると次のような出力が表示され、修正するコミットがチェックアウトされた状態になる。

Stopped at d286baa... commitの説明を追加
You can amend the commit now, with

        git commit --amend

Once you are satisfied with your changes, run

        git rebase --continue

修正対象のファイルを開いて適当に変更する。
終わったら、commit --amendで保存する。

$ git add sample.txt
$ git commit --amend

最後に、次のコマンドを実行して作業が終了した事を知らせる必要がある。

$ git rebase --continue

merge --squash でトピックブランチのコミットをまとめてマージする

$ git branch
* issue1
  master
$ git log --oneline
08084a5 pullの説明を追加
99daed2 commitの説明を追加
48eec1d first commit
$ git checkout master
Switched to branch 'master'
$ git merge --squash issue1
Auto-merging sample.txt
CONFLICT (content): Merge conflict in sample.txt
Squash commit -- not updating HEAD
Automatic merge failed; fix conflicts and then commit the result.

競合が発生したので、sample.txtを開いて競合箇所を修正してからコミットする。

$ git add -A
$ git commit
[master a6a3be4] merge issue1
 1 file changed, 3 insertions(+), 1 deletion(-)