当您执行“git fetch上游master:master”与“git pull上游master:master”时,确切的区别是什么

2023-12-28

我知道之间的区别git fetch and git pull. git pull基本上是一个git fetch + git merge在一个命令中。

但是,我正在研究如何使用上游更新我的分支(主分支)无需检查主分支。我遇到了这个答案 -合并、更新和拉取 Git 分支,无需签出 https://stackoverflow.com/questions/3216360/merge-update-and-pull-git-branches-without-using-checkouts/4157106#4157106

但是当我使用git fetch upstream master:master在我已经在 master 上签出之后,我遇到了这个错误 -

fatal: Refusing to fetch into current branch refs/heads/master of non-bare repository

所以,我尝试了git pull upstream master:master它起作用了。有趣的是做git pull upstream master:master用上游更新我的分叉不管我是否在master上。然而git fetch upstream master:master仅当我在时才有效不在主控上 branch.

阅读这里知识渊博的人对此的解释将会非常有趣。


git pull基本上是一个git fetch + git merge在一个命令中

是的——但是,正如你所怀疑的,事情远不止于此。

贝内特·麦克埃尔威的评论 https://stackoverflow.com/questions/3216360/merge-update-and-pull-git-branches-without-using-checkouts/4157106#comment62355072_4157106,在您链接到的答案中,实际上有一个关键项目。他提到你可以:

Use fetch origin branchB:branchB,如果合并不快进,它将安全失败。

另一个没有很好的记录:它是-u aka --update-head-ok选项中git fetch, which git pull sets. 文档 https://www.kernel.org/pub/software/scm/git/docs/git-fetch.html确实定义了它的作用,但有点神秘和可怕:

默认情况下git 获取拒绝更新对应的头部 到当前分支。该标志禁用检查。这纯粹是 供内部使用git pull与 沟通git 获取, 除非你正在实现你自己的 Porcelain,否则你就不会 应该使用它。

这让我们得出你的观察结果:

所以,我尝试了 gitpull upstream master:master它起作用了。有趣的是做git pull upstream master:master无论我是否在 master 上,都会用上游更新我的 fork。然而git fetch upstream master:master仅当我不在主分支上时才有效。

这是由于-u旗帜。如果你跑了git fetch upstream master:master,对于某种意义上的意义来说,这会起作用work,但给你留下了一个不同的问题。警告的存在是有原因的。让我们看看这个原因是什么,看看这个警告是否过于严厉。警告:这里有很多东西!下面的大部分复杂性是为了弥补历史错误,同时保留向后兼容性。

分支名称、参考文献和快进

首先,我们来谈谈参考 and 快进操作.

在 Git 中,一个参考只是谈论分支名称的一种奇特方式,例如master,或者像这样的标签名称v1.2,或远程跟踪名称,例如origin/master,或者,好吧,任意数量的其他名称,全部以一种常见且合理的方式:我们将每个特定的名称分组kind的名字变成名称空间,或作为单个词,命名空间 https://en.wikipedia.org/wiki/Namespace。分支名称位于refs/heads/,标签名称位于refs/tags/,依此类推,这样master真的只是refs/heads/master.

Every one of these names, all of which start with refs/, is a reference. There are a few extra references that don't start with refs as well, although Git is a little bit erratic internally in deciding whether names like HEAD and ORIG_HEAD and MERGE_HEAD are actually references.1 In the end, though, a reference mainly serves as a way to have a useful name for a Git object hash ID. Branch names in particular have a funny property: they move from one commit to another, typically in a way that Git refers to as a fast forward.

也就是说,给定一个具有一些提交的分支(此处用大写字母表示),以及具有更多提交的第二个分支,其中包括第一个分支上的所有提交:

...--E--F--G   <-- branch1
            \
             H--I   <-- branch2

Git is allowed to slide the name branch1 forward so that it points to either of the commits that were, before, reachable only through the name branch2.2 Compare this, to, say:

...--E--F--G------J   <-- branch1
            \
             H--I   <-- branch2

If we were to move the name branch1 to point to commit I instead of commit J, what would happen to commit J itself?3 This kind of motion, which leaves a commit behind, is a non-fast-forward operation on the branch name.

These names can be shortened by leaving off the refs/ part, or often, even the refs/heads/ part or the refs/tags/ part or whatever. Git will look in its reference-name database4 for the first one that matches, using the six-step rules described in the gitrevisions documentation https://www.kernel.org/pub/software/scm/git/docs/gitrevisions.html. If you have a refs/tags/master and a refs/heads/master, for instance, and say master, Git will match refs/tags/master first and use the tag.5


