done;
}
+# if you run 'git_commit_non_empty_tree "$@"' in a commit filter,
+# it will skip commits that leave the tree untouched, commit the other.
+git_commit_non_empty_tree()
+{
+ if test $# = 3 && test "$1" = $(git rev-parse "$3^{tree}"); then
+ map "$3"
+ else
+ git commit-tree "$@"
+ fi
+}
# override die(): this version puts in an extra line break, so that
# the progress is still visible
. git-sh-setup
if [ "$(is_bare_repository)" = false ]; then
- git diff-files --quiet &&
+ git diff-files --ignore-submodules --quiet &&
git diff-index --cached --quiet HEAD -- ||
die "Cannot rewrite branch(es) with a dirty working directory."
fi
filter_index=
filter_parent=
filter_msg=cat
-filter_commit='git commit-tree "$@"'
+filter_commit=
filter_tag_name=
filter_subdir=
orig_namespace=refs/original/
force=
+prune_empty=
while :
do
case "$1" in
force=t
continue
;;
+ --prune-empty)
+ shift
+ prune_empty=t
+ continue
+ ;;
-*)
;;
*)
esac
done
+case "$prune_empty,$filter_commit" in
+,)
+ filter_commit='git commit-tree "$@"';;
+t,)
+ filter_commit="$functions;"' git_commit_non_empty_tree "$@"';;
+,*)
+ ;;
+*)
+ die "Cannot set --prune-empty and --filter-commit at the same time"
+esac
+
case "$force" in
t)
rm -rf "$tempdir"
# Remove tempdir on exit
trap 'cd ../..; rm -rf "$tempdir"' 0
+ORIG_GIT_DIR="$GIT_DIR"
+ORIG_GIT_WORK_TREE="$GIT_WORK_TREE"
+ORIG_GIT_INDEX_FILE="$GIT_INDEX_FILE"
+GIT_WORK_TREE=.
+export GIT_DIR GIT_WORK_TREE
+
# Make sure refs/original is empty
-git for-each-ref > "$tempdir"/backup-refs
+git for-each-ref > "$tempdir"/backup-refs || exit
while read sha1 type name
do
case "$force,$name" in
,$orig_namespace*)
- die "Namespace $orig_namespace not empty"
+ die "Cannot create a new backup.
+A previous backup already exists in $orig_namespace
+Force overwriting the backup with -f"
;;
t,$orig_namespace*)
git update-ref -d "$name" $sha1
esac
done < "$tempdir"/backup-refs
-ORIG_GIT_DIR="$GIT_DIR"
-ORIG_GIT_WORK_TREE="$GIT_WORK_TREE"
-ORIG_GIT_INDEX_FILE="$GIT_INDEX_FILE"
-GIT_WORK_TREE=.
-export GIT_DIR GIT_WORK_TREE
-
# The refs should be updated if their heads were rewritten
-git rev-parse --no-flags --revs-only --symbolic-full-name --default HEAD "$@" |
-sed -e '/^^/d' >"$tempdir"/heads
+git rev-parse --no-flags --revs-only --symbolic-full-name \
+ --default HEAD "$@" > "$tempdir"/raw-heads || exit
+sed -e '/^^/d' "$tempdir"/raw-heads >"$tempdir"/heads
test -s "$tempdir"/heads ||
die "Which ref do you want to rewrite?"
export GIT_INDEX_FILE
git read-tree || die "Could not seed the index"
-ret=0
-
# map old->new commit ids for rewriting parents
mkdir ../map || die "Could not create map/ directory"
case "$filter_subdir" in
"")
git rev-list --reverse --topo-order --default HEAD \
- --parents "$@"
+ --parents --simplify-merges "$@"
;;
*)
git rev-list --reverse --topo-order --default HEAD \
- --parents "$@" -- "$filter_subdir"
+ --parents --simplify-merges "$@" -- "$filter_subdir"
esac > ../revs || die "Could not get the commits"
commits=$(wc -l <../revs | tr -d " ")
*)
# The commit may not have the subdirectory at all
err=$(git read-tree -i -m $commit:"$filter_subdir" 2>&1) || {
- if ! git rev-parse --verify $commit:"$filter_subdir" 2>/dev/null
+ if ! git rev-parse -q --verify $commit:"$filter_subdir"
then
rm -f "$GIT_INDEX_FILE"
else
die "tree filter failed: $filter_tree"
(
- git diff-index -r --name-only $commit
+ git diff-index -r --name-only $commit &&
git ls-files --others
- ) |
- git update-index --add --replace --remove --stdin
+ ) > "$tempdir"/tree-state || exit
+ git update-index --add --replace --remove --stdin \
+ < "$tempdir"/tree-state || exit
fi
eval "$filter_index" < /dev/null ||
eval "$filter_msg" > ../message ||
die "msg filter failed: $filter_msg"
@SHELL_PATH@ -c "$filter_commit" "git commit-tree" \
- $(git write-tree) $parentstr < ../message > ../map/$commit
+ $(git write-tree) $parentstr < ../message > ../map/$commit ||
+ die "could not write rewritten commit"
done <../revs
# In case of a subdirectory filter, it is possible that a specified head
# is not in the set of rewritten commits, because it was pruned by the
-# revision walker. Fix it by mapping these heads to the next rewritten
-# ancestor(s), i.e. the boundaries in the set of rewritten commits.
+# revision walker. Fix it by mapping these heads to the unique nearest
+# ancestor that survived the pruning.
-# NEEDSWORK: we should sort the unmapped refs topologically first
-while read ref
-do
- sha1=$(git rev-parse "$ref"^0)
- test -f "$workdir"/../map/$sha1 && continue
- # Assign the boundarie(s) in the set of rewritten commits
- # as the replacement commit(s).
- # (This would look a bit nicer if --not --stdin worked.)
- for p in $( (cd "$workdir"/../map; ls | sed "s/^/^/") |
- git rev-list $ref --boundary --stdin |
- sed -n "s/^-//p")
+if test "$filter_subdir"
+then
+ while read ref
do
- map $p >> "$workdir"/../map/$sha1
- done
-done < "$tempdir"/heads
+ sha1=$(git rev-parse "$ref"^0)
+ test -f "$workdir"/../map/$sha1 && continue
+ ancestor=$(git rev-list --simplify-merges -1 \
+ $ref -- "$filter_subdir")
+ test "$ancestor" && echo $(map $ancestor) >> "$workdir"/../map/$sha1
+ done < "$tempdir"/heads
+fi
# Finally update the refs
die "Could not rewrite $ref"
;;
esac
- git update-ref -m "filter-branch: backup" "$orig_namespace$ref" $sha1
+ git update-ref -m "filter-branch: backup" "$orig_namespace$ref" $sha1 ||
+ exit
done < "$tempdir"/heads
# TODO: This should possibly go, with the semantics that all positive given
echo "$ref -> $new_ref ($sha1 -> $new_sha1)"
if [ "$type" = "tag" ]; then
- new_sha1=$(git cat-file tag "$ref" |
+ new_sha1=$( ( printf 'object %s\ntype commit\ntag %s\n' \
+ "$new_sha1" "$new_ref"
+ git cat-file tag "$ref" |
sed -n \
-e "1,/^$/{
- s/^object .*/object $new_sha1/
- s/^type .*/type commit/
- s/^tag .*/tag $new_ref/
+ /^object /d
+ /^type /d
+ /^tag /d
}" \
-e '/^-----BEGIN PGP SIGNATURE-----/q' \
- -e 'p' |
+ -e 'p' ) |
git mktag) ||
die "Could not create new tag object for $ref"
if git cat-file tag "$ref" | \
trap - 0
+unset GIT_DIR GIT_WORK_TREE GIT_INDEX_FILE
+test -z "$ORIG_GIT_DIR" || {
+ GIT_DIR="$ORIG_GIT_DIR" && export GIT_DIR
+}
+test -z "$ORIG_GIT_WORK_TREE" || {
+ GIT_WORK_TREE="$ORIG_GIT_WORK_TREE" &&
+ export GIT_WORK_TREE
+}
+test -z "$ORIG_GIT_INDEX_FILE" || {
+ GIT_INDEX_FILE="$ORIG_GIT_INDEX_FILE" &&
+ export GIT_INDEX_FILE
+}
+
if [ "$(is_bare_repository)" = false ]; then
- unset GIT_DIR GIT_WORK_TREE GIT_INDEX_FILE
- test -z "$ORIG_GIT_DIR" || {
- GIT_DIR="$ORIG_GIT_DIR" && export GIT_DIR
- }
- test -z "$ORIG_GIT_WORK_TREE" || {
- GIT_WORK_TREE="$ORIG_GIT_WORK_TREE" &&
- export GIT_WORK_TREE
- }
- test -z "$ORIG_GIT_INDEX_FILE" || {
- GIT_INDEX_FILE="$ORIG_GIT_INDEX_FILE" &&
- export GIT_INDEX_FILE
- }
- git read-tree -u -m HEAD
+ git read-tree -u -m HEAD || exit
fi
-exit $ret
+exit 0