远程仓库与 GitHub


到目前为止,这本手册里出现的仓库都只待在一个地方:你自己的电脑。这在大多数情况下都没问题,直到你想要一份能在笔记本电脑损坏后保住数据的备份,或者想换一台电脑继续这个项目,或者想让别人也能参与进来。这时你需要的是一份仓库的拷贝,放在所有相关设备都能访问到的地方。这份拷贝就叫远程仓库(remote),大多数开发者会把它放在 GitHub 上。
什么是远程仓库(以及 GitHub 在其中扮演的角色)
远程仓库(remote) 是托管在别处(通常是 GitHub)的一份仓库拷贝,而不是存在你自己的电脑上。克隆一个仓库时,Git 已经帮你设置好了一个远程仓库,默认命名为 origin。运行 git remote -v(-v 表示同时显示名称和对应的 URL)就能看到它:
$ git remote -v
origin https://github.com/mara-chen/weather-app.git (fetch)
origin https://github.com/mara-chen/weather-app.git (push)Git 认识的每个远程仓库都会列出两次,一次对应拉取(fetch),一次对应推送(push),因为理论上这两者可以指向不同的地址。但实际上它们几乎总是同一个地址。origin 是 Git 在克隆时自动分配的昵称。你可以给它改名,或者再添加一个指向别处的远程仓库,但几乎每个你接触到的项目,主远程仓库都叫 origin。
这里正好可以再强调一下几乎每个新手都会踩的一个坑:Git 和 GitHub 不是一回事。Git 是运行在你机器上的版本控制工具,不管你有没有联网,它都会记录提交。GitHub 是一个网站,托管着你仓库的一份拷贝,为你提供一个可以推送和拉取的远程仓库。git remote、git push 和 git pull 就是连接这两者的命令:它们让运行在你笔记本上的 Git,和存放在 GitHub 服务器上的仓库进行对话。
origin。随时运行 git remote -v 就能看到你的仓库知道哪些远程仓库。Git 和 GitHub 不是一回事:Git 是你机器上的工具,GitHub 常常是你远程仓库所在的地方。早点把这两者分清楚,能省下不少之后的困惑。 把远程仓库放在哪里:GitHub 及其他选择
远程仓库能做的一切都是纯粹的 Git 操作,这就带来一个值得早点知道的结论:另一端的托管平台是可以随意更换的。对 Git 来说,每个托管平台不过是配置里的一个 URL,git push、git pull 和 git fetch 对所有平台的行为都完全一致。托管平台真正添加的东西,是更上一层的东西:代码的网页视图、审查流程、issue 跟踪和权限控制。下面把各个平台的取舍说清楚:
- GitHub 目前规模最大,托管了绝大多数开源项目。它的优势在于网络效应:你想要参与的项目大多已经在这里,教程默认以它为准,雇主也会去这里找你的个人主页。代价是它是一个封闭平台(归 Microsoft 所有),你越依赖它在 Git 托管之外提供的那些附加功能,就越难脱离它。
- GitLab 是最接近的完整替代品,核心功能相同,只是叫法略有不同(在这里,pull request 叫作"merge request")。它最突出的特点是提供一个可以自己搭建在自有服务器上的自托管版本,这也是那些希望把代码留在内部的公司常常选它的原因。代价是它打包了太多功能,整个平台可能会比你原本只想要的代码托管服务显得更沉重。
- Bitbucket 是 Atlassian 出的托管平台,设计上是为了配合它的项目跟踪和文档工具,也就是 Jira 和 Confluence。如果你的团队已经在用这些工具,它能直接融入进去。离开这个生态之后,它在三大平台里社群规模最小,开源项目的存在感也不高。
- Codeberg 是一个面向开源项目的非营利托管平台,运行在开源平台 Forgejo 之上。它的吸引力在于,没有任何公司拥有这个托管平台,而且平台本身也是开源的。代价是规模有限:集成少、社群小,而且专注于开源,而不是私有团队协作。
- 自托管 是所有这些平台背后的共同基础:你可以自己搭建免费开源的 Forgejo 或 Gitea,或者在自己掌控的硬件上运行 GitLab 的自托管版本。你能获得完全的控制权和隐私性;代价是维护它正常运行成了你自己的工作。
这本手册的示例用 GitHub,是因为它托管了大部分开源项目,也是你最可能最先在上面协作的地方。本章的每条命令换到其他任何托管平台都完全适用:换个 URL,日常操作流程照样不变。真正的差异出现在更上一层——每个网站用于审查和合并工作的网页界面,这正是下一章拉取请求流程要讲的内容。
发送和获取你的工作:git push 与 git pull
有了远程仓库之后,两个命令就能覆盖大部分日常工作。git push 把你本地的提交发送到远程仓库。git pull 把别处产生的提交拉下来,并合并到你当前所在的分支。
$ git push origin main
Enumerating objects: 5, done.
Writing objects: 100% (3/3), 312 bytes | 312.00 KiB/s, done.
To https://github.com/mara-chen/weather-app.git
9f8e7d6..a1b2c3d main -> mainorigin 是远程仓库,main 是你要发送的分支。最后一行值得细看:它显示远程上的 main 从一个提交移动到了下一个提交。
拉取的方式一样,只是方向相反:
$ git pull origin main
remote: Enumerating objects: 4, done.
Unpacking objects: 100% (4/4), done.
Updating a1b2c3d..b7c9e21
Fast-forward
src/index.js | 8 ++++++--
1 file changed, 6 insertions(+), 2 deletions(-)这就是和别人协作一个项目时的完整循环:开始前先把别人的改动拉下来,做自己的工作并提交,再推送回去。只要和别人共享一个分支,就要先 pull 再 push。跳过 pull 这一步,你的 push 可能会覆盖在一些你从未见过的提交上面,而 Git 会拒绝这样做,宁可拒绝也不冒着丢掉别人工作的风险。
git push 把你的提交发送到远程仓库,git pull 把别处产生的提交拉下来并合并到你的分支。开始工作前先 pull,做完之后再 push,你就能和项目里的其他人保持同步。跳过 pull 是导致 push 被拒绝最常见的原因。 git fetch 与 git pull 的区别
git pull 一步做了两件事:下载远程上发生的任何变化,然后立刻把这些变化合并到你当前所在的分支。git fetch 只做前一半。它只下载新的提交,从不把它们合并到你的分支里。你当前的分支,以及你的工作文件,都会原样保持不变,直到你告诉 Git 去合并或拉取。
这是几乎每个人至少踩过一次的坑:运行 git fetch,看到哪里都没有明显变化,就以为远程上什么都没发生。其实发生了:只是 fetch 还没碰到你的工作分支。要看看下载了什么,可以查看本章前面提到的远程跟踪分支:git log origin/main 会显示那些已经到了远程、但还没到达你本地 main 的提交。等你准备好把它们拉进来时,git merge origin/main 就能做到,或者直接用 git pull,它会一次帮你运行 fetch 加上那次合并。
git pull 一步下载新提交并合并到你的分支。git fetch 只下载它们,同时更新 Git 对远程当前状态的记忆;你的分支会保持原样,直到你去合并。fetch 之后你的文件里什么变化都没看到,这是正常的,新的提交正在远程跟踪分支上等着呢。 到这里,你已经掌握了什么
push、pull 和 fetch 让你的提交能送到共享的远程仓库,也能再拉回来,这已经涵盖了日常 Git 协作中的大部分工作。这一切都建立在提交循环之上:你依然要先在本地 stage、commit,远程仓库只是改变了这些提交能被送到哪里。接下来要讲的,是如何通过 GitHub 本身来完成这种协作——提出一次改动,让它在合并之前经过审查。拉取请求流程正是从这里接着讲下去的。如果本章里有哪个术语你还觉得没完全吃透,比如远程跟踪分支、refspec、上游,术语表里都有简明的解释,而什么是 Git也值得再看一遍,专门讲清楚 Git 和 GitHub 的区别。

