3 # Rebase the thicket of branches -- including their merge structure -- on top
4 # of the specified upstream branch (defaults to 'junio/next'), optionally
5 # retaining "fast-forwardability" by fake-merging (using the "ours" strategy)
6 # the previous state on top of the current upstream state ("merging rebase").
10 # start the rebased branch by a fake merge of the previous state
12 # The idea is to generate our very own rebase script, then call rebase -i with
13 # our fake editor to put the rebase script into place and then let the user edit
16 # To make things prettier, we rewrite the rebase script after letting the user
17 # edit it, to replace "new" rebase commands with calls to a temporary alias ".r"
18 # that is added to help with starting the merging rebase, merging things, and
19 # cleaning up afterwards.
26 git_dir
="$(git rev-parse --git-dir)" ||
27 die
"Not in a Git directory"
31 Usage: $0 [options] <upstream>
34 -m|--merging[=<msg>] allow fast-forwarding the current to the rebased branch
35 --onto=<commit> rebase onto the given commit
40 # Extra commands for use in the rebase script
41 extra_commands
="edit bud finish mark rewind merge start_merging_rebase cleanup"
45 GIT_SEQUENCE_EDITOR
="$GIT_EDITOR" &&
46 export GIT_EDITOR GIT_SEQUENCE_EDITOR
&&
50 sed -e '/^noop/d' < "$1" >> "$git_dir"/SHEARS-SCRIPT
&&
51 mv "$git_dir"/SHEARS-SCRIPT
"$1"
53 mv "$1" "$git_dir"/SHEARS-SCRIPT
&&
54 exprs
="$(for command in $extra_commands
56 printf " -e 's/^$command\$/exec git .r &/'"
57 printf " -e 's/^$command /exec git .r &/'"
59 eval sed $exprs < "$git_dir"/SHEARS-SCRIPT
> "$1"
62 exec "$GIT_EDITOR" "$@"
67 git update-ref
-m "Marking '$1' as rewritten" refs
/rewritten
/"$1" HEAD
71 git
reset --hard refs
/rewritten
/"$1"
75 shorthead
="$(git rev-parse --short --verify HEAD)" &&
76 git for-each-ref refs
/rewritten
/ |
78 die
"Refusing to leave unmarked revision $shorthead behind"
79 git
reset --hard refs
/rewritten
/onto
88 # parse command-line arguments
90 while test $# -gt 0 && test "a$1" != a-C
92 parents
="$parents $1" &&
100 # determine whether the merge needs to be redone
101 p
="$(git rev-parse HEAD)$parents" &&
102 o
="$(git rev-list -1 --parents $orig |
104 while p
=${p# }; o
=${o# }; test -n "$p$o"
106 p1
=${p%% *}; o1
=${o%% *};
107 test $o1 = "$(git rev-parse "$p1")" ||
break
108 p
=${p#$p1}; o
=${o#$o1}
110 # either redo merge or fast-forward
113 git
reset --hard $orig
116 msg
="$(git cat-file commit $orig |
121 for parent
in $parents
126 p
="$p $(git rev-parse --verify refs/rewritten/$parent \
130 msg
="Merge $msg into HEAD"
132 git merge
-n --no-ff -m "$msg" $p
135 start_merging_rebase
() {
136 git merge
-s ours
-m "$(cat "$git_dir"/SHEARS-MERGING-MESSAGE)" "$1"
140 rm -f "$git_dir"/SHEARS-SCRIPT
&&
143 git update-ref
-d refs
/rewritten
/$rewritten
145 for rewritten
in $
(git for-each-ref refs
/rewritten
/ |
146 sed 's/^[^ ]* commit.refs\/rewritten\///')
148 test onto
= "$rewritten" ||
150 git update-ref
-d refs
/rewritten
/$rewritten
152 git config
--unset alias..r
167 base_message
="${1#--merging=}"
180 echo "Unknown option: $1" >&2
190 case " $extra_commands " in
201 sed 's/[][\\\/*?]/\\&/g'
204 merge2branch_name
() {
205 git show
-s --format=%s
"$1" |
206 sed -n -e "s/^Merge [^']*'\([^']*\).*/\1/p" \
207 -e "s/^Merge pull request #[0-9]* from //p" |
214 case " $needslabel " in
218 needslabel
="$needslabel $n"
225 origtodo
="$(git rev-list --no-merges --cherry-pick --pretty=oneline \
226 --abbrev-commit --abbrev=7 --reverse --left-right --topo-order \
228 sed -n "s
/^
>/pick
/p
")"
229 shorthead
=$
(git rev-parse
--short $head)
230 shortonto
=$
(git rev-parse
--short $onto)
232 # --topo-order has the bad habit of breaking first-parent chains over
233 # merges, so we generate the topoligical order ourselves here
235 list
="$(git log --format='%h %p' --topo-order --reverse \
239 if test -n "$merging"
241 from
=$
(git rev-parse
--short "$upstream") &&
242 to
=$
(git rev-parse
--short "$onto") &&
243 cat > "$git_dir"/SHEARS-MERGING-MESSAGE
<< EOF &&
244 Start the merging-rebase to $onto
246 This commit starts the rebase of $from to $to
249 todo
="start_merging_rebase \"$shorthead\""
251 todo
="$(printf '%s\n%s\n' "$todo" \
254 toberebased
=" $(echo "$list" | cut -f 1 -d ' ' | tr '\n' ' ')"
258 # each tip is an end point of a commit->first parent chain
259 branch_tips
="$(echo "$list" |
264 ensure_labeled
$branch_tips
266 branch_tips
="$(printf '%s\n%s' "$branch_tips" "$shorthead")"
268 for tip
in $branch_tips
270 # if this is not a commit to be rebased, skip
271 case "$toberebased" in *" $tip "*) ;; *) continue;; esac
273 # if it is handled already, skip
274 case "$handled " in *" $tip "*) continue;; esac
276 # start sub-todo for this tip
281 # if already handled, this is our branch point
284 ensure_labeled
$commit
285 subtodo
="$(printf '\nrewind %s # %s\n%s' \
286 "$
(name_commit
$commit)" \
287 "$
(git show
-s --format=%s
$commit)" \
293 line
="$(echo "$list" | grep "^
$commit ")"
294 # if there is no line, branch from the 'onto' commit
297 subtodo
="$(printf '\nbud\n%s' \
305 parents2
="`for parent in ${parents#* }
307 case "$toberebased" in
309 printf refs/rewritten/
314 subtodo
="$(printf '%s # %s\n%s' \
315 "merge
$parents2-C $commit" \
316 "$
(git show
-s --format=%s
$commit)" \
321 line
="$(echo "$origtodo" |
322 grep "^pick
$commit")"
325 line
="# skip $commit"
327 subtodo
="$(printf '%s\n%s' "$line" "$subtodo")"
330 handled
="$handled $commit"
331 commit
=${parents%% *}
334 # try to figure out the branch name
335 merged_by
="$(echo "$list" |
336 sed -n "s
/^\
([^
]*\
) [^
]* $tip$
/\
1/p
" |
338 if test -n "$merged_by"
340 branch_name
="$(merge2branch_name "$merged_by")"
341 test -z "$branch_name" ||
342 subtodo
="$(echo "$subtodo" |
344 # Branch: $branch_name")"
347 todo
="$(printf '%s\n\n%s' "$todo" "$subtodo")"
350 for commit
in $needslabel
352 linenumber
="$(echo "$todo" |
353 grep -n -e "^\
(pick\|
# skip\) $commit" \
354 -e "^merge [0-9a-f/ ]* -C $commit")"
355 linenumber=${linenumber%%:*}
356 test -n "$linenumber" ||
357 die "Internal error
: could not
find $commit in $todo"
358 todo="$
(echo "$todo" |
359 sed "${linenumber}a\\
367 fixup="$
(echo "$todo" |
368 sed "$lastline,\$d" |
369 grep -n -e '^pick [^ ]* \(fixup\|squash\)!' |
371 test -n "$fixup" || break
373 linenumber=${fixup%%:*}
374 oneline="${fixup#* }"
375 shortsha1="${oneline%% *}"
376 oneline="${oneline#* }"
377 command=${oneline%%!*}
378 oneline="${oneline#*! }"
379 oneline_regex="^pick
[^
]* $
(string2regex
"$oneline")\$
"
380 targetline="$
(echo "$todo" |
381 sed "$linenumber,\$d" |
382 grep -n "$oneline_regex" |
384 targetline=${targetline%%:*}
385 if test -n "$targetline"
387 todo="$
(echo "$todo" |
388 sed -e "${linenumber}d" \
390 $command $shortsha1 $oneline")"
391 lastline=$(($linenumber+1))
393 echo "UNHANDLED
: $oneline" >&2
394 lastline=$(($linenumber))
398 todo="$
(printf '%s\n\n%s' "$todo" "cleanup $needslabel")"
402 this="$
(cd "$(dirname "$0")" && pwd)/$
(basename "$0")"
404 test -z "$
(git for-each-ref refs
/rewritten
/)" ||
405 die "There are still rewritten revisions
"
407 alias="$
(git config
--get alias..r
)"
409 test "a
$alias" = "a
!sh
\"$this\"" ||
410 die "There is already an
'.r' alias!"
412 git config alias..r "!sh
\"$this\"" &&
413 generate_script > "$git_dir"/SHEARS-SCRIPT &&
414 GIT_EDITOR="$
(cd "$git_dir" && pwd)/SHEARS-EDITOR
" &&
415 cat > "$GIT_EDITOR" << EOF &&
418 exec "$this" edit "$
(git var GIT_EDITOR
)" "\$@
"
420 chmod +x "$GIT_EDITOR" &&
421 GIT_EDITOR="\"$GIT_EDITOR\"" &&
422 GIT_SEQUENCE_EDITOR="$GIT_EDITOR" &&
423 export GIT_EDITOR GIT_SEQUENCE_EDITOR
426 test ! -d "$git_dir"/rebase-merge &&
427 test ! -d "$git_dir"/rebase-apply ||
428 die "Rebase already
in progress
"
433 head="$
(git rev-parse HEAD
)" &&
435 onto=${onto:-$upstream}||
436 die "Could not determine rebase parameters
"
441 git rebase -i --onto "$onto" HEAD