简体   繁体   中英

git rebase commit by commit

I have a branch of 10 commits. Now master changed and I want to rebase my branch to the new master's HEAD. However, some of the new master commits make even the first commit of my branch uncompilable (without merge conflicts). Rebasing a branch seems pointless to me if all of your commits are not compilable in the end.

What I'd like to do is tell get "do a rebase, but wait after rebasing of each commits from my branch". That way, I can detect if the code still compiles after each commit, make it compile (if required), and then commit and continue.

Is this possible? Are there alternatives for a clean rebase, such that each commit will still compile?

Yes, this is possible.

The simplest method is probably to use git rebase -i and change each pick to edit . After Git does each of the cherry-pick operations, it will drop back to the command line. You can now attempt the build (and run tests) and when something fails, fix it up. Once everything is good, run git add -u and git commit --amend as appropriate, then git rebase --continue to apply the next commit in sequence.

After you've become familiar with this method (don't start with this right away!) consider using --exec as well, if your Git supports it.

Note: if a commit to be copied—regardless of whether it is marked pick or edit —has a merge conflict, git rebase stops with that merge conflict. Even if you did mark it edit , Git won't stop again after resolving the conflict, so if you must resolve something as well as change something else, you should do both at once here (and do not use git commit --amend as you are still making the first copy).

Long details in case you're interested / curious

It's physically impossible for Git to alter any existing commit, because the true name of each commit—its hash ID—is a cryptographic checksum of all of the contents of that commit, including your name as author/committer, the time stamp, and of course all of the source code that goes with the snapshot.

It is, however, easy enough for Git to copy a commit—well, as long as it's an ordinary non-merge commit, with a single parent. Given some commit whose hash ID is H , Git can turn H , which is a snapshot, into a change-set against H 's parent. Think of this as what git show does: it extracts the parent's snapshot, then extracts the commit's snapshot, and whatever is different between them ( git diff <parent-of-H> <H> ), that's what changed . One can then apply these same changes to some other commit, by checking out that other commit, and then commit the result. That is, if we have two branches branch1 and branch2 , we can check out the tip commit of the second branch and create a new, temporary branch:

...--G--H--I--J   <-- branch1
      \
       K--L   <-- branch2, temp (HEAD)

We then turn commit H into a change-set, apply the change-set to commit L , and make a new commit that has a new and different hash ID. This new commit is so much like H that we call it H' for illustration:

...--G--H--I--J   <-- branch1
      \
       K--L   <-- branch2
           \
            H'  <-- temp (HEAD)

This copy operation is a git cherry-pick . (Technically each cherry-pick operation is a full three-way merge, rather than just applying a change-set-style patch, but we don't need to worry about this unless and until various complications occur.)

If we repeat the cherry-picking for all the commits that were on branch1 we get this:

...--G--H--I--J   <-- branch1
      \
       K--L   <-- branch2
           \
            H'-I'-J'  <-- temp (HEAD)

and if we now "peel the label" branch1 off J and attach it to J' instead, discarding the temporary name entirely, we end up with:

...--G--H--I--J   [abandoned]
      \
       K--L   <-- branch2
           \
            H'-I'-J'  <-- branch1 (HEAD)

Rebasing is therefore really a series of cherry-pick operations. An interactive rebase make this explicit: each commit hash ID turns into a pick command in a script.

Changing the pick to edit tells Git that after doing each of the various cherry-pick steps, it should stop and return to the command line. (Note that Git also stops and returns to the command line, all on its own, if there is a merge conflict.) Running git rebase --continue tells Git to consult the control files that this leaves behind—these are managed by something Git internally calls the sequencer —to see what other commit(s) still need whatever sort of handling. Hence if you mark every commit as "to be edited", Git stops after copying H to H' , so that you have this:

...--G--H--I--J   <-- branch1
      \
       K--L   <-- branch2
           \
            H'  <-- HEAD

(I've updated the drawing to rely on the way Git really does this: rather than attempting to invent a temporary branch name, Git uses "detached HEAD" mode to do the copying.)

At this point, if you make changes and run git add -u && git commit --amend , Git makes yet another new commit—let's call this H-prime-prime or H" —whose parent is the same as H' 's parent:

...--G--H--I--J   <-- branch1
      \
       K--L   <-- branch2
          |\
          | H'  [abandoned]
           \
            H"  <-- HEAD

When you now run git rebase --continue , Git cherry-picks commit I to I' appended to H" , and then—because you said "edit"—stops yet again:

...--G--H--I--J   <-- branch1
      \
       K--L   <-- branch2
          |\
          | H'  [abandoned]
           \
            H"-I'  <-- HEAD

and so on.

当您进行交互式rebase时, edit选项允许您暂停提交,使用git commit --amendgit commit --amend进行任何修改,然后使用git rebase --continue继续进行下一个git rebase --continue

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