注:本文使用的素材大部分来自git官方文档。
Git基本认知
一种分布式版本控制系统
Git是一种分布式版本控制系统,不同于其他的集中式版本控制系统。
集中式版本控制系统
集中式版本控制系统有以下特点:
- 有一个单一的集中管理的服务器,保存所有文件的修订版本;
- 协同工作的人们通过客户端连接到服务器,取出最新的文件或者提交更新;
- 服务器一旦损坏,又没有做备份的话,将丢失绝大部分数据,只剩下人们各自机器上保留的单独快照;
分布式版本控制系统
Git是一种分布式版本控制系统,有以下特点:
- 客户端不只是提取最新版本的文件快照,而是将代码仓库完整的镜像下来,包括完整的提交记录;
- 即使服务器发生故障,事后也可以用任意一个本地仓库恢复;
- 可以指定若干不同的远端仓库,在同一个项目中和不同工作小组的人相互协作;
记录快照 而非差异比较
区别于其他版本控制系统,Git对待数据的处理方式上有着显著的差别。
基于差异的版本控制
- 其它大部分系统将存储的数据看作是一组基本文件和每个文件随时间逐步积累的差异;
- 如下图所示,以文件变更列表的方式存储信息,存储每个文件与初始版本的差异;
基于快照流的版本控制
- Git 把数据看作是对小型文件系统的一系列快照,存储全部文件的快照和快照的索引;
- 为了效率,如果文件没有修改,Git 不再重新存储该文件,而是只保留一个链接指向之前存储的文件。
更高效且可靠
- Git绝大多数操作都只需要访问本地文件资源,大部分操作都能瞬间完成;
- Git使用SHA-1校验和作为数据引用,确保数据不丢失损坏;
- Git一般只添加数据,一旦你提交数据到数据库中,就很难通过将其从数据库中删除;
Git分区
- Git仓库可划分为三个区域;
- 工作区存放的是从 Git 仓库的压缩数据库中提取出来的某个版本的内容,供你使用或修改。
- 暂存区是一个文件(即.git/index),保存了下次将要提交的文件列表信息。
- Git仓库是用来保存项目元数据和对象数据库的地方。 从其它计算机克隆仓库时,复制的就是这里的数据。
Git对象管理
Git 的核心部分是一个简单的键值对数据库。 你可以向 Git 仓库中插入任意类型的内容,它会返回一个唯一的键,通过该键可以在任意时刻再次取回该内容。 Git对象即Git数据库中存储的数据,它在Git数据库中的存储模型如下图所示。
对象的存取
- Git通过底层命令git hash-object来存储git对象。
- 如果要取出git对象,则可以使用底层命令git cat-file。
数据对象
- 数据(blob)对象用于保存项目文件内容,如下图所示。
树对象
- 树(tree)对象用于保存项目文件内容,如下图所示;
提交对象
- 提交(commit)对象用于保存一次提交的信息。
- 它先指定一个顶层树对象,代表当前项目快照; 然后是可能存在的父提交(第一次提交对象不存在任何父提交); 之后是作者/提交者信息(依据你的 user.name 和 user.email 配置来设定,外加一个时间戳); 留空一行,最后是提交注释。
标签对象
- 标签(tag)对象非常类似于一个提交对象。
- 它包含一个标签创建者信息、一个日期、一段注释信息,以及一个指针。 主要的区别在于,标签对象通常指向一个提交对象,而不是一个树对象。
Git引用
- Git引用可以通过一个简单的名字来替代Git对象的SHA-1值,让Git对象的操作变得更简单。
- Git引用通常放在 .git/refs/ 目录下。
Git分支
- Git分支的本质其实就是一个Git引用,它指向了某一些列提交对象之首的那个提交对象。
HEAD引用
- HEAD引用通常是一个符号引用(一个指向其他引用的指针),指向当前所在的分支。
- 在一些特殊场景下,HEAD引用会指向一个Git对象的SHA-1值(当仓库处于分离HEAD状态时)。
- HEAD引用保存在 .git/HEAD 文件中。
标签引用
- 标签引用 和Git分支非常类似,不同的是分支指向的是一个提交对象,而标签引用指向的是标签对象。
- 标签引用保存在 .git/refs/tags 目录下。
远程引用
- 远程引用用来记录远程服务器上各分支的最后已知位置。
- 当你添加了一个远程版本库并对其执行过推送操作,Git 会记录下最近一次推送操作时每一个分支所对应的值,并保存在 refs/remotes 目录下。
- 远程引用和分支的区别在于,远程引用是只读的。 你永远不能通过 commit 命令来更新远程引用 。
常用命令揭秘
git status
- 经典的 Git 工作流程是通过操纵工作区、HEAD引用、索引(暂存)区这三个区域,来以连续的状态记录项目快照的。
- git status 的原理就是通过比较这三个区域中文件的区别,来输出不同结果的,具体会在下面的例子中讲解。
git init
- 假设一个目录中有一个文件 file.txt, 文件中的内容为该文件的 v1 版本。 现在运行 git init,这会创建一个 Git 仓库,其中的 HEAD 引用指向未创建的 master 分支。
- 此时,该文件只在工作区中,使用git status命令,将会看到该文件显示在“Changes not staged for commit”下面,因为该文件在暂存区与工作区之间存在不同。
git add
- 现在我们用 git add 命令将这个文件复制到暂存区中。
- 此时,由于暂存区和HEAD不同,使用git status 命令,将会看到该文件显示在“Changes to be commited”下面,说明现在预期的下一次提交与上一次提交不同。
git commit
- 现在我们用 git commit 命令提交这个文件 。
- 此时,由于工作区、暂存区、HEAD三个区域完全一样,使用git status命令,会看到”nothing to commit, working tree clean“的提示。
git reset
- git reset 命令执行过程最多可以分为三步:移动HEAD、更新暂存区、更新工作区。
- 我们通过模拟以下情景来观察这三步是如何执行的:将当前分支重置到文件的v2版本。
- 第一步:移动HEAD,即移动HEAD引用指向的分支。
- 当前模拟情景中的HEAD引用指向master分支,因此git会将当前分支的引用指向文件的v2版本提交点。
- 如果你使用的是reset --soft命令,那么此时reset命令就执行完了。
- 第二步:更新暂存区,该操作在reset –mixed或reset --hard时才会触发。
- 该步骤会取消已暂存的所有的东西,让暂存区和HEAD区保持一致。
- 如果你使用的是reset --mixed命令,那么此时reset命令就执行完了。
- 第三步:更新工作区,该操作reset --hard时才会触发。
- 该步骤会取消未暂存的所有的东西,让工作区和暂存区保持一致。
- reset --hard是reset命令比较危险的用法,该步骤下丢失的数据,将无法被找回。
git checkout
- git checkout [branch] 与 git reset –hard [branch]命令非常相似,不过有两个重要的区别。
- 不同于reset --hard命令会直接丢弃所有未暂存的修改,checkout命令会通过检查来确保不会将已更改的文件弄丢。
- 不同于reset –hard命令会移动HEAD分支的指向,checkout只会移动HEAD自身来指向另一分支。
|