Skip to content

Git 基础与 cherry-pick ​

S0-004必学入门约 60 分钟JDK 21学习准备与环境 · 工程工具

1. 本节定位 ​

Git 是唯一一个每天都在用、却很少系统学的工具。多数人是"会用 add、commit、push 就上路",然后在遇到冲突、误操作、需要挑选提交时卡住。

本节把 Git 的四个区域讲清楚,再把三个最容易搞混的"回退/挑选"命令(reset / revert / cherry-pick)一次分清楚。本知识库自己的仓库就是用这套流程管理的。

2. 学习目标 ​

完成本节后,你应该能够:

  • 说清工作区、暂存区、本地仓库、远程仓库的关系;
  • 用 status / diff / log --graph 读懂当前仓库状态;
  • 创建分支、合并分支、解决一次真实的冲突;
  • 分清 reset 的三种模式,并知道什么时候必须用 revert;
  • 用 cherry-pick 把一个提交搬到当前分支,并解释为什么它生成的是新提交。

3. 前置知识检查 ​

本节无前置知识。开始前确认:

  • 本机 git --version 能正常输出版本号(本机为 2.53.0);
  • 你知道"提交"是把改动记录到本地,而不是上传到服务器。

Git 和 GitHub 不是一回事

  • Git:版本控制工具,装在你本机,离线也能用;
  • GitHub / Gitee:托管 Git 仓库的网站,提供远程仓库、Pull Request、Issue 等协作功能。

没有 GitHub,Git 照样用;反过来不成立。

4. 为什么需要这个知识 ​

三个真实场景:

场景一:改坏了代码想回到昨天。如果没用 Git,只能靠复制备份文件。

场景二:紧急修 bug,但当前分支还有半成品功能没写完。你需要开一个干净的分支只提交修复——这要求你会分支操作。

场景三:你在分支 A 写了一个工具方法,分支 B 也需要它,但分支 A 的其他改动还不能合并。这时要用 cherry-pick 只挑那一个提交。

前两个场景决定了你能不能安全地改代码,第三个决定了你在团队里是否高效。

5. 核心概念 ​

5.1 四个区域 ​

