远程仓库与协作
本章要点
- 远程仓库的本质:一份用于同步的"公共副本"
- 远程操作全家桶:clone / remote / fetch / pull / push
fetchvspull的区别(重要)- 上游分支(upstream)与跟踪关系
- 推送被拒绝(non-fast-forward)的处理
- 标签(tag)与发布
远程仓库是什么
text
┌─────────────────────────┐
│ 远程仓库(origin) │
│ GitHub / GitLab / Gitee │
└──────────┬──────────────┘
│ push / pull / fetch
┌────────────┴────────────┐
▼ ▼
你的本地仓库 同事的本地仓库- 远程仓库 = 一份用于大家同步的公共副本,不是"权威",只是"约定俗成的中心"。
clone时 Git 自动把远程仓库命名为origin。
管理远程仓库
bash
$ git remote -v # 查看远程仓库地址
# origin https://github.com/user/repo.git (fetch)
# origin https://github.com/user/repo.git (push)
$ git remote add origin <url> # 添加远程仓库
$ git remote set-url origin <新url> # 修改远程地址
$ git remote remove origin # 移除远程仓库
$ git remote rename origin upstream # 重命名fetch:只下载,不合并
bash
$ git fetch origin # 把远程最新提交下载到本地,但不动工作区- fetch 后远程的新提交出现在
origin/main之类的远程跟踪分支上。 - 你的工作区、本地分支完全不受影响。
- 想对比/合并远程内容时再手动操作:
bash
$ git log origin/main..HEAD # 本地领先远程的提交
$ git merge origin/main # 把远程内容合并进当前分支💡 fetch 是"只读"操作,任何时候都安全,可以随便执行。
pull:fetch + merge
bash
$ git pull # 等价于 git fetch + git merge
$ git pull origin main # 明确指定拉取来源分支- 一步完成"下载远程更新 + 合并进当前分支"。
- 可能触发合并冲突(解决方式同第 4 章)。
bash
$ git pull --rebase # 用 rebase 方式整合(见第 7 章,团队常配置为默认)⚠️ 理解 fetch 与 pull 的区别是面试高频题: fetch 只下载不改动你的工作;pull 会直接把远程内容合并进当前分支(可能冲突)。
push:把本地提交推上去
bash
$ git push # 推送到当前分支的"上游分支"
$ git push origin main # 明确推送到 origin 的 main
$ git push -u origin main # -u 设置上游分支(第一次推送时用)上游分支(upstream)概念
- 本地分支可以"跟踪"一个远程分支,即建立 upstream 关系。
- 建立后,直接
git push/git pull无需再写远程名和分支名。
bash
$ git push -u origin feature/login # 第一次推送并建立跟踪
$ git branch -vv # 查看跟踪关系
# * feature/login 8f4a2c9 [origin/feature/login] feat(login): ...推送被拒绝:non-fast-forward
如果远程已经有了你本地没有的提交(同事先推送了),push 会被拒绝:
text
$ git push
! [rejected] main -> main (non-fast-forward)
error: failed to push some refs to ...
hint: Updates were rejected because the remote contains work that you do
hint: have locally. This is usually caused by another repository pushing
hint: to the same ref.正确处理流程(先拉后推):
bash
$ git pull --rebase origin main # ① 拉取远程更新,把自己的提交垫到最上面
# (如有冲突则解决,然后 git rebase --continue)
$ git push # ② 再推送⚠️ 不要用
git push --force强行覆盖远程(除非你清楚自己在做什么,见 5.7 与第 7 章)。
协作中的常见场景
场景一:同事推送了新代码,我怎么拿到
bash
$ git pull # 一步到位
# 或分步(更可控)
$ git fetch
$ git log --oneline origin/main..HEAD # 先看自己领先多少
$ git merge origin/main场景二:我推不上去(别人先推了)
bash
$ git pull --rebase # 拉取 + 把我的提交重放到远程最新之上
$ git push # 重新推送场景三:远程删除了一个分支,本地还有残留
bash
$ git fetch --prune origin # 清理已不存在的远程分支引用
$ git branch -r # 查看远程分支场景四:Fork 协作(开源项目常见)
text
别人的仓库(upstream)──fork──► 你的远程仓库(origin)
│ clone
▼
你的本地仓库bash
$ git remote add upstream <原仓库url> # 把原仓库加为 upstream
$ git fetch upstream
$ git checkout -b fix-xxx upstream/main # 基于 upstream 最新代码开分支
# ...开发提交...
$ git push origin fix-xxx # 推到自己的远程
# 然后去平台发起 Pull Request(见第 9 章)标签(Tag):给提交打"里程碑"
标签用于标记发布版本(v1.0.0 等),分为:
- 轻量标签:只是一个指针。
- 附注标签:包含打标签者、时间、说明,可签名(推荐用于发布)。
bash
$ git tag # 列出所有标签
$ git tag v1.0.0 # 创建轻量标签
$ git tag -a v1.0.0 -m "第一个正式版本" # 创建附注标签
$ git tag -a v1.0.0 <提交哈希> # 给历史提交打标签
$ git push origin v1.0.0 # 推送单个标签
$ git push origin --tags # 推送所有标签
$ git tag -d v1.0.0 # 删除本地标签
$ git push origin :refs/tags/v1.0.0 # 删除远程标签
$ git checkout v1.0.0 # 检出标签(处于 detached HEAD,详见 6.4)💡 发布流程惯例:代码合并到 main → 打
v版本号标签 → push 标签 → 触发 CI/CD 构建发布。
force push 与它的风险
git push --force(-f)用本地提交覆盖远程历史:
bash
$ git push --force origin feature/login- ⚠️ 会重写远程历史,如果别人基于旧历史开发,会造成混乱。
- ⚠️ 永远不要对共享分支(main/master/develop)force push。
- 只对自己的特性分支使用,且提前与团队沟通。
- 更安全的替代:
git push --force-with-lease——只在远程没有被别人动过时才覆盖,否则拒绝(强烈推荐用它替代--force)。
常见远程错误与解决
| 错误提示 | 原因 | 解决 |
|---|---|---|
non-fast-forward | 远程有本地没有的提交 | git pull --rebase 后 git push |
Permission denied (publickey) | SSH 密钥未配置 | 生成并配置 SSH key(见下) |
Could not resolve host | 网络/DNS 问题 | 检查网络与代理 |
fatal: remote origin already exists | 重复添加 origin | git remote set-url origin <url> |
Updates were rejected because the tip of your current branch is behind | 同上 non-fast-forward | 先 pull 再 push |
SSH 密钥配置(免密推送)
bash
$ ssh-keygen -t ed25519 -C "you@example.com" # 生成密钥(一路回车即可)
$ cat ~/.ssh/id_ed25519.pub # 复制公钥内容到 GitHub → Settings → SSH and GPG keys → New SSH key 粘贴保存。
