Skip to content
This page has been auto-translated and may contain errors.View in English

远程仓库和 GitHub

docs.scrimba.com

本手册中到目前为止的每个仓库都存在于一个地方:你自己的机器上。这在你想要一个能在笔记本电脑损坏时幸存下来的备份、想从第二台计算机上继续开发项目,或想让其他人接触代码之前都没问题。你需要的是一个仓库副本存放在所有涉及的机器都能访问的地方。这个副本称为远程仓库,GitHub 是大多数开发人员放置它的地方。

什么是远程仓库(以及 GitHub 如何融入其中)

远程仓库 是托管在你自己机器以外某处的仓库副本,通常在 GitHub 上。当你克隆一个仓库时,Git 已经为你设置了一个,默认命名为 origin。运行 git remote -v-v 参数要求显示 URL 以及名称)来查看它:

bash
$ git remote -v
origin  https://github.com/mara-chen/weather-app.git (fetch)
origin  https://github.com/mara-chen/weather-app.git (push)

Git 知道的每个远程仓库都列出两次,一次用于获取,一次用于推送,因为理论上这两个操作可能指向不同的地址。实际上它们几乎总是相同的。origin 是 Git 在克隆时自动分配的昵称。你可以重命名它或添加指向其他地方的第二个远程仓库,但你接触的几乎每个项目都会将其主要远程仓库称为 origin

现在是重申一个容易混淆的区别的好时机:Git 和 GitHub 不是同一个东西。Git 是在你机器上运行的版本控制工具,无论你是否连接到任何东西都会记录提交。GitHub 是一个托管你仓库副本的网站,并为你提供推送和拉取的远程仓库。git remotegit pushgit pull 是连接两者的命令:它们是你笔记本电脑上运行的 Git 与坐在 GitHub 服务器上的仓库通信的方式。

远程仓库只是一个名称映射到一个 URL,存储在你的仓库配置中。与此同时,Git 保存了一个 远程跟踪分支:一个本地书签,记录你的机器上次签入时远程分支的位置。origin/main 正是这样。它不是坐在 GitHub 上的实际 main 分支。它是你的仓库对 GitHub 的 main 在上次获取或拉取时位置的记忆。用 git branch -r 列出 Git 当前持有的远程跟踪分支:

bash
$ git branch -r
origin/main

远程仓库所做的一切都通过该配置条目加上这些跟踪书签运行。没有后台连接,也没有任何东西监视变更。Git 只在你告诉它时更新对远程仓库的了解,使用 fetchpull,这正是本章其余部分所涵盖的内容。

Juno什么是远程仓库 远程仓库是托管在别处(通常是 GitHub)的仓库副本,Git 将默认的仓库称为 origin。随时运行 git remote -v 查看你的仓库知道的远程仓库。Git 和 GitHub 不是同一个东西:Git 是你机器上的工具,GitHub 是你的远程仓库通常所在的地方。早期明确这个区别可以避免之后许多困惑。
Juno什么是远程仓库origin 只是 Git 在克隆时设置的远程仓库的默认名称,所以当项目需要时,你可以重命名它或添加更多远程仓库。git remote -v 显示每个远程仓库的 URL,用于获取和推送两种操作。牢记 Git 与 GitHub 的区别:你在本地运行的命令是 Git,而 GitHub 是这些命令可以到达的多个目的地之一。
Juno什么是远程仓库 远程仓库是你配置中的一个名称映射到 URL,与远程跟踪分支(如 origin/main)配对,这是你的机器对该分支在上次获取或拉取时位置的记忆。没有任何东西会自动更新;Git 只在被要求时检查远程仓库。GitHub 实际拥有的内容与远程跟踪分支记忆的内容之间的差距是几乎所有获取与拉取意外的根源。

在何处托管你的远程仓库:GitHub 和替代方案

