2 # Simple wrapper around svn featuring auto-ChangeLog entries and emailing.
4 # Copyright (C) 2006 Benoit Sigoure.
6 # This program is free software; you can redistribute it and/or
7 # modify it under the terms of the GNU General Public License
8 # as published by the Free Software Foundation; either version 2
9 # of the License, or (at your option) any later version.
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
21 # Quick install: alias svn=path/to/svn-wrapper.sh -- that's all.
27 # This script is a wrapper around the svn command-line client for UNIX. It has
28 # been designed mainly to automagically generate GNU-style ChangeLog entries
29 # when committing and mail them along with a diff and an optional comment from
30 # the author to a list of persons or a mailing list. It has been made so that
31 # it's as much portable as possible and covers as many use-case as possible.
33 # HOWEVER, there will be bugs, there will be cases in which the script doesn't
34 # wrap properly the svn-cli, etc. In this case, you can try to mail me at
35 # <tsuna at lrde dot epita dot fr>. Include the revision of the svn-wrapper
36 # you're using, and full description of what's wrong etc. so I can reproduce
39 # If you feel like, you can try to fix/enhance the script yourself. It only
40 # requires some basic Shell-scripting skills. Knowing sed might help :)
46 # If you're simply looking for the usage, run svn-wrapper.sh help (or
47 # svn help if you aliased `svn' on svn-wrapper.sh) as usual.
49 # This script is (hopefully) portable, widely commented and self-contained. Do
50 # not hesitate to hack it. It might look rather long (because it does a lot of
51 # things :P) but you should be able to easily locate which part of the code
54 # The script begins by defining several functions. Then it really starts where
55 # the comment "# `main' starts here. #" is placed.
56 # Some svn commands are hooked (eg, svn st displays colors). Hooks and
57 # extra commands are defined in functions named svn_<command-name>.
63 # * Handle things such as svn ci --force foobar.
64 # * svn automerge + automatic fill in branches/README.branches.
65 # * Automatic proxy configuration depending on the IP in ifconfig?
66 # * Customizable behavior/colors via some ~/.<config>rc file (?)
67 # * Automatically recognize svn cp and svn mv instead of writting "New" and
68 # "Remove" in the template ChangeLog entry (hard). Pair the and new/remove
69 # to prepare a good ChangeLog entry (move to X, copy from X) [even harder].
70 # => svn info says whether an item was copied from a given *URL* or not.
74 # No args, invoke svn...
75 test $# -lt 1 && exec $SVN
77 # Default values (the user can export them to override them).
86 # Override the locale.
90 # Pitfall: some users might be tempted to export SVN=svn-wrapper.sh for some
91 # reason. This is just *wrong*. The following is an attempt to save them from
93 if [ x
`basename "$SVN"` = 'xsvn-wrapper.sh' ]; then
97 # This code comes (mostly) from Autoconf.
98 # The user is always right.
99 if test "${PATH_SEPARATOR+set}" != set; then
100 trap "echo SIGINT; rm -f conf$$.sh; exit 130" SIGINT
101 echo "#! /bin/sh" >conf$$.sh
102 echo "exit 0" >>conf$$.sh
104 if (PATH
="/nonexistent;."; conf$$.sh
) >/dev
/null
2>&1; then
111 trap 'echo SIGINT; exit 130' SIGINT
115 revision
=`sed '/^# $Id[:].*$/!d;
118 s/[^0-9]*\([0-9][0-9]*\).*/\1/' "$0"`
121 # The `main' really starts after the functions definitions.
129 red
='\e[0;31m'; lred
='\e[1;31m'
130 green
='\e[0;32m'; lgreen
='\e[1;32m'
131 yellow
='\e[0;33m'; lyellow
='\e[1;33m'
132 blue
='\e[0;34m'; lblue
='\e[1;34m'
133 purple
='\e[0;35m'; lpurple
='\e[1;35m'
134 cyan
='\e[0;36m'; lcyan
='\e[1;36m'
135 grey
='\e[0;37m'; lgrey
='\e[1;37m'
136 white
='\e[0;38m'; lwhite
='\e[1;38m'
144 yellow
=''; lyellow
=''
146 purple
=''; lpurple
=''
156 echo "svn-wrapper: ${lred}abort${std}: $@" \
157 |
sed '1!s/^[ ]*/ /' >&2
164 echo "svn-wrapper: ${lred}warning${std}: $@" \
165 |
sed '1!s/^[ ]*/ /' >&2
171 echo "svn-wrapper: ${lyellow}notice${std}: $@" \
172 |
sed '1!s/^[ ]*/ /' >&2
179 read answer ||
return 1
184 return 42 # should never happen...
190 warn
"cannot find the environment variable $1
191 You might consider using \`export $1 <FIXME>\`"
194 # get_unique_file_name file-name
195 get_unique_file_name
()
198 echo "$1" && return 0
201 while test -e "$gufn.$i"; do
207 # ensure_not_empty description value
210 ene_val
=`echo "$2" | tr -d ' \t\n'`
211 test x
"$ene_val" = x
&& abort
"$1: empty value"
214 # find_prog prog-name
215 # return true if prog-name is in the PATH
216 # echo the full path to prog-name on stdout.
217 # Based on a code from texi2dvi
224 test x
"$dir" = x
&& continue
225 # The basic test for an executable is `test -f $f && test -x $f'.
226 # (`test -x' is not enough, because it can also be true for directories.)
227 # We have to try this both for $1 and $1.exe.
229 # Note: On Cygwin and DJGPP, `test -x' also looks for .exe. On Cygwin,
230 # also `test -f' has this enhancement, bot not on DJGPP. (Both are
231 # design decisions, so there is little chance to make them consistent.)
232 # Thusly, it seems to be difficult to make use of these enhancements.
234 if test -f "$dir/$1" && test -x "$dir/$1"; then
237 elif test -f "$dir/$1.exe" && test -x "$dir/$1.exe"; then
246 # return true if diffstat is in the PATH
249 if [ x
"$require_diffstat_cache" != x
]; then
250 return $require_diffstat_cache
252 if (echo | diffstat
) >/dev
/null
2>/dev
/null
; then :; else
253 warn
'diffstat is not installed on your system or not in your PATH.'
254 test -f /etc
/debian_version \
255 && notice
'you might want to `apt-get install diffstat`.'
256 require_diffstat_cache
=1
259 require_diffstat_cache
=0
264 # return 0 -> found sendmail
266 # 2 -> no mailer found
267 # The full path to the program found is echo'ed on stdout.
270 if [ x
"$require_mail_cache_rv" != x
]; then
271 echo "$require_mail_cache"
272 return $require_mail_cache_rv
275 PATH
="${PATH}${PATH_SEPARATOR}/sbin${PATH_SEPARATOR}/usr/sbin${PATH_SEPARATOR}/usr/libexec"
277 require_mail_cache
=`find_prog sendmail`
278 if [ $?
-eq 0 ] && [ x
"$require_mail_cache" != x
]; then
279 echo "$require_mail_cache"
280 require_mail_cache_rv
=0
285 require_mail_cache
=`find_prog mail`
286 if [ $?
-eq 0 ] && [ x
"$require_mail_cache" != x
]; then
287 echo "$require_mail_cache"
288 require_mail_cache_rv
=1
291 warn
'mail is not installed on your system or not in your PATH.'
292 test -f /etc
/debian_version \
293 && notice
'you might want to:
294 # apt-get install mailx
295 # dpkg-reconfigure exim'
296 require_mail_cache
=''
297 require_mail_cache_rv
=1
300 require_mail_cache
=''
301 require_mail_cache_rv
=42
305 # my_sendmail mail-file mail-subject mail-to [extra-headers]
306 # mail-to is a comma-separated list of email addresses.
307 # extra-headers is an optionnal argument and will be prepended at the
308 # beginning of the mail headers if the tool used to send mails supports it.
309 # The mail-file may also contain headers. They must be separated from the body
310 # of the mail by a blank line.
313 test -f "$1" || abort
"my_sendmail: Cannot find the mail file: $1"
314 test x
"$2" = x
&& warn
'my_sendmail: Empty subject.'
315 test x
"$3" = x
&& abort
'my_sendmail: No recipient specified.'
317 extra_headers
="Content-type: text/plain
318 X-Mailer: svn-wrapper v$version (r$revision)"
319 if test x
"$4" != x
; then
322 # Remove empty lines.
323 extra_headers
=`echo "$extra_headers" | sed '/^[ ]*$/d;s/^[ ]*//'`
326 test -f ~
/.signature
&& echo '-- ' >>"$1" && \
327 cat ~
/.signature
>>"$1"
328 # VCS-compat: handle user option 'sign'.
329 if (grep '^sign: false' ~
/.vcs
) >/dev
/null
2>/dev
/null
; then :; else
330 ($GPG -h) >/dev
/null
2>/dev
/null
332 if [ -e ~
/.gnupg
] ||
[ -e ~
/.gpg
] ||
[ -e ~
/.pgp
] && [ $gpg_rv -lt 42 ]
334 if yesno
"Sign the mail using $GPG ?"; then
335 # GPG sign the mail with the headers stripped.
336 cat "$1" |
sed '1,/^$/d' |
$GPG --clearsign >"$1.asc"
337 # Re-paste the headers before the signed body.
339 && cat "$1" |
sed '1,/^$/!d' |
cat - "$1.asc" >"$1.tmp" \
340 && mv -f "$1.tmp" "$1"
342 rm -f "$1.tmp" "$1.asc"
347 mailer
=`require_mail`
349 if [ $?
-ge 2 ] ||
[ x
"$mailer" = x
]; then
350 warn
'my_sendmail: No suitable mailer found.'
355 to
=`echo "$3" | sed 's/,//g'`
356 echo "$extra_headers" |
cat - "$1" |
$mailer $to;;
358 cat "$1" |
$mailer -s "$2" "$3";;
360 warn
'my_sendmail: Internal error.'; return 42;;
367 my_url
='http://www.lrde.epita.fr/~sigoure/svn-wrapper'
368 # You can use https if you feel paranoiac.
370 echo ">>> Fetching svn-wrapper.sh from $my_url/svn-wrapper.sh"
372 # --------------------- #
373 # Fetch the new version #
374 # --------------------- #
376 tmp_me
=`get_unique_file_name "$TMPDIR/svn-wrapper.sh"`
377 if (wget
--help) >/dev
/null
2>/dev
/null
; then
378 wget
--no-check-certificate "$my_url/svn-wrapper.sh" -O "$tmp_me"
381 curl
--help >/dev
/null
2>/dev
/null
382 if [ $?
-gt 42 ]; then
383 abort
'Cannot find wget or curl.
384 How can I download any update without them?'
387 curl
--insecure "$my_url/svn-wrapper.sh" >"$tmp_me"
391 || abort
"Cannot find the copy of myself I downloaded in $tmp_me"
393 # ---------------------------------------- #
394 # Compare versions and update if necessary #
395 # ---------------------------------------- #
398 tmp_ver
=`sed '/^# $Id[:].*$/!d;
401 s/[^0-9]*\([0-9][0-9]*\).*/\1/' "$tmp_me"`
402 test x
"$tmp_ver" = x
&& abort
"Cannot find the version of $tmp_me"
403 if [ "$my_ver" -lt "$tmp_ver" ]; then # There IS an update...
404 echo "An update is available, r$tmp_ver (your version is r$my_ver)"
407 if yesno
"Do you want to see the ChangeLog between r$my_ver and r$tmp_ver?"
409 my_chlog
=`get_unique_file_name "$TMPDIR/ChangeLog"`
411 wget
) wget
--no-check-certificate "$my_url/ChangeLog" -O "$my_chlog"
413 curl
) curl
--insecure "$my_url/ChangeLog" >"$my_chlog"
415 *) abort
'Should never be here.'
418 sed "/^r$my_ver/q" "$my_chlog" |
$PAGER
422 # Wanna see the diff?
423 if yesno
"Do you want to see the diff between r$my_ver and r$tmp_ver?"
425 (require_diffstat
&& diff -uw "$me" "$tmp_me" | diffstat
;
427 diff -uw "$me" "$tmp_me") |
$PAGER
431 if yesno
"Overwrite $me (r$my_ver) with $tmp_me (r$tmp_ver)?"; then
433 mv "$tmp_me" "$me" && exit 0
437 elif [ "$my_ver" -gt "$tmp_ver" ]; then
438 echo "Wow, you're more up to date than the master copy :)"
439 echo "Your version is r$my_ver and the master copy is r$tmp_ver."
440 if yesno
'Downgrade?'; then
442 mv "$tmp_me" "$me" && exit 0
445 echo "You're already up to date [r$my_ver] :)"
450 # ------------------------------- #
451 # Hooks for standard SVN commands #
452 # ------------------------------- #
454 # svn_commit [args...]
455 # Here is how the commit process goes:
456 # First we look in the arguments passed to commit:
457 # If there are some files or paths, the user wants to commit these only. In
458 # this case, we must search for ChangeLogs from these paths. We might find
459 # more than one ChangeLog, in this case the user will be prompted to pick up
461 # Otherwise (no path passed in the command line) the user just wants to
462 # commit the current working directory.
463 # In any case, we schedule "ChangeLog" for commit.
464 # Alright now that we know which ChangeLog to use, we look in the ChangeLog's
465 # directory if there is a ",svn-log" file which would mean that a previous
466 # commit didn't finish successfully. If there is such a file, the user is
467 # prompted to know whether they want to resume that commit or simply start a
469 # When the user wants to resume a commit, the ",svn-log" file is loaded and we
470 # retrieve the value of "$@" that was saved in the file.
471 # Otherwise we build a template ChangeLog entry.
472 # Then we open the template ChangeLog entry with $EDITOR so that the user
474 # Finally, we commit.
479 # Check if the user passed some paths to commit explicitly
480 # because in this case we must add the ChangeLog to the commit and search
481 # the ChangeLog from the dirname of that file.
482 i
=0; search_from
=''; add_changelog
=no
; extra_files
=''
483 while [ $i -lt $# ]; do
485 # If the argument is a valid path: add the ChangeLog in the list of
487 if test -e "$arg"; then
489 if test -d "$arg"; then
490 search_from_add
="$arg"
492 search_from_add
=`dirname "$arg"`
494 search_from
="$search_from:$search_from_add"
497 set dummy
"$@" "$arg"
501 if [ $add_changelog = no
]; then
502 # There is no path/file in the command line: the user wants to commit the
503 # current directory. Make it explicit now:
506 search_from
=`echo "$search_from" | sed 's/^://; s/^$/./'`
508 # ----------------- #
509 # Find ChangeLog(s) #
510 # ----------------- #
512 nb_chlogs
=0; change_log_dirs
=''
513 save_IFS
=$IFS; IFS
=':'
514 for dir
in $search_from; do
516 test -z "$dir" && dir
='.'
517 # First: come back to the original place
518 cd "$here" || abort
"Cannot cd to $here"
519 cd "$dir" ||
continue # Then: Enter $dir (which can be a relative path)
521 while [ $found -eq 0 ]; do
522 this_chlog_dir
=`pwd -P`
523 if [ -f .
/ChangeLog
]; then
525 nb_chlogs
=`expr $nb_chlogs + 1`
526 change_log_dirs
="$change_log_dirs:$this_chlog_dir"
530 # Stop searching when in / ... hmz :P
531 test x
"$this_chlog_dir" = x
/ && break
532 done # end while: did we find a ChangeLog
533 done # end for: find ChangeLogs in $search_from
534 if [ $nb_chlogs -gt 0 ]; then
535 change_log_dirs
=`echo "$change_log_dirs" | sed 's/^://' | tr ':' '\n' \
537 nb_chlogs
=`echo "$change_log_dirs" | wc -l`
540 # Did we find a ChangeLog? More than one?
541 if [ $nb_chlogs -eq 0 ]; then
542 if yesno
'svn-wrapper: Error: Cannot find a ChangeLog file!
543 You might want to create an empty one (eg: `touch ChangeLog` where appropriate)
544 Do you want to proceed without using a ChangeLog?'; then
551 elif [ $nb_chlogs -gt 1 ]; then
552 notice
"$nb_chlogs ChangeLogs were found, pick up one:"
555 for a_chlog_dir
in $change_log_dirs; do
557 echo "$i. $a_chlog_dir/ChangeLog"
559 echo -n "Which ChangeLog do you want to use? [1-$i] "
560 read chlog_no || abort
'Cannot read answer on stdin.'
563 *[^
0-9]*) abort
"Invalid ChangeLog number: $chlog_no"
565 test "$chlog_no" -le $i || abort
"Invalid ChangeLog number: $chlog_no
567 test "$chlog_no" -ge 1 || abort
"Invalid ChangeLog number: $chlog_no
569 change_log_dir
=`echo "$change_log_dirs" | tr ':' '\n' | sed "${chlog_no}!d"`
570 else # Only one ChangeLog found
571 change_log_dir
=$change_log_dirs
572 notice
"using $change_log_dir/ChangeLog"
575 test -f "$change_log_dir/ChangeLog" \
576 || abort
"No such file or directory: $change_log_dir/ChangeLog"
578 # Now we can safely schedule the ChangeLog for the commit.
579 extra_files
="$extra_files:$change_log_dir/ChangeLog"
581 svn_st_tmp
=`$SVN status "$change_log_dir"`
583 # Warn for files that are not added in the repos.
584 conflicts
=`echo "$svn_st_tmp" | sed '/^[^?]/d;
587 if test x
"$conflicts" != x
; then
588 warn
"make sure you don't want to \`svn add'
589 any of the following files before committing:"
590 echo "$conflicts" |
sed "$sed_svn_st_color"
591 echo -n 'Type [ENTER] to continue :)' && read chiche_is_gay
594 # Detect unresolved conflicts / missing files.
595 conflicts
=`echo "$svn_st_tmp" | sed '/^[C!]/!d'`
596 test x
"$conflicts" != x
&& abort
"there are unresolved conflicts (\`C')
597 and/or missing files (\`!'):
600 svn_info_tmp
=`$SVN info "$change_log_dir"`
601 test $?
-ne 0 && abort
"Failed to get svn info on $change_log_dir"
602 repos_root
=`echo "$svn_info_tmp" | sed '/^Repository Root: /!d;s///'`
603 # It looks like svn <1.3 didn't display a "Repository Root" entry.
604 test x
"$repos_root" = x
&& \
605 repos_root
=`echo "$svn_info_tmp" | sed '/^URL: /!d;s///'`
611 echo -n 'Please wait... '
612 svn_info_tmp
=`$SVN info "$repos_root"`
613 REV
=`echo "$svn_info_tmp" | sed '/^Revision: /!d;s///'`
614 test x
"$REV" = x
&& REV
=`echo "$svn_info_tmp" \
615 | sed '/^Last Changed Rev: /!d;s///'`
616 test x
"$REV" = x
&& abort
'Cannot detect the current revision.'
618 echo "next revision: $REV"
620 # VCS-compat: handle user option 'new_user'
622 grep '^new_user: false' ~
/.vcs
>/dev
/null
2>/dev
/null
&& new_user
='no'
624 tmp_log
="$change_log_dir/,svn-log"
625 if [ -f "$tmp_log" ] && yesno
"It looks like the last commit did not\
626 terminate successfully.
627 Would you like to resume it?"; then
629 internal_tags
=`sed '/^--- Internal stuff, DO NOT change please ---$/,$!d' \
631 saved_args
=`echo "$internal_tags" | sed '/^args: */!d;s///'`
632 extra_files
=`echo "$internal_tags" | sed '/^extra_files: */!d;s///'`
633 if [ x
"$saved_args" != x
]; then
634 if [ x
"$*" != x
] && [ x
"$saved_args" != x
"$*" ]; then
635 warn
"overriding arguments:
636 you invoked $me with the following arguments: $@
637 they have been replaced by these: $saved_args"
638 set dummy
$saved_args
641 notice
"setting the following arguments: $saved_args"
642 set dummy
$saved_args
646 # Ignore white spaces. Can't svn diff -x -w: svn 1.4 only.
647 svn_diff
=`svn_diffw "$@"`
648 test x
"$svn_diff" = x
&& svn_diff
=`$SVN diff "$@"`
649 if require_diffstat
; then
650 svn_diff_stat
=`echo "$svn_diff" | diffstat`
652 svn_diff_stat
='diffstat not available'
655 else # Build the template message.
657 # ------------------------------------ #
658 # Gather info for the template message #
659 # ------------------------------------ #
661 projname
=`$SVN propget project "$change_log_dir"`
662 # Try to be VCS-compatible and find a project name in a *.rb.
663 if [ x
"$projname" = x
] && [ -d "$change_log_dir/vcs" ]; then
664 projname
=`sed '/common_commit/!d;s/.*"\(.*\)<%= rev.*/\1/' \
665 "$change_log_dir"/vcs/*.rb`
666 test x
"$projname" != x
&& test x
$new_user = xyes \
667 && notice
"VCS-compat: found project name: $projname
668 in " "$change_log_dir"/vcs
/*.rb
670 test x
"$projname" != x
&& projname
=`echo "$projname" | sed '/[^ ]$/s/$/ /'`
672 mailto
=`$SVN propget mailto "$change_log_dir"`
673 if [ x
"$mailto" = x
]; then
674 test x
$new_user = xyes \
675 && warn
"no svn property mailto found in $change_log_dir
676 You might want to set default email adresses using:
677 svn propset mailto 'somebody@mail.com, foobar@example.com'\
679 # Try to be VCS-compatible and find a list of mails in a *.rb.
680 if [ -d "$change_log_dir/vcs" ]; then
681 mailto
=`grep '.@.*\..*' "$change_log_dir"/vcs/*.rb \
683 | sed 's/^.*[["]\([^["]*@[^]"]*\)[]"].*$/\1/' | xargs`
684 test x
"$mailto" != x
&& test x
$new_user = xyes \
685 && notice
"VCS-compat: found mailto: $mailto
686 in " "$change_log_dir"/vcs
/*.rb
688 fi # end guess mailto
690 # Ensure that emails are comma-separated.
691 mailto
=`echo "$mailto" | sed 's/[ ;]/,/g' | tr -s ',' | sed 's/,/, /g'`
692 test x
"$FULLNAME" = x
&& FULLNAME
='Type Your Name Here' \
694 test x
"$EMAIL" = x
&& EMAIL
='your.mail.here@FIXME.com' && warn_env EMAIL
696 # --ignore-externals appeared after svn 1.1.1
697 my_svn_st
=`$SVN status --ignore-externals "$@" \
698 || $SVN status "$@" | sed '/^Performing status on external/ {
702 # Files to put in the ChangeLog entry.
703 change_log_files
=`echo "$my_svn_st" | sed '
706 s/^M......\(.*\)$/ * \1: ./; t
707 s/^A......\(.*\)$/ * \1: New./; t
708 s/^D......\(.*\)$/ * \1: Remove./; t
711 if [ x
"$change_log_files" = x
]; then
712 yesno
'Nothing to commit, continue anyway?' ||
return 1
714 change_log_files
=`echo "$change_log_files" | sort`
716 svn_diff
=`svn_diffw "$@"`
717 test x
"$svn_diff" = x
&& svn_diff
=`$SVN diff "$@"`
718 if require_diffstat
; then
719 svn_diff_stat
=`echo "$svn_diff" | diffstat`
721 svn_diff_stat
='diffstat not available'
724 # Get any older svn-log out of the way.
725 test -f "$tmp_log" && mv "$tmp_log" `get_unique_file_name "$tmp_log"`
726 # If we can't get an older svn-log out of the way, find a new name...
727 test -f "$tmp_log" && tmp_log
=`get_unique_file_name "$tmp_log"`
728 if [ x
$new_user = no
]; then
729 commit_instructions
='
731 - Fill the ChangeLog entry.
732 - If you feel like, write a comment in the "Comment:" section.
733 This comment will only appear in the email, not in the ChangeLog.
734 By default only the location of the repository is in the comment.
735 - Some tags will be replaced. Tags are of the form: <TAG>. Unknown
736 tags will be left unchanged.
737 - Your ChangeLog entry will be used as commit message for svn.'
739 commit_instructions
=''
742 --You must fill this file correctly to continue-- -*- vcs -*-
744 Subject: ${projname}r<REV>: <TITLE>
745 From: $FULLNAME <$EMAIL>
749 Repository: $repos_root
753 <YYYY>-<MM>-<DD> $FULLNAME <$EMAIL>
758 --This line, and those below, will be ignored--
760 --Preview of the message that will be sent--
762 Repository: $repos_root
764 Your comments (if any) will appear here.
767 $YYYY-$MM-$DD $FULLNAME <$EMAIL>
769 Your ChangeLog entry will appear here.
773 $svn_diff" >"$tmp_log"
776 --- Internal stuff, DO NOT change please ---
777 args: $@" >>"$tmp_log"
778 echo "extra_files: $extra_files
779 vi: ft=diff" >>"$tmp_log"
781 fi # end: if svn-log; then resume? else create template
784 # ------------------ #
785 # Re-"parse" the log #
786 # ------------------ #
788 # hmz this section is a bit messy...
789 sed_escape
='s/@/\\@/' # helper string... !@#$%* escaping \\\\\\...
790 sed_eval_tags
="s/<MM>/$MM/g; s/<DD>/$DD/g; s/<YYYY>/$YYYY/g; s/<REV>/$REV/g"
791 full_log
=`sed '/^--This line, and those below, will be ignored--$/,$d;
792 /^--You must fill this/d' "$tmp_log"`
793 chlog_entry
=`echo "$full_log" | sed '/^ChangeLog:$/,$!d; //d'`
794 ensure_not_empty
'ChangeLog entry' "$chlog_entry"
795 full_log
=`echo "$full_log" | sed '/^ChangeLog:$/,$d'`
796 mail_comment
=`echo "$full_log" | sed '/^Comment:$/,$!d; //d'`
797 full_log
=`echo "$full_log" | sed '/^Comment:$/,$d'`
798 mail_title
=`echo "$full_log" | sed '/^Title: */!d;s///'`
799 ensure_not_empty
'commit title' "$mail_title"
800 mail_title
=`echo "$mail_title" | sed "$sed_eval_tags; $sed_escape"`
801 sed_eval_tags
="$sed_eval_tags; s@<TITLE>@$mail_title@g"
802 mail_comment
=`echo "$mail_comment" | sed "$sed_eval_tags"`
803 chlog_entry
=`echo "$chlog_entry" | sed "$sed_eval_tags; 1{
806 mail_subject
=`echo "$full_log" | sed '/^Subject: */!d;s///'`
807 ensure_not_empty
'mail subject' "$mail_subject"
808 mail_subject
=`echo "$mail_subject" | sed "$sed_eval_tags"`
809 mail_to
=`echo "$full_log" | sed '/^To:/!d'`
811 if test x
"$mail_to" = x
; then
814 mail_to
=`echo "$mail_to" | sed 's/^To: *//'`
815 ensure_not_empty
'"To:" field of the mail' "$mail_to"
817 mail_from
=`echo "$full_log" | sed '/^From: */!d;s///'`
818 ensure_not_empty
'"From:" field of the mail' "$mail_from"
820 # Check whether the user passed -m | --message
822 while [ $i -lt $# ]; do
824 # This is not really a reliable way of knowing whether -m | --message was
825 # passed but hum... Let's assume it'll do :s
826 test x
"$arg" = 'x-m' && has_message
=1
827 test x
"$arg" = 'x--message' && has_message
=1
829 set dummy
"$@" "$arg"
833 if [ $has_message -eq 0 ]; then
834 my_message
=`echo "$chlog_entry" | grep -v "^$YYYY-$MM-$DD" | sed '1,2 {
837 set dummy
--message "$my_message" "$@"
840 notice
'you are overriding the commit message.'
844 yesno
'Are you sure you want to commit?' ||
return 1
846 # Add the ChangeLog entry
847 old_chlog
=`get_unique_file_name "$change_log_dir/ChangeLog.old"`
848 mv "$change_log_dir/ChangeLog" "$old_chlog" || \
849 abort
'Could not backup ChangeLog'
850 trap "echo SIGINT; mv \"$old_chlog\" \"$change_log_dir/ChangeLog\";
852 echo "$chlog_entry" >"$change_log_dir/ChangeLog"
853 echo >>"$change_log_dir/ChangeLog"
854 cat "$old_chlog" >>"$change_log_dir/ChangeLog"
856 # Add extra files such as cwd or ChangeLog to the commit.
857 tmp_sed
='s/ /\\ /g' # Escape spaces for the shell.
858 extra_files
=`echo "$extra_files" | sed "$tmp_sed" | tr ':' '\n'`
859 set dummy
"$@" $extra_files
862 # --Commit-- finally! :D
863 $SVN commit
"$@" ||
{
865 mv "$old_chlog" "$change_log_dir/ChangeLog"
866 abort
"Commit failed, $SVN returned $svn_commit_rv"
870 mail_file
=`get_unique_file_name "$change_log_dir/+mail"`
874 Subject: $mail_subject
883 $svn_diff" |
sed 's/^\.$/ ./' >"$mail_file"
884 # We change lines with only a `.' because they could mean "end-of-mail"
887 if test x
$send_a_mail = xyes
; then
888 trap 'echo SIGINT; exec < /dev/null' SIGINT
889 my_sendmail
"$mail_file" "$mail_subject" "$mail_to" \
890 "X-svn-url: $repos_root
891 X-svn-revision: $REV"
892 fi # end do we have to send a mail?
895 save_mail_file
=`echo "$mail_file" | sed 's/+//'`
896 mkdir
-p "$change_log_dir/+committed" \
897 || warn
"Couldn't mkdir -p $change_log_dir/+committed"
898 if [ -d "$change_log_dir/vcs" ] \
899 ||
[ -d "$change_log_dir/+committed" ]
901 mkdir
-p "$change_log_dir/+committed/$REV" \
902 && mv "$mail_file" "$change_log_dir/+committed/$REV/mail"
904 return $svn_commit_rv
910 i
=0; src
=''; dst
=''; rev1
=''; rev2
=''; r_seen
=0
911 while [ $i -lt $# ]; do
913 if test -d "$arg"; then
914 if [ x
"$src" = x
]; then
916 elif [ x
"$dst" = x
]; then
921 -r*:*) rev1
=`echo "$arg" | sed 's/^[^PREVHEAD0-9]*//; s/:.*//'`
922 rev2
=`echo "$arg" | sed 's/^.*://; s/[^PREVHEAD0-9]*//'`
928 set dummy
"$@" "$arg"
932 if [ x
"$src" = x
]; then
935 test x
"$dst" = x
&& dst
='.'
937 if yesno
"Do you want to use the automerge feature for $src ?";
944 # svn_automerge src-path dst-path
947 test -f ChangeLog || abort
'You must use automerge in the folder where the
949 amerge
=`$SVN propget automerge`
950 if [ x
"$amerge" = x
]; then
951 # FIXME: Use svn log --stop-on-copy to detect the original branching.
955 svn_info_tmp
=`$SVN info`
956 REV
=`echo "$svn_info_tmp" | sed '/^Revision: /!d;s///'`
957 test x
"$REV" = x
&& REV
=`echo "$svn_info_tmp" \
958 | sed '/^Last Changed Rev: /!d;s///'`
959 test x
"$REV" = x
&& abort
'Cannot detect the current revision.'
962 if yesno
"Are you sure you want to run:
963 $SVN merge -r$amerge:$REV $1 $2 ?"; then
964 $SVN merge
"-r$amerge:$REV" "$1" "$2"
968 # svn_diffw [args...]
971 # Ignore white spaces. Can't svn diff -x -w: svn 1.4 only.
972 $SVN diff --no-diff-deleted --diff-cmd diff -x -uw "$@"
975 # svn_mail REV [mails...]
978 test $# -lt 1 && abort
"Not enough arguments provided;
979 Try 'svn help mail' for more info."
982 REV
=`svn_revision || abort 'Cannot get current revision number'`
983 test x
"$REV" = x
&& abort
'Cannot get current revision number'
984 if [ "$REV" -lt 1 ]; then
985 abort
'No previous revision.'
987 REV
=`expr "$REV" - 1`
990 REV
=`svn_revision || abort 'Cannot get current revision number'`
991 test x
"$REV" = x
&& abort
'Cannot get current revision number'
993 *[^
0-9]*) abort
"Syntax error in revision argument '$1'";;
998 found_committed
=0; found
=0
999 while [ $found -eq 0 ]; do
1000 this_chlog_dir
=`pwd -P`
1001 if [ -d .
/+committed
]; then
1003 if [ -d .
/+committed
/$REV ]; then
1011 # Stop searching when in / ... hmz :P
1012 test x
`pwd` = x
/ && break
1014 if [ $found -eq 0 ]; then
1015 if [ $found_committed -eq 0 ]; then
1016 abort
'Could not find the +committed directory.'
1018 abort
"Could not find the revision $REV in +committed."
1020 abort
'Internal error (should never be here).'
1023 mail_file
=''; subject
=''; to
=''
1024 if [ -f .
/+committed
/$REV/mail ]; then
1025 # svn-wrapper generated file
1026 mail_file
="./+committed/$REV/mail"
1027 subject
=`sed '/^Subject: /!d;s///' $mail_file | sed '1q'`
1028 to
=`sed '/^To: /!d;s///' $mail_file | sed '1q'`
1029 elif [ -f .
/+committed
/$REV/,iform
] && [ -f .
/+committed
/$REV/,message
]
1031 # VCS-generated file
1032 subject
=`sed '/^Subject: /!d;s///;s/^"//;s/"$//' ./+committed/$REV/,iform \
1033 | sed "s/<%= *rev *%>/$REV/g"`
1034 to
=`sed '/^To:/,/^[^-]/!d' ./+committed/$REV/,iform | sed '1d;s/^- //;$d' \
1035 | xargs | sed 's/ */, /g'`
1036 mail_file
=`get_unique_file_name "$TMPDIR/mail.r$REV"`
1037 echo "From: $FULLNAME <$EMAIL>
1040 " >"$mail_file" || abort
"Cannot create $mail_file"
1041 cat .
/+committed
/$REV/,message
>>"$mail_file" \
1042 || abort
"Cannot copy ./+committed/$REV/,message in $mail_file"
1044 abort
"Couldn't find the mail to re-send in `pwd`/+committed/$REV"
1046 if [ $# -gt 0 ]; then
1047 to
=`echo "$*" | sed 's/ */, /g'`
1050 test x
"$to" = x
&& abort
'Cannot find the list of recipients.
1051 Please report this bug.'
1052 test x
"$subject" = x
&& abort
'Cannot find the subject of the mail.
1053 Please report this bug.'
1055 if yesno
"Re-sending the mail of r$REV
1058 Are you sure?"; then :; else
1062 svn_info_tmp
=`$SVN info`
1063 test $?
-ne 0 && abort
"Failed to get svn info on `pwd`"
1064 repos_root
=`echo "$svn_info_tmp" | sed '/^Repository Root: /!d;s///'`
1065 # It looks like svn <1.3 didn't display a "Repository Root" entry.
1066 test x
"$repos_root" = x
&& \
1067 repos_root
=`echo "$svn_info_tmp" | sed '/^URL: /!d;s///'`
1069 my_sendmail
"$mail_file" "$subject" "$to" \
1070 "X-svn-url: $repos_root
1071 X-svn-revision: $REV"
1077 echo "Using svn-wrapper v$version (C) SIGOURE Benoit [GPL]"
1078 sed '/^# $Id[:].*$/!d;s/.*$Id[:] *//;s/ *$ *//;s/ \([0-9][0-9]*\)/ (r\1)/' "$me"
1081 # has_prop prop-name [path]
1082 # return value: 0 -> path has the property prop-name set.
1083 # 1 -> path has no property prop-name.
1087 hp_plist
=`$SVN proplist "$2"`
1088 test $?
-ne 0 && return 2
1089 hp_res
=`echo "$hp_plist" | sed "/^ *$1\$/!d"`
1090 test x
"$hp_res" = x
&& return 1
1094 # svn_propadd prop-name prop-val [path]
1098 && abort
'Not enough arguments provided;
1099 try `svn help propadd` for more info'
1101 && abort
'Too many arguments provided;
1102 try `svn help propadd` for more info'
1105 test x
"$path" = x
&& path
='.' && set dummy
"$@" '.' && shift
1106 has_prop
"$1" "$3" ||
{
1107 test $?
-eq 2 && return 1 # svn error
1108 # no property found:
1109 yesno
"'$path' has no property named '$1', do you want to add it?" \
1110 && $SVN propset
"$@"
1114 current_prop_val
=`$SVN propget "$1" "$3"`
1115 test $?
-ne 0 && abort
"Failed to get the current value of property '$1'."
1117 $SVN propset
"$1" "$current_prop_val
1118 $2" "$3" >/dev
/null || abort
"Failed to add '$3' in the property '$1'."
1120 current_prop_val
=`$SVN propget "$1" "$3" || echo "$current_prop_val
1122 echo "property '$1' updated on '$path', new value:
1126 # svn_propsed prop-name sed-script [path]
1130 && abort
'Not enough arguments provided;
1131 try `svn help propsed` for more info'
1133 && abort
'Too many arguments provided;
1134 try `svn help propsed` for more info'
1137 test x
"$path" = x
&& path
='.'
1138 has_prop
"$1" "$3" ||
{
1139 test $?
-eq 2 && return 1 # svn error
1140 # no property found:
1141 abort
"'$path' has no property named '$1'."
1144 prop_val
=`$SVN propget "$1" "$3"`
1145 test $?
-ne 0 && abort
"Failed to get the current value of property '$1'."
1147 prop_val
=`echo "$prop_val" | sed "$2"`
1148 test $?
-ne 0 && abort
"Failed to run the sed script '$2'."
1150 $SVN propset
"$1" "$prop_val" "$3" >/dev
/null \
1151 || abort
"Failed to update the property '$1' with value '$prop_val'."
1153 new_prop_val
=`$SVN propget "$1" "$3" || echo "$prop_val"`
1154 echo "property '$1' updated on '$path', new value:
1158 # svn_revision [args...]
1161 svn_revision_info_out
=`$SVN info "$@"`
1163 echo "$svn_revision_info_out" |
sed '/^Revision: /!d;s///'
1164 return $svn_revision_rv
1167 # svn_ignore [paths]
1170 if [ $# -eq 0 ]; then # Simply display ignore-list.
1171 $SVN propget
'svn:ignore'
1172 elif [ $# -eq 1 ]; then
1173 if [ -d "$1" ]; then # Display ignore-list.
1174 $SVN propget
'svn:ignore' "$1"
1175 else # Add in ignore-list.
1176 svn_propadd
'svn:ignore' `dirname "$1"` `basename "$1"`
1178 else # Add arguments in svn:ignore.
1179 # This part is a bit tricky:
1180 # For each argument, we find all the other arguments with the same dirname
1181 # $dname and we svn:ignore them all in $dname.
1182 while [ $# -ne 0 ]; do
1184 dname
=`dirname "$1"`
1185 files
=`basename "$1"`
1188 while [ $j -lt $argc ] && [ $# -ne 0 ]; do
1191 this_dname
=`dirname "$this_arg"`
1192 this_file
=`basename "$this_arg"`
1193 if [ x
"$dname" = x
"$this_dname" ]; then
1197 set dummy
"$@" "$this_arg"
1202 svn_propadd
'svn:ignore' "$files" "$dname"
1210 if [ $# -eq 0 ]; then
1215 Additionnal commands provided by svn-wrapper:
1216 automerge (amerge, am)
1230 automerge | auto-merge | amerge | am
)
1231 echo 'automerge (amerge, am): FIXME
1232 usage: automerge FIXME
1238 echo 'diffstat (ds): Display the histogram from svn diff-output.'
1239 $SVN help diff |
sed '1d;
1240 s/differences*/histogram/;
1241 2,35 s/diff/diffstat/g'
1244 echo "diffw (dw): Display the differences without taking whitespaces\
1246 $SVN help diff |
sed '1d;
1247 2,35 s/diff\([^a-z]\)/diffw\1/g;
1248 /--diff-cmd/,/--no-diff-deleted/d'
1251 echo 'ignore: Add some files in the svn:ignore property.
1252 usage: 1. ignore [PATH]
1253 2. ignore FILE [FILES...] [PATH]
1255 1. Display the value of svn:ignore property on [PATH].
1256 2. Add some files in the svn:ignore property of [PATH].
1258 If you want to add directories in the ignore-list, be careful:
1259 svn ignore foo/ bar/
1260 will add "foo/" in the property svn:ignore within the directory bar!
1262 svn ignore foo/ bar/ .
1263 (It'\''s somewhat like with mv)
1269 echo 'mail: Resend the mail of a given commit.
1270 usage: mail REV [emails]
1272 REV must have an email file associated in +committed/REV.
1273 REV can also be PREV or HEAD.
1275 By default the mail is sent to same email addresses as during the original
1276 commit unless more arguments are given.'
1278 propadd | padd | pa
)
1279 echo 'propadd (padd, pa): Add something in the value of a property.
1280 usage: propadd PROPNAME PROPVAL PATH
1282 PROPVAL will be appended at the end of the property PROPNAME.
1288 echo 'propsed (psed): Edit a property with sed.
1289 usage: propsed PROPNAME SED-ARGS PATH
1291 eg: svn propsed svn:externals "s/http/https/" .
1297 echo 'revision (rev): Display the revision number of a local or remote item.'
1298 $SVN help info |
sed '1d;
1299 s/information/revision/g;
1300 s/revision about/the revision of/g;
1301 2,35 s/info/revision/g;
1305 echo 'touch: Touch a file and svn add it.
1306 usage: touch FILE [FILES]...
1311 selfupdate | selfup | self-update | self-up
)
1312 echo 'selfupdate (selfup): Attempt to update svn-wrapper.sh
1319 echo 'version: Display the version info of svn and svn-wrapper.
1330 # svn_status [args...]
1333 svn_status_out
=`$SVN status "$@"`
1335 test x
"$svn_status_out" = x
&& return $svn_status_rv
1336 echo "$svn_status_out" |
sed "$sed_svn_st_color"
1337 return $svn_status_rv
1340 # svn_update [args...]
1343 svn_update_out
=`$SVN update "$@"`
1345 echo "$svn_update_out" |
sed "$sed_svn_up_colors"
1346 return $svn_update_rv
1349 # ------------------- #
1350 # `main' starts here. #
1351 # ------------------- #
1353 # Define colors if stdout is a tty.
1356 else # stdout isn't a tty => don't print colors.
1360 # Consider this as a sed function :P.
1364 s@^?\\(......\\)+@+\\1+@
1365 s@^?\\(......\\)\\(.*/\\)+@+\\1\\2+@
1366 s@^?\\(......\\),@,\\1,@
1367 s@^?\\(......\\)\\(.*/\\),@,\\1\\2,@
1368 s/^\\(.\\)C/\\1${lred}C${std}/
1371 s/^?/${lred}?${std}/; t
1372 s/^M/${lgreen}M${std}/; t
1373 s/^A/${lgreen}A${std}/; t
1374 s/^X/${lblue}X${std}/; t
1375 s/^+/${lyellow}+${std}/; t
1376 s/^D/${lyellow}D${std}/; t
1377 s/^,/${lred},${std}/; t
1378 s/^C/${lred}C${std}/; t
1379 s/^I/${purple}I${std}/; t
1380 s/^R/${lblue}R${std}/; t
1381 s/^!/${lred}!${std}/; t
1382 s/^~/${lwhite}~${std}/; t"
1391 s/^\\(.\\)C/\\1${lred}C${std}/
1392 s/^\\(.\\)U/\\1${lgreen}U${std}/
1393 s/^\\(.\\)D/\\1${lred}D${std}/
1396 s/^A/${lgreen}A${std}/; t
1397 s/^U/${lgreen}U${std}/; t
1398 s/^D/${lyellow}D${std}/; t
1399 s/^G/${purple}G${std}/; t
1400 s/^C/${lred}C${std}/; t"
1403 test "x$1" = x--debug
&& shift && set -x
1406 # ------------------------------- #
1407 # Hooks for standard SVN commands #
1408 # ------------------------------- #
1430 # -------------------- #
1431 # Custom SVN commands #
1432 # -------------------- #
1433 automerge | auto-merge | amerge | am
)
1435 if [ $# -ne 2 ]; then
1436 abort
"automerge: not enough arguments provided;
1437 try 'svn help automerge' for more info"
1443 require_diffstat
&& $SVN diff "$@" | diffstat
1457 propadd | padd | pa
)
1471 touch "$@" && svn add
"$@"
1473 selfupdate | selfup | self-update | self-up
)
1477 version |
-version |
--version)
1479 set dummy
'--version' "$@"