git-svn: fix delete+add branch tracking with empty files
[gitweb.git] / git-filter-branch.sh
index d04c346e12fb73539e9910951744e9d5558ad4d3..9a09ba138244b1665de78e94ce12fb8fdb878758 100755 (executable)
@@ -40,6 +40,16 @@ skip_commit()
        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
 
@@ -97,9 +107,11 @@ USAGE="[--env-filter <command>] [--tree-filter <command>] \
 OPTIONS_SPEC=
 . git-sh-setup
 
-git diff-files --quiet &&
+if [ "$(is_bare_repository)" = false ]; then
+       git diff-files --ignore-submodules --quiet &&
        git diff-index --cached --quiet HEAD -- ||
        die "Cannot rewrite branch(es) with a dirty working directory."
+fi
 
 tempdir=.git-rewrite
 filter_env=
@@ -107,11 +119,12 @@ filter_tree=
 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
@@ -124,6 +137,11 @@ do
                force=t
                continue
                ;;
+       --prune-empty)
+               shift
+               prune_empty=t
+               continue
+               ;;
        -*)
                ;;
        *)
@@ -174,6 +192,17 @@ do
        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"
@@ -191,8 +220,14 @@ die ""
 # 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
@@ -205,15 +240,10 @@ do
        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?"
@@ -222,19 +252,17 @@ GIT_INDEX_FILE="$(pwd)/../index"
 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 " ")
 
@@ -254,7 +282,7 @@ while read commit parents; do
        *)
                # 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
@@ -286,10 +314,11 @@ while read commit parents; do
                        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 ||
@@ -310,29 +339,26 @@ while read commit parents; do
                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
 
@@ -359,9 +385,17 @@ do
        ;;
        $_x40)
                echo "Ref '$ref' was rewritten"
-               git update-ref -m "filter-branch: rewrite" \
-                               "$ref" $rewritten $sha1 ||
-                       die "Could not rewrite $ref"
+               if ! git update-ref -m "filter-branch: rewrite" \
+                                       "$ref" $rewritten $sha1 2>/dev/null; then
+                       if test $(git cat-file -t "$ref") = tag; then
+                               if test -z "$filter_tag_name"; then
+                                       warn "WARNING: You said to rewrite tagged commits, but not the corresponding tag."
+                                       warn "WARNING: Perhaps use '--tag-name-filter cat' to rewrite the tag."
+                               fi
+                       else
+                               die "Could not rewrite $ref"
+                       fi
+               fi
        ;;
        *)
                # NEEDSWORK: possibly add -Werror, making this an error
@@ -374,7 +408,8 @@ do
                        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
@@ -406,15 +441,17 @@ if [ "$filter_tag_name" ]; then
                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" | \
@@ -446,6 +483,9 @@ test -z "$ORIG_GIT_INDEX_FILE" || {
        GIT_INDEX_FILE="$ORIG_GIT_INDEX_FILE" &&
        export GIT_INDEX_FILE
 }
-git read-tree -u -m HEAD
 
-exit $ret
+if [ "$(is_bare_repository)" = false ]; then
+       git read-tree -u -m HEAD || exit
+fi
+
+exit 0