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 Answers
Putting the "backwards" in "backwards compatible"
I've referred to this joke before, but it applies here as well:
If using
git fetchupdates ... remote tracking branches ... [then] what is the point of storing the SHA-1 of the latest fetched commit for the currently checked out branch inFETCH_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"...
7 Comments
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?.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).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]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]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
git pull uses git fetch followed by git merge FETCH_HEAD instead of git merge <remote>/<branch>?<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.<remote>/<branch> is convenience shorthand for the local refs that fetch, at its factory default settings, updates when it's done fetching.<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?
git fetchdoc : "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]."git fetchdoes 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 bygit init,git fetchorgit fetch origincannot work as expected without necessary configuration values likeremote.origin.fetch.