欢迎您访问 最编程 本站为您分享编程语言代码,编程技术文章!
您现在的位置是: 首页

阅读 Git | 理解 origin、HEAD 和 FETCH_HEAD

最编程 2024-03-08 10:53:03
...

前言


如果大家平常都在使用 Git 作为版本控制工具的话,那么一定每天都能见到 origin,诸如:

$ git push origin main
$ git fetch origin main
$ git pull origin main
# ...


这里的 origin,还有看似相同的 origin/masterorigin/main 又是什么呢?


正文


一、远程名称(Remote Name)


在 Git 中,其实无论是 origin,还是 upstream 并没有特殊的含义,但由于被广泛使用,因此它们有了约定俗成、众所周知的含义。

就好比如说,在现实世界中小明、小红是再普通不过的名字,但由于在小学语文课本的对话中常被作为男一女一,用于表示对话的两个人而已,并没有特别的意义在里面。而在技术博文中,经常可以看到使用 foobar 作为变量标识符举例,它们就相当于语文课本中的小明、小红一样。那么本文接下来要讨论的 originupstream 等也是同样的道理。


先来个餐前菜...


如果我跟你说,以下两条命令是完全等效的,你是不是就差不多猜得出 origin 表示什么了?

$ git push origin main
$ git push git@github.com:toFrankie/repo-demo.git main


是的,跟你猜的一样...


1.1 Origin


我们用示例来讲...


先在本地随意创建一个 Git 仓库 repo-demo,然后新增一个 README.md 文件,接着 Commit 一下(如下图):

16.webp.jpg

以上都没问题!接着,我们试着 Push 一下:

17.webp.jpg


可以看到 git push 失败了,原因很容易理解:我们只是在本地创建一个仓库,并没有将本仓库与远程仓库进行关联,因此 Git 无法理解是将其推送至哪个代码托管平台,然后也不知道是平台上的哪个远程仓库,是 GitHub 平台的,还是 GitLab 平台的?是平台上的 React 仓库,还是 Vue 仓库,还是别的什么仓库?Git 统统都不知道,那么自然是无法替你办事了。


因此,我们需要做的就是把本地的 repo-demo 仓库与远程仓库关联一下(请注意,一个本地仓库是可以关联多个远程仓库的):

$ git remote add origin <repo_address>


这里用到了 origin,我们先不管为什么用 origin,用其他(比如 foo)行不行的问题?(答案是可以的)


关联之后,再进行 Push 就能成功了。


14.webp.jpg


那么 git remote add 内部做了什么默默无闻的工作呢,它其实是往 .git/config 中写入了一个叫 [remote "origin"] 配置:

[core]
    repositoryformatversion = 0
    filemode = true
    bare = false
    logallrefupdates = true
    ignorecase = true
    precomposeunicode = true
