跳到主要内容

Git 高级工具

本章介绍 Git 的一些高级工具和技巧,包括深入理解 reset 命令、交互式暂存、凭证存储、GPG 签名等功能。掌握这些高级工具能让你更高效地使用 Git。

深入理解 Reset:三棵树模型

git reset 是 Git 中最容易让人困惑的命令之一。理解它的关键在于认识到 Git 管理着三个不同的"树"。

三棵树是什么?

Git 在正常操作中管理着三棵"树"(这里的"树"指的是"文件的集合"):

用途存储位置
HEAD最后一次提交的快照,下一次提交的父提交.git/HEAD
Index(暂存区)下一次提交的内容.git/index
Working Directory(工作区)沙盒,你实际编辑文件的地方项目目录

理解每棵树

HEAD:当前提交的快照

HEAD 是指向当前分支的指针,而分支是指向提交的指针。所以 HEAD 间接指向一个提交。

# 查看 HEAD 指向的内容
git cat-file -p HEAD

# 输出示例(一个提交对象)
tree cfda3bf7c8b6a1e9b8a5e8c7d6f5a4b3c2d1e0f9
parent 8a7b6c5d4e3f2a1b0c9d8e7f6a5b4c3d2e1f0a9b
author 张三 <[email protected]> 1715000000 +0800
committer 张三 <[email protected]> 1715000000 +0800

添加用户登录功能

HEAD 指向的 tree 对象就是当前提交的快照。

# 查看 HEAD 指向的 tree
git ls-tree -r HEAD

# 输出示例
100644 blob a906cb2a4a904a152e80877d4088654daad0c859 README.md
100644 blob 8f94139338f9404f26296befa88755fc2598c289 package.json
100644 blob e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 src/index.js

Index:暂存区

Index 是你预期的下一次提交。它存储着哪些文件将被包含在下一次提交中。

# 查看 Index 的内容
git ls-files -s

# 输出示例
100644 a906cb2a4a904a152e80877d4088654daad0c859 0 README.md
100644 8f94139338f9404f26296befa88755fc2598c289 0 package.json
100644 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 0 src/index.js

Index 本质上是 HEAD 的一个副本,当你 git add 文件时,Index 会更新。

Working Directory:工作区

工作区是你实际编辑文件的地方。你可以把它理解为"沙盒",在这里你可以自由地修改文件,直到你决定提交它们。

# 工作区就是你的项目目录
ls -la

# 输出示例
drwxr-xr-x .git
-rw-r--r-- README.md
-rw-r--r-- package.json
drwxr-xr-x src

Reset 的工作原理

理解了三棵树之后,git reset 就变得清晰了。reset 命令 essentially 在三个步骤中移动这三棵树:

第一步:移动 HEAD(--soft)

git reset --soft HEAD~1 只做第一件事:移动 HEAD 指针。

  • HEAD 从当前提交移动到上一个提交
  • Index 和 Working Directory 保持不变
  • 结果:看起来就像你撤销了最后一次提交,但所有修改都在暂存区

使用场景:当你想要重新提交最后一次提交的内容,或者修改提交信息时。

第二步:更新 Index(--mixed,默认)

git reset HEAD~1git reset --mixed HEAD~1 做前两件事:

  1. 移动 HEAD 指针
  2. 将 Index 重置为 HEAD 指向的内容
  • HEAD 移动到上一个提交
  • Index 被重置,与 HEAD 一致
  • Working Directory 保持不变
  • 结果:修改回到了工作区(未暂存状态)

使用场景:当你想要撤销提交并重新组织修改时,可以选择性地暂存文件。

第三步:更新 Working Directory(--hard)

git reset --hard HEAD~1 做全部三件事:

  1. 移动 HEAD 指针
  2. 将 Index 重置为 HEAD 指向的内容
  3. 将 Working Directory 重置为 Index 的内容
  • 所有内容都回退到上一个提交的状态
  • 当前提交的所有修改都会丢失

使用场景:当你确定要完全放弃当前的所有修改时。谨慎使用!

