git-filter-branch.shon commit filter-branch documentation: some more touch-ups. (32c37c1)
   1#!/bin/sh
   2#
   3# Rewrite revision history
   4# Copyright (c) Petr Baudis, 2006
   5# Minimal changes to "port" it to core-git (c) Johannes Schindelin, 2007
   6#
   7# Lets you rewrite the revision history of the current branch, creating
   8# a new branch. You can specify a number of filters to modify the commits,
   9# files and trees.
  10
  11set -e
  12
  13USAGE="git-filter-branch [-d TEMPDIR] [FILTERS] DESTBRANCH [REV-RANGE]"
  14. git-sh-setup
  15
  16warn () {
  17        echo "$*" >&2
  18}
  19
  20map()
  21{
  22        # if it was not rewritten, take the original
  23        if test -r "$workdir/../map/$1"
  24        then
  25                cat "$workdir/../map/$1"
  26        else
  27                echo "$1"
  28        fi
  29}
  30
  31# When piped a commit, output a script to set the ident of either
  32# "author" or "committer
  33
  34set_ident () {
  35        lid="$(echo "$1" | tr "A-Z" "a-z")"
  36        uid="$(echo "$1" | tr "a-z" "A-Z")"
  37        pick_id_script='
  38                /^'$lid' /{
  39                        s/'\''/'\''\\'\'\''/g
  40                        h
  41                        s/^'$lid' \([^<]*\) <[^>]*> .*$/\1/
  42                        s/'\''/'\''\'\'\''/g
  43                        s/.*/export GIT_'$uid'_NAME='\''&'\''/p
  44
  45                        g
  46                        s/^'$lid' [^<]* <\([^>]*\)> .*$/\1/
  47                        s/'\''/'\''\'\'\''/g
  48                        s/.*/export GIT_'$uid'_EMAIL='\''&'\''/p
  49
  50                        g
  51                        s/^'$lid' [^<]* <[^>]*> \(.*\)$/\1/
  52                        s/'\''/'\''\'\'\''/g
  53                        s/.*/export GIT_'$uid'_DATE='\''&'\''/p
  54
  55                        q
  56                }
  57        '
  58
  59        LANG=C LC_ALL=C sed -ne "$pick_id_script"
  60        # Ensure non-empty id name.
  61        echo "[ -n \"\$GIT_${uid}_NAME\" ] || export GIT_${uid}_NAME=\"\${GIT_${uid}_EMAIL%%@*}\""
  62}
  63
  64tempdir=.git-rewrite
  65filter_env=
  66filter_tree=
  67filter_index=
  68filter_parent=
  69filter_msg=cat
  70filter_commit='git commit-tree "$@"'
  71filter_tag_name=
  72filter_subdir=
  73while case "$#" in 0) usage;; esac
  74do
  75        case "$1" in
  76        --)
  77                shift
  78                break
  79                ;;
  80        -*)
  81                ;;
  82        *)
  83                break;
  84        esac
  85
  86        # all switches take one argument
  87        ARG="$1"
  88        case "$#" in 1) usage ;; esac
  89        shift
  90        OPTARG="$1"
  91        shift
  92
  93        case "$ARG" in
  94        -d)
  95                tempdir="$OPTARG"
  96                ;;
  97        --env-filter)
  98                filter_env="$OPTARG"
  99                ;;
 100        --tree-filter)
 101                filter_tree="$OPTARG"
 102                ;;
 103        --index-filter)
 104                filter_index="$OPTARG"
 105                ;;
 106        --parent-filter)
 107                filter_parent="$OPTARG"
 108                ;;
 109        --msg-filter)
 110                filter_msg="$OPTARG"
 111                ;;
 112        --commit-filter)
 113                filter_commit="$OPTARG"
 114                ;;
 115        --tag-name-filter)
 116                filter_tag_name="$OPTARG"
 117                ;;
 118        --subdirectory-filter)
 119                filter_subdir="$OPTARG"
 120                ;;
 121        *)
 122                usage
 123                ;;
 124        esac
 125done
 126
 127dstbranch="$1"
 128shift
 129test -n "$dstbranch" || die "missing branch name"
 130git show-ref "refs/heads/$dstbranch" 2> /dev/null &&
 131        die "branch $dstbranch already exists"
 132
 133test ! -e "$tempdir" || die "$tempdir already exists, please remove it"
 134mkdir -p "$tempdir/t"
 135cd "$tempdir/t"
 136workdir="$(pwd)"
 137
 138case "$GIT_DIR" in
 139/*)
 140        ;;
 141*)
 142        GIT_DIR="$(pwd)/../../$GIT_DIR"
 143        ;;
 144esac
 145export GIT_DIR GIT_WORK_TREE=.
 146
 147export GIT_INDEX_FILE="$(pwd)/../index"
 148git read-tree # seed the index file
 149
 150ret=0
 151
 152
 153mkdir ../map # map old->new commit ids for rewriting parents
 154
 155case "$filter_subdir" in
 156"")
 157        git rev-list --reverse --topo-order --default HEAD \
 158                --parents "$@"
 159        ;;
 160*)
 161        git rev-list --reverse --topo-order --default HEAD \
 162                --parents --full-history "$@" -- "$filter_subdir"
 163esac > ../revs
 164commits=$(cat ../revs | wc -l | tr -d " ")
 165
 166test $commits -eq 0 && die "Found nothing to rewrite"
 167
 168i=0
 169while read commit parents; do
 170        i=$(($i+1))
 171        printf "\rRewrite $commit ($i/$commits)"
 172
 173        case "$filter_subdir" in
 174        "")
 175                git read-tree -i -m $commit
 176                ;;
 177        *)
 178                git read-tree -i -m $commit:"$filter_subdir"
 179        esac
 180
 181        export GIT_COMMIT=$commit
 182        git cat-file commit "$commit" >../commit
 183
 184        eval "$(set_ident AUTHOR <../commit)"
 185        eval "$(set_ident COMMITTER <../commit)"
 186        eval "$filter_env" < /dev/null
 187
 188        if [ "$filter_tree" ]; then
 189                git checkout-index -f -u -a
 190                # files that $commit removed are now still in the working tree;
 191                # remove them, else they would be added again
 192                git ls-files -z --others | xargs -0 rm -f
 193                eval "$filter_tree" < /dev/null
 194                git diff-index -r $commit | cut -f 2- | tr '\n' '\0' | \
 195                        xargs -0 git update-index --add --replace --remove
 196                git ls-files -z --others | \
 197                        xargs -0 git update-index --add --replace --remove
 198        fi
 199
 200        eval "$filter_index" < /dev/null
 201
 202        parentstr=
 203        for parent in $parents; do
 204                for reparent in $(map "$parent"); do
 205                        parentstr="$parentstr -p $reparent"
 206                done
 207        done
 208        if [ "$filter_parent" ]; then
 209                parentstr="$(echo "$parentstr" | eval "$filter_parent")"
 210        fi
 211
 212        sed -e '1,/^$/d' <../commit | \
 213                eval "$filter_msg" | \
 214                sh -c "$filter_commit" "git commit-tree" $(git write-tree) \
 215                        $parentstr > ../map/$commit
 216done <../revs
 217
 218src_head=$(tail -n 1 ../revs | sed -e 's/ .*//')
 219target_head=$(head -n 1 ../map/$src_head)
 220case "$target_head" in
 221'')
 222        echo Nothing rewritten
 223        ;;
 224*)
 225        git update-ref refs/heads/"$dstbranch" $target_head
 226        if [ $(cat ../map/$src_head | wc -l) -gt 1 ]; then
 227                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
 228                sed 's/^/       /' ../map/$src_head >&2
 229                ret=1
 230        fi
 231        ;;
 232esac
 233
 234if [ "$filter_tag_name" ]; then
 235        git for-each-ref --format='%(objectname) %(objecttype) %(refname)' refs/tags |
 236        while read sha1 type ref; do
 237                ref="${ref#refs/tags/}"
 238                # XXX: Rewrite tagged trees as well?
 239                if [ "$type" != "commit" -a "$type" != "tag" ]; then
 240                        continue;
 241                fi
 242
 243                if [ "$type" = "tag" ]; then
 244                        # Dereference to a commit
 245                        sha1t="$sha1"
 246                        sha1="$(git rev-parse "$sha1"^{commit} 2>/dev/null)" || continue
 247                fi
 248
 249                [ -f "../map/$sha1" ] || continue
 250                new_sha1="$(cat "../map/$sha1")"
 251                export GIT_COMMIT="$sha1"
 252                new_ref="$(echo "$ref" | eval "$filter_tag_name")"
 253
 254                echo "$ref -> $new_ref ($sha1 -> $new_sha1)"
 255
 256                if [ "$type" = "tag" ]; then
 257                        # Warn that we are not rewriting the tag object itself.
 258                        warn "unreferencing tag object $sha1t"
 259                fi
 260
 261                git update-ref "refs/tags/$new_ref" "$new_sha1"
 262        done
 263fi
 264
 265cd ../..
 266rm -rf "$tempdir"
 267printf "\nRewritten history saved to the $dstbranch branch\n"
 268
 269exit $ret