Set COLORDIFF to DIFF when stdout is not attached to a terminal.
[svn-wrapper.git] / svn-wrapper.sh
blob69f11f49a6b67fb7b6b5d0594810bdb8f9f2dd2a
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 printf "$@ [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 printf "$@ [(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 case $2 in #(
249 # `value' has at least one non-space character.
250 *[-a-zA-Z0-9_!@*{}|,./:]*)
251 return;;
252 esac
253 ene_val=`echo "$2" | tr -d ' \t\n'`
254 test -z "$ene_val" && abort "$1: empty value"
257 # find_prog prog-name
258 # return true if prog-name is in the PATH
259 # echo the full path to prog-name on stdout.
260 # Based on a code from texi2dvi
261 find_prog()
263 save_IFS=$IFS
264 IFS=$PATH_SEPARATOR
265 for dir in $PATH; do
266 IFS=$save_IFS
267 test -z "$dir" && continue
268 # The basic test for an executable is `test -f $f && test -x $f'.
269 # (`test -x' is not enough, because it can also be true for directories.)
270 # We have to try this both for $1 and $1.exe.
272 # Note: On Cygwin and DJGPP, `test -x' also looks for .exe. On Cygwin,
273 # also `test -f' has this enhancement, bot not on DJGPP. (Both are
274 # design decisions, so there is little chance to make them consistent.)
275 # Thusly, it seems to be difficult to make use of these enhancements.
277 if test -f "$dir/$1" && test -x "$dir/$1"; then
278 echo "$dir/$1"
279 return 0
280 elif test -f "$dir/$1.exe" && test -x "$dir/$1.exe"; then
281 echo "$dir/$1.exe"
282 return 0
284 done
285 return 1
288 # find_progs prog [progs...]
289 # Look in PATH for one of the programs given in argument.
290 # If none of the progs can be found, the string "exit 2" is "returned".
291 find_progs()
293 # This code comes mostly from Autoconf.
294 for fp_prog in "$@"; do
295 fp_res=`find_prog $fp_prog`
296 if [ $? -eq 0 ]; then
297 echo "$fp_res"
298 return 0
300 done
301 echo "exit 2"
302 return 1
305 test x"$EDITOR" = xmissing && EDITOR=`find_progs vim vi emacs nano`
306 test x"$PAGER" = xmissing && PAGER=`find_progs less more`
307 test x"$AWK" = xmissing && AWK=`find_progs gawk mawk nawk awk`
308 test x"$DIFF" = xmissing && DIFF=`find_progs diff`
309 if test x"$COLORDIFF" = xmissing; then
310 if test -t 1; then
311 COLORDIFF=`find_progs colordiff diff`
312 else
313 COLORDIFF=$DIFF
317 # -R will tell less to interpret some terminal codes, which will turn on
318 # colors.
319 case $PAGER in #(
320 less) PAGER='less -R';;
321 esac
323 # require_diffstat
324 # return true if diffstat is in the PATH
325 require_diffstat()
327 if [ x"$require_diffstat_cache" != x ]; then
328 return $require_diffstat_cache
330 if (echo | diffstat) >/dev/null 2>/dev/null; then :; else
331 warn 'diffstat is not installed on your system or not in your PATH.'
332 test -f /etc/debian_version \
333 && notice 'you might want to `apt-get install diffstat`.'
334 require_diffstat_cache=1
335 return 1
337 require_diffstat_cache=0
338 return 0
341 # require_mail
342 # return 0 -> found a mailer
343 # return !0 -> no mailer found
344 # The full path to the program found is echo'ed on stdout.
345 require_mail()
347 save_PATH=$PATH
348 PATH="${PATH}${PATH_SEPARATOR}/sbin${PATH_SEPARATOR}/usr/sbin${PATH_SEPARATOR}/usr/libexec"
349 export PATH
350 find_progs sendEmail sendmail mail
351 rv=$?
352 PATH=$save_PATH
353 export PATH
354 return $rv
357 # my_sendmail mail-file mail-subject mail-to [extra-headers]
358 # mail-to is a comma-separated list of email addresses.
359 # extra-headers is an optionnal argument and will be prepended at the
360 # beginning of the mail headers if the tool used to send mails supports it.
361 # The mail-file may also contain headers. They must be separated from the body
362 # of the mail by a blank line.
363 my_sendmail()
365 test -f "$1" || abort "my_sendmail: Cannot find the mail file: $1"
366 test -z "$2" && warn 'my_sendmail: Empty subject.'
367 test -z "$3" && abort 'my_sendmail: No recipient specified.'
369 content_type='Content-type: text/plain'
370 extra_headers="X-Mailer: svn-wrapper v$version (g$revision)
371 Mime-Version: 1.0
372 Content-Transfer-Encoding: 7bit"
373 if test x"$4" != x; then
374 extra_headers="$4
375 $extra_headers"
376 # Remove empty lines.
377 extra_headers=`echo "$extra_headers" | sed '/^[ ]*$/d;s/^[ ]*//'`
380 # If we have a signature, add it.
381 if test -f ~/.signature; then
382 # But don't add it if it is already in the mail.
383 if grep -Fe "`cat ~/.signature`" "$1" >/dev/null; then :; else
384 echo '-- ' >>"$1" && cat ~/.signature >>"$1"
387 # VCS-compat: handle user option 'sign'.
388 if (grep '^sign: false' ~/.vcs) >/dev/null 2>/dev/null; then :; else
389 ($GPG -h) >/dev/null 2>/dev/null
390 gpg_rv=$?
391 if [ -e ~/.gnupg ] || [ -e ~/.gpg ] || [ -e ~/.pgp ] && [ $gpg_rv -lt 42 ]
392 then
393 if grep 'BEGIN PGP SIGNATURE' "$1" >/dev/null; then
394 notice 'message is already GPG-signed'
395 elif yesno "Sign the mail using $GPG ?"; then
396 # Strip the headers
397 sed '1,/^$/d' "$1" >"$1.msg"
398 sed '1,/^$/!d' "$1" >"$1.hdr"
399 # Sign the message, keep only the PGP signature.
400 $GPG --clearsign <"$1.msg" >"$1.tmp" || {
401 rm -f "$1.msg" "$1.hdr" "$1.tmp"
402 abort "\`$GPG' failed (r=$?)"
404 sed '/^--*BEGIN PGP SIGNATURE--*$/,/^--*END PGP SIGNATURE--*$/!d' \
405 "$1.tmp" >"$1.sig"
407 boundary="svn-wrapper-2-$RANDOM"
408 boundary="$boundary$RANDOM"
409 # Prepend some stuff before the PGP signature.
410 echo "
411 --$boundary
412 content-type: application/pgp-signature; x-mac-type=70674453;
413 name=PGP.sig
414 content-description: This is a digitally signed message part
415 content-disposition: inline; filename=PGP.sig
416 content-transfer-encoding: 7bit" | cat - "$1.sig" >"$1.tmp"
417 mv -f "$1.tmp" "$1.sig"
419 # Append some stuff after the PGP signature.
420 echo "
421 --$boundary--" >>"$1.sig"
422 # Re-paste the headers before the signed body and prepend some stuff.
423 echo "This is an OpenPGP/MIME signed message (RFC 2440 and 3156)
424 --$boundary
425 Content-Transfer-Encoding: 8bit
426 Content-Type: text/plain; charset=iso-8859-1; format=flowed
428 | cat "$1.hdr" - "$1.msg" "$1.sig" >"$1.tmp" \
429 && mv -f "$1.tmp" "$1"
430 content_type="Content-Type: multipart/signed;\
431 protocol=\"application/pgp-signature\"; micalg=pgp-sha1;\
432 boundary=\"$boundary\""
433 # Cleanup.
434 rm -f "$1.tmp" "$1.sig" "$1.msg" "$1.hdr"
435 extra_headers="$extra_headers
436 X-Pgp-Agent: `$GPG --version | sed q`"
440 extra_headers="$extra_headers
441 $content_type"
443 mailer=`require_mail`
444 if [ $? -ne 0 ]; then
445 warn 'my_sendmail: No suitable mailer found.'
446 return 1
448 case $mailer in
449 */sendmail)
450 to=`echo "$3" | sed 's/,//g'`
451 echo "$extra_headers" | cat - "$1" | $mailer $to;;
452 */mail)
453 cat "$1" | $mailer -s "$2" "$3";;
454 */sendEmail)
455 if [ x"$SMTP" = x ]; then
456 warn 'my_sendmail: (sendEmail) please tell me the SMTP server to use'
457 printf 'STMP server: '
458 read SMTP || abort 'could not read the SMTP server'
459 notice "hint: you can export SMTP=$SMTP if you don't want to be asked"
461 sendEmail -f "$FULLNAME <$EMAIL>" -t "$3" -u "$2" \
462 -s "$SMTP" -o message-file="$1";;
463 *) # wtf
464 abort 'my_sendmail: Internal error.';;
465 esac
468 # selfupdate
469 selfupdate()
471 my_url='http://www.lrde.epita.fr/~sigoure/svn-wrapper'
472 # You can use https if you feel paranoiac.
474 echo ">>> Fetching svn-wrapper.sh from $my_url/svn-wrapper.sh"
476 # --------------------- #
477 # Fetch the new version #
478 # --------------------- #
480 tmp_me=`get_unique_file_name "$TMPDIR/svn-wrapper.sh"`
481 if (wget --help) >/dev/null 2>/dev/null; then
482 wget --no-check-certificate "$my_url/svn-wrapper.sh" -O "$tmp_me"
483 my_wget='wget'
484 else
485 curl --help >/dev/null 2>/dev/null
486 if [ $? -gt 42 ]; then
487 abort 'Cannot find wget or curl.
488 How can I download any update without them?'
490 my_wget='curl'
491 curl --insecure "$my_url/svn-wrapper.sh" >"$tmp_me"
494 test -r $tmp_me \
495 || abort "Cannot find the copy of myself I downloaded in $tmp_me"
497 # ---------------------------------------- #
498 # Compare versions and update if necessary #
499 # ---------------------------------------- #
501 my_ver=$revision
502 tmp_ver=`sed '/^# $Id[:].*$/!d;
503 s/.*$Id[:] *\([a-f0-9]\{6\}\).*/\1/' "$tmp_me"`
504 test -z "$tmp_ver" && abort "Cannot find the revision of $tmp_me"
505 if [ x"$my_ver" != x"$tmp_ver" ]; then # There IS an update...
506 echo "An update is available, r$tmp_ver (your version is r$my_ver)"
508 # Wanna see the diff?
509 if yesno 'Do you want to see the diff?'
510 then
511 (require_diffstat && diff -uw "$me" "$tmp_me" | diffstat;
512 echo
513 $COLORDIFF -uw "$me" "$tmp_me") | $PAGER
516 # Let's go :)
517 if yesno "Overwrite $me (r$my_ver) with $tmp_me (r$tmp_ver)?"; then
518 chmod a+x "$tmp_me"
519 cp -p "$me" "$me.r$my_ver"
520 mv "$tmp_me" "$me" && exit 0
522 rm -f "$tmp_me"
523 return 1
524 else
525 echo "You're already up to date [r$my_ver] :)"
527 rm -f "$tmp_me"
530 # get_svn_diff_and_diffstat [files to diff]
531 # Helper for svn_commit
532 get_svn_diff_and_diffstat()
534 if $git_mode; then
535 svn_diff=`git diff --ignore-all-space --no-color -B -C --cached`
536 svn_diff_stat=`git diff --stat --ignore-all-space --no-color -B -C --cached`
537 else
538 # Ignore white spaces. Can't svn diff -x -w: svn 1.4 only.
539 svn_diff=`svn_diffw "$@"`
540 test -z "$svn_diff" && svn_diff=`$SVN diff "$@"`
541 if require_diffstat; then
542 svn_diff_stat=`echo "$svn_diff" | diffstat`
543 else
544 svn_diff_stat='diffstat not available'
549 # Helper. Sets the variables repos_url, git_branch, git_head, repos_root and
550 # extra_repos_info properly.
551 git_get_repos_info_()
553 # FIXME: 1st commit: "fatal: bad default revision 'HEAD'" on stderr
554 repos_url=`git config --get svn-remote.svn.url`
555 test -z "$repos_url" && repos_url='(git:unknown)'
556 git_branch=`git branch | awk '/^\*/ { print substr($0, 3) }'`
557 if [ x"$git_branch" = x'(no branch)' ]; then
558 yesno 'You are on a detached HEAD, do you really want to continue?' \
559 || return 1
561 git_head=`git rev-list --pretty=format:%h HEAD --max-count=1 | sed '1d;q'`
562 extra_repos_info="Git branch: $git_branch (HEAD: $git_head)"
563 repos_root=$repos_url
566 # Helper. Find the `mailto' property, be it an SVN property or a git-config
567 # option. Relies on the value of $change_log_dir and sets the values of
568 # $mailto (the value of the `mailto' property) and $to (a string to append in
569 # templates that contains the `To:' line, or an empty string if no mail must
570 # be sent).
571 get_mailto_property()
573 test -d "$change_log_dir" || abort 'Internal error in get_mailto_property:
574 $change_log_dir not pointing to a directory'
575 if $git_mode; then
576 mailto=`git config svnw.mailto`
577 if [ x"$mailto" = x ] \
578 && [ -f "$change_log_dir/.git/svn/git-svn/unhandled.log" ]
579 then
580 mailto=`grep mailto "$change_log_dir/.git/svn/git-svn/unhandled.log"`
581 sed_tmp='$!d;s/^.*+dir_prop: . mailto //;s/%40/@/g;s/%2C/,/g;s/%20/ /g;'
582 mailto=`echo "$mailto" | sed "$sed_tmp"`
584 if [ x"$mailto" = x ]; then
585 warn 'No mailto property set for this repository.
586 If this is a git-svn repository, do this in a SVN working copy:
587 svn propset mailto maintainer1@foo.com,maint2@bar.com .
588 If this is a real git repository, do this:
589 git config svnw.mailto maintainer1@foo.com,maint2@bar.com'
591 else
592 mailto=`$SVN propget mailto "$change_log_dir"`
595 if [ x"$mailto" = x ]; then
596 test x$new_user = xyes \
597 && warn "no svn property mailto found in $change_log_dir
598 You might want to set default email adresses using:
599 svn propset mailto 'somebody@mail.com, foobar@example.com'\
600 $change_log_dir" >&2
601 # Try to be VCS-compatible and find a list of mails in a *.rb.
602 if [ -d "$change_log_dir/vcs" ]; then
603 mailto=`grep '.@.*\..*' "$change_log_dir"/vcs/*.rb \
604 | tr '\n' ' ' \
605 | sed 's/^.*[["]\([^["]*@[^]"]*\)[]"].*$/\1/' | xargs`
606 test x"$mailto" != x && test x$new_user = xyes \
607 && notice "VCS-compat: found mailto: $mailto
608 in " "$change_log_dir"/vcs/*.rb
609 fi # end VCS compat
610 fi # end guess mailto
612 # Ensure that emails are comma-separated.
613 mailto=`echo "$mailto" | sed 's/[ ;]/,/g' | tr -s ',' | sed 's/,/, /g'`
616 if [ x"$mailto" != xdont_send_mails ] && [ x"$mailto" != xnothx ]; then
617 to="
618 To: $mailto"
624 # ------------------------------- #
625 # Hooks for standard SVN commands #
626 # ------------------------------- #
628 # svn_commit [args...]
629 # Here is how the commit process goes:
631 # First we look in the arguments passed to commit:
632 # If there are some files or paths, the user wants to commit these only. In
633 # this case, we must search for ChangeLogs from these paths. We might find
634 # more than one ChangeLog, in this case the user will be prompted to pick up
635 # one.
636 # Otherwise (no path passed in the command line) the user just wants to
637 # commit the current working directory.
638 # In any case, we schedule "ChangeLog" for commit.
640 # Alright now that we know which ChangeLog to use, we look in the ChangeLog's
641 # directory if there is a ",svn-log" file which would mean that a previous
642 # commit didn't finish successfully. If there is such a file, the user is
643 # prompted to know whether they want to resume that commit or simply start a
644 # new one.
645 # When the user wants to resume a commit, the ",svn-log" file is loaded and we
646 # retrieve the value of "$@" that was saved in the file.
647 # Otherwise we build a template ChangeLog entry.
648 # Then we open the template ChangeLog entry with $EDITOR so that the user
649 # fills it properly.
650 # Finally, we commit.
651 # Once the commit is sent, we ask the server to know which revision was
652 # commited and we also retrieve the diff. We then send a mail with these.
653 svn_commit()
655 here=`pwd -P`
656 dry_run=false
657 git_commit_all=false
658 use_log_message_from_file=false
659 log_message_to_use=
661 # Check if the user passed some paths to commit explicitly
662 # because in this case we must add the ChangeLog to the commit and search
663 # the ChangeLog from the dirname of that file.
664 i=0; search_from=; add_changelog=false; extra_files=
665 while [ $i -lt $# ]; do
666 arg=$1
667 case $arg in
668 --dry-run)
669 dry_run=:
670 shift
671 i=$((i + 1))
672 continue
674 -a|--all)
675 git_commit_all=:
677 --use-log-file)
678 shift
679 test -z "$1" && abort "$arg needs an argument"
680 test -r "$1" || abort "'$1' does not seem to be readable"
681 test -w "$1" || abort "'$1' does not seem to be writable"
682 test -d "$1" && abort "'$1' seems to be a directory"
683 use_log_message_from_file=:
684 log_message_to_use=$1
685 shift
686 continue
688 esac
689 # If the argument is a valid path: add the ChangeLog in the list of
690 # files to commit
691 if test -e "$arg"; then
692 add_changelog=:
693 if test -d "$arg"; then
694 search_from_add=$arg
695 else
696 search_from_add=`dirname "$arg"`
698 search_from="$search_from:$search_from_add"
700 shift
701 set dummy "$@" "$arg"
702 shift
703 i=$((i + 1))
704 done
705 if $add_changelog; then :; else
706 # There is no path/file in the command line: the user wants to commit the
707 # current directory. Make it explicit now:
708 extra_files=$here
710 search_from=`echo "$search_from" | sed 's/^://; s/^$/./'`
712 # ----------------- #
713 # Find ChangeLog(s) #
714 # ----------------- #
716 nb_chlogs=0; change_log_dirs=
717 save_IFS=$IFS; IFS=':'
718 for dir in $search_from; do
719 IFS=$save_IFS
720 $use_changelog || break
721 test -z "$dir" && dir='.'
722 # First: come back to the original place
723 cd "$here" || abort "Cannot cd to $here"
724 cd "$dir" || continue # Then: Enter $dir (which can be a relative path)
725 found=0
726 while [ $found -eq 0 ]; do
727 this_chlog_dir=`pwd -P`
728 if [ -f ./ChangeLog ]; then
729 found=1
730 nb_chlogs=$((nb_chlogs + 1))
731 change_log_dirs="$change_log_dirs:$this_chlog_dir"
732 else
733 cd ..
735 # Stop searching when in / ... hmz :P
736 test x"$this_chlog_dir" = x/ && break
737 done # end while: did we find a ChangeLog
738 done # end for: find ChangeLogs in $search_from
739 if [ $nb_chlogs -gt 0 ]; then
740 change_log_dirs=`echo "$change_log_dirs" | sed 's/^://' | tr ':' '\n' \
741 | sort -u`
742 nb_chlogs=`echo "$change_log_dirs" | wc -l`
745 # Did we find a ChangeLog? More than one?
746 if [ $nb_chlogs -eq 0 ] && $use_changelog; then
747 if yesno 'svn-wrapper: Error: Cannot find a ChangeLog file!
748 You might want to create an empty one (eg: `touch ChangeLog` where appropriate)
749 Do you want to proceed without using a ChangeLog?'; then
750 cd "$here"
751 $SVN commit "$@"
752 return $?
753 else
754 return 1
756 elif [ $nb_chlogs -gt 1 ]; then
757 notice "$nb_chlogs ChangeLogs were found, pick up one:"
759 IFS=':'; i=0
760 for a_chlog_dir in $change_log_dirs; do
761 i=$((i + 1))
762 echo "$i. $a_chlog_dir/ChangeLog"
763 done
764 printf "Which ChangeLog do you want to use? [1-$i] "
765 read chlog_no || abort 'Cannot read answer on stdin.'
767 case $chlog_no in
768 *[^0-9]*) abort "Invalid ChangeLog number: $chlog_no"
769 esac
770 test "$chlog_no" -le $i || abort "Invalid ChangeLog number: $chlog_no
771 max value was: $i"
772 test "$chlog_no" -ge 1 || abort "Invalid ChangeLog number: $chlog_no
773 min value was: 1"
774 change_log_dir=`echo "$change_log_dirs" | tr ':' '\n' | sed "${chlog_no}!d"`
775 else # Only one ChangeLog found
776 if $use_changelog; then
777 change_log_dir=$change_log_dirs
778 notice "using $change_log_dir/ChangeLog"
782 if $use_changelog; then
783 test -f "$change_log_dir/ChangeLog" \
784 || abort "No such file or directory: $change_log_dir/ChangeLog"
785 # Now we can safely schedule the ChangeLog for the commit.
786 extra_files="$extra_files:$change_log_dir/ChangeLog"
787 else
788 change_log_dir='.' # Hack. FIXME: Does this work in all cases?
791 if [ -d "$change_log_dir/.git" ] || $git_mode; then
792 SVN=git
793 git_mode=:
794 git_get_repos_info_
795 else
796 svn_st_tmp=`$SVN status "$change_log_dir"`
798 # Warn for files that are not added in the repos.
799 conflicts=`echo "$svn_st_tmp" | sed '/^ *$/d;
800 /^[^?]/d;
801 /^?.......*\/[,+]/d;
802 /^?......[,+]/d'`
803 if test x"$conflicts" != x; then
804 warn "make sure you don't want to \`svn add'
805 any of the following files before committing:"
806 echo "$conflicts" | sed "$sed_svn_st_color"
807 printf 'Type [ENTER] to continue :)' && read chiche_is_gay
810 # If there are changes in an svn:externals, advise the user to commit that
811 # first.
812 changed_externals=`echo "$svn_st_tmp" | $AWK \
813 'function printext()
815 if (ext && !printed)
817 print this_ext "\n";
818 printed = 1;
821 BEGIN { this_ext = ""; ext = 0; ext_modified = 0; }
822 /^Performing status on external/ {
823 ext = 1;
824 sub(/.* at ./, ""); sub(/.$/, ""); this_ext = $0;
825 printed = 0;
827 /^[ADMR]/ { ext_modified = ext; printext(); }
828 /^.[M]/ { ext_modified = ext; printext(); }
829 END { exit ext_modified; }'`
830 if [ $? -ne 0 ]; then
831 warn "the following external items have local modifications:
832 $changed_externals"
833 yesno "You are advised to commit them separately first. Continue anyway?" \
834 || return 1
837 # Detect unresolved conflicts / missing files.
838 conflicts=`echo "$svn_st_tmp" | sed '/^[C!]/!d'`
839 test x"$conflicts" != x && abort "there are unresolved conflicts (\`C')
840 and/or missing files (\`!'):
841 $conflicts"
843 svn_info_tmp=`$SVN info "$change_log_dir"`
844 test $? -ne 0 && abort "Failed to get svn info on $change_log_dir"
845 repos_root=`echo "$svn_info_tmp" | sed '/^Repository Root: /!d;s///'`
846 repos_url=`echo "$svn_info_tmp" | sed '/^URL: /!d;s///'`
847 # It looks like svn <1.3 didn't display a "Repository Root" entry.
848 test -z "$repos_root" && repos_root=$repos_url
851 cd "$here"
853 YYYY=`date '+%Y'`
854 MM=`date '+%m'`
855 DD=`date '+%d'`
857 # VCS-compat: handle user option 'new_user'
858 new_user='yes'
859 grep '^new_user: false' ~/.vcs >/dev/null 2>/dev/null && new_user='no'
861 edit_changelog=:
862 tmp_log="$change_log_dir/,svn-log"
863 $use_log_message_from_file \
864 && tmp_log=$log_message_to_use \
865 && edit_changelog=false
867 if [ -f "$tmp_log" ] \
868 && { $use_log_message_from_file \
869 || yesnewproceed "It looks like the last commit did not\
870 terminate successfully.
871 Would you like to resume it or proceed immediately?"; }; then
872 case $yesnoproceed_res in
873 *proceed) edit_changelog=false;;
874 esac
875 if test x"$yesnoproceed_res" = xupproceed; then
876 svn_update "$@" || abort 'update failed'
878 echo 'Resuming ...'
879 internal_tags=`sed '/^--- Internal stuff, DO NOT change please ---$/,$!d' \
880 "$tmp_log"`
881 saved_args=`echo "$internal_tags" | sed '/^args: */!d;s///'`
882 extra_files=`echo "$internal_tags" | sed '/^extra_files: */!d;s///'`
883 if [ x"$saved_args" != x ]; then
884 if [ x"$*" != x ] && [ x"$saved_args" != x"$*" ]; then
885 warn "overriding arguments:
886 you invoked $me with the following arguments: $@
887 they have been replaced by these: $saved_args"
888 set dummy $saved_args
889 shift
890 else
891 notice "setting the following arguments: $saved_args"
892 set dummy $saved_args
893 shift
895 elif [ x"$*" != x ]; then
896 warn "overriding arguments:
897 you invoked $me with the following arguments: $@
898 they have been dropped"
901 for i; do
902 case $i in
903 -a|--all)
904 git_commit_all=:
905 if [ $git_mode ]; then
906 (cd $change_log_dir && git add -v -u) || abort '`git add -v -u` failed'
909 esac
910 done
912 get_svn_diff_and_diffstat "$@"
914 # Update the file with the new diff/diffstat in case it changed.
915 $AWK 'BEGIN {
916 tlatbwbi_seen = 0;
917 ycewah_seen = 0;
919 /^--This line, and those below, will be ignored--$/ {
920 tlatbwbi_seen = 1;
922 /^ Your ChangeLog entry will appear here\.$/ {
923 if (tlatbwbi_seen) ycewah_seen = 1;
926 if (ycewah_seen != 2) print;
927 if (ycewah_seen == 1) ycewah_seen = 2;
929 END {
930 if (tlatbwbi_seen == 0)
932 print "--This line, and those below, will be ignored--\n\n" \
933 " Your ChangeLog entry will appear here.";
935 }' "$tmp_log" >"$tmp_log.tmp"
936 echo "
938 $svn_diff_stat
940 $svn_diff
942 $internal_tags" >>"$tmp_log.tmp"
943 mv -f "$tmp_log.tmp" "$tmp_log" || abort "failed to write '$tmp_log'"
945 else # Build the template message.
947 # ------------------------------------ #
948 # Gather info for the template message #
949 # ------------------------------------ #
951 if $git_mode; then
952 projname=`git config svnw.project`
953 if [ x"$projname" = x ] \
954 && [ -f "$change_log_dir/.git/svn/git-svn/unhandled.log" ]
955 then
956 projname=`grep project "$change_log_dir/.git/svn/git-svn/unhandled.log"`
957 sed_tmp='$!d;s/^.*+dir_prop: . project //'
958 projname=`echo "$projname" | sed "$sed_tmp"`
960 if [ x"$projname" = x ]; then
961 warn 'No project name set for this repository.
962 If this is a git-svn repository, do this in a SVN working copy:
963 svn propset project myproj .
964 If this is a real git repository, do this:
965 git config svnw.project myproj'
967 else
968 projname=`$SVN propget project "$change_log_dir"`
970 # Try to be VCS-compatible and find a project name in a *.rb.
971 if [ x"$projname" = x ] && [ -d "$change_log_dir/vcs" ]; then
972 projname=`sed '/common_commit/!d;s/.*"\(.*\)<%= rev.*/\1/' \
973 "$change_log_dir"/vcs/*.rb`
974 test x"$projname" != x && test x$new_user = xyes \
975 && notice "VCS-compat: found project name: $projname
976 in " "$change_log_dir"/vcs/*.rb
978 test x"$projname" != x && projname=`echo "$projname" | sed '/[^ ]$/s/$/ /'`
980 get_mailto_property
982 test -z "$FULLNAME" && FULLNAME='Type Your Name Here' \
983 && warn_env FULLNAME
984 test -z "$EMAIL" && EMAIL='your.mail.here@FIXME.com' && warn_env EMAIL
986 if $git_mode; then
987 if $git_commit_all; then
988 (cd $change_log_dir && git add -v -u) || abort '`git add -v -u` failed'
990 my_git_st=`git diff -C --raw --cached`
991 test $? -eq 0 || abort 'git diff failed'
992 # Format: ":<old_mode> <new_mode> <old_sha1> <new_sha1> <status>[
993 # <similarity score>]\t<file-name>"
994 change_log_files=`echo "$my_git_st" | sed '
995 t dummy_sed_1
996 : dummy_sed_1
997 s/^:[0-7 ]* [0-9a-f. ]* M[^ ]* \(.*\)$/ * \1: ./; t
998 s/^:[0-7 ]* [0-9a-f. ]* A[^ ]* \(.*\)$/ * \1: New./; t
999 s/^:[0-7 ]* [0-9a-f. ]* D[^ ]* \(.*\)$/ * \1: Remove./; t
1000 s/^:[0-7 ]* [0-9a-f. ]* R[^ ]* \([^ ]*\) \(.*\)$/ * \2: Rename from \1./;t
1001 s/^:[0-7 ]* [0-9a-f. ]* C[^ ]* \([^ ]*\) \(.*\)$/ * \2: Copy from \1./;t
1002 s/^:[0-7 ]* [0-9a-f. ]* T[^ ]* \(.*\)$/ * \1: ./; t
1003 s/^:[0-7 ]* [0-9a-f. ]* X[^ ]* \(.*\)$/ * \1: ???./; t
1004 s/^:[0-7 ]* [0-9a-f. ]* U[^ ]* \(.*\)$/ * \1: UNMERGED./; t
1006 else
1007 # --ignore-externals appeared after svn 1.1.1
1008 my_svn_st=`$SVN status --ignore-externals "$@" \
1009 || $SVN status "$@" | sed '/^Performing status on external/ {
1013 # Files to put in the ChangeLog entry.
1014 change_log_files=`echo "$my_svn_st" | sed '
1015 t dummy_sed_1
1016 : dummy_sed_1
1017 s/^M......\(.*\)$/ * \1: ./; t
1018 s/^A......\(.*\)$/ * \1: New./; t
1019 s/^D......\(.*\)$/ * \1: Remove./; t
1023 if [ x"$change_log_files" = x ]; then
1024 yesno 'Nothing to commit, continue anyway?' || return 1
1027 change_log_files=`echo "$change_log_files" | sort -u`
1029 get_svn_diff_and_diffstat "$@"
1031 # Get any older svn-log out of the way.
1032 test -f "$tmp_log" && mv "$tmp_log" `get_unique_file_name "$tmp_log"`
1033 # If we can't get an older svn-log out of the way, find a new name...
1034 test -f "$tmp_log" && tmp_log=`get_unique_file_name "$tmp_log"`
1035 if [ x$new_user = no ]; then
1036 commit_instructions='
1037 Instructions:
1038 - Fill the ChangeLog entry.
1039 - If you feel like, write a comment in the "Comment:" section.
1040 This comment will only appear in the email, not in the ChangeLog.
1041 By default only the location of the repository is in the comment.
1042 - Some tags will be replaced. Tags are of the form: <TAG>. Unknown
1043 tags will be left unchanged.
1044 - The tag <REV> may only be used in the Subject.
1045 - Your ChangeLog entry will be used as commit message for svn.'
1046 else
1047 commit_instructions=
1049 r_before_rev=r
1050 $git_mode && r_before_rev=
1051 test -z "$extra_repos_info" || extra_repos_info="
1052 $extra_repos_info"
1053 echo "\
1054 --You must fill this file correctly to continue-- -*- vcs -*-
1055 Title:
1056 Subject: ${projname}$r_before_rev<REV>: <TITLE>
1057 From: $FULLNAME <$EMAIL>$to
1059 Comment:
1060 URL: $repos_url$extra_repos_info
1062 ChangeLog:
1064 <YYYY>-<MM>-<DD> $FULLNAME <$EMAIL>
1066 <TITLE>
1067 $change_log_files
1069 --This line, and those below, will be ignored--
1070 $commit_instructions
1071 --Preview of the message that will be sent--
1073 URL: $repos_url$extra_repos_info
1074 Your comments (if any) will appear here.
1076 ChangeLog:
1077 $YYYY-$MM-$DD $FULLNAME <$EMAIL>
1079 Your ChangeLog entry will appear here.
1082 $svn_diff_stat
1084 $svn_diff" >"$tmp_log"
1086 echo "
1087 --- Internal stuff, DO NOT change please ---
1088 args: $@" >>"$tmp_log"
1089 echo "extra_files: $extra_files
1090 vi: ft=diff:noet:tw=76:" >>"$tmp_log"
1092 fi # end: if svn-log; then resume? else create template
1093 $edit_changelog && $EDITOR "$tmp_log"
1095 # ------------------ #
1096 # Re-"parse" the log #
1097 # ------------------ #
1099 # hmz this section is a bit messy...
1100 # helper string... !@#$%* escaping \\\\\\...
1101 sed_escape='s/\\/\\\\/g;s/@/\\@/g;s/&/\\\&/g'
1102 sed_eval_tags="s/<MM>/$MM/g; s/<DD>/$DD/g; s/<YYYY>/$YYYY/g"
1103 full_log=`sed '/^--*This line, and those below, will be ignored--*$/,$d;
1104 /^--You must fill this/d' "$tmp_log"`
1105 chlog_entry=`echo "$full_log" | sed '/^ChangeLog:$/,$!d; //d'`
1106 ensure_not_empty 'ChangeLog entry' "$chlog_entry"
1107 full_log=`echo "$full_log" | sed '/^ChangeLog:$/,$d'`
1108 mail_comment=`echo "$full_log" | sed '/^Comment:$/,$!d; //d'`
1109 full_log=`echo "$full_log" | sed '/^Comment:$/,$d'`
1110 mail_title=`echo "$full_log" | sed '/^Title: */!d;s///;'`
1111 ensure_not_empty 'commit title' "$mail_title"
1112 # Add a period at the end of the title.
1113 mail_title=`echo "$mail_title" | sed -e '/ *[.!?]$/!s/ *$/./' \
1114 -e "$sed_eval_tags; $sed_escape"`
1115 sed_eval_tags="$sed_eval_tags; s@<TITLE>\\.*@$mail_title@g"
1116 mail_comment=`echo "$mail_comment" | sed "$sed_eval_tags"`
1117 raw_chlog_entry=$chlog_entry # ChangeLog entry without tags expanded
1118 chlog_entry=`echo "$chlog_entry" | sed "$sed_eval_tags; 1{
1119 /^ *$/d
1121 mail_subject=`echo "$full_log" | sed '/^Subject: */!d;s///'`
1122 ensure_not_empty 'mail subject' "$mail_subject"
1123 mail_to=`echo "$full_log" | sed '/^To:/!d'`
1124 send_a_mail=:
1125 if test x"$mail_to" = x; then
1126 send_a_mail=false
1127 else
1128 mail_to=`echo "$mail_to" | sed 's/^To: *//;s///'`
1129 # If there is a <MAILTO> in the 'To:' line, we must expand it.
1130 case $mail_to in #(
1131 *'<MAILTO>'*)
1132 get_mailto_property
1133 # Are we meant to send a mail?
1134 case $to in #(
1135 '') # No, don't send a mail.
1136 mail_to=
1137 send_a_mail=false
1138 ;; #(
1139 *) # Yes, send a mail.
1140 mail_to=`echo "$mail_to" | sed "s#<MAILTO>#$mailto#g"`
1142 esac
1143 esac
1144 $send_a_mail && ensure_not_empty '"To:" field of the mail' "$mail_to"
1147 test -z "$FULLNAME" && warn_env FULLNAME && FULLNAME=$USER
1148 test -z "$EMAIL" && warn_env EMAIL && EMAIL=$USER
1149 myself=`echo "$FULLNAME <$EMAIL>" | sed "$sed_escape"`
1150 mail_from=`echo "$full_log" | sed "/^From: */!d;s///;s@<MYSELF>@$myself@g"`
1151 ensure_not_empty '"From:" field of the mail' "$mail_from"
1153 # ------------------------------------ #
1154 # Sanity checks on the ChangeLog entry #
1155 # ------------------------------------ #
1157 if echo "$chlog_entry" | grep '<REV>' >/dev/null; then
1158 warn 'Using the tag <REV> anywhere else than in the Subject is deprecated.'
1159 yesno 'Continue anyway?' || return 1
1162 if echo "$chlog_entry" | grep ': \.$' >/dev/null; then
1163 warn 'It looks like you did not fill all entries in the ChangeLog:'
1164 echo "$chlog_entry" | grep ': \.$'
1165 yesno 'Continue anyway?' || return 1
1168 if echo "$chlog_entry" | grep '^--* Internal stuff' >/dev/null; then
1169 warn "It looks like you messed up the delimiters and I did not properly
1170 find your ChangeLog entry. Here it is, make sure it is correct:"
1171 echo "$chlog_entry"
1172 yesno 'Continue anyway?' || return 1
1175 if echo "$chlog_entry" | grep -i 'dont[^a-z0-9]' >/dev/null; then
1176 warn "Please avoid typos such as ${lred}dont$std instead of\
1177 ${lgreen}don't$std:"
1178 echo "$chlog_entry" | grep -n -i 'dont[^a-z0-9]' \
1179 | sed "s/[dD][oO][nN][tT]/$lred&$std/g"
1180 yesno 'Continue anyway?' || return 1
1183 if echo "$chlog_entry" | grep -i 'cant[^a-z0-9]' >/dev/null; then
1184 warn "Please avoid typos such as ${lred}cant$std instead of\
1185 ${lgreen}can't$std:"
1186 echo "$chlog_entry" | grep -n -i 'cant[^a-z0-9]' \
1187 | sed "s/[cC][aA][nN][tT]/$lred&$std/g"
1188 yesno 'Continue anyway?' || return 1
1191 if echo "$chlog_entry" | grep '^.\{80,\}' >/dev/null; then
1192 warn 'Please avoid long lines in your ChangeLog entry (80 columns max):'
1193 echo "$chlog_entry" | grep '^.\{80,\}'
1194 yesno 'Continue anyway?' || return 1
1197 if echo "$mail_title" | grep -i '^[a-z][a-z]*ed[^a-z]' >/dev/null; then
1198 warn 'ChangeLog entries should be written in imperative form:'
1199 echo "$mail_title" | grep -i '^[a-z][a-z]*ed[^a-z]' \
1200 | sed "s/^\\([a-zA-Z][a-zA-Z]*ed\\)\\([^a-zA-Z]\\)/$lred\\1$std\\2/"
1201 yesno 'Continue anyway?' || return 1
1204 # Check whether the user passed -m | --message
1206 while [ $i -lt $# ]; do
1207 arg=$1
1208 # This is not really a reliable way of knowing whether -m | --message was
1209 # passed but hum... Let's assume it'll do :s
1210 if [ x"$arg" = 'x-m' ] || [ x"$arg" = 'x--message' ]; then
1211 my_message=$2
1213 shift
1214 set dummy "$@" "$arg"
1215 shift
1216 i=$((i + 1))
1217 done
1218 if [ x"$my_message" = x ]; then
1219 # The title must not be indented in the commit message and must be
1220 # followed by a blank line. This yields much better results with most
1221 # VC-viewer (especially for Git but including for SVN, such as Trac for
1222 # instance). We assume that the title will always be on the 1st line.
1223 sed_git_title="1s@^[ ]*<TITLE>\\.*@$mail_title\\
1224 @g; $sed_eval_tags"
1225 # First, remove empty lines at the beginning, if any.
1226 # Remove also the date information (useless in commit messages)
1227 my_message=`echo "$raw_chlog_entry" \
1228 | sed -e '1,4 {
1229 /^<YYYY>-<[MD][MD]>-<[DM][DM]>/d
1230 /^[1-9][0-9][0-9][0-9]-[0-9][0-9]*-[0-9][0-9]*/d
1231 /^ and .*<.*@.*>$/d
1232 /^ *$/d
1233 }' \
1234 | sed -e "$sed_git_title" \
1235 -e "$sed_eval_tags; 1{
1236 /^ *$/d
1238 else
1239 notice 'you are overriding the commit message.'
1242 # Show suspicious whitespace additions with Git.
1243 $git_mode && git diff --cached --check
1245 if $dry_run; then
1246 proposal_file=',proposal'
1247 test -f "$proposal_file" \
1248 && proposal_file=`get_unique_file_name "$proposal_file"`
1249 sed_tmp='s/<REV>/???/g;s/\([^.]\) *\.$/\1/'
1250 mail_subject=`echo "$mail_subject" | sed "$sed_eval_tags; $sed_tmp"`
1252 if [ x"$mailto" != xdont_send_mails ] && [ x"$mailto" != xnothx ]; then
1253 to="
1254 To: $mailto"
1256 echo "\
1257 From: $mail_from$to
1258 Subject: $mail_subject
1260 $mail_comment
1262 ChangeLog:
1263 $chlog_entry
1266 $svn_diff_stat
1268 $svn_diff" >"$proposal_file"
1269 notice "A proposal of your commit was left in '$proposal_file'"
1270 return 0
1273 # Are you sure?
1274 if $git_mode; then
1275 $git_commit_all \
1276 || notice 'You are using git, unlike SVN, do not forget to git add your
1277 changes'
1280 # Change edit_changelog so that we're asking the confirmation below.
1281 $use_log_message_from_file && edit_changelog=: \
1282 && notice "You are about to commit the following change:
1283 $mail_title"
1285 $edit_changelog \
1286 && {
1287 yesno 'Are you sure you want to commit?' \
1288 || return 1
1291 # Add the ChangeLog entry
1292 old_chlog=`get_unique_file_name "$change_log_dir/ChangeLog.old"`
1293 mv "$change_log_dir/ChangeLog" "$old_chlog" || \
1294 abort 'Could not backup ChangeLog'
1295 trap "echo SIGINT; mv \"$old_chlog\" \"$change_log_dir/ChangeLog\";
1296 exit 130" $SIGINT
1297 echo "$chlog_entry" >"$change_log_dir/ChangeLog"
1298 echo >>"$change_log_dir/ChangeLog"
1299 cat "$old_chlog" >>"$change_log_dir/ChangeLog"
1301 # Add extra files such as cwd or ChangeLog to the commit.
1302 tmp_sed='s/ /\\ /g' # Escape spaces for the shell.
1303 if $git_mode && $use_changelog; then
1304 # Schedule the ChangeLog for the next commit
1305 (cd "$change_log_dir" && git add ChangeLog) \
1306 || abort 'failed to git add the ChangeLog'
1307 extra_files=
1308 else
1309 extra_files=`echo "$extra_files" | sed "$tmp_sed" | tr ':' '\n'`
1312 # Always sign the commits with Git.
1313 $git_mode && set dummy -s "$@" && shift
1315 # Update the Git index if necessary (just in case the user changed his
1316 # working copy in the mean time)
1317 if $git_mode && $git_commit_all; then
1318 (cd $change_log_dir && git add -v -u) || abort '`git add -v -u` failed'
1321 # --Commit-- finally! :D
1322 $SVN commit -m "$my_message" "$@" $extra_files || {
1323 svn_commit_rv=$?
1324 mv "$old_chlog" "$change_log_dir/ChangeLog"
1325 abort "Commit failed, $SVN returned $svn_commit_rv"
1328 printf 'Getting the revision number... '
1329 if $git_mode; then
1330 REV=`git rev-list --pretty=format:%h HEAD --max-count=1 | sed '1d;q'`
1331 else
1332 svn_info_tmp=`$SVN info "$change_log_dir/ChangeLog"`
1333 REV=`echo "$svn_info_tmp" | sed '/^Revision: /!d;s///'`
1334 test -z "$REV" && REV=`echo "$svn_info_tmp" \
1335 | sed '/^Last Changed Rev: /!d;s///'`
1337 test -z "$REV" && abort 'Cannot detect the current revision.'
1338 echo "$REV"
1340 # Let's make sure we have the real diff by asking the ChangeSet we've just
1341 # committed to the server.
1343 # Backup the old stuff in case we fail to get the real diff from the server
1344 # for some reason...
1345 save_svn_diff=$svn_diff
1346 save_svn_diff_stat=$svn_diff_stat
1348 if $git_mode; then
1349 svn_diff=`git diff --ignore-all-space --no-color -C 'HEAD^' HEAD`
1350 svn_diff_stat=`git diff --stat --ignore-all-space --no-color -C 'HEAD^' HEAD`
1351 grep svn-remote "$change_log_dir/.git/config" >/dev/null 2>&1 && \
1352 notice 'Do not forget to use: git-svn dcommit to push your commits in SVN'
1353 else
1354 # Fetch the ChangeSet and filter out the ChangeLog entry. We don't use
1355 # svn diff -c because this option is not portable to older svn versions.
1356 REV_MINUS_ONE=$((REV - 1))
1357 svn_diff=`svn_diffw -r"$REV_MINUS_ONE:$REV" "$repos_root" \
1358 | $AWK '/^Index: / { if (in_chlog) in_chlog = 0; }
1359 /^Index: .*ChangeLog$/ { in_chlog = 1 }
1360 { if (!in_chlog) print }'`
1361 if [ x"$svn_diff" = x ]; then
1362 svn_diff=$save_svn_diff
1363 svn_diff_stat=$save_svn_diff_stat
1364 else
1365 if require_diffstat; then
1366 svn_diff_stat=`echo "$svn_diff" | diffstat`
1367 else
1368 svn_diff_stat='diffstat not available'
1373 # Expand <REV> and remove the final period from the mail subject if there is
1374 # only one period.
1375 sed_tmp="s/<REV>/$REV/g;"'s/\([^.]\) *\.$/\1/'
1376 mail_subject=`echo "$mail_subject" | sed "$sed_eval_tags; $sed_tmp"`
1378 mail_file=`get_unique_file_name "$change_log_dir/+mail"`
1379 echo "\
1380 From: $mail_from
1381 To: $mail_to
1382 Subject: $mail_subject
1384 $mail_comment
1386 ChangeLog:
1387 $chlog_entry
1390 $svn_diff_stat
1392 $svn_diff" | sed 's/^\.$/ ./' >"$mail_file"
1393 # We change lines with only a `.' because they could mean "end-of-mail"
1395 # Send the mail
1396 if $send_a_mail; then
1397 trap 'echo SIGINT; exec < /dev/null' $SIGINT
1398 # FIXME: Move the mail to the +committed right now, in case the user
1399 # CTLR+C the mail-sending-thing, so that the mail will be properly saved
1400 # their.
1401 my_sendmail "$mail_file" "$mail_subject" "$mail_to" \
1402 "X-svn-url: $repos_root
1403 X-svn-revision: $REV"
1404 fi # end do we have to send a mail?
1405 rm -f "$tmp_log"
1406 rm -f "$old_chlog"
1407 save_mail_file=`echo "$mail_file" | sed 's/+//'`
1408 mkdir -p "$change_log_dir/+committed" \
1409 || warn "Couldn't mkdir -p $change_log_dir/+committed"
1410 if [ -d "$change_log_dir/vcs" ] \
1411 || [ -d "$change_log_dir/+committed" ]
1412 then
1413 mkdir -p "$change_log_dir/+committed/$REV" \
1414 && mv "$mail_file" "$change_log_dir/+committed/$REV/mail"
1416 return $svn_commit_rv
1419 # svn_diffw [args...]
1420 svn_diff()
1422 # Ignore white spaces.
1423 if $git_mode; then
1424 git diff -C "$@"
1425 else
1426 diffarg='-u'
1427 # Can't svn diff -x -w: svn 1.4 only.
1428 # Moreover we *MUST* use -x -uw, not -x -u -w or -x -u -x -w ...
1429 # Hence the hack to stick both diff arguments together...
1430 # No comment :)
1431 test x"$1" = x'--SVNW-HACK-w' && shift && diffarg='-uw'
1432 $SVN diff --no-diff-deleted --diff-cmd $DIFF -x $diffarg "$@"
1436 # svn_diffw [args...]
1437 svn_diffw()
1439 # Ignore white spaces.
1440 if $git_mode; then
1441 svn_diff --ignore-all-space "$@"
1442 else
1443 svn_diff --SVNW-HACK-w "$@"
1447 # svn_mail REV [mails...]
1448 svn_mail()
1450 test $# -lt 1 && abort "Not enough arguments provided;
1451 Try 'svn help mail' for more info."
1452 case $1 in
1453 PREV)
1454 if $git_mode; then
1455 REV=`git rev-list --pretty=format:%h 'HEAD^' --max-count=1 | sed '1d;q'`
1456 else
1457 REV=`svn_revision || abort 'Cannot get current revision number'`
1458 test -z "$REV" && abort 'Cannot get current revision number'
1459 if [ "$REV" -lt 1 ]; then
1460 abort 'No previous revision.'
1462 REV=$((REV - 1))
1465 HEAD)
1466 REV=`svn_revision || abort 'Cannot get current revision number'`
1467 test -z "$REV" && abort 'Cannot get current revision number'
1469 *) REV=$1;;
1470 esac
1471 shift
1473 found_committed=0; found=0
1474 while [ $found -eq 0 ]; do
1475 this_chlog_dir=`pwd -P`
1476 if [ -d ./+committed ]; then
1477 found_committed=1
1478 if [ -d ./+committed/$REV ]; then
1479 found=1
1480 else
1481 cd ..
1483 else
1484 cd ..
1486 # Stop searching when in / ... hmz :P
1487 test x`pwd` = x/ && break
1488 done
1489 if [ $found -eq 0 ]; then
1490 if [ $found_committed -eq 0 ]; then
1491 abort 'Could not find the +committed directory.'
1492 else
1493 abort "Could not find the revision $REV in +committed."
1495 abort 'Internal error (should never be here).'
1498 mail_file=; subject=; to=
1499 if [ -f ./+committed/$REV/mail ]; then
1500 # svn-wrapper generated file
1501 mail_file="./+committed/$REV/mail"
1502 subject=`sed '/^Subject: /!d;s///' $mail_file | sed '1q'`
1503 to=`sed '/^To: /!d;s///' $mail_file | sed '1q'`
1504 elif [ -f ./+committed/$REV/,iform ] && [ -f ./+committed/$REV/,message ]
1505 then
1506 # VCS-generated file
1507 subject=`sed '/^Subject: /!d;s///;s/^"//;s/"$//' ./+committed/$REV/,iform \
1508 | sed "s/<%= *rev *%>/$REV/g"`
1509 to=`sed '/^To:/,/^[^-]/!d' ./+committed/$REV/,iform | sed '1d;s/^- //;$d' \
1510 | xargs | sed 's/ */, /g'`
1511 mail_file=`get_unique_file_name "$TMPDIR/mail.r$REV"`
1512 echo "From: $FULLNAME <$EMAIL>
1513 To: $to
1514 Subject: $subject
1515 " >"$mail_file" || abort "Cannot create $mail_file"
1516 cat ./+committed/$REV/,message >>"$mail_file" \
1517 || abort "Cannot copy ./+committed/$REV/,message in $mail_file"
1518 else
1519 abort "Couldn't find the mail to re-send in `pwd`/+committed/$REV"
1521 if [ $# -gt 0 ]; then
1522 to=`echo "$*" | sed 's/ */, /g'`
1525 test -z "$to" && abort 'Cannot find the list of recipients.
1526 Please report this bug.'
1527 test -z "$subject" && abort 'Cannot find the subject of the mail.
1528 Please report this bug.'
1530 if yesno "Re-sending the mail of r$REV
1531 Subject: $subject
1532 To: $to
1533 Are you sure?"; then :; else
1534 return 1
1537 if $git_mode; then
1538 git_get_repos_info_
1539 else
1540 svn_info_tmp=`$SVN info`
1541 test $? -ne 0 && abort "Failed to get svn info on `pwd`"
1542 repos_root=`echo "$svn_info_tmp" | sed '/^Repository Root: /!d;s///'`
1543 repos_url=`echo "$svn_info_tmp" | sed '/^URL: /!d;s///'`
1544 # It looks like svn <1.3 didn't display a "Repository Root" entry.
1545 test -z "$repos_root" && repos_root=$repos_url
1548 my_sendmail "$mail_file" "$subject" "$to" \
1549 "X-svn-url: $repos_url
1550 X-svn-revision: $REV"
1553 # svn_version
1554 svn_version()
1556 echo "Using svn-wrapper v$version-g$revision (C) SIGOURE Benoit [GPL]"
1559 # has_prop prop-name [path]
1560 # return value: 0 -> path has the property prop-name set.
1561 # 1 -> path has no property prop-name.
1562 # 2 -> svn error.
1563 has_prop()
1565 hp_plist=`$SVN proplist "$2"`
1566 test $? -ne 0 && return 2
1567 hp_res=`echo "$hp_plist" | sed "/^ *$1\$/!d"`
1568 test -z "$hp_res" && return 1
1569 return 0
1572 # svn_propadd prop-name prop-val [path]
1573 svn_propadd()
1575 if $git_mode; then
1576 abort 'propadd is only for SVN, not for Git.'
1578 test $# -lt 2 \
1579 && abort 'Not enough arguments provided;
1580 try `svn help propadd` for more info'
1581 test $# -gt 3 \
1582 && abort 'Too many arguments provided;
1583 try `svn help propadd` for more info'
1585 path=$3
1586 test -z "$path" && path='.' && set dummy "$@" '.' && shift
1587 has_prop "$1" "$3" || {
1588 test $? -eq 2 && return 1 # svn error
1589 # no property found:
1590 yesno "'$path' has no property named '$1', do you want to add it?" \
1591 && $SVN propset "$@"
1592 return $?
1595 current_prop_val=`$SVN propget "$1" "$3"`
1596 test $? -ne 0 && abort "Failed to get the current value of property '$1'."
1598 $SVN propset "$1" "$current_prop_val
1599 $2" "$3" >/dev/null || abort "Failed to add '$3' in the property '$1'."
1601 current_prop_val=`$SVN propget "$1" "$3" || echo "$current_prop_val
1602 $2"`
1603 echo "property '$1' updated on '$path', new value:
1604 $current_prop_val"
1607 # svn_propsed prop-name sed-script [path]
1608 svn_propsed()
1610 if $git_mode; then
1611 abort 'propsed is only for SVN, not for Git.'
1613 test $# -lt 2 \
1614 && abort 'Not enough arguments provided;
1615 try `svn help propsed` for more info'
1616 test $# -gt 3 \
1617 && abort 'Too many arguments provided;
1618 try `svn help propsed` for more info'
1620 path=$3
1621 test -z "$path" && path='.'
1622 has_prop "$1" "$3" || {
1623 test $? -eq 2 && return 1 # svn error
1624 # no property found:
1625 abort "'$path' has no property named '$1'."
1628 prop_val=`$SVN propget "$1" "$3"`
1629 test $? -ne 0 && abort "Failed to get the current value of property '$1'."
1631 prop_val=`echo "$prop_val" | sed "$2"`
1632 test $? -ne 0 && abort "Failed to run the sed script '$2'."
1634 $SVN propset "$1" "$prop_val" "$3" >/dev/null \
1635 || abort "Failed to update the property '$1' with value '$prop_val'."
1637 new_prop_val=`$SVN propget "$1" "$3" || echo "$prop_val"`
1638 echo "property '$1' updated on '$path', new value:
1639 $new_prop_val"
1642 # svn_revision [args...]
1643 svn_revision()
1645 if $git_mode; then
1646 short=`git rev-list --pretty=format:%h HEAD --max-count=1 | sed '1d;q'`
1647 long=`git rev-list --pretty=format:%H HEAD --max-count=1 | sed '1d;q'`
1648 echo "$short ($long)"
1649 else
1650 svn_revision_info_out=`$SVN info "$@"`
1651 svn_revision_rv=$?
1652 echo "$svn_revision_info_out" | sed '/^Revision: /!d;s///'
1653 return $svn_revision_rv
1657 # svn_ignore [paths]
1658 svn_ignore()
1660 if [ $# -eq 0 ]; then # Simply display ignore-list.
1661 if $git_mode; then
1662 test -f .gitignore && cat .gitignore
1663 else
1664 $SVN propget 'svn:ignore'
1666 elif [ $# -eq 1 ]; then
1667 b=`basename "$1"`
1668 d=`dirname "$1"`
1669 if $git_mode; then
1670 echo "$b" >>"$d/.gitignore"
1671 git add "$d/.gitignore"
1672 notice 'files ignored in this directory:'
1673 cat "$d/.gitignore"
1674 else
1675 svn_propadd 'svn:ignore' "$b" "$d"
1677 else # Add arguments in svn:ignore.
1678 # This part is a bit tricky:
1679 # For each argument, we find all the other arguments with the same dirname
1680 # $dname and we svn:ignore them all in $dname.
1681 while [ $# -ne 0 ]; do
1682 arg=$1
1683 dname=`dirname "$1"`
1684 files=`basename "$1"`
1685 shift
1686 j=0; argc=$#
1687 while [ $j -lt $argc ] && [ $# -ne 0 ]; do
1688 this_arg=$1
1689 shift
1690 this_dname=`dirname "$this_arg"`
1691 this_file=`basename "$this_arg"`
1692 if [ x"$dname" = x"$this_dname" ]; then
1693 files="$files
1694 $this_file"
1695 else
1696 set dummy "$@" "$this_arg"
1697 shift
1699 j=$((j + 1))
1700 done
1701 if $git_mode; then
1702 echo "$files" >>"$dname"/.gitignore
1703 git add "$dname"/.gitignore
1704 notice "files ignored in $dname:"
1705 cat "$dname"/.gitignore
1706 else
1707 svn_propadd 'svn:ignore' "$files" "$dname"
1709 done
1713 # svn_help
1714 svn_help()
1716 if [ $# -eq 0 ]; then
1717 svn_version
1718 $SVN help
1719 rv=$?
1720 echo '
1721 Additionnal commands provided by svn-wrapper:
1722 diffstat (ds)
1723 diffw (dw)
1724 ignore
1725 mail
1726 propadd (padd, pa) -- SVN only
1727 proposal
1728 propsed (psed) -- SVN only
1729 revision (rev)
1730 touch
1731 selfupdate (selfup)
1732 version'
1733 return $rv
1734 else
1735 case $1 in
1736 commit | ci)
1737 $SVN help commit | sed '/^Valid options:/i\
1738 Extra options provided by svn-wrapper:\
1739 \ --dry-run : do not commit, simply generate a patch with what\
1740 \ would have been comitted.\
1741 \ --use-log-file FILE : extract the ChangeLog entry from FILE. This\
1742 \ entry must be formated in a similar fashion to\
1743 \ what svn-wrapper usually asks you to fill in.\
1744 \ The FILE needs to be writable and will be\
1745 \ removed by svn-wrapper upon success.\
1749 diffstat | ds)
1750 require_diffstat
1751 echo 'diffstat (ds): Display the histogram from svn diff-output.'
1752 $SVN help diff | sed '1d;
1753 s/differences*/histogram/;
1754 2,35 s/diff/diffstat/g'
1756 diffw | dw)
1757 echo "diffw (dw): Display the differences without taking whitespaces\
1758 into account."
1759 $SVN help diff | sed '1d;
1760 2,35 s/diff\([^a-z]\)/diffw\1/g;
1761 /--diff-cmd/,/--no-diff-deleted/d'
1763 ignore)
1764 what='svn:ignore property'
1765 $git_mode && what='.gitignore file'
1766 cat <<EOF
1767 ignore: Add some files in the $what.
1768 usage: 1. ignore [PATH]
1769 2. ignore FILE [FILES...]
1771 1. Display the value of $what on [PATH].
1772 2. Add some files in the $what of the directory containing them.
1774 When adding ignores, each pattern is ignored in its own directory, e.g.:
1775 $bme ignore dir/file "d2/*.o"
1776 Will put 'file' in the $what of 'dir' and '*.o' in the
1777 $what of 'd2'
1779 Valid options:
1780 None.
1783 mail)
1784 echo 'mail: Resend the mail of a given commit.
1785 usage: mail REV [emails]
1787 REV must have an email file associated in +committed/REV.
1788 REV can also be PREV or HEAD.
1790 By default the mail is sent to same email addresses as during the original
1791 commit unless more arguments are given.'
1793 propadd | padd | pa)
1794 echo 'propadd (padd, pa): Add something in the value of a property.
1795 usage: propadd PROPNAME PROPVAL PATH
1796 This command only works in SVN mode.
1798 PROPVAL will be appended at the end of the property PROPNAME.
1800 Valid options:
1801 None.'
1803 proposal)
1804 echo 'proposal: Alias for: commit --dry-run.
1805 See: svn help commit.'
1807 propsed | psed)
1808 echo 'propsed (psed): Edit a property with sed.
1809 usage: propsed PROPNAME SED-ARGS PATH
1810 This command only works in SVN mode.
1812 eg: svn propsed svn:externals "s/http/https/" .
1814 Valid options:
1815 None.'
1817 revision | rev)
1818 echo 'revision (rev): Display the revision number of a local or remote item.'
1819 $SVN help info | sed '1d;
1820 s/information/revision/g;
1821 s/revision about/the revision of/g;
1822 2,35 s/info/revision/g;
1823 /-xml/d'
1825 touch)
1826 echo 'touch: Touch a file and svn add it.
1827 usage: touch FILE [FILES]...
1829 Valid options:
1830 None.'
1832 selfupdate | selfup | self-update | self-up)
1833 echo 'selfupdate (selfup): Attempt to update svn-wrapper.sh
1834 usage: selfupdate
1836 Valid options:
1837 None.'
1839 version)
1840 echo 'version: Display the version info of svn and svn-wrapper.
1841 usage: version
1843 Valid options:
1844 None.'
1846 *) $SVN help "$@";;
1847 esac
1851 # svn_status [args...]
1852 svn_status()
1854 if $git_mode; then
1855 git status "$@"
1856 return $?
1858 svn_status_out=`$SVN status "$@"`
1859 svn_status_rv=$?
1860 test -z "$svn_status_out" && return $svn_status_rv
1861 echo "$svn_status_out" | sed "$sed_svn_st_color"
1862 return $svn_status_rv
1865 # svn_update [args...]
1866 svn_update()
1868 svn_update_out=`$SVN update "$@"`
1869 svn_update_rv=$?
1870 echo "$svn_update_out" | sed "$sed_svn_up_colors"
1871 return $svn_update_rv
1874 # ------------------- #
1875 # `main' starts here. #
1876 # ------------------- #
1878 # Define colors if stdout is a tty.
1879 if test -t 1; then
1880 set_colors
1881 else # stdout isn't a tty => don't print colors.
1882 set_nocolors
1885 # Consider this as a sed function :P.
1886 sed_svn_st_color="
1887 t dummy_sed_1
1888 : dummy_sed_1
1889 s@^?\\(......\\)+@+\\1+@
1890 s@^?\\(......\\)\\(.*/\\)+@+\\1\\2+@
1891 s@^?\\(......\\),@,\\1,@
1892 s@^?\\(......\\)\\(.*/\\),@,\\1\\2,@
1893 s/^\\(.\\)C/\\1${lred}C${std}/
1894 t dummy_sed_2
1895 : dummy_sed_2
1896 s/^?/${lred}?${std}/; t
1897 s/^M/${lgreen}M${std}/; t
1898 s/^A/${lgreen}A${std}/; t
1899 s/^X/${lblue}X${std}/; t
1900 s/^+/${lyellow}+${std}/; t
1901 s/^D/${lyellow}D${std}/; t
1902 s/^,/${lred},${std}/; t
1903 s/^C/${lred}C${std}/; t
1904 s/^I/${purple}I${std}/; t
1905 s/^R/${lblue}R${std}/; t
1906 s/^!/${lred}!${std}/; t
1907 s/^~/${lwhite}~${std}/; t"
1909 sed_svn_up_colors="
1910 t dummy_sed_1
1911 : dummy_sed_1
1913 /^Updated/ t
1914 /^Fetching/ t
1915 /^External/ t
1916 s/^\\(.\\)C/\\1${lred}C${std}/
1917 s/^\\(.\\)U/\\1${lgreen}U${std}/
1918 s/^\\(.\\)D/\\1${lred}D${std}/
1919 t dummy_sed_2
1920 : dummy_sed_2
1921 s/^A/${lgreen}A${std}/; t
1922 s/^U/${lgreen}U${std}/; t
1923 s/^D/${lyellow}D${std}/; t
1924 s/^G/${purple}G${std}/; t
1925 s/^C/${lred}C${std}/; t"
1927 # For dev's:
1928 test "x$1" = x--debug && shift && set -x
1930 test "x$1" = x--git && shift && git_mode=: && SVN=git
1932 test "x$1" = x--no-changelog && shift && use_changelog=false
1934 case $1 in
1935 # ------------------------------- #
1936 # Hooks for standard SVN commands #
1937 # ------------------------------- #
1938 commit | ci)
1939 shift
1940 svn_commit "$@"
1942 help | \? | h)
1943 shift
1944 svn_help "$@"
1946 status | stat | st)
1947 shift
1948 svn_status "$@"
1950 update | up)
1951 shift
1952 svn_update "$@"
1954 # -------------------- #
1955 # Custom SVN commands #
1956 # -------------------- #
1957 diffstat | ds)
1958 shift
1959 if [ -d .git ]; then
1960 git diff --stat -C
1961 else
1962 require_diffstat && $SVN diff --no-diff-deleted "$@" | diffstat
1965 diff | di)
1966 shift
1967 DIFF=$COLORDIFF
1968 svn_diff "$@"
1970 diffw | dw)
1971 shift
1972 DIFF=$COLORDIFF
1973 svn_diffw "$@"
1975 ignore)
1976 shift
1977 svn_ignore "$@"
1979 log)
1980 if $git_mode; then # let Git handle log
1981 exec $SVN "$@"
1982 else # pipe svn log through PAGER by default
1983 exec $SVN "$@" | $PAGER
1986 mail)
1987 shift
1988 svn_mail "$@"
1990 propadd | padd | pa)
1991 shift
1992 svn_propadd "$@"
1994 proposal)
1995 shift
1996 svn_commit --dry-run "$@"
1998 propsed | psed)
1999 shift
2000 svn_propsed "$@"
2002 revision | rev)
2003 shift
2004 svn_revision "$@"
2006 touch)
2007 shift
2008 touch "$@" && $SVN add "$@"
2010 selfupdate | selfup | self-update | self-up)
2011 shift
2012 selfupdate "$@"
2014 version | -version | --version)
2015 shift
2016 set dummy '--version' "$@"
2017 shift
2018 svn_version
2019 exec $SVN "$@"
2021 *) exec $SVN "$@"
2023 esac