Git学习笔记 #3 远程仓库使用

本文最后更新于:2022年5月19日 中午

接上文,本文介绍了 Git 在远程仓库的使用,以及合作开发的简易教程。

本文大部分内容参考了 RCY 同学的教程,部分参考了 廖雪峰教程-Git菜鸟教程-Git,以及 Git 官网文档 Git-Documentation

远程的 Git

尽管 Git 本身是分布式的,但我们通常需要一个服务器来同步、传递我们的本地仓库数据,处于一种「伪集中式」的状态。但服务器上的内容不一定是最新的内容,只是一个用于传递的中间态而已。

因此有了一系列的相关网站,通常来说我们用以下两个:

  • GitHub:⼀个基于 Git 的代码托管服务平台,开源社区交流代码的重要网站,参考 https://www.github.com/ 。
  • GitLab:类似 Git,有完善的管理界面和权限控制,一般用于在企业、学校等内部网络搭建 Git 私服,参考 https://www.gitlab.com/ 。

从代码的私有性上来看,GitLab 是一个更好的选择。但是对于开源项目而言,GitHub 依然是代码托管的首选。

鉴权

在使用远程仓库之前,我们要先解决鉴权问题:云服务器需要知道你是否有权力访问这个仓库。

鉴权有两种方法:

  1. 用户名 + 密码
  2. SSH 密钥对

通常来说,使用密钥对比用户名密码更安全,这里不介绍相关原理,先简单讲下密钥对的配置。

1
2
3
4
5
$ ssh-keygen -t rsa -C "[email protected]" # 获取 SSH Key
# 参数 -t 代表 type 设置,默认使用 SSH2d 的 rsa
# 参数 -C 代表 Comment 设置,提供一个新注释
# 其他参数用于生成更多功能的密钥,请自行查阅相关网站
$ cat ~/.ssh/id_rsa.pub # 代表 concatenate,用于连接文件并打印到标准输出

密钥对生成时会需要设置密码,但由于其本身就是加密的,密码只是为了适应更特殊的情景,这里可以直接回车跳过

cat 命令会把公钥放到标准输出,复制到 GitHub 或 GitLab 上的对应位置即可。注意 id_rsa.pub 是可以公开的,而私钥存储在 id_rsa 文件,用于在每次操作远程仓库时与公钥进行匹配。

如果你有多台电脑或服务器,可以分别在各个主机上生成各自的 SSH 密钥对,并将公钥复制到 GitHub 或 GitLab,并区分命名。这样就可以实现在不同主机上的鉴权,方便多地办公。

克隆远程仓库

远程仓库通常是由 clone 或 push 开始的,这里假设我们已经在 GitHub 上有了一个仓库,现在要将其克隆到本地。

常见的克隆方法有两种:

1
2
$ git clone <仓库的 HTTPS URL 地址>
$ git clone <仓库的 SSH Key>

前者不需要配置 SSH 也能完成,但是每次操作远程仓库都需要用户名和密码。后者是在配置完 SSH Key 后使用的,可以省去填写用户名和密码的步骤,也能帮助你克隆私有仓库。如果前面生成密钥对时设置了密码,这里就需要输入。

此外,如果要克隆一个较大的仓库(仓库有很长的提交历史或大量二进制文件),常用的一个方法是限制 clone 的深度,只克隆最新的版本:

1
$ git clone --depth=1 https://github.com/hewei2001/HITSZ-OpenCS

远程分支

现在对本地仓库查看 log,会发现除了 HEAD、main 这些原有的指针,还多了 origin/ 的字样,这是远程仓库分支的默认标识。

这些远程分支与本地分支并存,反映了远程仓库在你上次和它「通信」时的状态,可以用前面的命令查看:

1
$ git branch --all  # 参数 --all 代表所有分支,包括远程分支

同样,也可以用 git checkout 切换到任意一个远程分支,只需要加上 origin/

远程分支有一个特别的属性,在你检出时自动进入一个「分离 HEAD 状态」。在此状态下,不会有 HEAD -> origin/main -> node,而是直接有 HEAD -> node。这样做是因为 Git 不能直接在远程分支上进行操作,必须在本地操作后将 main 分支同步到远程,origin/main 才会发生变动。

一旦发生了分离 HEAD 并提交修改,下次 push 时本地仓库将与远程仓库发生分歧,可能需要 Rebase 操作。

在实际操作中,我们只需要检出到本地分支即可。当克隆一个仓库时,它通常会自动地创建一个跟踪 origin/main 的 main 分支。

但如果一个远程分支 origin/test 在本地没有对应分支 test,我们却要检出 test,该命令会自动复制一个「跟踪分支」作为本地分支。

本地与远程的交互

下载 (Fetch)

所谓的拉取通常可由两个操作来完成,初学者往往分不清两个操作究竟分别做了什么:

1
2
$ git fetch  # 下载--下载远程分⽀到本地
$ git pull   # 拉取--使本地分支与远程分支同步

