I'm pretty sure you'd like a simple formula, do X then Y and it will all work. Unfortunately we can't give you this without more information from you. At the end, I'll suggest a few possibilities, but you must inspect both your commits, and the commits found in the repository over on GitHub.
What's going wrong
The ultimate problem here is that Git is not about branches. Git is all about commits. Branch names do matter, because branch names help us—and Git—find the commits. But it's really the commits that matter. The branch names only matter for finding the commits; after that, the names stop being important—which is why you're free to change them at any time.
The commits themselves are numbered. Those commit numbers, or hash IDs, are the way Git really finds the commits. If you have the number, that's all you really need: give that to Git and Git will find the commit, if you have it. The problem with the numbers is that they're incomprehensible and impossible for humans to deal with. For instance, 2283e0e9af55689215afa39c03beb2315ce18e83
is the hash ID for a commit in the Git repository for Git. Nobody wants to remember this! So we use a name like master
or main
to remember it for us.
When you use git push
, you are telling your Git to call up some other Git, in this case over at GitHub (origin
= https://github.com/randiltennakoon/my-bmi.git
, per your comment). Your Git and their Git have a conversation. Your Git lists out your commit hash IDs, and theirs lists out theirs (with a lot of fancy algorithmic work to minimize this communication) so that your two Gits can agree which commits you have, that they don't, that your Git wants to send to them.
Then, your Git sends these commits to them. They put them in a temporary quarantine area while they check out the rest of what you want to do. Your Git now asks their Git to set some of their branch names, to remember these new commits.
Your Git can ask their Git to set their master
. If you tell your Git to do that, and they don't have a master
, they'll generally say "OK!" and do it. After that, they will have your commits, under their name master
. But to do that you'd have to run a Git command you haven't run (yet).
Your Git can ask their Git to set their main
. If you tell your Git to do that, and they do have a main
, they'll take a look at the commits you just sent. Do those commits add on to their main
? Or do those new commits just wipe out their existing commits, as found from their name main
?
This is where your later push, in that comment, is failing:
$ git push -u origin main
To https://github.com/randiltennakoon/my-bmi.git
! [rejected] main -> main (non-fast-forward)
error: failed to push some refs to 'https://github.com/randiltennakoon/my-bmi.git'
hint: Updates were rejected because the tip of your current branch is behind
hint: its remote counterpart. Integrate the remote changes (e.g.
hint: 'git pull ...') before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.
Here, you changed your name master
to your name main
, to match their name main
. That's fine on its own—you can use any names you like, and most people find it helpful to use the same names in their Git as someone else (e.g., GitHub) is using. Then you asked your Git to call up their Git, send them your main
-branch commits, and ask them to set their main
to remember your last commit.
Here, we need to take a side trip. I'll try to keep it brief but there is a lot to know here!
What's in a commit
We've already mentioned that Git is all about commits, and that they are numbered. If you run git log
, your Git will show you your commits—all of them, in your case, because you have only one branch—along with their hash IDs (commit numbers), in reverse order.
These numbers will look random. They're actually not random at all: they are computed with a cryptographic hash from the entire contents of the commit. This is how Git makes sure that every commit gets a unique number. But it also means that no part of any existing commit can ever be changed. Whatever numbers your existing commits have, those are their numbers. Every Git everywhere—yours, the one over on GitHub, mine, Fred's, every Git—agrees that those are the right numbers for those commits. No other commits get those numbers, ever, anywhere. This is why the numbers are so big and ugly.
Now, every commit—regardless of its number—consists of two parts:
One part of a commit is a full snapshot of every file that Git knew about, as of the time you, or whoever, made the commit. That is, after your git add .
(or whatever) and git commit
, Git made a snapshot of all of the files it knew about at that time: that's any you added, plus any it already knew about. Git actually uses the versions of those files that are in Git's index a.k.a. staging area at this time, rather than the versions you can see and work with, but we'll skip over this detail.
The other part of a commit is what Git calls its metadata, or information about the commit itself. Here, Git stores your name, as the author of your commit, and your email address. Git adds a date-and-time-stamp. Git includes your log message where you explain why you made this commit (not what's in it, which we can see from the snapshot part of the commit, but rather why you did whatever you did, which we can't see from the snapshot). And, crucially for Git itself, Git adds the hash ID of the previous commit.
These hash IDs wind up forming a backwards-looking chain. Let's draw one, using single uppercase letters to stand in for the mind-bending hash IDs:
A <-B <-C
This is a simple chain of three commits, as you'd find in a nearly-new Git repository. Commit A
is the very first commit. It doesn't have any earlier commit—it can't; it's the first commit—so it doesn't point backwards at all. Commit B
, however, is the second commit, so it points backwards to A
, the first commit. Commit C
, currently the last commit, points backwards to B
.
In reality, commit C
literally contains the actual commit number—hash ID—of earlier commit B
, as part of C
's metadata. Similarly, B
contains the hash ID of earlier commit A
.
In order to find all three commits, Git just needs the hash ID of the last of these three commits. We have Git stick this hash ID into a name, like master
:
A--B--C <-- master
If we change the name from master
to main
, none of the commits change:
A--B--C <-- main
So this is trivial: we can change our branch names around all we like. A git checkout
of whichever branch name we pick will actually get commit C
—whatever its real hash ID is—because the one name we have, has the hash ID of commit C
in it.
If we make a new commit, our new commit—which we'll call D
, although in reality it gets some big ugly hash ID—will contain, as its backwards link, the hash ID of commit C
:
A--B--C--D <-- main
This is how a branch grows. The backwards connection from D
to C
says that the parent of commit D
is commit C
. The parent of C
is B
, and so on, except that once we hit A
, there's no parent at all, and it all stops.
GitHub has a Git repository too
Meanwhile, over on GitHub, as found by the URL https://github.com/randiltennakoon/my-bmi.git
, there's another Git repository. This other Git repository has some commits. Those commits have some hash IDs.
If those commits were the same commits that you have—if they had the same A-B-C
for instance—you'd be in good shape. You'd have your Git call up their Git. Your Git would list out hash ID D
. They'd say: Hey, I don't have D
, why don't you give me that one? Your Git would add D
to the list of commits to send over. Then your Git would say the parent of D
is C
. They would check and see that they do have C
. This means they also have A
and B
and so the only commit your Git has to send is D
.
Then, your Git would send just D
, and ask them to set their main
to remember D
instead of C
. That would be fine, because D
itself remembers C
. You're just adding on to their commits.
Alas, this is not what's happening. Instead, they have, e.g.:
G--H <-- main
in their repository. So if you send them your entire series of commits, they'll end up with:
A--B--C--D
G--H <-- main
Then your Git ask them to set their main
to point to D
. This will lose their commits. The name main
can only point to one last commit, such as H
or D
. Switching to D
does not add on to their commits. So they refuse.
How you fix it
What you need to do is:
- Obtain their commits.
- Decide how to combine your commits and their commits.
Step 2 is a big decision. It's the one we can't make for you.
Step 1 is easy: just run git fetch
. When it's done, you will have an origin/main
. This name is not a branch name, but it's almost as good in most ways, and better in one specific way: It's how your Git remembers their Git's branch name. Each git fetch
you run will get any new commits from them, then update your memory of their Git's main
branch name (and any other branches).1
In any case, this origin/main
lets you see their commits—now in your repository too, found by the same hash IDs that their Git is using, because every Git agrees that those commits get those IDs. You now have their commits and your commits. These don't hook up with each other, because you made your commits completely independent of theirs, by making yours without starting with theirs.
Run:
git log origin/main
to see all of their commits. What commits do they have? What's in those commits? Are they important? Should you keep those commits? If so, how do you want to combine your commits with theirs?
1The exact details here depend on what kind of git fetch
you run. The git pull
command, for instance, runs a limited git fetch
that doesn't pick up everything.
Combining method #1: git merge
The simplest method of combining two strings of commits is to use git merge
. This is still true in your case, but there's a hitch.
Normally, with git merge
, we start with something like this:
I--J <-- ours
/
...--G--H
\
K--L <-- theirs
That is, we have a branch—we're calling it ours
in this drawing—that ends at commit J
, and they have a branch that ends at commit L
. We have Git combine these two branches. Git does so by finding commit H
, which Git calls the merge base. You can see from this drawing that commit H
is the best shared commit. We start at the two branch ends—commits J
and L
—and begin working backwards, the way Git always does. From J
, we go back to I
, then to H
, and G
, and so on. From L
, we go back to K
, and then H
and G
and so on.
These two traversals meet up at commit H
. They keep meeting up from there on back, so commit H
is automatically the best (i.e., last) commit at which the two branches met. The merge operation then figures out what we've changed since commit H
, and what they changed, and combines these changes.
I don't know precisely what's in your Git and their Git, but I do know, from the way you went about making these two Git repositories, that what you have and what they have never meet up. You have something like:
A--B--C--D <-- main
G--H <-- origin/main
If we start at each end and work backwards, we never meet up.
Since Git version 2.9, git merge
won't merge these by default. It just complains that the histories are unrelated, and stops. You can tell Git to go ahead and merge anyway, using --allow-unrelated-histories
.
Whether this works at all depends on what's in the snapshots in D
and H
(or whatever last commits you have). If you get merge conflicts, you must resolve the merge conflicts yourself. Once that's all done—all conflicts are resolved, or there were no conflicts—Git can make a new merge commit, which I'll call M
for Merge
:
A--B--C--D
\
M <-- main
/
G--------H <-- origin/main
What's special about commit M
is that instead of just one parent, such as D
or H
, it has both of these as its two parents. So it combines the two separate chains of commits.
Assuming you want to make M
, and did make M
, you can now run:
git push -u origin main
You'll send, to the other Git, commits A-B-C-D-M
. Commit M
links back to commit H
, which is where their main
is right now. So if they obey your Git's polite request to set their main
to point to M
, they'll keep their G-H
commits too. They'll decree that this is fine.
What's in commit M
as a snapshot is the result of combining the two commits. If you have to do the combining by hand, because of merge conflicts, you pick what goes into commit M
, as a snapshot. If Git is able to do the combining on its own, Git will make commit M
on its own, and it will have everything you had in D
, and everything they had in H
.
Combining method #2: git rebase
The rebase command is quite complicated and I won't explain it properly here. See other StackOverflow answers for more. Instead, I'll just note that it works by copying commits, as if by running git cherry-pick
.
Let's assume that you do indeed have this:
A--B--C--D <-- main
G--H <-- origin/main
If you run git rebase origin/main
, your Git will save the four hash IDs for commits A-B-C-D
, in a temporary file. Your Git will then use git cherry-pick
or equivalent to copy each commit, in the A-B-C-D
order (forwards! Git has to list them out backwards, then reverse the backwards list to do this), to come after commit H
. If we call the copy of A
, A'
, and the copy of B
B'
, and so on, we can draw the result like this:
A--B--C--D <-- main
G--H <-- origin/main
\
A'-B'-C'-D' <-- ???
The question marks show that there's a problem here: how will your Git find the copy D'
? The answer is: it yanks the name main
off the old series of commits, and pastes it onto the end of the new series. The result is:
A--B--C--D ???
G--H <-- origin/main
\
A'-B'-C'-D' <-- main
It's now very difficult to find the old commits. If you saved their hash IDs somewhere (on paper, on a whiteboard, in a file, whatever), you can use those. There are other tricks Git has to help you find these commits for a while—for at least another 30 days, by default—but for most purposes commits A-B-C-D
seem to be gone. As a result, when you use git log
to look at your commits, you'll now have only six commits, not ten: git log
will show you commit D'
, then C'
, and so on. When it gets to A'
, well, commit A'
does have a parent: it links back to commit H
. So after A'
, git log
shows H
, and then G
, and then stops because the commits have run out.
You can now run:
git push -u origin main
Your Git will send commits A'
, B'
, C'
, and D'
, and ask their Git, politely, if they'll set their main
to point to D'
. Since D'
extends their branch, they'll say OK.
Combining method #3: run roughshod over them
Suppose that, upon looking at their commits, you decide that those commits are utterly worthless. There's no harm in just throwing them away entirely.
In this case, you can use what Git calls a force push, git push -f
or git push --force-with-lease
. The --force-with-lease
version adds a bit of extra safety (and is generally the way to go) but I won't go into any detail here about what this safety is.
What these options do is tell the other Git, in this case, over at GitHub: I command you, replace your main
with my commit hash ID! That is, unlike a regular push, it's not a polite request. It's an outright command. They don't have to obey, but if you own that repository, they will.
Obeying your push means that they take your A-B-C-D
chain (or whatever it is) and make their main
point to the last commit in this chain, just like your existing main
. So if you have:
A--B--C--D <-- main
G--H <-- origin/main
and you force-push to GitHub (git push --force-with-lease -u origin main
), they'll set their main
to point to D
. You end up with:
A--B--C--D <-- main, origin/main
G--H ???
Commits G
and H
appear to vanish. They'll actually vanish for real from the GitHub repository some time in the future—it's hard to say when as that's up to GitHub, but the 30 day rule that works in your Git does not apply. They might go away immediately, or in a day or so.
Because you did get them into your own (local) Git repository, your Git will hang on to G-H
for the at-least-30-days, should you decide that they were valuable after all. Finding them can be tricky though! We (humans) use names, not hash IDs, for a reason.