Posted By: Anonymous
While I am working on
child-branch on my working tree, I am informed that the remote
main (master) branch was updated. Then, I want to merge those changes into my
child-branch, without the need to checkout and pull
main. If I use
git fetch --all, I don’t get the result I expect:
user MINGW64 /d/DATA/Sources/Git/GitTest (child-branch) $ git fetch --all Fetching origin remote: Enumerating objects: 4, done. remote: Counting objects: 100% (4/4), done. remote: Compressing objects: 100% (2/2), done. remote: Total 3 (delta 0), reused 0 (delta 0), pack-reused 0 Unpacking objects: 100% (3/3), 652 bytes | 54.00 KiB/s, done. From https://github.com/myrepo/GitTest b84530e..4d6f085 main -> origin/main user MINGW64 /d/DATA/Sources/Git/GitTest (child-branch) $ git merge main Already up to date.
But, if I go with
git fetch origin main:main, it does do what I expect:
user MINGW64 /d/DATA/Sources/Git/GitTest (child-branch) $ git fetch origin main:main From https://github.com/myrepo/GitTest b84530e..4d6f085 main -> main user MINGW64 /d/DATA/Sources/Git/GitTest (child-branch) $ git merge main Updating b84530e..4d6f085 Fast-forward File_02 | 1 + 1 file changed, 1 insertion(+) create mode 100644 File_02
I want to understand why fetch behaves different with these different parameters because I am working on a project with many repositories, and normally I work on a child-branch of the master branch in each repository.
I want to write a command that updates the master branches of each repository contained in my project folder automatically, so that I don’t need to checkout and pull each master branch of each repository, and later I can just merge those changes into my child branches. I tried this:
find . -name .git -type d | xargs -n1 -I% sh -c 'echo %; git --git-dir=% --work-tree=%/.. fetch --all --recurse-submodules;'
But it doesn’t work because
fetch --all does not do what I expect, and I cannot use
fetch origin main:main because each repository may have different names for the master branches (the name of the master branch in the remote can be master, integration or main)
Is it possible to use some git command similar to
fetch --all that allows me to have the changes ready to merge into my local child-branches?
The key to understanding this aspect of Git is that your branches are completely independent of any other Git’s branches. You may or may not choose to use similar names, e.g., if you have a branch named
feature/tall, you might want some other Git also to have a branch named
feature/tall. But when you do this, these are still completely different branches. You do not share branches with some other Git repository. You only share commits with that Git repository. They have their own branch names.
git fetch --all
git fetch against all remotes. So the question here is how many remotes you have listed: what does
git remote print? If it prints one remote name, specifically
git fetch --all means the same thing as
git fetch origin.
What that means … well, your job at this point is to read the
git fetch documentation. It is long and quite complicated and if you need help interpreting it, that would be unsurprising. So let’s press on:
But, if I go with
git fetch origin main:main, it does do what I expect …
Let’s take a look at the SYNOPSIS section of the documentation:
git fetch [<options>] [<repository> [<refspec>â€¦â€‹]]
git fetch [<options>] <group>
git fetch --multiple [<options>] [(<repository> | <group>)â€¦â€‹]
git fetch --all [<options>]
All the various options start with one or two dashes, so
origin is not an option. You are not using
--all this time, so the final line is not relevant, and you are not using
--multiple, so the third line is not relevant. Hence
origin can only either be a
<group>â€”in which case no additional parameters are allowed;
main:main would be an error hereâ€”or a
This means the first line is the one that applies: we’re doing
git fetch <repository> <refspec>. So
origin here is the repository and
main:main is the refspec.
The next sections of the documentation are quite long and confusing:
git fetch has a very long history behind it, dating back to before the invention of the so-called remote. We get to skip over most of this because
origin is a
remote, so we now can skip directly down to this section:
The name of one of the following can be used instead of a URL as
- a remote in the Git configuration file:
- [two more options snipped here].
All of these also allow you to omit the refspec from the command line because they each contain a refspec which git will use by default.
origin supplies both a URLâ€”so that you don’t have to put in some long
ssh:// URL hereâ€”and a default refspec. That default refspec is the one that
git fetch --all uses; you’d get the same effect with
git fetch origin, which omits the separate
Last, then, we need to look at what a
<refspec> is. This requires going back up from the REMOTES section, and it gets rather confusing and detailed.
Rather than quoting the documentation at this point, I will summarize this, leaving out the newfangled negative refspecs. In its second-simplest form, a refspec is just a pair of words, separated by a colon, such as
main:main. The name on the left is the source and the name on the right is the destination.
The default refspec for most remotes, however, is not this second-simplest form. In fact, the setting you are likely to find, if you run:
git config --get-all remote.origin.fetch
This uses fully qualified references, i.e., names that begin with
refs/. A fully qualified reference is unambiguous.
Given a reference like
zorg, is this a branch name, or a tag name, or what? It’s not immediately clear to anyone or anything, including Git itself. Branch names, in Git, internally begin with
refs/heads/. Tag names internally begin with
refs/tags/. Another form of name, the remote-tracking name, begins with
refs/remotes/ and goes on to name the specific remote. Using this fully-spelled-out form tells Git that
refs/heads/zorg is definitely a branch name, or
refs/remotes/origin/zorg is definitely a remote-tracking name, while
refs/tags/v1.2 is definitely a tag name. When writing any form of script, it’s wise to use the fully-spelled-out names, so that there is no ambiguity.
When you use an ambiguous name, such as
main, Git will try to guess whether this is a branch name, or a tag name, or whatever. If there is in fact a branch named
main, and no tag named
main, Git will guess that you really meant
git fetch origin main:main
winds up being short for:
git fetch origin refs/heads/main:refs/heads/main
because there is a branch named
main (and no tag named
main to confuse things).
In the end, this explains why your
git fetch origin main:main did what you wanted it to do: Git qualified
refs/heads/main (in both cases) and wound up updating your local
refs/heads/main. But note that this is very different from the default refspec of:
This default refspec makes use of two special features:
It begins with a plus sign
+. This makes it a forced refspec. That is, the
:destinationpart of the update will happen regardless of whether the update is a fast-forward operation. For more about that, search StackOverflow.
It uses an asterisk
*in both the source and destination parts. The source part,
refs/heads/*, matches every branch in the source repository. That is, we match all branch names over on
origin. The destination part,
refs/remotes/origin/*, has its
*expanded to put in whatever name was matched on the left side. So theirâ€”
mainbranch becomes our
refs/remotes/origin/main, i.e., our remote-tracking name for their
main. If they have a branch named
develop, our Git will create or update a remote-tracking name
refs/remotes/origin/develop. If they have a branch named
feature/tall, our Git will create or update our
In this way, running
git fetch origin will have our Git get, from their Git repository over at
origin, all new commits they have on all branches, bring them over, put them in our Git repository here, and create-or-update all our remote-tracking names to match their branch names. That way, after
git fetch origin, we have an
origin/* for every branch they have over at
Is it possible to use some git command similar to
fetch --allthat allows me to have the changes ready to merge into my local child-branches?
git fetch. It will update all the
origin/* names. These are the names you should use to refer to commits you got from the other Git.
Your branches are not child branches. Branches have no parent/child relationships. Humans being human, we like to use same or similar names to keep track of similar commits, so after we make a branch
zorg in our Git we often like to have some other Git call its branch
zorg, or maybe
jean-baptiste/zorg or something. That lets us keep them together in our heads. But that’s just for us. All Git cares about are the commit hash IDs. Git has namesâ€”in our repository, for us to use, and in each other Git repository, for each other Git to useâ€”to help find the hash IDs of commits. But it’s only the commits themselves that matter. We can change our names any time we like.