Git 快照操作完全指南(超级详细版)

5 阅读12分钟

Git 的核心思想是记录文件系统的快照,而不是差异。每次提交(commit)都保存了项目所有文件在那一刻的完整状态(快照)。
理解快照操作,就是理解 工作区(Working Directory) → 暂存区(Staging Area / Index) → 版本库(Repository / Object Database) 之间的状态流转。

本文聚焦于所有与快照相关的 Git 命令,从基础到高级,每个命令都配有详细说明和实际示例。


一、核心概念:三个区域与文件状态

区域说明对应 Git 对象
工作区电脑文件系统中看到的实际文件普通文件
暂存区一个临时存储区域,记录下一次提交要包含的内容index 文件
版本库.git 目录,存储所有提交的快照对象commit / tree / blob

文件有四种状态:

  • 未跟踪(Untracked):新文件,从未被 Git 记录过
  • 已跟踪(Tracked):被 Git 管理的文件,又分为:
    • 未修改(Unmodified):与上次提交一致
    • 已修改(Modified):工作区被修改,但未暂存
    • 已暂存(Staged):修改已加入暂存区,待提交
┌────────────┐    add     ┌────────────┐   commit   ┌────────────┐
│  工作区    │ ────────→ │  暂存区    │ ─────────→ │  版本库    │
│ (Modified) │           │  (Staged)  │            │ (Committed)│
└────────────┘           └────────────┘            └────────────┘
       ↑                       │                           │
       └─────── restore ───────┘                           │
              (丢弃修改)                                    │
                                               ┌────────────┘
                                               ↓
                                      reset (移动 HEAD)

二、状态查看命令

1. git status – 查看当前快照状态

最常用的命令,显示工作区和暂存区的差异。

语法

git status [选项]

常用选项

选项说明
-s / --short简洁格式输出
-b显示分支信息(常与 -s 连用)
--ignored同时显示被忽略的文件
-v更详细,显示暂存区与 HEAD 的差异

示例

# 标准输出
$ git status
On branch main
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
        modified:   README.md
        new file:   index.html

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
        modified:   src/app.js

Untracked files:
  (use "git add <file>..." to include in what will be committed)
        newfile.txt

# 简洁模式
$ git status -s
M  README.md     # 已暂存但未提交(左边 M)
A  index.html    # 新增已暂存
 M src/app.js    # 工作区修改未暂存(右边 M)
?? newfile.txt   # 未跟踪

状态码含义(简洁模式)

符号含义
??未跟踪
A新增(已暂存)
M修改(左侧=暂存区,右侧=工作区)
D删除
R重命名
C复制
U未合并(冲突)

2. git diff – 查看快照之间的差异

比较工作区、暂存区、版本库之间的内容变化。

语法

git diff [选项] [<路径>]
git diff --cached [选项] [<路径>]
git diff <提交1> <提交2>

常用比较场景

命令比较对象
git diff工作区 vs 暂存区
git diff --cached / --staged暂存区 vs HEAD(最新提交)
git diff HEAD工作区 vs HEAD
git diff <commit>工作区 vs 指定提交
git diff <commit1> <commit2>两个提交之间
git diff <branch1> <branch2>两个分支的最新提交

常用选项

选项说明
--stat只显示统计信息(修改的文件及行数)
--name-only只显示文件名
--color-words按单词高亮差异(适合文本)
--ignore-space-change忽略空格变化
-U<n>显示 n 行上下文(默认 3)
--no-index比较两个非 Git 管理的文件

示例

# 查看工作区中哪些修改还未暂存
$ git diff
diff --git a/src/app.js b/src/app.js
index abc123..def456 100644
--- a/src/app.js
+++ b/src/app.js
@@ -10,7 +10,7 @@ function hello() {
-  console.log("Hello")
+  console.log("Hello, World!")
 }

# 查看暂存区与上次提交的差异(即即将提交的内容)
$ git diff --cached

# 查看工作区与上次提交的差异(包含已暂存和未暂存的全部修改)
$ git diff HEAD

# 只显示修改的文件名
$ git diff --name-only
README.md
src/app.js

