eftshift0's answer is the right one here, I think, but it sounds like you should learn a few Git tricks.
The first thing to know about Git is that it's mostly about commits. Branches—or really, branch names (see What exactly do we mean by "branch"?)—exist in Git so that Git, and we, can find the commits.
You probably already know that a commit is, in effect, a snapshot of a source tree. (If you didn't, well, now you do!) But it has a little bit more as well. First, every commit has a unique "true name" in the form of its hash ID. A branch name is a way to express the true name of some commit—the hash ID—and git rev-parse
will show you the actual hash ID:
$ git rev-parse master
c05048d43925ab8edcb36663752c2b4541911231
These big ugly IDs seem random, but are actually completely determined by the contents of the commit itself. These contents are pretty short—here's the above commit, with @<address>
munged a bit to cut down on spam:
$ git cat-file -p c05048d43925ab8edcb36663752c2b4541911231 | sed 's/@/ /'
tree a446a29ef7ca90d9c64825fb00a0f1e1a099ca18
parent e9983f8965c0875cc6727e9644c84ff1dfc99372
author Junio C Hamano <gitster pobox.com> 1536096807 -0700
committer Junio C Hamano <gitster pobox.com> 1536096807 -0700
Git 2.19-rc2
Signed-off-by: Junio C Hamano <gitster pobox.com>
The tree line above is how the commit saves the source snapshot (by using another Git internal object). The author and committer lines tell us who made the commit and when—1536096807 means Tue Sep 4 14:33:27 2018
. The parent line gives the big ugly hash ID of the commit that comes before this commit.
We say that each of these things that has a hash ID in it points to the object. For much more on this, see Think Like (a) Git, but for this answer, let me just draw it out like this:
... <-c05048d43925ab8edcb36663752c2b4541911231 <--master
The name master
points to this commit, which points to the earlier commit e9983f8965c0875cc6727e9644c84ff1dfc99372
. That earlier commit points to a still-earlier commit, and so on.
All of these names and objects are local to your own repository
You have all of these branch names, like master
and defect-875
, in your own repository. They do not depend on anything outside your own Git. They point to commits that you have locally as well, and these commits—including their snapshots and their parent commits and those commit's snapshots, all the way back in history—do not depend on anything outside your own Git either.
But you do get some commits from some other Git(s). You do this by having your Git call up those Gits, using the Internet. Their Git repository has their commits with their branch names. The commits all have unique hash IDs, but if they have a commit that has the same hash ID—the same true name—as some commit that you have, then they and you by definition have the same commit.
git fetch
gets the commits they have that you don't
Their branch names do not have to match, but the commit hash IDs do have to match. Either they match, and you have the commit, or they have a commit that you don't. So your Git calls up their Git and says: What names do you have and what are their commit hash IDs? Their Git gives your Git this list, and your Git says to itself: ah, I have this one or hm, I don't have that one. Your Git then gives their Git a list of commits that they have, that your Git wants, and their Git packages up these commits—including their snapshots—and sends them over.
At the end of this process, you have all of your commits (including any commits you already had that they have) plus all of their commits. Note that they send you not just the tip-most commits—the ones to which the branch names point directly—but also the parent commit, the parent's parent, and so on, as needed so that you get commits that connect back to your own commits.
git fetch
also gets their branch names; now what?
You also have their branch names. But these are their branch names, so now your Git renames these things. If your Git called up their Git using git fetch upstream
, your Git renames their master
, calling it upstream/master
, and renames their devel
, calling it upstream/devel
.
We can draw the commits like this, using round o
s to stand in for the actual hash IDs, although I have to now start guessing how many commits they have that you don't. Before you run git fetch upstream
, you have something like this:
... <-o <-o <-o <-- master
\
o <-- defect-875
where your defect-875
commit points back to the tip of your master
branch, which points back to some earlier commit, and so on. Once you run git fetch upstream
, though, you have something more like this:
o--o <-- upstream/devel
/
o--o--o <-- upstream/master
/
...--o--o--o <-- master
\
o <-- defect-875
(it's getting too hard to draw in the arrows so I've replaced most of them with lines—just remember that they always point backwards).
Now you can merge
When you run git merge
, you must tell your Git which commit to merge with. You usually do this by giving it a branch name, or a remote-tracking name like upstream/devel
. This name resolves to a commit hash ID—you can run git rev-parse
to see how that works, just as I showed above. It works because git fetch
obtained their devel
and renamed it to be your own upstream/devel
. If you have not yet run git fetch upstream
, you must do that first, so that your Git has their commits and has renamed their devel
to become your upstream/devel
.
Merging
At this point, all the work takes place in your own repository. Let's say my drawings have been accurate; but let's simplify this to just the interesting names, and attach the word HEAD
to the one you have checked out right now. I'll also put in one-letter names for three interesting commits:
o--R <-- upstream/devel
/
o--o--o
/
...--o--o--B
\
L <-- defect-875 (HEAD)
Running git merge upstream/devel
will find your current or HEAD
commit L
(for Left or Local or --ours
); your commit labeled upstream/devel
, which is commit R
(for Right or Remote or --theirs
); and use L
and R
to work back to the common starting point, which is commit B
(for Base).
Your Git will then, in effect, run two git diff
commands, to see what you changed—what's different in B
vs L
—and to see what they changed, i.e., what's different in B
vs R
:
git diff --find-renames <hash-of-B> <hash-of-L> # what we did
git diff --find-renames <hash-of-B> <hash-of-R> # what they did
Git now combines—merges—these two sets of changes. If they combine easily, Git applies the combined changes to the snapshot associated with commit B
, and makes a new commit out of the result. This is a merge commit, a special kind of commit. What makes it special is simply that it has two parents instead of one: it lists your own commit L
first, as its first parent, and then their commit R
as its second parent.
It might help a little if we flip the drawing upside down, so I will do that here. The result looks like this:
L----------------M <-- defect-875 (HEAD)
/ /
...--o--o--B /
\ /
o--o--o /
\ /
o--R <-- upstream/devel
This new commit M
exists on your own defect-875
branch.
Sending the new commits to upstream
You can now, if you are authorized anyway, use git push
to create a defect-875
branch on the Git repository your Git refers-to as upstream
, using:
git push upstream defect-875
This has your Git call up their Git, offer to them a list of commits that you have that they don't—which in this case is exactly the two commits L
and M
—and then suggest to their Git that they create a branch named defect-875
, using commit M
as its tip commit.
If they obey all these requests and suggestions, your Git will remember that they did that, and add to your own set of names, the name upstream/defect-875
:
L----------------M <-- defect-875 (HEAD), upstream/defect-875
/ /
...--o--o--B /
\ /
o--o--o /
\ /
o--R <-- upstream/devel
Your own branch defect-875
is not changed in any way: your name defect-875
still identifies your commit M
(by its actual hash ID, whatever that is). You have merely given their Git these two commits and had their Git set their name defect-875
to match yours.
If you like, you can now set the upstream for your own branch defect-875
to the name upstream/defect-875
:
git branch --set-upstream-to=upstream/defect-875 defect-875
If you want to do both when you run git push
, you can do all of them at once by adding the -u
flag to your git push
:
git push -u upstream defect-875
but that's just a convenience optimization.