Use more here-doc strings.
[svn-wrapper.git] / svn-wrapper.sh
blobe743b8fc5c28a1311b3935d03424501f8edabef4
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 # Whether we're diffing interactively or not
118 diff_interactive=no
120 me=$0
121 bme=`basename "$0"`
123 # Pitfall: some users might be tempted to export SVN=svn-wrapper.sh for some
124 # reason. This is just *wrong*. The following is an attempt to save them from
125 # some troubles.
126 if [ x`basename "$SVN"` = x"$bme" ]; then
127 echo "warning: setting SVN to $bme is wrong"
128 SVN='svn'
131 # This code comes (mostly) from Autoconf.
132 # The user is always right.
133 if test "${PATH_SEPARATOR+set}" != set; then
134 PATH_SEPARATOR=:
135 (PATH='/bin;/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 && {
136 (PATH='/bin:/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 ||
137 PATH_SEPARATOR=';'
141 version='0.4'
142 full_rev='$Id$'
143 # Get the first 6 digits (without forking)
144 revision=${full_rev#'$'Id': '}
145 revision=${revision%??????????????????????????????????' $'}
147 # The `main' really starts after the functions definitions.
149 # ---------------- #
150 # Helper functions #
151 # ---------------- #
153 set_colors()
155 red='\e[0;31m'; lred='\e[1;31m'
156 green='\e[0;32m'; lgreen='\e[1;32m'
157 yellow='\e[0;33m'; lyellow='\e[1;33m'
158 blue='\e[0;34m'; lblue='\e[1;34m'
159 purple='\e[0;35m'; lpurple='\e[1;35m'
160 cyan='\e[0;36m'; lcyan='\e[1;36m'
161 grey='\e[0;37m'; lgrey='\e[1;37m'
162 white='\e[0;38m'; lwhite='\e[1;38m'
163 std='\e[m'
166 set_nocolors()
168 red=; lred=
169 green=; lgreen=
170 yellow=; lyellow=
171 blue=; lblue=
172 purple=; lpurple=
173 cyan=; lcyan=
174 grey=; lgrey=
175 white=; lwhite=
176 std=
179 # abort err-msg
180 abort()
182 echo "svn-wrapper: ${lred}abort${std}: $@" \
183 | sed '1!s/^[ ]*/ /' >&2
184 exit 1
187 # warn msg
188 warn()
190 echo "svn-wrapper: ${lred}warning${std}: $@" \
191 | sed '1!s/^[ ]*/ /' >&2
194 # notice msg
195 notice()
197 echo "svn-wrapper: ${lyellow}notice${std}: $@" \
198 | sed '1!s/^[ ]*/ /' >&2
201 # yesno question
202 yesno()
204 printf "$@ [y/N] "
205 read answer || return 1
206 case $answer in
207 y* | Y*) return 0;;
208 *) return 1;;
209 esac
210 return 42 # should never happen...
213 # yesnewproceed what
214 # returns true if `yes' or `proceed', false if `new'.
215 # the answer is stored in $yesnoproceed_res which is /yes|new|proceed/
216 yesnewproceed()
218 printf "$@ [(y)es/(p)roceed/(u)p+proceed/(N)ew] "
219 read answer || return 1
220 case $answer in
221 y* | Y*) yesnoproceed_res=yes; return 0;;
222 p* | P*) yesnoproceed_res=proceed; return 0;;
223 u* | U*) yesnoproceed_res=upproceed; return 0;;
224 *) yesnoproceed_res=new; return 1;;
225 esac
226 return 42 # should never happen...
229 # warn_env env-var
230 warn_env()
232 warn "cannot find the environment variable $1
233 You might consider using \`export $1='<FIXME>'\`"
236 # get_unique_file_name file-name
237 get_unique_file_name()
239 test -e "$1" || {
240 echo "$1" && return 0
242 gufn=$1; i=1
243 while test -e "$gufn.$i"; do
244 i=$(($i + 1))
245 done
246 echo "$gufn.$i"
249 # ensure_not_empty description value
250 ensure_not_empty()
252 case $2 in #(
253 # `value' has at least one non-space character.
254 *[-a-zA-Z0-9_!@*{}|,./:]*)
255 return;;
256 esac
257 ene_val=`echo "$2" | tr -d ' \t\n'`
258 test -z "$ene_val" && abort "$1: empty value"
261 # find_prog prog-name
262 # return true if prog-name is in the PATH
263 # echo the full path to prog-name on stdout.
264 # Based on a code from texi2dvi
265 find_prog()
267 save_IFS=$IFS
268 IFS=$PATH_SEPARATOR
269 for dir in $PATH; do
270 IFS=$save_IFS
271 test -z "$dir" && continue
272 # The basic test for an executable is `test -f $f && test -x $f'.
273 # (`test -x' is not enough, because it can also be true for directories.)
274 # We have to try this both for $1 and $1.exe.
276 # Note: On Cygwin and DJGPP, `test -x' also looks for .exe. On Cygwin,
277 # also `test -f' has this enhancement, bot not on DJGPP. (Both are
278 # design decisions, so there is little chance to make them consistent.)
279 # Thusly, it seems to be difficult to make use of these enhancements.
281 if test -f "$dir/$1" && test -x "$dir/$1"; then
282 echo "$dir/$1"
283 return 0
284 elif test -f "$dir/$1.exe" && test -x "$dir/$1.exe"; then
285 echo "$dir/$1.exe"
286 return 0
288 done
289 return 1
292 # find_progs prog [progs...]
293 # Look in PATH for one of the programs given in argument.
294 # If none of the progs can be found, the string "exit 2" is "returned".
295 find_progs()
297 # This code comes mostly from Autoconf.
298 for fp_prog in "$@"; do
299 fp_res=`find_prog $fp_prog`
300 if [ $? -eq 0 ]; then
301 echo "$fp_res"
302 return 0
304 done
305 echo "exit 2"
306 return 1
309 test x"$EDITOR" = xmissing && EDITOR=`find_progs vim vi emacs nano`
310 test x"$PAGER" = xmissing && PAGER=`find_progs less more`
311 test x"$AWK" = xmissing && AWK=`find_progs gawk mawk nawk awk`
312 test x"$DIFF" = xmissing && DIFF=`find_progs diff`
313 if test x"$COLORDIFF" = xmissing; then
314 if test -t 1; then
315 COLORDIFF=`find_progs colordiff diff`
316 else
317 COLORDIFF=$DIFF
321 # If `less' is the $PAGER, turn on some useful options: -R to let it interpret
322 # some terminal codes (turns on colors and accentuated characters), -i to
323 # allow case-insensitive search if the pattern doesn't contain upper case
324 # letters.
325 case $PAGER in #(
326 less | 'less '*) PAGER="$PAGER -R -i";;
327 esac
329 # require_diffstat
330 # return true if diffstat is in the PATH
331 require_diffstat()
333 if [ x"$require_diffstat_cache" != x ]; then
334 return $require_diffstat_cache
336 if (echo | diffstat) >/dev/null 2>/dev/null; then :; else
337 warn 'diffstat is not installed on your system or not in your PATH.'
338 test -f /etc/debian_version \
339 && notice 'you might want to `apt-get install diffstat`.'
340 require_diffstat_cache=1
341 return 1
343 require_diffstat_cache=0
344 return 0
347 # require_mail
348 # return 0 -> found a mailer
349 # return !0 -> no mailer found
350 # The full path to the program found is echo'ed on stdout.
351 require_mail()
353 save_PATH=$PATH
354 PATH="${PATH}${PATH_SEPARATOR}/sbin${PATH_SEPARATOR}/usr/sbin${PATH_SEPARATOR}/usr/libexec"
355 export PATH
356 find_progs sendEmail sendmail mail
357 rv=$?
358 PATH=$save_PATH
359 export PATH
360 return $rv
363 # my_sendmail mail-file mail-subject mail-to [extra-headers]
364 # mail-to is a comma-separated list of email addresses.
365 # extra-headers is an optionnal argument and will be prepended at the
366 # beginning of the mail headers if the tool used to send mails supports it.
367 # The mail-file may also contain headers. They must be separated from the body
368 # of the mail by a blank line.
369 my_sendmail()
371 test -f "$1" || abort "my_sendmail: Cannot find the mail file: $1"
372 test -z "$2" && warn 'my_sendmail: Empty subject.'
373 test -z "$3" && abort 'my_sendmail: No recipient specified.'
375 content_type='Content-type: text/plain'
376 extra_headers="X-Mailer: svn-wrapper v$version (g$revision)
377 Mime-Version: 1.0
378 Content-Transfer-Encoding: 7bit"
379 if test x"$4" != x; then
380 extra_headers="$4
381 $extra_headers"
382 # Remove empty lines.
383 extra_headers=`echo "$extra_headers" | sed '/^[ ]*$/d;s/^[ ]*//'`
386 # If we have a signature, add it.
387 if test -f ~/.signature; then
388 # But don't add it if it is already in the mail.
389 if grep -Fe "`cat ~/.signature`" "$1" >/dev/null; then :; else
390 echo '-- ' >>"$1" && cat ~/.signature >>"$1"
393 # VCS-compat: handle user option 'sign'.
394 if (grep '^sign: false' ~/.vcs) >/dev/null 2>/dev/null; then :; else
395 ($GPG -h) >/dev/null 2>/dev/null
396 gpg_rv=$?
397 if [ -e ~/.gnupg ] || [ -e ~/.gpg ] || [ -e ~/.pgp ] && [ $gpg_rv -lt 42 ]
398 then
399 if grep 'BEGIN PGP SIGNATURE' "$1" >/dev/null; then
400 notice 'message is already GPG-signed'
401 elif yesno "Sign the mail using $GPG ?"; then
402 # Strip the headers
403 sed '1,/^$/d' "$1" >"$1.msg"
404 sed '1,/^$/!d' "$1" >"$1.hdr"
405 # Sign the message, keep only the PGP signature.
406 $GPG --clearsign <"$1.msg" >"$1.tmp" || {
407 rm -f "$1.msg" "$1.hdr" "$1.tmp"
408 abort "\`$GPG' failed (r=$?)"
410 sed '/^--*BEGIN PGP SIGNATURE--*$/,/^--*END PGP SIGNATURE--*$/!d' \
411 "$1.tmp" >"$1.sig"
413 boundary="svn-wrapper-2-$RANDOM"
414 boundary="$boundary$RANDOM"
415 # Prepend some stuff before the PGP signature.
416 echo "
417 --$boundary
418 content-type: application/pgp-signature; x-mac-type=70674453;
419 name=PGP.sig
420 content-description: This is a digitally signed message part
421 content-disposition: inline; filename=PGP.sig
422 content-transfer-encoding: 7bit" | cat - "$1.sig" >"$1.tmp"
423 mv -f "$1.tmp" "$1.sig"
425 # Append some stuff after the PGP signature.
426 echo "
427 --$boundary--" >>"$1.sig"
428 # Re-paste the headers before the signed body and prepend some stuff.
429 echo "This is an OpenPGP/MIME signed message (RFC 2440 and 3156)
430 --$boundary
431 Content-Transfer-Encoding: 8bit
432 Content-Type: text/plain; charset=iso-8859-1; format=flowed
434 | cat "$1.hdr" - "$1.msg" "$1.sig" >"$1.tmp" \
435 && mv -f "$1.tmp" "$1"
436 content_type="Content-Type: multipart/signed;\
437 protocol=\"application/pgp-signature\"; micalg=pgp-sha1;\
438 boundary=\"$boundary\""
439 # Cleanup.
440 rm -f "$1.tmp" "$1.sig" "$1.msg" "$1.hdr"
441 extra_headers="$extra_headers
442 X-Pgp-Agent: `$GPG --version | sed q`"
446 extra_headers="$extra_headers
447 $content_type"
449 mailer=`require_mail`
450 if [ $? -ne 0 ]; then
451 warn 'my_sendmail: No suitable mailer found.'
452 return 1
454 case $mailer in
455 */sendmail)
456 to=`echo "$3" | sed 's/,//g'`
457 echo "$extra_headers" | cat - "$1" | $mailer $to;;
458 */mail)
459 cat "$1" | $mailer -s "$2" "$3";;
460 */sendEmail)
461 if [ x"$SMTP" = x ]; then
462 warn 'my_sendmail: (sendEmail) please tell me the SMTP server to use'
463 printf 'STMP server: '
464 read SMTP || abort 'could not read the SMTP server'
465 notice "hint: you can export SMTP=$SMTP if you don't want to be asked"
467 sendEmail -f "$FULLNAME <$EMAIL>" -t "$3" -u "$2" \
468 -s "$SMTP" -o message-file="$1";;
469 *) # wtf
470 abort 'my_sendmail: Internal error.';;
471 esac
474 # selfupdate
475 selfupdate()
477 my_url='http://www.lrde.epita.fr/~sigoure/svn-wrapper'
478 # You can use https if you feel paranoiac.
480 echo ">>> Fetching svn-wrapper.sh from $my_url/svn-wrapper.sh"
482 # --------------------- #
483 # Fetch the new version #
484 # --------------------- #
486 tmp_me=`get_unique_file_name "$TMPDIR/svn-wrapper.sh"`
487 if (wget --help) >/dev/null 2>/dev/null; then
488 wget --no-check-certificate "$my_url/svn-wrapper.sh" -O "$tmp_me"
489 my_wget='wget'
490 else
491 curl --help >/dev/null 2>/dev/null
492 if [ $? -gt 42 ]; then
493 abort 'Cannot find wget or curl.
494 How can I download any update without them?'
496 my_wget='curl'
497 curl --insecure "$my_url/svn-wrapper.sh" >"$tmp_me"
500 test -r $tmp_me \
501 || abort "Cannot find the copy of myself I downloaded in $tmp_me"
503 # ---------------------------------------- #
504 # Compare versions and update if necessary #
505 # ---------------------------------------- #
507 my_ver=$revision
508 tmp_ver=`sed '/^# $Id[:].*$/!d;
509 s/.*$Id[:] *\([a-f0-9]\{6\}\).*/\1/' "$tmp_me"`
510 test -z "$tmp_ver" && abort "Cannot find the revision of $tmp_me"
511 if [ x"$my_ver" != x"$tmp_ver" ]; then # There IS an update...
512 echo "An update is available, r$tmp_ver (your version is r$my_ver)"
514 # Wanna see the diff?
515 if yesno 'Do you want to see the diff?'
516 then
517 (require_diffstat && diff -uw "$me" "$tmp_me" | diffstat;
518 echo
519 $COLORDIFF -uw "$me" "$tmp_me") | $PAGER
522 # Let's go :)
523 if yesno "Overwrite $me (r$my_ver) with $tmp_me (r$tmp_ver)?"; then
524 chmod a+x "$tmp_me"
525 cp -p "$me" "$me.r$my_ver"
526 mv "$tmp_me" "$me" && exit 0
528 rm -f "$tmp_me"
529 return 1
530 else
531 echo "You're already up to date [r$my_ver] :)"
533 rm -f "$tmp_me"
536 # get_svn_diff_and_diffstat [files to diff]
537 # Helper for svn_commit
538 get_svn_diff_and_diffstat()
540 if $git_mode; then
541 svn_diff=`git diff --ignore-all-space --no-color -B -C --cached`
542 svn_diff_stat=`git diff --stat --ignore-all-space --no-color -B -C --cached`
543 else
544 # Ignore white spaces. Can't svn diff -x -w: svn 1.4 only.
545 svn_diff=`svn_diffw "$@"`
546 test -z "$svn_diff" && svn_diff=`$SVN diff "$@"`
547 if require_diffstat; then
548 svn_diff_stat=`echo "$svn_diff" | diffstat`
549 else
550 svn_diff_stat='diffstat not available'
555 # Helper. Sets the variables repos_url, git_branch, git_head, repos_root,
556 # extra_repos_info and using_git_svn properly.
557 git_get_repos_info_()
559 # FIXME: 1st commit: "fatal: bad default revision 'HEAD'" on stderr
560 git_config_list=`git config -l`
561 using_git_svn=false
562 case $git_config_list in #(
563 *svn-remote.*.url=*)
564 repos_url=`echo "$git_config_list" \
565 | sed '/^svn-remote.svn.url=\(.*\)$/!d;s//\1/'`
566 using_git_svn=:
567 ;; #(
568 *remote.origin.url=*)
569 repos_url=`echo "$git_config_list" \
570 | sed '/^remote.origin.url=\(.*\)$/!d;s//\1/'`
572 esac
573 test -z "$repos_url" && repos_url='(git:unknown)'
574 git_branch=`git branch | awk '/^\*/ { print substr($0, 3) }'`
575 if [ x"$git_branch" = x'(no branch)' ]; then
576 yesno 'You are on a detached HEAD, do you really want to continue?' \
577 || return 1
579 git_head=`git rev-list --pretty=format:%h HEAD --max-count=1 | sed '1d;q'`
580 extra_repos_info="Git branch: $git_branch (HEAD: $git_head)"
581 repos_root=$repos_url
584 # git_warn_missing_prop(PROP-NAME, SAMPLE-VALUE)
585 git_warn_missing_prop()
587 if $using_git_svn; then
588 warn "No $1 property set for this repository.
589 This is a git-svn repository, so you can set it in an SVN working copy:
590 svn propset $1 $2 .
591 Otherwise you can just set the property in your git-svn repository:
592 git config svnw.$1 $2"
593 else
594 warn "No $1 property set for this repository.
595 You can set it like this:
596 git config svnw.$1 $2"
600 # Helper. Find the `mailto' property, be it an SVN property or a git-config
601 # option. Relies on the value of $change_log_dir and sets the values of
602 # $mailto (the value of the `mailto' property) and $to (a string to append in
603 # templates that contains the `To:' line, or an empty string if no mail must
604 # be sent).
605 get_mailto_property()
607 test -d "$change_log_dir" || abort 'Internal error in get_mailto_property:
608 $change_log_dir not pointing to a directory'
609 if $git_mode; then
610 mailto=`git config svnw.mailto`
611 if [ x"$mailto" = x ] \
612 && [ -f "$change_log_dir/.git/svn/git-svn/unhandled.log" ]
613 then
614 mailto=`grep mailto "$change_log_dir/.git/svn/git-svn/unhandled.log"`
615 sed_tmp='$!d;s/^.*+dir_prop: . mailto //;s/%40/@/g;s/%2C/,/g;s/%20/ /g;'
616 mailto=`echo "$mailto" | sed "$sed_tmp"`
618 if [ x"$mailto" = x ]; then
619 git_warn_missing_prop 'mailto' 'maintainer1@foo.com,maint2@bar.com'
621 else
622 mailto=`$SVN propget mailto "$change_log_dir"`
625 if [ x"$mailto" = x ]; then
626 test x$new_user = xyes \
627 && warn "no svn property mailto found in $change_log_dir
628 You might want to set default email adresses using:
629 svn propset mailto 'somebody@mail.com, foobar@example.com'\
630 $change_log_dir" >&2
631 # Try to be VCS-compatible and find a list of mails in a *.rb.
632 if [ -d "$change_log_dir/vcs" ]; then
633 mailto=`grep '.@.*\..*' "$change_log_dir"/vcs/*.rb \
634 | tr '\n' ' ' \
635 | sed 's/^.*[["]\([^["]*@[^]"]*\)[]"].*$/\1/' | xargs`
636 test x"$mailto" != x && test x$new_user = xyes \
637 && notice "VCS-compat: found mailto: $mailto
638 in " "$change_log_dir"/vcs/*.rb
639 fi # end VCS compat
640 fi # end guess mailto
642 # Ensure that emails are comma-separated.
643 mailto=`echo "$mailto" | sed 's/[ ;]/,/g' | tr -s ',' | sed 's/,/, /g'`
646 if [ x"$mailto" != xdont_send_mails ] && [ x"$mailto" != xnothx ]; then
647 to="
648 To: $mailto"
654 # ------------------------------- #
655 # Hooks for standard SVN commands #
656 # ------------------------------- #
658 # svn_commit [args...]
659 # Here is how the commit process goes:
661 # First we look in the arguments passed to commit:
662 # If there are some files or paths, the user wants to commit these only. In
663 # this case, we must search for ChangeLogs from these paths. We might find
664 # more than one ChangeLog, in this case the user will be prompted to pick up
665 # one.
666 # Otherwise (no path passed in the command line) the user just wants to
667 # commit the current working directory.
668 # In any case, we schedule "ChangeLog" for commit.
670 # Alright now that we know which ChangeLog to use, we look in the ChangeLog's
671 # directory if there is a ",svn-log" file which would mean that a previous
672 # commit didn't finish successfully. If there is such a file, the user is
673 # prompted to know whether they want to resume that commit or simply start a
674 # new one.
675 # When the user wants to resume a commit, the ",svn-log" file is loaded and we
676 # retrieve the value of "$@" that was saved in the file.
677 # Otherwise we build a template ChangeLog entry.
678 # Then we open the template ChangeLog entry with $EDITOR so that the user
679 # fills it properly.
680 # Finally, we commit.
681 # Once the commit is sent, we ask the server to know which revision was
682 # commited and we also retrieve the diff. We then send a mail with these.
683 svn_commit()
685 here=`pwd -P`
686 dry_run=false
687 git_commit_all=false
688 use_log_message_from_file=false
689 log_message_to_use=
691 # Check if the user passed some paths to commit explicitly
692 # because in this case we must add the ChangeLog to the commit and search
693 # the ChangeLog from the dirname of that file.
694 i=0; search_from=; add_changelog=false; extra_files=
695 while [ $i -lt $# ]; do
696 arg=$1
697 case $arg in
698 --dry-run)
699 dry_run=:
700 shift
701 i=$(($i + 1))
702 continue
704 -a|--all)
705 git_commit_all=:
707 --use-log-file)
708 shift
709 test -z "$1" && abort "$arg needs an argument"
710 test -r "$1" || abort "'$1' does not seem to be readable"
711 test -w "$1" || abort "'$1' does not seem to be writable"
712 test -d "$1" && abort "'$1' seems to be a directory"
713 use_log_message_from_file=:
714 log_message_to_use=$1
715 shift
716 continue
718 esac
719 # If the argument is a valid path: add the ChangeLog in the list of
720 # files to commit
721 if test -e "$arg"; then
722 add_changelog=:
723 if test -d "$arg"; then
724 search_from_add=$arg
725 else
726 search_from_add=`dirname "$arg"`
728 search_from="$search_from:$search_from_add"
730 shift
731 set dummy "$@" "$arg"
732 shift
733 i=$(($i + 1))
734 done
735 if $add_changelog; then :; else
736 # There is no path/file in the command line: the user wants to commit the
737 # current directory. Make it explicit now:
738 extra_files=$here
740 search_from=`echo "$search_from" | sed 's/^://; s/^$/./'`
742 # ----------------- #
743 # Find ChangeLog(s) #
744 # ----------------- #
746 nb_chlogs=0; change_log_dirs=
747 save_IFS=$IFS; IFS=':'
748 for dir in $search_from; do
749 IFS=$save_IFS
750 $use_changelog || break
751 test -z "$dir" && dir='.'
752 # First: come back to the original place
753 cd "$here" || abort "Cannot cd to $here"
754 cd "$dir" || continue # Then: Enter $dir (which can be a relative path)
755 found=0
756 while [ $found -eq 0 ]; do
757 this_chlog_dir=`pwd -P`
758 if [ -f ./ChangeLog ]; then
759 found=1
760 nb_chlogs=$(($nb_chlogs + 1))
761 change_log_dirs="$change_log_dirs:$this_chlog_dir"
762 else
763 cd ..
765 # Stop searching when in / ... hmz :P
766 test x"$this_chlog_dir" = x/ && break
767 done # end while: did we find a ChangeLog
768 done # end for: find ChangeLogs in $search_from
769 if [ $nb_chlogs -gt 0 ]; then
770 change_log_dirs=`echo "$change_log_dirs" | sed 's/^://' | tr ':' '\n' \
771 | sort -u`
772 nb_chlogs=`echo "$change_log_dirs" | wc -l`
775 # Did we find a ChangeLog? More than one?
776 if [ $nb_chlogs -eq 0 ] && $use_changelog; then
777 if yesno 'svn-wrapper: Error: Cannot find a ChangeLog file!
778 You might want to create an empty one (eg: `touch ChangeLog` where appropriate)
779 Do you want to proceed without using a ChangeLog?'; then
780 cd "$here"
781 $SVN commit "$@"
782 return $?
783 else
784 return 1
786 elif [ $nb_chlogs -gt 1 ]; then
787 notice "$nb_chlogs ChangeLogs were found, pick up one:"
789 IFS=':'; i=0
790 for a_chlog_dir in $change_log_dirs; do
791 i=$(($i + 1))
792 echo "$i. $a_chlog_dir/ChangeLog"
793 done
794 printf "Which ChangeLog do you want to use? [1-$i] "
795 read chlog_no || abort 'Cannot read answer on stdin.'
797 case $chlog_no in
798 *[^0-9]*) abort "Invalid ChangeLog number: $chlog_no"
799 esac
800 test "$chlog_no" -le $i || abort "Invalid ChangeLog number: $chlog_no
801 max value was: $i"
802 test "$chlog_no" -ge 1 || abort "Invalid ChangeLog number: $chlog_no
803 min value was: 1"
804 change_log_dir=`echo "$change_log_dirs" | tr ':' '\n' | sed "${chlog_no}!d"`
805 else # Only one ChangeLog found
806 if $use_changelog; then
807 change_log_dir=$change_log_dirs
808 notice "using $change_log_dir/ChangeLog"
812 if $use_changelog; then
813 test -f "$change_log_dir/ChangeLog" \
814 || abort "No such file or directory: $change_log_dir/ChangeLog"
815 # Now we can safely schedule the ChangeLog for the commit.
816 extra_files="$extra_files:$change_log_dir/ChangeLog"
817 else
818 change_log_dir='.' # Hack. FIXME: Does this work in all cases?
821 if [ -d "$change_log_dir/.git" ] || $git_mode; then
822 SVN=git
823 git_mode=:
824 git_get_repos_info_
825 else
826 svn_st_tmp=`$SVN status "$change_log_dir"`
828 # Warn for files that are not added in the repos.
829 conflicts=`echo "$svn_st_tmp" | sed '/^ *$/d;
830 /^[^?]/d;
831 /^?.......*\/[,+]/d;
832 /^?......[,+]/d'`
833 if test x"$conflicts" != x; then
834 warn "make sure you don't want to \`svn add'
835 any of the following files before committing:"
836 echo "$conflicts" | sed "$sed_svn_st_color"
837 printf 'Type [ENTER] to continue :)' && read chiche_is_gay
840 # If there are changes in an svn:externals, advise the user to commit that
841 # first.
842 changed_externals=`echo "$svn_st_tmp" | $AWK \
843 'function printext()
845 if (ext && !printed)
847 print this_ext "\n";
848 printed = 1;
851 BEGIN { this_ext = ""; ext = 0; ext_modified = 0; }
852 /^Performing status on external/ {
853 ext = 1;
854 sub(/.* at ./, ""); sub(/.$/, ""); this_ext = $0;
855 printed = 0;
857 /^[ADMR]/ { ext_modified = ext; printext(); }
858 /^.[M]/ { ext_modified = ext; printext(); }
859 END { exit ext_modified; }'`
860 if [ $? -ne 0 ]; then
861 warn "the following external items have local modifications:
862 $changed_externals"
863 yesno "You are advised to commit them separately first. Continue anyway?" \
864 || return 1
867 # Detect unresolved conflicts / missing files.
868 conflicts=`echo "$svn_st_tmp" | sed '/^[C!]/!d'`
869 test x"$conflicts" != x && abort "there are unresolved conflicts (\`C')
870 and/or missing files (\`!'):
871 $conflicts"
873 svn_info_tmp=`$SVN info "$change_log_dir"`
874 test $? -ne 0 && abort "Failed to get svn info on $change_log_dir"
875 repos_root=`echo "$svn_info_tmp" | sed '/^Repository Root: /!d;s///'`
876 repos_url=`echo "$svn_info_tmp" | sed '/^URL: /!d;s///'`
877 # It looks like svn <1.3 didn't display a "Repository Root" entry.
878 test -z "$repos_root" && repos_root=$repos_url
881 cd "$here"
883 YYYY=`date '+%Y'`
884 MM=`date '+%m'`
885 DD=`date '+%d'`
887 # VCS-compat: handle user option 'new_user'
888 new_user='yes'
889 grep '^new_user: false' ~/.vcs >/dev/null 2>/dev/null && new_user='no'
891 edit_changelog=:
892 tmp_log="$change_log_dir/,svn-log"
893 $use_log_message_from_file \
894 && tmp_log=$log_message_to_use \
895 && edit_changelog=false
897 if [ -f "$tmp_log" ] \
898 && { $use_log_message_from_file \
899 || yesnewproceed "It looks like the last commit did not\
900 terminate successfully.
901 Would you like to resume it or proceed immediately?"; }; then
902 case $yesnoproceed_res in
903 *proceed) edit_changelog=false;;
904 esac
905 if test x"$yesnoproceed_res" = xupproceed; then
906 svn_update "$@" || abort 'update failed'
908 echo 'Resuming ...'
909 internal_tags=`sed '/^--- Internal stuff, DO NOT change please ---$/,$!d' \
910 "$tmp_log"`
911 saved_args=`echo "$internal_tags" | sed '/^args: */!d;s///'`
912 extra_files=`echo "$internal_tags" | sed '/^extra_files: */!d;s///'`
913 if [ x"$saved_args" != x ]; then
914 if [ x"$*" != x ] && [ x"$saved_args" != x"$*" ]; then
915 warn "overriding arguments:
916 you invoked $me with the following arguments: $@
917 they have been replaced by these: $saved_args"
918 set dummy $saved_args
919 shift
920 else
921 notice "setting the following arguments: $saved_args"
922 set dummy $saved_args
923 shift
925 elif [ x"$*" != x ]; then
926 warn "overriding arguments:
927 you invoked $me with the following arguments: $@
928 they have been dropped"
931 for i; do
932 case $i in
933 -a|--all)
934 git_commit_all=:
935 if [ $git_mode ]; then
936 (cd $change_log_dir && git add -v -u) || abort '`git add -v -u` failed'
939 esac
940 done
942 get_svn_diff_and_diffstat "$@"
944 # Update the file with the new diff/diffstat in case it changed.
945 $AWK 'BEGIN {
946 tlatbwbi_seen = 0;
947 ycewah_seen = 0;
949 /^--This line, and those below, will be ignored--$/ {
950 tlatbwbi_seen = 1;
952 /^ Your ChangeLog entry will appear here\.$/ {
953 if (tlatbwbi_seen) ycewah_seen = 1;
956 if (ycewah_seen != 2) print;
957 if (ycewah_seen == 1) ycewah_seen = 2;
959 END {
960 if (tlatbwbi_seen == 0)
962 print "--This line, and those below, will be ignored--\n\n" \
963 " Your ChangeLog entry will appear here.";
965 }' "$tmp_log" >"$tmp_log.tmp"
966 cat >>"$tmp_log.tmp" <<EOF
968 $svn_diff_stat
970 $svn_diff
972 $internal_tags
974 mv -f "$tmp_log.tmp" "$tmp_log" || abort "failed to write '$tmp_log'"
976 else # Build the template message.
978 # ------------------------------------ #
979 # Gather info for the template message #
980 # ------------------------------------ #
982 if $git_mode; then
983 projname=`git config svnw.project`
984 if [ x"$projname" = x ] \
985 && [ -f "$change_log_dir/.git/svn/git-svn/unhandled.log" ]
986 then
987 projname=`grep project "$change_log_dir/.git/svn/git-svn/unhandled.log"`
988 sed_tmp='$!d;s/^.*+dir_prop: . project //'
989 projname=`echo "$projname" | sed "$sed_tmp"`
991 if [ x"$projname" = x ]; then
992 git_warn_missing_prop 'project' 'myproj'
994 else
995 projname=`$SVN propget project "$change_log_dir"`
997 # Try to be VCS-compatible and find a project name in a *.rb.
998 if [ x"$projname" = x ] && [ -d "$change_log_dir/vcs" ]; then
999 projname=`sed '/common_commit/!d;s/.*"\(.*\)<%= rev.*/\1/' \
1000 "$change_log_dir"/vcs/*.rb`
1001 test x"$projname" != x && test x$new_user = xyes \
1002 && notice "VCS-compat: found project name: $projname
1003 in " "$change_log_dir"/vcs/*.rb
1005 test x"$projname" != x && projname=`echo "$projname" | sed '/[^ ]$/s/$/ /'`
1007 get_mailto_property
1009 test -z "$FULLNAME" && FULLNAME='Type Your Name Here' \
1010 && warn_env FULLNAME
1011 test -z "$EMAIL" && EMAIL='your.mail.here@FIXME.com' && warn_env EMAIL
1013 if $git_mode; then
1014 if $git_commit_all; then
1015 (cd $change_log_dir && git add -v -u) || abort '`git add -v -u` failed'
1017 my_git_st=`git diff -C --raw --cached`
1018 test $? -eq 0 || abort 'git diff failed'
1019 # Format: ":<old_mode> <new_mode> <old_sha1> <new_sha1> <status>[
1020 # <similarity score>]\t<file-name>"
1021 change_log_files=`echo "$my_git_st" | sed '
1022 t dummy_sed_1
1023 : dummy_sed_1
1024 s/^:[0-7 ]* [0-9a-f. ]* M[^ ]* \(.*\)$/ * \1: ./; t
1025 s/^:[0-7 ]* [0-9a-f. ]* A[^ ]* \(.*\)$/ * \1: New./; t
1026 s/^:[0-7 ]* [0-9a-f. ]* D[^ ]* \(.*\)$/ * \1: Remove./; t
1027 s/^:[0-7 ]* [0-9a-f. ]* R[^ ]* \([^ ]*\) \(.*\)$/ * \2: Rename from \1./;t
1028 s/^:[0-7 ]* [0-9a-f. ]* C[^ ]* \([^ ]*\) \(.*\)$/ * \2: Copy from \1./;t
1029 s/^:[0-7 ]* [0-9a-f. ]* T[^ ]* \(.*\)$/ * \1: ./; t
1030 s/^:[0-7 ]* [0-9a-f. ]* X[^ ]* \(.*\)$/ * \1: ???./; t
1031 s/^:[0-7 ]* [0-9a-f. ]* U[^ ]* \(.*\)$/ * \1: UNMERGED./; t
1033 else
1034 # --ignore-externals appeared after svn 1.1.1
1035 my_svn_st=`$SVN status --ignore-externals "$@" \
1036 || $SVN status "$@" | sed '/^Performing status on external/ {
1040 # Files to put in the ChangeLog entry.
1041 change_log_files=`echo "$my_svn_st" | sed '
1042 t dummy_sed_1
1043 : dummy_sed_1
1044 s/^M......\(.*\)$/ * \1: ./; t
1045 s/^A......\(.*\)$/ * \1: New./; t
1046 s/^D......\(.*\)$/ * \1: Remove./; t
1050 if [ x"$change_log_files" = x ]; then
1051 yesno 'Nothing to commit, continue anyway?' || return 1
1054 change_log_files=`echo "$change_log_files" | sort -u`
1056 get_svn_diff_and_diffstat "$@"
1058 # Get any older svn-log out of the way.
1059 test -f "$tmp_log" && mv "$tmp_log" `get_unique_file_name "$tmp_log"`
1060 # If we can't get an older svn-log out of the way, find a new name...
1061 test -f "$tmp_log" && tmp_log=`get_unique_file_name "$tmp_log"`
1062 if [ x$new_user = no ]; then
1063 commit_instructions='
1064 Instructions:
1065 - Fill the ChangeLog entry.
1066 - If you feel like, write a comment in the "Comment:" section.
1067 This comment will only appear in the email, not in the ChangeLog.
1068 By default only the location of the repository is in the comment.
1069 - Some tags will be replaced. Tags are of the form: <TAG>. Unknown
1070 tags will be left unchanged.
1071 - The tag <REV> may only be used in the Subject.
1072 - Your ChangeLog entry will be used as commit message for svn.'
1073 else
1074 commit_instructions=
1076 r_before_rev=r
1077 $git_mode && r_before_rev=
1078 test -z "$extra_repos_info" || extra_repos_info="
1079 $extra_repos_info"
1080 cat >"$tmp_log" <<EOF
1081 --You must fill this file correctly to continue-- -*- vcs -*-
1082 Title:
1083 Subject: ${projname}$r_before_rev<REV>: <TITLE>
1084 From: $FULLNAME <$EMAIL>$to
1086 Comment:
1087 URL: $repos_url$extra_repos_info
1089 ChangeLog:
1091 <YYYY>-<MM>-<DD> $FULLNAME <$EMAIL>
1093 <TITLE>
1094 $change_log_files
1096 --This line, and those below, will be ignored--
1097 $commit_instructions
1098 --Preview of the message that will be sent--
1100 URL: $repos_url$extra_repos_info
1101 Your comments (if any) will appear here.
1103 ChangeLog:
1104 $YYYY-$MM-$DD $FULLNAME <$EMAIL>
1106 Your ChangeLog entry will appear here.
1109 $svn_diff_stat
1111 $svn_diff
1114 echo "
1115 --- Internal stuff, DO NOT change please ---
1116 args: $@" >>"$tmp_log"
1117 echo "extra_files: $extra_files
1118 vi: ft=diff:noet:tw=76:" >>"$tmp_log"
1120 fi # end: if svn-log; then resume? else create template
1121 $edit_changelog && $EDITOR "$tmp_log"
1123 # ------------------ #
1124 # Re-"parse" the log #
1125 # ------------------ #
1127 # hmz this section is a bit messy...
1128 # helper string... !@#$%* escaping \\\\\\...
1129 sed_escape='s/\\/\\\\/g;s/@/\\@/g;s/&/\\\&/g'
1130 sed_eval_tags="s/<MM>/$MM/g; s/<DD>/$DD/g; s/<YYYY>/$YYYY/g"
1131 full_log=`sed '/^--*This line, and those below, will be ignored--*$/,$d;
1132 /^--You must fill this/d' "$tmp_log"`
1133 chlog_entry=`echo "$full_log" | sed '/^ChangeLog:$/,$!d; //d'`
1134 ensure_not_empty 'ChangeLog entry' "$chlog_entry"
1135 full_log=`echo "$full_log" | sed '/^ChangeLog:$/,$d'`
1136 mail_comment=`echo "$full_log" | sed '/^Comment:$/,$!d; //d'`
1137 full_log=`echo "$full_log" | sed '/^Comment:$/,$d'`
1138 mail_title=`echo "$full_log" | sed '/^Title: */!d;s///;'`
1139 ensure_not_empty 'commit title' "$mail_title"
1140 # Add a period at the end of the title.
1141 mail_title=`echo "$mail_title" | sed -e '/ *[.!?]$/!s/ *$/./' \
1142 -e "$sed_eval_tags; $sed_escape"`
1143 sed_eval_tags="$sed_eval_tags; s@<TITLE>\\.*@$mail_title@g"
1144 mail_comment=`echo "$mail_comment" | sed "$sed_eval_tags"`
1145 raw_chlog_entry=$chlog_entry # ChangeLog entry without tags expanded
1146 chlog_entry=`echo "$chlog_entry" | sed "$sed_eval_tags; 1{
1147 /^ *$/d
1149 mail_subject=`echo "$full_log" | sed '/^Subject: */!d;s///'`
1150 ensure_not_empty 'mail subject' "$mail_subject"
1151 mail_to=`echo "$full_log" | sed '/^To:/!d'`
1152 send_a_mail=:
1153 if test x"$mail_to" = x; then
1154 send_a_mail=false
1155 else
1156 mail_to=`echo "$mail_to" | sed 's/^To: *//;s///'`
1157 # If there is a <MAILTO> in the 'To:' line, we must expand it.
1158 case $mail_to in #(
1159 *'<MAILTO>'*)
1160 get_mailto_property
1161 # Are we meant to send a mail?
1162 case $to in #(
1163 '') # No, don't send a mail.
1164 mail_to=
1165 send_a_mail=false
1166 ;; #(
1167 *) # Yes, send a mail.
1168 mail_to=`echo "$mail_to" | sed "s#<MAILTO>#$mailto#g"`
1170 esac
1171 esac
1172 $send_a_mail && ensure_not_empty '"To:" field of the mail' "$mail_to"
1175 test -z "$FULLNAME" && warn_env FULLNAME && FULLNAME=$USER
1176 test -z "$EMAIL" && warn_env EMAIL && EMAIL=$USER
1177 myself=`echo "$FULLNAME <$EMAIL>" | sed "$sed_escape"`
1178 mail_from=`echo "$full_log" | sed "/^From: */!d;s///;s@<MYSELF>@$myself@g"`
1179 ensure_not_empty '"From:" field of the mail' "$mail_from"
1181 # ------------------------------------ #
1182 # Sanity checks on the ChangeLog entry #
1183 # ------------------------------------ #
1185 if echo "$chlog_entry" | grep '<REV>' >/dev/null; then
1186 warn 'Using the tag <REV> anywhere else than in the Subject is deprecated.'
1187 yesno 'Continue anyway?' || return 1
1190 if echo "$chlog_entry" | grep ': \.$' >/dev/null; then
1191 warn 'It looks like you did not fill all entries in the ChangeLog:'
1192 echo "$chlog_entry" | grep ': \.$'
1193 yesno 'Continue anyway?' || return 1
1196 if echo "$chlog_entry" | grep '^--* Internal stuff' >/dev/null; then
1197 warn "It looks like you messed up the delimiters and I did not properly
1198 find your ChangeLog entry. Here it is, make sure it is correct:"
1199 echo "$chlog_entry"
1200 yesno 'Continue anyway?' || return 1
1203 if echo "$chlog_entry" | grep -i 'dont[^a-z0-9]' >/dev/null; then
1204 warn "Please avoid typos such as ${lred}dont$std instead of\
1205 ${lgreen}don't$std:"
1206 echo "$chlog_entry" | grep -n -i 'dont[^a-z0-9]' \
1207 | sed "s/[dD][oO][nN][tT]/$lred&$std/g"
1208 yesno 'Continue anyway?' || return 1
1211 if echo "$chlog_entry" | grep -i 'cant[^a-z0-9]' >/dev/null; then
1212 warn "Please avoid typos such as ${lred}cant$std instead of\
1213 ${lgreen}can't$std:"
1214 echo "$chlog_entry" | grep -n -i 'cant[^a-z0-9]' \
1215 | sed "s/[cC][aA][nN][tT]/$lred&$std/g"
1216 yesno 'Continue anyway?' || return 1
1219 if echo "$chlog_entry" | grep '^.\{80,\}' >/dev/null; then
1220 warn 'Please avoid long lines in your ChangeLog entry (80 columns max):'
1221 echo "$chlog_entry" | grep '^.\{80,\}'
1222 yesno 'Continue anyway?' || return 1
1225 if echo "$mail_title" | grep -i '^[a-z][a-z]*ed[^a-z]' >/dev/null; then
1226 warn 'ChangeLog entries should be written in imperative form:'
1227 echo "$mail_title" | grep -i '^[a-z][a-z]*ed[^a-z]' \
1228 | sed "s/^\\([a-zA-Z][a-zA-Z]*ed\\)\\([^a-zA-Z]\\)/$lred\\1$std\\2/"
1229 yesno 'Continue anyway?' || return 1
1232 case $chlog_entry in
1233 *@FIXME* | *[tT]ype' '[yY]our' '[nN]ame*)
1234 warn 'Suspicious ChangeLog entry:'
1235 echo "$chlog_entry" | grep -i '@FIXME\|[tT]ype [yY]our [nN]ame'
1236 yesno 'Continue anyway?' || return 1
1238 esac
1240 case $mail_from in
1241 *@FIXME* | *[tT]ype' '[yY]our' '[nN]ame*)
1242 warn "Suspicious the From field: $mail_from"
1243 yesno 'Continue anyway?' || return 1
1245 esac
1247 # Check whether the user passed -m | --message
1249 while [ $i -lt $# ]; do
1250 arg=$1
1251 # This is not really a reliable way of knowing whether -m | --message was
1252 # passed but hum... Let's assume it'll do :s
1253 if [ x"$arg" = 'x-m' ] || [ x"$arg" = 'x--message' ]; then
1254 my_message=$2
1256 shift
1257 set dummy "$@" "$arg"
1258 shift
1259 i=$(($i + 1))
1260 done
1261 if [ x"$my_message" = x ]; then
1262 # The title must not be indented in the commit message and must be
1263 # followed by a blank line. This yields much better results with most
1264 # VC-viewer (especially for Git but including for SVN, such as Trac for
1265 # instance). We assume that the title will always be on the 1st line.
1266 sed_git_title="1s@^[ ]*<TITLE>\\.*@$mail_title\\
1267 @g; $sed_eval_tags"
1268 # First, remove empty lines at the beginning, if any.
1269 # Remove also the date information (useless in commit messages)
1270 my_message=`echo "$raw_chlog_entry" \
1271 | sed -e '1,4 {
1272 /^<YYYY>-<[MD][MD]>-<[DM][DM]>/d
1273 /^[1-9][0-9][0-9][0-9]-[0-9][0-9]*-[0-9][0-9]*/d
1274 /^ and .*<.*@.*>$/d
1275 /^ *$/d
1276 }' \
1277 | sed -e "$sed_git_title" \
1278 -e "$sed_eval_tags; 1{
1279 /^ *$/d
1281 else
1282 notice 'you are overriding the commit message.'
1285 # Show suspicious whitespace additions with Git.
1286 $git_mode && git diff --cached --check
1288 if $dry_run; then
1289 proposal_file=',proposal'
1290 test -f "$proposal_file" \
1291 && proposal_file=`get_unique_file_name "$proposal_file"`
1292 sed_tmp='s/<REV>/???/g;s/\([^.]\) *\.$/\1/'
1293 mail_subject=`echo "$mail_subject" | sed "$sed_eval_tags; $sed_tmp"`
1295 if [ x"$mailto" != xdont_send_mails ] && [ x"$mailto" != xnothx ]; then
1296 to="
1297 To: $mailto"
1299 cat >"$proposal_file" <<EOF
1300 From: $mail_from$to
1301 Subject: $mail_subject
1303 $mail_comment
1305 ChangeLog:
1306 $chlog_entry
1309 $svn_diff_stat
1311 $svn_diff
1313 notice "A proposal of your commit was left in '$proposal_file'"
1314 return 0
1317 # Are you sure?
1318 if $git_mode; then
1319 $git_commit_all \
1320 || notice 'You are using git, unlike SVN, do not forget to git add your
1321 changes'
1324 # Change edit_changelog so that we're asking the confirmation below.
1325 $use_log_message_from_file && edit_changelog=: \
1326 && notice "You are about to commit the following change:
1327 $mail_title"
1329 $edit_changelog \
1330 && {
1331 yesno 'Are you sure you want to commit?' \
1332 || return 1
1335 # Add the ChangeLog entry
1336 old_chlog=`get_unique_file_name "$change_log_dir/ChangeLog.old"`
1337 mv "$change_log_dir/ChangeLog" "$old_chlog" || \
1338 abort 'Could not backup ChangeLog'
1339 trap "echo SIGINT; mv \"$old_chlog\" \"$change_log_dir/ChangeLog\";
1340 exit 130" $SIGINT
1341 echo "$chlog_entry" >"$change_log_dir/ChangeLog"
1342 echo >>"$change_log_dir/ChangeLog"
1343 cat "$old_chlog" >>"$change_log_dir/ChangeLog"
1345 # Add extra files such as cwd or ChangeLog to the commit.
1346 tmp_sed='s/ /\\ /g' # Escape spaces for the shell.
1347 if $git_mode && $use_changelog; then
1348 # Schedule the ChangeLog for the next commit
1349 (cd "$change_log_dir" && git add ChangeLog) \
1350 || abort 'failed to git add the ChangeLog'
1351 extra_files=
1352 else
1353 extra_files=`echo "$extra_files" | sed "$tmp_sed" | tr ':' '\n'`
1356 # Always sign the commits with Git (but not with git-svn).
1357 $using_git_svn || $git_mode && set dummy --signoff "$@" && shift
1359 # Update the Git index if necessary (just in case the user changed his
1360 # working copy in the mean time)
1361 if $git_mode && $git_commit_all; then
1362 (cd $change_log_dir && git add -v -u) || abort '`git add -v -u` failed'
1365 # --Commit-- finally! :D
1366 $SVN commit -m "$my_message" "$@" $extra_files || {
1367 svn_commit_rv=$?
1368 mv "$old_chlog" "$change_log_dir/ChangeLog"
1369 abort "Commit failed, $SVN returned $svn_commit_rv"
1372 printf 'Getting the revision number... '
1373 if $git_mode; then
1374 REV=`git rev-list --pretty=format:%h HEAD --max-count=1 | sed '1d;q'`
1375 else
1376 svn_info_tmp=`$SVN info "$change_log_dir/ChangeLog"`
1377 REV=`echo "$svn_info_tmp" | sed '/^Revision: /!d;s///'`
1378 test -z "$REV" && REV=`echo "$svn_info_tmp" \
1379 | sed '/^Last Changed Rev: /!d;s///'`
1381 test -z "$REV" && abort 'Cannot detect the current revision.'
1382 echo "$REV"
1384 # Let's make sure we have the real diff by asking the ChangeSet we've just
1385 # committed to the server.
1387 # Backup the old stuff in case we fail to get the real diff from the server
1388 # for some reason...
1389 save_svn_diff=$svn_diff
1390 save_svn_diff_stat=$svn_diff_stat
1392 if $git_mode; then
1393 svn_diff=`git diff --ignore-all-space --no-color -C 'HEAD^' HEAD`
1394 svn_diff_stat=`git diff --stat --ignore-all-space --no-color -C 'HEAD^' HEAD`
1395 $using_git_svn &&
1396 notice 'Do not forget to use `git-svn dcommit` to push your commits in SVN'
1397 else
1398 # Fetch the ChangeSet and filter out the ChangeLog entry. We don't use
1399 # svn diff -c because this option is not portable to older svn versions.
1400 REV_MINUS_ONE=$(($REV - 1))
1401 svn_diff=`svn_diffw -r"$REV_MINUS_ONE:$REV" "$repos_root" \
1402 | $AWK '/^Index: / { if (in_chlog) in_chlog = 0; }
1403 /^Index: .*ChangeLog$/ { in_chlog = 1 }
1404 { if (!in_chlog) print }'`
1405 if [ x"$svn_diff" = x ]; then
1406 svn_diff=$save_svn_diff
1407 svn_diff_stat=$save_svn_diff_stat
1408 else
1409 if require_diffstat; then
1410 svn_diff_stat=`echo "$svn_diff" | diffstat`
1411 else
1412 svn_diff_stat='diffstat not available'
1417 # Expand <REV> and remove the final period from the mail subject if there is
1418 # only one period.
1419 sed_tmp="s/<REV>/$REV/g;"'s/\([^.]\) *\.$/\1/'
1420 mail_subject=`echo "$mail_subject" | sed "$sed_eval_tags; $sed_tmp"`
1422 mail_file=`get_unique_file_name "$change_log_dir/+mail"`
1423 cat >"$mail_file-t" <<EOF
1424 From: $mail_from
1425 To: $mail_to
1426 Subject: $mail_subject
1428 $mail_comment
1430 ChangeLog:
1431 $chlog_entry
1434 $svn_diff_stat
1436 $svn_diff
1438 # We change lines with only a `.' because they could mean "end-of-mail"
1439 sed 's/^\.$/ ./' "$mail_file-t" >"$mail_file"
1440 rm -f "$mail_file-t"
1442 # Send the mail
1443 if $send_a_mail; then
1444 trap 'echo SIGINT; exec < /dev/null' $SIGINT
1445 # FIXME: Move the mail to the +committed right now, in case the user
1446 # CTLR+C the mail-sending-thing, so that the mail will be properly saved
1447 # their.
1448 my_sendmail "$mail_file" "$mail_subject" "$mail_to" \
1449 "X-svn-url: $repos_root
1450 X-svn-revision: $REV"
1451 fi # end do we have to send a mail?
1452 rm -f "$tmp_log"
1453 rm -f "$old_chlog"
1454 save_mail_file=`echo "$mail_file" | sed 's/+//'`
1455 mkdir -p "$change_log_dir/+committed" \
1456 || warn "Couldn't mkdir -p $change_log_dir/+committed"
1457 if [ -d "$change_log_dir/vcs" ] \
1458 || [ -d "$change_log_dir/+committed" ]
1459 then
1460 mkdir -p "$change_log_dir/+committed/$REV" \
1461 && mv "$mail_file" "$change_log_dir/+committed/$REV/mail"
1463 return $svn_commit_rv
1466 # svn_diffw [args...]
1467 svn_diff()
1469 # Ignore white spaces.
1470 if $git_mode; then
1471 git diff -C "$@"
1472 else
1473 # Extra args for diff (with SVN only, Git has a decent diff).
1474 : ${svn_diffarg=-u}
1476 # If $PAGER contains -R, it most likely understand colors and stuff, so we
1477 # can use it to show the colored diff.
1478 diff_cmd_='$SVN diff --no-diff-deleted --diff-cmd $DIFF -x $svn_diffarg "$@"'
1479 case $diff_interactive:$PAGER in #(
1480 yes:*-R*) eval "$diff_cmd_" | $PAGER;;
1481 *) eval "$diff_cmd_"
1482 esac
1486 # svn_diffw [args...]
1487 svn_diffw()
1489 # Ignore white spaces.
1490 if $git_mode; then
1491 svn_diff --ignore-all-space "$@"
1492 else
1493 svn_diffarg='-uw'
1494 svn_diff "$@"
1498 # svn_mail REV [mails...]
1499 svn_mail()
1501 test $# -lt 1 && abort "Not enough arguments provided;
1502 Try 'svn help mail' for more info."
1503 case $1 in
1504 PREV)
1505 if $git_mode; then
1506 REV=`git rev-list --pretty=format:%h 'HEAD^' --max-count=1 | sed '1d;q'`
1507 else
1508 REV=`svn_revision || abort 'Cannot get current revision number'`
1509 test -z "$REV" && abort 'Cannot get current revision number'
1510 if [ "$REV" -lt 1 ]; then
1511 abort 'No previous revision.'
1513 REV=$(($REV - 1))
1516 HEAD)
1517 REV=`svn_revision || abort 'Cannot get current revision number'`
1518 test -z "$REV" && abort 'Cannot get current revision number'
1520 *) REV=$1;;
1521 esac
1522 shift
1524 found_committed=0; found=0
1525 while [ $found -eq 0 ]; do
1526 this_chlog_dir=`pwd -P`
1527 if [ -d ./+committed ]; then
1528 found_committed=1
1529 if [ -d ./+committed/$REV ]; then
1530 found=1
1531 else
1532 cd ..
1534 else
1535 cd ..
1537 # Stop searching when in / ... hmz :P
1538 test x`pwd` = x/ && break
1539 done
1540 if [ $found -eq 0 ]; then
1541 if [ $found_committed -eq 0 ]; then
1542 abort 'Could not find the +committed directory.'
1543 else
1544 abort "Could not find the revision $REV in +committed."
1546 abort 'Internal error (should never be here).'
1549 mail_file=; subject=; to=
1550 if [ -f ./+committed/$REV/mail ]; then
1551 # svn-wrapper generated file
1552 mail_file="./+committed/$REV/mail"
1553 subject=`sed '/^Subject: /!d;s///' $mail_file | sed '1q'`
1554 to=`sed '/^To: /!d;s///' $mail_file | sed '1q'`
1555 elif [ -f ./+committed/$REV/,iform ] && [ -f ./+committed/$REV/,message ]
1556 then
1557 # VCS-generated file
1558 subject=`sed '/^Subject: /!d;s///;s/^"//;s/"$//' ./+committed/$REV/,iform \
1559 | sed "s/<%= *rev *%>/$REV/g"`
1560 to=`sed '/^To:/,/^[^-]/!d' ./+committed/$REV/,iform | sed '1d;s/^- //;$d' \
1561 | xargs | sed 's/ */, /g'`
1562 mail_file=`get_unique_file_name "$TMPDIR/mail.r$REV"`
1563 echo "From: $FULLNAME <$EMAIL>
1564 To: $to
1565 Subject: $subject
1566 " >"$mail_file" || abort "Cannot create $mail_file"
1567 cat ./+committed/$REV/,message >>"$mail_file" \
1568 || abort "Cannot copy ./+committed/$REV/,message in $mail_file"
1569 else
1570 abort "Couldn't find the mail to re-send in `pwd`/+committed/$REV"
1572 if [ $# -gt 0 ]; then
1573 to=`echo "$*" | sed 's/ */, /g'`
1576 test -z "$to" && abort 'Cannot find the list of recipients.
1577 Please report this bug.'
1578 test -z "$subject" && abort 'Cannot find the subject of the mail.
1579 Please report this bug.'
1581 if yesno "Re-sending the mail of r$REV
1582 Subject: $subject
1583 To: $to
1584 Are you sure?"; then :; else
1585 return 1
1588 if $git_mode; then
1589 git_get_repos_info_
1590 else
1591 svn_info_tmp=`$SVN info`
1592 test $? -ne 0 && abort "Failed to get svn info on `pwd`"
1593 repos_root=`echo "$svn_info_tmp" | sed '/^Repository Root: /!d;s///'`
1594 repos_url=`echo "$svn_info_tmp" | sed '/^URL: /!d;s///'`
1595 # It looks like svn <1.3 didn't display a "Repository Root" entry.
1596 test -z "$repos_root" && repos_root=$repos_url
1599 my_sendmail "$mail_file" "$subject" "$to" \
1600 "X-svn-url: $repos_url
1601 X-svn-revision: $REV"
1604 # svn_version
1605 svn_version()
1607 echo "Using svn-wrapper v$version-g$revision (C) SIGOURE Benoit [GPL]"
1610 # has_prop prop-name [path]
1611 # return value: 0 -> path has the property prop-name set.
1612 # 1 -> path has no property prop-name.
1613 # 2 -> svn error.
1614 has_prop()
1616 hp_plist=`$SVN proplist "$2"`
1617 test $? -ne 0 && return 2
1618 hp_res=`echo "$hp_plist" | sed "/^ *$1\$/!d"`
1619 test -z "$hp_res" && return 1
1620 return 0
1623 # svn_propadd prop-name prop-val [path]
1624 svn_propadd()
1626 if $git_mode; then
1627 abort 'propadd is only for SVN, not for Git.'
1629 test $# -lt 2 \
1630 && abort 'Not enough arguments provided;
1631 try `svn help propadd` for more info'
1632 test $# -gt 3 \
1633 && abort 'Too many arguments provided;
1634 try `svn help propadd` for more info'
1636 path=$3
1637 test -z "$path" && path='.' && set dummy "$@" '.' && shift
1638 has_prop "$1" "$3" || {
1639 test $? -eq 2 && return 1 # svn error
1640 # no property found:
1641 yesno "'$path' has no property named '$1', do you want to add it?" \
1642 && $SVN propset "$@"
1643 return $?
1646 current_prop_val=`$SVN propget "$1" "$3"`
1647 test $? -ne 0 && abort "Failed to get the current value of property '$1'."
1649 $SVN propset "$1" "$current_prop_val
1650 $2" "$3" >/dev/null || abort "Failed to add '$3' in the property '$1'."
1652 current_prop_val=`$SVN propget "$1" "$3" || echo "$current_prop_val
1653 $2"`
1654 echo "property '$1' updated on '$path', new value:
1655 $current_prop_val"
1658 # svn_propsed prop-name sed-script [path]
1659 svn_propsed()
1661 if $git_mode; then
1662 abort 'propsed is only for SVN, not for Git.'
1664 test $# -lt 2 \
1665 && abort 'Not enough arguments provided;
1666 try `svn help propsed` for more info'
1667 test $# -gt 3 \
1668 && abort 'Too many arguments provided;
1669 try `svn help propsed` for more info'
1671 path=$3
1672 test -z "$path" && path='.'
1673 has_prop "$1" "$3" || {
1674 test $? -eq 2 && return 1 # svn error
1675 # no property found:
1676 abort "'$path' has no property named '$1'."
1679 prop_val=`$SVN propget "$1" "$3"`
1680 test $? -ne 0 && abort "Failed to get the current value of property '$1'."
1682 prop_val=`echo "$prop_val" | sed "$2"`
1683 test $? -ne 0 && abort "Failed to run the sed script '$2'."
1685 $SVN propset "$1" "$prop_val" "$3" >/dev/null \
1686 || abort "Failed to update the property '$1' with value '$prop_val'."
1688 new_prop_val=`$SVN propget "$1" "$3" || echo "$prop_val"`
1689 echo "property '$1' updated on '$path', new value:
1690 $new_prop_val"
1693 # svn_revision [args...]
1694 svn_revision()
1696 if $git_mode; then
1697 short=`git rev-list --pretty=format:%h HEAD --max-count=1 | sed '1d;q'`
1698 long=`git rev-list --pretty=format:%H HEAD --max-count=1 | sed '1d;q'`
1699 echo "$short ($long)"
1700 else
1701 svn_revision_info_out=`$SVN info "$@"`
1702 svn_revision_rv=$?
1703 echo "$svn_revision_info_out" | sed '/^Revision: /!d;s///'
1704 return $svn_revision_rv
1708 # svn_ignore [paths]
1709 svn_ignore()
1711 if [ $# -eq 0 ]; then # Simply display ignore-list.
1712 if $git_mode; then
1713 test -f .gitignore && cat .gitignore
1714 else
1715 $SVN propget 'svn:ignore'
1717 elif [ $# -eq 1 ]; then
1718 b=`basename "$1"`
1719 d=`dirname "$1"`
1720 if $git_mode; then
1721 echo "$b" >>"$d/.gitignore"
1722 git add "$d/.gitignore"
1723 notice 'files ignored in this directory:'
1724 cat "$d/.gitignore"
1725 else
1726 svn_propadd 'svn:ignore' "$b" "$d"
1728 else # Add arguments in svn:ignore.
1729 # This part is a bit tricky:
1730 # For each argument, we find all the other arguments with the same dirname
1731 # $dname and we svn:ignore them all in $dname.
1732 while [ $# -ne 0 ]; do
1733 arg=$1
1734 dname=`dirname "$1"`
1735 files=`basename "$1"`
1736 shift
1737 j=0; argc=$#
1738 while [ $j -lt $argc ] && [ $# -ne 0 ]; do
1739 this_arg=$1
1740 shift
1741 this_dname=`dirname "$this_arg"`
1742 this_file=`basename "$this_arg"`
1743 if [ x"$dname" = x"$this_dname" ]; then
1744 files="$files
1745 $this_file"
1746 else
1747 set dummy "$@" "$this_arg"
1748 shift
1750 j=$(($j + 1))
1751 done
1752 if $git_mode; then
1753 echo "$files" >>"$dname"/.gitignore
1754 git add "$dname"/.gitignore
1755 notice "files ignored in $dname:"
1756 cat "$dname"/.gitignore
1757 else
1758 svn_propadd 'svn:ignore' "$files" "$dname"
1760 done
1764 # svn_help
1765 svn_help()
1767 if [ $# -eq 0 ]; then
1768 svn_version
1769 $SVN help
1770 rv=$?
1771 echo '
1772 Additionnal commands provided by svn-wrapper:
1773 diffstat (ds)
1774 diffw (dw)
1775 ignore
1776 mail
1777 propadd (padd, pa) -- SVN only
1778 proposal
1779 propsed (psed) -- SVN only
1780 revision (rev)
1781 touch
1782 selfupdate (selfup)
1783 version'
1784 return $rv
1785 else
1786 case $1 in
1787 commit | ci)
1788 $SVN help commit | sed '/^Valid options:/i\
1789 Extra options provided by svn-wrapper:\
1790 \ --dry-run : do not commit, simply generate a patch with what\
1791 \ would have been comitted.\
1792 \ --use-log-file FILE : extract the ChangeLog entry from FILE. This\
1793 \ entry must be formated in a similar fashion to\
1794 \ what svn-wrapper usually asks you to fill in.\
1795 \ The FILE needs to be writable and will be\
1796 \ removed by svn-wrapper upon success.\
1800 diffstat | ds)
1801 require_diffstat
1802 echo 'diffstat (ds): Display the histogram from svn diff-output.'
1803 $SVN help diff | sed '1d;
1804 s/differences*/histogram/;
1805 2,35 s/diff/diffstat/g'
1807 diffw | dw)
1808 echo "diffw (dw): Display the differences without taking whitespaces\
1809 into account."
1810 $SVN help diff | sed '1d;
1811 2,35 s/diff\([^a-z]\)/diffw\1/g;
1812 /--diff-cmd/,/--no-diff-deleted/d'
1814 ignore)
1815 what='svn:ignore property'
1816 $git_mode && what='.gitignore file'
1817 cat <<EOF
1818 ignore: Add some files in the $what.
1819 usage: 1. ignore [PATH]
1820 2. ignore FILE [FILES...]
1822 1. Display the value of $what on [PATH].
1823 2. Add some files in the $what of the directory containing them.
1825 When adding ignores, each pattern is ignored in its own directory, e.g.:
1826 $bme ignore dir/file "d2/*.o"
1827 Will put 'file' in the $what of 'dir' and '*.o' in the
1828 $what of 'd2'
1830 Valid options:
1831 None.
1834 mail)
1835 echo 'mail: Resend the mail of a given commit.
1836 usage: mail REV [emails]
1838 REV must have an email file associated in +committed/REV.
1839 REV can also be PREV or HEAD.
1841 By default the mail is sent to same email addresses as during the original
1842 commit unless more arguments are given.'
1844 propadd | padd | pa)
1845 echo 'propadd (padd, pa): Add something in the value of a property.
1846 usage: propadd PROPNAME PROPVAL PATH
1847 This command only works in SVN mode.
1849 PROPVAL will be appended at the end of the property PROPNAME.
1851 Valid options:
1852 None.'
1854 proposal)
1855 echo 'proposal: Alias for: commit --dry-run.
1856 See: svn help commit.'
1858 propsed | psed)
1859 echo 'propsed (psed): Edit a property with sed.
1860 usage: propsed PROPNAME SED-ARGS PATH
1861 This command only works in SVN mode.
1863 eg: svn propsed svn:externals "s/http/https/" .
1865 Valid options:
1866 None.'
1868 revision | rev)
1869 echo 'revision (rev): Display the revision number of a local or remote item.'
1870 $SVN help info | sed '1d;
1871 s/information/revision/g;
1872 s/revision about/the revision of/g;
1873 2,35 s/info/revision/g;
1874 /-xml/d'
1876 touch)
1877 echo 'touch: Touch a file and svn add it.
1878 usage: touch FILE [FILES]...
1880 Valid options:
1881 None.'
1883 selfupdate | selfup | self-update | self-up)
1884 echo 'selfupdate (selfup): Attempt to update svn-wrapper.sh
1885 usage: selfupdate
1887 Valid options:
1888 None.'
1890 version)
1891 echo 'version: Display the version info of svn and svn-wrapper.
1892 usage: version
1894 Valid options:
1895 None.'
1897 *) $SVN help "$@";;
1898 esac
1902 # svn_status [args...]
1903 svn_status()
1905 if $git_mode; then
1906 git status "$@"
1907 return $?
1909 svn_status_out=`$SVN status "$@"`
1910 svn_status_rv=$?
1911 test -z "$svn_status_out" && return $svn_status_rv
1912 echo "$svn_status_out" | sed "$sed_svn_st_color"
1913 return $svn_status_rv
1916 # svn_update [args...]
1917 svn_update()
1919 svn_update_out=`$SVN update "$@"`
1920 svn_update_rv=$?
1921 echo "$svn_update_out" | sed "$sed_svn_up_colors"
1922 return $svn_update_rv
1925 # ------------------- #
1926 # `main' starts here. #
1927 # ------------------- #
1929 # Define colors if stdout is a tty.
1930 if test -t 1; then
1931 set_colors
1932 else # stdout isn't a tty => don't print colors.
1933 set_nocolors
1936 # Consider this as a sed function :P.
1937 sed_svn_st_color="
1938 t dummy_sed_1
1939 : dummy_sed_1
1940 s@^?\\(......\\)+@+\\1+@
1941 s@^?\\(......\\)\\(.*/\\)+@+\\1\\2+@
1942 s@^?\\(......\\),@,\\1,@
1943 s@^?\\(......\\)\\(.*/\\),@,\\1\\2,@
1944 s/^\\(.\\)C/\\1${lred}C${std}/
1945 t dummy_sed_2
1946 : dummy_sed_2
1947 s/^?/${lred}?${std}/; t
1948 s/^M/${lgreen}M${std}/; t
1949 s/^A/${lgreen}A${std}/; t
1950 s/^X/${lblue}X${std}/; t
1951 s/^+/${lyellow}+${std}/; t
1952 s/^D/${lyellow}D${std}/; t
1953 s/^,/${lred},${std}/; t
1954 s/^C/${lred}C${std}/; t
1955 s/^I/${purple}I${std}/; t
1956 s/^R/${lblue}R${std}/; t
1957 s/^!/${lred}!${std}/; t
1958 s/^~/${lwhite}~${std}/; t"
1960 sed_svn_up_colors="
1961 t dummy_sed_1
1962 : dummy_sed_1
1964 /^Updated/ t
1965 /^Fetching/ t
1966 /^External/ t
1967 s/^\\(.\\)C/\\1${lred}C${std}/
1968 s/^\\(.\\)U/\\1${lgreen}U${std}/
1969 s/^\\(.\\)D/\\1${lred}D${std}/
1970 t dummy_sed_2
1971 : dummy_sed_2
1972 s/^A/${lgreen}A${std}/; t
1973 s/^U/${lgreen}U${std}/; t
1974 s/^D/${lyellow}D${std}/; t
1975 s/^G/${purple}G${std}/; t
1976 s/^C/${lred}C${std}/; t"
1978 # For dev's:
1979 test "x$1" = x--debug && shift && set -x
1981 test "x$1" = x--git && shift && git_mode=: && SVN=git
1983 test "x$1" = x--no-changelog && shift && use_changelog=false
1985 case $1 in
1986 # ------------------------------- #
1987 # Hooks for standard SVN commands #
1988 # ------------------------------- #
1989 commit | ci)
1990 shift
1991 svn_commit "$@"
1993 help | \? | h)
1994 shift
1995 svn_help "$@"
1997 status | stat | st)
1998 shift
1999 svn_status "$@"
2001 update | up)
2002 shift
2003 svn_update "$@"
2005 # -------------------- #
2006 # Custom SVN commands #
2007 # -------------------- #
2008 diffstat | ds)
2009 shift
2010 if [ -d .git ]; then
2011 git diff --stat -C
2012 else
2013 require_diffstat && $SVN diff --no-diff-deleted "$@" | diffstat
2016 diff | di)
2017 shift
2018 DIFF=$COLORDIFF
2019 diff_interactive=yes
2020 svn_diff "$@"
2022 diffw | dw)
2023 shift
2024 DIFF=$COLORDIFF
2025 diff_interactive=yes
2026 svn_diffw "$@"
2028 ignore)
2029 shift
2030 svn_ignore "$@"
2032 log)
2033 if $git_mode; then # let Git handle log
2034 exec $SVN "$@"
2035 else # pipe svn log through PAGER by default
2036 exec $SVN "$@" | $PAGER
2039 mail)
2040 shift
2041 svn_mail "$@"
2043 propadd | padd | pa)
2044 shift
2045 svn_propadd "$@"
2047 proposal)
2048 shift
2049 svn_commit --dry-run "$@"
2051 propsed | psed)
2052 shift
2053 svn_propsed "$@"
2055 revision | rev)
2056 shift
2057 svn_revision "$@"
2059 touch)
2060 shift
2061 touch "$@" && $SVN add "$@"
2063 selfupdate | selfup | self-update | self-up)
2064 shift
2065 selfupdate "$@"
2067 version | -version | --version)
2068 shift
2069 set dummy '--version' "$@"
2070 shift
2071 svn_version
2072 exec $SVN "$@"
2074 *) exec $SVN "$@"
2076 esac