Update the TODO list.
[svn-wrapper.git] / svn-wrapper.sh
blob9b2e7a2a01febd67feb8d31093ad5332baaba38b
1 #! /bin/sh
2 # Simple wrapper around SVN and Git featuring auto-ChangeLog entries and
3 # emailing.
4 # $Id$
5 # Copyright (C) 2006, 2007 Benoit Sigoure.
7 # This program is free software: you can redistribute it and/or modify
8 # it under the terms of the GNU General Public License as published by
9 # the Free Software Foundation, either version 3 of the License, or
10 # (at your option) any later version.
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU General Public License for more details.
17 # You should have received a copy of the GNU General Public License
18 # along with this program. If not, see <http://www.gnu.org/licenses/>.
20 # Quick install: alias svn=path/to/svn-wrapper.sh -- that's all.
22 # ------ #
23 # README #
24 # ------ #
26 # This script is a wrapper around the SVN and Git command-line clients for
27 # UNIX. It has been designed mainly to automagically generate GNU-style
28 # ChangeLog entries when committing and mail them along with a diff and an
29 # optional comment from the author to a list of persons or a mailing list.
30 # Although this script was originally written for SVN (hence the name), it
31 # also works with Git and tries to detect whether the current repository is a
32 # Git or SVN repository.
33 # This script has been written to be as much portable as possible and cover as
34 # many use-case as reasonably possible.
36 # HOWEVER, there will be bugs, there will be cases in which the script doesn't
37 # wrap properly the svn-cli, etc. In this case, you can try to mail me at
38 # <tsuna at lrde dot epita dot fr>. Include the revision of the svn-wrapper
39 # you're using, and full description of what's wrong etc. so I can reproduce
40 # your problem.
42 # If you feel like, you can try to fix/enhance the script yourself. It only
43 # requires some basic Shell-scripting skills. Knowing sed will prove useful :)
45 # ------------- #
46 # DOCUMENTATION #
47 # ------------- #
49 # If you're simply looking for the usage, run `svn-wrapper.sh help' (or
50 # `svn help' if you aliased `svn' on svn-wrapper.sh) as usual.
52 # This script is (hopefully) portable, widely commented and self-contained. Do
53 # not hesitate to hack it. It might look rather long (because it does a lot of
54 # things :P) but you should be able to easily locate which part of the code
55 # you're looking for.
57 # The script begins by defining several functions. Then it really starts where
58 # the comment "# `main' starts here. #" is placed.
59 # Some svn commands are hooked (eg, `svn st' displays colors). Hooks and
60 # extra commands are defined in functions named `svn_<command-name>'.
62 # ---- #
63 # TODO #
64 # ---- #
66 # * Write a real testsuite. :/
67 # * Automatic proxy configuration depending on the IP in ifconfig?
68 # * Customizable behavior/colors via some ~/.<config>rc file (?)
69 # * Handle things such as svn ci --force foobar.
71 # * svn automerge + automatic fill in branches/README.branches.
72 # => won't do (SVN is too borken WRT merges, I use Git now so I don't feel
73 # like dealing with this sort of SVN issue)
74 # * Automatically recognize svn cp and svn mv instead of writing "New" and
75 # "Remove" in the template ChangeLog entry (hard). Pair the and new/remove
76 # to prepare a good ChangeLog entry (move to X, copy from X) [even harder].
77 # => won't do (Git does this fine, can't be bothered to deal with this for
78 # SVN)
82 # Default values (the user can export them to override them).
83 use_changelog=:
84 git_mode=false
85 case $SVN in
86 git*) git_mode=:;;
87 '')
88 if [ -d .git ] || [ -d ../.git ]; then
89 SVN=git
90 git_mode=:
91 else
92 SVN=svn
95 esac
97 : ${EDITOR=missing}
98 export EDITOR
99 : ${GPG=gpg}
100 : ${TMPDIR=/tmp}
101 export TMPDIR
102 : ${PAGER=missing}
103 export PAGER
104 # Override the locale.
105 LC_ALL='C'
106 export LC_ALL
107 : ${AWK=missing}
108 : ${DIFF=missing}
109 : ${COLORDIFF=missing}
111 # Signal number for traps (using plain signal names is not portable and breaks
112 # on recent Debians that use ash as their default shell).
113 SIGINT=2
115 me=$0
116 bme=`basename "$0"`
118 # Pitfall: some users might be tempted to export SVN=svn-wrapper.sh for some
119 # reason. This is just *wrong*. The following is an attempt to save them from
120 # some troubles.
121 if [ x`basename "$SVN"` = x"$bme" ]; then
122 echo "warning: setting SVN to $bme is wrong"
123 SVN='svn'
126 # This code comes (mostly) from Autoconf.
127 # The user is always right.
128 if test "${PATH_SEPARATOR+set}" != set; then
129 PATH_SEPARATOR=:
130 (PATH='/bin;/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 && {
131 (PATH='/bin:/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 ||
132 PATH_SEPARATOR=';'
136 version='0.4'
137 full_rev='$Id$'
138 # Get the first 6 digits (without forking)
139 revision=${full_rev#'$'Id': '}
140 revision=${revision%??????????????????????????????????' $'}
142 # The `main' really starts after the functions definitions.
144 # ---------------- #
145 # Helper functions #
146 # ---------------- #
148 set_colors()
150 red='\e[0;31m'; lred='\e[1;31m'
151 green='\e[0;32m'; lgreen='\e[1;32m'
152 yellow='\e[0;33m'; lyellow='\e[1;33m'
153 blue='\e[0;34m'; lblue='\e[1;34m'
154 purple='\e[0;35m'; lpurple='\e[1;35m'
155 cyan='\e[0;36m'; lcyan='\e[1;36m'
156 grey='\e[0;37m'; lgrey='\e[1;37m'
157 white='\e[0;38m'; lwhite='\e[1;38m'
158 std='\e[m'
161 set_nocolors()
163 red=; lred=
164 green=; lgreen=
165 yellow=; lyellow=
166 blue=; lblue=
167 purple=; lpurple=
168 cyan=; lcyan=
169 grey=; lgrey=
170 white=; lwhite=
171 std=
174 # abort err-msg
175 abort()
177 echo "svn-wrapper: ${lred}abort${std}: $@" \
178 | sed '1!s/^[ ]*/ /' >&2
179 exit 1
182 # warn msg
183 warn()
185 echo "svn-wrapper: ${lred}warning${std}: $@" \
186 | sed '1!s/^[ ]*/ /' >&2
189 # notice msg
190 notice()
192 echo "svn-wrapper: ${lyellow}notice${std}: $@" \
193 | sed '1!s/^[ ]*/ /' >&2
196 # yesno question
197 yesno()
199 echo -n "$@ [y/N] "
200 read answer || return 1
201 case $answer in
202 y* | Y*) return 0;;
203 *) return 1;;
204 esac
205 return 42 # should never happen...
208 # yesnewproceed what
209 # returns true if `yes' or `proceed', false if `new'.
210 # the answer is stored in $yesnoproceed_res which is /yes|new|proceed/
211 yesnewproceed()
213 echo -n "$@ [(y)es/(p)roceed/(u)p+proceed/(N)ew] "
214 read answer || return 1
215 case $answer in
216 y* | Y*) yesnoproceed_res=yes; return 0;;
217 p* | P*) yesnoproceed_res=proceed; return 0;;
218 u* | U*) yesnoproceed_res=upproceed; return 0;;
219 *) yesnoproceed_res=new; return 1;;
220 esac
221 return 42 # should never happen...
224 # warn_env env-var
225 warn_env()
227 warn "cannot find the environment variable $1
228 You might consider using \`export $1='<FIXME>'\`"
231 # get_unique_file_name file-name
232 get_unique_file_name()
234 test -e "$1" || {
235 echo "$1" && return 0
237 gufn=$1; i=1
238 while test -e "$gufn.$i"; do
239 i=$((i + 1))
240 done
241 echo "$gufn.$i"
244 # ensure_not_empty description value
245 ensure_not_empty()
247 ene_val=`echo "$2" | tr -d ' \t\n'`
248 test x"$ene_val" = x && abort "$1: empty value"
251 # find_prog prog-name
252 # return true if prog-name is in the PATH
253 # echo the full path to prog-name on stdout.
254 # Based on a code from texi2dvi
255 find_prog()
257 save_IFS=$IFS
258 IFS=$PATH_SEPARATOR
259 for dir in $PATH; do
260 IFS=$save_IFS
261 test x"$dir" = x && continue
262 # The basic test for an executable is `test -f $f && test -x $f'.
263 # (`test -x' is not enough, because it can also be true for directories.)
264 # We have to try this both for $1 and $1.exe.
266 # Note: On Cygwin and DJGPP, `test -x' also looks for .exe. On Cygwin,
267 # also `test -f' has this enhancement, bot not on DJGPP. (Both are
268 # design decisions, so there is little chance to make them consistent.)
269 # Thusly, it seems to be difficult to make use of these enhancements.
271 if test -f "$dir/$1" && test -x "$dir/$1"; then
272 echo "$dir/$1"
273 return 0
274 elif test -f "$dir/$1.exe" && test -x "$dir/$1.exe"; then
275 echo "$dir/$1.exe"
276 return 0
278 done
279 return 1
282 # find_progs prog [progs...]
283 # Look in PATH for one of the programs given in argument.
284 # If none of the progs can be found, the string "exit 2" is "returned".
285 find_progs()
287 # This code comes mostly from Autoconf.
288 for fp_prog in "$@"; do
289 fp_res=`find_prog $fp_prog`
290 if [ $? -eq 0 ]; then
291 echo "$fp_res"
292 return 0
294 done
295 echo "exit 2"
296 return 1
299 test x"$EDITOR" = xmissing && EDITOR=`find_progs vim vi emacs nano`
300 test x"$PAGER" = xmissing && PAGER=`find_progs less more`
301 test x"$AWK" = xmissing && AWK=`find_progs gawk mawk nawk awk`
302 test x"$DIFF" = xmissing && DIFF=`find_progs diff`
303 test x"$COLORDIFF" = xmissing && COLORDIFF=`find_progs colordiff diff`
305 # -R will tell less to interpret some terminal codes, which will turn on
306 # colors.
307 case $PAGER in #(
308 less) PAGER='less -R';;
309 esac
311 # require_diffstat
312 # return true if diffstat is in the PATH
313 require_diffstat()
315 if [ x"$require_diffstat_cache" != x ]; then
316 return $require_diffstat_cache
318 if (echo | diffstat) >/dev/null 2>/dev/null; then :; else
319 warn 'diffstat is not installed on your system or not in your PATH.'
320 test -f /etc/debian_version \
321 && notice 'you might want to `apt-get install diffstat`.'
322 require_diffstat_cache=1
323 return 1
325 require_diffstat_cache=0
326 return 0
329 # require_mail
330 # return 0 -> found a mailer
331 # return !0 -> no mailer found
332 # The full path to the program found is echo'ed on stdout.
333 require_mail()
335 save_PATH=$PATH
336 PATH="${PATH}${PATH_SEPARATOR}/sbin${PATH_SEPARATOR}/usr/sbin${PATH_SEPARATOR}/usr/libexec"
337 export PATH
338 find_progs sendEmail sendmail mail
339 rv=$?
340 PATH=$save_PATH
341 export PATH
342 return $rv
345 # my_sendmail mail-file mail-subject mail-to [extra-headers]
346 # mail-to is a comma-separated list of email addresses.
347 # extra-headers is an optionnal argument and will be prepended at the
348 # beginning of the mail headers if the tool used to send mails supports it.
349 # The mail-file may also contain headers. They must be separated from the body
350 # of the mail by a blank line.
351 my_sendmail()
353 test -f "$1" || abort "my_sendmail: Cannot find the mail file: $1"
354 test x"$2" = x && warn 'my_sendmail: Empty subject.'
355 test x"$3" = x && abort 'my_sendmail: No recipient specified.'
357 content_type='Content-type: text/plain'
358 extra_headers="X-Mailer: svn-wrapper v$version (g$revision)
359 Mime-Version: 1.0
360 Content-Transfer-Encoding: 7bit"
361 if test x"$4" != x; then
362 extra_headers="$4
363 $extra_headers"
364 # Remove empty lines.
365 extra_headers=`echo "$extra_headers" | sed '/^[ ]*$/d;s/^[ ]*//'`
368 # If we have a signature, add it.
369 if test -f ~/.signature; then
370 # But don't add it if it is already in the mail.
371 if grep -Fe "`cat ~/.signature`" "$1" >/dev/null; then :; else
372 echo '-- ' >>"$1" && cat ~/.signature >>"$1"
375 # VCS-compat: handle user option 'sign'.
376 if (grep '^sign: false' ~/.vcs) >/dev/null 2>/dev/null; then :; else
377 ($GPG -h) >/dev/null 2>/dev/null
378 gpg_rv=$?
379 if [ -e ~/.gnupg ] || [ -e ~/.gpg ] || [ -e ~/.pgp ] && [ $gpg_rv -lt 42 ]
380 then
381 if grep 'BEGIN PGP SIGNATURE' "$1" >/dev/null; then
382 notice 'message is already GPG-signed'
383 elif yesno "Sign the mail using $GPG ?"; then
384 # Strip the headers
385 sed '1,/^$/d' "$1" >"$1.msg"
386 sed '1,/^$/!d' "$1" >"$1.hdr"
387 # Sign the message, keep only the PGP signature.
388 $GPG --clearsign <"$1.msg" >"$1.tmp" || {
389 rm -f "$1.msg" "$1.hdr" "$1.tmp"
390 abort "\`$GPG' failed (r=$?)"
392 sed '/^--*BEGIN PGP SIGNATURE--*$/,/^--*END PGP SIGNATURE--*$/!d' \
393 "$1.tmp" >"$1.sig"
395 boundary="svn-wrapper-2-$RANDOM"
396 boundary="$boundary$RANDOM"
397 # Prepend some stuff before the PGP signature.
398 echo "
399 --$boundary
400 content-type: application/pgp-signature; x-mac-type=70674453;
401 name=PGP.sig
402 content-description: This is a digitally signed message part
403 content-disposition: inline; filename=PGP.sig
404 content-transfer-encoding: 7bit" | cat - "$1.sig" >"$1.tmp"
405 mv -f "$1.tmp" "$1.sig"
407 # Append some stuff after the PGP signature.
408 echo "
409 --$boundary--" >>"$1.sig"
410 # Re-paste the headers before the signed body and prepend some stuff.
411 echo "This is an OpenPGP/MIME signed message (RFC 2440 and 3156)
412 --$boundary
413 Content-Transfer-Encoding: 8bit
414 Content-Type: text/plain; charset=iso-8859-1; format=flowed
416 | cat "$1.hdr" - "$1.msg" "$1.sig" >"$1.tmp" \
417 && mv -f "$1.tmp" "$1"
418 content_type="Content-Type: multipart/signed;\
419 protocol=\"application/pgp-signature\"; micalg=pgp-sha1;\
420 boundary=\"$boundary\""
421 # Cleanup.
422 rm -f "$1.tmp" "$1.sig" "$1.msg" "$1.hdr"
423 extra_headers="$extra_headers
424 X-Pgp-Agent: `$GPG --version | sed q`"
428 extra_headers="$extra_headers
429 $content_type"
431 mailer=`require_mail`
432 if [ $? -ne 0 ]; then
433 warn 'my_sendmail: No suitable mailer found.'
434 return 1
436 case $mailer in
437 */sendmail)
438 to=`echo "$3" | sed 's/,//g'`
439 echo "$extra_headers" | cat - "$1" | $mailer $to;;
440 */mail)
441 cat "$1" | $mailer -s "$2" "$3";;
442 */sendEmail)
443 if [ x"$SMTP" = x ]; then
444 warn 'my_sendmail: (sendEmail) please tell me the SMTP server to use'
445 echo -n 'STMP server: '
446 read SMTP || abort 'could not read the SMTP server'
447 notice "hint: you can export SMTP=$SMTP if you don't want to be asked"
449 sendEmail -f "$FULLNAME <$EMAIL>" -t "$3" -u "$2" \
450 -s "$SMTP" -o message-file="$1";;
451 *) # wtf
452 abort 'my_sendmail: Internal error.';;
453 esac
456 # selfupdate
457 selfupdate()
459 my_url='http://www.lrde.epita.fr/~sigoure/svn-wrapper'
460 # You can use https if you feel paranoiac.
462 echo ">>> Fetching svn-wrapper.sh from $my_url/svn-wrapper.sh"
464 # --------------------- #
465 # Fetch the new version #
466 # --------------------- #
468 tmp_me=`get_unique_file_name "$TMPDIR/svn-wrapper.sh"`
469 if (wget --help) >/dev/null 2>/dev/null; then
470 wget --no-check-certificate "$my_url/svn-wrapper.sh" -O "$tmp_me"
471 my_wget='wget'
472 else
473 curl --help >/dev/null 2>/dev/null
474 if [ $? -gt 42 ]; then
475 abort 'Cannot find wget or curl.
476 How can I download any update without them?'
478 my_wget='curl'
479 curl --insecure "$my_url/svn-wrapper.sh" >"$tmp_me"
482 test -r $tmp_me \
483 || abort "Cannot find the copy of myself I downloaded in $tmp_me"
485 # ---------------------------------------- #
486 # Compare versions and update if necessary #
487 # ---------------------------------------- #
489 my_ver=$revision
490 tmp_ver=`sed '/^# $Id[:].*$/!d;
491 s/.*$Id[:] *\([a-f0-9]\{6\}\).*/\1/' "$tmp_me"`
492 test x"$tmp_ver" = x && abort "Cannot find the revision of $tmp_me"
493 if [ x"$my_ver" != x"$tmp_ver" ]; then # There IS an update...
494 echo "An update is available, r$tmp_ver (your version is r$my_ver)"
496 # Wanna see the diff?
497 if yesno 'Do you want to see the diff?'
498 then
499 (require_diffstat && diff -uw "$me" "$tmp_me" | diffstat;
500 echo
501 $COLORDIFF -uw "$me" "$tmp_me") | $PAGER
504 # Let's go :)
505 if yesno "Overwrite $me (r$my_ver) with $tmp_me (r$tmp_ver)?"; then
506 chmod a+x "$tmp_me"
507 cp -p "$me" "$me.r$my_ver"
508 mv "$tmp_me" "$me" && exit 0
510 rm -f "$tmp_me"
511 return 1
512 else
513 echo "You're already up to date [r$my_ver] :)"
515 rm -f "$tmp_me"
518 # get_svn_diff_and_diffstat [files to diff]
519 # Helper for svn_commit
520 get_svn_diff_and_diffstat()
522 if $git_mode; then
523 svn_diff=`git diff --ignore-all-space --no-color -B -C --cached`
524 svn_diff_stat=`git diff --stat --ignore-all-space --no-color -B -C --cached`
525 else
526 # Ignore white spaces. Can't svn diff -x -w: svn 1.4 only.
527 svn_diff=`svn_diffw "$@"`
528 test x"$svn_diff" = x && svn_diff=`$SVN diff "$@"`
529 if require_diffstat; then
530 svn_diff_stat=`echo "$svn_diff" | diffstat`
531 else
532 svn_diff_stat='diffstat not available'
537 # Helper. Sets the variables repos_url, git_branch, git_head, repos_root and
538 # extra_repos_info properly.
539 git_get_repos_info_()
541 # FIXME: 1st commit: "fatal: bad default revision 'HEAD'" on stderr
542 repos_url=`git config --get svn-remote.svn.url`
543 test x"$repos_url" = x && repos_url='(git:unknown)'
544 git_branch=`git branch | awk '/^\*/ { print substr($0, 3) }'`
545 if [ x"$git_branch" = x'(no branch)' ]; then
546 yesno 'You are on a detached HEAD, do you really want to continue?' \
547 || return 1
549 git_head=`git rev-list --pretty=format:%h HEAD --max-count=1 | sed '1d;q'`
550 extra_repos_info="Git branch: $git_branch (HEAD: $git_head)"
551 repos_root=$repos_url
554 # ------------------------------- #
555 # Hooks for standard SVN commands #
556 # ------------------------------- #
558 # svn_commit [args...]
559 # Here is how the commit process goes:
561 # First we look in the arguments passed to commit:
562 # If there are some files or paths, the user wants to commit these only. In
563 # this case, we must search for ChangeLogs from these paths. We might find
564 # more than one ChangeLog, in this case the user will be prompted to pick up
565 # one.
566 # Otherwise (no path passed in the command line) the user just wants to
567 # commit the current working directory.
568 # In any case, we schedule "ChangeLog" for commit.
570 # Alright now that we know which ChangeLog to use, we look in the ChangeLog's
571 # directory if there is a ",svn-log" file which would mean that a previous
572 # commit didn't finish successfully. If there is such a file, the user is
573 # prompted to know whether they want to resume that commit or simply start a
574 # new one.
575 # When the user wants to resume a commit, the ",svn-log" file is loaded and we
576 # retrieve the value of "$@" that was saved in the file.
577 # Otherwise we build a template ChangeLog entry.
578 # Then we open the template ChangeLog entry with $EDITOR so that the user
579 # fills it properly.
580 # Finally, we commit.
581 # Once the commit is sent, we ask the server to know which revision was
582 # commited and we also retrieve the diff. We then send a mail with these.
583 svn_commit()
585 here=`pwd -P`
586 dry_run=false
587 git_commit_all=false
588 use_log_message_from_file=false
589 log_message_to_use=
591 # Check if the user passed some paths to commit explicitly
592 # because in this case we must add the ChangeLog to the commit and search
593 # the ChangeLog from the dirname of that file.
594 i=0; search_from=; add_changelog=false; extra_files=
595 while [ $i -lt $# ]; do
596 arg=$1
597 case $arg in
598 --dry-run)
599 dry_run=:
600 shift
601 i=$((i + 1))
602 continue
604 -a|--all)
605 git_commit_all=:
607 --use-log-file)
608 shift
609 test -z "$1" && abort "$arg needs an argument"
610 test -r "$1" || abort "'$1' does not seem to be readable"
611 test -w "$1" || abort "'$1' does not seem to be writable"
612 test -d "$1" && abort "'$1' seems to be a directory"
613 use_log_message_from_file=:
614 log_message_to_use=$1
615 shift
616 continue
618 esac
619 # If the argument is a valid path: add the ChangeLog in the list of
620 # files to commit
621 if test -e "$arg"; then
622 add_changelog=:
623 if test -d "$arg"; then
624 search_from_add=$arg
625 else
626 search_from_add=`dirname "$arg"`
628 search_from="$search_from:$search_from_add"
630 shift
631 set dummy "$@" "$arg"
632 shift
633 i=$((i + 1))
634 done
635 if $add_changelog; then :; else
636 # There is no path/file in the command line: the user wants to commit the
637 # current directory. Make it explicit now:
638 extra_files=$here
640 search_from=`echo "$search_from" | sed 's/^://; s/^$/./'`
642 # ----------------- #
643 # Find ChangeLog(s) #
644 # ----------------- #
646 nb_chlogs=0; change_log_dirs=
647 save_IFS=$IFS; IFS=':'
648 for dir in $search_from; do
649 IFS=$save_IFS
650 $use_changelog || break
651 test -z "$dir" && dir='.'
652 # First: come back to the original place
653 cd "$here" || abort "Cannot cd to $here"
654 cd "$dir" || continue # Then: Enter $dir (which can be a relative path)
655 found=0
656 while [ $found -eq 0 ]; do
657 this_chlog_dir=`pwd -P`
658 if [ -f ./ChangeLog ]; then
659 found=1
660 nb_chlogs=$((nb_chlogs + 1))
661 change_log_dirs="$change_log_dirs:$this_chlog_dir"
662 else
663 cd ..
665 # Stop searching when in / ... hmz :P
666 test x"$this_chlog_dir" = x/ && break
667 done # end while: did we find a ChangeLog
668 done # end for: find ChangeLogs in $search_from
669 if [ $nb_chlogs -gt 0 ]; then
670 change_log_dirs=`echo "$change_log_dirs" | sed 's/^://' | tr ':' '\n' \
671 | sort -u`
672 nb_chlogs=`echo "$change_log_dirs" | wc -l`
675 # Did we find a ChangeLog? More than one?
676 if [ $nb_chlogs -eq 0 ] && $use_changelog; then
677 if yesno 'svn-wrapper: Error: Cannot find a ChangeLog file!
678 You might want to create an empty one (eg: `touch ChangeLog` where appropriate)
679 Do you want to proceed without using a ChangeLog?'; then
680 cd "$here"
681 $SVN commit "$@"
682 return $?
683 else
684 return 1
686 elif [ $nb_chlogs -gt 1 ]; then
687 notice "$nb_chlogs ChangeLogs were found, pick up one:"
689 IFS=':'; i=0
690 for a_chlog_dir in $change_log_dirs; do
691 i=$((i + 1))
692 echo "$i. $a_chlog_dir/ChangeLog"
693 done
694 echo -n "Which ChangeLog do you want to use? [1-$i] "
695 read chlog_no || abort 'Cannot read answer on stdin.'
697 case $chlog_no in
698 *[^0-9]*) abort "Invalid ChangeLog number: $chlog_no"
699 esac
700 test "$chlog_no" -le $i || abort "Invalid ChangeLog number: $chlog_no
701 max value was: $i"
702 test "$chlog_no" -ge 1 || abort "Invalid ChangeLog number: $chlog_no
703 min value was: 1"
704 change_log_dir=`echo "$change_log_dirs" | tr ':' '\n' | sed "${chlog_no}!d"`
705 else # Only one ChangeLog found
706 if $use_changelog; then
707 change_log_dir=$change_log_dirs
708 notice "using $change_log_dir/ChangeLog"
712 if $use_changelog; then
713 test -f "$change_log_dir/ChangeLog" \
714 || abort "No such file or directory: $change_log_dir/ChangeLog"
715 # Now we can safely schedule the ChangeLog for the commit.
716 extra_files="$extra_files:$change_log_dir/ChangeLog"
717 else
718 change_log_dir='.' # Hack. FIXME: Does this work in all cases?
721 if [ -d "$change_log_dir/.git" ] || $git_mode; then
722 SVN=git
723 git_mode=:
724 git_get_repos_info_
725 else
726 svn_st_tmp=`$SVN status "$change_log_dir"`
728 # Warn for files that are not added in the repos.
729 conflicts=`echo "$svn_st_tmp" | sed '/^ *$/d;
730 /^[^?]/d;
731 /^?.......*\/[,+]/d;
732 /^?......[,+]/d'`
733 if test x"$conflicts" != x; then
734 warn "make sure you don't want to \`svn add'
735 any of the following files before committing:"
736 echo "$conflicts" | sed "$sed_svn_st_color"
737 echo -n 'Type [ENTER] to continue :)' && read chiche_is_gay
740 # If there are changes in an svn:externals, advise the user to commit that
741 # first.
742 changed_externals=`echo "$svn_st_tmp" | $AWK \
743 'function printext()
745 if (ext && !printed)
747 print this_ext "\n";
748 printed = 1;
751 BEGIN { this_ext = ""; ext = 0; ext_modified = 0; }
752 /^Performing status on external/ {
753 ext = 1;
754 sub(/.* at ./, ""); sub(/.$/, ""); this_ext = $0;
755 printed = 0;
757 /^[ADMR]/ { ext_modified = ext; printext(); }
758 /^.[M]/ { ext_modified = ext; printext(); }
759 END { exit ext_modified; }'`
760 if [ $? -ne 0 ]; then
761 warn "the following external items have local modifications:
762 $changed_externals"
763 yesno "You are advised to commit them separately first. Continue anyway?" \
764 || return 1
767 # Detect unresolved conflicts / missing files.
768 conflicts=`echo "$svn_st_tmp" | sed '/^[C!]/!d'`
769 test x"$conflicts" != x && abort "there are unresolved conflicts (\`C')
770 and/or missing files (\`!'):
771 $conflicts"
773 svn_info_tmp=`$SVN info "$change_log_dir"`
774 test $? -ne 0 && abort "Failed to get svn info on $change_log_dir"
775 repos_root=`echo "$svn_info_tmp" | sed '/^Repository Root: /!d;s///'`
776 repos_url=`echo "$svn_info_tmp" | sed '/^URL: /!d;s///'`
777 # It looks like svn <1.3 didn't display a "Repository Root" entry.
778 test x"$repos_root" = x && repos_root=$repos_url
781 cd "$here"
783 YYYY=`date '+%Y'`
784 MM=`date '+%m'`
785 DD=`date '+%d'`
787 # VCS-compat: handle user option 'new_user'
788 new_user='yes'
789 grep '^new_user: false' ~/.vcs >/dev/null 2>/dev/null && new_user='no'
791 edit_changelog=:
792 tmp_log="$change_log_dir/,svn-log"
793 $use_log_message_from_file \
794 && tmp_log=$log_message_to_use \
795 && edit_changelog=false
797 if [ -f "$tmp_log" ] \
798 && { $use_log_message_from_file \
799 || yesnewproceed "It looks like the last commit did not\
800 terminate successfully.
801 Would you like to resume it or proceed immediately?"; }; then
802 case $yesnoproceed_res in
803 *proceed) edit_changelog=false;;
804 esac
805 if test x"$yesnoproceed_res" = xupproceed; then
806 svn_update "$@" || abort 'update failed'
808 echo 'Resuming ...'
809 internal_tags=`sed '/^--- Internal stuff, DO NOT change please ---$/,$!d' \
810 "$tmp_log"`
811 saved_args=`echo "$internal_tags" | sed '/^args: */!d;s///'`
812 extra_files=`echo "$internal_tags" | sed '/^extra_files: */!d;s///'`
813 if [ x"$saved_args" != x ]; then
814 if [ x"$*" != x ] && [ x"$saved_args" != x"$*" ]; then
815 warn "overriding arguments:
816 you invoked $me with the following arguments: $@
817 they have been replaced by these: $saved_args"
818 set dummy $saved_args
819 shift
820 else
821 notice "setting the following arguments: $saved_args"
822 set dummy $saved_args
823 shift
825 elif [ x"$*" != x ]; then
826 warn "overriding arguments:
827 you invoked $me with the following arguments: $@
828 they have been dropped"
831 for i; do
832 case $i in
833 -a|--all)
834 git_commit_all=:
835 if [ $git_mode ]; then
836 (cd $change_log_dir && git add -v -u) || abort '`git add -v -u` failed'
839 esac
840 done
842 get_svn_diff_and_diffstat "$@"
844 # Update the file with the new diff/diffstat in case it changed.
845 $AWK 'BEGIN {
846 tlatbwbi_seen = 0;
847 ycewah_seen = 0;
849 /^--This line, and those below, will be ignored--$/ {
850 tlatbwbi_seen = 1;
852 /^ Your ChangeLog entry will appear here\.$/ {
853 if (tlatbwbi_seen) ycewah_seen = 1;
856 if (ycewah_seen != 2) print;
857 if (ycewah_seen == 1) ycewah_seen = 2;
859 END {
860 if (tlatbwbi_seen == 0)
861 print "--This line, and those below, will be ignored--";
862 }' "$tmp_log" >"$tmp_log.tmp"
863 echo "
865 $svn_diff_stat
867 $svn_diff
869 $internal_tags" >>"$tmp_log.tmp"
870 mv -f "$tmp_log.tmp" "$tmp_log" || abort "failed to write '$tmp_log'"
872 else # Build the template message.
874 # ------------------------------------ #
875 # Gather info for the template message #
876 # ------------------------------------ #
878 if $git_mode; then
879 projname=`git config svnw.project`
880 if [ x"$projname" = x ] \
881 && [ -f "$change_log_dir/.git/svn/git-svn/unhandled.log" ]
882 then
883 projname=`grep project "$change_log_dir/.git/svn/git-svn/unhandled.log"`
884 sed_tmp='$!d;s/^.*+dir_prop: . project //'
885 projname=`echo "$projname" | sed "$sed_tmp"`
887 if [ x"$projname" = x ]; then
888 warn 'No project name set for this repository.
889 If this is a git-svn repository, do this in a SVN working copy:
890 svn propset project myproj .
891 If this is a real git repository, do this:
892 git config svnw.project myproj'
894 else
895 projname=`$SVN propget project "$change_log_dir"`
897 # Try to be VCS-compatible and find a project name in a *.rb.
898 if [ x"$projname" = x ] && [ -d "$change_log_dir/vcs" ]; then
899 projname=`sed '/common_commit/!d;s/.*"\(.*\)<%= rev.*/\1/' \
900 "$change_log_dir"/vcs/*.rb`
901 test x"$projname" != x && test x$new_user = xyes \
902 && notice "VCS-compat: found project name: $projname
903 in " "$change_log_dir"/vcs/*.rb
905 test x"$projname" != x && projname=`echo "$projname" | sed '/[^ ]$/s/$/ /'`
907 if $git_mode; then
908 mailto=`git config svnw.mailto`
909 if [ x"$mailto" = x ] \
910 && [ -f "$change_log_dir/.git/svn/git-svn/unhandled.log" ]
911 then
912 mailto=`grep mailto "$change_log_dir/.git/svn/git-svn/unhandled.log"`
913 sed_tmp='$!d;s/^.*+dir_prop: . mailto //;s/%40/@/g;s/%2C/,/g;s/%20/ /g;'
914 mailto=`echo "$mailto" | sed "$sed_tmp"`
916 if [ x"$mailto" = x ]; then
917 warn 'No mailto property set for this repository.
918 If this is a git-svn repository, do this in a SVN working copy:
919 svn propset mailto maintainer1@foo.com,maint2@bar.com .
920 If this is a real git repository, do this:
921 git config svnw.mailto maintainer1@foo.com,maint2@bar.com'
923 else
924 mailto=`$SVN propget mailto "$change_log_dir"`
927 if [ x"$mailto" = x ]; then
928 test x$new_user = xyes \
929 && warn "no svn property mailto found in $change_log_dir
930 You might want to set default email adresses using:
931 svn propset mailto 'somebody@mail.com, foobar@example.com'\
932 $change_log_dir" >&2
933 # Try to be VCS-compatible and find a list of mails in a *.rb.
934 if [ -d "$change_log_dir/vcs" ]; then
935 mailto=`grep '.@.*\..*' "$change_log_dir"/vcs/*.rb \
936 | tr '\n' ' ' \
937 | sed 's/^.*[["]\([^["]*@[^]"]*\)[]"].*$/\1/' | xargs`
938 test x"$mailto" != x && test x$new_user = xyes \
939 && notice "VCS-compat: found mailto: $mailto
940 in " "$change_log_dir"/vcs/*.rb
941 fi # end VCS compat
942 fi # end guess mailto
944 # Ensure that emails are comma-separated.
945 mailto=`echo "$mailto" | sed 's/[ ;]/,/g' | tr -s ',' | sed 's/,/, /g'`
946 test x"$FULLNAME" = x && FULLNAME='Type Your Name Here' \
947 && warn_env FULLNAME
948 test x"$EMAIL" = x && EMAIL='your.mail.here@FIXME.com' && warn_env EMAIL
950 if $git_mode; then
951 if $git_commit_all; then
952 (cd $change_log_dir && git add -v -u) || abort '`git add -v -u` failed'
954 my_git_st=`git diff -C --raw --cached`
955 test $? -eq 0 || abort 'git diff failed'
956 # Format: ":<old_mode> <new_mode> <old_sha1> <new_sha1> <status>[
957 # <similarity score>]\t<file-name>"
958 change_log_files=`echo "$my_git_st" | sed '
959 t dummy_sed_1
960 : dummy_sed_1
961 s/^:[0-7 ]* [0-9a-f. ]* M[^ ]* \(.*\)$/ * \1: ./; t
962 s/^:[0-7 ]* [0-9a-f. ]* A[^ ]* \(.*\)$/ * \1: New./; t
963 s/^:[0-7 ]* [0-9a-f. ]* D[^ ]* \(.*\)$/ * \1: Remove./; t
964 s/^:[0-7 ]* [0-9a-f. ]* R[^ ]* \([^ ]*\) \(.*\)$/ * \2: Rename from \1./;t
965 s/^:[0-7 ]* [0-9a-f. ]* C[^ ]* \([^ ]*\) \(.*\)$/ * \2: Copy from \1./;t
966 s/^:[0-7 ]* [0-9a-f. ]* T[^ ]* \(.*\)$/ * \1: ./; t
967 s/^:[0-7 ]* [0-9a-f. ]* X[^ ]* \(.*\)$/ * \1: ???./; t
968 s/^:[0-7 ]* [0-9a-f. ]* U[^ ]* \(.*\)$/ * \1: UNMERGED./; t
970 else
971 # --ignore-externals appeared after svn 1.1.1
972 my_svn_st=`$SVN status --ignore-externals "$@" \
973 || $SVN status "$@" | sed '/^Performing status on external/ {
977 # Files to put in the ChangeLog entry.
978 change_log_files=`echo "$my_svn_st" | sed '
979 t dummy_sed_1
980 : dummy_sed_1
981 s/^M......\(.*\)$/ * \1: ./; t
982 s/^A......\(.*\)$/ * \1: New./; t
983 s/^D......\(.*\)$/ * \1: Remove./; t
987 if [ x"$change_log_files" = x ]; then
988 yesno 'Nothing to commit, continue anyway?' || return 1
991 change_log_files=`echo "$change_log_files" | sort -u`
993 get_svn_diff_and_diffstat "$@"
995 # Get any older svn-log out of the way.
996 test -f "$tmp_log" && mv "$tmp_log" `get_unique_file_name "$tmp_log"`
997 # If we can't get an older svn-log out of the way, find a new name...
998 test -f "$tmp_log" && tmp_log=`get_unique_file_name "$tmp_log"`
999 if [ x$new_user = no ]; then
1000 commit_instructions='
1001 Instructions:
1002 - Fill the ChangeLog entry.
1003 - If you feel like, write a comment in the "Comment:" section.
1004 This comment will only appear in the email, not in the ChangeLog.
1005 By default only the location of the repository is in the comment.
1006 - Some tags will be replaced. Tags are of the form: <TAG>. Unknown
1007 tags will be left unchanged.
1008 - The tag <REV> may only be used in the Subject.
1009 - Your ChangeLog entry will be used as commit message for svn.'
1010 else
1011 commit_instructions=
1014 if [ x"$mailto" != xdont_send_mails ] && [ x"$mailto" != xnothx ]; then
1015 to="
1016 To: $mailto"
1018 r_before_rev=r
1019 $git_mode && r_before_rev=
1020 test x"$extra_repos_info" = x || extra_repos_info="
1021 $extra_repos_info"
1022 echo "\
1023 --You must fill this file correctly to continue-- -*- vcs -*-
1024 Title:
1025 Subject: ${projname}$r_before_rev<REV>: <TITLE>
1026 From: $FULLNAME <$EMAIL>$to
1028 Comment:
1029 URL: $repos_url$extra_repos_info
1031 ChangeLog:
1033 <YYYY>-<MM>-<DD> $FULLNAME <$EMAIL>
1035 <TITLE>
1036 $change_log_files
1038 --This line, and those below, will be ignored--
1039 $commit_instructions
1040 --Preview of the message that will be sent--
1042 URL: $repos_url$extra_repos_info
1043 Your comments (if any) will appear here.
1045 ChangeLog:
1046 $YYYY-$MM-$DD $FULLNAME <$EMAIL>
1048 Your ChangeLog entry will appear here.
1051 $svn_diff_stat
1053 $svn_diff" >"$tmp_log"
1055 echo "
1056 --- Internal stuff, DO NOT change please ---
1057 args: $@" >>"$tmp_log"
1058 echo "extra_files: $extra_files
1059 vi: ft=diff:noet:tw=76:" >>"$tmp_log"
1061 fi # end: if svn-log; then resume? else create template
1062 $edit_changelog && $EDITOR "$tmp_log"
1064 # ------------------ #
1065 # Re-"parse" the log #
1066 # ------------------ #
1068 # hmz this section is a bit messy...
1069 # helper string... !@#$%* escaping \\\\\\...
1070 sed_escape='s/\\/\\\\/g;s/@/\\@/g;s/&/\\\&/g'
1071 sed_eval_tags="s/<MM>/$MM/g; s/<DD>/$DD/g; s/<YYYY>/$YYYY/g"
1072 full_log=`sed '/^--*This line, and those below, will be ignored--*$/,$d;
1073 /^--You must fill this/d' "$tmp_log"`
1074 chlog_entry=`echo "$full_log" | sed '/^ChangeLog:$/,$!d; //d'`
1075 ensure_not_empty 'ChangeLog entry' "$chlog_entry"
1076 full_log=`echo "$full_log" | sed '/^ChangeLog:$/,$d'`
1077 mail_comment=`echo "$full_log" | sed '/^Comment:$/,$!d; //d'`
1078 full_log=`echo "$full_log" | sed '/^Comment:$/,$d'`
1079 # Add a period at the end of the title.
1080 sed_tmp='/^Title: */!d;s///;/ *\.$/!s/ *$/./'
1081 mail_title=`echo "$full_log" | sed "$sed_tmp"`
1082 ensure_not_empty 'commit title' "$mail_title"
1083 mail_title=`echo "$mail_title" | sed "$sed_eval_tags; $sed_escape"`
1084 sed_eval_tags="$sed_eval_tags; s@<TITLE>\\.*@$mail_title@g"
1085 mail_comment=`echo "$mail_comment" | sed "$sed_eval_tags"`
1086 raw_chlog_entry=$chlog_entry # ChangeLog entry without tags expanded
1087 chlog_entry=`echo "$chlog_entry" | sed "$sed_eval_tags; 1{
1088 /^ *$/d
1090 mail_subject=`echo "$full_log" | sed '/^Subject: */!d;s///'`
1091 ensure_not_empty 'mail subject' "$mail_subject"
1092 mail_to=`echo "$full_log" | sed '/^To:/!d'`
1093 send_a_mail=:
1094 if test x"$mail_to" = x; then
1095 send_a_mail=false
1096 else
1097 mail_to=`echo "$mail_to" | sed 's/^To: *//'`
1098 ensure_not_empty '"To:" field of the mail' "$mail_to"
1100 mail_from=`echo "$full_log" | sed '/^From: */!d;s///'`
1101 ensure_not_empty '"From:" field of the mail' "$mail_from"
1103 # ------------------------------------ #
1104 # Sanity checks on the ChangeLog entry #
1105 # ------------------------------------ #
1107 if echo "$chlog_entry" | grep '<REV>' >/dev/null; then
1108 warn 'Using the tag <REV> anywhere else than in the Subject is deprecated.'
1109 yesno 'Continue anyway?' || return 1
1112 if echo "$chlog_entry" | grep ': \.$' >/dev/null; then
1113 warn 'It looks like you did not fill all entries in the ChangeLog:'
1114 echo "$chlog_entry" | grep ': \.$'
1115 yesno 'Continue anyway?' || return 1
1118 if echo "$chlog_entry" | grep '^--* Internal stuff' >/dev/null; then
1119 warn "It looks like you messed up the delimiters and I did not properly
1120 find your ChangeLog entry. Here it is, make sure it is correct:"
1121 echo "$chlog_entry"
1122 yesno 'Continue anyway?' || return 1
1125 if echo "$chlog_entry" | grep -i 'dont[^a-z0-9]' >/dev/null; then
1126 warn "Please avoid typos such as ${lred}dont$std instead of\
1127 ${lgreen}don't$std:"
1128 echo "$chlog_entry" | grep -n -i 'dont[^a-z0-9]' \
1129 | sed "s/[dD][oO][nN][tT]/$lred&$std/g"
1130 yesno 'Continue anyway?' || return 1
1133 if echo "$chlog_entry" | grep -i 'cant[^a-z0-9]' >/dev/null; then
1134 warn "Please avoid typos such as ${lred}cant$std instead of\
1135 ${lgreen}can't$std:"
1136 echo "$chlog_entry" | grep -n -i 'cant[^a-z0-9]' \
1137 | sed "s/[cC][aA][nN][tT]/$lred&$std/g"
1138 yesno 'Continue anyway?' || return 1
1141 if echo "$chlog_entry" | grep '^.\{80,\}' >/dev/null; then
1142 warn 'Please avoid long lines in your ChangeLog entry (80 columns max):'
1143 echo "$chlog_entry" | grep '^.\{80,\}'
1144 yesno 'Continue anyway?' || return 1
1147 if echo "$mail_title" | grep -i '^[a-z][a-z]*ed[^a-z]' >/dev/null; then
1148 warn 'ChangeLog entries should be written in imperative form:'
1149 echo "$mail_title" | grep -i '^[a-z][a-z]*ed[^a-z]' \
1150 | sed "s/^\\([a-zA-Z][a-zA-Z]*ed\\)\\([^a-zA-Z]\\)/$lred\\1$std\\2/"
1151 yesno 'Continue anyway?' || return 1
1154 # Check whether the user passed -m | --message
1156 while [ $i -lt $# ]; do
1157 arg=$1
1158 # This is not really a reliable way of knowing whether -m | --message was
1159 # passed but hum... Let's assume it'll do :s
1160 if [ x"$arg" = 'x-m' ] || [ x"$arg" = 'x--message' ]; then
1161 my_message=$2
1163 shift
1164 set dummy "$@" "$arg"
1165 shift
1166 i=$((i + 1))
1167 done
1168 if [ x"$my_message" = x ]; then
1169 # The title must not be indented in the commit message and must be
1170 # followed by a blank line. This yields much better results with most
1171 # VC-viewer (especially for Git but including for SVN, such as Trac for
1172 # instance). We assume that the title will always be on the 1st line.
1173 sed_git_title="1s@^[ ]*<TITLE>\\.*@$mail_title\\
1174 @g; $sed_eval_tags"
1175 # First, remove empty lines at the beginning, if any.
1176 # Remove also the date information (useless in commit messages)
1177 my_message=`echo "$raw_chlog_entry" \
1178 | sed -e '1,4 {
1179 /^<YYYY>-<[MD][MD]>-<[DM][DM]>/d
1180 /^[1-9][0-9][0-9][0-9]-[0-9][0-9]*-[0-9][0-9]*/d
1181 /^ and .*<.*@.*>$/d
1182 /^ *$/d
1183 }' \
1184 | sed -e "$sed_git_title" \
1185 -e "$sed_eval_tags; 1{
1186 /^ *$/d
1188 else
1189 notice 'you are overriding the commit message.'
1192 # Show suspicious whitespace additions with Git.
1193 $git_mode && git diff --cached --check
1195 if $dry_run; then
1196 proposal_file=',proposal'
1197 test -f "$proposal_file" \
1198 && proposal_file=`get_unique_file_name "$proposal_file"`
1199 sed_tmp='s/<REV>/???/g;s/\([^.]\) *\.$/\1/'
1200 mail_subject=`echo "$mail_subject" | sed "$sed_eval_tags; $sed_tmp"`
1202 if [ x"$mailto" != xdont_send_mails ] && [ x"$mailto" != xnothx ]; then
1203 to="
1204 To: $mailto"
1206 echo "\
1207 From: $mail_from$to
1208 Subject: $mail_subject
1210 $mail_comment
1212 ChangeLog:
1213 $chlog_entry
1216 $svn_diff_stat
1218 $svn_diff" >"$proposal_file"
1219 notice "A proposal of your commit was left in '$proposal_file'"
1220 return 0
1223 # Are you sure?
1224 if $git_mode; then
1225 $git_commit_all \
1226 || notice 'You are using git, unlike SVN, do not forget to git add your
1227 changes'
1230 # Change edit_changelog so that we're asking the confirmation below.
1231 $use_log_message_from_file && edit_changelog=: \
1232 && notice "You are about to commit the following change:
1233 $mail_title"
1235 $edit_changelog \
1236 && {
1237 yesno 'Are you sure you want to commit?' \
1238 || return 1
1241 # Add the ChangeLog entry
1242 old_chlog=`get_unique_file_name "$change_log_dir/ChangeLog.old"`
1243 mv "$change_log_dir/ChangeLog" "$old_chlog" || \
1244 abort 'Could not backup ChangeLog'
1245 trap "echo SIGINT; mv \"$old_chlog\" \"$change_log_dir/ChangeLog\";
1246 exit 130" $SIGINT
1247 echo "$chlog_entry" >"$change_log_dir/ChangeLog"
1248 echo >>"$change_log_dir/ChangeLog"
1249 cat "$old_chlog" >>"$change_log_dir/ChangeLog"
1251 # Add extra files such as cwd or ChangeLog to the commit.
1252 tmp_sed='s/ /\\ /g' # Escape spaces for the shell.
1253 if $git_mode && $use_changelog; then
1254 # Schedule the ChangeLog for the next commit
1255 (cd "$change_log_dir" && git add ChangeLog) \
1256 || abort 'failed to git add the ChangeLog'
1257 extra_files=
1258 else
1259 extra_files=`echo "$extra_files" | sed "$tmp_sed" | tr ':' '\n'`
1262 # Always sign the commits with Git.
1263 $git_mode && set dummy -s "$@" && shift
1265 # Update the Git index if necessary (just in case the user changed his
1266 # working copy in the mean time)
1267 if $git_mode && $git_commit_all; then
1268 (cd $change_log_dir && git add -v -u) || abort '`git add -v -u` failed'
1271 # --Commit-- finally! :D
1272 $SVN commit -m "$my_message" "$@" $extra_files || {
1273 svn_commit_rv=$?
1274 mv "$old_chlog" "$change_log_dir/ChangeLog"
1275 abort "Commit failed, $SVN returned $svn_commit_rv"
1278 echo -n 'Getting the revision number... '
1279 if $git_mode; then
1280 REV=`git rev-list --pretty=format:%h HEAD --max-count=1 | sed '1d;q'`
1281 else
1282 svn_info_tmp=`$SVN info "$change_log_dir/ChangeLog"`
1283 REV=`echo "$svn_info_tmp" | sed '/^Revision: /!d;s///'`
1284 test x"$REV" = x && REV=`echo "$svn_info_tmp" \
1285 | sed '/^Last Changed Rev: /!d;s///'`
1287 test x"$REV" = x && abort 'Cannot detect the current revision.'
1288 echo "$REV"
1290 # Let's make sure we have the real diff by asking the ChangeSet we've just
1291 # committed to the server.
1293 # Backup the old stuff in case we fail to get the real diff from the server
1294 # for some reason...
1295 save_svn_diff=$svn_diff
1296 save_svn_diff_stat=$svn_diff_stat
1298 if $git_mode; then
1299 svn_diff=`git diff --ignore-all-space --no-color -C 'HEAD^' HEAD`
1300 svn_diff_stat=`git diff --stat --ignore-all-space --no-color -C 'HEAD^' HEAD`
1301 grep svn-remote "$change_log_dir/.git/config" >/dev/null 2>&1 && \
1302 notice 'Do not forget to use: git-svn dcommit to push your commits in SVN'
1303 else
1304 # Fetch the ChangeSet and filter out the ChangeLog entry. We don't use
1305 # svn diff -c because this option is not portable to older svn versions.
1306 REV_MINUS_ONE=$((REV - 1))
1307 svn_diff=`svn_diffw -r"$REV_MINUS_ONE:$REV" "$repos_root" \
1308 | $AWK '/^Index: / { if (in_chlog) in_chlog = 0; }
1309 /^Index: .*ChangeLog$/ { in_chlog = 1 }
1310 { if (!in_chlog) print }'`
1311 if [ x"$svn_diff" = x ]; then
1312 svn_diff=$save_svn_diff
1313 svn_diff_stat=$save_svn_diff_stat
1314 else
1315 if require_diffstat; then
1316 svn_diff_stat=`echo "$svn_diff" | diffstat`
1317 else
1318 svn_diff_stat='diffstat not available'
1323 # Expand <REV> and remove the final period from the mail subject if there is
1324 # only one period.
1325 sed_tmp="s/<REV>/$REV/g;"'s/\([^.]\) *\.$/\1/'
1326 mail_subject=`echo "$mail_subject" | sed "$sed_eval_tags; $sed_tmp"`
1328 mail_file=`get_unique_file_name "$change_log_dir/+mail"`
1329 echo "\
1330 From: $mail_from
1331 To: $mail_to
1332 Subject: $mail_subject
1334 $mail_comment
1336 ChangeLog:
1337 $chlog_entry
1340 $svn_diff_stat
1342 $svn_diff" | sed 's/^\.$/ ./' >"$mail_file"
1343 # We change lines with only a `.' because they could mean "end-of-mail"
1345 # Send the mail
1346 if $send_a_mail; then
1347 trap 'echo SIGINT; exec < /dev/null' $SIGINT
1348 # FIXME: Move the mail to the +committed right now, in case the user
1349 # CTLR+C the mail-sending-thing, so that the mail will be properly saved
1350 # their.
1351 my_sendmail "$mail_file" "$mail_subject" "$mail_to" \
1352 "X-svn-url: $repos_root
1353 X-svn-revision: $REV"
1354 fi # end do we have to send a mail?
1355 rm -f "$tmp_log"
1356 rm -f "$old_chlog"
1357 save_mail_file=`echo "$mail_file" | sed 's/+//'`
1358 mkdir -p "$change_log_dir/+committed" \
1359 || warn "Couldn't mkdir -p $change_log_dir/+committed"
1360 if [ -d "$change_log_dir/vcs" ] \
1361 || [ -d "$change_log_dir/+committed" ]
1362 then
1363 mkdir -p "$change_log_dir/+committed/$REV" \
1364 && mv "$mail_file" "$change_log_dir/+committed/$REV/mail"
1366 return $svn_commit_rv
1369 # svn_diffw [args...]
1370 svn_diff()
1372 # Ignore white spaces.
1373 if $git_mode; then
1374 git diff -C "$@"
1375 else
1376 diffarg='-u'
1377 # Can't svn diff -x -w: svn 1.4 only.
1378 # Moreover we *MUST* use -x -uw, not -x -u -w or -x -u -x -w ...
1379 # Hence the hack to stick both diff arguments together...
1380 # No comment :)
1381 test x"$1" = x'--SVNW-HACK-w' && shift && diffarg='-uw'
1382 $SVN diff --no-diff-deleted --diff-cmd $DIFF -x $diffarg "$@"
1386 # svn_diffw [args...]
1387 svn_diffw()
1389 # Ignore white spaces.
1390 if $git_mode; then
1391 svn_diff --ignore-all-space "$@"
1392 else
1393 svn_diff --SVNW-HACK-w "$@"
1397 # svn_mail REV [mails...]
1398 svn_mail()
1400 test $# -lt 1 && abort "Not enough arguments provided;
1401 Try 'svn help mail' for more info."
1402 case $1 in
1403 PREV)
1404 if $git_mode; then
1405 REV=`git rev-list --pretty=format:%h 'HEAD^' --max-count=1 | sed '1d;q'`
1406 else
1407 REV=`svn_revision || abort 'Cannot get current revision number'`
1408 test x"$REV" = x && abort 'Cannot get current revision number'
1409 if [ "$REV" -lt 1 ]; then
1410 abort 'No previous revision.'
1412 REV=$((REV - 1))
1415 HEAD)
1416 REV=`svn_revision || abort 'Cannot get current revision number'`
1417 test x"$REV" = x && abort 'Cannot get current revision number'
1419 *) REV=$1;;
1420 esac
1421 shift
1423 found_committed=0; found=0
1424 while [ $found -eq 0 ]; do
1425 this_chlog_dir=`pwd -P`
1426 if [ -d ./+committed ]; then
1427 found_committed=1
1428 if [ -d ./+committed/$REV ]; then
1429 found=1
1430 else
1431 cd ..
1433 else
1434 cd ..
1436 # Stop searching when in / ... hmz :P
1437 test x`pwd` = x/ && break
1438 done
1439 if [ $found -eq 0 ]; then
1440 if [ $found_committed -eq 0 ]; then
1441 abort 'Could not find the +committed directory.'
1442 else
1443 abort "Could not find the revision $REV in +committed."
1445 abort 'Internal error (should never be here).'
1448 mail_file=; subject=; to=
1449 if [ -f ./+committed/$REV/mail ]; then
1450 # svn-wrapper generated file
1451 mail_file="./+committed/$REV/mail"
1452 subject=`sed '/^Subject: /!d;s///' $mail_file | sed '1q'`
1453 to=`sed '/^To: /!d;s///' $mail_file | sed '1q'`
1454 elif [ -f ./+committed/$REV/,iform ] && [ -f ./+committed/$REV/,message ]
1455 then
1456 # VCS-generated file
1457 subject=`sed '/^Subject: /!d;s///;s/^"//;s/"$//' ./+committed/$REV/,iform \
1458 | sed "s/<%= *rev *%>/$REV/g"`
1459 to=`sed '/^To:/,/^[^-]/!d' ./+committed/$REV/,iform | sed '1d;s/^- //;$d' \
1460 | xargs | sed 's/ */, /g'`
1461 mail_file=`get_unique_file_name "$TMPDIR/mail.r$REV"`
1462 echo "From: $FULLNAME <$EMAIL>
1463 To: $to
1464 Subject: $subject
1465 " >"$mail_file" || abort "Cannot create $mail_file"
1466 cat ./+committed/$REV/,message >>"$mail_file" \
1467 || abort "Cannot copy ./+committed/$REV/,message in $mail_file"
1468 else
1469 abort "Couldn't find the mail to re-send in `pwd`/+committed/$REV"
1471 if [ $# -gt 0 ]; then
1472 to=`echo "$*" | sed 's/ */, /g'`
1475 test x"$to" = x && abort 'Cannot find the list of recipients.
1476 Please report this bug.'
1477 test x"$subject" = x && abort 'Cannot find the subject of the mail.
1478 Please report this bug.'
1480 if yesno "Re-sending the mail of r$REV
1481 Subject: $subject
1482 To: $to
1483 Are you sure?"; then :; else
1484 return 1
1487 if $git_mode; then
1488 git_get_repos_info_
1489 else
1490 svn_info_tmp=`$SVN info`
1491 test $? -ne 0 && abort "Failed to get svn info on `pwd`"
1492 repos_root=`echo "$svn_info_tmp" | sed '/^Repository Root: /!d;s///'`
1493 repos_url=`echo "$svn_info_tmp" | sed '/^URL: /!d;s///'`
1494 # It looks like svn <1.3 didn't display a "Repository Root" entry.
1495 test x"$repos_root" = x && repos_root=$repos_url
1498 my_sendmail "$mail_file" "$subject" "$to" \
1499 "X-svn-url: $repos_url
1500 X-svn-revision: $REV"
1503 # svn_version
1504 svn_version()
1506 echo "Using svn-wrapper v$version-g$revision (C) SIGOURE Benoit [GPL]"
1509 # has_prop prop-name [path]
1510 # return value: 0 -> path has the property prop-name set.
1511 # 1 -> path has no property prop-name.
1512 # 2 -> svn error.
1513 has_prop()
1515 hp_plist=`$SVN proplist "$2"`
1516 test $? -ne 0 && return 2
1517 hp_res=`echo "$hp_plist" | sed "/^ *$1\$/!d"`
1518 test x"$hp_res" = x && return 1
1519 return 0
1522 # svn_propadd prop-name prop-val [path]
1523 svn_propadd()
1525 if $git_mode; then
1526 abort 'propadd is only for SVN, not for Git.'
1528 test $# -lt 2 \
1529 && abort 'Not enough arguments provided;
1530 try `svn help propadd` for more info'
1531 test $# -gt 3 \
1532 && abort 'Too many arguments provided;
1533 try `svn help propadd` for more info'
1535 path=$3
1536 test x"$path" = x && path='.' && set dummy "$@" '.' && shift
1537 has_prop "$1" "$3" || {
1538 test $? -eq 2 && return 1 # svn error
1539 # no property found:
1540 yesno "'$path' has no property named '$1', do you want to add it?" \
1541 && $SVN propset "$@"
1542 return $?
1545 current_prop_val=`$SVN propget "$1" "$3"`
1546 test $? -ne 0 && abort "Failed to get the current value of property '$1'."
1548 $SVN propset "$1" "$current_prop_val
1549 $2" "$3" >/dev/null || abort "Failed to add '$3' in the property '$1'."
1551 current_prop_val=`$SVN propget "$1" "$3" || echo "$current_prop_val
1552 $2"`
1553 echo "property '$1' updated on '$path', new value:
1554 $current_prop_val"
1557 # svn_propsed prop-name sed-script [path]
1558 svn_propsed()
1560 if $git_mode; then
1561 abort 'propsed is only for SVN, not for Git.'
1563 test $# -lt 2 \
1564 && abort 'Not enough arguments provided;
1565 try `svn help propsed` for more info'
1566 test $# -gt 3 \
1567 && abort 'Too many arguments provided;
1568 try `svn help propsed` for more info'
1570 path=$3
1571 test x"$path" = x && path='.'
1572 has_prop "$1" "$3" || {
1573 test $? -eq 2 && return 1 # svn error
1574 # no property found:
1575 abort "'$path' has no property named '$1'."
1578 prop_val=`$SVN propget "$1" "$3"`
1579 test $? -ne 0 && abort "Failed to get the current value of property '$1'."
1581 prop_val=`echo "$prop_val" | sed "$2"`
1582 test $? -ne 0 && abort "Failed to run the sed script '$2'."
1584 $SVN propset "$1" "$prop_val" "$3" >/dev/null \
1585 || abort "Failed to update the property '$1' with value '$prop_val'."
1587 new_prop_val=`$SVN propget "$1" "$3" || echo "$prop_val"`
1588 echo "property '$1' updated on '$path', new value:
1589 $new_prop_val"
1592 # svn_revision [args...]
1593 svn_revision()
1595 if $git_mode; then
1596 short=`git rev-list --pretty=format:%h HEAD --max-count=1 | sed '1d;q'`
1597 long=`git rev-list --pretty=format:%H HEAD --max-count=1 | sed '1d;q'`
1598 echo "$short ($long)"
1599 else
1600 svn_revision_info_out=`$SVN info "$@"`
1601 svn_revision_rv=$?
1602 echo "$svn_revision_info_out" | sed '/^Revision: /!d;s///'
1603 return $svn_revision_rv
1607 # svn_ignore [paths]
1608 svn_ignore()
1610 if [ $# -eq 0 ]; then # Simply display ignore-list.
1611 if $git_mode; then
1612 test -f .gitignore && cat .gitignore
1613 else
1614 $SVN propget 'svn:ignore'
1616 elif [ $# -eq 1 ]; then
1617 b=`basename "$1"`
1618 d=`dirname "$1"`
1619 if $git_mode; then
1620 echo "$b" >>"$d/.gitignore"
1621 git add "$d/.gitignore"
1622 notice 'files ignored in this directory:'
1623 cat "$d/.gitignore"
1624 else
1625 svn_propadd 'svn:ignore' "$b" "$d"
1627 else # Add arguments in svn:ignore.
1628 # This part is a bit tricky:
1629 # For each argument, we find all the other arguments with the same dirname
1630 # $dname and we svn:ignore them all in $dname.
1631 while [ $# -ne 0 ]; do
1632 arg=$1
1633 dname=`dirname "$1"`
1634 files=`basename "$1"`
1635 shift
1636 j=0; argc=$#
1637 while [ $j -lt $argc ] && [ $# -ne 0 ]; do
1638 this_arg=$1
1639 shift
1640 this_dname=`dirname "$this_arg"`
1641 this_file=`basename "$this_arg"`
1642 if [ x"$dname" = x"$this_dname" ]; then
1643 files="$files
1644 $this_file"
1645 else
1646 set dummy "$@" "$this_arg"
1647 shift
1649 j=$((j + 1))
1650 done
1651 if $git_mode; then
1652 echo "$files" >>"$dname"/.gitignore
1653 git add "$dname"/.gitignore
1654 notice "files ignored in $dname:"
1655 cat "$dname"/.gitignore
1656 else
1657 svn_propadd 'svn:ignore' "$files" "$dname"
1659 done
1663 # svn_help
1664 # FIXME: Use here-docs.
1665 svn_help()
1667 if [ $# -eq 0 ]; then
1668 svn_version
1669 $SVN help
1670 rv=$?
1671 echo '
1672 Additionnal commands provided by svn-wrapper:
1673 diffstat (ds)
1674 diffw (dw)
1675 ignore
1676 mail
1677 propadd (padd, pa) -- SVN only
1678 proposal
1679 propsed (psed) -- SVN only
1680 revision (rev)
1681 touch
1682 selfupdate (selfup)
1683 version'
1684 return $rv
1685 else
1686 case $1 in
1687 commit | ci)
1688 $SVN help commit | sed '/^Valid options:/i\
1689 Extra options provided by svn-wrapper:\
1690 \ --dry-run : do not commit, simply generate a patch with what\
1691 \ would have been comitted.\
1692 \ --use-log-file FILE : extract the ChangeLog entry from FILE. This\
1693 \ entry must be formated in a similar fashion to\
1694 \ what svn-wrapper usually asks you to fill in.\
1695 \ The FILE needs to be writable and will be\
1696 \ removed by svn-wrapper upon success.\
1700 diffstat | ds)
1701 require_diffstat
1702 echo 'diffstat (ds): Display the histogram from svn diff-output.'
1703 $SVN help diff | sed '1d;
1704 s/differences*/histogram/;
1705 2,35 s/diff/diffstat/g'
1707 diffw | dw)
1708 echo "diffw (dw): Display the differences without taking whitespaces\
1709 into account."
1710 $SVN help diff | sed '1d;
1711 2,35 s/diff\([^a-z]\)/diffw\1/g;
1712 /--diff-cmd/,/--no-diff-deleted/d'
1714 ignore)
1715 what='svn:ignore property'
1716 $git_mode && what='.gitignore file'
1717 echo 'ignore: Add some files in the '"$what"'.
1718 usage: 1. ignore [PATH]
1719 2. ignore FILE [FILES...]
1721 1. Display the value of '"$what"' on [PATH].
1722 2. Add some files in the '"$what"' of the directory containing them.
1724 When adding ignores, each pattern is ignored in its own directory, e.g.:
1725 '"$bme"' ignore dir/file "d2/*.o"
1726 Will put `file'\'' in the '"$what"' of `dir'\'' and `*.o'\'' in the
1727 '"$what"' of `d2'\''
1729 Valid options:
1730 None.'
1732 mail)
1733 echo 'mail: Resend the mail of a given commit.
1734 usage: mail REV [emails]
1736 REV must have an email file associated in +committed/REV.
1737 REV can also be PREV or HEAD.
1739 By default the mail is sent to same email addresses as during the original
1740 commit unless more arguments are given.'
1742 propadd | padd | pa)
1743 echo 'propadd (padd, pa): Add something in the value of a property.
1744 usage: propadd PROPNAME PROPVAL PATH
1745 This command only works in SVN mode.
1747 PROPVAL will be appended at the end of the property PROPNAME.
1749 Valid options:
1750 None.'
1752 proposal)
1753 echo 'proposal: Alias for: commit --dry-run.
1754 See: svn help commit.'
1756 propsed | psed)
1757 echo 'propsed (psed): Edit a property with sed.
1758 usage: propsed PROPNAME SED-ARGS PATH
1759 This command only works in SVN mode.
1761 eg: svn propsed svn:externals "s/http/https/" .
1763 Valid options:
1764 None.'
1766 revision | rev)
1767 echo 'revision (rev): Display the revision number of a local or remote item.'
1768 $SVN help info | sed '1d;
1769 s/information/revision/g;
1770 s/revision about/the revision of/g;
1771 2,35 s/info/revision/g;
1772 /-xml/d'
1774 touch)
1775 echo 'touch: Touch a file and svn add it.
1776 usage: touch FILE [FILES]...
1778 Valid options:
1779 None.'
1781 selfupdate | selfup | self-update | self-up)
1782 echo 'selfupdate (selfup): Attempt to update svn-wrapper.sh
1783 usage: selfupdate
1785 Valid options:
1786 None.'
1788 version)
1789 echo 'version: Display the version info of svn and svn-wrapper.
1790 usage: version
1792 Valid options:
1793 None.'
1795 *) $SVN help "$@";;
1796 esac
1800 # svn_status [args...]
1801 svn_status()
1803 if $git_mode; then
1804 git status "$@"
1805 return $?
1807 svn_status_out=`$SVN status "$@"`
1808 svn_status_rv=$?
1809 test x"$svn_status_out" = x && return $svn_status_rv
1810 echo "$svn_status_out" | sed "$sed_svn_st_color"
1811 return $svn_status_rv
1814 # svn_update [args...]
1815 svn_update()
1817 svn_update_out=`$SVN update "$@"`
1818 svn_update_rv=$?
1819 echo "$svn_update_out" | sed "$sed_svn_up_colors"
1820 return $svn_update_rv
1823 # ------------------- #
1824 # `main' starts here. #
1825 # ------------------- #
1827 # Define colors if stdout is a tty.
1828 if test -t 1; then
1829 set_colors
1830 else # stdout isn't a tty => don't print colors.
1831 set_nocolors
1834 # Consider this as a sed function :P.
1835 sed_svn_st_color="
1836 t dummy_sed_1
1837 : dummy_sed_1
1838 s@^?\\(......\\)+@+\\1+@
1839 s@^?\\(......\\)\\(.*/\\)+@+\\1\\2+@
1840 s@^?\\(......\\),@,\\1,@
1841 s@^?\\(......\\)\\(.*/\\),@,\\1\\2,@
1842 s/^\\(.\\)C/\\1${lred}C${std}/
1843 t dummy_sed_2
1844 : dummy_sed_2
1845 s/^?/${lred}?${std}/; t
1846 s/^M/${lgreen}M${std}/; t
1847 s/^A/${lgreen}A${std}/; t
1848 s/^X/${lblue}X${std}/; t
1849 s/^+/${lyellow}+${std}/; t
1850 s/^D/${lyellow}D${std}/; t
1851 s/^,/${lred},${std}/; t
1852 s/^C/${lred}C${std}/; t
1853 s/^I/${purple}I${std}/; t
1854 s/^R/${lblue}R${std}/; t
1855 s/^!/${lred}!${std}/; t
1856 s/^~/${lwhite}~${std}/; t"
1858 sed_svn_up_colors="
1859 t dummy_sed_1
1860 : dummy_sed_1
1862 /^Updated/ t
1863 /^Fetching/ t
1864 /^External/ t
1865 s/^\\(.\\)C/\\1${lred}C${std}/
1866 s/^\\(.\\)U/\\1${lgreen}U${std}/
1867 s/^\\(.\\)D/\\1${lred}D${std}/
1868 t dummy_sed_2
1869 : dummy_sed_2
1870 s/^A/${lgreen}A${std}/; t
1871 s/^U/${lgreen}U${std}/; t
1872 s/^D/${lyellow}D${std}/; t
1873 s/^G/${purple}G${std}/; t
1874 s/^C/${lred}C${std}/; t"
1876 # For dev's:
1877 test "x$1" = x--debug && shift && set -x
1879 test "x$1" = x--git && shift && git_mode=: && SVN=git
1881 test "x$1" = x--no-changelog && shift && use_changelog=false
1883 case $1 in
1884 # ------------------------------- #
1885 # Hooks for standard SVN commands #
1886 # ------------------------------- #
1887 commit | ci)
1888 shift
1889 svn_commit "$@"
1891 help | \? | h)
1892 shift
1893 svn_help "$@"
1895 status | stat | st)
1896 shift
1897 svn_status "$@"
1899 update | up)
1900 shift
1901 svn_update "$@"
1903 # -------------------- #
1904 # Custom SVN commands #
1905 # -------------------- #
1906 diffstat | ds)
1907 shift
1908 if [ -d .git ]; then
1909 git diff --stat -C
1910 else
1911 require_diffstat && $SVN diff --no-diff-deleted "$@" | diffstat
1914 diff | di)
1915 shift
1916 DIFF=$COLORDIFF
1917 svn_diff "$@"
1919 diffw | dw)
1920 shift
1921 DIFF=$COLORDIFF
1922 svn_diffw "$@"
1924 ignore)
1925 shift
1926 svn_ignore "$@"
1928 mail)
1929 shift
1930 svn_mail "$@"
1932 propadd | padd | pa)
1933 shift
1934 svn_propadd "$@"
1936 proposal)
1937 shift
1938 svn_commit --dry-run "$@"
1940 propsed | psed)
1941 shift
1942 svn_propsed "$@"
1944 revision | rev)
1945 shift
1946 svn_revision "$@"
1948 touch)
1949 shift
1950 touch "$@" && $SVN add "$@"
1952 selfupdate | selfup | self-update | self-up)
1953 shift
1954 selfupdate "$@"
1956 version | -version | --version)
1957 shift
1958 set dummy '--version' "$@"
1959 shift
1960 svn_version
1961 exec $SVN "$@"
1963 *) exec $SVN "$@"
1965 esac