背景

版本管理的演变

VCS(Version Control System)出现之前

  • 用目录拷贝区别不同版本
  • 公共文件容易被覆盖
  • 成员沟通成本高,代码集成效率低下

集中式 VCS(如 CVS)

  • 有集中的版本管理服务器
  • 具备文件版本管理和分支管理能力
  • 集成效率有明显提高
  • 客户端必须时刻与服务器相连

分布式 VCS(如 Git)

  • 服务端和客户端都有完整的版本库
  • 脱离服务端,客户端也可以管理版本
  • 支持查看历史和版本比较等多数操作,都不需要访问服务器,比集中式 VCS 更能提高版本管理效率

Git

Git 是一个开源的分布式版本控制系统,可以有效、高速地处理从很小到非常大的项目版本管理。
Git 是 Linus Torvalds 为了帮助管理 Linux 内核开发而开发的一个开放源码的版本控制软件。
Torvalds 开始着手开发 Git 是为了作为一种过渡方案来替代 BitKeeper

特点

  • 最优的存储能力
  • 非凡的性能
  • 开源
  • 容易做备份
  • 支持离线操作
  • 容易定制工作流程

Git 安装与最小配置

安装

安装 Git

最小配置

配置 user 信息 user.nameuser.email

$ git config --global user.name "your_name"
$ git config --global user.email "your_email"

config 的三个作用域,缺省等同于 local

$ git config --local  # local 即只针对某个仓库有效 
$ git config --global # global 即针对当前用户的所有仓库有效
$ git config --system # system 即针对系统所有登录的用户有效

显示 config 的配置, 加 --list 参数

$ git config --list --local
$ git config --list --golbal
$ git config --list --system

创建 Git 仓库

分为以下两种场景:

  1. 把已有的项目代码纳入 Git 管理
    $ cd 项目代码所在的目录
    $ git init

  2. 新项目直接使用 Git 管理
    $ cd 某个目录
    $ git init your_project # 该命令会在该目录下创建与项目名称同名的目录
    $ cd your_project

基本使用与常用命令

认识工作区与暂存区

以下为一些常用的基本命令:

$ git status  # 查看工作目录与暂存区的状态
$ git add files # 提交 files 到暂存区
$ git commit -m “commit message” # 生成 commit(-m 参数:编辑生成 commit 信息,若不加该参数将会进入文本编辑器)
$ git log # 查看 commit 历史

向仓库中添加文件的过程

常用命令介绍

文件重命名

git mv old_name new_name

回退到上一次 commit

git reset --hard

通过 git log 查看版本演变历史

  • 查看当前分支的 commit 历史信息 git log 不加任何参数时只查看当前分支
  • 查看所有分支的 commit 历史信息 git log --all
  • 图形化(字符画)展示 commit 历史信息 git log --all --graph
  • 查看历史 commit 信息列表(简洁信息) git log --oneline
  • 查看最近的 4 条历史 commit 信息列表 git log -n4 --oneline
  • 图形化界面工具,查看所有分支的 commit 历史信息 gitk --all

查看分支

  • 查看当前分支 git branch
  • 查看本地所有分支信息 git branch -v
  • 查看所有分支信息(包含远程分支) git branch -av

管理分支

  • 创建新分支 git checkout -b 新分支名称 hash值
    填写 commit 的 “hash值” 可以指定基于该 commit 创建新分支(可用 git log 命令查看对应 commit 的 “hash值” )
  • 切换分支 git checkout 其他已存在的分支

Git 原理剖析

首先进入 .git 目录下,接下来对 .git 目录下的各文件与目录进行解析

HEAD 文件

$ cd .git/
$ cat HEAD
ref: refs/heads/master

HEAD 文件中的内容为 ref: refs/heads/master,那么这个内容代表什么意思呢?

此时输入 git branch -av 显示我们当前所在分支为 * master* 即代表当前所在,由此我们了解了,HEAD 文件中的内容即是记录了我们当前所在的分支信息。而 refs/heads/master 看起来像是一个路径,这个接下来再说

config 文件

$ cd .git/
$ cat config
...
[user]
name = user.name # 使用 --local 参数设置的用户名
email = user.email # 使用 --local 参数设置的邮箱

显而易见 .git 目录下的 config 文件中保存了 --local 相关信息,此时修改文件中的内容后使用 git config --local --list 命令查看相应结果也会发生对应的变化

refs 目录

$ cd .git/
$ cd refs/
$ ll -a
drwxr-xr-x .
drwxr-xr-x ..
drwxr-xr-x heads
drwxr-xr-x tags

heads 目录