text
工作区                暂存区                  本地仓库              远程仓库
(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 里的红色(未暂存)与绿色(已暂存)就是在区分这两层。

5.2 提交是一个"快照 + 指针" ​

Git 的提交由一个 SHA 值(如 70223bc)唯一标识,它包含:这次的文件快照、作者、时间、父提交的引用。

关键理解:Git 分支只是一个指向某个提交的指针。 所以创建分支几乎零成本——新建一个指针而已。

5.3 reset / revert / cherry-pick 三兄弟 ​

命令作用是否改写历史什么时候用
reset把当前分支指针挪到另一个提交改写本地未推送的提交想撤销
revert生成一个"反向提交"抵消指定提交不改写(追加)已推送的提交要撤销
cherry-pick把指定提交的改动复制到当前分支不改写(追加)只要某个提交,不要整条分支

判断准则:改动如果已经推送到远程、可能被别人拉走了,就只能用 revert。用 reset 改写历史再强推,会把别人的工作也冲掉——这是团队协作里的红线。

reset 的三种模式:

模式移动分支指针重置暂存区重置工作区典型用途
--soft是否否撤销提交但保留改动在暂存区(准备重新提交)
--mixed(默认)是是否撤销提交,改动回到工作区
--hard是是是彻底丢弃(危险,改动找不回来)

--hard 会丢代码

git reset --hard 会直接覆盖工作区文件。误用后唯一的救命工具是 git reflog——它记录了分支指针的每一次移动,但只对本地的、未被 GC 的记录有效。养成"执行 --hard 前先 git status 看一眼"的习惯。

5.4 cherry-pick 的本质 ​

很多人以为 cherry-pick 是"复制提交对象",其实它是:取出指定提交引入的差异,在当前分支上重新应用并生成一个新提交。

这解释了它的两个特征:

  1. SHA 不同:新提交是新对象,与源提交没有父子关系;
  2. 可能冲突:差异应用的上下文不同,就可能需要手动解决。

6. 递进示例 ​

6.1 最小示例:从零到第一次提交 ​

powershell
git init -b main
git status                 # 看到未跟踪文件(红色)
git add a.txt              # 加入暂存区
git status                 # 看到待提交(绿色)
git commit -m "chore: 初始提交"
git log --oneline
text
0a76c0d chore: 初始提交

6.2 业务示例:分支、cherry-pick 与 revert ​

下面这段完整可运行,建议在空目录里亲手跑一遍:

powershell
# 准备:初始化并提交两次
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 --oneline
text
70223bc feat: 分支新增文件     ← 新提交,SHA 与 feature 分支上的不同
f0abec4 feat: 第二次提交
0a76c0d chore: 初始提交

对比一下:feature 分支上那个提交的 SHA 是 55004f8,cherry-pick 到 main 后变成了 70223bc。同样的改动,不同的提交对象——这就是 cherry-pick 的本质。

接着撤销它,注意用的是 revert 而不是 reset:

powershell
git revert --no-edit HEAD
git log --oneline -3
text
fcb9507 Revert "feat: 分支新增文件"   ← 追加了一个反向提交
70223bc feat: 分支新增文件
f0abec4 feat: 第二次提交

b.txt 被删掉了,但历史里同时保留了"加了"和"撤销"两条记录——这是可追溯的做法。

6.3 反例:用 reset 撤销已推送的提交 ​

powershell
git reset --hard HEAD~1
git push --force            # 危险操作

如果这期间有同事拉取过你的提交,强推之后他的本地历史就与远程分叉了,他的工作可能被覆盖。已推送的提交只应该用 revert。

7. 完整可运行代码 ​

本节没有 Java 代码,但有一条可完整复现的命令序列,用于练习冲突处理:

powershell
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

冲突后的文件内容:

text
<<<<<<< HEAD
main 的修改
=======
分支 A 的修改
>>>>>>> branch-a

解决冲突的标准动作:

  1. 读清楚两边的意图(不是"选一个删掉",而是要判断最终应该是什么内容);
  2. 把文件改成最终想要的样子,删掉所有 <<<<<<< / ======= / >>>>>>> 标记;
  3. git add conflict.txt(标记冲突已解决);
  4. git commit(完成合并)。

8. Debug 观察点 ​

  1. git status:任何时候先看它。它会直接告诉你"下一步该做什么"(是 add、commit,还是解决冲突);
  2. git log --oneline --graph --all:看清分支结构。--all 会显示所有分支,包括你当前不在的那个;
  3. git diff --staged:对比"暂存区 vs 最后一次提交",提交前看一眼,防止漏文件;
  4. git reflog:Git 的"时光机"。它记录分支指针的每次移动(包括 reset 和 cherry-pick)。误操作后先跑它,找到目标提交的 SHA,再 git reset --hard <sha> 回来。

9. 常见错误 ​

错误 1:把不该提交的文件提交了 ​

症状:target/、.idea/、密钥文件进了仓库。修复:先加 .gitignore,再从版本控制移除但保留本地文件:

powershell
git rm -r --cached target
git commit -m "chore: 移除构建产物"

注意:git rm --cached 只影响版本控制,不会删除本地文件;加 -r 是为了处理目录。

错误 2:reset --hard 后想找回改动 ​

修复:git reflog 找到操作前的 SHA,再 git reset --hard <sha>。如果改动从来没被提交过(只存在于工作区),reflog 也救不回来——这是 --hard 真正危险的地方。

错误 3:冲突标记没清理干净就提交 ​

症状:代码里残留 <<<<<<<,编译直接失败。修复:git diff 或搜索 <<<<<<< 确认清理完毕;编辑器里搜 <<< 是最快的检查方式。

错误 4:push 被拒绝(rejected - non-fast-forward) ​

原因:远程有你本地没有的提交。正确做法:先 git pull --rebase 把远程提交放到你的提交之前,解决完冲突再 push。错误做法:直接 push --force 覆盖别人的工作。

10. 动手实验 ​

目标:完整走一遍"分支 → 提交 → cherry-pick → revert"的流程。

步骤:

  1. 在一个空目录里执行第 6.2 节的命令序列,逐条核对输出;
  2. 用 git log --oneline --graph --all 观察提交结构,找出 feature 分支的指针在哪里;
  3. 用 git show <SHA> 查看 cherry-pick 前后两个提交的差异,确认内容相同、SHA 不同;
  4. 执行一次 git reset --hard HEAD~1,再用 git reflog 把它找回来;
  5. 用第 7 节的序列制造一次冲突并解决。

验收方式:能解释第 3 步"内容相同但 SHA 不同"的原因,以及第 4 步是用什么命令救回来的。

扩展任务:在本知识库仓库里执行 git log --oneline --graph -10,观察这个项目自己的提交历史,并找出每个提交属于哪一类变更(feat / docs / build / chore)。

11. 练习题 ​

基础题

  1. 判断:git add 会把文件上传到 GitHub。(对 / 错)
  2. 填空:git reset --______ 会同时重置工作区,是三种模式中最危险的。
  3. 简答:为什么已经推送的提交应该用 revert 而不是 reset?

命令阅读题

powershell
git switch -c hotfix
git commit -am "fix: 修复空指针"
git switch main
git cherry-pick hotfix
git branch -D hotfix
  1. 这一串命令在做什么?git branch -D 有什么风险?

实操题

  1. 在实验目录里制造一次"两个分支修改同一文件不同位置"的场景,执行合并。观察:这次为什么不冲突?(提示:Git 的合并是按行解决的)

综合任务

  1. 模拟一个真实的紧急修复流程:
    • 在 main 上有未完成的实验代码(未提交);
    • 需要立刻修一个线上 bug,且修复要单独提交;
    • 要求:不能把实验代码混进修复提交;
    • 写出你的完整命令序列,并说明每一步为什么这么选(提示:git stash 会让这件事变简单,它是暂存区之外的第四个"仓库")。
分级提示

提示 1(方向):第 4 题里 -D 是强制删除分支(-d 会在"未合并"时拒绝删除)。

提示 2(关键点):第 6 题的关键是"隔离"——把未完成的改动先收起来,让工作区干净,再开分支做修复。

提示 3(接近答案):git stash 保存改动并清空工作区,git stash pop 恢复;修复完成后回到原分支再 pop。

12. 参考答案 ​

基础题
  1. 错。git add 只是把改动加入本地暂存区,与网络无关。上传要用 git push。
  2. --hard。
  3. 因为 reset 会改写历史:远程分支被强行指向另一个提交后,已经拉取过旧提交的同事会与远程分叉,后续推送要么被拒、要么覆盖别人的工作。revert 只是追加一个新提交来抵消旧改动,历史始终是线性的、可追溯的。
命令阅读题(第 4 题)

流程是:新建 hotfix 分支 → 提交修复 → 回到 main → 把修复的改动 cherry-pick 过来 → 删除 hotfix 分支。

git branch -D 的风险:强制删除,即使该分支有未合并到其他分支的提交也会被删掉,这些提交可能再也找不回来(除非 reflog 还在)。所以删分支前先确认:git branch --merged 里有没有它,或用 -d 让 Git 帮你把关。

本例中 hotfix 的提交已经被 cherry-pick 到 main,删除是安全的(但严格说 cherry-pick 产生的是新提交,Git 仍会认为 hotfix 未合并,所以这里只能用 -D)。

实操题(第 5 题)

两个分支修改同一文件的不同行时不会冲突,因为 Git 的合并以"行块(hunk)"为单位:只要两块修改不重叠,就能自动合并。

反之,修改同一行、或相邻行(上下文重叠)时就会冲突,需要人工判断。

综合任务(第 6 题)
powershell
# 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,把修复提交单独挑到发布分支上。

13. 掌握标准 ​

  • [ ] 能说清四个区域的关系,并用 status 判断下一步该做什么;
  • [ ] 能独立创建分支、合并、解决一次冲突;
  • [ ] 能区分 reset / revert / cherry-pick 的适用场景;
  • [ ] 误操作后知道先用 reflog 找现场。

14. 扩展资源 ​

15. 下一步学习 ​

《Java 程序的运行与调试》 会把调试技能系统化;版本控制在团队协作中的应用(分支策略、Code Review)见 《Git 分支、合并与回退》。

进度只保存在本机浏览器,可随时清除

个人学习项目:AI 生成内容均经人工复核,第三方资源仅作索引