﻿---
title: Git Rebase, Squash, and History
date: 2024-04-27
excerpt: Merge, edit, and clean up commit history with git rebase -i, plus a look at the experimental git history command added in Git 2.54.
tags:
  - Git
updated: 2026-07-08 21:22:46
lang: en
i18n:
  cn: /rebase-and-squash
  translation: 2
---

## When to Use It

While developing a feature, it's easy to pile up temporary commits: fix a typo, add a test, roll back an experiment, rename something again. They're useful for local development, but if they land on the main branch as-is, the commit history gets fragmented.

`{sh} git rebase -i` is well suited for cleaning up these local commits before submitting. It can squash several consecutive commits into one, or stop at a historical commit so you can amend its content or message.

> [!danger] Warning
> `rebase` rewrites commit history. Don't casually rebase commits that have already been pushed to a remote and that others may have built on. The recommended use cases are local branches, personal feature branches, or tidying up history before opening a PR.

If you do need to push rewritten history, prefer `{sh} git push --force-with-lease`.

It's safer than `{sh} git push --force`: if the remote branch has new commits your local copy doesn't know about, the push will fail.

Common commands in `{sh} git rebase -i`:

> **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`
> - `d, drop = remove commit`

## Squashing Multiple Commits

To squash the last three commits, run:

`{sh} git rebase -i HEAD~3`

`HEAD~3` means three commits back from the current one. After the command runs, Git opens the editor specified by `core.editor` and lists those three commits.

You can also specify a commit hash:

`{sh} git rebase -i 8c0a3c`

This lists all commits between `8c0a3c` (exclusive) and `HEAD`.

> [!note]
> `{sh} git rebase -i` is good for consecutive commits. Handling non-consecutive commits usually takes multiple rebases, or a more deliberate branch-cleanup strategy first.

The editor can be set via `core.editor`. For example:

`{sh} git config --global core.editor "nvim"`

In the editor, change `pick` on the second and third lines to `squash` or `s`, then save and quit. Git will open the editor again so you can edit the squashed commit message.

If you use Vim, this substitution changes `pick` to `s` on lines 2 through 3:

```vim
:2,3s/pick/s/
```

Here `2,3` is the line range, and `s/pick/s/` replaces `pick` with `s`.

![Interactive rebase editor with later commits changed from pick to squash](https://assets.vluv.space/Dev/rebase-and-squash/rebase-and-squash-2024-06-22-19-06-43.webp)

Edit the commit message, then save and close the editor. If everything went well, your three commits should now be squashed into one.

![Commit message editor opened after squashing multiple commits](https://assets.vluv.space/Dev/rebase-and-squash/rebase-and-squash-2024-06-22-19-06-44.webp)

This step changes Git history. If those commits were already pushed to a remote, you'll need `{sh} git push --force-with-lease` to push the rewritten branch.

## Editing a Commit

If a commit somewhere in history has a mistake, you can also use `rebase -i` to stop at that commit, fix the files, and run `commit --amend`.

Suppose the current history looks like this, and the `to fix` commit contains an error: `1 == 2`.

```shell
* 7f3af51 (HEAD -> main) 4th
* a14992f to fix
* b16a285 second_amend
* c6bb6ae first
```

![Git log before editing the historical commit named to fix](https://assets.vluv.space/Dev/rebase-and-squash/rebase-and-squash-2024-06-22-19-06-45.webp)

Since we want to edit the earlier of the last two commits, run:

```shell
$ git rebase -i HEAD~2

[.git/COMMIT_EDITMSG]
- pick a14992f to fix
+ edit a14992f to fix
save and quit
```

Git stops at the `to fix` commit. Now edit the file, stage the change, and use `{sh} git commit --amend` to update the current commit.

```shell
$ vim ./fix.md
- 1 == 2
+ 1 != 2
save and quit

$ git add ./fix.md
$ git commit --amend

[.git/COMMIT_EDITMSG]
- to fix
+ fixed
save and quit

$ git rebase --continue
```

`{sh} git rebase --continue` tells Git to keep applying the remaining commits. When it finishes, the history looks like this:

```shell
* dc6de4f (HEAD -> main) 4th
* a33b9a0 fixed
* b16a285 second_amend
* c6bb6ae first
```

![Git log after amending the historical commit and continuing rebase](https://assets.vluv.space/Dev/rebase-and-squash/rebase-and-squash-2024-06-22-19-18-10.webp)

As you can see, `to fix` became `fixed`, and the following `4th` commit also got a new hash. That's the nature of rebase: it doesn't modify history in place, it regenerates a stretch of it.

## New Command: git history

Git 2.54.0 added an experimental command: `{sh} git history`. It also rewrites commit history, but its positioning differs from `{sh} git rebase -i`.

`{sh} git rebase -i` is more like a general-purpose editor: you first select a stretch of consecutive history, then decide in the todo list whether each commit is `pick`, `squash`, `edit`, or `drop`. It's suited to batch-cleaning a range of commits.

`{sh} git history` is more of a high-level, task-oriented command. For now it offers two main subcommands:

`{sh} git history reword <commit>`

`{sh} git history split <commit>`

`reword` edits the message of a specific commit:

`{sh} git history reword a14992f`

It opens the editor so you can revise that commit's message. This used to be doable with `{sh} git rebase -i` plus `reword`, but `{sh} git history reword <commit>` is more direct.

`split` splits one commit into two:

`{sh} git history split a14992f`

Git interactively asks which hunks should go into the new commit. This operation used to require a combination of `{sh} git rebase -i`, `edit`, `{sh} git reset`, re-running `add -p`, and multiple commits, which was longer and more error-prone.

There are several important differences from `{sh} git rebase -i`:

- `{sh} git history` is still experimental, and its behavior may change.
- `{sh} git history` by default updates all local branches pointing to descendants of the original commit; you can restrict it to the current `HEAD` with `--update-refs=head`.
- `{sh} git history` currently does not run Git hooks.
- `{sh} git history` doesn't yet support histories containing merge commits, nor operations that may produce conflicts.
- If you want to reapply a range of commits onto another base, or edit multiple commits at once, you should still use `{sh} git rebase`.

So you can think of it this way: `{sh} git rebase -i` is the general-purpose tool, and `{sh} git history` is Git's new, more opinionated entry point for history editing. Once it stabilizes, tasks like rewording a single commit message or splitting a single commit may be a better fit for `{sh} git history`.

## References

- [git scm: git-rebase](https://git-scm.com/docs/git-rebase)
- [git scm: git-history](https://git-scm.com/docs/git-history)
- [Atlassian: Git rebase](https://www.atlassian.com/git/tutorials/rewriting-history/git-rebase)
