Git 从入门到进阶:让每一次修改都有来路,也有退路
从第一个提交到分支合并、冲突处理和版本恢复,用记账练习理解 Git,再逐步掌握 rebase、bisect 与 worktree。
做出第一个 HTML 记账页面以后,真正让我在意的问题变了:页面能跑,但下次让 AI 改完,它还能不能跑?如果金额计算被改坏,我能不能找回上一个正常版本?
把文件复制成“最终版”“最终版2”“真的最终版”,很快就会失去比较的依据。我希望保存的不只是文件,还包括这一次为什么改、改了哪里、怎么验证。Git 就是用来管理这种变化的工具。
这篇从一个本地练习仓库开始,走到分支协作、撤销与定位问题。所谓“精通”,不是读完一篇文章,而是遇到错误时能判断当前状态,选出影响范围最小的处理方式。另一篇 GitHub 教程 会把这个仓库接到远程协作流程里。
一、先分清 Git、GitHub 和文件保存
Git 是分布式版本控制工具,可以在本机记录提交和比较历史,不要求先注册 GitHub。GitHub 是承载 Git 仓库及协作流程的平台。按下保存只更新文件;创建提交才把选中的内容记进 Git 历史;推送则把本地提交送到远程。
这三件事可能发生在不同时间。电脑上已经有一个 commit,不代表 GitHub 已经收到了它;GitHub 有源码,也不代表网站已部署成功。
我把一次提交看作一个值得留下的检查点。例如“新增按月份筛选,并验证空月份显示”。这个检查点应该能独立说明目的,而不是把一下午所有改动压在一句“更新代码”里。
二、弄懂三个位置,命令就容易理解
| 位置 | 放的是什么 | 应该问自己的问题 |
|---|---|---|
| 工作区 | 正在编辑的文件 | 我刚才改了什么? |
| 暂存区 | 准备放进下一次提交的内容 | 这次到底提交哪几处? |
| 本地仓库 | 已创建的提交与历史 | 上一个可用版本是什么? |
可以把正常流程记成:编辑文件 → 查看差异 → 选择暂存 → 再检查 → 提交。git add 暂存的是执行当时的内容;如果之后又编辑文件,新修改还需要重新暂存。git commit 也不会自动把所有未跟踪文件一起收进去。官方:记录更新到仓库
三、先完成一个最小练习
下面使用 Windows PowerShell。先从 Git 官方下载入口 安装适合系统的版本,重新打开终端,运行 git --version 确认命令可用。VS Code 的源代码管理面板也能完成很多相同操作;这里使用命令是为了看清每一步。
选一个还不存在的练习目录,不在正式项目里试验。先进入你准备存放练习的父目录,再执行:
mkdir git-ledger-practice
cd git-ledger-practice
git init -b main
git config user.name "Practice Author"
git config user.email "practice@example.com"
Set-Content -Encoding utf8 README.md '# Ledger Practice'
Set-Content -Encoding utf8 ledger.txt 'lunch=32'
git status
git add README.md ledger.txt
git diff --staged
git commit -m "Create ledger practice baseline"
git log --oneline
这里的名字和邮箱只是练习身份,配置只作用于这个仓库。正式协作时换成自己的提交身份;准备公开到 GitHub 时,可使用账户设置里提供的 noreply 邮箱。提交身份不等于 GitHub 登录凭据。
此时预期看到:当前分支为 main,历史中有一条初始提交,git status 显示工作区干净。所谓干净,是没有待提交的变化,不代表程序没有 Bug。
接下来把午饭金额由 32 改成 23:
Set-Content -Encoding utf8 ledger.txt 'lunch=23'
git diff
git add ledger.txt
git diff --staged
git commit -m "Correct lunch amount from 32 to 23"
git log --oneline -2
这一次差异应当只涉及金额那一行。你能从两个提交解释“之前是什么、现在是什么、为什么改”。在真实页面中,还要打开页面验证金额;Git 负责记录,不能替你判断业务是否正确。
四、日常最重要的四次查看
我会在让 AI 开始修改前运行 git status,先确认已有改动属于谁。AI 完成之后,再查看以下信息:
git status --short
git diff
git diff --staged
git log --oneline --graph --decorate -12
第一条看文件状态;第二条看未暂存的修改;第三条看即将进入提交的内容;第四条看最近的历史。新建但未跟踪的文件不会出现在普通 git diff 中,需要单独打开审阅,暂存后再检查。
如果同一个文件里混着两个任务,git add -p 可以按差异块挑选,但最终仍要通读暂存差异。对于刚起步的小项目,先做到“一次提交只解决一件事”,比追求复杂的提交规范更有价值。
不该入库的东西,从开始就排除
创建 .gitignore,根据项目实际情况列出生成物和私密配置,例如:
.env
.env.*
!.env.example
node_modules/
.venv/
__pycache__/
.env.example 只能放变量名和假值。.gitignore 不是保险箱,也不会自动移除已经被 Git 跟踪的文件。密码如果进入公开历史,应先撤销或轮换,再处理历史副本,不能只删掉当前文件就当作解决。
五、用分支试新功能,再决定是否合并
分支可以理解为指向一串提交末端的名字。创建分支不需要再复制整个项目目录。HEAD 通常指向你当前所在的分支;切换分支后,看到的文件会随之变化。
继续刚才的练习,在工作区干净时试验“增加交通费”:
git switch -c feature/transport
Add-Content -Encoding utf8 ledger.txt 'transport=8'
git add ledger.txt
git commit -m "Add transport expense example"
git switch main
Get-Content ledger.txt
git merge feature/transport
Get-Content ledger.txt
切回 main 后暂时看不到交通费;合并完成后应能看到。因为这次 main 没有分叉产生新提交,通常会发生 fast-forward:主分支直接前移。并不是每次合并都产生额外的合并提交。官方:分支与合并
我的分支习惯是按问题命名,例如 fix/empty-month-total。一条分支尽量短,完成检查后及时合回,不把它变成另一个长期失联的项目。
六、冲突不是失败,是需要人做决定
可以主动制造一次冲突:从同一版本分出两条线,一条把 lunch=23 改为 lunch=24,另一条改为 lunch=25,分别提交,再合并。Git 无法知道哪个金额符合真实情况,就会暂停合并。
此时先运行 git status,打开冲突文件,找到类似内容:
<<<<<<< HEAD
lunch=25
=======
lunch=24
>>>>>>> feature/correct-lunch
这段标记不是要保留的代码。核对需求后,把整段编辑成正确内容,例如 lunch=24,移除标记,验证文件和程序,再执行:
git add ledger.txt
git commit -m "Resolve lunch amount conflict"
不要把“全部使用我的版本”当成通用答案。两边都可能包含应当保留的改动。如果决定取消当前合并,可用 git merge --abort;开始合并前保持工作区干净,才更容易安全退出。
七、撤销之前,先判断修改走到了哪一步
同样是“不要这次修改”,发生在不同阶段,需要的动作完全不同。
| 当前情况 | 常见处理 | 影响 |
|---|---|---|
| 已暂存,但还想继续编辑 | git restore --staged ledger.txt | 撤出暂存,保留工作区修改 |
| 未提交,确认不要文件里的改动 | git restore ledger.txt | 默认恢复为暂存区内容,会丢弃该文件未暂存修改 |
| 最近提交只在本地,需要补漏 | git commit --amend | 替换最近提交,提交标识会改变 |
| 已共享的普通提交需要撤销 | git revert 提交ID | 新增一个反向提交,保留原历史 |
| 分支指针移动错误,需要找旧提交 | 先 git reflog | 查看本地引用移动记录,再建立恢复分支 |
git reset --soft 主要移动分支指针;默认 mixed reset 还会重置暂存区;--hard 进一步覆盖工作区,可能丢失内容。初学时不要把它当“刷新一下试试”。官方:Reset 的作用范围
对于已经被别人拉取的提交,我更倾向于使用 revert,让“做过什么”和“后来为何撤销”都有记录。合并提交的 revert 还涉及主线选择,不应直接照抄普通提交的操作。官方:git revert
恢复时可以先执行 git reflog 找到目标,再用 git branch rescue/checkpoint 提交ID 给它建立名字,先保住可检查的入口。reflog 是本地记录,有保留和清理规则,不能保证找回从未提交的文件,也不能替代备份。官方:git reflog
八、接入远程:先拿到变化,再整合变化
远程仓库通常起一个本地别名,例如 origin。origin/main 是本机记录的远程分支状态,只有在 fetch 等操作后才会更新,并不是持续联网的实时视图。
git remote -v
git fetch origin
git log --oneline --graph --decorate --all -15
git pull --ff-only
这组命令适用于已经配置远程、当前分支已有上游的仓库。fetch 获取远程内容,但不会自动把它合进当前分支;pull 会进一步整合。--ff-only 在出现分叉时停止,给你机会先看清情况,而不是悄悄做一个意料之外的合并。
第一次连接 GitHub、设置上游以及推送,请接着看 下一篇教程。拉取被拒绝时,不要立刻强推:先检查账号、分支、远程地址和提交图。
九、进阶工具:围绕问题选择,不围绕命令炫技
Merge 与 Rebase:选择怎样呈现历史
Merge 把两条历史汇合;Rebase 把一条分支上的提交重新应用到新的基础上,因此会生成新的提交标识。对于尚未共享的个人分支,整理后再提交审阅可能更清楚;对于多人正在使用的历史,擅自 rebase 会给其他人的同步制造麻烦。官方:变基
发生 rebase 冲突时,解决后暂存,再 git rebase --continue;想退出则 git rebase --abort。如果确实要改写已推送的个人分支,应先与协作者确认;即便使用 --force-with-lease,也要理解检查对象,不能把它当绝对安全保证。
Bisect:找出从哪个版本开始坏掉
当你知道一个好版本和一个坏版本,但中间有几十次提交,可让 Git 二分选择待检查版本:
git bisect start
git bisect bad
git bisect good 已验证正常的提交ID
每次运行同一套复现步骤,然后根据结果标记 git bisect good 或 git bisect bad,直到定位到引入问题的提交。结束后运行 git bisect reset。判断标准不稳定时,二分结果也会不可靠;先把 Bug 变成能反复验证的现象。官方:git bisect
Worktree:同时打开两个任务
当主目录正在开发页面,而你需要单独处理修复,可以创建另一个工作目录:
git worktree add ../ledger-fix -b fix/empty-state main
git worktree list
两个目录有各自的工作区,共享仓库对象。同一个分支通常不能同时在多个工作树中检出。完成后先确认修复已提交、工作区没有要保留的文件,再使用 git worktree remove ../ledger-fix 移除。不要直接批量删目录。官方:git worktree
十、让 AI 参与开发,版本边界更重要
我会给 AI 一段这样的任务说明:
先查看当前分支和未提交修改。只处理空月份显示错误,不改记账数据格式。完成后说明修改了什么、用什么场景验证;展示差异,保留其他任务的改动。不要擅自强推或清理文件。
提交粒度也应该跟任务一致。先修计算,再改视觉,分别验证、分别提交。这样一旦其中一个方向不合适,回退范围会小很多。AI 说“已修复”只是说明,实际页面和测试结果才是证据。
十一、怎样判断自己真的掌握了
不要以背下多少命令打分。在练习仓库里独立完成这五件事:
- 保存初始版本,再只提交一次金额修正,并解释两次提交的区别。
- 创建功能分支,确认主分支不受影响,再把功能合回来。
- 制造并解决一次同一行冲突,说出为什么保留那个结果。
- 把一个误暂存文件撤出暂存区,确认修改还在;撤销一个普通提交,确认原历史还在。
- 从提交图和差异里找到一个具体改动,给它建立可追溯的说明。
做到这些,再在真实项目里练习 rebase、bisect 和 worktree。Git 熟练度最终体现在:修改前有依据,修改后能解释,出了问题有办法恢复。
下一篇:GitHub 从入门到进阶:把一个人的代码,变成可协作、可验证的项目。
十二、常用 Git 命令:可复制输入、示例输出和解释
这一节按日常使用顺序整理。每项都写出使用前提、可复制命令和输出解释;既可以按场景查,也可以在独立练习目录里复现。首次阅读先掌握 status、diff、add、commit、log、switch、pull、push。
示例说明: 输出采自独立练习仓库,路径统一写成 D:/git-command-lab;提交 ID、对象数量、耗时和版本号会因机器不同而变化。远程操作使用本地裸仓库模拟,不代表向 GitHub 发布过代码。各条目前提不同,不要把整节命令不加判断地连续执行到正式项目中。
只复制“输入”代码块,不要把终端提示符、示例输出也粘进去。涉及文件名、分支名和仓库地址时,先换成自己的实际对象。下文文件内容都来自虚构的教学数据。
先看懂 status 的两个位置
M ledger.txt
M ledger.txt
MM ledger.txt
A note.txt
D old.txt
?? new.txt
UU conflict.txt
这是一组状态对照示例,不是同一时刻同一文件的真实列表。普通状态下,左列看“已暂存的变化”,右列看“尚未暂存的变化”。
| 标记 | 含义 | 下一步通常检查什么 |
|---|---|---|
| 空格 + M | 文件改了,尚未暂存 | git diff |
| M + 空格 | 修改已经暂存 | git diff --staged |
| MM | 暂存后又编辑过 | 两种 diff 都看 |
| A + 空格 | 新文件已暂存 | 检查内容后提交 |
| D + 空格 | 删除已暂存 | 确认确实要从仓库删除 |
| ?? | 尚未跟踪 | 决定添加还是忽略 |
| UU | 存在双方修改冲突 | 打开文件解决冲突 |
冲突状态有专门的两列含义,不能机械套用普通状态解释。官方:status 输出格式
01 · git --version:查看版本
确认 Git 是否已经安装,以及当前使用的版本。
使用前提: 在终端任意目录执行。
输入:
git --version
示例输出:
git version 2.53.0.windows.1
怎么看: 输出中的数字是这台电脑安装的版本,不是所有人都应该相同的值。若提示命令不存在,先安装 Git 或检查终端的 PATH。
02 · git init:初始化仓库
把一个目录交给 Git 管理,创建本地仓库。
使用前提: 当前位于练习父目录,ledger 目录尚不存在。
输入:
git init -b main ledger
示例输出:
Initialized empty Git repository in D:/git-command-lab/ledger/.git/
怎么看: Initialized empty Git repository 表示初始化成功;.git 是仓库元数据目录;-b main 指定初始分支名。这里没有创建 GitHub 仓库,也没有上传文件。接下来先执行 cd ledger。
03 · git config:设置和读取提交身份
配置写入提交记录的姓名与邮箱。
使用前提: 已经执行 cd ledger;示例身份只用于练习。
输入:
git config user.name "Practice Author"
git config user.email "practice@example.com"
git config --get user.name
git config --get user.email
示例输出:
Practice Author
practice@example.com
怎么看: 前两条写入配置,通常没有输出;后两条分别读出姓名、邮箱。不加 --global 时配置仅限当前仓库;加 --global 则影响这个操作系统用户的默认配置。这不是 GitHub 登录。
04 · git status:查看当前状态
查看所在分支,以及文件是否未跟踪、未暂存或已暂存。
使用前提: 仓库中已新建 README.md、ledger.txt,尚未暂存。
输入:
git status --short
示例输出:
?? README.md
?? ledger.txt
怎么看: ?? 表示 Git 尚未跟踪这个文件。去掉 --short 可查看完整说明。普通非冲突状态下,文件名前的两列分别表示暂存区相对 HEAD、工作区相对暂存区的变化;空格不是可以忽略的装饰。上面的对照表列出了常见标记。
05 · git add:把指定修改放进暂存区
选择下一次提交要包含的文件内容。
使用前提: README.md 和 ledger.txt 已存在。
输入:
git add README.md ledger.txt
示例输出: 成功时通常没有输出,终端返回输入提示符。
怎么看: 没有输出不等于没执行。紧接着运行 git status --short,应看到 A 后跟文件名,表示新增文件已暂存。再次编辑文件后,需要再次 git add 才会纳入新内容。
06 · git diff --staged:检查即将提交的内容
比较暂存区和最近提交。--cached 是同义写法。
使用前提: 两份新文件已暂存,还没有第一次提交。
输入:
git diff --staged
示例输出:
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..60f021b
--- /dev/null
+++ b/README.md
@@ -0,0 +1 @@
+# Ledger
diff --git a/ledger.txt b/ledger.txt
new file mode 100644
index 0000000..a597f74
--- /dev/null
+++ b/ledger.txt
@@ -0,0 +1 @@
+lunch=32
怎么看: new file mode 表示新增文件;--- /dev/null 表示之前不存在;+++ b/文件名表示新的文件。以 + 开头的是新增行。这里列出的内容才会进入下一次提交。官方 diff 说明
07 · git commit:创建一个本地版本
把暂存区内容保存为一次提交。
使用前提: 已经检查暂存差异并设置提交身份。
输入:
git commit -m "Create ledger baseline"
示例输出:
[main (root-commit) 3a637ad] Create ledger baseline
2 files changed, 2 insertions(+)
create mode 100644 README.md
create mode 100644 ledger.txt
怎么看: main 是分支;root-commit 表示第一条提交;后面的短字符串是提交 ID。files changed 统计改变的文件数,insertions 统计新增行数。-m 后面是这次修改的说明;提交尚未上传到远程。
08 · git status:确认工作区干净
检查本次修改是否已经处理完。
使用前提: 刚完成第一次提交,没有再编辑文件。
输入:
git status
示例输出:
On branch main
nothing to commit, working tree clean
怎么看: nothing to commit, working tree clean 表示没有待提交差异。不代表程序测试通过,也不代表远程已同步;这两件事需要分别验证。
09 · git diff:查看尚未暂存的修改
比较工作区与暂存区的文本差异。
使用前提: 在编辑器中把 ledger.txt 的 lunch=32 改为 lunch=23,尚未 git add。
输入:
git diff -- ledger.txt
示例输出:
diff --git a/ledger.txt b/ledger.txt
index a597f74..ffd2367 100644
--- a/ledger.txt
+++ b/ledger.txt
@@ -1 +1 @@
-lunch=32
+lunch=23
怎么看: --- 和 +++ 行是文件标识;-lunch=32 是旧行,+lunch=23 是新行。@@ -1 +1 @@ 指这段差异在旧文件、新文件中都从第1行开始。-- 用于分隔选项和文件名。新建未跟踪文件通常不会显示在普通 git diff 中。
10 · git log:查看提交历史
查看仓库记录过哪些版本。
使用前提: 金额修正已另做一次提交。
输入:
git log --oneline --graph --decorate -5
示例输出:
* c6a4156 (HEAD -> main) Correct lunch amount
* 3a637ad Create ledger baseline
怎么看: 每行是一条提交,左侧是简短 ID,右侧是说明。HEAD -> main 表示当前检出的 main 指向该提交。--graph 画出历史关系,--decorate 显示分支/标签名字,-5 最多显示5条。进入分页界面时按 q 退出。
11 · git show:查看某次提交的内容
针对一个版本查看记录和差异。
使用前提: HEAD 表示当前提交。
输入:
git show --stat --oneline HEAD
示例输出:
c6a4156 Correct lunch amount
ledger.txt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
怎么看: --stat 显示文件和增删统计,--oneline 简化提交头。去掉 --stat 可以查看具体差异;把 HEAD 换成从 git log 复制的提交 ID,可以检查那个版本。
12 · git branch:列出本地分支
看本机有哪些开发分支。
使用前提: 当前只有 main。
输入:
git branch
示例输出:
* main
怎么看: 星号 * 表示当前分支,不是“主分支”的固定标志。git branch -a 还会列出远程跟踪分支;git branch feature/note 只创建分支,不会自动切换。
13 · git switch -c:创建并切换分支
从当前提交开始一条新的开发分支。
使用前提: 工作区干净,而且 feature/note 尚不存在。
输入:
git switch -c feature/note
示例输出:
Switched to a new branch 'feature/note'
怎么看: Switched to a new branch 表示分支已创建且已切过去。-c 是 create;不要对已经存在的分支重复使用。旧教程中的 git checkout -b feature/note 也可完成相近操作。
14 · git switch:切换到已有分支
把当前工作区切换到指定分支的版本。
使用前提: feature/note 上的 note.txt 已提交,工作区干净。
输入:
git switch main
示例输出:
Switched to branch 'main'
怎么看: 返回 main 后,看不到尚未合并的 note.txt 是正常的。若未提交修改会被覆盖,Git 通常会拒绝切换;先处理改动,不要把强制切换当常规步骤。
15 · git merge:合并另一个分支
把指定分支的修改整合进当前分支。
使用前提: 当前在 main,准备接收 feature/note 的修改。
输入:
git merge feature/note
示例输出:
Updating c6a4156..4fdc2ad
Fast-forward
note.txt | 1 +
1 file changed, 1 insertion(+)
create mode 100644 note.txt
怎么看: Updating 后面列出旧、新提交 ID;Fast-forward 表示 main 可以直接前移,没有产生额外合并提交。如果出现 CONFLICT,需要先解决文件冲突再提交,不能按这个成功输出继续执行。
16 · git branch -d:删除已完成的本地分支
清理不再需要的本地分支名字。
使用前提: 当前在 main,feature/note 的修改已经合入。
输入:
git branch -d feature/note
示例输出:
Deleted branch feature/note (was 4fdc2ad).
怎么看: Deleted branch 表示删除该分支引用。它不删除当前 main 的文件,也不删除远程分支。若提示未合并,先检查差异,不要习惯性改用 -D 强制删除。
17 · git remote:添加和查看远程地址
为另一个仓库设置一个本地别名。
使用前提: 练习父目录已创建一个空裸仓库 remote.git;当前没有 origin。这里使用本地路径模拟远程,不访问 GitHub。
输入:
git remote add origin ../remote.git
git remote -v
示例输出:
origin ../remote.git (fetch)
origin ../remote.git (push)
怎么看: origin 是别名;(fetch) 是获取地址,(push) 是推送地址。git remote add 成功通常无输出,上述内容由第二条命令显示。真实 GitHub 操作时,将 ../remote.git 换成自己仓库页面提供的地址;不要把 token 写在 URL 里。
18 · git push:把本地提交推送出去
把指定分支的提交送到远程仓库。
使用前提: origin 已配置、目标允许写入;当前分支为 main。
输入:
git push -u origin main
示例输出:
branch 'main' set up to track 'origin/main'.
To ../remote.git
* [new branch] main -> main
怎么看: [new branch] main -> main 表示远程新建了 main;set up to track 表示设置好上游。网络推送还可能显示对象计数,那是传输进度。若后续返回 Everything up-to-date,表示该推送目标没有新提交可接收,不是在证明网站已经部署。
19 · git clone:复制一个已有仓库
下载仓库历史并检出一个工作副本。
使用前提: 在练习父目录执行,remote.git 已有提交,peer 目录尚不存在。
输入:
git clone ./remote.git peer
示例输出:
Cloning into 'peer'...
done.
怎么看: Cloning into 表示目标目录,done 表示这次复制完成。真实地址可以是 HTTPS 或 SSH。clone 通常会配置 origin;它不是重新创建一个空仓库。
20 · git fetch:获取远程变化
把远程提交和分支状态更新到本地,暂不合并当前分支。
使用前提: 回到原来的 ledger 目录;模拟协作者已从 peer 推送了一次新提交。
输入:
git fetch origin
示例输出:
From ../remote
4fdc2ad..e2c93df main -> origin/main
怎么看: main -> origin/main 表示远程跟踪分支已更新;当前 main 和工作文件尚未自动跟上。因此“fetch 成功”不等于“我的代码已经是新版本”。官方 fetch
21 · git pull --ff-only:拉取并快进更新
获取远程变化,并在无需分叉合并时更新当前分支。
使用前提: main 已配置上游,工作区干净,本地没有额外分叉提交。
输入:
git pull --ff-only
示例输出:
Updating 4fdc2ad..e2c93df
Fast-forward
peer.txt | 1 +
1 file changed, 1 insertion(+)
create mode 100644 peer.txt
怎么看: Fast-forward 表示更新成功,下面展示了新增的 peer.txt。--ff-only 遇到分叉会拒绝整合,此时先查看提交图,再决定 merge 或 rebase。Already up to date. 则表示本次无需再更新。官方 pull
22 · git tag:给版本命名
为确定的提交建立便于识别的版本标签。
使用前提: 当前版本已验证,v0.1.0 尚不存在。
输入:
git tag -a v0.1.0 -m "First practice version"
git tag --list
示例输出:
v0.1.0
怎么看: 第一条创建带说明的标签,通常无输出;第二条列出标签。标签此时只在本地,若确实要共享该标签,再执行 git push origin v0.1.0。它不会自动生成安装包或部署网站。
23 · git restore --staged:撤出暂存区
取消对某个文件的暂存,保留文件里的修改。
使用前提: 已经把金额改成24并暂存,但暂时不想提交它。
输入:
git restore --staged ledger.txt
git status --short
示例输出:
M ledger.txt
怎么看: M ledger.txt 前面的第一列是空格,第二列 M 表示工作区仍有修改。文件金额仍是24;这不是丢弃修改,只是从下一次提交候选中撤出。官方 restore
24 · git stash:临时保存未完成的改动
暂时收起工作区改动,腾出干净工作区。
使用前提: 金额改动还没完成,需要临时处理别的任务;只在自己的工作区操作。
输入:
git stash push -u -m "WIP lunch correction"
git stash list
示例输出:
Saved working directory and index state On main: WIP lunch correction
stash@{0}: On main: WIP lunch correction
怎么看: Saved working directory 表示保存成功;stash@{0} 是最新一份暂存记录。-u 还包含未跟踪文件,但不包含被忽略的文件。stash 主要是本地临时存放,不是远程备份。官方 stash
25 · git stash pop:取回临时保存的修改
把选中的 stash 应用回工作区,成功时移除该 stash 记录。
使用前提: 当前工作区干净,刚才的 stash 仍存在。
输入:
git stash pop "stash@{0}"
示例输出:
On branch main
Your branch is up to date with 'origin/main'.
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: ledger.txt
no changes added to commit (use "git add" and/or "git commit -a")
Dropped stash@{0} (1b4d5036039fbc4b053bfa3faae8a24c17ef9f98)
怎么看: modified: ledger.txt 表示修改回来了;Dropped 表示该 stash 已移除。若出现冲突,stash 一般会保留,先解决冲突。想保留记录可用 git stash apply "stash@{0}";PowerShell 中给这个引用加引号。
26 · git restore:丢弃文件未暂存的修改
用暂存区中的内容恢复指定工作区文件。
使用前提: 确认刚取回的金额24修改不需要;本例暂存区仍是金额23。此操作会覆盖未暂存内容。
输入:
git restore ledger.txt
git status --short
示例输出: 成功时通常没有输出,终端返回输入提示符。
怎么看: 两条命令都没有输出:restore 成功且 status --short 没有发现变化。ledger.txt 已回到23。它不能随意撤销;确认要保留的内容已经另存或提交后再操作。
27 · git revert:用新提交撤销旧提交
创建一个反向提交,保留原来的历史记录。
使用前提: 最近一次普通提交只把金额误改为999,现在要撤销;工作区干净。
输入:
git revert --no-edit HEAD
示例输出:
[main 2ea1708] Revert "Introduce wrong amount"
Date: Sat Oct 10 00:31:37 2026 +0800
1 file changed, 1 insertion(+), 1 deletion(-)
怎么看: 输出出现新的提交 ID,标题以 Revert 开始。HEAD 在执行前指向那条错误提交;--no-edit 使用默认撤销说明,不打开编辑器。金额恢复,但原错误提交仍在历史中。合并提交的撤销需要另行判断主线。
28 · git reset --soft:撤回本地提交,保留修改
把当前分支退到指定位置,同时保留暂存区和工作区。
使用前提: 在独立练习分支 practice/reset,最新提交新增 draft.txt,尚未共享;至少有两个提交。
输入:
git reset --soft HEAD~1
git status --short
示例输出:
A draft.txt
怎么看: HEAD~1 表示当前提交沿第一父提交后退一步。A draft.txt 表示文件还在,而且已经暂存,可以调整后重新提交。不要对别人正在使用的历史照搬;--hard 会覆盖更多内容,与 --soft 的效果完全不同。
29 · git reflog:查看本地指针移动记录
在误移动分支后寻找先前指向的提交。
使用前提: 刚执行过一次 reset,想找到退回之前的提交。
输入:
git reflog -3
示例输出:
2ea1708 HEAD@{0}: reset: moving to HEAD~1
d5d8602 HEAD@{1}: commit: Add local draft
2ea1708 HEAD@{2}: checkout: moving from main to practice/reset
怎么看: reset: moving to HEAD~1 记录了这次指针移动;commit: Add local draft 对应退回之前的提交。确认它以后可用 git branch rescue/draft 提交ID 建立恢复入口。reflog 不是永久历史,也不能保证恢复从未提交的内容。
30 · git mv:重命名并暂存
移动或重命名文件,并把变化加入暂存区。
使用前提: draft.txt 已提交,目标文件名 draft-renamed.txt 不存在。
输入:
git mv draft.txt draft-renamed.txt
git status --short
示例输出:
R draft.txt -> draft-renamed.txt
怎么看: R 旧名 -> 新名 表示识别到重命名,且已暂存。检查内容以后仍需要 git commit;git mv 本身不创建提交。
31 · git rm --cached:停止跟踪,保留本地文件
从暂存区移除文件,使下一次提交不再跟踪它。
使用前提: draft-renamed.txt 已提交,但现在只想把它留在本机。
输入:
git rm --cached draft-renamed.txt
git status --short
示例输出:
rm 'draft-renamed.txt'
D draft-renamed.txt
?? draft-renamed.txt
怎么看: D 表示“仓库中的删除”已暂存,?? 表示本地同名文件仍存在、现为未跟踪。配合 .gitignore 才能避免再次添加。去掉 --cached 的 git rm 还会删除工作区文件;不要混淆。历史中的旧副本不会因此消失。
32 · git check-ignore:解释文件为何被忽略
查出命中了哪一条忽略规则。
使用前提: 已在 .gitignore 第1行写入 .env,文件未被跟踪。
输入:
git check-ignore -v .env
示例输出:
.gitignore:1:.env .env
怎么看: .gitignore:1:.env 依次给出规则文件、行号、匹配模式,最后是被检查的路径。若没有匹配,通常无输出且退出码为1;脚本中不能只凭“无输出”判定成功。
33 · git clean -nd:预览未跟踪文件清理
只预览清理范围,不删除文件。
使用前提: 练习仓库中有未跟踪的文件,希望先看清会影响哪些路径。
输入:
git clean -nd
示例输出:
Would remove .gitignore
Would remove draft-renamed.txt
Would remove temporary.txt
怎么看: Would remove 表示如果执行删除,哪些路径会被移除;-n 是预览,-d 包含未跟踪目录。这里没有执行删除。不要为了让状态变干净就把 -n 改成 -f;尚未提交的素材、配置可能也在列表里。
常见提示不是都代表失败
| 示例提示 | 意思 | 处理方向 |
|---|---|---|
| nothing to commit, working tree clean | 没有待提交差异 | 如果刚改过文件,检查是否保存、是否在正确目录或是否被忽略 |
| fatal: not a git repository | 当前目录及上级没有找到可用仓库 | 先进入正确项目;不要在陌生目录直接 init |
| fatal: a branch named 'feature/note' already exists | 分支名已存在 | 确认目标后用 git switch feature/note |
| error: remote origin already exists. | 已有名为 origin 的远程 | 用 git remote -v 检查现有地址 |
| rejected ... non-fast-forward | 推送无法直接前移远程分支 | 先 fetch、看历史,再整合;不是立刻 force |
| CONFLICT (content) | 某段内容无法自动合并 | git status 查文件,解决并验证后继续 |
| Permission denied (publickey). | SSH 身份验证未通过 | 检查账号、公钥与目标仓库权限 |
最后,把最常用的本地提交流程串起来:
git status
git diff
git add ledger.txt
git diff --staged
git commit -m "Explain this change"
git status
当文件确实有待提交修改时,commit 会产生新的提交摘要;最后 status 应显示工作区干净,或明确列出本次没有包含的其他改动。工作区不必为了“看着干净”而清除别人的工作。
如需上传,再核对 git remote -v 和当前分支,运行适合自己分支的 push,并在远程页面确认结果。关于 GitHub 上如何审阅、自动检查和发布版本,继续阅读 GitHub 篇。
夏末