Reset 实战示例

假设你做了三次提交:

A <- B <- C (HEAD)

现在你想撤销最近的两次提交(B 和 C),但保留修改。

# 查看当前状态
git log --oneline
# c3d4e5f (HEAD -> main) 第三次提交
# b2c3d4e 第二次提交
# a1b2c3d 第一次提交

# 软重置:撤销提交,保留修改在暂存区
git reset --soft HEAD~2
git status
# Changes to be committed:
# 修改的内容...

# 混合重置:撤销提交,保留修改在工作区
git reset --mixed HEAD~2
git status
# Changes not staged for commit:
# 修改的内容...

# 硬重置:完全撤销,丢弃所有修改
git reset --hard HEAD~2
git status
# nothing to commit, working tree clean

Reset vs Checkout

git resetgit checkout 都涉及移动 HEAD,但行为不同:

命令HEADIndexWorking Directory是否安全
reset --soft移动不变不变安全
reset --mixed移动重置不变安全
reset --hard移动重置重置危险
checkout移动重置重置安全(会检查未提交修改)

关键区别:

  • checkout 会检查工作区是否有未提交的修改,如果有会拒绝切换
  • reset --hard 不会检查,直接覆盖

用路径参数的 Reset

git reset 还可以接受文件路径参数:

# 只重置特定文件的暂存
git reset HEAD -- filename.txt

# 这相当于
git restore --staged filename.txt

这种用法只会更新 Index,不会移动 HEAD。它本质上是"取消暂存"操作。

交互式暂存详解

git add -igit add --interactive 提供了一个交互式界面,让你更精确地控制暂存内容。

启动交互式暂存

git add -i

输出示例:

           staged     unstaged path
1: unchanged +0/-1 TODO
2: unchanged +1/-1 index.html
3: unchanged +5/-1 lib/simplegit.rb

*** Commands ***
1: status 2: update 3: revert 4: add untracked
5: patch 6: diff 7: quit 8: help
What now>

常用命令

status:查看状态

显示每个文件的暂存和未暂存的修改行数。

update:更新暂存

选择要暂存的文件:

What now> 2
staged unstaged path
1: unchanged +0/-1 TODO
2: unchanged +1/-1 index.html
3: unchanged +5/-1 lib/simplegit.rb
Update>> 1,2

revert:取消暂存

从暂存区移除已暂存的文件。

add untracked:添加未跟踪文件

添加新创建的文件到暂存区。

patch:交互式补丁

这是最强大的功能,让你逐块选择要暂存的内容:

What now> 5
staged unstaged path
1: unchanged +0/-1 TODO
2: unchanged +1/-1 index.html
3: unchanged +5/-1 lib/simplegit.rb
Patch update>> 1

然后 Git 会显示每个修改块,让你决定是否暂存:

diff --git a/TODO b/TODO
index 1234567..89abcde 100644
--- a/TODO
+++ b/TODO
@@ -1,3 +1,2 @@
- 完成文档
-- 添加测试
+- 添加单元测试
(1/1) Stage this hunk [y,n,q,a,d,e,?]?

选项说明:

选项含义
y暂存这个块
n不暂存这个块
q退出
a暂存这个块及之后所有块
d不暂存这个块及之后所有块
e手动编辑这个块
s分割成更小的块

diff:查看差异

显示已暂存内容的差异。

使用 git add -p 快速交互式暂存

如果你只想使用 patch 功能,可以直接使用:

git add -p
# 或
git add --patch

这会跳过菜单,直接进入块选择模式。

交互式暂存的使用场景

场景一:分离不相关的修改

你在一个文件中做了两处不相关的修改,想分别提交:

# 使用交互式暂存
git add -p filename.txt

# 第一处修改选择 y
# 第二处修改选择 n

# 提交第一个修改
git commit -m "修改一:修复登录问题"

# 然后暂存第二个修改
git add filename.txt
git commit -m "修改二:更新用户界面"

场景二:部分暂存调试代码

