svn_commit [git mode]: git add the ChangeLog before committing.
[svn-wrapper.git] / svn-wrapper.sh
blobb8670a4a1b119dd0100d2342b3ae968469b1a3e2
1 #! /bin/sh
2 # Simple wrapper around svn featuring auto-ChangeLog entries and emailing.
3 # $Id$
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,
19 # USA.
21 # Quick install: alias svn=path/to/svn-wrapper.sh -- that's all.
23 # ------ #
24 # README #
25 # ------ #
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
37 # your problem.
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 :)
42 # ------------- #
43 # DOCUMENTATION #
44 # ------------- #
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
52 # you're looking for.
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>.
59 # ---- #
60 # TODO #
61 # ---- #
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 # Default values (the user can export them to override them).
75 if [ x"$SVN" = x ]; then
76 if [ -d .git ]; then
77 SVN=git
78 else
79 SVN=svn
82 : ${EDITOR=missing}
83 export EDITOR
84 : ${GPG=gpg}
85 : ${TMPDIR=/tmp}
86 export TMPDIR
87 : ${PAGER=missing}
88 export PAGER
89 # Override the locale.
90 LC_ALL='C'
91 export LC_ALL
92 : ${AWK=missing}
94 # Signal number for traps (using plain signal names is not portable and breaks
95 # on recent Debians that use ash as their default shell).
96 SIGINT=2
98 # Pitfall: some users might be tempted to export SVN=svn-wrapper.sh for some
99 # reason. This is just *wrong*. The following is an attempt to save them from
100 # some troubles.
101 if [ x`basename "$SVN"` = 'xsvn-wrapper.sh' ]; then
102 SVN='svn'
105 # This code comes (mostly) from Autoconf.
106 # The user is always right.
107 if test "${PATH_SEPARATOR+set}" != set; then
108 trap "echo SIGINT; rm -f '$TMPDIR/conf$$.sh'; exit 130" $SIGINT
109 echo "#! /bin/sh" >"$TMPDIR/conf$$.sh"
110 echo "exit 0" >>"$TMPDIR/conf$$.sh"
111 chmod +x "$TMPDIR/conf$$.sh"
112 if (PATH="/nonexistent;."; conf$$.sh) >/dev/null 2>&1; then
113 PATH_SEPARATOR=';'
114 else
115 PATH_SEPARATOR=:
117 rm -f "$TMPDIR/conf$$.sh"
118 # "Remove" the trap
119 trap 'echo SIGINT; exit 130' $SIGINT
122 version='0.3'
123 revision=`sed '/^# $Id[:].*$/!d;
124 s/.*$Id[:] *//;
125 s/ *$ *//;
126 s/[^0-9]*\([0-9][0-9]*\).*/\1/' "$0"`
127 me="$0"
129 # The `main' really starts after the functions definitions.
131 # ---------------- #
132 # Helper functions #
133 # ---------------- #
135 set_colors()
137 red='\e[0;31m'; lred='\e[1;31m'
138 green='\e[0;32m'; lgreen='\e[1;32m'
139 yellow='\e[0;33m'; lyellow='\e[1;33m'
140 blue='\e[0;34m'; lblue='\e[1;34m'
141 purple='\e[0;35m'; lpurple='\e[1;35m'
142 cyan='\e[0;36m'; lcyan='\e[1;36m'
143 grey='\e[0;37m'; lgrey='\e[1;37m'
144 white='\e[0;38m'; lwhite='\e[1;38m'
145 std='\e[m'
148 set_nocolors()
150 red=''; lred=''
151 green=''; lgreen=''
152 yellow=''; lyellow=''
153 blue=''; lblue=''
154 purple=''; lpurple=''
155 cyan=''; lcyan=''
156 grey=''; lgrey=''
157 white=''; lwhite=''
158 std=''
161 # abort err-msg
162 abort()
164 echo "svn-wrapper: ${lred}abort${std}: $@" \
165 | sed '1!s/^[ ]*/ /' >&2
166 exit 1
169 # warn msg
170 warn()
172 echo "svn-wrapper: ${lred}warning${std}: $@" \
173 | sed '1!s/^[ ]*/ /' >&2
176 # notice msg
177 notice()
179 echo "svn-wrapper: ${lyellow}notice${std}: $@" \
180 | sed '1!s/^[ ]*/ /' >&2
183 # yesno question
184 yesno()
186 echo -n "$@ [y/N] "
187 read answer || return 1
188 case "$answer" in
189 y* | Y*) return 0;;
190 *) return 1;;
191 esac
192 return 42 # should never happen...
195 # yesnewproceed what
196 # returns true if `yes' or `proceed', false if `new'.
197 # the answer is stored in $yesnoproceed_res which is /yes|new|proceed/
198 yesnewproceed()
200 echo -n "$@ [(y)es/(p)roceed/(N)ew] "
201 read answer || return 1
202 case "$answer" in
203 y* | Y*) yesnoproceed_res=yes; return 0;;
204 p* | P*) yesnoproceed_res=proceed; return 0;;
205 *) yesnoproceed_res=new; return 1;;
206 esac
207 return 42 # should never happen...
210 # warn_env env-var
211 warn_env()
213 warn "cannot find the environment variable $1
214 You might consider using \`export $1 <FIXME>\`"
217 # get_unique_file_name file-name
218 get_unique_file_name()
220 test -e "$1" || {
221 echo "$1" && return 0
223 gufn="$1"; i=1
224 while test -e "$gufn.$i"; do
225 i=`expr $i + 1`
226 done
227 echo "$gufn.$i"
230 # ensure_not_empty description value
231 ensure_not_empty()
233 ene_val=`echo "$2" | tr -d ' \t\n'`
234 test x"$ene_val" = x && abort "$1: empty value"
237 # find_prog prog-name
238 # return true if prog-name is in the PATH
239 # echo the full path to prog-name on stdout.
240 # Based on a code from texi2dvi
241 find_prog()
243 save_IFS=$IFS
244 IFS=$PATH_SEPARATOR
245 for dir in $PATH; do
246 IFS=$save_IFS
247 test x"$dir" = x && continue
248 # The basic test for an executable is `test -f $f && test -x $f'.
249 # (`test -x' is not enough, because it can also be true for directories.)
250 # We have to try this both for $1 and $1.exe.
252 # Note: On Cygwin and DJGPP, `test -x' also looks for .exe. On Cygwin,
253 # also `test -f' has this enhancement, bot not on DJGPP. (Both are
254 # design decisions, so there is little chance to make them consistent.)
255 # Thusly, it seems to be difficult to make use of these enhancements.
257 if test -f "$dir/$1" && test -x "$dir/$1"; then
258 echo "$dir/$1"
259 return 0
260 elif test -f "$dir/$1.exe" && test -x "$dir/$1.exe"; then
261 echo "$dir/$1.exe"
262 return 0
264 done
265 return 1
268 # find_progs prog [progs...]
269 # Look in PATH for one of the programs given in argument.
270 # If none of the progs can be found, the string "exit 2" is "returned".
271 find_progs()
273 # This code comes mostly from Autoconf.
274 for fp_prog in "$@"; do
275 fp_res=`find_prog $fp_prog`
276 if [ $? -eq 0 ]; then
277 echo "$fp_res"
278 return 0
280 done
281 echo "exit 2"
282 return 1
285 test x"$EDITOR" = xmissing && EDITOR=`find_progs vim vi emacs nano`
286 test x"$PAGER" = xmissing && PAGER=`find_progs less more`
287 test x"$AWK" = xmissing && AWK=`find_progs gawk mawk nawk awk`
289 # require_diffstat
290 # return true if diffstat is in the PATH
291 require_diffstat()
293 if [ x"$require_diffstat_cache" != x ]; then
294 return $require_diffstat_cache
296 if (echo | diffstat) >/dev/null 2>/dev/null; then :; else
297 warn 'diffstat is not installed on your system or not in your PATH.'
298 test -f /etc/debian_version \
299 && notice 'you might want to `apt-get install diffstat`.'
300 require_diffstat_cache=1
301 return 1
303 require_diffstat_cache=0
304 return 0
307 # require_mail
308 # return 0 -> found sendmail
309 # 1 -> found mail
310 # 2 -> no mailer found
311 # The full path to the program found is echo'ed on stdout.
312 require_mail()
314 if [ x"$require_mail_cache_rv" != x ]; then
315 echo "$require_mail_cache"
316 return $require_mail_cache_rv
318 save_PATH="$PATH"
319 PATH="${PATH}${PATH_SEPARATOR}/sbin${PATH_SEPARATOR}/usr/sbin${PATH_SEPARATOR}/usr/libexec"
320 export PATH
321 require_mail_cache=`find_prog sendmail`
322 if [ $? -eq 0 ] && [ x"$require_mail_cache" != x ]; then
323 echo "$require_mail_cache"
324 require_mail_cache_rv=0
325 return 0
327 PATH="$save_PATH"
328 export PATH
329 require_mail_cache=`find_prog mail`
330 if [ $? -eq 0 ] && [ x"$require_mail_cache" != x ]; then
331 echo "$require_mail_cache"
332 require_mail_cache_rv=1
333 return 1
334 else
335 warn 'mail is not installed on your system or not in your PATH.'
336 test -f /etc/debian_version \
337 && notice 'you might want to:
338 # apt-get install mailx
339 # dpkg-reconfigure exim'
340 require_mail_cache=''
341 require_mail_cache_rv=1
342 return 1
344 require_mail_cache=''
345 require_mail_cache_rv=42
346 return 42
349 # my_sendmail mail-file mail-subject mail-to [extra-headers]
350 # mail-to is a comma-separated list of email addresses.
351 # extra-headers is an optionnal argument and will be prepended at the
352 # beginning of the mail headers if the tool used to send mails supports it.
353 # The mail-file may also contain headers. They must be separated from the body
354 # of the mail by a blank line.
355 my_sendmail()
357 test -f "$1" || abort "my_sendmail: Cannot find the mail file: $1"
358 test x"$2" = x && warn 'my_sendmail: Empty subject.'
359 test x"$3" = x && abort 'my_sendmail: No recipient specified.'
361 content_type='Content-type: text/plain'
362 extra_headers="X-Mailer: svn-wrapper v$version (r$revision)
363 Mime-Version: 1.0
364 Content-Transfer-Encoding: 7bit"
365 if test x"$4" != x; then
366 extra_headers="$4
367 $extra_headers"
368 # Remove empty lines.
369 extra_headers=`echo "$extra_headers" | sed '/^[ ]*$/d;s/^[ ]*//'`
372 # If we have a signature, add it.
373 if test -f ~/.signature; then
374 # But don't add it if it is already in the mail.
375 if grep -Fe "`cat ~/.signature`" "$1" >/dev/null; then :; else
376 echo '-- ' >>"$1" && cat ~/.signature >>"$1"
379 # VCS-compat: handle user option 'sign'.
380 if (grep '^sign: false' ~/.vcs) >/dev/null 2>/dev/null; then :; else
381 ($GPG -h) >/dev/null 2>/dev/null
382 gpg_rv=$?
383 if [ -e ~/.gnupg ] || [ -e ~/.gpg ] || [ -e ~/.pgp ] && [ $gpg_rv -lt 42 ]
384 then
385 if grep 'BEGIN PGP SIGNATURE' "$1" >/dev/null; then
386 notice 'message is already GPG-signed'
387 elif yesno "Sign the mail using $GPG ?"; then
388 # Strip the headers
389 sed '1,/^$/d' "$1" >"$1.msg"
390 sed '1,/^$/!d' "$1" >"$1.hdr"
391 # Sign the message, keep only the PGP signature.
392 $GPG --clearsign <"$1.msg" >"$1.tmp" || {
393 rm -f "$1.msg" "$1.hdr" "$1.tmp"
394 abort "\`$GPG' failed (r=$?)"
396 sed '/^--*BEGIN PGP SIGNATURE--*$/,/^--*END PGP SIGNATURE--*$/!d' \
397 "$1.tmp" >"$1.sig"
399 boundary="svn-wrapper-2-$RANDOM"
400 boundary="$boundary$RANDOM"
401 # Prepend some stuff before the PGP signature.
402 echo "
403 --$boundary
404 content-type: application/pgp-signature; x-mac-type=70674453;
405 name=PGP.sig
406 content-description: This is a digitally signed message part
407 content-disposition: inline; filename=PGP.sig
408 content-transfer-encoding: 7bit" | cat - "$1.sig" >"$1.tmp"
409 mv -f "$1.tmp" "$1.sig"
411 # Append some stuff after the PGP signature.
412 echo "
413 --$boundary--" >>"$1.sig"
414 # Re-paste the headers before the signed body and prepend some stuff.
415 echo "This is an OpenPGP/MIME signed message (RFC 2440 and 3156)
416 --$boundary
417 Content-Transfer-Encoding: 8bit
418 Content-Type: text/plain; charset=iso-8859-1; format=flowed
420 | cat "$1.hdr" - "$1.msg" "$1.sig" >"$1.tmp" \
421 && mv -f "$1.tmp" "$1"
422 content_type="Content-Type: multipart/signed;\
423 protocol=\"application/pgp-signature\"; micalg=pgp-sha1;\
424 boundary=\"$boundary\""
425 # Cleanup.
426 rm -f "$1.tmp" "$1.sig" "$1.msg" "$1.hdr"
427 extra_headers="$extra_headers
428 X-Pgp-Agent: `$GPG --version | sed q`"
432 extra_headers="$extra_headers
433 $content_type"
435 mailer=`require_mail`
436 r_mail_rv=$?
437 if [ $? -ge 2 ] || [ x"$mailer" = x ]; then
438 warn 'my_sendmail: No suitable mailer found.'
439 return 1
441 case $r_mail_rv in
442 0) # sendmail
443 to=`echo "$3" | sed 's/,//g'`
444 echo "$extra_headers" | cat - "$1" | $mailer $to;;
445 1) # mail
446 cat "$1" | $mailer -s "$2" "$3";;
447 *) # wtf
448 warn 'my_sendmail: Internal error.'; return 42;;
449 esac
452 # selfupdate
453 selfupdate()
455 my_url='http://www.lrde.epita.fr/~sigoure/svn-wrapper'
456 # You can use https if you feel paranoiac.
458 echo ">>> Fetching svn-wrapper.sh from $my_url/svn-wrapper.sh"
460 # --------------------- #
461 # Fetch the new version #
462 # --------------------- #
464 tmp_me=`get_unique_file_name "$TMPDIR/svn-wrapper.sh"`
465 if (wget --help) >/dev/null 2>/dev/null; then
466 wget --no-check-certificate "$my_url/svn-wrapper.sh" -O "$tmp_me"
467 my_wget='wget'
468 else
469 curl --help >/dev/null 2>/dev/null
470 if [ $? -gt 42 ]; then
471 abort 'Cannot find wget or curl.
472 How can I download any update without them?'
474 my_wget='curl'
475 curl --insecure "$my_url/svn-wrapper.sh" >"$tmp_me"
478 test -r $tmp_me \
479 || abort "Cannot find the copy of myself I downloaded in $tmp_me"
481 # ---------------------------------------- #
482 # Compare versions and update if necessary #
483 # ---------------------------------------- #
485 my_ver="$revision"
486 tmp_ver=`sed '/^# $Id[:].*$/!d;
487 s/.*$Id[:] *//;
488 s/ *$ *//;
489 s/[^0-9]*\([0-9][0-9]*\).*/\1/' "$tmp_me"`
490 test x"$tmp_ver" = x && abort "Cannot find the version of $tmp_me"
491 if [ "$my_ver" -lt "$tmp_ver" ]; then # There IS an update...
492 echo "An update is available, r$tmp_ver (your version is r$my_ver)"
494 # See the ChangeLog?
495 if yesno "Do you want to see the ChangeLog between r$my_ver and r$tmp_ver?"
496 then
497 my_chlog=`get_unique_file_name "$TMPDIR/ChangeLog"`
498 case $my_wget in
499 wget) wget --no-check-certificate "$my_url/ChangeLog" -O "$my_chlog"
501 curl) curl --insecure "$my_url/ChangeLog" >"$my_chlog"
503 *) abort 'Should never be here.'
505 esac
506 sed "/^r$my_ver/q" "$my_chlog" | $PAGER
507 rm -f "$my_chlog"
510 # Wanna see the diff?
511 if yesno "Do you want to see the diff between r$my_ver and r$tmp_ver?"
512 then
513 (require_diffstat && diff -uw "$me" "$tmp_me" | diffstat;
514 echo
515 diff -uw "$me" "$tmp_me") | $PAGER
518 # Let's go :)
519 if yesno "Overwrite $me (r$my_ver) with $tmp_me (r$tmp_ver)?"; then
520 chmod a+x "$tmp_me"
521 cp -p "$me" "$me.r$my_ver"
522 mv "$tmp_me" "$me" && exit 0
524 rm -f "$tmp_me"
525 return 1
526 elif [ "$my_ver" -gt "$tmp_ver" ]; then
527 echo "Wow, you're more up to date than the master copy :)"
528 echo "Your version is r$my_ver and the master copy is r$tmp_ver."
529 if yesno 'Downgrade?'; then
530 chmod a+x "$tmp_me"
531 mv "$tmp_me" "$me" && exit 0
533 else
534 echo "You're already up to date [r$my_ver] :)"
536 rm -f "$tmp_me"
539 # get_svn_diff_and_diffstat [files to diff]
540 # Helper for svn_commit
541 get_svn_diff_and_diffstat()
543 if [ x"$SVN" = xgit ]; then
544 svn_diff=`git diff --ignore-space-change --no-color -C HEAD`
545 svn_diff_stat=`git diff --stat --ignore-space-change --no-color -C HEAD`
546 else
547 # Ignore white spaces. Can't svn diff -x -w: svn 1.4 only.
548 svn_diff=`svn_diffw "$@"`
549 test x"$svn_diff" = x && svn_diff=`$SVN diff "$@"`
550 if require_diffstat; then
551 svn_diff_stat=`echo "$svn_diff" | diffstat`
552 else
553 svn_diff_stat='diffstat not available'
558 # ------------------------------- #
559 # Hooks for standard SVN commands #
560 # ------------------------------- #
562 # svn_commit [args...]
563 # Here is how the commit process goes:
565 # First we look in the arguments passed to commit:
566 # If there are some files or paths, the user wants to commit these only. In
567 # this case, we must search for ChangeLogs from these paths. We might find
568 # more than one ChangeLog, in this case the user will be prompted to pick up
569 # one.
570 # Otherwise (no path passed in the command line) the user just wants to
571 # commit the current working directory.
572 # In any case, we schedule "ChangeLog" for commit.
574 # Alright now that we know which ChangeLog to use, we look in the ChangeLog's
575 # directory if there is a ",svn-log" file which would mean that a previous
576 # commit didn't finish successfully. If there is such a file, the user is
577 # prompted to know whether they want to resume that commit or simply start a
578 # new one.
579 # When the user wants to resume a commit, the ",svn-log" file is loaded and we
580 # retrieve the value of "$@" that was saved in the file.
581 # Otherwise we build a template ChangeLog entry.
582 # Then we open the template ChangeLog entry with $EDITOR so that the user
583 # fills it properly.
584 # Finally, we commit.
585 # Once the commit is sent, we ask the server to know which revision was
586 # commited and we also retrieve the diff. We then send a mail with these.
587 svn_commit()
589 here=`pwd -P`
590 dry_run=no
592 # Check if the user passed some paths to commit explicitly
593 # because in this case we must add the ChangeLog to the commit and search
594 # the ChangeLog from the dirname of that file.
595 i=0; search_from=''; add_changelog=no; extra_files=''
596 while [ $i -lt $# ]; do
597 arg="$1"
598 if [ x"$arg" = x--dry-run ]; then
599 dry_run=yes
600 shift
601 i=`expr $i + 1`
602 continue
604 # If the argument is a valid path: add the ChangeLog in the list of
605 # files to commit
606 if test -e "$arg"; then
607 add_changelog=yes
608 if test -d "$arg"; then
609 search_from_add="$arg"
610 else
611 search_from_add=`dirname "$arg"`
613 search_from="$search_from:$search_from_add"
615 shift
616 set dummy "$@" "$arg"
617 shift
618 i=`expr $i + 1`
619 done
620 if [ $add_changelog = no ]; then
621 # There is no path/file in the command line: the user wants to commit the
622 # current directory. Make it explicit now:
623 extra_files="$here"
625 search_from=`echo "$search_from" | sed 's/^://; s/^$/./'`
627 # ----------------- #
628 # Find ChangeLog(s) #
629 # ----------------- #
631 nb_chlogs=0; change_log_dirs=''
632 save_IFS=$IFS; IFS=':'
633 for dir in $search_from; do
634 IFS=$save_IFS
635 test -z "$dir" && dir='.'
636 # First: come back to the original place
637 cd "$here" || abort "Cannot cd to $here"
638 cd "$dir" || continue # Then: Enter $dir (which can be a relative path)
639 found=0
640 while [ $found -eq 0 ]; do
641 this_chlog_dir=`pwd -P`
642 if [ -f ./ChangeLog ]; then
643 found=1
644 nb_chlogs=`expr $nb_chlogs + 1`
645 change_log_dirs="$change_log_dirs:$this_chlog_dir"
646 else
647 cd ..
649 # Stop searching when in / ... hmz :P
650 test x"$this_chlog_dir" = x/ && break
651 done # end while: did we find a ChangeLog
652 done # end for: find ChangeLogs in $search_from
653 if [ $nb_chlogs -gt 0 ]; then
654 change_log_dirs=`echo "$change_log_dirs" | sed 's/^://' | tr ':' '\n' \
655 | sort -u`
656 nb_chlogs=`echo "$change_log_dirs" | wc -l`
659 # Did we find a ChangeLog? More than one?
660 if [ $nb_chlogs -eq 0 ]; then
661 if yesno 'svn-wrapper: Error: Cannot find a ChangeLog file!
662 You might want to create an empty one (eg: `touch ChangeLog` where appropriate)
663 Do you want to proceed without using a ChangeLog?'; then
664 cd "$here"
665 $SVN commit "$@"
666 return $?
667 else
668 return 1
670 elif [ $nb_chlogs -gt 1 ]; then
671 notice "$nb_chlogs ChangeLogs were found, pick up one:"
673 IFS=':'; i=0
674 for a_chlog_dir in $change_log_dirs; do
675 i=`expr $i + 1`
676 echo "$i. $a_chlog_dir/ChangeLog"
677 done
678 echo -n "Which ChangeLog do you want to use? [1-$i] "
679 read chlog_no || abort 'Cannot read answer on stdin.'
681 case "$chlog_no" in
682 *[^0-9]*) abort "Invalid ChangeLog number: $chlog_no"
683 esac
684 test "$chlog_no" -le $i || abort "Invalid ChangeLog number: $chlog_no
685 max value was: $i"
686 test "$chlog_no" -ge 1 || abort "Invalid ChangeLog number: $chlog_no
687 min value was: 1"
688 change_log_dir=`echo "$change_log_dirs" | tr ':' '\n' | sed "${chlog_no}!d"`
689 else # Only one ChangeLog found
690 change_log_dir=$change_log_dirs
691 notice "using $change_log_dir/ChangeLog"
694 test -f "$change_log_dir/ChangeLog" \
695 || abort "No such file or directory: $change_log_dir/ChangeLog"
697 # Now we can safely schedule the ChangeLog for the commit.
698 extra_files="$extra_files:$change_log_dir/ChangeLog"
699 if [ -d "$change_log_dir/.git" ]; then
700 SVN=git
701 repos_url=`git show | sed '/git-svn-id/{
702 s/^[^:]*: *//
703 s/@.*$//
707 test x"$repos_url" = x && abort 'Could not find git-svn-id in `git show`'
708 repos_root=$repos_url
709 else
710 svn_st_tmp=`$SVN status "$change_log_dir"`
712 # Warn for files that are not added in the repos.
713 conflicts=`echo "$svn_st_tmp" | sed '/^ *$/d;
714 /^[^?]/d;
715 /^?.......*\/[,+]/d;
716 /^?......[,+]/d'`
717 if test x"$conflicts" != x; then
718 warn "make sure you don't want to \`svn add'
719 any of the following files before committing:"
720 echo "$conflicts" | sed "$sed_svn_st_color"
721 echo -n 'Type [ENTER] to continue :)' && read chiche_is_gay
724 # If there are changes in an svn:externals, advise the user to commit that
725 # first.
726 changed_externals=`echo "$svn_st_tmp" | $AWK \
727 'function printext()
729 if (ext && !printed)
731 print this_ext "\n";
732 printed = 1;
735 BEGIN { this_ext = ""; ext = 0; ext_modified = 0; }
736 /^Performing status on external/ {
737 ext = 1;
738 sub(/.* at ./, ""); sub(/.$/, ""); this_ext = $0;
739 printed = 0;
741 /^[ADMR]/ { ext_modified = ext; printext(); }
742 /^.[M]/ { ext_modified = ext; printext(); }
743 END { exit ext_modified; }'`
744 if [ $? -ne 0 ]; then
745 warn "the following external items have local modifications:
746 $changed_externals"
747 yesno "You are advised to commit them separately first. Continue anyway?" \
748 || return 1
751 # Detect unresolved conflicts / missing files.
752 conflicts=`echo "$svn_st_tmp" | sed '/^[C!]/!d'`
753 test x"$conflicts" != x && abort "there are unresolved conflicts (\`C')
754 and/or missing files (\`!'):
755 $conflicts"
757 svn_info_tmp=`$SVN info "$change_log_dir"`
758 test $? -ne 0 && abort "Failed to get svn info on $change_log_dir"
759 repos_root=`echo "$svn_info_tmp" | sed '/^Repository Root: /!d;s///'`
760 repos_url=`echo "$svn_info_tmp" | sed '/^URL: /!d;s///'`
761 # It looks like svn <1.3 didn't display a "Repository Root" entry.
762 test x"$repos_root" = x && repos_root=$repos_url
765 cd "$here"
767 YYYY=`date '+%Y'`
768 MM=`date '+%m'`
769 DD=`date '+%d'`
771 # VCS-compat: handle user option 'new_user'
772 new_user='yes'
773 grep '^new_user: false' ~/.vcs >/dev/null 2>/dev/null && new_user='no'
775 edit_changelog=yes
776 tmp_log="$change_log_dir/,svn-log"
777 if [ -f "$tmp_log" ] && yesnewproceed "It looks like the last commit did not\
778 terminate successfully.
779 Would you like to resume it or proceed immediately?"; then
780 test x"$yesnoproceed_res" = xproceed && edit_changelog=no
781 echo 'Resuming ...'
782 internal_tags=`sed '/^--- Internal stuff, DO NOT change please ---$/,$!d' \
783 "$tmp_log"`
784 saved_args=`echo "$internal_tags" | sed '/^args: */!d;s///'`
785 extra_files=`echo "$internal_tags" | sed '/^extra_files: */!d;s///'`
786 if [ x"$saved_args" != x ]; then
787 if [ x"$*" != x ] && [ x"$saved_args" != x"$*" ]; then
788 warn "overriding arguments:
789 you invoked $me with the following arguments: $@
790 they have been replaced by these: $saved_args"
791 set dummy $saved_args
792 shift
793 else
794 notice "setting the following arguments: $saved_args"
795 set dummy $saved_args
796 shift
798 elif [ x"$*" != x ]; then
799 warn "overriding arguments:
800 you invoked $me with the following arguments: $@
801 they have been dropped"
804 get_svn_diff_and_diffstat "$@"
806 # Update the file with the new diff/diffstat in case it changed.
807 $AWK 'BEGIN {
808 tlatbwbi_seen = 0;
809 ycewah_seen = 0;
811 /^--This line, and those below, will be ignored--$/ {
812 tlatbwbi_seen = 1;
814 /^ Your ChangeLog entry will appear here\.$/ {
815 if (tlatbwbi_seen) ycewah_seen = 1;
818 if (ycewah_seen != 2) print;
819 if (ycewah_seen == 1) ycewah_seen = 2;
820 }' "$tmp_log" >"$tmp_log.tmp"
821 echo "
822 $svn_diff_stat
824 $svn_diff
826 $internal_tags" >>"$tmp_log.tmp"
827 mv -f "$tmp_log.tmp" "$tmp_log"
829 else # Build the template message.
831 # ------------------------------------ #
832 # Gather info for the template message #
833 # ------------------------------------ #
835 if [ x"$SVN" = xgit ]; then
836 projname=`grep project "$change_log_dir/.git/svn/git-svn/unhandled.log"`
837 test $? = 0 \
838 || abort 'Failed to find "project" in git-svn/unhandled.log'
839 sed_tmp='$!d;s/^.*+dir_prop: . project //'
840 projname=`echo "$projname" | sed "$sed_tmp"`
841 else
842 projname=`$SVN propget project "$change_log_dir"`
844 # Try to be VCS-compatible and find a project name in a *.rb.
845 if [ x"$projname" = x ] && [ -d "$change_log_dir/vcs" ]; then
846 projname=`sed '/common_commit/!d;s/.*"\(.*\)<%= rev.*/\1/' \
847 "$change_log_dir"/vcs/*.rb`
848 test x"$projname" != x && test x$new_user = xyes \
849 && notice "VCS-compat: found project name: $projname
850 in " "$change_log_dir"/vcs/*.rb
852 test x"$projname" != x && projname=`echo "$projname" | sed '/[^ ]$/s/$/ /'`
854 if [ x"$SVN" = xgit ]; then
855 mailto=`grep mailto "$change_log_dir/.git/svn/git-svn/unhandled.log"`
856 test $? = 0 \
857 || abort 'Failed to find "mailto" in git-svn/unhandled.log'
858 sed_tmp='$!d;s/^.*+dir_prop: . mailto //;s/%40/@/g;s/%2C/,/g'
859 mailto=`echo "$mailto" | sed "$sed_tmp"`
860 else
861 mailto=`$SVN propget mailto "$change_log_dir"`
863 if [ x"$mailto" = x ]; then
864 test x$new_user = xyes \
865 && warn "no svn property mailto found in $change_log_dir
866 You might want to set default email adresses using:
867 svn propset mailto 'somebody@mail.com, foobar@example.com'\
868 $change_log_dir" >&2
869 # Try to be VCS-compatible and find a list of mails in a *.rb.
870 if [ -d "$change_log_dir/vcs" ]; then
871 mailto=`grep '.@.*\..*' "$change_log_dir"/vcs/*.rb \
872 | tr '\n' ' ' \
873 | sed 's/^.*[["]\([^["]*@[^]"]*\)[]"].*$/\1/' | xargs`
874 test x"$mailto" != x && test x$new_user = xyes \
875 && notice "VCS-compat: found mailto: $mailto
876 in " "$change_log_dir"/vcs/*.rb
877 fi # end VCS compat
878 fi # end guess mailto
880 # Ensure that emails are comma-separated.
881 mailto=`echo "$mailto" | sed 's/[ ;]/,/g' | tr -s ',' | sed 's/,/, /g'`
882 test x"$FULLNAME" = x && FULLNAME='Type Your Name Here' \
883 && warn_env FULLNAME
884 test x"$EMAIL" = x && EMAIL='your.mail.here@FIXME.com' && warn_env EMAIL
886 if [ x"$SVN" = xgit ]; then
887 my_svn_st=`git status`
888 test $? = 0 || abort 'Nothing to commit (or git status failed)'
889 change_log_files=`echo "$my_svn_st" | sed '
890 t dummy_sed_1
891 : dummy_sed_1
892 s/^# modified: *\(.*\)$/ * \1: ./; t
893 s/^# new file: *\(.*\)$/ * \1: New./; t
894 s/^# deleted: *\(.*\)$/ * \1: Remove./; t
895 # d < FIXME delete lines once git support works for sur
897 else
898 # --ignore-externals appeared after svn 1.1.1
899 my_svn_st=`$SVN status --ignore-externals "$@" \
900 || $SVN status "$@" | sed '/^Performing status on external/ {
904 # Files to put in the ChangeLog entry.
905 change_log_files=`echo "$my_svn_st" | sed '
906 t dummy_sed_1
907 : dummy_sed_1
908 s/^M......\(.*\)$/ * \1: ./; t
909 s/^A......\(.*\)$/ * \1: New./; t
910 s/^D......\(.*\)$/ * \1: Remove./; t
913 if [ x"$change_log_files" = x ]; then
914 yesno 'Nothing to commit, continue anyway?' || return 1
918 change_log_files=`echo "$change_log_files" | sort -u`
920 get_svn_diff_and_diffstat "$@"
922 # Get any older svn-log out of the way.
923 test -f "$tmp_log" && mv "$tmp_log" `get_unique_file_name "$tmp_log"`
924 # If we can't get an older svn-log out of the way, find a new name...
925 test -f "$tmp_log" && tmp_log=`get_unique_file_name "$tmp_log"`
926 if [ x$new_user = no ]; then
927 commit_instructions='
928 Instructions:
929 - Fill the ChangeLog entry.
930 - If you feel like, write a comment in the "Comment:" section.
931 This comment will only appear in the email, not in the ChangeLog.
932 By default only the location of the repository is in the comment.
933 - Some tags will be replaced. Tags are of the form: <TAG>. Unknown
934 tags will be left unchanged.
935 - The tag <REV> may only be used in the Subject.
936 - Your ChangeLog entry will be used as commit message for svn.'
937 else
938 commit_instructions=''
940 echo "\
941 --You must fill this file correctly to continue-- -*- vcs -*-
942 Title:
943 Subject: ${projname}r<REV>: <TITLE>
944 From: $FULLNAME <$EMAIL>
945 To: $mailto
947 Comment:
948 URL: $repos_url
950 ChangeLog:
952 <YYYY>-<MM>-<DD> $FULLNAME <$EMAIL>
954 <TITLE>
955 $change_log_files
957 --This line, and those below, will be ignored--
958 $commit_instructions
959 --Preview of the message that will be sent--
961 URL: $repos_url
962 Your comments (if any) will appear here.
964 ChangeLog:
965 $YYYY-$MM-$DD $FULLNAME <$EMAIL>
967 Your ChangeLog entry will appear here.
969 $svn_diff_stat
971 $svn_diff" >"$tmp_log"
973 echo "
974 --- Internal stuff, DO NOT change please ---
975 args: $@" >>"$tmp_log"
976 echo "extra_files: $extra_files
977 vi: ft=diff:noet:" >>"$tmp_log"
979 fi # end: if svn-log; then resume? else create template
980 test x"$edit_changelog" = xyes && $EDITOR "$tmp_log"
982 # ------------------ #
983 # Re-"parse" the log #
984 # ------------------ #
986 # hmz this section is a bit messy...
987 # helper string... !@#$%* escaping \\\\\\...
988 sed_escape='s/\\/\\\\/g;s/@/\\@/g;s/&/\\\&/g'
989 sed_eval_tags="s/<MM>/$MM/g; s/<DD>/$DD/g; s/<YYYY>/$YYYY/g"
990 full_log=`sed '/^--This line, and those below, will be ignored--$/,$d;
991 /^--You must fill this/d' "$tmp_log"`
992 chlog_entry=`echo "$full_log" | sed '/^ChangeLog:$/,$!d; //d'`
993 ensure_not_empty 'ChangeLog entry' "$chlog_entry"
994 full_log=`echo "$full_log" | sed '/^ChangeLog:$/,$d'`
995 mail_comment=`echo "$full_log" | sed '/^Comment:$/,$!d; //d'`
996 full_log=`echo "$full_log" | sed '/^Comment:$/,$d'`
997 mail_title=`echo "$full_log" | sed '/^Title: */!d;s///'`
998 ensure_not_empty 'commit title' "$mail_title"
999 mail_title=`echo "$mail_title" | sed "$sed_eval_tags; $sed_escape"`
1000 sed_eval_tags="$sed_eval_tags; s@<TITLE>@$mail_title@g"
1001 mail_comment=`echo "$mail_comment" | sed "$sed_eval_tags"`
1002 chlog_entry=`echo "$chlog_entry" | sed "$sed_eval_tags; 1{
1003 /^ *$/d
1005 mail_subject=`echo "$full_log" | sed '/^Subject: */!d;s///'`
1006 ensure_not_empty 'mail subject' "$mail_subject"
1007 mail_to=`echo "$full_log" | sed '/^To:/!d'`
1008 send_a_mail=yes
1009 if test x"$mail_to" = x; then
1010 send_a_mail=no
1011 else
1012 mail_to=`echo "$mail_to" | sed 's/^To: *//'`
1013 ensure_not_empty '"To:" field of the mail' "$mail_to"
1015 mail_from=`echo "$full_log" | sed '/^From: */!d;s///'`
1016 ensure_not_empty '"From:" field of the mail' "$mail_from"
1018 if echo "$chlog_entry" | grep '<REV>' >/dev/null; then
1019 warn 'Using the tag <REV> anywhere else than in the Subject is deprecated.'
1020 yesno 'Continue anyway?' || return 1
1023 if echo "$chlog_entry" | grep ': \.$' >/dev/null; then
1024 warn 'It looks like you did not fill all entries in the ChangeLog:'
1025 echo "$chlog_entry" | grep ': \.$'
1026 yesno 'Continue anyway?' || return 1
1029 # Check whether the user passed -m | --message
1031 while [ $i -lt $# ]; do
1032 arg="$1"
1033 # This is not really a reliable way of knowing whether -m | --message was
1034 # passed but hum... Let's assume it'll do :s
1035 if [ x"$arg" = 'x-m' ] || [ x"$arg" = 'x--message' ]; then
1036 my_message="$2"
1038 shift
1039 set dummy "$@" "$arg"
1040 shift
1041 i=`expr $i + 1`
1042 done
1043 if [ x"$my_message" = x ]; then
1044 my_message=`echo "$chlog_entry" | grep -v "^$YYYY-$MM-$DD" | sed -e '1,2 {
1045 /^ *$/d
1046 }' -e "$_sed_helper"`
1047 else
1048 notice 'you are overriding the commit message.'
1051 if [ x"$dry_run" = xyes ]; then
1052 proposal_file=',proposal'
1053 test -f "$proposal_file" \
1054 && proposal_file=`get_unique_file_name "$proposal_file"`
1055 mail_subject=`echo "$mail_subject" | sed "$sed_eval_tags; s/<REV>/???/g"`
1056 echo "\
1057 From: $mail_from
1058 To: $mail_to
1059 Subject: $mail_subject
1061 $mail_comment
1063 ChangeLog:
1064 $chlog_entry
1066 $svn_diff_stat
1068 $svn_diff" >"$proposal_file"
1069 notice "A proposal of your commit was left in '$proposal_file'"
1070 return 0
1073 # Are you sure?
1074 yesno 'Are you sure you want to commit?' || return 1
1076 # Add the ChangeLog entry
1077 old_chlog=`get_unique_file_name "$change_log_dir/ChangeLog.old"`
1078 mv "$change_log_dir/ChangeLog" "$old_chlog" || \
1079 abort 'Could not backup ChangeLog'
1080 trap "echo SIGINT; mv \"$old_chlog\" \"$change_log_dir/ChangeLog\";
1081 exit 130" $SIGINT
1082 echo "$chlog_entry" >"$change_log_dir/ChangeLog"
1083 echo >>"$change_log_dir/ChangeLog"
1084 cat "$old_chlog" >>"$change_log_dir/ChangeLog"
1086 # Add extra files such as cwd or ChangeLog to the commit.
1087 tmp_sed='s/ /\\ /g' # Escape spaces for the shell.
1088 if [ x"$SVN" = xgit ]; then
1089 # Schedule the ChangeLog for the next commit
1090 git add "$change_log_dir/ChangeLog" \
1091 || abort 'failed to git add the ChangeLog'
1092 extra_files=
1093 else
1094 extra_files=`echo "$extra_files" | sed "$tmp_sed" | tr ':' '\n'`
1097 # --Commit-- finally! :D
1098 $SVN commit -m "$my_message" "$@" $extra_files || {
1099 svn_commit_rv=$?
1100 mv "$old_chlog" "$change_log_dir/ChangeLog"
1101 abort "Commit failed, $SVN returned $svn_commit_rv"
1104 echo -n 'Getting the revision number... '
1105 if [ x"$SVN" = xgit ]; then
1106 REV=`git-show --pretty=format:%h | sed q`
1107 else
1108 svn_info_tmp=`$SVN info "$change_log_dir/ChangeLog"`
1109 REV=`echo "$svn_info_tmp" | sed '/^Revision: /!d;s///'`
1110 test x"$REV" = x && REV=`echo "$svn_info_tmp" \
1111 | sed '/^Last Changed Rev: /!d;s///'`
1113 test x"$REV" = x && abort 'Cannot detect the current revision.'
1114 echo "$REV"
1116 # Let's make sure we have the real diff by asking the ChangeSet we've just
1117 # committed to the server.
1119 # Backup the old stuff in case we fail to get the real diff from the server
1120 # for some reason...
1121 save_svn_diff=$svn_diff
1122 save_svn_diff_stat=$svn_diff_stat
1124 if [ x"$SVN" = xgit ]; then
1125 svn_diff=`git diff --ignore-space-change --no-color -C 'HEAD^' HEAD`
1126 svn_diff_stat=`git diff --stat --ignore-space-change --no-color -C 'HEAD^' HEAD`
1127 notice 'Do not forget to use: git-svn dcommit to push your commits in SVN'
1128 else
1129 # Fetch the ChangeSet and filter out the ChangeLog entry. We don't use
1130 # svn diff -c because this option is not portable to older svn versions.
1131 REV_MINUS_ONE=`expr "$REV" - 1`
1132 svn_diff=`svn_diffw -r"$REV_MINUS_ONE:$REV" "$repos_root" \
1133 | $AWK '/^Index: / { if (in_chlog) in_chlog = 0; }
1134 /^Index: .*ChangeLog$/ { in_chlog = 1 }
1135 { if (!in_chlog) print }'`
1136 if [ x"$svn_diff" = x ]; then
1137 svn_diff=$save_svn_diff
1138 svn_diff_stat=$save_svn_diff_stat
1139 else
1140 if require_diffstat; then
1141 svn_diff_stat=`echo "$svn_diff" | diffstat`
1142 else
1143 svn_diff_stat='diffstat not available'
1148 mail_subject=`echo "$mail_subject" | sed "$sed_eval_tags; s/<REV>/$REV/g"`
1150 mail_file=`get_unique_file_name "$change_log_dir/+mail"`
1151 echo "\
1152 From: $mail_from
1153 To: $mail_to
1154 Subject: $mail_subject
1156 $mail_comment
1158 ChangeLog:
1159 $chlog_entry
1161 $svn_diff_stat
1163 $svn_diff" | sed 's/^\.$/ ./' >"$mail_file"
1164 # We change lines with only a `.' because they could mean "end-of-mail"
1166 # Send the mail
1167 if test x$send_a_mail = xyes; then
1168 trap 'echo SIGINT; exec < /dev/null' $SIGINT
1169 # FIXME: Move the mail to the +committed right now, in case the user
1170 # CTLR+C the mail-sending-thing, so that the mail will be properly saved
1171 # their.
1172 my_sendmail "$mail_file" "$mail_subject" "$mail_to" \
1173 "X-svn-url: $repos_root
1174 X-svn-revision: $REV"
1175 fi # end do we have to send a mail?
1176 rm -f "$tmp_log"
1177 rm -f "$old_chlog"
1178 save_mail_file=`echo "$mail_file" | sed 's/+//'`
1179 mkdir -p "$change_log_dir/+committed" \
1180 || warn "Couldn't mkdir -p $change_log_dir/+committed"
1181 if [ -d "$change_log_dir/vcs" ] \
1182 || [ -d "$change_log_dir/+committed" ]
1183 then
1184 mkdir -p "$change_log_dir/+committed/$REV" \
1185 && mv "$mail_file" "$change_log_dir/+committed/$REV/mail"
1187 return $svn_commit_rv
1190 # svn_merge args...
1191 svn_merge()
1193 i=0; src=''; dst=''; rev1=''; rev2=''; r_seen=0
1194 while [ $i -lt $# ]; do
1195 arg="$1"
1196 if test -d "$arg"; then
1197 if [ x"$src" = x ]; then
1198 src="$arg"
1199 elif [ x"$dst" = x ]; then
1200 dst="$arg"
1202 else
1203 case "$arg" in
1204 -r*:*) rev1=`echo "$arg" | sed 's/^[^PREVHEAD0-9]*//; s/:.*//'`
1205 rev2=`echo "$arg" | sed 's/^.*://; s/[^PREVHEAD0-9]*//'`
1207 -r) r_seen=1 ;;
1208 esac
1210 shift
1211 set dummy "$@" "$arg"
1212 shift
1213 i=`expr $i + 1`
1214 done
1215 if [ x"$src" = x ]; then
1216 exec $SVN merge "$@"
1218 test x"$dst" = x && dst='.'
1220 if yesno "Do you want to use the automerge feature for $src ?";
1221 then :; else
1222 exec $SVN merge "$@"
1224 # FIXME: Incomplete.
1227 # svn_automerge src-path dst-path
1228 svn_automerge()
1230 test -f ChangeLog || abort 'You must use automerge in the folder where the
1231 ChangeLog is.'
1232 amerge=`$SVN propget automerge`
1233 if [ x"$amerge" = x ]; then
1234 # FIXME: Use svn log --stop-on-copy to detect the original branching.
1238 svn_info_tmp=`$SVN info`
1239 REV=`echo "$svn_info_tmp" | sed '/^Revision: /!d;s///'`
1240 test x"$REV" = x && REV=`echo "$svn_info_tmp" \
1241 | sed '/^Last Changed Rev: /!d;s///'`
1242 test x"$REV" = x && abort 'Cannot detect the current revision.'
1244 # FIXME: Incomplete.
1245 if yesno "Are you sure you want to run:
1246 $SVN merge -r$amerge:$REV $1 $2 ?"; then
1247 $SVN merge "-r$amerge:$REV" "$1" "$2"
1251 # svn_diffw [args...]
1252 svn_diffw()
1254 # Ignore white spaces. Can't svn diff -x -w: svn 1.4 only.
1255 if [ x"$SVN" = xgit ]; then
1256 git diff --ignore-space-change --no-color -C HEAD
1257 else
1258 $SVN diff --no-diff-deleted --diff-cmd diff -x -uw "$@"
1262 # svn_mail REV [mails...]
1263 svn_mail()
1265 test $# -lt 1 && abort "Not enough arguments provided;
1266 Try 'svn help mail' for more info."
1267 case "$1" in
1268 PREV)
1269 REV=`svn_revision || abort 'Cannot get current revision number'`
1270 test x"$REV" = x && abort 'Cannot get current revision number'
1271 if [ "$REV" -lt 1 ]; then
1272 abort 'No previous revision.'
1274 REV=`expr "$REV" - 1`
1276 HEAD)
1277 REV=`svn_revision || abort 'Cannot get current revision number'`
1278 test x"$REV" = x && abort 'Cannot get current revision number'
1280 *[^0-9]*) abort "Syntax error in revision argument '$1'";;
1281 *) REV="$1";;
1282 esac
1283 shift
1285 found_committed=0; found=0
1286 while [ $found -eq 0 ]; do
1287 this_chlog_dir=`pwd -P`
1288 if [ -d ./+committed ]; then
1289 found_committed=1
1290 if [ -d ./+committed/$REV ]; then
1291 found=1
1292 else
1293 cd ..
1295 else
1296 cd ..
1298 # Stop searching when in / ... hmz :P
1299 test x`pwd` = x/ && break
1300 done
1301 if [ $found -eq 0 ]; then
1302 if [ $found_committed -eq 0 ]; then
1303 abort 'Could not find the +committed directory.'
1304 else
1305 abort "Could not find the revision $REV in +committed."
1307 abort 'Internal error (should never be here).'
1310 mail_file=''; subject=''; to=''
1311 if [ -f ./+committed/$REV/mail ]; then
1312 # svn-wrapper generated file
1313 mail_file="./+committed/$REV/mail"
1314 subject=`sed '/^Subject: /!d;s///' $mail_file | sed '1q'`
1315 to=`sed '/^To: /!d;s///' $mail_file | sed '1q'`
1316 elif [ -f ./+committed/$REV/,iform ] && [ -f ./+committed/$REV/,message ]
1317 then
1318 # VCS-generated file
1319 subject=`sed '/^Subject: /!d;s///;s/^"//;s/"$//' ./+committed/$REV/,iform \
1320 | sed "s/<%= *rev *%>/$REV/g"`
1321 to=`sed '/^To:/,/^[^-]/!d' ./+committed/$REV/,iform | sed '1d;s/^- //;$d' \
1322 | xargs | sed 's/ */, /g'`
1323 mail_file=`get_unique_file_name "$TMPDIR/mail.r$REV"`
1324 echo "From: $FULLNAME <$EMAIL>
1325 To: $to
1326 Subject: $subject
1327 " >"$mail_file" || abort "Cannot create $mail_file"
1328 cat ./+committed/$REV/,message >>"$mail_file" \
1329 || abort "Cannot copy ./+committed/$REV/,message in $mail_file"
1330 else
1331 abort "Couldn't find the mail to re-send in `pwd`/+committed/$REV"
1333 if [ $# -gt 0 ]; then
1334 to=`echo "$*" | sed 's/ */, /g'`
1337 test x"$to" = x && abort 'Cannot find the list of recipients.
1338 Please report this bug.'
1339 test x"$subject" = x && abort 'Cannot find the subject of the mail.
1340 Please report this bug.'
1342 if yesno "Re-sending the mail of r$REV
1343 Subject: $subject
1344 To: $to
1345 Are you sure?"; then :; else
1346 return 1
1349 svn_info_tmp=`$SVN info`
1350 test $? -ne 0 && abort "Failed to get svn info on `pwd`"
1351 repos_root=`echo "$svn_info_tmp" | sed '/^Repository Root: /!d;s///'`
1352 repos_url=`echo "$svn_info_tmp" | sed '/^URL: /!d;s///'`
1353 # It looks like svn <1.3 didn't display a "Repository Root" entry.
1354 test x"$repos_root" = x && repos_root=$repos_url
1356 my_sendmail "$mail_file" "$subject" "$to" \
1357 "X-svn-url: $repos_url
1358 X-svn-revision: $REV"
1361 # svn_version
1362 svn_version()
1364 echo "Using svn-wrapper v$version (C) SIGOURE Benoit [GPL]"
1365 sed '/^# $Id[:].*$/!d;s/.*$Id[:] *//;s/ *$ *//;s/ \([0-9][0-9]*\)/ (r\1)/' "$me"
1368 # has_prop prop-name [path]
1369 # return value: 0 -> path has the property prop-name set.
1370 # 1 -> path has no property prop-name.
1371 # 2 -> svn error.
1372 has_prop()
1374 hp_plist=`$SVN proplist "$2"`
1375 test $? -ne 0 && return 2
1376 hp_res=`echo "$hp_plist" | sed "/^ *$1\$/!d"`
1377 test x"$hp_res" = x && return 1
1378 return 0
1381 # svn_propadd prop-name prop-val [path]
1382 svn_propadd()
1384 test $# -lt 2 \
1385 && abort 'Not enough arguments provided;
1386 try `svn help propadd` for more info'
1387 test $# -gt 3 \
1388 && abort 'Too many arguments provided;
1389 try `svn help propadd` for more info'
1391 path="$3"
1392 test x"$path" = x && path='.' && set dummy "$@" '.' && shift
1393 has_prop "$1" "$3" || {
1394 test $? -eq 2 && return 1 # svn error
1395 # no property found:
1396 yesno "'$path' has no property named '$1', do you want to add it?" \
1397 && $SVN propset "$@"
1398 return $?
1401 current_prop_val=`$SVN propget "$1" "$3"`
1402 test $? -ne 0 && abort "Failed to get the current value of property '$1'."
1404 $SVN propset "$1" "$current_prop_val
1405 $2" "$3" >/dev/null || abort "Failed to add '$3' in the property '$1'."
1407 current_prop_val=`$SVN propget "$1" "$3" || echo "$current_prop_val
1408 $2"`
1409 echo "property '$1' updated on '$path', new value:
1410 $current_prop_val"
1413 # svn_propsed prop-name sed-script [path]
1414 svn_propsed()
1416 test $# -lt 2 \
1417 && abort 'Not enough arguments provided;
1418 try `svn help propsed` for more info'
1419 test $# -gt 3 \
1420 && abort 'Too many arguments provided;
1421 try `svn help propsed` for more info'
1423 path="$3"
1424 test x"$path" = x && path='.'
1425 has_prop "$1" "$3" || {
1426 test $? -eq 2 && return 1 # svn error
1427 # no property found:
1428 abort "'$path' has no property named '$1'."
1431 prop_val=`$SVN propget "$1" "$3"`
1432 test $? -ne 0 && abort "Failed to get the current value of property '$1'."
1434 prop_val=`echo "$prop_val" | sed "$2"`
1435 test $? -ne 0 && abort "Failed to run the sed script '$2'."
1437 $SVN propset "$1" "$prop_val" "$3" >/dev/null \
1438 || abort "Failed to update the property '$1' with value '$prop_val'."
1440 new_prop_val=`$SVN propget "$1" "$3" || echo "$prop_val"`
1441 echo "property '$1' updated on '$path', new value:
1442 $new_prop_val"
1445 # svn_revision [args...]
1446 svn_revision()
1448 svn_revision_info_out=`$SVN info "$@"`
1449 svn_revision_rv=$?
1450 echo "$svn_revision_info_out" | sed '/^Revision: /!d;s///'
1451 return $svn_revision_rv
1454 # svn_ignore [paths]
1455 svn_ignore()
1457 if [ $# -eq 0 ]; then # Simply display ignore-list.
1458 $SVN propget 'svn:ignore'
1459 elif [ $# -eq 1 ]; then
1460 svn_propadd 'svn:ignore' `basename "$1"` `dirname "$1"`
1461 else # Add arguments in svn:ignore.
1462 # This part is a bit tricky:
1463 # For each argument, we find all the other arguments with the same dirname
1464 # $dname and we svn:ignore them all in $dname.
1465 while [ $# -ne 0 ]; do
1466 arg="$1"
1467 dname=`dirname "$1"`
1468 files=`basename "$1"`
1469 shift
1470 j=0; argc=$#
1471 while [ $j -lt $argc ] && [ $# -ne 0 ]; do
1472 this_arg="$1"
1473 shift
1474 this_dname=`dirname "$this_arg"`
1475 this_file=`basename "$this_arg"`
1476 if [ x"$dname" = x"$this_dname" ]; then
1477 files="$files
1478 $this_file"
1479 else
1480 set dummy "$@" "$this_arg"
1481 shift
1483 j=`expr $j + 1`
1484 done
1485 svn_propadd 'svn:ignore' "$files" "$dname"
1486 done
1490 # svn_help
1491 svn_help()
1493 if [ $# -eq 0 ]; then
1494 svn_version
1495 $SVN help
1496 rv=$?
1497 echo '
1498 Additionnal commands provided by svn-wrapper:
1499 automerge (amerge, am)
1500 diffstat (ds)
1501 diffw (dw)
1502 ignore
1503 mail
1504 propadd (padd, pa)
1505 proposal
1506 propsed (psed)
1507 revision (rev)
1508 touch
1509 selfupdate (selfup)
1510 version'
1511 return $rv
1512 else
1513 case $1 in
1514 automerge | auto-merge | amerge | am)
1515 echo 'automerge (amerge, am): FIXME
1516 usage: automerge FIXME
1518 FIXME' # FIXME
1520 commit | ci)
1521 $SVN help commit | sed '/^Valid options:/a\
1522 \ --dry-run : do not commit, simply generate a patch with what\
1523 \ would have been comitted (svn-wrapper extension).
1526 diffstat | ds)
1527 require_diffstat
1528 echo 'diffstat (ds): Display the histogram from svn diff-output.'
1529 $SVN help diff | sed '1d;
1530 s/differences*/histogram/;
1531 2,35 s/diff/diffstat/g'
1533 diffw | dw)
1534 echo "diffw (dw): Display the differences without taking whitespaces\
1535 into account."
1536 $SVN help diff | sed '1d;
1537 2,35 s/diff\([^a-z]\)/diffw\1/g;
1538 /--diff-cmd/,/--no-diff-deleted/d'
1540 ignore)
1541 echo 'ignore: Add some files in the svn:ignore property.
1542 usage: 1. ignore [PATH]
1543 2. ignore FILE [FILES...] [PATH]
1545 1. Display the value of svn:ignore property on [PATH].
1546 2. Add some files in the svn:ignore property of [PATH].
1548 If you want to add directories in the ignore-list, be careful:
1549 svn ignore foo/ bar/
1550 will add "foo/" in the property svn:ignore within the directory bar!
1551 Instead use:
1552 svn ignore foo/ bar/ .
1553 (It'\''s somewhat like with mv)
1555 Valid options:
1556 None.'
1558 mail)
1559 echo 'mail: Resend the mail of a given commit.
1560 usage: mail REV [emails]
1562 REV must have an email file associated in +committed/REV.
1563 REV can also be PREV or HEAD.
1565 By default the mail is sent to same email addresses as during the original
1566 commit unless more arguments are given.'
1568 propadd | padd | pa)
1569 echo 'propadd (padd, pa): Add something in the value of a property.
1570 usage: propadd PROPNAME PROPVAL PATH
1572 PROPVAL will be appended at the end of the property PROPNAME.
1574 Valid options:
1575 None.'
1577 proposal)
1578 echo 'proposal: Alias for: commit --dry-run.
1579 See: svn help commit.'
1581 propsed | psed)
1582 echo 'propsed (psed): Edit a property with sed.
1583 usage: propsed PROPNAME SED-ARGS PATH
1585 eg: svn propsed svn:externals "s/http/https/" .
1587 Valid options:
1588 None.'
1590 revision | rev)
1591 echo 'revision (rev): Display the revision number of a local or remote item.'
1592 $SVN help info | sed '1d;
1593 s/information/revision/g;
1594 s/revision about/the revision of/g;
1595 2,35 s/info/revision/g;
1596 /-xml/d'
1598 touch)
1599 echo 'touch: Touch a file and svn add it.
1600 usage: touch FILE [FILES]...
1602 Valid options:
1603 None.'
1605 selfupdate | selfup | self-update | self-up)
1606 echo 'selfupdate (selfup): Attempt to update svn-wrapper.sh
1607 usage: selfupdate
1609 Valid options:
1610 None.'
1612 version)
1613 echo 'version: Display the version info of svn and svn-wrapper.
1614 usage: version
1616 Valid options:
1617 None.'
1619 *) $SVN help "$@";;
1620 esac
1624 # svn_status [args...]
1625 svn_status()
1627 svn_status_out=`$SVN status "$@"`
1628 svn_status_rv=$?
1629 test x"$svn_status_out" = x && return $svn_status_rv
1630 echo "$svn_status_out" | sed "$sed_svn_st_color"
1631 return $svn_status_rv
1634 # svn_update [args...]
1635 svn_update()
1637 svn_update_out=`$SVN update "$@"`
1638 svn_update_rv=$?
1639 echo "$svn_update_out" | sed "$sed_svn_up_colors"
1640 return $svn_update_rv
1643 # ------------------- #
1644 # `main' starts here. #
1645 # ------------------- #
1647 # Define colors if stdout is a tty.
1648 if test -t 1; then
1649 set_colors
1650 else # stdout isn't a tty => don't print colors.
1651 set_nocolors
1654 # Consider this as a sed function :P.
1655 sed_svn_st_color="
1656 t dummy_sed_1
1657 : dummy_sed_1
1658 s@^?\\(......\\)+@+\\1+@
1659 s@^?\\(......\\)\\(.*/\\)+@+\\1\\2+@
1660 s@^?\\(......\\),@,\\1,@
1661 s@^?\\(......\\)\\(.*/\\),@,\\1\\2,@
1662 s/^\\(.\\)C/\\1${lred}C${std}/
1663 t dummy_sed_2
1664 : dummy_sed_2
1665 s/^?/${lred}?${std}/; t
1666 s/^M/${lgreen}M${std}/; t
1667 s/^A/${lgreen}A${std}/; t
1668 s/^X/${lblue}X${std}/; t
1669 s/^+/${lyellow}+${std}/; t
1670 s/^D/${lyellow}D${std}/; t
1671 s/^,/${lred},${std}/; t
1672 s/^C/${lred}C${std}/; t
1673 s/^I/${purple}I${std}/; t
1674 s/^R/${lblue}R${std}/; t
1675 s/^!/${lred}!${std}/; t
1676 s/^~/${lwhite}~${std}/; t"
1678 sed_svn_up_colors="
1679 t dummy_sed_1
1680 : dummy_sed_1
1682 /^Updated/ t
1683 /^Fetching/ t
1684 /^External/ t
1685 s/^\\(.\\)C/\\1${lred}C${std}/
1686 s/^\\(.\\)U/\\1${lgreen}U${std}/
1687 s/^\\(.\\)D/\\1${lred}D${std}/
1688 t dummy_sed_2
1689 : dummy_sed_2
1690 s/^A/${lgreen}A${std}/; t
1691 s/^U/${lgreen}U${std}/; t
1692 s/^D/${lyellow}D${std}/; t
1693 s/^G/${purple}G${std}/; t
1694 s/^C/${lred}C${std}/; t"
1696 # For dev's:
1697 test "x$1" = x--debug && shift && set -x
1699 case "$1" in
1700 # ------------------------------- #
1701 # Hooks for standard SVN commands #
1702 # ------------------------------- #
1703 commit | ci)
1704 shift
1705 svn_commit "$@"
1707 help | \? | h)
1708 shift
1709 svn_help "$@"
1711 # FIXME: Incomplete
1712 # merge)
1713 # shift
1714 # svn_merge "$@"
1715 # ;;
1716 status | stat | st)
1717 shift
1718 svn_status "$@"
1720 update | up)
1721 shift
1722 svn_update "$@"
1724 # -------------------- #
1725 # Custom SVN commands #
1726 # -------------------- #
1727 automerge | auto-merge | amerge | am)
1728 shift
1729 if [ $# -ne 2 ]; then
1730 abort "automerge: not enough arguments provided;
1731 try 'svn help automerge' for more info"
1733 svn_automerge "$@"
1735 diffstat | ds)
1736 shift
1737 if [ -d .git ]; then
1738 git diff --stat --no-color -C HEAD
1739 else
1740 require_diffstat && $SVN diff --no-diff-deleted "$@" | diffstat
1743 diffw | dw)
1744 shift
1745 svn_diffw "$@"
1747 ignore)
1748 shift
1749 svn_ignore "$@"
1751 mail)
1752 shift
1753 svn_mail "$@"
1755 propadd | padd | pa)
1756 shift
1757 svn_propadd "$@"
1759 proposal)
1760 shift
1761 svn_commit --dry-run "$@"
1763 propsed | psed)
1764 shift
1765 svn_propsed "$@"
1767 revision | rev)
1768 shift
1769 svn_revision "$@"
1771 touch)
1772 shift
1773 touch "$@" && $SVN add "$@"
1775 selfupdate | selfup | self-update | self-up)
1776 shift
1777 selfupdate "$@"
1779 version | -version | --version)
1780 shift
1781 set dummy '--version' "$@"
1782 shift
1783 svn_version
1784 exec $SVN "$@"
1786 *) exec $SVN "$@"
1788 esac