小技巧:

  • git cat-file -t 2b81da
    该命令可以查看以上对象 2b81da(这里它本应该表现为一个完整的 hash 值,截取其名称前 5-6 个字符或更多字符即可代表该对象)的类型
$ cd heads/
$ ll -a
drwxr-xr-x .
drwxr-xr-x ..
-rw-r--r-- master # master 分支
$ cat master
2b81da9321641975fed4724138fb77fe1f9b6a # 对象
$ git cat-file -t 2b81da
commit # 该对象是一个 commit 类型

接着上面 HEAD 文件没有说完的内容,refs/heads/master 看起来像是一个路径,由此我们进入这个路径进行探寻

对于 .git/refs/heads/master 文件,其中存放的是当前 master 分支指针指向的 commit

注释:refs 目录下的 heads 目录可以理解为存放当前分支的目录
tags 目录可以理解为存放该仓库所有标签的目录,标签一般意味着一段开发历程的里程碑

tags 目录

接上,下面再来看一下 tags 路径下有什么

小技巧:

  • git cat-file -p 345864
    该命令查看以上对象 345864 的内容
$ cd .git
$ cd tags/
$ ll -a
drwxr-xr-x .
drwxr-xr-x ..
-rw-r--r-- release-1.0.0 # 假设已生成了 1 个 tag
$ cat release-1.0.0
345864e3d80d85cdf7fbf55a4a77d5f846d419 # 这个 hash 值代表一个对象
$ git cat-file -t 345864
tag # 该对象是一个 tag 类型
$ git cat-file -p 345864
object f6237a79adb743a351ea26bf5eae51d4a14483
type commit # 以上对象是一个 commit 类型
tag release-1.0.0
tagger xxx <xxx@xxx.com> xxxxxxxxxx +0800

commit_message # commit message 内容
$ git cat-file -t f6237a
commit # 该对象是一个 commit 类型

注释:当我们查看 tags 路径下文件的内容时,显示了一个 hash 值,它通常代表一个对象,接着我们查看它的类型 —— 一个 tag 类型;接着是它的内容,内容中又包含一个 hash 值,它已经标注了自己的类型 —— 是一个 commit,并且还包含了 tag 名称、生成时间戳、提交人信息、commit message 等,可以再使用命令 git cat-file -t f6237a 检查一下

Git 文件系统的核心 - objects

$ cd .git
$ cd objects/
$ ll -a
drwxr-xr-x .
drwxr-xr-x ..
drwxr-xr-x de
drwxr-xr-x e0
drwxr-xr-x e1
drwxr-xr-x e2
drwxr-xr-x e3
drwxr-xr-x e4
drwxr-xr-x info
drwxr-xr-x pack
cd e0/ # 随便进入一个目录
$ ll -a
drwxr-xr-x .
drwxr-xr-x ..
-rw-r--r-- 8a9e0cde36b17bda8583a91a81c5e35f1408f4
$ git cat-file -t e08a9e0cde # 此时 Git 的策略是将父级目录名与该文件拼接成一个 hash 值
tree # 该对象是一个 tree 类型
$ git cat-file -p e08a9e0cde
100000 blob b72e61c8b6782cd0fab5w41ea2b044d1fe05be3f file_name # blob 代表文件类型
$ git cat-file -t b72e61c8
blob # 该对象是一个 blob 类型
$ git cat-file -p b72e61c8
file_name_content # 展现文件中的内容

注释:当我们进入 objects 目录下时使用 ll -a 命令可以看到一些以两个字母命名的目录。
随机选择进入一个这样的目录后再使用 ll -a 命令,得到的将是一个或多个类似 hash 值命名的对象,实际上 Git 的策略是以它的父级目录名加上该类似 hash 值的内容拼接作为一个 hash 值,查看该对象的类型,其为一个新的类型 —— tree,接着查看它的内容,其中又包含一个新类型 blob,代表文件类型,查看这个 blob 类型对象的内容即为实际文件的内容(即 file_name 对应的文件)

实际上,只要任何文件的内容是相同的,那么在 Git 看来它就是唯一的一个 blob

Git 对象彼此关系

Git 对象彼此关系

  • 一个 commit 对应一个 tree,它代表了这个 commit 在这个时间点整个仓库的快照,即在这个时间点:目录结构、文件是什么样子,将通过 tree 呈现出来 —— 见黄色部分 823gb7…
  • tree 代表文件夹,tree 中可以包含 tree
    • 可以看到 tree - 87a87a 代表 images 文件夹,具体见箭头指向
    • 可以看到 tree - bdd489 代表 styles 文件见,具体见箭头指向
  • blob 代表文件,blob 与文件名没有任何关系,即不管文件名为什么,文件内容相同那么就只有一个 blob