拓冰建站拓冰建站
首页 / 资讯中心 / 正文

Git协作全流程:从Fork分支到Pull Request的提交指南

1. 从“push不上去”说起搞清你要面对的是哪种协作场景先别急着敲命令。我见到太多人拿着git push就往同事仓库硬推结果要么被拒要么把别人的历史搞得一团糟。向别人的仓库提交代码本质上就不是你平时git add git commit git push那么简单——你需要先想清楚一个问题你在这个协作关系里处于什么角色我在团队里带新人的时候第一步从来不是教命令而是先让他们分清三种完全不同的场景你是仓库协作者——对方在你的GitHub/Gitee账号下把你加成了Collaborator或者你在公司GitLab项目里被分配了Developer权限。这种情况下你理论上可以直接往分支推但要不要直接推main/master是另一个需要谨慎决策的问题。你是外部贡献者——对方仓库不是你拥有的你也不是协作者比如你想给一个开源项目提代码或者你同事的私人仓库不想给你开权限。这时必须走forkPR/Pull Request流程。你只是想把本地已有项目和别人仓库“对接”上——比如别人建好了一个仓库里面有README初始化文件让你把本地代码传上去。这更多是远程仓库关联的问题和“提交到别人仓库”的语义略有差别但操作上也有交集。这个区分为什么重要因为它直接决定了你接下来要用的命令完全不同。如果搞混了你会浪费大量时间在权限报错上瞎折腾。很多人以为向别人仓库提交代码就是“clone下来改完再push上去”这个理解在只有你一个人开发的场景下勉强成立但凡涉及到其他协作者这种操作方式大概率会碰到remote: Permission to xxx denied或者failed to push some refs这种拦路虎。这篇文章我打算从最完整的链路讲起覆盖分支策略、提交信息规范、同步远程更新、发起合并请求的完整闭环最后把我在实际协作中踩过的坑和排查方法整理出来供你参考。适合刚接触团队协作开发的新人也适合那些一直在单人仓库里“自娱自乐”、第一次要往别人仓库提交代码的同学。2. 动手之前本地Git配置和对远端仓库的认知2.1 基础配置别偷懒尤其是user.name和user.email到别人仓库提代码第一步其实不是clone而是确认你本机的Git身份信息是“对的”。很多人觉得这个无所谓但提交记录里显示的名字和邮箱会跟着代码永久留在对方仓库的历史里。你总不想让对方的仓库历史里出现一个userDESKTOP-ABC123这种自动生成的主机名吧# 查看当前全局配置 git config --global user.name git config --global user.email # 如果没设置过或不对重新设置 git config --global user.name 你的真实姓名或昵称 git config --global user.email 你常用的邮箱这里有一个团队协作的分岔点如果你只改这一个仓库的身份不想影响全局设置可以在仓库目录里去掉--global用git config user.name和git config user.email单独设置。我在公司经常这么干——因为个人项目和公司项目用的邮箱不一样全局统一反而会污染提交记录。另外推荐一个容易被忽略的设置git config --global pull.rebase true这个设置的意思是以后执行git pull时默认用rebase而不是merge。为什么会推荐往下看到同步上游那部分你就明白了这里先记着。2.2 理解你将要面对的两个仓库origin与upstream说到“别人仓库”你的客户端里其实会同时存在多个远程仓库地址。很多人被origin这个概念搞糊涂过。以最常见的fork协作流程为例upstream别人的原始仓库比如https://github.com/某大神/awesome-project.gitorigin你自己fork出来的仓库比如https://github.com/你的名字/awesome-project.git你的操作路径是本地代码改完先推送到origin你自己的远端仓库然后通过GitHub/Gitee/GitLab的Pull Request或Merge Request功能请求upstream别人的仓库合并你的改动。很多新手容易犯的错是直接clone了别人的仓库然后本地改完就往origin推结果发现推不上去——因为你本地仓库里配置的origin指向的是别人的仓库地址而你对那个地址并没有写权限。这就是“Permission denied”最常见的来源。正确的起步姿势是# 方式A先fork再clone推荐走标准PR流程 # 1. 在GitHub/Gitee网页上点击Fork按钮把别人仓库复制到自己账号下 # 2. 克隆你自己的这份仓库 git clone https://github.com/你的名字/awesome-project.git cd awesome-project # 3. 把别人的原始仓库添加为upstream git remote add upstream https://github.com/某大神/awesome-project.git # 4. 确认远程仓库列表 git remote -v你会看到类似这样的输出origin https://github.com/你的名字/awesome-project.git (fetch) origin https://github.com/你的名字/awesome-project.git (push) upstream https://github.com/某大神/awesome-project.git (fetch) upstream https://github.com/某大神/awesome-project.git (push)如果你和对方是同事关系权限已经给你开好了那就不需要fork只有origin一个远程仓库直接往分支推就行。但为了代码 review 的规范性和安全性很多团队即使是内部仓库也会约定好不允许直接推主分支必须走MRMerge Request——这一点后面细说。2.3 clone后第一件事检查分支状态我拿到一个别人的项目代码后从来不会直接在当前分支上开干。先看一下你在哪个分支、有哪些分支这是避免后续混乱的前提git branch -a这个命令会列出本地分支和远程跟踪分支。如果发现你不在main或者develop上先切过去保证你的改动基于最新的主干开发。再强调一下不要直接在任何仓库的主分支上做开发。这是协作开发的第一条军规理由不复杂——主分支是所有人的基线你直接在main上改别人pull下来就会看到你改到一半、可能还没法运行的代码谁也受不了。3. 分支管理向别人仓库提交代码的“交通规则”3.1 切分支这件事比你想象的更重要在协作场景中分支等于你的工作区隔离舱。向别人仓库提交代码本质上就是“请对方把你这个分支上的改动合并进去”。所以分支的名字和用途应该在一开始就规划清楚。从最新的主干拉出一个功能分支# 切到主干并更新到最新 git checkout main git pull origin main # 基于最新的main创建分支 git checkout -b feature/login-page-fix # 或者如果你想基于最新的upstream创建分支 git fetch upstream git checkout -b feature/login-page-fix upstream/main第二种写法我很推荐。它的好处是你不需要先把upstream的改动合到本地main再切分支而是直接从别人仓库的最新状态拉分支省掉了一次合并操作分支基线也更新鲜。分支命名规范是很多团队代码Review时会被特意指出的问题。一个叫fix-bug的分支和一个叫fix/login-page-500-error的分支后者明显让你和reviewer省去很多沟通成本。常见的命名模式feature/xxx新功能比如feature/user-registrationfix/xxx修复bug比如fix/cart-total-calculationdocs/xxx文档改动refactor/xxx重构类改动不要小看这个习惯名字取得清楚对方仓库的维护者扫一眼分支列表就知道每件事在干嘛。这属于投入极小、回报极高的事情。3.2 新分支和旧分支修改前的环境准备如果你的本地仓库已经存在一段时间了切分支之前一定要确认工作区是干净的# 查看当前工作区状态 git status如果显示有未提交的修改要么git commit先存起来要么用git stash暂存。我个人的习惯是只要准备切分支就把当前改动要么提交要么stash绝不留着半成品跨分支跳跃。带着一堆改动切分支一旦有冲突git会直接拒绝切换这时候你就得停下来处理平白浪费时间。而且就算git让你切过去了代码也很容易变成“混合怪”。用stash暂存的方法git stash push -m 临时保存登录页修改 # 切分支干活过段时间想恢复 git stash pop别怕用stash它是在分支间临时搬运改动最安全的方式。4. 提交规范的执行力commit信息是写给后人看的“便签”4.1 commit频率和颗粒度怎么把握往别人仓库提代码commit信息某种意义上比代码本身更能体现一个人的专业度。别人review你的PR时会一个一个commit看过来如果你的提交历史一塌糊涂会严重拉低对方的Review效率。关于commit的颗粒度我的经验是三条一个commit尽量只做一件事。修复Bug的代码不要和重构的代码混在一起否则别人想单独回退其中一部分就非常痛苦。commit信息要能概括“为什么”。代码怎么改的看diff就知道了但为什么要这样改往往只有commit信息能说明白。别滥用git commit --amend。如果是分支刚提交完还没推到远程amend来修正上一个commit可以理解但如果已经推到远程了amend会改写历史会和别人的本地产生冲突这时候就要谨慎了。4.2 一套可以直接抄的commit信息模板我一直推崇用 Conventional Commits 规范来写提交信息它不复杂但对协作的改善立竿见影type(scope): subject # 示例 feat(login): 增加手机号验证码登录 fix(cart): 修复结算页总价计算错误 docs(readme): 补充部署说明 refactor(utils): 抽取日期格式化公共函数 test(api): 增加订单接口单元测试其中type是核心feat代表新功能fix代表修复bugdocs是文档refactor是重构test是测试chore是构建或辅助工具的变动。scope是可选的表示影响范围。subject用一句话描述中文英文都行关键是团队统一。为什么推荐这个规范它不光是好看而是它天然贴合了语义化版本号的判断标准——哪些改动属于minor升级哪些属于patch修复从type就能看出来。另外现在很多代码托管平台的Release Notes生成功能可以直接从这个规范里提取内容省得后期专门去整理变更日志。4.3 提交后自查别让commit带“脏”东西提交之前一定要用git status和git diff检查。# 查看工作区所有改动 git status # 查看具体改动内容 git diff # 只暂存你想要的文件 git add src/components/Login.vue git commit -m feat(login): 增加手机号验证码登录一句话不要用git add .无脑把所有文件都加进去。我见过各种垃圾文件被提交到公共仓库node_modules、target、.idea、本地配置文件等等。如果你是往别人仓库提交代码尤其是开源项目把.class文件或者IDE配置推进去那基本等于给人添乱。正确做法是每个项目根目录都放一份规范的.gitignore文件把依赖目录、构建产物、IDE配置、本地环境配置全部排除在外。如果对方的仓库里没有.gitignore你可以在自己的提交里主动补一个并在commit信息里说明。5. 推送到远程分支和它的“同义词”同步上游那些事5.1 本地提交后Push到哪个远程分支假设你在feature/login-page-fix分支上完成了两个commit现在要推送到远程了。如果你走的是标准fork流程推送目标是你的origin仓库git push origin feature/login-page-fix第一次推送新的本地分支时可能提示你设置上游跟踪git push -u origin feature/login-page-fix加-u的意思是把本地分支和远程分支建立跟踪关系之后在这个分支上只要直接敲git push或者git pull就行了。这个设置我很建议在第一次推送时加上能省掉后面每一次推送都要写明远程和分支名的麻烦。如果你是协作者直接推送对方仓库那就要注意推送到分支而不是主分支。很多团队规定主分支是“保护分支”直接推送会被服务器拒绝这种情况下你只能推到功能分支然后在网页上发起Merge Request。5.2 为什么push被拒了先pull还是先rebase这是“提交到别人仓库”全流程里最容易卡住的环节。你在本地开发的同时别人仓库的主干可能已经多了很多新提交。当你推送到远程时会收到类似这样的报错! [rejected] feature/login-page-fix - feature/login-page-fix (non-fast-forward) error: failed to push some refs to https://github.com/xxx/awesome-project.git hint: Updates were rejected because the tip of your current branch is behind hint: its remote counterpart. Integrate the remote changes first这个报错的核心是你本地分支落后于远程分支。在推送之前必须先把远程的新提交合并到本地才能成功推送。这时候有两个选择merge和rebase。它们的差异我直接说结论git pull --rebase origin main把你的本地提交“摘下来”在远程最新的提交之后重新“重放”一遍。这样历史是一条干净的直线不会有分支分叉和合并节点。git pull origin main默认merge会生成一个额外的merge commit历史里会多出一个“分叉-合并”的节点看起来更复杂一些。向别人仓库提交代码的场景我强烈建议用rebase方式。原因很简单PR/MR的reviewer在看你的合并请求时看到一条干净的单线历史远比看到一堆merge commit舒服。有了第2节里设置的pull.rebase true你直接执行git pull --rebase upstream main如果rebase过程中出现冲突git会停下并把冲突文件标记出来。解决冲突后执行git add 解决冲突的文件 git rebase --continue如果中途你想放弃这次rebase可以执行git rebase --abort本地分支会恢复到rebase之前的状态。首次提交PR之前做一次rebase把分支基线与远端主干对齐这是我从实战里总结出的最能让reviewer心平气和的仪式感之一。5.3 多人协作时保持你的origin分支随时最新如果你fork了别人的仓库还开了PR但对方迟迟没合并同时你的本地又继续开发了新的东西。这时候你可能需要把upstream的最新代码同步过来让你自己的origin分支也保持更新。同步的完整链路是# 拉取别人仓库的最新提交 git fetch upstream # 切回你的本地主分支 git checkout main # 将upstream/main合并或rebase到你的本地main git merge upstream/main # 或者git rebase upstream/main # 推送到你的origin/main让自己的GitHub fork仓库也同步 git push origin main养成定期同步上游的习惯能有效减少最后合并时的冲突概率。6. 发起Pull Request让别人的仓库接受你的代码6.1 PR前最后的检查清单推送完成后打开GitHub或Gitee/GitLab来到你的fork仓库页面会看到醒目的Compare pull request按钮点击进入PR创建页。但先别急着点“Create pull request”我先建议你过一遍下面的检查清单基础分支是否正确base仓库别人的仓库的分支一般选main或developcompare分支你的仓库选你刚推上去的功能分支。选反了是新手最常见的错误会导致对方的PR里出现一堆你根本没打算提交的改动。PR标题是否符合规范和commit信息一样建议用feat(login): 增加手机号验证码登录这种格式写PR标题。PR描述是否清楚至少包含三点——改了什么功能、为什么这么改、如何验证/测试。如果改动涉及UI通常还需要附上截图或录屏。其实养成了规范的commit信息习惯PR描述就能写得很轻快。如果项目有PR模板不要删掉模板内容按模板逐项填写。6.2 对方要求你改代码时该怎么修改PR提交上去之后最常见的结局不是直接合并而是reviewer提出修改意见。收到代码Review意见时心态上不要有抵触——对方针对的是代码本身不是针对你。哪怕是大神写的代码也经常会被要求修改这是协作的常态。修改流程很简单# 切回同一个功能分支 git checkout feature/login-page-fix # 根据review意见改代码 # ...修改文件... # 提交新改动 git add . git commit -m fix(login): 调整验证码获取接口的错误处理 # 推送到同一个远程分支PR会自动更新 git push origin feature/login-page-fix注意不用重新发起PR也不用关掉旧的PR重新创建。只要推送到同一个分支PR的更新是自动关联的。如果有人让你“amend掉之前的commit”来保持PR历史简洁你可以酌情处理——如果你的PR只有一个commit且尚未合并amend是合理的如果不是新增commit通常更符合协作习惯。6.3 在Review期间同步上游更新是很多冲突的根源一个容易踩的坑是对方让你改代码的同时他的主干也在被别人改动。你的PR因为落后于主干可能连merge都点不了页面会提示“This branch has conflicts that must be resolved”。这时候需要手动解决冲突。我的习惯是# 拉取对方仓库最新到本地 git fetch upstream # 在自己的功能分支上rebase upstream/main git rebase upstream/main # 遇到冲突就解决git add git rebase --continue # 直到rebase完成 # 强制推送到远程PR分支 git push --force-with-lease origin feature/login-page-fix注意这里我用了--force-with-lease而不是单纯的--force。--force-with-lease会先检查远程分支是否和上次你拉取时一致如果别人已经动过这个分支它会拒绝推送防止覆盖掉别人的提交。这是安全得多的强制推送方式强烈建议每次需要force push时都用它。6.4 PR被合并后别忘了清理分支对方的仓库接受了你的代码之后还有一步收尾工作要做清理你已经用完的功能分支。# 切回主分支 git checkout main # 拉取上游最新 git pull upstream main # 删除本地功能分支 git branch -d feature/login-page-fix # 删除远程fork仓库里的分支 git push origin --delete feature/login-page-fix注意git branch -d的小写d是安全删除如果分支有未合并的改动git会提示你不让删。这时如果你确认这些改动不要了才应该用git branch -D强制删除。7. 常见问题与排查技巧实录7.1 问题速查表我被问得最多的8个Git协作问题问题典型报错/现象排查思路解决办法推不上去权限不足remote: Permission to xx.git denied先确认你是否有该仓库写权限还是需要走fork流程fork后推送到自己的origin再发PR推送被拒本地落后! [rejected] (non-fast-forward)远程分支比本地领先需要先集成远端的改动git pull --rebase origin main后再push提交到了错误的分支git log发现提交不在预期分支检查当前分支名和提交记录用git cherry-pick把提交摘到正确分支或git reset回退身份信息错误提交记录里显示错误用户名/邮箱查看git config user.name/user.email修正配置并用git commit --amend --reset-author修正最后一个commit提交了不该提交的文件仓库里出现node_modules等检查.gitignore是否覆盖从版本控制中移除git rm -r --cached node_modules并加入.gitignore冲突解决到一半想放弃rebase/merge进入一半状态git status查看状态想放弃git rebase --abort或git merge --abort想继续解决并git rebase --continuefork后同步不到上游更新本地main还是旧版本确认upstream remote是否已添加git fetch upstreamgit merge upstream/main修改了PR但不想出现merge commitPR历史很乱本地分支落后于主干用rebase代替merge需要强制推送时用git push --force-with-lease7.2 实战排查一误提交到main分支怎么办假设你本来规划在feature/add-search分支上开发结果发现两个commit不小心提交到了本地的main分支上还没推送。这时候最简单做法是# 在main分支上创建一个新分支包含这两个提交 git checkout -b feature/add-search # 然后把main分支回退到这两个提交之前 git checkout main git reset --hard HEAD~2HEAD~2表示回退两个提交。如果你的误提交已经推到远程了那还要考虑远程分支的历史改写问题。如果远程只有你一个人在用可以用git push --force-with-lease强行同步如果多人共用就不能随便reset了需要用git revert生成新的反向提交。这也是我一直强调不要把主分支当开发分支的原因之一改了远程历史不是小事。7.3 实战排查二pull --rebase时冲突了别慌rebase冲突往往是新手最慌乱的时刻界面上一堆 HEAD和 feature/xxx标记。其实处理流程很固定打开冲突文件搜索 HEAD这里是当前分支通常是主干的代码。分隔线以下是你的改动。手动把代码调整成最终想要的版本然后删掉所有、、标记行。执行git add 冲突文件。执行git rebase --continue会弹出commit信息编辑器你基本可以直接保存退出因为rebase会保留原始提交信息。重复上述步骤直到rebase完成。解决冲突时有一个原则不要只闷头删标记要理解两边代码各自的意图。有时候别人的改动和你的改动名字不同但功能重叠你要主动判断哪些该保留、哪些该丢掉而不是机械地“上下合并”。如果拿不准最快的办法是在PR里at对方确认。7.4 实战排查三Gitee/GitLab提交规范差异虽然Git命令是通用的但不同的代码托管平台在细节上还是有差异。GitHub对应Pull RequestGitee同样也叫Pull RequestGitLab则叫Merge Request按钮位置和流程略有不同但核心逻辑一致。一个容易踩坑的细节是Gitee/GitLab有些仓库开启了“必须关联Issue才能提交MR/PR”的限制。如果你在提交MR时提示找不到关联Issue说明仓库已开启这个规则你需要先创建一个Issue说明你要做的事然后在MR描述里写上#issue编号关联。这不是bug是对方团队为了追踪需求而做的强制约束。另一个细节是很多企业内部GitLab默认禁用了git push --force即使你用的是--force-with-lease也可能被拒。遇到这种情况说明这个项目约定“不允许改写已推送的历史”你就老老实实新增commit来修正而不是强行force push。8. 写在最后的个人经验我在不同团队里带过不少人做协作开发发现“向别人仓库提交代码”这件看似基础的事其实最能反映一个人的工程素养。代码能力决定你能不能写出功能而Git协作习惯决定别人愿不愿意跟你合作。几个我经历过无数次、回头想想最有用的经验单独分享给你第一每次动手前先git fetchgit status。我见过太多人因为没拉最新代码改完才发现和远端差了十几条提交白白返工。花10秒钟确认基线能给你节省几十分钟的冲突处理时间。第二commit信息真的当回事。给自己定个规矩如果git log --oneline输出里最新几条信息含义模糊说明你还没养成规范。给自己定个要求提交消息必须能让你自己半年后看到还知道当时在干什么。第三PR描述别偷懒。把改动动机写清楚把测试方法写清楚reviewer能节省大量时间你自己也能减少被反复追问细节的次数。这是双赢。第四多和对方沟通。技术上碰到疑问比如“不清楚这个分支策略是否适合你们的仓库”直接在PR里提出来问比私下乱猜然后推一个完全不对的PR要高效得多。代码协作是人和人之间的协作不是冷冰冰的命令行交互。最后再给你一个实用建议如果你刚开始用这个流程可以找个小项目、小仓库先完整走一遍fork→clone→branch→commit→push→PR→合并的闭环把每一步每条命令亲手敲一遍。走完一次全流程你对Git协作的理解会比对着一百篇教程看都要深。后面再碰到踩坑你就不会再慌了。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门