likes
comments
collection
share

大话 Git 版本控制系统 (一)

作者站长头像
站长
· 阅读数 8

前言

为了向零基础的 Git 用户讲述全球最流行的版本控制系统 Git 的原理和操作,故撰写此文,欢迎读者批评斧正。

适用读者

本文面向的是无任何版本控制工具使用经验的读者,尤其是非计算机专业人士。

使用文件夹进行备份

下面我们先来尝试一下,不依赖 Git,也能很好地进行版本控制。理解了这种方式,我们就能轻松理解 Git。

最原始的做法

李雷和林涛合作撰写论文。李雷在桌面建立一个名为 论文 的文件夹,进入此文件夹,里面放一点东西,包括论文文件和相关资料,看起来像是这样:

论文
│  我的论文.docx

├─参考资料
│      张三的论文.pdf
│      李四的论文.pdf

├─图片
│      交互图.vsdx
│      架构图.vsdx

└─数据
        实验数据.xlsx

论文写到一半,李雷打算对架构图做一点修改,改动比较大,原始图片和备份一下,李雷特别懒,每次备份时就按住 Ctrl 键然后鼠标拖动一下复制一个副本,这样备份3次后变成这样:

论文
│  我的论文 - 副本 (2).docx
│  我的论文 - 副本 (3).docx
│  我的论文 - 副本.docx
│  我的论文.docx

├─参考资料
│      张三的论文.pdf
│      李四的论文.pdf

├─图片
│      交互图.vsdx
│      架构图 - 副本 (2).vsdx
│      架构图 - 副本 (3).vsdx
│      架构图 - 副本.vsdx
│      架构图.vsdx

└─数据
        实验数据.xlsx

名称相似的文件越来越多,看着眼花。反正自己硬盘大得很,为何不干脆把整个目录给备份了呢?于是改造成下面的结构:

├─论文
├─论文 - 副本
├─论文 - 副本 (2)
├─论文 - 副本 (3)
└─论文 - 副本 (4)

桌面的文件夹越来越多,再改造一下,把备份文件全拖到备份目录中,看起来像这样:

├─备份
│  ├─论文 - 副本
│  ├─论文 - 副本 (2)
│  ├─论文 - 副本 (3)
│  └─论文 - 副本 (4)
└─论文

这下干爽多了。

细化文件夹描述

再后来,李雷发现自己有对修改感觉后悔的可能,有回退到旧版备份的需求,这文件名看着让人不知道每一版修改了什么,因此把修改点加到目录名中,看起来像这样:

├─备份
│  ├─论文 - 细化了算法描述
│  ├─论文 - 优化了结尾
│  ├─论文 - 增加了若干文献
│  └─论文 - 修复了数据的错误
└─论文

不过分不清楚哪一版新,哪一版旧,于是把日期加上吧,变成这样:

├─备份
  ├─论文 - 2021-12-24 增加了若干文献
  ├─论文 - 2022-01-01 修复了数据的错误
  ├─论文 - 2022-01-01 细化了算法描述
  └─论文 - 2022-01-31 优化了结尾
└─论文

同一天可能会有几次修改,于是把版本号也加上。变成:

├─备份
  ├─论文 - V1 2021-12-24 增加了若干文献
  ├─论文 - V2 2022-01-01 修复了数据的错误
  ├─论文 - V3 2022-01-01 细化了算法描述
  └─论文 - V4 2022-01-31 优化了结尾
└─论文

再后来,为了分辨是谁做的修改,演变成:

├─备份
│  ├─论文 - V1 2021-12-24 增加了若干文献 by 李雷
│  ├─论文 - V2 2022-01-01 修复了数据的错误 by 林涛
│  ├─论文 - V3 2022-01-01 细化了算法描述 by 李雷
│  └─论文 - V4 2022-01-31 优化了结尾 by 林涛
└─论文

快速备份

