阅读 Git | 理解 origin、HEAD 和 FETCH_HEAD
前言
如果大家平常都在使用 Git 作为版本控制工具的话,那么一定每天都能见到 origin
,诸如:
$ git push origin main $ git fetch origin main $ git pull origin main # ...
这里的 origin
,还有看似相同的 origin/master
、origin/main
又是什么呢?
正文
一、远程名称(Remote Name)
在 Git 中,其实无论是 origin
,还是 upstream
并没有特殊的含义,但由于被广泛使用,因此它们有了约定俗成、众所周知的含义。
就好比如说,在现实世界中小明、小红是再普通不过的名字,但由于在小学语文课本的对话中常被作为男一女一,用于表示对话的两个人而已,并没有特别的意义在里面。而在技术博文中,经常可以看到使用 foo
、bar
作为变量标识符举例,它们就相当于语文课本中的小明、小红一样。那么本文接下来要讨论的 origin
、upstream
等也是同样的道理。
先来个餐前菜...
如果我跟你说,以下两条命令是完全等效的,你是不是就差不多猜得出 origin
表示什么了?
$ git push origin main $ git push git@github.com:toFrankie/repo-demo.git main
是的,跟你猜的一样...
1.1 Origin
我们用示例来讲...
先在本地随意创建一个 Git 仓库 repo-demo
,然后新增一个 README.md
文件,接着 Commit 一下(如下图):
以上都没问题!接着,我们试着 Push 一下:
可以看到 git push
失败了,原因很容易理解:我们只是在本地创建一个仓库,并没有将本仓库与远程仓库进行关联,因此 Git 无法理解是将其推送至哪个代码托管平台,然后也不知道是平台上的哪个远程仓库,是 GitHub 平台的,还是 GitLab 平台的?是平台上的 React 仓库,还是 Vue 仓库,还是别的什么仓库?Git 统统都不知道,那么自然是无法替你办事了。
因此,我们需要做的就是把本地的 repo-demo
仓库与远程仓库关联一下(请注意,一个本地仓库是可以关联多个远程仓库的):
$ git remote add origin <repo_address>
这里用到了 origin
,我们先不管为什么用 origin
,用其他(比如 foo
)行不行的问题?(答案是可以的)
关联之后,再进行 Push 就能成功了。
那么 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 的话,会看到以下指引:
我们来分析一下,这配置表示什么意思。
[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
只是一个名称(别名),用于指向远程仓库。这个别名是可以自行修改的,比如命名为foo
、bar
等。使用别名好处是「方便」。
比起记住一个远程仓库地址,别名实在方便太多了。将 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>
比如,像这样:
然后,我们修改下远程名称为 foo
,也是可以的:
接着,我们随意修改个文件 Push 一下,是这样的 git push foo main
:
到这里,你应彻底明白 origin
是什么了吧。
前面提到过,一个本地仓库是可以关联多个远程仓库的,举个例子:
$ git remote add bar git@github.com:toFrankie/git-test-demo.git
我们可以查看下 .git/congfig
配置文件,如下(或者通过 git remote -v
查看)
从图中可以看到,别名 foo
和 bar
分别指向了两个不同的远程仓库,然后使用方法与 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:
当我们将 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 小结
除了 origin
、upstream
等有众所周知的含义的远程名称之外,我们还可以这样使用:
由于一个本地仓库是可以关联多个远程仓库的,因此,可以设置多个「别名」分别指向不同的远程仓库(比如一个 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
小结一下:
- 常见的
origin
、upstream
都只是通过git remote add
命令创建的名称(Remote Name),用于指向某个远程仓库(Remote URL)。
- 常用
origin
作为远程仓库的别名,是一个较为主流的做法。同时,也是各大代码托管平台的默认名称(即git clone
一个远程仓库, Git 会默认将origin
指向该仓库)。如果你觉得不爽,完全可以自定义(git remote set-url
)为“阿猫”、“阿狗”等名称。
- 查看本地仓库关联的远程仓库信息,可以在
.git/config
文件或通过git remote -v
命令查看。
- 使用别名的最大好处是,无需记住远程仓库的 URL,也是唯一的好处吧。不用也是完全 OK 的,完全可以直接使用远程仓库 URL,但我想不会有这种朋友吧。
(建议)若无特殊需求,不要为了个性,试图更改 origin
、upstream
等被广泛使用的别名,其中所表示的约定俗成的、众所周知的含义。
二、远程分支(Remote Branch)
常说的「远程分支」是远程仓库对应分支在本地的一个副本。比如常见的 origin/master
、origin/main
、origin/develop
等都是远程分支,可以在 .git/refs/remotes/
目录下看到。
# 查看所有本地分支 $ git branch # 查看所有远程分支(-r 是 --remotes 的简写) $ git branch -r # 查看所有本地分支和远程分支(-a 是 --all 的简写) $ git branch -a
可以通过 git branch -r
命令查看所有的远程分支:
frankie@iMac repo-demo % ???? git branch -r origin/HEAD -> origin/main origin/dev origin/main