SSD Nodes Learn 🎉 VPS $4.99/月起
指南 Matt Connor作者: Matt Connor

Git 与 GitHub 有什么区别?VPS 用户必看

Git 是安装在本机或服务器上的版本控制程序,GitHub 是托管 Git 仓库的服务。了解 VPS 部署中两者的具体用途、关系与差异。

什么是 GitHub?

GitHub 是一项托管服务,用于存储 Git 仓库并围绕这些仓库提供网站功能。Git 是运行在您自己的计算机或服务器上的版本控制程序。GitHub 是构建在 Git 之上的单家公司产品,自 2018 年起归 Microsoft 所有。您可以每天使用 Git,却完全不打开 GitHub。没有 Git,您无法使用 GitHub。

当您拥有 VPS(虚拟专用服务器)后,这一点就很重要。Git 用于记录配置文件和部署脚本的历史。服务器无法保存这份历史时,GitHub 可以保存一份副本,还可用于运行构建和进行代码审查。本指南将通过一个示例,介绍如何从空目录开始,在服务器上完成部署,并在首次出现每个新术语时解释其含义。

Git 自动执行的操作

Git 是一个版本控制系统:它会记录目录随时间变化的状态,因此您可以查看发生了哪些更改、何时更改以及更改原因。Git 于 2005 年为 Linux 内核开发而编写。它是分布式系统,也就是说,每个仓库副本都包含完整历史记录。其设计中没有中央服务器。协作者的笔记本电脑可以与任何服务器一样,保存完整的仓库副本。

安装 Git,然后设置您的身份信息。如果没有姓名和电子邮件地址,Git 会拒绝记录提交,因为这两项信息都会写入提交本身。

sudo apt update && sudo apt install -y git
git --version
git config --global user.name "Your Name"
git config --global user.email "you@example.com"

在 Ubuntu 24.04 上,git --version 输出 git version 2.43.0。过去几年的任何版本在以下所有操作中的行为都相同。

示例:用于 VPS 部署文件的仓库

仓库通常简称为“repo”,是 Git 监视的目录。运行 git init 后,该目录才会成为仓库;此命令会在其中创建隐藏的 .git 文件夹。这个文件夹就是仓库。删除 .git 后,剩下的只是一个不包含历史记录的普通目录。

mkdir vps-deploy && cd vps-deploy
git init -b main
printf '.env\n*.key\n' > .gitignore

-b main 将第一个分支命名为 main。省略该参数时,Git 会输出一段很长的提示,说明默认分支名称。.gitignore 列出 Git 必须永远忽略的路径。第一天就将 secrets 文件写入其中,因为文件一旦被提交过,即使删除,仍会保留在历史记录中;要彻底移除它,必须重写之后的每个提交。

提交:历史记录的基本单位

现在添加一个脚本,并记录这次更改。

printf '#!/bin/sh\nsudo systemctl restart caddy\n' > restart.sh
git add restart.sh .gitignore
git commit -m "Add restart script and gitignore"
git log --oneline

git add 会将更改移入暂存区,其中列出了下一次提交将包含的内容。git commit 会将该列表作为一条记录写入历史。提交包含每个受跟踪文件的快照、提交消息、作者、时间戳,以及指向前一个提交的指针。git log --oneline 会为每个提交输出一行,每行以类似 a1b2c3d 的短哈希值开头。该哈希值就是提交的名称,几乎所有 Git 命令都接受它。

跳过 git add 步骤时,git commit 会返回 no changes added to commit (use "git add" and/or "git commit -a")。这不是故障。Git 表示暂存区为空,因此没有可创建快照的内容。迷失方向时,应运行 git status:它会列出当前分支、已暂存的更改,以及 Git 能看到但尚未跟踪的文件。

分支:历史记录的第二条线

分支是指向某个提交的可移动指针。main 是一个分支,它在任何方面都不是 Git 的特殊对象。创建分支不需要成本,因为 Git 只需写入一个新的指针,而不是复制文件。

git switch -c add-backup
printf '#!/bin/sh\nrestic backup /srv\n' > backup.sh
git add backup.sh
git commit -m "Add nightly backup"
git switch main
ls