1If a reference is a name that has, or can have, a reflog, then HEAD is a reference while ORIG_HEAD and the other *_HEAD names are not. It's all a little fuzzy at the edges here, though.

2These commits might be reachable through more names. The important thing is that they weren't reachable through branch1 before the fast-forward, and are afterward.

3The immediate answer is actually that nothing happens, but eventually, if commit I is not reachable through some name, Git will garbage collect the commit.

4This "database" is really just the combination of the directory .git/refs plus the file .git/packed-refs, at least for the moment. If Git finds both a file entry and a pathname, the pathname's hash overrides the one in the packed-refs file.

5Exception: git checkout tries the argument as a branch name first, and if that works, treats master as a branch name. Everything else in Git treats it as a tag name, since prefixing with refs/tags is step 3, vs step 4 for a branch name.


Refspecs

现在我们知道引用只是指向提交的名称,而分支名称是一种特定类型的引用,快进是日常事务,让我们看一下refspec。让我们从最常见和最容易解释的形式开始,它只有两个参考名称用冒号分隔,例如master:master or HEAD:branch.

每当您将两个 Git 相互连接时(例如在连接期间),Git 都会使用 refspecsgit fetch以及期间git push。左边的名字是source右边的名字是目的地。如果你正在做git fetch,源是otherGit 存储库,目的地是您自己的。如果你正在做git push,源是您的存储库,目的地是他们的。 (在特殊情况下使用., 意思是这个存储库,源和目标都是您自己,但一切仍然正常,就好像您的 Git 正在与另一个单独的 Git 通信一样。)

如果您使用完全限定名称(以refs/),你确定你会得到哪一个:分支、标签或其他什么。如果您使用部分限定或不限定的名称,Git 通常会弄清楚您的意思。你偶尔会遇到 Git 的情况can't弄清楚你的意思;在这种情况下,请使用完全限定名称。

您可以通过省略两个名称之一来进一步简化 refspec。 Git 知道你省略了哪一个名字,冒号的哪一侧消失了::dst没有来源名称,而src:没有目的地名称。如果你写name,Git 将其视为name::没有目的地的来源。

What these mean varies. An empty source for git push means delete: git push origin :branch has your Git ask their Git to delete the name entirely. An empty destination for git push means use the default which is normally the same branch name: git push origin branch pushes your branch by asking their Git to set their branch named branch.6 Note that it's normal to git push to their branch directly: you send them your commits, then ask them to set their refs/heads/branch. This is quite different from the normal fetch!

For git fetch,空目的地意味着不要更新我的任何参考资料。非空目的地意味着更新我提供的参考。不像git push不过,您在这里可能使用的通常目的地是远程跟踪名称:你会拿他们的refs/heads/master变成你自己的refs/remotes/origin/master。这样,您的分行名称master—your refs/heads/master——原封不动。

但由于历史原因,通常的形式git fetch只是写成git fetch remote branch,省略目的地。在这种情况下,Git 做了一些看似自相矛盾的事情:

  • 它写入分支名称更新nowhere。缺少目的地意味着没有(本地)分支得到更新。
  • 它将哈希 ID 写入.git/FETCH_HEAD。一切git fetch获取总是到这里。这就是地点和方式git pull发现什么git fetch did.
  • 它更新远程跟踪名称,例如refs/remotes/origin/master,甚至认为没有被告知这样做。 Git 称其为机会主义更新.

(其中大部分实际上是通过默认参考规范你会发现在你的.git/config file.)

您还可以通过添加前导加号来使引用规范复杂化+。这会设置“force”标志,该标志会覆盖分支名称移动的默认“快进”检查。这是远程跟踪名称的正常情况:您希望 Git 更新您的refs/remotes/origin/master匹配他们的 Gitrefs/heads/master even if这是一个非快进的更改,以便您的 Git 始终记住在哪里their master是,你的 Git 与他们的 Git 的最后一次对话。

请注意,仅当有要更新的目标时,前导加号才有意义。这里有三种可能性:

  • You're creating a new name. This is generally OK.7
  • 您没有更改名称:它用于映射以提交哈希H并且请求说将其设置为映射以提交哈希H。这显然是可以的。
  • You are changing the name. This one breaks down into three more sub-possibilities:
    • It's not a branch-like name at all, e.g., it's a tag and should not move. You will need a force flag to override the default rejection.8
    • 这是一个类似分支的名字,分支动作是快进。您不需要强制标志。
    • 这是一个类似分支的名字,但动作是not快进。您将需要强制标志。

