在回答之前,我想更改这里的术语,因为您使用某些词汇的方式与大多数git文档使用的词汇和方式不匹配。
首先,让我们谈谈git存储库中提交生成的图表。
每次在存储库中创建提交时,必须指定以下项目。其中许多是隐含的;实际上,git commit
和git merge
(它们是两个主要的提交创建者)将为您查找所有这些内容:
- 一个父提交或一组父提交
- 作者和“作者日期”(通常与提交者相同)
- 提交者和“提交日期”(通常与上面的作者相同)
- “树”(通常从索引/暂存区获取),以及
- 提交消息(通过
-m
参数或git commit
启动编辑器等方式)
Git将所有这些内容合并并生成一个新的唯一SHA-1值 - 那些40个字符的难以处理的字符串,如9c4ea50db79d3ce6fe3abccf20f1af27abae45b2
- 就是提交(它通过SHA-1 ID将此提交对象存储在仓库中)。如果提交是普通提交,则具有一个父提交,git commit
通过HEAD
(git将其存储在.git
目录中)查找该父提交,然后在git commit
开始创建新提交之前。如果它是“根提交”,例如在新存储库中的第一次提交,则没有父提交。如果它是“合并提交”(由git merge
创建),则具有至少两个父ID(这些来自git存储在.git
目录中的文件)。存储在任何给定提交中的父项都是原始的SHA-1 ID。
因此,给定任何一个提交SHA-1 ID,您可以从存储库中读取提交并获取其父提交。阅读这些内容会获取它们的父项。继续直到达到根提交,您就可以绘制出可能包含分支点和合并点的图表:
o-o-o-o-o-o-o
\ /
o-o-o
这是一个包含十个提交“节点”(o
)的提交图,其中一些节点分支出去,然后再合并回来。我用 -
\
和 /
字符之间画了线来连接这些节点。
当我们使用 Git 时,我们通常称这些线为“分支”,这是一个合理的名称,但“分支”还有另一个含义。
接下来,让我们这样定义“分支”(或“本地分支”),这就是 Git 的真正工作方式:“分支”是一个名称,比如 master
或 develop
,它存储一个 SHA-1 ID,并且具有另外一个特殊属性。这个特殊属性是,当我们新建一个提交时,存储的 ID 会发生改变,这样分支名称始终标识着分支的“末端”。
请记住,每个提交都有它的父ID,但没有任何子ID。当您创建新的提交时,新的提交“通过这些存储的ID”指向旧的提交,但旧的提交永远不会改变。只给出父ID很难找到子ID,但只给出子ID很容易找到父ID。因此,只要分支名称“指向”分支的“末端”,我们就可以轻松地找到“分支的其余部分”。所以 Git 简单地保证本地分支名称始终标识着分支的末端。
(很不幸,“分支”既意味着“存储分支末端ID的本地名称”,也意味着“从末端开始向后工作形成的提交链”,在什么情况下使用哪一个几乎总是很明显,但只是“几乎”。)
现在让我们定义“远程”:远程是你在本地 Git 仓库中创建的一个名称,用于记住关于另一个 Git 仓库的某些事情,通常是另一台机器上属于其他人的仓库。远程有一个url(或者有时有几个:例如remote.origin.url
和可选的remote.origin.pushurl
),这就是 Git 将从另一个仓库获取和推送的方式。
当然,另一个仓库有(本地)分支——分支名称指向最新提交的代码,而我们经常需要知道另一个仓库的本地分支(名称和最新提交的代码),或者更准确地说,上次我们检查时这些分支里的内容是什么(它们会过期,所以我们不时刷新它们)。这就是“远程分支”的概念。因此,我们可以通过以下示例来粗略定义“远程分支”:远程分支remotes/origin/master
是本地分支master
在远程origin
上上次我们连接并询问“你的master
里有什么?”时的副本。
更准确地说:每个远程分支remotes/R/B
是本地分支B
在远程R
上上次我们检查并保存的副本。
现在我们只需要另一个定义,即“跟踪分支”。跟踪分支是一种本地分支,我们已经告诉git有一个对应的远程分支,并且在该远程分支上有一个对应的分支名称。我们告诉git这两件事情的底层方法是使用git config
配置两个字符串。例如,假设我们希望本地分支greyhound
跟踪名为hare
的远程racetrack
上的分支。要在最低级别上执行此操作,我们可以这样做:
git config branch.greyhound.remote racetrack
git config branch.greyhound.merge hare
(为了使事情更加复杂 - 这似乎是一个历史性的偶然事件 - 实际跟踪是通过远程fetch
配置映射的。但是remote.racetrack.fetch
几乎总是设置为读取+refs/heads/*:refs/remotes/racetrack/*
。在很大程度上,我们可以假设refs/remotes/
部分是不变的。)因此,再次,在最底层,当git想要查看greyhound
是否已经追上hare
时,它实际上是查看remotes/racetrack/hare
。 hare
部分来自branch.greyhound.merge
行,而racetrack
部分来自branch.greyhound.remote
。
如果您省略remotes/
部分,git将提供该部分。 因此,您通常可以只写origin/master
,而不是remotes/origin/master
。(如果您创建了歧义,例如如果您创建了一个名为origin
的本地分支,则会有一些例外情况。但是只要您不这样做,就可以省略remotes/
,人们和git通常都这样做。)
这里最后一个侧面说明,因为我有其他定义。特殊引用HEAD
通常是“符号引用”。符号引用包含另一个引用的名称,通常是本地分支名称。这是git知道将本地分支调整到新的分支尖端的方法:如果您“在主分支上”,HEAD
包含ref:refs/heads/master
,因此git commit
添加一个新提交并更新master
。如果HEAD
包含一个原始SHA-1值,则git commit
像往常一样添加新的提交,然后更改HEAD
以具有新的SHA-1值,但没有本地分支名称指向新的提交。这就是“脱离HEAD”状态。
哇!现在回到手头的问题。您即将遇到的困扰目前正在被修复的git中。
您想要或需要:
- 在名为
origin
的远程,必须有一个带有master
分支的git仓库
- 在名为
develop
的远程,必须有一个带有master
分支的git仓库
- 在本地仓库中,您想要将
master
设置为跟踪origin/master
的分支
- 在本地仓库中,您想要将
trunk
设置为跟踪develop/master
的分支
- 当您使用
git push origin
命令时,您希望将master
推送到origin的master
,并且可能根本不推送trunk
- 当您使用
git push develop
命令时,您希望将trunk
推送到develop的master
,并且可能根本不推送master
- 当您使用
git push
命令(没有其他参数)时,您想要... 好吧,我不太确定您想要什么,但我会在下面猜测。
步骤1至4只需进行一次。如果master
分支尚未在一个或两个仓库中创建,您可以执行以下操作:
git push -u origin master:master
git push -u develop trunk:master
(如果你的git push版本足够新,拥有-u
),你可以一次完成所有操作。或者,如果你有这个选项,你可以使用--set-upstream
,或者如果喜欢,完全手动跟踪使用git config
。但无论如何,如果需要,请在远程仓库上创建分支,然后将它们捡起(作为本地仓库中的“远程分支”)并设置本地分支以“跟踪”远程分支。
接下来是剩余步骤,这些步骤可能会有点混乱。
在最近的git版本中(至少1.7开始),有一个配置设置push.default
,它影响当你省略refspec时git push
的工作方式,即步骤5到7中的push
命令。你需要将其设置为upstream
。这不是默认设置,所以必须运行:
git config push.default upstream
进行设置。
如果你运行裸的git push
(如#7),git使用相同的跟踪分支信息来选择远程。所以,现在你已经改变了push.default
,如果你在分支master
上并运行git push
,你实际上运行的是git push origin master:master
,如果你在分支trunk
上并运行git push
,你实际上运行的是git push develop trunk:master
。
然而,如果你只是让push.default upstream
设置完成它的工作,并在master
上运行git push develop
,那么你没有告诉它推送名为trunk
的本地分支。所以,要使5和6起作用,你需要两个不同的git配置项。
如果你运行git push remote
(没有refspec参数),git push
查找remote.remote.push
,如果设置了,就使用它。因此,你可以设置remote.origin.push master:master
和remote.develop.push trunk:master
。现在git push develop
有一个设置,不需要使用push.default
了。