你在调试时添加了一些 console.log,不想提交它们:

git add -p src/main.js

# 对于 console.log 的块选择 n
# 对于真正的修改选择 y

凭证存储

当使用 HTTPS 协议与远程仓库通信时,每次推送都需要认证。Git 提供了多种凭证存储方式。

凭证存储选项

选项存储位置安全性适用场景
cache内存低(明文)临时使用
store磁盘文件低(明文)个人开发机
manager系统凭证管理器推荐

cache:内存缓存

将凭证缓存在内存中,不写入磁盘:

# 缓存凭证 15 分钟(默认)
git config --global credential.helper cache

# 缓存 1 小时
git config --global credential.helper 'cache --timeout=3600'

# 缓存 8 小时
git config --global credential.helper 'cache --timeout=28800'

# 缓存 1 天
git config --global credential.helper 'cache --timeout=86400'

优点

  • 凭证不写入磁盘
  • 超时后自动清除

缺点

  • 每次重启终端需要重新输入
  • 安全性一般

store:文件存储

将凭证永久存储在磁盘文件中:

# 存储凭证到 ~/.git-credentials
git config --global credential.helper store

# 指定存储文件
git config --global credential.helper 'store --file ~/.my-git-credentials'

存储格式:

https://username:[email protected]
https://username:[email protected]

优点

  • 永久存储,无需重复输入

缺点

  • 明文存储,安全性低
  • 不适合多用户系统

manager:系统凭证管理器

使用操作系统的凭证管理器:

# Windows
git config --global credential.helper manager

# macOS
git config --global credential.helper osxkeychain

# Linux (需要安装 libsecret)
git config --global credential.helper /usr/share/doc/git/contrib/credential/libsecret/git-credential-libsecret

优点

  • 加密存储
  • 与系统集成
  • 安全性高

推荐:这是最安全、最便捷的方式。

多账户配置

如果你有多个 Git 账户,可以使用条件配置:

# ~/.gitconfig
[credential]
helper = manager

[credential "https://github.com"]
username = personal-user

[credential "https://github.company.com"]
username = work-user

使用 Personal Access Token

GitHub 从 2021 年 8 月起不再支持密码认证,需要使用 Personal Access Token:

# 1. 在 GitHub 创建 Token
# Settings -> Developer settings -> Personal access tokens -> Generate new token

# 2. 推送时使用 Token 作为密码
git push
# Username: your-username
# Password: ghp_xxxxxxxx(你的 Token)

# 3. 凭证管理器会自动保存

凭证清除

# 清除缓存的凭证
git credential-cache exit

# 清除 store 存储的凭证
rm ~/.git-credentials

# 或编辑文件删除特定行

GPG 签名

GPG(GNU Privacy Guard)签名可以证明提交确实是由你创建的,增强代码的可信度。

为什么需要签名?

在 Git 中,任何人都可以设置任何用户名和邮箱:

# 这是可以的,但会带来信任问题
git config user.name "Linus Torvalds"
git config user.email "[email protected]"

GPG 签名解决了这个问题:只有拥有对应 GPG 私钥的人才能创建有效的签名。

安装 GPG

Windows

# 下载安装 Gpg4win
# https://www.gpg4win.org/

# 或使用 winget
winget install GnuPG.Gpg4win

macOS

brew install gnupg

Linux

# Debian/Ubuntu
sudo apt install gnupg

# CentOS/RHEL
sudo yum install gnupg2

# Fedora
sudo dnf install gnupg2

生成 GPG 密钥

# 生成新密钥
gpg --full-generate-key

# 选择:
# (1) RSA and RSA (default)
# 密钥大小:4096
# 过期时间:选择合适的(如 1y)
# 输入姓名和邮箱(与 Git 配置一致)

# 列出密钥
gpg --list-secret-keys --keyid-format=long