后来发现 Word 不稳定,有时会崩溃,需要加大备份的频率,而且在 论文 这个目录下有时会误操作改掉一些东西,需要一个类似游戏中的“快速存档”的东西了,于是新建了一个 暂存 目录,如果没有里程碑式的变更需要存档,只是快速备份时,就使用它。

李雷已养成了习惯,每次稍微改动一些就保存一下,然后把论文目录中的内容无脑复制到暂存目录覆盖原文件。每次存档到备份目录时,他不是将论文目录复制过去,而是将暂存目录复制过去再改名,因为他不相信论文目录是可靠的。

目录结构演变为:

├─备份
│  ├─论文 - V1 2021-12-24 增加了若干文献 by 李雷
│  ├─论文 - V2 2022-01-01 修复了数据的错误 by 林涛
│  ├─论文 - V3 2022-01-01 细化了算法描述 by 李雷
│  └─论文 - V4 2022-01-31 优化了结尾 by 林涛
├─暂存
└─论文

加入恢复记录

李雷发现即便放在备份文件夹中的文件,还是有可能被不小心误修改掉。因此他在备份的时候,将文件夹进行 RAR 压缩,并且在压缩时勾选了 WinRAR 的 “加入恢复记录” 的选项,这下就不怕文件被意外改动了。目录结构变为:

├─备份
│  ├─论文 - V1 2021-12-24 增加了若干文献 by 李雷.rar
│  ├─论文 - V2 2022-01-01 修复了数据的错误 by 林涛.rar
│  ├─论文 - V3 2022-01-01 细化了算法描述 by 李雷.rar
│  └─论文 - V4 2022-01-31 优化了结尾 by 林涛.rar
├─暂存
└─论文

改造更高级的快照

后来随着工作越来越细,李雷感觉文件名命名不足以描述两个备份之间的差异,并且想节省下硬盘空间,且考虑到文件校验的需求,于是他想到如下的方法:

前方高能预警!方法复杂,如果跟着做一遍,Git 便理解了一半!

备份过程

李雷先在 论文 目录下建立了 .git 目录,用于备份,.git 目录的目录结构如下:

.git
│  HEAD
│  index

├─objects
└─refs
    ├─heads
    │      master

然后在根目录下放了几份文件,变成:

.git
│  HEAD
│  index

├─objects
└─refs
    ├─heads
    │      master

论文
│  我的论文.docx

├─参考资料
│      张三的论文.pdf
│      李四的论文.pdf

├─图片
│      交互图.vsdx
│      架构图.vsdx

└─数据
        实验数据.xlsx

下面开始逐一备份:

SHA-1 是一种比 MD5 安全的安全散列算法。李雷将每个文件都使用 SHA-1 计算器 计算 SHA-1 散列值,将文件名后面加上散列值后放到 .git/objects 目录下备份。每个文件的散列值几乎不可能相同,因此不会出现文件重名的情况。目录结构变为类似下面这样:

.git
│  HEAD
│  index

├─objects
│      我的论文_c2ed167304265c64979820f5f2221a27dc408d.docx (新增的文件)
│      张三的论文_cf272986596cd1aed9a37eec3f7145771b8f57.pdf (新增的文件)
│      李四的论文_c739bcb758ff50cb73bbfee27952d57066a4a4.pdf (新增的文件)
│      交互图_201f0ef48dba6682f2d2fe655ea3c08fbfbef2.vsdx (新增的文件)
│      架构图_5393ab7dcca7cb8c33ab8db10f40afec4d9119.vsdx (新增的文件)
│      实验数据_9de29bb2d1d6434b8b29ae775ad8c2e48c5391.xlsx (新增的文件)
└─refs
    ├─heads
    │      master


我的论文.docx

参考资料
│      张三的论文.pdf
│      李四的论文.pdf

图片
│      交互图.vsdx
│      架构图.vsdx

数据
│      实验数据.xlsx

为什么 .git/objects 下的文件是扁平化存放,未分目录结构呢?因为在不同的备份当中,目录结构可能会被调整,为了尽量达到文件的可复用性,因此扁平化存放。

