Chapter 04

远程仓库

连接世界——理解 fetch、pull、push 的本质差异与团队协作工作流

远程仓库是什么

远程仓库(Remote Repository)是托管在网络上的 Git 仓库,可以是 GitHub、GitLab、Gitee,也可以是你自己搭建的服务器,甚至只是你本地文件系统上的另一个路径。

关键认知:远程仓库本质上与本地仓库没有区别,它们都是完整的 Git 仓库。"远程"只是一个地址的别名,代表另一个 Git 仓库的位置。

一个本地仓库可以关联多个远程仓库——例如,你 fork 了一个开源项目后,可以同时关联自己的 fork(origin)和原始上游仓库(upstream)。

管理远程仓库(git remote)

# 查看当前所有远程仓库
git remote -v

# 添加远程仓库(别名 + URL)
git remote add origin https://github.com/user/repo.git

# 添加上游仓库(fork 工作流)
git remote add upstream https://github.com/original/repo.git

# 修改远程 URL(例如从 HTTPS 切换到 SSH)
git remote set-url origin git@github.com:user/repo.git

# 重命名远程
git remote rename origin github

# 删除远程
git remote remove upstream

# 查看远程仓库详情
git remote show origin

origin 的约定origin 只是一个约定俗成的默认名称,代表你最主要的远程仓库(通常是你克隆的那个仓库)。git clone 会自动将克隆源命名为 origin。你完全可以给它取任何名字,只是全球开发者都默认使用 origin

三个容易混淆的命令

这是初学者最常混淆的地方,务必彻底理解它们的区别。

git fetch — 只下载,不修改工作区

git fetch 从远程下载最新的提交历史,但不会修改你的工作区或本地分支。它只更新远程追踪分支(如 origin/main)。

# 拉取 origin 的所有分支更新
git fetch origin

# 拉取所有远程的更新
git fetch --all

# 拉取后查看远程有哪些新内容
git log main..origin/main --oneline
fetch 前:
origin/main ──▶ A ── B
main(本地) ──▶ A ── B

远程有了新提交 C、D,fetch 后:
origin/main ──▶ A ── B ── C ── D (已更新)
main(本地) ──▶ A ── B (未变化,你可以检查后再决定如何合并)

适用场景:想先看看远程有什么变化,再决定是否要合并。

git pull — fetch + merge(或 rebase)

git pull = git fetch + git merge(默认),它下载远程更新并立即合并到当前分支。

# 默认:fetch + merge
git pull origin main

# 更推荐:fetch + rebase(保持线性历史)
git pull --rebase origin main

# 配置 pull 默认使用 rebase
git config --global pull.rebase true

git push — 推送本地提交到远程

git push 将本地分支上的新 commit 推送到远程仓库。

# 推送到 origin 的 main 分支
git push origin main

# 首次推送,同时建立追踪关系(-u / --set-upstream)
git push -u origin main

# 之后有了 -u 追踪关系,可以简写为:
git push

远程追踪分支

远程追踪分支(如 origin/main)是本地仓库对远程分支状态的"快照",只在 fetch/pull/push 时更新。

# 查看所有分支的追踪关系
git branch -vv

# 输出示例:
#   main       a3f2c1d [origin/main] feat: add profile page
#   feature    c7d1e3f [origin/feature: ahead 2, behind 1] work in progress
#   local-only 8e4b9a2 local experiment

推送的安全性

普通推送与强制推送

# 推送功能分支
git push origin feature-login

# 强制推送(危险!会覆盖远程历史,可能导致他人丢失工作)
git push --force origin feature-login

# 安全的强制推送(推荐替代 --force)
# --force-with-lease 在远程有新提交时会拒绝,防止覆盖他人工作
git push --force-with-lease origin feature-login
!

永远不要对 main/master 分支执行 --force push!这会改写所有团队成员依赖的共享历史,导致无法挽回的混乱。强制推送只应在你独自维护的个人功能分支上使用,且首选 --force-with-lease

Pull Request / Merge Request 工作流

