简体   繁体   中英

git rebase is not applying a commit

I have a file that simply looks like this:

line 1
line 2
line 3
line 4

I have commits that have done 3 things:

  1. add a 5th line that says line 5
  2. capitalize the L in line 2
  3. make the number in line 2 22

I've done this in 3 separate commits, so I have the following graph:

*   F - add line 5
*   E - Merge branch 'branch' into 'master'
|\
| * D - change 2 to 22
* | C - capitalize line 2
|/
*   B - adding line numbers
*   A - Initial commit

My file now looks like this:

line 1
Line 22
line 3
line 4
line 5

Obviously, the merge at E contained a conflict that I resolved manually. I want to move F so that it falls after B (eventually squashing them, but that's a separate problem). So I did a git rebase -i --preserve-merges A , and the editor opened up, I did this:

pick B adding line numbers
pick F add line 5
pick C capitalize line 2
pick D change 2 to 22
pick E Merge branch 'branch'

The problem is after the rebase, I've lost commit F entirely. My graph now looks like this:

*   E - Merge branch 'branch' into 'master'
|\
| * D - change 2 to 22
* | C - capitalize line 2
|/
*   B - adding line numbers
*   A - Initial commit

F is just gone. It didn't get applied by the rebase. What went wrong, and how can I resolve it?

The prior comments and answers (including my comment) are factually correct as far as why what you're doing may not work, but probably aren't great as far as being useful answers.

First thing is, while the answer "it's a documented bug" may not be very satisfying, I would point out that the re-ordered TODO list in your example doesn't actually tell git what topology you're trying to reproduce. And that's the first clue that what you want to do won't be so easy, because how would you tell git what topology you want? How would you explain to git that you mean for C and D to both change parent from B to F , in a way that couldn't also be read as "put F before C , so that F' and D' each have B as parent"; or "put F before D , so that F' and C' each have B as parent"? Of course it turns out git did none of those things, which I suppose is the buggy bit. (That said, I'll bet it did rewrite F , but then made the parent of F' , C' , and D' all be B , which makes F' ultimately unreachable.)

Anyway, I think you mean to ask just: Can you get from

A -- B - C - E -- F <--(master)
      \     /
       - D -

to

A -- B -- F' - C' - E' <--(master)
            \      /
             - D' - 

without manually re-resolving the merge conflicts at E .

And the answer is, you can; but it won't be as simple as finding the right rebase options and issuing a single command.

The truth is rebase is designed to produce linear histories; its proponents tend to insist that a linear history is "easier to read" or "cleaner", and that this is more important than being "accurate" or "made up of commits that have been tested". Those statements are not objectively universal, but that is what the tool of rebase is designed to do. The preserve-merges option tries to let you have your cake and eat it to, but it has a lot of problems (not just the bug when using it with -i ). It might happen to work for some simple cases, but in general trying to rebase through a merge you want to keep is likely to be a headache at best.

So what to do instead? Here's one way... Note that when referring to a commit I've given an expression that resolves to the correct commit in this example, rather than just using the placeholder letter.

git checkout master
git branch temp
git rebase -i master~4
// in the editor, move F after B and remove D

At this point you should have

       F' -- C' <--(master)
      /
A -- B - C - E -- F <--(temp)
      \     /
       - D -

Then you need to rebase D . (In this case, since it's a single commit, you could possibly do a cherry-pick instead.)

git checkout temp^^2
git checkout -b branch
git rebase --onto master^ master branch

At this point you have

         D' <--(branch)
        /
       F' -- C' <--(master)
      /
A -- B - C - E -- F <--(temp)
      \     /
       - D -

and you need to reproduce the merge. There are again several ways to look at this problem, but the simplifying observation is that your end content should match what you had at F in the original history. So a valid solution is

git checkout master
git reset --hard $(git commit-tree -p master -p branch -m "Merging branch into master" temp^{tree})

(For the -m you could give whatever commit message was originally at E .) And then you can delete the temp branch and, if you're done with it, the branch branch.

In more complex scenarios, this little cheat of grabbing the merge result TREE from an existing commit may not work, because maybe no existing commit matches the intended state. In that case I think your best bet would be to try git rerere (documented at https://git-scm.com/docs/git-rerere ). This command's purpose is to record the resolution to a merge conflict so it can be reapplied if the same conflict is seen in a different merge, and I would think it could be applied here. (That said, I never use it since I disagree with the premises that lead to a workflow that routinely requires it, so I can't speak to exactly how well it will server here.)

I should add that if the merge were non-conflicting (and non-"evil"; ie the default merge strategy and options produce the desired result), and remain non-conflicting under the desired re-ordering, then the following procedure would work in place of the above:

    // first rewrite F
git checkout master
git checkout -b temp
git rebase --onto master~3 master^
    // now move master to exclude the original F
git checkout master
git reset --hard HEAD^
    // now move the rest of the history
git rebase --preserve-merges temp

So it may be that, with rerere in place, this (conceptually simpler) approach might work even in a conflicting case.

There is a warning inside the git documentation for rebase that the preserve-merges option should not be used together with the interactive mode. There is also a bug mentioned in the doc for this combination:

BUGS The todo list presented by --preserve-merges --interactive does not represent the topology of the revision graph. Editing commits and rewording their commit messages should work fine, but attempts to reorder commits tend to produce counterintuitive results.

For example, an attempt to rearrange...

See https://git-scm.com/docs/git-rebase

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM