It's been a minute since I've used git/github, and things seem to have changed a bit.
I set up a new repo on GitHub (with readme), then created a matching directory on my computer that I'll be working out of.
Opened the terminal in that matching directory...
git init
git remote add origin https://github.com/(username)/(reponame).git
git pull origin main
I got a warning about...
"Pulling without specifying how to reconcile divergent branches is discouraged."
but otherwise the pull worked fine and the readme is now in my local directory.
Obviously I have the correct "username" and "reponame" in the commands.
BUT then to make sure all is well, I modified the readme and tried to push it to the repo...
git add -A
git commit -m "First commit, updating readme"
git push origin main
and received the error:
error: src refspec main does not match any
error: failed to push some refs to 'https://github.com/(username)/(reponame).git'
Looking at the repo online, there is only one branch: main
And in the terminal when I check...
git show-ref
I get two outputs...
5...(redacted)...e refs/heads/master
c...(redacted)...0 refs/remotes/origin/main
If I try the command
push origin master
there is no error, but it creates a separate branch on the repo.
So, needless to say, I'm completely lost and have no clue what's going on.
Thanks for any suggestions.
CodePudding user response:
TL;DR: git branch -m main
For your specific case, the fix is easy: just rename your local branch master
to main
and you're good to go from here on using the newfangled names. Reading the rest of this is optional but should help you avoid future snags.
What happened (long and optional)
This sequence:
mkdir (new-directory); cd (new-directory) git init git remote add origin https://github.com/(username)/(reponame).git git pull origin main
isn't wrong functionally but is not what you should generally do; generally you just want to:
git clone https://github.com/(username)/(reponame).git (new-directory)
cd (new directory)
You can omit the new-directory
name if the directory name you want to make is the same as the reponame
: Git will strip off the .git
and use that. The git clone
command does the mkdir
and runs the remaining commands in the new directory:
- [reserved for the mkdir step, which is skipped if you provide an empty directory for
git clone
to use] git init
[run in the new directory created by or provided for step 1]git remote add origin url
[using the URL you provided]- [reserved for extra
git config
steps, if you use-c
options] git fetch origin
(notgit pull
!)git switch branch
, wherebranch
is from your-b
option.
This six-step process is really what git clone
is all about. The git init
step makes the new, empty repository databases in that empty directory, creating a hidden .git
directory to hold them. The git remote add
step saves your URL for future use, and step 5, the git fetch
, has your Git software call up the Git software that answers at the saved URL and copy over all the commits from that repository.
At this point in the process—at the end of step 5—you have all of their commits and no branches at all. The git fetch
step did see all of their branch names, but made remote-tracking names in your databases, instead of making branch names. This is fine for Git, since Git can work without any branch names at all, but not so good for humans, who like branch names. So we move on to step 6, a git checkout
or git switch
that creates one branch name.
The branch name that step 6 creates is going to be spelled the same as some branch name that the cloned repository has. If they—the origin
repository—have multiple branch names, you can choose which one using the -b
option. Most people don't bother with the -b
option at git clone
time, though. In this case, your Git software asks their Git software which name the other repository recommends.
GitHub give you a way to set this, per GitHub repository. GitHub, like Git, call this the default branch. In the old days, this defaulted to the name master
for a newly-created GitHub repository, but now it defaults to the name main
. Because people don't supply -b
options to their git clone
commands, it's sometimes important to know which name some GitHub repository is going to have as its "default branch" setting. That's the name they are going to recommend, and due to a slight flaw in Git, they'll recommend that branch name even if that branch name does not exist.
The empty Git repository dilemma
A Git repository—as distinguished from a checked-out working tree, which holds ordinary files that you can work on / with—is, at its heart, just two databases.
One, usually much bigger, holds Git's commit objects and other supporting objects. These store the commits, which are the history in the repository. Each commit, found by an object ID (OID) or more colloquially, a hash ID, stores a full snapshot of every file, frozen in time as of the form it had at the time you, or whoever, made the commit; and each commit also stores some metadata, or information about that particular commit. These pieces of information combine to link the commits to each other.
A second, separate database holds names: branch names, tag names, and all other forms of names. Each name stores exactly one OID (hash ID), and branch names in particular always store commit hash IDs. These stored hash IDs evolve over time.
The hash ID stored in any given branch name is, by definition, the last commit that is to be considered "on the branch". Making a new commit, then, consists of making a commit whose parent is whatever commit is currently the last one, then changing the stored hash ID in that branch name to hold the new commit's new hash ID.
These definitions mean that if we have no commits at all, there are no hash IDs, and therefore there cannot be any branch names either. A branch name must hold the hash ID of some existing commit. With no commits, there are no such hash IDs.
Nonetheless, to use Git—to have the ability to create files to git add
and then run git commit
—we must be "on" some branch. So when we run git init
and it creates a new, totally empty pair of databases, we have a dilemma: there cannot be any branches, and yet we must be "on" some branch.
Git solves this Gordian knot the way Alexander supposedly did, by just hacking it to pieces. Git decrees that the current branch name—the branch we're "on" when we work in a Git repository—is the name stored in the special name HEAD
, and that HEAD
can contain the name of a branch that does not exist.
So after git init
, we're "on" a non-existent branch! That branch simply springs into being when we make our first commit. The commit we make is a root commit—a commit with no parent commit, which is special in several ways—and the new commit, like any new commit, gets a new, unique hash ID. Now that there is a commit in the objects database, there's a valid hash ID to store in a branch name, so Git now creates the branch name in the names database, storing that hash ID into the name in that database.
GitHub's role in all of this
When you make a new repository on GitHub using their web interface, GitHub give you the opportunity to make a repository that contains the initial, somewhat-special root commit. This eliminates the dilemma: there's now a commit, so there can be a branch name. It also means that you can have some initial files, such as a README and/or a license and/or copyright and/or initial .gitignore
: whatever files you like, you can put in (provided GitHub give you a template with them).
You can, if you choose, make a totally-empty GitHub repository. When you do that, they now set the recommended ("default") branch name to main
. They used to set that to master
, in the old days. It is up to you to make sure that you create this branch!
(I suspect most people take up the "make an initial commit for me" offer, but I have no actual statistics on this. They—GitHub—would have to do the statistical analysis.)
What you did
You:
- had GitHub create a repository with an initial commit, which had some hash ID which they stored under the name
refs/heads/main
: branchmain
; - created your own totally empty repository, with no commits, which was on Git's own default initial branch
master
(full namerefs/heads/master
); and then - ran
git pull origin main
.
The git pull
command is mainly a convenience shorthand for running two other Git commands:
git fetch
, which gets most of the options and arguments you pass togit pull
; and then- a second Git command: one of either
git merge
orgit rebase
, with some hairy special casing for "pulling into an empty repository".1
The git fetch
, in your case, fetched the initial commit you made on GitHub, under their branch name main
, creating origin/main
in your repository as a remote-tracking name. Note that at this point you still have no branch names!
Now your Git ran the second command. In your case this was git merge
. This is where git pull
produced its warning:
"Pulling without specifying how to reconcile divergent branches is discouraged."
This warning is a little weird, because you don't have "divergent branches" at this point. In fact, you don't have any branches at all! But that's the special-case handling kicking in. The non-existent branch you're on, master
, that has no commits, is "divergent" from the existing, single-commit "branch"2 whose first, last, and only commit is now listed under the name origin/main
in your repository.
Nonetheless, git merge
was able to "merge" their main
—your origin/main
—with your master
. This created your branch name master
, identifying the same commit as their branch name main
—your origin/main
.
If we draw the commits in your repository, we just have the one commit, the one you cited as c...(redacted)...0
. Your Git finds this one commit using the name origin/main
—really refs/remotes/origin/main
, which is its full name, but humans normally shorten these, and Git will do that sometimes, depending on which Git command you use.
Because the special-case code now works (and has since Git 1.7 at least), this didn't destroy any work, and instead checked out the files from the one commit that you now have. And now you have your branch, named master
, identifying the same commit as "their" (your GitHub repository's) branch, named main
in the GitHub repository.
From here on, things are pretty straightforward: you modified the README
file, added, and committed. That made a new commit, 5...(redacted)...e
, whose parent is the initial commit, and stored the new commit's name in the name master
(refs/heads/master
), in your names database.
1This special casing was broken in early Git versions (1.5.something, maybe even 1.6.something) and I was badly burnt at least once. That's one of the reasons I started avoiding git pull
—which turned out to be a good thing, as it's by doing separate fetch and second-command sequences that one learns how Git works!
2I put quotes around the word "branch" here as origin/main
is not a branch name, it's a remote-tracking name. Is it a "branch" though? That depends on what we mean when we say "branch": What exactly do we mean by "branch"?
The fix
If you now rename your master
to call it main
instead:
git branch -m main
you end up with:
A <-- origin/main
\
B <-- main (HEAD)
where the letter A
stands in for the initial commit's hash ID, and B
stands in for your new commit 5...(redacted)...e
. Commit B
's parent is A
, and you can therefore git push origin main
to send commit B
to your GitHub repository, where they will add it to their main
, so that your Git updates your origin/main
in your names database:
A--B <-- main (HEAD), origin/main
and all is well.
You can also, at any time now—before or after pushing—set the upstream setting of your branch name—now main
because of your git branch -m
command—to the string origin/main
.3 You can do this with a separate git branch --set-upstream-to
command, or you can even do it together with your git push
as git push -u origin main
.
3The git branch --set-upstream-to
command and similar commands work with strings like this. For historical reasons, though, the configuration in the .git/config
file is broken into two parts: a remote and a branch. The mapping here is complicated, though if you use all the defaults,4 it seems simple: we just string together the remote name, origin
, plus the branch-name-as-seen-on-the-remote, main
, to get origin/main
.
4There's no reason not to use the defaults here. However, if you set up your fetch mappings weirdly, you can have origin/funky
as your remote-tracking name even if they call it main
. You can then call your (local) branch winkerbean
, for instance. This will be stored in your .git/config
as branch.winkerbean.remote = origin
and branch.winkerbean.merge = main
, even though the "upstream" of branch winkerbean
is now origin/funky
and git branch -r
shows you origin/funky
for origin's main
. Don't do this, you'll just drive yourself crazy. But it does actually all work.
Setting up your Git to make git init
use main
If you like the new world of main
instead of master
, or even just aren't stubborn (or whatever) enough to insist on living in the past,5 you can set up your Git so that when you run git init
, it puts you on the nonexistent branch name main
instead of the nonexistent branch name master
. This requires either a new enough Git, or a little trick, though.
In Git versions 2.28 and later, you can run:
git config --global init.defaultBranch main
and you're all set. If your Git predates version 2.28, though, you don't have init.defaultBranch
(and I think you don't have --initial-branch=
either). Here, you can use a simple trick. When git init
creates a new, empty repository, you'll be on master
, but you can change this non-existent branch's name with git checkout --orphan
:6
git checkout --orphan main
Alternatively, make your first commit—this is required in at least some of these older versions of Git—and then run:
git branch -m main
to rename the master
that git commit
created.
5But the past is so comfortable! It's all hazy with nostalgia and ... ♪ Can it be that it was all so simple then? Or has time rewritten every line? ♪ No, wait, past, present, future, it's all terrible. It is best never to have been born. But who among us has such luck? One in a million, perhaps. —Alfred Polgar
6You can do this with git switch --orphan
as well, but if your Git is older than 2.28, maybe it's older than 2.23 and you don't have the newfangled git switch
either. Note that git switch --orphan
empties Git's index, while git checkout --orphan
doesn't, though for this case that won't normally matter.
CodePudding user response:
I am not sure, but in this situation your local master
(it seems to me that your local branch is called master
, not main
) may not track your remote main
. Try to do it: checkout to your local master
and then git branch -u origin/main