Git Flow场景下使用git merge --squash是否会引发合并冲突?
Hey,我来帮你拆解这个Git Flow合并冲突的问题,结合你的测试场景一步步说清楚:
问题分析与解答
1. 你的提交同步方式是否存在错误?
答案是肯定的,你的同步流程踩了Git Flow的一个常见坑。在Git Flow中,当release分支合并完feature之后,正确同步到develop的方式是直接将release分支merge到develop,而不是通过cherry-pick单个commit再合并。
为什么你的方式有问题?因为cherry-pick会生成一个全新的提交(和release里的提交哈希完全不同),Git无法识别这两个提交是同一套变更。后续你再合并release到develop时,Git会把release里的变更当成“新的未合并内容”来处理,虽然这次测试中普通merge因为是追加内容没冲突,但如果是修改同一行代码,必然会触发冲突。
正确的同步流程应该是:
- 在release分支合并feature后,直接创建PR将release合并到develop,保留完整的合并历史。这样Git能清晰跟踪哪些变更已经同步,后续合并时不会重复处理。
2. git merge --squash是否不推荐在Git Flow中使用?
绝对不推荐,Git Flow的核心就是依赖清晰的分支历史和合并轨迹来管理版本,而--squash完全违背了这个设计:
- 它会把待合并分支的所有提交压缩成一个新的普通提交,丢失了单个功能的开发历史,后续排查问题或回滚时根本找不到具体的提交点。
- 它不会创建合并提交,破坏了Git的分支拓扑关系,Git无法跟踪哪些变更已经被合并,后续再合并同一分支时会重复处理变更,极易引发冲突。
- Git Flow中release、develop、feature分支的合并都需要保留明确的合并记录,
--squash会让历史变得混乱,完全不符合Git Flow的规范。
3. Git如何识别待合并的提交?为何修改提交信息会影响冲突结果?
Git识别待合并提交的逻辑
Git是通过提交历史的拓扑关系来识别的:它会先找到两个分支的最近共同祖先(LCA),然后合并这个共同祖先之后,两个分支上所有提交的变更。普通的git merge会创建一个合并提交,记录两个分支的合并点,这样Git后续能明确知道哪些提交已经被合并过,不会重复处理。
提交信息影响冲突的本质
其实提交信息本身不会直接导致冲突,问题出在git merge --squash的特性上:
--squash不会创建合并提交,它只是把待合并分支相对于共同祖先的所有变更,当成一个“补丁”应用到当前分支,然后生成一个新的普通提交。这意味着Git无法通过历史记录识别这些变更已经被cherry-pick到develop了。- 你的测试中,
git merge --squash && git commit --no-edit没冲突是侥幸:因为变更都是追加到文件末尾,Git在应用补丁时自动跳过了重复内容,但这种情况不稳定,换个变更方式就会冲突。 - 而当你自定义提交信息时,Git在处理补丁的过程中,严格对比了内容,发现待合并的变更(添加4,5,6)已经存在于当前分支,所以触发了冲突。本质上是
--squash不跟踪合并历史导致的重复处理。
模拟BitBucket PR合并的Shell脚本
#! /bin/bash set -euo pipefail IFS=$'\n\t' function log_step() { echo -e "\n\e[96m${*}\e[0m" } # Delegates the call to the merge strategy and passing args # You can uncomment the desired strategy function merge() { # merge_simple $* merge_squash_rename $* # merge_squash_no_edit $* } function merge_squash_rename(){ echo -e "\e[94mMerge ${1} into ${2}\e[0m" git checkout ${2} git merge --squash ${1} git commit -m "Merge ${1} into ${2}" } function merge_squash_no_edit(){ echo -e "\e[94mMerge ${1} into ${2}\e[0m" git checkout ${2} git merge --squash ${1} git commit --no-edit } function merge_simple(){ echo -e "\e[94mMerge ${1} into ${2}\e[0m" git checkout ${2} git merge ${1} } tmp_dir=$(mktemp -d -t strategit-XXXXXXXX) echo "Created directory ${tmp_dir}" cd ${tmp_dir} # Initial state : a file named hello.txt, containing "1,2,3" log_step "Create initial file with 1,2,3" git init echo '1,2,3' >> hello.txt git add hello.txt git commit -m 'First commit' # Create initial branches release + develop log_step "Create branches release and develop" git checkout -b release git checkout -b develop # Work on ticket FW-456 on release log_step "Create PR on release in order to add the line 4,5,6" git checkout release git checkout -b feature/FW-456 echo '4,5,6' >> hello.txt git add hello.txt git commit -m 'feature/FW-456' # Keep it for cherry-pick later ORIGINAL_COMMIT_HASH=$(git rev-parse HEAD) log_step "Merge the PR on release" merge feature/FW-456 release # report PR on develop by creating a report branch log_step "Create the report branch from develop" git checkout develop git checkout -b report/feature/FW-456 git cherry-pick ${ORIGINAL_COMMIT_HASH} log_step "Merge the report branch on develop" merge report/feature/FW-456 develop # Work on ticket FW-789 on develop log_step "Create a new PR from develop to add the line 7,8,9" git checkout develop git checkout -b feature/FW-789 echo '7,8,9' >> hello.txt git add hello.txt git commit -m 'feature/FW-789' log_step "Merge the PR in develop" merge feature/FW-789 develop # Now, report release into develop log_step "Create a PR on develop to report release in develop" git checkout develop git checkout -b chore/report-release-into-develop # "|| true" to prevent script from exiting because of the potential conflict error log_step "Try to merge" git merge release || true echo "" echo "***************************************************" echo "* *" echo -e "* See result in directory \e[92m${tmp_dir}\e[0m *" echo "* *" echo "***************************************************"
内容的提问来源于stack exchange,提问作者Arnaud Denoyelle