执行 git switch main 后,列表中缺少 backup.sh。没有任何内容被删除。该文件存在于 add-backup 分支中,而 main 分支从未包含它,因此切换分支时 Git 将它从工作目录中移除了。这种情况每个人都会遇到一次。git switch add-backup 会将它恢复。

远程仓库:GitHub 终于出现

到目前为止,所有操作都在没有网络连接的单台计算机上完成。远程仓库是同一仓库另一份副本对应的命名 URL。GitHub 会为您托管其中一份副本。主远程仓库通常命名为 origin

通过 GitHub 网站创建一个空仓库,然后将其连接到本地仓库。这里优先使用 SSH,而不是 HTTPS:SSH 密钥是由您控制的文件,不会像个人访问令牌那样过期。

ssh-keygen -t ed25519 -C "vps-deploy"
cat ~/.ssh/id_ed25519.pub
ssh -T git@github.com

将输出的公钥粘贴到 GitHub 帐户的 SSH keys 页面,然后再次运行测试。密钥正常时,GitHub 会回复 Hi yourname! You've successfully authenticated, but GitHub does not provide shell access.。GitHub 不会为您提供 shell,因此该拒绝信息表示测试成功。git@github.com: Permission denied (publickey). 表示您的密钥从未发送,或未被接受。请确认粘贴的是 .pub 文件,而不是旁边的私钥。

git remote add origin git@github.com:yourname/vps-deploy.git
git push -u origin main

git push 会将您的提交发送到远程仓库。-u 会记录本地 main 跟踪远程 main,因此之后只需执行 git push 即可。git clone <url> 是在新计算机上的反向操作:它会复制包含完整历史记录的整个仓库,并为您设置 origin。HTTPS 远程仓库同样可用,并且使用与网页相同的协议传输数据,因此适合阻止出站 22 端口的网络。如果这句话需要进一步解释,请参阅HTTP 请求实际由哪些部分组成,其中介绍了相关机制。

Pull request、issue 和 fork:属于 GitHub 而非 Git 的部分

上文全部内容都属于 Git,可用于任何服务器。下面这 3 个术语是 GitHub 的功能。其他托管平台会仿照它们,但 Git 本身不了解这些功能。

Pull request(PR)是将一个分支合并到另一个分支的请求,并通过页面提供讨论功能。您将 add-backup 推送到远程仓库,针对 main 创建 PR,网站会按提交逐一显示差异。用户可以针对单行代码发表评论。自动化检查会报告该分支是否通过检查。点击合并后,GitHub 会在自己的副本中执行合并,然后更新 main。这个名称源自早期工作流:您请求维护者将您的分支 pull 到其分支中。

Issue 是用于记录 bug 或任务的编号讨论串。它存储在 GitHub 的数据库中,而不是您的仓库中。在选择托管平台前,了解这一点很重要:克隆仓库后,您会获得所有提交,但不会获得任何 issue。要导出 issue,必须调用 API。

Fork 是其他人仓库在服务器端的独立副本。您可以写入该副本,将分支推送到其中,然后从您的副本向原仓库创建 pull request。对于维护者从未听说过您的项目,这是参与贡献的方式。Fork 是存储在 GitHub 上并保留来源信息的 clone。

软件通过用户使用的同一个 API 读取这 3 类对象。在您自己的服务器上运行的 pull request 审查代理会监视新的 PR,读取差异,并发布行级评论。在仓库根目录放置 AGENTS.md 文件等约定之所以存在,是因为现在不仅用户会读取仓库,工具也会读取仓库。

GitHub 对 VPS 所有者的实际作用

先将数据存储在服务器之外。部署脚本和操作手册应存放在被配置的服务器之外。这样可以从全新镜像重建 VPS,然后执行克隆和部署。将该仓库设为私有,并为服务器配置一个部署密钥:这是仅注册到一个仓库、而不是整个账户的 SSH 密钥,并将其设为只读。只读部署密钥泄露后,只会暴露一个仓库。账户密钥泄露后,攻击者可以访问你有权推送到的所有内容。

sudo git clone git@github.com:yourname/vps-deploy.git /srv/vps-deploy
cd /srv/vps-deploy
git pull --ff-only

--ff-only 会拒绝创建合并提交。服务器只负责获取变更时,合并始终意味着发生了意外,因此该标志会将难以理解的历史转换为明确的错误 fatal: Not possible to fast-forward, aborting.。服务器上的内容发生了不应有的变化。再次拉取前先找出原因。