那么怎么识别出不同备份的目录结构是如何的呢?李雷近一步地作了如下操作:

新建一个名为 参考资料目录.txt 的文件,里面的内容为:

张三的论文_cf272986596cd1aed9a37eec3f7145771b8f57.pdf
李四的论文_c739bcb758ff50cb73bbfee27952d57066a4a4.pdf

然后将此文件计算 SHA-1 值,也将其重命名放到 .git/objects 目录中,其他目录也是类似操作,注意最终还会生成一个名为 根目录.txt 的文件,内容为:

参考资料目录_aad636258f59ab361053de3b1057cc3e0bf6fd42.txt
图片目录_86db1b97672601980f892bf96c8c57652171193a.txt
数据目录_9316d4771f71435c71272f339d2a991b873921f9.txt
我的论文_c2ed167304265c64979820f5f2221a27dc408d.docx

也将此文件计算 SHA-1 值重命名(设被重命名为 根目录_f3c56785ec4f1186892a2e09cb40c01cc5b387f8.txt)放到 .git/objects 目录中。

于是目录树变为:

.git
│  HEAD
│  index
├─objects
│      我的论文_c2ed167304265c64979820f5f2221a27dc408d.docx
│      张三的论文_cf272986596cd1aed9a37eec3f7145771b8f57.pdf
│      李四的论文_c739bcb758ff50cb73bbfee27952d57066a4a4.pdf
│      交互图_201f0ef48dba6682f2d2fe655ea3c08fbfbef2.vsdx
│      架构图_5393ab7dcca7cb8c33ab8db10f40afec4d9119.vsdx
│      实验数据_9de29bb2d1d6434b8b29ae775ad8c2e48c5391.xlsx
|      参考资料目录_aad636258f59ab361053de3b1057cc3e0bf6fd42.txt (新增的文件)
|      图片目录_86db1b97672601980f892bf96c8c57652171193a.txt (新增的文件)
|      数据目录_9316d4771f71435c71272f339d2a991b873921f9.txt (新增的文件)
|      根目录_f3c56785ec4f1186892a2e09cb40c01cc5b387f8.txt (新增的文件)
└─refs
    ├─heads
    │      master


我的论文.docx

参考资料
│      张三的论文.pdf
│      李四的论文.pdf

图片
│      交互图.vsdx
│      架构图.vsdx

数据
│      实验数据.xlsx

当要备份快照的时候,如此操作:

首先将 .git/index 文件的内容修改成和 根目录_f3c56785ec4f1186892a2e09cb40c01cc5b387f8.txt 文件一致。

然后新建一个文件 快照.txt,内容为:

修改人: 林涛
修改时间: 2022-01-31 20:08
修改说明: 优化了结尾,与摘要部分首尾呼应。
目录: f3c56785ec4f1186892a2e09cb40c01cc5b387f8
上一版本: 无

目录 一栏就是 .git/index 文件的摘要值。

把这个文件也计算 SHA-1 值,命名加上 SHA-1 值,也放到 .git/objects 目录下,变成:

.git
│  HEAD
index
├─objects
│      我的论文_c2ed167304265c64979820f5f2221a27dc408d.docx
│      张三的论文_cf272986596cd1aed9a37eec3f7145771b8f57.pdf
│      李四的论文_c739bcb758ff50cb73bbfee27952d57066a4a4.pdf
│      交互图_201f0ef48dba6682f2d2fe655ea3c08fbfbef2.vsdx
│      架构图_5393ab7dcca7cb8c33ab8db10f40afec4d9119.vsdx
│      实验数据_9de29bb2d1d6434b8b29ae775ad8c2e48c5391.xlsx
|      参考资料目录_aad636258f59ab361053de3b1057cc3e0bf6fd42.txt
|      图片目录_86db1b97672601980f892bf96c8c57652171193a.txt
|      数据目录_9316d4771f71435c71272f339d2a991b873921f9.txt
|      根目录_f3c56785ec4f1186892a2e09cb40c01cc5b387f8.txt
|      快照_724b578ff18799ee98f439833e13ff5f38297a80.txt (新增的文件)
└─refs
    ├─heads
    │      master


