Git 必知必会:原理剖析

article/2025/9/11 20:46:07

作为程序员,最常用的版本管理工具便是 Git。但我相信大多数人和我一样,从没有认真了解过其具体实现的原理。但了解 Git 的原理,能有助于我们工作更好的使用 Git。下面,让我们一起来了解 Git 中的一些概念,以及实现。

一、概念

Git 是一个分布式版本控制软件,在使用的过程中,与 CVS 类不同,不需要使用服务端,就可以实现版本控制。但在我们常用的使用过程中,依然会一个中间服务器作为 original 来实现代码的托管控制 等,整个流程如下图所示:

52d7a03116e8947388982089ee074636.png
Git 中代码的流转过程

从图中可以看到,虽然有一个远程仓库 ,但它仅仅是提供托管以及作为权威库存在,所有程序员仓库节点都可以不依赖这个远程仓库独立存在并使用。

在进行开发工作的时候,我们对代码的源文件进行修改,然后通过 commit 命令提交到本地仓库,在通过 push 命令将代码同步到远程仓库中。那图中的 Git 本地仓库 以及 工作区 指的分别是哪些东西呢?

相信熟悉 Git 的同学都知道,我们克隆到本地的项目中,都会有一个 .git 的隐藏文件夹,这个文件夹中的内容就是 Git 本地仓库, 而工作区就是我们看到的其它原代码。所以,对一个 Git 仓库  来说,最重要的便是 .git 文件夹里面的内容了。

二、Git 的工作原理

刚提到,在本地工程中,.git 文件夹为 Git 本地仓库,库目录结构如下:

371c6b8f3630ffaa60f56088638779b7.png

.git 文件目录
  • hooks: 存储钩子的文件夹,可以注入到 git 生命周期的所有流程

  • objects: 存放 git 对象,代码文件、目录等都会转换成对象存储到这个目录下中

  • refs: 存储分支以及TAG的指针文件。

  • HEAD: 当前工作区执行的代码分支的指针,一般指向 refs 下的某个文件。

  • config: 存储当前项目的一些配置,如 remote url用户信息

在这些目录和文件中,其中最重要的为 objects 文件夹。在 Git 的设计中,所有的核心对象都会往里装。这些对象又分为:

  • blob: 而进制大对象,使用 zlib(一种无损压缩算法) 压缩算法对文件内容进行压缩后的结果。

  • tree: 对应于文件目录,用于存储文件名列表以及文件类型。

  • commit: 对应一个 commit , 存储信息中包含 顶层源目录的 tree hash值时间戳commit 日志信息0个或多个父commit hash值

我在本地建了一个项目,添加了几个文件,以及两次提交。在 objects 目录下,存储的文件类容如下:

b753cd8e9e65f61251c0e2fcd0422ef8.png

objects 目录示例

初看这个会一脸懵,其实很简单,以 objects/78/ace89700a69e490c86f54fbe9d12f0cfb2dbdb 这个文件为例,其中 78ace89700a69e490c86f54fbe9d12f0cfb2dbdb 这一串为这个文件的 Hash 值,计算时使用的是 SHA-1 的算法,其计算结果为 20 个字节组成,通常表示成 40 个 16 进制的形式的字符。对比 Hash 与文件结构可以看出,Git 使用的 Hash 前两个字符作为文件夹名称,后 38 个字符作为文件名,即表示为 hash[0,2]/hash[2:]  格式。在 git 中,也有相应的工具查看 Hash 文件存储的数据类型以及数据内容,可以使用如下命令进行查看:

# 查看文件类弄
git cat-file -t 78ace89700a69e490c86f54fbe9d12f0cfb2dbdb
# 查看文件内容
git cat-file -p 78ace89700a69e490c86f54fbe9d12f0cfb2dbdb

下面在来看一下各 Hash 值的计算:

计算 blob 的 Hash

在 Git 中, Blob 的文件中,存储的内容格式如下:

blob <content length><NUL><content>

其中 content length 指的是源文件内容的长度,NUL\0 ,而 content 为源文件的内容。举个例子, 我在一个空仓库中,添加了一个文件 Main.java ,其内容如下:

public class Main {public static void main(String [] args) {System.out.println("This is Main.java");}
}

因此,存储到 objcets 中的文件内容为:

blob 122\0public class Main {public static void main(String [] args) {System.out.println("This is Main.java");}
}

为了更好的验证这个逻辑,我写了一段 Java 记算此 Hash 的测试代码,代码如下:

public static HashData getBlobHash(File file) throws Exception {String content = new String(FileUtil.read(file));String header = "blob " + content.length() + "\0";return getHash(header + content);
}

运行出来的结果正好为 78ace89700a69e490c86f54fbe9d12f0cfb2dbdb , 与 Git 中记算的结果一致。

计算 tree 的 hash

在存储 Main.java 文件类容中,并没有将文件名存储进去,那 Git 是在哪儿存储的呢?对,就是现在要讲的 tree 的结构。先来看一下,我写的示例中的文件结构:

245c179bc9cffd61bd2199a42ee4691b.png

文件目录结构

和 blob 类似, tree 也有对应的内容存储结构:

tree <content length><NUL><file mode> <file name><NUL><file hash>

其中 content length 指的后面<file mode> <file name><NUL><file hash>的长度,NUL\0file mode 指的是文件类型, 列举其中几种:

  • 0100000000000000 (040000): 文件夹

  • 1000000110100100 (100644): 常规非执行文件

  • 1000000110110100 (100664): 常规不可执行组可写文件

  • 1000000111101101 (100755): 常规可执行文件

  • 1010000000000000 (120000): 软链文件

file name 指的是文件名称, file hashblob 或者 tree 的 hash 值,使用的是 20 位二进制值,非 40 位的 16 进制字符串。

具体来看上面例子中的信息,我们从最下面的 blob 文件往上看,通过上面的脚本, 可以计算出 Main.java 的 hash 为:78ace89700a69e490c86f54fbe9d12f0cfb2dbdbnew.txt 的 hash 为:fa49b077972391ad58037050f2a75f74e3671e92,因此,针对 example 这一级目录的文件类容如下:

tree 72<NUL>
100644 Main.java<NUL>78ace89700a69e490c86f54fbe9d12f0cfb2dbdb
100644 new.txt<NUL>fa49b077972391ad58037050f2a75f74e3671e92

PS:为了更好的阅读,上面的内容进行了换行处理,实际文件中并没有换行。格式中提到的 file hash 在此处为了便于展示,直接放了对应的 40 位的 16 进制字符串。

得到文件内容后,可以很方便的计算出 hash 为:f6e2e8e5243c07191d0c1f4353448bd57785c39d。也可以用 git 命令去验证:

➜ git cat-file -p f6e2e8e5243c07191d0c1f4353448bd57785c39d
100644 blob 78ace89700a69e490c86f54fbe9d12f0cfb2dbdb Main.java
100644 blob fa49b077972391ad58037050f2a75f74e3671e92 new.txt
➜ git cat-file -t f6e2e8e5243c07191d0c1f4353448bd57785c39d
tree

当然,计算这个,我也用 Java 实现了一个简单的计算。看一下代码的实现:

File[] allFiles = dir.listFiles();
//按文件名进行字典排序
SortedMap<File, HashData> allHash = new TreeMap<>(Comparator.comparing(File::getName));
for (File file : allFiles) {if (file.getName().equals(".git") || file.getName().equals(".DS_Store")) {continue;}// 计算每一个文件的 hashallHash.put(file, calcHash(file));
}
// 拼接文件子文件的 hash 
byte[] allContent = new byte[0];
for (Map.Entry<File, HashData> item : allHash.entrySet()) {String header = getFileMode(item.getKey()) + " " + item.getKey().getName() + "\0";byte[] content = merge(header, item.getValue().originalData);byte[] tempContent = merge(allContent, content);allContent = tempContent;
}
String header = "tree " + allContent.length + "\0";
byte[] mergedArray = merge(header, allContent);
HashData hash = getHash(mergedArray, "SHA-1");

计算 commit 的 hash