下面我们用一组图来解释 git fetch 操作:

使用fetch前的仓库

在这个例子中,虚线的结点代表远程仓库,它有两个我们本地仓库中没有的提交。而此时本地的远程分支 origin/main 还停留在你上次和它「通信」时的状态。如果此时使用 git fetch,则会出现:

使用fetch后的仓库

可以发现,本地仓库缺失的结点 C2 和 C3 被下载到了本地仓库,同时远程分支 origin/main 也被更新,反映到了这一下载,但是本地分支 main 依旧不变!

因此,我们可以知道:git fetch 并不会改变本地仓库的状态。它不会更新你的 main 分支,也不会修改你磁盘上原有的文件使其与远程「同步」,它只是将「同步」这一操作所需要的数据都下载下来了。

拉取 (Pull)

那么,如何将这些数据真正完成「同步」呢?实际上有很多方法,上一节提到的 git merge origin/maingit rebase origin/main 等命令都可实现。

实际上,由于先抓取更新再合并到本地分支这个流程很常用,因此 Git 提供了一个专门的命令来完成这两个操作。它就是我们要讲的 git pull

这里我们先用一个分叉的例子来演示:

使用pull前的仓库

分叉 (Branch Diverged) 是由于不同开发者进行了时空错位的提交导致的。观察该图,我们可以猜测本地开发者是在 C1 时刻克隆的仓库,并再克隆后完成了一次 C2 提交,而与此同时,另一个开发者在远程完成了 C3 提交。

此时如果想提交代码到远程,会提示 main 分支发生了分歧——因为远程仓库包含了本地尚不存在的结点,无法通过快速前移直接合并。Git 会提示你先将远程仓库拉取到本地解决分叉。

使用pull后的仓库

可以看到,git pull 其实就是 git fetchgit merge origin/main 两个指令的缩写。

需要注意的是,如果此时 C2 和 C3 有冲突,则 git pull 不完全执行,需要手动维护后再 merge,这个维护的过程对另一开发者是不可见的。

在实际操作中,我们偶尔会用 git fetchgit rebase origin/main 来避免一个不必要的合并。

推送 (Push)

相比于拉取,推送操作就尤为简单:

1
$ git push

如果无分歧发生,远程仓库将会接收本地新增的结点,而远程仓库中的 main 也会指向本地仓库中 main 的位置。此外,本地仓库的远程分支 origin/main,会在这次「通信」的过程中,也移动到本地 main 的位置。

再回顾一下上面的分叉的例子,如果我们已经 merge 解决了冲突,这时再用 git push 则会有:

分叉的第一种解决方案

可以看到,远程仓库清楚地记录了这次 merge 的历程!

而如果我们用 git fetchgit rebase origin/main 的组合(虽然很繁琐),但是远程仓库历史将变得十分整洁,并且能清楚地体现提交顺序

分叉的第二种解决方案

Push 的参数

上文介绍了最简单的 Push 命令,但这个命令看似简单,实际上却是最「模糊」的,很容易出现报错。完整的 Push 指令应该是:

1
2
3
$ git push <远程仓库名> <本地分支名>:<远程分支名>  # 冒号前后无空格
# 例如:
$ git push origin main:test # 将本地 main 推送到 origin 的 test

这个命令显式地指出了远程仓库名,如果只关联了一个远程,这个参数就可以缺省。但是如果关联了多个仓库(如 GitHub 和 Gitee),则必须用这个参数指明。使用 git remote -v 命令可以查看关联的远程仓库。

此外,它还指出了本地分支及其映射的上游分支,该命令可以用于不同名分支的推送,但实际工程中我们倾向于用本地远程同名分支(避免分歧),因此命令可以简化为:

1
$ git push origin test  # 将本地 test 推送到 origin 的 test

但是,如果远程仓库 origin 没有一个同名的 test 分支,又会报错,此时我们需要为 test 建立上游分支,并将其跟踪绑定

1
2
$ git push --set-upstream origin test # 推送同时绑定上游分支
$ git push -u origin test  # 参数 -u 为上述命令的缩写

注意,一次 Push 默认只推送一个分支,因此如果不加分支名,会默认推送当前 HEAD 所在的分支。因此指令还可以逐步缺省:

1
2
3
$ git push -u origin  	# 表示将 当前分支 推送并绑定 到 <origin>/同名上游分支
$ git push origin # 表示将 当前分支 推送 到 <origin>/同名上游分支(需先绑定)
$ git push # 表示将 当前分支 推送 到 <s远程>/同名分支(需先绑定且有权限)

最后一条指令就是我们最早提及的推送命令,它需要满足:

  1. 只有一个远程仓库;或者有多个仓库,但用 -u 绑定其中一个。
  2. 只有一条分支;或者有多条分支,但当前分支与上游分支同名并绑定。

Git学习笔记 #3 远程仓库使用
https://hwcoder.top/Git-Note-3
作者
Wei He
发布于
2021年9月3日
许可协议