☰
6篇文章讲清楚Git:Git 远程协作:fetch、pull、push 到底有什么区别?(3/6)
2026/9/27 2:49:43 网站建设 项目流程

前面两篇讲的都是本地的事,但代码最终是要和别人交换的,这就轮到远程仓库了。刚开始用的时候最容易被三个长得很像的名字绕晕:main、origin、origin/main,所以这一篇先把这三个东西掰开。

三个名字要分清

名字是什么在哪
origin远程仓库的别名只是本地的一个名字,指向一个 URL
main本地分支,我们自己的本地,.git/refs/heads/main
origin/main远程跟踪分支,我们对远程状态的缓存本地,.git/refs/remotes/origin/main

origin是git clone的时候自动起的默认名字,想改或者想加别的远程都可以:

gitremote-v# 看现在有哪些远程gitremoteaddupstream https://...# 加一个叫 upstream 的远程gitremoterenameorigin old-origin# 改名

origin/main 不会自己更新

这是这一篇里最需要理解的一点:origin/main是一个本地文件,它记录的是"上次和远程通信时,远程的 main 长什么样"。

它不会自动跟着远程变。别人往远程推了新提交,我们本地的origin/main还是老样子,直到我们跑一次网络命令(fetch或pull)去更新它。这就引出了fetch的重要性:它是唯一一个专门用来同步这个本地缓存的命令。

上游分支

本地分支可以和某个远程分支建立一条"跟踪关系",被跟踪的那个就叫上游分支(upstream)。

gitpush-uorigin main# -u 是 --set-upstream 的简写

设好之后有两个好处:直接敲git push或者git pull就够了,不用每次都写origin main;而且git status和git branch -vv会告诉我们"领先几个、落后几个":

gitbranch-vv
* main ae04634 [origin/main] alice 的提交

方括号里的[origin/main]就是上游分支。如果本地和远程有差距,这里还会直接显示出来:

* main ae04634 [origin/main: behind 1] alice 的提交

behind 1是说本地落后远程 1 个提交,ahead 2是说本地领先 2 个。每次git status的时候先扫一眼这行,能省掉很多"为什么推不上去"的困惑。

如果是已经存在的分支想补一个上游:

gitbranch-uorigin/main# 给当前分支设gitbranch-uorigin/main mybranch

fetch 和 pull

这两个是最容易混的一对,其实关系很简单:

git pull = git fetch + git merge

fetch:只下载,不动我们的代码

gitfetch origin

它做两件事:从远程下载本地没有的对象(提交、文件内容),然后更新origin/main这个远程跟踪分支的指向。它不会碰我们的工作区,也不会碰我们的main分支。

具体看一遍就清楚了。假设同事 Bob 往远程推了一个提交,我们本地还不知道:

gitlog--onelineorigin/main
ae04634 alice 的提交

还是旧的。跑一次 fetch:

gitfetch origingitlog--onelineorigin/main
bb2f7d5 bob 的提交 ae04634 alice 的提交

origin/main更新了。再看我们的本地分支:

gitbranch-vv
* main ae04634 [origin/main: behind 1] alice 的提交

main纹丝没动,还是ae04634,只是现在 Git 明确告诉我们"你落后远程 1 个提交"。

这就是 fetch 的价值所在:下载和合并是两个独立的动作,fetch 只做前一半。所以可以用它来安全地看看远程有什么变化,看完再决定怎么办:

gitfetch origingitlog--onelinemain..origin/main# 远程比我多了哪些提交gitdiffmain origin/main# 具体差了什么

pull:下载并合并

gitpull origin main

等价于先fetch再merge origin/main。图省事,但它一步就合并进来了,如果这时候本地有一堆没提交的改动,或者有冲突,会被打断在合并的中间状态里。所以更稳的做法还是分开:

gitfetch origingitlog--onelinemain..origin/main# 先看看来的是啥gitmerge origin/main# 再决定合不合

pull 用 merge 还是 rebase

git pull默认用 merge,会在历史里留下一个合并提交。如果本地只是有几个小提交、不希望历史变乱,可以改成 rebase:

gitpull--rebaseorigin main

想一次性设置好:

gitconfig--globalpull.rebasetrue# 全局默认用 rebasegitconfig--globalpull.rebase merges# 保留已有的合并提交,其余 rebasegitconfig--globalrebase.autoStashtrue# rebase 前自动 stash 未提交的改动
方式结果
git pull(默认 merge)产生一个合并提交,历史有分叉
git pull --rebase把本地提交搬到远程最新之后,历史是直线

选择的依据和 上一篇里 merge 与 rebase 的原则是一样的:如果这些本地提交还没推送给别人,用 rebase 把历史捋直是安全的;如果已经推送上去了,那就老实用 merge。

push

gitpush origin main# 推到远程的 maingitpush# 有上游分支时可以省略参数gitpush-uorigin main# 第一次推,顺便设好上游gitpush origin HEAD# 推到远程的同名分支

push的本意是把本地的提交送到远程,并让远程分支指向新位置。但它有一个很硬的限制:只接受快进。

推送被拒绝

最常见的报错长这样:

gitpush origin main
To /tmp/gitlab/remote.git ! [rejected] main -> main (non-fast-forward) error: failed to push some refs to '/tmp/gitlab/remote.git' hint: Updates were rejected because the tip of your current branch is behind hint: its remote counterpart. If you want to integrate the remote changes, hint: use 'git pull' before pushing again. hint: See the 'Note about fast-forwards' in 'git push --help' for details.

关键信息是non-fast-forward,它的意思是:在我们上次同步之后,远程已经有了新的提交,而我们本地的分支没有包含它们。

为什么不允许推?因为如果我们硬推上去,远程上别人那些提交就会被覆盖掉(在远程的视角看就是"这些提交突然没了")。Git 拒绝这个操作,是在保护别人的工作。

处理办法就按提示来:

gitfetch origingitlog--onelinemain..origin/main# 看看别人提交了什么gitrebase origin/main# 或者 git merge origin/maingitpush

先把自己的工作接在别人的提交后面,然后就能正常推了。

只有确认远程那些提交真的该被丢弃(比如自己误推了一个不该推的东西),才能考虑强推。而且强推要用--force-with-lease而不是--force,这两个的区别在下一篇会阐述

push 的完整语法

gitpush<远程名><本地分支>:<远程分支>

冒号两边的形式可以有以下几种形式:

gitpush origin main# 省略冒号:推同名分支gitpush origin main:dev# 把本地 main 推到远程的 dev 分支gitpush origin :dev# 本地留空 = 删除远程的 dev 分支gitpush origin--deletedev# 同上,更易读的写法

远程分支的删除和清理

删除远程分支

gitpush origin--deletefeature

这个命令删的是远程的feature分支。注意删完之后我们本地可能还留着一个origin/feature,因为它也是本地文件。

清理已经不存在的远程跟踪分支

同事把远程分支删了,我们本地跑git branch -a还是能看到它,因为这些origin/xxx是本地缓存,不会自己消失。清理有两种方式:

gitfetch--prune# 拉取的同时,把远程已删除的分支从本地缓存里清掉gitremote prune origin# 单独做这件事

想让它成为默认行为:

gitconfig--globalfetch.prunetrue

这里有三个名字需要额外解释一下,分开看是:

  • git branch -d feature删的是本地分支
  • git push origin --delete feature删的是远程分支
  • git fetch --prune清理的是本地缓存的远程跟踪分支(origin/feature)

三者是互相独立的,删了远程分支不会自动清掉本地的缓存,反过来也一样

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询