在之前的笔记中讲过 Git 的分支操作,无论简单还是深奥,那些都是一个仓库内的事情。今天这篇笔记重点描述两个仓库之间的联系,涉及到的 git 命令有 pull、push、clone 等,难点围绕着两个概念: remote-tracking branch 和 tracing branch。
序
如果接触过 github,或者国内的 OSChina,那么必然用过 git clone 命令。我一般是这么用的:
- 先在 github 上创建一个仓库(有时候还会捎带着使用 README.md 文件);
- 然后使用 git clone 克隆到本地;
- 在本地进行文件添加、修改等操作后 commit;
- 在本地执行 push 推送到远程仓库(有时候还会将本地新增的 branch 也 push 上去)。
针对本地原本存在的仓库,如何上传到远程版本仓库?——这是我一直摸索的,至今也没有弄明白的事情。好在我确定等我整理完这篇笔记,我就会有答案了!结论见第 5 章。
要解开上述问题,我们需要先弄清楚两个问题:
- 在 github(还有 OSChina)上创建仓库时做了哪些工作?
- git clone 究竟做了哪些工作?
OSChina 新建项目
我们新建项目时对话框如下,我在图中共标注了 6 个序号:
- 默认为空,不选择任何语言;我一般都是选择 C++。
- 默认为空,不添加 .gitignore 文件;可选条目很丰富,后期只需要我们自己微调。
- 默认为空,不添加任何许可证;我貌似一直没用过。
这六个选项中只有第一个不影响仓库本身,也就是除此之外的五个选项,只要你选择了任意一个——或者你添加了 .gitignore 文件,或者你添加了许可证,或者你使用了 Readme 文件,或者你使用了模板——那么仓库都不再是一个空仓库,会默认生成 master 分支,并产生第一次提交!
如果你想创建一个空仓库,2、3 选项需要保持默认,4、5、6 选项去掉勾选复选框。
如果你创建了一个空仓库,就会看到“快速设置——xxxx”的页面,其中会提到“强烈建议所有的git仓库都有一个README, LICENSE, .gitignore文件”,并且会给出简易的命令行入门教程:
1 | …… |
如果不是空仓库,那么你就会看到 master 分支,看到首次 commit 记录,看到你让 OSChina 自动生成的那些文件。
空仓库和非空仓库有什么影响呢?稍后就会看到,我们先认识一下入门教程中提到的两个命令。
git remote && git push
即便是在 OSChina 的 新手帮助手册 中,我们看到出现频率更多的是 git push
,而非 git clone
。看来 git clone
真不是一个好命令……
我们将 A 仓库的修改推送到 B 仓库,首先得建立两者之间的联系。通过在 A 仓库添加远程仓库实现:
运行
git remote add <shortname> <url>
添加一个新的远程 Git 仓库,同时指定一个你可以轻松引用的简写。
执行 git remote 可以查看已经配置的远程仓库服务器,也可以指定选项 -v
,看到略微详细一些的内容。
如果想要查看某一个远程仓库的更多信息,可以使用
git remote show <remote-name>
命令
添加远程仓库之后,就可以从远程仓库拉取数据(这个稍后介绍),也可以推送数据到远程仓库。
执行 git push <remote-name> <branch-name>:<remote-branch-name>
会将本地 <branch-name>
分支的数据推送到 <remote-name>
服务器的 <remote-branch-name>
分支。
git push 的默认行为
我们一般不会使用上述命令的完整形式,在实际业务中多见到省略远程分支名、省略远程分支名 & 本地分支名、省略远程分支名 & 本地分支名 & 省略远程主机名的形式。我曾误以为:
省略远程分支名,默认与本地分支名相同;在省略远程分支名的前提下,省略本地分支名,默认为当前分支;在省略远程分支名、本地分支名的前提下,省略远程主机名:如果只存在唯一远程主机,则使用;否则报错;
反复阅读 git-push 的英文手册之后,发现事实要复杂得多,上述的理解根本就是错的。
git push [<repository> [<refspec>…]]
When the command line does not specify what to push with
<refspec>...
arguments, the command finds the default<refspec>
by consultingremote.*.push
configuration, and if it is not found, honorspush.default
configuration to decide what to push.When neither the command-line nor the configuration specify what to push, the default behavior is used, which corresponds to the
simple
value forpush.default
: the current branch is pushed to the corresponding upstream branch, but as a safety measure, the push is aborted if the upstream branch does not have the same name as the local one.
ps:Git 1.x 的默认策略(即 push.default 属性)是 matching;在Git 2.0 之后 simple 成为新的默认策略。simple 策略要求存在上游分支 && 本地分支和上游分支同名。引用来源
<refspec>…
引用来源
Specify what destination ref to update with what source object. The format of a
<refspec>
parameter is an optional plus +, followed by the source object<src>
, followed by a colon:
, followed by the destination ref<dst>
.
疑问:上述文件中只提到了省略远程分支名 & 本地分支名时的行为,但只省略远程分支名时的行为呢?省略远程分支名 & 本地分支名 & 省略远程主机名的行为呢?
我只找到这样一句:
$ git push origin serverfix
- This is a bit of a shortcut. Git automatically expands theserverfix
branchname out torefs/heads/serverfix:refs/heads/serverfix
, which means, “Take my serverfix local branch and push it to update the remote’s serverfix branch.” 引用来源
尽信书不如无书
关于 Git 远程操作的命令的具体用法,可以去官网查阅,如果觉得手册中的内容细致得太花费精力,可以学习阮一峰老师的 Git远程操作详解,其中对于 git clone、git remote、git fetch、git pull 和 git push 的介绍完全能够满足我们的日常使用。ps:有些地方,因为翻译不恰当(其实很不负责)的问题很是头疼,花费了一下午时间。
阮老师在介绍 git push 命令的时候提到:
如果当前分支与远程分支之间存在追踪关系,则本地分支和远程分支都可以省略。
$ git push <remote-name>
(我反复读了 git push 的英文手册,此“追踪关系”是从remote.<repository>.push
configuration variable 来的,不是下文中提到的 tracking)如果当前分支只有一个追踪分支,那么主机名都可以省略。
$ git push
(想来此处的“追踪分支”也是指见鬼,这个“追踪分支”指的是什么?如果不加任何参数使用remote.<repository>.push
configuration variable,或者是指只有一个远程仓库$ git push
似乎只有下面一种情况:指定了上游分支)如果当前分支与多个主机存在追踪关系(1),则可以使用
[-u | --set-upstream]
选项指定一个默认主机(2),这样后面就可以不加任何参数使用git push
(3)。(1. “多个”到底指的是什么?什么时候会存在多个?remote.<repository>.push
configuration variable 2.“默认主机”的表述明面上似乎更容易理解,但本质上是错的:此选项就是指定 upstream branches,建立追踪关系 3. 事实:存在remote.<repository>.push
时不会使用push.default
,所以也就不会解析上游分支……)
类似观点见博客评论 过客 说,上述引用文字本身一团糟,我备注的文字也是一团糟,官方手册中我也没找到任何依据。
查看配置
remote.<repository>.push
configuration variable 可以通过 $ git config --add remote.origin.push fixbug
设置。
- 查看仓库级的 config,命令:
git config –local -l
- 查看全局级的 config,命令:
git config –global -l
- 查看系统级的 config,命令:
git config –system -l
- 查看当前生效的配置, 命令:
git config -l
命令参考来源 Git Config 命令查看配置文件
追踪(tracking)关系
我们先来区分三个概念:remote branches、 remote-tracking branches、 tracking branches
remote branches 远程分支:远程仓库中的分支
remote-tracking branches 远程跟踪分支:
Remote-tracking branches are references to the state of remote branches. They’re local references that you can’t move; they’re moved automatically for you whenever you do any network communication. Remote-tracking branches act as bookmarks to remind you where the branches in your remote repositories were the last time you connected to them. 引用来源
远程跟踪分支,但真实的意思应该是,远程分支在本地仓库的缓存,不执行git fetch命令,不会获取到远程分支的更新。千万不要将这些分支当做远程分支,以为是它们是自动更新的。引用来源
tracking branches 跟踪分支:
Checking out a local branch from a remote-tracking branch automatically creates what is called a “tracking branch” (and the branch it tracks is called an “upstream branch”). Tracking branches are local branches that have a direct relationship to a remote branch.
When you clone a repository, it generally automatically creates a master branch that tracks origin/master.
综上:
remote-tracking branches 和 tracking branches 都是本地分支,可以认为 remote-tracking branches 是 remote branches 在本地的镜像,在这个“镜像”上不能进行修改。
这个镜像 tracks 远程分支,所以称为 remote-tracking branches。
分支的概念本质上是指向某次 commit 的指针,remote-tracking branches 指针是死的,不能移动的;好在我们可以以它为基础建立 tracking branches,检出到工作区进行工作。
tracking branches 追踪 remote-tracking branches。在这个子场景里,后者又称为前者的 upstream branch。
参考 Git Branching - Remote Branches
指定上游分支
remote-tracking branches 对 remote branches 的跟踪是固定的。和 git 操作有紧密联系的是另外一个 tracking!本节也只讨论这个 tracking。
通过上面几个概念,我们知道了 tracking 关系的存在,那么我们怎么查看分支之间的追踪关系呢?怎么建立分支之间的追踪关系呢?
查看 $ git branch -vv
。ps 啰里啰嗦多说点:参数 -vv
用来查看追踪关系/上游分支的,如果只是单纯的用 $ git push <remote-name> <branch-name>[:<remote-branch-name>]
推送过,或者在设置了 remote.<repository>.push
configuration variable 的基础上使用 $ git push <remote-name> [<branch-name>[:<remote-branch-name>]]
推送过,而未设置 upstream branch,这个命令是看不到多余信息的,毕竟它本意也就是用来查看 upstream branch 而已。
从上文中,我们知道将 remote-tracking branches 检出 git checkout -b <branch-name> <remote-tracking branches>
时会建立追踪关系,其他的情况呢?
将本地分支与远程某分支建立追踪关系分为几种情况:
git branch (--set-upstream-to=<upstream> | -u <upstream>) [<branchname>]
Set up
<branchname>
‘s tracking information so<upstream>
is considered<branchname>
‘s upstream branch. If no<branchname>
is specified, then it defaults to the current branch.此命令用于给已存在的分支指定上游分支。一般使用 remote-tracking branches 作为
<upstream>
,但也可以指定本地的其他分支作[<branchname>]
的<upstream>
——语法上允许这么操作,但这么配置没有意义,两个完全相同的本地分支。相关命令:
git branch --unset-upstream [<branchname>]
git branch [--set-upstream | --track] <branchname> [<start-point>]
When creating a new branch, set up
branch.<name>.remote
andbranch.<name>.merge
configuration entries to mark the start-point branch as “upstream” from the new branch. 引用来源此命令为创建分支命令,在创建新分支的同时指定上游分支。如果未指定
<start-point>
值,默认为当前分支。git push [-u | --set-upstream] [<repository> [<refspec>…]]
For every branch that is up to date or successfully pushed, add upstream (tracking) reference,……引用来源
我目前习惯使用的 git pull、git push 其实都是使用了很多默认参数的,而我却忽视了默认参数的存在。
结论:追踪关系的本质是 remote-tracking branches 和 local branches 的映射(前者成为后者的 upstream branch),建立映射后的 local branches 称为 tracking branches。
上游分支相关的两个变量:branch.<name>.remote
配置项、branch.<name>.merge
配置项
本地仓库与远程仓库建立联系
远程仓库如果是空的,可以直接向其 push 任意分支;
如果不是空的,就不能(在已存在分支上)直接向其 push 内容,需要先 pull 合并后再进行 push 操作(pull 有限制条件:要么本地仓库为空,要么两者有共同祖先)。
OSChina 空仓库
- 本地无仓库,git init 建立仓库,一般来说也会进行 git commit 提交;
- 本地存在仓库,git remote add 添加远程仓库后,使用
git push -u <remote-name> <branch-name>
在远程仓库创建分支、建立追踪关系、并将本地分支数据推送到远程仓库; - 拉取怎么做呢?
OSChina 存在初始提交
本地无仓库,使用 git clone 简单省事;本地存在仓库,一般来说远程仓库的初始提交默认都是 mater 分支,所以:
- 如果本地分支不是和远程仓库 master 分支(或已存在的其他分支)建立追踪关系,直接使用
git push -u <remote-name> <branch-name>
即可; - 如果本地分支要与远程仓库已存在的分支建立追踪关系,
首先使用 git branch –set-upstream 建立追踪关系,然后 git pull 拉取远程分支数据,再将本地更新推送上去;这是错误的,拉取时会直接报错 “fatal: refusing to merge unrelated histories”
强调:两个没有任何关系的分支(即,没有共同祖先)无论在理论还是实际操作上都是不可以进行合并的。所以,在远程仓库存在数据且保有本地仓库的前提下,在两者之间强加联系,只能在远程仓库另建分支(此分支和远程仓库原有数据无关),远程仓库会存在两个毫无关系的、相互独立的分支!还不如在最初直接使用空的远程仓库!
如果远程仓库存在工作区?
这一小节描述的是一个问题,此问题出现在向远程仓库推送的过程中。当远程仓库由 git init 创建(无 --bare
参数),远程仓库存在工作区,且 push 的目的分支恰好是远程仓库的检出分支时,就会报错!
我们使用 OSChina、github 等版本库服务时不会出现这个错误,我们自己搭建 git 服务器(使用 git init –bare)时一般也会不会出现这个问题。出现此问题最普遍的场景是,我们 clone 电脑上版本库作为 bak,在 bak 中修改后 push 回原仓库。
- Git: push 出错的解决 master -> master (branch is currently checked out)
- http://www.cnblogs.com/abeen/archive/2010/06/17/1759496.html
git clone 做了哪些工作?
我们看官方文档的描述:
- Clones a repository into a newly created directory,
- creates remote-tracking branches for each branch in the cloned repository (visible using
git branch -r
), - and creates and checks out an initial branch that is forked from the cloned repository’s currently active branch.
- After the clone, a plain
git fetch
without arguments will update all the remote-tracking branches, - and a
git pull
without arguments will in addition merge the remote master branch into the current master branch, if any (this is untrue when “–single-branch” is given; see below). - This default configuration is achieved by creating references to the remote branch heads under
refs/remotes/origin
and by initializingremote.origin.url
andremote.origin.fetch
configuration variables.
如果有更多疑惑,请参考 Git 分支 - 远程分支,查看英文原著更容易理解 Git Branching - Remote Branches