与前面的 tree 和  blob 的相似,按照固定格式进行拼装即可:

commit <content length><NUL>tree <tree hash>
parent <parent hash>
author <username> <email> <timestamp>
committer <username> <email> <timestamp><commit message>

需要注意的是,此格式中的 tree hashparent hash 是 40 位的 16 进制值, 换行也是真实的换行。按照上面的格式进行拼装后,使用 SHA-1 可以很方便的计算出  Hash 值。

Git 存储树结构

通过前面 blob 、tree、以及 commit 计算后,通过 commit 作为入口,就可以将所有的文件夹以及文件内容进行关联起来,构建一个树目录结构。还是前面提到的那个例子,一共执行了三次提交:

  1. 在创建的 Git 项目中,添加 Main.java 后,并使用 git commit -am "first commit" 进行第一次提交

  2. 继续在 com/example 文件夹下,添加 new.txt 文件, 并使用git commit -am "add new file" 进行第二次提交

  3. 修改 Main.java 的内容, 并使用 git commit -am "modify Main.java" 进行第三次提交

在本例中, .git/HEAD 中指向的 .git/refs/heads/main 文件,此文件中存储了 commit 的  hash 值, 作为入口,可以将刚的三次提交,构建出一个树结构,如下图所示:

50cebc72b1ba9aab13502bb71379d859.png

仓库树结构示意图

三、Git LFS 二进制大文件处理

从上面的例子中,可以看到,Main.java 经过第一次的创建与后续的修改,创建了两个 blob ,虽然使用了 zlib 进行了压缩,但针对二进制文件文件,压缩率可以说很微小,这就会导致 objects 中存储的 blob 文件大小会快速增长。当我们首次克隆代码时,会把这些二进制的所有版本文件都下载下来,导致下载速度很慢。并且这些历史版本的文件,你可能永远也用不到,但是却不得不下载它。

6e2504eae19648f9d825a199dffc85ce.png

仓库大小变更

基于此,Git LFS 应运而生,它通过将大文件替换为一个很小的指针文件,并且在使用中,只会将当前需要使用到的二进制文件下载到本地即可。

安装配置 Git LFS

在 mac 上,我喜欢使用 Homebrew 进行安装,使用如下命令就可以安装好 git-lfs

brew install git-lfs

安装好后,就可以对项目进行配置,在不支持 git-lfs 的项目目录下,执行如下命令:

git lfs install

此命令会更新 .git/hooks/ 下的脚本,注入 git 命令的处理流程

f70b8618175fb0d9e4cbf9c8e4bf7270.png

install 前后对比

可以看到,在 hooks 文件夹中,多了 post-checkoutpost-commitpre-push 等脚本,根据这些脚本名称就知道其执行时间点。

下一步便是将我们要跟踪的二进制文件添加到跟踪目录中,使用如下命令:

git lfs track <file-type>
# 例如
# git lfs track "*.db"

执行后,会在项目根目录下创建 .gitattributes 文件,注意,需要将此文件上传到远程库中去。这个文件中的内容格式如下:

*.db filter=lfs diff=lfs merge=lfs -text

Git LFS 指针转移

在这里,我随机生成了一个无序的二进制文件 test.db, 添加到项目中,并且执行 git add . 命令,会将此文件加入进去,从文件目录中可以看到,lfs 目录下新增了一个文件:

afcd366ac5ca43d2dbc1b8a6f75338df.png

LFS文件

而在f2bb0511bd0ce670c75a1ce4d52ecea91bad219b 这个文件中存储的便是 lfs 的文件指针:

version https://git-lfs.github.com/spec/v1
oid sha256:d1ccb0fe6d165aab8c40c6b0aa796bf4f80011c050e168a625d3d2ab46b07306
size 41943040

通过此种方式,进行关联,降低代码库大小。最后,在看看官方图:

f99882238f1342fd07790124630f7a1d.gif

官方图例

写到最后

创建于 2005 年的 Git 中,有很多知识点值得学习,建议有时间同学深入研究。最后,如果文中有错误和遗漏,欢迎与我联系。


http://chatgpt.dhexx.cn/article/osOdTG0i.shtml

