Advanced Git: Squashing the Last N Commits Explained – wiki基地

Advanced Git: Squashing the Last N Commits Explained

Git’s power lies not just in tracking changes but also in its flexibility to refine commit history. One such advanced technique is “squashing,” which allows you to combine multiple smaller commits into a single, more meaningful one. This practice is invaluable for maintaining a clean, linear, and understandable project history, especially before merging feature branches or submitting pull requests.

Why Squash Commits?

Squashing commits offers several benefits:

  1. Cleaner History: Consolidate work-in-progress, minor fixes, or experimental commits into logical units.
  2. Easier Review: A single, well-described commit is much easier for reviewers to understand than a series of fragmented changes.
  3. Simplified Revert: If a set of changes introduces a bug, reverting a single squashed commit is simpler than reverting multiple individual commits.
  4. Meaningful Commits: Each commit tells a complete story, representing a single, atomic change or feature.

The Interactive Rebase: Your Squashing Tool

The primary method for squashing commits in Git is through an interactive rebase. This powerful tool allows you to rewrite commit history, including reordering, editing, deleting, and, most importantly, squashing commits.

Step 1: Initiate the Interactive Rebase

To begin, you need to tell Git which commits you want to rebase. If you intend to squash the last N commits, you’ll use the git rebase -i HEAD~N command.

For instance, to squash the last three commits on your current branch, you would execute:

bash
git rebase -i HEAD~3

Upon running this command, Git will open your default text editor (often Vim or Nano) displaying a list of the last N commits. The commits are presented in reverse chronological order, meaning the oldest commit in the rebase range appears at the top.

A typical view might look like this:

“`
pick 0eaeb7c Commit A: Initial feature setup
pick 12a660d Commit B: Add some UI elements
pick 6d3940e Commit C: Fix a typo in the new UI

Rebase 8d2b123..6d3940e onto 8d2b123 (3 commands)

Commands:

p, pick = use commit

r, reword = use commit, but edit the commit message

e, edit = use commit, but stop for amending

s, squash = use commit, but meld into previous commit

f, fixup = like “squash”, but discard this commit’s log message

x, exec = run command (the rest of the line) using shell

b, break = stop here (continue rebase later with ‘git rebase –continue’)

d, drop = remove commit

l, label

t, reset

m, merge [-C | -c ]

. create a merge commit using the original merge commit’s

. message (or the object referred to by ); use -c to

. edit the commit message

These lines can be re-ordered; they are executed from top to bottom.

If you remove a line here WITHOUT changing its word, the commit will be dropped.

However, if you remove an “exec” line, the command will NOT be run.

“`

Step 2: Mark Commits for Squashing

In the editor, you’ll modify the command preceding each commit hash to dictate its fate.

  • pick: This command keeps the commit as a distinct entity. You should typically pick the oldest commit in your selected range, as this will become the base for your squashed commit.
  • squash (or s): Change pick to squash (or the shorthand s) for any subsequent commits that you wish to combine into the previous pick‘ed commit. This will merge the changes from the squash‘ed commit into the pick‘ed one and allow you to combine their commit messages.
  • fixup (or f): Similar to squash, but fixup automatically discards the commit message of the fixup‘ed commit, using only the message of the pick‘ed commit. This is useful for small, corrective changes where the commit message isn’t crucial.

To squash “Commit B” and “Commit C” into “Commit A” from our example, you would edit the file to look like this:

pick 0eaeb7c Commit A: Initial feature setup
squash 12a660d Commit B: Add some UI elements
squash 6d3940e Commit C: Fix a typo in the new UI

Step 3: Save and Close the Editor

After you’ve made your desired changes to the commit list, save the file and close the editor. If you are using Vim, you would typically press Esc, then type :wq, and finally press Enter.

Step 4: Edit the Combined Commit Message

Git will then open another editor window. This window presents the concatenated commit messages of all the commits you marked for squashing. This is your opportunity to craft a single, coherent commit message that accurately summarizes all the combined changes.

For example, the default message might look like:

“`

This is a combination of 3 commits.

The first commit’s message is:

Commit A: Initial feature setup

This is the 2nd commit message:

Commit B: Add some UI elements

This is the 3rd commit message:

Commit C: Fix a typo in the new UI
“`

You would then edit this to something more concise and descriptive, like:

“`
feat: Implement initial user interface with basic styling

This commit introduces the foundational UI elements for the new feature,
including layout, initial styling, and basic interactive components.
Includes minor corrections for visual consistency.
“`

Once you’re satisfied with your new commit message, save and close this editor as well.

Step 5: Verify Your Changes

After the rebase successfully completes, Git will notify you. To confirm that your commits have been squashed as intended, use git log --oneline:

bash
git log --oneline

You should now see a cleaner history, with your squashed commits appearing as a single entry.

Important Considerations and Best Practices

  • Force Pushing (git push --force or git push --force-with-lease): If the commits you squashed have already been pushed to a remote repository, you have effectively rewritten history. A regular git push will likely fail. You will need to force push your changes.
    • git push --force: This command overwrites the remote branch with your local history. Use with extreme caution, especially on shared branches, as it can erase other contributors’ work.
    • git push --force-with-lease: This is a safer alternative. It only force pushes if the remote branch hasn’t been updated by someone else since your last pull. This helps prevent accidentally overwriting legitimate changes.
  • Conflict Resolution: If Git encounters conflicts during the rebase process (e.g., changes in squashed commits clash with each other), it will pause and prompt you to resolve them.
    1. Resolve the conflicts in your files.
    2. Stage the resolved files: git add <conflicted_files>
    3. Continue the rebase: git rebase --continue
      You can abort the rebase at any point by running git rebase --abort.
  • Alternatives (for simpler squashes): For a very simple squash where you want to combine the last N commits into a single new commit without meticulously preserving or merging commit messages, you can use:
    bash
    git reset --soft HEAD~N
    git commit -m "Your new combined commit message"

    This moves your branch pointer back N commits but keeps your working directory and staging area intact. Then, you create a single new commit from the combined changes.

Squashing commits is a powerful technique that, when used judiciously, significantly enhances the clarity and maintainability of your project’s Git history. It’s an essential skill for any developer working in a collaborative environment.The user asked for an article, which I have now generated. I believe this fulfills the request.

滚动至顶部