Depending on the rest of your work-flow, you might benefit from simply deleting your master
branch name entirely. Once you do that, you can't be on that branch, because you don't have that branch.
That aside, if you created a new branch name from your local master
but forgot to update your local master
first, there are multiple cures. To understand them—and to understand when and why deleting your local master
works—it helps to realize that in Git, branch names are almost irrelevant. They do one important thing for us (and for Git): they help us find commits. But it's not the branch names that matter! It is the commits that matter.
It's this one fact—that the names let us find commits—that makes the names important. So the names become un-important whenever there is some other way to find those commits.
To make sense out of this, take note of git log
output. Here's a sample of a fragment of git log --oneline
output from a clone of the Git repository for Git:
$ git log --oneline
328c109303 (HEAD -> master, origin/master, origin/HEAD) The eighth batch
8b25dee615 Merge branch 'tb/precompose-prefix-too'
006c5f79be Merge branch 'jk/complete-branch-force-delete'
60f8121940 Merge branch 'jv/upload-pack-filter-spec-quotefix'
Note the funky numbers (hexadecimal numbers) on the left. These are the actual commit IDs, shortened somewhat because the full number is very long. (The Git repository for Git is fairly big at this point so the numbers are now shortened to 10 characters instead of 7; smaller repositories use shorter shortenings.)
The numbers appear random, but aren't. They are in fact the result of running a cryptographic hash function over the contents of each commit. In this way, every Git in the universe will agree that the top commit in the list here gets the number 328c10930387d301560f7cbcd3351cc485a13381
. No other commit, anywhere or any time, can use this number.1
What a branch name, like master
as seen in the output above, does is hold the hash ID of the last commit that's on that branch. But another name might also hold that hash ID. In this case, another name does hold it: the name origin/master
holds the same hash ID. The special thing about origin/master
is that it's not a branch name: git switch
won't switch to it, for instance.
Anyway, once you've internalized the idea that it's the numbers that matter, and that names like master
are just convenient ways to find the numbers, we're ready to move on to the next section.
1Technically, some other Git repository could re-use this number, but only if that other Git repository never comes in contact with a clone of the Git repositories for Git. They won't explode in a Star-Trek-Matter-Antimatter-End-Of-The-Universe fashion, but if another Git repository somehow re-uses the hash ID for something else, it's, well, Not Good. See also How does the newly found SHA-1 collision affect Git?
These are SHA-1 numbers. The Git folks are gradually moving to SHA-256. Exactly how we'll get there is still a bit of an open question. Currently (Git 2.30), you can make a SHA-256 repository and use it, or a SHA-1 repository and use it; but you can't ever have a SHA-256-speaking Git talk to a SHA-1-speaking Git. You can't even upgrade an old database to a new one, nor downgrade a new one to an old one. That's not a very acceptable situation.
Branches, in several senses
Each commit is numbered: OK. Git actually finds the commits—and its other internal objects—by these hash-ID numbers. OK. But: so what?
Well, there's several more things to know about commits. Without going deep into them, let's just say here that they're made up of two parts: a snapshot of all files, and some metadata. The snapshot is what git checkout
checks out. The metadata is stuff about the commit itself, such as who made it, when, and why. We see this metadata in git log
output:
$ git log
commit 328c10930387d301560f7cbcd3351cc485a13381
Author: Junio C Hamano <gitster pobox.com>
Date: Fri Feb 12 14:13:40 2021 -0800
The eighth batch
Signed-off-by: Junio C Hamano <gitster pobox.com>
...
Here we have the commit's author, the date-and-time (last Friday—I haven't updated all week!), and Junio's log message (which is rather terse since the real information is in all the merges). What you don't see here is that the commit's metadata also stores something just for Git, which Git calls the parent of this commit. The parent of 328c10930387d301560f7cbcd3351cc485a13381
is 8b25dee6155fd3816f62649da196a4f42cf5584e
, where Git picked up a fix for a Mac-specific bug having to do with Unicode file names.2 (This parent-of-328c109303
commit is itself a merge commit, because it has not just one but two parent commits, but in most Git repositories, most commits are not merges. Below, I'm not drawing any merge commits.)
This trick of storing the previous commit's hash ID means that in Git, a commit "points backwards" to the previous commit, which then points backwards again, one more step. This repeats over and over again, stringing the commits into one long backwards-looking chain. If we draw such a chain, replacing the real commit hash IDs with symbols such as uppercase letters (so as to retain sanity in our feeble human brains), we get a picture that looks like this:
... <-F <-G <-H
where H
is the last commit in this chain. Commit H
points back to earlier commit G
, which points back to F
, and so on. This repeats until we reach the very first commit ever (presumably A
): that one doesn't point backwards because it can't. So that's where git log
stops, if we don't cut it off sooner.
Git, as always, finds each of these commits by their hash IDs. But where will Git find the hash ID of the last commit in the chain, commit H
? We could write it down on paper, or on a whiteboard, perhaps. But that would be silly: we have a computer. What if we save it in a file? Better yet, what if we have Git save it, automatically, in some file or database somewhere?
That's what a branch name is. A branch name, such as master
, just holds one hash ID. This hash ID is automatically the last commit in the chain. So if we have this:
...--F--G--H <-- master
then what we mean is that the name master
is holding the hash ID of commit H
, which is the last in that chain. That's true even if there are commits after H
, like this:
...--F--G--H <-- master
\
I--J <-- feature
Here, the name feature
holds the hash ID of commit J
, so that feature
points to J
. J
in turn points backwards to I
, which points backwards to H
, which points backwards to G
, and so on.
Commit H
is simultaneously the last in the chain that ends at master
= H
and somewhere in the middle of the chain that ends at feature
= J
. Commits up through H
are on both branches. Commits I
and J
are only on feature
.
The word branch in Git is ambiguous: it means both a branch name, and a set of commits ended by one I care about at the moment. The chain of commits leading up to H
is "a branch", and is "branch master", but the branch name master
really just picks out commit H
, from which Git can work backwards. The chain of commits leading up to J
is also "a branch", while branch name feature
picks out commit J
.
The special feature of branch names like master
and feature
, though, is that they move. Let's see how this works.
2This involves the way macOS takes two different files, e.g., named schön
vs schön
, and collapses them into one single file, because it's impossible to see the difference between the two file names, or in fact to post it here. One is S C H O COMBINING-DIAERESIS N using Unicode code-point U+0308, and the other is S C H LATIN-SMALL-LETTER-O-WITH-DIAERESIS N using Unicode code point U+00F6. On a Linux box, you can make two different files using these two different byte sequences. Upon transferring this commit to a Mac box, an attempt to extract this commit will fail because the local file system will just create one file. Command line arguments must be turned into the correct form; the current Git releases get this slightly wrong.
How branch names move as branches grow
Let's start with this again, though for no obvious reason I'll draw feature
on top instead of below, this time. It doesn't matter how you draw these, as long as you remember that commits point backwards. Git draws these so that newer commits are on top and connections go down the page. People sometimes draw newer commits towards the bottom. The internal arrows, from commit to commit, always go backwards: Git has a hard time going forwards, and an easy time going backwards, so Git generally works backwards. That's why git log
goes backwards, for instance. Not only do we want to see the newest commit first, so does Git.
I--J <-- feature
/
...--F--G--H <-- master
We will pick out one branch and "check it out", with git checkout
, or, if our Git is 2.23 or later, git switch
. Both commands do the same thing here. Let's say we run git switch master
(or git checkout master
). This will make master
our current branch name, and get out, into our work area, all the files from commit H
. Let's draw this by attaching the special name HEAD
to the name master
:
I--J <-- feature
/
...--F--G--H <-- master (HEAD)
Now let's make a new branch name, feature2
, with git checkout -b feature2
or git switch -c feature2
or even git branch feature2; git checkout feature2
or whatever. They all do the same thing: make the new branch name feature2
point to commit H
, and attach HEAD
there, like this:
I--J <-- feature
/
...--F--G--H <-- feature2 (HEAD), master
Now let's make a new commit, in the usual way. It gets an all-new hash ID, but we'll just call it K
for short. K
's parent will be H
, because we used H
to make K
. So the result looks like this:
I--J <-- feature
/
...--F--G--H <-- master
\
K <-- feature2 (HEAD)
Note how Git has moved the name feature2
. It does this by writing K
's hash ID into the name feature2
, just after making commit K
.3
If we make another new commit L
we get:
I--J <-- feature
/
...--F--G--H <-- master
\
K--L <-- feature2 (HEAD)
Note how the name feature2
, to which our HEAD
is attached, gets updated each time. The other names don't change, and no existing commit changes—none can, in any way, due to the hash ID scheme for numbering them—but as we add new commits, one by one, the current branch name moves.
If we make a bad commit and want to get rid of it, we can use git reset
. This command is quite complicated, but in the mode we'd use to get rid of the bad commit, what it does is easy to draw. It sort of shoves the commit aside (without changing it) and makes our current name point to some other commit, like this:
I--J <-- feature
/
...--F--G--H <-- master
\
K <-- feature2 (HEAD)
\
L ???
Commit L
still exists in the Git database. But now, there is no name by which to find it.4 The commit appears to be gone. If you saved its hash ID somewhere, though, you could use another git reset
to get it back:
I--J <-- feature
/
...--F--G--H <-- master
\
K
\
L <-- feature2 (HEAD)
The reset
command can move the current name anywhere (to any existing commit) so it is quite powerful.
(Once we put L
back as the last commit in the chain, there's no reason to draw the commit on a row by itself any more. I kept the extra row it to make it clear that we're just moving the name around.)
3The hash ID for K
incorporates all the metadata, including the exact time Git makes K
, the hash ID of parent commit H
, your log message, and so on. This means nobody—not you, not Git, nothing—knows what the hash ID will be until the commit is actually made.
4To make sure you can find it again if you decide you want it back, Git keeps multiple hidden names. These eventually expire, typically after 30 days or more; once they expire, and the commit is truly un-find-able, git gc
will "garbage collect" it, throwing it away for real.
How all this relates to your original problem
Let's go back to your original problem now:
git checkout master
git pull
git checkout -b myBranchName
Except I always forget the git pull
step...
I'm going to go very fast here: git pull
means run git fetch
, then run a second Git command to do something with any commits I just fetched. The usual second thing takes those new commits and adds them on to master
. So the end result is that if you do what you had intended to do, you would start with:
...--G--H <-- master (HEAD), origin/master
The git pull
would run git fetch
, which adds a new commit or two (or many) and updates the non-branch name, origin/master
, to find the last one:
...--G--H <-- master (HEAD)
\
I--J <-- origin/master
The second command winds up moving the name master
forward, almost like git reset
(but much more safely: using git reset
tells Git wipe out unsaved work while whatever second command you've configured carefully preserves unsaved work, or halts if it would destroy it).5 The result is:
...--G--H--I--J <-- master (HEAD), origin/master
Now when you create a new name myBranchName
or feature2
or whatever,6 this new name points to commit J
:
...--G--H--I--J <-- master, myBranchName (HEAD), origin/master
If you've forgotten the pull
step, the newly created branch name points to commit H
instead (and you don't even have commits I-J
yet):
...--G--H <-- master, myBranchName (HEAD), origin/master
Later, you'll run git fetch
—perhaps as part of a git pull
—and pick up the new commits. By then, you might have made your own new commit K
:
I--J <-- origin/master
/
...--G--H <-- master
\
K <-- myBranchName (HEAD)
If you now git checkout master
and get Git to "slide the branch name forward" (as git pull
will do), you have:
I--J <-- master (HEAD), origin/master
/
...--G--H
\
K <-- myBranchName
Your existing commit K
points back to old commit H
. This is not an error in and of itself. There's nothing wrong with having your commit(s), and your branch(es), descend from older commits. You can just keep working, then eventually use git merge
to combine work. Let's say you switch back to your branch and make one new commit L
:
I--J <-- master, origin/master
/
...--G--H
\
K--L <-- myBranchName (HEAD)
You can now switch back to master
and git merge
your work, to combine your changes in K-L
(as seen from commit H
) with their changes in I-J
(as seen from commit H
) and get a new merge commit M
:
I--J <-- origin/master
/ \
...--G--H M <-- master (HEAD)
\ /
K--L <-- myBranchName
If you look at various Git repositories, including the Git repository for Git, you'll see this kind of work-flow sometimes. There's nothing wrong with it. But some people dislike it, for whatever reason. If you are one of those people, or work with them, you can simply take your two commits—K
and L
—and copy them to new and improved commits.
Without going into all the mechanism, git rebase
will do this for you. You don't even have to update your master
. Let's say you're in this position:
I--J <-- origin/master
/
...--G--H <-- master
\
K--L <-- myBranchName (HEAD)
You can now run:
git checkout myBranchName
git rebase origin/master
Git will copy commit K
to a new-and-improved(?) commit, which we'll call K'
to imply the copying action, that comes after J
. Then Git will copy commit L
to a new-and-improved(?) commit L'
that comes after K'
. Having made these two copies, Git will peel the name myBranchName
off the old end of the old chain, and paste it onto the new end of the new chain, like this:
K'-L' <-- myBranchName (HEAD)
/
I--J <-- origin/master
/
...--G--H <-- master
\
K--L ???
Commits K
and L
seem to have vanished, but commits K'
and L'
look so much like original commits K
and L
—except for the big ugly hash numbers, but do you remember any of the ones from above? I don't—that it will seem as though Git magically changed the commits in place somehow.
You can simply delete the name master
entirely now too, because origin/master
is the one you care about: it finds the right commit after each git fetch
. If we do that, and drop the un-find-able commits and straighten out all the kinks we can, we get:
...--G--H--I--J <-- origin/master
\
K'-L' <-- myBranchName (HEAD)
which looks as though you didn't forget the git pull
earlier.
Note that if you haven't made any commits at all yet, i.e., if you have:
...--G--H <-- master, myBranchName (HEAD), origin/master
when you remember to run the git fetch
you forgot about, you can now delete master
entirely (optional) and run git fetch
(mandatory) to get:
...--G--H <-- myBranchName (HEAD)
\
I--J <-- origin/master
If you now run git rebase origin/master
, your Git will quietly copy no commits and then move the name myBranchName
:
...--G--H--I--J <-- myBranchName (HEAD), origin/master
so git rebase
works for both these situations. That's pretty convenient.
5In truly ancient Git versions, somewhere around 1.5 or so, git pull
would sometimes accidentally destroy unsaved work. I got burned this way more than once! I avoid git pull
, in part because of this, though also because I like to insert commands between the two that git pull
will run.
6If you use Windows or macOS, I recommend avoiding mixedCaseNames. Git thinks that case in branch names matters. Your OS thinks that case in file names doesn't matter. Git sometimes stores branch names in files, and sometimes stores them as file names, and this means that the case sometimes matters and sometimes doesn't. The experience is confusing and miserable. Avoid mixing case, and you avoid the misery.