4.4 推送更改(git push)


4.4 推送更改(git push)

本节摘要git push 把本地提交上传到远程,是"让队友看到你的工作"的唯一通道。本节讲清 push 的五个内部步骤与快进式/非快进式推送的区别,重点处理最经典的报错——non-fast-forward:为什么会发生、怎么用 pull 解决。最后覆盖 -u 设置跟踪、--tags、--delete 删除远程分支,以及 --force-with-lease 的安全强推。

学习目标

阅读完本节,你应当能够:

  1. 描述 git push 的内部步骤,说清快进式推送与非快进式推送的区别。
  2. 遇到 non-fast-forward 报错时,冷静用 pull 合并后重新推送。
  3. 用 -u 首次推送新分支并设置跟踪关系。
  4. 用 --tags 推送标签、--delete 删除远程分支。
  5. 解释 --force 的风险,并优先使用 --force-with-lease。

一、问题与直觉

你在本地辛辛苦苦提交了五条,觉得"这些改动应该让团队知道了"。但现实是:只要不 push,远程仓库就对你的工作一无所知。提交只存在你的本地仓库里,像一篇只写在自己日记本上的文章——队友看不到,CI 不触发,发布不包含。

git push 就是"把日记本上的文章投稿给编辑部的过程"。它把本地分支的提交上传到远程分支,让远程仓库跟上你的进度。它是 Git 协作里"输出"的半边天——pull 负责"输入",push 负责"输出",两者构成远程协作的循环。

但 push 不是无脑上传。远程仓库有自己的历史,你的本地历史要"接"上去,必须满足一个条件:你的历史必须包含远程当前的最新提交(或者远程没有你缺的东西)。这个条件不满足时,Git 会拒绝推送——它宁可报错,也不愿让任何人的工作悄悄消失。理解这个"拒绝机制",就是理解 push 的关键。

这个"拒绝机制"初看像阻碍,细想是保护。设想一个没有它的世界:你和同事都在 main 上工作,你推完,他直接推,他的本地没有你的提交,一推就把你的改动覆盖了——无声无息,连报错都没有。Git 的 non-fast-forward 拒绝正是为了防止这种"静默覆盖"。它逼你"先同步、再推送",让所有改动都经过合并或整合,而不是粗暴替换。协作工具的安全,往往就体现在这些"烦人"的拒绝里——它们是对每个人的工作成果的尊重。

二、核心原理

push 的五个内部步骤

执行 git push origin main 时,Git 依次:

  1. 连接远程:通过 origin 的 URL 连到远程仓库。
  2. 比较历史:对比本地 main 与远程 main 的提交历史。
  3. 找出新提交:找出本地有、远程没有的提交。
  4. 传输对象:把这些提交及相关对象上传到远程。
  5. 更新远程指针:如果是快进式,把远程分支指针移到你的最新提交。

第 5 步的"快进式"是关键——它决定 push 是成功还是被拒。

push 成功的前提:你的本地历史是远程历史的"延伸"(包含远程的最新提交)。否则被拒,需要先同步。

快进式推送(Fast-forward)

当远程分支当前指向的提交是你本地历史的祖先时,push 是快进式——远程只需把指针向前移到你的最新提交,没有冲突,直接成功。这是最理想的推送,也是默认预期。

非快进式推送(Non-fast-forward)与处理

如果远程分支有你的本地没有的提交(比如同事在你上次 pull 之后又推了新东西),你的本地历史不再是远程历史的延伸,push 被拒。报错信息很典型:

! [rejected] main -> main (non-fast-forward) error: failed to push some refs to ... hint: Updates were rejected because the tip of your current branch is behind hint: its remote counterpart. Integrate the remote changes before pushing again. hint: See the 'Note about fast-forwards' in 'git push --help' for details.

Git 已经告诉你该怎么办:先整合远程变化再推。标准流程:

git pull origin main # 拉取远程改动并合并 # 如有冲突,解决后提交 git push origin main # 再推,此时通常是快进式

这个"被拒→先拉→再推"的循环,是远程协作里最常见也最重要的一课。它背后的原则是:绝不覆盖别人的提交。Git 用拒绝机制强制你尊重他人的工作,这正是协作安全的核心。

