☰
Git 变更查看、取消暂存、丢弃与恢复全流程实战(90DaysOfDevOps Day 39 深度解析)
2026/10/1 20:14:29 网站建设 项目流程
  • 文档/教程

【免费下载链接】90DaysOfDevOps

This repository started out as a learning in public project for myself and has now become a structured learning map for many in the community. We have 3 years under our belt covering all things DevOps, including Principles, Processes, Tooling and Use Cases surrounding this vast topic.

项目地址:https://gitcode.com/gh_mirrors/90/90DaysOfDevOps
点击查看免费下载

本文以 90DaysOfDevOps 2022 学习路径 Day 39 文档 2022/Days/day39.md 为骨架,系统梳理在尚未接入 GitHub 等任何远程托管平台之前,如何仅凭本地仓库完成「查看变更 → 可视化对比 → 审阅历史 → 取消暂存 → 丢弃更改 → 恢复文件 → 分支整合」的完整闭环。读完本文,你将掌握git diff、git difftool、git log、git show、git ls-tree、git restore、git clean等命令的实战用法与输出解读,并透彻理解rebase与merge的本质差异与取舍。

前情提要:本地 Git 工作流的三个区域

在 Day 39 之前,本系列已经完成了 Git 的理论铺垫:Day 35 讲解版本控制系统的大图景,Day 36 完成安装与配置,Day 37 给出常用命令速查表(见 2022/Days/day37.md),Day 38 则以git init初始化的项目为例,演示了git add暂存、git commit提交、git rm删除、.gitignore忽略文件与git status -s短格式状态(见 2022/Days/day38.md)。

从 Day 38 的演示可以提炼出本地 Git 的三个核心区域,这也是理解 Day 39 全部命令的前提:

区域含义对应状态
工作区(Working Directory)磁盘上你正在编辑的实际文件红色M(已修改未暂存)或??(未跟踪)
暂存区 / 索引(Staging Area / Index)你git add进去、准备随下一次提交入快照的内容绿色A/M
版本库(Repository)已经git commit的历史快照集合无标记

Day 39 的每个操作,本质上都是在这三个区域之间搬运内容。下面按「查看 → 撤销 → 恢复 → 分支整合」的顺序逐一展开。

查看暂存与未暂存的变更

在提交之前先确认「到底改了什么」,是良好的工程习惯。核心命令有两个:

git diff --staged # 对比 暂存区 与 最近一次提交(HEAD),查看即将提交的内容 git diff # 对比 工作区 与 暂存区,查看尚未 git add 的内容

在 Day 37 的速查表中,git diff --staged的别名git diff --cached同样被收录,二者语义完全等价——「显示暂存区与最近一次提交之间的差异」。

如上图所示,先用git status -s快速确认文件状态(code.txt为已暂存的新文件A,main.js为已暂存的修改M),再执行git diff --staged,即可看到所有已暂存的新增文件与改动。

diff 输出的解读要点:

  • 文件级差异以---(变更前)与+++(变更后)标识;
  • 新增文件会以new file mode形式体现;
  • 行内新增内容以+开头,删除内容以-开头。
diff --git a/main.js b/main.js --- a/main.js +++ b/main.js @@ -1,0 +1 @@ +add some text

在 Day 39 的示例中,main.js被新增了一行add some text,+前缀说明这是新加入的行。这种纯文本 diff 已经足够定位改动,但对复杂改动而言可读性有限,于是就有了下一节的视觉化方案。

使用可视化 Diff 工具

文本 diff 对复杂项目不够直观,Git 支持将对比任务交给外部可视化工具。常见的可视化 diff 工具有:

  • KDiff3
  • P4Merge
  • WinMerge(仅 Windows)
  • VSCode

将 VSCode 配置为 Git 默认 diff 工具,需要设置两条全局配置:

git config --global diff.tool vscode git config --global difftool.vscode.cmd "code --wait --diff $LOCAL $REMOTE"

第一条指定默认 diff 工具为vscode;第二条定义该工具的实际调用命令——$LOCAL与$REMOTE是 Git 传入的两个临时文件路径,--wait让终端等待 VSCode 关闭对比窗口后才继续执行。code是 VSCode 在系统 PATH 中的命令行入口,需在安装 VSCode 时勾选相关选项。

修改配置后,可以用git config --global -e打开全局配置文件~/.gitconfig检查结果,文件中会生成[diff]与[difftool "vscode"]两个配置段,内容与命令行设置一一对应:

[diff] tool = vscode [difftool "vscode"] cmd = code --wait --diff $LOCAL $REMOTE

之后即可用git difftool打开可视化对比窗口:

git difftool # 对比工作区与暂存区 git difftool --staged # 对比暂存区与已提交的版本

