本节摘要:
git add是把工作区修改登记进暂存区的桥——它不复制文件,而是记录当前内容快照并更新索引。本节讲清 add 在"三区模型"中的位置、为什么需要暂存区,再逐个演示四种用法:添加单/多文件、添加目录、添加全部变化、交互式部分暂存(-p),最后用 status 验证效果。
阅读完本节,你应当能够:
假设你一个下午干了三件事:修了一个 bug(改了 app.js)、给文档加了一段说明(改了 readme.md)、顺手调了一下配置(改了 config.json)。现在你想提交,但不想把三件事混在一条历史里——你希望历史记录能讲清楚"这条提交是修 bug,那条是改文档"。
没有暂存区的系统怎么做?要么一次提交三件混着的事,要么为每件事单独开一个分支来回切。两种都笨。
Git 的解法就是在中间插一个暂存区:你先用 add 把 app.js 的改动放进暂存区,提交一次(提交信息写"修复 bug");再把 readme.md 放进去,提交一次("更新文档");最后处理 config.json。三次 add 夹两次 commit,历史清晰如水晶。
这就是 add 存在的全部意义:它让你决定"下一次提交装什么"。工作区是你的草稿纸,暂存区是你要装订的页签,commit 才是装订成册的动作。
回顾三区模型:工作区是你编辑文件的地方,暂存区是"待提交清单",仓库是历史档案。add 就是从工作区往暂存区输送内容的管道。
一个关键细节再强调一遍:add 不是把文件"搬"过去,而是记录文件当前内容的快照,然后在索引(.git/index)里登记"这个文件的新版本要进下次提交"。所以:
这个"快照登记"机制,是 Git 相比"复制粘贴式版本管理"的核心优势——它记录的是"意图"(我选了哪些版本),而不是机械地复制文件。
在讲用法之前,先补一个底层机制:add 往对象库写入什么。当你执行 git add index.html 时,Git 会做三件事——把 index.html 的内容压缩成一个 Blob 对象写进 .git/objects;给这个对象算一个 SHA-1 哈希;把"index.html 应指向这个对象"的引用更新到索引文件。整个过程在本地完成,毫秒级,不涉及任何网络。
这意味着:add 已经占用了对象库空间(写入了一份内容快照),即使你后来不提交、用 restore --staged 撤下,这个对象也可能残留在对象库里(直到被垃圾回收清理)。对普通项目这无足轻重,但它解释了为什么说"add 是快照"——快照在 add 那一刻就生成了,commit 只是把它正式挂进历史。

