Properly set $using_git_svn.
[svn-wrapper.git] / svn-wrapper.sh
bloba40eea1b950c77cfea6d79cf2c9096ed4dd7012b
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 using_git_svn=false
87 case $SVN in
88 git*) git_mode=:;;
89 '')
90 if [ -d .git ] || [ -d ../.git ]; then
91 SVN=git
92 git_mode=:
93 else
94 SVN=svn
97 esac
99 : ${EDITOR=missing}
100 export EDITOR
101 : ${GPG=gpg}
102 : ${TMPDIR=/tmp}
103 export TMPDIR
104 : ${PAGER=missing}
105 export PAGER
106 # Override the locale.
107 LC_ALL='C'
108 export LC_ALL
109 : ${AWK=missing}
110 : ${DIFF=missing}
111 : ${COLORDIFF=missing}
113 # Signal number for traps (using plain signal names is not portable and breaks
114 # on recent Debians that use ash as their default shell).
115 SIGINT=2
117 me=$0
118 bme=`basename "$0"`
120 # Pitfall: some users might be tempted to export SVN=svn-wrapper.sh for some
121 # reason. This is just *wrong*. The following is an attempt to save them from
122 # some troubles.
123 if [ x`basename "$SVN"` = x"$bme" ]; then
124 echo "warning: setting SVN to $bme is wrong"
125 SVN='svn'
128 # This code comes (mostly) from Autoconf.
129 # The user is always right.
130 if test "${PATH_SEPARATOR+set}" != set; then
131 PATH_SEPARATOR=:
132 (PATH='/bin;/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 && {
133 (PATH='/bin:/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 ||
134 PATH_SEPARATOR=';'
138 version='0.4'
139 full_rev='$Id$'
140 # Get the first 6 digits (without forking)
141 revision=${full_rev#'$'Id': '}
142 revision=${revision%??????????????????????????????????' $'}
144 # The `main' really starts after the functions definitions.
146 # ---------------- #
147 # Helper functions #
148 # ---------------- #
150 set_colors()
152 red='\e[0;31m'; lred='\e[1;31m'
153 green='\e[0;32m'; lgreen='\e[1;32m'
154 yellow='\e[0;33m'; lyellow='\e[1;33m'
155 blue='\e[0;34m'; lblue='\e[1;34m'
156 purple='\e[0;35m'; lpurple='\e[1;35m'
157 cyan='\e[0;36m'; lcyan='\e[1;36m'
158 grey='\e[0;37m'; lgrey='\e[1;37m'
159 white='\e[0;38m'; lwhite='\e[1;38m'
160 std='\e[m'
163 set_nocolors()
165 red=; lred=
166 green=; lgreen=
167 yellow=; lyellow=
168 blue=; lblue=
169 purple=; lpurple=
170 cyan=; lcyan=
171 grey=; lgrey=
172 white=; lwhite=
173 std=
176 # abort err-msg
177 abort()
179 echo "svn-wrapper: ${lred}abort${std}: $@" \
180 | sed '1!s/^[ ]*/ /' >&2
181 exit 1
184 # warn msg
185 warn()
187 echo "svn-wrapper: ${lred}warning${std}: $@" \
188 | sed '1!s/^[ ]*/ /' >&2
191 # notice msg
192 notice()
194 echo "svn-wrapper: ${lyellow}notice${std}: $@" \
195 | sed '1!s/^[ ]*/ /' >&2
198 # yesno question
199 yesno()
201 printf "$@ [y/N] "
202 read answer || return 1
203 case $answer in
204 y* | Y*) return 0;;
205 *) return 1;;
206 esac
207 return 42 # should never happen...
210 # yesnewproceed what
211 # returns true if `yes' or `proceed', false if `new'.
212 # the answer is stored in $yesnoproceed_res which is /yes|new|proceed/
213 yesnewproceed()
215 printf "$@ [(y)es/(p)roceed/(u)p+proceed/(N)ew] "
216 read answer || return 1
217 case $answer in
218 y* | Y*) yesnoproceed_res=yes; return 0;;
219 p* | P*) yesnoproceed_res=proceed; return 0;;
220 u* | U*) yesnoproceed_res=upproceed; return 0;;
221 *) yesnoproceed_res=new; return 1;;
222 esac
223 return 42 # should never happen...
226 # warn_env env-var
227 warn_env()
229 warn "cannot find the environment variable $1
230 You might consider using \`export $1='<FIXME>'\`"
233 # get_unique_file_name file-name
234 get_unique_file_name()
236 test -e "$1" || {
237 echo "$1" && return 0
239 gufn=$1; i=1
240 while test -e "$gufn.$i"; do
241 i=$((i + 1))
242 done
243 echo "$gufn.$i"
246 # ensure_not_empty description value
247 ensure_not_empty()
249 case $2 in #(
250 # `value' has at least one non-space character.
251 *[-a-zA-Z0-9_!@*{}|,./:]*)
252 return;;
253 esac
254 ene_val=`echo "$2" | tr -d ' \t\n'`
255 test -z "$ene_val" && abort "$1: empty value"
258 # find_prog prog-name
259 # return true if prog-name is in the PATH
260 # echo the full path to prog-name on stdout.
261 # Based on a code from texi2dvi
262 find_prog()
264 save_IFS=$IFS
265 IFS=$PATH_SEPARATOR
266 for dir in $PATH; do
267 IFS=$save_IFS
268 test -z "$dir" && continue
269 # The basic test for an executable is `test -f $f && test -x $f'.
270 # (`test -x' is not enough, because it can also be true for directories.)
271 # We have to try this both for $1 and $1.exe.
273 # Note: On Cygwin and DJGPP, `test -x' also looks for .exe. On Cygwin,
274 # also `test -f' has this enhancement, bot not on DJGPP. (Both are
275 # design decisions, so there is little chance to make them consistent.)
276 # Thusly, it seems to be difficult to make use of these enhancements.
278 if test -f "$dir/$1" && test -x "$dir/$1"; then
279 echo "$dir/$1"
280 return 0
281 elif test -f "$dir/$1.exe" && test -x "$dir/$1.exe"; then
282 echo "$dir/$1.exe"
283 return 0
285 done
286 return 1
289 # find_progs prog [progs...]
290 # Look in PATH for one of the programs given in argument.
291 # If none of the progs can be found, the string "exit 2" is "returned".
292 find_progs()
294 # This code comes mostly from Autoconf.
295 for fp_prog in "$@"; do
296 fp_res=`find_prog $fp_prog`
297 if [ $? -eq 0 ]; then
298 echo "$fp_res"
299 return 0
301 done
302 echo "exit 2"
303 return 1
306 test x"$EDITOR" = xmissing && EDITOR=`find_progs vim vi emacs nano`
307 test x"$PAGER" = xmissing && PAGER=`find_progs less more`
308 test x"$AWK" = xmissing && AWK=`find_progs gawk mawk nawk awk`
309 test x"$DIFF" = xmissing && DIFF=`find_progs diff`
310 if test x"$COLORDIFF" = xmissing; then
311 if test -t 1; then
312 COLORDIFF=`find_progs colordiff diff`
313 else
314 COLORDIFF=$DIFF
318 # -R will tell less to interpret some terminal codes, which will turn on
319 # colors.
320 case $PAGER in #(
321 less) PAGER='less -R';;
322 esac
324 # require_diffstat
325 # return true if diffstat is in the PATH
326 require_diffstat()
328 if [ x"$require_diffstat_cache" != x ]; then
329 return $require_diffstat_cache
331 if (echo | diffstat) >/dev/null 2>/dev/null; then :; else
332 warn 'diffstat is not installed on your system or not in your PATH.'
333 test -f /etc/debian_version \
334 && notice 'you might want to `apt-get install diffstat`.'
335 require_diffstat_cache=1
336 return 1
338 require_diffstat_cache=0
339 return 0
342 # require_mail
343 # return 0 -> found a mailer
344 # return !0 -> no mailer found
345 # The full path to the program found is echo'ed on stdout.
346 require_mail()
348 save_PATH=$PATH
349 PATH="${PATH}${PATH_SEPARATOR}/sbin${PATH_SEPARATOR}/usr/sbin${PATH_SEPARATOR}/usr/libexec"
350 export PATH
351 find_progs sendEmail sendmail mail
352 rv=$?
353 PATH=$save_PATH
354 export PATH
355 return $rv
358 # my_sendmail mail-file mail-subject mail-to [extra-headers]
359 # mail-to is a comma-separated list of email addresses.
360 # extra-headers is an optionnal argument and will be prepended at the
361 # beginning of the mail headers if the tool used to send mails supports it.
362 # The mail-file may also contain headers. They must be separated from the body
363 # of the mail by a blank line.
364 my_sendmail()
366 test -f "$1" || abort "my_sendmail: Cannot find the mail file: $1"
367 test -z "$2" && warn 'my_sendmail: Empty subject.'
368 test -z "$3" && abort 'my_sendmail: No recipient specified.'
370 content_type='Content-type: text/plain'
371 extra_headers="X-Mailer: svn-wrapper v$version (g$revision)
372 Mime-Version: 1.0
373 Content-Transfer-Encoding: 7bit"
374 if test x"$4" != x; then
375 extra_headers="$4
376 $extra_headers"
377 # Remove empty lines.
378 extra_headers=`echo "$extra_headers" | sed '/^[ ]*$/d;s/^[ ]*//'`
381 # If we have a signature, add it.
382 if test -f ~/.signature; then
383 # But don't add it if it is already in the mail.
384 if grep -Fe "`cat ~/.signature`" "$1" >/dev/null; then :; else
385 echo '-- ' >>"$1" && cat ~/.signature >>"$1"
388 # VCS-compat: handle user option 'sign'.
389 if (grep '^sign: false' ~/.vcs) >/dev/null 2>/dev/null; then :; else
390 ($GPG -h) >/dev/null 2>/dev/null
391 gpg_rv=$?
392 if [ -e ~/.gnupg ] || [ -e ~/.gpg ] || [ -e ~/.pgp ] && [ $gpg_rv -lt 42 ]
393 then
394 if grep 'BEGIN PGP SIGNATURE' "$1" >/dev/null; then
395 notice 'message is already GPG-signed'
396 elif yesno "Sign the mail using $GPG ?"; then
397 # Strip the headers
398 sed '1,/^$/d' "$1" >"$1.msg"
399 sed '1,/^$/!d' "$1" >"$1.hdr"
400 # Sign the message, keep only the PGP signature.
401 $GPG --clearsign <"$1.msg" >"$1.tmp" || {
402 rm -f "$1.msg" "$1.hdr" "$1.tmp"
403 abort "\`$GPG' failed (r=$?)"
405 sed '/^--*BEGIN PGP SIGNATURE--*$/,/^--*END PGP SIGNATURE--*$/!d' \
406 "$1.tmp" >"$1.sig"
408 boundary="svn-wrapper-2-$RANDOM"
409 boundary="$boundary$RANDOM"
410 # Prepend some stuff before the PGP signature.
411 echo "
412 --$boundary
413 content-type: application/pgp-signature; x-mac-type=70674453;
414 name=PGP.sig
415 content-description: This is a digitally signed message part
416 content-disposition: inline; filename=PGP.sig
417 content-transfer-encoding: 7bit" | cat - "$1.sig" >"$1.tmp"
418 mv -f "$1.tmp" "$1.sig"
420 # Append some stuff after the PGP signature.
421 echo "
422 --$boundary--" >>"$1.sig"
423 # Re-paste the headers before the signed body and prepend some stuff.
424 echo "This is an OpenPGP/MIME signed message (RFC 2440 and 3156)
425 --$boundary
426 Content-Transfer-Encoding: 8bit
427 Content-Type: text/plain; charset=iso-8859-1; format=flowed
429 | cat "$1.hdr" - "$1.msg" "$1.sig" >"$1.tmp" \
430 && mv -f "$1.tmp" "$1"
431 content_type="Content-Type: multipart/signed;\
432 protocol=\"application/pgp-signature\"; micalg=pgp-sha1;\
433 boundary=\"$boundary\""
434 # Cleanup.
435 rm -f "$1.tmp" "$1.sig" "$1.msg" "$1.hdr"
436 extra_headers="$extra_headers
437 X-Pgp-Agent: `$GPG --version | sed q`"
441 extra_headers="$extra_headers
442 $content_type"
444 mailer=`require_mail`
445 if [ $? -ne 0 ]; then
446 warn 'my_sendmail: No suitable mailer found.'
447 return 1
449 case $mailer in
450 */sendmail)
451 to=`echo "$3" | sed 's/,//g'`
452 echo "$extra_headers" | cat - "$1" | $mailer $to;;
453 */mail)
454 cat "$1" | $mailer -s "$2" "$3";;
455 */sendEmail)
456 if [ x"$SMTP" = x ]; then
457 warn 'my_sendmail: (sendEmail) please tell me the SMTP server to use'
458 printf 'STMP server: '
459 read SMTP || abort 'could not read the SMTP server'
460 notice "hint: you can export SMTP=$SMTP if you don't want to be asked"
462 sendEmail -f "$FULLNAME <$EMAIL>" -t "$3" -u "$2" \
463 -s "$SMTP" -o message-file="$1";;
464 *) # wtf
465 abort 'my_sendmail: Internal error.';;
466 esac
469 # selfupdate
470 selfupdate()
472 my_url='http://www.lrde.epita.fr/~sigoure/svn-wrapper'
473 # You can use https if you feel paranoiac.
475 echo ">>> Fetching svn-wrapper.sh from $my_url/svn-wrapper.sh"
477 # --------------------- #
478 # Fetch the new version #
479 # --------------------- #
481 tmp_me=`get_unique_file_name "$TMPDIR/svn-wrapper.sh"`
482 if (wget --help) >/dev/null 2>/dev/null; then
483 wget --no-check-certificate "$my_url/svn-wrapper.sh" -O "$tmp_me"
484 my_wget='wget'
485 else
486 curl --help >/dev/null 2>/dev/null
487 if [ $? -gt 42 ]; then
488 abort 'Cannot find wget or curl.
489 How can I download any update without them?'
491 my_wget='curl'
492 curl --insecure "$my_url/svn-wrapper.sh" >"$tmp_me"
495 test -r $tmp_me \
496 || abort "Cannot find the copy of myself I downloaded in $tmp_me"
498 # ---------------------------------------- #
499 # Compare versions and update if necessary #
500 # ---------------------------------------- #
502 my_ver=$revision
503 tmp_ver=`sed '/^# $Id[:].*$/!d;
504 s/.*$Id[:] *\([a-f0-9]\{6\}\).*/\1/' "$tmp_me"`
505 test -z "$tmp_ver" && abort "Cannot find the revision of $tmp_me"
506 if [ x"$my_ver" != x"$tmp_ver" ]; then # There IS an update...
507 echo "An update is available, r$tmp_ver (your version is r$my_ver)"
509 # Wanna see the diff?
510 if yesno 'Do you want to see the diff?'
511 then
512 (require_diffstat && diff -uw "$me" "$tmp_me" | diffstat;
513 echo
514 $COLORDIFF -uw "$me" "$tmp_me") | $PAGER
517 # Let's go :)
518 if yesno "Overwrite $me (r$my_ver) with $tmp_me (r$tmp_ver)?"; then
519 chmod a+x "$tmp_me"
520 cp -p "$me" "$me.r$my_ver"
521 mv "$tmp_me" "$me" && exit 0
523 rm -f "$tmp_me"
524 return 1
525 else
526 echo "You're already up to date [r$my_ver] :)"
528 rm -f "$tmp_me"
531 # get_svn_diff_and_diffstat [files to diff]
532 # Helper for svn_commit
533 get_svn_diff_and_diffstat()
535 if $git_mode; then
536 svn_diff=`git diff --ignore-all-space --no-color -B -C --cached`
537 svn_diff_stat=`git diff --stat --ignore-all-space --no-color -B -C --cached`
538 else
539 # Ignore white spaces. Can't svn diff -x -w: svn 1.4 only.
540 svn_diff=`svn_diffw "$@"`
541 test -z "$svn_diff" && svn_diff=`$SVN diff "$@"`
542 if require_diffstat; then
543 svn_diff_stat=`echo "$svn_diff" | diffstat`
544 else
545 svn_diff_stat='diffstat not available'
550 # Helper. Sets the variables repos_url, git_branch, git_head, repos_root,
551 # extra_repos_info and using_git_svn properly.
552 git_get_repos_info_()
554 # FIXME: 1st commit: "fatal: bad default revision 'HEAD'" on stderr
555 git_config_list=`git config -l`
556 using_git_svn=false
557 case $git_config_list in #(
558 *svn-remote.*.url=*)
559 repos_url=`echo "$git_config_list" \
560 | sed '/^svn-remote.svn.url=\(.*\)$/!d;s//\1/'`
561 using_git_svn=:
562 ;; #(
563 *remote.origin.url=*)
564 repos_url=`echo "$git_config_list" \
565 | sed '/^remote.origin.url=\(.*\)$/!d;s//\1/'`
567 esac
568 test -z "$repos_url" && repos_url='(git:unknown)'
569 git_branch=`git branch | awk '/^\*/ { print substr($0, 3) }'`
570 if [ x"$git_branch" = x'(no branch)' ]; then
571 yesno 'You are on a detached HEAD, do you really want to continue?' \
572 || return 1
574 git_head=`git rev-list --pretty=format:%h HEAD --max-count=1 | sed '1d;q'`
575 extra_repos_info="Git branch: $git_branch (HEAD: $git_head)"
576 repos_root=$repos_url
579 # git_warn_missing_prop(PROP-NAME, SAMPLE-VALUE)
580 git_warn_missing_prop()
582 if $using_git_svn; then
583 warn "No $1 property set for this repository.
584 This is a git-svn repository, so you can set it in an SVN working copy:
585 svn propset $1 $2 .
586 Otherwise you can just set the property in your git-svn repository:
587 git config svnw.$1 $2"
588 else
589 warn "No $1 property set for this repository.
590 You can set it like this:
591 git config svnw.$1 $2"
595 # Helper. Find the `mailto' property, be it an SVN property or a git-config
596 # option. Relies on the value of $change_log_dir and sets the values of
597 # $mailto (the value of the `mailto' property) and $to (a string to append in
598 # templates that contains the `To:' line, or an empty string if no mail must
599 # be sent).
600 get_mailto_property()
602 test -d "$change_log_dir" || abort 'Internal error in get_mailto_property:
603 $change_log_dir not pointing to a directory'
604 if $git_mode; then
605 mailto=`git config svnw.mailto`
606 if [ x"$mailto" = x ] \
607 && [ -f "$change_log_dir/.git/svn/git-svn/unhandled.log" ]
608 then
609 mailto=`grep mailto "$change_log_dir/.git/svn/git-svn/unhandled.log"`
610 sed_tmp='$!d;s/^.*+dir_prop: . mailto //;s/%40/@/g;s/%2C/,/g;s/%20/ /g;'
611 mailto=`echo "$mailto" | sed "$sed_tmp"`
613 if [ x"$mailto" = x ]; then
614 git_warn_missing_prop 'mailto' 'maintainer1@foo.com,maint2@bar.com'
616 else
617 mailto=`$SVN propget mailto "$change_log_dir"`
620 if [ x"$mailto" = x ]; then
621 test x$new_user = xyes \
622 && warn "no svn property mailto found in $change_log_dir
623 You might want to set default email adresses using:
624 svn propset mailto 'somebody@mail.com, foobar@example.com'\
625 $change_log_dir" >&2
626 # Try to be VCS-compatible and find a list of mails in a *.rb.
627 if [ -d "$change_log_dir/vcs" ]; then
628 mailto=`grep '.@.*\..*' "$change_log_dir"/vcs/*.rb \
629 | tr '\n' ' ' \
630 | sed 's/^.*[["]\([^["]*@[^]"]*\)[]"].*$/\1/' | xargs`
631 test x"$mailto" != x && test x$new_user = xyes \
632 && notice "VCS-compat: found mailto: $mailto
633 in " "$change_log_dir"/vcs/*.rb
634 fi # end VCS compat
635 fi # end guess mailto
637 # Ensure that emails are comma-separated.
638 mailto=`echo "$mailto" | sed 's/[ ;]/,/g' | tr -s ',' | sed 's/,/, /g'`
641 if [ x"$mailto" != xdont_send_mails ] && [ x"$mailto" != xnothx ]; then
642 to="
643 To: $mailto"
649 # ------------------------------- #
650 # Hooks for standard SVN commands #
651 # ------------------------------- #
653 # svn_commit [args...]
654 # Here is how the commit process goes:
656 # First we look in the arguments passed to commit:
657 # If there are some files or paths, the user wants to commit these only. In
658 # this case, we must search for ChangeLogs from these paths. We might find
659 # more than one ChangeLog, in this case the user will be prompted to pick up
660 # one.
661 # Otherwise (no path passed in the command line) the user just wants to
662 # commit the current working directory.
663 # In any case, we schedule "ChangeLog" for commit.
665 # Alright now that we know which ChangeLog to use, we look in the ChangeLog's
666 # directory if there is a ",svn-log" file which would mean that a previous
667 # commit didn't finish successfully. If there is such a file, the user is
668 # prompted to know whether they want to resume that commit or simply start a
669 # new one.
670 # When the user wants to resume a commit, the ",svn-log" file is loaded and we
671 # retrieve the value of "$@" that was saved in the file.
672 # Otherwise we build a template ChangeLog entry.
673 # Then we open the template ChangeLog entry with $EDITOR so that the user
674 # fills it properly.
675 # Finally, we commit.
676 # Once the commit is sent, we ask the server to know which revision was
677 # commited and we also retrieve the diff. We then send a mail with these.
678 svn_commit()
680 here=`pwd -P`
681 dry_run=false
682 git_commit_all=false
683 use_log_message_from_file=false
684 log_message_to_use=
686 # Check if the user passed some paths to commit explicitly
687 # because in this case we must add the ChangeLog to the commit and search
688 # the ChangeLog from the dirname of that file.
689 i=0; search_from=; add_changelog=false; extra_files=
690 while [ $i -lt $# ]; do
691 arg=$1
692 case $arg in
693 --dry-run)
694 dry_run=:
695 shift
696 i=$((i + 1))
697 continue
699 -a|--all)
700 git_commit_all=:
702 --use-log-file)
703 shift
704 test -z "$1" && abort "$arg needs an argument"
705 test -r "$1" || abort "'$1' does not seem to be readable"
706 test -w "$1" || abort "'$1' does not seem to be writable"
707 test -d "$1" && abort "'$1' seems to be a directory"
708 use_log_message_from_file=:
709 log_message_to_use=$1
710 shift
711 continue
713 esac
714 # If the argument is a valid path: add the ChangeLog in the list of
715 # files to commit
716 if test -e "$arg"; then
717 add_changelog=:
718 if test -d "$arg"; then
719 search_from_add=$arg
720 else
721 search_from_add=`dirname "$arg"`
723 search_from="$search_from:$search_from_add"
725 shift
726 set dummy "$@" "$arg"
727 shift
728 i=$((i + 1))
729 done
730 if $add_changelog; then :; else
731 # There is no path/file in the command line: the user wants to commit the
732 # current directory. Make it explicit now:
733 extra_files=$here
735 search_from=`echo "$search_from" | sed 's/^://; s/^$/./'`
737 # ----------------- #
738 # Find ChangeLog(s) #
739 # ----------------- #
741 nb_chlogs=0; change_log_dirs=
742 save_IFS=$IFS; IFS=':'
743 for dir in $search_from; do
744 IFS=$save_IFS
745 $use_changelog || break
746 test -z "$dir" && dir='.'
747 # First: come back to the original place
748 cd "$here" || abort "Cannot cd to $here"
749 cd "$dir" || continue # Then: Enter $dir (which can be a relative path)
750 found=0
751 while [ $found -eq 0 ]; do
752 this_chlog_dir=`pwd -P`
753 if [ -f ./ChangeLog ]; then
754 found=1
755 nb_chlogs=$((nb_chlogs + 1))
756 change_log_dirs="$change_log_dirs:$this_chlog_dir"
757 else
758 cd ..
760 # Stop searching when in / ... hmz :P
761 test x"$this_chlog_dir" = x/ && break
762 done # end while: did we find a ChangeLog
763 done # end for: find ChangeLogs in $search_from
764 if [ $nb_chlogs -gt 0 ]; then
765 change_log_dirs=`echo "$change_log_dirs" | sed 's/^://' | tr ':' '\n' \
766 | sort -u`
767 nb_chlogs=`echo "$change_log_dirs" | wc -l`
770 # Did we find a ChangeLog? More than one?
771 if [ $nb_chlogs -eq 0 ] && $use_changelog; then
772 if yesno 'svn-wrapper: Error: Cannot find a ChangeLog file!
773 You might want to create an empty one (eg: `touch ChangeLog` where appropriate)
774 Do you want to proceed without using a ChangeLog?'; then
775 cd "$here"
776 $SVN commit "$@"
777 return $?
778 else
779 return 1
781 elif [ $nb_chlogs -gt 1 ]; then
782 notice "$nb_chlogs ChangeLogs were found, pick up one:"
784 IFS=':'; i=0
785 for a_chlog_dir in $change_log_dirs; do
786 i=$((i + 1))
787 echo "$i. $a_chlog_dir/ChangeLog"
788 done
789 printf "Which ChangeLog do you want to use? [1-$i] "
790 read chlog_no || abort 'Cannot read answer on stdin.'
792 case $chlog_no in
793 *[^0-9]*) abort "Invalid ChangeLog number: $chlog_no"
794 esac
795 test "$chlog_no" -le $i || abort "Invalid ChangeLog number: $chlog_no
796 max value was: $i"
797 test "$chlog_no" -ge 1 || abort "Invalid ChangeLog number: $chlog_no
798 min value was: 1"
799 change_log_dir=`echo "$change_log_dirs" | tr ':' '\n' | sed "${chlog_no}!d"`
800 else # Only one ChangeLog found
801 if $use_changelog; then
802 change_log_dir=$change_log_dirs
803 notice "using $change_log_dir/ChangeLog"
807 if $use_changelog; then
808 test -f "$change_log_dir/ChangeLog" \
809 || abort "No such file or directory: $change_log_dir/ChangeLog"
810 # Now we can safely schedule the ChangeLog for the commit.
811 extra_files="$extra_files:$change_log_dir/ChangeLog"
812 else
813 change_log_dir='.' # Hack. FIXME: Does this work in all cases?
816 if [ -d "$change_log_dir/.git" ] || $git_mode; then
817 SVN=git
818 git_mode=:
819 git_get_repos_info_
820 else
821 svn_st_tmp=`$SVN status "$change_log_dir"`
823 # Warn for files that are not added in the repos.
824 conflicts=`echo "$svn_st_tmp" | sed '/^ *$/d;
825 /^[^?]/d;
826 /^?.......*\/[,+]/d;
827 /^?......[,+]/d'`
828 if test x"$conflicts" != x; then
829 warn "make sure you don't want to \`svn add'
830 any of the following files before committing:"
831 echo "$conflicts" | sed "$sed_svn_st_color"
832 printf 'Type [ENTER] to continue :)' && read chiche_is_gay
835 # If there are changes in an svn:externals, advise the user to commit that
836 # first.
837 changed_externals=`echo "$svn_st_tmp" | $AWK \
838 'function printext()
840 if (ext && !printed)
842 print this_ext "\n";
843 printed = 1;
846 BEGIN { this_ext = ""; ext = 0; ext_modified = 0; }
847 /^Performing status on external/ {
848 ext = 1;
849 sub(/.* at ./, ""); sub(/.$/, ""); this_ext = $0;
850 printed = 0;
852 /^[ADMR]/ { ext_modified = ext; printext(); }
853 /^.[M]/ { ext_modified = ext; printext(); }
854 END { exit ext_modified; }'`
855 if [ $? -ne 0 ]; then
856 warn "the following external items have local modifications:
857 $changed_externals"
858 yesno "You are advised to commit them separately first. Continue anyway?" \
859 || return 1
862 # Detect unresolved conflicts / missing files.
863 conflicts=`echo "$svn_st_tmp" | sed '/^[C!]/!d'`
864 test x"$conflicts" != x && abort "there are unresolved conflicts (\`C')
865 and/or missing files (\`!'):
866 $conflicts"
868 svn_info_tmp=`$SVN info "$change_log_dir"`
869 test $? -ne 0 && abort "Failed to get svn info on $change_log_dir"
870 repos_root=`echo "$svn_info_tmp" | sed '/^Repository Root: /!d;s///'`
871 repos_url=`echo "$svn_info_tmp" | sed '/^URL: /!d;s///'`
872 # It looks like svn <1.3 didn't display a "Repository Root" entry.
873 test -z "$repos_root" && repos_root=$repos_url
876 cd "$here"
878 YYYY=`date '+%Y'`
879 MM=`date '+%m'`
880 DD=`date '+%d'`
882 # VCS-compat: handle user option 'new_user'
883 new_user='yes'
884 grep '^new_user: false' ~/.vcs >/dev/null 2>/dev/null && new_user='no'
886 edit_changelog=:
887 tmp_log="$change_log_dir/,svn-log"
888 $use_log_message_from_file \
889 && tmp_log=$log_message_to_use \
890 && edit_changelog=false
892 if [ -f "$tmp_log" ] \
893 && { $use_log_message_from_file \
894 || yesnewproceed "It looks like the last commit did not\
895 terminate successfully.
896 Would you like to resume it or proceed immediately?"; }; then
897 case $yesnoproceed_res in
898 *proceed) edit_changelog=false;;
899 esac
900 if test x"$yesnoproceed_res" = xupproceed; then
901 svn_update "$@" || abort 'update failed'
903 echo 'Resuming ...'
904 internal_tags=`sed '/^--- Internal stuff, DO NOT change please ---$/,$!d' \
905 "$tmp_log"`
906 saved_args=`echo "$internal_tags" | sed '/^args: */!d;s///'`
907 extra_files=`echo "$internal_tags" | sed '/^extra_files: */!d;s///'`
908 if [ x"$saved_args" != x ]; then
909 if [ x"$*" != x ] && [ x"$saved_args" != x"$*" ]; then
910 warn "overriding arguments:
911 you invoked $me with the following arguments: $@
912 they have been replaced by these: $saved_args"
913 set dummy $saved_args
914 shift
915 else
916 notice "setting the following arguments: $saved_args"
917 set dummy $saved_args
918 shift
920 elif [ x"$*" != x ]; then
921 warn "overriding arguments:
922 you invoked $me with the following arguments: $@
923 they have been dropped"
926 for i; do
927 case $i in
928 -a|--all)
929 git_commit_all=:
930 if [ $git_mode ]; then
931 (cd $change_log_dir && git add -v -u) || abort '`git add -v -u` failed'
934 esac
935 done
937 get_svn_diff_and_diffstat "$@"
939 # Update the file with the new diff/diffstat in case it changed.
940 $AWK 'BEGIN {
941 tlatbwbi_seen = 0;
942 ycewah_seen = 0;
944 /^--This line, and those below, will be ignored--$/ {
945 tlatbwbi_seen = 1;
947 /^ Your ChangeLog entry will appear here\.$/ {
948 if (tlatbwbi_seen) ycewah_seen = 1;
951 if (ycewah_seen != 2) print;
952 if (ycewah_seen == 1) ycewah_seen = 2;
954 END {
955 if (tlatbwbi_seen == 0)
957 print "--This line, and those below, will be ignored--\n\n" \
958 " Your ChangeLog entry will appear here.";
960 }' "$tmp_log" >"$tmp_log.tmp"
961 echo "
963 $svn_diff_stat
965 $svn_diff
967 $internal_tags" >>"$tmp_log.tmp"
968 mv -f "$tmp_log.tmp" "$tmp_log" || abort "failed to write '$tmp_log'"
970 else # Build the template message.
972 # ------------------------------------ #
973 # Gather info for the template message #
974 # ------------------------------------ #
976 if $git_mode; then
977 projname=`git config svnw.project`
978 if [ x"$projname" = x ] \
979 && [ -f "$change_log_dir/.git/svn/git-svn/unhandled.log" ]
980 then
981 projname=`grep project "$change_log_dir/.git/svn/git-svn/unhandled.log"`
982 sed_tmp='$!d;s/^.*+dir_prop: . project //'
983 projname=`echo "$projname" | sed "$sed_tmp"`
985 if [ x"$projname" = x ]; then
986 git_warn_missing_prop 'project' 'myproj'
988 else
989 projname=`$SVN propget project "$change_log_dir"`
991 # Try to be VCS-compatible and find a project name in a *.rb.
992 if [ x"$projname" = x ] && [ -d "$change_log_dir/vcs" ]; then
993 projname=`sed '/common_commit/!d;s/.*"\(.*\)<%= rev.*/\1/' \
994 "$change_log_dir"/vcs/*.rb`
995 test x"$projname" != x && test x$new_user = xyes \
996 && notice "VCS-compat: found project name: $projname
997 in " "$change_log_dir"/vcs/*.rb
999 test x"$projname" != x && projname=`echo "$projname" | sed '/[^ ]$/s/$/ /'`
1001 get_mailto_property
1003 test -z "$FULLNAME" && FULLNAME='Type Your Name Here' \
1004 && warn_env FULLNAME
1005 test -z "$EMAIL" && EMAIL='your.mail.here@FIXME.com' && warn_env EMAIL
1007 if $git_mode; then
1008 if $git_commit_all; then
1009 (cd $change_log_dir && git add -v -u) || abort '`git add -v -u` failed'
1011 my_git_st=`git diff -C --raw --cached`
1012 test $? -eq 0 || abort 'git diff failed'
1013 # Format: ":<old_mode> <new_mode> <old_sha1> <new_sha1> <status>[
1014 # <similarity score>]\t<file-name>"
1015 change_log_files=`echo "$my_git_st" | sed '
1016 t dummy_sed_1
1017 : dummy_sed_1
1018 s/^:[0-7 ]* [0-9a-f. ]* M[^ ]* \(.*\)$/ * \1: ./; t
1019 s/^:[0-7 ]* [0-9a-f. ]* A[^ ]* \(.*\)$/ * \1: New./; t
1020 s/^:[0-7 ]* [0-9a-f. ]* D[^ ]* \(.*\)$/ * \1: Remove./; t
1021 s/^:[0-7 ]* [0-9a-f. ]* R[^ ]* \([^ ]*\) \(.*\)$/ * \2: Rename from \1./;t
1022 s/^:[0-7 ]* [0-9a-f. ]* C[^ ]* \([^ ]*\) \(.*\)$/ * \2: Copy from \1./;t
1023 s/^:[0-7 ]* [0-9a-f. ]* T[^ ]* \(.*\)$/ * \1: ./; t
1024 s/^:[0-7 ]* [0-9a-f. ]* X[^ ]* \(.*\)$/ * \1: ???./; t
1025 s/^:[0-7 ]* [0-9a-f. ]* U[^ ]* \(.*\)$/ * \1: UNMERGED./; t
1027 else
1028 # --ignore-externals appeared after svn 1.1.1
1029 my_svn_st=`$SVN status --ignore-externals "$@" \
1030 || $SVN status "$@" | sed '/^Performing status on external/ {
1034 # Files to put in the ChangeLog entry.
1035 change_log_files=`echo "$my_svn_st" | sed '
1036 t dummy_sed_1
1037 : dummy_sed_1
1038 s/^M......\(.*\)$/ * \1: ./; t
1039 s/^A......\(.*\)$/ * \1: New./; t
1040 s/^D......\(.*\)$/ * \1: Remove./; t
1044 if [ x"$change_log_files" = x ]; then
1045 yesno 'Nothing to commit, continue anyway?' || return 1
1048 change_log_files=`echo "$change_log_files" | sort -u`
1050 get_svn_diff_and_diffstat "$@"
1052 # Get any older svn-log out of the way.
1053 test -f "$tmp_log" && mv "$tmp_log" `get_unique_file_name "$tmp_log"`
1054 # If we can't get an older svn-log out of the way, find a new name...
1055 test -f "$tmp_log" && tmp_log=`get_unique_file_name "$tmp_log"`
1056 if [ x$new_user = no ]; then
1057 commit_instructions='
1058 Instructions:
1059 - Fill the ChangeLog entry.
1060 - If you feel like, write a comment in the "Comment:" section.
1061 This comment will only appear in the email, not in the ChangeLog.
1062 By default only the location of the repository is in the comment.
1063 - Some tags will be replaced. Tags are of the form: <TAG>. Unknown
1064 tags will be left unchanged.
1065 - The tag <REV> may only be used in the Subject.
1066 - Your ChangeLog entry will be used as commit message for svn.'
1067 else
1068 commit_instructions=
1070 r_before_rev=r
1071 $git_mode && r_before_rev=
1072 test -z "$extra_repos_info" || extra_repos_info="
1073 $extra_repos_info"
1074 echo "\
1075 --You must fill this file correctly to continue-- -*- vcs -*-
1076 Title:
1077 Subject: ${projname}$r_before_rev<REV>: <TITLE>
1078 From: $FULLNAME <$EMAIL>$to
1080 Comment:
1081 URL: $repos_url$extra_repos_info
1083 ChangeLog:
1085 <YYYY>-<MM>-<DD> $FULLNAME <$EMAIL>
1087 <TITLE>
1088 $change_log_files
1090 --This line, and those below, will be ignored--
1091 $commit_instructions
1092 --Preview of the message that will be sent--
1094 URL: $repos_url$extra_repos_info
1095 Your comments (if any) will appear here.
1097 ChangeLog:
1098 $YYYY-$MM-$DD $FULLNAME <$EMAIL>
1100 Your ChangeLog entry will appear here.
1103 $svn_diff_stat
1105 $svn_diff" >"$tmp_log"
1107 echo "
1108 --- Internal stuff, DO NOT change please ---
1109 args: $@" >>"$tmp_log"
1110 echo "extra_files: $extra_files
1111 vi: ft=diff:noet:tw=76:" >>"$tmp_log"
1113 fi # end: if svn-log; then resume? else create template
1114 $edit_changelog && $EDITOR "$tmp_log"
1116 # ------------------ #
1117 # Re-"parse" the log #
1118 # ------------------ #
1120 # hmz this section is a bit messy...
1121 # helper string... !@#$%* escaping \\\\\\...
1122 sed_escape='s/\\/\\\\/g;s/@/\\@/g;s/&/\\\&/g'
1123 sed_eval_tags="s/<MM>/$MM/g; s/<DD>/$DD/g; s/<YYYY>/$YYYY/g"
1124 full_log=`sed '/^--*This line, and those below, will be ignored--*$/,$d;
1125 /^--You must fill this/d' "$tmp_log"`
1126 chlog_entry=`echo "$full_log" | sed '/^ChangeLog:$/,$!d; //d'`
1127 ensure_not_empty 'ChangeLog entry' "$chlog_entry"
1128 full_log=`echo "$full_log" | sed '/^ChangeLog:$/,$d'`
1129 mail_comment=`echo "$full_log" | sed '/^Comment:$/,$!d; //d'`
1130 full_log=`echo "$full_log" | sed '/^Comment:$/,$d'`
1131 mail_title=`echo "$full_log" | sed '/^Title: */!d;s///;'`
1132 ensure_not_empty 'commit title' "$mail_title"
1133 # Add a period at the end of the title.
1134 mail_title=`echo "$mail_title" | sed -e '/ *[.!?]$/!s/ *$/./' \
1135 -e "$sed_eval_tags; $sed_escape"`
1136 sed_eval_tags="$sed_eval_tags; s@<TITLE>\\.*@$mail_title@g"
1137 mail_comment=`echo "$mail_comment" | sed "$sed_eval_tags"`
1138 raw_chlog_entry=$chlog_entry # ChangeLog entry without tags expanded
1139 chlog_entry=`echo "$chlog_entry" | sed "$sed_eval_tags; 1{
1140 /^ *$/d
1142 mail_subject=`echo "$full_log" | sed '/^Subject: */!d;s///'`
1143 ensure_not_empty 'mail subject' "$mail_subject"
1144 mail_to=`echo "$full_log" | sed '/^To:/!d'`
1145 send_a_mail=:
1146 if test x"$mail_to" = x; then
1147 send_a_mail=false
1148 else
1149 mail_to=`echo "$mail_to" | sed 's/^To: *//;s///'`
1150 # If there is a <MAILTO> in the 'To:' line, we must expand it.
1151 case $mail_to in #(
1152 *'<MAILTO>'*)
1153 get_mailto_property
1154 # Are we meant to send a mail?
1155 case $to in #(
1156 '') # No, don't send a mail.
1157 mail_to=
1158 send_a_mail=false
1159 ;; #(
1160 *) # Yes, send a mail.
1161 mail_to=`echo "$mail_to" | sed "s#<MAILTO>#$mailto#g"`
1163 esac
1164 esac
1165 $send_a_mail && ensure_not_empty '"To:" field of the mail' "$mail_to"
1168 test -z "$FULLNAME" && warn_env FULLNAME && FULLNAME=$USER
1169 test -z "$EMAIL" && warn_env EMAIL && EMAIL=$USER
1170 myself=`echo "$FULLNAME <$EMAIL>" | sed "$sed_escape"`
1171 mail_from=`echo "$full_log" | sed "/^From: */!d;s///;s@<MYSELF>@$myself@g"`
1172 ensure_not_empty '"From:" field of the mail' "$mail_from"
1174 # ------------------------------------ #
1175 # Sanity checks on the ChangeLog entry #
1176 # ------------------------------------ #
1178 if echo "$chlog_entry" | grep '<REV>' >/dev/null; then
1179 warn 'Using the tag <REV> anywhere else than in the Subject is deprecated.'
1180 yesno 'Continue anyway?' || return 1
1183 if echo "$chlog_entry" | grep ': \.$' >/dev/null; then
1184 warn 'It looks like you did not fill all entries in the ChangeLog:'
1185 echo "$chlog_entry" | grep ': \.$'
1186 yesno 'Continue anyway?' || return 1
1189 if echo "$chlog_entry" | grep '^--* Internal stuff' >/dev/null; then
1190 warn "It looks like you messed up the delimiters and I did not properly
1191 find your ChangeLog entry. Here it is, make sure it is correct:"
1192 echo "$chlog_entry"
1193 yesno 'Continue anyway?' || return 1
1196 if echo "$chlog_entry" | grep -i 'dont[^a-z0-9]' >/dev/null; then
1197 warn "Please avoid typos such as ${lred}dont$std instead of\
1198 ${lgreen}don't$std:"
1199 echo "$chlog_entry" | grep -n -i 'dont[^a-z0-9]' \
1200 | sed "s/[dD][oO][nN][tT]/$lred&$std/g"
1201 yesno 'Continue anyway?' || return 1
1204 if echo "$chlog_entry" | grep -i 'cant[^a-z0-9]' >/dev/null; then
1205 warn "Please avoid typos such as ${lred}cant$std instead of\
1206 ${lgreen}can't$std:"
1207 echo "$chlog_entry" | grep -n -i 'cant[^a-z0-9]' \
1208 | sed "s/[cC][aA][nN][tT]/$lred&$std/g"
1209 yesno 'Continue anyway?' || return 1
1212 if echo "$chlog_entry" | grep '^.\{80,\}' >/dev/null; then
1213 warn 'Please avoid long lines in your ChangeLog entry (80 columns max):'
1214 echo "$chlog_entry" | grep '^.\{80,\}'
1215 yesno 'Continue anyway?' || return 1
1218 if echo "$mail_title" | grep -i '^[a-z][a-z]*ed[^a-z]' >/dev/null; then
1219 warn 'ChangeLog entries should be written in imperative form:'
1220 echo "$mail_title" | grep -i '^[a-z][a-z]*ed[^a-z]' \
1221 | sed "s/^\\([a-zA-Z][a-zA-Z]*ed\\)\\([^a-zA-Z]\\)/$lred\\1$std\\2/"
1222 yesno 'Continue anyway?' || return 1
1225 # Check whether the user passed -m | --message
1227 while [ $i -lt $# ]; do
1228 arg=$1
1229 # This is not really a reliable way of knowing whether -m | --message was
1230 # passed but hum... Let's assume it'll do :s
1231 if [ x"$arg" = 'x-m' ] || [ x"$arg" = 'x--message' ]; then
1232 my_message=$2
1234 shift
1235 set dummy "$@" "$arg"
1236 shift
1237 i=$((i + 1))
1238 done
1239 if [ x"$my_message" = x ]; then
1240 # The title must not be indented in the commit message and must be
1241 # followed by a blank line. This yields much better results with most
1242 # VC-viewer (especially for Git but including for SVN, such as Trac for
1243 # instance). We assume that the title will always be on the 1st line.
1244 sed_git_title="1s@^[ ]*<TITLE>\\.*@$mail_title\\
1245 @g; $sed_eval_tags"
1246 # First, remove empty lines at the beginning, if any.
1247 # Remove also the date information (useless in commit messages)
1248 my_message=`echo "$raw_chlog_entry" \
1249 | sed -e '1,4 {
1250 /^<YYYY>-<[MD][MD]>-<[DM][DM]>/d
1251 /^[1-9][0-9][0-9][0-9]-[0-9][0-9]*-[0-9][0-9]*/d
1252 /^ and .*<.*@.*>$/d
1253 /^ *$/d
1254 }' \
1255 | sed -e "$sed_git_title" \
1256 -e "$sed_eval_tags; 1{
1257 /^ *$/d
1259 else
1260 notice 'you are overriding the commit message.'
1263 # Show suspicious whitespace additions with Git.
1264 $git_mode && git diff --cached --check
1266 if $dry_run; then
1267 proposal_file=',proposal'
1268 test -f "$proposal_file" \
1269 && proposal_file=`get_unique_file_name "$proposal_file"`
1270 sed_tmp='s/<REV>/???/g;s/\([^.]\) *\.$/\1/'
1271 mail_subject=`echo "$mail_subject" | sed "$sed_eval_tags; $sed_tmp"`
1273 if [ x"$mailto" != xdont_send_mails ] && [ x"$mailto" != xnothx ]; then
1274 to="
1275 To: $mailto"
1277 echo "\
1278 From: $mail_from$to
1279 Subject: $mail_subject
1281 $mail_comment
1283 ChangeLog:
1284 $chlog_entry
1287 $svn_diff_stat
1289 $svn_diff" >"$proposal_file"
1290 notice "A proposal of your commit was left in '$proposal_file'"
1291 return 0
1294 # Are you sure?
1295 if $git_mode; then
1296 $git_commit_all \
1297 || notice 'You are using git, unlike SVN, do not forget to git add your
1298 changes'
1301 # Change edit_changelog so that we're asking the confirmation below.
1302 $use_log_message_from_file && edit_changelog=: \
1303 && notice "You are about to commit the following change:
1304 $mail_title"
1306 $edit_changelog \
1307 && {
1308 yesno 'Are you sure you want to commit?' \
1309 || return 1
1312 # Add the ChangeLog entry
1313 old_chlog=`get_unique_file_name "$change_log_dir/ChangeLog.old"`
1314 mv "$change_log_dir/ChangeLog" "$old_chlog" || \
1315 abort 'Could not backup ChangeLog'
1316 trap "echo SIGINT; mv \"$old_chlog\" \"$change_log_dir/ChangeLog\";
1317 exit 130" $SIGINT
1318 echo "$chlog_entry" >"$change_log_dir/ChangeLog"
1319 echo >>"$change_log_dir/ChangeLog"
1320 cat "$old_chlog" >>"$change_log_dir/ChangeLog"
1322 # Add extra files such as cwd or ChangeLog to the commit.
1323 tmp_sed='s/ /\\ /g' # Escape spaces for the shell.
1324 if $git_mode && $use_changelog; then
1325 # Schedule the ChangeLog for the next commit
1326 (cd "$change_log_dir" && git add ChangeLog) \
1327 || abort 'failed to git add the ChangeLog'
1328 extra_files=
1329 else
1330 extra_files=`echo "$extra_files" | sed "$tmp_sed" | tr ':' '\n'`
1333 # Always sign the commits with Git (but not with git-svn).
1334 $using_git_svn || $git_mode && set dummy --signoff "$@" && shift
1336 # Update the Git index if necessary (just in case the user changed his
1337 # working copy in the mean time)
1338 if $git_mode && $git_commit_all; then
1339 (cd $change_log_dir && git add -v -u) || abort '`git add -v -u` failed'
1342 # --Commit-- finally! :D
1343 $SVN commit -m "$my_message" "$@" $extra_files || {
1344 svn_commit_rv=$?
1345 mv "$old_chlog" "$change_log_dir/ChangeLog"
1346 abort "Commit failed, $SVN returned $svn_commit_rv"
1349 printf 'Getting the revision number... '
1350 if $git_mode; then
1351 REV=`git rev-list --pretty=format:%h HEAD --max-count=1 | sed '1d;q'`
1352 else
1353 svn_info_tmp=`$SVN info "$change_log_dir/ChangeLog"`
1354 REV=`echo "$svn_info_tmp" | sed '/^Revision: /!d;s///'`
1355 test -z "$REV" && REV=`echo "$svn_info_tmp" \
1356 | sed '/^Last Changed Rev: /!d;s///'`
1358 test -z "$REV" && abort 'Cannot detect the current revision.'
1359 echo "$REV"
1361 # Let's make sure we have the real diff by asking the ChangeSet we've just
1362 # committed to the server.
1364 # Backup the old stuff in case we fail to get the real diff from the server
1365 # for some reason...
1366 save_svn_diff=$svn_diff
1367 save_svn_diff_stat=$svn_diff_stat
1369 if $git_mode; then
1370 svn_diff=`git diff --ignore-all-space --no-color -C 'HEAD^' HEAD`
1371 svn_diff_stat=`git diff --stat --ignore-all-space --no-color -C 'HEAD^' HEAD`
1372 $using_git_svn &&
1373 notice 'Do not forget to use `git-svn dcommit` to push your commits in SVN'
1374 else
1375 # Fetch the ChangeSet and filter out the ChangeLog entry. We don't use
1376 # svn diff -c because this option is not portable to older svn versions.
1377 REV_MINUS_ONE=$((REV - 1))
1378 svn_diff=`svn_diffw -r"$REV_MINUS_ONE:$REV" "$repos_root" \
1379 | $AWK '/^Index: / { if (in_chlog) in_chlog = 0; }
1380 /^Index: .*ChangeLog$/ { in_chlog = 1 }
1381 { if (!in_chlog) print }'`
1382 if [ x"$svn_diff" = x ]; then
1383 svn_diff=$save_svn_diff
1384 svn_diff_stat=$save_svn_diff_stat
1385 else
1386 if require_diffstat; then
1387 svn_diff_stat=`echo "$svn_diff" | diffstat`
1388 else
1389 svn_diff_stat='diffstat not available'
1394 # Expand <REV> and remove the final period from the mail subject if there is
1395 # only one period.
1396 sed_tmp="s/<REV>/$REV/g;"'s/\([^.]\) *\.$/\1/'
1397 mail_subject=`echo "$mail_subject" | sed "$sed_eval_tags; $sed_tmp"`
1399 mail_file=`get_unique_file_name "$change_log_dir/+mail"`
1400 echo "\
1401 From: $mail_from
1402 To: $mail_to
1403 Subject: $mail_subject
1405 $mail_comment
1407 ChangeLog:
1408 $chlog_entry
1411 $svn_diff_stat
1413 $svn_diff" | sed 's/^\.$/ ./' >"$mail_file"
1414 # We change lines with only a `.' because they could mean "end-of-mail"
1416 # Send the mail
1417 if $send_a_mail; then
1418 trap 'echo SIGINT; exec < /dev/null' $SIGINT
1419 # FIXME: Move the mail to the +committed right now, in case the user
1420 # CTLR+C the mail-sending-thing, so that the mail will be properly saved
1421 # their.
1422 my_sendmail "$mail_file" "$mail_subject" "$mail_to" \
1423 "X-svn-url: $repos_root
1424 X-svn-revision: $REV"
1425 fi # end do we have to send a mail?
1426 rm -f "$tmp_log"
1427 rm -f "$old_chlog"
1428 save_mail_file=`echo "$mail_file" | sed 's/+//'`
1429 mkdir -p "$change_log_dir/+committed" \
1430 || warn "Couldn't mkdir -p $change_log_dir/+committed"
1431 if [ -d "$change_log_dir/vcs" ] \
1432 || [ -d "$change_log_dir/+committed" ]
1433 then
1434 mkdir -p "$change_log_dir/+committed/$REV" \
1435 && mv "$mail_file" "$change_log_dir/+committed/$REV/mail"
1437 return $svn_commit_rv
1440 # svn_diffw [args...]
1441 svn_diff()
1443 # Ignore white spaces.
1444 if $git_mode; then
1445 git diff -C "$@"
1446 else
1447 diffarg='-u'
1448 # Can't svn diff -x -w: svn 1.4 only.
1449 # Moreover we *MUST* use -x -uw, not -x -u -w or -x -u -x -w ...
1450 # Hence the hack to stick both diff arguments together...
1451 # No comment :)
1452 test x"$1" = x'--SVNW-HACK-w' && shift && diffarg='-uw'
1453 $SVN diff --no-diff-deleted --diff-cmd $DIFF -x $diffarg "$@"
1457 # svn_diffw [args...]
1458 svn_diffw()
1460 # Ignore white spaces.
1461 if $git_mode; then
1462 svn_diff --ignore-all-space "$@"
1463 else
1464 svn_diff --SVNW-HACK-w "$@"
1468 # svn_mail REV [mails...]
1469 svn_mail()
1471 test $# -lt 1 && abort "Not enough arguments provided;
1472 Try 'svn help mail' for more info."
1473 case $1 in
1474 PREV)
1475 if $git_mode; then
1476 REV=`git rev-list --pretty=format:%h 'HEAD^' --max-count=1 | sed '1d;q'`
1477 else
1478 REV=`svn_revision || abort 'Cannot get current revision number'`
1479 test -z "$REV" && abort 'Cannot get current revision number'
1480 if [ "$REV" -lt 1 ]; then
1481 abort 'No previous revision.'
1483 REV=$((REV - 1))
1486 HEAD)
1487 REV=`svn_revision || abort 'Cannot get current revision number'`
1488 test -z "$REV" && abort 'Cannot get current revision number'
1490 *) REV=$1;;
1491 esac
1492 shift
1494 found_committed=0; found=0
1495 while [ $found -eq 0 ]; do
1496 this_chlog_dir=`pwd -P`
1497 if [ -d ./+committed ]; then
1498 found_committed=1
1499 if [ -d ./+committed/$REV ]; then
1500 found=1
1501 else
1502 cd ..
1504 else
1505 cd ..
1507 # Stop searching when in / ... hmz :P
1508 test x`pwd` = x/ && break
1509 done
1510 if [ $found -eq 0 ]; then
1511 if [ $found_committed -eq 0 ]; then
1512 abort 'Could not find the +committed directory.'
1513 else
1514 abort "Could not find the revision $REV in +committed."
1516 abort 'Internal error (should never be here).'
1519 mail_file=; subject=; to=
1520 if [ -f ./+committed/$REV/mail ]; then
1521 # svn-wrapper generated file
1522 mail_file="./+committed/$REV/mail"
1523 subject=`sed '/^Subject: /!d;s///' $mail_file | sed '1q'`
1524 to=`sed '/^To: /!d;s///' $mail_file | sed '1q'`
1525 elif [ -f ./+committed/$REV/,iform ] && [ -f ./+committed/$REV/,message ]
1526 then
1527 # VCS-generated file
1528 subject=`sed '/^Subject: /!d;s///;s/^"//;s/"$//' ./+committed/$REV/,iform \
1529 | sed "s/<%= *rev *%>/$REV/g"`
1530 to=`sed '/^To:/,/^[^-]/!d' ./+committed/$REV/,iform | sed '1d;s/^- //;$d' \
1531 | xargs | sed 's/ */, /g'`
1532 mail_file=`get_unique_file_name "$TMPDIR/mail.r$REV"`
1533 echo "From: $FULLNAME <$EMAIL>
1534 To: $to
1535 Subject: $subject
1536 " >"$mail_file" || abort "Cannot create $mail_file"
1537 cat ./+committed/$REV/,message >>"$mail_file" \
1538 || abort "Cannot copy ./+committed/$REV/,message in $mail_file"
1539 else
1540 abort "Couldn't find the mail to re-send in `pwd`/+committed/$REV"
1542 if [ $# -gt 0 ]; then
1543 to=`echo "$*" | sed 's/ */, /g'`
1546 test -z "$to" && abort 'Cannot find the list of recipients.
1547 Please report this bug.'
1548 test -z "$subject" && abort 'Cannot find the subject of the mail.
1549 Please report this bug.'
1551 if yesno "Re-sending the mail of r$REV
1552 Subject: $subject
1553 To: $to
1554 Are you sure?"; then :; else
1555 return 1
1558 if $git_mode; then
1559 git_get_repos_info_
1560 else
1561 svn_info_tmp=`$SVN info`
1562 test $? -ne 0 && abort "Failed to get svn info on `pwd`"
1563 repos_root=`echo "$svn_info_tmp" | sed '/^Repository Root: /!d;s///'`
1564 repos_url=`echo "$svn_info_tmp" | sed '/^URL: /!d;s///'`
1565 # It looks like svn <1.3 didn't display a "Repository Root" entry.
1566 test -z "$repos_root" && repos_root=$repos_url
1569 my_sendmail "$mail_file" "$subject" "$to" \
1570 "X-svn-url: $repos_url
1571 X-svn-revision: $REV"
1574 # svn_version
1575 svn_version()
1577 echo "Using svn-wrapper v$version-g$revision (C) SIGOURE Benoit [GPL]"
1580 # has_prop prop-name [path]
1581 # return value: 0 -> path has the property prop-name set.
1582 # 1 -> path has no property prop-name.
1583 # 2 -> svn error.
1584 has_prop()
1586 hp_plist=`$SVN proplist "$2"`
1587 test $? -ne 0 && return 2
1588 hp_res=`echo "$hp_plist" | sed "/^ *$1\$/!d"`
1589 test -z "$hp_res" && return 1
1590 return 0
1593 # svn_propadd prop-name prop-val [path]
1594 svn_propadd()
1596 if $git_mode; then
1597 abort 'propadd is only for SVN, not for Git.'
1599 test $# -lt 2 \
1600 && abort 'Not enough arguments provided;
1601 try `svn help propadd` for more info'
1602 test $# -gt 3 \
1603 && abort 'Too many arguments provided;
1604 try `svn help propadd` for more info'
1606 path=$3
1607 test -z "$path" && path='.' && set dummy "$@" '.' && shift
1608 has_prop "$1" "$3" || {
1609 test $? -eq 2 && return 1 # svn error
1610 # no property found:
1611 yesno "'$path' has no property named '$1', do you want to add it?" \
1612 && $SVN propset "$@"
1613 return $?
1616 current_prop_val=`$SVN propget "$1" "$3"`
1617 test $? -ne 0 && abort "Failed to get the current value of property '$1'."
1619 $SVN propset "$1" "$current_prop_val
1620 $2" "$3" >/dev/null || abort "Failed to add '$3' in the property '$1'."
1622 current_prop_val=`$SVN propget "$1" "$3" || echo "$current_prop_val
1623 $2"`
1624 echo "property '$1' updated on '$path', new value:
1625 $current_prop_val"
1628 # svn_propsed prop-name sed-script [path]
1629 svn_propsed()
1631 if $git_mode; then
1632 abort 'propsed is only for SVN, not for Git.'
1634 test $# -lt 2 \
1635 && abort 'Not enough arguments provided;
1636 try `svn help propsed` for more info'
1637 test $# -gt 3 \
1638 && abort 'Too many arguments provided;
1639 try `svn help propsed` for more info'
1641 path=$3
1642 test -z "$path" && path='.'
1643 has_prop "$1" "$3" || {
1644 test $? -eq 2 && return 1 # svn error
1645 # no property found:
1646 abort "'$path' has no property named '$1'."
1649 prop_val=`$SVN propget "$1" "$3"`
1650 test $? -ne 0 && abort "Failed to get the current value of property '$1'."
1652 prop_val=`echo "$prop_val" | sed "$2"`
1653 test $? -ne 0 && abort "Failed to run the sed script '$2'."
1655 $SVN propset "$1" "$prop_val" "$3" >/dev/null \
1656 || abort "Failed to update the property '$1' with value '$prop_val'."
1658 new_prop_val=`$SVN propget "$1" "$3" || echo "$prop_val"`
1659 echo "property '$1' updated on '$path', new value:
1660 $new_prop_val"
1663 # svn_revision [args...]
1664 svn_revision()
1666 if $git_mode; then
1667 short=`git rev-list --pretty=format:%h HEAD --max-count=1 | sed '1d;q'`
1668 long=`git rev-list --pretty=format:%H HEAD --max-count=1 | sed '1d;q'`
1669 echo "$short ($long)"
1670 else
1671 svn_revision_info_out=`$SVN info "$@"`
1672 svn_revision_rv=$?
1673 echo "$svn_revision_info_out" | sed '/^Revision: /!d;s///'
1674 return $svn_revision_rv
1678 # svn_ignore [paths]
1679 svn_ignore()
1681 if [ $# -eq 0 ]; then # Simply display ignore-list.
1682 if $git_mode; then
1683 test -f .gitignore && cat .gitignore
1684 else
1685 $SVN propget 'svn:ignore'
1687 elif [ $# -eq 1 ]; then
1688 b=`basename "$1"`
1689 d=`dirname "$1"`
1690 if $git_mode; then
1691 echo "$b" >>"$d/.gitignore"
1692 git add "$d/.gitignore"
1693 notice 'files ignored in this directory:'
1694 cat "$d/.gitignore"
1695 else
1696 svn_propadd 'svn:ignore' "$b" "$d"
1698 else # Add arguments in svn:ignore.
1699 # This part is a bit tricky:
1700 # For each argument, we find all the other arguments with the same dirname
1701 # $dname and we svn:ignore them all in $dname.
1702 while [ $# -ne 0 ]; do
1703 arg=$1
1704 dname=`dirname "$1"`
1705 files=`basename "$1"`
1706 shift
1707 j=0; argc=$#
1708 while [ $j -lt $argc ] && [ $# -ne 0 ]; do
1709 this_arg=$1
1710 shift
1711 this_dname=`dirname "$this_arg"`
1712 this_file=`basename "$this_arg"`
1713 if [ x"$dname" = x"$this_dname" ]; then
1714 files="$files
1715 $this_file"
1716 else
1717 set dummy "$@" "$this_arg"
1718 shift
1720 j=$((j + 1))
1721 done
1722 if $git_mode; then
1723 echo "$files" >>"$dname"/.gitignore
1724 git add "$dname"/.gitignore
1725 notice "files ignored in $dname:"
1726 cat "$dname"/.gitignore
1727 else
1728 svn_propadd 'svn:ignore' "$files" "$dname"
1730 done
1734 # svn_help
1735 svn_help()
1737 if [ $# -eq 0 ]; then
1738 svn_version
1739 $SVN help
1740 rv=$?
1741 echo '
1742 Additionnal commands provided by svn-wrapper:
1743 diffstat (ds)
1744 diffw (dw)
1745 ignore
1746 mail
1747 propadd (padd, pa) -- SVN only
1748 proposal
1749 propsed (psed) -- SVN only
1750 revision (rev)
1751 touch
1752 selfupdate (selfup)
1753 version'
1754 return $rv
1755 else
1756 case $1 in
1757 commit | ci)
1758 $SVN help commit | sed '/^Valid options:/i\
1759 Extra options provided by svn-wrapper:\
1760 \ --dry-run : do not commit, simply generate a patch with what\
1761 \ would have been comitted.\
1762 \ --use-log-file FILE : extract the ChangeLog entry from FILE. This\
1763 \ entry must be formated in a similar fashion to\
1764 \ what svn-wrapper usually asks you to fill in.\
1765 \ The FILE needs to be writable and will be\
1766 \ removed by svn-wrapper upon success.\
1770 diffstat | ds)
1771 require_diffstat
1772 echo 'diffstat (ds): Display the histogram from svn diff-output.'
1773 $SVN help diff | sed '1d;
1774 s/differences*/histogram/;
1775 2,35 s/diff/diffstat/g'
1777 diffw | dw)
1778 echo "diffw (dw): Display the differences without taking whitespaces\
1779 into account."
1780 $SVN help diff | sed '1d;
1781 2,35 s/diff\([^a-z]\)/diffw\1/g;
1782 /--diff-cmd/,/--no-diff-deleted/d'
1784 ignore)
1785 what='svn:ignore property'
1786 $git_mode && what='.gitignore file'
1787 cat <<EOF
1788 ignore: Add some files in the $what.
1789 usage: 1. ignore [PATH]
1790 2. ignore FILE [FILES...]
1792 1. Display the value of $what on [PATH].
1793 2. Add some files in the $what of the directory containing them.
1795 When adding ignores, each pattern is ignored in its own directory, e.g.:
1796 $bme ignore dir/file "d2/*.o"
1797 Will put 'file' in the $what of 'dir' and '*.o' in the
1798 $what of 'd2'
1800 Valid options:
1801 None.
1804 mail)
1805 echo 'mail: Resend the mail of a given commit.
1806 usage: mail REV [emails]
1808 REV must have an email file associated in +committed/REV.
1809 REV can also be PREV or HEAD.
1811 By default the mail is sent to same email addresses as during the original
1812 commit unless more arguments are given.'
1814 propadd | padd | pa)
1815 echo 'propadd (padd, pa): Add something in the value of a property.
1816 usage: propadd PROPNAME PROPVAL PATH
1817 This command only works in SVN mode.
1819 PROPVAL will be appended at the end of the property PROPNAME.
1821 Valid options:
1822 None.'
1824 proposal)
1825 echo 'proposal: Alias for: commit --dry-run.
1826 See: svn help commit.'
1828 propsed | psed)
1829 echo 'propsed (psed): Edit a property with sed.
1830 usage: propsed PROPNAME SED-ARGS PATH
1831 This command only works in SVN mode.
1833 eg: svn propsed svn:externals "s/http/https/" .
1835 Valid options:
1836 None.'
1838 revision | rev)
1839 echo 'revision (rev): Display the revision number of a local or remote item.'
1840 $SVN help info | sed '1d;
1841 s/information/revision/g;
1842 s/revision about/the revision of/g;
1843 2,35 s/info/revision/g;
1844 /-xml/d'
1846 touch)
1847 echo 'touch: Touch a file and svn add it.
1848 usage: touch FILE [FILES]...
1850 Valid options:
1851 None.'
1853 selfupdate | selfup | self-update | self-up)
1854 echo 'selfupdate (selfup): Attempt to update svn-wrapper.sh
1855 usage: selfupdate
1857 Valid options:
1858 None.'
1860 version)
1861 echo 'version: Display the version info of svn and svn-wrapper.
1862 usage: version
1864 Valid options:
1865 None.'
1867 *) $SVN help "$@";;
1868 esac
1872 # svn_status [args...]
1873 svn_status()
1875 if $git_mode; then
1876 git status "$@"
1877 return $?
1879 svn_status_out=`$SVN status "$@"`
1880 svn_status_rv=$?
1881 test -z "$svn_status_out" && return $svn_status_rv
1882 echo "$svn_status_out" | sed "$sed_svn_st_color"
1883 return $svn_status_rv
1886 # svn_update [args...]
1887 svn_update()
1889 svn_update_out=`$SVN update "$@"`
1890 svn_update_rv=$?
1891 echo "$svn_update_out" | sed "$sed_svn_up_colors"
1892 return $svn_update_rv
1895 # ------------------- #
1896 # `main' starts here. #
1897 # ------------------- #
1899 # Define colors if stdout is a tty.
1900 if test -t 1; then
1901 set_colors
1902 else # stdout isn't a tty => don't print colors.
1903 set_nocolors
1906 # Consider this as a sed function :P.
1907 sed_svn_st_color="
1908 t dummy_sed_1
1909 : dummy_sed_1
1910 s@^?\\(......\\)+@+\\1+@
1911 s@^?\\(......\\)\\(.*/\\)+@+\\1\\2+@
1912 s@^?\\(......\\),@,\\1,@
1913 s@^?\\(......\\)\\(.*/\\),@,\\1\\2,@
1914 s/^\\(.\\)C/\\1${lred}C${std}/
1915 t dummy_sed_2
1916 : dummy_sed_2
1917 s/^?/${lred}?${std}/; t
1918 s/^M/${lgreen}M${std}/; t
1919 s/^A/${lgreen}A${std}/; t
1920 s/^X/${lblue}X${std}/; t
1921 s/^+/${lyellow}+${std}/; t
1922 s/^D/${lyellow}D${std}/; t
1923 s/^,/${lred},${std}/; t
1924 s/^C/${lred}C${std}/; t
1925 s/^I/${purple}I${std}/; t
1926 s/^R/${lblue}R${std}/; t
1927 s/^!/${lred}!${std}/; t
1928 s/^~/${lwhite}~${std}/; t"
1930 sed_svn_up_colors="
1931 t dummy_sed_1
1932 : dummy_sed_1
1934 /^Updated/ t
1935 /^Fetching/ t
1936 /^External/ t
1937 s/^\\(.\\)C/\\1${lred}C${std}/
1938 s/^\\(.\\)U/\\1${lgreen}U${std}/
1939 s/^\\(.\\)D/\\1${lred}D${std}/
1940 t dummy_sed_2
1941 : dummy_sed_2
1942 s/^A/${lgreen}A${std}/; t
1943 s/^U/${lgreen}U${std}/; t
1944 s/^D/${lyellow}D${std}/; t
1945 s/^G/${purple}G${std}/; t
1946 s/^C/${lred}C${std}/; t"
1948 # For dev's:
1949 test "x$1" = x--debug && shift && set -x
1951 test "x$1" = x--git && shift && git_mode=: && SVN=git
1953 test "x$1" = x--no-changelog && shift && use_changelog=false
1955 case $1 in
1956 # ------------------------------- #
1957 # Hooks for standard SVN commands #
1958 # ------------------------------- #
1959 commit | ci)
1960 shift
1961 svn_commit "$@"
1963 help | \? | h)
1964 shift
1965 svn_help "$@"
1967 status | stat | st)
1968 shift
1969 svn_status "$@"
1971 update | up)
1972 shift
1973 svn_update "$@"
1975 # -------------------- #
1976 # Custom SVN commands #
1977 # -------------------- #
1978 diffstat | ds)
1979 shift
1980 if [ -d .git ]; then
1981 git diff --stat -C
1982 else
1983 require_diffstat && $SVN diff --no-diff-deleted "$@" | diffstat
1986 diff | di)
1987 shift
1988 DIFF=$COLORDIFF
1989 svn_diff "$@"
1991 diffw | dw)
1992 shift
1993 DIFF=$COLORDIFF
1994 svn_diffw "$@"
1996 ignore)
1997 shift
1998 svn_ignore "$@"
2000 log)
2001 if $git_mode; then # let Git handle log
2002 exec $SVN "$@"
2003 else # pipe svn log through PAGER by default
2004 exec $SVN "$@" | $PAGER
2007 mail)
2008 shift
2009 svn_mail "$@"
2011 propadd | padd | pa)
2012 shift
2013 svn_propadd "$@"
2015 proposal)
2016 shift
2017 svn_commit --dry-run "$@"
2019 propsed | psed)
2020 shift
2021 svn_propsed "$@"
2023 revision | rev)
2024 shift
2025 svn_revision "$@"
2027 touch)
2028 shift
2029 touch "$@" && $SVN add "$@"
2031 selfupdate | selfup | self-update | self-up)
2032 shift
2033 selfupdate "$@"
2035 version | -version | --version)
2036 shift
2037 set dummy '--version' "$@"
2038 shift
2039 svn_version
2040 exec $SVN "$@"
2042 *) exec $SVN "$@"
2044 esac