Understanding “git remote show” command output… Which is the meaning of: “Local ref configured for 'git push'”?

本秂侑毒 提交于 2021-01-21 10:12:15

问题


I have two remotes and two local branches:

  • local branch "master" is tracking remote branch "origin/master"
  • local branch "mirror" is tracking remote branch "github/master"

This is in my .git/config file:

...

[remote "origin"]
    url = http://my.gitlab.com/nandoquintana/repo.git
    fetch = +refs/heads/*:refs/remotes/origin/*
[remote "github"]
    url = https://github.com/nandoquintana/repo.git
    fetch = +refs/heads/*:refs/remotes/github/*
[branch "master"]
    remote = origin
    merge = refs/heads/master
[branch "mirror"]
    remote = github
    merge = refs/heads/master
[push]
    default = tracking

This is the output of "git remote show origin":

$ git remote show origin 

* remote origin
  Fetch URL: http://my.gitlab.com/nandoquintana/repo.git
  Push  URL: http://my.gitlab.com/nandoquintana/repo.git
  HEAD branch: master
  Remote branch:
    master tracked
  Local branch configured for 'git pull':
    master merges with remote master
  Local ref configured for 'git push':
    master pushes to master (up to date)

$ git remote show github

* remote github
  Fetch URL: https://github.com/nandoquintana/repo.git
  Push  URL: https://github.com/nandoquintana/repo.git
  HEAD branch: master
  Remote branch:
    master tracked
  Local branch configured for 'git pull':
    mirror merges with remote master
  Local ref configured for 'git push':
    master pushes to master (local out of date)

Both "push" and "pull" commands work properly:

  • "push" command sends committed edits in a local branch to "her" remote branch.
  • "pull" command brings commits from a remote branch to "her" local branch.

So, why "Local ref configured for 'git push'" is "master pushes to master"? why not "mirror pushes to master"? and what does "local out of date" means?

UPDATED after @torek answer:

Here we are some refs:

$ git ls-remote github
455063a9db09977535ac808af5729181b153f4c7    HEAD
455063a9db09977535ac808af5729181b153f4c7    refs/heads/master

$ cat .git/refs/heads/master
ca9e4399058a4998bd7c993f86d6740cfaec820b
$ cat .git/refs/heads/mirror
455063a9db09977535ac808af5729181b153f4c7
$ cat .git/refs/remotes/github/master
455063a9db09977535ac808af5729181b153f4c7

Exactly, "refs/remotes/github/master" and "refs/heads/master" are not equal. That is why "local out of date" message appears:

master pushes to master (local out of date)

That is not a problem for me, I positively know that code in "remotes/github/master" and in local "master" are different.

Nevertheless, code in "remotes/github/master" and local "mirror" are the same. In fact, refs "refs/remotes/github/master" and "refs/heads/mirror" are equal.

This is the message that would give me some peace of mind:

mirror pushes to master (up to date)

How could I configure remote/github... or push.default... to get this output?


回答1:


[Some reminders, to both myself and OP: remote origin = GitLab, remote github = GitHub. Fundamentally, the main problem with using git remote show is that it makes assumptions. These assumptions are about how you will run other Git commands—git fetch and git push—in the future, and about how the second Git repository involved in each of those Git commands will be laid out in the future. Per, apparently, an old Danish proverb: It is difficult to make predictions, especially about the future.]

Let me address the edited question at the top here:

This is the message that would give me some peace of mind:

mirror pushes to master (up to date)

How could I configure remote/github... or push.default... to get this output?

There is one thing that is worth a try: one push.default setting, upstream, tells Git that by default, git push should do this. if that works and does what you want, you're all set. But note that whatever Git says here, it's something of a lie anyway.

The fundamental problem is that mirror doesn't push to master. Until you enter a git push command, it doesn't push to anything at all; once you do enter that command, you control where it goes with that git push command, which can totally override any claim git remote show made.

Your options at git push time are:

  • Supply no additional arguments.

    In this case, your Git picks the remote from the current upstream. If the upstream is set to origin/... the remote is origin. If the upstream is set to github/... the remote is github. It looks like a simple string substitution (and usually it is, although for historical reasons, it's actually a horribly complicated string substitution that usually turns out to be simple: take the part before the slash).

    At this point, your Git proceeds on to the second case listed here.

  • Supply one additional argument. This argument names the other Git to connect to. It's usually a remote (origin or github), though at this point you can give a URL instead, for instance.

  • Supply two or more additional arguments. The first of these names the remote (or is a URL). The rest are refspecs, as defined below.

At this point, your Git connects to that remote and, in effect, runs git ls-remote to see what branches they have. This list is important because it's used with the refspecs you gave, or failed to give, based on your push.default setting, and the fact that when you do give a refspec, you can give just half a refspec.

It's the "half a refspec" case that's particularly problematic. No matter what else you do or set, if you actually run git push github mirror while you're on mirror, your Git will ask the Git on remote github to set ... well, this depends on whether you have an upstream set for it, and if not, your push.default setting. The details are in the git push documentation, in Git's usual cryptic form. The default default, though, is that for a "half refspec", the name mirror means mirror:mirror.

If you give a full refspec, e.g., git push github mirror:asdf, the second half of the refspec determines which branch name your Git asks their Git to set. If you give half a refspec, the half you gave usually becomes both names. With the default of push.default = simple, you can't accidentally push your mirror to anyone else's master, you must use the explicit full refspec (and then it's up to you to get it right).

If you give no refspec, your Git falls back on push.default, and there are only five settings for it. The default one, simple, has your Git compare your set of branches to their set of branches (from git ls-remote). If your branch, such as mirror, doesn't have any corresponding branch there, your Git won't ask their Git to set any branches.

Let's assume you are on your branch mirror, which has its upstream set to github/master, and that you have configured push.default to upstream (using git config push.default upstream):

  • If you run git push with no arguments, or git push github with no additional arguments, the remote will be github.

  • If you supply no refspec, the push.default setting applies: the refspec Git will construct will be mirror:master.

  • However, if you supply half a refspec, I'm not sure, even after re-reading the documentation several times, what full refspec Git will construct. I think it will be mirror:mirror, which is not what you want.

  • You can also configure a remote.github.push variable that supplies default refspecs. This might also allow you to get what you want, although push.default = upstream seems simpler.

We now return to the original answer. :-)


So, why "Local ref configured for 'git push'" is "master pushes to master"?

This means that:

  • assuming your current branch is master (i.e., you have run git checkout master)
  • and if you then run git push github (not git push github more-command-words)
  • Git—specifically your own Git—will attempt to push your current master to https://github.com/nandoquintana/repo.git's master

which may or may not succeed, depending on what the Git server at GitHub does with a polite request to set its master to whatever hash ID your Git sends with this push request.

(The same holds if you're on master and run git push origin, again with no additional arguments, except that the name origin refers to the Git at GitLab.)

On the other hand, let's assume your current branch is named mirror:

  • so you just ran git checkout mirror
  • and now you run git push github
  • your Git will—based on what it sees right now, hence assuming this is what it sees again—not attempt to push anything.

The reason for this is most likely that you have push.default configured, or defaulted, to simple. When push.default is set to simple, a git push without a refspec argument tries to push the current branch, but only if the other Git has a branch with the same name. So if the Git at github (i.e., at https://github.com/...) still does not have a branch named mirror, your Git will say to itself: Huh. No branch named mirror. Better not push anything.

(If, tomorrow, that other Git on GitHub does have a branch named mirror, your Git will say to itself: Aha! There's the branch named mirror! OK, I'll ask that other Git to update its mirror.)

why not "mirror pushes to master"?

Because it doesn't. Even if you re-configure your push.default, there's no default setting that means "if I fail to give git push a refspec, make a non-matching one up."

A brief segment on refspecs

All of this is keyed on the concept of a refspec. In Git, a refspec is essentially a pair of references.

A reference is the fancy word for "branch or tag name". (It can be more than just those two, but those are the main two.) A branch name like master is short-hand for its full spelling, refs/heads/master. This reference name, refs/heads/master, is Git's way to say "not just master, but the branch master". A short tag name like v1.2 is short-hand for the reference name refs/tags/v1.2. If you leave out the refs/heads/ or refs/tags/ part, Git usually figures out which one you mean by looking at the branches and tags you have now.

Anyway, a refspec is mostly just two of these things with a colon : in the middle:

refs/heads/master:refs/heads/master

for instance. You need two of them because you are using two Gits: one on your system, which you ask to do something, plus another one on some remote, such as gitlab or github. You have your Git call up the other Git over the Internet-phone. Then your Git and their Git talk to each other, after which your Git will fetch or push things.

The fetch and push steps need one reference for each Git, so that means you need two references: a refspec.

The two halves of a refspec are the source and the destination. If you're running git fetch, the source is the other Git and the destination is your own Git. If you are running git push, you are the source; the other Git becomes the destination. But in either case, the source gives some commits to the destination, and then the source's name—the left half of the refspec—is used to change something in the destination's name—the right half of the refspec.

For git fetch, it's entirely normal to have a different name on each side. We fetch from their refs/heads/master and write to our own refs/remotes/origin/master. We fetch from their refs/heads/master and write to our own refs/remotes/mirror/master. This lets us fetch from a bunch of different places, yet keep them all straight.

For git push, though, it's much more normal to use the same name on each side. We fetch from their master, into our refs/remotes/.../master. Then we work for a while and make sure whatever is in our master is an update that goes on top of their master, by merging or rebasing for instance. Then we call them up again, deliver our new commits, and ask them to set their master—not their nando/master, which they don't even have, but their master—to this latest commit that builds upon their previous commits.

We make sure that it builds upon theirs, by fetching first, then working, then pushing. If we lose a "race" with Sofia—we both fetch about the same time, but she works faster and then she pushes before we can—we get that "remote rejected" "not fast forward" error; we must fetch again, to pick up Sofia's work from GitHub or wherever, and make our work build atop her work, and then try the push again.

and what does "local out of date" means?

When your git remote show called up the remote—specifically github, i.e., https://github.com/nandoquintana/repo.git—over the Internet-phone, that other Git said:

I have these references:
  refs/heads/master  <some big ugly hash ID>

(try running git ls-remote github to see what they have, you will get the complete list).

Your own Git has a branch named master, but the big ugly hash ID your Git has for your refs/heads/master—your master branch—is different.

Since the two are different, your Git assumes you are either "ahead"—you have some commits they don't—or "behind" (they have some commits you don't), or perhaps both. Your Git can't tell how far behind you are, if you are behind at all, but it can tell how far "ahead" you are, by looking at all your commits. If you have commit 129bca4f... whose parent is e033fc12..., and they are at commit e033fc12..., then you're ahead by just one commit.

If your Git can find their Git's commit hash ID in your master branch's history, you are "ahead" and you can probably git push right now to send them your new commits, asking them to set their master to 129bca4f..., and they will probably take the commits and update their master.

But if they have commit 930ab988... and you don't have that commit at all, all your Git knows is that they have some commits you don't. You must be "behind". You can git fetch from them, get all the commits they have that you don't, and remember them with refs/remotes/github/master. You can then do whatever it takes to add those commits to your own master, so that you are even with them—neither ahead nor behind—and do any additional work it takes so that you are now ahead of them.

It's up to you to decide whether that's a good idea, and if so, whether to do it. All git remote show does is call them up, using git ls-remote, over the Internet-phone, and compare their references to your references to guess what git fetch and git push would do, based on those results. (If you use git pull, that just means run git fetch, then run git merge. The git remote show command tries to guess what that will do, too.)



来源:https://stackoverflow.com/questions/45960687/understanding-git-remote-show-command-output-which-is-the-meaning-of-loca

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!