* git_get_repos_info_: Rewrite with more generic code. Detect
[svn-wrapper.git] / svn-wrapper.sh
blob0d570677b88abea4ca3ccb066a76753bb301cae7
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,
550 # extra_repos_info and using_git_svn properly.
551 git_get_repos_info_()
553 # FIXME: 1st commit: "fatal: bad default revision 'HEAD'" on stderr
554 git_config_list=`git config -l`
555 using_git_svn=false
556 case $git_config_list in #(
557 *svn-remote.*.url=*)
558 repos_url=`echo "$git_config_list" \
559 | sed '/^svn-remote.svn.url=\(.*\)$/!d;s//\1/'`
560 using_git_svn=:
561 ;; #(
562 *remote.origin.url=*)
563 repos_url=`echo "$git_config_list" \
564 | sed '/^remote.origin.url=\(.*\)$/!d;s//\1/'`
566 esac
567 test -z "$repos_url" && repos_url='(git:unknown)'
568 git_branch=`git branch | awk '/^\*/ { print substr($0, 3) }'`
569 if [ x"$git_branch" = x'(no branch)' ]; then
570 yesno 'You are on a detached HEAD, do you really want to continue?' \
571 || return 1
573 git_head=`git rev-list --pretty=format:%h HEAD --max-count=1 | sed '1d;q'`
574 extra_repos_info="Git branch: $git_branch (HEAD: $git_head)"
575 repos_root=$repos_url
578 # git_warn_missing_prop(PROP-NAME, SAMPLE-VALUE)
579 git_warn_missing_prop()
581 if $using_git_svn; then
582 warn "No $1 property set for this repository.
583 This is a git-svn repository, so you can set it in an SVN working copy:
584 svn propset $1 $2 .
585 Otherwise you can just set the property in your git-svn repository:
586 git config svnw.$1 $2"
587 else
588 warn "No $1 property set for this repository.
589 You can set it like this:
590 git config svnw.$1 $2"
594 # Helper. Find the `mailto' property, be it an SVN property or a git-config
595 # option. Relies on the value of $change_log_dir and sets the values of
596 # $mailto (the value of the `mailto' property) and $to (a string to append in
597 # templates that contains the `To:' line, or an empty string if no mail must
598 # be sent).
599 get_mailto_property()
601 test -d "$change_log_dir" || abort 'Internal error in get_mailto_property:
602 $change_log_dir not pointing to a directory'
603 if $git_mode; then
604 mailto=`git config svnw.mailto`
605 if [ x"$mailto" = x ] \
606 && [ -f "$change_log_dir/.git/svn/git-svn/unhandled.log" ]
607 then
608 mailto=`grep mailto "$change_log_dir/.git/svn/git-svn/unhandled.log"`
609 sed_tmp='$!d;s/^.*+dir_prop: . mailto //;s/%40/@/g;s/%2C/,/g;s/%20/ /g;'
610 mailto=`echo "$mailto" | sed "$sed_tmp"`
612 if [ x"$mailto" = x ]; then
613 git_warn_missing_prop 'mailto' 'maintainer1@foo.com,maint2@bar.com'
615 else
616 mailto=`$SVN propget mailto "$change_log_dir"`
619 if [ x"$mailto" = x ]; then
620 test x$new_user = xyes \
621 && warn "no svn property mailto found in $change_log_dir
622 You might want to set default email adresses using:
623 svn propset mailto 'somebody@mail.com, foobar@example.com'\
624 $change_log_dir" >&2
625 # Try to be VCS-compatible and find a list of mails in a *.rb.
626 if [ -d "$change_log_dir/vcs" ]; then
627 mailto=`grep '.@.*\..*' "$change_log_dir"/vcs/*.rb \
628 | tr '\n' ' ' \
629 | sed 's/^.*[["]\([^["]*@[^]"]*\)[]"].*$/\1/' | xargs`
630 test x"$mailto" != x && test x$new_user = xyes \
631 && notice "VCS-compat: found mailto: $mailto
632 in " "$change_log_dir"/vcs/*.rb
633 fi # end VCS compat
634 fi # end guess mailto
636 # Ensure that emails are comma-separated.
637 mailto=`echo "$mailto" | sed 's/[ ;]/,/g' | tr -s ',' | sed 's/,/, /g'`
640 if [ x"$mailto" != xdont_send_mails ] && [ x"$mailto" != xnothx ]; then
641 to="
642 To: $mailto"
648 # ------------------------------- #
649 # Hooks for standard SVN commands #
650 # ------------------------------- #
652 # svn_commit [args...]
653 # Here is how the commit process goes:
655 # First we look in the arguments passed to commit:
656 # If there are some files or paths, the user wants to commit these only. In
657 # this case, we must search for ChangeLogs from these paths. We might find
658 # more than one ChangeLog, in this case the user will be prompted to pick up
659 # one.
660 # Otherwise (no path passed in the command line) the user just wants to
661 # commit the current working directory.
662 # In any case, we schedule "ChangeLog" for commit.
664 # Alright now that we know which ChangeLog to use, we look in the ChangeLog's
665 # directory if there is a ",svn-log" file which would mean that a previous
666 # commit didn't finish successfully. If there is such a file, the user is
667 # prompted to know whether they want to resume that commit or simply start a
668 # new one.
669 # When the user wants to resume a commit, the ",svn-log" file is loaded and we
670 # retrieve the value of "$@" that was saved in the file.
671 # Otherwise we build a template ChangeLog entry.
672 # Then we open the template ChangeLog entry with $EDITOR so that the user
673 # fills it properly.
674 # Finally, we commit.
675 # Once the commit is sent, we ask the server to know which revision was
676 # commited and we also retrieve the diff. We then send a mail with these.
677 svn_commit()
679 here=`pwd -P`
680 dry_run=false
681 git_commit_all=false
682 use_log_message_from_file=false
683 log_message_to_use=
685 # Check if the user passed some paths to commit explicitly
686 # because in this case we must add the ChangeLog to the commit and search
687 # the ChangeLog from the dirname of that file.
688 i=0; search_from=; add_changelog=false; extra_files=
689 while [ $i -lt $# ]; do
690 arg=$1
691 case $arg in
692 --dry-run)
693 dry_run=:
694 shift
695 i=$((i + 1))
696 continue
698 -a|--all)
699 git_commit_all=:
701 --use-log-file)
702 shift
703 test -z "$1" && abort "$arg needs an argument"
704 test -r "$1" || abort "'$1' does not seem to be readable"
705 test -w "$1" || abort "'$1' does not seem to be writable"
706 test -d "$1" && abort "'$1' seems to be a directory"
707 use_log_message_from_file=:
708 log_message_to_use=$1
709 shift
710 continue
712 esac
713 # If the argument is a valid path: add the ChangeLog in the list of
714 # files to commit
715 if test -e "$arg"; then
716 add_changelog=:
717 if test -d "$arg"; then
718 search_from_add=$arg
719 else
720 search_from_add=`dirname "$arg"`
722 search_from="$search_from:$search_from_add"
724 shift
725 set dummy "$@" "$arg"
726 shift
727 i=$((i + 1))
728 done
729 if $add_changelog; then :; else
730 # There is no path/file in the command line: the user wants to commit the
731 # current directory. Make it explicit now:
732 extra_files=$here
734 search_from=`echo "$search_from" | sed 's/^://; s/^$/./'`
736 # ----------------- #
737 # Find ChangeLog(s) #
738 # ----------------- #
740 nb_chlogs=0; change_log_dirs=
741 save_IFS=$IFS; IFS=':'
742 for dir in $search_from; do
743 IFS=$save_IFS
744 $use_changelog || break
745 test -z "$dir" && dir='.'
746 # First: come back to the original place
747 cd "$here" || abort "Cannot cd to $here"
748 cd "$dir" || continue # Then: Enter $dir (which can be a relative path)
749 found=0
750 while [ $found -eq 0 ]; do
751 this_chlog_dir=`pwd -P`
752 if [ -f ./ChangeLog ]; then
753 found=1
754 nb_chlogs=$((nb_chlogs + 1))
755 change_log_dirs="$change_log_dirs:$this_chlog_dir"
756 else
757 cd ..
759 # Stop searching when in / ... hmz :P
760 test x"$this_chlog_dir" = x/ && break
761 done # end while: did we find a ChangeLog
762 done # end for: find ChangeLogs in $search_from
763 if [ $nb_chlogs -gt 0 ]; then
764 change_log_dirs=`echo "$change_log_dirs" | sed 's/^://' | tr ':' '\n' \
765 | sort -u`
766 nb_chlogs=`echo "$change_log_dirs" | wc -l`
769 # Did we find a ChangeLog? More than one?
770 if [ $nb_chlogs -eq 0 ] && $use_changelog; then
771 if yesno 'svn-wrapper: Error: Cannot find a ChangeLog file!
772 You might want to create an empty one (eg: `touch ChangeLog` where appropriate)
773 Do you want to proceed without using a ChangeLog?'; then
774 cd "$here"
775 $SVN commit "$@"
776 return $?
777 else
778 return 1
780 elif [ $nb_chlogs -gt 1 ]; then
781 notice "$nb_chlogs ChangeLogs were found, pick up one:"
783 IFS=':'; i=0
784 for a_chlog_dir in $change_log_dirs; do
785 i=$((i + 1))
786 echo "$i. $a_chlog_dir/ChangeLog"
787 done
788 printf "Which ChangeLog do you want to use? [1-$i] "
789 read chlog_no || abort 'Cannot read answer on stdin.'
791 case $chlog_no in
792 *[^0-9]*) abort "Invalid ChangeLog number: $chlog_no"
793 esac
794 test "$chlog_no" -le $i || abort "Invalid ChangeLog number: $chlog_no
795 max value was: $i"
796 test "$chlog_no" -ge 1 || abort "Invalid ChangeLog number: $chlog_no
797 min value was: 1"
798 change_log_dir=`echo "$change_log_dirs" | tr ':' '\n' | sed "${chlog_no}!d"`
799 else # Only one ChangeLog found
800 if $use_changelog; then
801 change_log_dir=$change_log_dirs
802 notice "using $change_log_dir/ChangeLog"
806 if $use_changelog; then
807 test -f "$change_log_dir/ChangeLog" \
808 || abort "No such file or directory: $change_log_dir/ChangeLog"
809 # Now we can safely schedule the ChangeLog for the commit.
810 extra_files="$extra_files:$change_log_dir/ChangeLog"
811 else
812 change_log_dir='.' # Hack. FIXME: Does this work in all cases?
815 if [ -d "$change_log_dir/.git" ] || $git_mode; then
816 SVN=git
817 git_mode=:
818 git_get_repos_info_
819 else
820 svn_st_tmp=`$SVN status "$change_log_dir"`
822 # Warn for files that are not added in the repos.
823 conflicts=`echo "$svn_st_tmp" | sed '/^ *$/d;
824 /^[^?]/d;
825 /^?.......*\/[,+]/d;
826 /^?......[,+]/d'`
827 if test x"$conflicts" != x; then
828 warn "make sure you don't want to \`svn add'
829 any of the following files before committing:"
830 echo "$conflicts" | sed "$sed_svn_st_color"
831 printf 'Type [ENTER] to continue :)' && read chiche_is_gay
834 # If there are changes in an svn:externals, advise the user to commit that
835 # first.
836 changed_externals=`echo "$svn_st_tmp" | $AWK \
837 'function printext()
839 if (ext && !printed)
841 print this_ext "\n";
842 printed = 1;
845 BEGIN { this_ext = ""; ext = 0; ext_modified = 0; }
846 /^Performing status on external/ {
847 ext = 1;
848 sub(/.* at ./, ""); sub(/.$/, ""); this_ext = $0;
849 printed = 0;
851 /^[ADMR]/ { ext_modified = ext; printext(); }
852 /^.[M]/ { ext_modified = ext; printext(); }
853 END { exit ext_modified; }'`
854 if [ $? -ne 0 ]; then
855 warn "the following external items have local modifications:
856 $changed_externals"
857 yesno "You are advised to commit them separately first. Continue anyway?" \
858 || return 1
861 # Detect unresolved conflicts / missing files.
862 conflicts=`echo "$svn_st_tmp" | sed '/^[C!]/!d'`
863 test x"$conflicts" != x && abort "there are unresolved conflicts (\`C')
864 and/or missing files (\`!'):
865 $conflicts"
867 svn_info_tmp=`$SVN info "$change_log_dir"`
868 test $? -ne 0 && abort "Failed to get svn info on $change_log_dir"
869 repos_root=`echo "$svn_info_tmp" | sed '/^Repository Root: /!d;s///'`
870 repos_url=`echo "$svn_info_tmp" | sed '/^URL: /!d;s///'`
871 # It looks like svn <1.3 didn't display a "Repository Root" entry.
872 test -z "$repos_root" && repos_root=$repos_url
875 cd "$here"
877 YYYY=`date '+%Y'`
878 MM=`date '+%m'`
879 DD=`date '+%d'`
881 # VCS-compat: handle user option 'new_user'
882 new_user='yes'
883 grep '^new_user: false' ~/.vcs >/dev/null 2>/dev/null && new_user='no'
885 edit_changelog=:
886 tmp_log="$change_log_dir/,svn-log"
887 $use_log_message_from_file \
888 && tmp_log=$log_message_to_use \
889 && edit_changelog=false
891 if [ -f "$tmp_log" ] \
892 && { $use_log_message_from_file \
893 || yesnewproceed "It looks like the last commit did not\
894 terminate successfully.
895 Would you like to resume it or proceed immediately?"; }; then
896 case $yesnoproceed_res in
897 *proceed) edit_changelog=false;;
898 esac
899 if test x"$yesnoproceed_res" = xupproceed; then
900 svn_update "$@" || abort 'update failed'
902 echo 'Resuming ...'
903 internal_tags=`sed '/^--- Internal stuff, DO NOT change please ---$/,$!d' \
904 "$tmp_log"`
905 saved_args=`echo "$internal_tags" | sed '/^args: */!d;s///'`
906 extra_files=`echo "$internal_tags" | sed '/^extra_files: */!d;s///'`
907 if [ x"$saved_args" != x ]; then
908 if [ x"$*" != x ] && [ x"$saved_args" != x"$*" ]; then
909 warn "overriding arguments:
910 you invoked $me with the following arguments: $@
911 they have been replaced by these: $saved_args"
912 set dummy $saved_args
913 shift
914 else
915 notice "setting the following arguments: $saved_args"
916 set dummy $saved_args
917 shift
919 elif [ x"$*" != x ]; then
920 warn "overriding arguments:
921 you invoked $me with the following arguments: $@
922 they have been dropped"
925 for i; do
926 case $i in
927 -a|--all)
928 git_commit_all=:
929 if [ $git_mode ]; then
930 (cd $change_log_dir && git add -v -u) || abort '`git add -v -u` failed'
933 esac
934 done
936 get_svn_diff_and_diffstat "$@"
938 # Update the file with the new diff/diffstat in case it changed.
939 $AWK 'BEGIN {
940 tlatbwbi_seen = 0;
941 ycewah_seen = 0;
943 /^--This line, and those below, will be ignored--$/ {
944 tlatbwbi_seen = 1;
946 /^ Your ChangeLog entry will appear here\.$/ {
947 if (tlatbwbi_seen) ycewah_seen = 1;
950 if (ycewah_seen != 2) print;
951 if (ycewah_seen == 1) ycewah_seen = 2;
953 END {
954 if (tlatbwbi_seen == 0)
956 print "--This line, and those below, will be ignored--\n\n" \
957 " Your ChangeLog entry will appear here.";
959 }' "$tmp_log" >"$tmp_log.tmp"
960 echo "
962 $svn_diff_stat
964 $svn_diff
966 $internal_tags" >>"$tmp_log.tmp"
967 mv -f "$tmp_log.tmp" "$tmp_log" || abort "failed to write '$tmp_log'"
969 else # Build the template message.
971 # ------------------------------------ #
972 # Gather info for the template message #
973 # ------------------------------------ #
975 if $git_mode; then
976 projname=`git config svnw.project`
977 if [ x"$projname" = x ] \
978 && [ -f "$change_log_dir/.git/svn/git-svn/unhandled.log" ]
979 then
980 projname=`grep project "$change_log_dir/.git/svn/git-svn/unhandled.log"`
981 sed_tmp='$!d;s/^.*+dir_prop: . project //'
982 projname=`echo "$projname" | sed "$sed_tmp"`
984 if [ x"$projname" = x ]; then
985 git_warn_missing_prop 'project' 'myproj'
987 else
988 projname=`$SVN propget project "$change_log_dir"`
990 # Try to be VCS-compatible and find a project name in a *.rb.
991 if [ x"$projname" = x ] && [ -d "$change_log_dir/vcs" ]; then
992 projname=`sed '/common_commit/!d;s/.*"\(.*\)<%= rev.*/\1/' \
993 "$change_log_dir"/vcs/*.rb`
994 test x"$projname" != x && test x$new_user = xyes \
995 && notice "VCS-compat: found project name: $projname
996 in " "$change_log_dir"/vcs/*.rb
998 test x"$projname" != x && projname=`echo "$projname" | sed '/[^ ]$/s/$/ /'`
1000 get_mailto_property
1002 test -z "$FULLNAME" && FULLNAME='Type Your Name Here' \
1003 && warn_env FULLNAME
1004 test -z "$EMAIL" && EMAIL='your.mail.here@FIXME.com' && warn_env EMAIL
1006 if $git_mode; then
1007 if $git_commit_all; then
1008 (cd $change_log_dir && git add -v -u) || abort '`git add -v -u` failed'
1010 my_git_st=`git diff -C --raw --cached`
1011 test $? -eq 0 || abort 'git diff failed'
1012 # Format: ":<old_mode> <new_mode> <old_sha1> <new_sha1> <status>[
1013 # <similarity score>]\t<file-name>"
1014 change_log_files=`echo "$my_git_st" | sed '
1015 t dummy_sed_1
1016 : dummy_sed_1
1017 s/^:[0-7 ]* [0-9a-f. ]* M[^ ]* \(.*\)$/ * \1: ./; t
1018 s/^:[0-7 ]* [0-9a-f. ]* A[^ ]* \(.*\)$/ * \1: New./; t
1019 s/^:[0-7 ]* [0-9a-f. ]* D[^ ]* \(.*\)$/ * \1: Remove./; t
1020 s/^:[0-7 ]* [0-9a-f. ]* R[^ ]* \([^ ]*\) \(.*\)$/ * \2: Rename from \1./;t
1021 s/^:[0-7 ]* [0-9a-f. ]* C[^ ]* \([^ ]*\) \(.*\)$/ * \2: Copy from \1./;t
1022 s/^:[0-7 ]* [0-9a-f. ]* T[^ ]* \(.*\)$/ * \1: ./; t
1023 s/^:[0-7 ]* [0-9a-f. ]* X[^ ]* \(.*\)$/ * \1: ???./; t
1024 s/^:[0-7 ]* [0-9a-f. ]* U[^ ]* \(.*\)$/ * \1: UNMERGED./; t
1026 else
1027 # --ignore-externals appeared after svn 1.1.1
1028 my_svn_st=`$SVN status --ignore-externals "$@" \
1029 || $SVN status "$@" | sed '/^Performing status on external/ {
1033 # Files to put in the ChangeLog entry.
1034 change_log_files=`echo "$my_svn_st" | sed '
1035 t dummy_sed_1
1036 : dummy_sed_1
1037 s/^M......\(.*\)$/ * \1: ./; t
1038 s/^A......\(.*\)$/ * \1: New./; t
1039 s/^D......\(.*\)$/ * \1: Remove./; t
1043 if [ x"$change_log_files" = x ]; then
1044 yesno 'Nothing to commit, continue anyway?' || return 1
1047 change_log_files=`echo "$change_log_files" | sort -u`
1049 get_svn_diff_and_diffstat "$@"
1051 # Get any older svn-log out of the way.
1052 test -f "$tmp_log" && mv "$tmp_log" `get_unique_file_name "$tmp_log"`
1053 # If we can't get an older svn-log out of the way, find a new name...
1054 test -f "$tmp_log" && tmp_log=`get_unique_file_name "$tmp_log"`
1055 if [ x$new_user = no ]; then
1056 commit_instructions='
1057 Instructions:
1058 - Fill the ChangeLog entry.
1059 - If you feel like, write a comment in the "Comment:" section.
1060 This comment will only appear in the email, not in the ChangeLog.
1061 By default only the location of the repository is in the comment.
1062 - Some tags will be replaced. Tags are of the form: <TAG>. Unknown
1063 tags will be left unchanged.
1064 - The tag <REV> may only be used in the Subject.
1065 - Your ChangeLog entry will be used as commit message for svn.'
1066 else
1067 commit_instructions=
1069 r_before_rev=r
1070 $git_mode && r_before_rev=
1071 test -z "$extra_repos_info" || extra_repos_info="
1072 $extra_repos_info"
1073 echo "\
1074 --You must fill this file correctly to continue-- -*- vcs -*-
1075 Title:
1076 Subject: ${projname}$r_before_rev<REV>: <TITLE>
1077 From: $FULLNAME <$EMAIL>$to
1079 Comment:
1080 URL: $repos_url$extra_repos_info
1082 ChangeLog:
1084 <YYYY>-<MM>-<DD> $FULLNAME <$EMAIL>
1086 <TITLE>
1087 $change_log_files
1089 --This line, and those below, will be ignored--
1090 $commit_instructions
1091 --Preview of the message that will be sent--
1093 URL: $repos_url$extra_repos_info
1094 Your comments (if any) will appear here.
1096 ChangeLog:
1097 $YYYY-$MM-$DD $FULLNAME <$EMAIL>
1099 Your ChangeLog entry will appear here.
1102 $svn_diff_stat
1104 $svn_diff" >"$tmp_log"
1106 echo "
1107 --- Internal stuff, DO NOT change please ---
1108 args: $@" >>"$tmp_log"
1109 echo "extra_files: $extra_files
1110 vi: ft=diff:noet:tw=76:" >>"$tmp_log"
1112 fi # end: if svn-log; then resume? else create template
1113 $edit_changelog && $EDITOR "$tmp_log"
1115 # ------------------ #
1116 # Re-"parse" the log #
1117 # ------------------ #
1119 # hmz this section is a bit messy...
1120 # helper string... !@#$%* escaping \\\\\\...
1121 sed_escape='s/\\/\\\\/g;s/@/\\@/g;s/&/\\\&/g'
1122 sed_eval_tags="s/<MM>/$MM/g; s/<DD>/$DD/g; s/<YYYY>/$YYYY/g"
1123 full_log=`sed '/^--*This line, and those below, will be ignored--*$/,$d;
1124 /^--You must fill this/d' "$tmp_log"`
1125 chlog_entry=`echo "$full_log" | sed '/^ChangeLog:$/,$!d; //d'`
1126 ensure_not_empty 'ChangeLog entry' "$chlog_entry"
1127 full_log=`echo "$full_log" | sed '/^ChangeLog:$/,$d'`
1128 mail_comment=`echo "$full_log" | sed '/^Comment:$/,$!d; //d'`
1129 full_log=`echo "$full_log" | sed '/^Comment:$/,$d'`
1130 mail_title=`echo "$full_log" | sed '/^Title: */!d;s///;'`
1131 ensure_not_empty 'commit title' "$mail_title"
1132 # Add a period at the end of the title.
1133 mail_title=`echo "$mail_title" | sed -e '/ *[.!?]$/!s/ *$/./' \
1134 -e "$sed_eval_tags; $sed_escape"`
1135 sed_eval_tags="$sed_eval_tags; s@<TITLE>\\.*@$mail_title@g"
1136 mail_comment=`echo "$mail_comment" | sed "$sed_eval_tags"`
1137 raw_chlog_entry=$chlog_entry # ChangeLog entry without tags expanded
1138 chlog_entry=`echo "$chlog_entry" | sed "$sed_eval_tags; 1{
1139 /^ *$/d
1141 mail_subject=`echo "$full_log" | sed '/^Subject: */!d;s///'`
1142 ensure_not_empty 'mail subject' "$mail_subject"
1143 mail_to=`echo "$full_log" | sed '/^To:/!d'`
1144 send_a_mail=:
1145 if test x"$mail_to" = x; then
1146 send_a_mail=false
1147 else
1148 mail_to=`echo "$mail_to" | sed 's/^To: *//;s///'`
1149 # If there is a <MAILTO> in the 'To:' line, we must expand it.
1150 case $mail_to in #(
1151 *'<MAILTO>'*)
1152 get_mailto_property
1153 # Are we meant to send a mail?
1154 case $to in #(
1155 '') # No, don't send a mail.
1156 mail_to=
1157 send_a_mail=false
1158 ;; #(
1159 *) # Yes, send a mail.
1160 mail_to=`echo "$mail_to" | sed "s#<MAILTO>#$mailto#g"`
1162 esac
1163 esac
1164 $send_a_mail && ensure_not_empty '"To:" field of the mail' "$mail_to"
1167 test -z "$FULLNAME" && warn_env FULLNAME && FULLNAME=$USER
1168 test -z "$EMAIL" && warn_env EMAIL && EMAIL=$USER
1169 myself=`echo "$FULLNAME <$EMAIL>" | sed "$sed_escape"`
1170 mail_from=`echo "$full_log" | sed "/^From: */!d;s///;s@<MYSELF>@$myself@g"`
1171 ensure_not_empty '"From:" field of the mail' "$mail_from"
1173 # ------------------------------------ #
1174 # Sanity checks on the ChangeLog entry #
1175 # ------------------------------------ #
1177 if echo "$chlog_entry" | grep '<REV>' >/dev/null; then
1178 warn 'Using the tag <REV> anywhere else than in the Subject is deprecated.'
1179 yesno 'Continue anyway?' || return 1
1182 if echo "$chlog_entry" | grep ': \.$' >/dev/null; then
1183 warn 'It looks like you did not fill all entries in the ChangeLog:'
1184 echo "$chlog_entry" | grep ': \.$'
1185 yesno 'Continue anyway?' || return 1
1188 if echo "$chlog_entry" | grep '^--* Internal stuff' >/dev/null; then
1189 warn "It looks like you messed up the delimiters and I did not properly
1190 find your ChangeLog entry. Here it is, make sure it is correct:"
1191 echo "$chlog_entry"
1192 yesno 'Continue anyway?' || return 1
1195 if echo "$chlog_entry" | grep -i 'dont[^a-z0-9]' >/dev/null; then
1196 warn "Please avoid typos such as ${lred}dont$std instead of\
1197 ${lgreen}don't$std:"
1198 echo "$chlog_entry" | grep -n -i 'dont[^a-z0-9]' \
1199 | sed "s/[dD][oO][nN][tT]/$lred&$std/g"
1200 yesno 'Continue anyway?' || return 1
1203 if echo "$chlog_entry" | grep -i 'cant[^a-z0-9]' >/dev/null; then
1204 warn "Please avoid typos such as ${lred}cant$std instead of\
1205 ${lgreen}can't$std:"
1206 echo "$chlog_entry" | grep -n -i 'cant[^a-z0-9]' \
1207 | sed "s/[cC][aA][nN][tT]/$lred&$std/g"
1208 yesno 'Continue anyway?' || return 1
1211 if echo "$chlog_entry" | grep '^.\{80,\}' >/dev/null; then
1212 warn 'Please avoid long lines in your ChangeLog entry (80 columns max):'
1213 echo "$chlog_entry" | grep '^.\{80,\}'
1214 yesno 'Continue anyway?' || return 1
1217 if echo "$mail_title" | grep -i '^[a-z][a-z]*ed[^a-z]' >/dev/null; then
1218 warn 'ChangeLog entries should be written in imperative form:'
1219 echo "$mail_title" | grep -i '^[a-z][a-z]*ed[^a-z]' \
1220 | sed "s/^\\([a-zA-Z][a-zA-Z]*ed\\)\\([^a-zA-Z]\\)/$lred\\1$std\\2/"
1221 yesno 'Continue anyway?' || return 1
1224 # Check whether the user passed -m | --message
1226 while [ $i -lt $# ]; do
1227 arg=$1
1228 # This is not really a reliable way of knowing whether -m | --message was
1229 # passed but hum... Let's assume it'll do :s
1230 if [ x"$arg" = 'x-m' ] || [ x"$arg" = 'x--message' ]; then
1231 my_message=$2
1233 shift
1234 set dummy "$@" "$arg"
1235 shift
1236 i=$((i + 1))
1237 done
1238 if [ x"$my_message" = x ]; then
1239 # The title must not be indented in the commit message and must be
1240 # followed by a blank line. This yields much better results with most
1241 # VC-viewer (especially for Git but including for SVN, such as Trac for
1242 # instance). We assume that the title will always be on the 1st line.
1243 sed_git_title="1s@^[ ]*<TITLE>\\.*@$mail_title\\
1244 @g; $sed_eval_tags"
1245 # First, remove empty lines at the beginning, if any.
1246 # Remove also the date information (useless in commit messages)
1247 my_message=`echo "$raw_chlog_entry" \
1248 | sed -e '1,4 {
1249 /^<YYYY>-<[MD][MD]>-<[DM][DM]>/d
1250 /^[1-9][0-9][0-9][0-9]-[0-9][0-9]*-[0-9][0-9]*/d
1251 /^ and .*<.*@.*>$/d
1252 /^ *$/d
1253 }' \
1254 | sed -e "$sed_git_title" \
1255 -e "$sed_eval_tags; 1{
1256 /^ *$/d
1258 else
1259 notice 'you are overriding the commit message.'
1262 # Show suspicious whitespace additions with Git.
1263 $git_mode && git diff --cached --check
1265 if $dry_run; then
1266 proposal_file=',proposal'
1267 test -f "$proposal_file" \
1268 && proposal_file=`get_unique_file_name "$proposal_file"`
1269 sed_tmp='s/<REV>/???/g;s/\([^.]\) *\.$/\1/'
1270 mail_subject=`echo "$mail_subject" | sed "$sed_eval_tags; $sed_tmp"`
1272 if [ x"$mailto" != xdont_send_mails ] && [ x"$mailto" != xnothx ]; then
1273 to="
1274 To: $mailto"
1276 echo "\
1277 From: $mail_from$to
1278 Subject: $mail_subject
1280 $mail_comment
1282 ChangeLog:
1283 $chlog_entry
1286 $svn_diff_stat
1288 $svn_diff" >"$proposal_file"
1289 notice "A proposal of your commit was left in '$proposal_file'"
1290 return 0
1293 # Are you sure?
1294 if $git_mode; then
1295 $git_commit_all \
1296 || notice 'You are using git, unlike SVN, do not forget to git add your
1297 changes'
1300 # Change edit_changelog so that we're asking the confirmation below.
1301 $use_log_message_from_file && edit_changelog=: \
1302 && notice "You are about to commit the following change:
1303 $mail_title"
1305 $edit_changelog \
1306 && {
1307 yesno 'Are you sure you want to commit?' \
1308 || return 1
1311 # Add the ChangeLog entry
1312 old_chlog=`get_unique_file_name "$change_log_dir/ChangeLog.old"`
1313 mv "$change_log_dir/ChangeLog" "$old_chlog" || \
1314 abort 'Could not backup ChangeLog'
1315 trap "echo SIGINT; mv \"$old_chlog\" \"$change_log_dir/ChangeLog\";
1316 exit 130" $SIGINT
1317 echo "$chlog_entry" >"$change_log_dir/ChangeLog"
1318 echo >>"$change_log_dir/ChangeLog"
1319 cat "$old_chlog" >>"$change_log_dir/ChangeLog"
1321 # Add extra files such as cwd or ChangeLog to the commit.
1322 tmp_sed='s/ /\\ /g' # Escape spaces for the shell.
1323 if $git_mode && $use_changelog; then
1324 # Schedule the ChangeLog for the next commit
1325 (cd "$change_log_dir" && git add ChangeLog) \
1326 || abort 'failed to git add the ChangeLog'
1327 extra_files=
1328 else
1329 extra_files=`echo "$extra_files" | sed "$tmp_sed" | tr ':' '\n'`
1332 # Always sign the commits with Git (but not with git-svn).
1333 $using_git_svn || $git_mode && set dummy --signoff "$@" && shift
1335 # Update the Git index if necessary (just in case the user changed his
1336 # working copy in the mean time)
1337 if $git_mode && $git_commit_all; then
1338 (cd $change_log_dir && git add -v -u) || abort '`git add -v -u` failed'
1341 # --Commit-- finally! :D
1342 $SVN commit -m "$my_message" "$@" $extra_files || {
1343 svn_commit_rv=$?
1344 mv "$old_chlog" "$change_log_dir/ChangeLog"
1345 abort "Commit failed, $SVN returned $svn_commit_rv"
1348 printf 'Getting the revision number... '
1349 if $git_mode; then
1350 REV=`git rev-list --pretty=format:%h HEAD --max-count=1 | sed '1d;q'`
1351 else
1352 svn_info_tmp=`$SVN info "$change_log_dir/ChangeLog"`
1353 REV=`echo "$svn_info_tmp" | sed '/^Revision: /!d;s///'`
1354 test -z "$REV" && REV=`echo "$svn_info_tmp" \
1355 | sed '/^Last Changed Rev: /!d;s///'`
1357 test -z "$REV" && abort 'Cannot detect the current revision.'
1358 echo "$REV"
1360 # Let's make sure we have the real diff by asking the ChangeSet we've just
1361 # committed to the server.
1363 # Backup the old stuff in case we fail to get the real diff from the server
1364 # for some reason...
1365 save_svn_diff=$svn_diff
1366 save_svn_diff_stat=$svn_diff_stat
1368 if $git_mode; then
1369 svn_diff=`git diff --ignore-all-space --no-color -C 'HEAD^' HEAD`
1370 svn_diff_stat=`git diff --stat --ignore-all-space --no-color -C 'HEAD^' HEAD`
1371 $using_git_svn &&
1372 notice 'Do not forget to use `git-svn dcommit` to push your commits in SVN'
1373 else
1374 # Fetch the ChangeSet and filter out the ChangeLog entry. We don't use
1375 # svn diff -c because this option is not portable to older svn versions.
1376 REV_MINUS_ONE=$((REV - 1))
1377 svn_diff=`svn_diffw -r"$REV_MINUS_ONE:$REV" "$repos_root" \
1378 | $AWK '/^Index: / { if (in_chlog) in_chlog = 0; }
1379 /^Index: .*ChangeLog$/ { in_chlog = 1 }
1380 { if (!in_chlog) print }'`
1381 if [ x"$svn_diff" = x ]; then
1382 svn_diff=$save_svn_diff
1383 svn_diff_stat=$save_svn_diff_stat
1384 else
1385 if require_diffstat; then
1386 svn_diff_stat=`echo "$svn_diff" | diffstat`
1387 else
1388 svn_diff_stat='diffstat not available'
1393 # Expand <REV> and remove the final period from the mail subject if there is
1394 # only one period.
1395 sed_tmp="s/<REV>/$REV/g;"'s/\([^.]\) *\.$/\1/'
1396 mail_subject=`echo "$mail_subject" | sed "$sed_eval_tags; $sed_tmp"`
1398 mail_file=`get_unique_file_name "$change_log_dir/+mail"`
1399 echo "\
1400 From: $mail_from
1401 To: $mail_to
1402 Subject: $mail_subject
1404 $mail_comment
1406 ChangeLog:
1407 $chlog_entry
1410 $svn_diff_stat
1412 $svn_diff" | sed 's/^\.$/ ./' >"$mail_file"
1413 # We change lines with only a `.' because they could mean "end-of-mail"
1415 # Send the mail
1416 if $send_a_mail; then
1417 trap 'echo SIGINT; exec < /dev/null' $SIGINT
1418 # FIXME: Move the mail to the +committed right now, in case the user
1419 # CTLR+C the mail-sending-thing, so that the mail will be properly saved
1420 # their.
1421 my_sendmail "$mail_file" "$mail_subject" "$mail_to" \
1422 "X-svn-url: $repos_root
1423 X-svn-revision: $REV"
1424 fi # end do we have to send a mail?
1425 rm -f "$tmp_log"
1426 rm -f "$old_chlog"
1427 save_mail_file=`echo "$mail_file" | sed 's/+//'`
1428 mkdir -p "$change_log_dir/+committed" \
1429 || warn "Couldn't mkdir -p $change_log_dir/+committed"
1430 if [ -d "$change_log_dir/vcs" ] \
1431 || [ -d "$change_log_dir/+committed" ]
1432 then
1433 mkdir -p "$change_log_dir/+committed/$REV" \
1434 && mv "$mail_file" "$change_log_dir/+committed/$REV/mail"
1436 return $svn_commit_rv
1439 # svn_diffw [args...]
1440 svn_diff()
1442 # Ignore white spaces.
1443 if $git_mode; then
1444 git diff -C "$@"
1445 else
1446 diffarg='-u'
1447 # Can't svn diff -x -w: svn 1.4 only.
1448 # Moreover we *MUST* use -x -uw, not -x -u -w or -x -u -x -w ...
1449 # Hence the hack to stick both diff arguments together...
1450 # No comment :)
1451 test x"$1" = x'--SVNW-HACK-w' && shift && diffarg='-uw'
1452 $SVN diff --no-diff-deleted --diff-cmd $DIFF -x $diffarg "$@"
1456 # svn_diffw [args...]
1457 svn_diffw()
1459 # Ignore white spaces.
1460 if $git_mode; then
1461 svn_diff --ignore-all-space "$@"
1462 else
1463 svn_diff --SVNW-HACK-w "$@"
1467 # svn_mail REV [mails...]
1468 svn_mail()
1470 test $# -lt 1 && abort "Not enough arguments provided;
1471 Try 'svn help mail' for more info."
1472 case $1 in
1473 PREV)
1474 if $git_mode; then
1475 REV=`git rev-list --pretty=format:%h 'HEAD^' --max-count=1 | sed '1d;q'`
1476 else
1477 REV=`svn_revision || abort 'Cannot get current revision number'`
1478 test -z "$REV" && abort 'Cannot get current revision number'
1479 if [ "$REV" -lt 1 ]; then
1480 abort 'No previous revision.'
1482 REV=$((REV - 1))
1485 HEAD)
1486 REV=`svn_revision || abort 'Cannot get current revision number'`
1487 test -z "$REV" && abort 'Cannot get current revision number'
1489 *) REV=$1;;
1490 esac
1491 shift
1493 found_committed=0; found=0
1494 while [ $found -eq 0 ]; do
1495 this_chlog_dir=`pwd -P`
1496 if [ -d ./+committed ]; then
1497 found_committed=1
1498 if [ -d ./+committed/$REV ]; then
1499 found=1
1500 else
1501 cd ..
1503 else
1504 cd ..
1506 # Stop searching when in / ... hmz :P
1507 test x`pwd` = x/ && break
1508 done
1509 if [ $found -eq 0 ]; then
1510 if [ $found_committed -eq 0 ]; then
1511 abort 'Could not find the +committed directory.'
1512 else
1513 abort "Could not find the revision $REV in +committed."
1515 abort 'Internal error (should never be here).'
1518 mail_file=; subject=; to=
1519 if [ -f ./+committed/$REV/mail ]; then
1520 # svn-wrapper generated file
1521 mail_file="./+committed/$REV/mail"
1522 subject=`sed '/^Subject: /!d;s///' $mail_file | sed '1q'`
1523 to=`sed '/^To: /!d;s///' $mail_file | sed '1q'`
1524 elif [ -f ./+committed/$REV/,iform ] && [ -f ./+committed/$REV/,message ]
1525 then
1526 # VCS-generated file
1527 subject=`sed '/^Subject: /!d;s///;s/^"//;s/"$//' ./+committed/$REV/,iform \
1528 | sed "s/<%= *rev *%>/$REV/g"`
1529 to=`sed '/^To:/,/^[^-]/!d' ./+committed/$REV/,iform | sed '1d;s/^- //;$d' \
1530 | xargs | sed 's/ */, /g'`
1531 mail_file=`get_unique_file_name "$TMPDIR/mail.r$REV"`
1532 echo "From: $FULLNAME <$EMAIL>
1533 To: $to
1534 Subject: $subject
1535 " >"$mail_file" || abort "Cannot create $mail_file"
1536 cat ./+committed/$REV/,message >>"$mail_file" \
1537 || abort "Cannot copy ./+committed/$REV/,message in $mail_file"
1538 else
1539 abort "Couldn't find the mail to re-send in `pwd`/+committed/$REV"
1541 if [ $# -gt 0 ]; then
1542 to=`echo "$*" | sed 's/ */, /g'`
1545 test -z "$to" && abort 'Cannot find the list of recipients.
1546 Please report this bug.'
1547 test -z "$subject" && abort 'Cannot find the subject of the mail.
1548 Please report this bug.'
1550 if yesno "Re-sending the mail of r$REV
1551 Subject: $subject
1552 To: $to
1553 Are you sure?"; then :; else
1554 return 1
1557 if $git_mode; then
1558 git_get_repos_info_
1559 else
1560 svn_info_tmp=`$SVN info`
1561 test $? -ne 0 && abort "Failed to get svn info on `pwd`"
1562 repos_root=`echo "$svn_info_tmp" | sed '/^Repository Root: /!d;s///'`
1563 repos_url=`echo "$svn_info_tmp" | sed '/^URL: /!d;s///'`
1564 # It looks like svn <1.3 didn't display a "Repository Root" entry.
1565 test -z "$repos_root" && repos_root=$repos_url
1568 my_sendmail "$mail_file" "$subject" "$to" \
1569 "X-svn-url: $repos_url
1570 X-svn-revision: $REV"
1573 # svn_version
1574 svn_version()
1576 echo "Using svn-wrapper v$version-g$revision (C) SIGOURE Benoit [GPL]"
1579 # has_prop prop-name [path]
1580 # return value: 0 -> path has the property prop-name set.
1581 # 1 -> path has no property prop-name.
1582 # 2 -> svn error.
1583 has_prop()
1585 hp_plist=`$SVN proplist "$2"`
1586 test $? -ne 0 && return 2
1587 hp_res=`echo "$hp_plist" | sed "/^ *$1\$/!d"`
1588 test -z "$hp_res" && return 1
1589 return 0
1592 # svn_propadd prop-name prop-val [path]
1593 svn_propadd()
1595 if $git_mode; then
1596 abort 'propadd is only for SVN, not for Git.'
1598 test $# -lt 2 \
1599 && abort 'Not enough arguments provided;
1600 try `svn help propadd` for more info'
1601 test $# -gt 3 \
1602 && abort 'Too many arguments provided;
1603 try `svn help propadd` for more info'
1605 path=$3
1606 test -z "$path" && path='.' && set dummy "$@" '.' && shift
1607 has_prop "$1" "$3" || {
1608 test $? -eq 2 && return 1 # svn error
1609 # no property found:
1610 yesno "'$path' has no property named '$1', do you want to add it?" \
1611 && $SVN propset "$@"
1612 return $?
1615 current_prop_val=`$SVN propget "$1" "$3"`
1616 test $? -ne 0 && abort "Failed to get the current value of property '$1'."
1618 $SVN propset "$1" "$current_prop_val
1619 $2" "$3" >/dev/null || abort "Failed to add '$3' in the property '$1'."
1621 current_prop_val=`$SVN propget "$1" "$3" || echo "$current_prop_val
1622 $2"`
1623 echo "property '$1' updated on '$path', new value:
1624 $current_prop_val"
1627 # svn_propsed prop-name sed-script [path]
1628 svn_propsed()
1630 if $git_mode; then
1631 abort 'propsed is only for SVN, not for Git.'
1633 test $# -lt 2 \
1634 && abort 'Not enough arguments provided;
1635 try `svn help propsed` for more info'
1636 test $# -gt 3 \
1637 && abort 'Too many arguments provided;
1638 try `svn help propsed` for more info'
1640 path=$3
1641 test -z "$path" && path='.'
1642 has_prop "$1" "$3" || {
1643 test $? -eq 2 && return 1 # svn error
1644 # no property found:
1645 abort "'$path' has no property named '$1'."
1648 prop_val=`$SVN propget "$1" "$3"`
1649 test $? -ne 0 && abort "Failed to get the current value of property '$1'."
1651 prop_val=`echo "$prop_val" | sed "$2"`
1652 test $? -ne 0 && abort "Failed to run the sed script '$2'."
1654 $SVN propset "$1" "$prop_val" "$3" >/dev/null \
1655 || abort "Failed to update the property '$1' with value '$prop_val'."
1657 new_prop_val=`$SVN propget "$1" "$3" || echo "$prop_val"`
1658 echo "property '$1' updated on '$path', new value:
1659 $new_prop_val"
1662 # svn_revision [args...]
1663 svn_revision()
1665 if $git_mode; then
1666 short=`git rev-list --pretty=format:%h HEAD --max-count=1 | sed '1d;q'`
1667 long=`git rev-list --pretty=format:%H HEAD --max-count=1 | sed '1d;q'`
1668 echo "$short ($long)"
1669 else
1670 svn_revision_info_out=`$SVN info "$@"`
1671 svn_revision_rv=$?
1672 echo "$svn_revision_info_out" | sed '/^Revision: /!d;s///'
1673 return $svn_revision_rv
1677 # svn_ignore [paths]
1678 svn_ignore()
1680 if [ $# -eq 0 ]; then # Simply display ignore-list.
1681 if $git_mode; then
1682 test -f .gitignore && cat .gitignore
1683 else
1684 $SVN propget 'svn:ignore'
1686 elif [ $# -eq 1 ]; then
1687 b=`basename "$1"`
1688 d=`dirname "$1"`
1689 if $git_mode; then
1690 echo "$b" >>"$d/.gitignore"
1691 git add "$d/.gitignore"
1692 notice 'files ignored in this directory:'
1693 cat "$d/.gitignore"
1694 else
1695 svn_propadd 'svn:ignore' "$b" "$d"
1697 else # Add arguments in svn:ignore.
1698 # This part is a bit tricky:
1699 # For each argument, we find all the other arguments with the same dirname
1700 # $dname and we svn:ignore them all in $dname.
1701 while [ $# -ne 0 ]; do
1702 arg=$1
1703 dname=`dirname "$1"`
1704 files=`basename "$1"`
1705 shift
1706 j=0; argc=$#
1707 while [ $j -lt $argc ] && [ $# -ne 0 ]; do
1708 this_arg=$1
1709 shift
1710 this_dname=`dirname "$this_arg"`
1711 this_file=`basename "$this_arg"`
1712 if [ x"$dname" = x"$this_dname" ]; then
1713 files="$files
1714 $this_file"
1715 else
1716 set dummy "$@" "$this_arg"
1717 shift
1719 j=$((j + 1))
1720 done
1721 if $git_mode; then
1722 echo "$files" >>"$dname"/.gitignore
1723 git add "$dname"/.gitignore
1724 notice "files ignored in $dname:"
1725 cat "$dname"/.gitignore
1726 else
1727 svn_propadd 'svn:ignore' "$files" "$dname"
1729 done
1733 # svn_help
1734 svn_help()
1736 if [ $# -eq 0 ]; then
1737 svn_version
1738 $SVN help
1739 rv=$?
1740 echo '
1741 Additionnal commands provided by svn-wrapper:
1742 diffstat (ds)
1743 diffw (dw)
1744 ignore
1745 mail
1746 propadd (padd, pa) -- SVN only
1747 proposal
1748 propsed (psed) -- SVN only
1749 revision (rev)
1750 touch
1751 selfupdate (selfup)
1752 version'
1753 return $rv
1754 else
1755 case $1 in
1756 commit | ci)
1757 $SVN help commit | sed '/^Valid options:/i\
1758 Extra options provided by svn-wrapper:\
1759 \ --dry-run : do not commit, simply generate a patch with what\
1760 \ would have been comitted.\
1761 \ --use-log-file FILE : extract the ChangeLog entry from FILE. This\
1762 \ entry must be formated in a similar fashion to\
1763 \ what svn-wrapper usually asks you to fill in.\
1764 \ The FILE needs to be writable and will be\
1765 \ removed by svn-wrapper upon success.\
1769 diffstat | ds)
1770 require_diffstat
1771 echo 'diffstat (ds): Display the histogram from svn diff-output.'
1772 $SVN help diff | sed '1d;
1773 s/differences*/histogram/;
1774 2,35 s/diff/diffstat/g'
1776 diffw | dw)
1777 echo "diffw (dw): Display the differences without taking whitespaces\
1778 into account."
1779 $SVN help diff | sed '1d;
1780 2,35 s/diff\([^a-z]\)/diffw\1/g;
1781 /--diff-cmd/,/--no-diff-deleted/d'
1783 ignore)
1784 what='svn:ignore property'
1785 $git_mode && what='.gitignore file'
1786 cat <<EOF
1787 ignore: Add some files in the $what.
1788 usage: 1. ignore [PATH]
1789 2. ignore FILE [FILES...]
1791 1. Display the value of $what on [PATH].
1792 2. Add some files in the $what of the directory containing them.
1794 When adding ignores, each pattern is ignored in its own directory, e.g.:
1795 $bme ignore dir/file "d2/*.o"
1796 Will put 'file' in the $what of 'dir' and '*.o' in the
1797 $what of 'd2'
1799 Valid options:
1800 None.
1803 mail)
1804 echo 'mail: Resend the mail of a given commit.
1805 usage: mail REV [emails]
1807 REV must have an email file associated in +committed/REV.
1808 REV can also be PREV or HEAD.
1810 By default the mail is sent to same email addresses as during the original
1811 commit unless more arguments are given.'
1813 propadd | padd | pa)
1814 echo 'propadd (padd, pa): Add something in the value of a property.
1815 usage: propadd PROPNAME PROPVAL PATH
1816 This command only works in SVN mode.
1818 PROPVAL will be appended at the end of the property PROPNAME.
1820 Valid options:
1821 None.'
1823 proposal)
1824 echo 'proposal: Alias for: commit --dry-run.
1825 See: svn help commit.'
1827 propsed | psed)
1828 echo 'propsed (psed): Edit a property with sed.
1829 usage: propsed PROPNAME SED-ARGS PATH
1830 This command only works in SVN mode.
1832 eg: svn propsed svn:externals "s/http/https/" .
1834 Valid options:
1835 None.'
1837 revision | rev)
1838 echo 'revision (rev): Display the revision number of a local or remote item.'
1839 $SVN help info | sed '1d;
1840 s/information/revision/g;
1841 s/revision about/the revision of/g;
1842 2,35 s/info/revision/g;
1843 /-xml/d'
1845 touch)
1846 echo 'touch: Touch a file and svn add it.
1847 usage: touch FILE [FILES]...
1849 Valid options:
1850 None.'
1852 selfupdate | selfup | self-update | self-up)
1853 echo 'selfupdate (selfup): Attempt to update svn-wrapper.sh
1854 usage: selfupdate
1856 Valid options:
1857 None.'
1859 version)
1860 echo 'version: Display the version info of svn and svn-wrapper.
1861 usage: version
1863 Valid options:
1864 None.'
1866 *) $SVN help "$@";;
1867 esac
1871 # svn_status [args...]
1872 svn_status()
1874 if $git_mode; then
1875 git status "$@"
1876 return $?
1878 svn_status_out=`$SVN status "$@"`
1879 svn_status_rv=$?
1880 test -z "$svn_status_out" && return $svn_status_rv
1881 echo "$svn_status_out" | sed "$sed_svn_st_color"
1882 return $svn_status_rv
1885 # svn_update [args...]
1886 svn_update()
1888 svn_update_out=`$SVN update "$@"`
1889 svn_update_rv=$?
1890 echo "$svn_update_out" | sed "$sed_svn_up_colors"
1891 return $svn_update_rv
1894 # ------------------- #
1895 # `main' starts here. #
1896 # ------------------- #
1898 # Define colors if stdout is a tty.
1899 if test -t 1; then
1900 set_colors
1901 else # stdout isn't a tty => don't print colors.
1902 set_nocolors
1905 # Consider this as a sed function :P.
1906 sed_svn_st_color="
1907 t dummy_sed_1
1908 : dummy_sed_1
1909 s@^?\\(......\\)+@+\\1+@
1910 s@^?\\(......\\)\\(.*/\\)+@+\\1\\2+@
1911 s@^?\\(......\\),@,\\1,@
1912 s@^?\\(......\\)\\(.*/\\),@,\\1\\2,@
1913 s/^\\(.\\)C/\\1${lred}C${std}/
1914 t dummy_sed_2
1915 : dummy_sed_2
1916 s/^?/${lred}?${std}/; t
1917 s/^M/${lgreen}M${std}/; t
1918 s/^A/${lgreen}A${std}/; t
1919 s/^X/${lblue}X${std}/; t
1920 s/^+/${lyellow}+${std}/; t
1921 s/^D/${lyellow}D${std}/; t
1922 s/^,/${lred},${std}/; t
1923 s/^C/${lred}C${std}/; t
1924 s/^I/${purple}I${std}/; t
1925 s/^R/${lblue}R${std}/; t
1926 s/^!/${lred}!${std}/; t
1927 s/^~/${lwhite}~${std}/; t"
1929 sed_svn_up_colors="
1930 t dummy_sed_1
1931 : dummy_sed_1
1933 /^Updated/ t
1934 /^Fetching/ t
1935 /^External/ t
1936 s/^\\(.\\)C/\\1${lred}C${std}/
1937 s/^\\(.\\)U/\\1${lgreen}U${std}/
1938 s/^\\(.\\)D/\\1${lred}D${std}/
1939 t dummy_sed_2
1940 : dummy_sed_2
1941 s/^A/${lgreen}A${std}/; t
1942 s/^U/${lgreen}U${std}/; t
1943 s/^D/${lyellow}D${std}/; t
1944 s/^G/${purple}G${std}/; t
1945 s/^C/${lred}C${std}/; t"
1947 # For dev's:
1948 test "x$1" = x--debug && shift && set -x
1950 test "x$1" = x--git && shift && git_mode=: && SVN=git
1952 test "x$1" = x--no-changelog && shift && use_changelog=false
1954 case $1 in
1955 # ------------------------------- #
1956 # Hooks for standard SVN commands #
1957 # ------------------------------- #
1958 commit | ci)
1959 shift
1960 svn_commit "$@"
1962 help | \? | h)
1963 shift
1964 svn_help "$@"
1966 status | stat | st)
1967 shift
1968 svn_status "$@"
1970 update | up)
1971 shift
1972 svn_update "$@"
1974 # -------------------- #
1975 # Custom SVN commands #
1976 # -------------------- #
1977 diffstat | ds)
1978 shift
1979 if [ -d .git ]; then
1980 git diff --stat -C
1981 else
1982 require_diffstat && $SVN diff --no-diff-deleted "$@" | diffstat
1985 diff | di)
1986 shift
1987 DIFF=$COLORDIFF
1988 svn_diff "$@"
1990 diffw | dw)
1991 shift
1992 DIFF=$COLORDIFF
1993 svn_diffw "$@"
1995 ignore)
1996 shift
1997 svn_ignore "$@"
1999 log)
2000 if $git_mode; then # let Git handle log
2001 exec $SVN "$@"
2002 else # pipe svn log through PAGER by default
2003 exec $SVN "$@" | $PAGER
2006 mail)
2007 shift
2008 svn_mail "$@"
2010 propadd | padd | pa)
2011 shift
2012 svn_propadd "$@"
2014 proposal)
2015 shift
2016 svn_commit --dry-run "$@"
2018 propsed | psed)
2019 shift
2020 svn_propsed "$@"
2022 revision | rev)
2023 shift
2024 svn_revision "$@"
2026 touch)
2027 shift
2028 touch "$@" && $SVN add "$@"
2030 selfupdate | selfup | self-update | self-up)
2031 shift
2032 selfupdate "$@"
2034 version | -version | --version)
2035 shift
2036 set dummy '--version' "$@"
2037 shift
2038 svn_version
2039 exec $SVN "$@"
2041 *) exec $SVN "$@"
2043 esac