TL;DR
Your refspec is backwards: you wanted git push -f origin for-force-push:master
. However, that would get you the same thing you got with the one you called a "total disaster". I don't know what you really intend here and cannot give any further advice on that part.
Long
Both git fetch
and git push
use a refspec—well, one or more, but let's just describe one, first. The format of a refspec is +<src>:<dst>
with the +
being optional—it sets the force flag—and the src
and/or dst
being optional as well. If you drop some part(s)—source and/or destination—things get a little confusing, and if you use unqualified references for source and/or destination, things get a little confusing. The interaction of +
with git push --force
or git push --force-with-lease
is confusing as well. So it's best, for discussion purposes, to start with these assumptions:
The force flag, if present, is present as +
, and if not, is just absent.
The source and destination fields are both filled in. Both are fully-qualified.
This removes all the confusers except for what the source and destination mean, so now we can explain those simply enough. Remember that git fetch
means get commits (and/or other internal Git objects, but mostly commits) from them and git push
means send commits (and/or other internal Git objects) to them. Them is, by definition, some other Git repository, being acted on by various git something
commands run on another machine1.
If we're sending stuff to them, the source would be commits (and other Git objects) in our repository. We find these commits using our names, e.g., our branch names: refs/heads/for-force-push
for instance. The destination is a name in their repository: refs/heads/master
for instance. So, since the syntax is <src>:<dst>
, we'll use:
git push <them> refs/heads/for-force-push:master
If we're getting stuff from them—git fetch
; the badly-named git pull
does too much,2 so we'll ignore it here—then they are the "source" and we are the "destination", and we would use:
git fetch <them> refs/heads/master:refs/remotes/origin/master
for instance.
If you provide multiple refspecs, such as:
git push origin <spec1> <spec2>
Git will operate on all the refspecs, sending multiple update requests for instance. The push operation first sends any commits and/or other internal objects required, and then ends by asking (regular push) or commanding (force-push) the other Git to create or update various names—references—in their repository. The values their Git should stuff into these new or updated references are the values your Git found by resolving the source
parts, which also determined which commits and/or other objects had to be sent. Hence:
git push origin refs/heads/br1:refs/heads/br1 refs/heads/br2:refs/heads/br2
sends the commits we have on our branch br1
and the commits we have on our branch br2
, and then asks (politely) that their Git should set their branch name br1
to match our branch name br1
, and to set their branch name br2
to match our branch name br2
.
This lets you push more than one branch at a time, which is something a lot of people don't seem to know. It's important though, in pre-push (sender side) and pre- and post-receive hooks (receiving side) hooks: you must read through all the updates that are being requested or commanded.
1In a degenerate case, these commands might be run on your own machine: for instance, you can push or fetch from your laptop, to your laptop. You can do this over ssh and/or https if you have an ssh server and/or https server running on your laptop. This is sometimes how we set up a VM, using Docker or VirtualBox or whatever, for instance. The exact details depend a lot on the software you're using. But mostly we're sending from laptop to GitHub or whatever, and it's easier to think about this if "their" Git software is running on some server like GitHub, and "their" Git repository is a completely separate repository over on GitHub.
2If what git fetch
does had been called git pull
, we'd say "pull and merge" or "pull and rebase" and it would all make more sense. Instead, we say "fetch and merge-or-rebase" by saying git pull
, and then we have to stop and walk things back a bit and figure out whether we're merging or rebasing before we can move forward again. As with revert-vs-backout, Mercurial got this one right and Git got this one wrong, though the revert-vs-backout situation is pretty clearly worse.
Abbreviating
Writing out refs/heads/master
, refs/remotes/origin/master
, and so on is kind of a pain. Can't Git figure this out? If I want to push branch br1
, why can't I use:
git push origin br1:br1
for instance? Git would just have to fill in refs/heads
on both sides for me. If I:
git push origin v1.7:v1.7
can't Git figure out that v1.7
is a tag, and fill in refs/tags/
on both sides for me?
The answer here is: yes, Git can figure this out. The way it figures this out is complicated though. Someone needs to have the name. For git push
, we're sending—we're the source—so we need to have the name, so that Git can look up the commit hash ID. But when we use git push
we're not even required to put a name on the left as the source:
git push origin a123456:refs/heads/newbranch
is allowed if a123456
is the (abbreviated) hash ID of one of our commits, and:
git push origin HEAD:refs/heads/newbranch
is also allowed even if we're on a detached HEAD. So if the left (source) side of a git push
refspec isn't fully qualified, the right side can be used to qualify the name—but now the destination name has to match some existing name in the other Git. Which one will it match? You might not know. It's a bad idea to let Git match on its own, as it might match the wrong thing (a tag name for instance). I recommend using a fully-qualified reference here if you aren't using your own Git to do the resolving via the source. If you're trying to create a new branch from a detached HEAD—which is one of my own use cases for this—you must provide a fully qualified name anyway:
$ git push origin $(git rev-parse HEAD):xyzzy # simulate detached HEAD
error: The destination you provided is not a full refname (i.e.,
starting with "refs/"). We tried to guess what you meant by:
- Looking for a ref that matches 'xyzzy' on the remote side.
- Checking if the <src> being pushed ('11ae6ca18f6325c858f1e3ea2b7e6a045666336d')
is a ref in "refs/{heads,tags}/". If so we add a corresponding
refs/{heads,tags}/ prefix on the remote side.
Neither worked, so we gave up. You must fully qualify the ref.
hint: The <src> part of the refspec is a commit object.
hint: Did you mean to create a new branch by pushing to
hint: '11ae6ca18f6325c858f1e3ea2b7e6a045666336d:refs/heads/xyzzy'?
The git fetch
command is different from git push
, in part for historical reasons. When we use git push
, we must send the other Git some name(s) to set. That is, git push origin master:
makes no sense. So this form of git push
is simply invalid. However, git push origin :master
"means" git push --delete origin master
: that is, we have our Git ask their Git to delete their name master
. As before, since we didn't look up a name on our side, we rely on their Git's names to figure out whether this is a branch name or a tag name. It's not entirely wise to omit the refs/heads/
or refs/tags/
part here either—their names may change before our Git gets to look them up during the git push
—but at least this time you probably expect to match exactly one of those two kinds of names on the destination (i.e., we're not trying to create a new branch or tag, we're trying to delete an existing branch or tag).
So, during git push
, we can:
- abbreviate names that we actually have one of, because we know our Git will look up our name and find the fully qualified version;
- omit the
:dst
part entirely, because git push origin br1
means git push origin br1:br1
anyway.
This lets us push commits to the other Git and use the same branch name on both sides, which is probably our most common use case. And, if we've set origin/br1
as the upstream of the current branch br1
, we can just run git push
and be done with it—provided we haven't fiddled with the push.default
setting, anyway.3
The special syntax:
git push origin :
invokes the matching mode. Here, our Git calls up their Git and has them list out their branch names. Now we know if they have a branch named br1
, if they have a branch named master
, and so on. Our Git also knows, because it's looking right at our repository, whether we have branches named br1
and master
and so on. For all matching branch names, our Git tries to git push
that pair of names.
3This works with the default-since-Git-2.0 push.default
of simple
, and also with current
and upstream
. It does something different with matching
, which is the default in some ancient versions of Git; if you're using those, either upgrade, or stick with git push origin br1
. If you have set your push.default
to nothing
, Git will force you to spell it out: I tried this mode for a while but it was too painful / tiring, and I went back to simple
for most of my usage.
git fetch
is different
When we run git push
with no arguments or just one argument such as a remote name (git push origin
), the default, in modern Git, is:
- find the upstream setting of the current branch;
- make sure it's
origin/B
where B
is the name of the current branch (and replace origin
here with the appropriate remote name as needed), and require that branch B exist over on the other Git;
- do a
git push origin B:B
(again replace origin
here if appropriate).
So this pushes one set of commits, from the current branch, and asks the other Git to update one of its branches: the one whose name is in the current branch. If you find the first two bullet points here annoying—the requirement that branch B exist on the remote, and be set as the upstream of the current branch—you can change your push.default
to current
. Note that this makes it easy to accidentally expose a private branch that you did not mean ever to push, though, so be careful if you do this. (The matching
mode doesn't have this problem, so if you like the behavior Git had in 1.x, you can change your push.default
to matching
; just be aware that this may often push multiple branches.)
But git fetch
is different. If we run git fetch
with no arguments, Git will:
- find the upstream of the current branch, if set, and get the name of the remote from there: if we're on branch
paradise
and its upstream is phloston/paradise
, the remote that git fetch
will use will be phloston
; if that fails, fall back to origin
;
- look up
remote.remote.fetch
, which is a multi-valued configuration option;
- use those refspecs, in those configuration settings (plural), as the refspecs for the fetch.
If we provide one or more refspecs on the command line—as in:
git fetch origin master
for instance—then git fetch
uses the provided remote (we have to give one4) and refspec(s). Those refspecs control what happens—well, mostly. We'll come back to "mostly" in a bit.
If we leave them out, with git fetch
or git fetch origin
, though, we get the default from remote.origin.fetch
(or whatever other setting). This default is responsible for a lot of mysteries in Git. In particular, this is how single-branch clones work.
If we make a single-branch clone (of some URL) with, e.g.:
git clone -b somebranch --single-branch <url>
the remote.origin.fetch
setting in this clone will be:
+refs/heads/somebranch:refs/remotes/origin/somebranch
If we don't use --single-branch
(nor --depth
, which sets --single-branch
), we get instead:
+refs/heads/*:refs/remotes/origin/*
Note that these refspecs are all two-parters: there's a src
and a dst
, separated by a colon :
, and with the leading +
force-flag always set. The destination is a remote-tracking name, in the refs/remotes/origin/
names in this case. The source is a branch name. So this is why our Git copies their branch names to our remote-tracking names. Their master
or main
becomes our origin/master
or origin/main
. Their dev
becomes our origin/dev
. If they have an origin/whatever
, it's refs/remotes/origin/whatever
in their repository, so we don't copy that at all.
This tells us something else about refspecs: they can contain wildcard characters. We mostly only use these with git fetch
, and even then only with the default fetch values. But it is possible to use them elsewhere (feel free to experiment with this, but be careful, it's easy to make a mess: do these experiments with copies of repositories, or with junk temporary ones, not ones you want to keep). Be mindful and careful of the difference between shell *
expansion and Git *
expansion.
These default refspecs, for git fetch
without a refspec on the command line, are only used if you don't put a refspec on the command line. Or are they? Now we come to another difference between fetch and push.
In git push
, a request of the form git push remote src:
—a source without a destination—is invalid:
$ git push origin branch:
fatal: invalid refspec 'branch:'
(even if branch
is a valid branch, as it is in the test repository I used here). But with git fetch
, it's not an error:
$ git fetch origin branch:
From: <url>
* branch branch -> FETCH_HEAD
There's this funky FETCH_HEAD
in the output here. Now watch what happens when I delete origin/branch
and then re-run this same git fetch
:
$ git branch -r -d origin/branch
Deleted remote-tracking branch origin/branch (was 222c4dd).
$ git fetch origin branch:
From <url>
* branch branch -> FETCH_HEAD
* [new branch] branch -> origin/branch
The same things happen if I use branch
, without a colon. This is where the "mostly" part breaks down. If I repeat the git branch -r -d
to delete origin/branch
, and then fetch directly from the URL, without using the name origin
:
$ git branch -r -d origin/branch
Deleted remote-tracking branch origin/branch (was 222c4dd).
git fetch <url> branch
From <url>
* branch branch -> FETCH_HEAD
This time, there was no:
* [new branch] branch -> origin/branch
line. What's going on here? It's time for another answer section.
4Technically, we have to provide a positional argument but it need not be a remote. If it's not a remote, all the usual rules about remotes go out the window here. The obvious substitute rules apply: a refspec is required, and there's no remote
to get a default refspec, so you must give at least one; if you did give one or more, the remote.remote.fetch
lines would have been ignored, so the fact that we can't get them is irrelevant, except for the "mostly" note above.
Historical raisins (aka hysterical raisins or hysterical reasons)
In very old Git, remotes (like origin
) did not exist. One had to fetch directly from a URL every time. This was a pain, so Git grew several different ways to deal with it, which eventually resulted in the invention of remotes, and the standard first remote, origin
, that git clone
makes for us.
Without remotes, though, there could never be any remote-tracking names in the first place. If we don't have origin
, how can we have origin/branch
? The answer is: we didn't. Instead, git fetch
just wrote its information to a file, .git/FETCH_HEAD
:
$ cat .git/FETCH_HEAD
222c4dd303570d096f0346c3cd1dff6ea2c84f83 branch 'branch' of <url>
The exact format of what goes into this file is a bit complicated, but when git pull
was a shell script, git pull
depended heavily on it: git pull
ran git fetch
, then used the hash IDs (seen on the left), the not-for-merge
that's not included here to skip some lines if necessary, and the information on the right to build git merge
commands. So this would allow git pull url branch
to run:
git merge -m "merge branch 'branch' of <url>" <hash>
(which git pull
still does today, except now it's not a shell script any more and does not need the FETCH_HEAD
file left between running git fetch
and the subsequent git merge
as both fetch and merge steps are built into the C program).
With the invention of remotes, this could be simplified, but for a long time it wasn't: git fetch
still wrote .git/FETCH_HEAD
and git pull
was still a shell script that ran git fetch
and then ran git merge
after grepping out the right line(s) and building the command line arguments. Even today, for compatibility, git fetch
still writes this FETCH_HEAD
file.
Because of that, git fetch
can do a fetch from a URL or remote with a refspec that consists only of a source
part. The obvious thing for git fetch
to do in this case is to write only the FETCH_HEAD
file: the information is there, if we need it.
But ... this is not convenient. So with the invention of remotes, and the remote.origin.fetch
configuration line(s), git fetch
was told to read those lines and obey those refspecs by default. This will create remote-tracking names, which are much more convenient: you just run git fetch
or git fetch origin
and now you have origin/branch
as appropriate. Since the default setup uses the force flag, your origin/branch
is always updated to match their branch
.5
So git fetch
or git fetch origin
does wonderful things: it completely updates all of our remote-tracking names with any new commits that have appeared on the other Git repository, based on their current branch names.6 We now know everything there is to know about the state of their repository, at least as of the nanosecond that our fetch
ran. (By now, seconds may have passed and things could be wildly different, depending on how active their repository is.)
But what if we run, say, git fetch origin master
or git fetch origin main
? Now we're asking to update only the refspec master
. In git push
, master
was short for master:master
, which turned into refs/heads/master:refs/heads/master
. But for git fetch
, master
is short for master:
or refs/heads/master:
. This writes to .git/FETCH_HEAD
and then stops.
Well, it did that until Git version 1.8.4, that is. Up until that point, git fetch origin master
did not update our remote-tracking name origin/master
. But this was ... sub-optimal? Icky? I, for one, found it annoying, and apparently the Git maintainers did as well. They added what they called opportunistic updates.
If we've just fetched master
from origin
, and if origin
has default remote.origin.fetch
refspecs that include refs/heads/master:refs/remotes/origin/master
—with or without a force flag, and after expanding *
in any refspecs if appropriate—then git fetch
, since 1.8.4 anyway, will go ahead and update refs/remotes/origin/master
now.
These opportunistic updates work any time they can: git push origin master
checks to see if your push succeeded, and if so, updates your origin/master
appropriately. (This was in Git long before 1.8.4, which is why not doing it on fetch
was so inconsistent.) Similarly, git fetch origin master
, which "means" git fetch origin refs/heads/master:
and hence does not update a destination ref, still opportunistically updates the remote-tracking name per the default fetch refspec.
This all requires that you use the name origin
, so that Git can look up remote.origin.fetch
. That's why using the URL that origin
stands for causes the lack of opportunistic updating. The URL isn't a remote, and Git doesn't find the remote.origin.fetch
settings and cannot apply the opportunistic update rules.
5The force flag means do this update even if it's not a fast-forward, which means you need to search for what fast-forward means in Git. I'll refer here to another answer I wrote about git fetch
. This description applies to fetch and push, but git push
now has a fancier version, --force-if-lease
, that git fetch
lacks.
6Unless you set fetch.prune
to true
or use the various prune options, however, git fetch
still leaves stale remote-tracking names behind. I'm going to ignore this problem here.
What this means for you, as a useful special effect
Since git fetch
and git push
take refspecs, and fetch opportunistically updates anyway, and refspecs follow fast-forwarding rules, you can run:
git fetch origin refs/heads/br1:br1 refs/heads/br2:br2
as long as you're not on either branch right now. Your Git will call up the Git at origin
, look up their branch names, and then:
- update your remote-tracking
origin/br1
and origin/br2
, with forcing, opportunistically; but also
- create or update your
refs/heads/br1
, rejecting (as a non-fast-forward) the update if your br1
is ahead of theirs, and likewise for your branch br2
.
(I never actually do this myself, and if you have fetch.prune
set to true
and do some wildcarding with *
, you can shoot yourself in the foot this way. I clobbered a few refs in one of my junk repos I use for this, while writing this up. So avoid wildcards here, especially if you turn on pruning.)