So far the most confusing portion of git is rebasing onto another branch. Specifically, it's the command line arguments that are confusing.
Each time I want to rebase a small piece of one branch onto the tip of another, I have to review the git rebase documentation and it takes me about 5-10 minutes to understand what each of the 3 main arguments should be.
git rebase <upstream> <branch> --onto <newbase>
What is a good rule of thumb to help me memorize what each of these 3 parameters should be set to, given any kind of rebase onto another branch?
Bear in mind I have gone over the git-rebase documentation again, and again, and again, and again (and again), but it's always difficult to understand (like a boring scientific white-paper or something). So at this point I feel I need to involve other people to help me grasp it.
My goal is that I should never have to review the documentation for these basic parameters. I haven't been able to memorize them so far, and I've done a ton of rebases already. So it's a bit unusual that I've been able to memorize every other command and its parameters so far, but not rebase with --onto
.
-
You could either define some git aliases for your common use cases, or knock up a wizard script that reminds you what each of the parameters means before executing the command.Rory Hunter– Rory Hunter2014年03月25日 16:42:25 +00:00Commented Mar 25, 2014 at 16:42
-
6Ah, these mindblowing, pure elegance of git command options. So intuitively to use. Always a real pleasure.JensG– JensG2014年03月30日 22:44:49 +00:00Commented Mar 30, 2014 at 22:44
2 Answers 2
Let's skip --onto
for the moment. upstream
and branch
are pretty basic, and actually sort-of mimic checkout
and branch
- the second argument is optional:
git branch <newbranch>
git branch <newbranch> <base>
git checkout -b <newbranch>
git checkout -b <newbranch> <base>
git rebase <upstream>
git rebase <upstream> <branch>
(Aside, the names of these arguments in rebase
, "upstream" and "branch" aren't very descriptive IMO. I typically think of them like peachoftree, <start>
and <end>
, which is how I'll be using them: git rebase <start> <end>
)
When the second branch is omitted, the result is almost the same as first checking out that branch and then doing it as though you had not specified that branch. The exception is branch
which doesn't change your current branch:
git checkout <base> && git branch <newbranch> && git checkout <previous_branch>
git checkout <base> && git checkout -b <newbranch>
git checkout <end> && git rebase <start>
As for understanding what rebase
does when invoked, I first started by thinking of it as a special type of merge. It's not really, but it helped when first starting to understand rebase. To borrow peachoftree's example:
A--B--F--G master
\
C--D--E feature
A git merge master
results in this:
A--B--F-----G master
\ \
C--D--E--H feature
While a git rebase master
(while on branch feature
!) results in this:
A--B--F--G master
\
C'--D'--E' feature
In both cases, feature
now contains code from both master
and feature
. If you're not on feature
, the second argument can be used to switch to it as a shortcut: git rebase master feature
will do the same thing as above.
Now, for the special --onto
. The important part to remember with this is that it defaults to <start>
if not specified. So above, if I specified --onto
specifically, this would result in the same:
git rebase --onto master master
git rebase --onto master master feature
(I don't use --onto
without specifying <end>
simply because it's easier to mentally parse, even thought those two are the same if already on feature
.)
To see why --onto
is useful, here's a different example. Let's say I was on feature
and noticed a bug, which I then started fixing - but had branched off of feature
instead of master
by mistake:
A--B--F--G master
\
C--D--E feature
\
H--I bugfix
What I want is to "move" these commits for bugfix
so that they're no longer dependent on feature
. As it is, any sort of merge or rebase shown above in this answer will take the three feature
commits along with the two bugfix
commits.
For example, git rebase master bugfix
is wrong. The range <start>
to <end>
happens to include all the commits from feature
, which are replayed on top of master
:
A--B--F--G master
\ \
\ C'--D'--E'--H'--I' bugfix
\
C--D--E feature
What we actually want is the range of commits from feature
to bugfix
to be replayed on top of master
. That's what the --onto
is for - specifying a different "replay" target than the "start" branch:
git rebase --onto master feature bugfix
A--B--F--G master
\ \
\ H'--I' bugfix
\
C--D--E feature
Just a refresher, rebasing is mainly for when you want your commit history to appear linear if two branches have developed independently of one another, basically it rewrites commit history.
the way I like to do it is git rebase --onto <target branch> <start branch> <end branch>
where <target branch>
is the branch you are rebasing onto, <start branch>
is normally the branch from which <end branch>
split and <end branch>
is the branch you are rebasing.
if you start with
A--B--F--G master
\
C--D--E feature
and do
git rebase --onto master master feature
you will get
A--B--F--G master
\
C'--D'--E' feature
another good thing to know is that <target branch>
defaults to <start branch>
so you can do that same rebase as
git rebase --onto master feature
if you need more help, check out the Rebase without tears guide
-
Your result looks misleading. rebase should leave the
master
branch itself unchanged. You just get 'feature' to branch out likeG--C'--D'--E'
whilemaster
still stops atG
.Frank– Frank2014年03月26日 06:08:34 +00:00Commented Mar 26, 2014 at 6:08 -
@Frank, I did that to emphasize the whole linear history thing but now I think you're way is better. fixed.peachoftree– peachoftree2014年03月26日 15:28:23 +00:00Commented Mar 26, 2014 at 15:28
-
1Can you show an example where
<target branch>
and<start branch>
are different so as to help readers understand the most general case?Rufflewind– Rufflewind2014年04月30日 02:37:15 +00:00Commented Apr 30, 2014 at 2:37