PageRenderTime 49ms CodeModel.GetById 15ms RepoModel.GetById 0ms app.codeStats 0ms

/git-filter-branch.sh

https://bitbucket.org/slamuser/git
Shell | 516 lines | 411 code | 61 blank | 44 comment | 51 complexity | 82af6e89fbef9ad171f37cb125e0011f MD5 | raw file
  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. # The following functions will also be available in the commit filter:
  11. functions=$(cat << \EOF
  12. warn () {
  13. echo "$*" >&2
  14. }
  15. map()
  16. {
  17. # if it was not rewritten, take the original
  18. if test -r "$workdir/../map/$1"
  19. then
  20. cat "$workdir/../map/$1"
  21. else
  22. echo "$1"
  23. fi
  24. }
  25. # if you run 'skip_commit "$@"' in a commit filter, it will print
  26. # the (mapped) parents, effectively skipping the commit.
  27. skip_commit()
  28. {
  29. shift;
  30. while [ -n "$1" ];
  31. do
  32. shift;
  33. map "$1";
  34. shift;
  35. done;
  36. }
  37. # if you run 'git_commit_non_empty_tree "$@"' in a commit filter,
  38. # it will skip commits that leave the tree untouched, commit the other.
  39. git_commit_non_empty_tree()
  40. {
  41. if test $# = 3 && test "$1" = $(git rev-parse "$3^{tree}"); then
  42. map "$3"
  43. else
  44. git commit-tree "$@"
  45. fi
  46. }
  47. # override die(): this version puts in an extra line break, so that
  48. # the progress is still visible
  49. die()
  50. {
  51. echo >&2
  52. echo "$*" >&2
  53. exit 1
  54. }
  55. EOF
  56. )
  57. eval "$functions"
  58. # When piped a commit, output a script to set the ident of either
  59. # "author" or "committer
  60. set_ident () {
  61. lid="$(echo "$1" | tr "[A-Z]" "[a-z]")"
  62. uid="$(echo "$1" | tr "[a-z]" "[A-Z]")"
  63. pick_id_script='
  64. /^'$lid' /{
  65. s/'\''/'\''\\'\'\''/g
  66. h
  67. s/^'$lid' \([^<]*\) <[^>]*> .*$/\1/
  68. s/'\''/'\''\'\'\''/g
  69. s/.*/GIT_'$uid'_NAME='\''&'\''; export GIT_'$uid'_NAME/p
  70. g
  71. s/^'$lid' [^<]* <\([^>]*\)> .*$/\1/
  72. s/'\''/'\''\'\'\''/g
  73. s/.*/GIT_'$uid'_EMAIL='\''&'\''; export GIT_'$uid'_EMAIL/p
  74. g
  75. s/^'$lid' [^<]* <[^>]*> \(.*\)$/\1/
  76. s/'\''/'\''\'\'\''/g
  77. s/.*/GIT_'$uid'_DATE='\''&'\''; export GIT_'$uid'_DATE/p
  78. q
  79. }
  80. '
  81. LANG=C LC_ALL=C sed -ne "$pick_id_script"
  82. # Ensure non-empty id name.
  83. echo "case \"\$GIT_${uid}_NAME\" in \"\") GIT_${uid}_NAME=\"\${GIT_${uid}_EMAIL%%@*}\" && export GIT_${uid}_NAME;; esac"
  84. }
  85. USAGE="[--env-filter <command>] [--tree-filter <command>]
  86. [--index-filter <command>] [--parent-filter <command>]
  87. [--msg-filter <command>] [--commit-filter <command>]
  88. [--tag-name-filter <command>] [--subdirectory-filter <directory>]
  89. [--original <namespace>] [-d <directory>] [-f | --force]
  90. [<rev-list options>...]"
  91. OPTIONS_SPEC=
  92. . git-sh-setup
  93. if [ "$(is_bare_repository)" = false ]; then
  94. git diff-files --ignore-submodules --quiet &&
  95. git diff-index --cached --quiet HEAD -- ||
  96. die "Cannot rewrite branch(es) with a dirty working directory."
  97. fi
  98. tempdir=.git-rewrite
  99. filter_env=
  100. filter_tree=
  101. filter_index=
  102. filter_parent=
  103. filter_msg=cat
  104. filter_commit=
  105. filter_tag_name=
  106. filter_subdir=
  107. orig_namespace=refs/original/
  108. force=
  109. prune_empty=
  110. remap_to_ancestor=
  111. while :
  112. do
  113. case "$1" in
  114. --)
  115. shift
  116. break
  117. ;;
  118. --force|-f)
  119. shift
  120. force=t
  121. continue
  122. ;;
  123. --remap-to-ancestor)
  124. # deprecated ($remap_to_ancestor is set now automatically)
  125. shift
  126. remap_to_ancestor=t
  127. continue
  128. ;;
  129. --prune-empty)
  130. shift
  131. prune_empty=t
  132. continue
  133. ;;
  134. -*)
  135. ;;
  136. *)
  137. break;
  138. esac
  139. # all switches take one argument
  140. ARG="$1"
  141. case "$#" in 1) usage ;; esac
  142. shift
  143. OPTARG="$1"
  144. shift
  145. case "$ARG" in
  146. -d)
  147. tempdir="$OPTARG"
  148. ;;
  149. --env-filter)
  150. filter_env="$OPTARG"
  151. ;;
  152. --tree-filter)
  153. filter_tree="$OPTARG"
  154. ;;
  155. --index-filter)
  156. filter_index="$OPTARG"
  157. ;;
  158. --parent-filter)
  159. filter_parent="$OPTARG"
  160. ;;
  161. --msg-filter)
  162. filter_msg="$OPTARG"
  163. ;;
  164. --commit-filter)
  165. filter_commit="$functions; $OPTARG"
  166. ;;
  167. --tag-name-filter)
  168. filter_tag_name="$OPTARG"
  169. ;;
  170. --subdirectory-filter)
  171. filter_subdir="$OPTARG"
  172. remap_to_ancestor=t
  173. ;;
  174. --original)
  175. orig_namespace=$(expr "$OPTARG/" : '\(.*[^/]\)/*$')/
  176. ;;
  177. *)
  178. usage
  179. ;;
  180. esac
  181. done
  182. case "$prune_empty,$filter_commit" in
  183. ,)
  184. filter_commit='git commit-tree "$@"';;
  185. t,)
  186. filter_commit="$functions;"' git_commit_non_empty_tree "$@"';;
  187. ,*)
  188. ;;
  189. *)
  190. die "Cannot set --prune-empty and --commit-filter at the same time"
  191. esac
  192. case "$force" in
  193. t)
  194. rm -rf "$tempdir"
  195. ;;
  196. '')
  197. test -d "$tempdir" &&
  198. die "$tempdir already exists, please remove it"
  199. esac
  200. mkdir -p "$tempdir/t" &&
  201. tempdir="$(cd "$tempdir"; pwd)" &&
  202. cd "$tempdir/t" &&
  203. workdir="$(pwd)" ||
  204. die ""
  205. # Remove tempdir on exit
  206. trap 'cd ../..; rm -rf "$tempdir"' 0
  207. ORIG_GIT_DIR="$GIT_DIR"
  208. ORIG_GIT_WORK_TREE="$GIT_WORK_TREE"
  209. ORIG_GIT_INDEX_FILE="$GIT_INDEX_FILE"
  210. GIT_WORK_TREE=.
  211. export GIT_DIR GIT_WORK_TREE
  212. # Make sure refs/original is empty
  213. git for-each-ref > "$tempdir"/backup-refs || exit
  214. while read sha1 type name
  215. do
  216. case "$force,$name" in
  217. ,$orig_namespace*)
  218. die "Cannot create a new backup.
  219. A previous backup already exists in $orig_namespace
  220. Force overwriting the backup with -f"
  221. ;;
  222. t,$orig_namespace*)
  223. git update-ref -d "$name" $sha1
  224. ;;
  225. esac
  226. done < "$tempdir"/backup-refs
  227. # The refs should be updated if their heads were rewritten
  228. git rev-parse --no-flags --revs-only --symbolic-full-name \
  229. --default HEAD "$@" > "$tempdir"/raw-heads || exit
  230. sed -e '/^^/d' "$tempdir"/raw-heads >"$tempdir"/heads
  231. test -s "$tempdir"/heads ||
  232. die "Which ref do you want to rewrite?"
  233. GIT_INDEX_FILE="$(pwd)/../index"
  234. export GIT_INDEX_FILE
  235. # map old->new commit ids for rewriting parents
  236. mkdir ../map || die "Could not create map/ directory"
  237. # we need "--" only if there are no path arguments in $@
  238. nonrevs=$(git rev-parse --no-revs "$@") || exit
  239. if test -z "$nonrevs"
  240. then
  241. dashdash=--
  242. else
  243. dashdash=
  244. remap_to_ancestor=t
  245. fi
  246. rev_args=$(git rev-parse --revs-only "$@")
  247. case "$filter_subdir" in
  248. "")
  249. eval set -- "$(git rev-parse --sq --no-revs "$@")"
  250. ;;
  251. *)
  252. eval set -- "$(git rev-parse --sq --no-revs "$@" $dashdash \
  253. "$filter_subdir")"
  254. ;;
  255. esac
  256. git rev-list --reverse --topo-order --default HEAD \
  257. --parents --simplify-merges $rev_args "$@" > ../revs ||
  258. die "Could not get the commits"
  259. commits=$(wc -l <../revs | tr -d " ")
  260. test $commits -eq 0 && die "Found nothing to rewrite"
  261. # Rewrite the commits
  262. git_filter_branch__commit_count=0
  263. while read commit parents; do
  264. git_filter_branch__commit_count=$(($git_filter_branch__commit_count+1))
  265. printf "\rRewrite $commit ($git_filter_branch__commit_count/$commits)"
  266. case "$filter_subdir" in
  267. "")
  268. git read-tree -i -m $commit
  269. ;;
  270. *)
  271. # The commit may not have the subdirectory at all
  272. err=$(git read-tree -i -m $commit:"$filter_subdir" 2>&1) || {
  273. if ! git rev-parse -q --verify $commit:"$filter_subdir"
  274. then
  275. rm -f "$GIT_INDEX_FILE"
  276. else
  277. echo >&2 "$err"
  278. false
  279. fi
  280. }
  281. esac || die "Could not initialize the index"
  282. GIT_COMMIT=$commit
  283. export GIT_COMMIT
  284. git cat-file commit "$commit" >../commit ||
  285. die "Cannot read commit $commit"
  286. eval "$(set_ident AUTHOR <../commit)" ||
  287. die "setting author failed for commit $commit"
  288. eval "$(set_ident COMMITTER <../commit)" ||
  289. die "setting committer failed for commit $commit"
  290. eval "$filter_env" < /dev/null ||
  291. die "env filter failed: $filter_env"
  292. if [ "$filter_tree" ]; then
  293. git checkout-index -f -u -a ||
  294. die "Could not checkout the index"
  295. # files that $commit removed are now still in the working tree;
  296. # remove them, else they would be added again
  297. git clean -d -q -f -x
  298. eval "$filter_tree" < /dev/null ||
  299. die "tree filter failed: $filter_tree"
  300. (
  301. git diff-index -r --name-only --ignore-submodules $commit &&
  302. git ls-files --others
  303. ) > "$tempdir"/tree-state || exit
  304. git update-index --add --replace --remove --stdin \
  305. < "$tempdir"/tree-state || exit
  306. fi
  307. eval "$filter_index" < /dev/null ||
  308. die "index filter failed: $filter_index"
  309. parentstr=
  310. for parent in $parents; do
  311. for reparent in $(map "$parent"); do
  312. parentstr="$parentstr -p $reparent"
  313. done
  314. done
  315. if [ "$filter_parent" ]; then
  316. parentstr="$(echo "$parentstr" | eval "$filter_parent")" ||
  317. die "parent filter failed: $filter_parent"
  318. fi
  319. sed -e '1,/^$/d' <../commit | \
  320. eval "$filter_msg" > ../message ||
  321. die "msg filter failed: $filter_msg"
  322. workdir=$workdir @SHELL_PATH@ -c "$filter_commit" "git commit-tree" \
  323. $(git write-tree) $parentstr < ../message > ../map/$commit ||
  324. die "could not write rewritten commit"
  325. done <../revs
  326. # If we are filtering for paths, as in the case of a subdirectory
  327. # filter, it is possible that a specified head is not in the set of
  328. # rewritten commits, because it was pruned by the revision walker.
  329. # Ancestor remapping fixes this by mapping these heads to the unique
  330. # nearest ancestor that survived the pruning.
  331. if test "$remap_to_ancestor" = t
  332. then
  333. while read ref
  334. do
  335. sha1=$(git rev-parse "$ref"^0)
  336. test -f "$workdir"/../map/$sha1 && continue
  337. ancestor=$(git rev-list --simplify-merges -1 "$ref" "$@")
  338. test "$ancestor" && echo $(map $ancestor) >> "$workdir"/../map/$sha1
  339. done < "$tempdir"/heads
  340. fi
  341. # Finally update the refs
  342. _x40='[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]'
  343. _x40="$_x40$_x40$_x40$_x40$_x40$_x40$_x40$_x40"
  344. echo
  345. while read ref
  346. do
  347. # avoid rewriting a ref twice
  348. test -f "$orig_namespace$ref" && continue
  349. sha1=$(git rev-parse "$ref"^0)
  350. rewritten=$(map $sha1)
  351. test $sha1 = "$rewritten" &&
  352. warn "WARNING: Ref '$ref' is unchanged" &&
  353. continue
  354. case "$rewritten" in
  355. '')
  356. echo "Ref '$ref' was deleted"
  357. git update-ref -m "filter-branch: delete" -d "$ref" $sha1 ||
  358. die "Could not delete $ref"
  359. ;;
  360. $_x40)
  361. echo "Ref '$ref' was rewritten"
  362. if ! git update-ref -m "filter-branch: rewrite" \
  363. "$ref" $rewritten $sha1 2>/dev/null; then
  364. if test $(git cat-file -t "$ref") = tag; then
  365. if test -z "$filter_tag_name"; then
  366. warn "WARNING: You said to rewrite tagged commits, but not the corresponding tag."
  367. warn "WARNING: Perhaps use '--tag-name-filter cat' to rewrite the tag."
  368. fi
  369. else
  370. die "Could not rewrite $ref"
  371. fi
  372. fi
  373. ;;
  374. *)
  375. # NEEDSWORK: possibly add -Werror, making this an error
  376. warn "WARNING: '$ref' was rewritten into multiple commits:"
  377. warn "$rewritten"
  378. warn "WARNING: Ref '$ref' points to the first one now."
  379. rewritten=$(echo "$rewritten" | head -n 1)
  380. git update-ref -m "filter-branch: rewrite to first" \
  381. "$ref" $rewritten $sha1 ||
  382. die "Could not rewrite $ref"
  383. ;;
  384. esac
  385. git update-ref -m "filter-branch: backup" "$orig_namespace$ref" $sha1 ||
  386. exit
  387. done < "$tempdir"/heads
  388. # TODO: This should possibly go, with the semantics that all positive given
  389. # refs are updated, and their original heads stored in refs/original/
  390. # Filter tags
  391. if [ "$filter_tag_name" ]; then
  392. git for-each-ref --format='%(objectname) %(objecttype) %(refname)' refs/tags |
  393. while read sha1 type ref; do
  394. ref="${ref#refs/tags/}"
  395. # XXX: Rewrite tagged trees as well?
  396. if [ "$type" != "commit" -a "$type" != "tag" ]; then
  397. continue;
  398. fi
  399. if [ "$type" = "tag" ]; then
  400. # Dereference to a commit
  401. sha1t="$sha1"
  402. sha1="$(git rev-parse -q "$sha1"^{commit})" || continue
  403. fi
  404. [ -f "../map/$sha1" ] || continue
  405. new_sha1="$(cat "../map/$sha1")"
  406. GIT_COMMIT="$sha1"
  407. export GIT_COMMIT
  408. new_ref="$(echo "$ref" | eval "$filter_tag_name")" ||
  409. die "tag name filter failed: $filter_tag_name"
  410. echo "$ref -> $new_ref ($sha1 -> $new_sha1)"
  411. if [ "$type" = "tag" ]; then
  412. new_sha1=$( ( printf 'object %s\ntype commit\ntag %s\n' \
  413. "$new_sha1" "$new_ref"
  414. git cat-file tag "$ref" |
  415. sed -n \
  416. -e '1,/^$/{
  417. /^object /d
  418. /^type /d
  419. /^tag /d
  420. }' \
  421. -e '/^-----BEGIN PGP SIGNATURE-----/q' \
  422. -e 'p' ) |
  423. git mktag) ||
  424. die "Could not create new tag object for $ref"
  425. if git cat-file tag "$ref" | \
  426. sane_grep '^-----BEGIN PGP SIGNATURE-----' >/dev/null 2>&1
  427. then
  428. warn "gpg signature stripped from tag object $sha1t"
  429. fi
  430. fi
  431. git update-ref "refs/tags/$new_ref" "$new_sha1" ||
  432. die "Could not write tag $new_ref"
  433. done
  434. fi
  435. cd ../..
  436. rm -rf "$tempdir"
  437. trap - 0
  438. unset GIT_DIR GIT_WORK_TREE GIT_INDEX_FILE
  439. test -z "$ORIG_GIT_DIR" || {
  440. GIT_DIR="$ORIG_GIT_DIR" && export GIT_DIR
  441. }
  442. test -z "$ORIG_GIT_WORK_TREE" || {
  443. GIT_WORK_TREE="$ORIG_GIT_WORK_TREE" &&
  444. export GIT_WORK_TREE
  445. }
  446. test -z "$ORIG_GIT_INDEX_FILE" || {
  447. GIT_INDEX_FILE="$ORIG_GIT_INDEX_FILE" &&
  448. export GIT_INDEX_FILE
  449. }
  450. if [ "$(is_bare_repository)" = false ]; then
  451. git read-tree -u -m HEAD || exit
  452. fi
  453. exit 0