17 - 排错:冲突解决、损坏修复、大仓库优化
第十七章:排错
即使是最有经验的开发者也会遇到 Git 问题。本章是你的"急救手册"。
17.1 合并冲突深入
17.1.1 冲突类型
内容冲突(Content Conflict)
两个分支修改了同一文件的同一区域:
<<<<<<< HEAD
const config = {
apiUrl: "https://api.production.com",
timeout: 5000
};
=======
const config = {
apiUrl: "https://api.staging.com",
timeout: 10000
};
>>>>>>> feature
解决方法:
// 合并两者的最佳方案
const config = {
apiUrl: "https://api.production.com",
timeout: 10000 // 使用更长的超时
};
删除/修改冲突
一个分支删除了文件,另一个修改了它:
$ git merge feature
CONFLICT (modify/delete): src/legacy.js deleted in feature and modified in HEAD.
解决方法:
# 保留文件
$ git checkout HEAD -- src/legacy.js
$ git add src/legacy.js
# 删除文件
$ git rm src/legacy.js
重命名冲突
两个分支都重命名了同一文件:
CONFLICT (rename/rename): src/old.js renamed in both sides.
17.1.2 高级冲突解决
使用 diff3 风格(推荐)
# 启用 diff3 风格,显示共同祖先
$ git config --global merge.conflictstyle diff3
<<<<<<< HEAD
const timeout = 5000;
||||||| merged common ancestor
const timeout = 3000;
=======
const timeout = 10000;
>>>>>>> feature
// diff3 风格可以看到共同祖先是 3000,HEAD 改成了 5000,feature 改成了 10000
使用 rerere(Reuse Recorded Resolution)
# 启用 rerere,Git 会记住冲突解决方案
$ git config --global rerere.enabled true
# 以后遇到相同的冲突,Git 会自动应用之前的手动解决方案
批量解决冲突
# 接受所有当前分支的修改
$ git checkout --ours .
# 接受所有被合并分支的修改
$ git checkout --theirs .
# 对特定文件
$ git checkout --ours src/config.js
$ git checkout --theirs src/utils.js
# 使用合并工具
$ git mergetool
17.1.3 冲突预防策略
| 策略 | 说明 |
|---|---|
| 频繁同步主分支 | git pull --rebase 或 git rebase main |
| 小批量提交 | 每次提交只做一件事 |
| 代码分区 | 不同开发者负责不同模块 |
| 及时合并 PR | PR 存活时间越长,冲突概率越大 |
| 通信协调 | 修改共享文件前通知团队 |
17.2 仓库损坏修复
17.2.1 仓库完整性检查
# 检查仓库完整性
$ git fsck
Checking object directories: 100% (256/256), done.
Checking objects: 100% (1000/1000), done.
# 检查并显示详细信息
$ git fsck --full
$ git fsck --verbose
# 检查悬空对象(dangling objects)
$ git fsck --dangling
$ git fsck --unreachable
17.2.2 常见损坏问题
损坏的提交对象
# 错误信息
error: object file .git/objects/ab/cdef1234... is empty
fatal: loose object abc1234... is corrupt
# 修复步骤
# 1. 尝试从备份恢复
$ cp /backup/.git/objects/ab/cdef1234* .git/objects/ab/
# 2. 如果有远程仓库,重新克隆
$ git clone --mirror [email protected]:user/repo.git
损坏的 index 文件
# 错误信息
fatal: index file corrupt
# 修复方法
$ rm -f .git/index
$ git reset
# 或
$ git read-tree HEAD
损坏的 HEAD 引用
# 错误信息
fatal: bad default HEAD
# 修复方法
$ cat .git/HEAD
# 如果内容损坏
$ echo "ref: refs/heads/main" > .git/HEAD
17.2.3 恢复丢失的对象
# 1. 查找悬空对象
$ git fsck --unreachable | grep commit
unreachable commit abc1234...
# 2. 查看对象内容
$ git cat-file -p abc1234
tree def5678...
parent ghi9012...
author John <[email protected]> 1705123456 +0800
committer John <[email protected]> 1705123456 +0800
Some commit message
# 3. 创建分支引用
$ git branch recovered abc1234
# 4. 查看对象详情
$ git show abc1234
17.3 大仓库优化
17.3.1 浅克隆
# 只克隆最近一次提交
$ git clone --depth 1 https://github.com/user/repo.git
# 克隆最近 N 次提交
$ git clone --depth 100 https://github.com/user/repo.git
# 后续加深历史
$ git fetch --deepen=100
# 获取完整历史
$ git fetch --unshallow
17.3.2 Partial Clone(部分克隆)
# 只克隆 commit 历史,不下载 blob 对象
$ git clone --filter=blob:none https://github.com/user/repo.git
# 不下载大于指定大小的 blob
$ git clone --filter=blob:limit=1m https://github.com/user/repo.git
# 不下载树对象(最轻量级)
$ git clone --filter=tree:0 https://github.com/user/repo.git
# 按需下载 blob
$ git config remote.origin.promisor true
$ git config remote.origin.partialclonefilter "blob:limit=1m"
17.3.3 Sparse Checkout(稀疏检出)
# 初始化稀疏检出
$ git clone --no-checkout https://github.com/user/repo.git
$ cd repo
$ git sparse-checkout init --cone
# 设置要检出的目录
$ git sparse-checkout set src/api src/shared libs/core
# 检出
$ git checkout main
# 添加更多目录
$ git sparse-checkout add tests/unit
# 查看当前稀疏检出配置
$ git sparse-checkout list
# 禁用稀疏检出(检出所有文件)
$ git sparse-checkout disable
17.3.4 Git GC 优化
# 垃圾回收
$ git gc
# 激进 GC(更彻底但更慢)
$ git gc --aggressive
# 只做增量 repack
$ git gc --incremental
# 清理无用对象
$ git gc --prune=now
# 查看仓库大小
$ git count-objects -vH
count: 150
size: 2.50 MiB
in-pack: 5000
packs: 1
size-pack: 150.00 MiB
garbage: 0
size-garbage: 0 bytes
17.3.5 大文件优化
# 查找大文件
$ git rev-list --objects --all | \
git cat-file --batch-check='%(objecttype) %(objectname) %(objectsize) %(rest)' | \
sed -n 's/^blob //p' | \
sort -rnk2 | \
head -20
# 使用 BFG Repo-Cleaner 清理大文件
$ java -jar bfg.jar --strip-blobs-bigger-than 10M repo.git
# 使用 git filter-repo 清理历史
$ git filter-repo --strip-blobs-bigger-than 10M
17.4 常见错误排查
| 错误信息 | 原因 | 解决方案 |
|---|---|---|
fatal: not a git repository | 不在 Git 仓库目录 | cd 到正确目录或 git init |
fatal: remote origin already exists | 已配置 origin | git remote set-url origin <url> |
error: failed to push some refs | 远程有本地没有的提交 | git pull --rebase 后再 push |
fatal: refusing to merge unrelated histories | 仓库历史不相关 | git merge --allow-unrelated-histories |
error: pathspec 'xxx' did not match | 文件名或分支名错误 | 检查拼写,使用 Tab 补全 |
fatal: You have not concluded your merge | 上次合并未完成 | git merge --abort 或解决冲突 |
error: Entry 'xxx' not uptodate | 有未提交的修改 | git stash 或 git commit |
fatal: unable to access SSL | SSL 证书问题 | git config --global http.sslVerify false |
17.5 数据恢复
17.5.1 恢复删除的分支
# 查找分支的最后提交
$ git reflog | grep "feature-branch"
abc1234 HEAD@{5}: checkout: moving from feature-branch to main
# 恢复分支
$ git branch feature-branch abc1234
17.5.2 恢复被 amend 覆盖的提交
# 误用了 --amend
$ git commit --amend -m "wrong message"
# 找到原始提交
$ git reflog
abc1234 HEAD@{1}: commit: original message
# 恢复
$ git reset --soft abc1234
17.5.3 恢复被 reset 删除的提交
# 危险操作:git reset --hard HEAD~3
# 找到原始 HEAD
$ git reflog
abc1234 HEAD@{0}: reset: moving to HEAD~3
def5678 HEAD@{1}: commit: last good commit
# 恢复
$ git reset --hard def5678
业务场景
| 场景 | 推荐方案 |
|---|---|
| 合并冲突 | diff3 风格 + rerere + mergetool |
| 误删分支/提交 | git reflog 恢复 |
| 大仓库克隆慢 | shallow clone + partial clone + sparse checkout |
| 仓库文件过大 | git filter-repo + Git LFS |
| CI/CD 环境优化 | --depth 1 + --filter=blob:none |
| 多人冲突频繁 | 频繁 rebase + 小批量 PR |
扩展阅读
- git-scm.com: Reset Demystified
- git-scm.com: git-fsck
- BFG Repo-Cleaner
- git-filter-repo
- git-scm.com: Partial Clone
🔗 上一章:16 - GitLab 工作流 | 下一章:18 - 最佳实践