Search code examples
gitgit-branchgit-fetch

Can fetch but then can not find branch


This is the kind of occasional Git nuance that confounds me. Can someone explain what's going on here? I'm fetching from one repository (with redirect rules from config ignored) and pushing into another (with redirect rules from config applied):

$ HOME=/dev/null git fetch origin refs/heads/8.9.170
 * branch                  8.9.170    -> FETCH_HEAD

$ git push origin refs/heads/8.9.170
error: src refspec refs/heads/8.9.170 does not match any

$ git rev-parse refs/heads/8.9.170
refs/heads/8.9.170
fatal: ambiguous argument 'refs/heads/8.9.170': unknown revision or path not in the working tree.
Use '--' to separate paths from revisions, like this:
'git <command> [<revision>...] -- [<file>...]'

$ cat .git/config 
[core]
    repositoryformatversion = 0
    filemode = true
    bare = false
    logallrefupdates = true
[remote "origin"]
    url = https://chromium.googlesource.com/v8/v8.git
    fetch = +refs/heads/*:refs/remotes/origin/*
    fetch = +refs/branch-heads/*:refs/branch-heads/*
[branch "master"]
    remote = origin
    merge = refs/heads/master

Meanwhile, fetching a branch/ref that doesn't exist would throw an obvious error:

$ HOME=/dev/null git fetch origin refs/heads/obviously/invalid
fatal: couldn't find remote ref refs/heads/obviously/invalid

Solution

  • TL;DR

    Consider using:

    git push origin FETCH_HEAD:refs/heads/8.9.170
    

    See the long answer as to why.

    Long(ish)

    This part of your .git/config looks slightly odd, but not impossibly odd:

    [remote "origin"]
        url = https://chromium.googlesource.com/v8/v8.git
        fetch = +refs/heads/*:refs/remotes/origin/*
        fetch = +refs/branch-heads/*:refs/branch-heads/* 
    

    That second fetch = line seems at first blush unlikely to match anything: Git itself doesn't use the namespace refs/branch-heads/ for anything. If it matches nothing at all, that's fine; it's harmless. If it does match something, it will force-update any refs/branch-heads/ names that you fetch. You are explicitly not fetching any, but at first, this seems like an odd thing to do.

    But it turns out that https://chromium.googlesource.com/v8/v8.git does have a whole bunch of refs/branch-heads/ names in it (I checked with git ls-remote and saw them). What they're for, I have no idea. They also have the standard refs/heads/ branch names. Because they do have these refs/branch-heads/ names, you should be extra-careful where I note that you can do a full fetch below.

    Meanwhile, this output here:

    $ HOME=/dev/null git fetch origin refs/heads/8.9.170
     * branch                  8.9.170    -> FETCH_HEAD
    

    suggests that you have a truly ancient Git binary. This plus a few other items are likely the sources of all your subsequent trouble. Git versions since 1.8.4 would print:

    $ HOME=/dev/null git fetch origin refs/heads/8.9.170
     * branch                  8.9.170    -> FETCH_HEAD
       <hash>..<hash>          8.9.170    -> origin/8.9.170
    

    because modern Git will "opportunistically update" any fetched branch based on the fetch = settings, and while you have one nonstandard setting, that's preceded by the standard setting. So you must have a severely outdated Git. You can still get your work done with it though; you just need to be more explicit, by running:

    HOME=/dev/null git fetch origin +refs/heads/8.9.170:refs/remotes/origin/8.9.170
    

    which this time will update refs/remotes/origin/8.9.170 (forcefully, because of the plus sign), or more simply:

    HOME=/dev/null git fetch origin
    

    which fetches everything and updates all names based on the fetch = lines. Note that this will obey your extra rule for the refs/branch-heads/ entities, updating all your remote-tracking names (refs/remotes/origin/*) and these weird names (whatever they are).

    As it is, though, you're only dropping the new commit hash ID into the special .git/FETCH_HEAD file, where git fetch writes it so that git pull can figure out what just got fetched. Since you're not running git pull, this is not much use to you. But that's why we see the output that mentions FETCH_HEAD.

    Now, we can move on to the git push, which you'll need to change. You are using (and getting):

    $ git push origin refs/heads/8.9.170
    error: src refspec refs/heads/8.9.170 does not match any
    

    You don't have a branch named 8.9.170. Even if you had a modern Git (instead of a pre-1.8.4 Git), you'd still not have a branch named 8.9.170. You would, instead, have a remote-tracking name named 8.9.170. You therefore have two options at this point:

    1. Create a branch named 8.9.170. Then, your command would work as-is.

    2. Push from the name or hash ID that you do have.

    For option 1, this works better if you have a modern Git that has created the remote-tracking name. You can simply run git switch 8.9.170 or git checkout 8.9.170, which will create that branch and then check it out. Or, to avoid having to check it out (this takes a bit: the chromium source is big), you can run git branch 8.9.170 origin/8.9.170, which creates 8.9.170 from origin/8.9.170. Lacking any of these, you can extract the commit hash ID from .git/FETCH_HEAD, or use the name FETCH_HEAD, to create that branch.

    For option 2, which is simpler, you can just run this command:

    git push origin FETCH_HEAD:refs/heads/8.9.170
    

    which is the TL;DR at the front. The name FETCH_HEAD refers (temporarily!) to the hash ID obtained by the git fetch that, because your Git is ancient, failed to create a remote-tracking name. This temporary FETCH_HEAD storage lasts until the next git fetch, which overwrites it, so this has to be done pretty quickly. (That's why this works fine for git pull, which just runs git fetch, then immediately runs a second Git command to use the values from .git/FETCH_HEAD before they can get replaced.)