use strbuf_addstr() instead of strbuf_addf() with "%s"
[git] / contrib / examples / git-revert.sh
1 #!/bin/sh
2 #
3 # Copyright (c) 2005 Linus Torvalds
4 # Copyright (c) 2005 Junio C Hamano
5 #
6
7 case "$0" in
8 *-revert* )
9         test -t 0 && edit=-e
10         replay=
11         me=revert
12         USAGE='[--edit | --no-edit] [-n] <commit-ish>' ;;
13 *-cherry-pick* )
14         replay=t
15         edit=
16         me=cherry-pick
17         USAGE='[--edit] [-n] [-r] [-x] <commit-ish>'  ;;
18 * )
19         echo >&2 "What are you talking about?"
20         exit 1 ;;
21 esac
22
23 SUBDIRECTORY_OK=Yes ;# we will cd up
24 . git-sh-setup
25 require_work_tree
26 cd_to_toplevel
27
28 no_commit=
29 xopt=
30 while case "$#" in 0) break ;; esac
31 do
32         case "$1" in
33         -n|--n|--no|--no-|--no-c|--no-co|--no-com|--no-comm|\
34             --no-commi|--no-commit)
35                 no_commit=t
36                 ;;
37         -e|--e|--ed|--edi|--edit)
38                 edit=-e
39                 ;;
40         --n|--no|--no-|--no-e|--no-ed|--no-edi|--no-edit)
41                 edit=
42                 ;;
43         -r)
44                 : no-op ;;
45         -x|--i-really-want-to-expose-my-private-commit-object-name)
46                 replay=
47                 ;;
48         -X?*)
49                 xopt="$xopt$(git rev-parse --sq-quote "--${1#-X}")"
50                 ;;
51         --strategy-option=*)
52                 xopt="$xopt$(git rev-parse --sq-quote "--${1#--strategy-option=}")"
53                 ;;
54         -X|--strategy-option)
55                 shift
56                 xopt="$xopt$(git rev-parse --sq-quote "--$1")"
57                 ;;
58         -*)
59                 usage
60                 ;;
61         *)
62                 break
63                 ;;
64         esac
65         shift
66 done
67
68 set_reflog_action "$me"
69
70 test "$me,$replay" = "revert,t" && usage
71
72 case "$no_commit" in
73 t)
74         # We do not intend to commit immediately.  We just want to
75         # merge the differences in.
76         head=$(git-write-tree) ||
77                 die "Your index file is unmerged."
78         ;;
79 *)
80         head=$(git-rev-parse --verify HEAD) ||
81                 die "You do not have a valid HEAD"
82         files=$(git-diff-index --cached --name-only $head) || exit
83         if [ "$files" ]; then
84                 die "Dirty index: cannot $me (dirty: $files)"
85         fi
86         ;;
87 esac
88
89 rev=$(git-rev-parse --verify "$@") &&
90 commit=$(git-rev-parse --verify "$rev^0") ||
91         die "Not a single commit $@"
92 prev=$(git-rev-parse --verify "$commit^1" 2>/dev/null) ||
93         die "Cannot run $me a root commit"
94 git-rev-parse --verify "$commit^2" >/dev/null 2>&1 &&
95         die "Cannot run $me a multi-parent commit."
96
97 encoding=$(git config i18n.commitencoding || echo UTF-8)
98
99 # "commit" is an existing commit.  We would want to apply
100 # the difference it introduces since its first parent "prev"
101 # on top of the current HEAD if we are cherry-pick.  Or the
102 # reverse of it if we are revert.
103
104 case "$me" in
105 revert)
106         git show -s --pretty=oneline --encoding="$encoding" $commit |
107         sed -e '
108                 s/^[^ ]* /Revert "/
109                 s/$/"/
110         '
111         echo
112         echo "This reverts commit $commit."
113         test "$rev" = "$commit" ||
114         echo "(original 'git revert' arguments: $@)"
115         base=$commit next=$prev
116         ;;
117
118 cherry-pick)
119         pick_author_script='
120         /^author /{
121                 s/'\''/'\''\\'\'\''/g
122                 h
123                 s/^author \([^<]*\) <[^>]*> .*$/\1/
124                 s/'\''/'\''\'\'\''/g
125                 s/.*/GIT_AUTHOR_NAME='\''&'\''/p
126
127                 g
128                 s/^author [^<]* <\([^>]*\)> .*$/\1/
129                 s/'\''/'\''\'\'\''/g
130                 s/.*/GIT_AUTHOR_EMAIL='\''&'\''/p
131
132                 g
133                 s/^author [^<]* <[^>]*> \(.*\)$/\1/
134                 s/'\''/'\''\'\'\''/g
135                 s/.*/GIT_AUTHOR_DATE='\''&'\''/p
136
137                 q
138         }'
139
140         logmsg=$(git show -s --pretty=raw --encoding="$encoding" "$commit")
141         set_author_env=$(echo "$logmsg" |
142         LANG=C LC_ALL=C sed -ne "$pick_author_script")
143         eval "$set_author_env"
144         export GIT_AUTHOR_NAME
145         export GIT_AUTHOR_EMAIL
146         export GIT_AUTHOR_DATE
147
148         echo "$logmsg" |
149         sed -e '1,/^$/d' -e 's/^    //'
150         case "$replay" in
151         '')
152                 echo "(cherry picked from commit $commit)"
153                 test "$rev" = "$commit" ||
154                 echo "(original 'git cherry-pick' arguments: $@)"
155                 ;;
156         esac
157         base=$prev next=$commit
158         ;;
159
160 esac >.msg
161
162 eval GITHEAD_$head=HEAD
163 eval GITHEAD_$next='$(git show -s \
164         --pretty=oneline --encoding="$encoding" "$commit" |
165         sed -e "s/^[^ ]* //")'
166 export GITHEAD_$head GITHEAD_$next
167
168 # This three way merge is an interesting one.  We are at
169 # $head, and would want to apply the change between $commit
170 # and $prev on top of us (when reverting), or the change between
171 # $prev and $commit on top of us (when cherry-picking or replaying).
172
173 eval "git merge-recursive $xopt $base -- $head $next" &&
174 result=$(git-write-tree 2>/dev/null) || {
175         mv -f .msg "$GIT_DIR/MERGE_MSG"
176         {
177             echo '
178 Conflicts:
179 '
180                 git ls-files --unmerged |
181                 sed -e 's/^[^   ]*      /       /' |
182                 uniq
183         } >>"$GIT_DIR/MERGE_MSG"
184         echo >&2 "Automatic $me failed.  After resolving the conflicts,"
185         echo >&2 "mark the corrected paths with 'git-add <paths>'"
186         echo >&2 "and commit the result."
187         case "$me" in
188         cherry-pick)
189                 echo >&2 "You may choose to use the following when making"
190                 echo >&2 "the commit:"
191                 echo >&2 "$set_author_env"
192         esac
193         exit 1
194 }
195
196 # If we are cherry-pick, and if the merge did not result in
197 # hand-editing, we will hit this commit and inherit the original
198 # author date and name.
199 # If we are revert, or if our cherry-pick results in a hand merge,
200 # we had better say that the current user is responsible for that.
201
202 case "$no_commit" in
203 '')
204         git-commit -n -F .msg $edit
205         rm -f .msg
206         ;;
207 esac