I have seen so many posts and blogs about git
rebase on feature branch, but still confused 100%.
Scenario: I did the following steps while working on a newly created feature branch from master.
% git clone <repository>
% git checkout -b feature_branch origin/master
% git add .
% git commit -m "initial commit"
% git push
% git feature_branch [-u|--set-upstream-to]
% git checkout master
% git pull
% git rebase
% git checkout feature_branch
//make some changes locally
% git add .
% git commit
% git push
At this stage I get message saying your remote branch is ahead of your local repository. Please pull first. When and how did the change into my remote feature branch occured?
I know here there is local repo of master and my feature branch and when I pull things they come from remote origin/master
. So when I am making changes locally then my local feature_branch is ahead of remote feature_branch I assume.
Where could I be taking wrong steps in this context? What should be the actual steps for working on a initial local feature_branch
which then goes to remote
for backup in case something goes wrong while developing. I wanted to use rebase
to keep the git history clean. Then what will be the steps? THe above as I mentioned?
I'm making several assumptions here; I'll note them.
Here is the sequence of commands, along with what I assume and what they then do:
% git clone <repository>
I assume this works and you also then cd
into the new clone (you didn't show this).
% git checkout -b feature_branch origin/master
This creates, in your new local repository, a new branch name, feature_branch
, that selects the same commit as origin/master
. The name origin/master
—which is short for refs/remotes/origin/master
—is not actually a branch name, but rather, is your Git's memory of their (origin
's) master
, the last time your Git updated its own memory: in this case, at the time you ran git clone
.
They may or may not already have a branch named feature_branch
of their own! You created your feature_branch
locally. If they do have a feature_branch
, yours is not related to theirs, unless by some chance theirs is also exactly the same as their master
(which is your origin/master
).
% git add .
This wouldn't do anything, since you have not modified any files, and all the files in your working tree came out of the commit.
% git commit -m "initial commit"
This also would not do anything. Your existing feature_branch
is still even with their master
, your origin/master
.
If you did modify some files before git add .
and git commit
, this made one new commit on your feature_branch
.
% git push
This would give you an error, in a modern Git, along with a lot of advice suggesting git branch --set-upstream-to
or git push -u
or something along those lines:
% git feature_branch [-u|--set-upstream-to]
This isn't a command. If this was actually:
git branch --set-upstream-to origin/feature_branch
and you got no errors here, that means they already had their own feature_branch
. This seems like the most likely issue here.
(If you actually ran, instead:
git push -u origin feature_branch
that would create a new name, feature_branch
, over on the Git repository at origin
, based on your feature_branch
, and we probably would not see any errors later.)
% git checkout master % git pull
There isn't really any point to doing these two, and they likely did nothing. If they did do something, it will generally be harmless—though this depends on who is doing what with the Git repository over on origin
.
% git rebase
You are, at this point, still on your master
, and it would again do nothing.
% git checkout feature_branch //make some changes locally % git add . % git commit
Here, we finally get somewhere: we add a new commit, locally, to the local branch named feature_branch
. But if they had their own feature branch, your local branch and theirs have now diverged, and:
% git push
this would complain that you need to use git pull
or similar to accommodate their work.
What you probably want to do at this point is:
git rebase --onto origin/feature_branch origin/master
which will copy your one actually-new-commit that you've made on your feature_branch
, that extends out from your origin/master
. The new-and-improved copy of that one commit will go after their last commit on their feature_branch
, which is your origin/feature_branch
.
As a drawing, we might have something that looks like this:
K <-- feature_branch (HEAD)
/
...--G--H <-- master, origin/master
\
I--J <-- origin/feature_branch
What this drawing means is:
Your current branch is feature_branch
. That's the special name HEAD
here.
Your current commit is K
. The real hash ID of any commit is unique, but big and ugly and impossible for humans to deal with: it looks like random gibberish. So when we make simplified drawings of what's going on in a repository, we are often well-served by substituting in simpler names for the commits. So that's what I've done here.
Their master
(your origin/master
) and your master
both select commit H
.
Their feature_branch
—your origin/feature_branch
—selects some later than H
commit J
.
Note that I've drawn commits with later ones towards the right. Each commit links backwards to an earlier commit, so commit H
links backwards to some earlier commit G
, which links backwards to earlier commits I did not bother to try to draw. Meanwhile, commit J
links backwards to I
, which links backwards to H
.
This sort of backwards linkage is what Git commits are really all about. Every time you make a new commit, Git packages up a snapshot of every file, as of the form it has now (after git add
-ing a copy into Git: the copies you can see and work on/with aren't actually in Git, which has invisible secret copies). Git adds on metadata, such as your name and email address and the current date-and-time. Git makes this metadata such that it includes the hash ID of the current commit—in this case, commit K
—and writes all of that out to produce a new commit, which we can call L
:
L
/
K
/
...--G--H
\
I--J
And now, as its last trick, git commit
writes the raw hash ID of that commit into the current branch name, giving us:
K--L <-- feature_branch (HEAD)
/
...--G--H <-- master, origin/master
\
I--J <-- origin/feature_branch
I don't have to draw L
on a separate line here as I don't need to draw in any arrows pointing to commit K
, but if you'd made another branch name temp
that pointed to K
, I would leave L
on a separate line:
L <-- feature_branch (HEAD)
/
K <-- temp
/
...--G--H <-- master, origin/master
\
I--J <-- origin/feature_branch
This is (intended to be) the same drawing, but with the addition of the name temp
.
(You might want to run git log --all --graph --decorate --oneline
or any of the other commands shown in the answers on Pretty Git branch graphs to view other ways of looking at the set of commits you have.)
git rebase
is to copy commitsThe good thing about a commit is that it can never be changed... at all. You cannot change anything about an existing commit. Not even Git itself can do that.
The bad thing about a commit is that it can never be changed. Commit K
—the one you made on your feature_branch
back when you started with:
...--G--H <-- master, origin/master, feature_branch (HEAD)
\
I--J <-- origin/feature_branch
is stuck where it is, pointing backwards to existing commit H
. You might like everything else about it, but you don't like the fact that it points to H
, because their latest feature_branch
commit is commit J
. So you need a new and improved version of K
.
The rebase command is about making these new and improved commits. It can copy old commit K
to a new and improved K'
. It then has your Git repository abandon the old K
and use the new-and-improved K'
instead.
The bad things about using rebase are:
You have to be careful about how many commits it copies. Sometimes the defaults are right, and sometimes they're not.
Because it copies commits, anyone else who has the originals *also has to abandon the old ones for the new-and-improved ones. This takes work on their part.
If you haven't given out the old ones, there's nobody else who has the old ones, so rebase is safe. Otherwise, you need to tell whoever has them that you are doing this.
Anyway, given:
K <-- feature_branch (HEAD)
/
...--G--H <-- master, origin/master
\
I--J <-- origin/feature_branch
running:
git rebase --onto origin/feature_branch origin/master
tells your Git: do copy K
, stop copying at H
, and put the copies after J
.
If your commit graph is the one I drew, a simpler rebase would have the same effect—but your commit graph might instead look like this:
K <-- feature_branch (HEAD)
/
...--G--H <-- master, origin/master
\
I--J <-- origin/feature_branch
In this case, the simpler command (that I'm still not showing) would try to copy H
to a new and improved H'
, which you don't want to do.
In both cases, after the rebase, your new and improved K'
will come after J
, e.g.:
K [abandoned]
/
...--G--H <-- master, origin/master
\
I--J <-- origin/feature_branch
\
K' <-- feature_branch (HEAD)
Since we—and Git—find commits by starting from the branch names, following the arrows to the commits, and then working backwards (leftwards in these drawings), nobody will ever see commit K
again. It's still there—nobody and nothing can change it—but it might as well be gone.
(Eventually—after a minimum of 30 days by default—Git will notice that nobody can find K
, and strip it away for real, but you won't notice that either.)