Suppose you want to remove a file (containing confidential
information or copyright violation) from all commits:
git filter-branch --tree-filter 'rm filename' HEAD
However, if the file is absent from the tree of some commit, a
simple rm filename
will fail for that tree and commit. Thus you
may instead want to use rm -f filename
as the script.
Using --index-filter
with git rm yields a significantly faster
version. Like with using rm filename
, git rm --cached filename
will fail if the file is absent from the tree of a commit. If you
want to "completely forget" a file, it does not matter when it
entered history, so we also add --ignore-unmatch
:
git filter-branch --index-filter 'git rm --cached --ignore-unmatch filename' HEAD
Now, you will get the rewritten history saved in HEAD.
To rewrite the repository to look as if foodir/
had been its
project root, and discard all other history:
git filter-branch --subdirectory-filter foodir -- --all
Thus you can, e.g., turn a library subdirectory into a repository
of its own. Note the --
that separates filter-branch options from
revision options, and the --all
to rewrite all branches and tags.
To set a commit (which typically is at the tip of another
history) to be the parent of the current initial commit, in order
to paste the other history behind the current history:
git filter-branch --parent-filter 'sed "s/^\$/-p <graft-id>/"' HEAD
(if the parent string is empty - which happens when we are
dealing with the initial commit - add graftcommit as a parent).
Note that this assumes history with a single root (that is, no
merge without common ancestors happened). If this is not the
case, use:
git filter-branch --parent-filter \
'test $GIT_COMMIT = <commit-id> && echo "-p <graft-id>" || cat' HEAD
or even simpler:
git replace --graft $commit-id $graft-id
git filter-branch $graft-id..HEAD
To remove commits authored by "Darl McBribe" from the history:
git filter-branch --commit-filter '
if [ "$GIT_AUTHOR_NAME" = "Darl McBribe" ];
then
skip_commit "$@";
else
git commit-tree "$@";
fi' HEAD
The function skip_commit is defined as follows:
skip_commit()
{
shift;
while [ -n "$1" ];
do
shift;
map "$1";
shift;
done;
}
The shift magic first throws away the tree id and then the -p
parameters. Note that this handles merges properly! In case Darl
committed a merge between P1 and P2, it will be propagated
properly and all children of the merge will become merge commits
with P1,P2 as their parents instead of the merge commit.
NOTE
the changes introduced by the commits, and which are not
reverted by subsequent commits, will still be in the rewritten
branch. If you want to throw out changes together with the
commits, you should use the interactive mode of git rebase.
You can rewrite the commit log messages using --msg-filter
. For
example, git svn-id strings in a repository created by git svn
can be removed this way:
git filter-branch --msg-filter '
sed -e "/^git-svn-id:/d"
'
If you need to add Acked-by lines to, say, the last 10 commits
(none of which is a merge), use this command:
git filter-branch --msg-filter '
cat &&
echo "Acked-by: Bugs Bunny <bunny@bugzilla.org>"
' HEAD~10..HEAD
The --env-filter
option can be used to modify committer and/or
author identity. For example, if you found out that your commits
have the wrong identity due to a misconfigured user.email, you
can make a correction, before publishing the project, like this:
git filter-branch --env-filter '
if test "$GIT_AUTHOR_EMAIL" = "root@localhost"
then
GIT_AUTHOR_EMAIL=john@example.com
fi
if test "$GIT_COMMITTER_EMAIL" = "root@localhost"
then
GIT_COMMITTER_EMAIL=john@example.com
fi
' -- --all
To restrict rewriting to only part of the history, specify a
revision range in addition to the new branch name. The new branch
name will point to the top-most revision that a git rev-list of
this range will print.
Consider this history:
D--E--F--G--H
/ /
A--B-----C
To rewrite only commits D,E,F,G,H, but leave A, B and C alone,
use:
git filter-branch ... C..H
To rewrite commits E,F,G,H, use one of these:
git filter-branch ... C..H --not D
git filter-branch ... D..H --not C
To move the whole tree into a subdirectory, or remove it from
there:
git filter-branch --index-filter \
'git ls-files -s | sed "s-\t\"*-&newsubdir/-" |
GIT_INDEX_FILE=$GIT_INDEX_FILE.new \
git update-index --index-info &&
mv "$GIT_INDEX_FILE.new" "$GIT_INDEX_FILE"' HEAD