最近重新学了一遍Git工具的使用,在此记录一下。
Git是什么?
Git是属于分布式版本控制系统,同属于的还有Mercurial、Bazaar等。
Subversion(svn)属于集中式的版本控制系统,同属于的还有CVS、Perforce等。
两种类型版本控制系统有什么区别?
集中式的版本控制系统,以单台中央服务器为中心,将所有的版本控制历史都集中放在这台服务器上,而各个协同开发者的计算机上只保存着一个快照版本,在这种情况下,开发者每次想要提交或者查看文件的历史等,都需要请求网络,向刚刚所说的中央服务器获取或者提交数据。
分布式版本控制系统与集中式版本控制系统不同,它在本地计算机上建立代码仓库,保存着所有的版本控制历史的数据,在合适的时机与远程服务器代码仓库同步。在这种情况下,开发者每次想要提交或者查看文件的历史等,无需请求网络,直接向本地获取或者提交数据即可,因为本地保存着所有的版本历史数据。
分布式版本控制系统的优点:
开发者所有的Git操作都在本地代码仓库进行,不用依赖于网络。
不用担心代码仓库服务器宕机或者数据丢失等情况,因为每个开发者计算机上都保存有完整的版本历史数据,可以随时将开发者主机上的数据恢复至服务器代码仓库。
限于篇幅,本文接下去的内容不讨论svn等集中式的版本控制系统与分布式版本系统的用法差异。
为了更加贴近于实际的开发,我们将从Git仓库的创建到版本的提交为顺序讲述,最后还简述一下分支的用法和概念。
注意:本文是在本地代码仓库上的操作,还未涉及到远程仓库的连接,读者先不要将远程仓库的思想混进来,容易造成混乱。
先介绍几个Git中的基本概念。
Git中的文件区域
在git中总共有三个区域。分别是工作区、暂存区、git仓库区。
我们所有的文件修改以及变更都是在工作区进行的,然后保存至暂存区(git add命令),最后保存到git仓库区(git commit命令)。可能有读者会问git为什么要设计一个暂存区,为什么不支持从工作区直接将文件保存至git仓库区。这是因为这个暂存区作为一个中间区域,它的作用就是存放即将要提交到git仓库的文件。
有这个区域的存在,开发者可以将想要提交到仓库的文件保存到暂存区,不想提交的文件可以继续存放在工作区,这样子下次执行提交操作的时候,Git只会将暂存区所有的文件提交到仓库中。
开发者提交文件更加灵活。开发者假如后悔不想要提交某个文件(此时这个文件已经在暂存区),那么开发者可以将此文件从暂存区中移出(git reset HEAD
Git中的文件状态
在git中,文件有四种状态。未跟踪、未修改、已修改、已暂存。
已跟踪的文件是指本来就被纳入版本控制管理的文件,在上次快照中有它们的记录,工作一段时间后,它们的状态可能是未修改,已修改或者已暂存。
未跟踪文件既没有在git仓库中存有快照,也还未将文件快照保存到当前的暂存区域。初次克隆某个仓库时,工作目录中的所有文件都属于已跟踪文件,且状态为未修改。
Git仓库的创建
Git仓库的创建非常简单,在本地设备有安装Git情况下,直接在项目根目录下运行shell命令git init
即可。
$ git init
git status
git status这个命令是用来检查当前文件的状态的。为什么我这么早就介绍这个命令?因为这个命令能给我们提供很多有用的信息,帮助我们了解Git。这个命令应该算是有git最常用的命令了,它能够显示当前工作区文件的状态(已跟踪或未跟踪)、暂存区文件状态、文件冲突状态、当前所在的分支等。
$ git status
On branch master
Changes to be committed:
(use "git reset HEAD ..." to unstage)
new file: README
Changes not staged for commit:
(use "git add ..." to update what will be committed)
(use "git checkout -- ..." to discard changes in working directory)
modified: benchmarks.rb
从输出中可以看到当前处于master分支,新创建的文件README
已经被保存至暂存区域,benchmarks.rb
在工作区域已被修改,但是还没被保存至暂存区域,下次提交时候只会提交README
文件到代码仓库。
括号中的文本是git的辅助提示信息。比如这里git告诉你使用git reset HEAD
可以将文件从暂存区域移除。git add
用来添加或者更新文件到暂存区域等到下次提交。用git checkout --
命令来还原工作区文件的改变,其实就是将工作区某个或某些文件还原为未修改的状态。
git add
git add
的作用是将工作区的文件保存至暂存区。
重要是事情说三遍!
git 保存的不是文件差异或者变化量,而只是一系列文件快照。
git 保存的不是文件差异或者变化量,而只是一系列文件快照。
git 保存的不是文件差异或者变化量,而只是一系列文件快照。
这个命令的后面可以带有文件或者文件夹路径作为参数,当参数为目录时候,git add
的效果会递归至目录下所有的文件。
- 如果文件在工作区域已被跟踪,那么直接将文件快照保存至暂存区。
- 执行
git add
命令后,git会计算该文件的校验和,生成该文件快照,将校验和保存到暂存区,这里是保存文件快照。
注意:如果执行所要执行git add的目标文件处于未跟踪状态,此命令会先将目标文件标识为已跟踪文件。
在仓库建立之后,因为此时本地代码仓库和暂存区都是空的,并没有任何文件的快照。所以此时所有的文件状态都是未跟踪状态。
此时执行git status命令:
$ vim README
$ git status
On branch master
Untracked files:
(use "git add ..." to include in what will be committed)
README
nothing added to commit but untracked files present (use "git add" to track)
我们工作目录下有一个名为README
文件,此时显示它的状态就是未跟踪的。同时git也提示我们可以使用git add
来跟踪以及保存到暂存区。
此时我们执行git add *
命令(git 命令支持通配符,*表示将当前工作目录下所有的非忽略文件提交至暂存区,包括所有子文件夹的下的所有文件,当前这里你也可以直接使用git add README
命令,指定只作用于README
文件)。然后再次执行git status
:
$ git status
On branch master
Changes to be committed:
(use "git reset HEAD ..." to unstage)
new file: README
从输出中我们可以看到暂存区已经保存着README
文件的快照,并且等待提交。
git commit
git commit这个命令是用来将文件提交到本地代码仓库的。
当你用Git commit命令执行一个新的提交时候,Git只保存一个指向这次提交快照的索引。
可以看到这里有三个文件,file A、file B、file C。这里以在提交一个新的版本后,git会遍历一边所有的文件的指纹信息,如果发现新版本的文件指纹信息与上一个版本不一致,说明该文件发生了变化,此时git会将新的文件快照索引保存至本地的微型数据库中,如果文件未发生变化,那么会将上个版本的保存的快照索引直接复用写入微型数据库中。
注:git所有的数据都保存在本地项目仓库的.git文件夹下,这个文件夹默认为隐藏文件夹。
实际上,我们执行一次提交操作就是保存一个提交对象。
当执行git commit命令后,git会先计算每一个子目录的校验和(注意,git add是计算文件的校验和),然后在git仓库中将这些目录保存为树对象,然后git会创建一个提交对象,这个提交对象出了包含相关的提交信息以外,还包含着指向这个树对象的指针,如此它就可以在将来需要的时候,重现此次快照的内容了。git commit会将暂存区的所有文件提交到本地数据库中。保存的内容如下。
多次提交之后:
分支
分支应该算是版本控制系统最强大的功能之一了。
前面我们说了,在执行git提交时,git会创建并且生成一个提交对象,该对象包含一个指向文件树的指针,包含本次提交的作者等相关附属信息,包含零个或者多个指向该提交对象的父对象指针:首次提交时没有直接祖先的,普通提交有一个祖先,由两个或者多个分支合并产生的提交则有多个祖先。
其实分支的概念很简单,分支本质上仅仅是个指向某个提交对象的可变指针。
在创建一个新的代码仓库时,git会默认创建一个名为master的分支。我们的每次提交操作,都是基于某个分支的基础上进行的。在若干次提交操作之后,分支会自动向前移动。
分支的创建很简单。
git branch testing
这会在当前的提交对象上新建一个新的分支指针。如下:
那么,git是如何知道我们当前是在哪个分支上工作的呢?其实git保存着一个名为HEAD的特别指针。每当我们切换执行切换分支操作的时候,git其实只是将HEAD指针移动到目标分支上,这样子我们接下去的提交等操作都是针对于这个分支进行的。
在上面命令中,我们执行了
git branch
命令,这个命令仅仅是创建一个分支,而不会切换到新建的这个分支中。要切换分支,可以执行一下命令。
git checkout testing
这样子,HEAD就指向了testing分支。这样子就完成了分支的切换。
此时我们如果进行修改一个文件,然后提交。结果如下。
vim test.rb
git commit -a -m 'made a change'
注意,git commit 后面-a表示直接跳过保存至暂存区,直接提交到数据库。
好了。我们把分支切回master。然后再次修改一个文件,最后提交。
vim test.rb
git commit -a -m 'made other changes'
结果如下:
可以看到,我们的项目提交历史产生了分叉。这是因为我们的master和testing分支的父提交对象都是
f30ab
,我们后面可以在合适的时机将两个分支合并。
关于分支的合并见下章,未完待续。