十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

24 — 高级实验:把十五到二十三章练成真能力

24 — 高级实验:把十五到二十三章练成真能力 写在前面这一章要解决什么摘要本文是一篇 Git 高级操作动手实验教程围绕reflog、rebase、cherry-pick、revert、stash、bisect与冲突解决七组实验展开。读者在可丢弃目录中按「场景 → 步骤 → 预期结果 → 通过标准 → 容易卡的地方」完整走一遍把高级操作练成肌肉记忆。同时强调安全边界已推送用revert、未推送用reset、共享分支不改历史。高级操作看书看懂了上手还是懵。这一章把第 15–23 章的知识点拧成七组动手实验在可丢弃目录里走完整一遍。做完你才敢说「高级操作我用过了」。学完后你应该能不看资料用reflog找回被reset --hard吃掉的提交用交互式rebase合并、重排提交用cherry-pick从别的分支挑选提交用revert安全地撤回已推送的提交用stash救急切分支修 bug 再切回来用bisect二分查找第一个坏提交亲手解决一次冲突并理解标记含义读者设定大一同学读完 15–23 章知道这些命令是什么但还没真练过手。1. 定位为什么要单开一章高级实验1.1 一句话先记住高级操作没练过手等于没学会。这一章不强加新知识只把分散的高级操作串成可复现的练习逼你把动作练成肌肉记忆。1.2 只读不练会怎样只读不练真做了实验以为懂 rebase一用就冲突rebase 冲突解决过一次就不怕知道 reflog 能救命但没真救过找回过一次丢掉的提交心里有底cherry-pick 听过没用过挑过一次提交手法清楚了revert 和 reset 分不清实际用过知道一个加提交一个改指针1.3 实验规则规矩原因用可丢弃目录不怕点错虚构身份练手不泄露隐私每步后git status或git log知道自己在哪卡住时回查对应章节别瞎试不在生产仓库练这些命令威力大别碰真作业每个实验做完自检通过标准不是敲完就行要确认结果对2. 实验前准备找一个可以随便删的目录比如~/lab-advanced/mkdir-p~/lab-advancedcd~/lab-advancedgit--versiongit version 2.43.0本系列样例验证环境。你的版本接近即可。设个虚构身份省得真信息混进练习历史gitconfig--globaluser.nameAda Examplegitconfig--globaluser.emailadaexample.com你也可以只在每个实验仓库里设 local 配置避免改全局。准备就绪下面开始七组实验。3. 实验组 Areflog 恢复实验目标故意用reset --hard丢掉提交再用reflog找回来。场景你手贱敲了git reset --hard HEAD~2两个提交「消失」了。别慌reflog记着呢。步骤建仓并做三次提交cd~/lab-advancedmkdirlab-acdlab-agitinit-bmainprintf第一行\nfile.txtgitaddfile.txtgitcommit-mfeat: 第一版printf第二行\nfile.txtgitaddfile.txtgitcommit-mfeat: 加第二行printf第三行\nfile.txtgitaddfile.txtgitcommit-mfeat: 加第三行看一下现在的历史记住最新的那个哈希gitlog--oneline模拟手贱硬重置回两个提交前gitreset--hardHEAD~2gitlog--oneline只剩一条了。file.txt也只剩第一行。用 reflog 看你走过的路gitreflog你会看到每一项都有哈希和操作说明。找到刚才「加第三行」那条记录记下它的哈希比如a1b2c3d。恢复到那个提交gitreset--harda1b2c3dgitlog--onelinecatfile.txt三个提交全都回来了file.txt也恢复了三行。预期结果reflog里能看到reset: moving to HEAD~2这条记录恢复后git log --oneline是三条file.txt内容是三行通过标准能指出reflog里哪一行是reset前的提交不看资料能独立用reflog找回丢失的提交理解reflog是「操作日志」不是提交历史容易卡的地方卡点回查不知道找哪个哈希reflog输出里找feat: 加第三行那行恢复后还是旧的检查是不是用了--hard不是--softreflog 里没有记录确认你确实做过reset操作4. 实验组 Brebase 整理实验目标用交互式rebase把多个碎提交合并成一个干净的提交。场景你在功能分支上做了五个碎提交「加文件」「改错字」「又改」「再改」「终于对了」。合并前想把它们捏成一个体面的提交。步骤建仓并做五个碎提交cd~/lab-advancedmkdirlab-bcdlab-bgitinit-bmainprintfbase\napp.pygitaddapp.pygitcommit-minit: 初始化gitswitch-cfeature/nice连续五个碎提交printfdef hello():\n pass\napp.pygitaddapp.pygitcommit-mwip: 加函数框架printfdef hello():\n print(hi)\napp.pygitaddapp.pygitcommit-mwip: 加了打印printfdef hello():\n print(hello)\napp.pygitaddapp.pygitcommit-mwip: 改成 helloprintfdef hello():\n print(hello)\n\nhello()\napp.pygitaddapp.pygitcommit-mwip: 加调用printfdef hello():\n print(hello world)\n\nhello()\napp.pygitaddapp.pygitcommit-mwip: 最终版看一下现在的历史gitlog--oneline五个wip开头的碎提交。开始交互式 rebase把最近五个提交合并gitrebase-iHEAD~5编辑器会弹出来长这样pick abc1234 wip: 加函数框架 pick def5678 wip: 加了打印 pick ghi9012 wip: 改成 hello pick jkl3456 wip: 加调用 pick mno7890 wip: 最终版把后面四个pick改成squash或简写s意思是压进前一个提交pick abc1234 wip: 加函数框架 squash def5678 wip: 加了打印 squash ghi9012 wip: 改成 hello squash jkl3456 wip: 加调用 squash mno7890 wip: 最终版保存关闭编辑器。接着弹出第二个编辑器让你写提交说明改成feat: 加 hello 函数并调用保存关闭。看结果gitlog--oneline五个碎提交变成了一个干净的提交。预期结果git log --oneline只有一条feat: 加 hello 函数并调用代码内容跟rebase前一模一样历史变干净了通过标准独立完成交互式rebase合并提交理解pick和squash的区别知道rebase会改写历史哈希会变容易卡的地方卡点回查编辑器不会操作vi里按i进入编辑改完按Esc输入:wq保存退出rebase 中途改错了git rebase --abort放弃重来不知道选几个从第一个pick往后数都要合进去就都改成squash冲突了怎么办先解决冲突再git add再git rebase --continue5. 实验组 Ccherry-pick 选菜实验目标从另一个分支挑选一个特定的提交移植到当前分支。场景队友在feature/ui分支上修了一个 bug你也需要这个修复合到你的main上但不需要他分支上的其他东西。步骤建仓主线上做两步cd~/lab-advancedmkdirlab-ccdlab-cgitinit-bmainprintfv1\ncore.pygitaddcore.pygitcommit-mfeat: 核心功能 v1printfv2\ncore.pygitaddcore.pygitcommit-mfeat: 核心功能 v2开一个功能分支做三个提交其中一个是我们想要的gitswitch-cfeature/uiprintf样式1\nstyle.cssgitaddstyle.cssgitcommit-mfeat: 加样式文件printf紧急修复\nfix.txtgitaddfix.txtgitcommit-mfix: 关键补丁printf样式2\nstyle.cssgitaddstyle.cssgitcommit-mfeat: 更多样式找到「关键补丁」的哈希gitlog--oneline记下fix: 关键补丁那行的哈希比如x1y2z3a。切回主线只挑这个提交gitswitch maingitcherry-pick x1y2z3a看结果gitlog--onelinecatfix.txt主线历史里出现了fix: 关键补丁但style.css不在。你只挑了你想要的。预期结果主线历史里多了一条fix: 关键补丁fix.txt出现了但style.css没有cherry-pick 的提交哈希跟原始的不同因为父提交变了通过标准独立用cherry-pick挑选指定提交理解 cherry-pick 是「复制内容生成新提交」而不是「移动提交」知道挑过来的提交哈希会变容易卡的地方卡点回查挑过来有冲突跟合并冲突一样解决改文件、add、继续找不到哈希回原分支git log --oneline查挑错了一个git cherry-pick --abort放弃或git reset --hard HEAD~1退回6. 实验组 Drevert 公开撤回实验目标用revert安全地撤销一个已经推送的提交不加掩饰地留痕。场景你昨天推了一个有问题的提交到main同事已经拉了。你不能改历史只能加一个「反提交」来撤回。步骤建仓并做三次提交cd~/lab-advancedmkdirlab-dcdlab-dgitinit-bmainprintf功能A\na.txtgitadda.txtgitcommit-mfeat: 功能Aprintf功能B有bug\nb.txtgitaddb.txtgitcommit-mfeat: 功能B有bugprintf功能C\nc.txtgitaddc.txtgitcommit-mfeat: 功能C看一下当前历史gitlog--oneline发现「功能B」有 bug用 revert 撤回gitrevert HEAD~1编辑器弹出提交说明默认是Revert feat: 功能B有bug直接保存退出即可。看结果gitlog--onelinelscatb.txt2/dev/null||echob.txt 内容被撤回了历史里多了一条Revert提交b.txt的内容被还原了。原来的feat: 功能B提交还在历史里没被删掉。对比reset和revert的区别操作改不改历史适合场景reset改历史提交没了只在本地、没推送过revert不改历史加反提交已推送、别人拉过了预期结果git log --oneline里能看到原始提交和 revert 提交被撤回的提交内容被还原b.txt不再包含原来的内容通过标准独立用revert撤回一个提交能说清revert和reset --hard的区别理解为什么已推送的提交要用revert而不是reset容易卡的地方卡点回查revert 有冲突说明要撤回的提交跟后面的提交改了同一块先解决冲突再addgit revert --continue不知道该用 revert 还是 reset已推送的用 revert还没推的可以用 reset撤回的是哪个提交HEAD~1表示当前提交的上一条也可以用哈希7. 实验组 Estash 救急实验目标代码写到一半突然要切分支修 bug。先把改动藏起来修完再取回来。场景你在feature/new-ui分支写功能改到一半产品经理喊你修线上 bug。代码还不能提交但切分支又被挡住。步骤建仓在功能分支上写一半cd~/lab-advancedmkdirlab-ecdlab-egitinit-bmainprintf主程序\nmain.pygitaddmain.pygitcommit-mfeat: 主程序gitswitch-cfeature/new-uiprintf新界面还没写完\nui.pygitaddui.pyprintf更多内容\nmain.py此时你既没提交又有未暂存的改动gitstatus来活了需要切到main修 bug。先把改动藏起来gitstash push-m新界面写了一半gitstatus工作区干干净净。切到main修 buggitswitch mainprintf主程序修了bug\nmain.pygitaddmain.pygitcommit-mfix: 紧急修bug修完了切回功能分支把改动取回来gitswitch feature/new-uigitstash listgitstash popgitstatus刚才写到一半的改动全回来了。继续写完并提交gitaddui.py main.pygitcommit-mfeat: 完成新界面预期结果stash后工作区干净stash pop后改动回来了修 bug 的提交在main上功能开发的提交在分支上通过标准独立用stash保存和恢复半成品理解stash push和stash pop是一对知道stash list能看多个贮藏容易卡的地方卡点回查pop 有冲突跟之前一样解决冲突stash 里好多条git stash list看编号git stash pop stash{2}取指定条忘了 pop 就继续写先 pop 再继续别在 stash 还没取时又攒新改动8. 实验组 Fbisect 查案实验目标用git bisect二分查找定位第一个引入 bug 的提交。场景你知道项目上周还没 bug今天有了。中间有十几个提交不知道是哪个搞坏的。一个个试太慢二分查找最快。步骤建仓做一系列提交其中一个是「坏提交」cd~/lab-advancedmkdirlab-fcdlab-fgitinit-bmain# 提交 1好的printfok\ncheck.pygitaddcheck.pygitcommit-m第1次提交正常# 提交 2好的printfok\nok\ncheck.pygitaddcheck.pygitcommit-m第2次提交正常# 提交 3坏的偷偷改了状态printfok\nok\nbad\ncheck.pygitaddcheck.pygitcommit-m第3次提交引入bug# 提交 4坏的状态延续printfok\nok\nbad\nmore\ncheck.pygitaddcheck.pygitcommit-m第4次提交还在坏# 提交 5坏的状态延续printfok\nok\nbad\nmore\nstuff\ncheck.pygitaddcheck.pygitcommit-m第5次提交还是坏写一个判断脚本如果check.py里有bad就是坏提交否则是好提交printf#!/bin/bash\nif grep -q bad check.py; then\n exit 1\nelse\n exit 0\nfi\ntest.shchmodx test.sh开始二分查找gitbisect startgitbisect bad HEADgitbisect good HEAD~4让 bisect 自动跑gitbisect run ./test.sh它会自动切提交、跑脚本、二分缩小范围直到找到第3次提交引入bug is the first bad commit结束 bisect回到原来的状态gitbisect reset预期结果bisect 自动定位到第3次提交引入bug不用逐个手动测试结束后回到正常工作状态通过标准独立用bisect找到第一个坏提交理解二分查找的思路好/坏标记让范围减半知道用完要git bisect reset容易卡的地方卡点回查不知道哪个是好哪个是坏你确定的好提交用git bisect good坏的用git bisect bad脚本报错确认脚本有执行权限且在仓库根目录运行中途想放弃git bisect reset回到开始前的状态手动标记太慢用git bisect run自动化9. 实验组 G冲突解决实验目标亲手制造一次冲突读明白冲突标记手动解决它。场景两个人同一段代码改了不同的东西合并时必冲突。这不是灾难这是 Git 在保护你。步骤建仓做一个基础版本cd~/lab-advancedmkdirlab-gcdlab-ggitinit-bmainprintf第一行\n第二行\n第三行\ndata.txtgitadddata.txtgitcommit-minit: 基础版本开分支 A改第二行gitswitch-cbranch-aprintf第一行\nA改的行\n第三行\ndata.txtgitadddata.txtgitcommit-mfeat: 分支A改第二行切回主线也改第二行不同内容gitswitch mainprintf第一行\nB改的行\n第三行\ndata.txtgitadddata.txtgitcommit-mfeat: 主线也改第二行合并必然冲突gitmerge branch-a-m合并 branch-a看 Git 标记的冲突catdata.txt你会看到第一行 HEAD B改的行 A改的行 branch-a 第三行三段标记的含义标记内容 HEAD到你当前分支的版本到 branch-a要合并进来的版本其他行双方没分歧的行手动解决两边都留着printf第一行\nB改的行\nA改的行\n第三行\ndata.txt确认没有残留标记grepdata.txt||echo没有残留标记grepdata.txt||echo没有残留标记提交解决结果gitadddata.txtgitcommit-m合并 branch-a已解决冲突看最终历史gitlog--oneline--graph--all预期结果合并时报冲突打开文件能看到、、三段标记手动改完后提交成功图上有|\合并形状通过标准能解释三段标记各是谁的版本提交前搜过确保没残留独立解决冲突并提交不害怕冲突提示容易卡的地方卡点回查改完忘了 add解决冲突后必须git add再 commit标记没删干净提交前务必grep检查想放弃合并git merge --abort回到合并前不知道该留哪边看场景决定也可以两边都留10. 自评表做完七组实验后对照自评能力通过标准reflog不看资料能找回reset --hard丢掉的提交rebase用交互式 rebase 合并过碎提交cherry-pick从别的分支挑选过指定提交revert安全撤回过已推送的提交说清与 reset 区别stash写到一半切分支修过 bug改完取回来bisect用二分法定位过第一个坏提交冲突解决独立解决过一次冲突没残留标记安全意识没在真仓库练破坏性命令11. 卡住时怎么办症状去哪查提交丢了第 16 章 reflogrebase 中途想放弃git rebase --abortcherry-pick 有冲突跟合并冲突一样解决revert 有冲突同上stash pop 冲突同上bisect 找不准确认好/坏标记对了没冲突标记没删grep . -r全局搜12. 学完之后做完这七组实验你已经把高级操作练过手了。接下来回到你的真项目遇到问题不慌了需要深挖原理回顾 09–14 章遇到没见过的场景先git status看清楚状态记住可丢弃目录是你最好的老师。任何操作不确定先在玩具仓库试一遍。13. 常见问题问 1实验能跳过吗能跳但高级操作不练手永远停留在「知道但不会」的阶段。建议至少做 A、B、G 三组。问 2实验做坏了怎么办删掉重来。本来就是可丢弃目录。问 3rebase 和 merge 到底用哪个个人分支整理用 rebase多人协作的公共分支用 merge。原则别改别人已经拉走的历史。问 4revert 以后还能再 revert 回来吗能。revert 那个 revert 提交就行或者手动把代码改回去再提交。问 5bisect 没有测试脚本怎么办手动标记也行git bisect start然后每到一步你手动看代码好就git bisect good坏就git bisect bad。问 6stash 里存了多个怎么取指定的git stash list看编号git stash apply stash{1}取指定的但不删git stash pop stash{1}取并删。问 7cherry-pick 跟 merge 有什么区别cherry-pick 只拿一个提交merge 把整个分支合进来。一个是点菜一个是自助餐。问 8实验里哈希和书里不一样正常吗正常。哈希每次都不同看结构和说明。14. 总结、学习路线与思维升华14.1 这一章请记住的点记住什么实验高级操作不动手等于没学七组实验reflog / rebase / cherry-pick / revert / stash / bisect / 冲突解决自评对照表打勾心态不怕改坏可丢弃目录随便折腾安全已推送用 revert未推的用 reset共享分支不改历史14.2 在整个系列中的位置01–07 基础 08 新手实验 ← 第一轮动手 09–14 原理 15–23 进阶操作 24 高级实验 ← 当前进阶动手14.3 思维升华把高级操作练成肌肉记忆遇到真问题才不慌。丢提交想 reflog整理历史想 rebase挑提交想 cherry-pick公开撤回想 revert写一半想 stash找 bug 想 bisect冲突不慌手动删标记。每个操作你都做过一次第二次就不会手生。14.4 参考资料以Git 2.43.0验证。演示身份Ada Example adaexample.com。Pro Git 中文版 — 重写历史Pro Git 中文版 — 使用 Git 调试Pro Git 中文版 — 贮藏与清理Git 文档 — git-rebaseGit 文档 — git-cherry-pickGit 文档 — git-bisect图示署名assets/diagrams/ATTRIBUTION.md14.5 本章检查清单实验组 A用 reflog 找回过丢掉的提交实验组 B交互式 rebase 合并过碎提交实验组 Ccherry-pick 挑过指定提交实验组 Drevert 撤回过提交说清与 reset 区别实验组 Estash 保存并取回过半成品实验组 Fbisect 定位过第一个坏提交实验组 G独立解决过一次冲突无残留标记自评表至少六项打勾
返回列表