第 6 篇:Git 分支进阶:分支策略、stash、bugfix 和 feature 开发

文章目录

  • [Git 分支进阶:分支策略、stash、bugfix 和 feature 开发](#Git 分支进阶:分支策略、stash、bugfix 和 feature 开发)
    • [一、Fast-forward 模式的问题](#一、Fast-forward 模式的问题)
    • [二、使用 --no-ff 保留分支合并记录](#二、使用 --no-ff 保留分支合并记录)
    • [三、常见分支策略:master、dev 与临时分支](#三、常见分支策略:master、dev 与临时分支)
    • [四、临时修 Bug 前,先用 stash 保存现场](#四、临时修 Bug 前,先用 stash 保存现场)
    • [五、使用临时分支修复 Bug](#五、使用临时分支修复 Bug)
    • 六、恢复现场并继续开发
    • [七、将 Bug 修复同步到开发分支](#七、将 Bug 修复同步到开发分支)
    • [八、Feature 分支与未合并分支删除](#八、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 里仍然能看到当时发生过一次分支合并:

cpp 复制代码
hyb@139-159-150-152:~/gitcode$ git branch
* master

hyb@139-159-150-152:~/gitcode$ git branch -d dev1
Deleted branch dev1 (was c594fd1).

hyb@139-159-150-152:~/gitcode$ git log --graph --pretty=oneline --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 分支:

cpp 复制代码
hyb@139-159-150-152:~/gitcode$ git checkout -b dev2
Switched to a new branch 'dev2'

dev2 上修改 ReadMe 并提交:

cpp 复制代码
hyb@139-159-150-152:~/gitcode$ cat ReadMe
hello bit
hello git
hello world
hello version1
hello version2
hello version3
write bbb for new branch
a,b,c,d

hyb@139-159-150-152:~/gitcode$ git add .
hyb@139-159-150-152:~/gitcode$ git commit -m "modify ReadMe"
[dev2 41b082f] modify ReadMe

然后切回 master,使用 --no-ff 合并:

cpp 复制代码
hyb@139-159-150-152:~/gitcode$ git checkout master
Switched to branch 'master'

hyb@139-159-150-152:~/gitcode$ git merge --no-ff -m "merge with no-ff" dev2
Merge made by the 'recursive' strategy.
ReadMe | 1 +

hyb@139-159-150-152:~/gitcode$ cat ReadMe
hello bit
hello git
hello world
hello version1
hello version2
hello version3
write bbb for new branch
a,b,c,d

注意这里加了 -m "merge with no-ff"。因为 --no-ff 会生成一次新的合并提交,所以需要写提交说明。

查看提交历史:

cpp 复制代码
hyb@139-159-150-152:~/gitcode$ git log --graph --pretty=oneline --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 都使用临时分支处理 ,完成后再合并回 devmaster

这样做的核心目标是:稳定代码和开发中代码分开,公共分支和个人临时修改分开

比如一个团队里,主分支 master 负责发布版本;开发分支 dev 负责汇总大家正在开发的内容;每个人开发新功能时,再从 dev 上拉出自己的临时分支。等功能完成后,临时分支再合并回 dev

整体关系大概如下:

这里先不展开企业级分支模型,后面多人协作部分会更系统地看 featurereleasedevelophotfix 这些分支。当前阶段先记住一句话:不要把所有工作都堆在主分支上,临时任务应该用临时分支承载

四、临时修 Bug 前,先用 stash 保存现场

实际开发中经常遇到这种场景:我正在 dev2 分支上开发一个功能,代码写到一半,还没到适合提交的程度,突然发现 master 上有一个紧急 Bug 需要马上修。

这时会有一个尴尬点:当前工作区有未提交修改,直接切分支可能不方便;强行提交半成品代码也不好,因为这个提交本身并不完整。

比如当前 dev2 分支上有未提交内容:

cpp 复制代码
hyb@139-159-150-152:~/gitcode$ git branch
* dev2
    master

hyb@139-159-150-152:~/gitcode$ cat ReadMe
hello bit
hello git
hello world
hello version1
hello version2
hello version3
write bbb for new branch
a,b,c,d
i am coding ...

hyb@139-159-150-152:~/gitcode$ git status
On branch dev2
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: ReadMe

no changes added to commit (use "git add" and/or "git commit -a")

这时可以使用 git stash 暂存当前工作现场:

cpp 复制代码
hyb@139-159-150-152:~/gitcode$ git stash
Saved working directory and index state WIP on dev2: 41b082f modify ReadMe

hyb@139-159-150-152:~/gitcode$ git status
On branch dev2
nothing to commit, working tree clean

git stash 可以把当前工作区和暂存区的修改临时保存起来,让工作区恢复干净。这样就可以放心切换分支去处理别的事情。

注意,stash 适合保存"还不适合作为一次提交"的临时现场。它不是用来替代正常提交的。真正完成的功能还是应该用 commit 固化下来。

五、使用临时分支修复 Bug

现场保存好之后,就可以回到 master,从 master 创建一个专门修 Bug 的分支。

cpp 复制代码
hyb@139-159-150-152:~/gitcode$ git checkout master
Switched to branch 'master'

hyb@139-159-150-152:~/gitcode$ git checkout -b fix_bug
Switched to a new branch 'fix_bug'

fix_bug 分支上修复问题并提交:

cpp 复制代码
hyb@139-159-150-152:~/gitcode$ vim ReadMe

hyb@139-159-150-152:~/gitcode$ cat ReadMe
hello bit
hello git
hello world
hello version1
hello version2
hello version3
write bbb for new branch
a,b,c,d,e

hyb@139-159-150-152:~/gitcode$ git add ReadMe
hyb@139-159-150-152:~/gitcode$ git commit -m "fix bug"
[fix_bug 4bbc0c4] fix bug

修复完成后,切回 master,把 fix_bug 合并进去,然后删除临时分支:

cpp 复制代码
hyb@139-159-150-152:~/gitcode$ git checkout master
Switched to branch 'master'

hyb@139-159-150-152:~/gitcode$ git merge --no-ff -m "merge fix_bug branch" fix_bug
Merge made by the 'recursive' strategy.
ReadMe | 2 +-

hyb@139-159-150-152:~/gitcode$ cat ReadMe
hello bit
hello git
hello world
hello version1
hello version2
hello version3
write bbb for new branch
a,b,c,d,e

hyb@139-159-150-152:~/gitcode$ git branch -d fix_bug
Deleted branch fix_bug (was 4bbc0c4).

这套流程的好处是很清晰:

  • 未完成的新功能仍然放在原来的开发分支。
  • 紧急 Bug 在独立分支修复。
  • 修复完成后合并回稳定分支。
  • 临时分支完成使命后删除。

六、恢复现场并继续开发

Bug 修复完成后,还要回到 dev2 继续刚才没写完的功能。

cpp 复制代码
hyb@139-159-150-152:~/gitcode$ git checkout dev2
Switched to branch 'dev2'

hyb@139-159-150-152:~/gitcode$ git status
On branch dev2
nothing to commit, working tree clean

此时工作区是干净的,因为刚才未完成的内容被 stash 保存起来了。可以先查看保存列表:

cpp 复制代码
hyb@139-159-150-152:~/gitcode$ git stash list
stash@{0}: WIP on dev2: 41b082f modify ReadMe

使用 git stash pop 恢复现场:

cpp 复制代码
hyb@139-159-150-152:~/gitcode$ git stash pop
On branch dev2
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: ReadMe

no changes added to commit (use "git add" and/or "git commit -a")
Dropped refs/stash@{0} (4f873250b3503687b5efd26196776aee7e3724c2)

pop 的含义是:恢复现场,并把这条 stash 记录删除

再次查看 stash 列表,已经没有可恢复内容:

cpp 复制代码
hyb@139-159-150-152:~/gitcode$ git stash list
hyb@139-159-150-152:~/gitcode$

还有另一种恢复方式:

cpp 复制代码
git stash apply stash@{0}

apply 只恢复,不删除 stash 记录。如果确认不需要这条记录了,可以再手动删除:

cpp 复制代码
git stash drop stash@{0}

简单区分一下:

  • git stash pop:恢复并删除,适合最常见的临时保存场景。
  • git stash apply:只恢复不删除,适合想多次尝试恢复的场景。
  • git stash drop:删除指定 stash 记录。
  • git stash list:查看当前保存过的工作现场。

恢复现场后,继续完成开发并提交:

cpp 复制代码
hyb@139-159-150-152:~/gitcode$ cat ReadMe
hello bit
hello git
hello world
hello version1
hello version2
hello version3
write bbb for new branch
a,b,c,d
i am coding ... Done!

hyb@139-159-150-152:~/gitcode$ git add .
hyb@139-159-150-152:~/gitcode$ git commit -m "modify ReadMe"
[dev2 ed0916d] modify ReadMe

到这里,dev2 上的新功能也继续完成了。

七、将 Bug 修复同步到开发分支

现在还有一个问题:刚才 Bug 是在 master 上修好的,而 dev2 是之前从旧的 master 拉出来的,所以 dev2 上并没有这个 Bug 修复。

当前状态可以理解为:

如果最后直接切到 master 合并 dev2,有可能在 master 上遇到冲突。对于重要分支来说,这不是一个好习惯。更推荐的做法是:先在自己的开发分支上合并最新的 master,如果有冲突就在开发分支解决并测试,确认没问题后,再让 master 合并开发分支

这样可以把风险留在开发分支上,而不是把冲突处理过程直接放到稳定分支上。

直接在 master 上合并开发分支,风险大概是这样:

更推荐的流程是先让 dev2 吸收 master 的最新修复:

开发分支解决冲突并测试后,再合并回 master

对应操作如下。当前在 dev2 上,先合并 master

cpp 复制代码
hyb@139-159-150-152:~/gitcode$ git branch
* dev2
    master

hyb@139-159-150-152:~/gitcode$ git merge master
Auto-merging ReadMe
CONFLICT (content): Merge conflict in ReadMe
Automatic merge failed; fix conflicts and then commit the result.

出现冲突后查看文件:

cpp 复制代码
hyb@139-159-150-152:~/gitcode$ cat ReadMe
hello bit
hello git
hello world
hello version1
hello version2
hello version3
write bbb for new branch
<<<<<<< HEAD
a,b,c,d
i am coding ... Done!
=======
a,b,c,d,e
>>>>>>> master

这里的含义是:

  • HEAD 部分是当前 dev2 分支上的内容。
  • master 部分是主分支中修复 Bug 后的内容。
  • 最终文件应该同时保留 Bug 修复和 dev2 上的新功能内容。

手动整理后的文件如下:

cpp 复制代码
hyb@139-159-150-152:~/gitcode$ vim ReadMe

hyb@139-159-150-152:~/gitcode$ cat ReadMe
hello bit
hello git
hello world
hello version1
hello version2
hello version3
write bbb for new branch
a,b,c,d,e
i am coding ... Done!

hyb@139-159-150-152:~/gitcode$ git add .
hyb@139-159-150-152:~/gitcode$ git commit -m "merge master"
[dev2 447d29f] merge master

hyb@139-159-150-152:~/gitcode$ git status
On branch dev2
nothing to commit, working tree clean

此时 dev2 已经包含 master 上的 Bug 修复,也包含自己的新功能。确认测试没有问题后,再切回 master 合并 dev2

cpp 复制代码
hyb@139-159-150-152:~/gitcode$ git checkout master
Switched to branch 'master'

hyb@139-159-150-152:~/gitcode$ git merge dev2
Updating 193421f..447d29f
Fast-forward
ReadMe | 1 +

hyb@139-159-150-152:~/gitcode$ git status
On branch master
nothing to commit, working tree clean

这时 master 合并 dev2 就很顺利了,因为冲突已经在 dev2 上处理过。

最后删除 dev2

cpp 复制代码
hyb@139-159-150-152:~/gitcode$ git branch -d dev2
Deleted branch dev2 (was 447d29f).

这个流程在多人协作中很重要:不要把冲突解决和验证工作直接压到稳定分支上,先在自己的分支上同步主分支并解决问题,会稳很多

八、Feature 分支与未合并分支删除

新功能开发时,通常会创建一个独立的 feature 分支。比如要开发一个新需求,就从当前开发分支拉出一个临时分支,在里面开发、提交、测试,完成后再合并。

这类分支的价值是:把一个功能相关的改动集中在一起,不干扰其他分支

不过实际开发里也会遇到功能中止的情况。比如某个新功能做到一半,需求突然取消了,这个 feature 分支就不需要继续保留。如果这个分支上的提交还没有合并到主线,普通删除会失败。

先创建一个 dev3 分支模拟新功能开发:

cpp 复制代码
hyb@139-159-150-152:~/gitcode$ git checkout -b dev3
Switched to a new branch 'dev3'

dev3 上开发并提交:

cpp 复制代码
hyb@139-159-150-152:~/gitcode$ vim ReadMe

hyb@139-159-150-152:~/gitcode$ cat ReadMe
hello bit
hello git
hello world
hello version1
hello version2
hello version3
write bbb for new branch
a,b,c,d,e
i am coding ... Done!
i am writing new features ...

hyb@139-159-150-152:~/gitcode$ git add .
hyb@139-159-150-152:~/gitcode$ git commit -m "modify ReadMe for new features"
[dev3 cd2f149] modify ReadMe for new features

现在假设这个功能停止开发,切回 master 准备删除 dev3

cpp 复制代码
hyb@139-159-150-152:~/gitcode$ git checkout master
Switched to branch 'master'

使用普通删除:

cpp 复制代码
hyb@139-159-150-152:~/gitcode$ git branch -d dev3
error: The branch 'dev3' is not fully merged.
If you are sure you want to delete it, run 'git branch -D dev3'.

Git 阻止删除,是因为 dev3 上有提交还没有合并。如果直接删除,这些提交就会变得很难再找回来。这个保护非常有用,可以避免误删有价值的分支。

但如果确认这个功能确实不要了,可以强制删除:

cpp 复制代码
hyb@139-159-150-152:~/gitcode$ git branch -D dev3
Deleted branch dev3 (was cd2f149).

hyb@139-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 是强制删除,后者要谨慎使用。

到这里,分支已经不只是几个命令了,而是逐渐变成一种开发组织方式。后面继续看远程仓库时,分支会和 clonepushpull、远程分支一起出现,使用场景会更接近真实团队协作。

相关推荐
wok1571 小时前
Git Bash 执行中文命令报错 127:MSYS 参数编码 bug 排查与修复
git·bug·bash
霸道流氓气质16 小时前
CodeBuddy中使用 MCP 工具将 XMind 思维导图转换为 Markdown —— 实操流程汇总
git·myeclipse·xmind
OsDepK21 小时前
mimo code安装教程
git·电脑·ai编程
NexTunnel1 天前
公司老项目还在 SVN,远程维护怎么做更稳?
git·svn·gitlab·远程工作·nextunnel
满天星83035772 天前
【Git】原理及使用(七) (多人协作 [下])
git
郡杰2 天前
Git基础与开发平台搭建
git·后端
钟智强2 天前
我警告了 329 天
git·php·wordpress·webshell
乐观的冷风2 天前
我们需要将文件添加到暂存区才能提交,而移除文件后是无法添加到暂存区的,那么怎么移除一个文件让GIT不再将其纳入版本控制呢?上图中GIT已经给出了说明:
git
tjl521314_212 天前
Git SSH Host Key Verification Failed 问题排查与解决
运维·git·ssh