0

I think that my title is not clear enough, so I will describe the problem:

In my git project, I have 3 branches: master, b1, and b2. Here is the history tree:

 A---B---C  master
          \
           D---E---F  b1
                    \
                     G---H---I  b2

Suppose I want to rebase the b1 branch and edit the E commit. After rebasing, commits E and F will be replaced by new commits E' and F' which have different commit SHAs. Therefore, the history in b1 will be different from history in b2:

 A---B---C  master
           \
             D---E'---F'  b1
                     
 A---B---C---D---E---F---G---H---I  b2

So my question is: how to make sure that b2 follows b1 (automatically gets the same new commits as b1) after rebasing b1 so that their respective histories stay coherent.

Sopsop
  • 327
  • 2
  • 10

3 Answers3

1

After your first rebase, it's not this:

 A---B---C  master
           \
             D---E'---F'  b1
                     
 A---B---C---D---E---F---G---H---I  b2

but rather this:

A---B---C  master
         \
          D---Ea---F'  b1
           \      
            E---F---G---H---I  b2

Here, Ea means the amended E commit. And you want this, if I understand correctly:

A---B---C  master
         \
          D---Ea---F'  b1
                    \      
                     G'---H'---I'  b2

You can achieve this using an interactive rebase:

git checkout b2
git rebase -i b1

In the rebase edit you comment out the lines for commit E:

# pick df8efe6 E
pick a7fcbed G
pick 936b51a H
pick c77ca69 I

# ...
# Commands:
# p, pick = use commit
# ...
# If you remove a line here THAT COMMIT WILL BE LOST.

Responding to your comment, if you want to go all the way from the starting position (ABCDEFGHI) to the desired end positon:

git checkout b2
git rebase -i master

In the editor:

pick 1234567 D
edit a7fcbed E
pick 936b51a F
pick c77ca69 G
pick 1e8d614 H
pick 8daafa7 I

# ...
# p, pick = use commit
# e, edit = use commit, but stop for amending
# ...

When done, correct the b1 branch:

git checkout b1
git reset --hard 936b51a

You can also look at the other answers for inspiration. I'm not sure why you want to achieve all of this in a single command; you still have to amend commit E somewhere in the process. It saves you one interactive rebase to do it this way.

Han-Kwang Nienhuys
  • 3,084
  • 2
  • 12
  • 31
  • one extra note : when rebasing `b2` on `b1`, git should detect that `F` and `F'` introduce the same modifications ; in that case, `F` will already be dropped from the `rebase -i` list. – LeGEC Jul 08 '20 at 21:44
  • @LeGEC The rebase list (including E and F) is what I actually got when I tested this. Probably because the changed lines in te E->E' diff were to close to te D->E diff. Oh wait, I amended both E and F, that's why. Answer edited. – Han-Kwang Nienhuys Jul 08 '20 at 21:56
  • It worked, thank you! Now I would like to know if there is a way to do the whole process in one single command: rebase `b1` and automatically rebase `b2` according to `b1` changes. – Sopsop Jul 08 '20 at 22:22
1

If you only have two branches, with a linear history as in your diagram :

I would rebase b2 instead of b1 :

 A---B---C  master
          \
           D---E'---F'
                     \
                      G'---H'---I'  b2

and then update b1 :

git branch -f b1 F'

@Han-KwangNienhuys answer is perfectly valid, and you can apply it if you need to update b2 when b1 has already been rewritten.

LeGEC
  • 46,477
  • 5
  • 57
  • 104
0

So my question is: how to make sure that b2 follows b1 (automatically gets the same new commits as b1).

You need to rewrite b2's history too, you're rebasing all of the master..b2 commits and rehanging the b1 and b2 labels.

Interactive rebase lets you execute arbitrary commands, notably including git branch, after each step. So do

git rebase -i master b2

and in the pick list after the current b1 commit say exec git branch -f b1.

You could automate this, pipe the picklist buffer through

while read command commit rest; do case $command in 
*) printf %s\\n "$command${commit:+ $commit${rest:+ $rest}}" ;;&
pick) git for-each-ref refs/heads --points-at $commit \
        --format='exec git branch -f %(refname:short)' ;;
esac; done

and it'll add the exec git branch -f's after every pick for a branch tip. I've just built and tried this. Season to taste, of course.

If you're rebasing deep into a branched history, it's probably better to just construct the exact cherry-pick and branch sequence you want; rebase is a convenience command meant to ease a common task. Like all good tools it can be pushed a little farther than you might expect, but it's got its limits.

jthill
  • 55,082
  • 5
  • 77
  • 137