Handle multiple ChangeLogs. Many sanity fixes. Don't svn up before committing:
[svn-wrapper.git] / svn-wrapper.sh
blobb6e319c9d722e130fbadba1b3804f0b60f5cc9ba
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 # * Automatic proxy configuration depending on the IP in ifconfig?
65 # * Customizable behavior/colors via some ~/.<config>rc file (?)
66 # * Improve Auto-Emailing (if possible in sh :|)
70 # Default values (the user can export them to override them).
71 : ${SVN=svn}
72 : ${EDITOR=vi}
73 export EDITOR
74 : ${GPG=gpg}
75 : ${TMPDIR=/tmp}
76 export TMPDIR
77 : ${PAGER=more}
78 export PAGER
79 # Override the locale.
80 LC_ALL='C'
81 export LC_ALL
83 # No args, invoke svn...
84 test $# -lt 1 && exec $SVN
86 version='0.2'
88 # The `main' really starts after the functions definitions.
90 # ---------------- #
91 # Helper functions #
92 # ---------------- #
94 set_colors()
96 red='\e[0;31m'; lred='\e[1;31m'
97 green='\e[0;32m'; lgreen='\e[1;32m'
98 yellow='\e[0;33m'; lyellow='\e[1;33m'
99 blue='\e[0;34m'; lblue='\e[1;34m'
100 purple='\e[0;35m'; lpurple='\e[1;35m'
101 cyan='\e[0;36m'; lcyan='\e[1;36m'
102 grey='\e[0;37m'; lgrey='\e[1;37m'
103 white='\e[0;38m'; lwhite='\e[1;38m'
104 std='\e[m'
107 set_nocolors()
109 red=''; lred=''
110 green=''; lgreen=''
111 yellow=''; lyellow=''
112 blue=''; lblue=''
113 purple=''; lpurple=''
114 cyan=''; lcyan=''
115 grey=''; lgrey=''
116 white=''; lwhite=''
117 std=''
120 # abort err-msg
121 abort()
123 echo "svn-wrapper: ${lred}abort${std}: $@" | sed '1!s/^/ /' >&2
124 exit 1
127 # warn msg
128 warn()
130 echo "svn-wrapper: ${lred}warning${std}: $@" | sed '1!s/^/ /' >&2
133 # notice msg
134 notice()
136 echo "svn-wrapper: ${lyellow}notice${std}: $@" | sed '1!s/^/ /' >&2
139 # yesno question
140 yesno()
142 echo -n "$@ [y/N] "
143 read answer || return 1
144 case "$answer" in
145 y* | Y*) return 0;;
146 *) return 1;;
147 esac
148 return 42 # should never happen...
151 # warn_env env-var
152 warn_env()
154 warn "cannot find the environment variable $1
155 You might consider using \`export $1 <FIXME>\`"
158 # get_unique_file_name file-name
159 get_unique_file_name()
161 test -e "$1" || {
162 echo "$1" && return 0
164 gufn="$1"; i=1
165 while test -e "$gufn.$i"; do
166 i=`expr $i + 1`
167 done
168 echo "$gufn.$i"
171 # ensure_not_empty description value
172 ensure_not_empty()
174 ene_val=`echo "$2" | tr -d ' \t\n'`
175 test x"$ene_val" = x && abort "$1: empty value"
178 # selfupdate
179 selfupdate()
181 my_url='http://www.lrde.epita.fr/~sigoure/svn-wrapper'
182 # You can use https if you feel paranoiac.
184 echo ">>> Fetching svn-wrapper.sh from $my_url/svn-wrapper.sh"
186 # --------------------- #
187 # Fetch the new version #
188 # --------------------- #
190 tmp_me=`get_unique_file_name "$TMPDIR/svn-wrapper.sh"`
191 if (wget --help) >/dev/null 2>/dev/null; then
192 wget --no-check-certificate "$my_url/svn-wrapper.sh" -O "$tmp_me"
193 my_wget='wget'
194 else
195 curl --help >/dev/null 2>/dev/null
196 if [ $? -gt 42 ]; then
197 abort 'Cannot find wget or curl.
198 How can I download any update without them?'
200 my_wget='curl'
201 curl --insecure "$my_url/svn-wrapper.sh" >"$tmp_me"
204 test -r $tmp_me \
205 || abort "Can't find the copy of myself I downloaded in $tmp_me"
207 # ---------------------------------------- #
208 # Compare versions and update if necessary #
209 # ---------------------------------------- #
211 tmp_ver=`sed '/^# $Id[:].*$/!d;
212 s/.*$Id[:] *//;
213 s/ *$ *//;
214 s/[^0-9]*\([0-9][0-9]*\).*/\1/' "$tmp_me"`
215 my_ver=`sed '/^# $Id[:].*$/!d;
216 s/.*$Id[:] *//;
217 s/ *$ *//;
218 s/[^0-9]*\([0-9][0-9]*\).*/\1/' "$0"`
219 if [ $my_ver -lt $tmp_ver ]; then # There IS an update...
220 echo "An update is available, r$tmp_ver (your version is r$my_ver)"
222 # See the ChangeLog?
223 if yesno "Do you want to see the ChangeLog between r$my_ver and r$tmp_ver?"
224 then
225 my_chlog=`get_unique_file_name "$TMPDIR/ChangeLog"`
226 case $my_wget in
227 wget) wget --no-check-certificate "$my_url/ChangeLog" -O "$my_chlog"
229 curl) curl --insecure "$my_url/ChangeLog" >"$my_chlog"
231 *) abort 'Should never be here.'
233 esac
234 sed "/^r$my_ver/q" "$my_chlog" | $PAGER
235 rm -f "$my_chlog"
238 # Wanna see the diff?
239 if yesno "Do you want to see the diff between r$my_ver and r$tmp_ver?"
240 then
241 (diff -uw "$0" "$tmp_me" | diffstat;
242 echo
243 diff -uw "$0" "$tmp_me") | $PAGER
246 # Let's go :)
247 if yesno "Overwrite $0 (r$my_ver) with $tmp_me (r$tmp_ver)?"; then
248 chmod a+x "$tmp_me"
249 mv "$tmp_me" "$0" && exit 0
251 rm -f "$tmp_me"
252 return 1
253 elif [ $my_ver -gt $tmp_ver ]; then
254 echo "Wow, you're more up to date than the master copy :)"
255 echo "Your version is r$my_ver and the master copy is r$tmp_ver."
256 else
257 echo "You're already up to date [r$my_ver] :)"
259 rm -f "$tmp_me"
262 # ------------------------------- #
263 # Hooks for standard SVN commands #
264 # ------------------------------- #
266 # svn_commit [args...]
267 # Here is how the commit process goes:
268 # First we look in the arguments passed to commit:
269 # If there are some files or paths, the user wants to commit these only. In
270 # this case, we must search for ChangeLogs from these paths. We might find
271 # more than one ChangeLog, in this case the user will be prompted to pick up
272 # one.
273 # Otherwise (no path passed in the command line) the user just wants to
274 # commit the current working directory.
275 # In any case, we schedule "ChangeLog" for commit.
276 # Alright now that we know which ChangeLog to use, we look in the ChangeLog's
277 # directory if there is a ",svn-log" file which would mean that a previous
278 # commit didn't finish successfully. If there is such a file, the user is
279 # prompted to know whether they want to resume that commit or simply start a
280 # new one.
281 # When the user wants to resume a commit, the ",svn-log" file is loaded and we
282 # retrieve the value of "$@" that was saved in the file.
283 # Otherwise we build a template ChangeLog entry.
284 # Then we open the template ChangeLog entry with $EDITOR so that the user
285 # fills it properly.
286 # Finally, we commit.
287 svn_commit()
289 here=`pwd`
291 # Check if the user passed some paths to commit explicitly
292 # because in this case we must add the ChangeLog to the commit and search
293 # the ChangeLog from the dirname of that file.
294 i=0; search_from=''; add_changelog=no
295 while [ $i -lt $# ]; do
296 arg="$1"
297 # If the argument is a valid path: add the ChangeLog in the list of
298 # files to commit
299 if test -e "$arg"; then
300 add_changelog=yes
301 if test -d "$arg"; then
302 search_from_add="$arg"
303 else
304 search_from_add=`dirname "$arg"`
306 search_from="$search_from:$search_from_add"
308 shift
309 set dummy "$@" "$arg"
310 shift
311 i=`expr $i + 1`
312 done
313 if [ $add_changelog = no ]; then
314 # There is no path/file in the command line: the user wants to commit the
315 # current directory. Make it explicit now:
316 set dummy "$@" '.'
317 shift
319 search_from=`echo "$search_from" | sed 's/^://; s/^$/./'`
321 # ----------------- #
322 # Find ChangeLog(s) #
323 # ----------------- #
325 nb_chlogs=0; change_log_dirs=''
326 save_IFS=$IFS; IFS=':'
327 for dir in $search_from; do
328 IFS=$save_IFS
329 test -z "$dir" && dir='.'
330 # First: come back to the original place
331 cd "$here" || abort "Cannot cd to $here"
332 cd "$dir" || continue # Then: Enter $dir (which can be a relative path)
333 found=0
334 while [ $found -eq 0 ]; do
335 this_chlog_dir=`pwd -P`
336 if [ -f ./ChangeLog ]; then
337 found=1
338 nb_chlogs=`expr $nb_chlogs + 1`
339 change_log_dirs="$change_log_dirs:$this_chlog_dir"
340 else
341 cd ..
343 # Stop searching when in / ... hmz :P
344 test x"$this_chlog_dir" = x/ && break
345 done # end while: did we find a ChangeLog
346 done # end for: find ChangeLogs in $search_from
347 change_log_dirs=`echo "$change_log_dirs" | sed 's/^://'`
349 # Did we find a ChangeLog? More than one?
350 if [ $nb_chlogs -eq 0 ]; then
351 if yesno 'svn-wrapper: Error: Cannot find a ChangeLog file!
352 You might want to create an empty one (eg: `touch ChangeLog` where appropriate)
353 Do you want to proceed without using a ChangeLog?'; then
354 cd "$here"
355 $SVN commit "$@" && $SVN update
356 return $?
357 else
358 return 1
360 elif [ $nb_chlogs -gt 1 ]; then
361 notice "$nb_chlogs ChangeLogs were found, pick up one:"
363 IFS=':'; i=0
364 for a_chlog_dir in $change_log_dirs; do
365 i=`expr $i + 1`
366 echo "$i. $a_chlog_dir/ChangeLog"
367 done
368 echo -n "Which ChangeLog do you want to use? [1-$i] "
369 read chlog_no || abort "Cannot read answer on stdin."
371 case "$chlog_no" in
372 *[^0-9]*) abort "Invalid ChangeLog number: $chlog_no"
373 esac
374 test "$chlog_no" -le $i || abort "Invalid ChangeLog number: $chlog_no
375 max value was: $i"
376 test "$chlog_no" -ge 1 || abort "Invalid ChangeLog number: $chlog_no
377 min value was: 1"
378 change_log_dir=`echo "$change_log_dirs" | tr ':' '\n' | sed "${chlog_no}!d"`
379 else # Only one ChangeLog found
380 change_log_dir=$change_log_dirs
381 notice "using $change_log_dir/ChangeLog"
384 test -f "$change_log_dir/ChangeLog" \
385 || abort "No such file or directory: $change_log_dir/ChangeLog"
387 # Now we can safely schedule the ChangeLog for the commit.
388 set dummy "$@" "$change_log_dir/ChangeLog"
389 shift
391 # Warn for files that are not added in the repos.
392 conflicts=`$SVN status | sed '/^\?......[^,+]/!d'`
393 if test x"$conflicts" != x; then
394 warn "make sure you don't want to \`svn add'
395 any of the following files before committing:"
396 echo "$conflicts" | sed "$sed_svn_st_color"
397 echo -n "Type [ENTER] to continue :)" && read chiche_is_gay
400 # Detect unresolved conflicts / missing files.
401 conflicts=`$SVN status | sed '/^[C!]/!d'`
402 test x"$conflicts" != x && abort "there are unresolved conflicts (\`C')
403 and/or missing files (\`!'):
404 $conflicts"
406 repos_root=`$SVN info | sed '/^Repository Root/!d;s/^Repository Root: //'`
407 # It looks like svn <1.3 didn't display a "Repository Root" entry.
408 test x"$repos_root" = x && \
409 repos_root=`$SVN info | sed '/^URL/!d;s/^URL: //'`
410 cd "$here"
412 YYYY=`date '+%Y'`
413 MM=`date '+%m'`
414 DD=`date '+%d'`
415 REV=`$SVN info "$repos_root" | sed '/^Revision: /!d;s///'`
416 test x"$REV" = x && REV=`$SVN info | sed '/^Last Changed Rev: /!d;s///'`
417 test x"$REV" = x && warn 'Cannot detect the current revision.'
418 REV=`expr $REV + 1`
420 # VCS-compat: handle user option 'new_user'
421 new_user='yes'
422 grep '^new_user: false' ~/.vcs >/dev/null 2>/dev/null && new_user='no'
424 tmp_log="$change_log_dir/,svn-log"
425 if [ -f "$tmp_log" ] && yesno "It looks like the last commit did not\
426 terminate successfully.
427 Would you like to resume it?"; then
428 echo 'Resuming ...'
429 internal_tags=`sed '/^--- Internal stuff, DO NOT change please ---$/,$!d' \
430 "$tmp_log"`
431 saved_args=`echo "$internal_tags" | sed '/^args: */!d;s///'`
432 if [ x"$saved_args" != x ]; then
433 if [ x"$@" != x ] && [ x"$saved_args" != x"$@" ]; then
434 warn "overriding arguments:
435 you invoked $0 with the following arguments: $@
436 they have been replaced by these: $saved_args"
437 set dummy "$saved_args"
438 shift
439 else
440 notice "setting the following arguments: $saved_args"
441 set dummy "$saved_args"
442 shift
445 # Ignore white spaces. Can't svn diff -x -w: svn 1.4 only.
446 svn_diff=`$SVN diff --diff-cmd diff -x -uw "$@"`
447 test x"$svn_diff" = x && svn_diff=`$SVN diff "$@"`
448 svn_diff_stat=`echo "$svn_diff" | diffstat`
450 else # Build the template message.
452 # ------------------------------------ #
453 # Gather info for the template message #
454 # ------------------------------------ #
456 projname=`$SVN propget project "$change_log_dir"`
457 # Try to be VCS-compatible and find a project name in a *.rb.
458 if [ x"$projname" = x ] && [ -d "$change_log_dir/vcs" ]; then
459 projname=`sed '/common_commit/!d;s/.*"\(.*\)<%= rev.*/\1/' \
460 "$change_log_dir"/vcs/*.rb`
461 test x"$projname" != x && test x$new_user = xyes \
462 && notice "VCS-compat: found project name: $projname
463 in " "$change_log_dir"/vcs/*.rb
465 test x"$projname" != x && projname=`echo "$projname" | sed '/[^ ]$/s/$/ /'`
467 mailto=`$SVN propget mailto "$change_log_dir"`
468 if [ x"$mailto" = x ]; then
469 test x$new_user = xyes \
470 && warn "no svn property mailto found in $change_log_dir
471 You might want to set default email adresses using:
472 svn propset mailto 'somebody@mail.com, foobar@example.com'\
473 $change_log_dir" >&2
474 # Try to be VCS-compatible and find a list of mails in a *.rb.
475 if [ -d "$change_log_dir/vcs" ]; then
476 mailto=`grep '.@.*\..*' "$change_log_dir"/vcs/*.rb \
477 | tr '\n' ' ' \
478 | sed 's/^.*[["]\([^["]*@[^]"]*\)[]"].*$/\1/' | xargs`
479 test x"$mailto" != x && test x$new_user = xyes \
480 && notice "VCS-compat: found mailto: $mailto
481 in " "$change_log_dir"/vcs/*.rb
482 fi # end VCS compat
483 fi # end guess mailto
485 # Ensure that emails are comma-separated.
486 mailto=`echo "$mailto" | sed 's/[ ;]/,/g' | tr -s ',' | sed 's/,/, /g'`
487 test x"$FULLNAME" = x && FULLNAME='Type Your Name Here' \
488 && warn_env FULLNAME
489 test x"$EMAIL" = x && EMAIL='your.mail.here@FIXME.com' && warn_env EMAIL
491 # --ignore-externals appeared after svn 1.1.1
492 my_svn_st=`$SVN status --ignore-externals "$@" \
493 || $SVN status "$@" | sed '/^Performing status on external/ {
497 # Files to put in the ChangeLog entry.
498 change_log_files=`echo "$my_svn_st" | sed '
499 t dummy_sed_1
500 : dummy_sed_1
501 s/^M......\(.*\)$/ * \1: ./; t
502 s/^A......\(.*\)$/ * \1: New./; t
503 s/^D......\(.*\)$/ * \1: Remove./; t
506 if [ x"$change_log_files" = x ]; then
507 yesno 'Nothing to commit, continue anyway?' || return 1
510 svn_diff=`$SVN diff --diff-cmd diff -x -uw "$@"`
511 svn_diff_stat=`echo "$svn_diff" | diffstat`
513 # Get any older svn-log out of the way.
514 test -f "$tmp_log" && mv "$tmp_log" `get_unique_file_name "$tmp_log"`
515 # If we can't get an older svn-log out of the way, find a new name...
516 test -f "$tmp_log" && tmp_log=`get_unique_file_name "$tmp_log"`
517 if [ x$new_user = no ]; then
518 commit_instructions='
519 Instructions:
520 - Fill the ChangeLog entry.
521 - If you feel like, write a comment in the "Comment:" section.
522 This comment will only appear in the email, not in the ChangeLog.
523 By default only the location of the repository is in the comment.
524 - Some tags will be replaced. Tags are of the form: <TAG>. Unknown
525 tags will be left unchanged.
526 - Your ChangeLog entry will be used as commit message for svn.'
527 else
528 commit_instructions=''
530 echo "\
531 --You must fill this file correctly to continue-- -*- vcs -*-
532 Title:
533 Subject: ${projname}r<REV>: <TITLE>
534 From: $FULLNAME <$EMAIL>
535 To: $mailto
537 Comment:
538 Repository: $repos_root
540 ChangeLog:
542 <YYYY>-<MM>-<DD> $FULLNAME <$EMAIL>
544 <TITLE>
545 $change_log_files
547 --This line, and those below, will be ignored--
548 $commit_instructions
549 --Preview of the message that will be sent--
551 Repository: $repos_root
552 Next revision: $REV
553 Your comments (if any) will appear here.
555 ChangeLog:
556 $YYYY-$MM-$DD $FULLNAME <$EMAIL>
558 Your ChangeLog entry will appear here.
560 $svn_diff_stat
562 $svn_diff" >"$tmp_log"
564 echo "
565 --- Internal stuff, DO NOT change please ---
566 args: $@" >>"$tmp_log"
568 fi # end: if svn-log; then resume? else create template
569 $EDITOR "$tmp_log"
571 # ------------------ #
572 # Re-"parse" the log #
573 # ------------------ #
575 # hmz this section is a bit messy...
576 sed_escape='s/@/\\@/' # helper string... !@#$%* escaping \\\\\\...
577 sed_eval_tags="s/<MM>/$MM/g; s/<DD>/$DD/g; s/<YYYY>/$YYYY/g; s/<REV>/$REV/g"
578 full_log=`sed '/^--This line, and those below, will be ignored--$/,$d;
579 /^--You must fill this/d' "$tmp_log"`
580 chlog_entry=`echo "$full_log" | sed '/^ChangeLog:$/,$!d; //d'`
581 ensure_not_empty 'ChangeLog entry' "$chlog_entry"
582 full_log=`echo "$full_log" | sed '/^ChangeLog:$/,$d'`
583 mail_comment=`echo "$full_log" | sed '/^Comment:$/,$!d; //d'`
584 full_log=`echo "$full_log" | sed '/^Comment:$/,$d'`
585 mail_title=`echo "$full_log" | sed '/^Title: */!d;s///'`
586 ensure_not_empty 'commit title' "$mail_title"
587 mail_title=`echo "$mail_title" | sed "$sed_eval_tags; $sed_escape"`
588 sed_eval_tags="$sed_eval_tags; s@<TITLE>@$mail_title@g"
589 mail_comment=`echo "$mail_comment" | sed "$sed_eval_tags"`
590 chlog_entry=`echo "$chlog_entry" | sed "$sed_eval_tags; 1{
591 /^ *$/d
593 mail_subject=`echo "$full_log" | sed '/^Subject: */!d;s///'`
594 ensure_not_empty 'mail subject' "$mail_subject"
595 mail_subject=`echo "$mail_subject" | sed "$sed_eval_tags"`
596 mail_to=`echo "$full_log" | sed '/^To:/!d'`
597 send_a_mail=yes
598 if test x"$mail_to" = x; then
599 send_a_mail=no
600 else
601 mail_to=`echo "$mail_to" | sed 's/^To: *//'`
602 ensure_not_empty '"To:" field of the mail' "$mail_to"
604 mail_from=`echo "$full_log" | sed '/^From: */!d;s///'`
605 ensure_not_empty '"From:" field of the mail' "$mail_from"
607 # Check whether the user passed -m | --message
608 i=0; has_message=0
609 while [ $i -lt $# ]; do
610 arg="$1"
611 # This is not really a reliable way of knowing whether -m | --message was
612 # passed but hum... Let's assume it'll do :s
613 test x"$arg" = 'x-m' && has_message=1
614 test x"$arg" = 'x--message' && has_message=1
615 shift
616 set dummy "$@" "$arg"
617 shift
618 i=`expr $i + 1`
619 done
620 if [ $has_message -eq 0 ]; then
621 my_message=`echo "$chlog_entry" | grep -v "^$YYYY-$MM-$DD" | sed '1,2 {
622 /^ *$/d
624 set dummy --message "$my_message" "$@"
625 shift
626 else
627 notice 'you are overriding the commit message.'
630 # Are you sure?
631 yesno 'Are you sure you want to commit?' || return 1
633 # Add the ChangeLog entry
634 old_chlog=`get_unique_file_name "$change_log_dir/ChangeLog.old"`
635 mv "$change_log_dir/ChangeLog" "$old_chlog" || \
636 abort 'Could not backup ChangeLog'
637 trap "echo SIGINT; mv \"$old_chlog\" \"$change_log_dir/ChangeLog\"" SIGINT
638 echo "$chlog_entry" >"$change_log_dir/ChangeLog"
639 echo >>"$change_log_dir/ChangeLog"
640 cat "$old_chlog" >>"$change_log_dir/ChangeLog"
642 # --Commit-- finally! :D
643 $SVN commit "$@" || {
644 svn_commit_rv=$?
645 mv "$old_chlog" "$change_log_dir/ChangeLog"
646 abort "Commit failed, $SVN returned $svn_commit_rv"
649 # In the end, perform an svn up to update externals
650 $SVN update "$change_log_dir"
651 svn_commit_rv=$?
653 # Send the mail
654 if test x$send_a_mail = xyes; then
655 mail_file=`get_unique_file_name "$change_log_dir/+mail"`
656 echo "\
657 From: $mail_from
658 To: $mail_to
659 Subject: $mail_subject
661 $mail_comment
663 ChangeLog:
664 $chlog_entry
666 $svn_diff_stat
668 $svn_diff" | sed 's/^\.$/ ./' >"$mail_file"
669 # We change lines with only a `.' because they could mean "end-of-mail"
671 trap 'echo SIGINT; exec < /dev/null' SIGINT
672 test -f ~/.signature && echo '-- ' >>"$mail_file" && \
673 cat ~/.signature >>"$mail_file"
674 # VCS-compat: handle user option 'sign'.
675 if (grep '^sign: false' ~/.vcs) >/dev/null 2>/dev/null; then :; else
676 ($GPG -h) >/dev/null 2>/dev/null
677 gpg_rv=$?
678 if [ -e ~/.gnupg ] || [ -e ~/.gpg ] || [ -e ~/.pgp ] && [ $gpg_rv -lt 42 ]
679 then
680 yesno "Sign the mail using $GPG ?" && \
681 $GPG --clearsign "$mail_file"
682 test -f "$mail_file.asc" && mv "$mail_file.asc" "$mail_file"
685 # FIXME: This looks a bit fragile. What if $mail_to = -s foo ? Will it
686 # override the previous -s option?
687 cat "$mail_file" | mail -s "$mail_subject" "$mail_to"
688 fi # end do we have to send a mail?
689 rm -f "$tmp_log"
690 rm -f "$old_chlog"
691 save_mail_file=`echo "$mail_file" | sed 's/+//'`
692 if [ -d "$change_log_dir/vcs" ] \
693 || [ -d "$change_log_dir/+committed" ]
694 then
695 mkdir -p "$change_log_dir/+committed/$REV" \
696 && mv "$mail_file" "$change_log_dir/+committed/$REV/mail"
698 return $svn_commit_rv
701 # svn_version
702 svn_version()
704 echo "Using svn-wrapper v$version (C) SIGOURE Benoit [GPL]"
705 sed '/^# $Id[:].*$/!d;s/.*$Id[:] *//;s/ *$ *//;s/ \([0-9][0-9]*\)/ (r\1)/' "$0"
708 # has_prop prop-name [path]
709 # return value: 0 -> path has the property prop-name set.
710 # 1 -> path has no property prop-name.
711 # 2 -> svn error.
712 has_prop()
714 hp_plist=`$SVN proplist "$2"`
715 test $? -ne 0 && return 2
716 hp_res=`echo "$hp_plist" | sed "/^ *$1\$/!d"`
717 test x"$hp_res" = x && return 1
718 return 0
721 # svn_propadd prop-name prop-val [path]
722 svn_propadd()
724 test $# -lt 2 \
725 && abort 'Not enough arguments provided; try `svn help propadd` for more info'
726 test $# -gt 3 \
727 && abort 'Too many arguments provided; try `svn help propadd` for more info'
729 path="$3"
730 test x"$path" = x && path='.' && set dummy "$@" '.' && shift
731 has_prop "$1" "$3" || {
732 test $? -eq 2 && return 1 # svn error
733 # no property found:
734 yesno "'$path' has no property named '$1', do you want to add it?" \
735 && $SVN propset "$@"
736 return $?
739 current_prop_val=`$SVN propget "$1" "$3"`
740 test $? -ne 0 && abort "Failed to get the current value of property '$1'."
742 $SVN propset "$1" "$current_prop_val
743 $2" "$3" >/dev/null || abort "Failed to add '$3' in the property '$1'."
745 current_prop_val=`$SVN propget "$1" "$3" || echo "$current_prop_val
746 $2"`
747 echo "property '$1' updated on '$path', new value:
748 $current_prop_val"
751 # svn_propsed prop-name sed-script [path]
752 svn_propsed()
754 test $# -lt 2 \
755 && abort 'Not enough arguments provided; try `svn help propsed` for more info'
756 test $# -gt 3 \
757 && abort 'Too many arguments provided; try `svn help propsed` for more info'
759 path="$3"
760 test x"$path" = x && path='.'
761 has_prop "$1" "$3" || {
762 test $? -eq 2 && return 1 # svn error
763 # no property found:
764 abort "'$path' has no property named '$1'."
767 prop_val=`$SVN propget "$1" "$3"`
768 test $? -ne 0 && abort "Failed to get the current value of property '$1'."
770 prop_val=`echo "$prop_val" | sed "$2"`
771 test $? -ne 0 && abort "Failed to run the sed script '$2'."
773 $SVN propset "$1" "$prop_val" "$3" >/dev/null \
774 || abort "Failed to update the property '$1' with value '$prop_val'."
776 new_prop_val=`$SVN propget "$1" "$3" || echo "$prop_val"`
777 echo "property '$1' updated on '$path', new value:
778 $new_prop_val"
781 # svn_ignore [paths]
782 svn_ignore()
784 if [ $# -eq 0 ]; then # Simply display ignore-list.
785 $SVN propget 'svn:ignore'
786 elif [ $# -eq 1 ]; then
787 if [ -d "$1" ]; then # Display ignore-list for $1
788 $SVN propget 'svn:ignore' "$1"
789 elif [ x`dirname "$1"` != x. ]; then
790 # Add `basename $1` in the ignore-list of `dirname $1`
791 i_basename=`basename "$1"`
792 i_dirname=`dirname "$1"`
793 svn_propadd 'svn:ignore' "$i_basename" "$i_dirname"
794 else # Add $1 in ignore-list of `.'.
795 svn_propadd 'svn:ignore' "$1"
797 else # Add arguments in svn:ignore.
798 i=0; last_arg=''
799 while [ $i -lt $# ]; do
800 arg="$1"
801 shift
802 if [ $i -eq $# ]; then
803 last_arg="$arg"
804 else
805 set dummy "$@" "$arg"
806 shift
808 i=`expr $i + 1`
809 done
810 i_files=`echo "$*" | tr -s ' ' '\n'`
811 if [ -d "$last_arg" ]; then # Add in ignore-list of $last_arg
812 svn_propadd 'svn:ignore' "$i_files" "$last_arg"
813 else # Add in ignore-list of `.'
814 svn_propadd 'svn:ignore' "$i_files
815 $last_arg"
820 # svn_help
821 svn_help()
823 if [ $# -eq 0 ]; then
824 svn_version
825 $SVN help
826 rv=$?
827 echo '
828 Additionnal commands provided by svn-wrapper:
829 diffstat (ds)
830 ignore
831 propadd (padd, pa)
832 propsed (psed)
833 revision (rev)
834 touch
835 selfupdate (selfup)
836 version'
837 return $rv
838 else
839 case $1 in
840 diffstat | ds)
841 echo 'diffstat (ds): Display the histogram from svn diff-output.'
842 $SVN help diff | sed '1d;
843 s/differences*/histogram/;
844 2,35 s/diff/diffstat/g'
846 ignore)
847 echo 'ignore: Add some files in the svn:ignore property.
848 usage: 1. ignore [PATH]
849 2. ignore FILE [FILES...] [PATH]
851 1. Display the value of svn:ignore property on [PATH].
852 2. Add some files in the svn:ignore property of [PATH].
854 If you want to add directories in the ignore-list, be careful:
855 svn ignore foo/ bar/
856 will add "foo/" in the property svn:ignore within the directory bar!
857 Instead use:
858 svn ignore foo/ bar/ .
859 (It'\''s somewhat like with mv)
861 Valid options:
862 None.'
864 propadd | padd | pa)
865 echo 'propadd (padd, pa): Add something in the value of a property.
866 usage: propadd PROPNAME PROPVAL PATH
868 PROPVAL will be appended at the end of the property PROPNAME.
870 Valid options:
871 None.'
873 propsed | psed)
874 echo 'propsed (psed): Edit a property with sed.
875 usage: propsed PROPNAME SED-ARGS PATH
877 eg: svn propsed svn:externals "s/http/https/" .
879 Valid options:
880 None.'
882 revision | rev)
883 echo 'revision (rev): Display the revision number of a local or remote item.'
884 $SVN help info | sed '1d;
885 s/information/revision/g;
886 s/revision about/the revision of/g;
887 2,35 s/info/revision/g;
888 /-xml/d'
890 touch)
891 echo 'touch: Touch a file and svn add it.
892 usage: touch FILE [FILES]...
894 Valid options:
895 None.'
897 selfupdate | selfup | self-update | self-up)
898 echo 'selfupdate (selfup): Attempt to update svn-wrapper.sh
899 usage: selfupdate
901 Valid options:
902 None.'
904 version)
905 echo 'version: Display the version info of svn and svn-wrapper.
906 usage: version
908 Valid options:
909 None.'
911 *) $SVN help "$@";;
912 esac
916 # svn_status [args...]
917 svn_status()
919 svn_status_out=`$SVN status "$@"`
920 svn_status_rv=$?
921 echo "$svn_status_out" | sed "$sed_svn_st_color"
922 return $svn_status_rv
925 # svn_update [args...]
926 svn_update()
928 svn_update_out=`$SVN update "$@"`
929 svn_update_rv=$?
930 echo "$svn_update_out" | sed "$sed_svn_up_colors"
931 return $svn_update_rv
934 # ------------------- #
935 # `main' starts here. #
936 # ------------------- #
938 # Sanity checks.
939 if (echo | diffstat) >/dev/null 2>/dev/null; then :; else
940 warn 'diffstat is not installed on your system or not in your PATH.'
941 test -f /etc/debian_version \
942 && notice 'you might want to `apt-get install diffstat`.'
944 if (echo | mail) >/dev/null 2>/dev/null; then :; else
945 if [ $? -gt 100 ]; then
946 warn 'mail is not installed on your system or not in your PATH.'
947 test -f /etc/debian_version \
948 && notice 'you might want to:
949 # apt-get install mailx
950 # dpkg-reconfigure exim'
954 # Define colors if stdout is a tty.
955 if test -t 1; then
956 set_colors
957 else # stdout isn't a tty => don't print colors.
958 set_nocolors
961 # Considere this as a sed function :P.
962 sed_svn_st_color="
963 t dummy_sed_1
964 : dummy_sed_1
965 s@^?\\(......\\)\\([^+]*/\\)*+@+\\1\\2+@
966 s@^?\\(......\\)\\([^,]*/\\)*,@,\\1\\2,@
967 s/^\\(.\\)C/\\1${lred}C${std}/
968 t dummy_sed_2
969 : dummy_sed_2
970 s/^?/${lred}?${std}/; t
971 s/^M/${lgreen}M${std}/; t
972 s/^A/${lgreen}A${std}/; t
973 s/^X/${lblue}X${std}/; t
974 s/^+/${lyellow}+${std}/; t
975 s/^D/${lyellow}D${std}/; t
976 s/^,/${lred},${std}/; t
977 s/^C/${lred}C${std}/; t
978 s/^I/${purple}I${std}/; t
979 s/^R/${lblue}R${std}/; t
980 s/^!/${lred}!${std}/; t
981 s/^~/${lwhite}~${std}/; t"
983 sed_svn_up_colors="
984 t dummy_sed_1
985 : dummy_sed_1
987 /^Updated/ t
988 /^Fetching/ t
989 /^External/ t
990 s/^\\(.\\)C/\\1${lred}C${std}/
991 s/^\\(.\\)U/\\1${lgreen}U${std}/
992 s/^\\(.\\)D/\\1${lred}D${std}/
993 t dummy_sed_2
994 : dummy_sed_2
995 s/^A/${lgreen}A${std}/; t
996 s/^U/${lgreen}U${std}/; t
997 s/^D/${lyellow}D${std}/; t
998 s/^G/${purple}G${std}/; t
999 s/^C/${lred}C${std}/; t"
1001 # For dev's:
1002 test "x$1" = x--debug && shift && set -x
1004 case "$1" in
1005 # ------------------------------- #
1006 # Hooks for standard SVN commands #
1007 # ------------------------------- #
1008 commit | ci)
1009 shift
1010 svn_commit "$@"
1012 help | \? | h)
1013 shift
1014 svn_help "$@"
1016 status | stat | st)
1017 shift
1018 svn_status "$@"
1020 update | up)
1021 shift
1022 svn_update "$@"
1024 # -------------------- #
1025 # Custom SVN commands #
1026 # -------------------- #
1027 diffstat | ds)
1028 shift
1029 $SVN diff "$@" | diffstat
1031 ignore)
1032 shift
1033 svn_ignore "$@"
1035 propadd | padd | pa)
1036 shift
1037 svn_propadd "$@"
1039 propsed | psed)
1040 shift
1041 svn_propsed "$@"
1043 revision | rev)
1044 shift
1045 $SVN info "$@" | sed '/^Revision/!d;s/^Revision: //'
1047 touch)
1048 shift
1049 touch "$@" && svn add "$@"
1051 selfupdate | selfup | self-update | self-up)
1052 shift
1053 selfupdate "$@"
1055 version | -version | --version)
1056 shift
1057 set dummy '--version' "$@"
1058 shift
1059 svn_version
1060 exec $SVN "$@"
1062 *) exec $SVN "$@"
1064 esac