Before anything else, I'm just new to git branching. I wasn't aware that every feature branch should be branched out from master
and only use the pre-requisite feature branch with such relation to the next feature branch.
I have three branches. master
, feature-1
and feature-2
all pushed the a Bitbucket repository (with issue tracking enabled). The thing is commits M4
and M5
are critical commits that all branches should rebase it before committing a merge (task of git rebase
)
M1 -- M2 -- M3 -- M4 -- M5 [master]
\ /
A1 --- A2 [feature-1]
\
B1 -- B2 -- B3 [feature-2]
The development for feature-2
is done and is now needed to be merged to master. Here's the precedence of tasks I've done for feature-2
to rebase M4
and M5
commits to feature-2
.
git push
- development offeature-2
git checkout feature-2
git rebase master
- Resolve conflicts
git pull
After doing those steps I've noticed after executing git status
. I have to push again all the commits (feature-2
, M4
, M5
and conflict commits). Well all I have to do is just do git push
and initiate the pull request and I'm done, right? But that would add another git commit comments to the issue tracker.
Is there a way to rebase feature-2
to master
without the need to push again the commits of feature-2
, M4
and M5
and the git log
should contain conflict commits.
Update
- Change the question details for better clarrification
As far as I understand, the rules of your codebase (team) demand you to rebase your feature branch against the
master
. You could do that by sayinggit rebase --onto master A2 feature-2
which would mean "take commits of the feature-2 starting with A2 exclusive and place them on top of the master". You could then push your changes directly tomaster
and drop or keep thefeature-2
branch intact, again depending on the workflow conventions.If on the other hand, the rebase is not demanded - you could well do a simple merge of the
feature-2
intomaster
, pushing the change to themaster
, as @poke recommends.Since you have pushed both feature branches already, you shouldn’t rebase at all. Rebasing already published branches is strongly discouraged since it breaks other developer’s repositories. The reason for that is that a rebase is just a complete rewrite of the commits. The commits you are rebasing are recreated, with changed contents, and—most importantly—with different hashes. This causes the new commits to be incompatible to the old ones, so whoever had the old ones ends up being in conflict with the new ones which replace them.
The proper solution for this is to simply merge the changes. While this might not end up looking so pretty, it is a non-destructive action in which no existing commits are changed. All that happens is that commit are added which will result in no problems when pushing or pulling.
That being said, you can rebase and still publish the changed branches. But to do so, you will need to force-push the branches and other developers pulling those changes will need to reset their branches to the new versions.
To incorperate some of my comments below into the answer: It’s important to understand that in Git, branches are just pointers to commits. The whole history—without branches—is a large acyclic graph where commits just point to their parent commits. So to take your example from the question, this is the history, without any branch pointers:
Each letter represents a commit and all commits one is connected to to the left are its parent. So for example
F
’s parent isA
, andC
is a merge commit with the parentsB
andG
.Now, if we add branches to that visualization, then we just add pointers that point to some commits. It’s really nothing else (a branch is literally just a file that contains the hash of a commit):
Now, imagine we make a commit to the
feature-2
branch. We add that commit to the tree …… and then we move the branch pointer one forward:
Now, to understand what happens during a push, we need to remember that remote branches are also just branches, and as such just another set of pointers. So it actually looks like this:
I think you can imagine what happens during a push now: We tell the remote repository to update its branch pointer so it points to
K
. But the server only hasJ
, so we need to give the server everything to construct the tree accessible byK
(so any other commits in between, and all the actual contents of those commits). But of course we don’t need to physically pushJ
, orH
, or evenA
(although those are all technically on thefeature-2
branch since you can reach them); Git is smart enough to figure out which objects are actually missing (you can see Git calculating that when you start a push).So once we transferred the missing objects to the remote repository, we then tell the remote repository to update its
feature-1
pointer, so it will also point atK
. And if that succeeded, we also update our remote branch (origin/feature-2
) to point to it too (just to get in sync).Now, the situation is really the same with merges. Imagine that we merged
master
intofeature-2
(usinggit merge master
while onfeature-2
):Now, if we want to push
feature-2
, we again need to give the remote repository all the objects it doesn’t have. And since we are on a merge commit now, we need to check all parents: So if the server didn’t haveK
we would need to pushK
; But also, if it doesn’t haveE
, we would have to pushE
. And of course we need to follow those parents again to make sure that all objects exist on the remote. And once that’s done, we just tell the remote again to update the branch pointer.So to sum this up: A branch contains all commits that are somehow accessible by navigating the parents of its commits in the acyclic tree. But even if that means that branches are usually very “big” (in length of history), Git will only transfer those objects to the remote repository that it doesn’t have. So although a merge can add many more commits to a branch, those doesn’t necessarily have to be transferred if the remote already knows about them from another branch.
And finally, some last words on rebasing: Above we did
git merge master
to merge themaster
branch intofeature-2
. If we didgit rebase master
instead, the full tree would look like this now:As you can see, there are new commits
H'
,I'
,J'
andK'
. These are the rewritten commits so that they start atE
(wheremaster
pointed to at the time of the rebase) instead ofG
. Since we rebased, there is no merge commitL
. And as made clear above, the original commits still exist. It’s just that there is no pointer left that points to them; so they are “lost” and will eventually be garbage collected.So what is the problem when pushing now? The remote branch still points to the original
K
, but we want it to point toK'
now. So we start giving the remote repository all the objects it needs, just like before. But when we tell it to set update the branch pointer, it will refuse to do that. The reason for that is that by setting the pointer toK'
it would have to “go back in the history” and ignore the existence of commitsH
toK
. It doesn’t know that we have rebased those and there is also no link between the rewritten ones and the original ones. So just to prevent accidental data loss, the remote will refuse to update the branch pointer.Now, you can force push the branch. This will tell the remote repository to update the branch pointer even though doing so would throw those original commits away. So you do that, and the situation will look like this:
So far, everything is well: You decided to rebase the branch, and you told the remote repository to accept that without questioning it. But now imagine I wanted to pull that; and my branch is still pointing to
I
. So running pull does the same as a push in reverse: The remote gives me all the objects necessary to complete the history and then it tells me where to set the branch pointer. And at that point, my local Git refuses to do so for the same reason the remote repository did that before.With the push before, we had the knowledge that we wanted to replace the original commits; but with a pull we don’t have that, so we are now required to investigate, or ask around, whether we should just replace our local branch, or if there’s actually some fault on the remote. And the situation gets even worse if we did some work on our own locally which we would want to merge now.
And these problems happen to everyone that fetched those original commits once. In general, you want to avoid this mess completely, so the rule is to never rebase something that you have already published. You can rebase as long as nobody else ever got those original commits, but as soon as that’s no longer the case, it will be a mess for everyone involved. So a merge is definitely preferred.
Not really, considering a rebase will replay the commits of feature-2 on top of master, given you this:
As the
'
sign indicates, all the commits have changed (their SHA1 is different).Since
A1
andA2
were already merged intomaster
, a better rebase would beThat would get:
You should
git push --force
that newly revisedfeature-2
branch in order to update your pull request.A forced push should update a pull request in Bitbucket since it supports such a push since Q4 2012 (issue 4913).
A forced push won't have any other adverse effects, considering you are pushing a branch to your fork where, presumably, you are the only one to push updates.
Since the pull request will automatically updates itself with the new history of
feature-2
, that won't compromise said pull-request.