# 1.4 使用 Git 做版本控制
我們已經開發了一個可以運行的 Rails 應用,接下來要花點時間來做一件事。雖然這件事不是必須的,但是經驗豐富的軟件開發者都認為這是最基本的事情,即把應用的源代碼納入版本控制。版本控制系統可以跟蹤項目中代碼的變化,便于和他人協作,如果出現問題(例如不小心刪除了文件)還可以回滾到以前的版本。每個專業級軟件開發者都應該學習使用版本控制系統。
版本控制系統種類很多,Rails 社區基本都使用 [Git](http://git-scm.com/)。Git 由 Linus Torvalds 開發,最初目的是存儲 Linux 內核代碼。Git 相關的知識很多,本書只會介紹一些皮毛。網絡上有很多免費的資料,我特別推薦 Scott Chacon 寫的《[Pro Git](http://git-scm.com/book)》。[[9](#fn-9)]之所以強烈推薦使用 Git 做版本控制,不僅因為 Rails 社區都在用,還因為使用 Git 分享代碼更簡單([1.4.3 節](#bitbucket)),而且也便于應用的部署([1.5 節](#deploying))。
## 1.4.1 安裝和設置
[1.2.1 節](#development-environment)推薦使用的云端 IDE 默認已經集成 Git,不用再安裝。如果你沒使用云端 IDE,可以參照 [InstallRails.com](http://installrails.com/) 中的說明,在自己的系統中安裝 Git。
### 第一次運行前要做的系統設置
使用Git 前,要做一些一次性設置。這些設置對整個系統都有效,因此一臺電腦只需設置一次:
```
$ git config --global user.name "Your Name"
$ git config --global user.email your.email@example.com
$ git config --global push.default matching
$ git config --global alias.co checkout
```
注意,在 Git 配置中設定的名字和電子郵件地址會在所有公開的倉庫中顯示。(前兩個設置必須做。第三個設置是為了向前兼容未來的 Git 版本。第四個設置是可選的,如果設置了,就可以使用 `co` 代替 `checkout` 命令。為了最大程度上兼容沒有設置 `co` 的系統,本書仍將繼續使用全名 `checkout`,不過在現實中我基本都用 `git co`。)
### 第一次使用倉庫前要做的設置
下面的步驟每次新建倉庫時都要執行。首先進入第一個應用的根目錄,然后初始化一個新倉庫:
```
$ git init
Initialized empty Git repository in /home/ubuntu/workspace/hello_app/.git/
```
然后執行 `git add -A` 命令,把項目中的所有文件都放到倉庫中:
```
$ git add -A
```
這個命令會把當前目錄中的所有文件都放到倉庫中,但是匹配特殊文件 `.gitignore` 中模式的文件除外。`rails new` 命令會自動生成一個適用于 Rails 項目的 `.gitignore` 文件,而且你還可以添加其他模式。[[10](#fn-10)]
加入倉庫的文件一開始位于“暫存區”(staging area),這一區用于存放待提交的內容。執行 `status` 命令可以查看暫存區中有哪些文件:
```
$ git status
On branch master
Initial commit
Changes to be committed:
(use "git rm --cached <file>..." to unstage)
new file: .gitignore
new file: Gemfile
new file: Gemfile.lock
new file: README.rdoc
new file: Rakefile
.
.
.
```
(顯示的內容很多,所以我使用豎排點號省略了一些內容。)
如果想告訴 Git 保留這些改動,可以使用 `commit` 命令:
```
$ git commit -m "Initialize repository"
[master (root-commit) df0a62f] Initialize repository
.
.
.
```
旗標 `-m` 的意思是為這次提交添加一個說明。如果沒指定 `-m` 旗標,Git 會打開系統默認使用的編輯器,讓你在其中輸入說明。(本書所有的示例都會使用 `-m` 旗標。)
有一點很重要要注意:Git 提交只發生在本地,也就是說只在執行提交操作的設備中存儲內容。[1.4.4 節](#branch-edit-commit-merge)會介紹如何把改動推送(使用 `git push` 命令)到遠程倉庫中。
順便說一下,可以使用 `log` 命令查看提交的歷史:
```
$ git log
commit df0a62f3f091e53ffa799309b3e32c27b0b38eb4
Author: Michael Hartl <michael@michaelhartl.com>
Date: Wed August 20 19:44:43 2014 +0000
Initialize repository
```
如果倉庫的提交歷史很多,可能需要輸入 `q` 退出。
## 1.4.2 使用 Git 有什么好處
如果以前從未用過版本控制,現在可能不完全明白版本控制的好處。那我舉個例子說明一下吧。假如你不小心做了某個操作,例如把重要的 `app/controllers/` 文件夾刪除了:
```
$ ls app/controllers/
application_controller.rb concerns/
$ rm -rf app/controllers/
$ ls app/controllers/
ls: app/controllers/: No such file or directory
```
我們用 Unix 中的 `ls` 命令列出 `app/controllers/` 文件夾里的內容,然后用 `rm` 命令刪除這個文件夾。旗標 `-rf` 的意思是“強制遞歸”,無需明確征求同意就遞歸刪除所有文件、文件夾和子文件夾等。
查看一下狀態,看看發生了什么:
```
$ git status
On branch master
Changed but not updated:
(use "git add/rm <file>..." to update what will be committed)
(use "git checkout -- <file>..." to discard changes in working directory)
deleted: app/controllers/application_controller.rb
no changes added to commit (use "git add" and/or "git commit -a")
```
可以看出,刪除了一個文件。但是這個改動只發生在“工作樹”中,還未提交到倉庫。所以,我們可以使用 `checkout` 命令,并指定 `-f` 旗標,強制撤銷這次改動:
```
$ git checkout -f
$ git status
# On branch master
nothing to commit (working directory clean)
$ ls app/controllers/
application_controller.rb concerns/
```
刪除的文件夾和文件又回來了,這下放心了!
## 1.4.3 Bitbucket
我們已經把項目納入 Git 版本控制系統了,接下來可以把代碼推送到 [Bitbucket](http://www.bitbucket.com/) 中。Bitbucket 是一個專門用來托管和分享 Git 倉庫的網站。(本書前幾版使用 [GitHub](http://www.github.com/),換用 Bitbucket 的原因參見[旁注 1.4](#aside-github-bitbucket)。)在 Bitbucket 中放一份 Git 倉庫的副本有兩個目的:其一,對代碼做個完整備份(包括所有提交歷史);其二,便于以后協作。
##### 旁注 1.4:GitHub 和 Bitbucket
目前,托管 Git 倉庫最受歡迎的網站是 GitHub 和 Bitbucket。這兩個網站有很多相似之處:都可托管倉庫,也可以協作,而且瀏覽和搜索倉庫很方便。但二者之間有個重要的區別(對本書而言):GitHub 為開源項目提供無限量的免費倉庫,但私有倉庫收費;而 Bitbucket 提供了無限量的私有倉庫,僅當協作者超過一定數量時才收費。所以,選擇哪個網站,取決于具體的需求。
本書前幾版使用 GitHub,因為它對開源項目來說有很多好用的功能,但我越來越關注安全,所以推薦所有 Web 應用都放在私有倉庫中。因為 Web 應用的倉庫中可能包含潛在的敏感信息,例如密鑰和密碼,可能會威脅到使用這份代碼的網站的安全。當然,這類信息也有安全的處理方法,但是容易出錯,而且需要很多專業知識。
本書開發的演示應用可以安全地公開,但這只是特例,不能推廣。因此,為了盡量提高安全,我們不能冒險,還是默認就使用私有倉庫保險。既然 GitHub 對私有倉庫收費,而 Bitbucket 提供了不限量的免費私有倉庫,就我們的需求來說,Bitbucket 比 Github 更合適。
Bitbucket 的使用方法很簡單:
1. 如果沒有賬戶,先[注冊一個 Bitbucket 賬戶](https://bitbucket.org/account/signup/);
2. 把[公鑰](https://en.wikipedia.org/wiki/Public-key_cryptography)復制到剪切板。云端 IDE 用戶可以使用 `cat` 命令查看公鑰,如[代碼清單 1.11](#listing-cat-public-key) 所示,然后選中公鑰,復制。如果你在自己的系統中,執行[代碼清單 1.11](#listing-cat-public-key) 中的命令后沒有輸出,請參照“[如何在你的 Bitbucket 賬戶中設定公鑰](https://confluence.atlassian.com/display/BITBUCKET/How+to+install+a+public+key+on+your+Bitbucket+account;jsessionid=C5CF09B1641344FE876045B35D720C94.node1)”;
3. 點擊右上角的頭像,選擇“Manage account”(管理賬戶),然后點擊“SSH keys”(SSH 密鑰),如[圖 1.13](#fig-add-public-key) 所示。
圖 1.13:添加 SSH 公鑰
##### 代碼清單 1.11:使用 `cat` 命令打印公鑰
```
$ cat ~/.ssh/id_rsa.pub
```
添加公鑰之后,點擊“Create”(創建)按鈕,[新建一個倉庫](https://bitbucket.org/repo/create),如[圖 1.14](#fig-create_first_repository) 所示。填寫項目的信息時,記得要選中“This is a private repository”(這是私有倉庫)。填完后點擊“Create repository”(創建倉庫)按鈕,然后按照“Command line > I have an existing project”(命令行 > 現有項目)下面的說明操作,如[代碼清單 1.12](#listing-bitbucket-add-push) 所示。(如果與代碼清單 1.12 不同,可能是公鑰沒正確添加,我建議你再試一次。)推送倉庫時,如果詢問“Are you sure you want to continue connecting (yes/no)?”(確定繼續連接嗎?),輸入“yes”。
##### 代碼清單 1.12:添加 Bitbucket,然后推送倉庫
```
$ git remote add origin git@bitbucket.org:<username>/hello_app.git
$ git push -u origin --all # 第一次推送倉庫
```
這段代碼的意思是,先告訴 Git,你想添加 Bitbucket,作為這個倉庫的源,然后再把倉庫推送到這個遠端的源。(別管 `-u` 旗標的意思,如果好奇,可以搜索“git set upstream”。)當然了,你要把 `<username>` 換成你自己的用戶名。例如,我運行的命令是:
```
$ git remote add origin git@bitbucket.org:mhartl/hello_app.git
```
推送完畢后,在 Bitbucket 中會顯示一個 `hello_app` 倉庫的頁面。在這個頁面中可以瀏覽文件、查看完整的提交信息,除此之外還有很多其他功能(如[圖 1.15](#fig-bitbucket-repository-page) 所示)。
圖 1.14:在 Bitbucket 中創建存放這個應用的倉庫圖 1.15:一個 Bitbucket 倉庫的頁面
## 1.4.4 分支,編輯,提交,合并
如果你跟著 [1.4.3 節](#bitbucket)中的步驟做,可能注意到了,Bitbucket 沒有自動識別倉庫中的 `README.rdoc` 文件,而在倉庫的首頁提醒沒有 README 文件,如[圖 1.16](#fig-bitbucket-no-readme) 所示。這說明 `rdoc` 格式不常見,所以 Bitbucket 不支持。其實,我以及我認識的幾乎所有人都使用 Markdown 格式。這一節,我們要把 `README.rdoc` 文件改成 `README.md`,順便還要在其中添加一些針對本書的內容。在這個過程中,我們將首次演示我推薦在 Git 中使用的工作流程,即“分支,編輯,提交,合并”。[[11](#fn-11)]
圖 1.16:Bitbucket 提示沒有 README 文件
### 分支
Git 中的分支功能很強大。分支是對倉庫的高效復制,在分支中所做的改動(或許是實驗性質的)不會影響父級文件。大多數情況下,父級倉庫是 `master` 分支。我們可以使用 `checkout` 命令,并指定 `-b` 旗標,創建一個新“主題分支”(topic branch):
```
$ git checkout -b modify-README Switched to a new branch 'modify-README'
$ git branch
master
* modify-README
```
其中,第二個命令 `git branch` 的作用是列出所有本地分支。星號(`*`)表示當前所在的分支。注意,`git checkout -b modify-README` 命令先創建一個新分支,然后再切換到這個新分支——`modify-README` 分支前面的星號證明了這一點。(如果你在 [1.4 節](#version-control-with-git)中設置了別名 `co`,就要使用 `git co -b modify-README`。)
只有多個開發者協作開發一個項目時,才能看出分支的全部價值。[[12](#fn-12)]如果只有一個開發者,分支也有作用。一般情況下,要把主題分支的改動和主分支隔離開,這樣即便搞砸了,隨時都可以切換到主分支,然后刪除主題分支,丟掉改動。本節末尾會介紹具體做法。
順便說一下,像這種小改動,我一般不會新建分支。現在我這么做是為了讓你養成好習慣。
### 編輯
創建主題分支后,我們要編輯 README 文件,讓其更好地描述我們的項目。較之默認的 RDoc 格式,我更喜歡 [Markdown 標記語言](http://daringfireball.net/projects/markdown/)。如果文件擴展名是 `.md`,Bitbucket 會自動排版其中的內容。首先,使用 Git 提供的 Unix `mv` 命令修改文件名:
```
$ git mv README.rdoc README.md
```
然后把[代碼清單 1.13](#listing-new-readme) 中的內容寫入 `README.md`。
##### 代碼清單 1.13:新 `README` 文件,`README.md`
```
# Ruby on Rails Tutorial: "hello, world!"
This is the first application for the
[*Ruby on Rails Tutorial*](http://www.railstutorial.org/)
by [Michael Hartl](http://www.michaelhartl.com/).
```
### 提交
編輯后,查看一下該分支的狀態:
```
$ git status On branch modify-README
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
renamed: README.rdoc -> README.md
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git checkout -- <file>..." to discard changes in working directory)
modified: README.md
```
這里,我們本可以使用“[第一次使用倉庫前要做的設置](#first-time-repository-setup)”一節用過的 `git add -A`,但是 `git commit` 提供了 `-a` 旗標,可以直接提交現有文件中的全部改動(以及使用 `git mv` 新建的文件,對 Git 來說這不算新文件):
```
$ git commit -a -m "Improve the README file" 2 files changed, 5 insertions(+), 243 deletions(-)
delete mode 100644 README.rdoc
create mode 100644 README.md
```
使用 `-a` 旗標一定要小心,千萬別誤用了。如果上次提交之后項目中添加了新文件,應該使用 `git add -A`,先告訴 Git 新增了文件。
注意,我們使用現在時(嚴格來說是祈使語氣)編寫提交消息。Git 把提交當做一系列補丁,在這種情況下,說明現在做了什么比說明過去做了什么要更合理。而且這種用法和 Git 命令生成的提交信息相配。詳情參閱《[Shiny new commit styles](https://github.com/blog/926-shiny-new-commit-styles)》。
### 合并
我們已經改完了,現在可以把結果合并到主分支了:
```
$ git checkout master Switched to branch 'master'
$ git merge modify-README Updating 34f06b7..2c92bef
Fast forward
README.rdoc | 243 --------------------------------------------------
README.md | 5 +
2 files changed, 5 insertions(+), 243 deletions(-)
delete mode 100644 README.rdoc
create mode 100644 README.md
```
注意,Git 命令的輸出中經常會出現 `34f06b7` 這樣的字符串,這是 Git 內部對倉庫的指代。你得到的輸出結果不會和我的一模一樣,但大致相同。
合并之后,我們可以清理一下分支——如果不用這個主題分支了,可以使用 `git branch -d` 命令將其刪除:
```
$ git branch -d modify-README Deleted branch modify-README (was 2c92bef).
```
這一步可做可不做,其實一般都會留著這個主題分支,這樣就可以在兩個分支之間來回切換,并在合適的時候把改動合并到主分支中。
前面提過,還可以使用 `git branch -D` 命令放棄主題分支中的改動:
```
# 僅作演示之用,如果沒搞砸,千萬別這么做
$ git checkout -b topic-branch
$ <really screw up the branch>
$ git add -A
$ git commit -a -m "Major screw up"
$ git checkout master
$ git branch -D topic-branch
```
和旗標 `-d` 不同,如果指定旗標 `-D`,即使沒合并分支中的改動,也會刪除分支。
### 推送
我們已經更新了 `README` 文件,現在可以把改動推送到 Bitbucket,看看改動的效果。之前我們已經推送過一次([1.4.3 節](#bitbucket)),在大多數系統中都可以省略 `origin master`,直接執行 `git push`:
```
$ git push
```
正如前面所說,Bitbucket 對使用 Markdown 編寫的文件做了精美排版,如[圖 1.17](#fig-new-readme) 所示。
圖 1.17:使用 Markdown 格式重寫的 `README` 文件
- Ruby on Rails 教程
- 致中國讀者
- 序
- 致謝
- 作者譯者簡介
- 版權和代碼授權協議
- 第 1 章 從零開始,完成一次部署
- 1.1 簡介
- 1.2 搭建環境
- 1.3 第一個應用
- 1.4 使用 Git 做版本控制
- 1.5 部署
- 1.6 小結
- 1.7 練習
- 第 2 章 玩具應用
- 2.1 規劃應用
- 2.2 用戶資源
- 2.3 微博資源
- 2.4 小結
- 2.5 練習
- 第 3 章 基本靜態的頁面
- 3.1 創建演示應用
- 3.2 靜態頁面
- 3.3 開始測試
- 3.4 有點動態內容的頁面
- 3.5 小結
- 3.6 練習
- 3.7 高級測試技術
- 第 4 章 Rails 背后的 Ruby
- 4.1 導言
- 4.2 字符串和方法
- 4.3 其他數據類型
- 4.4 Ruby 類
- 4.5 小結
- 4.6 練習
- 第 5 章 完善布局
- 5.1 添加一些結構
- 5.2 Sass 和 Asset Pipeline
- 5.3 布局中的鏈接
- 5.4 用戶注冊:第一步
- 5.5 小結
- 5.6 練習
- 第 6 章 用戶模型
- 6.1 用戶模型
- 6.2 用戶數據驗證
- 6.3 添加安全密碼
- 6.4 小結
- 6.5 練習
- 第 7 章 注冊
- 7.1 顯示用戶的信息
- 7.2 注冊表單
- 7.3 注冊失敗
- 7.4 注冊成功
- 7.5 專業部署方案
- 7.6 小結
- 7.7 練習
- 第 8 章 登錄和退出
- 8.1 會話
- 8.2 登錄
- 8.3 退出
- 8.4 記住我
- 8.5 小結
- 8.6 練習
- 第 9 章 更新,顯示和刪除用戶
- 9.1 更新用戶
- 9.2 權限系統
- 9.3 列出所有用戶
- 9.4 刪除用戶
- 9.5 小結
- 9.6 練習
- 第 10 章 賬戶激活和密碼重設
- 10.1 賬戶激活
- 10.2 密碼重設
- 10.3 在生產環境中發送郵件
- 10.4 小結
- 10.5 練習
- 10.6 證明超時失效的比較算式
- 第 11 章 用戶的微博
- 11.1 微博模型
- 11.2 顯示微博
- 11.3 微博相關的操作
- 11.4 微博中的圖片
- 11.5 小結
- 11.6 練習
- 第 12 章 關注用戶
- 12.1 “關系”模型
- 12.2 關注用戶的網頁界面
- 12.3 動態流
- 12.4 小結
- 12.5 練習