远程仓库所做的一切都是普通的 Git,这有一个早期值得了解的后果:另一端的主机是可互换的。对于 Git,每个主机都是你配置中的一个 URL,git pushgit pullgit fetch 对所有主机的行为都相同。主机添加的东西在更高的一层:你的代码的网络视图、审查流程、问题跟踪和访问控制。以下是景观,权衡阐述明确:

  • GitHub 是目前最大的,托管了绝大多数开源工作。它的优势是网络效应:你想贡献的项目已经在那里,教程假设使用它,雇主在上面寻找你的资料。权衡是它是一个封闭平台(由 Microsoft 拥有),你对它在 Git 托管之外的功能依赖越多,你与它的绑定就越紧密。
  • GitLab 是最接近的完整替代方案,具有相同的核心功能但名称略有不同(拉取请求在那里称为"合并请求")。它的优势是自管理版本,你可以在自己的服务器上运行,这就是为什么保持代码内部的公司经常选择它。权衡是它捆绑了很多东西,平台的感觉可能比你来的代码托管更沉重。
  • Bitbucket 是来自 Atlassian 的主机,构建用于与其项目跟踪和文档工具 JiraConfluence 并肩放置。如果你的团队已经在这些工具中,它直接适配。在该生态系统之外,它在三大中拥有最小的社区,且几乎没有开源存在。
  • Codeberg 是开源项目的非营利主机,运行在开源平台 Forgejo 上。吸引力在于没有公司拥有你的主机,且平台本身是开源的。权衡是规模:更少的集成,更小的社区,以及对开源而非私人团队工作的关注。
  • 自托管 是所有这些的基础:在你控制的硬件上运行 ForgejoGitea(两者都是免费和开源的),或 GitLab 的自管理版本。你获得完全的控制和隐私;权衡是保持它运行成为你的工作。

本手册在其示例中使用 GitHub,因为它托管了大多数开源工作,是你最可能首次协作的地方。本章中的每个命令都无更改地转移到任何其他主机:交换 URL,日常循环保持不变。差异在于更高的一层,在每个网站审查和合并工作的网络界面中,这是下一章的 拉取请求流程 发挥作用的地方。

当选择权在你的手中时,它很少涉及 Git 功能,因为核心集合(托管的仓库、审查、问题、CI 管道)到处都存在。它归结为上下文:为开源做贡献或建立公众形象指向 GitHub,运行在 Atlassian 工具上的公司指向 Bitbucket,必须保留在你自己基础设施上的代码指向 GitLab 自管理或 Forgejo,以及价值观驱动的开源项目可能在 Codeberg 上感到最舒适。这个选择也不是永远的。在主机之间移动仓库本身是一个 git remote set-url 加上一个推送;迁移的昂贵部分是 Git 之上的一层,即问题、维基和 CI 配置,这些不会随提交一起传输。

自托管在你承诺之前值得审视。Forgejo 和 Gitea 在小服务器上运行得很舒服,但操作负担是真实的:安全更新、仓库和平台自身数据库的备份、账户和 SSH 密钥管理,以及正常运行时间,因为一个宕机的 forge 会阻止你的团队每次推送和拉取。当合规规则将代码保留在第三方基础设施之外、在气隙环境中,或当一个组织希望它的工具在自己的控制之下时,它才能自如偿还。在网络层之下没有任何改变:这些主机中的每一个都使用相同的 Git 协议,这就是为什么你的推送和获取无法区分它们。

