ARTICLE DETAIL

资讯详情

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

Git Pull 核心原理与实战:从 fetch+merge 到冲突排查与分支同步

Git Pull 核心原理与实战:从 fetch+merge 到冲突排查与分支同步 1. Git Pull 是什么为什么它几乎每天都要用1.1 它背后到底发生了什么说实话我第一次用 Git 的时候git pull就是我接触到的第一个命令。那时候教程告诉我在 GitHub 或 Gitee 上写了代码回到本地电脑先一行git pull把远端最新的东西拉下来再开始干活。我一直到很晚才意识到git pull并不是一个“原子”命令它其实是先git fetch把远端提交下载到本地再执行一次git merge把下载下来的提交合并到你当前所在的分支。也就是git pull git fetch git merge如果你能理解这条公式git pull 的很多怪异行为就都能解释通了。举个例子你和同事同时维护一个项目他在远端已经把某个模块重构了一遍你本地还停留在三天前的版本。你执行git pull origin main时流程是这样的Git 先联系远程仓库在 GitHub/Gitee 上叫origin把main分支上新增的提交全部拉到本地一个隐藏目录.git里然后 Git 把这些新提交合并到你当前的工作分支上同时更新工作目录里的文件如果新提交和你本地修改互不影响这个过程是静默的如果改了同一个文件的同一行就进入冲突状态需要你手动解决。整套逻辑其实和我们平时把文件传到网盘、再从另一台设备下载最新版是一个道理。只不过 Git 把“谁改了哪几行”记得一清二楚所以它的合并能力远超普通文件同步工具。1.2 什么场景该用什么场景要踩刹车先说什么时候用工作开始前、准备提交前、准备推送前、以及部署代码前。很多团队的习惯是早上开工第一件事先git pull先把远端的最新代码同步到自己本地再开始写代码。维护个人项目的人可能一周才拉一次但只要你准备 pushpush 前先 pull 几乎是纪律性动作。为什么 push 前必须 pull因为远端很可能在你上次拉取之后又有了别人的提交你不先同步就直接 push会遇到non-fast-forward的错误。本质上就是你的提交基底已经过时了远端仓库不会好心地把你们的代码合并好再收下你的推送它会直接拒绝让你先把同步问题解决。但我要提醒一句如果你本地有大量未提交的修改直接git pull是有点冒险的。Git 原则上不会覆盖你本地未提交的修改但如果新拉下来的提交要改动的文件正好和你本地修改的文件重叠它可能会拒绝合并提示Your local changes would be overwritten by merge或者直接让你陷入一堆需要手工处理的冲突。我自己的习惯是pull 之前先看一眼git status有未提交修改就先 commit 掉或者至少 stash 一下。还有一类场景要特别谨慎当你在一个已经被改动很深的分支上、或者正在做代码评审的中途这时候无脑 pull 可能会把大量陌生修改塞进你的工作区造成信息过载。这时更适合用git fetch先看看远端发生了什么再决定要不要合并。这个区别我后面会专门展开。2. 日常高频显式分支、Rebase 与老仓库合并2.1 养成指定远端和分支的习惯很多人用git pull不跟任何参数因为 clone 后默认配置了跟踪分支。但我要强调手动养成git pull origin main的习惯很有帮助。为什么要显式指定明确你要拉取的是哪个远端可能是origin、upstream或你自己加的备用仓库明确分支名避免本地分支名和远端不一致时拉错在没有配置 upstream 的全新分支上不带参数的git pull会直接报错而带参数就能正常工作。GitHub 新仓库默认分支已经从master改成了mainGitee 默认分支还是master。因此你 clone 下来的默认分支可能各不相同。如果你本地有一个旧仓库远端已经没人维护master、所有提交都去了main那么git pull origin master拿到的可能是空数据正确做法是git pull origin main。分支名不一致的问题尤其在 GitHub 和 Gitee 之间搬运仓库时特别常见。你把 GitHub 的仓库推到 Gitee本地分支叫mainGitee 上初始化出来的默认分支叫master两端一合并各种别扭。解决办法就是显式指定git pull gitee master、git pull origin main每次把远端名字和分支名说清楚少踩一半的坑。2.2 想让提交历史更干净git pull --rebase如果你在社区搜索 Git 实用技巧一定会看到“尽量用git pull --rebase”的说法。原因在于默认的git pull会产生 merge commit每次你把远端合并进本地Git 都会记一笔Merge branch xxx into xxx的提交。时间一长日志里会出现很多分叉和合并节点读起来像地铁线路图一样复杂。git pull --rebase做的事情是先把本地的提交“摘下来”。比如你有两个本地提交 A、B远端拿下来的新提交是 C、D那么 rebase 会把 A、B 依次重放到 C、D 的上面最后得到一条直线C → D → A → B。你的代码内容没变但历史干净了。什么时候用个人分支、功能分支别人不会同时在你这个分支上工作放心用推送前想保持历史整洁用如果你在一个团队共享分支上工作--rebase后可能改变提交哈希如果别人已经基于你原来的提交干活就会出现麻烦。这种情况下还是用默认的 merge 更稳妥。我实际用下来的建议是本地功能分支坚持git pull --rebase主分支或发版分支老老实实 merge。不要为了追求好看的历史去动大家都依赖的分支美观的代价可能是队友的困惑。2.3 解决 “refusing to merge unrelated histories”这是非常常见的新手报错症状是执行git pull时出现fatal: refusing to merge unrelated histories这句话看着吓人实际意思是你想合并的两个提交历史没有公共祖先。最典型的场景是你刚刚在本地git init创建了一个空仓库然后又在 Gitee/GitHub 上手工创建了一个仓库两边各自都有一次“初始提交”Git 认为这俩仓库是毫无关系的独立项目于是拒绝自动合并。解决方案是加上--allow-unrelated-historiesgit pull origin main --allow-unrelated-histories但这里我要泼一盆冷水这个参数是让你“强行把两个不相干的历史缝在一起”并不是每次都正确。如果你本来就应该从远端 clone而不是本地 init 后再去关联那么更合适的路径是把本地文件备份好重新git clone仓库再把文件拷贝进去。只有在确实需要把两个独立项目合并到同一个仓库时才用这个参数。注意--allow-unrelated-histories不是灵丹妙药它只是解除 Git 的安全检查合并后你仍可能面对非常多冲突因为这些文件在两个历史里都是“新增”的。务必先备份本地数据。3. GitHub / Gitee 实操差异协议、密钥与令牌3.1 SSH 还是 HTTPS先搞清楚你的 remote 地址同样一个仓库可以同时支持 HTTPS 和 SSH 两种方式拉取GitHub 和 Gitee 都一样。执行git remote -v能看到当前仓库用的是哪种地址HTTPS 形式https://github.com/user/repo.git或https://gitee.com/user/repo.gitSSH 形式gitgithub.com:user/repo.git或gitgitee.com:user/repo.git两种方式的区别主要在于鉴权HTTPS 需要输入用户名和密码GitHub 现在已不支持纯密码必须用 Personal Access TokenGitee 也支持令牌方式SSH 则是在本地生成一对公钥和私钥把公钥配置到平台后拉取推送时就不需要反复输账号密码。选择建议对比项HTTPSSSH首次配置需要生成 token 或记住密码需要生成密钥对、配置公钥日常使用每次要输凭证Git 会缓存免输密码体验顺滑安全性依赖 token 有效期私钥本地保存泄露风险低适合场景临时环境、公司电脑个人长期开发环境我个人推荐在常用的开发机上配置 SSH。尤其是你同时使用 GitHub 和 Gitee 时每个平台单独配置密钥能让git pull的过程变成纯粹的“拉数据”不用每次被凭证问题打断思路。3.2 配置 SSH 密钥连接 Gitee 的完整步骤在 Gitee 上如果你要免密拉取代码流程是这样的本地生成密钥对如果还没有ssh-keygen -t ed25519 -C your_emailexample.com一直回车会生成到~/.ssh/id_ed25519和~/.ssh/id_ed25519.pub。ed25519 算法目前兼容性很好长度短安全性高如果生产环境有老机器非要用 RSA也可以ssh-keygen -t rsa -b 4096。把公钥内容复制到 Giteecat ~/.ssh/id_ed25519.pub复制输出的整行内容登录 Gitee 后进入「设置」→「安全设置」→「SSH 公钥」粘贴保存。验证连通性ssh -T gitgitee.com第一次连接会提示确认远端主机指纹输入yes然后会看到欢迎信息类似“Hi 用户名! Youve successfully authenticated”。修改仓库地址为 SSH 形式git remote set-url origin gitgitee.com:user/repo.git完成以上步骤之后git pull、git push都不会再弹密码框。GitHub 的配置几乎一模一样只是域名从gitee.com换成github.com。踩过的坑如果你配置了多个平台的多把密钥SSH 有时会“选错钥匙”。这时可以编辑~/.ssh/config按 Host 区分比如Host github.com HostName github.com User git IdentityFile ~/.ssh/id_ed25519_github Host gitee.com HostName gitee.com User git IdentityFile ~/.ssh/id_ed25519_gitee这样git pull时 SSH 会自动选择对应平台的密钥不会因为 key 不匹配被远端拒之门外。3.3 GitHub 私有仓库与令牌方式GitHub 从 2021 年起不再接受账户密码直接推送你必须使用 Personal Access Token。生成路径是GitHub 头像 → Settings → Developer settings → Personal access tokens → Tokens (classic) → Generate new token。生成时要勾选仓库访问的权限范围至少需要repo这个 scope。然后把生成的 token 当成密码使用即可。在 HTTPS 方式下拉取私有仓库用户名填你的 GitHub 用户名密码填 token而不是账户密码。用 HTTPS token 时如果凭证管理器里缓存了一个错误 token之后拉取就会反复提示认证失败。解决方式清空本机 Git 凭据缓存Windows 上是“控制面板 → 凭据管理器”macOS 上是“钥匙串访问”再用新的 token 重新拉一次。Gitee 在登录层面也有类似机制支持密码、令牌等方式。如果 Gitee 界面要求验证码时提示“验证码错误”但你的验证码明明是对的这类问题大概率是浏览器缓存或输入法导致的换一个无痕窗口或者直接用 SSH 方式访问仓库能绕开绝大部分验证码相关的登录问题。4. 分支同步、合并与提交修正4.1 Git Fetch 和 Git Pull 的本质区别搜索热度很高的一个问题是“git fetch 和 git pull 区别”这里给一个最直接的解释git fetch 是“只下载不合并”git pull 是“下载完还要合并”。也就是说fetch 执行完之后你的工作区文件一个都不会变但对 Git 来说它已经知道了远端的最新状态。实际操作中怎么体会你执行git fetch origin然后看git log --oneline origin/main就能看到远端main分支的最新提交但你本地分支仍然在原来的位置。这时候你可以从容地查看代码变动甚至可以用git diff HEAD origin/main来比较差异充分了解了改动内容后再决定git merge origin/main还是git rebase origin/main。这个习惯对有代码洁癖的人来说非常友好因为它把“获取信息”和“改变工作目录”两个动作拆开了。我见过很多初学者一上来就git pull结果被大量陌生改动直接灌进工作区一脸懵。你完全可以先 fetch 再手动 merge效果和 pull 一样但你能更清楚地知道自己在干什么。4.2 分支合并merge、rebase还是 cherry-pickgit pull最常见的合并对象是“当前分支对应的远端分支”。但你的需求往往不只有这一种可能你想把同事的feature/login分支拉下来看看或者只拿某个特定提交。对应的命令分别是拉取某个具体分支并合并到当前分支git pull origin feature/login这其实等价于git fetch origin后执行git merge origin/feature/login。只想把某个分支的最新内容拿到本地观察不动当前工作区git fetch origin feature/login git checkout -b local-login origin/feature/login只想拿一个提交而不是整条分支git fetch origin git cherry-pick commit-hash这里多说一句 cherry-pick 的使用感受。它非常适合“别人在别的分支上修了一个 bug你要在自己分支上同步这个修复”的场景。比如有人修好了某问题并推送到了 master你正在自己的功能分支开发不想把整个 master 合并进来就可以直接 cherry-pick 那个修复提交。同样的逻辑也可以用在 git pull 之后的补救上如果你已经把远端整个合并进来发现引入了很多无关代码可以考虑用 cherry-pick 只挑关键的改动。4.3 解决冲突的正确姿势不管是git pull还是git pull --rebase只要双方改了同一个区域冲突就一定会出现。Git 会把冲突标记写进文件像这样 HEAD 你的本地修改 远端带来的修改 origin/main打开文件后把这个区域改成你想要的最终结果删掉标记行然后git add这个文件。这里有个容易搞错的点git merge流程冲突后解决完文件要执行git commit来结束合并过程而git rebase流程冲突后解决完文件执行git add后需要执行git rebase --continue来继续重放剩余的本地提交。如果你发现自己陷入了一个不知道如何抽身的 rebase 过程可以使用git rebase --abort回到拉取之前的状态merge 过程则用git merge --abort。这两个命令相当于游戏里的“回档”能让你在冲突泥潭里随时跑路。不过要记住已经提交过的内容不会凭空消失冲突解决完再继续才符合多数团队的预期。4.4 提交信息修正git commit --amend 与拉取的关系git commit --amend是又一个高频操作它用来修改最后一次提交的提交信息、或者往最后一次提交里追加文件。用法很简单git commit --amend -m 新的提交信息或者想把某个漏掉的文件塞进上一次提交git add 忘提交的文件 git commit --amend --no-edit用--no-edit会保留原来的提交信息只是把新文件补充进去非常方便。但如果你在git pull之后才发现需要 amend 上一次提交就要格外小心你上一次提交可能已经被推送给远端了。如果别人已经看到了你那笔提交你用 amend 重写它会导致提交哈希变化再次推送时必须使用强制推送git push --force-with-lease。这个命令有风险--force-with-lease虽然比--force稍微安全一点它会在远端还是你上次看到的状态时才会覆盖但我依然建议已经推送过的提交就不要再 amend 了宁可新开一笔提交。原因很简单重写共享历史会让其他协作者一头雾水影响团队信任。5. 常见报错现场与排查清单5.1 fatal: not a git repository这个报错几乎是每个新手都见过的完整提示是fatal: not a git repository (or any of the parent directories): .git它意思就是当前目录不是 Git 仓库或者你的 Git 命令执行位置压根不在项目里。排查顺序如下先用pwd看当前目录确认是不是在项目文件夹里用ls -a看看有没有.git目录有.git才说明这是个 Git 仓库如果你原本在子目录里Git 会自动向上找.git所以如果代码目录本身没初始化才需要执行git init如果你是 clone 下来的仓库但.git不见了比如复制项目时漏了隐藏文件那就只能重新 clone 或者重新关联远端。一个实际经验很多人喜欢复制项目文件夹复制时把.git隐藏目录落下了。结果新文件夹里的文件全都是源代码但 Git 不认识它。解决办法不是到处问而是检查.git是否存在。另外如果你把一个仓库从 GitHub 迁移到 Gitee改了 remote 地址留意.git/config文件里的remote origin配置改错路径也会出现各种诡异拉取异常。5.2 认证失败Invalid username or password / Permission denied这类报错分两类。第一类是 HTTPS 方式下的用户名或密码错误remote: Invalid username or password fatal: Authentication failed for https://gitee.com/...GitHub 场景下基本就是 token 过期或者把账户密码当成 token 用了。Gitee 场景下可能是密码错误或需要令牌。解决路径重新生成 token在 Git 询问凭据时输入正确组合如果本地已经缓存了旧凭据先清理再重试。第二类是 SSH 方式下的Permission denied (publickey)。原因一般是公钥没配置到平台、或者 SSH 客户端没找到对的私钥。诊断方法ssh -T gitgitee.com ssh -T gitgithub.com看输出里是否提示你连接成功。如果公钥配置没问题就看~/.ssh/config是否指向了正确的密钥文件。我还要提醒一件事有些团队电脑上存在多个账号公钥加到了这个平台但git remote -v看到的是另一个平台的地址也会报Permission denied。仔细核对 remote 地址不要想当然以为“换了仓库地址就等于换了配置”。5.3 网络慢、超时与大仓库拉不动在 GitHub 上拉取大仓库时偶尔会出现下载到一半中断、超时的情况。Gitee 服务器在国内通常快很多。这里不讨论任何旁门左道就说常规范围内能做的优化手段。第一个建议是浅克隆只拉取最近一次提交git clone --depth 1 https://github.com/user/repo.git这样下载量会小很多。但要注意浅克隆之后git pull会有一些限制因为本地历史不完整。如果之后想要完整历史需要执行git fetch --unshallow第二个建议是调整 Git 的 HTTP 缓存区对大文件友好一些git config http.postBuffer 524288000这个配置表示把 HTTP 缓冲区调大到 500MB对某些二进制文件较大的项目能减少RPC failed; HTTP 500类错误。第三个建议是换到更稳定的网络环境比如公司内网、校园网。还有一个很务实的思路如果你只是想把 GitHub 上的项目当依赖或源码看可以在 Gitee 上搜索对应的搬运副本从 Gitee 拉取通常比直接从 GitHub 拉取稳定得多。很多开源项目在 Gitee 上都有搬运仓库拉取体验会好不少。5.4 快速排查清单汇总报错 / 现象可能原因检查/解决动作fatal: not a git repository当前位置不是仓库或丢失 .gitpwd、ls -a、重新 clone 或 git initAuthentication failedtoken/密码错误或过期生成新 token、清凭据缓存Permission denied (publickey)SSH 公钥没配或私钥不对ssh -T 验证、检查 config、重新添加公钥refusing to merge unrelated histories两个独立历史相遇谨慎使用 --allow-unrelated-historiesnon-fast-forward本地落后于远端先 pull/rebase 再 pushRPC failed; curl HTTP 500大文件传输问题调大 http.postBuffer、用浅克隆Your local changes would be overwritten本地未提交修改冲突commit 或 stash 后再 pull再补充一个很多人没注意的小问题在 Windows 上使用 Git 时如果命令行是 cmd 而不是 Git Bash某些交互式 rebase/冲突编辑器可能打不开。原因很简单那些工具需要 Unix 风格的环境。建议安装 Git 时把 Git Bash 组件选上之后操作 Git 都从 Git Bash 进会省掉不少莫名其妙的麻烦。5.5 我的一些操作习惯最后分享几个我自己长期坚持的习惯不一定适合所有人但至少能让你在git pull上少受几次折磨。第一动手改代码之前必定先拉一次远端。不管是个人项目还是团队项目这一步能最大限度避免后续冲突。哪怕你昨天刚 clone 下来今天也值得再拉一次因为你不知道别人是不是已经推进了几笔提交。第二拉取之前先git status确认工作区干净。如果不干净我会先 commit 起一个临时提交或者用git stash把修改藏起来等拉取完成后再git stash pop。这个习惯帮我在大量场景里避免了“本地修改被远端覆盖”的恐慌。第三推送之前先拉取而且优先用git pull --rebase。如果你所在分支只有你一个人在动rebase 能保证推送历史是一条漂亮的直线review 的时候体验极佳。如果发现远端已经有你完全不认识的提交那就老老实实 merge先把别人的代码消化再推。第四遇到看不懂的报错先看git status再查资料。Git 很多报错其实都是状态问题比如正处于 merge 中间态、rebase 中间态或者某个 index.lock 文件残留。git status会直接告诉你“当前正处于哪个操作过程中”这是排查一切 Git 异常的第一步。我用 Git 这些年git pull看起来是最简单的一条命令但全维度真正理解它的人并不多。把 fetch、merge、rebase、remote 配置、密钥认证这些底层概念串起来之后你在 GitHub 和 Gitee 之间搬运代码、同步分支、修复冲突都会顺手很多。如果你现在正好卡在某一个报错上照着上面的清单一步步排查大概率能自己搞定。
返回列表