Rebase over some branch with ignoring previusly dropped commit(s)

39 Views Asked by At

I have two branches A and B, the difference between those branches that B has some dropped commits from A (other one commits are identical).

When I have make some new commits in branch A I want merge them intro branch B, but left dropped/modificated commits still to be dropped/modificated (in another words: cherry-pick only new commits from A into B).

When I do:

git rebase A

it not only rebase new commits but also resore dropped ones, how to avoid it?

is it possible to achive it using rebase or other ways, maybe possible to indificate same commit in two branches and start cherry picking since it?

Example:

A: 1 2 3 4 5 6
B: 1   7 4

Expected Result A -> B:

A: 1 2 3 4 5 6
B: 1   7 4 5 6

So we keep updating B branch with new commits comming into A branch while B branch modificated copy of A

2

There are 2 best solutions below

0
DEgITx On

I have found one solution, idea behind this that we using

git checkout A
git rebase --onto B A_since_commit B

A_since_commit = 4 in question above

while we located in A branch. We taken current B branch and took commits since A_since_commit from A branch and rewrite A branch by modificated B branch. Since we don't want to rewrite current branch, we need temporary one.

Solution:

# creation copy of A branch and checkout _temp_
git checkout -b _temp_ A
# take commits from A
git rebase --onto B $COMMIT_ID
# Replace old branch with temporary and remove old one
git checkout B
git reset --hard _temp_
git branch -D _temp_

We can also detect last same commit with different ID in parent A branch, so full solution (bash):

BRANCH_A="A"
BRANCH_B="B"

# Detect same commit id in parent branch (can be improved)
git checkout $BRANCH_B
COMMIT_MESSAGE=$(git log -1 --pretty=%B)
COMMIT_ID=$(git log $BRANCH_A --grep="$COMMIT_MESSAGE" --pretty=format:"%h" --no-patch)

git branch -D _temp_
git checkout -b _temp_ $BRANCH_A
git rebase --onto $BRANCH_B $COMMIT_ID
git checkout $BRANCH_B
git reset --hard _temp_
git branch -D _temp_
0
Kaz On

Remember that git rebase is a script which uses cherry-pick. When you rebase, you are cherry-picking commits. But you don't get to pick which ones. However, there is a form of rebase where you do get to pick commits: the interactive rebase (git rebase -i or git rebase --interactive). In interactive rebase, you can delete commits you don't want.

However, if B deletes some commits relative to A, it means that B has diverged from A permanently. You cannot rebase B past the difference where it has rolled back A commits, so that it can then keep tracking A.

To rebase B means to move it forward along the A stream, so that its branch point is based on a more recent commit of A. Everything behind the branch point of B is a shared ancestry: identical between A and B. The shared parentage cannot look different in B (with deleted commits) and A (all commits present). Impossible.

To move B to a new branch point on A, past the unwanted commits, and not have those unwanted commits in B requires that those commits must be explicitly reverted on B. In other words B needs a commit (or multiple commits) whose effect it is to revert the unwanted A commits.

The reverts in B are forever divergent from A (or, for as long as they are not upstreamed into A!). Each subsequent rebase of B will be rebasing those reverts again and again. New conflicts can show up. That's the prospect if B needs to be maintained long-term.

           *--*-- B
       P0/
--*--*--*--U0--U1--U2--W0--A
                        ^       W0 is an A commit wanted in B
            ^^^^^^^^^           U0 to U2 are not wanted in B.

At this point, B could cherry-pick W0. We call it W0' since it's the cherry-picked W0, not the same commit:

           *--*-- W0' B
       P0/
--*--*--*--U0--U1--U2--W0--A

Now suppose our goal is to get past this, to migrate B to a new branch point P1:

           ... old B       ... B
       P0 /            P1/
--*--*--*--U0--U1--U2--W0--A

You can see that it's logically inescapable that B has U0 through U2 in its ancestry, if it is to be rebased to point P1. So B has to look something like this:

           ... old B       RU0-U2 --*--* B
       P0 /            P1/
--*--*--*--U0--U1--U2--W0--A

On the new, rebased B branch, there is a RU0-U2 commit that reverts U0 through U1. W0 is picked up because it is a common ancestor; the branch point P1 points to W0. Then the --*--* are the B-specific commits we had in the first place; they are now rebased on top of W0 and the revert. The reverts don't have to be one commit; each commit can have an individual revert.