简体   繁体   English

强制推送 Git 子树

[英]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 :(.这是一场彻底的灾难:(。

TL;DR TL; 博士

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.我不知道你在这里真正想要什么,也不能就此提供任何进一步的建议。

Long

Both git fetch and git push use a refspec —well, one or more, but let's just describe one, first. git fetchgit 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 --forcegit 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 themgit 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 的情况显然更糟。


Abbreviating缩写

Writing out refs/heads/master , refs/remotes/origin/master , and so on is kind of a pain.写出refs/heads/masterrefs/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期间,我们可以:

  • abbreviate names that we actually have one of, because we know our Git will look up our name and find the fully qualified version;缩写我们实际拥有的名称之一,因为我们知道我们的 Git 会查找我们的名称并找到完全限定的版本;
  • omit the : 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 也知道,因为它正在查看我们的存储库,是否有名为br1master分支等等。 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 ,也适用于currentupstream 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 中的默认值是:

  • find the upstream setting of the current branch;找到当前分支的上游设置;
  • make sure it's 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 上;
  • do a 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 将:

  • find the upstream of the current branch, if set, and get the name of the remote from there: if we're on branch 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
  • look up remote. remote .fetch remote. remote .fetch查找remote. remote .fetch remote. remote .fetch , which is a multi-valued configuration option; remote. remote .fetch ,这是一个多值配置选项;
  • use those refspecs, in those configuration settings (plural), as the refspecs for the fetch.在这些配置设置(复数)中使用这些 refspecs 作为获取的 refspecs。

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 fetchgit 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 .他们的mastermain成为我们的origin/masterorigin/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被忽略,因此我们无法获取它们的事实是无关紧要的,除了上面的“主要”注释。


Historical raisins (aka hysterical raisins or hysterical reasons )历史葡萄干(又名歇斯底里的葡萄干或歇斯底里的原因

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 fetchgit 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 fetchgit 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 mastergit 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 pushmastermaster:master缩写,它变成了refs/heads/master:refs/heads/master But for git fetch , master is short for master: or refs/heads/master: .但是对于git fetchmastermaster: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.此描述适用于 fetchpush,但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.我将在这里忽略这个问题。


What this means for you, as a useful special effect这对你意味着什么,作为一个有用的特殊效果

Since git fetch and git push take refspecs , and fetch opportunistically updates anyway, and refspecs follow fast-forwarding rules, you can run:由于git fetchgit 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,查找它们的分支名称,然后:

  • update your remote-tracking origin/br1 and origin/br2 , with forcing, opportunistically;机会性地强制更新您的远程跟踪origin/br1origin/br2 but also但是也
  • create or update your 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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM