git-sequencer is planned as a backend for user scripts that execute a sequence of git instructions and perhaps need manual intervention, for example git-rebase or git-am. Mentored-by: Christian Couder <chriscool@xxxxxxxxxxxxx> Mentored-by: Daniel Barkalow <barkalow@xxxxxxxxxxxx> Signed-off-by: Stephan Beyer <s-beyer@xxxxxxx> --- .gitignore | 1 + Makefile | 1 + command-list.txt | 1 + git-sequencer.sh | 2042 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 2045 insertions(+), 0 deletions(-) create mode 100755 git-sequencer.sh diff --git a/.gitignore b/.gitignore index a213e8e..a617039 100644 --- a/.gitignore +++ b/.gitignore @@ -110,6 +110,7 @@ git-revert git-rm git-send-email git-send-pack +git-sequencer git-sh-setup git-shell git-shortlog diff --git a/Makefile b/Makefile index b01cf1c..08facb1 100644 --- a/Makefile +++ b/Makefile @@ -248,6 +248,7 @@ SCRIPT_SH += git-rebase--interactive.sh SCRIPT_SH += git-rebase.sh SCRIPT_SH += git-repack.sh SCRIPT_SH += git-request-pull.sh +SCRIPT_SH += git-sequencer.sh SCRIPT_SH += git-sh-setup.sh SCRIPT_SH += git-stash.sh SCRIPT_SH += git-submodule.sh diff --git a/command-list.txt b/command-list.txt index 3583a33..44bb5b0 100644 --- a/command-list.txt +++ b/command-list.txt @@ -101,6 +101,7 @@ git-rev-parse ancillaryinterrogators git-rm mainporcelain common git-send-email foreignscminterface git-send-pack synchingrepositories +git-sequencer plumbingmanipulators git-shell synchelpers git-shortlog mainporcelain git-show mainporcelain common diff --git a/git-sequencer.sh b/git-sequencer.sh new file mode 100755 index 0000000..2c14af9 --- /dev/null +++ b/git-sequencer.sh @@ -0,0 +1,2042 @@ +#!/bin/sh +# A git sequencer prototype. + +SUBDIRECTORY_OK=Yes +OPTIONS_KEEPDASHDASH= +OPTIONS_SPEC="\ +git sequencer [options] [--] [<file>] +git sequencer (--continue | --abort | --skip | --edit | --status) +-- + Options to start a sequencing process +allow-dirty run even if working tree is dirty +B,batch run in batch-mode +onto= checkout the given commit or branch first +no-advice suppress advice when pausing (conflicts, etc) +q,quiet suppress output +v,verbose be more verbose + Options to restart/change a sequencing process or show information +continue continue paused sequencer process +abort restore original branch and abort +skip skip current patch and continue +status show the status of the sequencing process +edit invoke editor to let user edit the remaining insns + Options to be used by user scripts +caller= provide information string: name|abort|cont|skip +" + +. git-sh-setup +require_work_tree + +git var GIT_COMMITTER_IDENT >/dev/null || + die 'You need to set your committer info first' + +SEQ_DIR="$GIT_DIR/sequencer" +TODO="$SEQ_DIR/todo" +DONE="$SEQ_DIR/done" +MSG="$SEQ_DIR/message" +PATCH="$SEQ_DIR/patch" +AUTHOR_SCRIPT="$SEQ_DIR/author-script" +ORIG_AUTHOR_SCRIPT="$SEQ_DIR/author-script.orig" +CALLER_SCRIPT="$SEQ_DIR/caller-script" +WHY_FILE="$SEQ_DIR/why" +MARK_PREFIX='refs/sequencer-marks' + +warn () { + printf "%s\n" "$*" >&2 +} + +cleanup () { + for ref in $(git for-each-ref --format='%(refname)' "$MARK_PREFIX") + do + git update-ref -d "$ref" "$ref" || return + done + test -z "$ORIG_HEAD" || git update-ref ORIG_HEAD "$ORIG_HEAD" + rm -rf "$SEQ_DIR" +} + +print_advice () { + case "$ADVICE,$WHY" in + t,conflict) + echo " +After resolving the conflicts, mark the corrected paths with + + git add <paths> + +and run + + $(print_caller --continue) + +Note, that your working tree must match the index." + ;; + t,pause) + echo " +You can now edit files and add them to the index. +Once you are satisfied with your changes, run + + $(print_caller --continue) + +If you only want to change the commit message, run +git commit --amend before." + ;; + t,run) + echo " +Running failed: + $@ + +You can now fix the problem. When manual runs pass, +add the changes to the index and invoke + + $(print_caller --continue)" + ;; + t,todo) + echo " +Fix with git sequencer --edit or abort with $(print_caller --abort)." + ;; + esac +} + +# Die if there has been a conflict +die_to_continue () { + warn "$*" + test -z "$BATCHMODE" || + die_abort 'Aborting, because of batch mode.' + test -n "$WHY" || WHY=conflict + echo "$WHY" >"$WHY_FILE" + print_advice + test -z "$ORIG_HEAD" || git update-ref ORIG_HEAD "$ORIG_HEAD" + exit 3 +} + +die_abort () { + restore + cleanup + die "$1" +} + +perform () { + case "$VERBOSE" in + 0) + "$@" >/dev/null + ;; + 1) + output=$("$@" 2>&1 ) + status=$? + test $status -ne 0 && printf '%s\n' "$output" + return $status + ;; + 2) + "$@" + ;; + esac +} + +# test if working tree is dirty +require_clean_work_tree () { + if test -z "$ALLOW_DIRTY" + then + git rev-parse --verify HEAD >/dev/null && + git update-index --ignore-submodules --refresh && + git diff-files --quiet --ignore-submodules || + die 'Working tree is dirty.' + fi + git diff-index --cached --quiet HEAD --ignore-submodules -- || + die 'Index is dirty' +} + +ORIG_REFLOG_ACTION="$GIT_REFLOG_ACTION" + +comment_for_reflog () { + if test -z "$ORIG_REFLOG_ACTION" + then + GIT_REFLOG_ACTION='sequencer' + test -z "$CALLER" || + GIT_REFLOG_ACTION="$GIT_REFLOG_ACTION ($CALLER)" + export GIT_REFLOG_ACTION + fi +} + +# Get commit message from commit $1 +commit_message () { + git cat-file commit "$1" | sed -e '1,/^$/d' +} + +LAST_COUNT= +remove_from_todo () { + sed -e 1q <"$TODO" >>"$DONE" + sed -e 1d <"$TODO" >"$TODO.new" + mv -f "$TODO.new" "$TODO" + todo_ack "$TODO" + if test "$VERBOSE" -gt 0 + then + count=$(grep -c '^[^#]' <"$DONE") + total=$(expr "$count" + "$(grep -c '^[^#]' <"$TODO")") + if test "$LAST_COUNT" != "$count" + then + LAST_COUNT="$count" + test "$VERBOSE" -eq 1 -a -t 1 -o "$VERBOSE" -gt 1 && + printf 'Sequencing (%d/%d)\r' "$count" "$total" + test "$VERBOSE" -lt 2 || echo + fi + fi +} + +# Generate message and author script files +save_commit_data () { + test -f "$MSG" || + commit_message "$1" >"$MSG" + test -f "$AUTHOR_SCRIPT" || + get_author_ident_from_commit "$1" >"$AUTHOR_SCRIPT" +} + +# Generate a patch and die with "conflict" status code +die_with_patch () { + save_commit_data "$1" + perform git rerere + die_to_continue "$2" +} + +reset_almost_hard () { + perform git read-tree --reset -u $ALLOW_DIRTY "$1" && + perform git reset "$1" +} + +restore () { + # XXX: We should undo all "ref" invocations, but that's left to be + # done in the builtin-sequencer. + read HEADNAME <"$SEQ_DIR/head-name" + case $HEADNAME in + refs/*) + git symbolic-ref HEAD "$HEADNAME" + ;; + esac && + reset_almost_hard "$ORIG_HEAD" +} + +has_action () { + grep '^[^#]' "$1" >/dev/null +} + +# Check if text file $1 contains a commit message +has_message () { + test -n "$(sed -n -e '/^Signed-off-by:/d;/^[^#]/p' <"$1")" +} + +# Count parents of commit $1 +count_parents() { + git cat-file commit "$1" | sed -n -e '1,/^$/p' | grep -c '^parent' +} + +# Evaluate the author script to get author information to +# apply it with "with_author git foo" then. +get_current_author () { + if test -f "$AUTHOR_SCRIPT" + then + . "$AUTHOR_SCRIPT" + else + . "$ORIG_AUTHOR_SCRIPT" + fi || die_abort 'Author script is damaged. This must not happen!' +} + +# Run command with author information +with_author () { + GIT_AUTHOR_NAME="$GIT_AUTHOR_NAME" \ + GIT_AUTHOR_EMAIL="$GIT_AUTHOR_EMAIL" \ + GIT_AUTHOR_DATE="$GIT_AUTHOR_DATE" \ + "$@" +} + +# clean WHY_FILE and reset WHY +clean_why () { + rm -f "$WHY_FILE" + WHY= +} + +# Usage: pick_one (cherry-pick|revert) [-*|--edit] sha1 +pick_one () { + what="$1" + shift + + case "$what,$1" in + revert,*) + test "$1" != '--edit' && + what='revert --no-edit' + ;; + cherry-pick,-*) + ;; + cherry-pick,*) + # fast forward + if test "$(git rev-parse --verify "$1^" 2>/dev/null)" = \ + "$(git rev-parse --verify HEAD)" + then + reset_almost_hard "$1" + return + fi + ;; + esac + $use_perform git $what "$@" +} + +nth_string () { + case "$1" in + *1[0-9]|*[04-9]) + echo "$1th" + ;; + *1) + echo "$1st" + ;; + *2) + echo "$1nd" + ;; + *3) + echo "$1rd" + ;; + esac +} + +make_squash_message () { + if test -f "$squash_msg" + then + count=$(($(sed -n -e 's/^# This is [^0-9]*\([1-9][0-9]*\).*/\1/p' \ + <"$squash_msg" | sed -n -e '$p')+1)) + echo "# This is a combination of $count commits." + sed -e '1d' -e '2,/^./{ + /^$/d + }' <"$squash_msg" + else + count=2 + echo '# This is a combination of 2 commits.' + echo '# The first commit message is:' + echo + commit_message HEAD + fi + echo + echo "# This is the $(nth_string "$count") commit message:" + echo + commit_message "$1" +} + +make_squash_message_multiple () { + revlist=$(git rev-list --reverse "$sha1..HEAD") + count=$(echo "$revlist" | wc -l) + squash_i=0 + echo "# This is a combination of $count commits." + for cur_sha1 in $revlist + do + squash_i=$(($squash_i+1)) + if test -f "$squash_msg" + then + count=$(($count + $(sed -n -e \ + 's/^# This is [^0-9]*\([1-9][0-9]*\).*/\1/p' \ + <"$squash_msg" | sed -n -e '$p')+1)) + sed -e '1d' -e '2,/^./{ + /^$/d + }' <"$squash_msg" + fi + echo + echo "# This is the $(nth_string "$squash_i") commit message:" + echo + commit_message "$cur_sha1" + done +} + +peek_next_command () { + sed -n -e '/^#/d' -e '1s/ .*$//p' <"$TODO" +} + +# If $1 is a mark, make a ref from it; otherwise keep it. +# Note on marks: +# * :0 is allowed +# * :01 is the same as :1 +mark_to_ref () { + arg="$1" + ref=$(expr "x$arg" : 'x:0*\([0-9][0-9]*\)$') + test -n "$ref" && + arg="$MARK_PREFIX/$ref" + printf '%s\n' "$arg" +} + +mark_to_commit () { + git rev-parse --verify "$(mark_to_ref "$1")" +} + + +fallback_3way () { + # Cleanup if not done before. + rm -fr "$PATCH-merge-"* + + # First see if the patch records the index info that we can use. + perform git apply --build-fake-ancestor "$PATCH-merge-index" "$PATCH" && + GIT_INDEX_FILE="$PATCH-merge-index" git write-tree >"$PATCH-merge-base" || + die_to_continue 'Repository lacks necessary blobs to fall back on 3-way merge. Please hand-edit.' + + test "$VERBOSE" -eq 0 || + echo 'Using index info to reconstruct a base tree...' + GIT_INDEX_FILE="$PATCH-merge-index" git apply --cached "$PATCH" || + die_to_continue 'Patch does not apply to blobs recorded in its index.' + + his_tree=$(GIT_INDEX_FILE="$PATCH-merge-index" git write-tree) && + orig_tree=$(cat "$PATCH-merge-base") && + rm -fr "$PATCH-merge-"* || + die_to_continue 'Writing new tree failed.' + + test "$VERBOSE" -eq 0 || + echo 'Falling back to patching base and 3-way merge...' + + # This is not so wrong. Depending on which base we picked, + # orig_tree may be wildly different from ours, but his_tree + # has the same set of wildly different changes in parts the + # patch did not touch, so recursive ends up canceling them, + # saying that we reverted all those changes. + + eval GITHEAD_$his_tree='"$firstline"' + export GITHEAD_$his_tree + perform git merge-recursive "$orig_tree" -- HEAD "$his_tree" || { + perform git rerere + die_to_continue 'Failed to merge in the changes.' + } +} + +# Run hook "$@" (with arguments) if executable +run_hook () { + test -z "$1" || return + hookname="$1" + hook="$GIT_DIR/hooks/$hookname" + shift + if test -x "$hook" + then + "$hook" "$@" || + die_to_continue "Hook $hookname failed." + fi +} + +# Add Signed-off-by: line if general option --signoff is given +dashdash_signoff () { + add_signoff= + if test -n "$SIGNOFF" + then + last_signed_off_by=$( + sed -n -e '/^Signed-off-by: /p' <"$MSG" | sed -n -e '$p' + ) + test "$last_signed_off_by" = "$SIGNOFF" || + add_signoff=$( + test '' = "$last_signed_off_by" && echo + echo "$SIGNOFF" + ) + fi + { + test -s "$MSG" && cat "$MSG" + test -n "$add_signoff" && echo "$add_signoff" + } >"$MSG.new" + mv "$MSG.new" "$MSG" +} + + +### --caller-related functions + +# Show string for caller invocation for --abort/--continue/--skip +print_caller_info () { + case "$1" in + --abort) + echo "$CALLER_ABRT" + ;; + --continue) + echo "$CALLER_CONT" + ;; + --skip) + echo "$CALLER_SKIP" + ;; + *) + warn 'Internal error: Unknown print_caller argument!' + ;; + esac +} + +# Print the program to invoke to (--)abort/continue/skip +print_caller() { + caller_info=$(print_caller_info "$1") + if test -n "$CALLERCOMPARE" -a -n "$caller_info" + then + test -n "$CALLER" && printf "$CALLER " + echo "$caller_info" + else + echo "git sequencer $1" + fi +} + +# Test if --caller was set correctly +# $1 must be abort/continue/skip +test_caller () { + caller_info=$(print_caller_info "--$1") + test -n "$CALLERCOMPARE" -a \ + "$CALLERCOMPARE" != "$CALLERSTRING" -a \ + -n "$caller_info" && + die "You must use '$CALLER $caller_info' to $1!" +} + +# Generate $CALLER_SCRIPT file from "git foo|--abort|--continue|--skip" +# string $1 +generate_caller_script () { + echo "$1" | sed -e 's/^\(.*\)|\(.*\)|\(.*\)|\(.*\)$/\ +CALLERCOMPARE="\0"\ +CALLER="\1"\ +CALLER_ABRT="\2"\ +CALLER_CONT="\3"\ +CALLER_SKIP="\4"/' >"$CALLER_SCRIPT" +} + +# Run caller script and set GIT_CHERRY_PICK_HELP +assure_caller () { + test -r "$CALLER_SCRIPT" && + . "$CALLER_SCRIPT" + + # we do not want cherry-pick to print *any* help + GIT_CHERRY_PICK_HELP="" + export GIT_CHERRY_PICK_HELP +} + + +### Helpers for check_* functions: + +# Print a warning on todo checking +todo_warn () { + printf 'Warning at line %d, %s: %s\n' "$line" "$command" "$*" +} + +# Raise an error on todo checking +todo_error () { + printf 'Error at line %d, %s: %s\n' "$line" "$command" "$*" + retval=1 +} + +# Test if $1 is a commit on todo checking +commit_check () { + test "$(git cat-file -t "$1" 2>/dev/null)" = commit || + todo_error "'$1' is not a commit." +} + +# A helper function to check if $1 is a an available mark during check_* +arg_is_mark_check () { + for cur_mark in $available_marks + do + test "$cur_mark" -eq "${1#:}" && return + done + todo_error "Mark $1 is not yet defined." +} + +# A helper function for check_* and mark usage: +# check if "$1" is a commit or a defined mark +arg_is_mark_or_commit_check () { + if expr "x$1" : 'x:[0-9][0-9]*$' >/dev/null + then + arg_is_mark_check "$1" + else + commit_check "$1" + fi +} + + +### Author script functions + +# Take "Name <e-mail>" in stdin and outputs author script +make_author_script_from_string () { + sed -e "s/'/'"'\\'"''/g" \ + -e 's/^\(.*\) <\(.*\)>.*$/GIT_AUTHOR_NAME='\''\1'\''\ +GIT_AUTHOR_EMAIL='\''\2'\''\ +GIT_AUTHOR_DATE=/' +} + + +### General option functions (and options spec) + +OPTIONS_GENERAL=' General options +author= override author +C,reuse-commit= reuse message and authorship data from commit +F,file= take commit message from given file +m,message= specify commit message +M,reuse-message= reuse message from commit +signoff add signoff +e,edit invoke editor to edit commit message' + +# Check if option is a general option +check_general_option () { + general_shift=1 + case "$1" in + --signoff) + return 0 + ;; + --author) + general_shift=2 + author_opt="t$author_opt" + expr "x$2" : 'x.* <.*>' >/dev/null || + todo_error "Author \"$2\" not in the correct format \"Name <e-mail>\"." + ;; + -m) + general_shift=2 + msg_opt="t$msg_opt" + ;; + -C) + general_shift=2 + msg_opt="t$msg_opt" + author_opt="t$author_opt" + commit_check "$2" + ;; + -M) + general_shift=2 + msg_opt="t$msg_opt" + commit_check "$2" + ;; + -F) + general_shift=2 + msg_opt="t$msg_opt" + test -r "$2" || + todo_error "Cannot read file '$2'." + ;; + -e) + test -z "$BATCHMODE" || + todo_error '--batch and --edit options do not make sense together.' + ;; + *) + return 1 + ;; + esac +} + +# Set a general option variable or return 1 +handle_general_option () { + general_shift=1 + case "$1" in + --signoff) + SIGNOFF=$(git var GIT_COMMITTER_IDENT | sed -e ' + s/>.*/>/ + s/^/Signed-off-by: /') + ;; + --author) + general_shift=2 + AUTHOR=t + echo "$2" | + make_author_script_from_string >"$AUTHOR_SCRIPT" + ;; + -m) + general_shift=2 + MESSAGE="$2" + ;; + -C) + general_shift=2 + AUTHOR=t + get_author_ident_from_commit "$2" >"$AUTHOR_SCRIPT" + MESSAGE=$(commit_message "$2") + ;; + -M) + general_shift=2 + MESSAGE=$(commit_message "$2") + ;; + -F) + general_shift=2 + MESSAGE=$(cat "$2") + ;; + -e) + EDIT=--edit + ;; + *) + return 1 + ;; + esac +} + + +### Functions for checking and realizing TODO instructions +# Note that options_*, check_* and insn_* function names are reserved. + +options_pause="\ +pause +-- +" + +# Check the "pause" instruction +check_pause () { + shift + test -z "$BATCHMODE" || + todo_error '"pause" instruction and --batch do not make sense together.' + test $# -eq 0 || + todo_error 'The pause instruction takes no arguments.' + return 0 +} + +# Realize the "pause" instruction +insn_pause () { + save_commit_data HEAD + echo 'pause' >"$WHY_FILE" + WHY=pause print_advice + test -z "$ORIG_HEAD" || git update-ref ORIG_HEAD "$ORIG_HEAD" + exit 2 +} + + +options_run="\ +run [--dir=<path>] [--] <cmd> <args>... +-- +dir= change directory before running command +" + +# Check the "run" instruction +check_run () { + while test $# -gt 0 + do + case "$1" in + --dir) + test -e "$2" || + todo_error "Path $2 does not exist." + test -d "$2" || + todo_error "Path $2 is not a directory." + shift 2 + ;; + --) + shift + break + ;; + -*) + todo_error "Unknown option $1" + ;; + esac + done + # we don't check if cmd exists + return 0 +} + +# Realize the "run" instruction +insn_run () { + runpath=./ + while test $# -gt 0 + do + case "$1" in + --dir) + runpath="$2" + shift 2 + ;; + --) + shift + break + ;; + esac + done + + savepath="$PWD" + cd "$runpath" + "$@" + success="$?" + cd "$savepath" + + if test "$success" -ne 0 + then + test -z "$BATCHMODE" || + die_abort "Running $1 failed. Aborting because of batch mode." + save_commit_data HEAD + echo 'run' >"$WHY_FILE" + WHY=run print_advice "$@" + test -z "$ORIG_HEAD" || git update-ref ORIG_HEAD "$ORIG_HEAD" + exit 3 + fi +} + + +options_patch="\ +patch [options] <file> +-- +3,3way fall back to 3-way merge +k pass to git-mailinfo (keep subject) +n pass to git-mailinfo (no utf8) +$OPTIONS_GENERAL + Options passed to git-apply +R,reverse reverse changes +context= ensure context of ... lines +p= remove ... leading slashes +unidiff-zero bypass unidiff checks +exclude= do not apply changes to given files +no-add ignore additions of patch +whitespace= set whitespace error behavior +inaccurate-eof support inaccurate EOFs +u no-op (backward compatibility) +binary no-op (backward compatibility) +" + +# Check the "patch" instruction +check_patch () { + while test $# -gt 1 + do + case "$1" in + -3|-k|-n|-u|--binary|-R|--reverse|--unidiff-zero|--no-add|--inaccurate-eof) + : + ;; + -p|--whitespace|--exclude|--context) + shift + ;; + --) + shift + break + ;; + -*) + check_general_option "$@" || + todo_warn "Unknown option $1" + ;; + *) + todo_error "Wrong number of arguments. ($# given, 1 wanted)" + break + ;; + esac + shift $general_shift + done + + if test -f "$1" -a -r "$1" + then + grep -e '^diff' "$1" >/dev/null || + todo_error "File '$1' contains no patch." + else + todo_error "Cannot open file '$1'." + fi + return 0 +} + +# Realize the "patch" instruction +insn_patch () { + apply_opts= + mailinfo_opts= + threeway= + + # temporary files + infofile="$SEQ_DIR/patch-info" + msgfile="$SEQ_DIR/patch-msg" + + while test "$#" -gt 1 + do + case "$1" in + -3) + threeway=t + ;; + -k|-n) + mailinfo_opts="$mailinfo_opts $1" + ;; + -u|--binary) + : Do nothing. It is there due to b/c only. + ;; + -R|--reverse|--unidiff-zero|--no-add|--inaccurate-eof) + apply_opts="$apply_opts $1" + ;; + -p) + shift + apply_opts="$apply_opts -p$1" + ;; + --whitespace|--exclude) + apply_opts="$apply_opts $1=$2" + shift + ;; + --context) + shift + apply_opts="$apply_opts -C$1" + ;; + --) + shift + break + ;; + *) + handle_general_option "$@" + ;; + esac + shift $general_shift + done + + filename="$1" + + git mailinfo $mailinfo_opts "$msgfile" "$PATCH" \ + <"$filename" >"$infofile" || + die_abort 'Could not read or parse mail' + + # if author not set by option, read author information of patch + if test -z "$AUTHOR" + then + cp "$ORIG_AUTHOR_SCRIPT" "$AUTHOR_SCRIPT" + sed -e "s/'/'"'\\'"''/g" -n -e ' + s/^Author: \(.*\)$/GIT_AUTHOR_NAME='\''\1'\''/p; + s/^Email: \(.*\)$/GIT_AUTHOR_EMAIL='\''\1'\''/p; + s/^Date: \(.*\)$/GIT_AUTHOR_DATE='\''\1'\''/p + ' <"$infofile" >>"$AUTHOR_SCRIPT" + # If sed's result is empty, we keep the original + # author script by appending. + fi + + # Ignore every mail that's not containing a patch + test -s "$PATCH" || { + warn 'Does not contain patch!' + return 0 + } + + edit_msg= + if grep -e '^Subject:' "$infofile" >/dev/null + then + # add subject to commit message + sed -n -e '/^Subject:/ s/Subject: //p' <"$infofile" + echo + echo + cat "$msgfile" + else + cat "$msgfile" + edit_msg=t + fi | git stripspace >"$MSG" + rm -f "$infofile" "$msgfile" + + firstline=$(sed -e '1q' <"$MSG") + + get_current_author + + test -n "$MESSAGE" && printf '%s\n' "$MESSAGE" >"$MSG" + test -z "$firstline" && firstline=$(sed -e '1q' <"$MSG") + + dashdash_signoff + + with_author run_hook applypatch-msg "$MSG" + failed= + git apply $apply_opts --index "$PATCH" || failed=t + + if test -n "$failed" -a -n "$threeway" && (with_author fallback_3way) + then + # Applying the patch to an earlier tree and merging the + # result may have produced the same tree as ours. + git diff-index --quiet --cached HEAD -- && { + echo 'No changes -- Patch already applied.' + return 0 + # XXX: do we want that? + } + # clear apply_status -- we have successfully merged. + failed= + fi + + if test -n "$failed" + then + die_to_continue "Patch failed: $firstline" + # XXX: We actually needed a git-apply flag that creates + # conflict markers and sets the DIFF_STATUS_UNMERGED flag. + fi + + with_author run_hook pre-applypatch + + test -n "$EDIT" && edit_msg=t + if ! has_message "$MSG" || test -n "$edit_msg" + then + echo " +# Please enter the commit message for the applied patch. +# (Comment lines starting with '#' will not be included)" >>"$MSG" + + git_editor "$MSG" || + die_with_patch HEAD 'Editor returned error.' + has_message "$MSG" || + die_with_patch HEAD 'No commit message given.' + fi + + tree=$(git write-tree) && + parent=$(git rev-parse --verify HEAD) && + commit=$(with_author git commit-tree "$tree" -p "$parent" <"$MSG") && + git update-ref -m "$GIT_REFLOG_ACTION: $firstline" HEAD "$commit" "$parent" || + die_to_continue 'Could not commit tree.' + + test -x "$GIT_DIR/hooks/post-applypatch" && + with_author "$GIT_DIR/hooks/post-applypatch" + + return 0 +} + + +options_pick="\ +pick [options] <commit> +-- +R,reverse revert introduced changes +mainline= specify parent number to use for merge commits +$OPTIONS_GENERAL +" + +# Check the "pick" instruction +check_pick () { + mainline= + while test $# -gt 1 + do + case "$1" in + -R) + ;; + --mainline) + shift + mainline="$1" + test "$mainline" -gt 0 || { + todo_error '--mainline needs an integer beginning from 1.' + mainline= + } + ;; + --) + shift + break + ;; + -*) + check_general_option "$@" || + todo_warn "Unknown option $1" + ;; + *) + todo_error "Wrong number of arguments. ($# given, 1 wanted)" + break + ;; + esac + shift $general_shift + done + + if test -n "$mainline" + then + parents=$(count_parents "$1") + test "$parents" -lt "$mainline" && + todo_error "Commit has only $parents (less than $mainline) parents." + test "$parents" -eq 1 && + todo_warn 'Commit is not a merge at all.' + fi + + commit_check "$1" + + return 0 +} + +# Realize the "pick" instruction +insn_pick () { + op=cherry-pick + mainline= + while test $# -gt 1 + do + case "$1" in + -R) + op=revert + ;; + --mainline) + mainline="$1 $2" + shift + ;; + --) + shift + break + ;; + -*) + handle_general_option "$@" + ;; + esac + shift $general_shift + done + + sha1=$(git rev-parse --verify "$1") + + edit_msg="$EDIT" + + # Don't edit on pick, but later, if author or message given. + test -n "$AUTHOR" -o -n "$MESSAGE" && edit_msg= + + # Be kind to users and ignore --mainline=1 on non-merge commits + test -n "$mainline" -a 2 -gt $(count_parents "$sha1") && mainline= + + use_perform= + test -n "$edit_msg" || + use_perform=perform + + pick_one "$op" $edit_msg $mainline $sha1 || + die_with_patch $sha1 "Could not apply $sha1." + + test -n "$EDIT" || + use_perform=perform + + get_current_author + signoff= + test -n "$SIGNOFF" && signoff=-s + if test -n "$AUTHOR" -a -n "$MESSAGE" + then + # this is just because we only want to do ONE amending commit + $use_perform git commit --amend $EDIT $signoff --no-verify \ + --author "$GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL>" \ + --message="$MESSAGE" + elif test -n "$AUTHOR" + then + # correct author if AUTHOR is set + $use_perform git commit --amend $EDIT --no-verify -C HEAD \ + --author "$GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL>" + elif test -n "$MESSAGE" + then + # correct commit message if MESSAGE is set + $use_perform git commit --amend $EDIT $signoff --no-verify \ + -C HEAD --message="$MESSAGE" + elif test -n "$SIGNOFF" + then + # only add signoff + $use_perform git commit --amend $EDIT $signoff --no-verify \ + -C HEAD + fi + + return 0 +} + +options_edit="\ +edit <commit> +-- +" + +# Check the "edit" instruction +check_edit () { + shift + test -z "$BATCHMODE" || + todo_error '"edit" instruction and --batch do not make sense together.' + test $# -eq 1 || + todo_error "Wrong number of arguments. ($# given, 1 wanted)" + return 0 +} + +# Realize the "edit" instruction +insn_edit () { + shift + insn_pick "$1" + echo '# pausing' >>"$DONE" + insn_pause +} + + +options_squash="\ +squash <commit> +squash [options] --from <mark> +-- +from squash all commits from <mark> +collect-signoffs collect Signed-off-by: lines +include-merges do not fail on merge commits +$OPTIONS_GENERAL +" + +# Check the "squash" instruction +check_squash () { + from= + collect= + merges= + while test $# -gt 1 + do + case "$1" in + --from) + from=t + ;; + --collect-signoffs) + collect=t + todo_warn 'Not yet implemented.' + ;; + --include-merges) + merges=t + ;; + --) + shift + break + ;; + *) + check_general_option "$@" || + todo_error "Unknown option $1" + ;; + esac + shift $general_shift + done + + # in --from mode? + if test -n "$from" + then + test -z "$merges" && + cat "$DONE" "$TODO" | + sed -n -e '/^[ \t]*mark[ \t]*:\{0,1\}'"${1#:}"'\($\|[^0-9]\)/,'"$line"'p' | + grep '^[ \t]*merge' >/dev/null && + todo_error "$1..HEAD contains a merge commit. You may try --include-merges." + + arg_is_mark_check "$1" + else + test -n "$merges" && + todo_error '--include-merges only makes sense with --from <mark>.' + test -n "$collect" && + todo_error '--collect-signoffs only makes sense with --from <mark>.' + + commit_check "$1" + fi + + return 0 +} + +# Realize the "squash" instruction +insn_squash () { + squash_msg="$MSG-squash" + from= + while test $# -gt 1 + do + case "$1" in + --from) + from=t + ;; + --collect-signoffs) + warn '--collect-signoffs is not implemented.' + # XXX + ;; + --include-merges) + : # This has to be done during check_squash + ;; + --) + shift + break + ;; + *) + handle_general_option "$@" + ;; + esac + shift $general_shift + done + + if test -n "$from" + then + sha1=$(mark_to_commit ":${1#:}") + else + sha1=$(git rev-parse --verify "$1") + fi + + # Hm, somehow I don't think --skip on a conflicting squash + # may be useful, but if someone wants to do it, it should + # do the obvious: skip what squash would do. + echo "$(git rev-parse HEAD)" >"$SEQ_DIR/skiphead" + + if test -n "$MESSAGE" + then + printf '%s\n' "$MESSAGE" >"$MSG" + else + if test -n "$from" + then + make_squash_message_multiple "$sha1" >"$MSG" + else + make_squash_message "$sha1" >"$MSG" + fi + fi + + case "$(peek_next_command)" in + squash) + edit_commit= + use_perform=perform + cp "$MSG" "$squash_msg" + ;; + *) + edit_commit=-e + use_perform= + rm -f "$squash_msg" + ;; + esac + + test -n "$MESSAGE" && edit_commit= + test -n "$EDIT" && edit_commit=-e + + # is --author (or equivalent) set? + if test -n "$AUTHOR" + then + # evaluate author script + get_current_author + else + # if --author is not given, we get the authorship + # information from the commit before. + eval "$(get_author_ident_from_commit HEAD)" + # but we do not write an author script + fi + + # --from or not + failed= + if test -n "$from" + then + perform git reset --soft "$sha1" + else + perform git reset --soft HEAD^ + + pick_one cherry-pick -n "$sha1" || failed=t + fi + + dashdash_signoff + + if test -z "$failed" + then + # This is like --amend, but with a different message + with_author $use_perform git commit --no-verify \ + -F "$MSG" $edit_commit || failed=t + else + cp "$MSG" "$GIT_DIR/SQUASH_MSG" + warn + die_with_patch $sha1 "Could not apply $sha1." + fi + + return 0 +} + + +options_mark="\ +mark <mark> +-- +" + +# Check the "mark" instruction +check_mark () { + shift + test $# -eq 1 || + todo_error "Wrong number of arguments. ($# given, 1 wanted)" + my_mark=$(expr "x${1#:}" : 'x0*\([0-9][0-9]*\)$') + test -n "$my_mark" || + todo_error "Mark $1 not an integer." + expr "x$available_marks " : " $my_mark " >/dev/null && + todo_error "Mark :$my_mark already defined. Choose another integer." + available_marks="$available_marks $my_mark" + + return 0 +} + +# Realize the "mark" instruction +insn_mark () { + shift + given="$1" + + mark=$(mark_to_ref ":${given#:}") + git update-ref "$mark" HEAD + return 0 +} + + +options_merge="\ +merge [options] <commit-ish> ... +-- +standard generate default commit message +s,strategy= use merge strategy ... +$OPTIONS_GENERAL +" + +# Check the "merge" instruction +check_merge () { + while test $# -gt 1 + do + case "$1" in + --standard) + msg_opt="t$msg_opt" + ;; + -s) + shift + ;; + --) + shift + break + ;; + -*) + check_general_option "$@" || + todo_error "Unknown option $1" + ;; + esac + shift $general_shift + done + + test $# -gt 0 || + todo_error 'What are my parents? Need new parents!' + + while test $# -gt 0 + do + arg_is_mark_or_commit_check "$1" + shift + done + return 0 +} + +# Realize the "merge" instruction +insn_merge () { + standard= + + while test $# -gt 1 + do + case "$1" in + --standard) + standard=t + ;; + -s) + shift + strategy="-s $1" + ;; + --) + shift + break + ;; + -*) + handle_general_option "$@" + ;; + esac + shift $general_shift + done + + new_parents= + for p in "$@" + do + new_parents="$new_parents $(mark_to_ref $p)" + done + new_parents="${new_parents# }" + + get_current_author + + if test -n "$standard" + then + for cur_parent in $new_parents + do + printf '%s\t\t%s' \ + "$(git rev-parse "$cur_parent")" "$cur_parent" + done | git fmt-merge-msg >"$MSG" + fi + + test -n "$MESSAGE" && + printf '%s\n' "$MESSAGE" >"$MSG" + + dashdash_signoff + if ! has_message "$MSG" || test -n "$EDIT" + then + echo " +# Please enter the merge commit message. +# (Comment lines starting with '#' will not be included)" >>"$MSG" + + git_editor "$MSG" || + die_with_patch HEAD 'Editor returned error.' + has_message "$MSG" || + die_with_patch HEAD 'No commit message given.' + fi + + if ! perform git merge --no-commit $strategy $new_parents + then + perform git rerere + cp "$MSG" "$GIT_DIR/MERGE_MSG" + die_to_continue "Error merging $new_parents." + fi + with_author perform git commit -F "$MSG" --no-verify + return 0 +} + + +options_reset="\ +reset <commit-ish> +-- +" + +# Check the "reset" instruction +check_reset () { + shift + test $# -eq 1 || + todo_error "Wrong number of arguments. ($# given, 1 wanted)" + arg_is_mark_or_commit_check "$1" + + return 0 +} + +# Realize the "reset" instruction +insn_reset () { + shift + reset_almost_hard "$(mark_to_commit "$1")" +} + + +options_ref="\ +ref <ref> +-- +" + +# Check the "ref" instruction +check_ref () { + shift + test $# -eq 1 || + todo_error "Wrong number of arguments. ($# given, 1 wanted)" + return 0 +} + +# Realize the "ref" instruction +insn_ref () { + shift + perform git update-ref "$1" HEAD +} + + +### Instruction main loop + +# Run check_* or insn_* with massaged options +# Usage: run_insn (check|do) <insn> <insn options> +run_insn () { + c_or_i="$1" + insn="$2" + shift + shift + eval "option_spec=\"\$options_$insn\"" + eval "$(printf "%s" "$option_spec" | + git rev-parse --parseopt -- "$@" || + echo return 1)" + case "$c_or_i" in + check) + check_$insn "$@" + ;; + do) + insn_$insn "$@" + ;; + esac +} + +# Execute the first line of the current TODO file +execute_next () { + read -r command rol <"$TODO" + test "$VERBOSE" -gt 1 && + printf 'Next line: %s\n' "$command $rol" + + remove_from_todo + case "$command" in + '#'*|'') + ;; + *) + comment_for_reflog $command + # reset general options + rm -f "$AUTHOR_SCRIPT" "$MSG" + echo 'HEAD' >"$SEQ_DIR/skiphead" + general_shift=1 + AUTHOR= + EDIT= + MESSAGE= + SIGNOFF= + # XXX: eval is evil! + eval "run_insn do $command $rol" || + die_to_continue 'An unexpected error occured.' + ;; + esac +} + +# Execute the rest of the TODO file and finish +execute_rest () { + while has_action "$TODO" + do + execute_next + done + + comment_for_reflog finish + if test -n "$ONTO" + then + git update-ref -m "$GIT_REFLOG_ACTION: $ONTO" "$ONTO" HEAD && + git symbolic-ref HEAD "$ONTO" + fi && + cleanup + exit +} + +# We don't need to check a todo file if it has not changed +# since it has last been acknowledged to be sane. +todo_has_changed () { + test -f "$1.sum" || return 0 + test "$(git hash-object "$1")" != "$(cat "$1.sum")" +} + +# acknowledge todo file to be sane +todo_ack () { + git hash-object "$1" >"$1.sum" +} + +# Main loop to check instructions +todo_check () { + todo="$TODO" + test -n "$1" && todo="$1" + todo_has_changed "$todo" || return 0 + + test "$VERBOSE" -eq 1 -a -t 1 -o "$VERBOSE" -gt 1 && + printf 'Checking...\r' + test "$VERBOSE" -lt 2 || echo + available_marks=' ' + for cur_mark in $(git for-each-ref --format='%(refname)' "$MARK_PREFIX") + do + available_marks="$available_marks ${cur_mark##*/}" + done + + retval=0 + line=1 + while read -r command rol + do + case "$command" in + '#'*|'') + ;; + *) + eval 'test -n "$options_'"$command"'"' || { + retval=1 + todo_error "Unknown $command instruction" + continue + } + + general_shift=1 + msg_opt= + author_opt= + eval "run_insn check $command $rol" || + todo_error "Unknown option used" + expr "$msg_opt" : 'ttt*' >/dev/null && + todo_error 'You can only provide one commit message option.' + expr "$author_opt" : 'ttt*' >/dev/null && + todo_error 'You can only provide one author option.' + ;; + esac + line=$(expr "$line" + 1) + done <"$todo" + test $retval -ne 0 || todo_ack "$todo" + return $retval +} + +prepare_editable_todo () { + echo '# ALREADY DONE:' + sed -e 's/^/# /' <"$DONE" + echo '# ' + echo "$markline" + cat "$TODO" +} + +# expand shortcuts in TODO file $1 +expand_shortcuts () { + sed -e ' + s/^[ \t]*p\>/pick/; + s/^[ \t]*e\>/edit/; + s/^[ \t]*s\>/squash/; + ' <"$1" >"$TODO.cut" + mv "$TODO.cut" "$1" +} + +get_saved_options () { + read ALLOW_DIRTY <"$SEQ_DIR/allow-dirty" + read VERBOSE <"$SEQ_DIR/verbose" + read ADVICE <"$SEQ_DIR/advice" + read ONTO <"$SEQ_DIR/onto" + read ORIG_HEAD <"$SEQ_DIR/head" + test -f "$WHY_FILE" && + read WHY <"$WHY_FILE" + return 0 +} + +# Realize sequencer invocation +do_startup () { + test -d "$SEQ_DIR" && + die 'sequencer already started' + + require_clean_work_tree + + ORIG_HEAD=$(git rev-parse --verify HEAD) || + die 'No HEAD?' + + mkdir "$SEQ_DIR" || + die "Could not create temporary $SEQ_DIR" + + # save options + echo "$ALLOW_DIRTY" >"$SEQ_DIR/allow-dirty" + echo "$VERBOSE" >"$SEQ_DIR/verbose" + echo "$ADVICE" >"$SEQ_DIR/advice" + test -n "$CALLERSTRING" && + generate_caller_script "$CALLERSTRING" + # generate empty DONE and "onto" file + : >"$DONE" + : >"$SEQ_DIR/onto" + + if test -n "$BATCHMODE" + then + GIT_CHERRY_PICK_HELP=' Aborting (batch mode)' + export GIT_CHERRY_PICK_HELP + else + assure_caller + fi + + comment_for_reflog start + + # save old head before checking out the given <branch> + git symbolic-ref HEAD >"$SEQ_DIR/head-name" 2>/dev/null || + echo 'detached HEAD' >"$SEQ_DIR/head-name" + echo $ORIG_HEAD >"$SEQ_DIR/head" + # do it here so that die_abort can work ;) + + if test -n "$ONTO" + then + # if ONTO is a branch name, then keep it, otherwise + # we don't care anymore and erase ONTO. + if git-show-ref --quiet --verify -- "refs/heads/${ONTO##*/}" + then + ONTO="refs/heads/${ONTO##*/}" + echo "$ONTO" >"$SEQ_DIR/onto" + perform git checkout "$(git rev-parse "$ONTO")" || + die_abort "Could not checkout branch $ONTO" + else + perform git checkout "$ONTO" || + die_abort "Could not checkout commit $ONTO" + ONTO= + fi + fi + + (git var GIT_AUTHOR_IDENT || git var COMMITTER_IDENT) | + make_author_script_from_string >"$ORIG_AUTHOR_SCRIPT" + + # read from file or from stdin? + if test -z "$1" + then + : >"$TODO" || + die_abort "Could not generate TODO file $TODO" + while read -r line + do + printf '%s\n' "$line" >>"$TODO" || + die_abort "Could not append to TODO file $TODO" + done + else + cp "$1" "$TODO" || + die_abort "Could not find TODO file $1." + fi + expand_shortcuts "$TODO" + + has_action "$TODO" || die_abort 'Nothing to do' + todo_check || WHY=todo die_to_continue "TODO file contains errors." + + execute_rest + exit +} + +# Realize --continue. +do_continue () { + test -d "$SEQ_DIR" || die 'No sequencer running' + test_caller 'continue' + + expand_shortcuts "$TODO" + todo_check || WHY=todo die_to_continue "TODO file contains errors." + + comment_for_reflog continue + + git rev-parse --verify HEAD >/dev/null || + die_to_continue 'Cannot read HEAD' + + get_saved_options + if test -z "$ALLOW_DIRTY" + then + git update-index --ignore-submodules --refresh && + git diff-files --quiet --ignore-submodules || + die_to_continue 'Working tree is dirty. (Use git add or git stash first?)' + fi + + # do we have anything to commit? (staged changes) + if ! git diff-index --cached --quiet --ignore-submodules HEAD -- + then + get_current_author + + # After "pause", we should amend (merge-safe!). + # On conflict, we do not have a commit to amend, so we + # should just commit. + case "$WHY" in + pause|run) + with_author git commit --amend --no-verify -F "$MSG" -e || + die_to_continue 'Could not commit staged changes.' + ;; + conflict) + with_author git commit --no-verify -F "$MSG" -e || + die_to_continue 'Could not commit staged changes.' + echo '# resolved CONFLICTS of the last instruction' >>"$DONE" + ;; + *) + die_to_continue 'There are staged changes. Do not know what to do with them.' + esac + fi + + require_clean_work_tree + clean_why + execute_rest + exit +} + +# Realize --abort. +do_abort () { + test -d "$SEQ_DIR" || die 'No sequencer running' + test_caller 'abort' + get_saved_options + + comment_for_reflog abort + perform git rerere clear + restore + cleanup + exit +} + +# Realize --skip. +do_skip () { + test -d "$SEQ_DIR" || die 'No sequencer running' + test_caller 'skip' + + expand_shortcuts "$TODO" + todo_check || WHY=todo die_to_continue "TODO file contains errors." + get_saved_options + + comment_for_reflog skip + perform git rerere clear + clean_why + + reset_almost_hard "$(cat "$SEQ_DIR/skiphead")" && + echo '# SKIPPED the last instruction' >>"$DONE" && + execute_rest + exit +} + +# Realize --edit. +do_edit () { + test -d "$SEQ_DIR" || die 'No sequencer running' + + markline='### BEGIN EDITING BELOW THIS LINE ###' + prepare_editable_todo >"$TODO.new" + if todo_has_changed "$TODO" + then + sane= + else + sane=t + todo_ack "$TODO.new" + fi + + # XXX: does not make sense + # when input does not come from a terminal + git_editor "$TODO.new" || + die 'Editor returned an error.' + + if test -t 0 -a -t 1 + then + sane=t + echo + # interactive: + until has_action "$TODO.new" && todo_check "$TODO.new" + do + has_action "$TODO.new" || echo 'TODO file empty.' + printf 'What to do with the file? [c]orrect/[e]dit again/[r]ewind/[s]ave/[?] ' + read reply + case "$reply" in + [cC]*) + git_editor "$TODO.new" + expand_shortcuts "$TODO.new" + ;; + [eE]*) + prepare_editable_todo >"$TODO.new" + git_editor "$TODO.new" + expand_shortcuts "$TODO.new" + ;; + [rRxXqQ]*) + rm -f "$TODO.new" "$TODO.new.sum" + exit 0 + ;; + [sS]*) + test "$WHY" != 'pause' || + echo 'todo' >"$WHY_FILE" + sane= + break + ;; + [?hH]*) + cat <<EOF + +Help: +s - save TODO file and exit +c - respawn editor to correct TODO file +e - drop changes and respawn editor on original TODO file +r - drop changes and exit as if nothing happened +? - print this help +EOF + ;; + esac + echo + done + else + # defaults: + has_action "$TODO.new" || { + echo "Nothing to do" + exit + } + todo_check "$TODO.new" || + die 'TODO file contains errors. Aborting.' + fi + cp "$TODO" "$TODO.old" + if grep "^$markline" "$TODO.new" >/dev/null + then + sed -e "1,/^$markline/d" <"$TODO.new" >"$TODO" + else + cp "$TODO.new" "$TODO" + fi + rm -f "$TODO.new" "$TODO.new.sum" + test -z "$sane" || todo_ack "$TODO" + echo + echo 'TODO file contains:' + echo + cat "$TODO" + exit 0 +} + +# Realize --status. +do_status () { + test -d "$SEQ_DIR" || die 'No sequencer running.' + get_saved_options + + if has_action "$DONE" + then + echo 'Already done (or tried):' + sed -e 's/^/ /' <"$DONE" + echo + fi + case "$WHY" in + pause) + echo 'Intentionally paused.' + ;; + run) + echo 'Interrupted because running failed.' + ;; + conflict) + echo 'Interrupted by conflict at' + sed -n -e 's/^/ /;$p' <"$DONE" + ;; + todo) + echo 'Interrupted because of errors in the TODO file.' + ;; + *) + echo 'Current state is broken.' + esac + test "$VERBOSE" -gt 1 && echo 'Running verbosely.' + test "$VERBOSE" -lt 1 && echo 'Running quietly.' + test -n "$ONTO" && + echo "Sequencing on branch ${ONTO##*/}." + if has_action "$TODO" + then + echo + + echo 'Still to do:' + sed -e 's/^/ /' <"$TODO" + fi + if test "$WHY" = todo + then + echo + echo 'But there are errors. To edit, run:' + echo ' git sequencer --edit' + else + echo + echo 'To abort & restore, invoke:' + echo " $(print_caller --abort)" + echo 'To continue, invoke:' + echo " $(print_caller --continue)" + test "$WHY" = 'pause' -o "$WHY" = 'run' || { + echo 'To skip the current instruction, invoke:' + echo " $(print_caller --skip)" + } + fi + exit 0 +} + +is_standalone () { + test $# -eq 2 && + test "$2" = '--' && + test -z "$ALLOW_DIRTY" && + test -z "$BATCHMODE" && + test "$ADVICE" = t && + test -z "$onto" && + test "$VERBOSE" -eq 1 +} + + +### Option handling and startup + +onto= +BATCHMODE= +ALLOW_DIRTY= +CALLERSTRING= +VERBOSE=1 +ADVICE=t +CALLER= +CALLER_ABRT= +CALLER_CONT= +CALLER_SKIP= +while test $# -gt 0 +do + case "$1" in + --continue) + is_standalone "$@" || usage + assure_caller + do_continue + ;; + --abort) + is_standalone "$@" || usage + assure_caller + do_abort + ;; + --skip) + is_standalone "$@" || usage + assure_caller + do_skip + ;; + --status) + is_standalone "$@" || usage + assure_caller + do_status + ;; + --edit) + is_standalone "$@" || usage + do_edit + ;; + --caller) + ############################################################### + # This feature is for user scripts only. (Hence undocumented.) + # User scripts should pass an argument like: + # --caller="git foo|abrt|go|next" + # on every git sequencer call. (It is only ignored on + # --edit and --status.) + # So git sequencer knows that + # "git foo abrt" will abort, + # "git foo go" will continue and + # "git foo next" will skip the sequencing process. + # This is useful if your user script does some extra + # preparations or cleanup before/after calling + # git sequencer --caller="..." --abort|--continue|--skip + # + # Running git-sequencer without the same --caller string + # fails then, until the sequencing process has finished or + # aborted. + # + # Btw, --caller="my_tiny_script.sh|-a||" will be + # interpreted, that users must invoke "my_tiny_script.sh -a" + # to abort, but can invoke "git sequencer --continue" and + # "git sequencer --skip" to continue or skip. + # And it is also possible to provide three different scripts + # by --caller="|script 1|tool 2|util 3". + # + # If your user script does not need any special + # abort/continue/skip behavior, then just do NOT pass + # the --caller option. + ############################################################### + shift + expr "x$1" : 'x.*|.*|.*|.*$' >/dev/null || + die 'Wrong --caller format.' + CALLERSTRING="$1" + ;; + --allow-dirty) + ALLOW_DIRTY='HEAD' + # this means: true + ;; + -B) + BATCHMODE=t + # XXX: we still have abort on editor invokations + ;; + --no-advice) + ADVICE=f + ;; + --onto) + shift + ONTO="$1" + test $(git cat-file -t "$ONTO") = 'commit' || + die "$ONTO is no commit or branch." + ;; + -q) + VERBOSE=0 + ADVICE=f + # XXX: If there were no editors, + # we could just do exec >/dev/null + ;; + -v) + VERBOSE=2 + # or increment it if we have several verbosity steps + ;; + --) + shift + break + ;; + *) + die "$1 currently not implemented." + ;; + esac + shift +done +test $# -gt 1 && usage + +do_startup "$1" -- 1.6.0.rc0.49.gd39f -- To unsubscribe from this list: send the line "unsubscribe git" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html