# 输出示例
sec rsa4096/3AA5C34371567BD2 2024-01-01 [SC] [expires: 2025-01-01]
3AA5C34371567BD2...
uid [ultimate] 张三 <[email protected]>
ssb rsa4096/BF4F8E8A6B7E4C2D 2024-01-01 [E] [expires: 2025-01-01]

记下 sec 行中的密钥 ID(如 3AA5C34371567BD2)。

导出公钥

# 导出公钥
gpg --armor --export 3AA5C34371567BD2

# 复制输出的内容,从 -----BEGIN PGP PUBLIC KEY BLOCK----- 到 -----END PGP PUBLIC KEY BLOCK-----

将公钥添加到 GitHub:

  1. 进入 Settings -> SSH and GPG keys
  2. 点击 "New GPG key"
  3. 粘贴公钥内容

配置 Git 使用 GPG

# 设置签名密钥
git config --global user.signingkey 3AA5C34371567BD2

# 启用提交签名
git config --global commit.gpgsign true

# 启用标签签名
git config --global tag.gpgsign true

# 配置 GPG 程序路径(如果需要)
git config --global gpg.program gpg

签名提交

# 正常提交(已启用自动签名)
git commit -m "添加新功能"

# 手动签名提交
git commit -S -m "添加新功能"

# 查看签名信息
git log --show-signature

# 输出示例
commit abc123def456...
gpg: Signature made 2024-01-01
gpg: using RSA key 3AA5C34371567BD2
gpg: Good signature from "张三 <[email protected]>" [ultimate]
Author: 张三 <[email protected]>
Date: Mon Jan 1 10:00:00 2024 +0800

添加新功能

签名标签

# 创建签名标签
git tag -s v1.0.0 -m "版本 1.0.0"

# 验证标签
git tag -v v1.0.0

# 输出示例
gpg: Signature made 2024-01-01
gpg: using RSA key 3AA5C34371567BD2
gpg: Good signature from "张三 <[email protected]>" [ultimate]

GPG Agent 配置

避免每次签名都输入密码:

# ~/.gnupg/gpg-agent.conf
default-cache-ttl 3600
max-cache-ttl 7200

# 重启 agent
gpgconf --kill gpg-agent

故障排除

问题:签名失败

# 检查 GPG 是否正确安装
gpg --version

# 检查密钥是否存在
gpg --list-secret-keys

# 测试签名
echo "test" | gpg --clearsign

问题:Git 找不到 GPG

# 指定 GPG 路径
where gpg # Windows
which gpg # Linux/macOS

git config --global gpg.program "C:/Program Files (x86)/GnuPG/bin/gpg.exe"

问题:IDE 不支持 GPG

某些 IDE 可能不支持 GPG 签名。可以只在命令行签名:

# 只对特定仓库禁用签名
git config commit.gpgsign false

# 或全局禁用,需要时手动签名
git config --global commit.gpgsign false

Rerere:重用冲突解决方案

git rerere(Reuse Recorded Resolution)是一个较少人知道但非常有用的功能,它可以记住你是如何解决冲突的,并在下次遇到相同冲突时自动应用。

启用 Rerere

# 启用 rerere
git config --global rerere.enabled true

# 或者在特定仓库启用
git config rerere.enabled true

Rerere 的工作原理

当你解决合并冲突后,Git 会记录:

  1. 冲突发生前的状态
  2. 冲突发生时的状态
  3. 你解决冲突后的状态

下次遇到相同的冲突时,Git 会自动应用之前的解决方案。

使用场景

场景一:长期运行的分支合并

你有一个长期运行的分支,经常需要与 main 分支同步:

# 第一次合并,手动解决冲突
git merge main
# 解决冲突...
git commit

# rerere 记录了解决方案

# 后来再次合并
git merge main
# 如果有相同的冲突,rerere 会自动解决

场景二:测试合并

你想测试一个合并是否会有冲突,但不想实际完成合并:

# 合并,让 rerere 记录冲突
git merge feature-branch

# 查看冲突解决方案
git rerere diff

# 放弃合并(但 rerere 已经记录了)
git merge --abort

# 之后再合并时,会自动应用解决方案
git merge feature-branch

