Corpus Gigantus
Corpus Gigantus

Reputation: 595

What to do with changes after git rebase?

I am rebasing one branch feature on another branch master. During the rebase some files are auto-merged in a way that causes compilation errors when the rebase is complete. I fix these errors on the now-rebased feature branch and thus have a set if changed files.

Ideally I would like the commit log to only hold commits from master, then commits from feature (and no other commits), but the changes have to go somewhere.

What is your "best practice" of handling such changes resulting from rebasing? Do you put them in a separate commit on feature? Squash them into an existing commit on feature (I'm going to force push anyway)?

Or is there a way to avoid this problem altogether? E.g., is there a way that I can stop the rebasing after the last commit from feature is applied, but before git registers the rebasing as completed? If there is I could build and fix at this time and then do a final git rebase --continue

Upvotes: 0

Views: 5476

Answers (1)

torek
torek

Reputation: 487893

Remember that rebase works by copying commits. The original commits remain unchanged. For instance, suppose you have the following series of commits:

...--G--H--K--L   <-- master
         \
          I--J   <-- feature

Here, Alice made commits I and J one after the other atop commit H (each of these letters stands in for some big ugly Git hash ID). Around the same time (shortly before and/or afterward), Bob made commits K and L atop master.

Someone—the best case is usually that this "someone" is Alice herself—is now given the job of coming up with some new and improved commits, which we will call I' and J', that are a great deal like commits I and J but which:

  • come after K-and-L;
  • have different snapshots because they are based on K and L; and
  • have any other changes needed to make them work.

Alice can run:

git checkout feature
git rebase master

and Git will do its best to make new commits I' and J' on its own:

                I'-J'  <-- feature
               /
...--G--H--K--L   <-- master
         \
          I--J   [abandoned]

If Git thinks it's going badly, Git will stop and demand that Alice fix any merge conflicts that Git has detected during this copying process, before Git moves the branch name feature to locate commit J'. But Git is not very smart, and if Git thinks everything is going smoothly, Git will just run right on ahead and finish the rebase, producing these copies that don't actually work.

If that's the case, it's Alice's job (or whoever is doing the rebase) to control Git. She can use git rebase -i and replace one or all pick commands with edit commands instead, for instance. Git will then stop after each cherry-pick—remember that rebase's commit copying is a series of git cherry-pick operations—and allow Alice to fix up the commit. She can build and test the system, locate any bugs, make some edits, and run git add and git commit --amend as necessary to repair them, replacing, e.g.:

                I'  <-- HEAD
               /
...--G--H--K--L   <-- master
         \
          I--J   <-- feature

with:

                I'  [abandoned]
               /
              | I"  <-- HEAD
              |/
...--G--H--K--L   <-- master
         \
          I--J   <-- feature

and then run git rebase --continue to have Git go on to cherry-pick J to make J' and then stop again due to another edit:

                I'  [abandoned]
               /
              | I"-J'  <-- HEAD
              |/
...--G--H--K--L   <-- master
         \
          I--J   <-- feature

Should it be necessary, Alice can now use git commit --amend as before (along with the pre-commit steps) to replace J' with J".

The final result is, presumably a working series of replacement commits. When the rebase is complete, Git moves the name feature to point to the last of the copied commits:

                I'  [abandoned]
               /
              | I"-J'  <-- feature
              |/
...--G--H--K--L   <-- master
         \
          I--J   [abandoned]

Removing the abandoned (and invisible) commits from this picture, we now have a completely working updated feature. The fact that the hash IDs of new copies I" (modified from I' to make it work) and J' (which worked right off the bat after all) differ from those of the original I and J is equally invisible since no human ever notices these hash IDs.

(Note, however, that Git notices the hash IDs—in fact, those are what Git really cares about—so it's important that Alice and everyone else be careful not to reintroduce the original I and J commits. Only Alice has any temporary commits made during the rebase-and-edit procedure, so only Alice has to be careful not to reintroduce those, and it's unlikely that Alice will do so accidentally. But if commits I and J were given out to other Git users, and those other Git users have them now, those other Git users could easily reintroduce I and J accidentally.)

This is not your only option

If you're Alice, you can:

  • do the entire rebase all at once;
  • check for errors only once, at the end;
  • localize those errors; and
  • use git rebase -i to pick specific commits to modify.

You can also make "fixup" commits with git commit --fixup, then use git rebase -i --autosquash to have Git helpfully update the interactive rebase command sheet. This is a more advanced way to achieve the same result as above.

(Note that it's easy to miss a bad intermediate commit this way, if the final commit somehow fixes it up.)

Additional rebase tricks

If you have automated tests that will find these problems—this can be as simple as a failed compilation, perhaps—consider adding -x command to a rebase. This uses the interactive machinery—you can use git rebase -i here, in other words—to run a command after each pick (or pick-and-fixup etc; see the documentation for details). If the supplied command fails, the rebase will stop automatically. If not, it will go on to the next pick.

Having good automated tests is extremely helpful here.

If the tests are very slow, consider doing the full rebase followed by a single test, and if that fails, using git bisect to find the failure point.

Upvotes: 3

Related Questions