ARTICLE DETAIL

资讯详情

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

解开Git clone与pull的区别:从原理到工程实践一次讲透

解开Git clone与pull的区别:从原理到工程实践一次讲透 git clone 和 git pull 是日常用 Git 时出现频率最高的两条命令但我敢说十个人里至少有三四个没真正想清楚它们到底有什么区别。很多人只是背答案第一次拉代码用 clone以后更新用 pull。听起来没错可一旦遇到报错、分支合并、认证失败这种粗糙理解根本帮不上忙。我曾经带过一个项目新人入职按文档操作在已经初始化过git init的目录里直接执行git pull origin main结果 Git 提示fatal: refusing to merge unrelated histories另一次另一个同事反复用 clone 拉取同一个仓库本地堆了好几个带(1)(2)的目录还来问我哪个才是最新代码。这些尴尬都能追溯到同一个问题没有理解 clone 和 pull 的作用边界。这篇文章我不想复述官方文档而是从实际工程场景出发把 git clone、git pull 到底是什么、背后做了什么、什么时候该用谁、常见的认证和网络报错怎么处理一次讲透。适合刚接触 Git 的新手也适合带新人的小组长拿去做内部培训材料。1. 两个命令的定位一个管搬家一个管补货1.1 各自的人设初始化仓库 vs 增量同步git clone 负责的是从零到一当本地还没有这个仓库的时候你拿到一个远程地址用 clone 把仓库完整复制到本地。它会自动创建一个新目录在新的目录里初始化.git设置远程地址origin然后把所有 Git 对象和文件拉下来最后帮你切到默认分支。整个过程不需要你提前做什么准备甚至你本地电脑上连 Git 都还没初始化过clone 也会帮你把一切都搞定。git pull 负责的是从一到 N前提是你本地已经有一个仓库并且仓库里已经配置了远程地址也有一条正在跟踪远程分支的本地分支。pull 会把远程仓库里新增的提交拉下来合并到你当前所在的分支上。它不会创建新的目录不会重新初始化仓库也不会把已经存在的文件再复制一遍。所以从角色的角度来说clone 是初始化操作pull 是更新操作。你可以在同一台电脑上对一个仓库执行一百次 pull但正常情况下一个仓库只会被 clone 一次。1.2 用搬家和补货来理解我经常用这个类比给团队新人解释clone 就像把整间店铺从城市的另一边搬到你现在的位置货架、装修、仓库里的全部库存、历年的账本全都搬过来然后在新的地址重新开张。pull 则像是这家店已经开起来了每天从总仓补一次货把新到的商品放进店里已有的陈列不会打乱重来。如果你在一个已经开店营业的仓库里再 clone 一次相当于你在隔壁重新开了一家一模一样的店两家店各自独立、后续互不干扰。这通常不是你想要的因为它没有把新货补进原来的店反而产生了一份完全独立的副本。这种理解看起来简单却能解释很多实际问题。比如为什么你在一个已经 clone 过的项目目录里执行git clone会报错因为那个目录已经存在同名目录或.gitclone 不愿意在非空目录里强制覆盖。再比如为什么你在项目里反复执行 pull 不会产生一堆冗余目录因为 pull 是沿着原先那条仓库关系链路做增量同步。1.3 一张表看明白关键差异对比项git clonegit pull使用前提本地还没有这个仓库本地已有仓库且配置了远程地址执行位置在目标目录的父目录执行会自动建新目录在已有仓库目录内执行是否创建仓库会初始化 .git 并配置 origin不会只是更新对象和分支引用获取范围通常是整个仓库历史和所有分支/Tag只拉取当前分支相关的远程提交合并行为不合并直接检出默认分支会把拉取结果合并到当前分支能不能反复执行一个项目正常只 clone 一次可以无限次执行是日常更新手段典型报错目录非空、无权限、网络不通无上游分支、本地修改冲突、合并冲突这张表的价值在于当你面对一条奇怪的 Git 报错时先问一句我在做的是搬家还是补货思路会立刻清晰很多。2. 原理拆解clone 是复制整个仓库pull 是拉取并合并提交2.1 clone 到底在背后做了什么执行git clone https://github.com/example/repo.git时Git 并不是只把当前目录里的文件下载下来那么简单。它会经历一个完整的仓库复制流程第一在本地创建一个新目录目录名默认是仓库名比如 repo除非你在命令后面显式指定目录名。第二在这个新目录里初始化.git把远程地址写入配置默认远程名是origin。第三从远程服务器上把所有 Git 对象拉下来包括所有提交、所有分支引用、所有 Tag、所有历史版本对应的文件内容。第四在本地创建远程跟踪分支比如refs/remotes/origin/main、refs/remotes/origin/develop。第五根据远程仓库的 HEAD 指示的默认分支创建一个本地分支比如 main/master并设置上游跟踪关系。也就是说clone 之后你本地拥有的不是远程仓库的一个快照而是一个完整的、独立的 Git 仓库。即便远程服务器之后挂了只要本地这个目录还在你仍然拥有完整历史可以提交、切换分支、回滚。如果你不想把历史全部拉下来可以限制 clone 范围# 只拉取最近 1 次提交适合快速查看 git clone --depth 1 https://github.com/example/repo.git # 只克隆指定分支不关心其他分支 git clone --branch main --single-branch https://github.com/example/repo.git浅克隆和单分支克隆在后续 pull 时可能出现一些限制比如从浅克隆浅层无法直接 push 到受保护分支或者执行某些需要完整历史操作时提示缺少对象。这时候需要git fetch --unshallow补全历史。2.2 pull 其实是 fetch merge 的组合拳git pull 看着是一条命令实际由两步组成先是git fetch把远程仓库里当前分支相关的提交和对象下载到本地更新origin/main这类远程跟踪分支然后git merge把下载下来的远程跟踪分支合并到你当前所在的本地分支。举个例子你在 main 分支上执行git pull等价于手动运行这两条命令git fetch origin git merge origin/main为什么 Git 要拆成 fetch 和 merge 两步因为不是每次都想合并。如果你只想看看远程有没有新提交、先对比一下差异还不想动工作区代码那执行git fetch就够了。执行完后你可以看origin/main和本地main差多少甚至直接git log HEAD..origin/main查看别人提交了什么。所以理解了 pull fetch merge你就能理解很多衍生问题为什么有时候 pull 会直接覆盖某个文件因为 merge 会把远程分支的内容合进当前分支如果本地没有冲突文件自然会被更新。为什么有时候 pull 会进入 merge conflict 状态因为 fetch 完后的合并撞到了本地已有提交的冲突区。为什么有时候 pull 会拒绝执行因为当前分支没有设置上游分支Git 不知道要从哪个远程分支拉取。2.3 别忘了上游分支这个概念clone 和 pull 的区别还藏在分支的跟踪关系里。clone 完成之后Git 会自动把本地主分支和origin/main关联起来这样你直接敲git pull时Git 才能知道该从哪里拉。如果你不是 clone 出来的仓库而是手动执行了git remote add origin https://example.com/user/repo.git git branch --set-upstream-toorigin/main main这样设置完跟踪关系之后git pull才能正常使用。否则 Git 会提示There is no tracking information for the current branch. Please specify which branch you want to merge with.这个问题本质上是你把 fetch 和 merge 的条件没有配置好。clone 帮你把这些全部自动配置好了这就是为什么新项目第一件事永远是 clone而不是手动 init remote add pull。我还喜欢用git remote show origin或者直接看.git/config来确认跟踪关系cat .git/config里面的内容大概是[remote origin] url https://github.com/example/repo.git fetch refs/heads/*:refs/remotes/origin/* [branch main] remote origin merge refs/heads/main看到branch.main下面有remote和merge就说明当前 main 分支有上游分支。这些配置理解透了你对 pull 的掌控力会完全不一样。3. 实际项目里怎么选场景决定命令3.1 新环境、新任务只要本地还没有仓库就用 clone最常见的场景你电脑刚刚重装或者新加入一个项目需要把代码拉下来。这时候用 clone 是唯一合理的选择。不要把git init和git remote add用得那么复杂除非你在做特殊操作。一个干净利落的初始化流程是cd ~/workspace git clone https://github.com/example/repo.git cd repo git branch -a执行完git branch -a你会看到本地分支、远程分支、远程跟踪分支的全貌。如果是带分支的开发任务可以直接通过远程分支创建本地分支git checkout -b feature/xxx origin/feature/xxx这种用法本质上也是基于 clone 后完整的远程引用列表。需要提醒的是clone 的目标目录必须是不存在或空的。如果你在一个已有代码的非空目录里git cloneGit 会拒绝并提示目录非空。这时候不要强制用git clone url .去硬塞先确认目录里的代码到底有没有保留价值。3.2 已有仓库需要更新用 pull 保持本地跟上远程当本地仓库已经存在远程仓库出现了新的提交你想把最新的代码合到本地继续开发这个操作就交给 pull。日常最常见的流程是git status git pullgit status是为了确认工作区是干净的。如果本地有未提交修改而 pull 会覆盖这些文件Git 会主动中止这时候不要慌先处理本地修改或者 stash再重新 pull。在多人协作时我强烈建议给自己设定一条纪律每天开始工作前先git pull一次结束工作提交前再git pull一次。这样做可以最大程度减少合并冲突。如果你发现自己连续一个星期都没 pull等最后一次性 pull 时几乎一定会遇到冲突而且那些冲突还很难定位。3.3 多人协作分支merge、rebase、ff-only 怎么选git pull 的核心是合并所以 pull 的合并策略也会直接影响你的提交历史。默认行为是 merge也就是在本地分支分叉时生成一个 merge commit。如果你希望历史尽量线性可以用git pull --rebase。如果你希望完全禁止分叉合并可以用git pull --ff-only。三者的关系我整理成了表策略命令结果适用场景默认 mergegit pull有分叉时会生成 merge commit历史出现分叉团队约定用 merge 保留合并轨迹rebasegit pull --rebase本地提交重放到远程最新提交之后历史线性个人开发分支、希望历史简洁fast-forward onlygit pull --ff-only只能快进合并不能快进就报错自动化部署、需要无 merge commit 的环境--ff-only的意思是只有当本地分支可以直接 fast-forward 到远程分支最新位置时才执行 pull。比如本地 main 在 A 点远程 main 没有其他分叉提交只是前进到了 B 点那么本地可以直接快进到 B。如果本地 main 也有自己独有的提交和远程分支形成了分叉--ff-only会直接失败告诉你不能快进。这其实是个非常好的保护机制。有些 CI/CD 流程或者自动化脚本不希望出现 merge commit用git pull --ff-only可以在代码部署前及时发现本地和远端已经分叉这件事。3.4 想保持提交历史干净试试 pull --rebase我在团队里观察到很多开发者的主分支历史慢慢变成一团乱麻很大一部分原因是每次 pull 都默认 merge遇到分叉就啪地生成一个 merge commit。时间一长git log里全是Merge branch main of ...这种鸡肋提交。更优雅的做法是 pull 时用 rebasegit pull --rebase origin main这条命令等价于git fetch origin git rebase origin/main它会把本地独有但尚未推送到远程的提交一个个摘下来然后放到远程最新提交的后面再按顺序重新应用。这样你的提交历史就是一条直线没有多余的 merge commit。但 rebase 也有代价它会改写提交哈希。如果本地这些提交已经被别人 pull 走了你再去 rebase 会产生分叉导致别人下次 pull 时出现一堆重复提交。所以我的原则是个人分支随便 rebase共享的主分支不要轻易 rebase尤其在提交已经 push 到远端之后。如果你希望团队整体避免踩坑可以在.gitconfig里做一个约定git config pull.rebase true这样每次git pull默认就是 fetch rebase而不是 fetch merge。当然这个配置不一定是所有人的偏好需要在团队内部达成共识。4. 认证和网络clone 与 pull 都绕不开的坑4.1 账号密码、Token、SSH Key到底怎么选clone 和 pull 都需要访问远程仓库认证方式决定了你能不能成功。现在主流的认证方式有三种账号密码、Personal Access Token、SSH Key。账号密码这种方式在很多代码托管平台上已经被逐步淘汰。比如 GitHub 从 2021 年开始就不再支持账号密码作为 Git 操作认证只支持 Token 或者 SSH Key。如果你还在用密码认证很容易遇到Authentication failed或者 no support authentication 这类报错。Token 的方式相对简单以 HTTPS URL 为例可以在地址里直接带 Tokengit clone https://username:tokengithub.com/example/repo.git但是把 Token 明文写在 URL 里很危险一旦历史被分享出去Token 就暴露了。更推荐的方式是用 Git 的 credential helper 缓存凭据git config --global credential.helper store因为 store 是明文存储如果安全意识更高可以使用manager-core或者系统自带的 keychain。实际开发中我更喜欢用 SSH Key因为配置好之后 clone 和 pull 都不会有输入凭据的烦恼而且 SSH Key 的粒度可以通过部署密钥来控制权限更清晰git clone gitgithub.com:example/repo.gitSSH 方式需要本地生成密钥然后把公钥配置到平台账号下。如果公司有内网 GitLab通常也都是用 SSH 或内部 HTTP 方式。4.2 clone 时报错 no support authentication 怎么办这个报错非常典型尤其是git clone code 报错 no support authentication这类场景。我在排查时一般按这个顺序来先看下远程仓库地址到底长什么样git remote -v如果 URL 里带了特殊字符尤其是密码里有、%等字符Git 解析 URL 时会把它们当成普通分隔符导致认证信息被截断。解决办法是使用 URL 编码替代特殊字符或者改用不带密码的 URL再通过 credential helper 提供凭据。再看认证类型。如果你用的是 HTTPS 和账号密码但服务端已经禁用了密码认证那就必须换成 Token 或者 SSH。生成 Token 时要注意权限范围比如 GitHub 的 Token 至少需要勾选repo权限GitLab 需要read_repository或write_repository权限。最后检查本地是否缓存了错误的凭据。已经通过git config --global credential.helper store或者其他方式缓存的旧 Token会导致每次请求都带着过期 Token明明是正常地址也认证失败。这时候需要删除或更新凭据缓存。如果使用 Git Credential Manager可以在系统凭据管理器里搜git把对应的条目删掉。4.3 clone 失败网络问题与镜像/浅克隆的取舍网络不稳定是 clone 失败的另一大原因尤其仓库体积大的时候特别明显。常见的报错包括error: RPC failed; curl 56 OpenSSL SSL_read: Connection was reset, errno 104 fatal: unable to access https://github.com/...: Failed to connect to github.com port 443: Connection timed out遇到这种问题第一反应不要跳到换网络或等网络恢复可以先尝试调整 Git 本身的网络参数。比如增加 postBuffer 可以缓解大仓库传输过程中缓冲区不足的问题git config http.postBuffer 524288000意思是把 HTTP 传输缓冲区调到 500MB。多数 RPC failed 问题都能通过这个参数缓解。如果单位时间内传输速度过慢导致 Git 自动断开可以设置git config http.lowSpeedLimit 0 git config http.lowSpeedTime 999999这样 Git 不会因为传输速度慢而主动中断。当然这只是绕过网络波动的临时手段如果仓库本身有几百 MBclone 本质还是要下载完所有历史所以再快的网络也会卡在体积上。更聪明的做法是先用浅克隆把工作区还原出来git clone --depth 1 https://github.com/example/repo.git浅克隆只拉取最近一次提交信息量很小网络波动的影响也会小很多。后续如果真的需要完整历史再补git fetch --unshallow。日常开发只需要看当前代码的话这种方案非常香。对于巨型 monorepo你还可以结合稀疏检出只拉取你关心的子目录git clone --filterblob:none --sparse https://github.com/example/monorepo.git cd monorepo git sparse-checkout set apps/backend这样 git 不会下载所有历史版本的文件 blob只会在需要时按需下载指定路径的内容。这种方式和 clone 之后再用 pull 增量更新是绝配前期下载体积能小很多后续 pull 依然按正常流程工作。如果项目在企业内网平台比如 GitLab 或者 Gerrit通常会有内网域名或镜像地址。clone 前先确认你拿到的地址是不是最接近你的那个地址别绕远路。5. 高频报错与避坑手册5.1 refusing to merge unrelated histories 的两种场景fatal: refusing to merge unrelated histories是新手最容易踩的坑而且往往就是把 clone 和 pull 概念混淆的直接结果。第一种场景本地已经有一个git init过的目录你在里面加了远程地址然后直接执行git pull origin main。Git 发现本地仓库的历史和远程仓库的历史没有任何共同祖先于是拒绝合并。第二种场景你手动把两个本来不相关的仓库合并到一起也会遇到同样的报错。解决方法是在 pull 命令后加一个参数git pull origin main --allow-unrelated-histories但我要提醒一句这个参数是强制合并的意思不是帮我解决冲突的意思。如果你只是想要一个本地已有内容的仓库去同步一个远程仓库更合理的操作是放弃当前这个本地仓库结构把目录里的重要文件备份出来然后重新git clone远程仓库再把备份的改动逐步应用上去。这样才不会得到一份历史完全混乱的项目。5.2 pull 拒绝执行本地有未提交的修改还有一种高频场景早上打开项目直接拉代码git pull结果提示error: Your local changes to the following files would be overwritten by merge这是因为本地某个文件有未提交的修改而 pull 要更新的内容会覆盖这个文件。Git 为了保护你的工作成果选择中止操作。此时不要用git checkout .或git reset --hard暴力放弃除非你确定这些修改不重要。正确操作是先暂存翻天覆地的修改git stash git pull git stash popgit stash会把本地未提交的修改放进一个暂存区工作区变成干净状态pull 就能顺利执行。pull 完成后再用git stash pop把你的修改取回来。如果取回来时出现冲突Git 会像普通合并冲突一样标记文件你手动解决后再git add和git commit就行。我个人的习惯是执行 pull 前必看一眼git status。宁可多花三秒也好过 pull 到一半被拒绝手忙脚乱。5.3 clone 太慢或者仓库太大浅克隆和稀疏检出了解一下前面已经提过--depth 1和--sparse这里再展开说说。日常 clone 一个普通仓库一秒两秒就结束了大家没什么感知。但遇到动辄几个 GB 的 monorepo原本的 clone 可能要十分钟以上还容易断线。浅克隆的核心思路是只保留最近 N 次提交git clone --depth 1 https://github.com/example/repo.git--depth 1就是只拉最近一次提交。因为所有历史版本的文件快照都没有被拉取体积会急剧下降。后续执行 pull 时Git 会尝试以这个浅层状态为基础做增量更新。如果某个时刻你需要更深的历史可以git fetch --deepen 50这不是把整个历史全部拉回来而是再加深 50 次提交。如果仓库很大但目录结构清晰稀疏检出更适合你。典型操作git clone --filterblob:none --sparse https://github.com/example/monorepo.git cd monorepo git sparse-checkout set apps/web-server这条命令的含义是clone 的时候不要下载所有 blob 对象也不要检出全部目录只在工作区里保留apps/web-server这个目录。后续如果想添加新目录git sparse-checkout add docs/team可以看到clone 本身是支持很多高级参数的。它不是一个笨拙的全量下载而是可以按需裁剪。理解这些参数之后clone 和 pull 的配合会更灵活clone 时做一次最小化准备pull 时按需更新当前需要的路径整个工作流既省流量又不丢失完整性。5.4 一个小技巧用 remote -v 和 status 快速定位问题排查 Git 问题最重要的不是背命令而是先获取足够的信息。我在任何 Git 操作失败后第一件事查看两条输出git remote -v git statusgit remote -v告诉你当前仓库和哪个远程地址关联以及走的是 HTTPS 还是 SSH。这一步能直接排除拉错仓库或者改错地址的问题。git status告诉你当前分支、跟踪信息、工作区是否干净。在此基础上再看报错十有八九都能猜出原因。比如 clone 和 pull 的混淆很多时候就是git remote -v完全没有输出说明你不在一个正常关联远程的仓库里却想执行 pull。那就不是 pull 的问题而是仓库初始化的问题解决思路应该是重新 clone 或正确添加 remote。写在最后的个人经验用了十多年 Git我的体会是clone 和 pull 的差别根本不需要靠死记硬背关键是要在大脑里建立起仓库状态的概念。输入命令前先想清楚我现在这个目录是不是一个已经初始化且关联远程的仓库如果答案是是才有资格谈 pull如果不是直接 clone。我自己的习惯是每次进入新项目不管界面工具多方便都会先在终端跑一次干净的 clone然后把常用的 pull 参数固定下来。比如在个人项目里设置git config pull.rebase true在主分支上偶尔用git pull --ff-only做部署更新遇到冲突就老老实实解决不偷懒用--force。这套流程不够炫技但足够稳尤其适合带团队的时候能让所有人都少走弯路。如果你身边也有人分不清 clone 和 pull不妨把这里提到的报错场景分享给他。真正踩过一次unrelated histories或者一次local changes would be overwritten比背十遍文档都记得牢。希望这篇东西能帮你少踩几个 Git 的坑也让你的协作流程顺畅一点。
返回列表