一、添加指定文件。 最精确的用法,指定一个或多个文件:
git add index.html git add style.css script.js # 多个文件空格隔开
二、添加整个目录。 递归添加目录下所有新建和修改的文件:
git add src # src 目录下所有变化
三、添加全部变化。 一个点代表"当前目录全部新建和修改的文件":
git add .
注意:git add . 不包含已删除的文件。想连删除一起记录,用 -A(--all):
git add -A
-A 是"所有变化"(新建、修改、删除)的完整写法,等于 git add .(处理新增修改)加上 git add -u(处理已跟踪文件的修改与删除)。
四、交互式部分暂存。 最精细的用法,只暂存一个文件里的部分修改块(hunk):
git add -p index.html
执行后 Git 会逐个显示修改块,问你怎么处理,常用选项:
| 选项 | 含义 |
|---|---|
| y | 暂存当前块 |
| n | 不暂存当前块 |
| q | 退出 |
| a | 暂存本文件剩余所有块 |
| s | 把当前块拆成更小的块 |
| e | 手动编辑当前块 |
-p 是"把提交拆得跟你的思路一样细"的高级武器。它的典型场景:一个文件里同时改了功能代码和格式排版,你想把两件事拆成两次提交。
add 向左是登记,commit 向右是固化。add 决定"装什么",commit 决定"什么时候装"。
add 本身几乎不输出信息(除非出错),它是否成功要看 git status。理解 status 的三个分区:
| status 分区 | 含义 | 对应动作 |
|---|---|---|
| Changes to be committed | 已暂存,准备提交 | 已 add,下一步 commit |
| Changes not staged for commit | 已跟踪但工作区有新改动 | 未 add,需要 add |
| Untracked files | 从未被 Git 跟踪的新文件 | 需要 add 才开始跟踪 |
一个经典场景:你 add 了 app.js,然后又改了它。此时 status 会同时显示:
看到这个不要慌,它是"三区不一致"的正常呈现,意味着你需要决定:再 add 一次把新改动也纳入,还是不 add 只提交旧版本。
跟着敲一遍,感受状态如何一步步变化:
echo a > a.txt echo b > b.txt git add a.txt b.txt # 两个文件都进暂存区 git status # 看到两个文件都在 "to be committed" echo c > c.txt # 新建第三个文件 git status # c.txt 出现在 Untracked files git add . # 把 c.txt 也加进来 echo a2 >> a.txt # 又改了 a.txt git status # a.txt 同时出现在两个分区 git add a.txt # 再次 add,同步最新版本 git commit -m "添加初始文件" # 全部固化
最后一次"a.txt 同时出现在两个分区"尤其值得体验——它是理解"add 是快照"的活教材。多跑几遍这个序列,直到你能预判每一步 status 的输出。
| 场景 | 推荐用法 | 理由 |
|---|---|---|
| 新项目首次提交 | git add . | 快速纳入全部 |
| 只提交部分文件的修改 | git add 文件... | 精确控制 |
| 一次提交所有变化含删除 | git add -A | 完整捕捉 |
| 一个文件里拆两类修改 | git add -p | 原子提交 |
| 日常小改动 | git add -A 或 git add . | 省事 |
-p 的更深一层的价值git add -p 看起来只是"高级技巧",但它背后是一种提交哲学:一次提交只做一件事(Single Responsibility)。真正成熟的仓库里,历史不是流水账,而是一部"为什么这么改"的编年史——每条提交都能被独立回滚、独立审查、独立 blame。想达到这个标准,-p 是日常武器。
实际操作中它的常见搭档是 git diff(看未暂存改动的具体内容)和 git diff --cached(看已暂存改动的具体内容)。先 diff 后 -p,你才能在拆块时知道每一块到底是什么。
很多新人习惯一上来就 git add . 或 git add -A,把所有东西一股脑塞进暂存区。这没有错,但它抹掉了暂存区最大的价值——选择性。如果你本来就想"一次提交所有改动",那没问题;但如果你手里同时有"功能 A 改了一半"和"功能 B 已完成",全 add 之后再提交,两条线就缠在一起了。养成习惯:提交前先想"这次提交该包含什么",再决定 add 什么。哪怕只花十秒钟,历史质量会有质的差别。
⚠️ 常见坑:
git add .不包含已删除的文件。删了文件却只 add 了目录,提交后删除不会进历史,仓库里还是留着旧文件。
💡 关键直觉:把 add 想成"挑菜"——你从案板上挑出要下锅的菜登记到点菜单,没挑的留在案板。commit 是"起锅",只炒点菜单上的菜。挑菜的质量决定这一锅菜的水平。
"git add 后撤销怎么办?" 用 git restore --staged 文件 把文件从暂存区撤下(保留工作区修改),2.7 节会详细讲。这个操作完全可逆,add 错了不必慌。
"git add 能添加忽略的文件吗?" 不能,除非用 -f 强制。被 .gitignore 匹配的文件,Git 默认不理会。想确认为什么某个文件没被 add,用 git check-ignore 查一下。强制添加一般只在确认该文件必须入库、且忽略规则写错了时才用。
"add 和 commit 之间隔很久会不会有问题?" 不会。暂存区只是清单,内容在对象库里按需存储。但建议别让暂存区"过期太久"——放久了你会忘记当时为什么选这些版本,写提交信息时还得回想。理想状态是 add 完尽快 commit,让暂存区始终反映"即将发生的事"。
"git add -A 和 git add . 到底啥区别?" 区别在删除文件的处理:add . 只加新建和修改,add -A 连删除一起。日常图省事用 -A,想精确控制用明确路径。
空白提交问题:如果你 add 了文件但内容根本没变(比如 touch 了一下没改动),git add 不会产生任何暂存效果,commit 时会提示"没有要提交的改动"。这不叫 bug,叫 Git 在帮你把关——别提交无意义的空提交。
符号链接与二进制文件:add 对符号链接处理的是链接本身而非指向的内容;二进制文件(图片、模型)也能 add,但 diff 时 Git 只能判断"变了没变",看不出变化细节。大二进制文件频繁改动会让仓库迅速膨胀,这类资产通常不该进 Git 仓库,而是用专门的对象存储或 LFS(大文件存储)方案。
add 的反向操作:想撤销暂存,用 git restore --staged 文件;想完全放弃某个文件的暂存和修改,用 git restore 文件。这两个命令 2.7 节会系统讲。这里先记住"add 能去也能回"——它始终是可逆操作,暂存区不是悬崖,大胆用。
跨平台路径分隔符:add 时使用反斜杠还是正斜杠?Git 内部统一用正斜杠(/)表示路径,Windows 用户在终端里也可以直接用正斜杠,避免转义问题。例如 git add src/utils/helper.js 在 Windows 同样成立。
把 add 放在 Git 命令的全景里看,它和其他命令有一个本质区别:add 不产生新对象、不移动指针、不改历史——它只是在暂存区这个"选择器"上勾选内容。真正产生对象的动作是 commit(写入 Commit/Tree 对象),真正移动指针的是 checkout、merge、reset。理解了这个分层,你就明白为什么"add 错了"永远可救——它没对仓库造成任何不可逆影响,改勾选即可。
这也是为什么 Git 教程几乎都会强调"暂存区是 Git 最独特的设计":别的系统把"选择要提交什么"藏在心里,Git 把它显式化成一个可查看、可修改的区域。善用这个显式化,你的提交历史就能保持干净、原子、可回溯——这是长期项目健康度的重要指标。坚持一段时间后你会发现,写提交信息越来越省力,因为每一条提交都是你亲手挑出来的、边界清楚的一小块工作。
下一节提交快照:
git commit把暂存区固化成历史记录——提交信息怎么写,这里有一整套讲究。