Code

Merge branch 'ns/stash'
[git.git] / git-rebase--interactive.sh
1 #!/bin/sh
2 #
3 # Copyright (c) 2006 Johannes E. Schindelin
5 # SHORT DESCRIPTION
6 #
7 # This script makes it easy to fix up commits in the middle of a series,
8 # and rearrange commits.
9 #
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] [--verbose]
14         [--onto <branch>] <upstream> [<branch>])'
16 . git-sh-setup
17 require_work_tree
19 DOTEST="$GIT_DIR/.dotest-merge"
20 TODO="$DOTEST"/todo
21 DONE="$DOTEST"/done
22 REWRITTEN="$DOTEST"/rewritten
23 PRESERVE_MERGES=
24 STRATEGY=
25 VERBOSE=
27 warn () {
28         echo "$*" >&2
29 }
31 require_clean_work_tree () {
32         # test if working tree is dirty
33         git rev-parse --verify HEAD > /dev/null &&
34         git update-index --refresh &&
35         git diff-files --quiet &&
36         git diff-index --cached --quiet HEAD ||
37         die "Working tree is dirty"
38 }
40 ORIG_REFLOG_ACTION="$GIT_REFLOG_ACTION"
42 comment_for_reflog () {
43         case "$ORIG_REFLOG_ACTION" in
44         ''|rebase*)
45                 GIT_REFLOG_ACTION="rebase -i ($1)"
46                 export GIT_REFLOG_ACTION
47         esac
48 }
50 mark_action_done () {
51         sed -e 1q < "$TODO" >> "$DONE"
52         sed -e 1d < "$TODO" >> "$TODO".new
53         mv -f "$TODO".new "$TODO"
54 }
56 make_patch () {
57         parent_sha1=$(git rev-parse --verify "$1"^ 2> /dev/null)
58         git diff "$parent_sha1".."$1" > "$DOTEST"/patch
59 }
61 die_with_patch () {
62         make_patch "$1"
63         die "$2"
64 }
66 die_abort () {
67         rm -rf "$DOTEST"
68         die "$1"
69 }
71 pick_one () {
72         case "$1" in -n) sha1=$2 ;; *) sha1=$1 ;; esac
73         git rev-parse --verify $sha1 || die "Invalid commit name: $sha1"
74         test -d "$REWRITTEN" &&
75                 pick_one_preserving_merges "$@" && return
76         parent_sha1=$(git rev-parse --verify $sha1^ 2>/dev/null)
77         current_sha1=$(git rev-parse --verify HEAD)
78         if [ $current_sha1 = $parent_sha1 ]; then
79                 git reset --hard $sha1
80                 sha1=$(git rev-parse --short $sha1)
81                 warn Fast forward to $sha1
82         else
83                 git cherry-pick $STRATEGY "$@"
84         fi
85 }
87 pick_one_preserving_merges () {
88         case "$1" in -n) sha1=$2 ;; *) sha1=$1 ;; esac
89         sha1=$(git rev-parse $sha1)
91         if [ -f "$DOTEST"/current-commit ]
92         then
93                 current_commit=$(cat "$DOTEST"/current-commit) &&
94                 git rev-parse HEAD > "$REWRITTEN"/$current_commit &&
95                 rm "$DOTEST"/current-commit ||
96                 die "Cannot write current commit's replacement sha1"
97         fi
99         # rewrite parents; if none were rewritten, we can fast-forward.
100         fast_forward=t
101         preserve=t
102         new_parents=
103         for p in $(git rev-list --parents -1 $sha1 | cut -d\  -f2-)
104         do
105                 if [ -f "$REWRITTEN"/$p ]
106                 then
107                         preserve=f
108                         new_p=$(cat "$REWRITTEN"/$p)
109                         test $p != $new_p && fast_forward=f
110                         case "$new_parents" in
111                         *$new_p*)
112                                 ;; # do nothing; that parent is already there
113                         *)
114                                 new_parents="$new_parents $new_p"
115                         esac
116                 fi
117         done
118         case $fast_forward in
119         t)
120                 echo "Fast forward to $sha1"
121                 test $preserve=f && echo $sha1 > "$REWRITTEN"/$sha1
122                 ;;
123         f)
124                 test "a$1" = a-n && die "Refusing to squash a merge: $sha1"
126                 first_parent=$(expr "$new_parents" : " \([^ ]*\)")
127                 # detach HEAD to current parent
128                 git checkout $first_parent 2> /dev/null ||
129                         die "Cannot move HEAD to $first_parent"
131                 echo $sha1 > "$DOTEST"/current-commit
132                 case "$new_parents" in
133                 \ *\ *)
134                         # redo merge
135                         author_script=$(get_author_ident_from_commit $sha1)
136                         eval "$author_script"
137                         msg="$(git cat-file commit $sha1 | \
138                                 sed -e '1,/^$/d' -e "s/[\"\\]/\\\\&/g")"
139                         # NEEDSWORK: give rerere a chance
140                         if ! git merge $STRATEGY -m "$msg" $new_parents
141                         then
142                                 echo "$msg" > "$GIT_DIR"/MERGE_MSG
143                                 warn Error redoing merge $sha1
144                                 warn
145                                 warn After fixup, please use
146                                 die "$author_script git commit"
147                         fi
148                         ;;
149                 *)
150                         git cherry-pick $STRATEGY "$@" ||
151                                 die_with_patch $sha1 "Could not pick $sha1"
152                 esac
153         esac
156 do_next () {
157         read command sha1 rest < "$TODO"
158         case "$command" in
159         \#|'')
160                 mark_action_done
161                 continue
162                 ;;
163         pick)
164                 comment_for_reflog pick
166                 mark_action_done
167                 pick_one $sha1 ||
168                         die_with_patch $sha1 "Could not apply $sha1... $rest"
169                 ;;
170         edit)
171                 comment_for_reflog edit
173                 mark_action_done
174                 pick_one $sha1 ||
175                         die_with_patch $sha1 "Could not apply $sha1... $rest"
176                 make_patch $sha1
177                 warn
178                 warn "You can amend the commit now, with"
179                 warn
180                 warn "  git commit --amend"
181                 warn
182                 exit 0
183                 ;;
184         squash)
185                 comment_for_reflog squash
187                 test -z "$(grep -ve '^$' -e '^#' < $DONE)" &&
188                         die "Cannot 'squash' without a previous commit"
190                 mark_action_done
191                 failed=f
192                 pick_one -n $sha1 || failed=t
193                 MSG="$DOTEST"/message
194                 echo "# This is a combination of two commits." > "$MSG"
195                 echo "# The first commit's message is:" >> "$MSG"
196                 echo >> "$MSG"
197                 git cat-file commit HEAD | sed -e '1,/^$/d' >> "$MSG"
198                 echo >> "$MSG"
199                 echo "# And this is the 2nd commit message:" >> "$MSG"
200                 echo >> "$MSG"
201                 git cat-file commit $sha1 | sed -e '1,/^$/d' >> "$MSG"
202                 git reset --soft HEAD^
203                 author_script=$(get_author_ident_from_commit $sha1)
204                 case $failed in
205                 f)
206                         # This is like --amend, but with a different message
207                         eval "$author_script"
208                         export GIT_AUTHOR_NAME GIT_AUTHOR_EMAIL GIT_AUTHOR_DATE
209                         git commit -F "$MSG" -e
210                         ;;
211                 t)
212                         cp "$MSG" "$GIT_DIR"/MERGE_MSG
213                         warn
214                         warn "Could not apply $sha1... $rest"
215                         warn "After you fixed that, commit the result with"
216                         warn
217                         warn "  $(echo $author_script | tr '\012' ' ') \\"
218                         warn "    git commit -F \"$GIT_DIR\"/MERGE_MSG -e"
219                         die_with_patch $sha1 ""
220                 esac
221                 ;;
222         *)
223                 warn "Unknown command: $command $sha1 $rest"
224                 die_with_patch $sha1 "Please fix this in the file $TODO."
225         esac
226         test -s "$TODO" && return
228         comment_for_reflog finish &&
229         HEADNAME=$(cat "$DOTEST"/head-name) &&
230         OLDHEAD=$(cat "$DOTEST"/head) &&
231         SHORTONTO=$(git rev-parse --short $(cat "$DOTEST"/onto)) &&
232         if [ -d "$REWRITTEN" ]
233         then
234                 test -f "$DOTEST"/current-commit &&
235                         current_commit=$(cat "$DOTEST"/current-commit) &&
236                         git rev-parse HEAD > "$REWRITTEN"/$current_commit
237                 NEWHEAD=$(cat "$REWRITTEN"/$OLDHEAD)
238         else
239                 NEWHEAD=$(git rev-parse HEAD)
240         fi &&
241         message="$GIT_REFLOG_ACTION: $HEADNAME onto $SHORTONTO)" &&
242         git update-ref -m "$message" $HEADNAME $NEWHEAD $OLDHEAD &&
243         git symbolic-ref HEAD $HEADNAME &&
244         rm -rf "$DOTEST" &&
245         warn "Successfully rebased and updated $HEADNAME."
247         exit
250 do_rest () {
251         while :
252         do
253                 do_next
254         done
255         test -f "$DOTEST"/verbose &&
256                 git diff --stat $(cat "$DOTEST"/head)..HEAD
257         exit
260 while case $# in 0) break ;; esac
261 do
262         case "$1" in
263         --continue)
264                 comment_for_reflog continue
266                 test -d "$DOTEST" || die "No interactive rebase running"
268                 require_clean_work_tree
269                 do_rest
270                 ;;
271         --abort)
272                 comment_for_reflog abort
274                 test -d "$DOTEST" || die "No interactive rebase running"
276                 HEADNAME=$(cat "$DOTEST"/head-name)
277                 HEAD=$(cat "$DOTEST"/head)
278                 git symbolic-ref HEAD $HEADNAME &&
279                 git reset --hard $HEAD &&
280                 rm -rf "$DOTEST"
281                 exit
282                 ;;
283         --skip)
284                 comment_for_reflog skip
286                 test -d "$DOTEST" || die "No interactive rebase running"
288                 git reset --hard && do_rest
289                 ;;
290         -s|--strategy)
291                 shift
292                 case "$#,$1" in
293                 *,*=*)
294                         STRATEGY="-s `expr "z$1" : 'z-[^=]*=\(.*\)'`" ;;
295                 1,*)
296                         usage ;;
297                 *)
298                         STRATEGY="-s $2"
299                         shift ;;
300                 esac
301                 ;;
302         --merge)
303                 # we use merge anyway
304                 ;;
305         -C*)
306                 die "Interactive rebase uses merge, so $1 does not make sense"
307                 ;;
308         -v|--verbose)
309                 VERBOSE=t
310                 ;;
311         -p|--preserve-merges)
312                 PRESERVE_MERGES=t
313                 ;;
314         -i|--interactive)
315                 # yeah, we know
316                 ;;
317         ''|-h)
318                 usage
319                 ;;
320         *)
321                 test -d "$DOTEST" &&
322                         die "Interactive rebase already started"
324                 git var GIT_COMMITTER_IDENT >/dev/null ||
325                         die "You need to set your committer info first"
327                 comment_for_reflog start
329                 ONTO=
330                 case "$1" in
331                 --onto)
332                         ONTO=$(git rev-parse --verify "$2") ||
333                                 die "Does not point to a valid commit: $2"
334                         shift; shift
335                         ;;
336                 esac
338                 require_clean_work_tree
340                 if [ ! -z "$2"]
341                 then
342                         git show-ref --verify --quiet "refs/heads/$2" ||
343                                 die "Invalid branchname: $2"
344                         git checkout "$2" ||
345                                 die "Could not checkout $2"
346                 fi
348                 HEAD=$(git rev-parse --verify HEAD) || die "No HEAD?"
349                 UPSTREAM=$(git rev-parse --verify "$1") || die "Invalid base"
351                 test -z "$ONTO" && ONTO=$UPSTREAM
353                 mkdir "$DOTEST" || die "Could not create temporary $DOTEST"
354                 : > "$DOTEST"/interactive || die "Could not mark as interactive"
355                 git symbolic-ref HEAD > "$DOTEST"/head-name ||
356                         die "Could not get HEAD"
358                 echo $HEAD > "$DOTEST"/head
359                 echo $UPSTREAM > "$DOTEST"/upstream
360                 echo $ONTO > "$DOTEST"/onto
361                 test t = "$VERBOSE" && : > "$DOTEST"/verbose
362                 if [ t = "$PRESERVE_MERGES" ]
363                 then
364                         # $REWRITTEN contains files for each commit that is
365                         # reachable by at least one merge base of $HEAD and
366                         # $UPSTREAM. They are not necessarily rewritten, but
367                         # their children might be.
368                         # This ensures that commits on merged, but otherwise
369                         # unrelated side branches are left alone. (Think "X"
370                         # in the man page's example.)
371                         mkdir "$REWRITTEN" &&
372                         for c in $(git merge-base --all $HEAD $UPSTREAM)
373                         do
374                                 echo $ONTO > "$REWRITTEN"/$c ||
375                                         die "Could not init rewritten commits"
376                         done
377                         MERGES_OPTION=
378                 else
379                         MERGES_OPTION=--no-merges
380                 fi
382                 SHORTUPSTREAM=$(git rev-parse --short $UPSTREAM)
383                 SHORTHEAD=$(git rev-parse --short $HEAD)
384                 SHORTONTO=$(git rev-parse --short $ONTO)
385                 cat > "$TODO" << EOF
386 # Rebasing $SHORTUPSTREAM..$SHORTHEAD onto $SHORTONTO
388 # Commands:
389 #  pick = use commit
390 #  edit = use commit, but stop for amending
391 #  squash = use commit, but meld into previous commit
392 EOF
393                 git rev-list $MERGES_OPTION --pretty=oneline --abbrev-commit \
394                         --abbrev=7 --reverse $UPSTREAM..$HEAD | \
395                         sed "s/^/pick /" >> "$TODO"
397                 test -z "$(grep -ve '^$' -e '^#' < $TODO)" &&
398                         die_abort "Nothing to do"
400                 cp "$TODO" "$TODO".backup
401                 ${VISUAL:-${EDITOR:-vi}} "$TODO" ||
402                         die "Could not execute editor"
404                 test -z "$(grep -ve '^$' -e '^#' < $TODO)" &&
405                         die_abort "Nothing to do"
407                 git checkout $ONTO && do_rest
408         esac
409         shift
410 done