Juno在何处托管你的远程仓库 GitHub 是大多数项目所在的地方,当你刚开始时是最安全的默认选择,但它只是多个主机之一:GitLab、Bitbucket 和非营利 Codeberg 都存储相同的 Git 仓库。无论远程仓库在哪里,推送和拉取的工作方式都相同,所以如果项目存在于其他地方,你在这里学到的没有什么是浪费的。
Juno在何处托管你的远程仓库 主机在 Git 上方的一层有所不同:GitLab 将拉取请求称为合并请求并提供自管理版本,Bitbucket 插入 Atlassian 的工具,Codeberg 运行开源 Forgejo。根据你的协作者和工具已经在的地方选择,并记住稍后迁移在 Git 方面很便宜;是问题和 CI 不会传输。
Juno在何处托管你的远程仓库 由于远程仓库是映射到 URL 的名称,在主机之间移动项目是 git remote set-url 加上一次推送。自托管 Forgejo 或 Gitea 让你获得控制和隐私,并花费修补、备份和正常运行时间;当政策要求时采取这个权衡,而不是为了乐趣,那个背过传呼机的人说。所有特定主机的东西都存在于 Git 协议之上的网络层。

发送和获取你的工作:git push 和 git pull

配置了远程仓库后,两个命令覆盖了大部分日常工作。git push 将你的本地提交发送到远程仓库。git pull 拉取在其他地方做出的提交,并将它们合并到你所在的分支中。

bash
$ 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 -> main

origin 是远程仓库,main 是你发送的分支。最后一行是值得阅读的部分:它显示远程仓库上的 main 从一个提交移动到下一个。

拉取的工作方式相同,但反向:

bash
$ 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(-)

这是与其他人在一个项目上协作的完整循环:在你开始之前拉取任何改变的内容,完成你自己的工作并提交它,将其推送回来。当你与任何人共享一个分支时,在推送之前总是拉取。跳过拉取,你的推送可能会落在你从未看过的提交之上,Git 将拒绝而不是冒险丢失某人的工作。

第一次推送一个新分支时,添加 -u--set-upstream 的缩写):

bash
$ git push -u origin feature/add-forecast-icons
Enumerating objects: 5, done.
Writing objects: 100% (5/5), 412 bytes | 412.00 KiB/s, done.
To https://github.com/mara-chen/weather-app.git
 * [new branch]      feature/add-forecast-icons -> feature/add-forecast-icons
branch 'feature/add-forecast-icons' set up to track 'origin/feature/add-forecast-icons'.

这将 feature/add-forecast-icons 记录为跟踪 origin/feature/add-forecast-icons,这个链接称为 上游。一旦设置,之后的每次推送和拉取都可以删除远程和分支名称,在分支上运行简单的 git pushgit pull。如果一个分支还没有配置上游,Git 会说出来,并打印确切的命令来修复它,所以你很少需要记住该标志,只需在它出现时识别这条消息。

该上游链接作为两行纯文本存在于你的仓库配置中,在 -u 运行的那一刻写入:branch.feature/add-forecast-icons.remote 命名远程仓库,branch.feature/add-forecast-icons.merge 命名远程仓库上的分支。在你之前推送过的分支上运行 git config --get branch.main.remote,你将看到完全相同的值返回。那两行也是 git status 读取的内容,用来告诉你你的分支比 origin/main 领先两个提交,或者两者已经分开了:比较完全针对你的远程跟踪分支运行,这是一个本地检查,没有从 GitHub 获取任何东西。设置一次上游,之后 Git 显示的每个领先-落后计数都读取那个相同的配置行对。

Juno发送和获取你的工作git push 将你的提交发送到远程仓库,git pull 拉取在其他地方做出的提交并将它们合并到你的分支中。在你开始工作之前拉取,在你完成时推送,你就与接触项目的任何人保持同步。跳过拉取是推送被拒绝的最常见原因。
Juno发送和获取你的工作git push origin main 发送提交,git pull origin main 获取并合并它们,一旦一个分支有了用 git push -u 设置的上游,两个命令都可以在没有命名远程仓库或分支的情况下工作。在共享分支上推送之前拉取,否则你的推送会被拒绝,因为远程仓库已经超过了你的本地历史。
Juno发送和获取你的工作 你用 git push -u 设置的上游归结为两行配置,branch.<name>.remotebranch.<name>.merge,在标志运行的那一刻写入。git status 读取那个相同的对来告诉你你的分支相对于 origin/main 领先或落后多远,一个它完全针对你的本地远程跟踪分支进行的比较。为每个分支设置一次,每个速记推送或拉取,以及每个领先-落后计数,都是免费的。

