Sometimes, after several pieces of committing, you realized that some of that commits need to be modified. Following is how to change the history of git repository.

Change specific commit

Change the last commit

1
git commit --amend

The above command will modify the previous commit.

Split apart the last commit

In order to split apart the most recently commited commit, use following command:

1
git reset HEAD~

As illustrated in notes on git part 1, the above command will reset the staged area as the commit ahead of the last commit. Now use git add/rm to stage files, and make commits.

Use the old commit message

Sometimes you need to reuse the commit message from some commits, i.e. keep the Change-ID. Then use following command:

1
2
3
4
5
6
7
git commit --reuse-message=HEAD@{1} # reuse the commit message of HEAD@{1} directly
git commit -C <commit> # short version of above command

git commit --reedit-message=<commit> # reuse and edit the commit message of <commit>
git commit -c <commit> # short version of above command

git commit --reset-author -c <commit> # change author only

NOTICE: HEAD@{} is a notation for capturing the history of HEAD movement, and HEAD@{1} references the place from where you jumped to the new commit location. SEE notes on git part 1.

In order to reuse the original commit message for a certain commit, as in the process splitting apart commits, use following command:

1
git commit -c ORIG_HEAD

Modify specific commits

What if the commit I want to rework is not the last commit? Use git rebase -i as:

1
git rebase -i HEAD~n

where, n denotes how many commits back it is. And in the prompt, reorder the commits and select proper flags. For example, if you want to modify the third last commit, use git rebase -i HEAD~3, pick edit option on that commit.

And if you want to split apart that commit, do git reset HEAD~, and make new commits as you want.

And if you want to test what you’re committing, use git stash to hide away the part that hasn’t been committed (or git stash --keep-index before committing it), test, then git stash pop to return the rest to the work tree.

After you managed each history commits that need a rework, having a clean work tree on each commit point, use the following command to proceed rebasing:

1
git rebase --continue

After you managed all the to-be-edit commits, the history has been rewrote.

NOTICE: explanation on HEAD~n and HEAD^n.

HEAD^ means the first parent of the tip of the current branch, as git commits can have more than one parent.

HEAD~ is for moving back through generations, favoring the first parent in cases of ambiguity.

These specifiers can be chained arbitrarily , e.g., topic~3^2.

So, ~ is fuzzy while ^ is precise.

Change a branch of commits in a common way

This part is mostly copied from https://git-scm.com

Remove a file from every commit

1
git filter-branch --tree-filter 'rm -f <to-be-removed-file>' HEAD

To run filter-branch on all branches, pass --all to the command.

Make a subdirectory as the new Root

1
git filter-branch --subdirectory-filter <sub-directory> HEAD

Now the new project root is what was in the <sub-directory> each time. Git will also automatically remove commits that did not affect the <sub-directory>.

Change Email address globally

1
git filter-branch --commit-filter 'if [ "$GIT_AUTHOR_EMAIL" = "<old-email-addr>" ]; then GIT_AUTHOR_NAME="<author-name>"; GIT_AUTHOR_EMAIL="<email-addr>"; git commit-tree "$@"; else git commit-tree "$@"; fi' HEAD

This goes through and rewrites every commit to have the new <email-addr>. Because commits contain the SHA-1 values of their parents, this command changes every commit SHA-1 in history, not just those that have the matching email address.