Let's say that I am on a local repo and its branch is my_name/branch_A
When I do git rebase <branch_B>
, I sometimes get many conflicts in the files that I did not modify.
Why does this happen?
I like to just get the HEAD of all the files from branch_B
except for the ones I modified in my_name/branch_A
. How can this be done without manually resolving these conflicts that I did not introduce myself.
Rebase copies commits (and then abandons the originals). This is the root of the problem.
Let's look at an example. First, note that I'm drawing commit graphs with older commits towards the left, and newer commits towards the right. The commits have single-letter names here, instead of Git's true 40-character hash names like f1c93ab7...
. Branch names appear on the right, with an arrow pointing to the tip commit of that branch, because that is how Git actually stores these things.
Your current branch name is my_name/branch_A
, and you have a branch named branch_B
as well. There are some commits on each branch that are not on the other branch, and some commits that are on both branches. Meanwhile your branch, my_name/branch_A
, forks off from some third point—so there are commits on your branch that are not on branch_B
, but are not "your commits":
...--A--B--C--D--E <-- branch_uhoh
\ \
\ I--J <-- my_name/branch_A
\
F--G--H <-- branch_B
You made commits I
and J
and now you would like to have those two commits come after commit H
, i.e., to rework your commits to be "after" the tip of branch_B
.
In other words, you did not make commits C--D--E
. Nonetheless, these commits are on your branch. In fact, commits A
and B
are on your branch too: those two commits are on all the branches.
If you now run git rebase branch_B
(while you're still on your branch), Git has to figure out two things:
- what to copy, and
- where to start putting the copies.
The name branch_B
tells Git both of these things. The commits to be copied are "those that are on my_name/branch_A
but not on branch_B
. That's C-D-E-I-J
. The place to put them is "after the tip commit of branch_B
, i.e., after commit H
.
If this all succeeds, Git would set your branch name to point to the new copies:
...--A--B--C--D--E <-- branch_uhoh
\ \
\ I--J [abandoned]
\
F--G--H <-- branch_B
\
C'-D'-E'-I'-J' <-- my_name/branch_A
The names with the "prime" marks (C'
and so on) are the copies.
(You can view the commits that git rebase
would/will copy with git log <upstream>..HEAD
, which in this case is git log branch_B..HEAD
. I would add --oneline
here in most cases to get a one-line log message per commit.)
Well, if that's the problem, what should you do? The obvious answer is: tell Git not to copy C-D-E
. You want this result instead:
...--A--B--C--D--E <-- branch_uhoh
\ \
\ I--J [abandoned]
\
F--G--H <-- branch_B
\
I'-J' <-- my_name/branch_A
That is, Git should copy only those commits "between" (after, really) the tip of branch_uhoh
and your branch, so as to get commits I
and J
; but it should copy them to (again, after, really) the tip of branch_B
. The way to do that is to write:
git rebase --onto branch_B branch_uhoh
In other words, tell rebase both things, instead of telling it one thing and letting it figure out the two from that.
(But how do you find branch_uhoh
? Well, the "right" way is usually to remember it, but you can just run git log --graph --decorate --oneline
and find the cutoff commit by its ID or any name that shows up. Instead of branch_uhoh
you can cut-and-paste the hash ID of the actual cutoff commit. Most often, you can have Git itself remember the "upstream" name for you, and you rebase only within and onto the upstream and you need no arguments: you just run git rebase
and it does all the work. But there are special cases where you need to transplant commits, where this is not sufficient; then you need git rebase --onto
.)