一次完整的"被拒→解决→再推"演示

用两个目录模拟两个人协作,亲手把被拒的循环跑一遍:

# 目录一:模拟同事,先推送 git clone 远程地址 repoA && cd repoA echo a > a.txt && git add . && git commit -m "A 的改动" git push origin main # 目录二:模拟你,基于旧状态开发 git clone 远程地址 repoB && cd repoB echo b > b.txt && git add . && git commit -m "B 的改动" git push origin main # 被拒!remote 已有 A 的提交 # 解决:先拉再推 git pull origin main # 拉取 A 的改动并合并 git push origin main # 成功

这个循环完整再现了"我明明 push 了怎么被拒"的困惑及其解法。亲手跑一遍,你以后再遇到 non-fast-forward 会条件反射地"先 pull"。值得注意 pull 后可能发生合并冲突(3.4 节的内容),那也顺便复习了。

-u:首次推送新分支

推送一条远程还没有的分支时,用 -u(--set-upstream)同时设置跟踪关系:

git push -u origin feature/login

设置后,本地 feature/login 记住"我的上游是 origin/feature/login",以后 git pushgit pull 都可以省略参数。没有 -u 的话,Git 会提示你"没有上游分支",需要每次写全参数。

推送标签与删除远程分支

推送标签(默认 push 不推标签):

git push origin --tags

删除远程分支

git push origin --delete feature/old-branch # 等价旧写法 git push origin :feature/old-branch

删除远程分支后,本地对应的远程跟踪分支也要清理(4.6 节 --prune)。

强制推送的风险与安全版本

普通 push 被拒时,git push --force 可以强行覆盖远程历史。但它极其危险——它会无条件地把远程分支变成你的本地状态,可能抹掉别人推送的提交

更安全的替代是 --force-with-lease

git push --force-with-lease origin main

它只在"远程分支的状态与你上次拉取时一致"时才强推。如果你拉取之后远程被别人推了新东西,它会拒绝,从而保护他人的工作。凡是需要强推的场景,默认用 --force-with-lease,裸 --force 留给"你完全确定远程没别人动过"的极少数情况。

三、工程实践要点

push 前的一分钟检查

  1. git status:确认当前分支和要推送的内容。
  2. git log --oneline:确认要推的是预期的提交。
  3. git pull(若不确定):先同步远程,避免推送被拒。

push 安全原则

  • 推送前先同步:不确定时先 pull 再 push,减少被拒概率。
  • 不推私有内容:密钥、本地配置、不该公开的文件,别推上去(2.3 节提过 .gitignore)。
  • 不强推共享分支:共享分支的历史是团队的共同财产,改写它要用 PR 流程而不是强推。
  • 强推用 --force-with-lease:唯一可接受的强推姿势。

push 的常见误操作场景

场景 正确做法
推错分支 用 push 远程名 正确分支,或删除远程错误分支
推到不存在的远程 先 remote add 或检查 remote -v
被拒 non-fast-forward pull 合并再推,别强推
需要撤回已推送提交 用 revert 出反向提交再推(第 5 章)

push 的常见报错排错表

push 报错五花八门,但按信息归个类,绝大多数都能对号入座:

报错关键词 含义 处理
non-fast-forward 本地落后远程 pull 合并再推,或 --force-with-lease(慎用)
no upstream branch 本地分支没设跟踪 git push -u origin 分支名
Authentication failed 凭据错误 更新令牌/密码,检查是否过期
Permission denied 无写权限 确认是否有推送权限,或改用 PR
Repository not found 地址错误或无访问权 检查远程地址与账号可见性
cannot lock ref 引用锁冲突 等一会重试,或检查是否有并发 push

记住:报错的第一行是"发生了什么",hint 行是"该怎么办"。Git 的报错信息是排错手册而不是天书,按提示逐条走,绝大多数 push 问题都能自己解决。

push 前的最后一次检查清单

推送到共享远程前,花十秒过一遍清单,能挡掉大部分"推了又后悔"的事故:

  1. git status:确认当前分支、确认没有把不该推的文件(密钥、日志)留在暂存区。
  2. git log --oneline -n 3:确认要推的提交确实是要推的那些,消息别写错。
  3. git pull(或先 fetch 对比):确认本地已包含远程最新提交,避免被 non-fast-forward 拒。
  4. 拿不准分支归属:确认要推的分支名和远程名写对,尤其是多远程环境。

