Git 版本控制系統(3) 還沒 push 前可以做的事

關於 Git 可以參考我的 Git 版本控制 課程資料

這一集要講的是:還沒 push 前可以做的事,也就是 reset 跟 rebase。

相較於 SVN 這種 commit 就是送到遠端伺服器,Git 的 commit 其實東西都還是在本地端,所以只要你還沒 push 出去分享給別人,你的 commit logs 是可以修改的!! 這種功能非常的 powerful,可以讓你 undo 和 rewrite commit history。如果你用 Git 只會 git commit 然後接著馬上 git push,那你就沒有學到精隨啊!~

使用告誡:如果你已經 push 出去了,請千萬不要做 rewrite history 的動作,會天下大亂啊。正確善用 undo changes/rewrite history 的功能,我們可以讓東西準備好弄的整整齊齊才 push 出去給別人 pull。

讓我們來學幾招吧:

amend

git commit -C HEAD -a –amend 快速修正前一次 commit 的錯誤,只要修 typo 之後打這行,就會替換掉前一次的 commit

reset

git reset 砍掉 commit 重來,但是修改的程式還是留在 working tree。例如:

git reset HEAD^ 就會回到前一版本(一個^表示是前一版),並把其中的 changes 繼續留在 working tree 中。適合發現前一次 commit 有問題或是想要修改 commit log,可以修改後再重新 commit。

git reset 如果加上 –soft 參數則會把 changes 直接加到 staging area。

加上 –hard 參數表示不留 staging area 也不留 working tree(完全刪除任何修改記錄),例如:

git reset –hard HEAD^ 則會完全抹掉前一次的 commit。

另個常用的情境是要把修改的檔案從 staging area 移走,指令就是 git reset HEAD filename (但還是留在working tree)。BTW,要回復 working tree 中修改的檔案成本來的樣子,指令是 git checkout filename (重新拿出本來的檔案)

順道一提,如果東西已經 push 出來了,要怎麼回復? 這時候就得用 revert 了。

git revert 會用一個新的 commit 來回復所有的變更(適合已經push出去給別人的情境)。加上 -n 可以不先 commit,這樣可以多 revert 幾次後再一次 commit。

指令會有差別都是因為 remote repo.<-> local repo.<->staging area<->working tree 分了四層移來移去的關係,你只要想清楚這幾層就融會貫通了 (請參考第一集有解釋 staging area 和 working tree)。

rebase

我在 使用 rebase 避免無謂的 merge 一文中有解釋過 rebase 的其中一個用途。而 rebase 的真正潛力是,我們可以從指定的版號之後,重新隨你意 commit 一次來重建 history,超威的。首先輸入 git rebase -i 版號 就會可以跳出 editor 可以編輯,我們可以

a. 變更 commit 順序
b. 將多個 commit 合併 squash
c. 將一個 commit 打散 (edit 會停著讓你可以 git reset HEAD^ 打散重新 commit,完成後 git rebase –continue )

另一種 rebase 用法是不需要打 -i ,直接指定另一個 branch 或 tag,這樣就會重新 commit 另一個 branch 的東西,然後才 commit 自己的 (也就是 使用 rebase 避免無謂的 merge 的用法)。

git rebase 若有 conflict 就會停下來, 跟 merge 一樣處理完 add,然後 git rebase –continue 就會繼續 commit (也可以 –skip 或 –abort 放棄啦)

rebase 有個 onto 參數用法,使用的情境是:假設你有三個有 dependency 的 branch 分別叫做 master/contact/search,後來發現 search branch 只有 depend on matser,於是你可以輸入 git rebase –onto master contact search 這樣就會讓 search branch 從 master 的地方開始重新 commit。

再次提醒,rebase 千萬只能適合東西還沒 push 的情境,或是你自己的 local 專用私人 branch。rebase 一個已經 push 出去的 repository,然後你又把修改的 history push 出去,是會造成超級大災難的。

在學習 rebase 的過程中,很容易拿來跟 merge 比較一下。我發現一個有趣的不同點:如果有檔案在要被 merged 的 branch 中被刪除,如果用 rebase 檔案最後會不存在,但是用 merge 的話檔案最後還在 XDXD

另外,在 rebase branch 之後,如果再做 merge,就會發現因為 master 直接就是被 merge 的祖先,所以線圖直接變成一條線,而有這種 parent 關係的 merge 就叫做 fast-forward。換句話說,因為沒有發生任何 merge commit,也不會發生 conflict,Git 內部單純只是變更 reference 參照,所以謂之 fast-forward。

好心提醒,因為開 local branch 是如此便宜無害,所以要做 rebase 時建議您可以先開一個 local branch 來實驗 rebase。老實說,rebase 還挺危險的 XDXD

最後,rebase 我認為算是 Git 初學者最難理解的功能吧,但是如果不知道什麼是 rebase,就不能說是懂 Git 啊。

Rails3: Railtie 和 Plugins 系統

Rails3 提供了一些新的方法來擴充 Rails,其中最重要的算是了解 Railtie 吧。(我個人對 Generator 比較沒興趣)

Railtie 是 Rails 的核心程式,提供了 hooks 來修改啟動時的載入流程。Rails 的主要元件 (Action Mailer, Action Controller, Action View, Active Record, Active Resource) 都有 Railtie 來各自負責自己的載入流程。這樣設計的好處有 1. 主要元件也是可以抽換的(例如換 ORM) 2. 要在 Rails 裡 hook 變得十分乾淨,只要撰寫 railtie 即可,不像之前只能用 alias_method_chain。

不過,擴充 Rails 不一定會用到 Railtie。會需要實作的情境是你要在 Rails 框架啟動流程中做些互動,例如:

1. 建立 initializers
2. 新增 generator
3. 修改 Rails config.*
4. 訂閱 Rails +ActiveSupport::Notifications+
5. 新增 rake tasks

因此,我歸類出寫 Rails Plugin 的考量有 1. 要不要寫成 Gem 2. 需不需要用到 Railstie:

傳統 vender/plugin

傳統的 plugin 寫法依然適用,Rails 還是會去載入 plugin 目錄下的 init.rb。有趣的是,在 Rails 內部是把 plugin 自動包裝成一個 Rails::Plugin 類別,而這個 Plugin class 也是繼承自 Rails::Railtie 的。

包成 Rubygem

自從有了 Bundler 之後,包成 Gem 可以說是最佳實務了。透過標準的 Gem 格式,可以 1.設定 dependcncies 2. 有版號 version 3. 方便分享及安裝

如何包成 Gem 請參考 Rubygems 套件管理工具 這篇,很簡單的。

沒用到 railtie

Rails3 架構師 wycats 開示:”如果你沒有要 hook 在 Rails lifecycle 之中,不要用 Railtie。就如同一般的 Ruby library,你先 require 你需要的 Rails 元件,然後再修改或擴充即可” (例如 override 或 include something)


# in your_lib.rb
require "active_record" 
require "your_lib/extensions" 
class ActiveRecord::Base
  include YourLib::Extensions
end

用到 railtie

如果你需要 hook 或載入 rake, generator 等,那就需要用到 railtie 了,該怎麼寫呢?

基本的寫法如下:


# my_new_gem/my_new_gem.rb
require 'rails'
class MyCoolRailtie < Rails::Railtie

  # console 時載入
  console do 
    Foo.console_mode!
  end

  # 載入 generator
  generators do
    require 'path/to/generator'
  end

  # 載入 rake tasks
  rake_tasks do
    require 'path/to/railtie.tasks'
  end

  # 建立 config/initializers
  initializer "my_cool_railtie.boot_foo" do
    Foo.boot(Bar)
  end

end

除了上述這些,還有更多細微的 hook 如 config.after _initialize, config.middlewares, before_configuration, before_eager_load, before_initialize, to_prepare 等等族繁不及背載,如果沒仔細研究 Rails 的啟動流程恐怕也搞不清楚所有的差異。

如果你的 gem 不只給 rails 用,可以這樣寫:


# lib/my_new_gem/my_cool_railtie.rb
module MyNewGem
  class MyCoolRailtie < ::Rails::Railtie
    # Railtie code here
  end
end

# lib/my_new_gem.rb
require 'my_new_gem/my_cool_railtie.rb' if defined?(Rails)

請期待下集 "Rails3: Engine 和 Plugins 系統",Rails::Engine 可以讓你包裝出獨立的 App 元件。

參考資料

Plugin Authors: Toward a Better Future
Rails 3 Plugins - Part 1 - The Big Picture
Extending Rails 3 with Railties
Class Rails::Railtie

Heroku: Ruby 社群的雲端平台

一直想要寫一篇來介紹 Heroku,而這篇簡單的介紹堆在 draft 好一陣子了。雖然比不上 @deduce 上週寫的 Ruby語言的雲端運算平台:Heroku 來的詳細,但既然都寫了就貼出來吧 XD

常常有人在羨慕 Python 跟 Java 有 Google App Engine 可以用,然後 Ruby 沒有。雖然說 Ruby 可以透過 JRuby 跑在 GAE 上(方案1, 方案2),但是好像不太多人這麼做,為什麼呢? 這是因為 Ruby 社群大多用 Heroku 啊。

