#!/bin/sh # # Copyright (c) 2005 Junio C Hamano. # setglobal SUBDIRECTORY_OK = 'Yes' setglobal OPTIONS_KEEPDASHDASH = '' setglobal OPTIONS_STUCKLONG = 't' setglobal OPTIONS_SPEC = '"\ git rebase [-i] [options] [--exec ] [--onto ] [] [] git rebase [-i] [options] [--exec ] [--onto ] --root [] git-rebase --continue | --abort | --skip | --edit-todo -- Available options are v,verbose! display a diffstat of what changed upstream q,quiet! be quiet. implies --no-stat autostash automatically stash/stash pop before and after fork-point use 'merge-base --fork-point' to refine upstream onto=! rebase onto given branch instead of upstream p,preserve-merges! try to recreate merges instead of ignoring them s,strategy=! use the given merge strategy no-ff! cherry-pick all commits, even if unchanged m,merge! use merging strategies to rebase i,interactive! let the user edit the list of commits to rebase x,exec=! add exec lines after each commit of the editable list k,keep-empty preserve empty commits during rebase f,force-rebase! force rebase even if branch is up to date X,strategy-option=! pass the argument through to the merge strategy stat! display a diffstat of what changed upstream n,no-stat! do not show diffstat of what changed upstream verify allow pre-rebase hook to run rerere-autoupdate allow rerere to update index with resolved conflicts root! rebase all reachable commits up to the root(s) autosquash move commits that begin with squash!/fixup! under -i committer-date-is-author-date! passed to 'git am' ignore-date! passed to 'git am' whitespace=! passed to 'git apply' ignore-whitespace! passed to 'git apply' C=! passed to 'git apply' S,gpg-sign? GPG-sign commits Actions: continue! continue abort! abort and check out the original branch skip! skip current patch and continue edit-todo! edit the todo list during an interactive rebase '" source git-sh-setup set_reflog_action rebase require_work_tree_exists cd_to_toplevel setglobal LF = '' '' setglobal ok_to_skip_pre_rebase = '' setglobal resolvemsg = "" $[gettext 'When you have resolved this problem, run "git rebase --continue". If you prefer to skip this patch, run "git rebase --skip" instead. To check out the original branch and stop rebasing, run "git rebase --abort".] "" unset onto unset restrict_revision setglobal cmd = '' setglobal strategy = '' setglobal strategy_opts = '' setglobal do_merge = '' setglobal merge_dir = ""$GIT_DIR"/rebase-merge" setglobal apply_dir = ""$GIT_DIR"/rebase-apply" setglobal verbose = '' setglobal diffstat = '' test $[git config --bool rebase.stat] = true && setglobal diffstat = 't' setglobal autostash = $[git config --bool rebase.autostash || echo false] setglobal fork_point = 'auto' setglobal git_am_opt = '' setglobal rebase_root = '' setglobal force_rebase = '' setglobal allow_rerere_autoupdate = '' # Non-empty if a rebase was in progress when 'git rebase' was invoked setglobal in_progress = '' # One of {am, merge, interactive} setglobal type = '' # One of {"$GIT_DIR"/rebase-apply, "$GIT_DIR"/rebase-merge} setglobal state_dir = '' # One of {'', continue, skip, abort}, as parsed from command line setglobal action = '' setglobal preserve_merges = '' setglobal autosquash = '' setglobal keep_empty = '' test $[git config --bool rebase.autosquash] = "true" && setglobal autosquash = 't' match $[git config --bool commit.gpgsign] { with true setglobal gpg_sign_opt = '-S' with * setglobal gpg_sign_opt = '' } proc read_basic_state { test -f "$state_dir/head-name" && test -f "$state_dir/onto" && setglobal head_name = $[cat "$state_dir"/head-name] && setglobal onto = $[cat "$state_dir"/onto] && # We always write to orig-head, but interactive rebase used to write to # head. Fall back to reading from head to cover for the case that the # user upgraded git with an ongoing interactive rebase. if test -f "$state_dir"/orig-head { setglobal orig_head = $[cat "$state_dir"/orig-head] } else { setglobal orig_head = $[cat "$state_dir"/head] } && setglobal GIT_QUIET = $[cat "$state_dir"/quiet] && test -f "$state_dir"/verbose && setglobal verbose = 't' test -f "$state_dir"/strategy && setglobal strategy = $[cat "$state_dir"/strategy] test -f "$state_dir"/strategy_opts && setglobal strategy_opts = $[cat "$state_dir"/strategy_opts] test -f "$state_dir"/allow_rerere_autoupdate && setglobal allow_rerere_autoupdate = $[cat "$state_dir"/allow_rerere_autoupdate] test -f "$state_dir"/gpg_sign_opt && setglobal gpg_sign_opt = $[cat "$state_dir"/gpg_sign_opt] } proc write_basic_state { echo $head_name > "$state_dir"/head-name && echo $onto > "$state_dir"/onto && echo $orig_head > "$state_dir"/orig-head && echo $GIT_QUIET > "$state_dir"/quiet && test t = $verbose && : > "$state_dir"/verbose test -n $strategy && echo $strategy > "$state_dir"/strategy test -n $strategy_opts && echo $strategy_opts > \ "$state_dir"/strategy_opts test -n $allow_rerere_autoupdate && echo $allow_rerere_autoupdate > \ "$state_dir"/allow_rerere_autoupdate test -n $gpg_sign_opt && echo $gpg_sign_opt > "$state_dir"/gpg_sign_opt } proc output { match $verbose { with '' setglobal output = $[@ARGV] setglobal status = $Status test $status != 0 && printf "%s\n" $output return $status with * @ARGV } } proc move_to_original_branch { match $head_name { with refs/* setglobal message = ""rebase finished: $head_name onto $onto"" git update-ref -m $message \ $head_name $[git rev-parse HEAD] $orig_head && git symbolic-ref \ -m "rebase finished: returning to $head_name" \ HEAD $head_name || die $[eval_gettext "Could not move back to \$head_name] } } proc apply_autostash { if test -f "$state_dir/autostash" { setglobal stash_sha1 = $[cat "$state_dir/autostash] if git stash apply $stash_sha1 2>&1 >/dev/null { echo $[gettext 'Applied autostash.] } else { git stash store -m "autostash" -q $stash_sha1 || die $[eval_gettext "Cannot store \$stash_sha1] gettext 'Applying autostash resulted in conflicts. Your changes are safe in the stash. You can run "git stash pop" or "git stash drop" at any time. ' } } } proc finish_rebase { apply_autostash && do { git gc --auto || true; } && rm -rf $state_dir } proc run_specific_rebase { if test $interactive_rebase = implied { setglobal GIT_EDITOR = ':' export GIT_EDITOR setglobal autosquash = '' } source git-rebase--$type setglobal ret = $Status if test $ret -eq 0 { finish_rebase } elif test $ret -eq 2 # special exit status for rebase -i { apply_autostash && rm -rf $state_dir && die "Nothing to do" } exit $ret } proc run_pre_rebase_hook { if test -z $ok_to_skip_pre_rebase && test -x $[git rev-parse --git-path hooks/pre-rebase] { $[git rev-parse --git-path hooks/pre-rebase] $(1+"$@") || die $[gettext "The pre-rebase hook refused to rebase.] } } test -f "$apply_dir"/applying && die $[gettext "It looks like git-am is in progress. Cannot rebase.] if test -d $apply_dir { setglobal type = 'am' setglobal state_dir = $apply_dir } elif test -d $merge_dir { if test -f "$merge_dir"/interactive { setglobal type = 'interactive' setglobal interactive_rebase = 'explicit' } else { setglobal type = 'merge' } setglobal state_dir = $merge_dir } test -n $type && setglobal in_progress = 't' setglobal total_argc = $Argc while test $# != 0 { match $1 { with --no-verify setglobal ok_to_skip_pre_rebase = 'yes' with --verify setglobal ok_to_skip_pre_rebase = '' with --continue|--skip|--abort|--edit-todo test $total_argc -eq 2 || usage setglobal action = $(1##--) with --onto=* setglobal onto = $(1#--onto=) with --exec=* setglobal cmd = ""$(cmd)exec $(1#--exec=)$(LF)"" test -z $interactive_rebase && setglobal interactive_rebase = 'implied' with --interactive setglobal interactive_rebase = 'explicit' with --keep-empty setglobal keep_empty = 'yes' with --preserve-merges setglobal preserve_merges = 't' test -z $interactive_rebase && setglobal interactive_rebase = 'implied' with --autosquash setglobal autosquash = 't' with --no-autosquash setglobal autosquash = '' with --fork-point setglobal fork_point = 't' with --no-fork-point setglobal fork_point = '' with --merge setglobal do_merge = 't' with --strategy-option=* setglobal strategy_opts = ""$strategy_opts $[git rev-parse --sq-quote "--$(1#--strategy-option=)]"" setglobal do_merge = 't' test -z $strategy && setglobal strategy = 'recursive' with --strategy=* setglobal strategy = $(1#--strategy=) setglobal do_merge = 't' with --no-stat setglobal diffstat = '' with --stat setglobal diffstat = 't' with --autostash setglobal autostash = 'true' with --no-autostash setglobal autostash = 'false' with --verbose setglobal verbose = 't' setglobal diffstat = 't' setglobal GIT_QUIET = '' with --quiet setglobal GIT_QUIET = 't' setglobal git_am_opt = ""$git_am_opt -q"" setglobal verbose = '' setglobal diffstat = '' with --whitespace=* setglobal git_am_opt = ""$git_am_opt --whitespace=$(1#--whitespace=)"" match $(1#--whitespace=) { with fix|strip setglobal force_rebase = 't' } with --ignore-whitespace setglobal git_am_opt = ""$git_am_opt $1"" with --committer-date-is-author-date|--ignore-date setglobal git_am_opt = ""$git_am_opt $1"" setglobal force_rebase = 't' with -C* setglobal git_am_opt = ""$git_am_opt $1"" with --root setglobal rebase_root = 't' with --force-rebase|--no-ff setglobal force_rebase = 't' with --rerere-autoupdate|--no-rerere-autoupdate setglobal allow_rerere_autoupdate = $1 with --gpg-sign setglobal gpg_sign_opt = '-S' with --gpg-sign=* setglobal gpg_sign_opt = ""-S$(1#--gpg-sign=)"" with -- shift break } shift } test $Argc -gt 2 && usage if test -n $action { test -z $in_progress && die $[gettext "No rebase in progress?] # Only interactive rebase uses detailed reflog messages if test $type = interactive && test $GIT_REFLOG_ACTION = rebase { setglobal GIT_REFLOG_ACTION = ""rebase -i ($action)"" export GIT_REFLOG_ACTION } } if test $action = "edit-todo" && test $type != "interactive" { die $[gettext "The --edit-todo action can only be used during interactive rebase.] } match $action { with continue # Sanity check git rev-parse --verify HEAD >/dev/null || die $[gettext "Cannot read HEAD] git update-index --ignore-submodules --refresh && git diff-files --quiet --ignore-submodules || do { echo $[gettext "You must edit all merge conflicts and then mark them as resolved using git add] exit 1 } read_basic_state run_specific_rebase with skip output git reset --hard HEAD || exit $? read_basic_state run_specific_rebase with abort git rerere clear read_basic_state match $head_name { with refs/* git symbolic-ref -m "rebase: aborting" HEAD $head_name || die $[eval_gettext "Could not move back to \$head_name] } output git reset --hard $orig_head finish_rebase exit with edit-todo run_specific_rebase } # Make sure no rebase is in progress if test -n $in_progress { setglobal state_dir_base = $(state_dir##*/) setglobal cmd_live_rebase = '"git rebase (--continue | --abort | --skip)'" setglobal cmd_clear_stale_rebase = ""rm -fr \"$state_dir"\"" die " $[eval_gettext 'It seems that there is already a $state_dir_base directory, and I wonder if you are in the middle of another rebase. If that is the case, please try $cmd_live_rebase If that is not the case, please $cmd_clear_stale_rebase and run me again. I am stopping in case you still have something valuable there.]" } if test -n $rebase_root && test -z $onto { test -z $interactive_rebase && setglobal interactive_rebase = 'implied' } if test -n $interactive_rebase { setglobal type = 'interactive' setglobal state_dir = $merge_dir } elif test -n $do_merge { setglobal type = 'merge' setglobal state_dir = $merge_dir } else { setglobal type = 'am' setglobal state_dir = $apply_dir } if test -z $rebase_root { match "$Argc" { with 0 if ! setglobal upstream_name = $[git rev-parse --symbolic-full-name \ --verify -q @{upstream}] { source git-parse-remote error_on_missing_default_upstream "rebase" "rebase" \ "against" "git rebase $[gettext ']" } test $fork_point = auto && setglobal fork_point = 't' with * setglobal upstream_name = $1 if test $upstream_name = "-" { setglobal upstream_name = '"@{-1}'" } shift } setglobal upstream = $[peel_committish $(upstream_name)] || die $[eval_gettext "invalid upstream \$upstream_name] setglobal upstream_arg = $upstream_name } else { if test -z $onto { setglobal empty_tree = $[git hash-object -t tree /dev/null] setglobal onto = $[git commit-tree $empty_tree] setglobal squash_onto = $onto } unset upstream_name unset upstream test $Argc -gt 1 && usage setglobal upstream_arg = '--root' } # Make sure the branch to rebase onto is valid. setglobal onto_name = $(onto-"$upstream_name") match $onto_name { with *...* if setglobal left = $(onto_name%...*), right = $(onto_name#*...) && setglobal onto = $[git merge-base --all $(left:-HEAD) $(right:-HEAD)] { match $onto { with ?*"$LF"?* die $[eval_gettext "\$onto_name: there are more than one merge bases] with '' die $[eval_gettext "\$onto_name: there is no merge base] } } else { die $[eval_gettext "\$onto_name: there is no merge base] } with * setglobal onto = $[peel_committish $onto_name] || die $[eval_gettext "Does not point to a valid commit: \$onto_name] } # If the branch to rebase is given, that is the branch we will rebase # $branch_name -- branch being rebased, or HEAD (already detached) # $orig_head -- commit object name of tip of the branch before rebasing # $head_name -- refs/heads/ or "detached HEAD" setglobal switch_to = '' match "$Argc" { with 1 # Is it "rebase other $branchname" or "rebase other $commit"? setglobal branch_name = $1 setglobal switch_to = $1 if git show-ref --verify --quiet -- "refs/heads/$1" && setglobal orig_head = $[git rev-parse -q --verify "refs/heads/$1] { setglobal head_name = ""refs/heads/$1"" } elif setglobal orig_head = $[git rev-parse -q --verify $1] { setglobal head_name = '"detached HEAD'" } else { die $[eval_gettext "fatal: no such branch: \$branch_name] } with 0 # Do not need to switch branches, we are already on it. if setglobal branch_name = $[git symbolic-ref -q HEAD] { setglobal head_name = $branch_name setglobal branch_name = $[expr "z$branch_name" : 'zrefs/heads/\(.*\)] } else { setglobal head_name = '"detached HEAD'" setglobal branch_name = 'HEAD' ;# detached } setglobal orig_head = $[git rev-parse --verify HEAD] || exit with * die "BUG: unexpected number of arguments left to parse" } if test $fork_point = t { setglobal new_upstream = $[git merge-base --fork-point $upstream_name \ $(switch_to:-HEAD)] if test -n $new_upstream { setglobal restrict_revision = $new_upstream } } if test $autostash = true && ! shell {require_clean_work_tree} 2>/dev/null { setglobal stash_sha1 = $[git stash create "autostash] || die $[gettext 'Cannot autostash] mkdir -p $state_dir && echo $stash_sha1 >"$state_dir/autostash" && setglobal stash_abbrev = $[git rev-parse --short $stash_sha1] && echo $[eval_gettext 'Created autostash: $stash_abbrev] && git reset --hard } require_clean_work_tree "rebase" $[gettext "Please commit or stash them.] # Now we are rebasing commits $upstream..$orig_head (or with --root, # everything leading up to $orig_head) on top of $onto # Check if we are already based on $onto with linear history, # but this should be done only when upstream and onto are the same # and if this is not an interactive rebase. setglobal mb = $[git merge-base $onto $orig_head] if test $type != interactive && test $upstream = $onto && test $mb = $onto && test -z $restrict_revision && # linear history? ! shell {git rev-list --parents "$onto".."$orig_head" | sane_grep " .* "} > /dev/null { if test -z $force_rebase { # Lazily switch to the target branch if needed... test -z $switch_to || env GIT_REFLOG_ACTION="$GIT_REFLOG_ACTION: checkout $switch_to" \ git checkout -q $switch_to -- say $[eval_gettext "Current branch \$branch_name is up to date.] finish_rebase exit 0 } else { say $[eval_gettext "Current branch \$branch_name is up to date, rebase forced.] } } # If a hook exists, give it a chance to interrupt run_pre_rebase_hook $upstream_arg @ARGV if test -n $diffstat { if test -n $verbose { echo $[eval_gettext "Changes from \$mb to \$onto:] } # We want color (if set), but no pager env GIT_PAGER='' git diff --stat --summary $mb $onto } test $type = interactive && run_specific_rebase # Detach HEAD and reset the tree say $[gettext "First, rewinding head to replay your work on top of it...] env GIT_REFLOG_ACTION="$GIT_REFLOG_ACTION: checkout $onto_name" \ git checkout -q "$onto^0" || die "could not detach HEAD" git update-ref ORIG_HEAD $orig_head # If the $onto is a proper descendant of the tip of the branch, then # we just fast-forwarded. if test $mb = $orig_head { say $[eval_gettext "Fast-forwarded \$branch_name to \$onto_name.] move_to_original_branch finish_rebase exit 0 } if test -n $rebase_root { setglobal revisions = ""$onto..$orig_head"" } else { setglobal revisions = ""$(restrict_revision-$upstream)..$orig_head"" } run_specific_rebase