[英]Force Push a Git Subtree
Every single article on the Internet on "force pushing a Git Subtree" uses the gh-pages:gh-pages
example, like Git force push subtree: error: unknown option `force' , etc.互联网上关于“强制推送 Git 子树”的gh-pages:gh-pages
使用gh-pages:gh-pages
示例,例如Git force push subtree: error: unknown option `force'等。
However, that gh-pages:gh-pages
means但是, gh-pages:gh-pages
意味着
force the push of the gh-pages branch to the remote gh-pages branch at origin强制将 gh-pages 分支推送到原始的远程 gh-pages 分支
and I simply cannot apply to my situation.我根本无法适用于我的情况。
I have a single master
branch, and I've done我有一个master
分支,我已经完成了
$ git subtree split --prefix=my/subtree -b for-force-push
Created branch 'for-force-push'
1044de7a3abdccd0992c6973d23e0faabcc0082c
The next line should be:下一行应该是:
git push -f origin master:for-force-push
right?对?
However, I saw:然而,我看到:
. . .
remote: Create a pull request for 'for-force-push' on GitHub by visiting:
. . .
remote: Create pull request for for-force-push:
remote: https://bitbucket.org/. . .
. . .
Ie, every single of my remote tell me to Create pull request, except my subtree, which sees no update.即,我的每个遥控器都告诉我创建拉取请求,除了我的子树,它看不到更新。 It is a gist repo BTW.顺便说一句,这是一个要点回购。
Is I'm doing something wrong or just subtree of gist repo cannot be force-pushed?是我做错了什么还是不能强制推送gist repo的子树?
PS.附注。
Found another "shortcut" of找到了另一个“捷径”
git push origin `git subtree split --prefix=my/subtree master`:master --force
but that simply put my git in the status of:但这只是让我的 git 处于以下状态:
On branch master
Your branch and 'origin/master' have diverged,
and have 140 and 7 different commits each, respectively.
which is a total disaster :(.这是一场彻底的灾难:(。
Your refspec is backwards: you wanted git push -f origin for-force-push:master
.你的 refspec 是倒退的:你想要git push -f origin for-force-push:master
。 However, that would get you the same thing you got with the one you called a "total disaster".但是,这会给您带来与您所谓的“彻底灾难”相同的东西。 I don't know what you really intend here and cannot give any further advice on that part.我不知道你在这里真正想要什么,也不能就此提供任何进一步的建议。
Both git fetch
and git push
use a refspec —well, one or more, but let's just describe one, first. git fetch
和git push
使用一个refspec —好吧,一个或多个,但让我们先描述一个。 The format of a refspec is +<src>:<dst>
with the +
being optional—it sets the force flag—and the src
and/or dst
being optional as well. refspec 的格式是+<src>:<dst>
,其中+
是可选的——它设置强制标志——并且src
和/或dst
也是可选的。 If you drop some part(s)—source and/or destination—things get a little confusing, and if you use unqualified references for source and/or destination, things get a little confusing.如果您删除某些部分(源和/或目的地),事情会变得有点混乱,如果您对源和/或目的地使用不合格的引用,事情就会变得有点混乱。 The interaction of +
with git push --force
or git push --force-with-lease
is confusing as well. +
与git push --force
或git push --force-with-lease
的交互也令人困惑。 So it's best, for discussion purposes, to start with these assumptions:因此,出于讨论目的,最好从以下假设开始:
The force flag, if present, is present as +
, and if not, is just absent. force 标志(如果存在)以+
形式存在,如果不存在,则不存在。
The source and destination fields are both filled in. Both are fully-qualified.源和目标字段均已填写。两者都是完全限定的。
This removes all the confusers except for what the source and destination mean , so now we can explain those simply enough.这消除了除了源和目标的含义之外的所有混淆,所以现在我们可以简单地解释这些。 Remember that git fetch
means get commits (and/or other internal Git objects, but mostly commits) from them and git push
means send commits (and/or other internal Git objects) to them .请记住, git fetch
意味着从他们那里获取提交(和/或其他内部 Git 对象,但主要是提交),而git push
意味着向他们发送提交(和/或其他内部 Git 对象) 。 Them is, by definition, some other Git repository, being acted on by various git something
commands run on another machine 1 .根据定义,它们是其他一些 Git 存储库,由在另一台机器上运行的各种git something
命令执行操作1 。
If we're sending stuff to them , the source would be commits (and other Git objects) in our repository.如果我们向他们发送内容,源将是我们存储库中的提交(和其他 Git 对象)。 We find these commits using our names , eg, our branch names: refs/heads/for-force-push
for instance.我们使用我们的名字找到这些提交,例如,我们的分支名称:例如refs/heads/for-force-push
。 The destination is a name in their repository : refs/heads/master
for instance.目标是其存储库中的名称:例如refs/heads/master
。 So, since the syntax is <src>:<dst>
, we'll use:因此,由于语法是<src>:<dst>
,我们将使用:
git push <them> refs/heads/for-force-push:master
If we're getting stuff from them — git fetch
;如果我们从他们那里得到东西—— git fetch
; the badly-named git pull
does too much, 2 so we'll ignore it here—then they are the "source" and we are the "destination", and we would use:名不副实的git pull
做得太多了, 2所以我们在这里忽略它——那么它们是“源”,我们是“目的地”,我们将使用:
git fetch <them> refs/heads/master:refs/remotes/origin/master
for instance.例如。
If you provide multiple refspecs, such as:如果您提供多个 refspecs,例如:
git push origin <spec1> <spec2>
Git will operate on all the refspecs, sending multiple update requests for instance. Git 将对所有 refspec 进行操作,例如发送多个更新请求。 The push operation first sends any commits and/or other internal objects required, and then ends by asking (regular push) or commanding (force-push) the other Git to create or update various names—references—in their repository.推送操作首先发送所需的任何提交和/或其他内部对象,然后通过要求(常规推送)或命令(强制推送)其他 Git 在其存储库中创建或更新各种名称(引用)来结束。 The values their Git should stuff into these new or updated references are the values your Git found by resolving the source
parts, which also determined which commits and/or other objects had to be sent.他们的 Git 应该填充到这些新的或更新的引用中的值是你的 Git 通过解析source
部分找到的值,这也决定了哪些提交和/或其他对象必须被发送。 Hence:因此:
git push origin refs/heads/br1:refs/heads/br1 refs/heads/br2:refs/heads/br2
sends the commits we have on our branch br1
and the commits we have on our branch br2
, and then asks (politely) that their Git should set their branch name br1
to match our branch name br1
, and to set their branch name br2
to match our branch name br2
.发送我们在我们的分支br1
的提交和我们在我们的分支br2
上的提交,然后(礼貌地)要求他们的 Git 应该将他们的分支名称br1
设置为与我们的分支名称br1
匹配,并将他们的分支名称br2
设置为匹配我们的分行名称br2
。
This lets you push more than one branch at a time , which is something a lot of people don't seem to know.这使您可以一次推送多个分支,这似乎是很多人不知道的。 It's important though, in pre-push (sender side) and pre- and post-receive hooks (receiving side) hooks: you must read through all the updates that are being requested or commanded.但是,在推送前(发送方)和接收前和接收后挂钩(接收方)挂钩中很重要:您必须通读请求或命令的所有更新。
1 In a degenerate case, these commands might be run on your own machine: for instance, you can push or fetch from your laptop, to your laptop. 1在退化的情况下,这些命令可能会在您自己的机器上运行:例如,您可以从您的笔记本电脑推送或获取到您的笔记本电脑。 You can do this over ssh and/or https if you have an ssh server and/or https server running on your laptop.如果您的笔记本电脑上运行着 ssh 服务器和/或 https 服务器,则可以通过 ssh 和/或 https 执行此操作。 This is sometimes how we set up a VM, using Docker or VirtualBox or whatever, for instance.这有时是我们设置 VM 的方式,例如,使用 Docker 或 VirtualBox 等。 The exact details depend a lot on the software you're using.确切的细节很大程度上取决于您使用的软件。 But mostly we're sending from laptop to GitHub or whatever, and it's easier to think about this if "their" Git software is running on some server like GitHub, and "their" Git repository is a completely separate repository over on GitHub.但大多数情况下,我们是从笔记本电脑发送到 GitHub 或其他任何东西,如果“他们的”Git 软件运行在像 GitHub 这样的服务器上,并且“他们的”Git 存储库是 GitHub 上的一个完全独立的存储库,那么考虑这一点会更容易。
2 If what git fetch
does had been called git pull
, we'd say "pull and merge" or "pull and rebase" and it would all make more sense. 2如果git fetch
所做的被称为git pull
,我们会说“pull and merge”或“pull and rebase”,这一切都会更有意义。 Instead, we say "fetch and merge-or-rebase" by saying git pull
, and then we have to stop and walk things back a bit and figure out whether we're merging or rebasing before we can move forward again.相反,我们通过说git pull
来说“获取和合并或变基”,然后我们必须停下来走回去,弄清楚我们是合并还是变基,然后才能再次前进。 As with revert-vs-backout, Mercurial got this one right and Git got this one wrong, though the revert-vs-backout situation is pretty clearly worse.与 revert-vs-backout 一样,Mercurial 做对了这一点,而 Git 做错了这一点,尽管 revert-vs-backout 的情况显然更糟。
Writing out refs/heads/master
, refs/remotes/origin/master
, and so on is kind of a pain.写出refs/heads/master
、 refs/remotes/origin/master
等等是一种痛苦。 Can't Git figure this out? Git 不能解决这个问题吗? If I want to push branch br1
, why can't I use:如果我想推送分支br1
,为什么我不能使用:
git push origin br1:br1
for instance?例如? Git would just have to fill in refs/heads
on both sides for me. Git 只需要为我填写两边的refs/heads
。 If I:如果我:
git push origin v1.7:v1.7
can't Git figure out that v1.7
is a tag, and fill in refs/tags/
on both sides for me? Git 是不是搞不清楚v1.7
是个标签,给我两边都填上refs/tags/
吗?
The answer here is: yes, Git can figure this out.答案是:是的,Git可以解决这个问题。 The way it figures this out is complicated though.不过,它计算出来的方式很复杂。 Someone needs to have the name.有人需要有这个名字。 For git push
, we're sending—we're the source—so we need to have the name, so that Git can look up the commit hash ID.对于git push
,我们正在发送——我们是源——所以我们需要有名称,以便 Git 可以查找提交哈希 ID。 But when we use git push
we're not even required to put a name on the left as the source:但是当我们使用git push
我们甚至不需要在左边放一个名字作为源:
git push origin a123456:refs/heads/newbranch
is allowed if a123456
is the (abbreviated) hash ID of one of our commits, and:如果a123456
是我们提交之一的(缩写)哈希 ID,则允许,并且:
git push origin HEAD:refs/heads/newbranch
is also allowed even if we're on a detached HEAD.即使我们在分离的 HEAD 上也是允许的。 So if the left (source) side of a git push
refspec isn't fully qualified, the right side can be used to qualify the name—but now the destination name has to match some existing name in the other Git.因此,如果git push
refspec 的左侧(源)不是完全限定的,则右侧可用于限定名称——但现在目标名称必须与另一个Git 中的某个现有名称匹配。 Which one will it match?它会匹配哪一个? You might not know.你可能不知道。 It's a bad idea to let Git match on its own, as it might match the wrong thing (a tag name for instance).让 Git 自己匹配是一个坏主意,因为它可能匹配错误的东西(例如标签名称)。 I recommend using a fully-qualified reference here if you aren't using your own Git to do the resolving via the source.如果您不使用自己的 Git 通过源进行解析,我建议在此处使用完全限定的参考。 If you're trying to create a new branch from a detached HEAD —which is one of my own use cases for this—you must provide a fully qualified name anyway:如果你试图从一个分离的 HEAD创建一个新分支——这是我自己的用例之一——无论如何你必须提供一个完全限定的名称:
$ git push origin $(git rev-parse HEAD):xyzzy # simulate detached HEAD
error: The destination you provided is not a full refname (i.e.,
starting with "refs/"). We tried to guess what you meant by:
- Looking for a ref that matches 'xyzzy' on the remote side.
- Checking if the <src> being pushed ('11ae6ca18f6325c858f1e3ea2b7e6a045666336d')
is a ref in "refs/{heads,tags}/". If so we add a corresponding
refs/{heads,tags}/ prefix on the remote side.
Neither worked, so we gave up. You must fully qualify the ref.
hint: The <src> part of the refspec is a commit object.
hint: Did you mean to create a new branch by pushing to
hint: '11ae6ca18f6325c858f1e3ea2b7e6a045666336d:refs/heads/xyzzy'?
The git fetch
command is different from git push
, in part for historical reasons. git fetch
命令与git push
不同,部分是由于历史原因。 When we use git push
, we must send the other Git some name(s) to set.当我们使用git push
,我们必须向其他 Git发送一些名称以进行设置。 That is, git push origin master:
makes no sense.也就是说, git push origin master:
没有意义。 So this form of git push
is simply invalid.所以这种形式的git push
根本就无效。 However, git push origin :master
"means" git push --delete origin master
: that is, we have our Git ask their Git to delete their name master
.然而, git push origin :master
“意味着” git push --delete origin master
:也就是说,我们让我们的 Git 要求他们的 Git删除他们的名字master
。 As before, since we didn't look up a name on our side, we rely on their Git's names to figure out whether this is a branch name or a tag name.和以前一样,由于我们没有在我们这边查找名称,因此我们依靠他们的Git 名称来确定这是分支名称还是标签名称。 It's not entirely wise to omit the refs/heads/
or refs/tags/
part here either— their names may change before our Git gets to look them up during the git push
—but at least this time you probably expect to match exactly one of those two kinds of names on the destination (ie, we're not trying to create a new branch or tag, we're trying to delete an existing branch or tag).在这里省略refs/heads/
或refs/tags/
部分也不是完全明智的——在我们的 Git 在git push
期间查找它们之前,它们的名称可能会改变——但至少这次你可能希望与其中的一个完全匹配目标上的这两种名称(即,我们不是在尝试创建新的分支或标签,而是在尝试删除现有的分支或标签)。
So, during git push
, we can:因此,在git push
期间,我们可以:
: dst
part entirely, because git push origin br1
means git push origin br1:br1
anyway.完全省略: dst
部分,因为git push origin br1
git push origin br1:br1
无论如何都意味着git push origin br1:br1
。 This lets us push commits to the other Git and use the same branch name on both sides, which is probably our most common use case.这让我们可以将提交推送到另一个 Git 并在两侧使用相同的分支名称,这可能是我们最常见的用例。 And, if we've set origin/br1
as the upstream of the current branch br1
, we can just run git push
and be done with it—provided we haven't fiddled with the push.default
setting, anyway.而且,如果我们已经将origin/br1
设置为当前分支br1
的上游,我们就可以运行git push
并完成它——前提是我们没有摆弄push.default
设置,无论如何。 3 3
The special syntax:特殊语法:
git push origin :
invokes the matching mode.调用匹配模式。 Here, our Git calls up their Git and has them list out their branch names.在这里,我们的 Git 调用他们的 Git 并让他们列出他们的分支名称。 Now we know if they have a branch named br1
, if they have a branch named master
, and so on.现在我们知道他们是否有一个名为br1
的分支,如果他们有一个名为master
的分支,等等。 Our Git also knows, because it's looking right at our repository, whether we have branches named br1
and master
and so on.我们的 Git 也知道,因为它正在查看我们的存储库,是否有名为br1
和master
分支等等。 For all matching branch names, our Git tries to git push
that pair of names.对于所有匹配的分支名称,我们的 Git 会尝试git push
这对名称。
3 This works with the default-since-Git-2.0 push.default
of simple
, and also with current
and upstream
. 3这适用于simple
的 default-since-Git-2.0 push.default
,也适用于current
和upstream
。 It does something different with matching
, which is the default in some ancient versions of Git;它与matching
做一些不同的事情,这是一些古老版本的 Git 中的默认设置; if you're using those, either upgrade, or stick with git push origin br1
.如果你正在使用这些,要么升级,要么坚持使用git push origin br1
。 If you have set your push.default
to nothing
, Git will force you to spell it out: I tried this mode for a while but it was too painful / tiring, and I went back to simple
for most of my usage.如果您将push.default
设置为nothing
,Git 会强迫您将其拼写出来:我尝试了一段时间这种模式,但它太痛苦/太累了,并且在大多数使用中我又回到了simple
。
git fetch
is different git fetch
是不同的When we run git push
with no arguments or just one argument such as a remote name ( git push origin
), the default , in modern Git, is:当我们不带参数或只有一个参数(例如远程名称( git push origin
))运行git push
时,现代 Git 中的默认值是:
origin/ B
where B
is the name of the current branch (and replace origin
here with the appropriate remote name as needed), and require that branch B exist over on the other Git;确保它是origin/ B
,其中B
是当前分支的名称(并根据需要将这里的origin
替换为适当的远程名称),并要求分支B存在于另一个 Git 上;git push origin B : B
(again replace origin
here if appropriate).做一个git push origin B : B
(如果合适,再次在这里替换origin
)。 So this pushes one set of commits, from the current branch, and asks the other Git to update one of its branches: the one whose name is in the current branch.因此,这推一组提交的,从目前的分支,并要求其他的Git来更新它的一个分支:他的名字是在当前分支之一。 If you find the first two bullet points here annoying—the requirement that branch B exist on the remote, and be set as the upstream of the current branch—you can change your push.default
to current
.如果你觉得这里的前两个要点很烦人——要求分支B存在于远程,并被设置为当前分支的上游——你可以将push.default
更改为current
。 Note that this makes it easy to accidentally expose a private branch that you did not mean ever to push, though, so be careful if you do this.请注意,这很容易意外地暴露您本不想推送的私有分支,因此这样做时要小心。 (The matching
mode doesn't have this problem, so if you like the behavior Git had in 1.x, you can change your push.default
to matching
; just be aware that this may often push multiple branches.) ( matching
模式没有这个问题,所以如果你喜欢 Git 在 1.x 中的行为,你可以将你的push.default
更改为matching
;请注意,这可能经常推送多个分支。)
But git fetch
is different.但是git fetch
是不同的。 If we run git fetch
with no arguments, Git will:如果我们不带参数运行git fetch
,Git 将:
paradise
and its upstream is phloston/paradise
, the remote that git fetch
will use will be phloston
;找到当前分支的上游(如果设置),并从那里获取远程的名称:如果我们在分支paradise
并且其上游是phloston/paradise
,则git fetch
将使用的远程将是phloston
; if that fails, fall back to origin
;如果失败,回退到origin
;remote. remote .fetch
remote. remote .fetch
查找remote. remote .fetch
remote. remote .fetch
, which is a multi-valued configuration option; remote. remote .fetch
,这是一个多值配置选项;If we provide one or more refspecs on the command line—as in:如果我们在命令行上提供一个或多个 refspecs——如:
git fetch origin master
for instance—then git fetch
uses the provided remote (we have to give one 4 ) and refspec(s).例如——然后git fetch
使用提供的遥控器(我们必须给出一个4 )和 refspec(s)。 Those refspecs control what happens—well, mostly.这些refspecs 控制发生的事情——嗯,主要是。 We'll come back to "mostly" in a bit.我们稍后会回到“大部分”。
If we leave them out, with git fetch
or git fetch origin
, though, we get the default from remote.origin.fetch
(or whatever other setting).但是,如果我们将它们排除在外,使用git fetch
或git fetch origin
,我们会从remote.origin.fetch
(或任何其他设置)获得默认值。 This default is responsible for a lot of mysteries in Git.这个默认值造成了 Git 中的许多谜团。 In particular, this is how single-branch clones work.特别是,这就是单分支克隆的工作方式。
If we make a single-branch clone (of some URL) with, eg:如果我们制作一个单分支克隆(一些 URL),例如:
git clone -b somebranch --single-branch <url>
the remote.origin.fetch
setting in this clone will be:此克隆中的remote.origin.fetch
设置将是:
+refs/heads/somebranch:refs/remotes/origin/somebranch
If we don't use --single-branch
(nor --depth
, which sets --single-branch
), we get instead:如果我们不使用--single-branch
(也不使用--depth
,它设置--single-branch
),我们会得到:
+refs/heads/*:refs/remotes/origin/*
Note that these refspecs are all two-parters: there's a src
and a dst
, separated by a colon :
, and with the leading +
force-flag always set.请注意,这些 refspecs 都是两部分:有一个src
和一个dst
,由冒号:
分隔,并且始终设置前导+
force-flag。 The destination is a remote-tracking name , in the refs/remotes/origin/
names in this case.目的地是远程跟踪名称,在本例中为refs/remotes/origin/
名称。 The source is a branch name.源是一个分支名称。 So this is why our Git copies their branch names to our remote-tracking names.所以这就是为什么我们的 Git 将他们的分支名称复制到我们的远程跟踪名称。 Their master
or main
becomes our origin/master
or origin/main
.他们的master
或main
成为我们的origin/master
或origin/main
。 Their dev
becomes our origin/dev
.他们的dev
成为我们的origin/dev
。 If they have an origin/whatever
, it's refs/remotes/origin/whatever
in their repository, so we don't copy that at all.如果他们有一个origin/whatever
,那就是他们存储库中的refs/remotes/origin/whatever
,所以我们根本不会复制它。
This tells us something else about refspecs: they can contain wildcard characters.这告诉我们有关 refspec 的其他信息:它们可以包含通配符。 We mostly only use these with git fetch
, and even then only with the default fetch values.我们大多只将这些与git fetch
一起使用,即使这样也只使用默认的fetch 值。 But it is possible to use them elsewhere (feel free to experiment with this, but be careful, it's easy to make a mess: do these experiments with copies of repositories, or with junk temporary ones, not ones you want to keep).但是可以在其他地方使用它们(随意尝试这个,但要小心,很容易弄乱:使用存储库的副本或垃圾临时副本进行这些实验,而不是您想要保留的)。 Be mindful and careful of the difference between shell *
expansion and Git *
expansion.注意并小心shell *
扩展和Git *
扩展之间的区别。
These default refspecs, for git fetch
without a refspec on the command line, are only used if you don't put a refspec on the command line.这些默认 refspecs,用于git fetch
在命令行上没有 refspec 的情况,仅在您未在命令行上放置 refspec 时使用。 Or are they?还是他们? Now we come to another difference between fetch and push.现在我们来看看 fetch 和 push 之间的另一个区别。
In git push
, a request of the form git push remote src :
—a source without a destination—is invalid:在git push
,形式为git push remote src :
的请求git push remote src :
—a source without a destination— 是无效的:
$ git push origin branch:
fatal: invalid refspec 'branch:'
(even if branch
is a valid branch, as it is in the test repository I used here). (即使branch
是一个有效的分支,就像我在这里使用的测试存储库中一样)。 But with git fetch
, it's not an error :但是使用git fetch
,这不是错误:
$ git fetch origin branch:
From: <url>
* branch branch -> FETCH_HEAD
There's this funky FETCH_HEAD
in the output here.这里的输出中有这个时髦的FETCH_HEAD
。 Now watch what happens when I delete origin/branch
and then re-run this same git fetch
:现在看看当我删除origin/branch
然后重新运行相同的git fetch
时会发生什么:
$ git branch -r -d origin/branch
Deleted remote-tracking branch origin/branch (was 222c4dd).
$ git fetch origin branch:
From <url>
* branch branch -> FETCH_HEAD
* [new branch] branch -> origin/branch
The same things happen if I use branch
, without a colon.如果我使用没有冒号的branch
,也会发生同样的事情。 This is where the "mostly" part breaks down.这是“主要”部分分解的地方。 If I repeat the git branch -r -d
to delete origin/branch
, and then fetch directly from the URL , without using the name origin
:如果我重复git branch -r -d
来删除origin/branch
,然后直接从 URL获取,而不使用名称origin
:
$ git branch -r -d origin/branch
Deleted remote-tracking branch origin/branch (was 222c4dd).
git fetch <url> branch
From <url>
* branch branch -> FETCH_HEAD
This time, there was no:这一次,没有:
* [new branch] branch -> origin/branch
line.线。 What's going on here?这里发生了什么? It's time for another answer section.现在是另一个答案部分的时候了。
4 Technically, we have to provide a positional argument but it need not be a remote . 4从技术上讲,我们必须提供一个位置参数,但它不必是一个远程. If it's not a remote, all the usual rules about remotes go out the window here.如果它不是遥控器,则有关遥控器的所有常见规则都在这里消失了。 The obvious substitute rules apply: a refspec is required, and there's no remote
to get a default refspec, so you must give at least one;明显的替代规则适用:需要 refspec,并且没有remote
获取默认 refspec,因此您必须至少提供一个; if you did give one or more, the remote. remote .fetch
如果你确实给了一个或多个, remote. remote .fetch
remote. remote .fetch
lines would have been ignored, so the fact that we can't get them is irrelevant, except for the "mostly" note above. remote. remote .fetch
被忽略,因此我们无法获取它们的事实是无关紧要的,除了上面的“主要”注释。
In very old Git, remotes (like origin
) did not exist .在非常古老的 Git 中,遥控器(如origin
)不存在。 One had to fetch directly from a URL every time.每次都必须直接从 URL 中获取。 This was a pain, so Git grew several different ways to deal with it, which eventually resulted in the invention of remotes, and the standard first remote, origin
, that git clone
makes for us.这很痛苦,因此 Git 开发了几种不同的方法来处理它,最终导致了遥控器的发明,以及git clone
为我们制作的第一个标准遥控器origin
。
Without remotes, though, there could never be any remote-tracking names in the first place.但是,如果没有遥控器,首先就不可能有任何远程跟踪名称。 If we don't have origin
, how can we have origin/branch
?如果我们没有origin
,我们怎么会有origin/branch
? The answer is: we didn't.答案是:我们没有。 Instead, git fetch
just wrote its information to a file, .git/FETCH_HEAD
:相反, git fetch
只是将其信息.git/FETCH_HEAD
文件.git/FETCH_HEAD
:
$ cat .git/FETCH_HEAD
222c4dd303570d096f0346c3cd1dff6ea2c84f83 branch 'branch' of <url>
The exact format of what goes into this file is a bit complicated, but when git pull
was a shell script, git pull
depended heavily on it: git pull
ran git fetch
, then used the hash IDs (seen on the left), the not-for-merge
that's not included here to skip some lines if necessary, and the information on the right to build git merge
commands.进入这个文件的确切格式有点复杂,但是当git pull
是一个 shell 脚本时, git pull
严重依赖它: git pull
运行git fetch
,然后使用哈希 ID(见左侧),而not-for-merge
未包含在此处以在必要时跳过某些行,以及有关构建git merge
命令的权利的信息。 So this would allow git pull url branch
to run:所以这将允许git pull url branch
运行:
git merge -m "merge branch 'branch' of <url>" <hash>
(which git pull
still does today, except now it's not a shell script any more and does not need the FETCH_HEAD
file left between running git fetch
and the subsequent git merge
as both fetch and merge steps are built into the C program). (今天git pull
仍然在做,除了现在它不再是 shell 脚本,并且不需要在运行git fetch
和随后的git merge
之间留下FETCH_HEAD
文件,因为 fetch 和 merge 步骤都内置在 C 程序中)。
With the invention of remotes, this could be simplified, but for a long time it wasn't: git fetch
still wrote .git/FETCH_HEAD
and git pull
was still a shell script that ran git fetch
and then ran git merge
after grepping out the right line(s) and building the command line arguments.随着遥控器的发明,这可以简化,但在很长一段时间内都不是: git fetch
仍然写.git/FETCH_HEAD
并且git pull
仍然是一个 shell 脚本,它运行git fetch
然后在.git/FETCH_HEAD
之后运行git merge
正确的行并构建命令行参数。 Even today, for compatibility, git fetch
still writes this FETCH_HEAD
file.即使在今天,为了兼容性, git fetch
仍然写入这个FETCH_HEAD
文件。
Because of that, git fetch
can do a fetch from a URL or remote with a refspec that consists only of a source
part.因此, git fetch
可以从 URL 或远程使用仅包含source
部分的 refspec 进行提取。 The obvious thing for git fetch
to do in this case is to write only the FETCH_HEAD
file: the information is there, if we need it.在这种情况下, git fetch
要做的显而易见的事情是只写入FETCH_HEAD
文件:如果需要,信息就在那里。
But ... this is not convenient .但是……这样不方便。 So with the invention of remotes, and the remote.origin.fetch
configuration line(s), git fetch
was told to read those lines and obey those refspecs by default.因此,随着遥控器的发明和remote.origin.fetch
配置行, git fetch
被告知读取这些行并默认遵守这些 refspecs 。 This will create remote-tracking names, which are much more convenient: you just run git fetch
or git fetch origin
and now you have origin/branch
as appropriate.这将创建远程跟踪名称,这更方便:您只需运行git fetch
或git fetch origin
,现在您就拥有了适当的origin/branch
。 Since the default setup uses the force flag, your origin/branch
is always updated to match their branch
.由于默认设置使用force标志,因此您的origin/branch
始终会更新以匹配它们的branch
。 5 5
So git fetch
or git fetch origin
does wonderful things: it completely updates all of our remote-tracking names with any new commits that have appeared on the other Git repository, based on their current branch names.所以git fetch
或git fetch origin
做了一些很棒的事情:它根据他们当前的分支名称,用其他 Git 存储库中出现的任何新提交完全更新我们所有的远程跟踪名称。 6 We now know everything there is to know about the state of their repository, at least as of the nanosecond that our fetch
ran. 6我们现在知道关于他们存储库状态的所有信息,至少在我们的fetch
运行的纳秒内。 (By now, seconds may have passed and things could be wildly different, depending on how active their repository is.) (到现在为止,可能已经过去了几秒钟,事情可能会大不相同,这取决于他们的存储库的活跃程度。)
But what if we run, say, git fetch origin master
or git fetch origin main
?但是如果我们运行,比如说, git fetch origin master
或git fetch origin main
呢? Now we're asking to update only the refspec master
.现在我们要求只更新 refspec master
。 In git push
, master
was short for master:master
, which turned into refs/heads/master:refs/heads/master
.在git push
, master
是master:master
缩写,它变成了refs/heads/master:refs/heads/master
。 But for git fetch
, master
is short for master:
or refs/heads/master:
.但是对于git fetch
, master
是master:
或refs/heads/master:
缩写。 This writes to .git/FETCH_HEAD
and then stops .这将写入.git/FETCH_HEAD
然后停止。
Well, it did that until Git version 1.8.4, that is.嗯,直到 Git 版本 1.8.4,它才这样做。 Up until that point, git fetch origin master
did not update our remote-tracking name origin/master
.在那之前, git fetch origin master
没有更新我们的远程跟踪名称origin/master
。 But this was ... sub-optimal?但这是......次优? Icky?恶心? I, for one, found it annoying, and apparently the Git maintainers did as well.一方面,我觉得它很烦人,而且显然 Git 维护者也是如此。 They added what they called opportunistic updates .他们添加了他们所谓的机会更新。
If we've just fetched master
from origin
, and if origin
has default remote.origin.fetch
refspecs that include refs/heads/master:refs/remotes/origin/master
—with or without a force flag, and after expanding *
in any refspecs if appropriate—then git fetch
, since 1.8.4 anyway, will go ahead and update refs/remotes/origin/master
now .如果我们刚刚从origin
获取master
,并且如果origin
具有默认的remote.origin.fetch
,其中包括refs/heads/master:refs/remotes/origin/master
— 带或不带强制标志,并且在任何扩展*
之后refspecs 如果合适——那么git fetch
,不管怎样,从 1.8.4 开始,将继续更新refs/remotes/origin/master
now 。
These opportunistic updates work any time they can: git push origin master
checks to see if your push succeeded, and if so, updates your origin/master
appropriately.这些机会性更新在任何时候都可以工作: git push origin master
检查您的推送是否成功,如果成功,则适当地更新您的origin/master
。 (This was in Git long before 1.8.4, which is why not doing it on fetch
was so inconsistent.) Similarly, git fetch origin master
, which "means" git fetch origin refs/heads/master:
and hence does not update a destination ref, still opportunistically updates the remote-tracking name per the default fetch refspec. (这是在 1.8.4之前很久的Git 中,这就是为什么不在fetch
上这样做如此不一致。)类似地, git fetch origin master
,这“意味着” git fetch origin refs/heads/master:
因此不会更新目标 ref,仍然会根据默认的 fetch refspec机会性地更新远程跟踪名称。
This all requires that you use the name origin
, so that Git can look up remote.origin.fetch
.这一切都要求您使用名称origin
,以便 Git 可以查找remote.origin.fetch
。 That's why using the URL that origin
stands for causes the lack of opportunistic updating.这就是为什么使用origin
代表的 URL 会导致缺少机会更新的原因。 The URL isn't a remote, and Git doesn't find the remote.origin.fetch
settings and cannot apply the opportunistic update rules. URL 不是远程的,并且 Git 找不到remote.origin.fetch
设置并且无法应用机会更新规则。
5 The force flag means do this update even if it's not a fast-forward , which means you need to search for what fast-forward means in Git. 5 force 标志表示即使不是 fast-forward 也要执行此更新,这意味着您需要在 Git 中搜索fast-forward 的含义。 I'll refer here to another answer I wrote about git fetch
.我会在这里参考我写的关于git fetch
另一个答案。 This description applies to fetch and push, but git push
now has a fancier version, --force-if-lease
, that git fetch
lacks.此描述适用于 fetch和push,但git push
现在有一个更好的版本--force-if-lease
,这是git fetch
缺乏的。
6 Unless you set fetch.prune
to true
or use the various prune options, however, git fetch
still leaves stale remote-tracking names behind. 6除非您将fetch.prune
设置为true
或使用各种 prune 选项,否则git fetch
仍然会留下陈旧的远程跟踪名称。 I'm going to ignore this problem here.我将在这里忽略这个问题。
Since git fetch
and git push
take refspecs , and fetch opportunistically updates anyway, and refspecs follow fast-forwarding rules, you can run:由于git fetch
和git push
获取refspecs ,并且无论如何都会随机获取更新,并且 refspecs 遵循快进规则,您可以运行:
git fetch origin refs/heads/br1:br1 refs/heads/br2:br2
as long as you're not on either branch right now.只要你不是在任一分支现在。 Your Git will call up the Git at origin
, look up their branch names, and then:您的 Git 将调用origin
处的 Git,查找它们的分支名称,然后:
origin/br1
and origin/br2
, with forcing, opportunistically;机会性地强制更新您的远程跟踪origin/br1
和origin/br2
; but also但是也refs/heads/br1
, rejecting (as a non-fast-forward) the update if your br1
is ahead of theirs, and likewise for your branch br2
.创建或更新您的refs/heads/br1
,如果您的br1
领先于他们的更新,则拒绝(作为非快进)更新,对于您的分支br2
也是br2
。 (I never actually do this myself, and if you have fetch.prune
set to true
and do some wildcarding with *
, you can shoot yourself in the foot this way. I clobbered a few refs in one of my junk repos I use for this, while writing this up. So avoid wildcards here, especially if you turn on pruning.) (我自己从来没有真正这样做过,如果你将fetch.prune
设置为true
并使用*
做一些通配符,你可以用这种方式在脚下开枪。我在我用于这个的垃圾仓库之一中破坏了一些参考, 在写这个的时候。所以避免在这里使用通配符,特别是如果你打开修剪。)
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.