以 root 身份克隆仓库,然后以其他用户运行 Git,会得到 fatal: detected dubious ownership in repository at '/srv/vps-deploy'。Git 拒绝读取由其他用户拥有的仓库,因为恶意的 .git/config 可以让 Git 执行命令。使用 chown 修复所有权,不要添加 safe.directory 例外,因为例外只会绕过检查,并未消除原因。

GitHub Actions:构建和部署流水线

Actions 是 GitHub 的 CI/CD 系统(持续集成和持续交付)。将 YAML 文件提交到 .github/workflows/ 下后,GitHub 会在指定事件发生时运行该文件。

name: check
on:
  push:
    branches: [main]
jobs:
  shellcheck:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v7
      - run: sudo apt-get update && sudo apt-get install -y shellcheck
      - run: shellcheck *.sh

该文件称为工作流作业在一台机器上运行。步骤是一个命令或一个已发布的操作。uses: 从其他仓库引入操作,@v7 将其固定到主版本(截至 2026 年 8 月,actions/checkout 的当前版本为 v7)。始终固定版本,因为未固定的操作可能会运行您未审查过的代码,并访问您的密钥。

runs-on: ubuntu-latest 会向 GitHub 请求一台全新的虚拟机,并在作业结束时将其销毁。公共仓库可免费使用标准运行器;截至 2026 年 8 月,免费计划每月为私有仓库提供 2,000 分钟。根据该数值制定预算前,请查看当前定价页面。

密钥存储在仓库设置中,并通过 ${{ secrets.DEPLOY_KEY }} 读取。由来自 fork 的拉取请求触发的工作流会获得只读令牌,且无法访问这些密钥,因为否则陌生人可以创建一个唯一任务是输出密钥内容的 PR。

在您自己的 VPS 上运行 Actions runner

runs-on: self-hosted 会将作业发送到您拥有的计算机。仓库的 runner 设置页面会提供下载命令、仓库 Web 地址和一个有效期为 1 小时的注册令牌。将后两项填入 REPO_URLRUNNER_TOKEN,然后执行 3 条命令即可完成设置。

./config.sh --url "$REPO_URL" --token "$RUNNER_TOKEN"
sudo ./svc.sh install
sudo ./svc.sh start
./svc.sh status

svc.sh status 应显示服务处于 active 状态,并显示最近的日志行。runner 会向 GitHub 建立出站 HTTPS 连接并请求作业,因此无需为它开放任何入站端口。svc.sh install 会写入 systemd unit,这是很多人会跳过的一步:没有这一步,runner 会随着 SSH 会话结束而退出,之后的每个作业都会无解释地停留在 queued 状态。VPS 上完整的 self-hosted runner 设置介绍了长期运行的 runner 所需的加固和清理步骤。

这样部署就不再需要从互联网可访问的入站 SSH 密钥,因为作业已经在这台服务器上运行。构建缓存也会在多次运行之间保留,不会产生按分钟计费。

有一项警告不能忽略。GitHub 的官方文档建议仅在私有仓库中使用 self-hosted runner,因为公共仓库的 fork 可以通过创建 pull request,在您的 runner 上运行危险代码。runner 会执行该分支 workflow 文件中指定的任何内容。在您能够控制推送权限的私有仓库中,风险较小。在公共仓库中,应将任何 self-hosted runner 视为陌生人可以在其上执行代码的计算机。

完全需要 GitHub 吗?

不需要。Git 是标准,GitHub 提供的是便利。Forgejo 和 Gitea 都是自托管代码托管平台;代码托管平台是附带 issue 和拉取请求功能的 Git 主机。两者都以单个 Go 二进制文件发布,都能运行在小型 VPS 上。Forgejo 是 Gitea 在 2022 年的一个分支项目,目前为 Codeberg 提供支持。迁移仓库只需执行一条命令,因为两者使用相同的传输协议。

git remote -v
git remote set-url origin git@git.example.com:you/vps-deploy.git
git push origin main

每个提交都会被迁移,因为每个克隆仓库都已包含完整历史记录。不会迁移的是 GitHub 在此之上构建的功能层:issue 和拉取请求讨论串。CI 也不会迁移。Forgejo 有自己的 Actions 实现,会从 .forgejo/workflows/ 读取相似的 YAML;其文档明确说明了限制:GitHub Actions 和 Forgejo Actions 并不相同,功能可能无法立即运行。它还需要单独的 runner。应将这一步规划为移植,而不是复制。