Rerere 命令

# 查看当前状态
git rerere status

# 查看解决方案的差异
git rerere diff

# 清除解决方案记录
git rerere gc

# 手动应用解决方案
git rerere

Rerere 存储

Rerere 的数据存储在 .git/rr-cache/ 目录中:

# 查看存储的解决方案
ls .git/rr-cache/

# 每个目录对应一个冲突解决方案
# 包含 preimage(冲突前)、postimage(解决后)等文件

注意事项

  1. 验证自动解决方案:rerere 自动应用的解决方案可能不完全正确,务必检查结果
  2. 分享解决方案.git/rr-cache/ 可以提交到仓库,让团队成员共享解决方案
  3. 清理旧记录:定期运行 git rerere gc 清理过期的记录

高级配置技巧

Git Attributes

.gitattributes 文件用于定义特定文件的处理方式:

# 自动检测文本文件并规范化换行符
* text=auto

# 指定文件的换行符
*.sh text eol=lf
*.bat text eol=crlf

# 标记为二进制文件(不进行差异比较)
*.png binary
*.jpg binary

# 自定义差异比较工具
*.docx diff=pandoc

# 使用 LFS
*.psd filter=lfs diff=lfs merge=lfs -text

Git Ignore 模板

创建全局 gitignore 模板:

# 设置全局 gitignore 文件
git config --global core.excludesfile ~/.gitignore_global

# ~/.gitignore_global 内容
# IDE
.idea/
.vscode/
*.swp

# macOS
.DS_Store

# Windows
Thumbs.db

# 编译输出
*.o
*.class

Git Config Include

使用 include 指令组织配置:

# ~/.gitconfig
[include]
path = ~/.gitconfig.alias
path = ~/.gitconfig.work

# ~/.gitconfig.alias
[alias]
st = status
co = checkout
br = branch
ci = commit

# ~/.gitconfig.work(工作相关配置)
[user]
name = 工作用名
email = [email protected]

条件配置:

# 根据目录使用不同配置
[includeIf "gitdir:~/work/"]
path = ~/.gitconfig.work

[includeIf "gitdir:~/personal/"]
path = ~/.gitconfig.personal

Git Worktree:多工作目录

当你需要同时在多个分支上工作时,反复切换分支会变得很繁琐。git worktree 允许你将同一个仓库的多个分支同时检出到不同的目录,每个目录都是一个独立的工作区。

为什么需要 Worktree?

