I created a local Git branch off master (lets call it BranchB). I then committed my changes on that branch (to BranchB). But when it came for the time to push to the remote repository, too much coffee got to me, and I wrote git push origin master
instead of just git push
to push the new branch to the repository.
I then went into GitHub to see the commits on master. The changes that I recently accidentally pushed to master did not appear here?
So what exactly happened when I pushed to master while on Branch B? Does that just push the commits that were done on master since I specified master.
The error message error: refusing to update checked out branch: refs/heads/master is emitted by the remote repository and it means you're trying to push code to remote non-bare repository that has different code currently checked out in the working directory.
Git Push Origin pushes all the branches to the main branch. Git Push Origin Master pushes your master branch to the origin. Behavior could be changed via git config.
The <refspec>
you gave here as a parameter for your push is master
, where the full canonical form would have been <src>:<dst>
, <src>
being the source of the push and <dst>
the destination ref. When you omit one, git assumes your parameter is <src>
and that no <dst>
has been given. In that case, and if the branch has a <repository>.push
config set (so in most cases), that ref is used as the source for your push.
So yes, it pushed master (with no new commits) to its remote counterpart. Harmless no-op.
(Maybe check your own config with git config -l | grep origin
or something along these lines.)
You would have pushed BranchB on master with an explicit :
git push origin BranchB:master
That's what I understand from the doc, namely the <refspec>
paragraph in git-push man page. Seems to make sense with your results.
Short answer: you're fine. Git most likely said:
Everything up-to-date
and did nothing at all. If not, you only sent commits that you made on master
, to your GitHub Git's master
.
The longer answer is still that you're fine, but you may be working under a few misconceptions about Git branches. They won't bite you ... yet. đŸ˜…
Every Git commit is uniquely identified by its hash ID. Given a hash ID, Git can find the commit. Inside the commit is another hash ID—the commit's parent commit, the commit that comes before this one. Also inside the commit, of course, is the name and email address of whoever made the commit, and their log message, and (though it's slightly indirect) the full and complete snapshot of all the files that make up that commit.
What this means is that from any given commit, we can work backwards to the previous commit. So if we have a sequence of commits:
... <-F <-G <-H
we just need the hash ID of the last such commit, such as H
. Git will then be able to read H
and fish out the hash ID of its parent G
, and read G
and fish out the ID of F
, and so on.
master
therefore just identifies one commitThe way Git finds the hash ID of the last commit of any branch is through the branch name. The name itself, master
or BranchB
or whatever, just holds the hash ID:
...--F--G--H <-- master, BranchB
Here both names store the same hash ID. So all the commits that are on master
are also on BranchB
.
To make a new commit, Git will save a snapshot, save your name and email and so on, and save the hash ID of the current commit H
. This becomes a new commit I
, which gets assigned its new unique hash ID:
...--F--G--H <-- master, BranchB
\
I
One of these names has to change now!
HEAD
remembers which branch you're onIn order to know which name to update, Git attaches the special name HEAD
to one branch. If HEAD
is attached to BranchB
, that's the name that Git will update:
...--F--G--H <-- master
\
I <-- BranchB (HEAD)
It really is that simple.
Well, "that simple" isn't so simple: branch names do move like this, and Git finds commits by starting at the latest and working backwards. You can, at any time, ask Git to move any branch name to any existing commit. Let's say we force Git to move BranchB
back to commit H
. How do we then find commit I
?
If we do do this, it becomes pretty hard to find I
. (There are a bunch of mechanisms for that but let's not worry about them yet.) The simple part is that as long as the branch name moves forward—along new commits we make, one at a time, or a bunch of commits we pick up that also move forward, we're good, because from somewhere past I
that leads back to I
, we can always work back to I
.
This is what Git's special term fast forward means. A branch-name update is a fast-forward operation if, from the new "last" commit, we can still get back to the old "last" commit.
This is where git push
comes in. When you run git push
, you have your Git call up some other Git, at some URL. Your Git and their Git have a conversation. Your Git will hand over new commits to their Git, as needed, and then your Git asks their Git to set one of their branch names.
Remember, their Git is a Git repository, just like yours. So their Git has their own branch names. They already have a master
:
...--F--G--H <-- master
Your Git will call up their Git and ask them: Hey, why don't you set your master
to point to commit H
? They'll say: It already does. Yours will say: Oh, right, okay bye! and your Git will print:
Everything up-to-date
Or, you could send some new commit hash ID. Your Git will offer their Git your new commit(s), as many as it takes to link this new one back to H
, or wherever it goes that your Git and their Git share some earlier commit. Then your Git will as their Git: Hey, how about setting your master
to this other hash ID? They might say okay, or they might say: No! that's not a fast-forward! And now that you know what fast-forward means, you know that if they reject your request, it's because whatever commit their master
names, that's not in the backwards chain that you get when you look at your master
and work backwards.
That is, suppose that you're beaten to the punch—someone else, somewhere else, sends them a new commit:
...--F--G--H--L <-- master
Your Git has made a few new commits (and you're on your master
):
J--K <-- master (HEAD)
/
...--F--G--H <-- origin/master
\
I <-- BranchB
so your Git sends them the J-K
chain and asks them to set their master
to K
, but that would lose their L
—which you don't even have. You'll have to get their L
and figure out how to keep their L
.
As long as they don't have any commits that your git push
request would throw away, though, they'll take your request. So if they don't have L
, and you run git push origin master
, you'll send your J-K
and ask them to set their master to K
and they will.
This is true even if you've re-attached your HEAD
to your BranchB
:
J--K <-- master
/
...--F--G--H <-- origin/master
\
I <-- BranchB (HEAD)
A git push origin master
has your Git call up their Git, offer them your commit K
, and ask them to move their master
there, where your master
is.
A git push origin BranchB
has your Git call up their Git, offer them your commit I
, and ask them to move (or create) their BranchB
there.
Note that you can also git push origin master BranchB
to offer them both tip commits (K
and I
in the drawing above), and make both requests. Or, as RomainValeri noted, you can set up a full refspec argument for each word past the word origin
, e.g., git push BranchB:master
or git push master:BranchB
. These have your Git send theirs your commits as before, and then request that they set their destination branch name (the name after the colon) to match your source branch's tip commit (from the name before the colon). Usually, mixing names like this is not a great idea—it's overly confusing, if nothing else. But there are some special cases where it's a useful shortcut.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With