Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Rebasing commit from one parent onto another

I have an existing repository that I'm working on, yet recently I've learned a lot about good practices and the git itself. I wanted to shake my history to my liking, especially some early commits.

Below is the fragment of the current history. I want to rebase the e5cb9b8 commit onto the 1a92e84 so I can have just one/two levels of depth in the history graph (visually, changing the parent of e5cb9b8). I've tried rebasing

git rebase -p --onto 1a92e84 e5cb9b8 master

and cherrypicking into new branch created from 9ecbe00. Cherrypicking gets me flat history, rebase fails with the following message

error: commit ca230d8c048d22de6f219da8a22e70a773827c38 is a merge but no -m option was given.

fatal: cherry-pick failed

Could not pick ca230d8c048d22de6f219da8a22e70a773827c38

* | 45a0a21 - (7 weeks ago) #17 Updates README.md - Kamil Pacanek
|/
*   ca230d8 - (8 weeks ago) Merge pull request #2 from KamilPacanek/feat/rmb-support-removing-parts - Kamil Pacanek
|\
| * e5cb9b8 - (8 weeks ago) Adds support for removing parts on ReactorCells - KamilPacanek
* |   1a92e84 - (8 weeks ago) Merge pull request #1 from KamilPacanek/enable-gh-pages - Kamil Pacanek
|\ \
| |/
| * 81761ff - (8 weeks ago) Adds GH Pages support - KamilPacanek
|/
* 9ecbe00 - (8 weeks ago) Initial commit - KamilPacanek

Expected:

* | 45a0a21' - (7 weeks ago) #17 Updates README.md - Kamil Pacanek
|/
*   ca230d8' - (8 weeks ago) Merge pull request #2 from KamilPacanek/feat/rmb-support-removing-parts - Kamil Pacanek
|\
| * e5cb9b8' - (8 weeks ago) Adds support for removing parts on ReactorCells - KamilPacanek
|/
*    1a92e84 - (8 weeks ago) Merge pull request #1 from KamilPacanek/enable-gh-pages - Kamil Pacanek
|\ 
| |
| * 81761ff - (8 weeks ago) Adds GH Pages support - KamilPacanek
|/
* 9ecbe00 - (8 weeks ago) Initial commit - KamilPacanek

I've searched the StackOverflow for similar issues and it seems like no one described such modifications so deep into the history.

Solution

Ok, for future readers and myself I'm writing down the solution for my problem. @alfunx answer was a key to solve it - I've tracked down in other topic explanation of git rebase and git rebase --onto and started to experiment on my repository. Through trial and error, I've managed* to achieve the expected history graph by executing

git rebase -ir --onto 1a92e84 81761ff develop

and replacing followig fragment of todo-list

label onto

# Branch KamilPacanek/feat/rmb-support-removing-parts
reset onto
pick e5cb9b8 Adds support for removing parts on ReactorCells
label KamilPacanek/feat/rmb-support-removing-parts

# Branch enh/add-uranium-cells
reset 9ecbe00 # Initial commit
merge -C 1a92e84 onto # Merge pull request #1 from KamilPacanek/enable-gh-pages
merge -C ca230d8 KamilPacanek/feat/rmb-support-removing-parts # Merge pull request #2 from KamilPacanek/feat/rmb-support-removing-parts
label branch-point

with following (changed the reset position and removed one, redundant, merge commit):

label onto

# Branch KamilPacanek/feat/rmb-support-removing-parts
reset onto
pick e5cb9b8 Adds support for removing parts on ReactorCells
label KamilPacanek/feat/rmb-support-removing-parts

# Branch enh/add-uranium-cells
reset onto
merge -C ca230d8 KamilPacanek/feat/rmb-support-removing-parts # Merge pull request #2 from KamilPacanek/feat/rmb-support-removing-parts
label branch-point

*) Actually, there was more to do to achieve that state, but I don't want to obfuscate the solution with secondary issues. First git rebase failed with that message:

error: refusing to update ref with bad name 'refs/rewritten/Implement-durability-loss.'

hint: Could not execute the todo command

hint: label Implement-durability-loss.

As you can see I have a period at the end of the commit message. After I solved that (with another rebase with reword command) I was able to move forward.

Also, it seems like git rebase --abort doesn't clear the refs that are created during git rebase -r.. so I had a mess in history. Solved that one via rm -rf .git/refs/rewritten. Another thing, I had a leftover .git/sequencer folder that was removable via git revert --quit.

