文章目录Git 分支进阶分支策略、stash、bugfix 和 feature 开发一、Fast-forward 模式的问题二、使用 --no-ff 保留分支合并记录三、常见分支策略master、dev 与临时分支四、临时修 Bug 前先用 stash 保存现场五、使用临时分支修复 Bug六、恢复现场并继续开发七、将 Bug 修复同步到开发分支八、Feature 分支与未合并分支删除九、本文总结Git 分支进阶分支策略、stash、bugfix 和 feature 开发上一篇已经把分支的基础操作梳理了一遍包括创建分支、切换分支、合并分支、删除分支以及冲突解决。掌握这些命令之后还需要继续往前走一步实际开发中应该怎样组织分支遇到临时 Bug 怎么处理开发到一半的代码又该怎么临时保存。这一篇重点整理几个更贴近日常开发的内容Fast-forward模式的问题。使用--no-ff保留分支合并记录。常见的master/dev/临时分支管理方式。使用git stash保存工作现场。用临时分支修复 Bug。将主分支上的修复同步回开发分支。feature 分支开发中止时如何删除未合并分支。下面还是接着前面的仓库状态继续操作。一、Fast-forward 模式的问题通常情况下合并分支时只要条件允许Git 会优先采用Fast-forward模式也就是快进合并。比如master分支没有新的提交而dev分支只是在master的基础上往前走了几步那么合并时 Git 不需要额外生成一个合并提交只要把master指针直接移动到dev指向的位置即可。这种方式很快也很干净但它也有一个问题如果合并完成后删除了临时分支从历史记录里就不容易看出这次提交到底是直接在主分支上做的还是从某个分支合并进来的。换句话说Fast-forward会让提交历史变成一条直线。直线历史本身没有错但如果团队希望保留“这个功能曾经在一个独立分支上开发过”的信息快进合并就不够明显。对比一下上一篇解决冲突之后的合并历史。因为冲突合并会产生一次新的合并提交所以历史上能看出分叉与合并即使后面把dev1分支删除git log --graph里仍然能看到当时发生过一次分支合并hyb139-159-150-152:~/gitcode$ git branch*master hyb139-159-150-152:~/gitcode$ git branch-d dev1 Deleted branchdev1(was c594fd1).hyb139-159-150-152:~/gitcode$ git log--graph--prettyoneline--abbrev-commit*2976afc(HEAD-master)merge ReadMe|\|*c594fd1 modify ReadMe*|c10f6d0 modify ReadMe|/这里能看出master的最新提交来自一次合并而不是普通的线性提交。这种历史在回溯问题时更有价值。二、使用 --no-ff 保留分支合并记录如果希望即使可以快进也强制 Git 生成一次合并提交就可以使用--no-ff。--no-ff的意思是禁用Fast-forward让 Git 按普通合并方式处理。这样做的好处是合并历史里会明确保留一次 merge commit方便之后知道某个功能分支是什么时候合并进来的。先创建并切换到一个新的dev2分支hyb139-159-150-152:~/gitcode$ git checkout-b dev2 Switched to anewbranchdev2在dev2上修改ReadMe并提交hyb139-159-150-152:~/gitcode$ cat ReadMe hello bit hello git hello world hello version1 hello version2 hello version3 write bbbfornewbranch a,b,c,d hyb139-159-150-152:~/gitcode$ git add.hyb139-159-150-152:~/gitcode$ git commit-mmodify ReadMe[dev241b082f]modify ReadMe然后切回master使用--no-ff合并hyb139-159-150-152:~/gitcode$ git checkout master Switched to branchmasterhyb139-159-150-152:~/gitcode$ git merge--no-ff-mmerge with no-ffdev2 Merge made by therecursivestrategy.ReadMe|1hyb139-159-150-152:~/gitcode$ cat ReadMe hello bit hello git hello world hello version1 hello version2 hello version3 write bbbfornewbranch a,b,c,d注意这里加了-m merge with no-ff。因为--no-ff会生成一次新的合并提交所以需要写提交说明。查看提交历史hyb139-159-150-152:~/gitcode$ git log--graph--prettyoneline--abbrev-commit*5bd16b4(HEAD-master)merge with no-ff|\|*41b082f(dev2)modify ReadMe|/此时历史结构就很清楚了总结一下Fast-forward历史更线性、更简洁但删除分支后不容易看出分支合并痕迹。--no-ff会多一次合并提交但能保留分支开发和合并的轨迹。实际项目里没有绝对固定的答案。如果是个人小项目快进合并很清爽如果是多人协作、功能分支比较多的项目--no-ff往往更方便追踪历史。三、常见分支策略master、dev 与临时分支命令学会之后真正容易出问题的地方反而是分支到底应该怎么用一种常见思路是master分支保持稳定通常只放已经验证过、可以发布的代码。dev分支用于日常开发整合它比master更活跃也更容易变化。每个功能或 Bug 都使用临时分支处理完成后再合并回dev或master。这样做的核心目标是稳定代码和开发中代码分开公共分支和个人临时修改分开。比如一个团队里主分支master负责发布版本开发分支dev负责汇总大家正在开发的内容每个人开发新功能时再从dev上拉出自己的临时分支。等功能完成后临时分支再合并回dev。整体关系大概如下这里先不展开企业级分支模型后面多人协作部分会更系统地看feature、release、develop、hotfix这些分支。当前阶段先记住一句话不要把所有工作都堆在主分支上临时任务应该用临时分支承载。四、临时修 Bug 前先用 stash 保存现场实际开发中经常遇到这种场景我正在dev2分支上开发一个功能代码写到一半还没到适合提交的程度突然发现master上有一个紧急 Bug 需要马上修。这时会有一个尴尬点当前工作区有未提交修改直接切分支可能不方便强行提交半成品代码也不好因为这个提交本身并不完整。比如当前dev2分支上有未提交内容hyb139-159-150-152:~/gitcode$ git branch*dev2 master hyb139-159-150-152:~/gitcode$ cat ReadMe hello bit hello git hello world hello version1 hello version2 hello version3 write bbbfornewbranch a,b,c,d i am coding...hyb139-159-150-152:~/gitcode$ git status On branch dev2 Changesnotstagedforcommit:(usegit add file...to update what will be committed)(usegit restore file...to discard changes in working directory)modified:ReadMe no changes added tocommit(usegit addand/orgit commit -a)这时可以使用git stash暂存当前工作现场hyb139-159-150-152:~/gitcode$ git stash Saved working directoryandindex state WIP on dev2:41b082f modify ReadMe hyb139-159-150-152:~/gitcode$ git status On branch dev2 nothing to commit,working tree cleangit stash可以把当前工作区和暂存区的修改临时保存起来让工作区恢复干净。这样就可以放心切换分支去处理别的事情。注意stash适合保存“还不适合作为一次提交”的临时现场。它不是用来替代正常提交的。真正完成的功能还是应该用commit固化下来。五、使用临时分支修复 Bug现场保存好之后就可以回到master从master创建一个专门修 Bug 的分支。hyb139-159-150-152:~/gitcode$ git checkout master Switched to branchmasterhyb139-159-150-152:~/gitcode$ git checkout-b fix_bug Switched to anewbranchfix_bug在fix_bug分支上修复问题并提交hyb139-159-150-152:~/gitcode$ vim ReadMe hyb139-159-150-152:~/gitcode$ cat ReadMe hello bit hello git hello world hello version1 hello version2 hello version3 write bbbfornewbranch a,b,c,d,e hyb139-159-150-152:~/gitcode$ git add ReadMe hyb139-159-150-152:~/gitcode$ git commit-mfix bug[fix_bug4bbc0c4]fix bug修复完成后切回master把fix_bug合并进去然后删除临时分支hyb139-159-150-152:~/gitcode$ git checkout master Switched to branchmasterhyb139-159-150-152:~/gitcode$ git merge--no-ff-mmerge fix_bug branchfix_bug Merge made by therecursivestrategy.ReadMe|2-hyb139-159-150-152:~/gitcode$ cat ReadMe hello bit hello git hello world hello version1 hello version2 hello version3 write bbbfornewbranch a,b,c,d,e hyb139-159-150-152:~/gitcode$ git branch-d fix_bug Deleted branchfix_bug(was4bbc0c4).这套流程的好处是很清晰未完成的新功能仍然放在原来的开发分支。紧急 Bug 在独立分支修复。修复完成后合并回稳定分支。临时分支完成使命后删除。六、恢复现场并继续开发Bug 修复完成后还要回到dev2继续刚才没写完的功能。hyb139-159-150-152:~/gitcode$ git checkout dev2 Switched to branchdev2hyb139-159-150-152:~/gitcode$ git status On branch dev2 nothing to commit,working tree clean此时工作区是干净的因为刚才未完成的内容被stash保存起来了。可以先查看保存列表hyb139-159-150-152:~/gitcode$ git stash list stash{0}:WIP on dev2:41b082f modify ReadMe使用git stash pop恢复现场hyb139-159-150-152:~/gitcode$ git stash pop On branch dev2 Changesnotstagedforcommit:(usegit add file...to update what will be committed)(usegit restore file...to discard changes in working directory)modified:ReadMe no changes added tocommit(usegit addand/orgit commit -a)Dropped refs/stash{0}(4f873250b3503687b5efd26196776aee7e3724c2)pop的含义是恢复现场并把这条 stash 记录删除。再次查看 stash 列表已经没有可恢复内容hyb139-159-150-152:~/gitcode$ git stash list hyb139-159-150-152:~/gitcode$还有另一种恢复方式git stash apply stash{0}apply只恢复不删除 stash 记录。如果确认不需要这条记录了可以再手动删除git stash drop stash{0}简单区分一下git stash pop恢复并删除适合最常见的临时保存场景。git stash apply只恢复不删除适合想多次尝试恢复的场景。git stash drop删除指定 stash 记录。git stash list查看当前保存过的工作现场。恢复现场后继续完成开发并提交hyb139-159-150-152:~/gitcode$ cat ReadMe hello bit hello git hello world hello version1 hello version2 hello version3 write bbbfornewbranch a,b,c,d i am coding...Done!hyb139-159-150-152:~/gitcode$ git add.hyb139-159-150-152:~/gitcode$ git commit-mmodify ReadMe[dev2 ed0916d]modify ReadMe到这里dev2上的新功能也继续完成了。七、将 Bug 修复同步到开发分支现在还有一个问题刚才 Bug 是在master上修好的而dev2是之前从旧的master拉出来的所以dev2上并没有这个 Bug 修复。当前状态可以理解为如果最后直接切到master合并dev2有可能在master上遇到冲突。对于重要分支来说这不是一个好习惯。更推荐的做法是先在自己的开发分支上合并最新的master如果有冲突就在开发分支解决并测试确认没问题后再让master合并开发分支。这样可以把风险留在开发分支上而不是把冲突处理过程直接放到稳定分支上。直接在master上合并开发分支风险大概是这样更推荐的流程是先让dev2吸收master的最新修复开发分支解决冲突并测试后再合并回master对应操作如下。当前在dev2上先合并masterhyb139-159-150-152:~/gitcode$ git branch*dev2 master hyb139-159-150-152:~/gitcode$ git merge master Auto-merging ReadMeCONFLICT(content):Merge conflict in ReadMe Automatic merge failed;fix conflictsandthen commit the result.出现冲突后查看文件hyb139-159-150-152:~/gitcode$ cat ReadMe hello bit hello git hello world hello version1 hello version2 hello version3 write bbbfornewbranchHEAD a,b,c,d i am coding...Done!a,b,c,d,emaster这里的含义是HEAD部分是当前dev2分支上的内容。master部分是主分支中修复 Bug 后的内容。最终文件应该同时保留 Bug 修复和dev2上的新功能内容。手动整理后的文件如下hyb139-159-150-152:~/gitcode$ vim ReadMe hyb139-159-150-152:~/gitcode$ cat ReadMe hello bit hello git hello world hello version1 hello version2 hello version3 write bbbfornewbranch a,b,c,d,e i am coding...Done!hyb139-159-150-152:~/gitcode$ git add.hyb139-159-150-152:~/gitcode$ git commit-mmerge master[dev2447d29f]merge master hyb139-159-150-152:~/gitcode$ git status On branch dev2 nothing to commit,working tree clean此时dev2已经包含master上的 Bug 修复也包含自己的新功能。确认测试没有问题后再切回master合并dev2hyb139-159-150-152:~/gitcode$ git checkout master Switched to branchmasterhyb139-159-150-152:~/gitcode$ git merge dev2 Updating193421f..447d29f Fast-forward ReadMe|1hyb139-159-150-152:~/gitcode$ git status On branch master nothing to commit,working tree clean这时master合并dev2就很顺利了因为冲突已经在dev2上处理过。最后删除dev2hyb139-159-150-152:~/gitcode$ git branch-d dev2 Deleted branchdev2(was447d29f).这个流程在多人协作中很重要不要把冲突解决和验证工作直接压到稳定分支上先在自己的分支上同步主分支并解决问题会稳很多。八、Feature 分支与未合并分支删除新功能开发时通常会创建一个独立的 feature 分支。比如要开发一个新需求就从当前开发分支拉出一个临时分支在里面开发、提交、测试完成后再合并。这类分支的价值是把一个功能相关的改动集中在一起不干扰其他分支。不过实际开发里也会遇到功能中止的情况。比如某个新功能做到一半需求突然取消了这个 feature 分支就不需要继续保留。如果这个分支上的提交还没有合并到主线普通删除会失败。先创建一个dev3分支模拟新功能开发hyb139-159-150-152:~/gitcode$ git checkout-b dev3 Switched to anewbranchdev3在dev3上开发并提交hyb139-159-150-152:~/gitcode$ vim ReadMe hyb139-159-150-152:~/gitcode$ cat ReadMe hello bit hello git hello world hello version1 hello version2 hello version3 write bbbfornewbranch a,b,c,d,e i am coding...Done!i am writingnewfeatures...hyb139-159-150-152:~/gitcode$ git add.hyb139-159-150-152:~/gitcode$ git commit-mmodify ReadMe for new features[dev3 cd2f149]modify ReadMefornewfeatures现在假设这个功能停止开发切回master准备删除dev3hyb139-159-150-152:~/gitcode$ git checkout master Switched to branchmaster使用普通删除hyb139-159-150-152:~/gitcode$ git branch-d dev3 error:The branchdev3isnotfully merged.If you are sure you want todeleteit,rungit branch -D dev3.Git 阻止删除是因为dev3上有提交还没有合并。如果直接删除这些提交就会变得很难再找回来。这个保护非常有用可以避免误删有价值的分支。但如果确认这个功能确实不要了可以强制删除hyb139-159-150-152:~/gitcode$ git branch-D dev3 Deleted branchdev3(was cd2f149).hyb139-159-150-152:~/gitcode$ git branch*master这里要区分两个命令git branch -d 分支名安全删除。分支已经合并时才允许删除。git branch -D 分支名强制删除。即使分支没有合并也会删除。所以-D不要随手用。它适合“明确废弃”的实验分支或需求取消分支不适合不加确认地清理分支。九、本文总结Fast-forward合并会让历史保持线性但删除分支后不容易看出分支合并痕迹。git merge --no-ff -m 说明 分支名可以强制生成合并提交保留分支合并历史。常见分支策略里master保持稳定dev承载日常开发临时分支负责具体功能或 Bug。开发到一半需要切走时可以用git stash保存现场让工作区恢复干净。git stash pop会恢复并删除 stash 记录git stash apply只恢复不删除。修复紧急 Bug 时可以从稳定分支创建fix_bug临时分支修复后合并回去并删除。主分支有新修复后建议先在自己的开发分支合并主分支解决冲突并测试再合并回主分支。feature 分支适合承载独立功能开发功能完成后合并功能取消后可以按情况删除。git branch -d是安全删除git branch -D是强制删除后者要谨慎使用。到这里分支已经不只是几个命令了而是逐渐变成一种开发组织方式。后面继续看远程仓库时分支会和clone、push、pull、远程分支一起出现使用场景会更接近真实团队协作。