这涵盖了更新参考文献的所有规则,except对于最后一条规则,我们需要更多背景知识。


6You can complicate this by setting push.default to upstream. In this case, if your branch fred has its upstream set to origin/barney, git push origin fred asks their Git to set their branch named barney.

7For various cases of updates, you can write hooks that do whatever you like to verify names and/or updates.

8In Git versions before 1.8.3, Git accidentally used branch rules for tag updates. So this only applies to 1.8.3 and later.


HEAD很特别

请记住,分支名称如master只是识别一些特定的提交哈希:

$ git rev-parse master
468165c1d8a442994a825f3684528361727cd8c0

你也看到了git checkout branchname以一种方式表现,并且git checkout --detach branchname or git checkout hash表现另一种方式,给出关于“分离的头”的可怕警告。尽管HEAD在大多数方面都可以作为参考,但在某些方面,它非常特别。尤其,HEAD通常是一个象征性参考,其中它包含分支名称的全名。那是:

$ git checkout master
Switched to branch 'master'
$ cat .git/HEAD
ref: refs/heads/master

告诉我们当前分支名称 is master: that HEAD附于master. But:

$ git checkout --detach master
HEAD is now at 468165c1d... Git 2.17
$ cat .git/HEAD
468165c1d8a442994a825f3684528361727cd8c0

之后git checkout master让我们重新开始master照常。

这意味着当我们有一个分离头, Git 知道我们签出了哪个提交,因为正确的哈希 ID 就在那里,在名称中HEAD。如果我们要做出一些任意的change到存储在的值refs/heads/master,Git 仍然会知道我们已经签出了哪个提交。

But if HEAD只包含name master,Git 知道的唯一方法current承诺是,比如说,468165c1d8a442994a825f3684528361727cd8c0, 就是它refs/heads/master映射到468165c1d8a442994a825f3684528361727cd8c0。如果我们做了某事changed refs/heads/master对于其他一些哈希 ID,Git 会认为我们已签出其他提交。

这有关系吗?是的,它确实!让我们看看为什么:

$ git status
... nothing to commit, working tree clean
$ git rev-parse master^
1614dd0fbc8a14f488016b7855de9f0566706244
$ echo 1614dd0fbc8a14f488016b7855de9f0566706244 > .git/refs/heads/master
$ git status
...
Changes to be committed:
...
        modified:   GIT-VERSION-GEN
$ echo 468165c1d8a442994a825f3684528361727cd8c0 > .git/refs/heads/master
$ git status
...
nothing to commit, working tree clean

更改存储在的哈希 IDmaster改变了 Git 对状态的看法!

状态涉及 HEAD vs 索引加上索引 vs 工作树

The git status命令运行两个git diff胀,git diff --name-statuses,内部):

  • 比较 HEAD 与索引
  • 比较索引与工作树

请记住,index,又名暂存区 or the cache,保存的内容当前提交直到我们开始修改它以保存我们将做出的下一次承诺。工作树只是整个过程的一个小帮手更新索引,然后提交过程。我们需要它只是因为索引中的文件采用特殊的 Git-only 格式,我们系统上的大多数程序都无法使用它。

If HEAD保存当前提交的原始哈希 ID,然后进行比较HEAD无论我们如何处理我们的数据,vs 索引都保持不变分支机构名称。但如果HEAD holds 一个具体的分行名称,以及我们change一个特定分支名称的值,然后进行比较,我们将比较不同的致力于我们的索引。索引和工作树将保持不变,但 Git 的想法相对差异(不同的)当前提交和索引之间会发生变化。

This is why git fetch默认拒绝更新当前分支名称。这也是为什么您无法推送到非裸存储库的当前分支的原因:该非裸存储库具有一个索引和工作树,其内容是可能是为了匹配当前提交。如果您通过更改存储在分支名称中的哈希来更改 Git 对当前提交的看法,则索引和工作树可能会停止匹配提交。

这并不致命——事实上,一点也不致命。正是如此git reset --soft作用:它改变了分店名称到哪个HEAD被附加,而不触及索引和工作树中的内容。同时git reset --mixed更改分支名称and索引,但保持工作树不变,并且git reset --hard一次性更改分支名称、索引和工作树。

快进“合并”基本上是git reset --hard