git fetch 与 git pull

git pull 在一步中做两件事:它下载远程仓库上的任何改变,然后立即将那些改变合并到你所在的分支中。git fetch 只做第一部分。它下载新的提交,但从不将它们合并到你的分支中。你的当前分支和你的工作文件保持完全相同,直到你告诉 Git 合并或拉取。

这是几乎每个人至少会遇到一次的陷阱:运行 git fetch,在任何地方看不到可见的改变,并假设远程仓库上没有发生任何事情。有事情发生了:fetch 还没有接触你的工作分支。要查看下来的内容,请看本章前面的远程跟踪分支:git log origin/main 显示坐在远程仓库上的提交,还没有到达你的本地 main。当你准备好将它们引入时,git merge origin/main 进行合并,或 git pull 在一个命令中运行获取和那次合并。

在你想看后再合并时,自己到达获取。说一位队友,Priya,提到她推送了什么到 maingit fetch 后跟 git log origin/maingit diff main origin/main 让你在它落入你的工作分支之前准确读取改变了什么。一旦你满意,git merge origin/main 就会将它引入。日常来说,大多数人直接到达 git pull,因为看和合并在一个步骤中是他们想要的。获取在一个共享分支上赚取其价值,其中你宁愿先读取传入的工作,或在你决定现在是否是合并的好时机之前。

获取实际更新的内容由 refspec 管理:一个映射,存储在你的远程仓库配置中,告诉 Git 要从远程仓库带下哪些分支,以及要在本地用哪些名称存储它们。一个普通克隆上的默认 refspec 大致读为 +refs/heads/*:refs/remotes/origin/*,意思是"取远程仓库头下的每个分支,并将其镜像到我的远程跟踪分支中,即使这会覆盖它们曾经指向的内容,也要移动它们以匹配"。这是 origin/main 保持最新的整个机制:获取读取远程仓库的分支并重写你的远程跟踪分支以匹配,然后停止。关于你自己的 main 的什么都不改变,直到一个单独的合并、变基或拉取作用在获取带下的东西上。

Junogit fetch 与 git pullgit pull 下载新提交并在一步中将它们合并到你的分支。git fetch 只下载它们并更新 Git 对远程仓库位置的记忆;你的分支保持不动直到你合并。在获取后立即在你的文件中看不到改变是预期的,新提交在远程跟踪分支上等待。
Junogit fetch 与 git pullgit fetch 下载不合并,git pull 下载并在一步中合并。当你想用 git log origin/main 在它们落入你的分支之前读取传入提交时,到达获取,以及当你准备好立即合并它们时拉取。混淆两者是团队中最常见的 Git 混淆之一。
Junogit fetch 与 git pull 一个 refspec 决定获取准确地下载什么,以及它更新哪些远程跟踪分支,origin/main 包括在内,而默认的镜像远程仓库头下的每个分支。获取只会移动你的远程跟踪分支,永远不会移动你签出的分支,所以合并或拉取保持一个单独的、刻意的步骤。

这给你留下什么

推送、拉取和获取让你的提交到达共享远程仓库并返回,这覆盖了 Git 中日常协作所需的大部分内容。这里的一切都建立在 提交循环 上:你仍然首先在本地暂存和提交,远程仓库只改变这些提交可以传输的地方。下一部分是通过 GitHub 本身进行该协作,在它合并之前提出更改并进行审查。拉取请求流程 正好从这里开始。如果本章中的一个术语仍然感到不稳定,远程跟踪分支、refspec、上游,术语表 对每个都有清晰定义,而 什么是 Git 值得再看一眼,单独查看 Git 与 GitHub 的区别。