t/helper: merge test-dump-untracked-cache into test-tool
[git] / git-rebase--interactive.sh
1 # This shell script fragment is sourced by git-rebase to implement
2 # its interactive mode.  "git rebase --interactive" makes it easy
3 # to fix up commits in the middle of a series and rearrange commits.
4 #
5 # Copyright (c) 2006 Johannes E. Schindelin
6 #
7 # The original idea comes from Eric W. Biederman, in
8 # https://public-inbox.org/git/m1odwkyuf5.fsf_-_@ebiederm.dsl.xmission.com/
9 #
10 # The file containing rebase commands, comments, and empty lines.
11 # This file is created by "git rebase -i" then edited by the user.  As
12 # the lines are processed, they are removed from the front of this
13 # file and written to the tail of $done.
14 todo="$state_dir"/git-rebase-todo
15
16 GIT_CHERRY_PICK_HELP="$resolvemsg"
17 export GIT_CHERRY_PICK_HELP
18
19 comment_char=$(git config --get core.commentchar 2>/dev/null)
20 case "$comment_char" in
21 '' | auto)
22         comment_char="#"
23         ;;
24 ?)
25         ;;
26 *)
27         comment_char=$(echo "$comment_char" | cut -c1)
28         ;;
29 esac
30
31 orig_reflog_action="$GIT_REFLOG_ACTION"
32
33 comment_for_reflog () {
34         case "$orig_reflog_action" in
35         ''|rebase*)
36                 GIT_REFLOG_ACTION="rebase -i ($1)"
37                 export GIT_REFLOG_ACTION
38                 ;;
39         esac
40 }
41
42 append_todo_help () {
43         gettext "
44 Commands:
45 p, pick <commit> = use commit
46 r, reword <commit> = use commit, but edit the commit message
47 e, edit <commit> = use commit, but stop for amending
48 s, squash <commit> = use commit, but meld into previous commit
49 f, fixup <commit> = like \"squash\", but discard this commit's log message
50 x, exec <command> = run command (the rest of the line) using shell
51 d, drop <commit> = remove commit
52 l, label <label> = label current HEAD with a name
53 t, reset <label> = reset HEAD to a label
54 m, merge [-C <commit> | -c <commit>] <label> [# <oneline>]
55 .       create a merge commit using the original merge commit's
56 .       message (or the oneline, if no original merge commit was
57 .       specified). Use -c <commit> to reword the commit message.
58
59 These lines can be re-ordered; they are executed from top to bottom.
60 " | git stripspace --comment-lines >>"$todo"
61
62         if test $(get_missing_commit_check_level) = error
63         then
64                 gettext "
65 Do not remove any line. Use 'drop' explicitly to remove a commit.
66 " | git stripspace --comment-lines >>"$todo"
67         else
68                 gettext "
69 If you remove a line here THAT COMMIT WILL BE LOST.
70 " | git stripspace --comment-lines >>"$todo"
71         fi
72 }
73
74 die_abort () {
75         apply_autostash
76         rm -rf "$state_dir"
77         die "$1"
78 }
79
80 has_action () {
81         test -n "$(git stripspace --strip-comments <"$1")"
82 }
83
84 git_sequence_editor () {
85         if test -z "$GIT_SEQUENCE_EDITOR"
86         then
87                 GIT_SEQUENCE_EDITOR="$(git config sequence.editor)"
88                 if [ -z "$GIT_SEQUENCE_EDITOR" ]
89                 then
90                         GIT_SEQUENCE_EDITOR="$(git var GIT_EDITOR)" || return $?
91                 fi
92         fi
93
94         eval "$GIT_SEQUENCE_EDITOR" '"$@"'
95 }
96
97 expand_todo_ids() {
98         git rebase--helper --expand-ids
99 }
100
101 collapse_todo_ids() {
102         git rebase--helper --shorten-ids
103 }
104
105 # Switch to the branch in $into and notify it in the reflog
106 checkout_onto () {
107         GIT_REFLOG_ACTION="$GIT_REFLOG_ACTION: checkout $onto_name"
108         output git checkout $onto || die_abort "$(gettext "could not detach HEAD")"
109         git update-ref ORIG_HEAD $orig_head
110 }
111
112 get_missing_commit_check_level () {
113         check_level=$(git config --get rebase.missingCommitsCheck)
114         check_level=${check_level:-ignore}
115         # Don't be case sensitive
116         printf '%s' "$check_level" | tr 'A-Z' 'a-z'
117 }
118
119 # Initiate an action. If the cannot be any
120 # further action it  may exec a command
121 # or exit and not return.
122 #
123 # TODO: Consider a cleaner return model so it
124 # never exits and always return 0 if process
125 # is complete.
126 #
127 # Parameter 1 is the action to initiate.
128 #
129 # Returns 0 if the action was able to complete
130 # and if 1 if further processing is required.
131 initiate_action () {
132         case "$1" in
133         continue)
134                 exec git rebase--helper ${force_rebase:+--no-ff} $allow_empty_message \
135                      --continue
136                 ;;
137         skip)
138                 git rerere clear
139                 exec git rebase--helper ${force_rebase:+--no-ff} $allow_empty_message \
140                      --continue
141                 ;;
142         edit-todo)
143                 git stripspace --strip-comments <"$todo" >"$todo".new
144                 mv -f "$todo".new "$todo"
145                 collapse_todo_ids
146                 append_todo_help
147                 gettext "
148 You are editing the todo file of an ongoing interactive rebase.
149 To continue rebase after editing, run:
150     git rebase --continue
151
152 " | git stripspace --comment-lines >>"$todo"
153
154                 git_sequence_editor "$todo" ||
155                         die "$(gettext "Could not execute editor")"
156                 expand_todo_ids
157
158                 exit
159                 ;;
160         show-current-patch)
161                 exec git show REBASE_HEAD --
162                 ;;
163         *)
164                 return 1 # continue
165                 ;;
166         esac
167 }
168
169 setup_reflog_action () {
170         comment_for_reflog start
171
172         if test ! -z "$switch_to"
173         then
174                 GIT_REFLOG_ACTION="$GIT_REFLOG_ACTION: checkout $switch_to"
175                 output git checkout "$switch_to" -- ||
176                         die "$(eval_gettext "Could not checkout \$switch_to")"
177
178                 comment_for_reflog start
179         fi
180 }
181
182 init_basic_state () {
183         orig_head=$(git rev-parse --verify HEAD) || die "$(gettext "No HEAD?")"
184         mkdir -p "$state_dir" || die "$(eval_gettext "Could not create temporary \$state_dir")"
185         rm -f "$(git rev-parse --git-path REBASE_HEAD)"
186
187         : > "$state_dir"/interactive || die "$(gettext "Could not mark as interactive")"
188         write_basic_state
189 }
190
191 init_revisions_and_shortrevisions () {
192         shorthead=$(git rev-parse --short $orig_head)
193         shortonto=$(git rev-parse --short $onto)
194         if test -z "$rebase_root"
195                 # this is now equivalent to ! -z "$upstream"
196         then
197                 shortupstream=$(git rev-parse --short $upstream)
198                 revisions=$upstream...$orig_head
199                 shortrevisions=$shortupstream..$shorthead
200         else
201                 revisions=$onto...$orig_head
202                 shortrevisions=$shorthead
203                 test -z "$squash_onto" ||
204                 echo "$squash_onto" >"$state_dir"/squash-onto
205         fi
206 }
207
208 complete_action() {
209         test -s "$todo" || echo noop >> "$todo"
210         test -z "$autosquash" || git rebase--helper --rearrange-squash || exit
211         test -n "$cmd" && git rebase--helper --add-exec-commands "$cmd"
212
213         todocount=$(git stripspace --strip-comments <"$todo" | wc -l)
214         todocount=${todocount##* }
215
216 cat >>"$todo" <<EOF
217
218 $comment_char $(eval_ngettext \
219         "Rebase \$shortrevisions onto \$shortonto (\$todocount command)" \
220         "Rebase \$shortrevisions onto \$shortonto (\$todocount commands)" \
221         "$todocount")
222 EOF
223         append_todo_help
224         gettext "
225         However, if you remove everything, the rebase will be aborted.
226
227         " | git stripspace --comment-lines >>"$todo"
228
229         if test -z "$keep_empty"
230         then
231                 printf '%s\n' "$comment_char $(gettext "Note that empty commits are commented out")" >>"$todo"
232         fi
233
234
235         has_action "$todo" ||
236                 return 2
237
238         cp "$todo" "$todo".backup
239         collapse_todo_ids
240         git_sequence_editor "$todo" ||
241                 die_abort "$(gettext "Could not execute editor")"
242
243         has_action "$todo" ||
244                 return 2
245
246         git rebase--helper --check-todo-list || {
247                 ret=$?
248                 checkout_onto
249                 exit $ret
250         }
251
252         expand_todo_ids
253
254         test -n "$force_rebase" ||
255         onto="$(git rebase--helper --skip-unnecessary-picks)" ||
256         die "Could not skip unnecessary pick commands"
257
258         checkout_onto
259         require_clean_work_tree "rebase"
260         exec git rebase--helper ${force_rebase:+--no-ff} $allow_empty_message \
261              --continue
262 }
263
264 git_rebase__interactive () {
265         initiate_action "$action"
266         ret=$?
267         if test $ret = 0; then
268                 return 0
269         fi
270
271         setup_reflog_action
272         init_basic_state
273
274         init_revisions_and_shortrevisions
275
276         git rebase--helper --make-script ${keep_empty:+--keep-empty} \
277                 ${rebase_merges:+--rebase-merges} \
278                 ${rebase_cousins:+--rebase-cousins} \
279                 $revisions ${restrict_revision+^$restrict_revision} >"$todo" ||
280         die "$(gettext "Could not generate todo list")"
281
282         complete_action
283 }