Git And Collaboration Workflows

Interactive Rebase

Interactive rebase edits a local commit series before it becomes shared history.

Why This Matters

Review is easier when commits are ordered, focused, and buildable. Interactive rebase can reorder, squash, fix up, edit, or reword commits without changing the final intended code.

Working Model

git rebase -i <upstream> opens a todo list from oldest to newest. Each instruction controls how Git replays one commit. Autosquash can place fixup! and squash! commits next to their targets.

Practical Rules

  • Create a backup branch before rewriting a complicated series.
  • Use reword for messages and edit when commit contents must change.
  • Use fixup when the intermediate message adds no value.
  • Keep each resulting commit coherent and testable where practical.
  • Review the rewritten range with git range-diff.

Failure Modes

  • Squashing every commit into one unreviewable change.
  • Reordering commits so an early commit depends on code introduced later.
  • Editing shared history without agreement.
  • Treating a clean final diff as proof every rewritten commit is valid.

Verification

  • Inspect each rewritten commit, not only the branch tip.
  • Run tests at the final tip and, for critical series, at intermediate commits.
  • Compare old and new series with range-diff.
  • Confirm commit messages describe intent rather than editing chronology.

What You Should Be Able To Do

After this lesson, you should be able to explain interactive rebase commands and how to turn noisy local history into a coherent review series, choose a suitable approach for a real PHP project, and verify the result instead of relying on assumptions.

Interactive Rebase Edits Local History Deliberately

Interactive rebase lets you rewrite a sequence of commits before they become shared history. You can reorder commits, change messages, squash related commits, split a commit, drop a mistaken commit, or mark a commit for editing. It is a preparation tool for review, not a way to falsify public history.

Use it on commits you own. If a branch is already under review, rewriting may still be fine, but communicate it. If other developers have based work on your commits, coordinate first.

What The Todo List Means

git rebase -i <base> opens a todo list of commits after the base. Commands such as pick, reword, edit, squash, fixup, and drop tell Git how to replay each commit. The list is executed from top to bottom. Reordering lines reorders commits, which can create conflicts or test failures if later commits depend on earlier ones.

Use reword for message changes without touching content. Use squash or fixup when several commits represent one logical change. Use edit when a commit needs content changes or splitting. Avoid dropping commits casually; confirm the change is truly unwanted.

Splitting A Commit

To split a commit, mark it edit. When Git stops, reset the commit's changes back into the working tree with a mixed reset to the parent, then stage and commit smaller pieces. Each new commit should compile or at least represent a coherent step when possible.

Splitting is valuable when one commit mixes unrelated behavior, such as a database migration, formatting cleanup, and a controller change. It lets reviewers understand risk in smaller units. It also makes later revert or cherry-pick operations safer.

Commit Messages Matter

Interactive rebase is a good time to rewrite vague messages. A useful message explains why the change exists and what boundary it affects. fix stuff helps nobody six months later. Validate refund amount before provider capture gives future reviewers a search target and a design clue.

Do not over-polish history until it hides the development reality needed for review. If a commit fixes a bug introduced two commits earlier in the same local branch, squashing may be appropriate. If a commit documents a deliberate change of approach after review, keeping it separate may help.

Autosquash And Fixup Commits

git commit --fixup <commit> creates a fixup commit that interactive rebase can automatically place next to the target when run with --autosquash. This is useful during review: make small fixup commits while responding to comments, then autosquash before final merge if the repository expects tidy commits.

Do not autosquash away review context if the platform depends on incremental commits for re-review. Follow team norms and explain force pushes when they rewrite visible history.

Testing During Rewrites

After interactive rebase, run tests. Reordering or splitting commits can change content even when the final diff looks similar. If the project values bisectable commits, run focused checks at important intermediate commits too. A series where only the final commit works may be acceptable for squash-merge workflows but less useful for repositories that preserve every commit.

Use git range-diff to compare the old and new series after a substantial rewrite. It shows which commits correspond and how patches changed, making review after force-push much more manageable.

Recovery

If an interactive rebase goes wrong, git rebase --abort returns to the pre-rebase branch while the rebase is active. After completion, reflog can find the old branch tip. Creating a temporary safety branch before a major rewrite is simpler than relying on memory.

After this lesson, you should be able to use interactive rebase to prepare local commits for review, reword messages, squash fixups, split mixed commits, test rewritten history, communicate force pushes, and recover when a rewrite goes wrong.