这份清单的每一条都指向一个真实事故:密钥误推、提交内容错、推错分支、强推覆盖同事提交。push 是"出门",出门前检查一遍总比在路上返工便宜。

⚠️ 常见坑:git push --force 覆盖了同事的提交,事后才发现。强推永远是最后手段,优先 --force-with-lease,且与团队沟通后再做。

💡 关键直觉:把 push 想成"往共享工作台放文件"。快进式是"台子上没别人东西,直接放上去";非快进式是"台子上已经有别人的文件,你硬放会把它们盖住"——Git 拦住你,让你先看看别人的文件(pull),再决定怎么摆。

常见问题与 FAQ

"为什么我提交了但远程没变化?" 因为 commit 只写本地,必须 push 才上传。三步链:改 → commit → push,缺了 push,远程永远不知道你的工作。

"push 被拒是不是我做错了什么?" 不是。被拒是远程保护机制在起作用——防止覆盖别人的提交。按提示 pull 合并再推,是标准且安全的行为。

"--tags 和单独推标签有什么区别?" --tags 一次性推所有本地标签;git push origin 标签名 只推指定标签。想精确控制用后者,图省事用前者。

"push 很慢怎么办?" 大仓库推送慢,可能是有大二进制文件或历史积压。检查是否有大文件误入仓库(git 社区用 LFS 管理大文件),或考虑压缩对象。

"push 能推多个分支吗?" 能,git push origin 分支A 分支B 一次推多个;git push --all origin 推所有本地分支(慎用,协作环境一般不推荐,容易把不想公开的分支也推上去)。

"push 之前一定要 pull 吗?" 不一定。如果你的本地分支领先远程且远程没有新提交,直接 push 就是快进式,无需 pull。只有当你怀疑远程有别人推的新提交时才需要先 pull——这也是"push 前先同步"这个习惯的由来。

"为什么我 pull 之后 push 还是被拒?" 可能 pull 产生了合并,但之后又有别人推送;或者你 pull 的是别的分支。排查:git status 看 ahead/behind 状态,git log origin/main 看远程最新,确认本地确实包含了远程的最新提交再推。

push 与 PR/MR 的关系

理解了 push,还要知道它和平台协作功能(Pull Request / Merge Request)的分工,否则会困惑"我明明推了,为什么代码没进 main"。

在大多数团队的实践中:

  1. 你在本地 feature 分支开发,git push origin feature/login 把分支推到远程。
  2. 在平台上基于这条远程分支发起 PR/MR,请求把它合入 main。
  3. 团队成员在 PR 里审查、讨论、跑 CI。
  4. 审查通过后,在平台上点击"合并",改动正式进入远程 main。

关键认知:push 只负责"把分支送到远程",不负责"合并进 main"。合并动作由 PR 流程(或直接 merge)完成。所以"推了却没进 main"不是 bug,而是流程设计——把"共享代码"和"合入主线"两个动作分开,让审查有机会介入。这是团队工程实践里 push 最常见的真实用法。

要点串联

  • push 本质:上传本地提交到远程,是协作的"输出"通道。
  • 五个步骤:连接、比历史、找新提交、传对象、更新指针。
  • 快进式:远程历史包含于本地,直接更新指针,成功。
  • 非快进式:远程有本地没有的提交,被拒,先 pull 再推。
  • -u 跟踪:首次推送新分支用 -u,之后 push/pull 免参数。
  • 标签与删除:--tags 推送标签,--delete 删除远程分支。
  • 强推纪律:--force 危险,优先 --force-with-lease 保护他人工作。
  • 拒绝机制:non-fast-forward 是防静默覆盖的保护,不是 bug。
  • 被拒循环:被拒 → pull 合并 → 再推,远程协作必修课。
  • push 与 PR 分工:push 送分支到远程,PR 决定是否合入主线。
  • 安全原则:推送前同步、不推私有内容、不强推共享分支。
  • 多分支推送:push 可推多个分支,--all 慎用。

下一节逆向操作:git pull 与 git fetch——把远程的更新拿到本地来。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U