首页 > 解决方案 > -m 在 git remote add 中有什么作用

问题描述

我希望

git remote add -m <local branch> <origin> <url> 

将允许我为

git push <origin> <local branch>/master

通过 git push <origin>

  1. 做什么git remote add -m <branch name>
  2. 有没有办法制作“别名”命令,即git push <origin> = git push <origin> <local branch>/master

标签: git

解决方案


TL;博士

你不能在这里得到你想要的,因为git push需要refspecs

让我分几个部分来回答这个问题,因为这个问题本身就是一个问题。

我希望 [ git remote add -m ...] 将允许我为 [首先没有意义的推送命令] 创建一个“快捷方式”

它不会,但主要是因为该git push命令实际上没有多大意义。我们可以找到有用的东西git remote add -m 但在此之前,我们需要一些好的背景。

关于的一些基本事实git push

让我们先回顾一下git push自己。至少需要git push两条信息:

  • 如何联系其他 Git;和
  • 一旦建立联系,该告诉其他 Git 什么。

“要告诉什么”实际上是Git 称为refspecs的一个或多个事物的列表,我们稍后会介绍。第一部分——<em>如何联系其他 Git——有着悠久且有些肮脏的历史,但现代的方法是使用远程.

这或多或少自然地导致了语法:

     git push remote refspec [refspec2 refspec3 ...]

请注意,这里的参数是positional,因为关键字后面的第一个参数1push是远程。 遥控器只是一个简短的名称,例如origin,Git 用于多种目的,最重要的是存储一个长 URL——嗯,可能比六个字母长一个origin,例如。如果你不小心在这个位置输入了一个分支名称,Git 仍然会将其视为远程名称:

$ git push master
fatal: 'master' does not appear to be a git repository

换句话说,这里的任何东西,在 Git 看来,就像一个普通的词——不是原始 URL,这是允许的,也不是使用我所谓的“有点肮脏的历史”定义的东西——需要定义为远程,命令进入的地方git remote。我们将解决这个问题,但我们需要先完成git push命令。

远程名称之后的部分origin是一些refspecs的列表。我在这里反复使用了“refspec”这个词,但没有定义它。 Git Glossary以这种方式定义了它,但毫无帮助:

fetch 和 push 使用“refspec”来描述远程 ref 和本地 ref 之间的映射。

我想说的是,在它的第二简单的形式中,它只是一对用冒号分隔的引用master:master或者develop:develop是很好的例子。在 Git 词汇表中也定义了一个参考ref,这一次更好。您现在可以看到更多详细信息,或者等待:我将在下面的另一部分中详细介绍。

我说这种master:master形式是第二种最简单的形式,因为最简单的 refspec 形式去掉了冒号,只使用了一个名称。这意味着master,它是一个有效的分支名称,也是一个有效的 refspec!当您编写git branch masterorgit checkout master时,您使用单词master作为分支名称,但是当您编写时git push origin master,您使用单词master作为refspec

这是值得坐下来思考的:一个词的使用方式会影响它的含义。 在许多人类语言中都是如此,2以及在 Git 命令行使用的小型计算机语言等计算机语言中也是如此。在 Git 的例子中,参数的位置git push决定了 Git 如何使用它们:第一个是远程,其余的是 refspecs。

我们稍后再讨论这个问题。现在让我们继续讨论遥控器和git remote.


1更准确地说,它是关键字之后的第一个非标志参数push,因为您可以运行git push -f origin ...,例如。

2例如,拼写为let的英语单词可以是动词,意思是允许,也可以是动词,意思是出租(“an apartment for let”或“rooms to let”)。这个特殊的例子是惊人的,因为它也是一个名词,意思是阻碍,动词意思是防止,使它成为一个自动反义词或同义词a la cleave,分开,或与助词to,紧贴在一起。为了好玩,请参阅 Wikipedia 链接以获取更多反义词。


git remote用于定义或修改遥控器

如我们所见,远程是存储 URL 的短名称。当您运行git clone将其他 Git 的存储库复制到您自己的系统时,您的 Git 会为您设置一个远程,以保存您传递到的 URL,git clone这样您就不必再次输入它。您的 Git 将此 URL 保存在 name 下origin,这就是为什么您有一个名为origin. 3

如果您使用git init, notgit clone来创建您的存储库,您将不会有一个名为origin. 在这种情况下,您可以自由创建一个。如果您愿意,您可以调用它origin,因为您可以随意调用它。

