martlin
martlin

Reputation: 252

Git: Rebase to equal branch (with different history) conflicts, cherry-pick doesn't

I've found an interresting GIT usecase, in which cherry pick of the set of commits does not produce any conflict, but the rebase of the exactly the same commits does result in conflits. I'm aware it's some specific case, but I would like to understand why that happens.

Setup

First of all: I'm working with specific git flow, which can be described as follows:

For instance, the history can look like this:

$ git log --all --oneline --graph
*   4035836 (development) Merge branch 'logging_in' into development
|\  
| * 016f383 created the login page
| * 55373c5 implemented the logging in logic
|/  
*   2cb0c46 Merge branch 'renamed_contacts' into development
|\  
| * 84dc37b updated the contacts page contents
| * 91acf76 renamed the contacts page file
|/  
*   2750381 Merge branch 'contacts_page' into development
|\  
| * f899247 added contacts page
|/  
*   3ca4134 Merge branch 'wellcome_page' into development
|\  
| * ac3095c added wellcome page
|/  
| * 60e684a (HEAD -> master, production) Merge branch 'logging_in' into development
| * 65afa29 Merge branch 'renamed_contacts' into development
| * 2be22da Merge branch 'contacts_page' into development
| * d709e94 Merge branch 'wellcome_page' into development
|/  
* c4c7b06 created readme

As a result, there are two branches, development and production, with exactly the same contents:

$ git diff development..production
$ 

My problem

In theory, rebase of sub-branch of the development branch to release should then work with no issues whatsoever. But it seems it doesn't.

I was working on some task (i.e. task branch branched of the development branch) and I've decided to not include the task in this "release". Thus, I've merged the production branch to master to indicate the current version is completed, created new branch named development_v2 off the master, and I wanted to rebase the task branch to the new development_v2 branch:

* c41d4a9 (rename_wellcome) renamed the wellcome to welcome
*   4035836 (development) Merge branch 'logging_in' into development
(...)
| * 60e684a (HEAD -> master, production, development_v2) Merge branch 'logging_in' into development
(...)
|/  
* c4c7b06 created readme

However, when I tried to do the rebase, it produces conflicts:

$ git checkout rename_wellcome
Switched to branch 'rename_wellcome'
$ git rebase development_v2 
CONFLICT (rename/delete): contacts.html deleted in HEAD and renamed to about-us.html in 91acf76 (renamed the contacts page file). Version 91acf76 (renamed the contacts page file) of about-us.html left in tree.
error: could not apply 91acf76... renamed the contacts page file
Resolve all conflicts manually, mark them as resolved with
"git add/rm <conflicted_files>", then run "git rebase --continue".
You can instead skip this commit: run "git rebase --skip".
To abort and get back to the state before "git rebase", run "git rebase --abort".
Could not apply 91acf76... renamed the contacts page file

But, when I faked the rebase by creating new branch and cherry-picking the rename_wellcome branch contents to that new branch, it all works as expected:

$ git checkout development_v2
Switched to branch 'development_v2'
$ git checkout -b rename_wellcome_v2
Switched to a new branch 'rename_wellcome_v2'
$ git cheery-pick c41d4a9
Removing wellcome.html
[rename_wellcome_v2 0068e97] renamed the wellcome to welcome
 Date: Tue Sep 21 20:48:23 2021 +0200
 2 files changed, 1 insertion(+), 1 deletion(-)
 create mode 100644 welcome.html
 delete mode 100644 wellcome.html

Resulting, as expected in:

$ git log --all --oneline --graph
* 0068e97 (HEAD -> rename_wellcome_v2) renamed the wellcome to welcome
* 60e684a (production, master, development_v2) Merge branch 'logging_in' into development
(...)
| * c41d4a9 (rename_wellcome) renamed the wellcome to welcome
| *   4035836 (development) Merge branch 'logging_in' into development
(...)
|/  
* c4c7b06 created readme

My question

I understand that renaming (and so with updating the contents) of files causes troubles as in some cases, there can get some file updated in one branch, while on the other does not exist, because it was replaced by the different one.

But I'm wondering, why this happens. I thought rebase and cherry-pick works the same (re-applying the commits/patches), so I'm surprised in one case it tells there are conflicts, but on the other one runs without any conflicts.

Could someone explain why the behaviour differs? And when exactly (what combination of renames/deletes/updates has to happen on each branch)?

Upvotes: 1

Views: 210

Answers (1)

eftshift0
eftshift0

Reputation: 30212

The problem is that, because you are keeping master and development completely separate from each other and also because of the way you are trying to rebase (just providing the target branch), git is trying to apply all the revisions that are not in master that are in development[1].... which is, well, just about everything. So it's not just the tip of renamed_wellcome that is getting rebased.

In order to pull this off, you need to do this:

git rebase --onto master renamed_wellcome~ renamed_wellcome

[1] git does not consider contents of revisions when checking what has to be rebased. It will only check the revisions themselves... so even if a revision has been applied in master (by cherry-picking or whatever other means that is not a merge.. a real one, a squash creates the same headache because it creates a different revision), if it's not the original revision from development, to git it's just another completely different revision, even if contents and everything else in there is the same. Git can't just do that effort of analysis when rebasing. When just providing the target branch to rebase, git will find the last revision that is present in both branches (source and target branches... which in your case is c4c7b06) and then it will start applying all the revisions after that from source branch on top of target branch.

Upvotes: 2

Related Questions