[remote "origin"]
    url = git@github.com:toFrankie/repo-demo.git
    fetch = +refs/heads/*:refs/remotes/origin/*


如果你本地的仓库是通过 git clone 下来的,Git 会默认将远程仓库命名为 origin,自动帮你关联上远端仓库(可在 .git/config 文件中看到已有 [remote "origin"] 配置项了),因此 Commit 之后就能直接 Push 了。


When a repo is cloned, it has a default remote called origin that points to your fork on GitHub, not the original repo it was forked from.(引自 Github page


如果我们在 GitHub 新创建一个 Repository 的话,会看到以下指引:


15.webp.jpg

我们来分析一下,这配置表示什么意思。

[remote "origin"]
    url = git@github.com:toFrankie/repo-demo.git
    fetch = +refs/heads/*:refs/remotes/origin/*


通过 git remote add 命令,添加了一个叫做 origin 的远程名称(Remote Name),


  • 其中 url 参数,表示该远程名称对应的远程仓库地址。
  • 其中 fetch 参数分为两部分,以冒号 : 进行分割,冒号左边表示本地仓库文件夹,冒号右边表示远程仓库在本地的副本文件夹。里面的加号 + 表示往里面添加数据的意思。

当使用 git fetch origin 时,Git 将远程仓库下的所有分支拉取到本地的 refs/remotes/origin/ 目录下,然后 git merge 时,它会把 refs/remotes/origin/ 目录下的对应分支合并到 refs/heads/ 目录下对应分支上。

那么 origin 究竟是什么呢?


请注意,origin 只是一个名称(别名),用于指向远程仓库。这个别名是可以自行修改的,比如命名为 foobar 等。使用别名好处是「方便」。


比起记住一个远程仓库地址,别名实在方便太多了。将 origin 作为远程仓库的别名是较为普遍的做法,况且所有代码托管平台默认就是 origin


回到文章开头的例子:

$ git push origin main

# 相当于(其中 origin 指向了 git@github.com:toFrankie/repo-demo.git 远程仓库)
$ git push git@github.com:toFrankie/repo-demo.git main


以上两种方式是完全等价的,这样就更能体现别名的优势了,简洁很多。

既然是别名,自然是可以修改的,主要有以下命令:

# 新增远程名称(一个本地仓库,可以关联多个远程仓库)
$ git remote add <remote-name> <repo-address>

# 删除已存在远程名称(只会移除本地仓库与远程仓库的管理,不会删除远程仓库的代码哈)
$ git remote rm <remote-name>

# 更新远程名称关联的远程仓库
$ git remote set-url <remote-name> <repo-address>

# 修改远程名称(也可以先删除再添加)
$ git remote rename <old-remote-name> <new-remote-name>


比如,像这样:


20.webp.jpg

然后,我们修改下远程名称为 foo,也是可以的:

21.webp.jpg

接着,我们随意修改个文件 Push 一下,是这样的 git push foo main

22.webp.jpg

到这里,你应彻底明白 origin 是什么了吧。

前面提到过,一个本地仓库是可以关联多个远程仓库的,举个例子:

$ git remote add bar git@github.com:toFrankie/git-test-demo.git


我们可以查看下 .git/congfig 配置文件,如下(或者通过 git remote -v 查看)


23.webp.jpg


从图中可以看到,别名 foobar 分别指向了两个不同的远程仓库,然后使用方法与 origin 是相同的,比如:

# 将本地的 main 分支推送至 foo 对应的远程仓库(repo-demo)
$ git push foo main

# 将本地的 main 分支推送至 bar 对应的远程仓库(git-test-demo)
$ git push bar main


1.2 upstream(一个特殊的 remote name)


upstream 的译为“上游”。当你 git clone 一个别人的 Repository 到本地,由于你不是该仓库的成员,因此你是无法向该仓库推送代码的。此时,相较于本地仓库,别人的这个 Repository 称为 upstream

我们可以 Fork 这个 Repository 到自己 GitHub 账号下,然后通过 git clone 将这个 Fork 出来的仓库克隆到本地电脑上。(下文将这个别人的仓库称为 Upstream-Repo,Fork 出来的仓库称为 Origin-Repo

大致关系如图所示(源自),其中 Upstream-Repo 对应图中的 Original,Origin-Repo 对应图中 Fork:

24.webp.jpg


当我们将 Origin-Repo 克隆到本地,Git 会默认创建一个 origin 的别名指向 Origin-Repo 的仓库地址。


如果要跟踪 Upstream-Repo 仓库的变更,您需要添加另一个名为 upstream 的别名,使其指向 Upstream-Repo 仓库。

# 1. 添加上游仓库的别名
$ git remote add upstream <upstream-repo-address>

# 2. 获取上游仓库的变更
$ git fetch upstream

# 3. 有需要的话,可以通过 merge 或 rebase 方式合并到本地分支中,比如:
$ git merge upstream/main


尽管添加了 upstream,诸如 git push upstream main 等方式试图向 Upstream-Repo 提交代码仍然是不被允许的,因为你不是 Upstream-Repo 仓库的成员。想给 Upstream-Repo 仓库贡献代码的话,只能通过 Pull Request 的方式。

当然,这一节提到的 upstream 也是一个约定俗称的别名,也是可以自定义的。


1.3 小结


除了 originupstream 等有众所周知的含义的远程名称之外,我们还可以这样使用:


由于一个本地仓库是可以关联多个远程仓库的,因此,可以设置多个「别名」分别指向不同的远程仓库(比如一个 GitHub、一个 GitLab、一个 Gitee),然后通过别名的方式方便、快速地拉取某个远程仓库的代码或者将代码推送至某个远程仓库。


# 添加 github 别名
$ git remote add github git@github.com:toFrankie/repo-demo.git

# 添加 gitlab 别名
$ git remote add gitlab git@gitlab.com:toFrankie/repo-demo.git

# 添加 gitee 别名
$ git remote add gitee git@gitee.com:toFrankie/repo-demo.git


小结一下:


  • 常见的 originupstream 都只是通过 git remote add 命令创建的名称(Remote Name),用于指向某个远程仓库(Remote URL)。
  • 常用 origin 作为远程仓库的别名,是一个较为主流的做法。同时,也是各大代码托管平台的默认名称(即 git clone 一个远程仓库, Git 会默认将 origin 指向该仓库)。如果你觉得不爽,完全可以自定义(git remote set-url)为“阿猫”、“阿狗”等名称。
  • 查看本地仓库关联的远程仓库信息,可以在 .git/config 文件或通过 git remote -v 命令查看。
  • 使用别名的最大好处是,无需记住远程仓库的 URL,也是唯一的好处吧。不用也是完全 OK 的,完全可以直接使用远程仓库 URL,但我想不会有这种朋友吧。


(建议)若无特殊需求,不要为了个性,试图更改 originupstream 等被广泛使用的别名,其中所表示的约定俗成的、众所周知的含义。


二、远程分支(Remote Branch)


常说的「远程分支」是远程仓库对应分支在本地的一个副本。比如常见的 origin/masterorigin/mainorigin/develop 等都是远程分支,可以在 .git/refs/remotes/ 目录下看到。

# 查看所有本地分支
$ git branch

# 查看所有远程分支(-r 是 --remotes 的简写)
$ git branch -r

# 查看所有本地分支和远程分支(-a 是 --all 的简写)
$ git branch -a


可以通过 git branch -r 命令查看所有的远程分支: