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