主题
界面
主题
Git 是唯一一个每天都在用、却很少系统学的工具。多数人是"会用 add、commit、push 就上路",然后在遇到冲突、误操作、需要挑选提交时卡住。
本节把 Git 的四个区域讲清楚,再把三个最容易搞混的"回退/挑选"命令(reset / revert / cherry-pick)一次分清楚。本知识库自己的仓库就是用这套流程管理的。
完成本节后,你应该能够:
status / diff / log --graph 读懂当前仓库状态;reset 的三种模式,并知道什么时候必须用 revert;cherry-pick 把一个提交搬到当前分支,并解释为什么它生成的是新提交。本节无前置知识。开始前确认:
git --version 能正常输出版本号(本机为 2.53.0);Git 和 GitHub 不是一回事
没有 GitHub,Git 照样用;反过来不成立。
三个真实场景:
场景一:改坏了代码想回到昨天。如果没用 Git,只能靠复制备份文件。
场景二:紧急修 bug,但当前分支还有半成品功能没写完。你需要开一个干净的分支只提交修复——这要求你会分支操作。
场景三:你在分支 A 写了一个工具方法,分支 B 也需要它,但分支 A 的其他改动还不能合并。这时要用 cherry-pick 只挑那一个提交。
前两个场景决定了你能不能安全地改代码,第三个决定了你在团队里是否高效。
工作区 暂存区 本地仓库 远程仓库
(working dir) (staging / index) (local repo) (remote)
│ │ │ │
│ git add │ git commit │ git push │
├────────────────────▶├──────────────────────▶├───────────────────▶│
│ │ │ │
│◀────────────────────┴───────────────────────┤◀───────────────────┤
│ git restore / git reset │ git pull │| 区域 | 存放什么 | 常用命令 |
|---|---|---|
| 工作区 | 你正在编辑的文件 | git diff |
| 暂存区 | 下次提交要包含的改动 | git add |
| 本地仓库 | 已提交的历史记录 | git commit / git log |
| 远程仓库 | 团队共享的记录 | git push / git pull |
暂存区的意义:它让你能只提交"这一个功能相关的改动",而不是把编辑过的所有文件一锅端。git status 里的红色(未暂存)与绿色(已暂存)就是在区分这两层。
Git 的提交由一个 SHA 值(如 70223bc)唯一标识,它包含:这次的文件快照、作者、时间、父提交的引用。
关键理解:Git 分支只是一个指向某个提交的指针。 所以创建分支几乎零成本——新建一个指针而已。
| 命令 | 作用 | 是否改写历史 | 什么时候用 |
|---|---|---|---|
reset | 把当前分支指针挪到另一个提交 | 改写 | 本地未推送的提交想撤销 |
revert | 生成一个"反向提交"抵消指定提交 | 不改写(追加) | 已推送的提交要撤销 |
cherry-pick | 把指定提交的改动复制到当前分支 | 不改写(追加) | 只要某个提交,不要整条分支 |
判断准则:改动如果已经推送到远程、可能被别人拉走了,就只能用 revert。用 reset 改写历史再强推,会把别人的工作也冲掉——这是团队协作里的红线。
reset 的三种模式:
| 模式 | 移动分支指针 | 重置暂存区 | 重置工作区 | 典型用途 |
|---|---|---|---|---|
--soft | 是 | 否 | 否 | 撤销提交但保留改动在暂存区(准备重新提交) |
--mixed(默认) | 是 | 是 | 否 | 撤销提交,改动回到工作区 |
--hard | 是 | 是 | 是 | 彻底丢弃(危险,改动找不回来) |
--hard 会丢代码
git reset --hard 会直接覆盖工作区文件。误用后唯一的救命工具是 git reflog——它记录了分支指针的每一次移动,但只对本地的、未被 GC 的记录有效。养成"执行 --hard 前先 git status 看一眼"的习惯。
很多人以为 cherry-pick 是"复制提交对象",其实它是:取出指定提交引入的差异,在当前分支上重新应用并生成一个新提交。
这解释了它的两个特征:
git init -b main
git status # 看到未跟踪文件(红色)
git add a.txt # 加入暂存区
git status # 看到待提交(绿色)
git commit -m "chore: 初始提交"
git log --oneline0a76c0d chore: 初始提交下面这段完整可运行,建议在空目录里亲手跑一遍:
# 准备:初始化并提交两次
git init -b main
"初始内容" | Set-Content -Encoding utf8 a.txt
git add . && git commit -m "chore: 初始提交"
"第二版" | Set-Content -Encoding utf8 a.txt
git commit -am "feat: 第二次提交"
# 在 feature 分支新增一个文件
git switch -c feature
"分支内容" | Set-Content -Encoding utf8 b.txt
git add . && git commit -m "feat: 分支新增文件"
# 回到 main,把 feature 上那一个提交"挑"过来
git switch main
git cherry-pick feature
git log --oneline70223bc feat: 分支新增文件 ← 新提交,SHA 与 feature 分支上的不同
f0abec4 feat: 第二次提交
0a76c0d chore: 初始提交对比一下:feature 分支上那个提交的 SHA 是 55004f8,cherry-pick 到 main 后变成了 70223bc。同样的改动,不同的提交对象——这就是 cherry-pick 的本质。
接着撤销它,注意用的是 revert 而不是 reset:
git revert --no-edit HEAD
git log --oneline -3fcb9507 Revert "feat: 分支新增文件" ← 追加了一个反向提交
70223bc feat: 分支新增文件
f0abec4 feat: 第二次提交b.txt 被删掉了,但历史里同时保留了"加了"和"撤销"两条记录——这是可追溯的做法。
git reset --hard HEAD~1
git push --force # 危险操作如果这期间有同事拉取过你的提交,强推之后他的本地历史就与远程分叉了,他的工作可能被覆盖。已推送的提交只应该用 revert。
本节没有 Java 代码,但有一条可完整复现的命令序列,用于练习冲突处理:
git init -b main
"第一行" | Set-Content -Encoding utf8 conflict.txt
git add . && git commit -m "chore: 初始提交"
# 分支 A 修改第一行
git switch -c branch-a
"分支 A 的修改" | Set-Content -Encoding utf8 conflict.txt
git commit -am "feat: A 修改第一行"
# main 也修改同一行 —— 冲突由此产生
git switch main
"main 的修改" | Set-Content -Encoding utf8 conflict.txt
git commit -am "feat: main 修改第一行"
git merge branch-a
# 预期:CONFLICT (content): Merge conflict in conflict.txt冲突后的文件内容:
<<<<<<< HEAD
main 的修改
=======
分支 A 的修改
>>>>>>> branch-a解决冲突的标准动作:
<<<<<<< / ======= / >>>>>>> 标记;git add conflict.txt(标记冲突已解决);git commit(完成合并)。git status:任何时候先看它。它会直接告诉你"下一步该做什么"(是 add、commit,还是解决冲突);git log --oneline --graph --all:看清分支结构。--all 会显示所有分支,包括你当前不在的那个;git diff --staged:对比"暂存区 vs 最后一次提交",提交前看一眼,防止漏文件;git reflog:Git 的"时光机"。它记录分支指针的每次移动(包括 reset 和 cherry-pick)。误操作后先跑它,找到目标提交的 SHA,再 git reset --hard <sha> 回来。症状:target/、.idea/、密钥文件进了仓库。修复:先加 .gitignore,再从版本控制移除但保留本地文件:
git rm -r --cached target
git commit -m "chore: 移除构建产物"注意:git rm --cached 只影响版本控制,不会删除本地文件;加 -r 是为了处理目录。
reset --hard 后想找回改动 修复:git reflog 找到操作前的 SHA,再 git reset --hard <sha>。如果改动从来没被提交过(只存在于工作区),reflog 也救不回来——这是 --hard 真正危险的地方。
症状:代码里残留 <<<<<<<,编译直接失败。修复:git diff 或搜索 <<<<<<< 确认清理完毕;编辑器里搜 <<< 是最快的检查方式。
push 被拒绝(rejected - non-fast-forward) 原因:远程有你本地没有的提交。正确做法:先 git pull --rebase 把远程提交放到你的提交之前,解决完冲突再 push。错误做法:直接 push --force 覆盖别人的工作。
目标:完整走一遍"分支 → 提交 → cherry-pick → revert"的流程。
步骤:
git log --oneline --graph --all 观察提交结构,找出 feature 分支的指针在哪里;git show <SHA> 查看 cherry-pick 前后两个提交的差异,确认内容相同、SHA 不同;git reset --hard HEAD~1,再用 git reflog 把它找回来;验收方式:能解释第 3 步"内容相同但 SHA 不同"的原因,以及第 4 步是用什么命令救回来的。
扩展任务:在本知识库仓库里执行 git log --oneline --graph -10,观察这个项目自己的提交历史,并找出每个提交属于哪一类变更(feat / docs / build / chore)。
基础题
git add 会把文件上传到 GitHub。(对 / 错)git reset --______ 会同时重置工作区,是三种模式中最危险的。revert 而不是 reset?命令阅读题
git switch -c hotfix
git commit -am "fix: 修复空指针"
git switch main
git cherry-pick hotfix
git branch -D hotfixgit branch -D 有什么风险?实操题
综合任务
main 上有未完成的实验代码(未提交);git stash 会让这件事变简单,它是暂存区之外的第四个"仓库")。提示 1(方向):第 4 题里 -D 是强制删除分支(-d 会在"未合并"时拒绝删除)。
提示 2(关键点):第 6 题的关键是"隔离"——把未完成的改动先收起来,让工作区干净,再开分支做修复。
提示 3(接近答案):git stash 保存改动并清空工作区,git stash pop 恢复;修复完成后回到原分支再 pop。
git add 只是把改动加入本地暂存区,与网络无关。上传要用 git push。--hard。reset 会改写历史:远程分支被强行指向另一个提交后,已经拉取过旧提交的同事会与远程分叉,后续推送要么被拒、要么覆盖别人的工作。revert 只是追加一个新提交来抵消旧改动,历史始终是线性的、可追溯的。流程是:新建 hotfix 分支 → 提交修复 → 回到 main → 把修复的改动 cherry-pick 过来 → 删除 hotfix 分支。
git branch -D 的风险:强制删除,即使该分支有未合并到其他分支的提交也会被删掉,这些提交可能再也找不回来(除非 reflog 还在)。所以删分支前先确认:git branch --merged 里有没有它,或用 -d 让 Git 帮你把关。
本例中 hotfix 的提交已经被 cherry-pick 到 main,删除是安全的(但严格说 cherry-pick 产生的是新提交,Git 仍会认为 hotfix 未合并,所以这里只能用 -D)。
两个分支修改同一文件的不同行时不会冲突,因为 Git 的合并以"行块(hunk)"为单位:只要两块修改不重叠,就能自动合并。
反之,修改同一行、或相邻行(上下文重叠)时就会冲突,需要人工判断。
# 1. 收起未完成的实验代码
git stash push -m "实验:集合练习"
# 2. 确认工作区干净(这一步很关键,避免把实验代码带进修复提交)
git status
# 3. 开修复分支并提交修复
git switch -c hotfix/null-pointer
# ... 修改代码 ...
git commit -am "fix: 修复订单查询空指针"
# 4. 回到 main 并合并修复
git switch main
git merge hotfix/null-pointer
# 5. 推送修复
git push
# 6. 恢复实验代码
git stash pop每一步的理由:stash 保证修复提交只包含修复内容(提交干净、便于回滚与 code review);git status 是提交前的自检;独立分支让修复可以单独推送与回溯;最后 stash pop 回到被打断的工作。
如果修复本身也需要独立发布(不跟随 main 上线),还可以把 merge 换成 cherry-pick,把修复提交单独挑到发布分支上。
status 判断下一步该做什么;reset / revert / cherry-pick 的适用场景;reflog 找现场。《Java 程序的运行与调试》 会把调试技能系统化;版本控制在团队协作中的应用(分支策略、Code Review)见 《Git 分支、合并与回退》。
无,可直接开始