# 显示统计信息
$ git diff --stat
 README.md    | 2 +-
 src/app.js   | 4 ++--
 2 files changed, 3 insertions(+), 3 deletions(-)

# 比较两个提交
$ git diff abc123 def456

# 比较当前分支与 main 分支的差异
$ git diff main

# 只查看某个文件的差异
$ git diff README.md

# 忽略空白变化
$ git diff --ignore-space-change

3. git show – 显示某个提交的快照详情

展示指定提交的元数据和完整差异(即该提交引入的快照变化)。

语法

git show [选项] [<提交>]

常用选项

选项说明
--stat只显示统计信息
--name-only只显示修改的文件
-s / --no-patch只显示提交信息,不显示差异
--format=自定义输出格式

示例

# 显示最新提交的快照变化
$ git show

# 显示指定提交
$ git show abc123

# 只显示该提交修改的文件列表
$ git show --name-only abc123

# 只显示提交消息和统计,不显示具体差异
$ git show -s --stat abc123

# 显示某个文件在某次提交中的内容(不是差异,而是该版本的文件)
$ git show abc123:README.md

# 显示某个标签对应的提交
$ git show v1.0

4. git log – 查看快照历史

虽然主要用于查看提交历史,但也能展示快照信息。

常用快照相关选项

选项说明
-p / --patch显示每次提交的差异(即每个快照的变化)
--stat显示每次提交的简略统计
--oneline单行简洁显示
--graph以 ASCII 图形显示分支结构
--pretty=format:自定义格式

示例

# 显示最近 2 次提交的详细快照差异
$ git log -p -2

# 显示每次提交修改的文件统计
$ git log --stat

# 单行显示,每条提交就是一个快照标识
$ git log --oneline
abc1234 (HEAD -> main) 修复登录验证
def5678 添加用户注册功能

三、暂存操作(工作区 → 暂存区)

5. git add – 将工作区的快照加入暂存区

语法

git add <文件/目录>...

常用选项

选项说明
-A / --all添加所有修改、删除、新增的文件(整个工作区)
-u / --update只添加已跟踪文件的修改/删除,不添加新文件
-p / --patch交互式选择部分修改块暂存(精细控制快照)
-i / --interactive交互式添加界面
-f / --force强制添加被 .gitignore 忽略的文件
-n / --dry-run模拟添加,不实际执行

示例

# 添加单个文件
$ git add index.html

# 添加多个文件
$ git add index.html style.css

# 添加整个目录(递归)
$ git add src/

# 添加当前目录所有变更(包括删除和新增)
$ git add -A
# 或
$ git add .

# 只添加已跟踪文件的修改(不添加新文件)
$ git add -u

# 交互式选择部分修改块(精细化快照)
$ git add -p README.md
# 输出类似:
# diff --git a/README.md b/README.md
# @@ -1,3 +1,4 @@
# -Hello
# +Hello World
# Stage this hunk [y,n,q,a,d,e,?]? 
# 选项:y-暂存,n-不暂存,e-手动编辑该块,s-分割成更小块

# 强制添加被忽略的文件
$ git add -f debug.log

# 预览会添加哪些文件(不实际添加)
$ git add -n

6. git restore --staged – 将文件移出暂存区(取消暂存)

语法

git restore --staged <文件>...

这是 Git 2.23 引入的命令,用于替代 git reset HEAD <file> 的取消暂存功能。

示例

# 取消暂存某个文件(保留工作区的修改)
$ git restore --staged README.md

# 取消暂存所有文件
$ git restore --staged .

# 效果等同于旧的写法:git reset HEAD README.md

7. git reset --mixed – 移动 HEAD 并重置暂存区(但不改工作区)

这也是取消暂存的一种方式,但更强大。

# 将暂存区重置为 HEAD 的内容,但保留工作区的修改(默认行为)
$ git reset --mixed HEAD
# 等价于取消所有暂存

# 取消暂存特定文件(旧语法)
$ git reset HEAD README.md

四、提交操作(暂存区 → 版本库)

8. git commit – 将暂存区的快照永久存入版本库

每次 commit 会创建一个新的快照对象(commit + tree + blob)。

语法

git commit [-m "<消息>"]

常用选项