When you use git pull to run git fetch and then git merge, the git merge step is very often able to do what Git calls a fast-forward merge. This is not a merge at all, though: it's a fast-forward operation on the current branch name, followed immediately by updating the index and work-tree contents to the new commit, the same way git reset --hard would. The key difference is that git pull checks—well, is supposed to check9—that no in-progress work will be destroyed by this git reset --hard, while git reset --hard itself deliberately does not check, to let you throw away in-progress work that you no longer want.


9Historically, git pull keeps getting this wrong, and it gets fixed after someone loses a bunch of work. Avoid git pull!


把所有这些放在一起

当你跑步时git pull upstream master:master,Git 首先运行:

git fetch --update-head-ok upstream master:master

这让你的 Git 在列出的 URL 处调用另一个 Gitupstream并从他们那里收集提交,通过他们的名字找到master— 的左侧master:master参考规格。然后你的 Git 更新你自己的master,大概refs/heads/master,使用参考规范的右侧。这fetch步骤通常会失败,如果master是您当前的分支 - 如果您的.git/HEAD包含ref: refs/heads/master——但是-u or --update-head-ok标志可以防止失败。

(如果一切顺利的话,你的git pull将运行第二个,git merge, step:

git merge -m <message> <hash ID extracted from .git/FETCH_HEAD>

但让我们先完成第一步。)

快进规则确保您的master更新是一个快进操作。如果没有,则获取失败并且您的master不变,并且pull停在这里。所以到目前为止我们还可以:你的master当且仅当有可能给定从以下位置获得的新提交(如果有)时,才快进upstream.

此时,如果您的master已经变了and这是您当前的分支,您的存储库现在不同步:您的索引和工作树不再与您的相匹配master。然而,git fetch已将正确的哈希 ID 留在.git/FETCH_HEAD还有你的git pull现在继续进行类似重置的更新。这实际上相当于使用git read-tree而不是git reset,但只要它成功——考虑到预先pull检查,它should成功 - 最终效果是相同的:您的索引和工作树将与新提交匹配。

或者,也许master is not您当前的分支机构。也许你的.git/HEAD包含代替ref: refs/heads/branch。在这种情况下,您的refs/heads/master安全快进git fetch即使没有也会做--update-head-ok. Your .git/FETCH_HEAD包含与更新的哈希 ID 相同的哈希 IDmaster, 和你的git pull runs git merge尝试合并 - 这可能是也可能不是快进操作,具体取决于您的分支名称的提交branch现在点。如果合并成功,Git 要么进行提交(真正的合并),要么像以前一样调整索引和工作树(快进“合并”)并将适当的哈希 ID 写入.git/refs/heads/branch。如果合并失败,Git 会因合并冲突而停止,并让您像往常一样清理混乱。

最后一种可能的情况是你的HEAD是分离的,但这与ref: refs/heads/branch案件。唯一的区别是,新的哈希 ID,当一切都说完并完成后,直接进入.git/HEAD而不是进入.git/refs/heads/branch.

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

