Say I have two branches, master
and dev
.
I created a branch from master
, made changes and merged to master
.
Now I created another branch from master
, but pushed my changes to dev
and am trying to merge them, but when I make the pull request, the first branch merged to master
appears in my request. How can I avoid this?
(The problem is that I should have merged the first branch to dev
not master
but now I can't do that anymore, because the same changes were merged to dev
with another, third branch.)
Normally, you would see other people commits when creating a PR, unless you pushed your fixes onto a common branch (like master). You should: create a new branch locally ( git switch -c newBranchForFix <oldSHA1> ) from a known starting point (before your current commits)
A pull will pull all the commits, including their dependencies - it won't cherry-pick individual commits. So if you want to request that only your commits be pulled, and there are other people's commits in the same branch, you have to first separate your commits into a different branch. Yes, it will.
Create a pull request Click Create. Select the source branch which is wanted to be merged. Select the target branch to which you want the changes to be merged. Give an appropriate subject line and description that will be used as a commit message for a merged pull request.
Note: if this is TL;DR, skip down to the last section, Drawing your pull request.
Let's draw what you already did. Remember, in Git, each commit "points back" to its parent commit, or for a merge commit, to both of its (two) parents; and a branch name like master
or dev
or newbranch
simply points to one commit, with the commits themselves doing the pointing-back so as to represent a whole series of commits.
I have to make some assumptions here. Bitbucket (which I have not used since they converted from a Mercurial-only shop to Git) and GitHub (which I do use) are a little bit different, but since they both use Git underneath, have a lot of similarities. I am going to assume you're using what Bitbucket's web site calls a "forking workflow".
I am going to try to avoid making many other assumptions. That makes this answer very long and complicated. (It might have served you well to have provided more information in your question, so that the answer could be shorter.)
So, you started with:
... <-B <-C <-D <-- master
where commit D
points back to commit C
, C
points back to B
, and so on, and master
points to D
. Note that each of these single uppercase letters stands in for an actual commit hash ID, one of those incomprehensible 1badc0ffee4deadcab...
or whatever things. We use the letters here because they're simple and easy and comprehensible; Git uses the hashes because the letters run out after just 26 commits. :-) (And for other better reasons.)
We know commits always point backwards. They have to: you can't point forwards to a commit you haven't made yet! So let's not bother with the interior arrows. But the branch names move over time, so let's keep drawing their arrows:
...--B--C--D <-- master
Now you created a new branch, which also points to D
:
...--B--C--D <-- master, newbranch (HEAD)
(Here's a quick exercise for you: which of these three commits are on master
, and which are on newbranch
? This is a little bit of a trick question, because the answer is that all three commits are on both branches.)
We've added this HEAD
notation so that we can tell which branch you're "on", as in, git status
says on branch newbranch
. Now you make a new commit E
. Git makes the current branch—the one marked HEAD
—point to the new commit we just made, and has the new commit point back to the previous branch-tip:
...--B--C--D <-- master
\
E <-- newbranch (HEAD)
Let's make two more commits just for the heck of it:
...--B--C--D <-- master
\
E--F--G <-- newbranch (HEAD)
Now the name newbranch
points to G
, which points back to F
, and so on.
newbranch
The next part is tricky. You did:
git checkout master; git merge newbranch
or maybe:
git checkout master; git merge --no-ff newbranch
but you don't say which of these you did. The git checkout
will make the HEAD
label attach to master
. But, given the drawing we've drawn here, you can see that Git could just slide the name master
forward, in the left-to-right direction that is the opposite of all the internal arrows in the branches, so that the name master
points to G
:
...--B--C--D
\
E--F--G <-- master (HEAD), newbranch
Git calls this label-sliding operation a fast-forward. One critical thing about this is that a fast-forward operation does not make a merge commit. This makes it harder to see where master
was before you ran git merge
, because we can straighten out the kink in the drawing and get:
...--B--C--D--E--F--G <-- master (HEAD), newbranch
Given that D
is really 4384e3cde2ce8ecd194202e171ae16333d241326
or some such big ugly hash ID, you probably don't remember what it really was. How will you know that you merged exactly three commits, and that master
used to be 4384e3cde2ce8ecd194202e171ae16333d241326
?
You can use the --no-ff
kind of merge to force Git to make a real merge even if it otherwise doesn't have to. This helps figure out what happened. Instead of just sliding the name master
forward, Git will do this:
...--B--C--D---------H <-- master (HEAD)
\ /
E--F--G <-- newbranch
In other words, here Git makes a new merge commit H
. The new commit points back to both D
and G
. This is in fact what makes it a merge commit: it points to two parents.
The precise shape of the post-git merge
-command graph doesn't affect your current problem. You would get this same thing with either of the two post-merge graphs. Still, it's important to note when merge commits go in, and what they do: they record this kind of actual history, for future exploration. And in either case, it's important to be able to draw the graph, to predict what will happen with a pull request. Both post-merge
-command graphs result in a problem.
Sometimes, making this "merge bubble" commit H
pointing back to both G
and D
* is just useless clutter. If there's no future need to remember that this group of commits came in all at once via merging, you can and probably should use the fast-forward merge. If you want to force a real merge, you now know that there is this --no-ff
option to git merge
to do that.
If you use GitHub to make merges (perhaps via pull requests), you can use GitHub's controls to decide whether to force real merges. These controls are different from Git's (naturally, as they are GUI clicky buttons in your browser). GitHub generally tries to hide these graphs from you, which I think is a mistake—the graph is crucial in determining what is actually possible and what will happen when you try various things. For instance, it shows, visually, why your pull request had the problem.
(I have not used Bitbucket's web interface but I assume it works much like GitHub's.)
You now say:
Now I created another branch from
master
, but pushed my changes todev
and am trying to merge them, but when I make the pull request, that first branch merged tomaster
appears in my request.
Let's assume you made a fast-forward merge and therefore have this in your Git repositories, to start with. I'll assume you've deleted newbranch
or that we don't care about it, and that your new new branch, which you "pushed to dev
", is called zaphod
:
...--D--E--F--G <-- master, zaphod (HEAD)
Now you make a few new commits (let's put in two):
...--D--E--F--G <-- master
\
H--I <-- zaphod (HEAD)
You then do something—it's not clear to me precisely what series of Git commands and/or Web and/or GUI clicky boxes—to get your Bitbucket repository to make the name dev
also refer to commit I
, or perhaps to a new merge commit J
that points back to I
.
Note that there are two Git repositories of your own involved at this point: one on your local computer, and one on Bitbucket. The interesting thing about Git hash IDs, which we've been drawing here as single uppercase letters, is that they are the same in all copies of every repository. (This is not true of branch names!) So over on Bitbucket, instead of zaphod
, we have the name dev
pointing to commit J
. We also don't really need the HEAD
notation since you don't commit directly on Bitbucket:
...--D--E--F--G <-- master
\
H--I <-- dev
Meanwhile, there's a third repository that we need to bring in. This third repository is the one you forked from, in your Bitbucket "forking workflow".
I will assume for the moment that this third repository started out looking like this:
...--B--C--D <-- master, dev
(If this is not the case, it's your job to draw the appropriate portions of the graph that they do have in this third repository.)
With all the above out of the way, let's look at what a pull request really is.
All that a pull request is, is a request (hence the name) that someone else add, to their repository, some set of commits from one of your repositories, using a name associated with your particular repository—such as your dev
—and a name associated with their repository, often the same name again.
In this case, the repository you make the pull request from is the one you have stored on Bitbucket that is your fork of their repository. So that's why we need the drawing of the graph that's in your Bitbucket repository:
...--B--C--D--E--F--G <-- master
\
H--I <-- dev
We also need to know what they have in their repository. I've assumed this:
...--B--C--D <-- master, dev
Note that your D
and their D
are literally the same commit.
What you are asking them to do is to set their dev
to point to commit I
.
For them to do that, they must take your commits E--F--G--H--I
into their repository, resulting in:
...--B--C--D <-- master
\
E--F--G--H--I <-- dev
But that's not what you really want them to do right now. You want them to take some commit(s) that somewhat resemble H
and I
, and do this:
...--B--C--D <-- master
\
H2--I2 <-- dev
To get that to happen, you must first make H2
and I2
yourself.
To make H2
and I2
, you will need to copy your original commits H
and I
to new commits. You will need these new commits to come right after commit D
. So now you need to locate their tip-of-dev
commit.
I've been drawing this as commit D
, but that might not be correct. I don't have your repositories and have had to make various guesses (including your chosen Bitbucket workflow).
If you have their Bitbucket repository set up as an upstream, you can run git fetch remote-name
to get your own Git, on your local computer, to obtain their latest commit from their dev
and record it as remote-name/dev
. (Typically your Bitbucket fork would be named origin
, so that origin/dev
records your fork's dev
in your repository on Bitbucket. You might name their Bitbucket repository upstream
so that upstream/dev
names the commit at the tip of their dev
branch.)
Let's say, for concreteness, that their Bitbucket repository, which you can refer to by the name upstream
from your local Git repository, actually has this in it:
...--B--C--D <-- master
\
N--O <-- dev
Here you'd run git fetch upstream
, and now your repository—your local one, not your copy on Bitbucket yet—would have:
H--I <-- zaphod (HEAD), origin/dev
/
...--B--C--D--E--F--G <-- master
\
N--O <-- upstream/dev
You then need to create your own branch that points to commit O
. To do that locally, you can use git checkout
with -b
and --track
:
$ git checkout -b for-upstream-dev --track upstream/dev
H--I <-- zaphod, origin/dev
/
...--B--C--D--E--F--G <-- master
\
N--O <-- upstream/dev, for-upstream-dev (HEAD)
Now you can copy commits H
and I
to commits H2
and I2
, using git cherry-pick
:
$ git cherry-pick zaphod~2..zaphod
H--I <-- zaphod, origin/dev
/
...--B--C--D--E--F--G <-- master
\
N--O <-- upstream/dev
\
H2--I2 <-- for-upstream-dev (HEAD)
Once you have this set up in your local repository, you need to get your Bitbucket repository to set its dev
to point to commit I2
. To do that you must force-push. Normally, force-pushing is bad, as it tells another Git repository to lose some commits, but here you want your Bitbucket repository to lose its H--I
commits. So:
$ git push --force origin for-upstream-dev:dev
would have your (local) Git call up the origin
Git—that's your Bitbucket Git—and tell it: I give you my I2
, and of course my H2
and O
and N
if you need them; now you set your dev
to my I2
!
Assuming they obey this command, Bitbucket will now change its copy of your fork to look like this:
...--B--C--D--E--F--G <-- master
\
N--O--H2--I2 <-- dev
(And, when the origin
Git accepts this push, your own Git moves your origin/dev
to point to I2
, to remember that origin
now has its dev
pointing to I2
.)
Now compare this graph to the one in the upstream Bitbucket repository, the one your Git calls upstream
:
...--B--C--D <-- master
\
N--O <-- dev
The difference between these two graphs is that you will ask them—your upstream
—to take your origin
's H2
and I2
and add those to their dev
... which is what you asked for in your question.
Note that your own (local) repository still has the complicated, tangled mess of branches named zaphod
and so on. Once your upstream has accepted your changes to their dev
—taken your H2
and I2
—it's probably time to clean up your local repository, making it look at least a little bit, if not a lot, like your origin
repository. To do that, you can delete your own zaphod
branch entirely:
H--I [abandoned]
/
...--B--C--D--E--F--G <-- master
\
N--O <-- upstream/dev (maybe)
\
H2--I2 <-- for-origin-dev, origin/dev, upstream/dev (maybe)
Note that once you drop a branch label, the only way to find the commits that you used to find from the branch label, is to use some other name (like the raw hash ID). If all names for some commit(s) are gone, so that there is no way to find them, they will eventually be garbage collected, i.e., tossed out entirely. This is what will happen to the original H--I
, now that they are abandoned.
The (maybe)
s above are because your own Git won't know that upstream
has accepted your pull request until you run git fetch upstream
to have your (local) Git call up the upstream Git and find its latest dev
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With