Git 1.5.3 introduces git-rebase --interactive, which lets you alter the commit history in various ways, including splitting, squashing (combining), inserting, and removing patches. In each case git-rebase rewrites the subsequent commit history so no one else is the wiser.
Start by doing:
git-rebase -i f00bab
where f00bab is the commit before the first commit you want to change.
Git opens an editor describing the commits since that commit, in chronological order, in the following format:
pick 1e4dfd7 Foo bar commit.
pick 6b78037 Baz quuz commit.
You can edit this list to tell Git to do certain things:
- Remove a line to delete the corresponding commit.
- Move lines around to reorder commits.
- Change pick to squash on a line to combine that commit with the previous commit.
- Change pick to edit to modify or split that commit (see below).
- Git will attempt to reapply all other commits (lines which are still labeled pick).
When you squash a commit, Git prompts you for a new message for the combined commit.
When you choose edit, Git applies that commit but pauses the rebasing process so you can edit your tree. There are a couple of ways to proceed:
- To edit the commit message only, just do git commit --amend.
- To edit the commit itself, make some changes, git add them, and do git commit --amend.
- To split the commit into multiple commits, do git reset HEAD^ to rewind the branch without touching the working copy. Stage and commit (git add ...; git commit) your first change. Repeat that as many times as you like until the branch catches up to your working copy. git-add --interactive can be useful if you want to pick and choose parts of files to stage. Notice that if you only use git add and git add --interactive then your working tree never changes. If you really want to work with the intermediate states— for example, to run unit tests or whatnot— use git-stash to put away your working copy temporarily.
- You can also make additional commits here, which will appear right after the commit being edited.
In any case, when you're finished at that point in history, use git-rebase --continue to move on.
Whenever you change a commit, Git applies the patches from subsequent commits (at least to the best of its ability; if a change you make causes a subsequent patch to not apply cleanly, then Git will stop to ask you to resolve the conflict). However, these new commits will have different identifiers.
Further reading: git-rebase documentation