ARTICLE DETAIL

资讯详情

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

Git协作实战:分支管理、PR流程与冲突解决全解析

Git协作实战:分支管理、PR流程与冲突解决全解析 Git 这东西属于“单干时觉得没必要、一协作就原形毕露”的工具。你一个人 commit、push永远碰不到分支管理和冲突解决但只要你跟别人一起开发一个项目哪怕只是两个人改同一个仓库用不了多久你就会发现分支、PR、冲突这三件事搞不透协作效率基本为零。这篇文章我准备用一次完整的 Git 协作模拟把分支管理、PR 流程和冲突解决这三座大山一次性讲透。我会从环境准备开始带你模拟两个协作者在同一个仓库里开发、合代码、制造冲突、解决冲突的完整链路。全程实操向每一步都有命令、有输出、有解释不只是告诉你“怎么做”还会告诉你“为什么这么做”。不管你是刚装好 Git 的大学生还是已经用了一段时间但遇到冲突只会慌的开发者这篇都能给你一套可以直接照搬的协作方案。1. 环境准备与仓库初始化1.1 Git 安装与全局配置先解决环境问题。Windows 用户直接去 Git 官网下载 Git for Windows一路 Next 装完。这里有两个安装环节容易踩坑一是安装向导里有个“Adjusting your PATH environment”选项一定要选 “Git from the command line and also from 3rd-party software”不然后面在 PowerShell 或 CMD 里敲git会直接报 “无法将‘git’项识别为 cmdlet、函数、脚本文件或可运行程序的名称”二是行尾转换那里选默认的 “Checkout Windows-style, commit Unix-style line endings” 就行后面冲突跟它关系不大但保持默认最省事。macOS 用户直接brew install gitLinux 用户apt install git或yum install git。装完后打开终端验证一下git --version看到版本号就说明装好了。接下来做全局配置这一步是很多新手最容易漏的但如果不配commit 记录里会带着一串乱码用户名PR 也没法关联到你的账号git config --global user.name 你的名字 git config --global user.email 你的邮箱这里建议直接用 GitHub/GitLab 注册邮箱因为平台是靠邮箱关联提交记录的。再顺手配一下默认分支名和常用别名git config --global init.defaultBranch main git config --global alias.lg log --oneline --graph --all --decorateinit.defaultBranch main是为了避免 GitHub 用main、本地还老默认master两边不一致的情况。alias.lg是我个人非常推荐的一个别名后面看分支图谱几乎是刚需一条命令就能看清整个仓库的分叉情况。1.2 SSH 密钥配置与远程仓库连接配置完身份之后下一步是把本地 Git 和远程仓库打通。现在 GitHub 和 GitLab 都不推荐用密码 push 了最稳的方式是 SSH 密钥。生成密钥ssh-keygen -t ed25519 -C 你的邮箱一路回车就好。生成的公钥在~/.ssh/id_ed25519.pub把它复制出来cat ~/.ssh/id_ed25519.pub然后去 GitHub 的 Settings - SSH and GPG keys 里 New SSH key粘贴保存。这个动作的本质是把你的公钥存在服务器上之后每次 push 的时候Git 用私钥签名、服务器用公钥验签建立了免密通道。它跟你登录网银用证书的原理是一样的理解成“本地持有钥匙服务器存锁”就行。验证是否通了ssh -T gitgithub.com出现Hi xxx! Youve successfully authenticated就代表配置完成。这一步非常关键——如果你后面 push 的时候频繁输密码或者报Permission denied (publickey)回来检查这一节。踩过太多次坑了十个新人里至少有三个卡在密钥配置上。1.3 搭建协作模拟环境这篇文章的实战目标是模拟两个人协作开发所以我们先建一个远程裸仓库再分别从两个“身份”的角度去操作。创建一个模拟远程仓库mkdir git-collab-demo cd git-collab-demo git init --bare--bare的意思是初始化一个没有工作区的纯仓库只存 Git 历史这就是远程仓库的标准形态。实际工作中你在 GitHub 上点 “New repository” 创建出来的仓库底层就是这个东西。然后在本地克隆出两个工作目录模拟两个开发者git clone gitgithub.com:你的用户名/git-collab-demo.git alice git clone gitgithub.com:你的用户名/git-collab-demo.git bob如果你用本地裸仓库模拟就把 URL 替换成裸仓库的本地路径。alice和bob这两个目录就是两个协作者的“电脑”后面所有冲突和 PR 就是在这两个目录之间产生的。这个模拟环境搭好之后后面的操作才能有真实的感知——不是一个人在单仓库里自娱自乐而是真的有两套工作区在同一个远程仓库上协同。2. 分支管理从主分支到功能分支2.1 为什么一定要用分支开发很多新手刚学 Git 的时候不太理解分支的意义觉得“我直接在主分支上改不就行了”。等你跟别人协作一次就知道问题了如果所有人都直接在main上开发那main上的代码永远是“半成品”可能你今天提交了一个没写完的功能别人拉下来跑都跑不起来。而且两个人同时改动同一个文件动不动就冲突改出问题想回滚都不知道从哪里下手。分支的核心价值是给每个功能或任务建立一条独立的开发线。你在自己的分支上可以随便 commit、随便折腾失败了直接删掉分支重来完全不干扰别人的工作功能开发完、测试通过之后再通过 PR 合并回主分支。这等于把“破坏性操作”隔离在主干之外main永远保持稳定可发布的状态。我见过一个比喻说得很好分支就像修路。你不能为了修一条支路就把主路全挖了。你得在旁边临时开一条辅道施工完了再接回主路。Git 的分支就是这个辅道。2.2 三种主流的分支工作流对比光知道“要开分支”还不够还得知道分支怎么组织。目前业界主流的分支工作流有三种我做了个表直接对比它们的适用范围工作流核心特征优势适用场景Git Flow长期分支master/develop 短期分支feature/release/hotfix流程严谨版本发布可控有明确版本节奏的正式项目GitHub Flow只有 main feature 分支feature 完成后直接 PR 合并轻量、灵活适合持续交付互联网产品、Web 应用GitLab Flow在 GitHub Flow 上增加环境分支pre-production/production兼顾环境和发布流程需要多环境部署的项目如果是自己学习或小型项目我强烈建议从 GitHub Flow 开始。它的规则极简main永远是稳定的任何功能开发都开一个新分支分支名用feature/功能名或fix/修复名做完 PR 合并后立刻删除分支。规则越简单越不容易乱。2.3 从零创建分支与合并分支现在我们实际操作一下。用alice这个目录创建一个新功能分支cd alice git checkout -b feature/login-page这个命令等于两步git branch feature/login-page创建分支git checkout feature/login-page切到你新建的分支上。Git 的分支本质上是一个指向某个 commit 的指针创建分支只是新增了一个指针成本极低这也是 Git 鼓励“频繁开分支”的根本原因。在这个分支上做一些开发比如写一个登录页的骨架文件login.htmlecho h1Login Page/h1 login.html git add login.html git commit -m feat: add login page skeleton提交后推送到远程git push -u origin feature/login-page-u参数的完整写法是--set-upstream它的作用是让本地分支和远程分支建立关联之后在这个分支上直接git push就不用再写远程名和分支名了。在main上同样看一下分支状态感受一下git lg的威力git checkout main git lg这时候git lg会展示两条提交线main停在最初的初始提交上feature/login-page则往前走了一步。分支的本质图景你从这里能直接感受到。3. 拉取请求PR的完整流程3.1 PR 到底是什么PRPull Request拉取请求这个词国内也叫“合并请求”。它跟直接 push 到主分支最大的区别是它提供了一种“审查-讨论-合并”的机制。PR 不是 Git 原生的功能而是 GitHub、GitLab 这些平台提供的协作能力。它的逻辑是你开发完一个功能分支推送到远程之后向项目维护者发起一个“请把我的改动拉进去”的请求维护者可以查看 diff、提评论、要求修改都通过了点一下 Merge 按钮代码合入主分支。非要用一句话总结的话PR 是代码合并的“仪式化”流程。它逼着你在代码进入主干之前先过一个检查关卡。对个人开发者来说这个流程可能显得冗余但对团队协作来说它是质量保障的关键一环。3.2 Fork 工作流与分支工作流的区别说到 PR有两个概念容易懵Fork 和分支。分支是在同一个仓库内开辟开发线你和同事都有这个仓库的写权限直接在仓库里开分支、推分支。Fork 则是在你自己的 GitHub 账号下复制一份别人的仓库你在自己的副本里改改完之后发起 PR 给原始仓库。开源项目一般都走 Fork 工作流因为路人没有原始仓库的写权限公司内部项目一般走分支工作流因为团队成员本来就共享一个仓库。两种流的核心区别可以理解为“你对目标仓库有没有写权限”。有权限就用分支 PR没权限就 Fork PR。理解这个之后你看到开源项目里的Fork按钮就不会不知道点不点了。3.3 一次完整 PR 的实操演示我们把feature/login-page分支的改动通过 PR 合入main。如果用的是 GitHub直接 push 分支后命令行会给出一个Create a pull request for feature/login-page的提示或者你去仓库页面点 “Compare pull request” 按钮。PR 的标题和描述建议写清楚改了什么、为什么改、测试过什么。比如这次可以写“feat: add login page skeleton”描述里补充一句“新增登录页基础结构后续接表单验证”。实际工作中PR 描述写得好不好直接影响 review 效率。描述里的关键信息包括背景为什么要做、改动清单核心变化、测试方式怎么验证、截图或演示链接。这里我推荐一个简单模板## 背景 [为什么做这个改动] ## 改动 - [具体改动点1] - [具体改动点2] ## 测试 - [ ] 本地验证通过 - [ ] 相关用例覆盖 ## 关联 Closes #123Reviewer 看到这样的 PR10 分钟就能完成审查反之一个只有两三行标题、没有描述、没有测试说明的 PR审查人根本不敢点 Merge。3.4 用 GitHub CLI 管理 PR如果你不想每次都点网页操作可以用 GitHub 官方的命令行工具gh。装好之后先认证gh auth login然后创建 PR 就一句话gh pr create --title feat: add login page skeleton --body 新增登录页基础结构查看 PR 列表、看 CI 状态、甚至直接合并都可以在命令行里完成gh pr list gh pr status gh pr merge --squashgh pr merge有三种合并方式--merge普通 merge commit、--squash把分支上所有 commit 压成一个、--rebase变基合并。这三个的区别我后面会细讲它们是区分“会用 Git”和“对 Git 有理解”的分水岭。3.5 代码评审的实战心得评审阶段是最能体现团队协作水平的地方。我自己 review 代码的经验是先看整体再看细节。整体包括——这个 PR 有没有必要、改动范围是不是合理、有没有把不相关的东西夹带进来细节包括——命名、边界条件、异常处理、有没有明显的性能问题。写评语也有技巧。不要只说“你这里写得不对”要给修改建议不要一次性提 50 条意见把作者淹没挑最核心的问题点出来。对作者来说收到 review 意见不要觉得挫败那是别人在帮你守质量底线。我见过很多新人第一次收到 review 意见就开始防御性解释本质上没必要——代码评审讨论的是代码不是你这个人把心态摆正这轮流程你才能走顺。4. 冲突解决从根源到实操4.1 冲突是怎么产生的冲突是 Git 协作中躲不开的话题。要说清楚怎么解决得先说清楚冲突为什么会发生。Git 合并代码的基本单位是行。当 Git 合并两个分支时它会自动尝试把双方的改动拼接起来如果两个分支改的是不同的文件或者改的是同一文件的不同位置Git 可以直接自动合并完全不需要你参与。只有当两个分支修改了同一个文件的同一个区域时Git 不知道到底该听谁的这时候就会报冲突。一句话总结冲突的本质是“两个人改了同一行代码Git 不知道以谁为准”。打个比方你和室友合租你们俩对客厅装修各有一份计划。你打算把客厅刷成白色室友打算刷成灰色这个改动落在同一面墙上你总不能指望别人替你们做决定吧。Git 也是一样它把决定权交给你只是把两种改动都给你标了出来。4.2 手动冲突解决全流程演示现在我们就模拟一次真实的冲突场景。场景是alice和bob同时修改同一个文件的同一行。先让alice基于main创建一个分支并修改config.txtcd alice git checkout -b feature/alice-config echo color blue config.txt git add config.txt git commit -m config: set color to blue git push -u origin feature/alice-config然后让bob也基于main创建分支同样修改config.txt的同一行cd bob git checkout -b feature/bob-config echo color red config.txt git add config.txt git commit -m config: set color to red git push -u origin feature/bob-config先把alice的分支通过 PR 合入maincd alice git checkout main git pull origin main git merge feature/alice-config git push origin main这时候main上config.txt的内容是color blue。接下来bob尝试把分支合入main冲突来了cd bob git checkout main git pull origin main git merge feature/bob-config这时候 Git 会提示Auto-merging config.txt CONFLICT (content): Merge conflict in config.txt Automatic merge failed; fix conflicts and then commit the result.打开config.txt你会看到冲突标记 HEAD color blue color red feature/bob-config这个标记的含义很明确 HEAD到之间是你当前分支main的内容到 feature/bob-config之间是对方分支的内容。解决冲突就是把两段内容决定成一段然后删掉标记。假设你决定保留两行配置color blue color red把文件里的冲突标记和其中一方内容清除后执行git add config.txt git commit -m merge: resolve conflict in config.txt git push origin main冲突解决完毕。整个过程不难难的是很多人第一次看到就懵了。记住一件事看到冲突标记先别慌——Git 不会吞掉你的代码它只是把两种意见原封不动摆在那里等你拍板。4.3 merge 冲突与 rebase 冲突的差异上面演示的是git merge的冲突。但日常协作中还有一种更常见的冲突来源——git rebase。Merge 和 rebase 达成的是同样的目标把两个分支的改动整合到一起。但路径完全不同merge 是“创造一个合并提交把两条开发线汇到一起”rebase 是“把我这分支的提交取下来在目标分支最新的提交上重新放一遍”。打个比方merge 像两个人从两个方向修路修到中间汇合rebase 像你把自己的那段进度搬到别人的进度前面重新施工。Rebase 的冲突解决流程和 merge 几乎一样标记格式也一样唯一区别是merge 解决完冲突只需 commit 一次而 rebase 解决完每个冲突都要git add然后git rebase --continue一步步走完所有提交。举个例子。你在feature分支上有两个提交main分支在你开发期间也有新提交你执行git rebase main时如果两个提交都碰到冲突就得连续解决两次。每次解决完git add 冲突文件 git rebase --continue如果中途想放弃直接git rebase --abort回到 rebase 之前的状态非常安全。那 merge 和 rebase 到底选哪个我的建议是合并功能分支优先用 merge它保留的提交历史更真实保持主线整洁用 rebase但只 rebase 自己还没推送到远程的提交。永远不要 rebase 一个已经推到远程并被别人拉取过的分支——那会产生重复提交把所有人的历史搅成一锅粥。4.4 三种合并模式的取舍前面提到的 PR 合并有三种模式我详细拆解一下。普通 merge commitGit 会把 feature 分支的提交历史和 main 的提交历史合并到一起保留两条线的分叉和汇合点。好处是完整保留开发过程的“真实记录”坏处是提交历史会有一堆分叉和合流时间久了看起来比较乱。Squash and merge把 feature 分支上的所有提交压缩成一个合入 main。好处是 main 的提交历史非常干净每个功能只占一个提交坏处是 feature 分支上细粒度的开发记录全部丢失。适合“功能开发过程中的提交很碎、没人关心过程细节”的场景。Rebase and merge先把 feature 分支变基到最新 main再用 fast-forward 方式合并。好处是历史线呈线性没有分叉坏处是变基会导致 commit hash 变化对一个已经公开的分支有影响。这三个模式的选择本质上是在回答一个问题你希望 main 的历史长什么样我个人的项目习惯是个人开发用 squash团队大项目用 merge commit需要严格线性历史的项目用 rebase。4.5 冲突预防的四个技巧写完冲突解决我再分享四个从源头减少冲突的实操技巧这些是我实际项目中验证过有效的策略。第一任务拆细、分支开早。一个分支做一件事一个 PR 改动尽量控制在几百行以内。分支存活时间越短、改动的文件越少冲突概率天然就越低。第二勤拉远程更新。开发过程中经常git fetch origin和git rebase origin/main或git merge origin/main别等所有代码写完再一次性合入把问题分散在过程中消化。第三模块化代码结构。两个人尽量不同时修改同一个文件如果确实需要改同一个模块先沟通谁负责哪一块也能减少一半以上的冲突。第四让 CI 帮你跑检查。很多冲突不是 Git 层面的内容冲突而是语义冲突——两人改了同一个函数Git 能自动合并但跑起来是坏的。这类冲突最危险所以一定要有自动化测试在 PR 合并前跑一遍。5. 故障恢复与日常排查5.1 误删分支与误改代码的恢复分支和冲突都玩熟了之后还有一个能力非常关键——出了事能把仓库恢复回来。Git 最强大的地方不是它不会出错而是它几乎把所有操作都记录下来了。你要学会的是出问题时怎么找回。误删分支是高频事故。比如你删了feature/login-page分支后来发现里面的一个提交还没合入。别慌提交对象在 Git 里还有引用git reflog这个命令会列出 HEAD 的每一次变动记录包括那些“并不存在于任何分支”上的操作。找到删除分支前的那个 commit hash直接基于它新建分支git checkout -b feature/login-page-restored commit-hash同理如果你发现某个改动把代码改坏了想回到之前的状态git log --oneline git revert 坏提交的hashgit revert会生成一个新的反向提交把坏改动取消掉但不会改写历史。它跟git reset的区别在于reset 是“回去”且修改历史revert 是“撤销”且保留历史。在团队协作中请永远优先考虑 revert因为 reset 会改变提交 hash一旦别人已经基于旧 hash 做了操作reset 就会导致历史错乱。5.2 stash 与临时代码处理日常开发还会碰到一种情况你正在feature分支开发一半突然需要切到main修个紧急 bug但现在的改动还不想提交。这时候git stash就是救命工具。git stash push -m wip: login page debug git checkout main # 修完 bug 回来 git checkout feature git stash popgit stash的逻辑是把工作区还没提交的改动暂时存到一个独立区域让你可以干净地切换分支。git stash list可以查看所有暂存git stash pop会把最近的暂存恢复到工作区并删除记录git stash apply则可以保留暂存记录适合同一个临时改动要应用到多个分支的场景。我用 stash 的经验是给 stash 加个备注-m非常关键。如果你不加备注过几天你会看到一长串没有说明的 stash 列表完全想不起来哪个是哪个。加了备注一目了然。5.3 常见问题排查速查表最后分享一张我自己整理的问题排查速查表都是高频问题问题现场核心原因解决方案提示not a git repository当前目录不是 Git 仓库git init初始化或确认进入了正确目录push 时提示rejected远程有本地没有的新提交先git pull或git fetchgit rebase提示failed to push some refs远程分支领先于本地git pull --rebase后再 pushPermission denied (publickey)SSH 密钥未配置或不匹配检查密钥文件、重新添加公钥提示fatal: refusing to merge unrelated histories合并的两个分支没有共同历史确实要合并则加--allow-unrelated-histories提交后想改提交信息提交信息写错了或漏东西git commit --amend修改最近一次提交想撤销某个文件的修改工作区文件改乱了git checkout -- 文件名恢复不记得之前做了什么操作操作历史需要回溯git reflog查看完整 HEAD 变动这里面refusing to merge unrelated histories特别容易在新手把本地仓库和远程仓库关联时碰到原因是本地初始化和远程初始化产生了两个“祖先是空”的仓库Git 拒绝把它们强行嫁接到一起。如果你确定要合并加--allow-unrelated-histories就好。5.4 分支清理与仓库卫生协作时间长了远程仓库会堆积大量已经合并过的废弃分支。保持仓库整洁也是团队协作里容易被忽略但很重要的一环。合并完 PR 后及时删除远程分支git push origin --delete feature/login-page本地分支同步清理git branch -d feature/login-page本地还有一批已经被远程删除但本地还存在的分支可以用这个命令一键清理git fetch --prune--prune参数的作用是清理本地缓存的“远程已经不存在的分支引用”。维护好分支卫生能让git branch -a的输出干净很多团队里每个人看到的分支列表都是“正在进行的工作”而不是“历史遗留的乱葬岗”。我个人在操作中的体会是Git 的很多命令就像停车场里的车车多了确实需要规划和管理。你花三分钟养成做一次清理的习惯省下来的远不止三分钟——找分支、看错分支、合并错内容、误以为改动丢了这些坑我都踩过基本都是“仓库不整洁”连带出来的。最后再分享一个配合这个模拟环境的小练习把文中的alice和bob两个人操作完整执行一遍然后在真实的 GitHub 或 GitLab 上走一次 PR 流程你会发现理论上的“会”和实际上的“熟”之间差了整整一段手动流程的距离。分支管理、PR 协作、冲突解决这三件事真的只有亲手撞过一次墙才能变成你自己的东西。
返回列表