summaryrefslogtreecommitdiff
path: root/git-revert-script
diff options
context:
space:
mode:
Diffstat (limited to 'git-revert-script')
-rwxr-xr-xgit-revert-script187
1 files changed, 157 insertions, 30 deletions
diff --git a/git-revert-script b/git-revert-script
index 22f2082fb1..dd5866ec96 100755
--- a/git-revert-script
+++ b/git-revert-script
@@ -1,37 +1,164 @@
#!/bin/sh
+#
+# Copyright (c) 2005 Linus Torvalds
+# Copyright (c) 2005 Junio C Hamano
+#
. git-sh-setup-script || die "Not a git archive"
-# We want a clean tree and clean index to be able to revert.
-status=$(git status)
-case "$status" in
-'nothing to commit') ;;
+case "$0" in
+*-revert-* )
+ me=revert ;;
+*-cherry-pick-* )
+ me=cherry-pick ;;
+esac
+
+usage () {
+ case "$me" in
+ cherry-pick)
+ die "usage git $me [-n] [-r] <commit-ish>"
+ ;;
+ revert)
+ die "usage git $me [-n] <commit-ish>"
+ ;;
+ esac
+}
+
+no_commit= replay=
+while case "$#" in 0) break ;; esac
+do
+ case "$1" in
+ -n|--n|--no|--no-|--no-c|--no-co|--no-com|--no-comm|\
+ --no-commi|--no-commit)
+ no_commit=t
+ ;;
+ -r|--r|--re|--rep|--repl|--repla|--replay)
+ replay=t
+ ;;
+ -*)
+ usage
+ ;;
+ *)
+ break
+ ;;
+ esac
+ shift
+done
+
+test "$me,$replay" = "revert,t" && usage
+
+case "$no_commit" in
+t)
+ # We do not intend to commit immediately. We just want to
+ # merge the differences in.
+ head=$(git-write-tree) ||
+ die "Your index file is unmerged."
+ ;;
*)
- echo "$status"
- die "Your working tree is dirty; cannot revert a previous patch." ;;
+ check_clean_tree || die "Cannot run $me from a dirty tree."
+ head=$(git-rev-parse --verify HEAD) ||
+ die "You do not have a valid HEAD"
+ ;;
esac
rev=$(git-rev-parse --verify "$@") &&
-commit=$(git-rev-parse --verify "$rev^0") || exit
-if git-diff-tree -R -M -p $commit | git-apply --index &&
- msg=$(git-rev-list --pretty=oneline --max-count=1 $commit)
-then
- {
- echo "$msg" | sed -e '
- s/^[^ ]* /Revert "/
- s/$/"/'
- echo
- echo "This reverts $commit commit."
- test "$rev" = "$commit" ||
- echo "(original 'git revert' arguments: $@)"
- } | git commit -F -
-else
- # Now why did it fail?
- parents=`git-cat-file commit "$commit" 2>/dev/null |
- sed -ne '/^$/q;/^parent /p' |
- wc -l`
- case $parents in
- 0) die "Cannot revert the root commit nor non commit-ish." ;;
- 1) die "The patch does not apply." ;;
- *) die "Cannot revert a merge commit." ;;
- esac
-fi
+commit=$(git-rev-parse --verify "$rev^0") ||
+ die "Not a single commit $@"
+prev=$(git-rev-parse --verify "$commit^1" 2>/dev/null) ||
+ die "Cannot run $me a root commit"
+git-rev-parse --verify "$commit^2" >/dev/null 2>&1 &&
+ die "Cannot run $me a multi-parent commit."
+
+# "commit" is an existing commit. We would want to apply
+# the difference it introduces since its first parent "prev"
+# on top of the current HEAD if we are cherry-pick. Or the
+# reverse of it if we are revert.
+
+case "$me" in
+revert)
+ git-rev-list --pretty=oneline --max-count=1 $commit |
+ sed -e '
+ s/^[^ ]* /Revert "/
+ s/$/"/'
+ echo
+ echo "This reverts $commit commit."
+ test "$rev" = "$commit" ||
+ echo "(original 'git revert' arguments: $@)"
+ base=$commit next=$prev
+ ;;
+
+cherry-pick)
+ pick_author_script='
+ /^author /{
+ h
+ s/^author \([^<]*\) <[^>]*> .*$/\1/
+ s/'\''/'\''\'\'\''/g
+ s/.*/GIT_AUTHOR_NAME='\''&'\''/p
+
+ g
+ s/^author [^<]* <\([^>]*\)> .*$/\1/
+ s/'\''/'\''\'\'\''/g
+ s/.*/GIT_AUTHOR_EMAIL='\''&'\''/p
+
+ g
+ s/^author [^<]* <[^>]*> \(.*\)$/\1/
+ s/'\''/'\''\'\'\''/g
+ s/.*/GIT_AUTHOR_DATE='\''&'\''/p
+
+ q
+ }'
+ set_author_env=`git-cat-file commit "$commit" |
+ sed -ne "$pick_author_script"`
+ eval "$set_author_env"
+ export GIT_AUTHOR_NAME
+ export GIT_AUTHOR_EMAIL
+ export GIT_AUTHOR_DATE
+
+ git-cat-file commit $commit | sed -e '1,/^$/d'
+ case "$replay" in
+ '')
+ echo "(cherry picked from $commit commit)"
+ test "$rev" = "$commit" ||
+ echo "(original 'git cherry-pick' arguments: $@)"
+ ;;
+ esac
+ base=$prev next=$commit
+ ;;
+
+esac >.msg
+
+# This three way merge is an interesting one. We are at
+# $head, and would want to apply the change between $commit
+# and $prev on top of us (when reverting), or the change between
+# $prev and $commit on top of us (when cherry-picking or replaying).
+
+echo >&2 "First trying simple merge strategy to $me."
+git-read-tree -m -u $base $head $next &&
+result=$(git-write-tree 2>/dev/null) || {
+ echo >&2 "Simple $me fails; trying Automatic $me."
+ git-merge-cache -o git-merge-one-file-script -a || {
+ echo >&2 "Automatic $me failed. After fixing it up,"
+ echo >&2 "you can use \"git commit -F .msg\""
+ case "$me" in
+ cherry-pick)
+ echo >&2 "You may choose to use the following when making"
+ echo >&2 "the commit:"
+ echo >&2 "$set_author_env"
+ esac
+ exit 1
+ }
+ result=$(git-write-tree) || exit
+}
+echo >&2 "Finished one $me."
+
+# If we are cherry-pick, and if the merge did not result in
+# hand-editing, we will hit this commit and inherit the original
+# author date and name.
+# If we are revert, or if our cherry-pick results in a hand merge,
+# we had better say that the current user is responsible for that.
+
+case "$no_commit" in
+'')
+ git commit -F .msg
+ rm -f .msg
+ ;;
+esac