Update draft Release Notes for 1.5.3
[gitweb.git] / git-filter-branch.sh
old mode 100755 (executable)
new mode 100644 (file)
index 29e0d02..3772951
@@ -81,7 +81,7 @@
 #      This is the filter for rewriting the commit's parent list.
 #      It will receive the parent string on stdin and shall output
 #      the new parent string on stdout. The parent string is in
-#      format accepted by `git-commit-tree`: empty for initial
+#      format accepted by `git commit-tree`: empty for initial
 #      commit, "-p parent" for a normal commit and "-p parent1
 #      -p parent2 -p parent3 ..." for a merge commit.
 #
@@ -93,7 +93,7 @@
 #
 # --commit-filter COMMAND:: The filter for performing the commit
 #      If this filter is passed, it will be called instead of the
-#      `git-commit-tree` command, with those arguments:
+#      `git commit-tree` command, with those arguments:
 #
 #              TREE_ID [-p PARENT_COMMIT_ID]...
 #
 #      attached, the rewritten tag won't have it. Sorry. (It is by
 #      definition impossible to preserve signatures at any rate, though.)
 #
+# --subdirectory-filter DIRECTORY:: Only regard the history, as seen by
+#      the given subdirectory. The result will contain that directory as
+#      its project root.
+#
 # EXAMPLE USAGE
 # -------------
 # Suppose you want to remove a file (containing confidential information
 #
 # A significantly faster version:
 #
-#      git-filter-branch --index-filter 'git-update-index --remove filename' newbranch
+#      git-filter-branch --index-filter 'git update-index --remove filename' newbranch
 #
 # Now, you will get the rewritten history saved in the branch 'newbranch'
 # (your current branch is left untouched).
 #
 # To remove commits authored by "Darl McBribe" from the history:
 #
-#      git-filter-branch --commit-filter 'if [ "$GIT_AUTHOR_NAME" = "Darl McBribe" ]; then shift; while [ -n "$1" ]; do shift; echo "$1"; shift; done; else git-commit-tree "$@"; fi' newbranch
+#      git-filter-branch --commit-filter 'if [ "$GIT_AUTHOR_NAME" = "Darl McBribe" ]; then shift; while [ -n "$1" ]; do shift; echo "$1"; shift; done; else git commit-tree "$@"; fi' newbranch
 #
 # (the shift magic first throws away the tree id and then the -p
 # parameters). Note that this handles merges properly! In case Darl
 #
 #      git-filter-branch ... new-H C..H --not D
 #      git-filter-branch ... new-H 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' directorymoved
 
 # Testsuite: TODO
 
@@ -184,7 +196,8 @@ USAGE="git-filter-branch [-d TEMPDIR] [FILTERS] DESTBRANCH [REV-RANGE]"
 
 map()
 {
-       [ -r "$workdir/../map/$1" ] || return 1
+       # if it was not rewritten, take the original
+       test -r "$workdir/../map/$1" || echo "$1"
        cat "$workdir/../map/$1"
 }
 
@@ -221,19 +234,15 @@ set_ident () {
        echo "[ -n \"\$GIT_${uid}_NAME\" ] || export GIT_${uid}_NAME=\"\${GIT_${uid}_EMAIL%%@*}\""
 }
 
-# list all parent's object names for a given commit
-get_parents () {
-       git-rev-list -1 --parents "$1" | sed "s/^[0-9a-f]*//"
-}
-
 tempdir=.git-rewrite
 filter_env=
 filter_tree=
 filter_index=
 filter_parent=
 filter_msg=cat
-filter_commit='git-commit-tree "$@"'
+filter_commit='git commit-tree "$@"'
 filter_tag_name=
+filter_subdir=
 while case "$#" in 0) usage;; esac
 do
        case "$1" in
@@ -279,6 +288,9 @@ do
        --tag-name-filter)
                filter_tag_name="$OPTARG"
                ;;
+       --subdirectory-filter)
+               filter_subdir="$OPTARG"
+               ;;
        *)
                usage
                ;;
@@ -288,7 +300,7 @@ done
 dstbranch="$1"
 shift
 test -n "$dstbranch" || die "missing branch name"
-git-show-ref "refs/heads/$dstbranch" 2> /dev/null &&
+git show-ref "refs/heads/$dstbranch" 2> /dev/null &&
        die "branch $dstbranch already exists"
 
 test ! -e "$tempdir" || die "$tempdir already exists, please remove it"
