I have used the steps outlined here to successfully update my master branch of the fork. So the master branch of the fork is now even with the original source's master.
I have several different branches and I wanted to make one of them (called new_branch) also even with the original source's master. So I modified the steps outlined at the link the following way.
git fetch upstream
(step 4 at the link)
git checkout new_branch
(step 5)
git merge upstream/new_branch
(step 6)
Step 6 produces merge: upstream/new_branch - not something we can merge
in the terminal
I still went ahead with the next step.
git push origin new_branch
(step 7)
After step 7 all I get is Everything up-to-date
.
However, github branch `new_branch' still says it is 41 commits behind the source of the fork.
Is it not possible to bring your non-master branch up to date with the source of the fork?
*I ran git fetch
and git branch -r
to see what I have. (I did run git fetch
before though)
TL;DR
You need to rebase your commits, typically using
upstream/master
. You may then need to usegit push --force-with-lease
or similar to update yourorigin
'snew_branch
, which is yourorigin/new_branch
. See the long answer for details.Long
This is all fairly complicated, so here is a capsule summary:
github.com
. For sanity in terms of referring to each of the three repositories, let's use their names as seen from your repository, on your computer:laptop
when we need a name;origin
: a GitHub repository to which you can write directly; andupstream
: a GitHub repository to which you can't write, but where you can generate a "pull request" using GitHub's ability to do that.laptop
andorigin
, in whatever direction we like, we can usegit fetch
andgit push
.laptop
andupstream
, we're only allowed to usegit fetch
.Now, keeping in mind that we find commits by some sort of name, let's note that there are lots of kinds of names. In your own repository on
laptop
, you have full and total control over all these names, so you can do anything you want. For your own sanity, there are certain patterns you'll want to follow.In the repository named
origin
, you have rather less control over names, but we'll see how you can usegit push
to affect its branch names. In the repository namedupstream
you have essentially no control over names.Note that I'm completely ignoring tags here. They complicate the picture a bit, and this is already quite long.
Fetch, push, and remote-tracking names
Let's talk now about
git fetch
andgit push
. I'm going to assume you know how commits work and how Git finds commits, by starting from the last commit in a branch and working backwards, and that when wegit checkout
some branch by name (or do the same thing withgit switch
) and then make new commits, Git updates the branch name to hold the new commit's hash ID. The new commit points back to the commit that was the last one in the branch, so the branch has automatically been extended by this operation. This all uses the word branch, which is quite ambiguous when using Git, in a rather cavalier and presumptive manner, assuming that the reader can figure out which of any number of different and perhaps even contradictory definitions of branch might apply.What I won't assume here is that you know about remote-tracking names, since a lot of your original question hinges on these. So let's talk about these. Let's also define remote, since it's involved.
A remote is just a short name for another Git repository. We already see that we have two remotes in
laptop
: the nameorigin
refers to your fork on GitHub, and the nameupstream
refers to another repository on GitHub, from which you created your fork. A remote always stores a URL, but it also acts as a prefix for these remote-tracking names.A remote-tracking name—which Git calls a remote-tracking branch name, but look at how badly the word branch is already abused; let's give that poor word a rest here—is a name that your (
laptop
) Git creates and updates based on some branch name as seen in some other Git repository.Remember, again, that a branch name—as seen in any Git repository, whether it's
laptop
,origin
, orupstream
—holds the hash ID of a tip commit. So no matter which of the three repositories we look at, we have some string of commits that ends with the most recent:where
H
is the hash ID of the most recent commit on that branch. If this is all in your local laptop repository, you can see these commits withgit log
or some other commit viewer.Your first goal is to get all the commits, which means
git fetch
In order to fuss around with commits, we need to have them. You have total control over the repository on your laptop, so that's where we'd like to have them. We will get these commits with
git fetch
.What
git fetch
needs is the name of the repository you want to get commits from. That is, we'll pick one of your remotes:origin
orupstream
. Your Git will use the stored URL to call up a Git over on GitHub and connect it to one of those repositories.Like your local Git, the Git you call up—the corresponding repository, really, but let's just pretend that each Git is a person who does this sort of work—can see branch names and commit hash IDs. With
git fetch
, your Git asks them to report back to you all their branch names and those hash IDs. Your Git can therefore see their Git's name-and-ID pairs.For each of these IDs, your Git can now check to see if you have this commit. If you do, great! There's nothing more to do. But if you don't, well, your Git wants this commit, and their Git is happy to give it to you. Their Git is obligated to offer you any commits that go with that commit (parents), and your Git can check to see if you have those, and ask for them too, or say no thanks, already have that one as appropriate. Since this repeats for every branch name that they offer,1 you wind up having them send you every commit that they have, that you don't. At the end of this "get all the commits" process, you now have all of their commits.
We noted earlier, though, that hash IDs are terrible for humans. That's why their Git has their branch names in the first place. It would be nice, would it not, if your Git could refer to their branch names too? So that's what your Git does: it copies their names, but changes them. You have your branch names, and they have theirs. Your Git doesn't dare overwrite your branch names with their information. Instead, your Git takes their branch names and turns them into your remote-tracking names.
To do this, your Git just shoves the name of the remote, and a slash, in front of their names.2 Their
master
becomes yourorigin/master
, if we're having Git fetch fromorigin
, or yourupstream/master
, if we're having Git fetch fromupstream
.Once this is all done, your Git disconnects from their Git. Their Git can now update their branch names, and if and when they do, your Git's remote-tracking names are out of date. This does happen, all the time; to fix it, you just run
git fetch
again, toorigin
to update yourorigin/*
names, and toupstream
to update yourupstream/*
names. But while you're disconnected, you still have all the commits, and your Git remembers where their Git's branches are.1If you set up a single-branch clone, your Git only asks them about that one branch, and hence you don't get everything. The point of a single-branch clone is ... well, generally to save space and time, but they don't save that much of either one on their own, so until you are really familiar with Git, avoid single-branch clones.
2Technically it's more complicated than this, which sort of helps if you accidentally name your own branch
origin/xyz
or something. But—just don't do that, and then you won't need to get into these technicalities.When you need to send commits, you need
git push
Let's say you ran
git fetch origin
andgit fetch upstream
, back to back, and now have all the commits. The Git your laptop is callingorigin
is under your control. This means you can send any new-to-them commits toorigin
.Depending on who controls
upstream
and what settings they have made, though, you may not be able to send stuff directly toupstream
. We'll worry about that later, because your next goal may be to updatemaster
over onorigin
—which your Git callsorigin/master
.In fact, you already did this:
Unfortunately, the stuff at that link is just a recipe. It doesn't explain why the listed commands are listed, leaving you in the situation of Cueball in this xkcd:
So, let's look closely at
git push
. Whilegit push
is as close as Git gets to the opposite ofgit fetch
, they're not quite opposites. The biggest difference is that withgit push
, there is no notion of a remote-tracking name.To use
git push
, you will often run it with two arguments:git fetch
; andThe refspec is the hard part here. We can keep it simple, because a branch name works as a refspec. But we maybe shouldn't keep it this simple, because that neglects something very important.
Note that if you run
git push
without these two arguments, the effect depends on whether you have Git 1.x or Git 2.x. In Git 1.x,git push
will probably try to push too much. In Git versions starting with Git 2.0,git push
defaults to pushing just the current branch, which is usually a lot closer to what most users want.The remote part, such as
origin
ingit push origin
, is easy. It's just the same as before: we're picking who to call up. The push command is going to send them commits, which is the obvious counterpart togit fetch
, which got commits from them. What's really different is this final refspec thing.3 So we need to define what a refspec is, and how you write one, forgit push
.The simplest form of a refspec is, as we already noted, just a branch name, such as
master
ornew_branch
. Forgit push
, this is shorthand formaster:master
ornew_branch:new_branch
respectively.The more complicated form here has a colon in the middle:
master:master
for instance. The colon separates your local name for some commit from the request you plan to make to the other Git.Let's go on now to see how
git push
works:First, your Git calls up their Git, just as before.
But now, instead of having them list out their branch names so that you can get all the commits they have that you don't, you have your Git list out one commit hash ID for them. That hash ID is from the left-hand side of your refspec.
They look at this hash ID and see if they have this commit. If they do, great! If not, your Git and their Git go through the same kind of conversation that happened before, to figure out what commits your Git needs to send them. Your Git will send them all the commits leading up to this commit as needed. They save those somewhere,4 then go on to the last part.
For the last part, your Git now gives them polite requests or forceful commands. These requests-or-commands are of the form:
The branch name for this last request-or-command comes from the right side of the colon. The commit hash ID comes from the left side, just as with the commits-to-send. So
git push master:master
takes your master—whatever commit hash ID that is—and sends that hash ID over to their Git. Their Git determines whether they have that commit already, or not, and makes sure to get it if they need it. Then your Git asks them to set their branch name,master
, to that hash ID.There are a couple of important things to note here:
master
, they will set theirmaster
. Or maybe they won't, because ...In any case, if they do say OK, I have made the branch-name change, or created a new branch name, as you requested/commanded, at this point, your Git says: Aha, I should now update my remote-tracking name. So if you've convinced
origin
, for instance, to update theirmaster
, your Git will now update yourorigin/master
the same way.3There is a bunch of history here. Skipping over all the history, we leave a puzzle: why do we use refspecs with push, but not with fetch? So let's fill it in a bit.
Technically, the
git fetch
command takes refspecs too. Before the invention of remotes and remote-tracking names, people sometimes needed (or at least very much wanted) to use refspecs with fetch. The invention of remote-tracking names got rid of most of the need, but the lack of remote-tracking names withpush
means we still need them withpush
.Fortunately, remotes and refspecs were invented well before Git became widespread. The wrong default for
git push
, however, persisted through Git 1.7 and 1.8, and some people still use these versions. (Git 2.0 came out just about the same time as Git 1.9 and 1.9 does not seem to be in use.)A lone branch name as a refspec has different meaning for
fetch
vspush
, but since we generally don't put in refspecs when we rungit fetch
, we don't have to worry about that here.4The receiver of a push operation stuffs incoming commits into a "quarantine" location in case they choose, in the end, to reject them. Older versions of Git lack the quarantine trick but it was pretty important for sites like GitHub.
Sometimes you want to send, but aren't allowed to
git push
Here, you'll need to make a pull request. This is a GitHub feature: it's not part of Git. We won't cover it here; there are existing questions and answers for this. It's worth mentioning, though, that this makes use of the way a GitHub "fork" remembers the source repository. It does not use remote-tracking names, but rather a bunch of stuff that the GitHub folks invented.
Triangular work: you don't necessarily want to have a
master
branchThe purpose of a branch name, in Git, is to be able to locate a specific commit. Moreover, given a branch name, you can use that name with
git checkout
orgit switch
. This puts your local Git into a state in which you are, asgit status
will say,on
that branch (on branch master
oron branch develop
or whatever). Once you're in this state, new commits you make will advance this branch name. You might have some series of commits ending at the one with hashH
:You do some work and run
git commit
and poof, you have a new commitI
with parentH
, and the branch name now locates the new commit:The reason you have a
master
onlaptop
, though, is because you ran:and when you did that, your Git called up the Git over at GitHub and copied the commits from that repository into a new repository. Then, your (laptop) Git created your own local branch name
master
, making it identify the same commit that your (local) Git callsorigin/master
as well:You then create your new feature branch,
new_branch
, pointing to commitH
:You check out
new_branch
so thatHEAD
attaches to that name:and then you make your new commit
I
:Notice how, all along, the remote-tracking name,
origin/master
, still identifies commitH
.Some time now passes. A bunch of new commits get added to the repository you forked. You run
git remote add upstream url
, whereurl
is the URL for the repository over on GitHub that you forked. Then you run:This has your Git call up their Git and get the new commits from them:
Right now this is what you are doing (well, have already done):
First,
git checkout master
attaches yourHEAD
to yourmaster
.Next,
git merge upstream/master
has Git figure out that it can move your branch namemaster
forward to point directly to commitT
(this is a tricky thing thatgit merge
does; we haven't covered it here). The result looks like this:Note how no commits have changed. There are no new commits. All we did was move one label, so that branch name
master
now points to commitT
.Last,
git push origin master
ends up sending commitsJ-K-...-T
to your GitHub fork, and then asks them to set theirmaster
to point to commitT
. Since this is just an add-on (they still don't know about commitI
but don't care because commitsJ
throughT
simply add on), they accept the polite request, and your Git updates yourorigin/master
.The result after that last step is this, in your local (
laptop
) Git:But: Suppose we delete the name
master
entirely. We'll start with the same starting point as before, minus the one branch name:We'll do
git remote add
if needed and thengit fetch upstream
if needed (we already did both, but let's just pretend we needed to) to get:Now, instead of any checkouts, we will just run this:
The name on the left locates the commit we want to send. This is commit
T
: the last commit on what we're callingupstream/master
, which is whatupstream
's Git callsmaster
.The name on the right,
master
, is the name we're going to askorigin
to set.The same commits flow to
origin
as before. They now have the commits up to and includingT
(but they don't haveI
), just as before. Then we ask them to set theirmaster
to point toT
, and they do, and we update ourorigin/master
:The end result is mostly the same. We did not have to
git checkout master
locally though, so we're still onnew_feature
.Rebase
The one remaining problem we have is that our commit
I
has, as its parent, commitH
. We cannot change this! Existing commitI
exists, and has a hash ID. That commit is forever set in stone. But what we can do is have our Git compare the snapshot in commitI
to that in commitH
, to see what we changed, and make the same changes as a new commit, that comes after commitT
.In other words, Git makes a new commit by copying commit
I
. Let's call this new-and-improvedI
,I'
(eye-prime), and draw it in:This process, of copying some commit, is a cherry-pick operation in Git, and you can do it with
git cherry-pick
. If you have several commits in a row to copy, though, the fast and easy way to do this is withgit rebase
. As a bonus feature,git rebase
follows up the copying with a branch-name-move. Note how in the diagram above, we don't have a name by which to find our new copyI'
. Rebase takes care of that by ripping the current branch name off the last commit we copy and making it point to that last copy. In this case, we can draw that as:We do lose the easy way to find the hash ID of commit
I
, but that's because we want our Git to abandon the old commit(s) in favor of the new-and-improved ones.That's the essence of rebasing. While I won't go into detail (because again this is so long), to achieve this kind of operation, we generally just run:
In this case the target is commit
T
, which we can find using the nameupstream/master
. If we've updatedorigin
'smaster
so that ourorigin/master
locates commitT
too, we can usegit rebase origin/master
, butgit rebase upstream/master
will work fine.If we've kept our
master
branch name, and updated it, we can usegit rebase master
as well. The key is that we need to tellgit rebase
to locate commitT
. Any name that finds commitT
will be fine, here. In fact, we can even useT
's hash ID, if we want to cut-and-paste it with the mouse for instance.Now, if you ever ran
git push origin new_branch
before, you have already sent commitI
to the Git over atorigin
. If you now attempt to send commitI'
toorigin
and get the Git over atorigin
to point their namenew_branch
to commitI'
, they will say no!The reason they say no is that this polite request is asking them to discard some original commit(s) in favor of their new-and-improved replacements. Your Git already did this, when you ran
git rebase
. But now you need to get their Git, over atorigin
, to do the same. This means you must use a force push operation.You may have read that force-pushes are bad. They are. But we're faced with two bad alternatives: you can leave your pull request behind, or you can abandon the original commits in favor of these new-and-supposedly-improved replacements. If you abandon the originals, you need to convince everyone else who has copied these commits into their Git repositories to do the same.
Some people prefer to leave the pull request behind. There's nothing fundamentally wrong with that either. But if your company, or your personal preference, dictates that you should rebase, do it, and then use
git push --force
orgit push --force-with-lease
. (To find out the precise difference between these two, search StackOverflow.)