【问题标题】:Why can’t I use git pull with a local branch?为什么我不能在本地分支上使用 git pull?
【发布时间】:2018-02-15 19:39:16
【问题描述】:

我是 git 新手。指定本地分支和不指定有什么区别?

$ git pull origin master : local-branch
 ! [rejected]   master  -> local-branch  (non-fast-forward)

$ git pull origin master
 * branch      master     -> FETCH_HEAD

【问题讨论】:

  • 命令的准确拼写非常重要:git pull origin master<space><colon><space>local-branchgit pull origin master:local-branch,没有空格,做不同的事情。在我看来,你跑了git pull origin master:local-branch
  • 欢迎来到 Stack Overflow!我编辑了您的问题的标题,以便更容易理解。
  • 运行命令时你在哪个分支?
  • 当您未指定本地分支名称时,合并发生在当前结帐的分支上

标签: git


【解决方案1】:

分支在某个时候分叉了。 local-branch 上必须有提交,master 上不存在,因此,您可能必须重写本地历史记录,并在远程分支之上重新设置本地更改。

【讨论】:

  • 我已经在本地分支上提交了所有内容,git pull origin origin-branch:local-branch 和 git pull origin origin-branch 有什么区别。为什么第一个不起作用,但是第二个可以工作。
【解决方案2】:

TL;DR:您将不适当的 refspec 传递给 git fetch。因为git fetch 非常安全,所以它会检测到这一点并使整个事情失败。

git pull = git fetch 加上第二个 Git 命令

我建议完全避免git pull(至少作为初学者)。它一次做的事情太多了。

git pull 所做的是运行两个 Git 命令。

它运行的第一个命令是git fetch。这个命令,当以正常方式作为普通用户命令使用时,是相当安全的。您只需运行git fetch 而不使用任何参数,或者git fetch origin 如果您想指定获取到origin。 (反正默认一般是去origin。)

运行 git fetch origin 只是从另一个 Git 获得新的提交,你的 Git 让你使用这个简写名称 origin。简写名称代替了较长的 URL,例如 git://github.com/someuser/long/path/to/repository。这个 remote,正如您的 Git 所称的那样,指的是另一个 Git 存储库。另一个 Git 存储库有自己的自己的 分支,并由其他人或其他人操纵,因此其分支会随着时间的推移获得新的提交。这个git fetch 步骤让你的Git 通过互联网电话调用另一个Git 并下载新的提交。然后,您的 Git 会使用 Git 所称的 远程跟踪分支名称 记住它们,例如 origin/master

换句话说,你的 Git 调用他们的 Git 并问:嘿,你有什么分支?他们可能会说:我有 master,它的提示提交是 @987654342 @。您的 Git 检查您的存储库并发现您有或没有 a234567...。如果您,您的 Git 会让他们的 Git 向您发送该提交,以及完成该提交所需的一切:它的所有文件和历史记录。如果您已经提交了该提交,则此步骤进行得非常快,因为不需要任何东西。然后你的 Git 会说:好的,现在我知道 origin 的主人是 a234567... 我会把它记录为 origin/master = a234567...

这就是为什么这一步如此安全:您的 Git 会调用其他 Git,例如 origin 的那个。您的 Git 会获取您没有的任何提交(和其他数据),并将其添加到您的存储库中,而根本不会触及任何 您的 提交或分支。如果另一个 Git 已删除一些提交——这有点困难,但有可能——你的 Git 会更新你对另一个 Git 中的内容的记忆,它们的分支甚至可能被缩短了......但是这仍然没有触及任何你的提交和分支,没有任何方式!

在这一点上,事情变得更加复杂(并且更不安全)

git pull 运行第二个 Git 命令是有原因的。如果它所做的只是这种默认的git fetch 所做的,那么你的 分支将永远不会获得你的 Git 从他们的 Git 中获取的任何新提交。

如果您正在做自己的工作,并且已经做出了自己的承诺,那么可能现在是时候将您的工作与他们的工作结合了。要将您的工作与他们的工作结合起来,您现在必须运行第二个 Git 命令。

正确的运行命令可能git merge。或者,它可能git rebase。或者您可能根本不希望做任何一个:查看git fetch 的新功能通常是一个好主意,并基于此在合并和变基(甚至其他选项)之间做出决定。但是git pull 命令要求您提前做出决定,然后才能看到会发生什么。

默认情况下,git pull 将运行 git merge 作为其第二步,如果这是您想要的(并且您事先知道这一点),您可以使用 git pull 来运行这两个步骤git fetch git merge。但我还是建议分开做:它会让你更清楚,尤其是当出现问题时。您会知道哪个命令失败了,这在您搜索 StackOverflow 以了解如何处理该失败时非常有帮助。

