Reputation: 61
I have a single branch in my repo: develop. Code has already been committed and pushed in there.
There's no master branch in the repo.
I'd like to create a master branch so I can raise a Pull Request into it.
I've already tried two approaches: 1. create the master branch from develop:
git checkout develop
git checkout -b master develop
The problem here is there's nothing to compare: master and develop are already the same.
git checkout --orphan master
git reset --hard
The problem here is the branches have different history and I get: "there isn’t anything to compare. (branches) are entirely different commit histories"
git version 2.17.2 (Apple Git-113)
How do I create an empty master so I can raise a Pull Request from the already existing develop?
Upvotes: 6
Views: 4393
Reputation: 361
To solve this issue, the detailed explanations from -torek, was really helpful. Let me summarize the steps you can follow to resolve it:
-> git checkout -b master
-> git reset --hard <commit hash copied in step 1>
-> git push origin master
These commands will create a new branch called "master," reset it to the commit specified by the hash obtained in step 1, and push the changes to the remote repository.
Once these steps are completed, you can proceed to the UI and create a pull request (PR) from the "develop" branch to the "master" branch.
Upvotes: 0
Reputation: 489173
Just create a branch name. That's all there is to it, really. Pick some existing commit and make the branch name master
identify that commit. That's now your master
branch.
There is a problem already, though, because the word branch is ambiguous, in Git. (See What exactly do we mean by "branch"?) When we say "branch", sometimes we mean a name, like master
or develop
, and sometimes we mean something else.
What Git really stores is a series of commits. Each commit has a unique number assigned to it: a hash ID, which looks like 8dca754b1e874719a732bc9ab7b0e14b21b1bc10
for instance. You see these in git log
output.
Each commit holds a complete snapshot of your project, as a set of files (not folders, just files: the folders, if any, will be created as needed to hold the files, at the time you run git checkout
). And, each commit has a bit of metadata: information about the commit, such as who made it, when, and why (its log message), and—crucially—the raw hash ID of the previous commit.
It's these raw hash IDs that make up backwards-looking chains of commits. If we use a single uppercase letter to fake out the hash IDs—this is much more useful for humans but obviously won't work for thousands of commits—we can draw a simple three-commit repository like this:
A <-B <-C
Here commit C
is the latest commit. It records that commit B
is the earlier commit, so we say that C
points to B
. Another way to say this is that B
is C
's parent. Meanwhile commit B
says that commit A
comes before B
. Commit A
is the very first commit anyone ever made, and therefore has no parent. This lets the action stop: history starts—or ends, if you're working backwards like Git does—here.
Sometimes, when we say branch, we mean the whole series of commits. This chain of commits, A-B-C
, starting from the end and working backwards, is a branch. You direct Git to find the last commit, C
, which then points back to the earlier commit B
, which points back again. No matter how many commits there are, we can always have Git start at the end, and work its way all the way back to the beginning, as long as we know which commit is the last commit. But how do we find the last commit?
In this example it's easy: there are only three commits, and we made them use sequential letter names, so obviously C
is the last one. But in a real repository, there may be thousands of commits:
$ git rev-list --all --count
58314
and their hash IDs look totally random. How do we know which commit is the last one? Furthermore, what if we have a series of commits like:
I--J
/
...--F--G--H
\
K--L
where there are two "last" commits?
A branch name, like master
or develop
, just holds the actual hash ID of the last commit that we want to call "part of the branch":
I--J <-- master
/
...--F--G--H
\
K--L <-- develop
By making the name master
point to commit J
, we declare that J
is the last commit of the branch master
. By making the name develop
point to commit L
, we declare that L
is the last commit of the branch develop
.
Note that commits H
, and G
, and F
, and whatever else comes earlier, are on both branches. This is a peculiar feature of Git: usually, most commits are on every branch. It's only the last few—or few hundred, or whatever—that are only on one or two or ten of the branches.
For a branch name to exist, it must point to some existing commit. You can make commits without using branch names—this is slightly tricky: it uses what Git calls detached HEAD mode—but you can't have a branch name unless it points to some actual commit. By pointing to that commit, that branch name declares that that commit is the last commit in that branch.
This is true even if the name points to a commit in the middle of someone else's chain. Suppose that you don't have a master, but do have:
...--F--G--H--K--L <-- develop
You can now tell Git: create the name master
, pointing to commit H
by finding the actual hash of H and running:
git branch master <hash-of-H>
or:
git checkout -b master <hash-of-H>
Now you have:
...--F--G--H <-- master
\
K--L <-- develop
Note that the commits did not change at all. You just added the label master
—a branch name—to remember the hash ID of commit H
.
Now that you have the name master
identifying commit H
, you can:
git checkout master
and then do some work and make a new commit. When you make a new commit, Git packages up a new snapshot of your project and writes that out. Git adds your name as the author and committer of this new commit, sets up its date-and-time-stamps, and uses your log message as the reason this commit now exists. Git adds the raw hash ID of existing commit H
as the new commit's parent. Git then saves the new commit into the database of "all existing commits", which assigns the new commit its new, unique hash ID. We'll just call this new hash ID I
:
I <-- ???
/
...--F--G--H <-- ???
\
K--L <-- develop
Now there's the tricky part: having created commit I
, Git moves the current branch name so that it points to the new commit. Since the current branch name is master
, Git changes master
so that it points to new commit I
:
I <-- master (HEAD)
/
...--F--G--H
\
K--L <-- develop
You should ask yourself two questions now:
master
and not develop
?H
?The answer to Q1 here is in the diagram: I have attached the special name HEAD
to the name master
. When you use git checkout
to select a branch, Git attaches this name, HEAD
, to that branch. That not only tells Git which commit you have checked out right now, but also which name needs to be updated when you make a new commit.
The answer to Q2 is not as obvious until you find that there's a general principle of Git, which is: once a commit is made, that commit is frozen for all time. Nothing inside any existing commit can ever be changed—not by you, not by Git. The reason for this is that the actual hash ID of the commit is a checksum of the data inside the commit. Change any of the data, even a single bit, and you change the checksum—you get a new, different commit, not the original commit after all. the original commit remains undisturbed.
The files inside a commit are all frozen, read-only, and saved for all time. They're also compressed, and in a special Git-only format. This helps keep Git repositories from becoming instantly huge: if every commit saves every file every time—which it does—how will the repository's size not get totally out of hand? One of the tricks Git uses is that if you're saving the same version of a file, it just re-uses the existing frozen copy. It can do that because the frozen copy can't change.
(I like to call these frozen, Git-only copies of files "freeze-dried". The freeze-dried files don't really have names, inside the Git repository: they have hash ID numbers instead. Note that they have to be thawed out and "rehydrated" into ordinary files for you to use them—the frozen copy is useless for getting any new work done. That's why git checkout
makes a work-tree, where you can do your work. The freeze-dried files in the commit become normal files in the work-tree, with Git first creating any folders needed to hold them, using the freeze-dried names stored with the commit.)
Usually, we use git clone
to make our repositories, and then send our work back to whichever repository we started with later, with git push
. The git clone
process copies the commits from the original repository. But it doesn't exactly copy the branch names. Instead, it takes their branch names—their repository's master
and develop
and so on—and renames them, calling them origin/master
and origin/develop
and so on.
Having just made a fresh new clone of their Git, our Git has no branch names at all! It has all of their branch names, renamed to our origin/*
remote-tracking names.1 But our Git would like us to have a branch—typically master
, but if they didn't even have a master
, our Git will pick some other name now, such as develop
. As the last step of git clone
, our Git must:
origin/name
name, andgit checkout
that commit by that branch name.The name picked in the first step is:
-b
argument if you said git clone -b name
, ormaster
.That last master
is a special case, used as a sort of last resort.2
So, if you clone a repository that only has a develop
, you'll get your own origin/develop
name—a remote-tracking name, not really a branch at all—pointing to the last commit in their develop
. But then as the last step of git clone
, your Git will create your own develop
, pointing to this same commit, and then git checkout develop
so that you're on your one single branch name, develop
, and have this one commit checked out.
If you now create the new name master
in your repository, you get this:
...--o--o <-- develop, master, origin/develop
(with HEAD
attached to either develop
or master
, depending on how you created the name master
: did you use git branch
, or git checkout -b
?).
You can now run git push origin master
to tell their Git: create a branch name master
pointing to this same commit as your develop
. What git push
does is send their Git any commits that you have, that they don't—any commits you've made that they will need for the create or update some branch name part—and then asks them, politely, to create or update some of their branch names to match some of your branch names:
git push origin branch1 branch2 branch3
has your Git call up the Git at origin
—at the URL stored under your name origin
—and send them any commits they need, then ask them to set their branches named branch1
, branch2
, and branch3
to point to the same commits that your names branch1
, branch2
, and branch3
point-to. Since they can only do this if they have those commits, your Git will first send them the commits, if they don't have them. Your Git will not only send them these tip commits (for the three branches), but also any history—any earlier commits—needed to connect those tip commits to the rest of the commits in their repository.
1Git documentation calls these remote-tracking branch names. But they're not technically branch names. In particular, if you git checkout origin/master
for instance, you wind up with a detached HEAD. So I prefer to just call them remote-tracking names, dropping the word branch entirely.
2If you clone a totally empty repository, with no commits, their Git has no branch names, because it has no commits and a branch name requires a commit. So they don't recommend anything, in this case. Your Git must then use master
as your branch name. Of course, you don't have any commits either. So you now wind up with the same peculiar state you have when you create a new, empty repository with git init
: you're on branch master
, but branch master
doesn't exist! You're on a non-existent branch. Other parts of Git call this an orphan branch. It's a weird state in which your next commit creates the branch you're on, so that you can be on it properly, as it were. Until then, your HEAD
just records the name of the branch to create.
Here's what you have learned (and need to know):
git log
command will show you the hash IDs. Every Git in the universe must agree on the hash IDs.3git clone
, git fetch
, and git push
. The names matter too, because they're how each Git will find its commits, but the names are local to each Git: they're not necessarily universal, like the hash IDs are. In particular, branch names are expected to move, over time.master
, as seen in the other Git? Or did they mean the remote-tracking name origin/master
as seen in my Git? These are different names, and may point to different commits! A branch name in some other Git may change between now and the next time you look. Who has access to that other Git repository, and what are they doing to it right now?3There's a future complication coming to Git—no one knows exactly when, yet—in which hash IDs will be renumbered, going from SHA-1 to SHA-256. Exactly how that will be handled is not yet defined.
Upvotes: 8