我的论文.docx

参考资料
│      张三的论文.pdf
│      李四的论文.pdf

图片
│      交互图.vsdx
│      架构图.vsdx

数据
│      实验数据.xlsx

然后将 .git/HEAD 文件的内容修改为 724b578ff18799ee98f439833e13ff5f38297a80

下次假设修改了架构图和论文,再备份一次,生成新的快照,其 快照.txt上一版本 这个字段应该填 724b578ff18799ee98f439833e13ff5f38297a80 。把新生成的几个文件放到 .git/objects 目录下,就变成类似下面的结构:

.git
│  HEAD
│  index
├─objects
│      我的论文_c2ed167304265c64979820f5f2221a27dc408d.docx
│      张三的论文_cf272986596cd1aed9a37eec3f7145771b8f57.pdf
│      李四的论文_c739bcb758ff50cb73bbfee27952d57066a4a4.pdf
│      交互图_201f0ef48dba6682f2d2fe655ea3c08fbfbef2.vsdx
│      架构图_5393ab7dcca7cb8c33ab8db10f40afec4d9119.vsdx
│      架构图_b975318497fca65e447d310fba597637619b8c48.vsdx (新增的文件)
│      实验数据_9de29bb2d1d6434b8b29ae775ad8c2e48c5391.xlsx
|      参考资料目录_aad636258f59ab361053de3b1057cc3e0bf6fd42.txt
|      图片目录_86db1b97672601980f892bf96c8c57652171193a.txt
|      图片目录_8b9d9e9f14e312b609f9bb31ad3ef5753e1fafb9.txt (新增的文件)
|      数据目录_9316d4771f71435c71272f339d2a991b873921f9.txt
|      根目录_f3c56785ec4f1186892a2e09cb40c01cc5b387f8.txt
|      快照_724b578ff18799ee98f439833e13ff5f38297a80.txt
|      快照_e6238652872781a640e9c3a4fc62346d14443611.txt (新增的文件)
└─refs
    ├─heads
    │      master


我的论文.docx

参考资料
│      张三的论文.pdf
│      李四的论文.pdf

图片
│      交互图.vsdx
│      架构图.vsdx

数据
│      实验数据.xlsx

然后将 .git/HEAD 文件的内容修改为 e6238652872781a640e9c3a4fc62346d14443611

还原过程

如何知道当前最新备份的是哪个快照呢?看 .git/HEAD 文件中的 SHA-1 值便知是 e6238652872781a640e9c3a4fc62346d14443611,在 .git/objects 目录下找到 快照_e6238652872781a640e9c3a4fc62346d14443611.txt 文件,打开文件可以知道对应的根目录,进而在 .git/objects 目录下找到对应的根目录文件,依次向下扩展,就得到了整个备份。如果要还原,就把对应的文件改名覆盖到根目录下即可。

如果要还原上上个快照呢?看 快照_e6238652872781a640e9c3a4fc62346d14443611.txt 文件的 的上一版本 这个字段记录的是 724b578ff18799ee98f439833e13ff5f38297a80 ,就可以找到上上个快照了,还原过程同上。

如何校验

对文件名计算 SHA-1 值后,和文件名对比,便知文件是否被意外修改过。具体原理可参阅 信息摘要算法,本文不再赘述。

小结

从本文可以看到,最简便的备份方法是整个目录一起备份。为了最大限度减少体积,并支持文件校验,使用了将文件进行信息摘要后修改文件名存放到扁平化的目录的方式,并使用文件记录目录结构,通过信息摘要的 SHA-1 值能定位到具体的目录和文件,每次快照时都如此操作,从而实现了多个版本的快照备份的效果。理解这一方法,有助于理解 Git 的工作原理。有了本章的铺垫,在后续的章节中,将进一步对 Git 进行更深入的讲解。敬请期待!