实际上是 fetch 部分在这里失败

无论如何,您的示例中失败的部分:

! [rejected]   master  -> local-branch  (non-fast-forward)

实际上是git fetch 步骤,而不是git merge 步骤。这是因为您使用的语法,以及git pull 调用git fetch 的方式:

git pull origin master:local-branch

(注意冒号周围没有空格)。

git pull 调用git fetch 的方式是将这些参数中的大部分原样传递。特别是,这个git pull 运行:

git fetch origin master:local-branch

作为它的git fetch 步骤。1origin 部分仍然只是你的遥控器的名称——一些 URL 的简写,并且在组成你方的名称时很有用,比如 @ 987654371@ 来记住他们的分支名称——但其余参数是 the git fetch documentation 所称的 refspec

根据这些 refspec 中的内容以及您的 Git 从其他 Git 获得的内容,第一步(git pullgit fetch 部分)可能会失败。如果它成功,Git 继续运行第二步,默认为git merge。传递给 git merge 的参数有点棘手,但重要的是要记住 git merge 始终在您的当前分支中工作,无论是哪个分支。

注意:如果你运行git pull origin a b c,它会运行git fetch origin a b c(这不是一个真正的问题),然后是git merge <hash1> <hash2> <hash3>。第二个命令执行 Git 所说的 octopus 合并,如果您是初学者,这绝对不是您想要的。这意味着git pull很容易误用!特别是,很容易认为这会使您自己的几个分支保持最新。但它不会——它不能——因为它总是适用于你当前的分支,而且你一次只能得到一个当前分支。


1其实它也偷偷传过--update-head-ok,或者是脚本的时候做回来了;但你不应该自己这样做。


参考和参考规范

在 Git 中,reference 主要是分支名称、标签名称或远程跟踪分支名称等的全名变体。也就是说,名称masterrefs/heads/master 的缩写,它是分支master 的完整参考名称。您可以缩短参考名称;如果你这样做了,找出全名的规则会在the gitrevisions documentation 中说明。有关详细信息,请参阅链接,并注意将短名称转换为分支引用实际上在第四步:如果这三个步骤中的任何一个可行,其他三个步骤可以首先解决它。

refspec 主要是一对引用,它们之间有一个冒号(并且没有空格)。左边的名字是source,右边的名字是destination,所以master:master表示源名字是master,目的地名字是master,而master:local-branch 表示源名称为master,目标名称为local-branch。和往常一样,如果你给出一个短名称,Git 会尝试找出正确的全名。

对于git fetchsource 是另一个 Git 上的名称(这是任何新提交的源),destination 通常应该是远程-tracking 分支名称:适当的一对,例如:

refs/heads/master:refs/remotes/origin/master

(这个refs/remotes/ 是您所有远程跟踪分支名称的前缀)。2但这不是您正在运行的。相反,你正在给你的 Git:

master:local-branch

你的 Git 扩展到:

refs/heads/master:refs/heads/local-branch

你的 Git 像往常一样调用他们的 Git,获取他们在他们的 master 上的所有提交,而你的存储库中没有,然后尝试安全调整你的 refs/heads/local-branch 引用以匹配他们当前的master 设置。这不安全——这样做不是“快进”——所以你的 Git 会因错误而停止。


2对于git push,这些是相反的:git push origin refs/heads/master:refs/heads/jackiewillen 会要求他们将名为jackiewillen他们的 分支设置为指向与你的分支名为master。请注意,这里没有远程跟踪名称的概念:当您推送到另一个 Git 时,您只是要求他们直接设置他们的分支,而不是取他们的名字并从他们那里构造 origin/whatever


在图形方面,快进很简单

要了解什么是“快进”以及为什么它是“安全的”——而“非快进”相应地安全——你需要了解 Git 的引用如何与 Git 的提交配合使用图形。不过,这个答案已经太长了,所以我将把大部分内容推迟到网站Think Like (a) Git

不过,正如上述网站指出的那样,从根本上说,引用使提交可访问。这意味着更改某些引用的值(在这种情况下更改由分支名称标识的特定提交)会更改可访问提交的集合。如果 new 分支提示是旧分支提示的后代——例如,是孩子、孙子或曾祖孙——那么所有旧提交仍然可以访问。旧的分支提示是孩子的父母、祖父母或曾祖父母,因此以这种方式移动分支名称就是 Git 所说的 fast-forward 操作.

