1

If using git fetch updates all the remote tracking branches under refs/remotes/<remote> with the SHA-1 of the latest commit for their upstream branches, what is the point of storing the SHA-1 of the latest fetched commit for the currently checked out branch in FETCH_HEAD if the remote tracking branches already have this?

2
  • 1
    From the git fetch doc : "The names of refs that are fetched, together with the object names they point at, are written to .git/FETCH_HEAD. This information may be used by scripts or other git commands, such as git-pull[1]." Commented Jul 28, 2022 at 12:44
  • git fetch does not update the remote tracking branches unless it's specified in gitconfig. In most cases, it's automatically configured. But in some cases, like in a repository created by git init, git fetch or git fetch origin cannot work as expected without necessary configuration values like remote.origin.fetch. Commented Jul 28, 2022 at 13:03

2 Answers 2

0

Putting the "backwards" in "backwards compatible"

I've referred to this joke before, but it applies here as well:

If using git fetch updates ... remote tracking branches ... [then] what is the point of storing the SHA-1 of the latest fetched commit for the currently checked out branch in FETCH_HEAD ...?

Somewhere between "very little" and "none" at this point, but Git has always done this, including before the very invention of "remote-tracking [branch] names" in the first place. To maintain backwards compatibility, Git must keep on doing this.

As recently as Git version 2.5, git pull was still a shell script, and that shell script would read .git/FETCH_HEAD to extract the data to pass to git merge. Git 2.6 (released in Sep 2015) had the code in C, by which point that wouldn't necessarily be required, but it would take some historical sleuthing to find out at what point it actually stopped happening (often some of the C rewrites initially just emulate the scripts to avoid making too many wholesale, untested code changes).

Finally, the if clause above is still a bit overpowered. It is true that if and when git fetch updates remote-tracking names, it's mostly redundant to keep this data in .git/FETCH_HEAD as well, and now that git pull is C code that has no logical reason to fish information out of a file that it can just pass around in memory, there's no git pull-oriented logic either. But git fetch doesn't update a remote-tracking name in several special cases:

  • when you use a raw URL, there is no remote involved, so there are no remote-tracking names; or
  • if the default fetch refspecs (when using a remote R, the result of git config --get-all remote.R.fetch) do not have a standard-style map in them,1 fetch won't update any remote-tracking names opportunistically.

If Git would forbid these two cases, that would eliminate all reasons except for "backwards compatible", but the "backwards compatible" clause will probably persist for another decade beyond that point.


1By "standard-style map" I mean something that translates refs/heads/foo into refs/remotes/whatever. Single-branch clones have +refs/heads/somebranch:refs/remotes/origin/somebranch for instance, while normal clones have +refs/heads/*:refs/remotes/origin/*, for remote origin. The remote name and the mapped name don't have to match, in these default fetch refspecs, which is weird and smells like someone overgeneralized back in the day, but now there's "backwards compatibility"...

Sign up to request clarification or add additional context in comments.

7 Comments

"when you use a raw URL, there is no remote involved, so there are no remote-tracking names" Wouldn't this just create a local remote tracking branch (and thus still making the storing of the tip commit of the currently checked out branch in FETCH_HEAD pointless)? "if the default fetch refspecs (when using a remote R, the result of git config --get-all remote.R.fetch) do not have a standard-style map in them¹, fetch won't update any remote-tracking names opportunistically." If no remote-tracking names would be updated in this case, what would be stored in FETCH_HEAD?
No, when using a raw URL you find that .git/FETCH_HEAD contains the raw URL and branch names. A URL is not a valid remote name and Git has not created any remote-tracking names. (Try it, it's easy to test out.) The same goes for a remote where you've deleted the remote.<remote>.fetch line from .git/config (again, try it out, it's easy to test).
I see. I found that, after using git fetch using cat .git/FETCH_HEAD gave a list of tip commits of all the remote branches (I assume because git fetch fetches all repositories from the default remote?). The tip commits were all accompanied by not-for-merge except for the currently checked out branch which I assume is for git to be able to tell which remote tip commit to merge into the currently checked out local branch when pulling. However, if I were to use git cat-file -p FETCH_HEAD, it instead returns what appears to be the contents of a commit. I was under the impression- [1/2]
-that the Git cat-file command is very similar in use to the cat command in Windows Powershell so I am a bit confused as to why the content returned appears to be different. Apart from this, just to confirm what you said, the storing of the tip commits of fetched branches within FETCH_HEAD is mainly for backwards compatibility reasons but also because remote tracking branches don't have their tip commits updated when either a URL is used to fetch or the default fetch refspecs do not have a "standard-style" map, and so FETCH_HEAD provides a place to store the tip commits when- [2/3]
fetching within either of these scenarios where they otherwise would not be stored in the remote-tracking branches. [3/3]
|
0

What other ref names get updated when you fetch is a matter of fetch options. The factory default config tells it to update refs/remotes/origin/* if you don't supply explicit options, but you can, you can have it fetch anything you want and update whatever refs you say. But it always logs what it did in FETCH_HEAD. Only fetch updates that log.

5 Comments

In that case, is there any particular reason why git pull uses git fetch followed by git merge FETCH_HEAD instead of git merge <remote>/<branch>?
Yes, fetch is what fetches history, nothing else fetches history. Pull is a convenience command to do a fetch and then do whatever you like to do after you fetch, just like clone is a convenience command to do an init and set up a remote and do a fetch. <remote>/<branch> is a convention, followed by the convenience commands, the configs define how it works, how fetch maps the refs it fetched from the other repo to any tracking refs it updates in the local one.
What I think you're missing is, all refs are local. <remote>/<branch> is convenience shorthand for the local refs that fetch, at its factory default settings, updates when it's done fetching.
Sorry, I'm not quite sure that I understand. I get that fetch and pull are convenience commands but I'm not sure I understood what you meant by "<remote>/<branch> is a convention, followed by the convenience commands, the configs define how it works, how fetch maps the refs it fetched from the other repo to any tracking refs it updates in the local one." How does this explain why Git uses git merge FETCH_HEAD instead of git merge <remote>/<branch> when git pull is used?
fetch is not a convenience command, it's plumbing, it's a core command. You tell pull what to fetch and then do whatever with the result no matter what you told it to fetch it'll be logged in FETCH_HEAD. Updating FETCH_HEAD isn't the extra step, it's how Git works. It's updating the other refs that's the convenience, part of the standard workflow that's taught first, having evolved around the core machinery as the quickest way to work with it.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.