Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why git rebase shows conflicts in the files I did not modify?

Tags:

git

git-rebase

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.

like image 796
leopoodle Avatar asked Nov 10 '16 10:11

leopoodle


People also ask

Why do I get conflicts when rebasing?

Handling Conflicts When Rebasing When applying commits to a new base state, it is possible that the new base has made changes that are conflicting with the changes you are trying to apply. For example, if on main someone edited the same file and line you did on your branch. The same thing happens when merging.

What is the main issue with git rebase?

The golden rule of git rebase is to never use it on public branches. The rebase moves all of the commits in main onto the tip of feature . The problem is that this only happened in your repository. All of the other developers are still working with the original main .

Can rebase have conflicts?

If the change that you submitted has a merge conflict, you need to manually resolve it using git rebase. Rebasing is used to integrate changes from one branch into another to resolve conflicts when multiple commits happen on the same file. Never do a rebase on public (master) branches.


1 Answers

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.)

like image 84
torek Avatar answered Sep 21 '22 23:09

torek