Heroku 這家公司提供了與 GAE 類似的 PaaS 雲端服務,讓任何符合 Rack 介面的 Ruby 應用程式可以跑在上頭(所以不只是 Rails,像是 Sinatra 也可以跑在上面),以及提供 PostgreSQL 這套關聯式資料庫和其他許多的外掛服務。它的底層硬體資源是採用了可擴展的 Amazon EC2,透過 Heroku 的 dynos (它的資源單位) 只要拉上拉下就可以增加伺服器負載了,非常厲害,請看 Rapportive 的故事:

想要擁有一個可隨時延展的架構(scalable architecture),你只要選對廠商(hosting provider)就可以做到。如果Rapportive一開始隨便選了一個便宜的VPS,流量一衝進來的時候可能就會像全面啟動電影中Cobb的混沌世界(Limbo)一樣崩潰。Rapportive的服務是放在知名廠商Heroku上,對於突然湧進的流量只需要增加Dynos的數量(Heroku提供服務的基本單位),基本上你是不需要修改你的程式的;當然,程式的優化、調整可以在同樣能耐的硬體等級上容納更多人。

使用Heroku、不需要調整程式、只需要增加Dyno數量?真的有這麼美好嗎?事實上Rapportive就是這麼辦到的,在來自全世界的流量突然湧進時,Rahul Vohra手邊沒有電腦,於是他隨即拿起iPhone並且利用Nezumi這個設計來管理Heroku的應用程式,將Rapportive的Dynos增加到20個,就這麼簡單,可能不到一分鐘吧?!系統的能耐馬上就提昇了。

它的佈署方式採用 Git,所以如果你熟悉 Git,那麼使用 Heroku 真是超級簡單的:

安裝步驟:

gem install heroku
heroku create your_app_name

佈署:

git push heroku master (當然,你的專案必須是 Git repository)
接著打開瀏覽器 your_app_name.heroku.com 就可以看到了。

只要一個指令就可以搞定佈署,它會自動幫你調整 database.yml 檔案。可參考 Heroku Quickstart Guide。根據最新的實地測試,Rails3 也可以正確跑起來。

總而言之,它的優點 1. 支援所有 Rack 應用程式 2. 佈署容易 3. 擴充方便外掛多。相較於 GAE,除了免費方案沒 G 社大方之外,其他毫不遜色啊。

Ruby 1.9.2 和 Rails 3.0.0 正式版發佈

歷經了兩年的開發,這個月 Ruby 社群有了兩個重大的世代交替消息:

關於 Ruby 1.9,可以參考我之前的演講:Ruby 1.9 投影片,最重要的部分就是 M17N Encoding 了。至於安裝請服用 RVM

關於 Rails3,可以參考我之前整理的:

如果是 Rails 初學者,可以直接看我正在撰寫的 Ruby on Rails 實戰手冊 一書。

BTW,JRuby 1.5.2 也是這個月發佈,而且 Rails3 也說支援 JRuby 1.5.2+,這個月真熱鬧。有人說今天就差 TextMate 2 發佈了 XDXD

Rails3 實戰手冊

Update(2011/4): 預計延到 2011 年中出版。
Update(2014/1): 無限延期…

Rails3 實戰手冊,這是我最近一次上課的講義,也是預計年底出版的內容 (書名未定)。也因為會要出版的關係,這份手冊的內容是 Copyright 的,而不是我一般慣用的 CC 授權 (除了投影片仍用 CC 授權),所以請不要轉載內文,更何況我會一直修訂啊。

之前應 OSSF 工作坊開了 Ruby/Rails 課程好幾次,所以陸陸續續開始寫投影片跟教材,加上這禮拜趁去台北市教師研習中心開課又重新好好整理了一次。目前狀況前幾章的完成度較高,會繼續努力修改成完整一本書的樣子,預計年底明年由 PcUSER 電腦人出版。

希望這本書能讓想入門 Rails 的朋友,可以實際應用在工作上無礙。有任何意見(書名有建議嗎?),歡迎告訴我。

Rails3 RC 發佈: 重點導覽

RailsConf 結束之後就嚷嚷要發佈的 Rails 3 Release Candidate 候選版本終於出了(好吧,是三個禮拜前的事情了,這篇拖稿了 XD),如果你還沒進場,又是一個好時機。從我上次(疑,竟然是半年前了!!)整理 Rails3 Beta 發佈: 重點導覽 以來,又有了一些不錯的文件,經過筆者過濾挑選,重新整理 Rails3 的八大重點:

1. Bundler
2. ActiveRecord 的新 Query API
3. ActiveRecord 的新 Validation API
4. Views: XSS, Block Helper, UJS
5. I18n
6. 新的 Routing API
7. 新的 ActionMailer
8. Metal