要将新的远程(无论您想如何称呼)添加到现有存储库,您可以使用. 这里需要the和 the ,因为远程主要URL 的名称。名称和 URL 现已配对,此时,您可以使用它对其进行更改git remote add name urlnameurlgit remote

您询问的选项, 只是作为单个命令后跟, 的变体。这意味着它的完整描述列在文档部分下-m branchgit remote addgit remote set-headset-headgit remote

设置或删除命名远程的默认分支(即 symbolic-ref 的目标refs/remotes/<name>/HEAD)。不需要远程的默认分支,但允许指定远程的名称来代替特定的分支。...

不幸的是,这一点信息虽然是正确的,但它本身并没有什么用处。要了解什么set-head——以及因此——有什么-m好处,我们需要看看 Git 处理名称到哈希 ID 转换的方式,这是一个独立于git push和的概念git fetch


3其实git clone本质上是git init在一个新的目录下,后面git remote add跟着,后面git fetch跟着git checkout。我在这里的这个简短列表中省略了一些额外的可选步骤,并且git clone还处理了被中断和删除它可能已创建的任何不完整存储库的特殊情况,但实际上,这四个步骤 - 如果您计算制作,则为五个新目录作为一个步骤 - 将为您提供一个工作克隆。


参考:分支名称、远程跟踪名称、标签等

正如我在其他地方所说,Git 主要关注的是提交,而提交——事实上,所有的 Git 对象——都由它们的哈希 ID 标识。任何 Git 对象的哈希 ID 都是由字母和数字组成的大而难看的字符串,例如5d826e972970a784bd7a7bdf587512510097b8c7(这是 Git 存储库中的实际提交 Git)。这些东西对于人类来说太笨重了,特别是因为它们似乎是完全随机的(尽管它们绝不是随机的)。

鉴于哈希 ID 存在问题,并且我们通过向它们添加提交(同时保留旧提交)来不断增长存储库,Git 有一个解决方案。这些是分支名称、标签名称等。总的来说,Git 将这些名称称为references。为了使这些不同的名称相互配合,Git 将它们放在namespaces中,我将把它的定义留给 Wikipedia。在这里只要说master是 的缩写就足够了refs/heads/master,而标签 likev2.3是 的缩写refs/tags/v2.3

Git 使用这些引用来存储哈希 ID。你的 Git 有一张大桌子,你可以随时使用git for-each-ref. 这个管道命令 <sup>4 打印整个表,或者如果你这样说的话,可以打印一些子集,并让你给出格式化指令,告诉它如何打印出来,但这里是默认输出的一个示例:

$ git for-each-ref
5d826e972970a784bd7a7bdf587512510097b8c7 commit refs/heads/master
5d826e972970a784bd7a7bdf587512510097b8c7 commit refs/remotes/origin/HEAD
98cdfbb84ad2ed6a2eb43dafa357a70a4b0a0fad commit refs/remotes/origin/maint
5d826e972970a784bd7a7bdf587512510097b8c7 commit refs/remotes/origin/master
bc1bbc6f855c3b5ef7fcbd0f688f647c4e5b208b commit refs/remotes/origin/next
dfcf84ebfa17eb0bb3b57806fa530e87d8c8f1b8 commit refs/remotes/origin/pu
b2cc3488ba006e3ba171e85dffbe6f332f84bf9a commit refs/remotes/origin/todo
[massive snippage]
e8f2650052f3ff646023725e388ea1112b020e79 tag    refs/tags/v2.17.0
8548c552c627322ac6e8a221d6fe9be531c3aeb1 tag    refs/tags/v2.17.0-rc0
53e83f73a113f0dbfec850d222681ae21eadd834 tag    refs/tags/v2.17.0-rc1
5c35c7ea8cfdb6951a2e3923309d46138e1724b4 tag    refs/tags/v2.17.0-rc2
5b62a68cad663be4cd19fd59d053c57d88811c80 tag    refs/tags/v2.17.1
7f8020239f3b8ebc28299a97ba7db23d74e65447 tag    refs/tags/v2.17.2

左边大丑的是hash ID,右边的名字,以 开头refs/,就是引用的全名。

还有另一个基本的 Git 管道命令,git rev-parse它将引用(或该引用的简短形式)转换为哈希 ID:

$ git rev-parse master
5d826e972970a784bd7a7bdf587512510097b8c7
$ git rev-parse v2.17.0
e8f2650052f3ff646023725e388ea1112b020e79