git difftool --staged允许你在提交前逐个浏览所有变更文件。作者的经验是:这种方式比纯文本 diff 更易追踪改动,且与之后在 GitHub 等托管平台上看到的 PR 对比视图高度相似。此外,VSCode 等主流 IDE 大多内置了差异对比面板,日常场景下无需频繁从终端调用,但当你暂时没有安装 IDE 时,git difftool依然是最可靠的替代方案。

查看提交历史

git log提供仓库内全部提交的完整视图,每次提交包含:唯一于该仓库的十六进制提交哈希、当前所在分支、作者、日期与提交信息。

git log # 完整提交历史 git log --oneline # 每行一个提交:短哈希 + 提交信息 git log --oneline --reverse # 倒序,从最早(第一次)提交开始

--oneline输出的短哈希(一般是完整哈希的前 7 位)可以直接用于后续的git show等 diff 类命令;--oneline --reverse则将顺序反转,让第一次提交显示在最上方,适合回顾项目起点。

深入查看单个提交

提交信息写得有意义(这正是 Day 38「提交最佳实践」的要求)时,浏览历史就足够高效;而当需要查看某个提交到底动了哪些文件时,使用git show:

git show <commit ID> # 查看指定提交的详情:作者、时间与变更内容 git show HEAD~1 # 查看当前提交之前第 1 步的提交(~N 表示回退 N 步)

git show的输出包含提交元信息与对应的完整 diff。若想以「快照目录树」的视角罗列某次提交的全部文件,可以使用git ls-tree:

git ls-tree HEAD~1 # 列出上一个快照目录树中的全部对象
100644 blob <hash> README.md 100644 blob <hash> main.js

从输出可以看到两个blob对象。在 Git 的对象模型中,blob代表文件内容,tree代表目录,此外还有commit与tag两类对象——这正是 Day 38 中git init之后目录里隐藏的.git数据库所存储的信息结构。拿到 blob 哈希后,可以继续用git show <blob hash>直接查看该文件在该快照中的具体内容,实现「从提交 → 目录树 → 文件内容」的逐层下钻。

取消暂存文件

实战中经常出现git add .把全部文件加入暂存区后,才发现某些文件并不想纳入本次快照的情况。Day 39 推荐用git restore --staged精确撤销git add操作:

git restore --staged newfile.txt # 将 newfile.txt 移出暂存区

操作前git status -s中newfile.txt显示为A(已暂存新增);执行git restore --staged后其状态变为??(未跟踪、未暂存),而code.txt的AM、main.js的M等其余文件状态保持不变。对已修改并暂存的文件(如main.js,绿色M)同样适用,可以单独把它的暂存也撤掉。

补充:Day 38 还介绍过一个相关场景——如果某个已经纳入跟踪的文件(例如日志目录)想改由.gitignore忽略,则需要使用git rm --cached把它从暂存区/索引中移除,这与git restore --staged面向「已暂存但未提交」的文件处理的是不同阶段的问题。

作者在 90DaysOfDevOps 期间经常利用该命令「先写笔记、暂不提交」,避免把未完成的草稿推送到公开仓库——这正是「暂存区独立于提交历史」设计带来的灵活性。

丢弃本地更改

如果对工作区的改动不满意,可以把它整体丢弃。git restore的第二个形态是从快照恢复文件:

git restore . # 用当前快照覆盖工作区中所有被跟踪文件的修改

执行后,main.js的M标记消失,工作区恢复干净;但注意:未跟踪文件不受影响——示例中的newfile.txt(??)依然存在,因为 Git 从未跟踪过它,自然也没有可供恢复的先前版本。

要清除这类未跟踪文件,使用git clean:

git clean # 预览将被清理的文件(默认会给出警告) git clean -fd # -f 强制执行,-d 同时清理目录

-f(force)与-d(directory)缺一不可,组合使用会删除工作区中所有未跟踪的文件与目录。Day 37 速查表中还提到git clean -n可以只「演练」不执行,先看清会被删掉什么。git clean的删除不可恢复,务必确认后果后再运行。

从快照恢复文件到更早版本

Git 的看家本领之一是快速回到某个快照。Day 39 用一个「误删 README.md」的场景演示了完整流程。

首先,用系统命令直接删除文件(注意:这里用的是 Unix 命令而非 Git 命令),再执行git rm readme.md让 Git 数据库同步记录删除,随后提交这次删除并确认工作区与暂存区均已清空。此时想找回文件,有两个思路:

  • 撤销最近一次提交:原文提到可用git undo,需要说明的是,原生 Git 并没有内置git undo命令,这一表述通常对应git revert <commit>(生成一个反向提交)或git reset --hard HEAD~1(直接丢弃最近一次提交),也可通过自定义 alias 把undo映射到这些命令;
  • 若删除发生在若干提交之前、又不希望回退中间的所有提交,则用git restore --source精确指定来源快照:
git log --oneline # 找到文件仍存在的提交 git restore --source=HEAD~1 README.md # 从上一个快照单独恢复 README.md

git restore --source=<commit> <path>只把目标文件恢复进工作区,不影响提交历史。恢复后README.md重新出现,并作为未跟踪文件(??)等待你重新git add、git commit。

作者特别强调:快照恢复点是「非常快的恢复点」而非备份——建议始终在仓库之外另存代码副本,用真正的备份方案兜底。

Rebase 与 Merge:何时用哪个

git rebase与git merge是 Git 使用中最容易纠结的决策,但首先要明确:两者解决的是同一个问题——把一个分支的变更整合进另一个分支,只是方式不同。

典型场景:feature分支上开发新功能,同时main分支也在持续产生新提交,两条线从共同历史节点分叉。此时有两种整合策略。

方式一:merge —— 简单但会留下合并提交

git merge main # 在 feature 分支上执行,把 main 的改动并入当前分支

merge 的优势是非破坏性:现有分支完全不被改动。代价是:每次整合上游变更都会产生一个额外的合并提交(merge commit);如果main非常活跃,feature 分支的历史会被这些合并提交反复「污染」,分叉越来越多,越来越难读。

方式二:rebase —— 干净但会重写历史

git checkout feature git rebase main

rebase 会把整个 feature 分支「搬」到 main 的最新提交之上,等效于吸收 main 的所有新提交,但不用合并提交,而是为原分支的每个提交创建全新的提交来重写项目历史:

对比上一张图可见,rebase 后得到一条没有分叉的线性历史,消除了所有不必要的合并提交,可读性显著提升。

取舍与黄金法则

rebase 的代价同样明确:

  1. 协作风险:重写历史前必须遵守「Rebase 黄金法则」——只 rebase 那些尚未被其他人拉取/基于其开发的分支;对已经共享出去的历史做 rebase,会让协作者的仓库与你的仓库分叉,严重时足以破坏整个协作流程;
  2. 信息丢失:合并提交本身记录了「上游变更是在哪个时间点并入 feature 的」这一上下文,rebase 会丢掉它。
维度mergerebase
提交历史形态保留分叉,出现合并提交线性,无合并提交
是否重写历史否,非破坏性是,为每个提交创建新提交
分支污染频繁整合会污染 feature 历史历史干净
协作风险低高(违反黄金法则时)
上下文保留「何时并入上游」丢失合并上下文

实践中的常见策略是:合并用于共享分支(如把 feature 合回 main),rebase用于整合尚未共享的本地提交,让本地历史保持整洁。

命令速查表

目的命令
查看暂存区与上次提交的差异git diff --staged(等价git diff --cached)
查看工作区与暂存区的差异git diff
配置可视化 diff 工具git config --global diff.tool vscode
打开可视化对比git difftool/git difftool --staged
查看完整提交历史git log
单行提交历史git log --oneline
从最早提交开始git log --oneline --reverse
查看单个提交详情git show <commit ID>/git show HEAD~1
列出快照目录树git ls-tree HEAD~1
取消暂存git restore --staged <file>
丢弃工作区跟踪文件改动git restore .
清理未跟踪文件git clean -fd(先用git clean -n预览)
从指定快照恢复文件git restore --source=HEAD~1 README.md
合并分支git merge main
变基分支git checkout feature && git rebase main

小结与下一步

Day 39 完成了本地 Git 工作流的最后一块拼图:查看(diff/log/show/ls-tree)、撤销(restore/clean)与整合(merge/rebase),再加上 Day 38 的暂存与提交,本地版本控制能力已经闭环。作者强调这些能力之所以重要,是因为它们将在 Day 40 起接入 GitHub、GitLab、BitBucket 等托管平台时全部复用——届时你看到的 Pull Request 对比视图,本质上就是 Day 39 练习过的 diff 逻辑的云端呈现(见 2022/Days/day40.md)。

本系列相关的完整学习路径可参阅 2022.md 中的「Use Git Effectively」章节;Day 37 的 Git 命令速查表(2022/Days/day37.md)覆盖了 reset、revert、reflog 等更多撤销类命令,可作为本文的延伸阅读。

  • 文档/教程

【免费下载链接】90DaysOfDevOps

This repository started out as a learning in public project for myself and has now become a structured learning map for many in the community. We have 3 years under our belt covering all things DevOps, including Principles, Processes, Tooling and Use Cases surrounding this vast topic.

项目地址:https://gitcode.com/gh_mirrors/90/90DaysOfDevOps
点击查看免费下载

相关推荐

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询