想象这样一个场景:你正在 feature 分支上开发新功能,突然需要紧急修复一个生产环境的 bug。传统做法是:

  1. 暂存当前工作(git stash
  2. 切换到 main 分支
  3. 创建 hotfix 分支
  4. 修复 bug
  5. 切换回 feature 分支
  6. 恢复暂存的工作

这个过程不仅繁琐,还容易出错。使用 worktree,你可以直接在另一个目录中处理紧急修复,无需打扰当前工作:

# 在新目录中创建 hotfix 分支的工作树
git worktree add ../hotfix hotfix

Worktree 的工作原理

关键点:所有 worktree 共享同一个 Git 仓库(对象数据库、配置、引用),但各自拥有独立的 HEAD、暂存区和工作目录。

基本用法

创建工作树

# 创建新工作树,自动创建同名分支
git worktree add ../hotfix

# 基于现有分支创建工作树
git worktree add ../feature feature-branch

# 创建新分支并检出
git worktree add -b new-feature ../new-feature

# 在特定提交处创建分离 HEAD 的工作树
git worktree add --detach ../detached abc123

# 创建孤儿分支(用于新项目初始化)
git worktree add --orphan new-project ../new-project

列出工作树

# 列出所有工作树
git worktree list

# 输出示例
F:/project/main abc1234 [main]
F:/project/hotfix def5678 [hotfix]
F:/project/feature ghi9012 [feature/new-api]

# 详细信息
git worktree list -v

锁定工作树

如果工作树位于可移动设备或网络共享上,可以锁定它防止被自动清理:

# 锁定工作树
git worktree lock ../hotfix

# 锁定并说明原因
git worktree lock --reason "位于USB设备" ../hotfix

# 解锁
git worktree unlock ../hotfix

移动工作树

# 移动工作树到新位置
git worktree move ../hotfix ../new-location/hotfix

删除工作树

# 删除干净的工作树
git worktree remove ../hotfix

# 强制删除(有未提交修改)
git worktree remove --force ../hotfix

# 清理已删除目录的工作树元数据
git worktree prune

工作树的分支限制

一个分支同一时间只能在一个工作树中检出。如果尝试在另一个工作树中检出已被检出的分支,会收到错误:

$ git worktree add ../feature main
fatal: 'main' is already checked out at 'F:/project/main'

解决方案:

# 在现有工作树中先切换分支
cd ../main && git checkout develop

# 或强制创建(会破坏现有工作树的状态,谨慎使用)
git worktree add --force ../feature main

实际应用场景

场景一:紧急修复

# 正在 feature 分支开发,收到紧急 bug 通知
# 在新目录创建 hotfix 分支
git worktree add -b hotfix-urgent ../hotfix main

# 切换到 hotfix 目录
cd ../hotfix

# 修复并测试
git add .
git commit -m "fix: 修复紧急问题"

# 推送修复
git push origin hotfix-urgent

# 切回原工作继续开发
cd ../project

# 删除 hotfix 工作树
git worktree remove ../hotfix

场景二:并行开发多个功能

# 为三个功能创建三个工作树
git worktree add ../feature-auth feature/auth
git worktree add ../feature-api feature/api
git worktree add ../feature-ui feature/ui

# 可以在不同终端窗口同时工作
# 终端1:cd ../feature-auth
# 终端2:cd ../feature-api
# 终端3:cd ../feature-ui

场景三:运行长时间测试

# 在主目录开发,在另一个目录运行测试
git worktree add ../testing main

# 在测试目录运行测试
cd ../testing
npm run long-running-test

# 同时在主目录继续开发
cd ../project
# 继续编码...

Worktree 与子模块的区别

特性WorktreeSubmodule
本质同一仓库的多份工作拷贝嵌入另一个仓库
分支关系同一仓库的不同分支完全独立的仓库
共享数据共享对象数据库各自独立的对象库
使用场景并行开发、紧急修复依赖管理、代码复用

配置选项

# 设置默认使用相对路径
git config worktree.useRelativePaths true

# 设置工作树自动清理时间
git config gc.worktreePruneExpire "3.months.ago"

# 启用工作树特定配置
git config extensions.worktreeConfig true

# 然后可以为每个工作树设置独立配置
git config --worktree user.email "[email protected]"

Sparse Checkout:稀疏检出

大型仓库可能包含数千个文件和目录,但你通常只需要其中一小部分。Sparse checkout(稀疏检出)允许你只检出需要的文件和目录,显著减少磁盘占用和加快操作速度。

适用场景

  • 单体仓库(Monorepo):包含多个项目的大型仓库,只需检出特定项目
  • 大型前端项目:只需要特定模块或组件
  • 深度嵌套的项目结构:只关注特定目录
  • 磁盘空间有限:减少不必要的文件占用

基本用法

启用稀疏检出

# 方法一:克隆时直接启用稀疏检出(推荐)
git clone --filter=blob:none --sparse https://github.com/large-org/monorepo.git
cd monorepo
git sparse-checkout set project-a project-b

# 方法二:在现有仓库中启用
git sparse-checkout init
git sparse-checkout set src/components src/utils

设置检出目录

# 设置要检出的目录(会覆盖之前设置)
git sparse-checkout set project-a project-b project-c

# 从标准输入读取
git sparse-checkout set --stdin << EOF
project-a
project-b
project-c
EOF

# 添加新目录(不覆盖原有设置)
git sparse-checkout add project-d

# 查看当前设置
git sparse-checkout list

禁用稀疏检出

# 恢复完整检出
git sparse-checkout disable

# 重新启用之前的设置
git sparse-checkout init

Cone 模式

Git 默认使用"cone 模式",这是性能最优的模式。在这种模式下,你只需指定目录名,Git 会自动包含该目录下的所有内容。

# 检出 projects/frontend 及其所有子目录
git sparse-checkout set projects/frontend

# 等价于包含:
# - 根目录下的所有文件
# - projects/ 目录下的所有文件(不递归)
# - projects/frontend/ 目录下的所有文件(递归)

非 Cone 模式

如果需要更精细的控制,可以使用非 cone 模式(但性能较差,不推荐):

# 启用非 cone 模式
git sparse-checkout set --no-cone << EOF
/src/components/**
!/src/components/test/**
*.md
EOF

Sparse Index

Git 2.37+ 支持 sparse index,可以进一步提升性能:

# 启用 sparse index(默认关闭)
git sparse-checkout set --sparse-index src/

# 这会使索引文件只包含稀疏检出范围内的条目
# 对 git status、git add 等命令有显著性能提升

与 Partial Clone 结合

Sparse checkout 与 partial clone(部分克隆)结合使用效果最佳:

# 部分克隆 + 稀疏检出
git clone --filter=blob:none --sparse https://github.com/large-org/monorepo.git

cd monorepo

# 设置稀疏检出目录
git sparse-checkout set project-a

这种组合的优势:

  • 部分克隆:不立即下载文件内容,只下载目录结构
  • 稀疏检出:只检出需要的目录
  • 按需获取:需要时才下载文件内容

稀疏检出与深度克隆

# 稀疏检出 + 浅克隆
git clone --depth 1 --filter=blob:none --sparse https://github.com/large-org/monorepo.git

实际案例

案例:Chrome 源码

# Chrome 源码非常庞大(20GB+),但我们可能只需要特定模块

# 克隆时不下载文件内容
git clone --filter=blob:none https://chromium.googlesource.com/chromium/src

cd src

# 只检出需要的目录
git sparse-checkout set chrome/browser ui/base third_party/blink

案例:前端 Monorepo

# 一个包含多个应用的 monorepo
# apps/web-app
# apps/admin-panel
# apps/mobile-app
# packages/shared
# packages/ui-kit

# 作为前端开发者,只需要 web-app 和相关包
git clone --filter=blob:none --sparse https://github.com/company/monorepo.git
cd monorepo
git sparse-checkout set apps/web-app packages/shared packages/ui-kit

注意事项

  1. 分支切换:切换分支时,稀疏检出设置会保持,但如果新分支有目录结构变化,可能需要重新应用:
git sparse-checkout reapply
  1. 合并操作:合并时如果涉及稀疏检出范围外的文件,可能会产生问题。解决方法是临时禁用稀疏检出:
git sparse-checkout disable
git merge feature-branch
git sparse-checkout init
  1. IDE 兼容性:某些 IDE 可能不完全支持稀疏检出,需要额外配置或插件。

Git Maintenance:自动维护

Git 2.30+ 引入了 git maintenance 命令,用于自动执行仓库维护任务,保持仓库性能最优。相比手动运行 git gc,自动维护更加智能和非阻塞。

为什么需要自动维护?

Git 仓库在使用过程中会积累各种需要清理的数据:

  • 松散对象:未打包的对象文件
  • 过期引用:不再需要的引用日志
  • 碎片化:频繁操作导致的数据碎片

传统做法是定期运行 git gc,但这会阻塞操作,而且容易被遗忘。

维护任务类型

Git 维护分为三种频率:

频率任务说明
每小时prefetchloose-objectsincremental-repack轻量维护,快速完成
每天gc完整垃圾回收
每周commit-graphpack-refs优化仓库结构

启用自动维护

# 为当前仓库启用自动维护
git maintenance start

# 这会创建一个定时任务(Linux/macOS 使用 cron,Windows 使用任务计划程序)

注册仓库

# 将仓库添加到维护列表
git maintenance register

# 从维护列表移除
git maintenance unregister

# 查看维护状态
git maintenance repo

手动运行维护

# 运行所有适当的维护任务
git maintenance run

# 运行特定频率的任务
git maintenance run --schedule=hourly
git maintenance run --schedule=daily
git maintenance run --schedule=weekly

# 运行特定任务
git maintenance run --task=gc
git maintenance run --task=commit-graph
git maintenance run --task=prefetch

# 运行所有任务
git maintenance run --task=prefetch --task=loose-objects --task=commit-graph --task=gc --task=pack-refs

维护任务详解

prefetch(预获取)

# 在后台获取远程仓库更新,不修改本地分支
# 类似于 git fetch,但不会更新远程跟踪分支
# 下次 git fetch 会更快
git maintenance run --task=prefetch

预获取的工作原理:

  • 下载远程仓库的对象到本地
  • 不更新 refs/remotes/ 中的引用
  • 后续 git fetch 只需要处理增量更新

loose-objects(松散对象清理)

# 将松散对象打包成 pack 文件
git maintenance run --task=loose-objects

当仓库中有大量松散对象时,会:

  1. 将松散对象打包到 .git/objects/pack/ 目录
  2. 删除已打包的松散对象文件
  3. 减少文件数量,提升性能

incremental-repack(增量重打包)

# 执行增量打包,比完整 gc 更快
git maintenance run --task=incremental-repack

增量重打包会:

  1. 将多个小 pack 文件合并成更大的 pack 文件
  2. 重新组织对象以提高访问效率
  3. git gc 更快,不会完全重写 pack 文件

commit-graph(提交图)

# 生成或更新提交图文件
git maintenance run --task=commit-graph

提交图是一个二进制文件,存储提交之间的祖先关系。它能显著加速以下操作:

  • git log --graph
  • git commit --graph
  • 分支拓扑查询

gc(垃圾回收)

# 执行完整垃圾回收
git maintenance run --task=gc

这是传统的 git gc 操作,包括:

  • 打包所有松散对象
  • 清理过期数据
  • 优化仓库结构

pack-refs(打包引用)

# 将引用打包到单个文件
git maintenance run --task=pack-refs

这会将 .git/refs/ 目录下的引用文件打包到 .git/packed-refs,减少文件数量。

配置选项

# 禁用特定任务
git config maintenance.gc.enabled false

# 修改任务调度频率
git config maintenance.gc.schedule daily

# 自定义任务参数
git config maintenance.loose-objects.batchSize 1000

# 启用后台维护
git config maintenance.auto true

# 设置维护锁超时
git config maintenance.lockTimeout 3600

与传统 gc 的对比

方面git gcgit maintenance
运行方式手动/自动触发后台定时运行
阻塞操作是,可能耗时较长否,增量执行
性能影响短时间高负载分散负载
维护效果完整清理渐进式维护
适用场景不定期深度清理日常自动维护

最佳实践

# 1. 新克隆的仓库,注册自动维护
git clone https://github.com/user/repo.git
cd repo
git maintenance register

# 2. 配置首选项
git config maintenance.auto true
git config maintenance.strategy incremental

# 3. 对于大型仓库,使用更激进的策略
git config maintenance.gc.schedule weekly
git config maintenance.incremental-repack.schedule hourly

# 4. 定期检查维护状态
git maintenance repo

小结

本章我们学习了:

  1. 三棵树模型:深入理解 HEAD、Index、Working Directory 之间的关系
  2. Reset 原理:--soft、--mixed、--hard 三种模式的区别
  3. 交互式暂存:精确控制暂存内容
  4. 凭证存储:cache、store、manager 三种方式
  5. GPG 签名:证明提交的真实性
  6. Rerere:重用冲突解决方案
  7. Git Worktree:多工作目录并行开发
  8. Sparse Checkout:稀疏检出大型仓库
  9. Git Maintenance:自动后台维护

掌握这些高级工具,能让你更高效、更安全地使用 Git。

参考资料