创建版本库及第一次提交
通过如下操作来查看一下您的Git版本。
$ git --version
git version 1.7.4
在开始 Git 之旅之前,我们需要设置一下 Git 的配置变量,这是一次行的工作。即这些设置会在全局文件(用户目录下的.gitconfig)或系统文件(如/etc/gitconfig)中做永久记录。
(1) 告诉 Git 当前用户的姓名和邮件地址,配置的用户名和邮件地址代替这里的用户名和邮件地址,
$ git config --gloabal user.name "Uetucci Luo"
$ git config --globbal user.email "uetucii@163.com"
(2) 设置一些 Git 别名,以便可以使用更为简洁的子命令。
例如:输入 git ci 即相当于 git commit,输入 git st 即相当于 git status。
如果拥有系统管理员权限(例如通过执行 sudo 命令获取管理员权限),希望注册的命令别名能够悲所有用户使用,可以使用如下命令:
$ sudo git config --system alias.st status
$ sudo git config --system alias.ci commit
$ sudo git config --system alias.co checkout
$ sudo git config --system alias.br branch
也可以运行下面的命令,只在本用户的全局配置中添加 Git 命令别名:
$ git config --global alias.st status
$ git config --global alias.ci commit
$ git config --global alias.co checkout
$ git config --global alias.br branch
(3) 在 Git 命令输出中开启颜色显示。
$ git config --global color.ui true
下面就从一个空目录开始初始化版本库,将这个版本库命名为"DEMO版本库",为了方便说明,我们使用名为 /path/to/my/workspace 的目录作为个人的工作区根目录,可以在磁盘中创建该目录病设置正确的权限。
首先建立一个新的工作目录,进入该目录后,执行 git init 创建版本库。
$ cd /path/to/my/worksapce
$ mkdir demo
$ cd demo
$ git init
Initialized empty Git repository in /path/to/my/workspace/demo/.git/
实际上,如果 Git 的版本是 1.6.5 或更新的版本,可以在 git init 命令的后面直接输入目录的名称,自动完成目录的创建。
$ cd /path/to/my/workspace
$ git init demo
Initialized empty Git repository in /path/to/my/workspace/demo/.git/
$ cd demo
从上面版本库初始化的输出中可以看到, git init 命令在工作区创建了隐藏目录 .git。
$ ls -aF
./ ../ .git/
这个隐藏的 .git 目录就是 Git 版本库(又叫仓库,repository)。
.git 版本库所在的目录为 /path/to/my/workspace/demo,它被称为工作区。
下面为工作区中加点料:在工作区中创建一个 welcome.txt,内容就是一行 "Hello."。
$ echo "Hello." > welcome.txt
为了将这个新建立的文件添加到版本库,需要执行下面的命令:
$ git add welcome.txt
Git和大部分其他版本控制系统都需要再执行一次提交操作,对于Git来说就是执行git commit命令完成提交。在提交过程中需要输入提交说明,这个要求对于Git来说是强制性的,不像其他很多版本控制系统(如CVS、Subversion)允许空白的提交说明。
在Git提交时,如果在命令行不提供提交说明(没有使用-m参数),Git会自动打开一个编辑器,要求您在其中输入提交说明,输入完毕保存退出。
下面进行提交。为了说明方便,使用-m参数直接给出了提交说明。
$ git ci -m "initialized"
[master (root-commit) 229fdd0] initialized
1 files changed, 1 insertions(+), 0 deletions(-)
create mode 100644 welcome.txt
从上面的命令及输出可以看出:
使用了 Git 命令别名,即git ci相当于执行git commit。在一开始就进行了Git命令别名的设置。
通过-m参数设置提交说明为:”initialized”。该提交说明也显示在命令输出的第一行中。
命令输出的第一行还显示了当前处于名为master的分支上,提交ID为229fdd0,且该提交是该分支的第一个提交,即根提交(root-commit)。根提交和其他提交的区别在于没有关联的父提交,
命令输出的第二行开始显示本次提交所做修改的统计:修改了一个文件,包含一行的插入。
从命令的输出的第二行可以看出,此次提交创建了新文件 welcome.txt。
思考:为什么工作区根目录下有一个 .git 目录
Git及其他分布式版本控制系统(如Mercurial/Hg、Bazaar)的一个显著特点是,版本库位于工作区的根目录下。对于Git来说,版本库位于工作区根目录下的.git目录中,且仅此一处,在工作区的子目录下则没有任何其他跟踪文件或目录。
传统的集中式版本控制系统等版本库和工作区式分开的,甚至是在不同的主机上,因此必须建立工作区和版本库的对应。
Git这种将版本库放在工作区根目录下,所有的版本控制操作除了和其他远程版本库之间的互操作)都在本地即可完成,不像Subversion只有寥寥无几的几个命令才能脱离网络执行。而且Git也没有CVS和Subversion的安全泄漏问题(只要保护好.git目录),也没有Subversion在本地文件搜索时出现搜索结果混乱的问题,甚至Git还提供了一条git grep命令来更好地搜索工作区的文件内容。例如:
$ git grep "工作区文件内容搜索"
02-git-solo/010-git-init.rst::command:`git grep`\ 命令来更好地搜索工作区的文件内容。
当工作区中包含了子目录,在子目录中执行Git命令时,如何定位版本库呢?
实际上,当在Git工作区目录下执行操作的时候,会对目录依次向上递归查找.git 目录,找到的.git目录就是工作区对应的版本库,.git所在的目录就是工作区的根目录,文件.git/index记录了工作区文件的状态(实际上是暂存区的状态)。
例如在非Git工作区执行git命令,会因为找不到.git目录而报错。
$ cd /path/to/my/workspace
$ git status
fatal: Not a git repository (or any of the parent directories): .git
如果用 strace 命令区跟踪执行git status命令时的磁盘访问,会看到沿目录依次向上递归的过程。
$ strace -e 'trace=file' git status
...
getcwd("/path/to/my/workspace", 4096) = 14
...
access(".git/objects", X_OK) = -1 ENOENT (No such file or directory)
access("./objects", X_OK) = -1 ENOENT (No such file or directory)
...
chdir("..") = 0
...
access(".git/objects", X_OK) = -1 ENOENT (No such file or directory)
access("./objects", X_OK) = -1 ENOENT (No such file or directory)
...
chdir("..") = 0
...
access(".git/objects", X_OK) = -1 ENOENT (No such file or directory)
access("./objects", X_OK) = -1 ENOENT (No such file or directory)
fatal: Not a git repository (or any of the parent directories): .git
那么有什么办法知道Git版本库的位置,以及工作区的根目录在哪里呢?
可以用Git的一个底层命令来实现,具体操作过程如下:
(1) 在工作区中建立目录a/b/c,进入到该目录中。
$ cd /path/to/my/workspace/demo
$ mkdir - a/b/c
$ cd /path/to/my/workspace/demo/a/b/c
(2) 显示版本库 .git 所在的位置。
$ git rev-parse --git-dir
/path/to/my/workspace/demo/.git
(3) 显示工作区根目录
$ git rev-parse --show-toplevel
/path/to/my/workspace/demo/
(4) 相对于工作区根目录的相对目录。
$ git rev-parse --show-prefix
a/b/c
(5) 显示从当前目录(cd) 后退 (up) 到工作区的根的深度。
$ git rev-parse --show-cdup
../../../
把版本库.git
目录放在工作区,是不是太不安全了?
从存储安全的角度上来讲,将版本库放在工作区目录下,有点“把鸡蛋装在一个篮子里”的味道。如果忘记了工作区中还有版本库,直接从工作区的根执行目录删除就会连版本库一并删除,这个风险的确是蛮高的。
Git克隆可以降低因为版本库和工作区混杂在一起导致的版本库被破坏的风险。可以通过克隆版本库,在本机另外的磁盘/目录中建立Git克隆,并在工作区有改动提交时,手动或自动地执行向克隆版本库的推送(git push)操作。如果使用网络协议,还可以实现在其他机器上建立克隆,这样就更安全了(双机备份)。对于使用Git做版本控制的团队,每个人都是一个备份,因此团队开发中的Git版本库更安全,管理员甚至根本无须顾虑版本库存储安全问题。
思考:git config 命令的各参数有和区别?
在之前出现的git config命令,有的使用了--global参数,有的使用了--system参数,这两个参数有什么区别么?
- 执行下面的命令,将打开 /path/to/my/worksapce/demo/.git/config 文件进行编辑。
$ cd /path/to/my/worksapce/demo/
$ git config -a
- 执行下面的命令,将打开 /home/jiangxin/.gitconfig (用户主目录下的 .gitconfig 文件)全局配置文件进行编辑。
$ git config -e --gloabal
- 执行下面的命令,将打开/etc/gitconfig系统级配置文件进行编辑。如果 Git 安装在非标准位置,则这个系统级的配置文件也可能是在另外的位置。
$ git config -e --system
Git的三个配置文件分别是版本库级别的配置文件、全局配置文件(用户主目录下)和系统级配置文件(/etc目录下)。其中版本库级别配置文件的优先级最高,全局配置文件其次,系统级配置文件优先级最低。
Git 配置文件采用的是INI文件格式。实例如下:
[core]
repositoryformatversion = 0
filemode = true
bare = false
logallrefupdates = true
git config 命令可以用于读取和更改INI配置文件的内容。使用只带一个参数的 git config <section>.<key> 命令可用来读取INI配置文件中某个配置的键值,例如读取 [core] 小节的bare的属性值,可以用如下命令:
$ git config core.bare
false
如果想更改或设置INI文件中某个属性的值也非常简单,命令格式是:git config <section>.<key> <value>。可以用如下操作:
$ git config a.b something
$ git config x.y.z others
如果打开.git/config文件,会看到如下内容:
[a]
b = something
[x "y"]
z = others
可以用git config命令操作任何其他的INI文件。
- 想配置文件 test.ini 中添加配置。
$ GIT_CONFIG=test.ini config a.b.c.d "hello, world"
- 从配置文件 test.ini 中读取配置。
$ GIT_CONFIG=test.ini git config a.b.c.d
hello, world
思考:是谁完成的提交
一开始,先为Git设置了user.name和user.email全局环境变量,如果不设置会有什么结果呢?
执行下面的命令,删除Git全局配置文件中关于user.name和user.email的设置:
$ git config --unset --global user.name
$ git config --unset --global user.email
执行下面的命令将看不到输出。
$ git config user.name
$ git config user.email
下面再尝试进行一次提交,看看提交的过程会有什么不同,以及提交之后显示的提交者是谁?
在下面的命令中使用了--allow-empty参数,这是因为没有对工作区的文件进行任何修改,Git默认不会执行提交,使用了--allow-empty参数后,允许执行空白提交。
$ cd /path/to/my/workspace/demo
$ git commit --allow-empty -m "who does commit?"
[master 252dc53] who does commit?
Committer: JiangXin <jiangxin@hp.moon.ossxp.com>
Your name and email address were configured automatically based
on your username and hostname. Please check that they are accurate.
You can suppress this message by setting them explicitly:
git config --global user.name "Your Name"
git config --global user.email you@example.com
If the identity used for this commit is wrong, you can fix it with:
git commit --amend --author='Your Name <you@example.com>'
因为没有设置user.name和user.email变量,提交输出乱得一塌糊涂。仔细看看上面执行git commit命令的输出,原来Git提供了详细的帮助指引来告诉如何设置必需的变量,以及如何修改之前提交中出现的错误的提交者信息。
如果此时查看版本库的提交日志,会看到有两次提交。
$ git log --pretty=fuller
commit 252dc539b5b5f9683edd54849c8e0a246e88979c
Author: JiangXin <jiangxin@hp.moon.ossxp.com>
AuthorDate: Mon Nov 29 10:39:35 2010 +0800
Commit: JiangXin <jiangxin@hp.moon.ossxp.com>
CommitDate: Mon Nov 29 10:39:35 2010 +0800
who does commit?
commit 9e8a761ff9dd343a1380032884f488a2422c495a
Author: Jiang Xin <jiangxin@ossxp.com>
AuthorDate: Sun Nov 28 12:48:26 2010 +0800
Commit: Jiang Xin <jiangxin@ossxp.com>
CommitDate: Sun Nov 28 12:48:26 2010 +0800
initialized.
最早的提交(下面的提交),提交者的信息是由之前设置的环境变量user.name和user.email给出的。而最新的提交(上面第一个提交)因为删除了user.name和user.email,提交时Git对提交者的用户名和邮件地址做了大胆的猜测,这个猜测可能是错的。
为了保证提交时提交者和作者信息的正确性,重新恢复user.name和user.email的设置。记住不要照抄照搬下面的命令,请使用您自己的用户名和邮件地址。
$ git config --global user.name "Uetucci Luo"
$ git config --global user.email "uetucii@163.com"
然后执行下面的命令,重新修改最新的提交,改正作者和提交者的错误信息
$ git commit --amend --allow-empty --reset-author
说明:
参数--amend是对刚刚的提交进行修补,这样就可以改正前面错误的提交(用户信息错误),而不会产生另外的新提交。
参数--allow-empty是因为要进行修补的提交实际上是一个空白提交,Git默认不允许空白提交。
参数--reset-author的含义是将Author(提交者)的ID重置,否则只会影响最新的Commit(提交者)的ID。这条命令也会重置AuthorDate信息。
通过日志,可以看到最新提交的作者和提交者的信息已经改正了。
$ git log --pretty=fuller
commit a0c641e92b10d8bcca1ed1bf84ca80340fdefee6
Author: Jiang Xin <jiangxin@ossxp.com>
AuthorDate: Mon Nov 29 11:00:06 2010 +0800
Commit: Jiang Xin <jiangxin@ossxp.com>
CommitDate: Mon Nov 29 11:00:06 2010 +0800
who does commit?
commit 9e8a761ff9dd343a1380032884f488a2422c495a
Author: Jiang Xin <jiangxin@ossxp.com>
AuthorDate: Sun Nov 28 12:48:26 2010 +0800
Commit: Jiang Xin <jiangxin@ossxp.com>
CommitDate: Sun Nov 28 12:48:26 2010 +0800
initialized.
思考:随意设置提交者姓名,是否不太安全
使用过CVS、Subversion等集中式版本控制系统的用户会知道,每次提交的时候须要认证,认证成功后,登录ID就作为提交者ID出现在版本库的提交日志中。很显然,对于CVS或Subversion这样的版本控制系统,很难冒充他人提交。那么像Git这样的分布式版本控制系统,可以随心所欲的设定提交者,这似乎太不安全了。
Git可以随意设置提交的用户名和邮件地址信息,这是分布式版本控制系统的特性使然,每个人都是自己版本库的主人,很难也没有必要进行身份认证从而使用经过认证的用户名作为提交的用户名。
在进行“独奏”的时候,还要为自己强制加上一个“指纹识别”实在是太没有必要了。但是团队合作时授权就成为必需了。不过一般来说,设置的Git服务器只会在个人向服务器版本库执行推送操作(推送其本地提交)的时候进行身份认证,并不对所推送的提交本身所包含的用户名作出检查。但Android项目是个例外。
Android项目为了更好的使用Git实现对代码的集中管理,开发了一套叫做Gerrit的审核服务器来管理Git提交,对提交者的邮件地址进行审核。例如下面的示例中在向Gerrit服务器推送的时候,提交中的提交者邮件地址为jiangxin@ossxp.com,但是在Gerrit中注册用户时使用的邮件地址为jiangxin@moon.ossxp.com。因为两者不匹配,从而导致推送失败。
$ git push origin master
Counting objects: 3, done.
Writing objects: 100% (3/3), 222 bytes, done.
Total 3 (delta 0), reused 0 (delta 0)
To ssh://localhost:29418/new/project.git
! [remote rejected] master -> master (you are not committer jiangxin@ossxp.com)
error: failed to push some refs to 'ssh://localhost:29418/new/project.git'
即使没有使用类似Gerrit的服务,作为提交者也不应该随意改变user.name和user.email的环境变量设置,因为当多人协同时这会给他人造成迷惑,也会给一些项目管理软件造成麻烦。
思考:命名别名是干什么的
一开始,通过对alias.ci等Git环境变量的设置,为Git设置了命令别名。命令别名可以帮助用户解决从其他版本控制系统迁移到Git后的使用习惯问题。像CVS和Subversion在提交的时候,一般习惯使用ci(check in)子命令,在检出的时候则习惯使用co(check out)子命令。如果Git不能提供对ci和co这类简洁命令的支持,对于拥有其他版本控制系统使用经验的用户来说,Git的用户体检就会打折扣。
实际上别名还可以包含命令参数。例如下面的别名设置指令:
$ git config --global alias.ci "commit -s"
如上设置后,当使用git ci命令提交的时候,会自动带上-s参数,这样会在提交的说明中自动添加上包含提交者姓名和邮件地址的签名标识,类似于Signed-off-by: User Name email@address。这对于一些项目(Git、Linux kernel、Android等)来说是必要甚至是必须的。
备份本章的工作成果
执行下面的命令,算是对本章工作成果的备份。
$ cd /path/to/my/workspace
$ git clone demo demo-step-1
Cloning into demo-step-1...
done.