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