Git Squash: How to Combine Your Last N Commits – wiki大全

Git Squash: How to Combine Your Last N Commits

Git squash is a powerful feature within Git’s interactive rebase that allows developers to combine multiple commits into a single, more meaningful commit. This is particularly useful for maintaining a clean, understandable, and linear project history, which is crucial for collaboration and future maintenance.

Why Use Git Squash?

Imagine you’re working on a new feature. You might make several small commits during development:
* “WIP: Add basic user interface”
* “Fix typo in UI”
* “Implement backend logic for feature X”
* “Add tests for backend logic”
* “Refactor UI code”

While these granular commits are helpful during development for saving progress, they can clutter the project history when pushed to a shared remote. When reviewing the history, one might prefer to see a single, cohesive commit like “Feat: Implement user authentication module” that encapsulates all related changes.

Git squash helps achieve this by:
1. Cleaning up history: Removing “fixup,” “WIP,” or minor formatting commits.
2. Creating logical commits: Grouping related changes into a single, atomic unit.
3. Simplifying reverts: If a feature introduced across many small commits needs to be undone, a single squashed commit makes git revert much easier.
4. Easier code reviews: Reviewers can focus on the logical changes of a feature rather than sifting through numerous incremental saves.

Prerequisites: Interactive Rebase

Squashing commits is done through Git’s interactive rebase command (git rebase -i). This command allows you to rewrite commit history by reordering, editing, dropping, or combining commits.

Important Note: Never rebase or squash commits that have already been pushed to a shared remote repository where others might have pulled those changes. Rewriting history that others depend on can cause significant issues and force inconvenient git pull --rebase or git push --force operations. Only squash commits that exist solely on your local branch or a feature branch that hasn’t been merged into a shared main branch yet.

How to Squash Your Last N Commits

Let’s say you want to squash your last N commits. The general command is:

bash
git rebase -i HEAD~N

Where N is the number of commits you want to go back from your current HEAD.

Example Walkthrough:

Suppose your recent commit history looks like this (newest at the top):

commit 5f3d1b9 (HEAD -> feature/my-feature) Refactor UI code
commit b2a8c0f Add tests for backend logic
commit 7e6f5d4 Implement backend logic for feature X
commit a1b2c3d Fix typo in UI
commit d4e5f67 WIP: Add basic user interface
commit 9876543 (origin/main) Initial setup

You want to combine the last 5 commits into a single commit for your feature/my-feature branch.

  1. Start the interactive rebase:

    bash
    git rebase -i HEAD~5

    This will open your default text editor (e.g., Vim, Nano) with a list of the last 5 commits, from oldest to newest:

    “`
    pick d4e5f67 WIP: Add basic user interface
    pick a1b2c3d Fix typo in UI
    pick 7e6f5d4 Implement backend logic for feature X
    pick b2a8c0f Add tests for backend logic
    pick 5f3d1b9 Refactor UI code

    Rebase 9876543..5f3d1b9 onto 9876543 (5 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) for each commit

    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 one line subject)

    . Use -c to re-use the given commit message

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

    If you remove a line here THAT COMMIT WILL BE LOST.

    However, if you remove everything, the rebase will be aborted.

    “`

  2. Modify the commands:

    To squash, you keep the first commit as pick and change all subsequent commits you want to combine into that first one to squash (or s).

    pick d4e5f67 WIP: Add basic user interface
    s a1b2c3d Fix typo in UI
    s 7e6f5d4 Implement backend logic for feature X
    s b2a8c0f Add tests for backend logic
    s 5f3d1b9 Refactor UI code

    • pick means to use the commit as is.
    • squash means to use the commit’s changes but combine its message with the previous commit’s message.

    Save and close the editor.

  3. Edit the combined commit message:

    Git will then open another editor window, prompting you to write a new commit message for the combined commit. It will usually pre-populate it with all the commit messages from the squashed commits.

    “`

    This is a combination of 5 commits.

    This is the 1st commit message:

    WIP: Add basic user interface

    This is the commit message #2:

    Fix typo in UI

    This is the commit message #3:

    Implement backend logic for feature X

    This is the commit message #4:

    Add tests for backend logic

    This is the commit message #5:

    Refactor UI code

    Please enter the commit message for your changes. Lines starting

    with ‘#’ will be ignored, and an empty message aborts the commit.

    “`

    Delete the irrelevant lines and write a clear, concise, and descriptive commit message that summarizes all the changes. For example:

    “`
    Feat: Implement user authentication module

    This commit introduces the full user authentication flow, including:
    – Basic UI for login/registration
    – Backend logic for user creation and session management
    – Unit tests for all backend components
    – UI code refactoring for better maintainability
    “`

    Save and close the editor.

  4. Rebase complete:

    Git will apply the rebase. If there are no conflicts, you’ll see a success message:

    Successfully rebased and updated refs/heads/feature/my-feature.

Now, if you check your git log, you will see only one commit that encapsulates all the changes you just squashed, sitting on top of Initial setup:

commit <new_commit_hash> (HEAD -> feature/my-feature) Feat: Implement user authentication module
commit 9876543 (origin/main) Initial setup

Advanced Squashing (Using fixup)

Instead of squash, you can use fixup (or f). fixup works identically to squash but discards the commit’s log message, using only the log message of the commit it’s being squashed into. This is useful when you have a minor “fix” commit and you don’t want its message to appear in the final combined message.

For example, if you change s to f for the “Fix typo in UI” commit:

pick d4e5f67 WIP: Add basic user interface
f a1b2c3d Fix typo in UI
s 7e6f5d4 Implement backend logic for feature X
s b2a8c0f Add tests for backend logic
s 5f3d1b9 Refactor UI code

Git would then present you with the commit message editor, but without the “Fix typo in UI” message.

Handling Conflicts

If during the rebase Git encounters conflicts (when changes from one commit overlap with changes from another, and Git doesn’t know how to combine them automatically), it will pause the rebase and inform you.

You’ll need to:
1. Open the conflicted files and manually resolve the conflicts.
2. git add <resolved_file> for each file.
3. git rebase --continue to proceed with the rebase.

If you get stuck or want to abandon the rebase, use git rebase --abort.

Conclusion

Git squash is an indispensable tool for maintaining a clean, coherent, and meaningful commit history. By combining related incremental changes into logical units, you enhance readability, simplify future maintenance, and make collaboration more efficient. Remember to use it responsibly, especially avoiding rewriting history that has already been shared.

滚动至顶部