4. 核心操作(下):git diff、git rm、git mv 与 .gitignore
好,咱们接着聊。上一节我们把 git add 和 git commit 讲透了,这一节要深入几个日常高频操作。说实话,这几个命令刚开始用的时候容易懵,尤其是 git diff,输出格式看着像天书。别急,我带你一个个拆开看。
4.1 git diff:工作区 vs 暂存区 vs 仓库
git diff 说白了就是「找不同」。它能告诉你文件到底改了哪里。但很多人搞不清它到底在比较谁和谁。我刚开始也犯过这错,以为 git diff 就是看所有差异,结果发现它默认只比较工作区和暂存区。
咱们先理清三个区域的关系:
- 工作区:你正在编辑的目录,肉眼可见的文件
- 暂存区:执行 git add 后存放改动的地方,可以理解为「待提交区」
- 仓库:执行 git commit 后,改动正式存入的历史记录
git diff 的几种常见用法:
| 命令 | 比较对象 | 典型场景 |
|---|---|---|
git diff |
工作区 vs 暂存区 | 看看改了哪些还没 add |
git diff --staged |
暂存区 vs 最近一次提交 | 看看即将 commit 的内容 |
git diff HEAD |
工作区 vs 最近一次提交 | 看所有未提交的改动 |
git diff <commit1> <commit2> |
两个历史提交之间 | 对比版本差异 |
重点记忆:不加参数时,git diff 只看「还没 add 的内容」。如果你已经 git add 了,想看看暂存了什么,记得加 --staged。
举个例子。假设我修改了 index.html,然后执行:
# 修改文件后,还没 add
git diff
# 输出类似:
# diff --git a/index.html b/index.html
# index 1234567..89abcde 100644
# --- a/index.html
# +++ b/index.html
# @@ -10,6 +10,8 @@
# <title>我的网站</title>
# </head>
# <body>
# + <h1>欢迎光临</h1>
# + <p>这是新加的内容</p>
# </body>
# </html>
这个输出怎么看?我教你个口诀:「减号是旧的,加号是新的」。@@ -10,6 +10,8 @@ 表示旧文件第10行开始,共6行;新文件第10行开始,共8行。多了两行,就是那两个加号开头的。
我的小技巧:如果你觉得默认输出太乱,可以用 git diff --word-diff 或 git diff --color-words,它会高亮显示具体改动的单词,而不是整行。我在 code review 时特别喜欢用这个。
4.2 git rm 与 git mv:删除和移动的正确姿势
很多人直接在文件管理器里删文件,然后发现 git 还在追踪它。嗯,这其实是个坑。正确的做法是用 git rm。
git rm 的两种场景:
git rm file.txt:从工作区和暂存区同时删除,相当于「删文件 + git add 删除操作」git rm --cached file.txt:只从暂存区删除,保留工作区文件。这个很有用,比如不小心把密码文件 add 了,可以用这个「反悔」
我曾经踩过的坑:有一次我直接 rm 了一个重要配置文件,然后 git status 发现它还在暂存区。我心想「删都删了,直接 commit 吧」,结果同事拉下来发现文件还在。后来才明白,git 只记录「你告诉它的操作」,你手动删文件它不知道。所以一定要用 git rm。
git mv 呢? 它相当于「移动/重命名 + git add」。用法很简单:
# 把 old.txt 重命名为 new.txt
git mv old.txt new.txt
# 把文件移到子目录
git mv file.txt src/file.txt
执行后,git 会自动识别这是重命名操作,而不是「删了一个文件又加了一个新文件」。这在查看历史时特别有用,你能看到文件的完整演变过程。
4.3 .gitignore 文件编写规则
.gitignore 是每个项目必备的「门卫」。它告诉 git:哪些文件不要管。我见过新手把 node_modules 都提交到仓库,结果仓库体积直接爆炸。嗯,这就是没配 .gitignore 的后果。
基本规则:
#开头的是注释/开头表示从根目录匹配/结尾表示匹配目录*匹配任意多个字符?匹配一个字符!开头表示取反,即不忽略**匹配任意层级的目录
来看一个实际项目的 .gitignore:
# 编译产物
dist/
build/
*.o
*.class
# 依赖目录
node_modules/
vendor/
# 环境配置
.env
.env.local
*.config.js
# 系统文件
.DS_Store
Thumbs.db
# 日志
*.log
logs/
# 取反:不忽略某个特定文件
!important.log
注意:.gitignore 只对「尚未被追踪」的文件生效。如果你已经 git add 过的文件,即使后来加到 .gitignore 里,git 仍然会追踪它。解决办法是先用 git rm --cached 把它从暂存区移除。
我个人习惯在项目初始化时就创建 .gitignore。很多语言和框架都有官方推荐的模板,比如 GitHub 上的 gitignore 仓库。直接复制过来改改就行,省心。
4.4 文件的生命周期状态图
理解了上面的操作,咱们来画一张图。文件在 git 里一共有四种状态:
| 状态 | 含义 | 如何进入 | 如何离开 |
|---|---|---|---|
| 未追踪 | git 不知道这个文件存在 | 新建文件 | git add → 暂存 |
| 已暂存 | 文件已加入暂存区 | git add | git commit → 已提交;git reset HEAD → 已修改 |
| 已修改 | 文件被修改但未暂存 | 编辑已追踪的文件 | git add → 已暂存;git checkout -- file → 恢复 |
| 已提交 | 文件安全存储在仓库中 | git commit | 编辑文件 → 已修改 |
这四种状态的流转关系,我习惯用一句话概括:「未追踪→已暂存→已提交→已修改→已暂存→已提交」,形成一个循环。
避坑指南:我曾经在项目里遇到一个情况——同事把 .idea 目录(IDE 配置)提交到了仓库。每次他改个配置,其他人 pull 下来就冲突。后来我们统一在 .gitignore 里加了 .idea/,然后用 git rm --cached 把已追踪的移除掉。从此天下太平。
最后送你一个实用命令:git status -s(短格式输出)。它会用两个字母表示文件状态:
??未追踪M已修改A已暂存D已删除
比如 M file.txt 表示工作区修改了, M file.txt(注意空格位置)表示暂存区修改了。这个命令我每天用几十次,比 git status 的详细输出快多了。
好了,这一节的内容就到这里。记住:diff 是找不同,rm 和 mv 是正规操作,.gitignore 是项目门卫,状态图是理解 git 的钥匙。下一节咱们聊聊分支操作,那才是 git 的精髓所在。