Differences between git fetch and git fetch origin master

那年仲夏 提交于 2019-12-03 23:27:09

问题


I was doing a fetch/merge and wanted to know if there is any difference between doing

git fetch

and

git fetch origin master

I do not have any other branches and origin points to my remote repository on GitHub.

When I do:

git fetch origin master
remote: Counting objects: 4, done.
remote: Compressing objects: 100% (2/2), done.
remote: Total 3 (delta 1), reused 0 (delta 0)
Unpacking objects: 100% (3/3), done.
From github.com:XXXXXXXXXXXXXXX
 * branch            master     -> FETCH_HEAD

But just:

git fetch
From github.com:XXXXXXXXXXXXXXX
   531d466..aaf6df0  master     -> origin/master

Note that master points to different things; in one case FETCH_HEAD and in other case, origin/master? Are they different?


回答1:


Here's the "TL;DR" version (which glosses over a lot of special cases): git fetch always updates FETCH_HEAD, with more than one line in various cases. It sometimes updates "remote branches", which are refs whose full name starts with refs/remotes/. The rest is mostly about the "sometimes", which vary based on both the number of arguments given to git fetch, and the git version.


I had a chance to test this. Let's distinguish three cases, all of which assumes running git fetch without extra options like -a or even --all. Let's also exclude the weirder variants of git fetch, like using a URL directly, or the insteadOf entries, or files listed in .git/remotes or .git/branches. (I admit I'm just guessing, but I think these are left-overs from the days before the [remote "name"] entries went into git's config files. Edit, 2019: that turns out to be correct.)

  1. git fetch, and no other arguments.

    Git determines your current branch (in the usual way, by reading HEAD, but you can of course see what it is with git branch or git status). It then looks for a config entry for that branch naming its remote. For instance, suppose you're on branch dummy and .git/config has (among other entries):

    [branch "dummy"]
        remote = remote-X
    

    In this case git fetch is equivalent to git fetch remote-X. After that point, this is equivalent to case 2, which is:

  2. git fetch remote (and no more arguments beyond that).

    Git does not look at your current branch this time. The remote to use is the one given on the command line. It does look for a config section for the given remote. Let's say you're using remote-X: in this case, it looks for:

    [remote "remote-X"]
        url = ...
    

    If that section does not exist, or there is no url = entry, you get an error: fatal: 'remote-X' does not appear to be a git repository.1 Otherwise that gives the URL, and git fetch will attempt to connect to there. Assuming it can connect...

    Normally there's also at least one config entry, possibly more, reading:

        fetch = +refs/heads/*:refs/remotes/remote-X/*
    

    (the name of the remote is hard-coded here). Assuming there is...

    Next, git fetch asks the remote what refs it has (branches and tags mostly, although you can get all refs, but most people just care about branches and tags). You can do this same thing yourself with git ls-remote remote-X, which spills out stuff like this:

    676699a0e0cdfd97521f3524c763222f1c30a094        HEAD
    222c4dd303570d096f0346c3cd1dff6ea2c84f83        refs/heads/branch
    676699a0e0cdfd97521f3524c763222f1c30a094        refs/heads/master
    

    The treatment of the HEAD ref is not entirely consistent (I've seen it behave oddly) but usually here it just gets dropped.2 The remaining branches are renamed and updated according to the fetch = refspec. (If there are multiple fetch = refspecs, they're renamed and updated according to all of them. This is mainly useful for bringing over refs/notes/ or making your own "remote tags" name-space under refs/rtags/, for instance.)

    In this case, fetch will bring over any objects needed for the two branches branch and master, and update the (local) "remote branch" names, refs/remotes/remote-X/branch and refs/remotes/remote-X/master, as needed. For each one that is updated, fetch prints a line like this:

       22b38d1..676699a  master     -> remote-X/master
    

    If the fetch = lines are missing, you get something quite different. The output will read:

     * branch            HEAD       -> FETCH_HEAD
    

    In this case, it's as if the (missing) fetch = line were there and contained fetch = HEAD.

  3. git fetch remote refspec (the refspec part is one or more refspecs, really, as described below).

    This is similar to case 2, only this time, the "refspecs" are supplied on the command line, instead of from the fetch = configuration entries for the remote. However, the fetch behavior is pretty different here.


Let's pause a moment and describe a refspec properly, in this particular case. (Refspecs also occur for git push but, as usual with git, implementation details leak out and they work slightly differently there.) A refspec has an optional leading plus (+) sign, which I'll ignore here;3 then two parts, separated by a colon (:). Both are often just a branch name, but you can (and fetch = lines do) spell out the "full" ref-name, refs/heads/branch in the case of a branch name.

For fetch operations, the name on the left is the name on the remote itself (as shown by git ls-remote for instance). The name on the right is the name to be stored/updated in your local git repository. As a special case, you can have an asterisk (*) after a slash as the last component, like refs/heads/*, in which case the part matched on the left is replaced on the right. Hence refs/heads/*:refs/remotes/remote-X/* is what causes refs/heads/master (as seen on the remote, with git ls-remote) to become refs/remotes/remote-X/master (as seen in your local repository, and in shorter form, on the right side of the -> line git fetch prints).

If you don't put in the :, though, git fetch has no good place to put a copy of "the branch over there". Let's say it's going to bring over the remote's refs/heads/master (the master branch on the remote). Instead of updating your refs/heads/master—obviously that would be bad if you have your own commits in branch master—it just dumps the update into FETCH_HEAD.

Here's where things get particularly squirrely. Let's say you run git fetch remote-X master branch, i.e., give at least one, and maybe several, refspecs, but all with no colons.

  • If your git version older than 1.8.4, the update only goes into FETCH_HEAD. If you gave two colon-less refspecs, FETCH_HEAD now contains two lines:

    676699a0e0cdfd97521f3524c763222f1c30a094        branch 'master' of ...
    222c4dd303570d096f0346c3cd1dff6ea2c84f83        branch 'branch' of ...
    
  • If your git version is 1.8.4 or newer, the update goes there—this part is unchanged—but also, the fetch takes the opportunity to record these branches permanently in their proper remote branches, as given by the fetch = lines for the remote.

    For whatever reason, though, git fetch only prints out an update -> line for the remote branches that are actually updated. Since it always records all the updates in FETCH_HEAD, it always prints the branch names here.

    (The other issue, besides needing git 1.8.4 or newer, with getting the remote branches updated is that those fetch = lines must exist. If they don't, there's no mapping by which the fetch knows to rename refs/heads/* to refs/remotes/remote-X/*.)

In other words, git 1.8.4 and newer really does "opportunistically update" all the remote branches. Older versions of git do it on git push, so it has been inconsistent before. Even in git 1.8.4 it's still inconsistent with git pull, I think (although I don't use git pull enough to notice :-) ); that's supposed to be fixed in git 1.9.

Now let's get back to the difference between git fetch remote and git fetch remote refspec ....


  • If you run git fetch remote, i.e., omit all the refspecs, the fetch falls back to the fetch = lines as usual. The fetch operation brings over all the refs from the fetch lines. All of these go into FETCH_HEAD, but this time they're marked "not-for-merge" (with tabs, which I changed to one space to fit on the web pages better):

    676699a0e0cdfd97521f3524c763222f1c30a094 not-for-merge branch ...
    

    Refs that are not branches, e.g., refs/notes/ refs that are brought over, read instead:

    f07cf14302eab6ca614612591e55f7340708a61b not-for-merge 'refs/notes/commits' ...
    

    Meanwhile, remote branch refs are updated if necessary, with messages telling you which ones were updated:

       22b38d1..676699a  master     -> remote-X/master
    

    Again, everything gets dumped into FETCH_HEAD, but only refs that "need updates" are updated and printed. New branches get the "new branch" printed and old ones have their abbreviated old-and-new SHA-1 printed, as for master -> remote-X/master above.

  • If, on the other hand, you run git fetch remote refspec ..., the fetch brings over only the specified refspecs. These all go into FETCH_HEAD as usual,6 but this time every one of them is printed. Then, if your git is 1.8.4 or newer, any reference-updates that can be mapped (via sensible fetch = lines) and need updating are also updated and printed:

     * branch            master     -> FETCH_HEAD
     * branch            branch     -> FETCH_HEAD
       22b38d1..676699a  master     -> remote-X/master
    

    If your version of git is older than 1.8.4, the update of remote-X/master does not occur for this case—or rather, it does not occur unless one of your command-line refspecs was refs/heads/master:refs/remotes/remote-X/master, or refs/heads/*:refs/remotes/remote-X/*, or the variants of those with the plus-signs in front.


1This is not a great error message. The remote-X argument was never supposed to be a "repository", it was supposed to be a "remote"! It might be nice if git said something more informative here.

2There's a flaw in the git remote protocol: HEAD is usually an indirect ref as it's the current branch on the remote, so it should come over as "ref: refs/heads/master" for instance, but instead it comes over as the fully resolved SHA-1. At least one git command (git clone) attempts to "guess" the current branch on the remote by comparing this SHA-1 to that of each branch-head. In the above, for instance, it's clear that the remote is "on branch master", as HEAD and refs/heads/master have the same SHA-1. But if multiple branch names point to the same commit, and HEAD matches that commit-ID, there's no way to tell which branch (if any) HEAD is on. The remote could be in "detached HEAD" state too, in which case it's not on any branch, regardless of SHA-1 values.

Edit, 2019: this bug was fixed in Git version 1.8.4.3. As long as both Git versions—on the machine you're cloning from, and on your own machine—are 1.8.4.3 or newer, Git no longer has to guess.

3The plus sign means "accept forced updates", i.e., take updates that would be rejected by the "nothing but fast forward"4 rule for branches, or "never change tags"5 for tags.

4A "fast forward" for a label, changing it from an old SHA-1 to a new one, is possible when the old SHA-1 in the commit Directed Acyclic Graph is an ancestor of the new SHA-1.

5The "never change tags" rule was new in git 1.8.2. If your git is older than that, git uses the branch rules for tags too, allowing fast-forwarding without "forced update".

6But without the not-for-merge this time. Basically, when you supply colon-less refspecs, git fetch assumes they're "for merge" and puts them into FETCH_HEAD so that git merge FETCH_HEAD can find them. (I have not tested what happens with non-branch refs.)



来源:https://stackoverflow.com/questions/21537244/differences-between-git-fetch-and-git-fetch-origin-master

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