1

I have a git repo that looks like this:

   /C''-D''-F (featureB)
A-B-C---D---E (main)
   \C'--D'--G (featureA)

I would like to move the common commits C and D earlier in the main branch history so that the repo looks like this:

       /F (featureB)
A-B-C-D-E (main)
       \G (featureA)

It is important to note that the C/C'/C'' and D/D'/D'' commits modify the code identically, but due to being in different branches, they all have different commit hashes.

knittl
  • 246,190
  • 53
  • 318
  • 364
procyon
  • 31
  • 7
  • 1
    Are C and D actually the same exact commit, with the same commit hashes, in all three branches? If so, it's not clear that there's a difference between your two situations. A branch is really just a list of commits, and `featureB` for example has the same list in both examples. So you might want to clarify your question. Also: try to make your question as specific as possible; nobody here wants to sit down and write a general `git rebase` tutorial for you when there's plenty of good [documentation](https://git-scm.com/docs/git-rebase) that you could read. – Caleb Jul 17 '21 at 20:48
  • @Caleb: They modify the code identically but have different commit hashes across the three branches. – procyon Jul 17 '21 at 20:51
  • @Caleb: Thanks I will clarify the question to mention that `C` and `D` have different commit hashes. – procyon Jul 17 '21 at 20:53
  • 1
    It's better to give them different names to make that clear, usually we use `C'` to designate a commit that is basically the same as C, but not quite. – knittl Jul 18 '21 at 06:16

2 Answers2

4

Let's rephrase the task at hand:

I want to take the changes of the N latest commits of a branch and move/copy them in a way that their parent commit becomes a different one.

This is what git rebase allows you to do. You can tell it which range of commits to copy and onto which parent commit you want to copy it:

git rebase --onto main^ featureA^ featureA
git rebase --onto main^ featureB^ featureB

will rebase the latest commit of featureA and featureB onto the second last commit of branch main. You can also use the commit hashes directly:

git rebase --onto D D' featureA
git rebase --onto D D'' featureB
knittl
  • 246,190
  • 53
  • 318
  • 364
  • Thanks for your answer, @knittl. It seems however that `git rebase main featureA` results in `error: could not apply 697g054... G`. I think git rebase is trying to merge the commit `G` at the end of `featureA`, even though I still would like `G` to be on a separate branch as shown in the second diagram. Is there a way we can modify your commands to enable this? – procyon Jul 17 '21 at 23:26
  • 1
    I think @procyon wants to rebase `--onto main~1` or `main^` (commit `D`-on-`main`), not onto `main` itself (commit `E`). – torek Jul 18 '21 at 05:41
  • 1
    @torek you are right, I selected the wrong target. Let me quickly fix that :) – knittl Jul 18 '21 at 06:09
  • knittl, your latest answer works great! Thanks to you and @torek for rephrasing my question to show what I actually meant :) – procyon Jul 18 '21 at 23:42
1

[C and D]...modify the code identically but have different commit hashes across the three branches.

In that case you'll probably want to first remove the C and D commits from your two feature branches, and then rebase to the main branch. You can use git rebase -i to do the first part, using the drop option for the the commits you don't want. Then (from each branch) git rebase main to replay the commits in each branch on top of the current state of the main branch.

Caleb
  • 124,013
  • 19
  • 183
  • 272
  • Thanks for your answer, @Caleb. It seems however that `git rebase main` after dropping `C` and `D` from `featureA` using `git rebase -i` results in `error: could not apply 697g054... G`. I think git rebase is trying to merge the commit `G` at the end of `featureA`, even though I still would like `G` to be on a separate branch as shown in the second diagram. Is there a way we can modify your commands to enable this? – procyon Jul 18 '21 at 00:22
  • That sounds like you've got a merge conflict; is it possible that `C` and `C'` or `D` and `D'` aren't actually *exactly* identical, even if they're effectively the same? Even differences in white space could be a problem. In any case, if there's a conflict, you should also see a message about that and an instruction to run `git rebase --continue` once you've fixed the problem. Run `git status` to figure out which file(s) are affected, and then look for merge markers (e.g. `<<<<<`, `>>>>>`) in them. There are definitely existing SO questions about resolving merge conflicts, so check those out. – Caleb Jul 18 '21 at 07:14