(Git 直接用git merge 命令阐明了这个“快进”的想法,这有点奇怪,因为快进操作不是实际上是在全部。如果git merge 可以进行快进,你必须告诉它不要使用--no-ff 来强制 Git 进行新的合并提交。即便如此,新的合并 commit,这是一个作为形容词的合并,不需要合并动作作为动词工作。只有当需要合并作为动词时,git merge才会默认做一个合并作为形容词提交。但这是git merge 的事情:它使用相同的可达性概念,但方式不同。)

无论如何,这实际上是规则:如果分支名称的当前值新提议的祖先,则分支名称更改是一个快进操作分支名称的值。否则,这不是快进。

git fetch git push 中,通常允许这样的快进操作。这样的操作是安全的:一旦更改了分支名称值,新值将容纳所有旧提交加上新提交。没有提交被取消;添加了新的提交。相反,通常禁止非快进操作。这样的操作是安全的:一旦更改了分支名称值,一些现有的提交就不再可访问,至少不能从更改的分支名称访问。如果无法通过其他名称访问它们,则这些提交将被遗忘,并有资格进行垃圾收集。这些提交最终会被删除。

要强制更改分支名称值,即使它不是快进,Git 允许您指定“强制标志”。 git fetchgit push 都支持 --force,它在 git fetchgit push 命令建议的所有更新上设置强制标志。但是有一种方法可以直接在 refspec 中编写:前导加号 + 设置强制标志。

因此,即使不是快进操作,命令:

git fetch origin +refs/heads/master:refs/remotes/origin/master

将更改您的 origin/master 远程跟踪分支名称(目标引用)以匹配另一个 Git 的 master 分支。这就是为什么git fetch origin 的默认 refspec 通常是:

+refs/heads/*:refs/remotes/origin/*

两个星号的意思是“匹配任何东西”和“使用匹配的东西”。因此,默认情况下,只需运行 git fetch origin 即可获取他们拥有的所有内容并更新您的 origin/whatevers。

但是那个有效的呢?

这里有一个奇怪的地方:

* branch      master     -> FETCH_HEAD

* branch 部分表示该名称指的是一个分支。但是FETCH_HEAD 不是分支名称,并且此git fetch 步骤每次都有效。这是怎么回事?

秘密在于,origin 之类的遥控器和origin/master 之类的远程跟踪分支名称是较晚的发明。起初,Git 根本没有它们。事实上,Git 也没有面向用户的git fetch;它只有git pull。这就是为什么git push 的反义词是git fetch:只有git pull 是一个错误。 Git 应该让 git pullgit fetch 所做的事情。 (Mercurial 的 hg pull 和 Git 的 git fetch 一样,3 所以 Mercurial 的拉取和推送实际上是配对的,而 Git 却不是。)

无论如何,作为副作用:

  1. git fetch origin refs/heads/master,它有一个带有源但没有目标的 refspec,本质上意味着 获取他们的 master,然后将其丢弃。 不过,在丢弃它之前,Git 现在4 先更新origin/master
  2. 所有 git fetch 获取,git fetch 转储到 .git/FETCH_HEAD。此文件的内容故意与gitrevisions(7) 中列出的七个步骤的第 1 步中解析的引用相似。但它们并不完全相同:它们会增加额外的数据,专门留给git pull 使用。

这个特殊的FETCH_HEAD 文件的内容只持续到下一个 git fetch,它会覆盖它。 (git fetch--append 选项告诉git fetch 附加到FETCH_HEAD 而不是覆盖它,但这很少有用。)这对git pull 来说已经足够了,因为git pull 首先运行git fetch——将此关键信息放入.git/FETCH_HEAD——然后,只要fetch 步骤成功,立即使用拉取代码从.git/FETCH_HEAD 读取的数据运行git mergegit rebase,其中git fetch 步骤离开了。

如果你避开git pullgit fetch 使用.git/FETCH_HEAD 执行的这个特殊的歌舞套路就变得无关紧要:你不需要知道它发生了,你可以忽略FETCH_HEAD 的内容。如果您确实使用了git pull并且它有效,那么您也不需要知道这一点。您只需要在 git pull 因某种原因失败的情况下知道这一点。


3在某一时刻,可能只是为了让人感到困惑,:-) Mercurial 有一个名为 fetch 的扩展名,它运行 hg pull,后跟 hg merge。这意味着hg pull = git fetchhg fetch = git pull

4从 Git 1.8.4 开始,添加了 git fetch 期间的机会性更新。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2016-11-02
    • 2016-03-24
    • 2019-09-18
    • 2014-11-13
    • 2020-06-08
    • 2018-09-16
    • 2015-07-12
    相关资源
    最近更新 更多