Git 隐身术:exclude、clean、fetch/pull 与 LFS,一次讲透本地工作区
不想产生 diff 的私人目录、不敢跑的 clean、总搞混的 fetch 和大文件坑——这篇全搞定
你在仓库里放了一套本地调试工具,不想提交、不想改
.gitignore产生 diff,还想git status干干净净——.git/info/exclude就是为此存在的。但 Git 工作区远不止 ignore 这一件事:清文件用错命令会删光.venv,fetch和pull搞混会在错误时机 merge,大文件没走 LFS 会把仓库拖垮。这篇把这几块串成一张地图。
🎯 一张地图:Git 工作区你到底在管什么?
日常开发里,和 Git 打交道其实就四类诉求:
| 诉求 | 你在问的问题 | 对应机制 |
|---|---|---|
| 看不见 | 某些文件/目录不要出现在 git status 里 | ignore 体系 |
| 清干净 | 构建产物、临时文件、误生成的目录怎么删 | clean / restore / reset |
| 同步远程 | 远程有新提交了,我该怎么拉 | fetch / pull |
| 管大文件 | 模型、数据集、大二进制要不要进仓库 | Git LFS |
很多人把这几件事混为一谈——比如以为 git clean 能撤销已修改的代码,或者以为 .gitignore 能隐藏已经 tracked 的文件。下面按层拆开讲。
🏛️ 忽略文件的四层体系
Git 决定「某个路径要不要出现在未跟踪列表里」,会依次叠加多条规则。语法完全一样,差别在于谁生效、会不会进仓库。
┌─────────────────────────────────────────────────────────┐
│ ① 全局 exclude │
│ ~/.gitignore_global (core.excludesfile) │
│ → 所有仓库、仅本机 │
├─────────────────────────────────────────────────────────┤
│ ② 仓库 .gitignore │
│ → 团队共享,会 commit,clone 后人人都有 │
├─────────────────────────────────────────────────────────┤
│ ③ 本机 .git/info/exclude │
│ → 仅当前仓库、仅本机,不进版本库 │
├─────────────────────────────────────────────────────────┤
│ ④ 已 tracked 的文件 │
│ → 以上规则全部无效,必须 git rm --cached 才能「脱钩」 │
└─────────────────────────────────────────────────────────┘
三层 ignore 怎么选?
| 规则放哪里 | 是否 commit | 典型场景 |
|---|---|---|
全局 core.excludesfile | 否 | macOS 的 .DS_Store、编辑器 swap 文件——所有项目通用 |
.gitignore | 是 | __pycache__/、.venv/、构建产物——团队统一 |
.git/info/exclude | 否 | 本地实验目录、私人脚本、还没决定是否提交的 devtools |
配置全局 exclude 一次即可:
git config --global core.excludesfile ~/.gitignore_global
🧠 明星选手:.git/info/exclude
它是什么?
仓库里的 .git/info/exclude,语法与 .gitignore 完全相同,但:
- 不会被
git commit - 不会出现在
git diff里 - 只有你这台机器、这个 clone 生效
查看和编辑:
cat .git/info/exclude
每行一条规则,和 .gitignore 一样支持 *、/ 前缀、目录匹配等。
举个例子
你在 feature 分支上顺手建了一个 sandbox/ 目录:临时脚本、本地配置、还没定稿的小工具,自己用着顺手,也不影响 CI。
但团队还没拍板要不要纳入仓库——写进 .gitignore 会改 tracked 文件,PR 里多出一笔 diff;现在 commit 又显得太早。
解法:把目录写进 .git/info/exclude:
sandbox/
效果:
- 磁盘上文件照常在,命令照跑
git status显示working tree clean- 同事 clone 不会自动带上这些规则——符合「私人实验」预期
以后若要团队化:从 exclude 删掉 → 迁到 .gitignore + 部署侧的 ignore(如 Docker 镜像排除)→ 正常 commit。
exclude vs .gitignore:决策表
| 情况 | 用哪个 |
|---|---|
| 全公司/全团队都要忽略 | .gitignore |
| 只有我不想看见、不影响别人 | .git/info/exclude |
| 所有项目都要忽略 | 全局 core.excludesfile |
| 文件已经被 commit 过 | 先 git rm --cached,再 ignore——换哪个 ignore 文件都一样 |
⚠️ exclude 不能做的事
- 不能让已 tracked 的文件「隐身」——ignore 只对 untracked 路径生效
- 不是安全机制——只是不提交,文件仍在磁盘上,别用来「藏密钥」
- 不能自动同步给同事——正式方案仍要进
.gitignore或文档说明
🔍 工作区清理:restore、checkout、reset、clean 破坏性分级
「把工作区弄干净」至少有三种完全不同的操作,搞混后果差很多。
它们各管什么?
| 命令 | 作用对象 | 干什么 |
|---|---|---|
git restore <file> | 已跟踪文件的工作区 | 丢弃未 stage 的本地修改 |
git restore . / git checkout . | 当前目录下已跟踪文件 | 批量丢弃未 stage 的本地修改(后者是旧写法,效果相同) |
git restore --staged <file> | 暂存区 | 取消 stage,改动保留在工作区 |
git reset --hard <commit> | 工作区 + 暂存区 | 全部回到某 commit,本地改动全没 |
git clean | 未跟踪文件/目录 | 物理删除 |
一句话记忆:restore / checkout . / reset 动的是 Git 已知的文件;clean 动的是 Git 还不认识的文件。
git checkout . 老项目里很常见,和 git restore . 一样只清未 stage 的改动;已 git add 的内容还在暂存区,需要 git restore --staged . 或 git reset 才能一并撤掉。
git clean 详解
# 先预览,养成习惯
git clean -fdn
# 删除未跟踪的文件和目录(仍尊重 ignore / exclude)
git clean -fd
# 连 ignore 规则下的也删——例如 .venv、node_modules
git clean -fdx
| 参数 | 含义 |
|---|---|
-f | force,必须加,否则拒绝执行 |
-d | 包含未跟踪的目录 |
-n | dry-run,只列出会删什么 |
-x | 连 ignored 文件一起删 |
-i | 交互模式,逐个确认 |
和 exclude 的关系:写在 .git/info/exclude 或 .gitignore 里的路径,git clean -fd 不会删;只有加 -x 才会清掉。所以 exclude 里的 sandbox/ 既不会出现在 status,也不会被普通 clean 误伤。
破坏性一览(从低到高)
| 操作 | 丢失什么 | 能救吗 |
|---|---|---|
restore --staged | 无(只是 unstage) | — |
restore <file> / checkout . | 该文件(或当前目录)未 stage 的改动 | 难,除非 IDE 本地历史 |
reset --soft | 无文件内容,只动 commit 指针 | reflog 可救 |
reset --hard | 工作区 + 暂存区改动 | reflog 有限可救 |
clean -fd | 未跟踪文件 | 基本不可救 |
clean -fdx | 未跟踪 + ignored(含 .venv) | 基本不可救 |
git stash:暂时藏起来,不是删掉
临时切分支、又不想 commit 半成品时用:
git stash push -m "wip: feature x"
git stash list
git stash pop
和 clean 的区别:stash 有意保留内容,clean 是直接删。和 exclude 的区别:stash 针对已跟踪文件的改动,exclude 针对不想被 Git 看见的未跟踪路径。
误操作后悔药:git reflog
reset --hard、误删分支之类,reflog 记录 HEAD 移动历史,短期内可以找回:
git reflog
git reset --hard HEAD@{2}
不是万能——clean 删掉的未跟踪文件,reflog 管不了。
🧠 远程同步:fetch 和 pull 到底差在哪?
这是被问最多的问题之一。核心就一句:
git fetch只更新「远程长什么样」;git pull= fetch + 把远程合进你当前分支。
关键细节:pull 只作用于你当前检出的分支。 人在 feature/foo 上时,git pull 更新的是 feature/foo 对应的远程,不会顺带把 develop 拉新。
常见困惑:在别的分支,怎么把 develop 合进来?
很多人(包括我)一开始会这样干:
# 当前在 feature/foo,想把最新的 develop 合进来
git checkout develop
git pull # 更新本地 develop
git checkout feature/foo
git merge develop # 把 develop 并进 feature
能跑通,但四步里有两步纯粹是为了「先更新 develop」。其实 pull 绑的是当前分支,不是「把远程所有分支都同步一遍」。
更省事的做法——不切分支:
git fetch origin # 只更新 origin/* 指针
git merge origin/develop # 仍在 feature/foo 上,直接合远程 develop
# 或
git rebase origin/develop
fetch 之后 origin/develop 已经是最新的,本地 develop 分支是否落后并不重要;merge/rebase 读的是 remote-tracking 分支 origin/develop,不是必须先 checkout develop && pull。
如果团队习惯「本地 develop 也要随时跟远程对齐」,checkout 再 pull 也没错——只是 feature 分支要拿新 develop 时,不必绕这一圈。
时序图
远程 origin/main 多了 3 个 commit
│
┌─────────┴─────────┐
│ │
git fetch git pull(当前在 main 上)
│ │
更新 origin/main 更新 origin/main
指针(remote-tracking) +
│ merge/rebase 到本地 main
本地 main 不变 │
│ 本地 main 前进(或冲突)
│ │
可以先看 diff、 直接改你正在写的分支
再决定怎么合 (在 feature 上 pull 只动 feature)
对比表
git fetch | git pull | |
|---|---|---|
| 改当前分支工作区 | 否 | 是 |
更新 origin/* 指针 | 是 | 是 |
| 可能产生 merge 冲突 | 否 | 是 |
| 典型用法 | 「先看看远程变了啥」 | 「我要立刻合进来继续干」 |
更稳妥的工作流:先 fetch,再决定
git fetch origin
# 看本地比远程落后多少
git log HEAD..origin/main --oneline
# 确认后再合——merge 或 rebase 二选一
git merge origin/main
# 或
git rebase origin/main
git pull 本质是帮你把上面几步打包执行;在多人协作、或当前分支有未 push 的 commit 时,拆开做更可控。
pull 的两种口味
| 方式 | 命令 | 历史形状 |
|---|---|---|
| merge(默认常见) | git pull | 多一个 merge commit |
| rebase | git pull --rebase | 线性,把你的 commit「接到」远程后面 |
团队若有规范,跟规范走;个人分支 rebasing 通常历史更干净。
fetch 不会做的事
- 不会删你本地的分支
- 不会改你工作区里未 commit 的文件
- 不会自动解决冲突
所以 fetch 是相对安全的「只读远程」操作——这也是 CI、IDE 后台同步常用 fetch 的原因。
⚙️ Git LFS:大文件该这样进仓库
问题从哪来?
Git 把每个版本的文件内容都存进对象库。一个 500MB 的模型权重 commit 十次,仓库体积就膨胀几个 GB,clone 和 fetch 都变慢——而且 Git 设计上更适合文本 diff,不适合大二进制。
Git LFS(Large File Storage) 的做法:Git 里只存轻量指针文件,真实 blob 放在 LFS 服务器上。
普通 Git commit:
tree → blob(完整 500MB 文件内容)
LFS commit:
tree → pointer 文本(~130 字节,含 oid、size)
↓
LFS 服务器 → 真实 500MB 文件
什么时候用 LFS?
| 文件类型 | 建议 |
|---|---|
| 源码、配置、小 JSON/YAML | 普通 Git |
| 模型权重、音频/视频、大型 PDF、数据集切片 | Git LFS |
| 本地调试产物、构建 output | ignore / exclude,不进仓库 |
| 敏感凭证 | 绝不进 Git 或 LFS,用环境变量/密钥管理 |
和 exclude 的决策树:
这个文件需要版本管理吗?
├─ 否 → .gitignore / .git/info/exclude
└─ 是 → 体积大吗?
├─ 是 → Git LFS
└─ 否 → 普通 Git
基本使用流程
# 安装并初始化(每个用户机器做一次)
git lfs install
# 指定哪些扩展名走 LFS
git lfs track "*.bin"
git lfs track "models/**"
# .gitattributes 会生成/更新,需要 commit
git add .gitattributes
clone 已有 LFS 的仓库后,若大文件显示成几行 pointer 文本而不是真文件:
git lfs pull
⚠️ LFS 常见坑
| 现象 | 原因 | 处理 |
|---|---|---|
| 文件只有 130 字节左右的文本 | 只 clone 了 pointer,没拉 LFS 对象 | git lfs install + git lfs pull |
| CI 构建缺文件 | Runner 没装 git-lfs 或没 pull | CI 步骤加 git lfs pull |
| 仓库已经很大 | 历史里普通 commit 过大文件 | 需要 git lfs migrate 改写历史——另作专题 |
| 带宽/存储配额爆 | LFS 对象单独计费 | 评估是否真的需要版本管理大文件 |
LFS 不是 ignore 的替代品:pointer 仍然进 Git 历史,只是大 blob 外置;不想版本管理的文件,应该 exclude 或 gitignore。
🚀 进阶一块:和本篇相关的几个邻居
单篇讲透主菜,这几项知道存在即可,需要时再深挖。
Git Worktree:同一仓库多个工作目录
一个 repo 同时检出多个分支到不同目录,共享 .git/objects/,各自独立 HEAD 和暂存区——适合 hotfix 与 feature 并行、或多个 AI Agent 各干各的分支。详见同目录下的 Git Worktree 专题。
skip-worktree / assume-unchanged
对已 tracked 文件告诉 Git「本地改动别显示」——比 exclude 更 hack,适合极少数「必须改本地配置但永不提交」的场景,团队协作容易踩坑,优先用 exclude 管 untracked。
Sparse Checkout
超大 monorepo 只 checkout 子目录,节省磁盘和 IDE 索引时间——和 ignore 无关,但同属「工作区瘦身」工具箱。
⚠️ 一张总坑清单
| 误区 | 真相 |
|---|---|
| ignore 了就能删 tracked 文件 | 不行,要先 git rm --cached |
| exclude 可以藏 API Key | 不行,只是不 commit,磁盘上明文仍在 |
git pull 等于「安全更新」 | 会改当前分支,可能冲突,先 fetch 更稳 |
git clean 能撤销代码修改 | 不能,那是 restore / reset 的事 |
git clean -fd 会删 ignore 里的 .venv | 不会,要加 -x 才会 |
| LFS 让大文件「不进 Git」 | pointer 进 Git,blob 外置;不想版本管理请 ignore |
.gitignore 改完历史 commit 就变小 | 历史仍在,要 filter-repo / BFG 等重写 |
📝 总结
| 你想… | 用这个 |
|---|---|
| 本机私人目录、零 diff、不污染团队 | .git/info/exclude |
| 团队统一忽略规则 | .gitignore |
| 所有仓库通用的忽略 | 全局 core.excludesfile |
| 删掉未跟踪的构建产物 | git clean -fdn 预览 → git clean -fd |
| 丢弃已跟踪文件的本地改动 | git restore 或 git checkout . |
| 只看远程更新、暂不合 | git fetch |
| 拉远程并合进当前分支 | git pull(或 fetch + merge/rebase) |
| 版本管理大二进制 | Git LFS |
| 临时存半成品改动 | git stash |
Git 工作区没有银弹,但有清晰分层:ignore 管「看不看见」,clean/restore 管「删不删」,fetch/pull 管「怎么同步,LFS 管「大文件怎么存」**。把 .git/info/exclude 放进工具箱——下次你有「本地要用、还不想产生 diff」的实验代码,就知道该往哪写了。