1

I saw some behaviour (not developer friendly in my opinion) with git rebase. I will explain steps briefly:

Step 1. I have a master branch with commits m1 and m2.

Step2: Created a feature branch 'feature_branch' and added commit f1, f2. Mean time some other developer added commit m3.

Step 3: I have rebased feature_branch with master (using command git rebase master)so that feature branch have now commits in the order m1, m2, m3, f1, f2 (I fixed some merge conflicts. This perfectly OK in view of f1 and f2). While pushing git asked me to do pull. I did pull and fixed some merge conflicts again before pushing changes to feature branch.

Step4: Now a commit of m4 is made on master by some developer

Step 5: Rebase feature_branch again with master.

In step 5, I have observed that feature branch get m1, m2, m3, m4 in correct order and start applying f1 and f2 one by one on top of that. UP TO THIS I AM PEREFECTLY OK.

But just before git apply applying f1 and f2 one by one (as part of rebase), the local workspace folder files has already f1+f2 changes (This is confusing). While applying f1 it get merge conflicts with changes in files due f1+f2. I fixed the conflicts and continue rebasing. While f2 is being applied it gets conflicts again. My understanding while playing the commits f1 and f2, i am supposed to get same conflicts which i had got already during step 3(consider in m4 there is no changes in files which f1 and f2 have). But getting weird conflicts. Why is this happening?

Apology for a big question.

Thanks, Prasanth

Prasanth
  • 33
  • 5
  • I think something has gone wrong in step 3. Why did you have to pull again if you have already rebased f1 and f2 on top of m3? – mkrieger1 Jun 18 '23 at 19:34
  • git asked me to do pull before pushing the changes. – Prasanth Jun 18 '23 at 19:37
  • Yes. Sorry, that was a rhetorical question, I didn't expect you to answer that. I meant, the fact that you were asked to pull again before pushing m1-m2-m3-f1-f2 indicates that something else was going on which we can't see here and which may have caused your subsequent confusion. – mkrieger1 Jun 18 '23 at 19:39
  • Thanks for quick answer. I sorry if i made a feeling that git is wrong. I usually follows this order of rebase, pull and push. I believe without pull may be we can use -f option while pushing. – Prasanth Jun 18 '23 at 19:42
  • 3
    In step 3, I hope it was a 'pull -r' be que once you decide that your strategy is to rebase, it's better to continue doing it. And in this case, when you push your feature branch update, it's always with `git push --force-with-lease` because you would like to push an history change (absolutely avoid '-f' to force) – Philippe Jun 18 '23 at 21:57
  • Please check this [answer](https://stackoverflow.com/a/30927009). – kadewu Jun 19 '23 at 02:15

2 Answers2

1

Firstly, when you run a git rebase, the commits that you've made on your feature branch are temporarily saved away by Git, like a patch. The branch you're rebasing onto is checked out, in this case, the master branch. This is why you're seeing your local workspace already containing the changes from the master branch.

Then, the changes that you made on your feature_branch are reapplied one by one onto this branch. This is why you're having to solve conflicts again -- Git is essentially trying to recreate the commits as they were originally done, so if those commits had conflicts with the master branch at that point, you'll have to solve those conflicts again.

The reason you're seeing different conflicts than you did in step 3 could be due to the fact that the base of the rebase has moved. In step 3, you were rebasing onto m3, but in step 5, you're rebasing onto m4. Even if m4 didn't touch the same files as f1 and f2, it could have changed the context in which f1 and f2 are applied, leading to different conflicts.

One way to avoid having to solve the same conflicts over and over again is to use git rerere ("reuse recorded resolution"), which allows Git to remember how you've resolved a conflict so that the next time it sees the same conflict, Git can automatically resolve it for you.

To enable rerere, you can use the following command:

git config --global rerere.enabled true

Next time you rebase and resolve conflicts, Git will remember these resolutions and apply them when you rebase again.

ziwi
  • 54
  • 4
1

I'm pretty sure you left out a step:

Step 2.5: You pushed feature_branch.

At that moment feature looked like:

m1-m2-f1-f2

In step 3, after you rebased feature_branch onto master, feature_branch looked something like:

m1-m2-m3-f1'-f2'

(Note the "prime" here in f1' and f2', which is used to signify that they are like f1 and f2 but slightly different, e.g. each has a new commit ID, and in this case also a new parent commit ID, and committer datetime, etc.)

Now when you tried to push, you probably saw a message like:

Your branch and 'origin/feature_branch' have diverged, and have 3 and 2 different commits each, respectively.

This is because origin/feature_branch has commits f1 and f2 and your local branch no longer has those commits, since now they are f1' and f2' which are different commit IDs. When Git detects this, it always tells you to pull and then push, but in your case, you don't want to pull because that will merge the old version of your branch back into your current, better, version. By pulling here you end up with duplicate versions of your commits. Instead you want to force push after purposefully rewriting your branch, such as with amend, or rebase.

The Fix:

To prevent this next time, as mentioned in Phillipe's comment, after rebasing your branch, if you've already pushed it before, don't pull like Git tells you to, but instead you should use git push --force-with-lease.

For now you can use git reflog to either find f2' or even f2'' and then git reset --hard <commit-id-you-want>. Then you can force push your feature_branch to remove the merge with the old version of those commits.

The Explanation:

The fact that you didn't do that explains everything you witnessed. Since you had conflicts replaying f1 and f2 on top of m3, after resolving them your new state is compatible with f1' and f2' only. When you pulled, that merged in the original versions of f1 and f2 again which aren't compatible with the new state, so you had conflicts again. After resolving them and completing the merge, your branch probably looked something like this:

m1-m2-m3-f1'-f2'-MC
     \---f1--f2-/

When you rebased onto master a second time in step 5, you now have 5 commits that aren't on master, and, when you rebase the merge commits fall out by default, so only the 4 commits will be replayed. (Had you added the option -i to the rebase you could have witnessed it.) Presumably commit m4 didn't introduce any conflicts with f1' and f2', so replaying them as "1 of 4" and "2 of 4" went just fine, and at that moment your temporary branch looked like this:

m1-m2-m3-m4-f1''-f2''
# conflict on replaying f1

This explains why you already saw the changes from f1 and f2 there, but it was still stuck on trying to replay the original f1 and f2; thus it had conflicts again because the merge commit that resolved the conflicts last time isn't being replayed during the rebase. If you're still in that state you can actually elect to skip those commits as you no longer need them, or just quit where you are:

git rebase --quit

Now your branch should look like:

m1-m2-m3-m4-f1''-f2''

If you are detached you could just repoint your branch to this commit:

git switch -C feature_branch # recreate branch pointing to f2''
# then
git push --force-with-lease
TTT
  • 22,611
  • 8
  • 63
  • 69