like image 342
Kamil Gierach-Pacanek Avatar asked May 29 '19 13:05

Kamil Gierach-Pacanek


People also ask

Can you rebase a commit?

What is git rebase? From a content perspective, rebasing is changing the base of your branch from one commit to another making it appear as if you'd created your branch from a different commit. Internally, Git accomplishes this by creating new commits and applying them to the specified base.

What is the golden rule of rebasing?

The Golden Rule of Rebasing reads: “Never rebase while you're on a public branch.” This way, no one else will be pushing other changes, and no commits that aren't in your local repo will exist on the remote branch.

How do you rebase a child branch with the parent branch?

The solution is to use git rebase --onto after rebasing the first branch. This will rebase all commits of feature2 that follow the old head of feature1 (i.e. F ) onto the new head of feature1 (i.e. F' ).

What are the risks of rebasing?

Rebasing can be dangerous! Rewriting history of shared branches is prone to team work breakage. This can be mitigated by doing the rebase/squash on a copy of the feature branch, but rebase carries the implication that competence and carefulness must be employed.


2 Answers

The base you chose for the rebase is wrong and should be 81761ff instead of e5cb9b8. I would suggest you to do an interactive rebase and use --rebase-merges instead of --preserve-merges. So the command should be:

git rebase -ir --onto 1a92e84 81761ff master

Now, Git will probably produce one additional merge commit. To avoid that and to produce your desired outcome, you should adjust the todo-list to something like:

label onto
pick e5cb9b8 Adds support for removing parts on ReactorCells
label new
reset onto
merge -C ca230d8 new
pick 45a0a21 #17 Updates README.md
# Remaining commits...

In the todo-list, label can be used to mark the current commit (HEAD), and reset can be used to set the HEAD to some commit/label. merge is obviously used to produce a merge commit, -C makes the merge use the same commit message as the original merge commit.

This is quite advanced and really not needed for most users and use cases, so please consider man git-rebase, section Rebase Merges (or the online version here) for more in depth information. The example there is actually quite similar to your situation.

like image 105
alfunx Avatar answered Oct 18 '22 02:10

alfunx


If you go with git rebase -ir, do so with Git 2.25 (Q1 2020).

A label used in the todo list that are generated by "git rebase --rebase-merges" is used as a part of a refname; the logic to come up with the label has been tightened to avoid names that cannot be used as such.

See commit cd55222 (17 Nov 2019) by Matthew Rogers (soniqua).
See commit 867bc1d (17 Nov 2019) by Johannes Schindelin (dscho).
(Merged by Junio C Hamano -- gitster -- in commit 917d0d6, 05 Dec 2019)

rebase -r: let label generate safer labels

Signed-off-by: Matthew Rogers
Signed-off-by: Johannes Schindelin

The label todo command in interactive rebases creates temporary refs in the refs/rewritten/ namespace. These refs are stored as loose refs, i.e. as files in .git/refs/rewritten/, therefore they have to conform with file name limitations on the current filesystem in addition to the accepted ref format.

This poses a problem in particular on NTFS/FAT, where e.g. the colon, double-quote and pipe characters are disallowed as part of a file name.

Let's safeguard against this by replacing not only white-space characters by dashes, but all non-alpha-numeric ones.

However, we exempt non-ASCII UTF-8 characters from that, as it should be quite possible to reflect branch names such as ↯↯↯ in refs/file names.


And:

With Git 2.25 (Q1 2020), The logic to avoid duplicate label names generated by "git rebase --rebase-merges" forgot that the machinery itself uses "onto" as a label name, which must be avoided by auto-generated labels, which has been corrected.

See commit e02058a (18 Nov 2019) by Doan Tran Cong Danh (congdanhqx-zz).
(Merged by Junio C Hamano -- gitster -- in commit 995b1b1, 05 Dec 2019)

sequencer: handle rebase-merges for "onto" message

Signed-off-by: Doan Tran Cong Danh
Acked-by: Johannes Schindelin

In order to work correctly, git rebase --rebase-merges needs to make initial todo list with unique labels.

Those unique labels is being handled by employing a hashmap and appending an unique number if any duplicate is found.

But, we forget that beside those labels for side branches, we also have a special label onto for our so-called new-base.

In a special case that any of those labels for side branches named `onto', git will run into trouble.

Correct it.

like image 33
VonC Avatar answered Oct 18 '22 01:10

VonC