Skip to content

Git 对象模型

Git 的核心是一个内容寻址文件系统,所有数据都以对象的形式存储。Git Doctor 直接解析这些对象,不依赖 git 命令。

对象类型

Git 有四种基本对象类型:

类型说明存储内容
Blob文件内容文件的实际数据
Tree目录结构文件名、权限、子对象引用
Commit提交信息Tree 引用、父提交、作者、消息
Tag标签指向 Commit 的引用

Blob 对象

存储文件的实际内容,不包含文件名或任何元数据。

结构

blob <size>\0<content>

示例

blob 12\0Hello World!

Git Doctor 解析

typescript
// 读取压缩数据
const compressed = fs.readFileSync(objectPath)

// Zlib 解压
const decompressed = zlib.inflateSync(compressed)

// 解析头部
const nullIndex = decompressed.indexOf(0)
const header = decompressed.slice(0, nullIndex).toString()
const [type, size] = header.split(' ')
const content = decompressed.slice(nullIndex + 1)

Tree 对象

存储目录结构,包含文件名、权限模式和子对象引用。

结构

tree <size>\0
<mode> <name>\0<sha-20bytes>
<mode> <name>\0<sha-20bytes>
...

Mode 值说明

Mode说明
100644普通文件
100755可执行文件
040000目录(子 Tree)
120000符号链接
160000Git 子模块

Git Doctor 解析

typescript
function parseTreeObject(content: Buffer): TreeEntry[] {
  const entries = []
  let offset = 0

  while (offset < content.length) {
    // 解析 mode(到空格)
    const spaceIndex = content.indexOf(0x20, offset)
    const mode = content.slice(offset, spaceIndex).toString()

    // 解析 name(到 NUL)
    const nullIndex = content.indexOf(0x00, spaceIndex)
    const name = content.slice(spaceIndex + 1, nullIndex).toString()

    // 读取 20 字节 SHA(二进制)
    const sha = content.slice(nullIndex + 1, nullIndex + 21).toString('hex')

    entries.push({ mode, name, sha })
    offset = nullIndex + 21
  }

  return entries
}

Commit 对象

存储提交的元数据。

结构

commit <size>\0
tree <tree-sha>
parent <parent-sha>
author <name> <email> <timestamp> <timezone>
committer <name> <email> <timestamp> <timezone>

<commit message>

字段说明

字段说明
tree根目录 Tree 对象的 SHA
parent父提交的 SHA(可多个或无)
author原始作者信息
committer实际提交者信息
message提交消息

Git Doctor 解析

typescript
function parseCommitObject(sha: string, content: Buffer): CommitInfo {
  const text = content.toString('utf-8')
  const lines = text.split('\n')

  const info = { sha, tree: '', parents: [], author: '', message: '' }
  let i = 0

  // 解析头部字段
  while (i < lines.length && lines[i] !== '') {
    if (lines[i].startsWith('tree ')) {
      info.tree = lines[i].slice(5)
    } else if (lines[i].startsWith('parent ')) {
      info.parents.push(lines[i].slice(7))
    } else if (lines[i].startsWith('author ')) {
      const match = lines[i].match(/^author (.+) <(.+)> (\d+)/)
      if (match) {
        info.author = match[1]
        info.authorEmail = match[2]
        info.authorDate = new Date(parseInt(match[3]) * 1000)
      }
    }
    i++
  }

  // 空行后是提交消息
  info.message = lines.slice(i + 1).join('\n').trim()
  return info
}

对象存储

松散对象(Loose Objects)

每个对象单独存储为一个文件:

.git/objects/<sha前2字符>/<sha后38字符>

示例:

SHA: e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
路径: .git/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391

存储格式

原始数据 = <type> <size>\0<content>
存储数据 = zlib.deflate(原始数据)

Pack 文件

当对象数量增多时,Git 会打包对象:

.git/objects/pack/
├── pack-<hash>.pack    # 打包的对象数据
└── pack-<hash>.idx     # 索引文件

引用系统

.git/HEAD

内容可能是:

  • 直接 SHA:e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
  • 符号引用:ref: refs/heads/master

分支引用

.git/refs/
├── heads/          # 本地分支
│   ├── master
│   └── develop
├── remotes/        # 远程分支
│   └── origin/
│       └── master
└── tags/           # 标签
    └── v1.0.0

对象关系图

                   HEAD


              refs/heads/main


              ┌─────────┐
              │ Commit  │
              │ abc123  │
              └────┬────┘
                   │ tree

              ┌─────────┐
              │  Tree   │
              │ def456  │
              └────┬────┘
         ┌─────────┼─────────┐
         │         │         │
         ▼         ▼         ▼
    ┌────────┐ ┌────────┐ ┌────────┐
    │  Blob  │ │  Tree  │ │  Blob  │
    │ file.js│ │  src/  │ │ README │
    └────────┘ └────────┘ └────────┘

SHA-1 计算

Git 使用 SHA-1 算法计算对象哈希:

typescript
import * as crypto from 'crypto'

function computeSha(type: string, content: Buffer): string {
  const header = Buffer.from(`${type} ${content.length}\0`)
  const store = Buffer.concat([header, content])
  return crypto.createHash('sha1').update(store).digest('hex')
}

下一步

基于 MIT 许可发布