如果喜歡看 screencast,官方也錄了 rubyonrails.org/screencasts/rails3

安裝

升級到 ruby 1.8.7 或 1.9.2,以及 rubygems 要 1.3.6 以上


gem update --system
gem install rails --pre
gem install mysql2

開始改用 mysql2 gem 吧,這一套修正了 multithreaded 及 Ruby 1.9 的 Encoding 問題

升級

如果是升級,重點 Bundler 和 Views 部分必須先搞懂,不然完全動不了。其他 API 變更的部份(Routing, ActiveRecord, ActionMailer) 在 Rails 3.1 之前暫時是可以 works。當然有空還是趕快研究一下囉。以下是兩篇有關升級的新文章:

* Upgrading a Rails 2 App to Rails 3
* The Road to Rails 3: make your Rails 2.3 project more Rails 3 oriented

Rails 本身升級是沒有問題,怕的是周邊的 Plugin 沒能跟上。railsplugins.org 有整理一些,可以找找 github 上有沒有 branch,而 Rails3 透過 Bundle 安裝還蠻方便的。

1. Bundler

* Bundler: Rails3 用來管理 Gem dependencies 的神器

2. ActiveRecord Query Interface

Rails2 的 finders, named_scope, with_scope 等用法,在 Rails3 統一為一種 Relation 用法,可以更容易做 composable(chainable)、Decoupled from SQL 以及 Lazy Loading。另外如果是有傳參數的 scope,也偏好定義成 class method,而不是 scope + lambda 了。以下是一些不錯的文章:

* Active Record Query Interface 3.0 基本用法介紹
* ActiveRelation: ActiveRecord Gets a Facelift 基本用法介紹
* Active Record Queries in Rails 3 基本用法介紹
* Advanced Queries in Rails 3 進階用法介紹
* The Skinny on Scopes (Formerly named_scope) 進階用法介紹,跨 Model 的 Scope 太厲害了。
* New Active Record scoping syntax 如果你在 Rails2 有使用 with_scope 或 with_exclusive_scope( :find => x, :create => y ) 請讀這篇。新的 Rails3 建議你改用 scoping, unscoped, create_with 等新用法,因為舊的用法行為有差異。

3. ActiveRecord Validation

* AR Validation 提供了新的 API 只要一行就可以整合同一個屬性的多個驗證,可以減少行數。
* 可以客製 EachValidator 整合進上述的新 API 之中
* 可以客製 Validator 包裝一大堆 validations,例如 validates_with ReallyComplexValidator
* save(false) 終於拿掉了,要跳過驗證儲存請 save(:validate => false)

* Validations in Rails 3
* Improved validations in Rails 3
* validates :rails_3, :awesome => true
* Independent Model Validators

另外就是驗證錯誤的 API 也改了:

* Errors#on 跟 on_base 拿掉了,請改用 Errors#[]
* Errors#add_to_base(msg) 拿掉了,請改用 Errors#[:base] << msg * Views 裡面常用的 error_messages_for 跟 error_message_on Helper 被拿掉了,請自己寫過(你可以參考 scafffold 產生出來的程式)或裝回 legacy plugin

4. View

* Rails3 如何換使用 jQuery
* Unobtrusive JavaScript in Rails 3
* 所有有傳 code block 的 Helper 都改成 <%= 的形式,
請參考 Rails3 如何寫 Helper 可以傳 Block 參數 一文
* Rails3 處理 XSS: Rails 3 Security Strategy,HTML 預設都會逸出了,不要逸出請用 raw Helper 或字串的 html_safe! 方法。

5. I18n

* 詞彙檔中的變數替換由 {{}} 變成 %{} 了。這有點惱人,我還碰到 redmine 的問題(redmine 使用 Rails2,但是卻因為裝了 Rails3 所以 i18n 也升級到 0.4.1 版本就爆炸了)
* 提供更多 backends,例如 ActiveRecord
* 加速功能!! Run, I18n, run!

6. Routing

* Rails3 重點導覽:新的 Routes
* The Lowdown on Routes in Rails 3 很詳盡的用法介紹
* The Powerful New Rails Router 進階用法
* Rack in Rails 3 結合 Rack 端點的應用

7. ActionMailer

底層由 TMail 換 Mail gem,一整個砍掉重練 API 大改。原本動態的 Notifiter.deliver_welcome_email 寫法變成 Notifiter.welcome_email.deliver,其中的 Notifiter.welcome_email 是一個 Mail::Message 物件。

* A Whole New ActionMailer
* Action Mailer in Rails 3
* 一個範例
* Inline Attachments for ActionMailer

8. Metal

* Rails3: 新的 Metal 機制