当您执行“git fetch上游master:master”与“git pull上游master:master”时,确切的区别是什么 的相关文章

  • 有没有办法列出Git中未修改的文件?

    我从另一个来源以 tarball 的形式获取了一些更改 我想知道哪些文件没有更改 目标是 Git 克隆 因此可以轻松查看新增内容和更改内容 有人知道如何获取未更改内容的列表 不包括未跟踪的内容 吗 编辑 换句话说 我希望利用 Git 来查找
  • 使用 Git 的 Spring Cloud 配置服务器 - 无法克隆或签出存储库连接超时

    我正在使用 GIT 在 Spring Cloud Config Server 上进行 POC Spring Boot 1 5 3 RELEASE 爪哇1 8 弹簧工具套件https github com kishornpatil https
  • 从 git 中删除历史记录 - git 命令失败

    我正在尝试从 Git 历史记录中清除项目 bin 目录 我已经将 bin 添加到 gitignore 并运行 git rm cached r bin成功地 现在我尝试使用 GitHub 帮助页面中推荐的命令来清除历史记录 git filte
  • Git - 推送到远程存储库中的远程跟踪分支

    当简单地做git push到远程存储库 其master分支得到更新 对于非裸存储库来说 这是不希望出现的情况 最近的 Git 版本显示的警告消息清楚地表明了这一点 我希望能够推送到远程存储库 并拥有其之一远程追踪分支进行更新 稍后 当我登录
  • 有没有办法缓存 https 凭据以推送提交?

    我最近转而将我的存储库同步到 GitHub 上的 https 由于防火墙问题 并且每次都要求输入密码 有没有办法缓存凭据 而不是每次都进行身份验证git push 自 Git 1 7 9 2012 年发布 以来 Git 中有一个巧妙的机制可
  • DVCS命令的统一

    当处理多个 开源 项目时 多个版本控制系统开始出现问题 虽然它们共享共同的操作 但我经常在输入时犯错误hg add反而git add 我记得前段时间看到过一个项目 通过提供基本命令以统一的方式访问不同的源代码控制软件提交 ci add等在外
  • 忽略 git 中的本地配置文件

    Rails 应用程序中有一些本地文件 属于我们存储库的一部分 我希望 git 忽略它们 基本上 我希望 git 忽略我对 config environments 目录和 config application rb 文件中的任何内容所做的所有
  • 重新打包存储库对于大型二进制文件有用吗?

    我正在尝试将大量历史记录从 Perforce 转换为 Git 并且一个文件夹 现在是 git 分支 包含大量大型二进制文件 我的问题是运行时内存不足git gc aggressive 我的主要问题是重新打包存储库是否可能对大型二进制文件产生
  • SSH 到 Openshift 服务器失败

    我正在 openshift 服务器上使用 jboss catridge 我希望与其他人共享此实例并添加其他用户的公钥 id rsa pub 当其他人尝试访问该实例时 他会收到以下错误 我在他的实例中尝试了同样的方法 但看到了同样的错误 与
  • git - 更新 fork 的 master 并将我的分支重新建立到它之上?

    我分叉了一个 github 项目 然后将其克隆到本地 然后我在新分支中做了一些更改my github the project repo 然后我添加并提交了更改 并推送到我的 github 存储库并提交了拉取请求 所有者已收到我的请求 并希望
  • 如何从 android.googlesource.com 或 github.com 下载单个目录?

    我想下载 https android googlesource com platform frameworks base git master tools aapt https android googlesource com platfo
  • Git:显示分支之间的差异,忽略合并的提交

    我的存储库历史记录看起来像这样 x y z branch a b c d e master 我想获得 branch 完整历史记录的单个差异 即 像 git diff 输出 我不想要像 git log p 产生的一大堆差异 而不包括任何从 m
  • 如何 git grep 仅一组文件扩展名

    如何执行 git grep 并将检查的文件限制为一组文件 我希望能够 grep cpp 和 h 文件的内容来查找 MyFunc 例如 git grep MyFunc hc 但是 这也匹配 c 文件和 cs 文件 Use git grep M
  • 自动将所有 GitHub 存储库镜像到 gitlab

    对于 GitLab 必须手动为每个存储库设置拉 推镜像 我想知道那里有any way可以自动将所有 Github 存储库同步到 GitLab 这样 当您在 GitHub 中创建新的存储库时 GitLab 中的存储库将自动创建 并充当拉取镜像
  • 哪个是更智能的 git 协议,ssh 或 git(通过 ssh)或 https 协议?

    哪个高效 SSH 或 Git 文件压缩 我对 Git 的理解是 git 协议很智能 因为通信两端都有一个协议代理来压缩文件传输 从而通过有效地使用网络带宽来实现更快的克隆 From 我发现了以下说法 For secure authentic
  • 第一次使用node.js - “ReferenceError:节点未定义”

    我刚刚安装了node js 我尝试编写应该检查版本的node v 但它不起作用 这是输出 gt node v ReferenceError node is not defined at repl 1 2 at REPLServer self
  • 如何合并两个连续的 git 存储库

    我有一个相当独特的情况 我有一个名为 Project1 的存储库 我在其中工作了一些时间 几个月 一年后 我创建了存储库 Project1 Again 从 Project1 停止的地方开始 现在 我希望修订历史记录是连续的 因此我希望它们合
  • git 别名中的 AWK 语句

    我正在尝试创建一个 git 别名来以特定格式打印日志中的所有拉取请求 但是 我在使用 AWK 删除双空格时遇到问题 这是使用以下命令的 git log 的输出 git log merges grep pull request pretty
  • Netbeans 和 Git,.obj 文件被忽略

    我正在开发一个涉及 obj 文件的小型 git 项目 当我查看 项目选项卡 时 我发现它们被忽略了 但如果我查看我的 gitignore 我无法理解为什么 DepthPeeling nbproject private DepthPeelin
  • 如何将工作树与提交进行比较?

    我在用着 git diff mycommit 用于比较我的工作树mycommit 但它似乎忽略当前索引中不存在的文件 您可以按如下方式重现它 git init echo A gt A txt git add git commit m A g

随机推荐