选项说明
-m "<消息>"直接提供提交消息(不打开编辑器)
-a / --all自动暂存所有已跟踪文件的修改,然后提交(跳过 git add
--amend修改上一次提交(替换快照或消息)
--no-edit--amend 一起使用,不修改消息
-s / --signoff添加 Signed-off-by 签名
--allow-empty允许空提交(没有文件变更)
-v在编辑器中显示差异

示例

# 标准提交(打开编辑器写消息)
$ git commit

# 带消息的提交
$ git commit -m "修复登录页面的样式问题"

# 多行消息
$ git commit -m "添加用户认证模块" -m "实现了 JWT 令牌验证"

# 跳过暂存区,直接提交已跟踪文件的修改(相当于先 add -u 再 commit)
$ git commit -a -m "更新配置文件和文档"

# 修改上一次提交(修正快照内容或消息)
# 先 add 遗漏的文件
$ git add forgotten-file.txt
$ git commit --amend --no-edit   # 保持原消息,只追加新内容

# 只修改上一次提交的消息
$ git commit --amend -m "新的提交消息"

# 空提交(用于触发 CI)
$ git commit --allow-empty -m "触发构建"

# 添加签名
$ git commit -s -m "更新版权信息"

9. git commit --amend 的内部原理

实际上不是“修改”,而是用一个新的提交替换上一次提交的指针。原提交仍然存在(可通过 reflog 找回)。

# 假设当前历史
A -- B (HEAD)

# 执行 git commit --amend 后
A -- B' (HEAD)
  \
   B (仍然存在,但不可达)

五、撤销与回退快照

10. git restore – 丢弃工作区的修改(未暂存的快照恢复)

语法

git restore <文件>...           # 丢弃工作区修改,恢复到暂存区或 HEAD 的状态
git restore --source=<提交> <文件>   # 从指定提交恢复文件到工作区

示例

# 丢弃工作区中 README.md 的修改(不可恢复!)
$ git restore README.md

# 丢弃当前目录下所有工作区修改
$ git restore .

# 从某个提交恢复某个文件到工作区(同时覆盖工作区)
$ git restore --source=abc123 README.md

# 从上一个提交恢复文件
$ git restore --source=HEAD~1 src/app.js

11. git reset – 重置 HEAD 和暂存区/工作区(改变快照指针)

reset 命令用于移动当前分支的 HEAD 指针,并可选择性地重置暂存区和工作区。

三种模式

模式HEAD 指向暂存区工作区典型用途
--soft改变不变不变撤销提交,保留修改(修改仍在暂存区)
--mixed (默认)改变重置到目标提交不变撤销提交,保留修改在工作区(需重新 add)
--hard改变重置到目标提交重置到目标提交彻底丢弃提交及所有修改(危险)

语法

git reset [--soft|--mixed|--hard] [<提交>]

示例

# 撤销最后一次提交,但保留修改在暂存区(相当于取消 commit)
$ git reset --soft HEAD~1
# 现在修改仍在暂存区,可直接再次 commit

# 撤销最后一次提交,并将修改移回工作区(默认)
$ git reset HEAD~1
# 或
$ git reset --mixed HEAD~1

# 彻底丢弃最近 3 次提交的所有更改(不可恢复!除非 reflog)
$ git reset --hard HEAD~3

# 取消暂存区的文件(保留工作区修改) – 等同于 git restore --staged
$ git reset HEAD README.md

# 将当前分支指向另一个提交,但不改变工作区(常用于移动分支起点)
$ git reset --soft abc123

⚠️ 警告reset --hard 会丢弃工作区和暂存区的所有未提交修改。执行前请确认或使用 git stash

12. git revert – 通过反向新提交来撤销快照

reset 不同,revert 不删除历史,而是创建一个反向快照(即撤销某次提交引入的变化)。适合公共分支。

语法

git revert <提交哈希>

示例

# 撤销某次提交(生成一个新提交)
$ git revert abc123
# 会打开编辑器,确认撤销消息,然后创建新提交

# 撤销最近一次提交
$ git revert HEAD

# 撤销但不自动提交(可批量撤销多个)
$ git revert -n abc123 def456
$ git commit -m "撤销 abc123 和 def456"

# 撤销一个合并提交(需指定主线)
$ git revert -m 1 merge-commit-hash

# 解决冲突后继续
$ git revert abc123
# 手动解决冲突
$ git add .
$ git revert --continue

13. git checkout (旧) / git switch (新) – 切换分支或恢复快照

部分快照恢复功能也可用 git checkout 实现,但 Git 2.23 后推荐分离用途。

恢复文件的旧方法

# 丢弃工作区修改(旧语法,等同于 git restore)
$ git checkout -- README.md

# 从某次提交恢复文件(旧语法)
$ git checkout abc123 -- src/app.js

推荐的新语法:使用 git restoregit switch 代替。

14. git clean – 删除未跟踪的快照文件(工作区)

删除那些从未被 Git 记录的文件(即不属于任何快照的文件)。

语法

git clean [选项]

常用选项

选项说明
-n / --dry-run预览要删除的文件(安全)
-f / --force强制删除(必须)
-d同时删除未跟踪的目录
-x删除被 .gitignore 忽略的文件
-X只删除被 .gitignore 忽略的文件

示例

# 预览会删除哪些未跟踪文件
$ git clean -n

# 删除所有未跟踪文件(不包含目录)
$ git clean -f

# 删除未跟踪文件和目录
$ git clean -fd

# 删除包括被忽略的文件(如 .log, node_modules/)
$ git clean -fdx

# 只删除被 .gitignore 忽略的文件
$ git clean -fX

六、删除与移动快照中的文件

15. git rm – 从工作区和暂存区删除文件(并记录删除快照)

语法

git rm <文件>...

常用选项

选项说明
--cached只从暂存区删除,保留工作区文件(停止跟踪)
-f / --force强制删除(文件有未暂存修改时)
-r递归删除目录

示例

# 删除文件并暂存删除操作
$ git rm old-file.txt

# 删除目录
$ git rm -r old-dir/

# 只从 Git 中移除跟踪,但保留工作区文件(适合停止跟踪配置文件)
$ git rm --cached config.local.json

# 强制删除(本地有修改且未暂存)
$ git rm -f important.txt

执行 git rm 后,该删除操作会进入暂存区,下次 commit 会生成一个文件被删除的新快照。

16. git mv – 移动或重命名文件(记录重命名快照)

语法

git mv <源文件> <目标文件>

说明
等价于执行:

mv old new
git add new
git rm old

示例

# 重命名文件
$ git mv index.html home.html

# 移动文件到子目录
$ git mv style.css assets/css/

# 移动并重命名
$ git mv old-name.txt src/new-name.txt

执行后,Git 会自动检测为重命名,并在 git status 中显示 renamed


七、临时保存快照:Stash

17. git stash – 临时保存工作区和暂存区的快照(不提交)

当你需要切换分支但当前工作有未完成的修改时,可以使用 stash 把这些修改(包括暂存区)保存到一个栈中,恢复干净的工作区。

语法

git stash [push] [-m "<消息>"]
git stash list
git stash pop [stash@{n}]
git stash apply [stash@{n}]
git stash drop [stash@{n}]
git stash clear

常用子命令

命令说明
git stashgit stash push保存当前工作区和暂存区的修改(不包括未跟踪文件)
git stash push -u同时保存未跟踪的文件
git stash push -a保存所有文件(包括被忽略的)
git stash list列出所有储藏
git stash pop应用最新的储藏并删除它
git stash apply应用最新的储藏(不删除)
git stash drop删除指定的储藏
git stash clear删除所有储藏
git stash branch <分支名>从储藏创建新分支(自动应用并删除)
git stash show显示储藏的差异摘要

示例

# 保存当前修改(自动生成消息)
$ git stash
Saved working directory and index state WIP on main: abc123 最后一次提交

# 保存并添加描述
$ git stash push -m "WIP: 登录验证逻辑未完成"

# 保存包括未跟踪的文件
$ git stash push -u

# 查看储藏列表
$ git stash list
stash@{0}: On main: WIP: 登录验证逻辑未完成
stash@{1}: On feature: 未完成的功能

# 应用最新的储藏(保留储藏)
$ git stash apply

# 应用并删除最新的储藏
$ git stash pop

# 应用指定的储藏
$ git stash apply stash@{1}

# 查看储藏的修改内容
$ git stash show stash@{0}
$ git stash show -p stash@{0}   # 显示详细差异

# 删除指定的储藏
$ git stash drop stash@{0}

# 删除所有储藏
$ git stash clear

# 从储藏创建新分支(自动应用并删除储藏)
$ git stash branch new-feature stash@{0}

储藏的本质
git stash 会创建两个(或三个,包括未跟踪文件)提交对象,分别对应当前暂存区的快照和工作区的快照,并重置工作区到 HEAD 的状态。这些提交不在任何分支上,可通过 git reflog 查看。


八、忽略文件:影响快照内容

18. .gitignore – 指定哪些文件不被 Git 跟踪

虽然不是命令,但是影响快照操作的关键配置文件。被忽略的文件不会被 git add 添加,也不会出现在 git status 的未跟踪列表中。

语法(在项目根目录或子目录创建 .gitignore 文件)

常用规则

# 注释
*.log          # 忽略所有 .log 文件
/temp          # 只忽略根目录下的 temp 文件/目录
build/         # 忽略 build 目录及其所有内容
!important.log # 不忽略 important.log(即使前面有 *.log)
*.class        # 忽略所有 .class 文件

示例

# 生成一个典型的 Node.js 项目 .gitignore
$ cat > .gitignore << EOF
node_modules/
.env
*.log
dist/
.DS_Store
EOF

# 将 .gitignore 加入暂存区并提交
$ git add .gitignore
$ git commit -m "添加 .gitignore"

注意:如果文件已经被 Git 跟踪,.gitignore 不会自动停止跟踪。需要先执行:

$ git rm --cached <file>

九、高级快照操作

19. git add -p – 部分暂存(精细控制快照内容)

允许只暂存文件中的某些修改块(hunk),而不是整个文件。这是精细控制快照内容的强大工具。

示例

$ git add -p src/app.js
# Git 会逐个显示修改块,并询问操作:
# y - 暂存此块
# n - 不暂存此块
# q - 退出
# a - 暂存此文件剩余所有块
# d - 不暂存此文件剩余所有块
# s - 将当前块分割成更小的块
# e - 手动编辑当前块
# ? - 帮助

典型场景:一个文件中有多处修改,只想提交其中一部分。

20. git commit --amend – 修正上一个快照

前面已介绍。可以理解为用一个新的快照替换上一个快照。

21. git restore -p – 交互式丢弃工作区修改

类似 git add -p,但用于丢弃修改。

$ git restore -p README.md
# 交互式选择要丢弃的修改块

22. git reset -p – 交互式重置暂存区

$ git reset -p HEAD
# 选择要从暂存区移除的修改块

23. git cherry-pick – 复制其他分支的快照到当前分支

从一个或多个现有提交中提取快照变化,应用到当前分支。

语法

git cherry-pick <提交哈希>...

示例

# 将另一个分支的某个提交应用到当前分支
$ git cherry-pick abc123

# 复制多个提交
$ git cherry-pick abc123 def456

# 只应用更改,不自动提交(便于合并多个 cherry-pick)
$ git cherry-pick -n abc123
$ git commit -m "手动提交"

# 处理冲突后继续
$ git cherry-pick abc123
# 解决冲突
$ git add .
$ git cherry-pick --continue

24. git rebase -i – 交互式重写快照历史

可以重新排序、合并、删除、修改提交(快照)。这是 Git 最强大的历史重写工具。

示例

# 对最近 3 次提交进行交互式变基
$ git rebase -i HEAD~3

# 打开的编辑器中,你会看到类似内容:
pick abc123 添加登录功能
pick def456 修复登录bug
pick 789xyz 优化登录样式

# 可以修改命令:
# pick   - 保留该提交
# reword - 保留快照,但修改提交消息
# edit   - 保留快照,但停下来允许修改内容
# squash - 将该快照合并到前一个快照中
# fixup  - 类似 squash,但丢弃该提交的消息
# drop   - 删除该提交

# 保存退出后,Git 会按指令重写历史

注意:不要对已经推送到公共仓库的提交执行 rebase,否则会给协作者带来困扰。


十、快照操作组合示例

场景1:放弃所有本地修改(回到最新提交的干净快照)

# 丢弃工作区和暂存区的所有修改
$ git reset --hard HEAD
# 删除未跟踪的文件和目录
$ git clean -fd

场景2:撤销最后一次提交,但保留修改在工作区

$ git reset HEAD~1
# 现在修改回到工作区,可以重新编辑后再次 add/commit

场景3:修改上一次提交的快照(追加文件或修改内容)

# 修改文件或添加新文件
$ echo "new content" >> README.md
$ git add README.md
# 将新内容合并到上一次提交
$ git commit --amend --no-edit

场景4:将某个文件恢复到指定历史快照

# 方法1:使用 restore
$ git restore --source=abc123 README.md

# 方法2:使用 checkout(旧)
$ git checkout abc123 -- README.md

# 方法3:直接查看并重定向
$ git show abc123:README.md > README.md

场景5:只暂存文件中的部分修改(精细快照)

$ git add -p src/app.js
# 交互选择要暂存的块
$ git commit -m "只提交了部分修改"

场景6:临时保存当前快照,去做其他事,然后恢复

$ git stash push -m "WIP"
$ git checkout other-branch
# 做其他工作...
$ git checkout original-branch
$ git stash pop

场景7:合并多个提交为一个快照(压缩历史)

$ git rebase -i HEAD~4
# 将后三个提交的 pick 改为 squash 或 fixup
# 保存后编辑合并后的提交消息

十一、快照相关配置

git config 对快照操作的影响

# 设置默认提交消息模板
$ git config --global commit.template ~/.gitmessage.txt

# 设置默认编辑器(用于提交消息)
$ git config --global core.editor "code --wait"

# 让 git diff 显示颜色
$ git config --global color.ui auto

# 设置别名简化快照操作
$ git config --global alias.st status
$ git config --global alias.ci commit
$ git config --global alias.co checkout
$ git config --global alias.br branch
$ git config --global alias.unstage 'restore --staged'
$ git config --global alias.discard 'restore'
$ git config --global alias.last 'log -1 HEAD'

十二、快照操作速查表

操作命令说明
查看状态git status -s简洁查看工作区/暂存区状态
查看差异git diff工作区 vs 暂存区
查看暂存区差异git diff --cached暂存区 vs HEAD
查看工作区 vs HEADgit diff HEAD所有未提交修改
添加文件到暂存区git add <file>工作区 → 暂存区
交互式添加git add -p精细控制快照块
取消暂存git restore --staged <file>暂存区 → 工作区(保留修改)
提交git commit -m "msg"暂存区 → 版本库
提交所有已跟踪修改git commit -a -m "msg"跳过 add
修改上一次提交git commit --amend替换最新快照
丢弃工作区修改git restore <file>工作区 → 暂存区/HEAD
彻底回退到指定提交git reset --hard <commit>危险!丢弃所有
撤销提交但保留修改git reset --soft HEAD~1撤销 commit
撤销提交并取消暂存git reset HEAD~1撤销 commit 和 add
安全撤销提交git revert <commit>生成反向新提交
删除文件git rm <file>工作区+暂存区删除
停止跟踪但保留文件git rm --cached <file>只从暂存区删除
移动/重命名git mv <old> <new>记录重命名快照
临时保存git stash push -m "msg"保存当前快照
恢复并删除暂存git stash pop应用最新储藏
删除未跟踪文件git clean -fd清理工作区
查看提交快照git show <commit>显示提交的差异
查看历史快照git log -p -2显示最近2次提交的差异

结语

Git 的快照模型是其强大和优雅的根源。理解工作区、暂存区、版本库之间的数据流动,就能掌握所有快照操作的本质。每个命令都是在移动或比较不同区域之间的快照内容。

建议在日常工作中多使用 git statusgit diffgit add -p 来精细控制快照内容,避免一次性提交大量无关修改。对于历史重写操作(如 rebase -i, reset --hard),请谨慎使用,并确保重要数据已备份或已推送。

不断实践,你会越来越得心应手。