系列文章原载于自己的博客,TOPI.CO (http://topi.co) ,某天不小心就 push 错啦,懒得从头再来,上传到 Ruby-China 来,一是方便自己回顾,另外也方便跟我一样的初学者
欲使用 Git 进行版本控制,首先要基于现有文档建立项目仓库。下面以本文档的版本管理为例,演示如何将其作为项目并纳于 Git 的版本控制之下。
设想源文档皆位于 $HOME/myproject 目录下,下文为叙述方便,以 Bash 变量 $WORK 代替该目录。首先需初始化 Git 仓库:
$ cd $WORK
$ git init
Git 会作出以下回应:
Initialized empty Git repository in /path/to/project/.git/
上述操作的结果是在 $WORK 目录下创建了一个 .git 隐藏目录,它就是所谓的 Git 仓库,不过现在它还是空的。另外 $WORK 目录也不再是普通的文档目录了,今后我们将其称为工作树。
下面应当有选择地将工作树中的一些文档存储至 Git 仓库中。由于 Git 在向仓库中添加文档时并非是简单地文档复制过去,势必要将所添加文档进行一番处理,生成 Git 仓库所能接受的数据格式,Git 称这个过程为 "生成快照 (take a snapshot)"。若将工作树下所有文档 (包含子目录) 生成快照,可采用以下命令:
$ cd $WORK
$ git add .
所生成的快照被存放到一个临时的存储区域,Git 称该区域为索引。使用git-commit
命令可将索引提交至仓库中,这个过程称为提交,每一次提交都意味着版本在进行一次更新。git-commit 最简单的用法如下:
$ git commit
执行上述 git-commit 命令时,Git 会自动调用系统默认的文本编辑器,要求你输入版本更新说明并保存。请记住,输入简约的版本更新说明是非常有必要的,它就像剧本一样,可以帮助你快速回忆起对项目的重大改动。
对于简短的版本更新信息,可以使用 git-commit 的“-m”选项,如下:
$ git commit -m "你的版本更新信息"
上述过程即为建立 Git 仓库的一般过程,我将其总结为图 1.1 所示之流程 init -> add -> commit
:
{% img /images/local_operations.png 400 368 [Local Operations] %}
在使用 Git 之前,你需要配置个人信息,参见 Git 使用指南 (上)。Git 不喜欢匿名的人,因为它要求每个人在向仓库提交数据时,都应当承担一定的责任。配置个人信息,请使用以下命令:
$ git config --global user.name "username"
$ git config --global user.email "[email protected]"
另外,在生成文档内容快照时,工作树中有一些文档是你不希望接受 Git 管理的,譬如程序编译时生成的中间文件,对于这样的文件如何避免为之生成快照?
譬如对于上一节的用例,在工作树中存在以下文件(或子目录):
doc-env.tex git-tutor.tex Makefile zh
git-tutor main.tex vfonts.tex
其中的 zh 目录存放着 TEX 文档编译时生成的中间文件,因此该目录不应该被 Git 所管理。为解决此类问题,Git 提供了文档忽略机制,可以将工作树中你不希望接受 Git 管理的文档信息写到同一目录下的.gitignore
文件中。对于本例中的
zh 目录,采用如下操作可将其排除仓库之外,然后再对 $WORK 生成快照即可。
$ cd $WORK
$ echo "zh" > .gitignore
$ git add .
有关 gitignore 文件的诸多细节知识可阅读其使用手册:
$ man gitignore
###仓库与工作树
按照前文的说法,Git 仓库就是那个 .git 目录,其中存放的是我们所提交的文档索引内容,Git 可基于文档索引内容对其所管理的文档进行内容追踪,从而实现文档的版本控制。工作树是包含 .git 的目录,在前文示例中即 $WORK 目录。
为了更加明确仓库与工作树的概念,下面做一个实验:
$ cp -R $WORK/.git /tmp/myproject.git
$ cd /tmp
$ git clone myproject.git another_project
首先,我们将 $WORK 目录中的 .git 目录复制到 /tmp 目录下并进行重命名 myproject.git,然后使用 git-clone 命令从 myproject.git 中生成 another_project 目录。若进入 another_project 目录观察一下,就会发现该目录所包含的内容是等同于 $WORK 目录的。
上述实验意味着,只要我们拥有仓库,即 myproject.git,那么就可以很容易地生成工作树,而这个工作树又包含着一个仓库,即 another_project/.git。所以,我们可以这样理解:在 Git 中,仓库与工作树之间无需分的很清楚。
在工作树中,我们日常所进行的工作无非是对 Git 仓库所管理的文档进行修改,或者添加/删除一些文件。这些操作与采用 Git 管理我们的文档之前没有任何差异,只是在你认为一个工作阶段完成之时,要记得通知 Git,命令它记下你所进行更新,这一步骤是通过生成文档快照并将其加入到索引中来实现的。
譬如今天,我向 $WORK 目录添加了一份新文档 first.rb,我需要通知 Git 记住我的这一更新:
$ cd $WORK
$ git add first.rb
这样,Git 就会将有关 first.rb 的更新添加到索引中。然后我又对其它文档进行了一些修改,譬如修改了 config.rb 以及 database.yml 文件,继续使用 git-add 命令将它们的更新添加到索引中:
$ git add config.rb database.yml
晚上,这一天的工作告以段落,我觉得有必要将今天所做的提交到仓库中,于是执行 git-commit 操作,将索引内容添加到仓库中。
可能一天下来,你对工作树中的许多文档都进行了更新 (文档添加、修改、删除),但是我忘记了它们的名字,此时若将所做的全部更新添加到索引中,比较省力的做法就是:
$ cd $WORK
$ git add .
$ git commit -a
... 输入日志信息 ...
其中,git-add .
命令通常能够判断出当前目录 (包括其子目录) 下用户所添加的新文档,并将其信息追加到索引中。git-commit -a
选项可将所有被修改的文档或者已删除的文档的当前状态提交倒仓库中。
记住,如果只是修改或者删除了已被 Git 管理的文档,没必要使用 git-add 命令的。
本节并未讲述新的 Git 命令,完全是前面所讲过的一些命令的重复介绍,只是它们出现的场景有所区别而已。另外,要注意的问题是,Git 不会主动记录你对文档进行的更新,除非你对它发号施令。
###查看版本历史
在工作树中,使用git-log
命令可以查看当前项目的日志,也就是你在使用 git-commit 向仓库提交新版本时所属如的版本更新信息。
$ git log
如果你想看一下每一次版本的大致变动情况,可使用以下命令:
$ git log --stat --summary
下面分析一下 git-log 命令的回应信息。
git-log 命令给出了以下回应信息:
commit efb02d6e4f2f7b563337762e5c0013805e392618
Author: yourname <[email protected]>
Date: Sun Feb 26 14:30:25 2012 +0800
Your Project项目初始化
commit da4b9ce37531bbb43d8187d7a651e228e26f1212
Author: yourname <[email protected]>
Date: Sun Feb 26 14:50:07 2012 +0800
添加 .gitignore 文件
commit 859640634390eb7f3fb2ad45ecb8731435031e60
Author: yourname <[email protected]>
Date: Sun Feb 26 15:00:50 2012 +0800
添加 config.rb 和 database.yml 文件
lines 1-17/17 (END)
从上面的项目日志信息中可以看到对 myproject 项目所做的一些阶段性工作,每一个版本都对应着一次项目版本更新提交。在项目日志信息中,每条日志的首行 (就是那一串莫名奇妙的数字) 为版本更新提交所进行的命名,我们可以将该命名理解为项目版本号。项目版本号应该是唯一的,默认由 Git 自动生成,用以标示项目的某一次更新。如果我们将项目版本号用作git-show
命令的参数,即可查看该次项目版本的更新细节:
$ git show da4b9ce37531bbb43d8187d7a651e228e26f1212
除了使用完整的版本号查看项目版本更新细节之外,也还可以使用以下方式:
$ git show da4b # 一般只使用版本号的前几个字符即可
$ git show HEAD # 显示当前分支的最新版本的更新细节
每一个项目版本号通常都对应存在一个父版本号,也就是项目的前一次版本状态。可使用如下命令查看当前项目版本的父版本更新细节:
$ git show HEAD^ # 查看 HEAD 的父版本更新细节
$ git show HEAD^^ # 查看 HEAD 的祖父版本更新细节
$ git show HEAD~4 # 查看 HEAD 的祖父之祖父的版本更新细节
你可以对项目版本号进行自定义 (添加 tag),然后就可以使用自定义的版本号查看对应的项目版本更新细节:
$ git tag v0.1 dfb02
$ git show
实际上,上述命令并非是真正的进行版本号自定义,只是制造了一个 tag 对象而已,这在进行项目版本对外发布时比较有用。本文档后续章节会对 tag 的一些细节进行介绍。
版本控制系统的一个重要任务就是提供撤销和恢复某一阶段工作的功能。git-reset
命令就是为这样的任务而准备的,它可以将项目当前版本定位到之前提交的任何版本中。
git-reset 命令有三个选项:--mixed、 --soft 和 --hard。我们在日常使用中仅使用前两个选项;第三个选项由于杀伤力太大,容易损坏项目仓库,需谨慎使用。
--mixed 是 git-reset 的默认选项,它的作用是重置索引内容,将其定位到指定的项目版本,而不改变你的工作树中的所有内容,只是提示你有哪些文件还未更新。 --soft 选项既不触动索引的位置,也不改变工作树中的任何内容,但是会要求它们处于一个良好的次序之内。该选项会保留你在工作树中的所有更新并使之处于待提交状态。
你可以随便创建一个 Git 仓库并向其提交一些版本更新,然后测试 --mixed 与 --soft 选项的效果。
如果欲查看 git-reset 命令对工作树的影响,可使用git-status
命令。
前文中,我一直没有解释这样一个现象,那就是在正文中,总是使用类似 git-reset 这样的命令形式,但是在终端中实际输入这些指令时,所采用的命令形式又变为 git reset。
我猜测这样做的原因是后者作为命令形式对于用户更为友好一些,因为我们已经习惯了在终端中输入这样的命令格式。但是在查阅命令的说明文档时,需要使用第一种命令格式,譬如要查看 git reset 命令的用法:
$ man git-reset