Code

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