一些 Git 命令,比如git checkoutand git branch,很自然地需要一个分支名称,并且希望你使用一个没有前导refs/heads/部分的命令。其他 Git 命令,例如git log,则不会:它们通常运行git rev-parse来完成将名称转换为哈希 ID 的工作。

什么时候git rev-parse做这项工作,将名称转换为大多数 Git 命令真正需要的哈希 ID,它遵循一个六步过程。此过程在gitrevisions 文档中进行了描述。这里<refname>是您在命令中输入的文字字符串,如、masterv2.3origin/master

  1. 如果$GIT_DIR/<refname>存在,这就是你的意思(这通常只对HEAD, FETCH_HEAD, ORIG_HEAD,MERGE_HEAD和有用CHERRY_PICK_HEAD);

  2. 否则,refs/<refname>如果存在;

  3. 否则,refs/tags/<refname>如果存在;

  4. 否则,refs/heads/<refname>如果存在;

  5. 否则,refs/remotes/<refname>如果存在;

  6. 否则,refs/remotes/<refname>/HEAD如果它存在。

这个相当长的序列实际上非常重要:它解释了很多关于 Git 的内容,尤其是在一些糟糕或极端的情况下。特别是,如果您不小心创建了一个名为的标签master,Git 会开始表现得很奇怪:git checkout master仍然可以工作,但git rev-parse master会开始告诉您有关标签的信息,而不是分支的尖端。

让我们暂时回到桌子的想法。该表记录了每个引用的当前哈希 ID。标记名称应该是一个特定哈希 ID 的人类可读名称,因此永远不会更改,但分支名称应该是分支上最新提交的人类可读名称。这意味着从分支名称到哈希 ID 的映射是不断变化的!每次向master分支添加新提交时,Git 都会更改查找名称的结果master

但是——除了一些知道他们正在处理一个分支名称的命令——查找名称master需要经过六个步骤的过程,因为 Git 认为这可能master是一个标签名称。因此,在此过程的第 3 步,Git 检查表:是否存在refs/tags/master? 如果不是,Git 继续第 4 步,并查找refs/heads/master.

这意味着,如果您不小心创建了一个名为的标签master一些Git 命令会在第 3 步停止并为您获取该标签的哈希 ID 。其他 Git 命令,例如git checkout master,首先尝试将名称作为分支,从第 4 步开始,并从分支名称中找到哈希 ID!所以现在,一些 Git 命令向您显示标记的提交,而其他命令则使用分支


4管道命令,在 Git 中,是任何用于其他命令用于完成某些特定工作的命令,而不是普通人每天使用的命令。这并不意味着不能每天都使用它们,但它们通常不会被美化:例如,它们通常不使用颜色,或者如果它们很长则通过寻呼机运行。Git 的面向用户的命令被称为瓷器,因为它们意味着干净和闪亮,而不是瓷器后面的肮脏管道。


您自己的 Git 会自动更新您的远程跟踪名称

我们在上面看到的一些引用以 开头refs/remotes/,后跟一个远程名称,例如origin和另一个斜杠。这些是您自己的 Git远程跟踪名称5 这些充当您的 Git 记住其他 Git中的哈希 ID 的方法。

也就是说,你的 Git 有你的大表,里面放满了 和 之类的refs/heads/master引用refs/heads/develop。但是他们的Git 也是如此:下面列出的 URL 上的 Gitorigin自己 refs/heads/masterrefs/heads/develop. 您的 Git 出于多种原因(包括总体上对您有所帮助)想要记住他们的Git 记录为他们的主分支的内容。因此,您的 Git 会更新refs/remotes/origin/master记住他们在. refs/heads/master

请注意,这些远程跟踪名称可以缩短为origin/master. 这是因为上面的第 5 步:如果你输入origin/master,并且你的 Git 还没有通过前面的任何步骤找到哈希 ID,它会进入第 5 步,停留refs/remotes/在前面,查看你的 Git 的表,然后找到refs/remotes/origin/master. 因此,当您编写代码时origin/master,您的 Git 会将其转换为refs/remotes/origin/master,其中包含您的 Git 上次与另一个 Git 对话时保存/更新的哈希 ID origin。(哇!)