相关文章

git 原理简介

文章目录 关于版本控制分为三种版本控制方案本地版本控制集中化的版本控制分布式版本控制 git基本底层原理git提交流程原理Git的引用(分支)git log原理总结参考链接 关于版本控制 什么是“版本控制”&#xff1f;我为什么要关心它呢&#xff1f; 版本控制是一种记录一个或若干文…

【转】以太坊钱包分析与介绍

林修平在亚太区以太坊社区培训与交流Meetup深圳站上跟我们分享了他对以太坊各种钱包模式的介绍&#xff0c;还分析了Parity多签名钱包安全事件的技术原因。昨日&#xff0c;EthFans得到作者授权&#xff0c;分享他的PPT。鉴于PPT较长&#xff0c;为保证阅读体验&#xff0c;编者…

币久网好像暂停ZEC交易了,各位的ZEC币暂时不要转入币久网了

今天突然发现币久网不能交易ZEC了&#xff0c;然后看到了官方公告&#xff0c;难不成ZEC也是P网的&#xff1f; 各位如果需要其他的钱包地址&#xff0c;比如Windows本地Zcash钱包地址&#xff0c;可以移步去我的其他博文看看 http://blog.csdn.net/squirrel1311/article/detai…

『0001』 - 如何通过 MyEtherWallet 创建钱包以及如何通过 Ethereum Wallet 和 MetaMask 恢复钱包账号

视频教程 来源&#xff1a; 黎跃春区块链博客 学习目标 钱包介绍创建钱包发送和接收以太币恢复钱包如何获取以太币 钱包介绍 Ethereum Wallet 钱包 开启Ethereum智能合约开发(Smart Contract)最快的方式就是Ethereum Wallet&#xff0c;它支持Windows, MacOSX 和 Linux开发…

web3.js链接以太坊并查询钱包u余额

web3.js链接以太坊并查询钱包USDT余额 环境:一、链接以太坊主网:二、创建一个ABI那么该如何获取ABI? 三、查询代码四、输出结果五、整体代码 环境: web3.js版本:6.14.15 不会安装以及使用web3.js的小伙伴看:https://blog.csdn.net/qq_45844443/article/details/124330035 一…

Clickhouse 以太坊分析:基础交易数据清洗

概述 读者可前往我的网站获得更好的阅读体验。 笔者最近遇到了许多关于数据分析的文章&#xff0c;大部分都使用了 Dune 等 SaaS 工具&#xff0c;这些工具往往提供了清洗后的区块链数据和数据库分析工具。对于大部分数据分析师而言&#xff0c;这些工具可以应对一系列复杂的…

DApp创建本地钱包并实现签名转账(BSC,Polygon,ETH)

文章目录 1. 项目准备2. 钱包相关概念3.随机创建一个钱包4.根据助记词导入钱包5.根据keystore导入钱包6.签名转账 1. 项目准备 安装ether.js npm install --save ethers引入ether.js:的三种方法 es3: var ethers require(‘ethers’);es5/es6 const ethers require(‘et…

使用ethers.js开发以太坊Web钱包 - 将私钥保存在客户端

为什么需要 Keystore 文件 通过这篇文章理解开发HD 钱包涉及的 BIP32、BIP44、BIP39&#xff0c;私钥其实就代表了一个账号&#xff0c;最简单的保管账号的方式就是直接把私钥保存起来&#xff0c;如果私钥文件被人盗取&#xff0c;我们的数字资产将洗劫一空。 Keystore 文件就…

CEX暴雷怎么办 一文读懂加密钱包产业现状

你的钱其实并不在你的借记卡里&#xff0c;借记卡只是授权你的银行帐户向银行系统数据库发送交易。同样&#xff0c;你的代币也并不在你的加密钱包里。加密钱包只是持有私有密钥以证明对数字资产的所有权&#xff0c;而这些资产是存储在公共区块链网络上的。私钥能让你对加密钱…

区块链隐私保护文献 An Efficient NIZK Scheme for Privacy-Preserving Transactions over Account-Model Blockchain

读&#xff1a;An Efficient NIZK Scheme for Privacy-Preserving Transactions over Account-Model Blockchain 本文的目的 找到一种适用于轻量级设备及智能合约的高效零知识证明 前人的工作 不能直接迁移UTXO模型的隐私保护方案的原因 在考虑用户余额的隐私时&#xff0…

最新消息,我的Zcash钱包收到鱼池的ZEC打款了

最新消息&#xff0c;我的Zcash钱包收到鱼池的ZEC打款了 有图有真相哈&#xff0c;竟然价值17元啊&#xff0c;哈哈&#xff0c;看到钱途了。。。 如果你们需要测试打款的&#xff0c;可以往我Zcash钱包打款哈 我的Zcash钱包地址&#xff1a;t1PH7xk25VYdJcR5TnkEuEq8jpsiQ…

区块链钱包—BTC Java版离线签名交易

对于离线交易不做过多解释~&#xff0c;说白了就是拿上一笔未发出交易记录进行私钥的签名然后广播到链上。 主要是对区块链离线交易进行utxo上链。 代码参考&#xff1a; https://gitee.com/DHing/signature-transaction UnspentUtxo交易查询参考&#xff1a;https://blog.c…

Zcash钱包(ZEC钱包)官方客户端常见命令实测(一)

一、如何查询链接节点 $ ./src/zcash-cli getpeerinfo 二、如何获取一个 t- 开头的钱包地址 $ ./src/zcash-cli getnewaddress 三、如何获取一个 z- 开头的钱包地址 $ ./src/zcash-cli z_getnewaddress

通过代码生成以太坊助记词、根据钱包地址获取私钥

生成助记词、公私钥 新建一个目录 在目录下执行 npm init &#xff08;需安装nodejs&#xff09;nodejs中文官网 安装依赖 &#xff08;没有yarn的需要安装&#xff0c;直接执行 npm install -g yarn&#xff09; yarn add bip39 ethereum-hdwallet 在目录下新建js文件 将下…

windows下的Zcash钱包(ZEC钱包)-zcash4win 1.0.12

官方下载地址&#xff1a;https://zcash4win.com/ 百度云盘下载地址&#xff1a;https://pan.baidu.com/s/1i46zcSt 密码: by6b QQ群交流分享&#xff1a;480956296

windows下的Zcash钱包(ZEC钱包)-zcash4win 1.0.11

官方下载地址&#xff1a;https://zcash4win.com/ 下载完后&#xff0c;直接下一步下一步&#xff0c;安装即可 技巧&#xff1a; 1、安装好后打开&#xff0c;开始下载文件&#xff0c;然后关闭 2、下载这两个文件&#xff1a;https://s3.amazonaws.com/zcashfinalmpc/sprou…

可能最详细的教程,新手如何获取Zcash钱包(ZEC钱包)官方客户端地址的方法

折腾了2天&#xff0c;终于折腾出了Zcash的官方地址了&#xff08;非第三方交易平台的&#xff09; 先上个图&#xff0c;有图有真相嘛&#xff0c;哈哈&#xff0c;图片有水印&#xff0c;看下面那个命令也是获取钱包地址的 好了&#xff0c;开始上教程&#xff0c;我这边使用…

今天终于找到了一款windows下的Zcash钱包(ZEC钱包),推荐给大家

折腾了好几天&#xff0c;想说下linux真心不适合我这类新人&#xff0c;所以导出百度&#xff0c;终于在官网看到一个链接 MacOS&#xff0c;想来这个应该是苹果系统的&#xff0c;有苹果&#xff0c;肯定也会有Windows的&#xff0c;于是点进去继续淘宝 后台请教朋友&#xff…

可能最详细的教程,新手如何获取Zcash钱包(ZEC)官方地址的方法

折腾了2天&#xff0c;终于折腾出了Zcash的官方地址了&#xff08;非第三方交易平台的&#xff09; 先上个图&#xff0c;有图有真相嘛&#xff0c;哈哈&#xff0c;图片有水印&#xff0c;看下面那个命令也是获取钱包地址的 好了&#xff0c;开始上教程&#xff0c;我这边使用…