2007-06-03 00:31:28 +00:00
#!/bin/sh
#
# Rewrite revision history
# Copyright (c) Petr Baudis, 2006
# Minimal changes to "port" it to core-git (c) Johannes Schindelin, 2007
#
2007-07-03 23:41:55 +00:00
# Lets you rewrite the revision history of the current branch, creating
# a new branch. You can specify a number of filters to modify the commits,
# files and trees.
2007-06-03 00:31:28 +00:00
2007-06-06 07:43:41 +00:00
USAGE = "git-filter-branch [-d TEMPDIR] [FILTERS] DESTBRANCH [REV-RANGE]"
2007-06-03 00:31:28 +00:00
. git-sh-setup
2007-07-04 08:36:24 +00:00
warn ( ) {
echo " $* " >& 2
}
2007-06-03 00:31:28 +00:00
map( )
{
2007-06-06 18:38:35 +00:00
# if it was not rewritten, take the original
2007-07-04 12:08:17 +00:00
if test -r " $workdir /../map/ $1 "
then
cat " $workdir /../map/ $1 "
else
echo " $1 "
fi
2007-06-03 00:31:28 +00:00
}
filter-branch: fail gracefully when a filter fails
A common mistake is to provide a filter which fails unwantedly. For
example, this will stop in the middle:
git filter-branch --env-filter '
test $GIT_COMMITTER_EMAIL = xyz &&
export GIT_COMMITTER_EMAIL = abc' rewritten
When $GIT_COMMITTER_EMAIL is not "xyz", the test fails, and consequently
the whole filter has a non-zero exit status. However, as demonstrated
in this example, filter-branch would just stop, and the user would be
none the wiser.
Also, a failing msg-filter would not have been caught, as was the
case with one of the tests.
This patch fixes both issues, by paying attention to the exit status
of msg-filter, and by saying what failed before exiting.
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2007-07-04 14:36:01 +00:00
# override die(): this version puts in an extra line break, so that
# the progress is still visible
die( )
{
echo >& 2
echo " $* " >& 2
exit 1
}
2007-06-03 00:31:28 +00:00
# When piped a commit, output a script to set the ident of either
# "author" or "committer
set_ident ( ) {
lid = " $( echo " $1 " | tr "A-Z" "a-z" ) "
uid = " $( echo " $1 " | tr "a-z" "A-Z" ) "
pick_id_script = '
/^'$lid' /{
s/'\' '/' \' '\\' \' \' ' /g
h
s/^'$lid' \( [ ^<] *\) <[ ^>] *> .*$/\1 /
s/'\' '/' \' '\' \' \' ' /g
s/.*/export GIT_'$uid' _NAME = '\' '&' \' ' /p
g
s/^'$lid' [ ^<] * <\( [ ^>] *\) > .*$/\1 /
s/'\' '/' \' '\' \' \' ' /g
s/.*/export GIT_'$uid' _EMAIL = '\' '&' \' ' /p
g
s/^'$lid' [ ^<] * <[ ^>] *> \( .*\) $/\1 /
s/'\' '/' \' '\' \' \' ' /g
s/.*/export GIT_'$uid' _DATE = '\' '&' \' ' /p
q
}
'
LANG = C LC_ALL = C sed -ne " $pick_id_script "
# Ensure non-empty id name.
echo " [ -n \"\$GIT_ ${ uid } _NAME\" ] || export GIT_ ${ uid } _NAME=\"\${GIT_ ${ uid } _EMAIL%%@*}\" "
}
tempdir = .git-rewrite
filter_env =
filter_tree =
filter_index =
filter_parent =
filter_msg = cat
2007-07-03 05:52:14 +00:00
filter_commit = 'git commit-tree "$@"'
2007-06-03 00:31:28 +00:00
filter_tag_name =
2007-06-08 00:30:35 +00:00
filter_subdir =
2007-06-03 00:31:28 +00:00
while case " $# " in 0) usage; ; esac
do
case " $1 " in
--)
shift
break
; ;
-*)
; ;
*)
break;
esac
# all switches take one argument
ARG = " $1 "
case " $# " in 1) usage ; ; esac
shift
OPTARG = " $1 "
shift
case " $ARG " in
-d)
tempdir = " $OPTARG "
; ;
--env-filter)
filter_env = " $OPTARG "
; ;
--tree-filter)
filter_tree = " $OPTARG "
; ;
--index-filter)
filter_index = " $OPTARG "
; ;
--parent-filter)
filter_parent = " $OPTARG "
; ;
--msg-filter)
filter_msg = " $OPTARG "
; ;
--commit-filter)
filter_commit = " $OPTARG "
; ;
--tag-name-filter)
filter_tag_name = " $OPTARG "
; ;
2007-06-08 00:30:35 +00:00
--subdirectory-filter)
filter_subdir = " $OPTARG "
; ;
2007-06-03 00:31:28 +00:00
*)
usage
; ;
esac
done
dstbranch = " $1 "
2007-06-06 07:43:41 +00:00
shift
2007-06-03 00:31:28 +00:00
test -n " $dstbranch " || die "missing branch name"
2007-07-03 05:52:14 +00:00
git show-ref " refs/heads/ $dstbranch " 2> /dev/null &&
2007-06-03 00:31:28 +00:00
die " branch $dstbranch already exists "
test ! -e " $tempdir " || die " $tempdir already exists, please remove it "
2007-07-18 13:17:43 +00:00
mkdir -p " $tempdir /t " &&
cd " $tempdir /t " &&
workdir = " $( pwd ) " ||
die ""
2007-06-03 00:31:28 +00:00
case " $GIT_DIR " in
/*)
; ;
*)
2007-06-06 07:16:56 +00:00
GIT_DIR = " $( pwd ) /../../ $GIT_DIR "
2007-06-03 00:31:28 +00:00
; ;
esac
2007-06-06 07:16:56 +00:00
export GIT_DIR GIT_WORK_TREE = .
2007-06-03 00:31:28 +00:00
export GIT_INDEX_FILE = " $( pwd ) /../index "
2007-07-18 13:17:43 +00:00
git read-tree || die "Could not seed the index"
2007-06-03 00:31:28 +00:00
ret = 0
2007-07-18 13:17:43 +00:00
# map old->new commit ids for rewriting parents
mkdir ../map || die "Could not create map/ directory"
2007-06-03 00:31:28 +00:00
2007-06-08 00:30:35 +00:00
case " $filter_subdir " in
"" )
2007-07-03 05:52:14 +00:00
git rev-list --reverse --topo-order --default HEAD \
2007-06-08 21:28:39 +00:00
--parents " $@ "
2007-06-08 00:30:35 +00:00
; ;
*)
2007-07-03 05:52:14 +00:00
git rev-list --reverse --topo-order --default HEAD \
2007-06-08 21:28:50 +00:00
--parents --full-history " $@ " -- " $filter_subdir "
2007-07-18 13:17:43 +00:00
esac > ../revs || die "Could not get the commits"
2007-07-14 08:05:43 +00:00
commits = $( wc -l <../revs | tr -d " " )
2007-06-03 00:31:28 +00:00
test $commits -eq 0 && die "Found nothing to rewrite"
i = 0
2007-06-08 21:28:39 +00:00
while read commit parents; do
2007-06-06 15:24:07 +00:00
i = $(( $i + 1 ))
2007-07-04 14:33:30 +00:00
printf " \rRewrite $commit ( $i / $commits ) "
2007-06-03 00:31:28 +00:00
2007-06-08 00:30:35 +00:00
case " $filter_subdir " in
"" )
2007-07-03 05:52:14 +00:00
git read-tree -i -m $commit
2007-06-08 00:30:35 +00:00
; ;
*)
2007-07-03 05:52:14 +00:00
git read-tree -i -m $commit :" $filter_subdir "
2007-07-18 13:17:43 +00:00
esac || die "Could not initialize the index"
2007-06-03 00:31:28 +00:00
export GIT_COMMIT = $commit
2007-07-18 13:17:43 +00:00
git cat-file commit " $commit " >../commit ||
die " Cannot read commit $commit "
2007-06-03 00:31:28 +00:00
filter-branch: fail gracefully when a filter fails
A common mistake is to provide a filter which fails unwantedly. For
example, this will stop in the middle:
git filter-branch --env-filter '
test $GIT_COMMITTER_EMAIL = xyz &&
export GIT_COMMITTER_EMAIL = abc' rewritten
When $GIT_COMMITTER_EMAIL is not "xyz", the test fails, and consequently
the whole filter has a non-zero exit status. However, as demonstrated
in this example, filter-branch would just stop, and the user would be
none the wiser.
Also, a failing msg-filter would not have been caught, as was the
case with one of the tests.
This patch fixes both issues, by paying attention to the exit status
of msg-filter, and by saying what failed before exiting.
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2007-07-04 14:36:01 +00:00
eval " $( set_ident AUTHOR <../commit) " ||
die " setting author failed for commit $commit "
eval " $( set_ident COMMITTER <../commit) " ||
die " setting committer failed for commit $commit "
eval " $filter_env " < /dev/null ||
die " env filter failed: $filter_env "
2007-06-03 00:31:28 +00:00
if [ " $filter_tree " ] ; then
2007-07-18 13:17:43 +00:00
git checkout-index -f -u -a ||
die "Could not checkout the index"
2007-06-03 00:31:28 +00:00
# files that $commit removed are now still in the working tree;
# remove them, else they would be added again
2007-07-03 05:52:14 +00:00
git ls-files -z --others | xargs -0 rm -f
filter-branch: fail gracefully when a filter fails
A common mistake is to provide a filter which fails unwantedly. For
example, this will stop in the middle:
git filter-branch --env-filter '
test $GIT_COMMITTER_EMAIL = xyz &&
export GIT_COMMITTER_EMAIL = abc' rewritten
When $GIT_COMMITTER_EMAIL is not "xyz", the test fails, and consequently
the whole filter has a non-zero exit status. However, as demonstrated
in this example, filter-branch would just stop, and the user would be
none the wiser.
Also, a failing msg-filter would not have been caught, as was the
case with one of the tests.
This patch fixes both issues, by paying attention to the exit status
of msg-filter, and by saying what failed before exiting.
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2007-07-04 14:36:01 +00:00
eval " $filter_tree " < /dev/null ||
die " tree filter failed: $filter_tree "
2007-07-03 05:52:14 +00:00
git diff-index -r $commit | cut -f 2- | tr '\n' '\0' | \
xargs -0 git update-index --add --replace --remove
git ls-files -z --others | \
xargs -0 git update-index --add --replace --remove
2007-06-03 00:31:28 +00:00
fi
filter-branch: fail gracefully when a filter fails
A common mistake is to provide a filter which fails unwantedly. For
example, this will stop in the middle:
git filter-branch --env-filter '
test $GIT_COMMITTER_EMAIL = xyz &&
export GIT_COMMITTER_EMAIL = abc' rewritten
When $GIT_COMMITTER_EMAIL is not "xyz", the test fails, and consequently
the whole filter has a non-zero exit status. However, as demonstrated
in this example, filter-branch would just stop, and the user would be
none the wiser.
Also, a failing msg-filter would not have been caught, as was the
case with one of the tests.
This patch fixes both issues, by paying attention to the exit status
of msg-filter, and by saying what failed before exiting.
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2007-07-04 14:36:01 +00:00
eval " $filter_index " < /dev/null ||
die " index filter failed: $filter_index "
2007-06-03 00:31:28 +00:00
parentstr =
2007-06-08 21:28:39 +00:00
for parent in $parents ; do
2007-06-06 18:38:35 +00:00
for reparent in $( map " $parent " ) ; do
parentstr = " $parentstr -p $reparent "
done
2007-06-03 00:31:28 +00:00
done
if [ " $filter_parent " ] ; then
filter-branch: fail gracefully when a filter fails
A common mistake is to provide a filter which fails unwantedly. For
example, this will stop in the middle:
git filter-branch --env-filter '
test $GIT_COMMITTER_EMAIL = xyz &&
export GIT_COMMITTER_EMAIL = abc' rewritten
When $GIT_COMMITTER_EMAIL is not "xyz", the test fails, and consequently
the whole filter has a non-zero exit status. However, as demonstrated
in this example, filter-branch would just stop, and the user would be
none the wiser.
Also, a failing msg-filter would not have been caught, as was the
case with one of the tests.
This patch fixes both issues, by paying attention to the exit status
of msg-filter, and by saying what failed before exiting.
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2007-07-04 14:36:01 +00:00
parentstr = " $( echo " $parentstr " | eval " $filter_parent " ) " ||
die " parent filter failed: $filter_parent "
2007-06-03 00:31:28 +00:00
fi
sed -e '1,/^$/d' <../commit | \
filter-branch: fail gracefully when a filter fails
A common mistake is to provide a filter which fails unwantedly. For
example, this will stop in the middle:
git filter-branch --env-filter '
test $GIT_COMMITTER_EMAIL = xyz &&
export GIT_COMMITTER_EMAIL = abc' rewritten
When $GIT_COMMITTER_EMAIL is not "xyz", the test fails, and consequently
the whole filter has a non-zero exit status. However, as demonstrated
in this example, filter-branch would just stop, and the user would be
none the wiser.
Also, a failing msg-filter would not have been caught, as was the
case with one of the tests.
This patch fixes both issues, by paying attention to the exit status
of msg-filter, and by saying what failed before exiting.
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2007-07-04 14:36:01 +00:00
eval " $filter_msg " > ../message ||
die " msg filter failed: $filter_msg "
sh -c " $filter_commit " "git commit-tree" \
$( git write-tree) $parentstr < ../message > ../map/$commit
2007-06-03 00:31:28 +00:00
done <../revs
2007-06-08 21:28:39 +00:00
src_head = $( tail -n 1 ../revs | sed -e 's/ .*//' )
2007-06-05 15:58:13 +00:00
target_head = $( head -n 1 ../map/$src_head )
case " $target_head " in
'' )
echo Nothing rewritten
; ;
*)
2007-07-18 13:17:43 +00:00
git update-ref refs/heads/" $dstbranch " $target_head ||
die " Could not update $dstbranch with $target_head "
2007-07-14 08:05:43 +00:00
if [ $( wc -l <../map/$src_head ) -gt 1 ] ; then
2007-06-05 15:58:13 +00:00
echo " WARNING: Your commit filter caused the head commit to expand to several rewritten commits. Only the first such commit was recorded as the current $dstbranch head but you will need to resolve the situation now (probably by manually merging the other commits). These are all the commits: " >& 2
sed 's/^/ /' ../map/$src_head >& 2
ret = 1
fi
; ;
esac
2007-06-03 00:31:28 +00:00
if [ " $filter_tag_name " ] ; then
2007-07-03 05:52:14 +00:00
git for -each-ref --format= '%(objectname) %(objecttype) %(refname)' refs/tags |
2007-06-03 00:31:28 +00:00
while read sha1 type ref; do
ref = " ${ ref #refs/tags/ } "
# XXX: Rewrite tagged trees as well?
if [ " $type " != "commit" -a " $type " != "tag" ] ; then
continue ;
fi
if [ " $type " = "tag" ] ; then
# Dereference to a commit
sha1t = " $sha1 "
2007-07-03 05:52:14 +00:00
sha1 = " $( git rev-parse " $sha1 " ^{ commit} 2>/dev/null) " || continue
2007-06-03 00:31:28 +00:00
fi
[ -f " ../map/ $sha1 " ] || continue
new_sha1 = " $( cat " ../map/ $sha1 " ) "
export GIT_COMMIT = " $sha1 "
filter-branch: fail gracefully when a filter fails
A common mistake is to provide a filter which fails unwantedly. For
example, this will stop in the middle:
git filter-branch --env-filter '
test $GIT_COMMITTER_EMAIL = xyz &&
export GIT_COMMITTER_EMAIL = abc' rewritten
When $GIT_COMMITTER_EMAIL is not "xyz", the test fails, and consequently
the whole filter has a non-zero exit status. However, as demonstrated
in this example, filter-branch would just stop, and the user would be
none the wiser.
Also, a failing msg-filter would not have been caught, as was the
case with one of the tests.
This patch fixes both issues, by paying attention to the exit status
of msg-filter, and by saying what failed before exiting.
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2007-07-04 14:36:01 +00:00
new_ref = " $( echo " $ref " | eval " $filter_tag_name " ) " ||
die " tag name filter failed: $filter_tag_name "
2007-06-03 00:31:28 +00:00
echo " $ref -> $new_ref ( $sha1 -> $new_sha1 ) "
if [ " $type " = "tag" ] ; then
# Warn that we are not rewriting the tag object itself.
warn " unreferencing tag object $sha1t "
fi
2007-07-18 13:17:43 +00:00
git update-ref " refs/tags/ $new_ref " " $new_sha1 " ||
die " Could not write tag $new_ref "
2007-06-03 00:31:28 +00:00
done
fi
cd ../..
rm -rf " $tempdir "
2007-07-04 14:33:30 +00:00
printf " \nRewritten history saved to the $dstbranch branch\n "
2007-06-03 00:31:28 +00:00
exit $ret