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.
91 # Pitfall: some users might be tempted to export SVN=svn-wrapper.sh for some
92 # reason. This is just *wrong*. The following is an attempt to save them from
94 if [ x
`basename "$SVN"` = 'xsvn-wrapper.sh' ]; then
98 # This code comes (mostly) from Autoconf.
99 # The user is always right.
100 if test "${PATH_SEPARATOR+set}" != set; then
101 trap "echo SIGINT; rm -f '$TMPDIR/conf$$.sh'; exit 130" SIGINT
102 echo "#! /bin/sh" >"$TMPDIR/conf$$.sh"
103 echo "exit 0" >>"$TMPDIR/conf$$.sh"
104 chmod +x
"$TMPDIR/conf$$.sh"
105 if (PATH
="/nonexistent;."; conf$$.sh
) >/dev
/null
2>&1; then
110 rm -f "$TMPDIR/conf$$.sh"
112 trap 'echo SIGINT; exit 130' SIGINT
116 revision
=`sed '/^# $Id[:].*$/!d;
119 s/[^0-9]*\([0-9][0-9]*\).*/\1/' "$0"`
122 # The `main' really starts after the functions definitions.
130 red
='\e[0;31m'; lred
='\e[1;31m'
131 green
='\e[0;32m'; lgreen
='\e[1;32m'
132 yellow
='\e[0;33m'; lyellow
='\e[1;33m'
133 blue
='\e[0;34m'; lblue
='\e[1;34m'
134 purple
='\e[0;35m'; lpurple
='\e[1;35m'
135 cyan
='\e[0;36m'; lcyan
='\e[1;36m'
136 grey
='\e[0;37m'; lgrey
='\e[1;37m'
137 white
='\e[0;38m'; lwhite
='\e[1;38m'
145 yellow
=''; lyellow
=''
147 purple
=''; lpurple
=''
157 echo "svn-wrapper: ${lred}abort${std}: $@" \
158 |
sed '1!s/^[ ]*/ /' >&2
165 echo "svn-wrapper: ${lred}warning${std}: $@" \
166 |
sed '1!s/^[ ]*/ /' >&2
172 echo "svn-wrapper: ${lyellow}notice${std}: $@" \
173 |
sed '1!s/^[ ]*/ /' >&2
180 read answer ||
return 1
185 return 42 # should never happen...
191 warn
"cannot find the environment variable $1
192 You might consider using \`export $1 <FIXME>\`"
195 # get_unique_file_name file-name
196 get_unique_file_name
()
199 echo "$1" && return 0
202 while test -e "$gufn.$i"; do
208 # ensure_not_empty description value
211 ene_val
=`echo "$2" | tr -d ' \t\n'`
212 test x
"$ene_val" = x
&& abort
"$1: empty value"
215 # find_prog prog-name
216 # return true if prog-name is in the PATH
217 # echo the full path to prog-name on stdout.
218 # Based on a code from texi2dvi
225 test x
"$dir" = x
&& continue
226 # The basic test for an executable is `test -f $f && test -x $f'.
227 # (`test -x' is not enough, because it can also be true for directories.)
228 # We have to try this both for $1 and $1.exe.
230 # Note: On Cygwin and DJGPP, `test -x' also looks for .exe. On Cygwin,
231 # also `test -f' has this enhancement, bot not on DJGPP. (Both are
232 # design decisions, so there is little chance to make them consistent.)
233 # Thusly, it seems to be difficult to make use of these enhancements.
235 if test -f "$dir/$1" && test -x "$dir/$1"; then
238 elif test -f "$dir/$1.exe" && test -x "$dir/$1.exe"; then
246 # find_progs prog [progs...]
247 # Look in PATH for one of the programs given in argument.
248 # If none of the progs can be found, the string "exit 2" is "returned".
251 # This code comes mostly from Autoconf.
252 for fp_prog
in "$@"; do
253 fp_res
=`find_prog $fp_prog`
254 if [ $?
-eq 0 ]; then
263 test x
"$EDITOR" = xmissing
&& EDITOR
=`find_progs vim vi emacs nano`
264 test x
"$PAGER" = xmissing
&& PAGER
=`find_progs less more`
265 test x
"$AWK" = xmissing
&& AWK
=`find_progs gawk mawk nawk awk`
268 # return true if diffstat is in the PATH
271 if [ x
"$require_diffstat_cache" != x
]; then
272 return $require_diffstat_cache
274 if (echo | diffstat
) >/dev
/null
2>/dev
/null
; then :; else
275 warn
'diffstat is not installed on your system or not in your PATH.'
276 test -f /etc
/debian_version \
277 && notice
'you might want to `apt-get install diffstat`.'
278 require_diffstat_cache
=1
281 require_diffstat_cache
=0
286 # return 0 -> found sendmail
288 # 2 -> no mailer found
289 # The full path to the program found is echo'ed on stdout.
292 if [ x
"$require_mail_cache_rv" != x
]; then
293 echo "$require_mail_cache"
294 return $require_mail_cache_rv
297 PATH
="${PATH}${PATH_SEPARATOR}/sbin${PATH_SEPARATOR}/usr/sbin${PATH_SEPARATOR}/usr/libexec"
299 require_mail_cache
=`find_prog sendmail`
300 if [ $?
-eq 0 ] && [ x
"$require_mail_cache" != x
]; then
301 echo "$require_mail_cache"
302 require_mail_cache_rv
=0
307 require_mail_cache
=`find_prog mail`
308 if [ $?
-eq 0 ] && [ x
"$require_mail_cache" != x
]; then
309 echo "$require_mail_cache"
310 require_mail_cache_rv
=1
313 warn
'mail is not installed on your system or not in your PATH.'
314 test -f /etc
/debian_version \
315 && notice
'you might want to:
316 # apt-get install mailx
317 # dpkg-reconfigure exim'
318 require_mail_cache
=''
319 require_mail_cache_rv
=1
322 require_mail_cache
=''
323 require_mail_cache_rv
=42
327 # my_sendmail mail-file mail-subject mail-to [extra-headers]
328 # mail-to is a comma-separated list of email addresses.
329 # extra-headers is an optionnal argument and will be prepended at the
330 # beginning of the mail headers if the tool used to send mails supports it.
331 # The mail-file may also contain headers. They must be separated from the body
332 # of the mail by a blank line.
335 test -f "$1" || abort
"my_sendmail: Cannot find the mail file: $1"
336 test x
"$2" = x
&& warn
'my_sendmail: Empty subject.'
337 test x
"$3" = x
&& abort
'my_sendmail: No recipient specified.'
339 extra_headers
="Content-type: text/plain
340 X-Mailer: svn-wrapper v$version (r$revision)"
341 if test x
"$4" != x
; then
344 # Remove empty lines.
345 extra_headers
=`echo "$extra_headers" | sed '/^[ ]*$/d;s/^[ ]*//'`
348 test -f ~
/.signature
&& echo '-- ' >>"$1" && \
349 cat ~
/.signature
>>"$1"
350 # VCS-compat: handle user option 'sign'.
351 if (grep '^sign: false' ~
/.vcs
) >/dev
/null
2>/dev
/null
; then :; else
352 ($GPG -h) >/dev
/null
2>/dev
/null
354 if [ -e ~
/.gnupg
] ||
[ -e ~
/.gpg
] ||
[ -e ~
/.pgp
] && [ $gpg_rv -lt 42 ]
356 if yesno
"Sign the mail using $GPG ?"; then
357 # GPG sign the mail with the headers stripped.
358 cat "$1" |
sed '1,/^$/d' |
$GPG --clearsign >"$1.asc"
359 # Re-paste the headers before the signed body.
361 && cat "$1" |
sed '1,/^$/!d' |
cat - "$1.asc" >"$1.tmp" \
362 && mv -f "$1.tmp" "$1"
364 rm -f "$1.tmp" "$1.asc"
369 mailer
=`require_mail`
371 if [ $?
-ge 2 ] ||
[ x
"$mailer" = x
]; then
372 warn
'my_sendmail: No suitable mailer found.'
377 to
=`echo "$3" | sed 's/,//g'`
378 echo "$extra_headers" |
cat - "$1" |
$mailer $to;;
380 cat "$1" |
$mailer -s "$2" "$3";;
382 warn
'my_sendmail: Internal error.'; return 42;;
389 my_url
='http://www.lrde.epita.fr/~sigoure/svn-wrapper'
390 # You can use https if you feel paranoiac.
392 echo ">>> Fetching svn-wrapper.sh from $my_url/svn-wrapper.sh"
394 # --------------------- #
395 # Fetch the new version #
396 # --------------------- #
398 tmp_me
=`get_unique_file_name "$TMPDIR/svn-wrapper.sh"`
399 if (wget
--help) >/dev
/null
2>/dev
/null
; then
400 wget
--no-check-certificate "$my_url/svn-wrapper.sh" -O "$tmp_me"
403 curl
--help >/dev
/null
2>/dev
/null
404 if [ $?
-gt 42 ]; then
405 abort
'Cannot find wget or curl.
406 How can I download any update without them?'
409 curl
--insecure "$my_url/svn-wrapper.sh" >"$tmp_me"
413 || abort
"Cannot find the copy of myself I downloaded in $tmp_me"
415 # ---------------------------------------- #
416 # Compare versions and update if necessary #
417 # ---------------------------------------- #
420 tmp_ver
=`sed '/^# $Id[:].*$/!d;
423 s/[^0-9]*\([0-9][0-9]*\).*/\1/' "$tmp_me"`
424 test x
"$tmp_ver" = x
&& abort
"Cannot find the version of $tmp_me"
425 if [ "$my_ver" -lt "$tmp_ver" ]; then # There IS an update...
426 echo "An update is available, r$tmp_ver (your version is r$my_ver)"
429 if yesno
"Do you want to see the ChangeLog between r$my_ver and r$tmp_ver?"
431 my_chlog
=`get_unique_file_name "$TMPDIR/ChangeLog"`
433 wget
) wget
--no-check-certificate "$my_url/ChangeLog" -O "$my_chlog"
435 curl
) curl
--insecure "$my_url/ChangeLog" >"$my_chlog"
437 *) abort
'Should never be here.'
440 sed "/^r$my_ver/q" "$my_chlog" |
$PAGER
444 # Wanna see the diff?
445 if yesno
"Do you want to see the diff between r$my_ver and r$tmp_ver?"
447 (require_diffstat
&& diff -uw "$me" "$tmp_me" | diffstat
;
449 diff -uw "$me" "$tmp_me") |
$PAGER
453 if yesno
"Overwrite $me (r$my_ver) with $tmp_me (r$tmp_ver)?"; then
455 cp -p "$me" "$me.r$my_ver"
456 mv "$tmp_me" "$me" && exit 0
460 elif [ "$my_ver" -gt "$tmp_ver" ]; then
461 echo "Wow, you're more up to date than the master copy :)"
462 echo "Your version is r$my_ver and the master copy is r$tmp_ver."
463 if yesno
'Downgrade?'; then
465 mv "$tmp_me" "$me" && exit 0
468 echo "You're already up to date [r$my_ver] :)"
473 # ------------------------------- #
474 # Hooks for standard SVN commands #
475 # ------------------------------- #
477 # svn_commit [args...]
478 # Here is how the commit process goes:
480 # First we look in the arguments passed to commit:
481 # If there are some files or paths, the user wants to commit these only. In
482 # this case, we must search for ChangeLogs from these paths. We might find
483 # more than one ChangeLog, in this case the user will be prompted to pick up
485 # Otherwise (no path passed in the command line) the user just wants to
486 # commit the current working directory.
487 # In any case, we schedule "ChangeLog" for commit.
489 # Alright now that we know which ChangeLog to use, we look in the ChangeLog's
490 # directory if there is a ",svn-log" file which would mean that a previous
491 # commit didn't finish successfully. If there is such a file, the user is
492 # prompted to know whether they want to resume that commit or simply start a
494 # When the user wants to resume a commit, the ",svn-log" file is loaded and we
495 # retrieve the value of "$@" that was saved in the file.
496 # Otherwise we build a template ChangeLog entry.
497 # Then we open the template ChangeLog entry with $EDITOR so that the user
499 # Finally, we commit.
500 # Once the commit is sent, we ask the server to know which revision was
501 # commited and we also retrieve the diff. We then send a mail with these.
506 # Check if the user passed some paths to commit explicitly
507 # because in this case we must add the ChangeLog to the commit and search
508 # the ChangeLog from the dirname of that file.
509 i
=0; search_from
=''; add_changelog
=no
; extra_files
=''
510 while [ $i -lt $# ]; do
512 # If the argument is a valid path: add the ChangeLog in the list of
514 if test -e "$arg"; then
516 if test -d "$arg"; then
517 search_from_add
="$arg"
519 search_from_add
=`dirname "$arg"`
521 search_from
="$search_from:$search_from_add"
524 set dummy
"$@" "$arg"
528 if [ $add_changelog = no
]; then
529 # There is no path/file in the command line: the user wants to commit the
530 # current directory. Make it explicit now:
533 search_from
=`echo "$search_from" | sed 's/^://; s/^$/./'`
535 # ----------------- #
536 # Find ChangeLog(s) #
537 # ----------------- #
539 nb_chlogs
=0; change_log_dirs
=''
540 save_IFS
=$IFS; IFS
=':'
541 for dir
in $search_from; do
543 test -z "$dir" && dir
='.'
544 # First: come back to the original place
545 cd "$here" || abort
"Cannot cd to $here"
546 cd "$dir" ||
continue # Then: Enter $dir (which can be a relative path)
548 while [ $found -eq 0 ]; do
549 this_chlog_dir
=`pwd -P`
550 if [ -f .
/ChangeLog
]; then
552 nb_chlogs
=`expr $nb_chlogs + 1`
553 change_log_dirs
="$change_log_dirs:$this_chlog_dir"
557 # Stop searching when in / ... hmz :P
558 test x
"$this_chlog_dir" = x
/ && break
559 done # end while: did we find a ChangeLog
560 done # end for: find ChangeLogs in $search_from
561 if [ $nb_chlogs -gt 0 ]; then
562 change_log_dirs
=`echo "$change_log_dirs" | sed 's/^://' | tr ':' '\n' \
564 nb_chlogs
=`echo "$change_log_dirs" | wc -l`
567 # Did we find a ChangeLog? More than one?
568 if [ $nb_chlogs -eq 0 ]; then
569 if yesno
'svn-wrapper: Error: Cannot find a ChangeLog file!
570 You might want to create an empty one (eg: `touch ChangeLog` where appropriate)
571 Do you want to proceed without using a ChangeLog?'; then
578 elif [ $nb_chlogs -gt 1 ]; then
579 notice
"$nb_chlogs ChangeLogs were found, pick up one:"
582 for a_chlog_dir
in $change_log_dirs; do
584 echo "$i. $a_chlog_dir/ChangeLog"
586 echo -n "Which ChangeLog do you want to use? [1-$i] "
587 read chlog_no || abort
'Cannot read answer on stdin.'
590 *[^
0-9]*) abort
"Invalid ChangeLog number: $chlog_no"
592 test "$chlog_no" -le $i || abort
"Invalid ChangeLog number: $chlog_no
594 test "$chlog_no" -ge 1 || abort
"Invalid ChangeLog number: $chlog_no
596 change_log_dir
=`echo "$change_log_dirs" | tr ':' '\n' | sed "${chlog_no}!d"`
597 else # Only one ChangeLog found
598 change_log_dir
=$change_log_dirs
599 notice
"using $change_log_dir/ChangeLog"
602 test -f "$change_log_dir/ChangeLog" \
603 || abort
"No such file or directory: $change_log_dir/ChangeLog"
605 # Now we can safely schedule the ChangeLog for the commit.
606 extra_files
="$extra_files:$change_log_dir/ChangeLog"
608 svn_st_tmp
=`$SVN status "$change_log_dir"`
610 # Warn for files that are not added in the repos.
611 conflicts
=`echo "$svn_st_tmp" | sed '/^ *$/d;
615 if test x
"$conflicts" != x
; then
616 warn
"make sure you don't want to \`svn add'
617 any of the following files before committing:"
618 echo "$conflicts" |
sed "$sed_svn_st_color"
619 echo -n 'Type [ENTER] to continue :)' && read chiche_is_gay
622 # If there are changes in an svn:externals, advise the user to commit that
624 changed_externals
=`echo "$svn_st_tmp" | $AWK \
633 BEGIN { this_ext = ""; ext = 0; ext_modified = 0; }
634 /^Performing status on external/ {
636 sub(/.* at ./, ""); sub(/.$/, ""); this_ext = $0;
639 /^[ADMR]/ { ext_modified = ext; printext(); }
640 /^.[M]/ { ext_modified = ext; printext(); }
641 END { exit ext_modified; }'`
642 if [ $?
-ne 0 ]; then
643 warn
"the following external items have local modifications:
645 yesno
"You are advised to commit them separately first. Continue anyway?" \
649 # Detect unresolved conflicts / missing files.
650 conflicts
=`echo "$svn_st_tmp" | sed '/^[C!]/!d'`
651 test x
"$conflicts" != x
&& abort
"there are unresolved conflicts (\`C')
652 and/or missing files (\`!'):
655 svn_info_tmp
=`$SVN info "$change_log_dir"`
656 test $?
-ne 0 && abort
"Failed to get svn info on $change_log_dir"
657 repos_root
=`echo "$svn_info_tmp" | sed '/^Repository Root: /!d;s///'`
658 repos_url
=`echo "$svn_info_tmp" | sed '/^URL: /!d;s///'`
659 # It looks like svn <1.3 didn't display a "Repository Root" entry.
660 test x
"$repos_root" = x
&& repos_root
=$repos_url
667 # VCS-compat: handle user option 'new_user'
669 grep '^new_user: false' ~
/.vcs
>/dev
/null
2>/dev
/null
&& new_user
='no'
671 tmp_log
="$change_log_dir/,svn-log"
672 if [ -f "$tmp_log" ] && yesno
"It looks like the last commit did not\
673 terminate successfully.
674 Would you like to resume it?"; then
676 internal_tags
=`sed '/^--- Internal stuff, DO NOT change please ---$/,$!d' \
678 saved_args
=`echo "$internal_tags" | sed '/^args: */!d;s///'`
679 extra_files
=`echo "$internal_tags" | sed '/^extra_files: */!d;s///'`
680 if [ x
"$saved_args" != x
]; then
681 if [ x
"$*" != x
] && [ x
"$saved_args" != x
"$*" ]; then
682 warn
"overriding arguments:
683 you invoked $me with the following arguments: $@
684 they have been replaced by these: $saved_args"
685 set dummy
$saved_args
688 notice
"setting the following arguments: $saved_args"
689 set dummy
$saved_args
693 # Ignore white spaces. Can't svn diff -x -w: svn 1.4 only.
694 svn_diff
=`svn_diffw "$@"`
695 test x
"$svn_diff" = x
&& svn_diff
=`$SVN diff "$@"`
696 if require_diffstat
; then
697 svn_diff_stat
=`echo "$svn_diff" | diffstat`
699 svn_diff_stat
='diffstat not available'
702 # Update the file with the new diff/diffstat in case it changed.
707 /^--This line, and those below, will be ignored--$/ {
710 /^ Your ChangeLog entry will appear here\.$/ {
711 if (tlatbwbi_seen) ycewah_seen = 1;
714 if (ycewah_seen != 2) print;
715 if (ycewah_seen == 1) ycewah_seen = 2;
716 }' "$tmp_log" >"$tmp_log.tmp"
722 $internal_tags" >>"$tmp_log.tmp"
723 mv -f "$tmp_log.tmp" "$tmp_log"
725 else # Build the template message.
727 # ------------------------------------ #
728 # Gather info for the template message #
729 # ------------------------------------ #
731 projname
=`$SVN propget project "$change_log_dir"`
732 # Try to be VCS-compatible and find a project name in a *.rb.
733 if [ x
"$projname" = x
] && [ -d "$change_log_dir/vcs" ]; then
734 projname
=`sed '/common_commit/!d;s/.*"\(.*\)<%= rev.*/\1/' \
735 "$change_log_dir"/vcs/*.rb`
736 test x
"$projname" != x
&& test x
$new_user = xyes \
737 && notice
"VCS-compat: found project name: $projname
738 in " "$change_log_dir"/vcs
/*.rb
740 test x
"$projname" != x
&& projname
=`echo "$projname" | sed '/[^ ]$/s/$/ /'`
742 mailto
=`$SVN propget mailto "$change_log_dir"`
743 if [ x
"$mailto" = x
]; then
744 test x
$new_user = xyes \
745 && warn
"no svn property mailto found in $change_log_dir
746 You might want to set default email adresses using:
747 svn propset mailto 'somebody@mail.com, foobar@example.com'\
749 # Try to be VCS-compatible and find a list of mails in a *.rb.
750 if [ -d "$change_log_dir/vcs" ]; then
751 mailto
=`grep '.@.*\..*' "$change_log_dir"/vcs/*.rb \
753 | sed 's/^.*[["]\([^["]*@[^]"]*\)[]"].*$/\1/' | xargs`
754 test x
"$mailto" != x
&& test x
$new_user = xyes \
755 && notice
"VCS-compat: found mailto: $mailto
756 in " "$change_log_dir"/vcs
/*.rb
758 fi # end guess mailto
760 # Ensure that emails are comma-separated.
761 mailto
=`echo "$mailto" | sed 's/[ ;]/,/g' | tr -s ',' | sed 's/,/, /g'`
762 test x
"$FULLNAME" = x
&& FULLNAME
='Type Your Name Here' \
764 test x
"$EMAIL" = x
&& EMAIL
='your.mail.here@FIXME.com' && warn_env EMAIL
766 # --ignore-externals appeared after svn 1.1.1
767 my_svn_st
=`$SVN status --ignore-externals "$@" \
768 || $SVN status "$@" | sed '/^Performing status on external/ {
772 # Files to put in the ChangeLog entry.
773 change_log_files
=`echo "$my_svn_st" | sed '
776 s/^M......\(.*\)$/ * \1: ./; t
777 s/^A......\(.*\)$/ * \1: New./; t
778 s/^D......\(.*\)$/ * \1: Remove./; t
781 if [ x
"$change_log_files" = x
]; then
782 yesno
'Nothing to commit, continue anyway?' ||
return 1
784 change_log_files
=`echo "$change_log_files" | sort -u`
786 svn_diff
=`svn_diffw "$@"`
787 test x
"$svn_diff" = x
&& svn_diff
=`$SVN diff "$@"`
788 if require_diffstat
; then
789 svn_diff_stat
=`echo "$svn_diff" | diffstat`
791 svn_diff_stat
='diffstat not available'
794 # Get any older svn-log out of the way.
795 test -f "$tmp_log" && mv "$tmp_log" `get_unique_file_name "$tmp_log"`
796 # If we can't get an older svn-log out of the way, find a new name...
797 test -f "$tmp_log" && tmp_log
=`get_unique_file_name "$tmp_log"`
798 if [ x
$new_user = no
]; then
799 commit_instructions
='
801 - Fill the ChangeLog entry.
802 - If you feel like, write a comment in the "Comment:" section.
803 This comment will only appear in the email, not in the ChangeLog.
804 By default only the location of the repository is in the comment.
805 - Some tags will be replaced. Tags are of the form: <TAG>. Unknown
806 tags will be left unchanged.
807 - The tag <REV> may only be used in the Subject.
808 - Your ChangeLog entry will be used as commit message for svn.'
810 commit_instructions
=''
813 --You must fill this file correctly to continue-- -*- vcs -*-
815 Subject: ${projname}r<REV>: <TITLE>
816 From: $FULLNAME <$EMAIL>
824 <YYYY>-<MM>-<DD> $FULLNAME <$EMAIL>
829 --This line, and those below, will be ignored--
831 --Preview of the message that will be sent--
834 Your comments (if any) will appear here.
837 $YYYY-$MM-$DD $FULLNAME <$EMAIL>
839 Your ChangeLog entry will appear here.
843 $svn_diff" >"$tmp_log"
846 --- Internal stuff, DO NOT change please ---
847 args: $@" >>"$tmp_log"
848 echo "extra_files: $extra_files
849 vi: ft=diff:noet:" >>"$tmp_log"
851 fi # end: if svn-log; then resume? else create template
854 # ------------------ #
855 # Re-"parse" the log #
856 # ------------------ #
858 # hmz this section is a bit messy...
859 sed_escape
='s/@/\\@/g;s/&/\\\&/g' # helper string... !@#$%* escaping \\\\\\...
860 sed_eval_tags
="s/<MM>/$MM/g; s/<DD>/$DD/g; s/<YYYY>/$YYYY/g"
861 full_log
=`sed '/^--This line, and those below, will be ignored--$/,$d;
862 /^--You must fill this/d' "$tmp_log"`
863 chlog_entry
=`echo "$full_log" | sed '/^ChangeLog:$/,$!d; //d'`
864 ensure_not_empty
'ChangeLog entry' "$chlog_entry"
865 full_log
=`echo "$full_log" | sed '/^ChangeLog:$/,$d'`
866 mail_comment
=`echo "$full_log" | sed '/^Comment:$/,$!d; //d'`
867 full_log
=`echo "$full_log" | sed '/^Comment:$/,$d'`
868 mail_title
=`echo "$full_log" | sed '/^Title: */!d;s///'`
869 ensure_not_empty
'commit title' "$mail_title"
870 mail_title
=`echo "$mail_title" | sed "$sed_eval_tags; $sed_escape"`
871 sed_eval_tags
="$sed_eval_tags; s@<TITLE>@$mail_title@g"
872 mail_comment
=`echo "$mail_comment" | sed "$sed_eval_tags"`
873 chlog_entry
=`echo "$chlog_entry" | sed "$sed_eval_tags; 1{
876 mail_subject
=`echo "$full_log" | sed '/^Subject: */!d;s///'`
877 ensure_not_empty
'mail subject' "$mail_subject"
878 mail_to
=`echo "$full_log" | sed '/^To:/!d'`
880 if test x
"$mail_to" = x
; then
883 mail_to
=`echo "$mail_to" | sed 's/^To: *//'`
884 ensure_not_empty
'"To:" field of the mail' "$mail_to"
886 mail_from
=`echo "$full_log" | sed '/^From: */!d;s///'`
887 ensure_not_empty
'"From:" field of the mail' "$mail_from"
889 if echo "$chlog_entry" |
grep -q '<REV>'; then
890 warn
'Using the tag <REV> anywhere else than in the Subject is deprecated.'
891 yesno
'Continue anyway?' ||
return 1
894 # Check whether the user passed -m | --message
896 while [ $i -lt $# ]; do
898 # This is not really a reliable way of knowing whether -m | --message was
899 # passed but hum... Let's assume it'll do :s
900 test x
"$arg" = 'x-m' && has_message
=1
901 test x
"$arg" = 'x--message' && has_message
=1
903 set dummy
"$@" "$arg"
907 if [ $has_message -eq 0 ]; then
908 my_message
=`echo "$chlog_entry" | grep -v "^$YYYY-$MM-$DD" | sed '1,2 {
911 set dummy
--message "$my_message" "$@"
914 notice
'you are overriding the commit message.'
918 yesno
'Are you sure you want to commit?' ||
return 1
920 # Add the ChangeLog entry
921 old_chlog
=`get_unique_file_name "$change_log_dir/ChangeLog.old"`
922 mv "$change_log_dir/ChangeLog" "$old_chlog" || \
923 abort
'Could not backup ChangeLog'
924 trap "echo SIGINT; mv \"$old_chlog\" \"$change_log_dir/ChangeLog\";
926 echo "$chlog_entry" >"$change_log_dir/ChangeLog"
927 echo >>"$change_log_dir/ChangeLog"
928 cat "$old_chlog" >>"$change_log_dir/ChangeLog"
930 # Add extra files such as cwd or ChangeLog to the commit.
931 tmp_sed
='s/ /\\ /g' # Escape spaces for the shell.
932 extra_files
=`echo "$extra_files" | sed "$tmp_sed" | tr ':' '\n'`
933 set dummy
"$@" $extra_files
936 # --Commit-- finally! :D
937 $SVN commit
"$@" ||
{
939 mv "$old_chlog" "$change_log_dir/ChangeLog"
940 abort
"Commit failed, $SVN returned $svn_commit_rv"
943 echo -n 'Getting the revision number... '
944 svn_info_tmp
=`$SVN info "$change_log_dir/ChangeLog"`
945 REV
=`echo "$svn_info_tmp" | sed '/^Revision: /!d;s///'`
946 test x
"$REV" = x
&& REV
=`echo "$svn_info_tmp" \
947 | sed '/^Last Changed Rev: /!d;s///'`
948 test x
"$REV" = x
&& abort
'Cannot detect the current revision.'
951 # Let's make sure we have the real diff by asking the ChangeSet we've just
952 # committed to the server.
954 # Backup the old stuff in case we fail to get the real diff from the server
956 save_svn_diff
=$svn_diff
957 save_svn_diff_stat
=$svn_diff_stat
959 # Fetch the ChangeSet and filter out the ChangeLog entry. We don't use
960 # svn diff -c because this option is not portable to older svn versions.
961 REV_MINUS_ONE
=`expr "$REV" - 1`
962 svn_diff
=`svn_diffw -r"$REV_MINUS_ONE:$REV" \
963 | awk '/^Index: / { if (in_chlog) in_chlog = 0; }
964 /^Index: .*ChangeLog$/ { in_chlog = 1 }
965 { if (!in_chlog) print }'`
966 if [ x
"$svn_diff" = x
]; then
967 svn_diff
=$save_svn_diff
968 svn_diff_stat
=$save_svn_diff_stat
970 if require_diffstat
; then
971 svn_diff_stat
=`echo "$svn_diff" | diffstat`
973 svn_diff_stat
='diffstat not available'
977 mail_subject
=`echo "$mail_subject" | sed "$sed_eval_tags; s/<REV>/$REV/g"`
979 mail_file
=`get_unique_file_name "$change_log_dir/+mail"`
983 Subject: $mail_subject
992 $svn_diff" |
sed 's/^\.$/ ./' >"$mail_file"
993 # We change lines with only a `.' because they could mean "end-of-mail"
996 if test x
$send_a_mail = xyes
; then
997 trap 'echo SIGINT; exec < /dev/null' SIGINT
998 my_sendmail
"$mail_file" "$mail_subject" "$mail_to" \
999 "X-svn-url: $repos_root
1000 X-svn-revision: $REV"
1001 fi # end do we have to send a mail?
1004 save_mail_file
=`echo "$mail_file" | sed 's/+//'`
1005 mkdir
-p "$change_log_dir/+committed" \
1006 || warn
"Couldn't mkdir -p $change_log_dir/+committed"
1007 if [ -d "$change_log_dir/vcs" ] \
1008 ||
[ -d "$change_log_dir/+committed" ]
1010 mkdir
-p "$change_log_dir/+committed/$REV" \
1011 && mv "$mail_file" "$change_log_dir/+committed/$REV/mail"
1013 return $svn_commit_rv
1019 i
=0; src
=''; dst
=''; rev1
=''; rev2
=''; r_seen
=0
1020 while [ $i -lt $# ]; do
1022 if test -d "$arg"; then
1023 if [ x
"$src" = x
]; then
1025 elif [ x
"$dst" = x
]; then
1030 -r*:*) rev1
=`echo "$arg" | sed 's/^[^PREVHEAD0-9]*//; s/:.*//'`
1031 rev2
=`echo "$arg" | sed 's/^.*://; s/[^PREVHEAD0-9]*//'`
1037 set dummy
"$@" "$arg"
1041 if [ x
"$src" = x
]; then
1042 exec $SVN merge
"$@"
1044 test x
"$dst" = x
&& dst
='.'
1046 if yesno
"Do you want to use the automerge feature for $src ?";
1048 exec $SVN merge
"$@"
1050 # FIXME: Incomplete.
1053 # svn_automerge src-path dst-path
1056 test -f ChangeLog || abort
'You must use automerge in the folder where the
1058 amerge
=`$SVN propget automerge`
1059 if [ x
"$amerge" = x
]; then
1060 # FIXME: Use svn log --stop-on-copy to detect the original branching.
1064 svn_info_tmp
=`$SVN info`
1065 REV
=`echo "$svn_info_tmp" | sed '/^Revision: /!d;s///'`
1066 test x
"$REV" = x
&& REV
=`echo "$svn_info_tmp" \
1067 | sed '/^Last Changed Rev: /!d;s///'`
1068 test x
"$REV" = x
&& abort
'Cannot detect the current revision.'
1070 # FIXME: Incomplete.
1071 if yesno
"Are you sure you want to run:
1072 $SVN merge -r$amerge:$REV $1 $2 ?"; then
1073 $SVN merge
"-r$amerge:$REV" "$1" "$2"
1077 # svn_diffw [args...]
1080 # Ignore white spaces. Can't svn diff -x -w: svn 1.4 only.
1081 $SVN diff --no-diff-deleted --diff-cmd diff -x -uw "$@"
1084 # svn_mail REV [mails...]
1087 test $# -lt 1 && abort
"Not enough arguments provided;
1088 Try 'svn help mail' for more info."
1091 REV
=`svn_revision || abort 'Cannot get current revision number'`
1092 test x
"$REV" = x
&& abort
'Cannot get current revision number'
1093 if [ "$REV" -lt 1 ]; then
1094 abort
'No previous revision.'
1096 REV
=`expr "$REV" - 1`
1099 REV
=`svn_revision || abort 'Cannot get current revision number'`
1100 test x
"$REV" = x
&& abort
'Cannot get current revision number'
1102 *[^
0-9]*) abort
"Syntax error in revision argument '$1'";;
1107 found_committed
=0; found
=0
1108 while [ $found -eq 0 ]; do
1109 this_chlog_dir
=`pwd -P`
1110 if [ -d .
/+committed
]; then
1112 if [ -d .
/+committed
/$REV ]; then
1120 # Stop searching when in / ... hmz :P
1121 test x
`pwd` = x
/ && break
1123 if [ $found -eq 0 ]; then
1124 if [ $found_committed -eq 0 ]; then
1125 abort
'Could not find the +committed directory.'
1127 abort
"Could not find the revision $REV in +committed."
1129 abort
'Internal error (should never be here).'
1132 mail_file
=''; subject
=''; to
=''
1133 if [ -f .
/+committed
/$REV/mail ]; then
1134 # svn-wrapper generated file
1135 mail_file
="./+committed/$REV/mail"
1136 subject
=`sed '/^Subject: /!d;s///' $mail_file | sed '1q'`
1137 to
=`sed '/^To: /!d;s///' $mail_file | sed '1q'`
1138 elif [ -f .
/+committed
/$REV/,iform
] && [ -f .
/+committed
/$REV/,message
]
1140 # VCS-generated file
1141 subject
=`sed '/^Subject: /!d;s///;s/^"//;s/"$//' ./+committed/$REV/,iform \
1142 | sed "s/<%= *rev *%>/$REV/g"`
1143 to
=`sed '/^To:/,/^[^-]/!d' ./+committed/$REV/,iform | sed '1d;s/^- //;$d' \
1144 | xargs | sed 's/ */, /g'`
1145 mail_file
=`get_unique_file_name "$TMPDIR/mail.r$REV"`
1146 echo "From: $FULLNAME <$EMAIL>
1149 " >"$mail_file" || abort
"Cannot create $mail_file"
1150 cat .
/+committed
/$REV/,message
>>"$mail_file" \
1151 || abort
"Cannot copy ./+committed/$REV/,message in $mail_file"
1153 abort
"Couldn't find the mail to re-send in `pwd`/+committed/$REV"
1155 if [ $# -gt 0 ]; then
1156 to
=`echo "$*" | sed 's/ */, /g'`
1159 test x
"$to" = x
&& abort
'Cannot find the list of recipients.
1160 Please report this bug.'
1161 test x
"$subject" = x
&& abort
'Cannot find the subject of the mail.
1162 Please report this bug.'
1164 if yesno
"Re-sending the mail of r$REV
1167 Are you sure?"; then :; else
1171 svn_info_tmp
=`$SVN info`
1172 test $?
-ne 0 && abort
"Failed to get svn info on `pwd`"
1173 repos_root
=`echo "$svn_info_tmp" | sed '/^Repository Root: /!d;s///'`
1174 repos_url
=`echo "$svn_info_tmp" | sed '/^URL: /!d;s///'`
1175 # It looks like svn <1.3 didn't display a "Repository Root" entry.
1176 test x
"$repos_root" = x
&& repos_root
=$repos_url
1178 my_sendmail
"$mail_file" "$subject" "$to" \
1179 "X-svn-url: $repos_url
1180 X-svn-revision: $REV"
1186 echo "Using svn-wrapper v$version (C) SIGOURE Benoit [GPL]"
1187 sed '/^# $Id[:].*$/!d;s/.*$Id[:] *//;s/ *$ *//;s/ \([0-9][0-9]*\)/ (r\1)/' "$me"
1190 # has_prop prop-name [path]
1191 # return value: 0 -> path has the property prop-name set.
1192 # 1 -> path has no property prop-name.
1196 hp_plist
=`$SVN proplist "$2"`
1197 test $?
-ne 0 && return 2
1198 hp_res
=`echo "$hp_plist" | sed "/^ *$1\$/!d"`
1199 test x
"$hp_res" = x
&& return 1
1203 # svn_propadd prop-name prop-val [path]
1207 && abort
'Not enough arguments provided;
1208 try `svn help propadd` for more info'
1210 && abort
'Too many arguments provided;
1211 try `svn help propadd` for more info'
1214 test x
"$path" = x
&& path
='.' && set dummy
"$@" '.' && shift
1215 has_prop
"$1" "$3" ||
{
1216 test $?
-eq 2 && return 1 # svn error
1217 # no property found:
1218 yesno
"'$path' has no property named '$1', do you want to add it?" \
1219 && $SVN propset
"$@"
1223 current_prop_val
=`$SVN propget "$1" "$3"`
1224 test $?
-ne 0 && abort
"Failed to get the current value of property '$1'."
1226 $SVN propset
"$1" "$current_prop_val
1227 $2" "$3" >/dev
/null || abort
"Failed to add '$3' in the property '$1'."
1229 current_prop_val
=`$SVN propget "$1" "$3" || echo "$current_prop_val
1231 echo "property '$1' updated on '$path', new value:
1235 # svn_propsed prop-name sed-script [path]
1239 && abort
'Not enough arguments provided;
1240 try `svn help propsed` for more info'
1242 && abort
'Too many arguments provided;
1243 try `svn help propsed` for more info'
1246 test x
"$path" = x
&& path
='.'
1247 has_prop
"$1" "$3" ||
{
1248 test $?
-eq 2 && return 1 # svn error
1249 # no property found:
1250 abort
"'$path' has no property named '$1'."
1253 prop_val
=`$SVN propget "$1" "$3"`
1254 test $?
-ne 0 && abort
"Failed to get the current value of property '$1'."
1256 prop_val
=`echo "$prop_val" | sed "$2"`
1257 test $?
-ne 0 && abort
"Failed to run the sed script '$2'."
1259 $SVN propset
"$1" "$prop_val" "$3" >/dev
/null \
1260 || abort
"Failed to update the property '$1' with value '$prop_val'."
1262 new_prop_val
=`$SVN propget "$1" "$3" || echo "$prop_val"`
1263 echo "property '$1' updated on '$path', new value:
1267 # svn_revision [args...]
1270 svn_revision_info_out
=`$SVN info "$@"`
1272 echo "$svn_revision_info_out" |
sed '/^Revision: /!d;s///'
1273 return $svn_revision_rv
1276 # svn_ignore [paths]
1279 if [ $# -eq 0 ]; then # Simply display ignore-list.
1280 $SVN propget
'svn:ignore'
1281 elif [ $# -eq 1 ]; then
1282 svn_propadd
'svn:ignore' `basename "$1"` `dirname "$1"`
1283 else # Add arguments in svn:ignore.
1284 # This part is a bit tricky:
1285 # For each argument, we find all the other arguments with the same dirname
1286 # $dname and we svn:ignore them all in $dname.
1287 while [ $# -ne 0 ]; do
1289 dname
=`dirname "$1"`
1290 files
=`basename "$1"`
1293 while [ $j -lt $argc ] && [ $# -ne 0 ]; do
1296 this_dname
=`dirname "$this_arg"`
1297 this_file
=`basename "$this_arg"`
1298 if [ x
"$dname" = x
"$this_dname" ]; then
1302 set dummy
"$@" "$this_arg"
1307 svn_propadd
'svn:ignore' "$files" "$dname"
1315 if [ $# -eq 0 ]; then
1320 Additionnal commands provided by svn-wrapper:
1321 automerge (amerge, am)
1335 automerge | auto-merge | amerge | am
)
1336 echo 'automerge (amerge, am): FIXME
1337 usage: automerge FIXME
1343 echo 'diffstat (ds): Display the histogram from svn diff-output.'
1344 $SVN help diff |
sed '1d;
1345 s/differences*/histogram/;
1346 2,35 s/diff/diffstat/g'
1349 echo "diffw (dw): Display the differences without taking whitespaces\
1351 $SVN help diff |
sed '1d;
1352 2,35 s/diff\([^a-z]\)/diffw\1/g;
1353 /--diff-cmd/,/--no-diff-deleted/d'
1356 echo 'ignore: Add some files in the svn:ignore property.
1357 usage: 1. ignore [PATH]
1358 2. ignore FILE [FILES...] [PATH]
1360 1. Display the value of svn:ignore property on [PATH].
1361 2. Add some files in the svn:ignore property of [PATH].
1363 If you want to add directories in the ignore-list, be careful:
1364 svn ignore foo/ bar/
1365 will add "foo/" in the property svn:ignore within the directory bar!
1367 svn ignore foo/ bar/ .
1368 (It'\''s somewhat like with mv)
1374 echo 'mail: Resend the mail of a given commit.
1375 usage: mail REV [emails]
1377 REV must have an email file associated in +committed/REV.
1378 REV can also be PREV or HEAD.
1380 By default the mail is sent to same email addresses as during the original
1381 commit unless more arguments are given.'
1383 propadd | padd | pa
)
1384 echo 'propadd (padd, pa): Add something in the value of a property.
1385 usage: propadd PROPNAME PROPVAL PATH
1387 PROPVAL will be appended at the end of the property PROPNAME.
1393 echo 'propsed (psed): Edit a property with sed.
1394 usage: propsed PROPNAME SED-ARGS PATH
1396 eg: svn propsed svn:externals "s/http/https/" .
1402 echo 'revision (rev): Display the revision number of a local or remote item.'
1403 $SVN help info |
sed '1d;
1404 s/information/revision/g;
1405 s/revision about/the revision of/g;
1406 2,35 s/info/revision/g;
1410 echo 'touch: Touch a file and svn add it.
1411 usage: touch FILE [FILES]...
1416 selfupdate | selfup | self-update | self-up
)
1417 echo 'selfupdate (selfup): Attempt to update svn-wrapper.sh
1424 echo 'version: Display the version info of svn and svn-wrapper.
1435 # svn_status [args...]
1438 svn_status_out
=`$SVN status "$@"`
1440 test x
"$svn_status_out" = x
&& return $svn_status_rv
1441 echo "$svn_status_out" |
sed "$sed_svn_st_color"
1442 return $svn_status_rv
1445 # svn_update [args...]
1448 svn_update_out
=`$SVN update "$@"`
1450 echo "$svn_update_out" |
sed "$sed_svn_up_colors"
1451 return $svn_update_rv
1454 # ------------------- #
1455 # `main' starts here. #
1456 # ------------------- #
1458 # Define colors if stdout is a tty.
1461 else # stdout isn't a tty => don't print colors.
1465 # Consider this as a sed function :P.
1469 s@^?\\(......\\)+@+\\1+@
1470 s@^?\\(......\\)\\(.*/\\)+@+\\1\\2+@
1471 s@^?\\(......\\),@,\\1,@
1472 s@^?\\(......\\)\\(.*/\\),@,\\1\\2,@
1473 s/^\\(.\\)C/\\1${lred}C${std}/
1476 s/^?/${lred}?${std}/; t
1477 s/^M/${lgreen}M${std}/; t
1478 s/^A/${lgreen}A${std}/; t
1479 s/^X/${lblue}X${std}/; t
1480 s/^+/${lyellow}+${std}/; t
1481 s/^D/${lyellow}D${std}/; t
1482 s/^,/${lred},${std}/; t
1483 s/^C/${lred}C${std}/; t
1484 s/^I/${purple}I${std}/; t
1485 s/^R/${lblue}R${std}/; t
1486 s/^!/${lred}!${std}/; t
1487 s/^~/${lwhite}~${std}/; t"
1496 s/^\\(.\\)C/\\1${lred}C${std}/
1497 s/^\\(.\\)U/\\1${lgreen}U${std}/
1498 s/^\\(.\\)D/\\1${lred}D${std}/
1501 s/^A/${lgreen}A${std}/; t
1502 s/^U/${lgreen}U${std}/; t
1503 s/^D/${lyellow}D${std}/; t
1504 s/^G/${purple}G${std}/; t
1505 s/^C/${lred}C${std}/; t"
1508 test "x$1" = x--debug
&& shift && set -x
1511 # ------------------------------- #
1512 # Hooks for standard SVN commands #
1513 # ------------------------------- #
1535 # -------------------- #
1536 # Custom SVN commands #
1537 # -------------------- #
1538 automerge | auto-merge | amerge | am
)
1540 if [ $# -ne 2 ]; then
1541 abort
"automerge: not enough arguments provided;
1542 try 'svn help automerge' for more info"
1548 require_diffstat
&& $SVN diff "$@" | diffstat
1562 propadd | padd | pa
)
1576 touch "$@" && svn add
"$@"
1578 selfupdate | selfup | self-update | self-up
)
1582 version |
-version |
--version)
1584 set dummy
'--version' "$@"