Hook svn log to always pipe it through $PAGER.
[svn-wrapper.git] / svn-wrapper.sh
blobfe6a8c40e292ba789f863899288ec89dd9e2342c
1 #! /bin/sh
2 # Simple wrapper around SVN and Git featuring auto-ChangeLog entries and
3 # emailing.
4 # $Id$
5 # Copyright (C) 2006, 2007 Benoit Sigoure.
7 # This program is free software: you can redistribute it and/or modify
8 # it under the terms of the GNU General Public License as published by
9 # the Free Software Foundation, either version 3 of the License, or
10 # (at your option) any later version.
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU General Public License for more details.
17 # You should have received a copy of the GNU General Public License
18 # along with this program. If not, see <http://www.gnu.org/licenses/>.
20 # Quick install: alias svn=path/to/svn-wrapper.sh -- that's all.
22 # ------ #
23 # README #
24 # ------ #
26 # This script is a wrapper around the SVN and Git command-line clients for
27 # UNIX. It has been designed mainly to automagically generate GNU-style
28 # ChangeLog entries when committing and mail them along with a diff and an
29 # optional comment from the author to a list of persons or a mailing list.
30 # Although this script was originally written for SVN (hence the name), it
31 # also works with Git and tries to detect whether the current repository is a
32 # Git or SVN repository.
33 # This script has been written to be as much portable as possible and cover as
34 # many use-case as reasonably possible. It won't work with Solaris'
35 # brain-damaged stock /bin/sh.
37 # HOWEVER, there will be bugs, there will be cases in which the script doesn't
38 # wrap properly the svn-cli, etc. In this case, you can try to mail me at
39 # <tsuna at lrde dot epita dot fr>. Include the revision of the svn-wrapper
40 # you're using, and full description of what's wrong etc. so I can reproduce
41 # your problem.
43 # If you feel like, you can try to fix/enhance the script yourself. It only
44 # requires some basic Shell-scripting skills. Knowing sed will prove useful :)
46 # ------------- #
47 # DOCUMENTATION #
48 # ------------- #
50 # If you're simply looking for the usage, run `svn-wrapper.sh help' (or
51 # `svn help' if you aliased `svn' on svn-wrapper.sh) as usual.
53 # This script is (hopefully) portable, widely commented and self-contained. Do
54 # not hesitate to hack it. It might look rather long (because it does a lot of
55 # things :P) but you should be able to easily locate which part of the code
56 # you're looking for.
58 # The script begins by defining several functions. Then it really starts where
59 # the comment "# `main' starts here. #" is placed.
60 # Some svn commands are hooked (eg, `svn st' displays colors). Hooks and
61 # extra commands are defined in functions named `svn_<command-name>'.
63 # ---- #
64 # TODO #
65 # ---- #
67 # * Write a real testsuite. :/
68 # * Automatic proxy configuration depending on the IP in ifconfig?
69 # * Customizable behavior/colors via some ~/.<config>rc file (?)
70 # * Handle things such as svn ci --force foobar.
72 # * svn automerge + automatic fill in branches/README.branches.
73 # => won't do (SVN is too borken WRT merges, I use Git now so I don't feel
74 # like dealing with this sort of SVN issue)
75 # * Automatically recognize svn cp and svn mv instead of writing "New" and
76 # "Remove" in the template ChangeLog entry (hard). Pair the and new/remove
77 # to prepare a good ChangeLog entry (move to X, copy from X) [even harder].
78 # => won't do (Git does this fine, can't be bothered to deal with this for
79 # SVN)
83 # Default values (the user can export them to override them).
84 use_changelog=:
85 git_mode=false
86 case $SVN in
87 git*) git_mode=:;;
88 '')
89 if [ -d .git ] || [ -d ../.git ]; then
90 SVN=git
91 git_mode=:
92 else
93 SVN=svn
96 esac
98 : ${EDITOR=missing}
99 export EDITOR
100 : ${GPG=gpg}
101 : ${TMPDIR=/tmp}
102 export TMPDIR
103 : ${PAGER=missing}
104 export PAGER
105 # Override the locale.
106 LC_ALL='C'
107 export LC_ALL
108 : ${AWK=missing}
109 : ${DIFF=missing}
110 : ${COLORDIFF=missing}
112 # Signal number for traps (using plain signal names is not portable and breaks
113 # on recent Debians that use ash as their default shell).
114 SIGINT=2
116 me=$0
117 bme=`basename "$0"`
119 # Pitfall: some users might be tempted to export SVN=svn-wrapper.sh for some
120 # reason. This is just *wrong*. The following is an attempt to save them from
121 # some troubles.
122 if [ x`basename "$SVN"` = x"$bme" ]; then
123 echo "warning: setting SVN to $bme is wrong"
124 SVN='svn'
127 # This code comes (mostly) from Autoconf.
128 # The user is always right.
129 if test "${PATH_SEPARATOR+set}" != set; then
130 PATH_SEPARATOR=:
131 (PATH='/bin;/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 && {
132 (PATH='/bin:/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 ||
133 PATH_SEPARATOR=';'
137 version='0.4'
138 full_rev='$Id$'
139 # Get the first 6 digits (without forking)
140 revision=${full_rev#'$'Id': '}
141 revision=${revision%??????????????????????????????????' $'}
143 # The `main' really starts after the functions definitions.
145 # ---------------- #
146 # Helper functions #
147 # ---------------- #
149 set_colors()
151 red='\e[0;31m'; lred='\e[1;31m'
152 green='\e[0;32m'; lgreen='\e[1;32m'
153 yellow='\e[0;33m'; lyellow='\e[1;33m'
154 blue='\e[0;34m'; lblue='\e[1;34m'
155 purple='\e[0;35m'; lpurple='\e[1;35m'
156 cyan='\e[0;36m'; lcyan='\e[1;36m'
157 grey='\e[0;37m'; lgrey='\e[1;37m'
158 white='\e[0;38m'; lwhite='\e[1;38m'
159 std='\e[m'
162 set_nocolors()
164 red=; lred=
165 green=; lgreen=
166 yellow=; lyellow=
167 blue=; lblue=
168 purple=; lpurple=
169 cyan=; lcyan=
170 grey=; lgrey=
171 white=; lwhite=
172 std=
175 # abort err-msg
176 abort()
178 echo "svn-wrapper: ${lred}abort${std}: $@" \
179 | sed '1!s/^[ ]*/ /' >&2
180 exit 1
183 # warn msg
184 warn()
186 echo "svn-wrapper: ${lred}warning${std}: $@" \
187 | sed '1!s/^[ ]*/ /' >&2
190 # notice msg
191 notice()
193 echo "svn-wrapper: ${lyellow}notice${std}: $@" \
194 | sed '1!s/^[ ]*/ /' >&2
197 # yesno question
198 yesno()
200 echo -n "$@ [y/N] "
201 read answer || return 1
202 case $answer in
203 y* | Y*) return 0;;
204 *) return 1;;
205 esac
206 return 42 # should never happen...
209 # yesnewproceed what
210 # returns true if `yes' or `proceed', false if `new'.
211 # the answer is stored in $yesnoproceed_res which is /yes|new|proceed/
212 yesnewproceed()
214 echo -n "$@ [(y)es/(p)roceed/(u)p+proceed/(N)ew] "
215 read answer || return 1
216 case $answer in
217 y* | Y*) yesnoproceed_res=yes; return 0;;
218 p* | P*) yesnoproceed_res=proceed; return 0;;
219 u* | U*) yesnoproceed_res=upproceed; return 0;;
220 *) yesnoproceed_res=new; return 1;;
221 esac
222 return 42 # should never happen...
225 # warn_env env-var
226 warn_env()
228 warn "cannot find the environment variable $1
229 You might consider using \`export $1='<FIXME>'\`"
232 # get_unique_file_name file-name
233 get_unique_file_name()
235 test -e "$1" || {
236 echo "$1" && return 0
238 gufn=$1; i=1
239 while test -e "$gufn.$i"; do
240 i=$((i + 1))
241 done
242 echo "$gufn.$i"
245 # ensure_not_empty description value
246 ensure_not_empty()
248 ene_val=`echo "$2" | tr -d ' \t\n'`
249 test x"$ene_val" = x && abort "$1: empty value"
252 # find_prog prog-name
253 # return true if prog-name is in the PATH
254 # echo the full path to prog-name on stdout.
255 # Based on a code from texi2dvi
256 find_prog()
258 save_IFS=$IFS
259 IFS=$PATH_SEPARATOR
260 for dir in $PATH; do
261 IFS=$save_IFS
262 test x"$dir" = x && continue
263 # The basic test for an executable is `test -f $f && test -x $f'.
264 # (`test -x' is not enough, because it can also be true for directories.)
265 # We have to try this both for $1 and $1.exe.
267 # Note: On Cygwin and DJGPP, `test -x' also looks for .exe. On Cygwin,
268 # also `test -f' has this enhancement, bot not on DJGPP. (Both are
269 # design decisions, so there is little chance to make them consistent.)
270 # Thusly, it seems to be difficult to make use of these enhancements.
272 if test -f "$dir/$1" && test -x "$dir/$1"; then
273 echo "$dir/$1"
274 return 0
275 elif test -f "$dir/$1.exe" && test -x "$dir/$1.exe"; then
276 echo "$dir/$1.exe"
277 return 0
279 done
280 return 1
283 # find_progs prog [progs...]
284 # Look in PATH for one of the programs given in argument.
285 # If none of the progs can be found, the string "exit 2" is "returned".
286 find_progs()
288 # This code comes mostly from Autoconf.
289 for fp_prog in "$@"; do
290 fp_res=`find_prog $fp_prog`
291 if [ $? -eq 0 ]; then
292 echo "$fp_res"
293 return 0
295 done
296 echo "exit 2"
297 return 1
300 test x"$EDITOR" = xmissing && EDITOR=`find_progs vim vi emacs nano`
301 test x"$PAGER" = xmissing && PAGER=`find_progs less more`
302 test x"$AWK" = xmissing && AWK=`find_progs gawk mawk nawk awk`
303 test x"$DIFF" = xmissing && DIFF=`find_progs diff`
304 test x"$COLORDIFF" = xmissing && COLORDIFF=`find_progs colordiff diff`
306 # -R will tell less to interpret some terminal codes, which will turn on
307 # colors.
308 case $PAGER in #(
309 less) PAGER='less -R';;
310 esac
312 # require_diffstat
313 # return true if diffstat is in the PATH
314 require_diffstat()
316 if [ x"$require_diffstat_cache" != x ]; then
317 return $require_diffstat_cache
319 if (echo | diffstat) >/dev/null 2>/dev/null; then :; else
320 warn 'diffstat is not installed on your system or not in your PATH.'
321 test -f /etc/debian_version \
322 && notice 'you might want to `apt-get install diffstat`.'
323 require_diffstat_cache=1
324 return 1
326 require_diffstat_cache=0
327 return 0
330 # require_mail
331 # return 0 -> found a mailer
332 # return !0 -> no mailer found
333 # The full path to the program found is echo'ed on stdout.
334 require_mail()
336 save_PATH=$PATH
337 PATH="${PATH}${PATH_SEPARATOR}/sbin${PATH_SEPARATOR}/usr/sbin${PATH_SEPARATOR}/usr/libexec"
338 export PATH
339 find_progs sendEmail sendmail mail
340 rv=$?
341 PATH=$save_PATH
342 export PATH
343 return $rv
346 # my_sendmail mail-file mail-subject mail-to [extra-headers]
347 # mail-to is a comma-separated list of email addresses.
348 # extra-headers is an optionnal argument and will be prepended at the
349 # beginning of the mail headers if the tool used to send mails supports it.
350 # The mail-file may also contain headers. They must be separated from the body
351 # of the mail by a blank line.
352 my_sendmail()
354 test -f "$1" || abort "my_sendmail: Cannot find the mail file: $1"
355 test x"$2" = x && warn 'my_sendmail: Empty subject.'
356 test x"$3" = x && abort 'my_sendmail: No recipient specified.'
358 content_type='Content-type: text/plain'
359 extra_headers="X-Mailer: svn-wrapper v$version (g$revision)
360 Mime-Version: 1.0
361 Content-Transfer-Encoding: 7bit"
362 if test x"$4" != x; then
363 extra_headers="$4
364 $extra_headers"
365 # Remove empty lines.
366 extra_headers=`echo "$extra_headers" | sed '/^[ ]*$/d;s/^[ ]*//'`
369 # If we have a signature, add it.
370 if test -f ~/.signature; then
371 # But don't add it if it is already in the mail.
372 if grep -Fe "`cat ~/.signature`" "$1" >/dev/null; then :; else
373 echo '-- ' >>"$1" && cat ~/.signature >>"$1"
376 # VCS-compat: handle user option 'sign'.
377 if (grep '^sign: false' ~/.vcs) >/dev/null 2>/dev/null; then :; else
378 ($GPG -h) >/dev/null 2>/dev/null
379 gpg_rv=$?
380 if [ -e ~/.gnupg ] || [ -e ~/.gpg ] || [ -e ~/.pgp ] && [ $gpg_rv -lt 42 ]
381 then
382 if grep 'BEGIN PGP SIGNATURE' "$1" >/dev/null; then
383 notice 'message is already GPG-signed'
384 elif yesno "Sign the mail using $GPG ?"; then
385 # Strip the headers
386 sed '1,/^$/d' "$1" >"$1.msg"
387 sed '1,/^$/!d' "$1" >"$1.hdr"
388 # Sign the message, keep only the PGP signature.
389 $GPG --clearsign <"$1.msg" >"$1.tmp" || {
390 rm -f "$1.msg" "$1.hdr" "$1.tmp"
391 abort "\`$GPG' failed (r=$?)"
393 sed '/^--*BEGIN PGP SIGNATURE--*$/,/^--*END PGP SIGNATURE--*$/!d' \
394 "$1.tmp" >"$1.sig"
396 boundary="svn-wrapper-2-$RANDOM"
397 boundary="$boundary$RANDOM"
398 # Prepend some stuff before the PGP signature.
399 echo "
400 --$boundary
401 content-type: application/pgp-signature; x-mac-type=70674453;
402 name=PGP.sig
403 content-description: This is a digitally signed message part
404 content-disposition: inline; filename=PGP.sig
405 content-transfer-encoding: 7bit" | cat - "$1.sig" >"$1.tmp"
406 mv -f "$1.tmp" "$1.sig"
408 # Append some stuff after the PGP signature.
409 echo "
410 --$boundary--" >>"$1.sig"
411 # Re-paste the headers before the signed body and prepend some stuff.
412 echo "This is an OpenPGP/MIME signed message (RFC 2440 and 3156)
413 --$boundary
414 Content-Transfer-Encoding: 8bit
415 Content-Type: text/plain; charset=iso-8859-1; format=flowed
417 | cat "$1.hdr" - "$1.msg" "$1.sig" >"$1.tmp" \
418 && mv -f "$1.tmp" "$1"
419 content_type="Content-Type: multipart/signed;\
420 protocol=\"application/pgp-signature\"; micalg=pgp-sha1;\
421 boundary=\"$boundary\""
422 # Cleanup.
423 rm -f "$1.tmp" "$1.sig" "$1.msg" "$1.hdr"
424 extra_headers="$extra_headers
425 X-Pgp-Agent: `$GPG --version | sed q`"
429 extra_headers="$extra_headers
430 $content_type"
432 mailer=`require_mail`
433 if [ $? -ne 0 ]; then
434 warn 'my_sendmail: No suitable mailer found.'
435 return 1
437 case $mailer in
438 */sendmail)
439 to=`echo "$3" | sed 's/,//g'`
440 echo "$extra_headers" | cat - "$1" | $mailer $to;;
441 */mail)
442 cat "$1" | $mailer -s "$2" "$3";;
443 */sendEmail)
444 if [ x"$SMTP" = x ]; then
445 warn 'my_sendmail: (sendEmail) please tell me the SMTP server to use'
446 echo -n 'STMP server: '
447 read SMTP || abort 'could not read the SMTP server'
448 notice "hint: you can export SMTP=$SMTP if you don't want to be asked"
450 sendEmail -f "$FULLNAME <$EMAIL>" -t "$3" -u "$2" \
451 -s "$SMTP" -o message-file="$1";;
452 *) # wtf
453 abort 'my_sendmail: Internal error.';;
454 esac
457 # selfupdate
458 selfupdate()
460 my_url='http://www.lrde.epita.fr/~sigoure/svn-wrapper'
461 # You can use https if you feel paranoiac.
463 echo ">>> Fetching svn-wrapper.sh from $my_url/svn-wrapper.sh"
465 # --------------------- #
466 # Fetch the new version #
467 # --------------------- #
469 tmp_me=`get_unique_file_name "$TMPDIR/svn-wrapper.sh"`
470 if (wget --help) >/dev/null 2>/dev/null; then
471 wget --no-check-certificate "$my_url/svn-wrapper.sh" -O "$tmp_me"
472 my_wget='wget'
473 else
474 curl --help >/dev/null 2>/dev/null
475 if [ $? -gt 42 ]; then
476 abort 'Cannot find wget or curl.
477 How can I download any update without them?'
479 my_wget='curl'
480 curl --insecure "$my_url/svn-wrapper.sh" >"$tmp_me"
483 test -r $tmp_me \
484 || abort "Cannot find the copy of myself I downloaded in $tmp_me"
486 # ---------------------------------------- #
487 # Compare versions and update if necessary #
488 # ---------------------------------------- #
490 my_ver=$revision
491 tmp_ver=`sed '/^# $Id[:].*$/!d;
492 s/.*$Id[:] *\([a-f0-9]\{6\}\).*/\1/' "$tmp_me"`
493 test x"$tmp_ver" = x && abort "Cannot find the revision of $tmp_me"
494 if [ x"$my_ver" != x"$tmp_ver" ]; then # There IS an update...
495 echo "An update is available, r$tmp_ver (your version is r$my_ver)"
497 # Wanna see the diff?
498 if yesno 'Do you want to see the diff?'
499 then
500 (require_diffstat && diff -uw "$me" "$tmp_me" | diffstat;
501 echo
502 $COLORDIFF -uw "$me" "$tmp_me") | $PAGER
505 # Let's go :)
506 if yesno "Overwrite $me (r$my_ver) with $tmp_me (r$tmp_ver)?"; then
507 chmod a+x "$tmp_me"
508 cp -p "$me" "$me.r$my_ver"
509 mv "$tmp_me" "$me" && exit 0
511 rm -f "$tmp_me"
512 return 1
513 else
514 echo "You're already up to date [r$my_ver] :)"
516 rm -f "$tmp_me"
519 # get_svn_diff_and_diffstat [files to diff]
520 # Helper for svn_commit
521 get_svn_diff_and_diffstat()
523 if $git_mode; then
524 svn_diff=`git diff --ignore-all-space --no-color -B -C --cached`
525 svn_diff_stat=`git diff --stat --ignore-all-space --no-color -B -C --cached`
526 else
527 # Ignore white spaces. Can't svn diff -x -w: svn 1.4 only.
528 svn_diff=`svn_diffw "$@"`
529 test x"$svn_diff" = x && svn_diff=`$SVN diff "$@"`
530 if require_diffstat; then
531 svn_diff_stat=`echo "$svn_diff" | diffstat`
532 else
533 svn_diff_stat='diffstat not available'
538 # Helper. Sets the variables repos_url, git_branch, git_head, repos_root and
539 # extra_repos_info properly.
540 git_get_repos_info_()
542 # FIXME: 1st commit: "fatal: bad default revision 'HEAD'" on stderr
543 repos_url=`git config --get svn-remote.svn.url`
544 test x"$repos_url" = x && repos_url='(git:unknown)'
545 git_branch=`git branch | awk '/^\*/ { print substr($0, 3) }'`
546 if [ x"$git_branch" = x'(no branch)' ]; then
547 yesno 'You are on a detached HEAD, do you really want to continue?' \
548 || return 1
550 git_head=`git rev-list --pretty=format:%h HEAD --max-count=1 | sed '1d;q'`
551 extra_repos_info="Git branch: $git_branch (HEAD: $git_head)"
552 repos_root=$repos_url
555 # Helper. Find the `mailto' property, be it an SVN property or a git-config
556 # option. Relies on the value of $change_log_dir and sets the values of
557 # $mailto (the value of the `mailto' property) and $to (a string to append in
558 # templates that contains the `To:' line, or an empty string if no mail must
559 # be sent).
560 get_mailto_property()
562 test -d "$change_log_dir" || abort 'Internal error in get_mailto_property:
563 $change_log_dir not pointing to a directory'
564 if $git_mode; then
565 mailto=`git config svnw.mailto`
566 if [ x"$mailto" = x ] \
567 && [ -f "$change_log_dir/.git/svn/git-svn/unhandled.log" ]
568 then
569 mailto=`grep mailto "$change_log_dir/.git/svn/git-svn/unhandled.log"`
570 sed_tmp='$!d;s/^.*+dir_prop: . mailto //;s/%40/@/g;s/%2C/,/g;s/%20/ /g;'
571 mailto=`echo "$mailto" | sed "$sed_tmp"`
573 if [ x"$mailto" = x ]; then
574 warn 'No mailto property set for this repository.
575 If this is a git-svn repository, do this in a SVN working copy:
576 svn propset mailto maintainer1@foo.com,maint2@bar.com .
577 If this is a real git repository, do this:
578 git config svnw.mailto maintainer1@foo.com,maint2@bar.com'
580 else
581 mailto=`$SVN propget mailto "$change_log_dir"`
584 if [ x"$mailto" = x ]; then
585 test x$new_user = xyes \
586 && warn "no svn property mailto found in $change_log_dir
587 You might want to set default email adresses using:
588 svn propset mailto 'somebody@mail.com, foobar@example.com'\
589 $change_log_dir" >&2
590 # Try to be VCS-compatible and find a list of mails in a *.rb.
591 if [ -d "$change_log_dir/vcs" ]; then
592 mailto=`grep '.@.*\..*' "$change_log_dir"/vcs/*.rb \
593 | tr '\n' ' ' \
594 | sed 's/^.*[["]\([^["]*@[^]"]*\)[]"].*$/\1/' | xargs`
595 test x"$mailto" != x && test x$new_user = xyes \
596 && notice "VCS-compat: found mailto: $mailto
597 in " "$change_log_dir"/vcs/*.rb
598 fi # end VCS compat
599 fi # end guess mailto
601 # Ensure that emails are comma-separated.
602 mailto=`echo "$mailto" | sed 's/[ ;]/,/g' | tr -s ',' | sed 's/,/, /g'`
605 if [ x"$mailto" != xdont_send_mails ] && [ x"$mailto" != xnothx ]; then
606 to="
607 To: $mailto"
613 # ------------------------------- #
614 # Hooks for standard SVN commands #
615 # ------------------------------- #
617 # svn_commit [args...]
618 # Here is how the commit process goes:
620 # First we look in the arguments passed to commit:
621 # If there are some files or paths, the user wants to commit these only. In
622 # this case, we must search for ChangeLogs from these paths. We might find
623 # more than one ChangeLog, in this case the user will be prompted to pick up
624 # one.
625 # Otherwise (no path passed in the command line) the user just wants to
626 # commit the current working directory.
627 # In any case, we schedule "ChangeLog" for commit.
629 # Alright now that we know which ChangeLog to use, we look in the ChangeLog's
630 # directory if there is a ",svn-log" file which would mean that a previous
631 # commit didn't finish successfully. If there is such a file, the user is
632 # prompted to know whether they want to resume that commit or simply start a
633 # new one.
634 # When the user wants to resume a commit, the ",svn-log" file is loaded and we
635 # retrieve the value of "$@" that was saved in the file.
636 # Otherwise we build a template ChangeLog entry.
637 # Then we open the template ChangeLog entry with $EDITOR so that the user
638 # fills it properly.
639 # Finally, we commit.
640 # Once the commit is sent, we ask the server to know which revision was
641 # commited and we also retrieve the diff. We then send a mail with these.
642 svn_commit()
644 here=`pwd -P`
645 dry_run=false
646 git_commit_all=false
647 use_log_message_from_file=false
648 log_message_to_use=
650 # Check if the user passed some paths to commit explicitly
651 # because in this case we must add the ChangeLog to the commit and search
652 # the ChangeLog from the dirname of that file.
653 i=0; search_from=; add_changelog=false; extra_files=
654 while [ $i -lt $# ]; do
655 arg=$1
656 case $arg in
657 --dry-run)
658 dry_run=:
659 shift
660 i=$((i + 1))
661 continue
663 -a|--all)
664 git_commit_all=:
666 --use-log-file)
667 shift
668 test -z "$1" && abort "$arg needs an argument"
669 test -r "$1" || abort "'$1' does not seem to be readable"
670 test -w "$1" || abort "'$1' does not seem to be writable"
671 test -d "$1" && abort "'$1' seems to be a directory"
672 use_log_message_from_file=:
673 log_message_to_use=$1
674 shift
675 continue
677 esac
678 # If the argument is a valid path: add the ChangeLog in the list of
679 # files to commit
680 if test -e "$arg"; then
681 add_changelog=:
682 if test -d "$arg"; then
683 search_from_add=$arg
684 else
685 search_from_add=`dirname "$arg"`
687 search_from="$search_from:$search_from_add"
689 shift
690 set dummy "$@" "$arg"
691 shift
692 i=$((i + 1))
693 done
694 if $add_changelog; then :; else
695 # There is no path/file in the command line: the user wants to commit the
696 # current directory. Make it explicit now:
697 extra_files=$here
699 search_from=`echo "$search_from" | sed 's/^://; s/^$/./'`
701 # ----------------- #
702 # Find ChangeLog(s) #
703 # ----------------- #
705 nb_chlogs=0; change_log_dirs=
706 save_IFS=$IFS; IFS=':'
707 for dir in $search_from; do
708 IFS=$save_IFS
709 $use_changelog || break
710 test -z "$dir" && dir='.'
711 # First: come back to the original place
712 cd "$here" || abort "Cannot cd to $here"
713 cd "$dir" || continue # Then: Enter $dir (which can be a relative path)
714 found=0
715 while [ $found -eq 0 ]; do
716 this_chlog_dir=`pwd -P`
717 if [ -f ./ChangeLog ]; then
718 found=1
719 nb_chlogs=$((nb_chlogs + 1))
720 change_log_dirs="$change_log_dirs:$this_chlog_dir"
721 else
722 cd ..
724 # Stop searching when in / ... hmz :P
725 test x"$this_chlog_dir" = x/ && break
726 done # end while: did we find a ChangeLog
727 done # end for: find ChangeLogs in $search_from
728 if [ $nb_chlogs -gt 0 ]; then
729 change_log_dirs=`echo "$change_log_dirs" | sed 's/^://' | tr ':' '\n' \
730 | sort -u`
731 nb_chlogs=`echo "$change_log_dirs" | wc -l`
734 # Did we find a ChangeLog? More than one?
735 if [ $nb_chlogs -eq 0 ] && $use_changelog; then
736 if yesno 'svn-wrapper: Error: Cannot find a ChangeLog file!
737 You might want to create an empty one (eg: `touch ChangeLog` where appropriate)
738 Do you want to proceed without using a ChangeLog?'; then
739 cd "$here"
740 $SVN commit "$@"
741 return $?
742 else
743 return 1
745 elif [ $nb_chlogs -gt 1 ]; then
746 notice "$nb_chlogs ChangeLogs were found, pick up one:"
748 IFS=':'; i=0
749 for a_chlog_dir in $change_log_dirs; do
750 i=$((i + 1))
751 echo "$i. $a_chlog_dir/ChangeLog"
752 done
753 echo -n "Which ChangeLog do you want to use? [1-$i] "
754 read chlog_no || abort 'Cannot read answer on stdin.'
756 case $chlog_no in
757 *[^0-9]*) abort "Invalid ChangeLog number: $chlog_no"
758 esac
759 test "$chlog_no" -le $i || abort "Invalid ChangeLog number: $chlog_no
760 max value was: $i"
761 test "$chlog_no" -ge 1 || abort "Invalid ChangeLog number: $chlog_no
762 min value was: 1"
763 change_log_dir=`echo "$change_log_dirs" | tr ':' '\n' | sed "${chlog_no}!d"`
764 else # Only one ChangeLog found
765 if $use_changelog; then
766 change_log_dir=$change_log_dirs
767 notice "using $change_log_dir/ChangeLog"
771 if $use_changelog; then
772 test -f "$change_log_dir/ChangeLog" \
773 || abort "No such file or directory: $change_log_dir/ChangeLog"
774 # Now we can safely schedule the ChangeLog for the commit.
775 extra_files="$extra_files:$change_log_dir/ChangeLog"
776 else
777 change_log_dir='.' # Hack. FIXME: Does this work in all cases?
780 if [ -d "$change_log_dir/.git" ] || $git_mode; then
781 SVN=git
782 git_mode=:
783 git_get_repos_info_
784 else
785 svn_st_tmp=`$SVN status "$change_log_dir"`
787 # Warn for files that are not added in the repos.
788 conflicts=`echo "$svn_st_tmp" | sed '/^ *$/d;
789 /^[^?]/d;
790 /^?.......*\/[,+]/d;
791 /^?......[,+]/d'`
792 if test x"$conflicts" != x; then
793 warn "make sure you don't want to \`svn add'
794 any of the following files before committing:"
795 echo "$conflicts" | sed "$sed_svn_st_color"
796 echo -n 'Type [ENTER] to continue :)' && read chiche_is_gay
799 # If there are changes in an svn:externals, advise the user to commit that
800 # first.
801 changed_externals=`echo "$svn_st_tmp" | $AWK \
802 'function printext()
804 if (ext && !printed)
806 print this_ext "\n";
807 printed = 1;
810 BEGIN { this_ext = ""; ext = 0; ext_modified = 0; }
811 /^Performing status on external/ {
812 ext = 1;
813 sub(/.* at ./, ""); sub(/.$/, ""); this_ext = $0;
814 printed = 0;
816 /^[ADMR]/ { ext_modified = ext; printext(); }
817 /^.[M]/ { ext_modified = ext; printext(); }
818 END { exit ext_modified; }'`
819 if [ $? -ne 0 ]; then
820 warn "the following external items have local modifications:
821 $changed_externals"
822 yesno "You are advised to commit them separately first. Continue anyway?" \
823 || return 1
826 # Detect unresolved conflicts / missing files.
827 conflicts=`echo "$svn_st_tmp" | sed '/^[C!]/!d'`
828 test x"$conflicts" != x && abort "there are unresolved conflicts (\`C')
829 and/or missing files (\`!'):
830 $conflicts"
832 svn_info_tmp=`$SVN info "$change_log_dir"`
833 test $? -ne 0 && abort "Failed to get svn info on $change_log_dir"
834 repos_root=`echo "$svn_info_tmp" | sed '/^Repository Root: /!d;s///'`
835 repos_url=`echo "$svn_info_tmp" | sed '/^URL: /!d;s///'`
836 # It looks like svn <1.3 didn't display a "Repository Root" entry.
837 test x"$repos_root" = x && repos_root=$repos_url
840 cd "$here"
842 YYYY=`date '+%Y'`
843 MM=`date '+%m'`
844 DD=`date '+%d'`
846 # VCS-compat: handle user option 'new_user'
847 new_user='yes'
848 grep '^new_user: false' ~/.vcs >/dev/null 2>/dev/null && new_user='no'
850 edit_changelog=:
851 tmp_log="$change_log_dir/,svn-log"
852 $use_log_message_from_file \
853 && tmp_log=$log_message_to_use \
854 && edit_changelog=false
856 if [ -f "$tmp_log" ] \
857 && { $use_log_message_from_file \
858 || yesnewproceed "It looks like the last commit did not\
859 terminate successfully.
860 Would you like to resume it or proceed immediately?"; }; then
861 case $yesnoproceed_res in
862 *proceed) edit_changelog=false;;
863 esac
864 if test x"$yesnoproceed_res" = xupproceed; then
865 svn_update "$@" || abort 'update failed'
867 echo 'Resuming ...'
868 internal_tags=`sed '/^--- Internal stuff, DO NOT change please ---$/,$!d' \
869 "$tmp_log"`
870 saved_args=`echo "$internal_tags" | sed '/^args: */!d;s///'`
871 extra_files=`echo "$internal_tags" | sed '/^extra_files: */!d;s///'`
872 if [ x"$saved_args" != x ]; then
873 if [ x"$*" != x ] && [ x"$saved_args" != x"$*" ]; then
874 warn "overriding arguments:
875 you invoked $me with the following arguments: $@
876 they have been replaced by these: $saved_args"
877 set dummy $saved_args
878 shift
879 else
880 notice "setting the following arguments: $saved_args"
881 set dummy $saved_args
882 shift
884 elif [ x"$*" != x ]; then
885 warn "overriding arguments:
886 you invoked $me with the following arguments: $@
887 they have been dropped"
890 for i; do
891 case $i in
892 -a|--all)
893 git_commit_all=:
894 if [ $git_mode ]; then
895 (cd $change_log_dir && git add -v -u) || abort '`git add -v -u` failed'
898 esac
899 done
901 get_svn_diff_and_diffstat "$@"
903 # Update the file with the new diff/diffstat in case it changed.
904 $AWK 'BEGIN {
905 tlatbwbi_seen = 0;
906 ycewah_seen = 0;
908 /^--This line, and those below, will be ignored--$/ {
909 tlatbwbi_seen = 1;
911 /^ Your ChangeLog entry will appear here\.$/ {
912 if (tlatbwbi_seen) ycewah_seen = 1;
915 if (ycewah_seen != 2) print;
916 if (ycewah_seen == 1) ycewah_seen = 2;
918 END {
919 if (tlatbwbi_seen == 0)
921 print "--This line, and those below, will be ignored--\n\n" \
922 " Your ChangeLog entry will appear here.";
924 }' "$tmp_log" >"$tmp_log.tmp"
925 echo "
927 $svn_diff_stat
929 $svn_diff
931 $internal_tags" >>"$tmp_log.tmp"
932 mv -f "$tmp_log.tmp" "$tmp_log" || abort "failed to write '$tmp_log'"
934 else # Build the template message.
936 # ------------------------------------ #
937 # Gather info for the template message #
938 # ------------------------------------ #
940 if $git_mode; then
941 projname=`git config svnw.project`
942 if [ x"$projname" = x ] \
943 && [ -f "$change_log_dir/.git/svn/git-svn/unhandled.log" ]
944 then
945 projname=`grep project "$change_log_dir/.git/svn/git-svn/unhandled.log"`
946 sed_tmp='$!d;s/^.*+dir_prop: . project //'
947 projname=`echo "$projname" | sed "$sed_tmp"`
949 if [ x"$projname" = x ]; then
950 warn 'No project name set for this repository.
951 If this is a git-svn repository, do this in a SVN working copy:
952 svn propset project myproj .
953 If this is a real git repository, do this:
954 git config svnw.project myproj'
956 else
957 projname=`$SVN propget project "$change_log_dir"`
959 # Try to be VCS-compatible and find a project name in a *.rb.
960 if [ x"$projname" = x ] && [ -d "$change_log_dir/vcs" ]; then
961 projname=`sed '/common_commit/!d;s/.*"\(.*\)<%= rev.*/\1/' \
962 "$change_log_dir"/vcs/*.rb`
963 test x"$projname" != x && test x$new_user = xyes \
964 && notice "VCS-compat: found project name: $projname
965 in " "$change_log_dir"/vcs/*.rb
967 test x"$projname" != x && projname=`echo "$projname" | sed '/[^ ]$/s/$/ /'`
969 get_mailto_property
971 test x"$FULLNAME" = x && FULLNAME='Type Your Name Here' \
972 && warn_env FULLNAME
973 test x"$EMAIL" = x && EMAIL='your.mail.here@FIXME.com' && warn_env EMAIL
975 if $git_mode; then
976 if $git_commit_all; then
977 (cd $change_log_dir && git add -v -u) || abort '`git add -v -u` failed'
979 my_git_st=`git diff -C --raw --cached`
980 test $? -eq 0 || abort 'git diff failed'
981 # Format: ":<old_mode> <new_mode> <old_sha1> <new_sha1> <status>[
982 # <similarity score>]\t<file-name>"
983 change_log_files=`echo "$my_git_st" | sed '
984 t dummy_sed_1
985 : dummy_sed_1
986 s/^:[0-7 ]* [0-9a-f. ]* M[^ ]* \(.*\)$/ * \1: ./; t
987 s/^:[0-7 ]* [0-9a-f. ]* A[^ ]* \(.*\)$/ * \1: New./; t
988 s/^:[0-7 ]* [0-9a-f. ]* D[^ ]* \(.*\)$/ * \1: Remove./; t
989 s/^:[0-7 ]* [0-9a-f. ]* R[^ ]* \([^ ]*\) \(.*\)$/ * \2: Rename from \1./;t
990 s/^:[0-7 ]* [0-9a-f. ]* C[^ ]* \([^ ]*\) \(.*\)$/ * \2: Copy from \1./;t
991 s/^:[0-7 ]* [0-9a-f. ]* T[^ ]* \(.*\)$/ * \1: ./; t
992 s/^:[0-7 ]* [0-9a-f. ]* X[^ ]* \(.*\)$/ * \1: ???./; t
993 s/^:[0-7 ]* [0-9a-f. ]* U[^ ]* \(.*\)$/ * \1: UNMERGED./; t
995 else
996 # --ignore-externals appeared after svn 1.1.1
997 my_svn_st=`$SVN status --ignore-externals "$@" \
998 || $SVN status "$@" | sed '/^Performing status on external/ {
1002 # Files to put in the ChangeLog entry.
1003 change_log_files=`echo "$my_svn_st" | sed '
1004 t dummy_sed_1
1005 : dummy_sed_1
1006 s/^M......\(.*\)$/ * \1: ./; t
1007 s/^A......\(.*\)$/ * \1: New./; t
1008 s/^D......\(.*\)$/ * \1: Remove./; t
1012 if [ x"$change_log_files" = x ]; then
1013 yesno 'Nothing to commit, continue anyway?' || return 1
1016 change_log_files=`echo "$change_log_files" | sort -u`
1018 get_svn_diff_and_diffstat "$@"
1020 # Get any older svn-log out of the way.
1021 test -f "$tmp_log" && mv "$tmp_log" `get_unique_file_name "$tmp_log"`
1022 # If we can't get an older svn-log out of the way, find a new name...
1023 test -f "$tmp_log" && tmp_log=`get_unique_file_name "$tmp_log"`
1024 if [ x$new_user = no ]; then
1025 commit_instructions='
1026 Instructions:
1027 - Fill the ChangeLog entry.
1028 - If you feel like, write a comment in the "Comment:" section.
1029 This comment will only appear in the email, not in the ChangeLog.
1030 By default only the location of the repository is in the comment.
1031 - Some tags will be replaced. Tags are of the form: <TAG>. Unknown
1032 tags will be left unchanged.
1033 - The tag <REV> may only be used in the Subject.
1034 - Your ChangeLog entry will be used as commit message for svn.'
1035 else
1036 commit_instructions=
1038 r_before_rev=r
1039 $git_mode && r_before_rev=
1040 test x"$extra_repos_info" = x || extra_repos_info="
1041 $extra_repos_info"
1042 echo "\
1043 --You must fill this file correctly to continue-- -*- vcs -*-
1044 Title:
1045 Subject: ${projname}$r_before_rev<REV>: <TITLE>
1046 From: $FULLNAME <$EMAIL>$to
1048 Comment:
1049 URL: $repos_url$extra_repos_info
1051 ChangeLog:
1053 <YYYY>-<MM>-<DD> $FULLNAME <$EMAIL>
1055 <TITLE>
1056 $change_log_files
1058 --This line, and those below, will be ignored--
1059 $commit_instructions
1060 --Preview of the message that will be sent--
1062 URL: $repos_url$extra_repos_info
1063 Your comments (if any) will appear here.
1065 ChangeLog:
1066 $YYYY-$MM-$DD $FULLNAME <$EMAIL>
1068 Your ChangeLog entry will appear here.
1071 $svn_diff_stat
1073 $svn_diff" >"$tmp_log"
1075 echo "
1076 --- Internal stuff, DO NOT change please ---
1077 args: $@" >>"$tmp_log"
1078 echo "extra_files: $extra_files
1079 vi: ft=diff:noet:tw=76:" >>"$tmp_log"
1081 fi # end: if svn-log; then resume? else create template
1082 $edit_changelog && $EDITOR "$tmp_log"
1084 # ------------------ #
1085 # Re-"parse" the log #
1086 # ------------------ #
1088 # hmz this section is a bit messy...
1089 # helper string... !@#$%* escaping \\\\\\...
1090 sed_escape='s/\\/\\\\/g;s/@/\\@/g;s/&/\\\&/g'
1091 sed_eval_tags="s/<MM>/$MM/g; s/<DD>/$DD/g; s/<YYYY>/$YYYY/g"
1092 full_log=`sed '/^--*This line, and those below, will be ignored--*$/,$d;
1093 /^--You must fill this/d' "$tmp_log"`
1094 chlog_entry=`echo "$full_log" | sed '/^ChangeLog:$/,$!d; //d'`
1095 ensure_not_empty 'ChangeLog entry' "$chlog_entry"
1096 full_log=`echo "$full_log" | sed '/^ChangeLog:$/,$d'`
1097 mail_comment=`echo "$full_log" | sed '/^Comment:$/,$!d; //d'`
1098 full_log=`echo "$full_log" | sed '/^Comment:$/,$d'`
1099 # Add a period at the end of the title.
1100 sed_tmp='/^Title: */!d;s///;/ *[.!?]$/!s/ *$/./'
1101 mail_title=`echo "$full_log" | sed "$sed_tmp"`
1102 ensure_not_empty 'commit title' "$mail_title"
1103 mail_title=`echo "$mail_title" | sed "$sed_eval_tags; $sed_escape"`
1104 sed_eval_tags="$sed_eval_tags; s@<TITLE>\\.*@$mail_title@g"
1105 mail_comment=`echo "$mail_comment" | sed "$sed_eval_tags"`
1106 raw_chlog_entry=$chlog_entry # ChangeLog entry without tags expanded
1107 chlog_entry=`echo "$chlog_entry" | sed "$sed_eval_tags; 1{
1108 /^ *$/d
1110 mail_subject=`echo "$full_log" | sed '/^Subject: */!d;s///'`
1111 ensure_not_empty 'mail subject' "$mail_subject"
1112 mail_to=`echo "$full_log" | sed '/^To:/!d'`
1113 send_a_mail=:
1114 if test x"$mail_to" = x; then
1115 send_a_mail=false
1116 else
1117 mail_to=`echo "$mail_to" | sed 's/^To: *//;s///'`
1118 # If there is a <MAILTO> in the 'To:' line, we must expand it.
1119 case $mail_to in #(
1120 *'<MAILTO>'*)
1121 get_mailto_property
1122 # Are we meant to send a mail?
1123 case $to in #(
1124 '') # No, don't send a mail.
1125 mail_to=
1126 send_a_mail=false
1127 ;; #(
1128 *) # Yes, send a mail.
1129 mail_to=`echo "$mail_to" | sed "s#<MAILTO>#$mailto#g"`
1131 esac
1132 esac
1133 $send_a_mail && ensure_not_empty '"To:" field of the mail' "$mail_to"
1136 test x"$FULLNAME" = x && warn_env FULLNAME && FULLNAME=$USER
1137 test x"$EMAIL" = x && warn_env EMAIL && EMAIL=$USER
1138 myself=`echo "$FULLNAME <$EMAIL>" | sed "$sed_escape"`
1139 mail_from=`echo "$full_log" | sed "/^From: */!d;s///;s@<MYSELF>@$myself@g"`
1140 ensure_not_empty '"From:" field of the mail' "$mail_from"
1142 # ------------------------------------ #
1143 # Sanity checks on the ChangeLog entry #
1144 # ------------------------------------ #
1146 if echo "$chlog_entry" | grep '<REV>' >/dev/null; then
1147 warn 'Using the tag <REV> anywhere else than in the Subject is deprecated.'
1148 yesno 'Continue anyway?' || return 1
1151 if echo "$chlog_entry" | grep ': \.$' >/dev/null; then
1152 warn 'It looks like you did not fill all entries in the ChangeLog:'
1153 echo "$chlog_entry" | grep ': \.$'
1154 yesno 'Continue anyway?' || return 1
1157 if echo "$chlog_entry" | grep '^--* Internal stuff' >/dev/null; then
1158 warn "It looks like you messed up the delimiters and I did not properly
1159 find your ChangeLog entry. Here it is, make sure it is correct:"
1160 echo "$chlog_entry"
1161 yesno 'Continue anyway?' || return 1
1164 if echo "$chlog_entry" | grep -i 'dont[^a-z0-9]' >/dev/null; then
1165 warn "Please avoid typos such as ${lred}dont$std instead of\
1166 ${lgreen}don't$std:"
1167 echo "$chlog_entry" | grep -n -i 'dont[^a-z0-9]' \
1168 | sed "s/[dD][oO][nN][tT]/$lred&$std/g"
1169 yesno 'Continue anyway?' || return 1
1172 if echo "$chlog_entry" | grep -i 'cant[^a-z0-9]' >/dev/null; then
1173 warn "Please avoid typos such as ${lred}cant$std instead of\
1174 ${lgreen}can't$std:"
1175 echo "$chlog_entry" | grep -n -i 'cant[^a-z0-9]' \
1176 | sed "s/[cC][aA][nN][tT]/$lred&$std/g"
1177 yesno 'Continue anyway?' || return 1
1180 if echo "$chlog_entry" | grep '^.\{80,\}' >/dev/null; then
1181 warn 'Please avoid long lines in your ChangeLog entry (80 columns max):'
1182 echo "$chlog_entry" | grep '^.\{80,\}'
1183 yesno 'Continue anyway?' || return 1
1186 if echo "$mail_title" | grep -i '^[a-z][a-z]*ed[^a-z]' >/dev/null; then
1187 warn 'ChangeLog entries should be written in imperative form:'
1188 echo "$mail_title" | grep -i '^[a-z][a-z]*ed[^a-z]' \
1189 | sed "s/^\\([a-zA-Z][a-zA-Z]*ed\\)\\([^a-zA-Z]\\)/$lred\\1$std\\2/"
1190 yesno 'Continue anyway?' || return 1
1193 # Check whether the user passed -m | --message
1195 while [ $i -lt $# ]; do
1196 arg=$1
1197 # This is not really a reliable way of knowing whether -m | --message was
1198 # passed but hum... Let's assume it'll do :s
1199 if [ x"$arg" = 'x-m' ] || [ x"$arg" = 'x--message' ]; then
1200 my_message=$2
1202 shift
1203 set dummy "$@" "$arg"
1204 shift
1205 i=$((i + 1))
1206 done
1207 if [ x"$my_message" = x ]; then
1208 # The title must not be indented in the commit message and must be
1209 # followed by a blank line. This yields much better results with most
1210 # VC-viewer (especially for Git but including for SVN, such as Trac for
1211 # instance). We assume that the title will always be on the 1st line.
1212 sed_git_title="1s@^[ ]*<TITLE>\\.*@$mail_title\\
1213 @g; $sed_eval_tags"
1214 # First, remove empty lines at the beginning, if any.
1215 # Remove also the date information (useless in commit messages)
1216 my_message=`echo "$raw_chlog_entry" \
1217 | sed -e '1,4 {
1218 /^<YYYY>-<[MD][MD]>-<[DM][DM]>/d
1219 /^[1-9][0-9][0-9][0-9]-[0-9][0-9]*-[0-9][0-9]*/d
1220 /^ and .*<.*@.*>$/d
1221 /^ *$/d
1222 }' \
1223 | sed -e "$sed_git_title" \
1224 -e "$sed_eval_tags; 1{
1225 /^ *$/d
1227 else
1228 notice 'you are overriding the commit message.'
1231 # Show suspicious whitespace additions with Git.
1232 $git_mode && git diff --cached --check
1234 if $dry_run; then
1235 proposal_file=',proposal'
1236 test -f "$proposal_file" \
1237 && proposal_file=`get_unique_file_name "$proposal_file"`
1238 sed_tmp='s/<REV>/???/g;s/\([^.]\) *\.$/\1/'
1239 mail_subject=`echo "$mail_subject" | sed "$sed_eval_tags; $sed_tmp"`
1241 if [ x"$mailto" != xdont_send_mails ] && [ x"$mailto" != xnothx ]; then
1242 to="
1243 To: $mailto"
1245 echo "\
1246 From: $mail_from$to
1247 Subject: $mail_subject
1249 $mail_comment
1251 ChangeLog:
1252 $chlog_entry
1255 $svn_diff_stat
1257 $svn_diff" >"$proposal_file"
1258 notice "A proposal of your commit was left in '$proposal_file'"
1259 return 0
1262 # Are you sure?
1263 if $git_mode; then
1264 $git_commit_all \
1265 || notice 'You are using git, unlike SVN, do not forget to git add your
1266 changes'
1269 # Change edit_changelog so that we're asking the confirmation below.
1270 $use_log_message_from_file && edit_changelog=: \
1271 && notice "You are about to commit the following change:
1272 $mail_title"
1274 $edit_changelog \
1275 && {
1276 yesno 'Are you sure you want to commit?' \
1277 || return 1
1280 # Add the ChangeLog entry
1281 old_chlog=`get_unique_file_name "$change_log_dir/ChangeLog.old"`
1282 mv "$change_log_dir/ChangeLog" "$old_chlog" || \
1283 abort 'Could not backup ChangeLog'
1284 trap "echo SIGINT; mv \"$old_chlog\" \"$change_log_dir/ChangeLog\";
1285 exit 130" $SIGINT
1286 echo "$chlog_entry" >"$change_log_dir/ChangeLog"
1287 echo >>"$change_log_dir/ChangeLog"
1288 cat "$old_chlog" >>"$change_log_dir/ChangeLog"
1290 # Add extra files such as cwd or ChangeLog to the commit.
1291 tmp_sed='s/ /\\ /g' # Escape spaces for the shell.
1292 if $git_mode && $use_changelog; then
1293 # Schedule the ChangeLog for the next commit
1294 (cd "$change_log_dir" && git add ChangeLog) \
1295 || abort 'failed to git add the ChangeLog'
1296 extra_files=
1297 else
1298 extra_files=`echo "$extra_files" | sed "$tmp_sed" | tr ':' '\n'`
1301 # Always sign the commits with Git.
1302 $git_mode && set dummy -s "$@" && shift
1304 # Update the Git index if necessary (just in case the user changed his
1305 # working copy in the mean time)
1306 if $git_mode && $git_commit_all; then
1307 (cd $change_log_dir && git add -v -u) || abort '`git add -v -u` failed'
1310 # --Commit-- finally! :D
1311 $SVN commit -m "$my_message" "$@" $extra_files || {
1312 svn_commit_rv=$?
1313 mv "$old_chlog" "$change_log_dir/ChangeLog"
1314 abort "Commit failed, $SVN returned $svn_commit_rv"
1317 echo -n 'Getting the revision number... '
1318 if $git_mode; then
1319 REV=`git rev-list --pretty=format:%h HEAD --max-count=1 | sed '1d;q'`
1320 else
1321 svn_info_tmp=`$SVN info "$change_log_dir/ChangeLog"`
1322 REV=`echo "$svn_info_tmp" | sed '/^Revision: /!d;s///'`
1323 test x"$REV" = x && REV=`echo "$svn_info_tmp" \
1324 | sed '/^Last Changed Rev: /!d;s///'`
1326 test x"$REV" = x && abort 'Cannot detect the current revision.'
1327 echo "$REV"
1329 # Let's make sure we have the real diff by asking the ChangeSet we've just
1330 # committed to the server.
1332 # Backup the old stuff in case we fail to get the real diff from the server
1333 # for some reason...
1334 save_svn_diff=$svn_diff
1335 save_svn_diff_stat=$svn_diff_stat
1337 if $git_mode; then
1338 svn_diff=`git diff --ignore-all-space --no-color -C 'HEAD^' HEAD`
1339 svn_diff_stat=`git diff --stat --ignore-all-space --no-color -C 'HEAD^' HEAD`
1340 grep svn-remote "$change_log_dir/.git/config" >/dev/null 2>&1 && \
1341 notice 'Do not forget to use: git-svn dcommit to push your commits in SVN'
1342 else
1343 # Fetch the ChangeSet and filter out the ChangeLog entry. We don't use
1344 # svn diff -c because this option is not portable to older svn versions.
1345 REV_MINUS_ONE=$((REV - 1))
1346 svn_diff=`svn_diffw -r"$REV_MINUS_ONE:$REV" "$repos_root" \
1347 | $AWK '/^Index: / { if (in_chlog) in_chlog = 0; }
1348 /^Index: .*ChangeLog$/ { in_chlog = 1 }
1349 { if (!in_chlog) print }'`
1350 if [ x"$svn_diff" = x ]; then
1351 svn_diff=$save_svn_diff
1352 svn_diff_stat=$save_svn_diff_stat
1353 else
1354 if require_diffstat; then
1355 svn_diff_stat=`echo "$svn_diff" | diffstat`
1356 else
1357 svn_diff_stat='diffstat not available'
1362 # Expand <REV> and remove the final period from the mail subject if there is
1363 # only one period.
1364 sed_tmp="s/<REV>/$REV/g;"'s/\([^.]\) *\.$/\1/'
1365 mail_subject=`echo "$mail_subject" | sed "$sed_eval_tags; $sed_tmp"`
1367 mail_file=`get_unique_file_name "$change_log_dir/+mail"`
1368 echo "\
1369 From: $mail_from
1370 To: $mail_to
1371 Subject: $mail_subject
1373 $mail_comment
1375 ChangeLog:
1376 $chlog_entry
1379 $svn_diff_stat
1381 $svn_diff" | sed 's/^\.$/ ./' >"$mail_file"
1382 # We change lines with only a `.' because they could mean "end-of-mail"
1384 # Send the mail
1385 if $send_a_mail; then
1386 trap 'echo SIGINT; exec < /dev/null' $SIGINT
1387 # FIXME: Move the mail to the +committed right now, in case the user
1388 # CTLR+C the mail-sending-thing, so that the mail will be properly saved
1389 # their.
1390 my_sendmail "$mail_file" "$mail_subject" "$mail_to" \
1391 "X-svn-url: $repos_root
1392 X-svn-revision: $REV"
1393 fi # end do we have to send a mail?
1394 rm -f "$tmp_log"
1395 rm -f "$old_chlog"
1396 save_mail_file=`echo "$mail_file" | sed 's/+//'`
1397 mkdir -p "$change_log_dir/+committed" \
1398 || warn "Couldn't mkdir -p $change_log_dir/+committed"
1399 if [ -d "$change_log_dir/vcs" ] \
1400 || [ -d "$change_log_dir/+committed" ]
1401 then
1402 mkdir -p "$change_log_dir/+committed/$REV" \
1403 && mv "$mail_file" "$change_log_dir/+committed/$REV/mail"
1405 return $svn_commit_rv
1408 # svn_diffw [args...]
1409 svn_diff()
1411 # Ignore white spaces.
1412 if $git_mode; then
1413 git diff -C "$@"
1414 else
1415 diffarg='-u'
1416 # Can't svn diff -x -w: svn 1.4 only.
1417 # Moreover we *MUST* use -x -uw, not -x -u -w or -x -u -x -w ...
1418 # Hence the hack to stick both diff arguments together...
1419 # No comment :)
1420 test x"$1" = x'--SVNW-HACK-w' && shift && diffarg='-uw'
1421 $SVN diff --no-diff-deleted --diff-cmd $DIFF -x $diffarg "$@"
1425 # svn_diffw [args...]
1426 svn_diffw()
1428 # Ignore white spaces.
1429 if $git_mode; then
1430 svn_diff --ignore-all-space "$@"
1431 else
1432 svn_diff --SVNW-HACK-w "$@"
1436 # svn_mail REV [mails...]
1437 svn_mail()
1439 test $# -lt 1 && abort "Not enough arguments provided;
1440 Try 'svn help mail' for more info."
1441 case $1 in
1442 PREV)
1443 if $git_mode; then
1444 REV=`git rev-list --pretty=format:%h 'HEAD^' --max-count=1 | sed '1d;q'`
1445 else
1446 REV=`svn_revision || abort 'Cannot get current revision number'`
1447 test x"$REV" = x && abort 'Cannot get current revision number'
1448 if [ "$REV" -lt 1 ]; then
1449 abort 'No previous revision.'
1451 REV=$((REV - 1))
1454 HEAD)
1455 REV=`svn_revision || abort 'Cannot get current revision number'`
1456 test x"$REV" = x && abort 'Cannot get current revision number'
1458 *) REV=$1;;
1459 esac
1460 shift
1462 found_committed=0; found=0
1463 while [ $found -eq 0 ]; do
1464 this_chlog_dir=`pwd -P`
1465 if [ -d ./+committed ]; then
1466 found_committed=1
1467 if [ -d ./+committed/$REV ]; then
1468 found=1
1469 else
1470 cd ..
1472 else
1473 cd ..
1475 # Stop searching when in / ... hmz :P
1476 test x`pwd` = x/ && break
1477 done
1478 if [ $found -eq 0 ]; then
1479 if [ $found_committed -eq 0 ]; then
1480 abort 'Could not find the +committed directory.'
1481 else
1482 abort "Could not find the revision $REV in +committed."
1484 abort 'Internal error (should never be here).'
1487 mail_file=; subject=; to=
1488 if [ -f ./+committed/$REV/mail ]; then
1489 # svn-wrapper generated file
1490 mail_file="./+committed/$REV/mail"
1491 subject=`sed '/^Subject: /!d;s///' $mail_file | sed '1q'`
1492 to=`sed '/^To: /!d;s///' $mail_file | sed '1q'`
1493 elif [ -f ./+committed/$REV/,iform ] && [ -f ./+committed/$REV/,message ]
1494 then
1495 # VCS-generated file
1496 subject=`sed '/^Subject: /!d;s///;s/^"//;s/"$//' ./+committed/$REV/,iform \
1497 | sed "s/<%= *rev *%>/$REV/g"`
1498 to=`sed '/^To:/,/^[^-]/!d' ./+committed/$REV/,iform | sed '1d;s/^- //;$d' \
1499 | xargs | sed 's/ */, /g'`
1500 mail_file=`get_unique_file_name "$TMPDIR/mail.r$REV"`
1501 echo "From: $FULLNAME <$EMAIL>
1502 To: $to
1503 Subject: $subject
1504 " >"$mail_file" || abort "Cannot create $mail_file"
1505 cat ./+committed/$REV/,message >>"$mail_file" \
1506 || abort "Cannot copy ./+committed/$REV/,message in $mail_file"
1507 else
1508 abort "Couldn't find the mail to re-send in `pwd`/+committed/$REV"
1510 if [ $# -gt 0 ]; then
1511 to=`echo "$*" | sed 's/ */, /g'`
1514 test x"$to" = x && abort 'Cannot find the list of recipients.
1515 Please report this bug.'
1516 test x"$subject" = x && abort 'Cannot find the subject of the mail.
1517 Please report this bug.'
1519 if yesno "Re-sending the mail of r$REV
1520 Subject: $subject
1521 To: $to
1522 Are you sure?"; then :; else
1523 return 1
1526 if $git_mode; then
1527 git_get_repos_info_
1528 else
1529 svn_info_tmp=`$SVN info`
1530 test $? -ne 0 && abort "Failed to get svn info on `pwd`"
1531 repos_root=`echo "$svn_info_tmp" | sed '/^Repository Root: /!d;s///'`
1532 repos_url=`echo "$svn_info_tmp" | sed '/^URL: /!d;s///'`
1533 # It looks like svn <1.3 didn't display a "Repository Root" entry.
1534 test x"$repos_root" = x && repos_root=$repos_url
1537 my_sendmail "$mail_file" "$subject" "$to" \
1538 "X-svn-url: $repos_url
1539 X-svn-revision: $REV"
1542 # svn_version
1543 svn_version()
1545 echo "Using svn-wrapper v$version-g$revision (C) SIGOURE Benoit [GPL]"
1548 # has_prop prop-name [path]
1549 # return value: 0 -> path has the property prop-name set.
1550 # 1 -> path has no property prop-name.
1551 # 2 -> svn error.
1552 has_prop()
1554 hp_plist=`$SVN proplist "$2"`
1555 test $? -ne 0 && return 2
1556 hp_res=`echo "$hp_plist" | sed "/^ *$1\$/!d"`
1557 test x"$hp_res" = x && return 1
1558 return 0
1561 # svn_propadd prop-name prop-val [path]
1562 svn_propadd()
1564 if $git_mode; then
1565 abort 'propadd is only for SVN, not for Git.'
1567 test $# -lt 2 \
1568 && abort 'Not enough arguments provided;
1569 try `svn help propadd` for more info'
1570 test $# -gt 3 \
1571 && abort 'Too many arguments provided;
1572 try `svn help propadd` for more info'
1574 path=$3
1575 test x"$path" = x && path='.' && set dummy "$@" '.' && shift
1576 has_prop "$1" "$3" || {
1577 test $? -eq 2 && return 1 # svn error
1578 # no property found:
1579 yesno "'$path' has no property named '$1', do you want to add it?" \
1580 && $SVN propset "$@"
1581 return $?
1584 current_prop_val=`$SVN propget "$1" "$3"`
1585 test $? -ne 0 && abort "Failed to get the current value of property '$1'."
1587 $SVN propset "$1" "$current_prop_val
1588 $2" "$3" >/dev/null || abort "Failed to add '$3' in the property '$1'."
1590 current_prop_val=`$SVN propget "$1" "$3" || echo "$current_prop_val
1591 $2"`
1592 echo "property '$1' updated on '$path', new value:
1593 $current_prop_val"
1596 # svn_propsed prop-name sed-script [path]
1597 svn_propsed()
1599 if $git_mode; then
1600 abort 'propsed is only for SVN, not for Git.'
1602 test $# -lt 2 \
1603 && abort 'Not enough arguments provided;
1604 try `svn help propsed` for more info'
1605 test $# -gt 3 \
1606 && abort 'Too many arguments provided;
1607 try `svn help propsed` for more info'
1609 path=$3
1610 test x"$path" = x && path='.'
1611 has_prop "$1" "$3" || {
1612 test $? -eq 2 && return 1 # svn error
1613 # no property found:
1614 abort "'$path' has no property named '$1'."
1617 prop_val=`$SVN propget "$1" "$3"`
1618 test $? -ne 0 && abort "Failed to get the current value of property '$1'."
1620 prop_val=`echo "$prop_val" | sed "$2"`
1621 test $? -ne 0 && abort "Failed to run the sed script '$2'."
1623 $SVN propset "$1" "$prop_val" "$3" >/dev/null \
1624 || abort "Failed to update the property '$1' with value '$prop_val'."
1626 new_prop_val=`$SVN propget "$1" "$3" || echo "$prop_val"`
1627 echo "property '$1' updated on '$path', new value:
1628 $new_prop_val"
1631 # svn_revision [args...]
1632 svn_revision()
1634 if $git_mode; then
1635 short=`git rev-list --pretty=format:%h HEAD --max-count=1 | sed '1d;q'`
1636 long=`git rev-list --pretty=format:%H HEAD --max-count=1 | sed '1d;q'`
1637 echo "$short ($long)"
1638 else
1639 svn_revision_info_out=`$SVN info "$@"`
1640 svn_revision_rv=$?
1641 echo "$svn_revision_info_out" | sed '/^Revision: /!d;s///'
1642 return $svn_revision_rv
1646 # svn_ignore [paths]
1647 svn_ignore()
1649 if [ $# -eq 0 ]; then # Simply display ignore-list.
1650 if $git_mode; then
1651 test -f .gitignore && cat .gitignore
1652 else
1653 $SVN propget 'svn:ignore'
1655 elif [ $# -eq 1 ]; then
1656 b=`basename "$1"`
1657 d=`dirname "$1"`
1658 if $git_mode; then
1659 echo "$b" >>"$d/.gitignore"
1660 git add "$d/.gitignore"
1661 notice 'files ignored in this directory:'
1662 cat "$d/.gitignore"
1663 else
1664 svn_propadd 'svn:ignore' "$b" "$d"
1666 else # Add arguments in svn:ignore.
1667 # This part is a bit tricky:
1668 # For each argument, we find all the other arguments with the same dirname
1669 # $dname and we svn:ignore them all in $dname.
1670 while [ $# -ne 0 ]; do
1671 arg=$1
1672 dname=`dirname "$1"`
1673 files=`basename "$1"`
1674 shift
1675 j=0; argc=$#
1676 while [ $j -lt $argc ] && [ $# -ne 0 ]; do
1677 this_arg=$1
1678 shift
1679 this_dname=`dirname "$this_arg"`
1680 this_file=`basename "$this_arg"`
1681 if [ x"$dname" = x"$this_dname" ]; then
1682 files="$files
1683 $this_file"
1684 else
1685 set dummy "$@" "$this_arg"
1686 shift
1688 j=$((j + 1))
1689 done
1690 if $git_mode; then
1691 echo "$files" >>"$dname"/.gitignore
1692 git add "$dname"/.gitignore
1693 notice "files ignored in $dname:"
1694 cat "$dname"/.gitignore
1695 else
1696 svn_propadd 'svn:ignore' "$files" "$dname"
1698 done
1702 # svn_help
1703 # FIXME: Use here-docs.
1704 svn_help()
1706 if [ $# -eq 0 ]; then
1707 svn_version
1708 $SVN help
1709 rv=$?
1710 echo '
1711 Additionnal commands provided by svn-wrapper:
1712 diffstat (ds)
1713 diffw (dw)
1714 ignore
1715 mail
1716 propadd (padd, pa) -- SVN only
1717 proposal
1718 propsed (psed) -- SVN only
1719 revision (rev)
1720 touch
1721 selfupdate (selfup)
1722 version'
1723 return $rv
1724 else
1725 case $1 in
1726 commit | ci)
1727 $SVN help commit | sed '/^Valid options:/i\
1728 Extra options provided by svn-wrapper:\
1729 \ --dry-run : do not commit, simply generate a patch with what\
1730 \ would have been comitted.\
1731 \ --use-log-file FILE : extract the ChangeLog entry from FILE. This\
1732 \ entry must be formated in a similar fashion to\
1733 \ what svn-wrapper usually asks you to fill in.\
1734 \ The FILE needs to be writable and will be\
1735 \ removed by svn-wrapper upon success.\
1739 diffstat | ds)
1740 require_diffstat
1741 echo 'diffstat (ds): Display the histogram from svn diff-output.'
1742 $SVN help diff | sed '1d;
1743 s/differences*/histogram/;
1744 2,35 s/diff/diffstat/g'
1746 diffw | dw)
1747 echo "diffw (dw): Display the differences without taking whitespaces\
1748 into account."
1749 $SVN help diff | sed '1d;
1750 2,35 s/diff\([^a-z]\)/diffw\1/g;
1751 /--diff-cmd/,/--no-diff-deleted/d'
1753 ignore)
1754 what='svn:ignore property'
1755 $git_mode && what='.gitignore file'
1756 echo 'ignore: Add some files in the '"$what"'.
1757 usage: 1. ignore [PATH]
1758 2. ignore FILE [FILES...]
1760 1. Display the value of '"$what"' on [PATH].
1761 2. Add some files in the '"$what"' of the directory containing them.
1763 When adding ignores, each pattern is ignored in its own directory, e.g.:
1764 '"$bme"' ignore dir/file "d2/*.o"
1765 Will put `file'\'' in the '"$what"' of `dir'\'' and `*.o'\'' in the
1766 '"$what"' of `d2'\''
1768 Valid options:
1769 None.'
1771 mail)
1772 echo 'mail: Resend the mail of a given commit.
1773 usage: mail REV [emails]
1775 REV must have an email file associated in +committed/REV.
1776 REV can also be PREV or HEAD.
1778 By default the mail is sent to same email addresses as during the original
1779 commit unless more arguments are given.'
1781 propadd | padd | pa)
1782 echo 'propadd (padd, pa): Add something in the value of a property.
1783 usage: propadd PROPNAME PROPVAL PATH
1784 This command only works in SVN mode.
1786 PROPVAL will be appended at the end of the property PROPNAME.
1788 Valid options:
1789 None.'
1791 proposal)
1792 echo 'proposal: Alias for: commit --dry-run.
1793 See: svn help commit.'
1795 propsed | psed)
1796 echo 'propsed (psed): Edit a property with sed.
1797 usage: propsed PROPNAME SED-ARGS PATH
1798 This command only works in SVN mode.
1800 eg: svn propsed svn:externals "s/http/https/" .
1802 Valid options:
1803 None.'
1805 revision | rev)
1806 echo 'revision (rev): Display the revision number of a local or remote item.'
1807 $SVN help info | sed '1d;
1808 s/information/revision/g;
1809 s/revision about/the revision of/g;
1810 2,35 s/info/revision/g;
1811 /-xml/d'
1813 touch)
1814 echo 'touch: Touch a file and svn add it.
1815 usage: touch FILE [FILES]...
1817 Valid options:
1818 None.'
1820 selfupdate | selfup | self-update | self-up)
1821 echo 'selfupdate (selfup): Attempt to update svn-wrapper.sh
1822 usage: selfupdate
1824 Valid options:
1825 None.'
1827 version)
1828 echo 'version: Display the version info of svn and svn-wrapper.
1829 usage: version
1831 Valid options:
1832 None.'
1834 *) $SVN help "$@";;
1835 esac
1839 # svn_status [args...]
1840 svn_status()
1842 if $git_mode; then
1843 git status "$@"
1844 return $?
1846 svn_status_out=`$SVN status "$@"`
1847 svn_status_rv=$?
1848 test x"$svn_status_out" = x && return $svn_status_rv
1849 echo "$svn_status_out" | sed "$sed_svn_st_color"
1850 return $svn_status_rv
1853 # svn_update [args...]
1854 svn_update()
1856 svn_update_out=`$SVN update "$@"`
1857 svn_update_rv=$?
1858 echo "$svn_update_out" | sed "$sed_svn_up_colors"
1859 return $svn_update_rv
1862 # ------------------- #
1863 # `main' starts here. #
1864 # ------------------- #
1866 # Define colors if stdout is a tty.
1867 if test -t 1; then
1868 set_colors
1869 else # stdout isn't a tty => don't print colors.
1870 set_nocolors
1873 # Consider this as a sed function :P.
1874 sed_svn_st_color="
1875 t dummy_sed_1
1876 : dummy_sed_1
1877 s@^?\\(......\\)+@+\\1+@
1878 s@^?\\(......\\)\\(.*/\\)+@+\\1\\2+@
1879 s@^?\\(......\\),@,\\1,@
1880 s@^?\\(......\\)\\(.*/\\),@,\\1\\2,@
1881 s/^\\(.\\)C/\\1${lred}C${std}/
1882 t dummy_sed_2
1883 : dummy_sed_2
1884 s/^?/${lred}?${std}/; t
1885 s/^M/${lgreen}M${std}/; t
1886 s/^A/${lgreen}A${std}/; t
1887 s/^X/${lblue}X${std}/; t
1888 s/^+/${lyellow}+${std}/; t
1889 s/^D/${lyellow}D${std}/; t
1890 s/^,/${lred},${std}/; t
1891 s/^C/${lred}C${std}/; t
1892 s/^I/${purple}I${std}/; t
1893 s/^R/${lblue}R${std}/; t
1894 s/^!/${lred}!${std}/; t
1895 s/^~/${lwhite}~${std}/; t"
1897 sed_svn_up_colors="
1898 t dummy_sed_1
1899 : dummy_sed_1
1901 /^Updated/ t
1902 /^Fetching/ t
1903 /^External/ t
1904 s/^\\(.\\)C/\\1${lred}C${std}/
1905 s/^\\(.\\)U/\\1${lgreen}U${std}/
1906 s/^\\(.\\)D/\\1${lred}D${std}/
1907 t dummy_sed_2
1908 : dummy_sed_2
1909 s/^A/${lgreen}A${std}/; t
1910 s/^U/${lgreen}U${std}/; t
1911 s/^D/${lyellow}D${std}/; t
1912 s/^G/${purple}G${std}/; t
1913 s/^C/${lred}C${std}/; t"
1915 # For dev's:
1916 test "x$1" = x--debug && shift && set -x
1918 test "x$1" = x--git && shift && git_mode=: && SVN=git
1920 test "x$1" = x--no-changelog && shift && use_changelog=false
1922 case $1 in
1923 # ------------------------------- #
1924 # Hooks for standard SVN commands #
1925 # ------------------------------- #
1926 commit | ci)
1927 shift
1928 svn_commit "$@"
1930 help | \? | h)
1931 shift
1932 svn_help "$@"
1934 status | stat | st)
1935 shift
1936 svn_status "$@"
1938 update | up)
1939 shift
1940 svn_update "$@"
1942 # -------------------- #
1943 # Custom SVN commands #
1944 # -------------------- #
1945 diffstat | ds)
1946 shift
1947 if [ -d .git ]; then
1948 git diff --stat -C
1949 else
1950 require_diffstat && $SVN diff --no-diff-deleted "$@" | diffstat
1953 diff | di)
1954 shift
1955 DIFF=$COLORDIFF
1956 svn_diff "$@"
1958 diffw | dw)
1959 shift
1960 DIFF=$COLORDIFF
1961 svn_diffw "$@"
1963 ignore)
1964 shift
1965 svn_ignore "$@"
1967 log)
1968 if $git_mode; then # let Git handle log
1969 exec $SVN "$@"
1970 else # pipe svn log through PAGER by default
1971 exec $SVN "$@" | $PAGER
1974 mail)
1975 shift
1976 svn_mail "$@"
1978 propadd | padd | pa)
1979 shift
1980 svn_propadd "$@"
1982 proposal)
1983 shift
1984 svn_commit --dry-run "$@"
1986 propsed | psed)
1987 shift
1988 svn_propsed "$@"
1990 revision | rev)
1991 shift
1992 svn_revision "$@"
1994 touch)
1995 shift
1996 touch "$@" && $SVN add "$@"
1998 selfupdate | selfup | self-update | self-up)
1999 shift
2000 selfupdate "$@"
2002 version | -version | --version)
2003 shift
2004 set dummy '--version' "$@"
2005 shift
2006 svn_version
2007 exec $SVN "$@"
2009 *) exec $SVN "$@"
2011 esac