PR(GitHub 术语)或 MR(GitLab 术语)是团队协作的核心机制,它不是 Git 的功能,而是代码托管平台提供的工作流工具。

功能分支工作流(团队常用)

# 1. 从最新的 main 创建功能分支
git switch main && git pull
git switch -c feature/user-profile

# 2. 开发功能,提交
git add . && git commit -m "feat: add user profile page"

# 3. 推送功能分支到远程
git push -u origin feature/user-profile

# 4. 在 GitHub/GitLab 上创建 Pull Request
#    base: main  ←  compare: feature/user-profile

# 5. 等待 Code Review,根据反馈修改并继续 push
git commit -m "fix: address review comments"
git push

# 6. PR 被批准后,在平台上点击 Merge
# 7. 删除功能分支
git branch -d feature/user-profile

Fork 工作流(开源贡献)

原始仓库(upstream)
↓ Fork(在 GitHub 上点击)
你的 Fork(origin)
↓ git clone
本地仓库
# 克隆你的 fork
git clone git@github.com:your-username/repo.git

# 添加原始仓库为 upstream
git remote add upstream https://github.com/original/repo.git

# 定期同步原始仓库的更新
git fetch upstream
git switch main
git merge upstream/main

# 创建功能分支,开发,推送到自己的 fork
git switch -c feature/my-contribution
git push origin feature/my-contribution

# 然后在 GitHub 上从 your-fork 向 original 发起 PR

解决远程冲突

最常见的场景:你推送时,远程已有他人提交(push 被拒绝)。

# 推送被拒绝,提示 "remote contains work that you do not have locally"
git push origin main
# ! [rejected] main -> main (fetch first)

# 解决方案 1:先 pull 再 push(使用 rebase 保持线性历史)
git pull --rebase origin main
# 如果有冲突,解决冲突后:
git add .
git rebase --continue
git push origin main

# 解决方案 2:普通 pull(会产生 merge commit)
git pull origin main
git push origin main

Partial Clone 与 Bundle URI(Git 2.39+)

这两个特性是 Git 大仓库场景下的性能利器,在 Git 2.39+ 中得到增强。

# 部分克隆:不下载 blob 对象(按需获取)
git clone --filter=blob:none https://github.com/large/monorepo.git

# 部分克隆:不下载 tree 和 blob(最激进的优化)
git clone --filter=tree:0 https://github.com/large/monorepo.git

# 配合稀疏检出:只检出 packages/web 子目录
cd monorepo
git sparse-checkout init --cone       # cone 模式(更快)
git sparse-checkout set packages/web   # 只检出此目录

# 查看当前稀疏检出配置
git sparse-checkout list

# 增加更多目录
git sparse-checkout add packages/shared

# 禁用稀疏检出,恢复完整检出
git sparse-checkout disable

Bundle URI 工作原理 — 当 Git 服务器(如 GitHub)启用 Bundle URI 时,git clone 会先发现 bundle 的 CDN 地址,从 CDN 下载到某个时间点的历史快照,再从 Git 服务器获取之后的增量更新。对于大型仓库,这意味着服务器只需处理少量增量请求,而非从零传输全部历史。

常见远程操作误区

git pull 默认行为已在 Git 2.27+ 改变 — 从 Git 2.27 起,如果 pull.rebase 未配置,git pull 会警告歧义。建议显式配置:git config --global pull.rebase true(推荐,保持线性历史)或 git config --global pull.rebase false(传统 merge 方式)。

push.default 配置影响推送行为 — 未配置 -u 追踪关系时,git push 的行为取决于 push.default 配置(默认 simple:只推送当前追踪的分支)。建议首次推送新分支时总是加 -u origin branchname,明确建立追踪关系。

本章小结 — 远程仓库本质上是另一个 Git 仓库的别名。fetch 安全地下载不合并,pull = fetch + merge/rebase,push 上传本地提交。--force-with-lease 是比 --force 更安全的强制推送方式。partial clone 和 sparse-checkout 是大型仓库的性能解决方案。