Designing A Reviewable Commit Series

A reviewable series usually moves from foundation to behavior to cleanup. For example, one commit may add a value object and tests, the next may use it in a controller, and a final commit may update documentation. That sequence lets reviewers check each idea separately. Interactive rebase can turn a messy development branch into that shape before review.

Do not split commits so aggressively that the story becomes artificial. A test and the code that makes it pass often belong together. A migration and the code that reads the new column may need separate commits only when deployment requires an intermediate compatible step. The unit of history should match the unit of reasoning.

When responding to review, fixup commits are useful because they preserve the conversation while review is active. Before merge, autosquash them if the repository prefers clean commits. If the platform preserves commit history and reviewers already approved earlier commits, explain any autosquash or reorder so the final review is not guesswork.

Editing Commit Content

When an interactive rebase stops at an edit commit, use normal Git tools carefully. You can amend the commit after changing files, or reset it and create several commits. Always inspect git status before continuing. It is easy to leave a file unstaged and accidentally move part of the intended change into the next commit.

If you split a commit that includes a migration or generated file, keep generated output with the source change that requires it. A commit that updates a schema without its generated client may fail tests, while a generated-only commit may be impossible to understand.

What Not To Rewrite

Do not use interactive rebase on published release history simply to make it look cleaner. History that explains a revert, a production fix, or a security patch is useful. Rewriting it can break tags, confuse deployment records, and force collaborators to repair local clones. Clean local review history is valuable; rewriting shared audit history is usually not.

Do not squash unrelated changes together. A formatting cleanup, dependency update, schema migration, and behavior change may be easier to push as one commit, but harder to review and harder to revert. If the final repository uses squash merges, the pull request description still needs to explain those parts.

Do not edit a commit series until you know the target base. Rebasing interactively onto stale main can produce a beautiful series that immediately conflicts again. Fetch first, choose the correct base, then rewrite.

Practice Exercise

Create a branch with four commits: one good change, one typo fix for the first change, one unrelated change, and one poor commit message. Use interactive rebase to autosquash the fixup, reword the message, and leave the unrelated change separate. Then use git log --oneline and git show to verify the final story.

Repeat by marking a mixed commit for edit and splitting it into two commits. This teaches the mechanics before you need them on an important pull request. During review, prefer a series that lets another developer understand one decision at a time. If a rewrite makes the branch prettier but hides risk, keep the clearer history instead. Use interactive rebase to improve communication, not to erase context that future maintainers need. A good final check is to compare the branch against its merge base and read the commits in order. The diff should match the pull request description, and every commit message should still be true after squashing, reordering, or splitting. If the branch is already approved, tell reviewers exactly which commits changed after the rewrite. That habit protects review continuity during the final approval pass.

Practice

Practice: Clean A Noisy Series

A branch contains implementation, typo fix, test, review fix, and debug-removal commits. Design a meaningful final series.

Your answer must:

  • state the intended outcome;
  • show the commands, data flow, or implementation shape;
  • identify at least one unsafe alternative;
  • explain how the result will be verified.
Show solution

Keep a focused implementation commit, fold its direct fixups into it, retain a separate test commit only if it remains understandable alone, remove debug-only changes, and reword messages around intent.

The important part is not memorising one command or vendor screen. The solution makes the invariant, failure behavior, and verification evidence explicit.

Practice: Split An Overloaded Commit

One local commit mixes a refactor and a behavior change. Explain how interactive rebase can split it.

Your answer must:

  • state the intended outcome;
  • show the commands, data flow, or implementation shape;
  • identify at least one unsafe alternative;
  • explain how the result will be verified.
Show solution

Mark the commit edit, reset it while keeping changes in the working tree, stage and commit the refactor separately, then stage and commit the behavior change. Continue the rebase and verify both commits.

The important part is not memorising one command or vendor screen. The solution makes the invariant, failure behavior, and verification evidence explicit.

Practice: Use Autosquash

A review fix belongs to an earlier local commit. Plan a fixup! workflow.

Your answer must:

  • state the intended outcome;
  • show the commands, data flow, or implementation shape;
  • identify at least one unsafe alternative;
  • explain how the result will be verified.
Show solution

Create the correction with git commit --fixup=<target>, then run an interactive rebase with --autosquash from the appropriate upstream. Inspect the todo order, finish the rebase, and compare the series.

The important part is not memorising one command or vendor screen. The solution makes the invariant, failure behavior, and verification evidence explicit.