Merge branch 'master' into next
[git/platforms/storm.git] / git-rebase--interactive.sh
blob2a01182c7134d28e93477da88fce336a949f05af
1 #!/bin/sh
3 # Copyright (c) 2006 Johannes E. Schindelin
5 # SHORT DESCRIPTION
7 # This script makes it easy to fix up commits in the middle of a series,
8 # and rearrange commits.
10 # The original idea comes from Eric W. Biederman, in
11 # http://article.gmane.org/gmane.comp.version-control.git/22407
13 USAGE='(--continue | --abort | --skip | [--preserve-merges] [--first-parent]
14 [--preserve-tags] [--verbose] [--onto <branch>] <upstream> [<branch>])'
16 OPTIONS_SPEC=
17 . git-sh-setup
18 require_work_tree
20 DOTEST="$GIT_DIR/.dotest-merge"
21 TODO="$DOTEST"/git-rebase-todo
22 DONE="$DOTEST"/done
23 MSG="$DOTEST"/message
24 SQUASH_MSG="$DOTEST"/message-squash
25 PRESERVE_MERGES=
26 STRATEGY=
27 VERBOSE=
28 test -f "$DOTEST"/strategy && STRATEGY="$(cat "$DOTEST"/strategy)"
29 test -f "$DOTEST"/verbose && VERBOSE=t
31 GIT_CHERRY_PICK_HELP=" After resolving the conflicts,
32 mark the corrected paths with 'git add <paths>', and
33 run 'git rebase --continue'"
34 export GIT_CHERRY_PICK_HELP
36 mark_prefix=refs/rebase-marks/
38 warn () {
39 echo "$*" >&2
42 output () {
43 case "$VERBOSE" in
44 '')
45 output=$("$@" 2>&1 )
46 status=$?
47 test $status != 0 && printf "%s\n" "$output"
48 return $status
51 "$@"
53 esac
56 require_clean_work_tree () {
57 # test if working tree is dirty
58 git rev-parse --verify HEAD > /dev/null &&
59 git update-index --refresh &&
60 git diff-files --quiet &&
61 git diff-index --cached --quiet HEAD -- ||
62 die "Working tree is dirty"
65 ORIG_REFLOG_ACTION="$GIT_REFLOG_ACTION"
67 comment_for_reflog () {
68 case "$ORIG_REFLOG_ACTION" in
69 ''|rebase*)
70 GIT_REFLOG_ACTION="rebase -i ($1)"
71 export GIT_REFLOG_ACTION
73 esac
76 last_count=
77 mark_action_done () {
78 sed -e 1q < "$TODO" >> "$DONE"
79 sed -e 1d < "$TODO" >> "$TODO".new
80 mv -f "$TODO".new "$TODO"
81 count=$(grep -c '^[^#]' < "$DONE")
82 total=$(($count+$(grep -c '^[^#]' < "$TODO")))
83 if test "$last_count" != "$count"
84 then
85 last_count=$count
86 printf "Rebasing (%d/%d)\r" $count $total
87 test -z "$VERBOSE" || echo
91 make_patch () {
92 parent_sha1=$(git rev-parse --verify "$1"^) ||
93 die "Cannot get patch for $1^"
94 git diff-tree -p "$parent_sha1".."$1" > "$DOTEST"/patch
95 test -f "$DOTEST"/message ||
96 git cat-file commit "$1" | sed "1,/^$/d" > "$DOTEST"/message
97 test -f "$DOTEST"/author-script ||
98 get_author_ident_from_commit "$1" > "$DOTEST"/author-script
101 die_with_patch () {
102 make_patch "$1"
103 git rerere
104 die "$2"
107 cleanup_before_quit () {
108 for ref in $(git for-each-ref --format='%(refname)' "${mark_prefix%/}")
110 git update-ref -d "$ref" "$ref" || return 1
111 done
112 rm -rf "$DOTEST"
115 die_abort () {
116 cleanup_before_quit
117 die "$1"
120 has_action () {
121 grep '^[^#]' "$1" >/dev/null
124 redo_merge () {
125 rm_sha1=$1
126 shift
128 eval "$(get_author_ident_from_commit $rm_sha1)"
129 msg="$(git cat-file commit $rm_sha1 | sed -e '1,/^$/d')"
131 if ! GIT_AUTHOR_NAME="$GIT_AUTHOR_NAME" \
132 GIT_AUTHOR_EMAIL="$GIT_AUTHOR_EMAIL" \
133 GIT_AUTHOR_DATE="$GIT_AUTHOR_DATE" \
134 output git merge $STRATEGY -m "$msg" "$@"
135 then
136 git rerere
137 printf "%s\n" "$msg" > "$GIT_DIR"/MERGE_MSG
138 die Error redoing merge $rm_sha1
140 unset rm_sha1
143 pick_one () {
144 no_ff=
145 case "$1" in -n) sha1=$2; no_ff=t ;; *) sha1=$1 ;; esac
146 output git rev-parse --verify $sha1 || die "Invalid commit name: $sha1"
147 parent_sha1=$(git rev-parse --verify $sha1^) ||
148 die "Could not get the parent of $sha1"
149 current_sha1=$(git rev-parse --verify HEAD)
150 if test "$no_ff$current_sha1" = "$parent_sha1"; then
151 output git reset --hard $sha1
152 test "a$1" = a-n && output git reset --soft $current_sha1
153 sha1=$(git rev-parse --short $sha1)
154 output warn Fast forward to $sha1
155 else
156 output git cherry-pick "$@"
160 nth_string () {
161 case "$1" in
162 *1[0-9]|*[04-9]) echo "$1"th;;
163 *1) echo "$1"st;;
164 *2) echo "$1"nd;;
165 *3) echo "$1"rd;;
166 esac
169 make_squash_message () {
170 if test -f "$SQUASH_MSG"; then
171 COUNT=$(($(sed -n "s/^# This is [^0-9]*\([1-9][0-9]*\).*/\1/p" \
172 < "$SQUASH_MSG" | sed -ne '$p')+1))
173 echo "# This is a combination of $COUNT commits."
174 sed -e 1d -e '2,/^./{
175 /^$/d
176 }' <"$SQUASH_MSG"
177 else
178 COUNT=2
179 echo "# This is a combination of two commits."
180 echo "# The first commit's message is:"
181 echo
182 git cat-file commit HEAD | sed -e '1,/^$/d'
184 echo
185 echo "# This is the $(nth_string $COUNT) commit message:"
186 echo
187 git cat-file commit $1 | sed -e '1,/^$/d'
190 peek_next_command () {
191 sed -n "1s/ .*$//p" < "$TODO"
194 mark_to_ref () {
195 if expr "$1" : "^:[0-9][0-9]*$" >/dev/null
196 then
197 echo "$mark_prefix$(printf %d ${1#:})"
198 else
199 echo "$1"
203 do_next () {
204 rm -f "$DOTEST"/message "$DOTEST"/author-script \
205 "$DOTEST"/amend || exit
206 read command sha1 rest < "$TODO"
207 case "$command" in
208 '#'*|'')
209 mark_action_done
211 pick|p)
212 comment_for_reflog pick
214 mark_action_done
215 pick_one $sha1 ||
216 die_with_patch $sha1 "Could not apply $sha1... $rest"
218 edit|e)
219 comment_for_reflog edit
221 mark_action_done
222 pick_one $sha1 ||
223 die_with_patch $sha1 "Could not apply $sha1... $rest"
224 make_patch $sha1
225 : > "$DOTEST"/amend
226 warn
227 warn "You can amend the commit now, with"
228 warn
229 warn " git commit --amend"
230 warn
231 warn "Once you are satisfied with your changes, run"
232 warn
233 warn " git rebase --continue"
234 warn
235 exit 0
237 squash|s)
238 comment_for_reflog squash
240 has_action "$DONE" ||
241 die "Cannot 'squash' without a previous commit"
243 mark_action_done
244 make_squash_message $sha1 > "$MSG"
245 case "$(peek_next_command)" in
246 squash|s)
247 EDIT_COMMIT=
248 USE_OUTPUT=output
249 cp "$MSG" "$SQUASH_MSG"
252 EDIT_COMMIT=-e
253 USE_OUTPUT=
254 rm -f "$SQUASH_MSG" || exit
256 esac
258 failed=f
259 author_script=$(get_author_ident_from_commit HEAD)
260 output git reset --soft HEAD^
261 pick_one -n $sha1 || failed=t
262 echo "$author_script" > "$DOTEST"/author-script
263 if test $failed = f
264 then
265 # This is like --amend, but with a different message
266 eval "$author_script"
267 GIT_AUTHOR_NAME="$GIT_AUTHOR_NAME" \
268 GIT_AUTHOR_EMAIL="$GIT_AUTHOR_EMAIL" \
269 GIT_AUTHOR_DATE="$GIT_AUTHOR_DATE" \
270 $USE_OUTPUT git commit --no-verify -F "$MSG" $EDIT_COMMIT || failed=t
272 if test $failed = t
273 then
274 cp "$MSG" "$GIT_DIR"/MERGE_MSG
275 warn
276 warn "Could not apply $sha1... $rest"
277 die_with_patch $sha1 ""
280 mark)
281 mark_action_done
283 mark=$(mark_to_ref :${sha1#:})
284 test :${sha1#:} = "$mark" && die "Invalid mark '$sha1'"
286 git rev-parse --verify "$mark" > /dev/null 2>&1 && \
287 warn "mark $sha1 already exist; overwriting it"
289 git update-ref "$mark" HEAD || die "update-ref failed"
291 merge|m)
292 comment_for_reflog merge
294 if ! git rev-parse --verify $sha1 > /dev/null
295 then
296 die "Invalid reference merge '$sha1' in"
297 "$command $sha1 $rest"
300 new_parents=
301 for p in $rest
303 new_parents="$new_parents $(mark_to_ref $p)"
304 done
305 new_parents="${new_parents# }"
306 test -n "$new_parents" || \
307 die "You forgot to give the parents for the" \
308 "merge $sha1. Please fix it in $TODO"
310 mark_action_done
311 redo_merge $sha1 $new_parents
313 reset|r)
314 comment_for_reflog reset
316 tmp=$(git rev-parse --verify "$(mark_to_ref $sha1)") ||
317 die "Invalid parent '$sha1' in $command $sha1 $rest"
319 mark_action_done
320 output git reset --hard $tmp
322 tag|t)
323 comment_for_reflog tag
325 mark_action_done
326 output git tag -f "$sha1"
329 warn "Unknown command: $command $sha1 $rest"
330 die_with_patch $sha1 "Please fix this in the file $TODO."
332 esac
333 test -s "$TODO" && return
335 comment_for_reflog finish &&
336 HEADNAME=$(cat "$DOTEST"/head-name) &&
337 OLDHEAD=$(cat "$DOTEST"/head) &&
338 SHORTONTO=$(git rev-parse --short $(cat "$DOTEST"/onto)) &&
339 NEWHEAD=$(git rev-parse HEAD) &&
340 case $HEADNAME in
341 refs/*)
342 message="$GIT_REFLOG_ACTION: $HEADNAME onto $SHORTONTO)" &&
343 git update-ref -m "$message" $HEADNAME $NEWHEAD $OLDHEAD &&
344 git symbolic-ref HEAD $HEADNAME
346 esac && {
347 test ! -f "$DOTEST"/verbose ||
348 git diff-tree --stat $(cat "$DOTEST"/head)..HEAD
349 } &&
350 cleanup_before_quit &&
351 git gc --auto &&
352 warn "Successfully rebased and updated $HEADNAME."
354 exit
357 do_rest () {
358 while :
360 do_next
361 done
364 get_value_from_list () {
365 # args: "key" " key1#value1 key2#value2"
366 case "$2" in
367 *" $1#"*)
368 stm_tmp="${2#* $1#}"
369 echo "${stm_tmp%% *}"
370 unset stm_tmp
373 return 1
375 esac
378 insert_value_at_key_into_list () {
379 # args: "value" "key" " key1#value1 key2#value2"
380 case "$3 " in
381 *" $2#$1 "*)
382 echo "$3"
384 *" $2#"*)
385 echo "$3"
386 return 1
389 echo "$3 $2#$1"
391 esac
394 create_extended_todo_list () {
396 if test t = "${PRESERVE_TAGS:-}"
397 then
398 tag_list=$(git show-ref --abbrev=7 --tags | \
400 while read sha1 tag
402 tag=${tag#refs/tags/}
403 if test ${last_sha1:-0000} = $sha1
404 then
405 saved_tags="$saved_tags:$tag"
406 else
407 printf "%s" "${last_sha1:+ $last_sha1#$saved_tags}"
408 last_sha1=$sha1
409 saved_tags=$tag
411 done
412 echo "${last_sha1:+ $last_sha1:$saved_tags}"
414 else
415 tag_list=
417 while IFS=_ read commit parents subject
419 if test t = "$PRESERVE_MERGES" -a \
420 "${last_parent:-$commit}" != "$commit"
421 then
422 if test t = "${delayed_mark:-f}"
423 then
424 marked_commits=$(insert_value_at_key_into_list \
425 dummy $last_parent "${marked_commits:-}")
426 delayed_mark=f
428 test "$last_parent" = $SHORTUPSTREAM && \
429 last_parent=$SHORTONTO
430 echo "reset $last_parent"
432 last_parent="${parents%% *}"
434 get_value_from_list $commit "${marked_commits:-}" \
435 >/dev/null && echo mark
437 if tmp=$(get_value_from_list $commit "$tag_list")
438 then
439 for t in $(echo $tmp | tr : ' ')
441 echo tag $t
442 done
445 case "$parents" in
446 *' '*)
447 delayed_mark=t
448 new_parents=
449 for p in ${parents#* }
451 marked_commits=$(insert_value_at_key_into_list \
452 dummy "$p" "${marked_commits:-}")
453 if test "$p" = $SHORTUPSTREAM
454 then
455 new_parents="$new_parents $SHORTONTO"
456 else
457 new_parents="$new_parents $p"
459 done
460 unset p
461 echo merge $commit $new_parents
462 unset new_parents
465 echo "pick $commit $subject"
467 esac
468 done
469 test -n "${last_parent:-}" -a "${last_parent:-}" != $SHORTUPSTREAM && \
470 echo reset $last_parent
471 ) | \
472 perl -e 'print reverse <>' | \
473 while read cmd args
475 : ${commit_mark_list:=} ${last_commit:=000}
476 case "$cmd" in
477 pick)
478 last_commit="${args%% *}"
480 mark)
481 : ${next_mark:=0}
482 if commit_mark_list=$(insert_value_at_key_into_list \
483 $next_mark $last_commit "$commit_mark_list")
484 then
485 args=":$next_mark"
486 next_mark=$(($next_mark + 1))
487 else
488 die "Internal error: two marks for" \
489 "the same commit"
492 reset)
493 if tmp=$(get_value_from_list $args "$commit_mark_list")
494 then
495 args=":$tmp"
498 merge)
499 new_args=
500 for i in ${args#* }
502 if tmp=$(get_value_from_list $i \
503 "$commit_mark_list")
504 then
505 new_args="$new_args :$tmp"
506 else
507 new_args="$new_args $i"
509 done
510 last_commit="${args%% *}"
511 args="$last_commit ${new_args# }"
513 esac
514 echo "$cmd $args"
515 done
518 while test $# != 0
520 case "$1" in
521 --continue)
522 comment_for_reflog continue
524 test -d "$DOTEST" || die "No interactive rebase running"
526 # Sanity check
527 git rev-parse --verify HEAD >/dev/null ||
528 die "Cannot read HEAD"
529 git update-index --refresh && git diff-files --quiet ||
530 die "Working tree is dirty"
532 # do we have anything to commit?
533 if git diff-index --cached --quiet HEAD --
534 then
535 : Nothing to commit -- skip this
536 else
537 . "$DOTEST"/author-script ||
538 die "Cannot find the author identity"
539 if test -f "$DOTEST"/amend
540 then
541 git reset --soft HEAD^ ||
542 die "Cannot rewind the HEAD"
544 export GIT_AUTHOR_NAME GIT_AUTHOR_EMAIL GIT_AUTHOR_DATE &&
545 git commit --no-verify -F "$DOTEST"/message -e ||
546 die "Could not commit staged changes."
549 require_clean_work_tree
550 do_rest
552 --abort)
553 comment_for_reflog abort
555 git rerere clear
556 test -d "$DOTEST" || die "No interactive rebase running"
558 HEADNAME=$(cat "$DOTEST"/head-name)
559 HEAD=$(cat "$DOTEST"/head)
560 case $HEADNAME in
561 refs/*)
562 git symbolic-ref HEAD $HEADNAME
564 esac &&
565 output git reset --hard $HEAD &&
566 cleanup_before_quit
567 exit
569 --skip)
570 comment_for_reflog skip
572 git rerere clear
573 test -d "$DOTEST" || die "No interactive rebase running"
575 output git reset --hard && do_rest
577 -s|--strategy)
578 case "$#,$1" in
579 *,*=*)
580 STRATEGY="-s "$(expr "z$1" : 'z-[^=]*=\(.*\)') ;;
581 1,*)
582 usage ;;
584 STRATEGY="-s $2"
585 shift ;;
586 esac
588 -m|--merge)
589 # we use merge anyway
591 -C*)
592 die "Interactive rebase uses merge, so $1 does not make sense"
594 -v|--verbose)
595 VERBOSE=t
597 -p|--preserve-merges)
598 PRESERVE_MERGES=t
600 -f|--first-parent)
601 FIRST_PARENT=t
602 PRESERVE_MERGES=t
604 -t|--preserve-tags)
605 PRESERVE_TAGS=t
607 -i|--interactive)
608 # yeah, we know
610 ''|-h)
611 usage
614 test -d "$DOTEST" &&
615 die "Interactive rebase already started"
617 git var GIT_COMMITTER_IDENT >/dev/null ||
618 die "You need to set your committer info first"
620 comment_for_reflog start
622 ONTO=
623 case "$1" in
624 --onto)
625 ONTO=$(git rev-parse --verify "$2") ||
626 die "Does not point to a valid commit: $2"
627 shift; shift
629 esac
631 require_clean_work_tree
633 if test ! -z "$2"
634 then
635 output git show-ref --verify --quiet "refs/heads/$2" ||
636 die "Invalid branchname: $2"
637 output git checkout "$2" ||
638 die "Could not checkout $2"
641 HEAD=$(git rev-parse --verify HEAD) || die "No HEAD?"
642 UPSTREAM=$(git rev-parse --verify "$1") || die "Invalid base"
644 mkdir "$DOTEST" || die "Could not create temporary $DOTEST"
646 test -z "$ONTO" && ONTO=$UPSTREAM
648 : > "$DOTEST"/interactive || die "Could not mark as interactive"
649 git symbolic-ref HEAD > "$DOTEST"/head-name 2> /dev/null ||
650 echo "detached HEAD" > "$DOTEST"/head-name
652 echo $HEAD > "$DOTEST"/head
653 echo $UPSTREAM > "$DOTEST"/upstream
654 echo $ONTO > "$DOTEST"/onto
655 test -z "$STRATEGY" || echo "$STRATEGY" > "$DOTEST"/strategy
656 test t = "$VERBOSE" && : > "$DOTEST"/verbose
658 SHORTUPSTREAM=$(git rev-parse --short=7 $UPSTREAM)
659 SHORTHEAD=$(git rev-parse --short=7 $HEAD)
660 SHORTONTO=$(git rev-parse --short=7 $ONTO)
661 common_rev_list_opts="--abbrev-commit --abbrev=7
662 --left-right --cherry-pick $UPSTREAM...$HEAD"
663 if test t = "$PRESERVE_MERGES" -o t = "${FIRST_PARENT:-f}" \
664 -o t = "${PRESERVE_TAGS:-}"
665 then
666 opts=
667 test t = "${FIRST_PARENT:-f}" && \
668 opts="$opts --first-parent"
669 test t != "$PRESERVE_MERGES" && \
670 opts="$opts --no-merges"
671 git rev-list --pretty='format:%h_%p_%s' --topo-order \
672 $opts $common_rev_list_opts | \
673 grep -v ^commit | \
674 create_extended_todo_list
675 else
676 git rev-list --no-merges --reverse --pretty=oneline \
677 $common_rev_list_opts | sed -n "s/^>/pick /p"
678 fi > "$TODO"
680 cat >> "$TODO" << EOF
682 # Rebase $SHORTUPSTREAM..$SHORTHEAD onto $SHORTONTO
684 # In the todo insn whenever you need to refer to a commit, in addition
685 # to the usual commit object name, you can use ':mark' syntax to refer
686 # to a commit previously marked with the 'mark' insn.
688 # Commands:
689 # pick = use commit
690 # edit = use commit, but stop for amending
691 # squash = use commit, but meld into previous commit
692 # mark :mark = mark the current HEAD for later reference
693 # reset commit = reset HEAD to the commit
694 # merge commit-M commit-P ... = redo merge commit-M with the
695 # current HEAD and the parents commit-P
696 # tag = reset tag to the current HEAD
698 # If you remove a line here THAT COMMIT WILL BE LOST.
699 # However, if you remove everything, the rebase will be aborted.
703 has_action "$TODO" ||
704 die_abort "Nothing to do"
706 cp "$TODO" "$TODO".backup
707 git_editor "$TODO" ||
708 die "Could not execute editor"
710 has_action "$TODO" ||
711 die_abort "Nothing to do"
713 output git checkout $ONTO && do_rest
715 esac
716 shift
717 done