一、 前言
版本控制系统是主要用于管理代码,它使得代码的更新迭代、备份、多人协作开发都变得简单,本文讲述的Git就是一款分布式版本控制系统,有别于CVS和SVN等集中式版本控制系统,Git可以让研发团队更加高效地协同工作,从而提高生产率。使用Git,开发人员的工作不会因为频繁地遭遇提交冲突而中断,管理人员也无须为数据的备份而担心。经过Linux这样的庞大项目的考验之后,Git 被证明可以胜任任何规模的团队,即便团队成员分布于世界各地。
二、常见的版本控制系统
早期人们将代码拷贝至各个软盘、文件目录中,通过使用命令diff和patch这种刀耕火种的方式来管理代码的更新迭代,但是当项目代码多了之后,可能连自己都已经记不清到底哪个是最后测试的代码版本,并且最后测试的代码版本有时也并非是众多版本中最优的版本,这极大的延误了开发工作
2.1 CSV
CVS全名是(Concurrent Versions System),是由荷兰阿姆斯特丹 VU 大学的Dick Grune 教于1985年实现的。当时 Dick Grune 和两个学生共同开发一个项目,但是三个人的工作时间无法协调到一起,迫切需要一个记录和协同开发的工具软件。于是Dick Grune通过脚本语言对RCS(一个针对单独文件的版本管理工具)进行封装,设计出有史以来第一个被大规模使用的版本控制工具CVS。到了1989年,Brian Berliner用C语言将CVS进行了重写。CVS采用了CS架构,版本库位于服务器端。实际上就是一个RCS文件容器。CVS成功的为后来的版本控制系统确立了标准,比如提交说明、检入、检出、里程碑、分支等概念,它的命令行格式也被后来的版本控制系统竞相模仿。
2.2 SVN
全称是Subversion,由于其命令行工具名为svn,因此通常被简称为SVN。SVN由CollabNet公司于2000年资助并开始开发,目的是创建一个更好用的版本控制系统以取代CVS。SVN的前期开发使用 CVS 做版本控制,到了2001年,SVN 已经可以用于自己的版本控制了。SVN最具有特色的功能是轻量级拷贝,轻松地解决了CVS存在的里程碑和分支的创建速度慢又不可见的问题,使用SVN 创建里程碑和分支只在眨眼之间。SVN在版本库授权上也有改进,不再像CVS那样依赖操作系统本身对版本库目录和文件进行授权,而是采用授权文件的方式来实现。SVN还有一个创举,就是在工作区跟踪目录下(.svn目录)为当前目录中的每一个文件都保存一份冗余的原始拷贝。这样做的好处是部分命令不再需要网络连接,例如文件修改的差异比较,以及错误更改的回退等。正是由于这些闪亮的功能特性,才使得SVN在CVS之后诞生的诸多版本控制系统中脱颖而出,成为开源社区一时的新宠,也成为当时各个企业进行版本控制的最佳选择之一。但是,相对于CVS,SVN在本质上并没有突破,都属于集中式版本控制系统。即一个项目只有唯一的一个版本库与之对应,所有的项目成员都通过网络向该服务器进行提交。这样的设计除了容易出现单点故障以外,在查看日志和提交数据等操作时的延迟,会让基于广域网协同工作的团队抓狂。
2.3 Git
Linux 之父 Linus是坚定的CVS反对者,他也同样地反对SVN。这就是为什么在 1991-2002 这十余年间,Linus 宁可以手工修补文件的方式维护代码,也迟迟不愿使用CVS的原因。2002年至2005年,Linus顶着开源社区精英们口诛笔伐的压力,选择了一个商业版本控制系统–BitKeeper作为Linux内核的代码管理工具。BitKeeper不同于CVS和SVN 等集中式版本控制工具,而是一款分布式版本控制工具。分布式版本控制系统最大的反传统之处在于,可以不需要集中式的版本库,每个人都工作在通过克隆建立的本地版本库中。也就是说每个人都拥有一个完整的版本库,查看提交日志、提交、创建里程碑和分支、合并分支、回退等所有操作都直接在本地完成而不需要网络连接。每个人都是本地版本库的主人,不再有谁能提交谁不能提交的限制,加上多样的协同工作模型让开源项目的参与度有爆发式增长。
在2005年4月,Andrew Tridgell(即大名鼎鼎的 Samba 的作者)试图对 BitKeeper 进行反向工程,以开发一个能与 BitKeeper 交互的开源工具。这激怒了BitKecper软件的所有者BitMover 公司,要求收回对 Linux 社区免费使用 BitKceper 的授权。迫不得已,Linus选择了自己开发一个分布式版本控制工具以替代BitKeeper。2005年4月3日–开始开发Git、2005年4月6日–项目发布、2005年4月7日–Git就可以作为自身的版本控制工具了、2005年4月18日–发生第一个多分支合并、2005年4月29日–Git的性能就已经达到了Linus的预期、2005年6月16日–Linux内核2.6.12发布,那时Git 已经在维护 Linux 核心的源代码了。Linus以一个文件系统专家和内核设计者的视角对 Git 进行了设计,其独特的设计让 Git拥有非凡的性能和最为优化的存储能力。完成原型设计后,在2005年7月26日,Linus功成身退,将Git的维护交给另外一个Git的主要贡献者JunioCHamano,直到现在。
最初的 Git 除了一些核心命令以外,其他的都用脚本语言开发,而且每个功能都作为条独立的命令,例如克隆操作用git-clone ,提交操作用git-commit 。这导致 Git 拥有庞大的命令集,使用习惯也和其他版本控制系统格格不入。随着Git的开发者和使用者的增加,Git 也在逐渐演变,例如到1.5.4版本时,将一百多个独立的命令封装为一个git命令,使它看起来更像是一个独立的工具,也使Git 更贴近于普通用户的使用习惯。
经过短短几年的发展,众多的开源项目都纷纷从SVN或其他版本控制系统迁移到Git。虽然版本控制系统的迁移过程是痛苦的,但是因为迁移到Git 会带来开发效率的极大提升以及巨大的效益,所以很快就会忘记迁移的痛苦过程,而且很快就会适应新的工作模式。在Git 的官方网站上的重量级项目,每一个都是人们耳熟能详的,除了Git 和 Linux 内核外,还有Perl、Eclipse、Gnome、KDE、Qt、Ruby on Rails、Android、PostgreSQL、Debian、X.org,当然还有 GitHub 的上百万个项目。Git 虽然是在Linux下开发的,但现在已经可以跨平台运行在所有主流的操作系统上,包括 Linux、Mac OS和 Windows 等。
三、Git的一些基础概念
3.1 仓库中的三个区域和文件的三种状态
在git中有三个区域,分别是工作区、暂存区、版本库,这些区域在初始化git仓库时就确定了,比如新建一个目录/myapp
,那么在初始化的这一刻会在/myapp
目录下新建一个.git
目录,.git
目录就是版本库,那么在/myapp目录下但除了.git目录的其他区域,叫做工作区,工作区的文件修改后,不能直接提交到版本库,需要先提交到暂存区,暂存区就是用来存储下一次需要提交到版本库的文件的地方。
- 工作区:用来存放目前需要更新的代码文件
- 暂存区:当工作区域的代码修改了,首先需要提交到暂存区,用来存储下一次提交的文件的地方
.git目录
:版本库,用来保存元数据和对象数据库的地方。该目录非常重要,每次克隆镜像仓库的时候,实际拷贝的就是这个目录里面的数据。- 已修改(modified):已修改表示修改了某个文件,但还没有提交到暂存区
- 已暂存(staged):已暂存表示把已修改的文件放在下次提交时要保存的清单中,也就是放到了暂存区中,可以理解及暂存区就是一个中间人,你要提交哪些文件必须先告知这个中间人,不管是一次提交一个还是一次提交多个
- 已提交(committed):表示该文件已经被安全地保存在本地数据库中了
需要注意的是,并不是说一个文件在工作区,修改之后提交到暂存区或者提交到版本库,这个文件就不存在了,这个文件是一直在的,除非你明确指定删除它。工作区的文件修改后提交到暂存区,它只是告诉了暂存区,我改了哪些文件,这些文件下次提交记得帮我提交到版本库,而工作区的文件是不会动的。
3.2 Git的安装
Git的安装方式有三种,源码编译、通用二进制安装、包管理器安装。本人涉及到使用Git的场景并不会去定制Git的特性或体验最新的特性,所以源码编译的方式就不研究了。通用二进制解包之后还需要调整环境变量,觉得麻烦。所以直接选择使用包管理器安装。在有yum或者apt的系统上可以使用下面的命令安装git
~]# yum install git-core
~]# apt instal git-core
3.3 Git的配置文件
git是分布式代码版本控制工具,在代码更新中有一个非常重要的工作就是查看代码是谁更新的、什么时候更新的、更新了什么。所以在使用git前需要对git做一些配置,这些配置设定一次即可。当然也还可以设置一些个性化的配置
/etc/gitconfig
:系统中对所有用户都普遍适用的配置,使用git config --system~/.gitconfig
:用户目录下的配置文件只适用于该用户。git config --global.git/config
:项目目录的.git 目录中的配置文件,这里的配置仅仅针对当前项目有效。
生效优先级 .git/config
> ~/.gitconfig
> /etc/gitconfig
3.3.1 配置用户信息
每次提交都会引用用户信息,表明这次提交是谁提交的,什么使用提交的,命令很好理解,分别是用户名和邮箱
~]# git config --global user.name "Arthur"
~]# git config --global user.email busyops@outlook.com
如果用了--global
选项,那么更改的配置文件就是家目录下的~/.gitconfig
,以后所有的项目都会默认使用这里配置的用户信息。如果要在某个特定的项目中使用其他名字或者邮箱,只要去掉--global
选项重新配置即可,新的设定保存在当前项目的.git/config
文件里。设置完成后可以使用git config --list
查看,有时候会看到重复的键值对,那就说明在不同的配置文件中都配置了相同的信息
3.3.2 其他的一些个性化设置
git除了定义用户信息外,还可以做一些个性化的设置,比如设定默认调用的文本编辑器、设定文件差异分析工具。
~]# git config --global core.editor emacs 设置默认的编辑器为emace
~]# git config --global merge.tool vimdiff 设置默认差异分析工具为vimdiff
四、Git命令和用法
4.1 初始化一个仓库
初始化仓库非常简单,进入到任意目录执行git init
即可,执行完成后这个目录就是项目目录,在这个目录内会生成一个名为.git的目录,目录内会自动创建一些git需要的、用于版本控制的文件。初始化完成后,目录内不会跟踪任何一个文件,需要让git跟踪哪些文件,就把哪些文件复制到这个项目目录,然后执行下面的命令
~]# git add .
~]# git commit -m 'v1.1'
这两条命令的意思是,把所有文件都纳入版本控制,提交到版本库并给这次提交取名为v1.1 ,这就是本项目的第一次提交。后续文件发生的改变将被跟踪,可以随时增加需要跟踪的文件并提交第二次、第三次。任何时刻也可以恢复到提交的文件的那一刻,类似快照。
工作目录下面的所有文件都不外乎这两种状态:已跟踪或未跟踪。已跟踪的文件是指已经被纳入版本控制管理的文件,在上次提交(快照)中有它们的记录,工作一段时间后,它们的状态可能是未更新,已修改或者已放入暂存区。而所有其他文件都属于未跟踪文件。它们既没有上次更新时的快照,也不在当前的暂存区域。初次克隆某个仓库时,工作目录中的所有文件都属于已跟踪文件,且状态为未修改。在编辑过某些文件之后,Git 将这些文件标为已修改。我们逐步把这些修改过的文件放到暂存区域,然后等最后一次性提交暂存区域的所有文件更新,如此重复。
4.2 检查文件的状态
要确定哪些文件当前处于什么状态,可以用 git status 命令。如果在克隆仓库之后立即执行此命令,会看到类似这样的输出
~]# git status
# On branch master
nothing to commit (working directory clean)
这说明现在的工作目录相当干净。换句话说,当前没有任何跟踪着的文件,也没有任何
文件在上次提交后更改过。此外,上面的信息还表明,当前目录下没有出现任何处于未跟踪
的新文件,否则 Git 会在这里列出来。最后,该命令还显示了当前所在的分支是 master,
这是默认的分支名称
这说明你现在的工作目录相当干净。换句话说,当前没有任何跟踪着的文件,也没有任何
文件在上次提交后更改过。此外,上面的信息还表明,当前目录下没有出现任何处于未跟踪
的新文件,否则 Git 会在这里列出来。最后,该命令还显示了当前所在的分支是 master,
这是默认的分支名称,实际是可以修改的,现在不必多虑。下一章我们就会详细讨论分支和
引用。
用 vim 编辑一个新文件 README,保存退出后运行 git status 会看到该文件出现在未跟踪文件列表中:
~]# vim README
~]# git status
# On branch master
# Untracked files:
# (use "git add <file>..." to include in what will be committed)
#
# README
nothing added to commit but untracked files present (use "git add" to track)
就是在“Untracked files”这行下面。Git 不会自动将之纳入跟踪范围,除非你明明白白地告诉它这么做,因而不用担心把临时文件什么的也归入版本管理。
使用命令 git add 开始跟踪一个新文件。所以,要跟踪 README 文件,运行:
~]# git add README
此时再运行 git status 命令,会看到 README 文件已被跟踪,并处于暂存状态:
~]# git status
# On branch master
# Changes to be committed:
# (use "git reset HEAD <file>..." to unstage)
#
# new file: README
#
只要在 “Changes to be committed” 这行下面的,就说明是已暂存状态。如果此时提交,那么该文件此时此刻的版本将被留存在历史记录中。任何文件的修改,只要是需要下次需要提交到版本库的,都必须要执行下git add命令才行
4.3 文件比较
要查看尚未暂存的文件更新了哪些部分,不加参数直接输入git diff
,此命令比较的是工作目录中当前文件和暂存区域快照之间的差异,也就是修改之后还没有暂存起来的变化内容。若要看已经暂存起来的文件和上次提交时的快照之间的差异,可以用 git diff --cached
命令。
4.4 文件删除
要从项目中删除某个文件,需要使用git rm
命令,如果只是使用系统自带的rm命令在工作区删除,运行git status 时就会未暂存清单中看到,但是有些使用如果只是想在暂存清单中移除文件跟踪,不删除工作区的源文件,需要结合--cached
使用,这种方式适用于不小心将某个文件纳入了暂存区,但是这个文件不需要暂存的情况。删除文件可以使用git rm log/*.log
,也可以使用git rm log/\*.log
,后者会递归删除指定目录下所有已.log结尾的文件
4.5 文件移动
要在 Git 中对文件改名,需要使用命令~]# $ git mv old_name new_name
,其实运行 git mv 就相当于运行了下面三条命令:
~]# mv README.txt README
~]# git rm README.txt
~]# git add README
如此分开操作,Git 也会意识到这是一次改名,所以不管何种方式都一样。当然,直接用git mv 轻便得多
4.6 撤销操作
有时候提交完了才发现漏掉了几个文件没有加,或者提交信息写错了。想要撤消刚才的提交操作,可以使用--amend
选项重新提交,此命令将使用当前的暂存区域快照提交。如果刚才提交完没有作任何改动,直接运行此命令的话,相当于重新编辑提交说明,而所提交的文件快照和之前的一样。启动文本编辑器后,会看到上次提交时的说明,编辑它确认没问题后保存退出,就会使用新的提交说明覆盖刚才失误的提交。如果刚才提交时忘了暂存某些修改,可以先补上暂存操作,然后再运行 --amend 提交,相当于
~]# git commit -m 'initial commit'
~]# git add forgotten_file
~]# git commit --amend
有两个修改过的文件,我们想要分开提交,可以使用 git reset HEAD <file>
的方式取消暂存,这样就回到了之前已修改未暂存的状态,如果觉得刚才的修改完全没有必要,使用git checkout -- <file>
,这个命令就是将版本库中上一次提交时的文件拿出来覆盖为当前版本,这条命令有些危险,所有对文件的修改都没有了,在用这条命令前,请务必确定真的不再需要保留刚才的修改。
4.6 查看提交历史
默认不用任何参数的话,git log会按提交时间列出所有的更新,最近的更新排在最上面。每次更新都有一个SHA-1校验码、作者的名字、电子邮件地址、提交时间、提交说明。git log有许多子选项可以帮助过滤log,
git log [option]
-p: 展开显示每次提交的内容差异
-2: 仅显示最近的两次更新。结合-p使用
--stat:每个提交都列出了修改过的文件,以及其中添加和移除的行数,并在最后列出所有增减行数小计。
--pretty:可以指定使用完全不同于默认格式的方式展示提交历史。比如用 oneline 将每个提交放在一行显示,这在提交数很大时非常有用。另外还有short, full 和 fuller 可以用,展示的信息或多或少有些不同,
4.7 命令别名
可以用 git config 为命令设置别名。来看看下面的例子:
~]$ git config --global alias.co checkout
~]$ git config --global alias.br branch
~]$ git config --global alias.ci commit
~]$ git config --global alias.st status
现在,如果要输入 git commit 只需键入 git ci 即可。而随着 Git 使用的深入,会有很多经常要用到的命令,遇到这种情况,不妨建个别名提高效率。