- 文档/教程
【免费下载链接】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.
本文以 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.mdgit 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 mainrebase 会把整个 feature 分支「搬」到 main 的最新提交之上,等效于吸收 main 的所有新提交,但不用合并提交,而是为原分支的每个提交创建全新的提交来重写项目历史:
对比上一张图可见,rebase 后得到一条没有分叉的线性历史,消除了所有不必要的合并提交,可读性显著提升。
取舍与黄金法则
rebase 的代价同样明确:
- 协作风险:重写历史前必须遵守「Rebase 黄金法则」——只 rebase 那些尚未被其他人拉取/基于其开发的分支;对已经共享出去的历史做 rebase,会让协作者的仓库与你的仓库分叉,严重时足以破坏整个协作流程;
- 信息丢失:合并提交本身记录了「上游变更是在哪个时间点并入 feature 的」这一上下文,rebase 会丢掉它。
| 维度 | merge | rebase |
|---|---|---|
| 提交历史形态 | 保留分叉,出现合并提交 | 线性,无合并提交 |
| 是否重写历史 | 否,非破坏性 | 是,为每个提交创建新提交 |
| 分支污染 | 频繁整合会污染 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.
相关推荐
90DaysOfDevOps 之 Git 暂存与变更:从 git init 到 .gitignore 的本地仓库全流程实操
90DaysOfDevOps 之 Git 暂存与变更:从 git init 到 .gitignore 的本地仓库全流程实操 本篇文章承接 90DaysOfDev
文档/教程ClickHouse v26.3.17.56-lts 版本变更深度解析:从对象存储预取恢复到查询条件缓存修复
ClickHouse v26.3.17.56 lts 版本变更深度解析:从对象存储预取恢复到查询条件缓存修复 本篇文章以 ClickHouse 官方 LTS 分
数据库OLAP列式数据库大数据实时分析数据分析Oh My Zsh themes 插件实战:运行时切换主题、随机主题与主题清单
Oh My Zsh themes 插件实战:运行时切换主题、随机主题与主题清单 Oh My Zsh 的 themes 插件让你可以在同一个 shell 会话中通
文档/教程
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考