用Git做版本控制

版本控制系统可以跟踪项目代码的变化,便于和他人协作,如果出现问题(例如不小心删除了文件)还可以回滚到以前的版本。每个软件开发者都应该学习使用版本控制系统。

版本控制工具很多,Rails 社区更多的会使用 Git,它最初是由 Linus Torvalds 开发用来存储 Linux 内核代码的。Git 的知识很多,这里我们只会介绍一些简单的内容,网络上有很多免费的资料可以阅读,我特别推荐 Scott Chacon 的《Pro Git》(Apress 2009 年出版。中文版)。之所以推荐你将代码放到 Git 这个版本控制系统中是因为这几乎是 Rails 社区的普遍做法,还因为这样做更利于代码的分享,也便于程序的部署。

安装与设置

第一次运行的设置

安装Git后,你应该做一些只需做一次的事情:系统设置——这样的设置在每台电脑上只需做一次:

$ git config --global user.name "Your Name"
$ git config --global user.email your.email@example.com

我还想用co代替字数较多的checkout命令,那么要做如下设置:

$ git config --global alias.co checkout

最后,你还可以设置编辑 Git 提交信息时使用的编辑器。如果你使用的是图形界面的编辑器,例如 Sublime Text、TextMate、gVim 或 MacVim,要加上一个旗标确保编辑器会在终端中保持状态而不是立马结束命令:

$ git config --global core.editor "subl -w"

设置第一个仓库

下面的步骤你每次新建一个仓库时都要执行。首先进入刚创建的应用程序的根目录,然后初始化一个新仓库:

$ git init 
Initialized empty Git repository in /Users/mhartl/rails_projects/first_app/.git/

添加文件并提交

最后我们要把 Rails 项目中的文件添加到 Git 中,然后提交结果。你可以使用下述命令添加所有的文件(除了 .gitignore 中忽略的文件):

$ git add .

这里的点号(.)代表当前目录,Git 会自动的将所有的文件,包括子目录中的文件添加到 Git 中。这个命令会将项目的文件添加到暂存区域(staging area),这个区域包含未提交的改动。你可以使用 status 命令查看暂存区域有哪些文件

$ git status
# On branch master
#
# Initial commit
#
# Changes to be committed:
#   (use "git rm --cached <file>..." to unstage)
#
#       new file:   README.rdoc
#       new file:   Rakefile
.
.
.
(显示的结果很长,所以我用点号代替了。)

用 commit 命令告诉 Git 你想保存这些改动:

$ git commit -m "Initial commit"
[master (root-commit) df0a62f] Initial commit
42 files changed, 8461 insertions(+), 0 deletions(-)
create mode 100644 README.rdoc
create mode 100644 Rakefile
.
.
.

参数 -m 允许你为这次提交添加一个信息,如果没有提供 -m,Git 会打开你在前面设置的编辑器,你需要在编辑器中填写信息。

有一点很重要,Git 提交是针对本地的,数据只存在执行提交的电脑中。这一点和另一个很著名的开源版本控制系统 SVN 不同,SVN 提交时会更新远程仓库。git 将 SVN 中的提交分成了两部分:本地保存的更改(git commit)和将更改推送到远程仓库(git push)。

顺便说一下,你可以使用 log 命令查看提交的历史信息:

$git log
commit df0a62f3f091e53ffa799309b3e32c27b0b38eb4
Author: Michael Hartl <michael@michaelhartl.com>
Date:   Thu Oct 15 11:36:21 2009 -0700
Initial commit

Git为我们带来了什么好处?

现在你可能还不是完全清楚将源码纳入版本控制系统有什么好处,那我就举个例子来说明一下吧。(后续章节中还有很多例子)假设你不小心做了一些改动,比如说删除了 app/controllers/ 文件夹:

$ ls app/controllers/
application_controller.rb
$ 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

删除的文件夹和文件又回来了,这下放心了!

分支,编辑,提交,合并

分支

Git 中的分支功能很强大,分支是对仓库的复制,在分支中所做的改动(或许是实验性质的)不会影响父级文件。大多数情况下,父级仓库是 master 分支。我们可以使用 checkout 命令,并指定 -b 旗标创建一个新分支:

$ 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 前面的星号证明了这一点。

分支的唯一价值是在多个开发人员协同开发一个项目时使开发的过程更明了,不过对只有一个开发者的项目(比如本教程)也有用。一般而言,主分支是和从分支隔离开的,所以即便我们搞砸了也只需切换回到主分支并删除从分支来丢掉改动。在本节末尾我们会看到怎么做。

顺便说一下,对于较小的改动我一般不会动用新分支,这里是对好的习惯做一个演示。

编辑

创建了从分支后,我们要编辑文件让其更好的描述我们的项目。较之默认的 RDoc 格式,我更喜欢 Markdown 标记语言,如果文件扩展名是 .md,GitHub 会自动为你排版。首先我们使用 Unix 命令 mv(移动,move)的 Git 版本来修改文件名,然后写入代码 1.8 所示的内容:

$ git mv README.rdoc README.md
$ subl README.md

代码 1.8 新的 README 文件,README.md

# Ruby on Rails Tutorial: first application
This is the first application for
[*Ruby on Rails Tutorial: Learn Rails by Example*](http://railstutorial.org/)
by [Michael Hartl](http://michaelhartl.com/).

提交

编辑后,查看一下该分支的状态:

$ git status
# On branch modify-README
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
#       renamed:    README.rdoc -> README.md
#
# Changed but not updated:
#   (use "git add <file>..." to update what will be committed)
#   (use "git checkout -- <file>..." to discard changes in working directory)
#
#       modified:   README.md
#

这时,我们可以使用git add .来提交,但是 Git 提供了参数 -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 告诉 Git 你添加的文件。

合并

我们已经修改完了,现在可以将其合并到主分支了:

$ 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 branch -d 删除这个从分支:

$ git branch -d modify-README
Deleted branch modify-README (was 2c92bef).

这一步是可选的,事实上一般我们都会留着这个从分支,这样你就可以在主、从分支之间来回切换,在合适的时候将改动合并到主分支中。

如前面提到的,你可以使用 git branch -D 放弃对从分支所做的修改:

# For illustration only; don't do this unless you mess up a branch
$ git checkout -b topic-branch
$ <really screw up the branch>
$ git add .
$ git commit -a -m "Major screw up"
$ git checkout master
$ git branch -D topic-branch

和参数 -d 不同,即使还未合并 -D 也会删除分支。