每次你运行时git fetch origin(没有 refspecs),你的 Git 都会更新你所有origin/*名字。当你运行时,如果推送成功,你的 Git 会更新你的推送名称,因为如果推送成功,你的 Git 刚刚要求Git设置其分支名称之一,并且他们接受了,所以你的 Git 知道什么哈希他们刚刚设置的 ID。git push origin refspecorigin/*origin


5 Git 传统上称这些远程跟踪分支名称,我认为这是一个糟糕的名称,所以我习惯称它们为远程跟踪名称。这只是一点点改进,但​​我认为这是一个改进。


把它们放在一起

好吧,你可能会说,但是 tag-master 与 branch-master,嗯,这是一个“我弄坏了一些东西”的案例。如果我没有破坏任何东西,这怎么配合git remote set-head

好吧,再看一下 what git remote set-headdoes 的描述(记住git remote add -m ...是在做 a git remote set-head):

套 ...refs/remotes/<name>/HEAD

现在看一下 gitrevisions 过程的第 6 步(git rev-parse以及大多数 Git 命令):

  1. 否则,refs/remotes/<refname>/HEAD如果它存在。

正在做的事情git remote set-head是进行设置,以便第 6 步做一些有用的事情——或者至少是潜在有用的事情。还请查看我为 Git 的 Git 存储库获得的输出,尤其是这一行:

5d826e972970a784bd7a7bdf587512510097b8c7 commit refs/remotes/origin/HEAD

该名称origin/HEAD 已经解析为特定的提交,即与origin/master. 6 我可以选择更改它,这样它origin/HEAD就会引用相同的提交,例如origin/next. 为此,我可以使用git remote set-head.

如果我origin在 Git 需要提交哈希 ID 和 Git 调用的地方使用名称(远程名称)git rev-parse,我的 Git 将完成完整的六步过程。例如,我们可以假设我的 Git不会找到名为 的分支origin,因此它确实进入了第 6 步。到达第 6 步后,我的 Git 将读取 myorigin/HEAD并看到我已经告诉我的 Git 读取我的origin/next. 所以使用git remote set-head origin next使我的 origin/HEAD链接到我的 origin/next.

但这对 没有什么好处git push,因为当你运行时git push,你没有在这里给出一个简单的参考,你给出了一个refspec。refspec 是由冒号分隔的两部分。如果你运行:

git push origin origin

你的 refspec 是字面意思origin,比喻的意思 origin:origin。这没有任何用处。(事实上​​,它做了一些奇怪的事情,这可能是一个错误。7


6这里有一个重要的细节我要略过:这个名字是一个象征性的参考。实际上,它说“我自己没有哈希 ID,其他人 - 即refs/remotes/origin/master- 有哈希 ID。现在去查找那个名字。” 这意味着每当我的 Git 更新 myorigin/master时,也会更新git rev-parse将为 my 生成的值origin/HEAD

7由于符号引用,这变成了refs/remotes/origin/HEAD:refs/remotes/origin/HEAD,它做了一些根本不应该发生的事情。

我在这里设置了一个带有预推送挂钩的存储库:

$ cat .git/hooks/pre-push
#! /bin/sh
echo pre-push begins
while read line; do printf '%s\n' "$line"; done
echo pre-push ends

这个 pre-push 钩子向我们展示了我的本地 Git 将要求服务器 Git 设置什么引用,设置什么哈希 ID,以及服务器对该引用有什么现有哈希 ID。这是发生的事情:

$ git push origin origin
pre-push begins
refs/remotes/origin/HEAD 11ae6ca18f6325c858f1e3ea2b7e6a045666336d refs/remotes/origin/HEAD 0000000000000000000000000000000000000000
pre-push ends
Total 0 (delta 0), reused 0 (delta 0)
To [url]
 * [new branch]      origin/HEAD -> origin/HEAD

在服务器上,那里的 Git 实际上创建了引用refs/remotes/origin/HEAD。这个服务器 Git 裸存储库根本没有origin远程,所以它不应该创建这个引用。整个事情应该被拒绝。

我的本地 Git 要求服务器设置服务器's 是有意义——我的 Git 表示为 my ,我的符号引用连接到它——但这不是实际发生的情况。而且,即使这样翻译的,这也是我的 Git 向服务器 Git 发出的请求,要求服务器将服务器设置为我的 Git 记录的哈希 ID,这是我的 Git 在服务器的. 这通常不是很有用。因此,即使在本地解决得更好一点,整个想法也不会走得太远。masterorigin/masterorigin/mastermastermastergit push origin/HEAD

(我在测试机器上的本地 Git ——这也是这里的服务器,通过 ssh 连接到自身——在 2.19.1 上稍微过时了,但非常现代。)


推荐阅读