大多数项目继续使用 GitHub,原因很简单:贡献者。公开代码必须托管在用户已有账号的平台上。您的私有部署脚本则不需要这样做。这是两个独立的决定,您可以分别作出不同选择。

最先出现什么问题,以及错误信息的含义

推送被拒绝。您会看到:

 ! [rejected]        main -> main (fetch first)
error: failed to push some refs to 'github.com:yourname/vps-deploy.git'
hint: Updates were rejected because the remote contains work that you do
hint: not have locally.

自上次拉取后,远程仓库中有新的提交,通常是您在 Web 编辑器中进行的修改。运行 git pull --rebase,将您的提交重新应用到对方提交之上,然后再次推送。不要在共享分支上使用 git push --force,因为它会从服务器上的该分支中移除其他提交。

fatal: refusing to merge unrelated histories您在本地运行了 git init,同时又让 GitHub 创建了包含 README 的仓库。两边的历史没有共同提交,因此 Git 无法自动判断应如何合并。最稳妥的做法是将 GitHub 中的副本克隆到新目录,再将您的文件移入其中。

error: src refspec main does not match any您指定的分支在此处不存在。通常是因为仓库还没有任何提交,或者您的分支名称是 master。运行 git branch --show-current 即可确认。

密钥已进入某个提交。立即轮换凭据。从推送完成时起就应将其视为公开信息,因为 fork、镜像和缓存视图都可能保存副本,而您无法删除这些副本。

FAQ

GitHub 和 Git 是同一回事吗?

不是。Git 是安装在计算机上的版本控制程序,无需网络或账户也能运行。GitHub 是一项商业托管服务,用于存储 Git 仓库,并在此基础上提供 Web 界面、问题、拉取请求和 CI。Git 于 2005 年发布,GitHub 于 2008 年在 Git 之上上线。您可以永远使用 Git,而不使用 GitHub。GitHub 的所有功能底层都依赖 Git。

在 VPS 上使用 Git 需要 GitHub 账户吗?

不需要。git initgit commitgit log 在完全未配置远程仓库的服务器上也能工作,这已经足够用于跟踪 /etc 文件或部署脚本的变更。当您需要一份即使服务器损坏也能保留的历史副本,或需要让第二台计算机克隆该仓库时,账户才会有用。Forgejo 和 Gitea 等自托管代码托管平台可在您拥有的硬件上满足相同需求;您也可以将普通 SSH 远程仓库指向另一台主机上的裸仓库,完全不需要代码托管平台软件。

什么是拉取请求?

拉取请求是将一个分支合并到另一个分支的请求,并附带讨论页面。您推送一个分支,针对 main 创建 PR,托管平台会逐个提交显示变更,审阅者可以针对具体行发表评论,自动化检查也可以报告通过或失败。拉取请求是 GitHub 的功能,不是 Git 的功能,因此 Git 本身没有对应命令。其他托管平台也实现了相同概念,有时称为合并请求。

应该在自己的 VPS 上运行 GitHub Actions runner 吗?

对于私有仓库,通常可以。任务运行在您已经付费使用的硬件上,不会按分钟计费,构建缓存可以保持有效;部署也不再需要将入站 SSH 密钥暴露到互联网,因为 runner 会主动连接 GitHub 并请求任务。对于公开仓库,GitHub 建议不要这样做:任何人都可以 fork 您的仓库,并创建一个拉取请求,使其工作流在您的计算机上运行代码。

以后可以将仓库迁出 GitHub 吗?

代码可以,而且很容易。每个克隆都包含完整历史,因此执行 git remote set-url origin <new url> 后再推送,即可迁移每个提交包含的全部内容。GitHub 所拥有的那一层不会随之迁移:问题、拉取请求讨论和 Actions 历史记录存储在 GitHub 的数据库中,而不在您的 .git 文件夹内。迁移工具可以通过 API 复制问题,工作流文件通常需要针对新托管平台的 CI 进行修改。了解这一点后,更应将正式文档放入仓库,而不是只保存在问题讨论串中。

#github#git#version-control#ci-cd#developer-tools