@@ -300,61 +312,71 @@ case "$GIT_DIR" in
 /*)
        ;;
 *)
-       export GIT_DIR="$(pwd)/../../$GIT_DIR"
+       GIT_DIR="$(pwd)/../../$GIT_DIR"
        ;;
 esac
+export GIT_DIR GIT_WORK_TREE=.
 
 export GIT_INDEX_FILE="$(pwd)/../index"
-git-read-tree # seed the index file
+git read-tree # seed the index file
 
 ret=0
 
 
 mkdir ../map # map old->new commit ids for rewriting parents
 
-git-rev-list --reverse --topo-order --default HEAD "$@" >../revs
+case "$filter_subdir" in
+"")
+       git rev-list --reverse --topo-order --default HEAD \
+               --parents "$@"
+       ;;
+*)
+       git rev-list --reverse --topo-order --default HEAD \
+               --parents --full-history "$@" -- "$filter_subdir"
+esac > ../revs
 commits=$(cat ../revs | wc -l | tr -d " ")
 
 test $commits -eq 0 && die "Found nothing to rewrite"
 
 i=0
-while read commit; do
+while read commit parents; do
        i=$(($i+1))
        printf "$commit ($i/$commits) "
 
-       git-read-tree -i -m $commit
+       case "$filter_subdir" in
+       "")
+               git read-tree -i -m $commit
+               ;;
+       *)
+               git read-tree -i -m $commit:"$filter_subdir"
+       esac
 
        export GIT_COMMIT=$commit
-       git-cat-file commit "$commit" >../commit
+       git cat-file commit "$commit" >../commit
 
        eval "$(set_ident AUTHOR <../commit)"
        eval "$(set_ident COMMITTER <../commit)"
        eval "$filter_env" < /dev/null
 
        if [ "$filter_tree" ]; then
-               git-checkout-index -f -u -a
+               git checkout-index -f -u -a
                # files that $commit removed are now still in the working tree;
                # remove them, else they would be added again
-               git-ls-files -z --others | xargs -0 rm -f
+               git ls-files -z --others | xargs -0 rm -f
                eval "$filter_tree" < /dev/null
-               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
+               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
        fi
 
        eval "$filter_index" < /dev/null
 
        parentstr=
-       for parent in $(get_parents $commit); do
-               if [ -r "../map/$parent" ]; then
-                       for reparent in $(cat "../map/$parent"); do
-                               parentstr="$parentstr -p $reparent"
-                       done
-               else
-                       # if it was not rewritten, take the original
-                       parentstr="$parentstr -p $parent"
-               fi
+       for parent in $parents; do
+               for reparent in $(map "$parent"); do
+                       parentstr="$parentstr -p $reparent"
+               done
        done
        if [ "$filter_parent" ]; then
                parentstr="$(echo "$parentstr" | eval "$filter_parent")"
@@ -362,18 +384,18 @@ while read commit; do
 
        sed -e '1,/^$/d' <../commit | \
                eval "$filter_msg" | \
-               sh -c "$filter_commit" git-commit-tree $(git-write-tree) $parentstr | \
+               sh -c "$filter_commit" "git commit-tree" $(git write-tree) $parentstr | \
                tee ../map/$commit
 done <../revs
 
-src_head=$(tail -n 1 ../revs)
+src_head=$(tail -n 1 ../revs | sed -e 's/ .*//')
 target_head=$(head -n 1 ../map/$src_head)
 case "$target_head" in
 '')
        echo Nothing rewritten
        ;;
 *)
-       git-update-ref refs/heads/"$dstbranch" $target_head
+       git update-ref refs/heads/"$dstbranch" $target_head
        if [ $(cat ../map/$src_head | wc -l) -gt 1 ]; then
                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
@@ -383,7 +405,7 @@ case "$target_head" in
 esac
 
 if [ "$filter_tag_name" ]; then
-       git-for-each-ref --format='%(objectname) %(objecttype) %(refname)' refs/tags |
+       git for-each-ref --format='%(objectname) %(objecttype) %(refname)' refs/tags |
        while read sha1 type ref; do
                ref="${ref#refs/tags/}"
                # XXX: Rewrite tagged trees as well?
@@ -394,7 +416,7 @@ if [ "$filter_tag_name" ]; then
                if [ "$type" = "tag" ]; then
                        # Dereference to a commit
                        sha1t="$sha1"
-                       sha1="$(git-rev-parse "$sha1"^{commit} 2>/dev/null)" || continue
+                       sha1="$(git rev-parse "$sha1"^{commit} 2>/dev/null)" || continue
                fi
 
                [ -f "../map/$sha1" ] || continue
@@ -409,7 +431,7 @@ if [ "$filter_tag_name" ]; then
                        warn "unreferencing tag object $sha1t"
                fi
 
-               git-update-ref "refs/tags/$new_ref" "$new_sha1"
+               git update-ref "refs/tags/$new_ref" "$new_sha1"
        done
 fi