2 # Simple wrapper around SVN and Git featuring auto-ChangeLog entries and
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.
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. It won't work with Solaris'
35 # brain-damaged stock /bin/sh.
37 # HOWEVER, there will be bugs, there will be cases in which the script doesn't
38 # wrap properly the svn-cli, etc. In this case, you can try to mail me at
39 # <tsuna at lrde dot epita dot fr>. Include the revision of the svn-wrapper
40 # you're using, and full description of what's wrong etc. so I can reproduce
43 # If you feel like, you can try to fix/enhance the script yourself. It only
44 # requires some basic Shell-scripting skills. Knowing sed will prove useful :)
50 # If you're simply looking for the usage, run `svn-wrapper.sh help' (or
51 # `svn help' if you aliased `svn' on svn-wrapper.sh) as usual.
53 # This script is (hopefully) portable, widely commented and self-contained. Do
54 # not hesitate to hack it. It might look rather long (because it does a lot of
55 # things :P) but you should be able to easily locate which part of the code
58 # The script begins by defining several functions. Then it really starts where
59 # the comment "# `main' starts here. #" is placed.
60 # Some svn commands are hooked (eg, `svn st' displays colors). Hooks and
61 # extra commands are defined in functions named `svn_<command-name>'.
67 # * Write a real testsuite. :/
68 # * Automatic proxy configuration depending on the IP in ifconfig?
69 # * Customizable behavior/colors via some ~/.<config>rc file (?)
70 # * Handle things such as svn ci --force foobar.
72 # * svn automerge + automatic fill in branches/README.branches.
73 # => won't do (SVN is too borken WRT merges, I use Git now so I don't feel
74 # like dealing with this sort of SVN issue)
75 # * Automatically recognize svn cp and svn mv instead of writing "New" and
76 # "Remove" in the template ChangeLog entry (hard). Pair the and new/remove
77 # to prepare a good ChangeLog entry (move to X, copy from X) [even harder].
78 # => won't do (Git does this fine, can't be bothered to deal with this for
83 # Default values (the user can export them to override them).
90 if [ -d .git
] ||
[ -d ..
/.git
]; then
106 # Override the locale.
111 : ${COLORDIFF=missing}
113 # Signal number for traps (using plain signal names is not portable and breaks
114 # on recent Debians that use ash as their default shell).
117 # Whether we're diffing interactively or not
123 # Pitfall: some users might be tempted to export SVN=svn-wrapper.sh for some
124 # reason. This is just *wrong*. The following is an attempt to save them from
126 if [ x
`basename "$SVN"` = x
"$bme" ]; then
127 echo "warning: setting SVN to $bme is wrong"
131 # This code comes (mostly) from Autoconf.
132 # The user is always right.
133 if test "${PATH_SEPARATOR+set}" != set; then
135 (PATH
='/bin;/bin'; FPATH
=$PATH; sh
-c :) >/dev
/null
2>&1 && {
136 (PATH
='/bin:/bin'; FPATH
=$PATH; sh
-c :) >/dev
/null
2>&1 ||
143 # Get the first 6 digits (without forking)
144 revision
=${full_rev#'$'Id': '}
145 revision
=${revision%??????????????????????????????????' $'}
147 # The `main' really starts after the functions definitions.
155 red
='\e[0;31m'; lred
='\e[1;31m'
156 green
='\e[0;32m'; lgreen
='\e[1;32m'
157 yellow
='\e[0;33m'; lyellow
='\e[1;33m'
158 blue
='\e[0;34m'; lblue
='\e[1;34m'
159 purple
='\e[0;35m'; lpurple
='\e[1;35m'
160 cyan
='\e[0;36m'; lcyan
='\e[1;36m'
161 grey
='\e[0;37m'; lgrey
='\e[1;37m'
162 white
='\e[0;38m'; lwhite
='\e[1;38m'
182 echo "svn-wrapper: ${lred}abort${std}: $@" \
183 |
sed '1!s/^[ ]*/ /' >&2
190 echo "svn-wrapper: ${lred}warning${std}: $@" \
191 |
sed '1!s/^[ ]*/ /' >&2
197 echo "svn-wrapper: ${lyellow}notice${std}: $@" \
198 |
sed '1!s/^[ ]*/ /' >&2
205 read answer ||
return 1
210 return 42 # should never happen...
214 # returns true if `yes' or `proceed', false if `new'.
215 # the answer is stored in $yesnoproceed_res which is /yes|new|proceed/
218 printf "$@ [(y)es/(p)roceed/(u)p+proceed/(N)ew] "
219 read answer ||
return 1
221 y
* | Y
*) yesnoproceed_res
=yes; return 0;;
222 p
* | P
*) yesnoproceed_res
=proceed
; return 0;;
223 u
* | U
*) yesnoproceed_res
=upproceed
; return 0;;
224 *) yesnoproceed_res
=new
; return 1;;
226 return 42 # should never happen...
232 warn
"cannot find the environment variable $1
233 You might consider using \`export $1='<FIXME>'\`"
236 # get_unique_file_name file-name
237 get_unique_file_name
()
240 echo "$1" && return 0
243 while test -e "$gufn.$i"; do
249 # ensure_not_empty description value
253 # `value' has at least one non-space character.
254 *[-a-zA-Z0-9_!@
*{}|
,.
/:]*)
257 ene_val
=`echo "$2" | tr -d ' \t\n'`
258 test -z "$ene_val" && abort
"$1: empty value"
261 # find_prog prog-name
262 # return true if prog-name is in the PATH
263 # echo the full path to prog-name on stdout.
264 # Based on a code from texi2dvi
271 test -z "$dir" && continue
272 # The basic test for an executable is `test -f $f && test -x $f'.
273 # (`test -x' is not enough, because it can also be true for directories.)
274 # We have to try this both for $1 and $1.exe.
276 # Note: On Cygwin and DJGPP, `test -x' also looks for .exe. On Cygwin,
277 # also `test -f' has this enhancement, bot not on DJGPP. (Both are
278 # design decisions, so there is little chance to make them consistent.)
279 # Thusly, it seems to be difficult to make use of these enhancements.
281 if test -f "$dir/$1" && test -x "$dir/$1"; then
284 elif test -f "$dir/$1.exe" && test -x "$dir/$1.exe"; then
292 # find_progs prog [progs...]
293 # Look in PATH for one of the programs given in argument.
294 # If none of the progs can be found, the string "exit 2" is "returned".
297 # This code comes mostly from Autoconf.
298 for fp_prog
in "$@"; do
299 fp_res
=`find_prog $fp_prog`
300 if [ $?
-eq 0 ]; then
309 test x
"$EDITOR" = xmissing
&& EDITOR
=`find_progs vim vi emacs nano`
310 test x
"$PAGER" = xmissing
&& PAGER
=`find_progs less more`
311 test x
"$AWK" = xmissing
&& AWK
=`find_progs gawk mawk nawk awk`
312 test x
"$DIFF" = xmissing
&& DIFF
=`find_progs diff`
313 if test x
"$COLORDIFF" = xmissing
; then
315 COLORDIFF
=`find_progs colordiff diff`
321 # If `less' is the $PAGER, turn on some useful options: -R to let it interpret
322 # some terminal codes (turns on colors and accentuated characters), -i to
323 # allow case-insensitive search if the pattern doesn't contain upper case
326 less |
'less '*) PAGER
="$PAGER -R -i";;
330 # return true if diffstat is in the PATH
333 if [ x
"$require_diffstat_cache" != x
]; then
334 return $require_diffstat_cache
336 if (echo | diffstat
) >/dev
/null
2>/dev
/null
; then :; else
337 warn
'diffstat is not installed on your system or not in your PATH.'
338 test -f /etc
/debian_version \
339 && notice
'you might want to `apt-get install diffstat`.'
340 require_diffstat_cache
=1
343 require_diffstat_cache
=0
348 # return 0 -> found a mailer
349 # return !0 -> no mailer found
350 # The full path to the program found is echo'ed on stdout.
354 PATH
="${PATH}${PATH_SEPARATOR}/sbin${PATH_SEPARATOR}/usr/sbin${PATH_SEPARATOR}/usr/libexec"
356 find_progs sendEmail sendmail
mail
363 # my_sendmail mail-file mail-subject mail-to [extra-headers]
364 # mail-to is a comma-separated list of email addresses.
365 # extra-headers is an optionnal argument and will be prepended at the
366 # beginning of the mail headers if the tool used to send mails supports it.
367 # The mail-file may also contain headers. They must be separated from the body
368 # of the mail by a blank line.
371 test -f "$1" || abort
"my_sendmail: Cannot find the mail file: $1"
372 test -z "$2" && warn
'my_sendmail: Empty subject.'
373 test -z "$3" && abort
'my_sendmail: No recipient specified.'
375 content_type
='Content-type: text/plain'
376 extra_headers
="X-Mailer: svn-wrapper v$version (g$revision)
378 Content-Transfer-Encoding: 7bit"
379 if test x
"$4" != x
; then
382 # Remove empty lines.
383 extra_headers
=`echo "$extra_headers" | sed '/^[ ]*$/d;s/^[ ]*//'`
386 # If we have a signature, add it.
387 if test -f ~
/.signature
; then
388 # But don't add it if it is already in the mail.
389 if grep -Fe "`cat ~/.signature`" "$1" >/dev
/null
; then :; else
390 echo '-- ' >>"$1" && cat ~
/.signature
>>"$1"
393 # VCS-compat: handle user option 'sign'.
394 if (grep '^sign: false' ~
/.vcs
) >/dev
/null
2>/dev
/null
; then :; else
395 ($GPG -h) >/dev
/null
2>/dev
/null
397 if [ -e ~
/.gnupg
] ||
[ -e ~
/.gpg
] ||
[ -e ~
/.pgp
] && [ $gpg_rv -lt 42 ]
399 if grep 'BEGIN PGP SIGNATURE' "$1" >/dev
/null
; then
400 notice
'message is already GPG-signed'
401 elif yesno
"Sign the mail using $GPG ?"; then
403 sed '1,/^$/d' "$1" >"$1.msg"
404 sed '1,/^$/!d' "$1" >"$1.hdr"
405 # Sign the message, keep only the PGP signature.
406 $GPG --clearsign <"$1.msg" >"$1.tmp" ||
{
407 rm -f "$1.msg" "$1.hdr" "$1.tmp"
408 abort
"\`$GPG' failed (r=$?)"
410 sed '/^--*BEGIN PGP SIGNATURE--*$/,/^--*END PGP SIGNATURE--*$/!d' \
413 boundary
="svn-wrapper-2-$RANDOM"
414 boundary
="$boundary$RANDOM"
415 # Prepend some stuff before the PGP signature.
418 content-type: application/pgp-signature; x-mac-type=70674453;
420 content-description: This is a digitally signed message part
421 content-disposition: inline; filename=PGP.sig
422 content-transfer-encoding: 7bit" |
cat - "$1.sig" >"$1.tmp"
423 mv -f "$1.tmp" "$1.sig"
425 # Append some stuff after the PGP signature.
427 --$boundary--" >>"$1.sig"
428 # Re-paste the headers before the signed body and prepend some stuff.
429 echo "This is an OpenPGP/MIME signed message (RFC 2440 and 3156)
431 Content-Transfer-Encoding: 8bit
432 Content-Type: text/plain; charset=iso-8859-1; format=flowed
434 |
cat "$1.hdr" - "$1.msg" "$1.sig" >"$1.tmp" \
435 && mv -f "$1.tmp" "$1"
436 content_type
="Content-Type: multipart/signed;\
437 protocol=\"application/pgp-signature\"; micalg=pgp-sha1;\
438 boundary=\"$boundary\""
440 rm -f "$1.tmp" "$1.sig" "$1.msg" "$1.hdr"
441 extra_headers
="$extra_headers
442 X-Pgp-Agent: `$GPG --version | sed q`"
446 extra_headers
="$extra_headers
449 mailer
=`require_mail`
450 if [ $?
-ne 0 ]; then
451 warn
'my_sendmail: No suitable mailer found.'
456 to
=`echo "$3" | sed 's/,//g'`
457 echo "$extra_headers" |
cat - "$1" |
$mailer $to;;
459 cat "$1" |
$mailer -s "$2" "$3";;
461 if [ x
"$SMTP" = x
]; then
462 warn
'my_sendmail: (sendEmail) please tell me the SMTP server to use'
463 printf 'STMP server: '
464 read SMTP || abort
'could not read the SMTP server'
465 notice
"hint: you can export SMTP=$SMTP if you don't want to be asked"
467 sendEmail
-f "$FULLNAME <$EMAIL>" -t "$3" -u "$2" \
468 -s "$SMTP" -o message-file
="$1";;
470 abort
'my_sendmail: Internal error.';;
477 my_url
='http://www.lrde.epita.fr/~sigoure/svn-wrapper'
478 # You can use https if you feel paranoiac.
480 echo ">>> Fetching svn-wrapper.sh from $my_url/svn-wrapper.sh"
482 # --------------------- #
483 # Fetch the new version #
484 # --------------------- #
486 tmp_me
=`get_unique_file_name "$TMPDIR/svn-wrapper.sh"`
487 if (wget
--help) >/dev
/null
2>/dev
/null
; then
488 wget
--no-check-certificate "$my_url/svn-wrapper.sh" -O "$tmp_me"
491 curl
--help >/dev
/null
2>/dev
/null
492 if [ $?
-gt 42 ]; then
493 abort
'Cannot find wget or curl.
494 How can I download any update without them?'
497 curl
--insecure "$my_url/svn-wrapper.sh" >"$tmp_me"
501 || abort
"Cannot find the copy of myself I downloaded in $tmp_me"
503 # ---------------------------------------- #
504 # Compare versions and update if necessary #
505 # ---------------------------------------- #
508 tmp_ver
=`sed '/^# $Id[:].*$/!d;
509 s/.*$Id[:] *\([a-f0-9]\{6\}\).*/\1/' "$tmp_me"`
510 test -z "$tmp_ver" && abort
"Cannot find the revision of $tmp_me"
511 if [ x
"$my_ver" != x
"$tmp_ver" ]; then # There IS an update...
512 echo "An update is available, r$tmp_ver (your version is r$my_ver)"
514 # Wanna see the diff?
515 if yesno
'Do you want to see the diff?'
517 (require_diffstat
&& diff -uw "$me" "$tmp_me" | diffstat
;
519 $COLORDIFF -uw "$me" "$tmp_me") |
$PAGER
523 if yesno
"Overwrite $me (r$my_ver) with $tmp_me (r$tmp_ver)?"; then
525 cp -p "$me" "$me.r$my_ver"
526 mv "$tmp_me" "$me" && exit 0
531 echo "You're already up to date [r$my_ver] :)"
536 # get_svn_diff_and_diffstat [files to diff]
537 # Helper for svn_commit
538 get_svn_diff_and_diffstat
()
541 svn_diff
=`git diff --ignore-all-space --no-color -B -C --cached`
542 svn_diff_stat
=`git diff --stat --ignore-all-space --no-color -B -C --cached`
544 # Ignore white spaces. Can't svn diff -x -w: svn 1.4 only.
545 svn_diff
=`svn_diffw "$@"`
546 test -z "$svn_diff" && svn_diff
=`$SVN diff "$@"`
547 if require_diffstat
; then
548 svn_diff_stat
=`echo "$svn_diff" | diffstat`
550 svn_diff_stat
='diffstat not available'
555 # Helper. Sets the variables repos_url, git_branch, git_head, repos_root,
556 # extra_repos_info and using_git_svn properly.
557 git_get_repos_info_
()
559 # FIXME: 1st commit: "fatal: bad default revision 'HEAD'" on stderr
560 git_config_list
=`git config -l`
562 case $git_config_list in #(
564 repos_url
=`echo "$git_config_list" \
565 | sed '/^svn-remote.svn.url=\(.*\)$/!d;s//\1/'`
568 *remote.origin.url
=*)
569 repos_url
=`echo "$git_config_list" \
570 | sed '/^remote.origin.url=\(.*\)$/!d;s//\1/'`
573 test -z "$repos_url" && repos_url
='(git:unknown)'
574 git_branch
=`git branch | awk '/^\*/ { print substr($0, 3) }'`
575 if [ x
"$git_branch" = x
'(no branch)' ]; then
576 yesno
'You are on a detached HEAD, do you really want to continue?' \
579 git_head
=`git rev-list --pretty=format:%h HEAD --max-count=1 | sed '1d;q'`
580 extra_repos_info
="Git branch: $git_branch (HEAD: $git_head)"
581 repos_root
=$repos_url
584 # git_warn_missing_prop(PROP-NAME, SAMPLE-VALUE)
585 git_warn_missing_prop
()
587 if $using_git_svn; then
588 warn
"No $1 property set for this repository.
589 This is a git-svn repository, so you can set it in an SVN working copy:
591 Otherwise you can just set the property in your git-svn repository:
592 git config svnw.$1 $2"
594 warn
"No $1 property set for this repository.
595 You can set it like this:
596 git config svnw.$1 $2"
600 # Helper. Find the `mailto' property, be it an SVN property or a git-config
601 # option. Relies on the value of $change_log_dir and sets the values of
602 # $mailto (the value of the `mailto' property) and $to (a string to append in
603 # templates that contains the `To:' line, or an empty string if no mail must
605 get_mailto_property
()
607 test -d "$change_log_dir" || abort
'Internal error in get_mailto_property:
608 $change_log_dir not pointing to a directory'
610 mailto
=`git config svnw.mailto`
611 if [ x
"$mailto" = x
] \
612 && [ -f "$change_log_dir/.git/svn/git-svn/unhandled.log" ]
614 mailto
=`grep mailto "$change_log_dir/.git/svn/git-svn/unhandled.log"`
615 sed_tmp
='$!d;s/^.*+dir_prop: . mailto //;s/%40/@/g;s/%2C/,/g;s/%20/ /g;'
616 mailto
=`echo "$mailto" | sed "$sed_tmp"`
618 if [ x
"$mailto" = x
]; then
619 git_warn_missing_prop
'mailto' 'maintainer1@foo.com,maint2@bar.com'
622 mailto
=`$SVN propget mailto "$change_log_dir"`
625 if [ x
"$mailto" = x
]; then
626 test x
$new_user = xyes \
627 && warn
"no svn property mailto found in $change_log_dir
628 You might want to set default email adresses using:
629 svn propset mailto 'somebody@mail.com, foobar@example.com'\
631 # Try to be VCS-compatible and find a list of mails in a *.rb.
632 if [ -d "$change_log_dir/vcs" ]; then
633 mailto
=`grep '.@.*\..*' "$change_log_dir"/vcs/*.rb \
635 | sed 's/^.*[["]\([^["]*@[^]"]*\)[]"].*$/\1/' | xargs`
636 test x
"$mailto" != x
&& test x
$new_user = xyes \
637 && notice
"VCS-compat: found mailto: $mailto
638 in " "$change_log_dir"/vcs
/*.rb
640 fi # end guess mailto
642 # Ensure that emails are comma-separated.
643 mailto
=`echo "$mailto" | sed 's/[ ;]/,/g' | tr -s ',' | sed 's/,/, /g'`
646 if [ x
"$mailto" != xdont_send_mails
] && [ x
"$mailto" != xnothx
]; then
654 # ------------------------------- #
655 # Hooks for standard SVN commands #
656 # ------------------------------- #
658 # svn_commit [args...]
659 # Here is how the commit process goes:
661 # First we look in the arguments passed to commit:
662 # If there are some files or paths, the user wants to commit these only. In
663 # this case, we must search for ChangeLogs from these paths. We might find
664 # more than one ChangeLog, in this case the user will be prompted to pick up
666 # Otherwise (no path passed in the command line) the user just wants to
667 # commit the current working directory.
668 # In any case, we schedule "ChangeLog" for commit.
670 # Alright now that we know which ChangeLog to use, we look in the ChangeLog's
671 # directory if there is a ",svn-log" file which would mean that a previous
672 # commit didn't finish successfully. If there is such a file, the user is
673 # prompted to know whether they want to resume that commit or simply start a
675 # When the user wants to resume a commit, the ",svn-log" file is loaded and we
676 # retrieve the value of "$@" that was saved in the file.
677 # Otherwise we build a template ChangeLog entry.
678 # Then we open the template ChangeLog entry with $EDITOR so that the user
680 # Finally, we commit.
681 # Once the commit is sent, we ask the server to know which revision was
682 # commited and we also retrieve the diff. We then send a mail with these.
688 use_log_message_from_file
=false
691 # Check if the user passed some paths to commit explicitly
692 # because in this case we must add the ChangeLog to the commit and search
693 # the ChangeLog from the dirname of that file.
694 i
=0; search_from
=; add_changelog
=false
; extra_files
=
695 while [ $i -lt $# ]; do
709 test -z "$1" && abort
"$arg needs an argument"
710 test -r "$1" || abort
"'$1' does not seem to be readable"
711 test -w "$1" || abort
"'$1' does not seem to be writable"
712 test -d "$1" && abort
"'$1' seems to be a directory"
713 use_log_message_from_file
=:
714 log_message_to_use
=$1
719 # If the argument is a valid path: add the ChangeLog in the list of
721 if test -e "$arg"; then
723 if test -d "$arg"; then
726 search_from_add
=`dirname "$arg"`
728 search_from
="$search_from:$search_from_add"
731 set dummy
"$@" "$arg"
735 if $add_changelog; then :; else
736 # There is no path/file in the command line: the user wants to commit the
737 # current directory. Make it explicit now:
740 search_from
=`echo "$search_from" | sed 's/^://; s/^$/./'`
742 # ----------------- #
743 # Find ChangeLog(s) #
744 # ----------------- #
746 nb_chlogs
=0; change_log_dirs
=
747 save_IFS
=$IFS; IFS
=':'
748 for dir
in $search_from; do
750 $use_changelog ||
break
751 test -z "$dir" && dir
='.'
752 # First: come back to the original place
753 cd "$here" || abort
"Cannot cd to $here"
754 cd "$dir" ||
continue # Then: Enter $dir (which can be a relative path)
756 while [ $found -eq 0 ]; do
757 this_chlog_dir
=`pwd -P`
758 if [ -f .
/ChangeLog
]; then
760 nb_chlogs
=$
(($nb_chlogs + 1))
761 change_log_dirs
="$change_log_dirs:$this_chlog_dir"
765 # Stop searching when in / ... hmz :P
766 test x
"$this_chlog_dir" = x
/ && break
767 done # end while: did we find a ChangeLog
768 done # end for: find ChangeLogs in $search_from
769 if [ $nb_chlogs -gt 0 ]; then
770 change_log_dirs
=`echo "$change_log_dirs" | sed 's/^://' | tr ':' '\n' \
772 nb_chlogs
=`echo "$change_log_dirs" | wc -l`
775 # Did we find a ChangeLog? More than one?
776 if [ $nb_chlogs -eq 0 ] && $use_changelog; then
777 if yesno
'svn-wrapper: Error: Cannot find a ChangeLog file!
778 You might want to create an empty one (eg: `touch ChangeLog` where appropriate)
779 Do you want to proceed without using a ChangeLog?'; then
786 elif [ $nb_chlogs -gt 1 ]; then
787 notice
"$nb_chlogs ChangeLogs were found, pick up one:"
790 for a_chlog_dir
in $change_log_dirs; do
792 echo "$i. $a_chlog_dir/ChangeLog"
794 printf "Which ChangeLog do you want to use? [1-$i] "
795 read chlog_no || abort
'Cannot read answer on stdin.'
798 *[^
0-9]*) abort
"Invalid ChangeLog number: $chlog_no"
800 test "$chlog_no" -le $i || abort
"Invalid ChangeLog number: $chlog_no
802 test "$chlog_no" -ge 1 || abort
"Invalid ChangeLog number: $chlog_no
804 change_log_dir
=`echo "$change_log_dirs" | tr ':' '\n' | sed "${chlog_no}!d"`
805 else # Only one ChangeLog found
806 if $use_changelog; then
807 change_log_dir
=$change_log_dirs
808 notice
"using $change_log_dir/ChangeLog"
812 if $use_changelog; then
813 test -f "$change_log_dir/ChangeLog" \
814 || abort
"No such file or directory: $change_log_dir/ChangeLog"
815 # Now we can safely schedule the ChangeLog for the commit.
816 extra_files
="$extra_files:$change_log_dir/ChangeLog"
818 change_log_dir
='.' # Hack. FIXME: Does this work in all cases?
821 if [ -d "$change_log_dir/.git" ] ||
$git_mode; then
826 svn_st_tmp
=`$SVN status "$change_log_dir"`
828 # Warn for files that are not added in the repos.
829 conflicts
=`echo "$svn_st_tmp" | sed '/^ *$/d;
833 if test x
"$conflicts" != x
; then
834 warn
"make sure you don't want to \`svn add'
835 any of the following files before committing:"
836 echo "$conflicts" |
sed "$sed_svn_st_color"
837 printf 'Type [ENTER] to continue :)' && read chiche_is_gay
840 # If there are changes in an svn:externals, advise the user to commit that
842 changed_externals
=`echo "$svn_st_tmp" | $AWK \
851 BEGIN { this_ext = ""; ext = 0; ext_modified = 0; }
852 /^Performing status on external/ {
854 sub(/.* at ./, ""); sub(/.$/, ""); this_ext = $0;
857 /^[ADMR]/ { ext_modified = ext; printext(); }
858 /^.[M]/ { ext_modified = ext; printext(); }
859 END { exit ext_modified; }'`
860 if [ $?
-ne 0 ]; then
861 warn
"the following external items have local modifications:
863 yesno
"You are advised to commit them separately first. Continue anyway?" \
867 # Detect unresolved conflicts / missing files.
868 conflicts
=`echo "$svn_st_tmp" | sed '/^[C!]/!d'`
869 test x
"$conflicts" != x
&& abort
"there are unresolved conflicts (\`C')
870 and/or missing files (\`!'):
873 svn_info_tmp
=`$SVN info "$change_log_dir"`
874 test $?
-ne 0 && abort
"Failed to get svn info on $change_log_dir"
875 repos_root
=`echo "$svn_info_tmp" | sed '/^Repository Root: /!d;s///'`
876 repos_url
=`echo "$svn_info_tmp" | sed '/^URL: /!d;s///'`
877 # It looks like svn <1.3 didn't display a "Repository Root" entry.
878 test -z "$repos_root" && repos_root
=$repos_url
887 # VCS-compat: handle user option 'new_user'
889 grep '^new_user: false' ~
/.vcs
>/dev
/null
2>/dev
/null
&& new_user
='no'
892 tmp_log
="$change_log_dir/,svn-log"
893 $use_log_message_from_file \
894 && tmp_log
=$log_message_to_use \
895 && edit_changelog
=false
897 if [ -f "$tmp_log" ] \
898 && { $use_log_message_from_file \
899 || yesnewproceed
"It looks like the last commit did not\
900 terminate successfully.
901 Would you like to resume it or proceed immediately?"; }; then
902 case $yesnoproceed_res in
903 *proceed
) edit_changelog
=false
;;
905 if test x
"$yesnoproceed_res" = xupproceed
; then
906 svn_update
"$@" || abort
'update failed'
909 internal_tags
=`sed '/^--- Internal stuff, DO NOT change please ---$/,$!d' \
911 saved_args
=`echo "$internal_tags" | sed '/^args: */!d;s///'`
912 extra_files
=`echo "$internal_tags" | sed '/^extra_files: */!d;s///'`
913 if [ x
"$saved_args" != x
]; then
914 if [ x
"$*" != x
] && [ x
"$saved_args" != x
"$*" ]; then
915 warn
"overriding arguments:
916 you invoked $me with the following arguments: $@
917 they have been replaced by these: $saved_args"
918 set dummy
$saved_args
921 notice
"setting the following arguments: $saved_args"
922 set dummy
$saved_args
925 elif [ x
"$*" != x
]; then
926 warn
"overriding arguments:
927 you invoked $me with the following arguments: $@
928 they have been dropped"
935 if [ $git_mode ]; then
936 (cd $change_log_dir && git add
-v -u) || abort
'`git add -v -u` failed'
942 get_svn_diff_and_diffstat
"$@"
944 # Update the file with the new diff/diffstat in case it changed.
949 /^--This line, and those below, will be ignored--$/ {
952 /^ Your ChangeLog entry will appear here\.$/ {
953 if (tlatbwbi_seen) ycewah_seen = 1;
956 if (ycewah_seen != 2) print;
957 if (ycewah_seen == 1) ycewah_seen = 2;
960 if (tlatbwbi_seen == 0)
962 print "--This line, and those below, will be ignored--\n\n" \
963 " Your ChangeLog entry will appear here.";
965 }' "$tmp_log" >"$tmp_log.tmp"
972 $internal_tags" >>"$tmp_log.tmp"
973 mv -f "$tmp_log.tmp" "$tmp_log" || abort
"failed to write '$tmp_log'"
975 else # Build the template message.
977 # ------------------------------------ #
978 # Gather info for the template message #
979 # ------------------------------------ #
982 projname
=`git config svnw.project`
983 if [ x
"$projname" = x
] \
984 && [ -f "$change_log_dir/.git/svn/git-svn/unhandled.log" ]
986 projname
=`grep project "$change_log_dir/.git/svn/git-svn/unhandled.log"`
987 sed_tmp
='$!d;s/^.*+dir_prop: . project //'
988 projname
=`echo "$projname" | sed "$sed_tmp"`
990 if [ x
"$projname" = x
]; then
991 git_warn_missing_prop
'project' 'myproj'
994 projname
=`$SVN propget project "$change_log_dir"`
996 # Try to be VCS-compatible and find a project name in a *.rb.
997 if [ x
"$projname" = x
] && [ -d "$change_log_dir/vcs" ]; then
998 projname
=`sed '/common_commit/!d;s/.*"\(.*\)<%= rev.*/\1/' \
999 "$change_log_dir"/vcs/*.rb`
1000 test x
"$projname" != x
&& test x
$new_user = xyes \
1001 && notice
"VCS-compat: found project name: $projname
1002 in " "$change_log_dir"/vcs
/*.rb
1004 test x
"$projname" != x
&& projname
=`echo "$projname" | sed '/[^ ]$/s/$/ /'`
1008 test -z "$FULLNAME" && FULLNAME
='Type Your Name Here' \
1009 && warn_env FULLNAME
1010 test -z "$EMAIL" && EMAIL
='your.mail.here@FIXME.com' && warn_env EMAIL
1013 if $git_commit_all; then
1014 (cd $change_log_dir && git add
-v -u) || abort
'`git add -v -u` failed'
1016 my_git_st
=`git diff -C --raw --cached`
1017 test $?
-eq 0 || abort
'git diff failed'
1018 # Format: ":<old_mode> <new_mode> <old_sha1> <new_sha1> <status>[
1019 # <similarity score>]\t<file-name>"
1020 change_log_files
=`echo "$my_git_st" | sed '
1023 s/^:[0-7 ]* [0-9a-f. ]* M[^ ]* \(.*\)$/ * \1: ./; t
1024 s/^:[0-7 ]* [0-9a-f. ]* A[^ ]* \(.*\)$/ * \1: New./; t
1025 s/^:[0-7 ]* [0-9a-f. ]* D[^ ]* \(.*\)$/ * \1: Remove./; t
1026 s/^:[0-7 ]* [0-9a-f. ]* R[^ ]* \([^ ]*\) \(.*\)$/ * \2: Rename from \1./;t
1027 s/^:[0-7 ]* [0-9a-f. ]* C[^ ]* \([^ ]*\) \(.*\)$/ * \2: Copy from \1./;t
1028 s/^:[0-7 ]* [0-9a-f. ]* T[^ ]* \(.*\)$/ * \1: ./; t
1029 s/^:[0-7 ]* [0-9a-f. ]* X[^ ]* \(.*\)$/ * \1: ???./; t
1030 s/^:[0-7 ]* [0-9a-f. ]* U[^ ]* \(.*\)$/ * \1: UNMERGED./; t
1033 # --ignore-externals appeared after svn 1.1.1
1034 my_svn_st
=`$SVN status --ignore-externals "$@" \
1035 || $SVN status "$@" | sed '/^Performing status on external/ {
1039 # Files to put in the ChangeLog entry.
1040 change_log_files
=`echo "$my_svn_st" | sed '
1043 s/^M......\(.*\)$/ * \1: ./; t
1044 s/^A......\(.*\)$/ * \1: New./; t
1045 s/^D......\(.*\)$/ * \1: Remove./; t
1049 if [ x
"$change_log_files" = x
]; then
1050 yesno
'Nothing to commit, continue anyway?' ||
return 1
1053 change_log_files
=`echo "$change_log_files" | sort -u`
1055 get_svn_diff_and_diffstat
"$@"
1057 # Get any older svn-log out of the way.
1058 test -f "$tmp_log" && mv "$tmp_log" `get_unique_file_name "$tmp_log"`
1059 # If we can't get an older svn-log out of the way, find a new name...
1060 test -f "$tmp_log" && tmp_log
=`get_unique_file_name "$tmp_log"`
1061 if [ x
$new_user = no
]; then
1062 commit_instructions
='
1064 - Fill the ChangeLog entry.
1065 - If you feel like, write a comment in the "Comment:" section.
1066 This comment will only appear in the email, not in the ChangeLog.
1067 By default only the location of the repository is in the comment.
1068 - Some tags will be replaced. Tags are of the form: <TAG>. Unknown
1069 tags will be left unchanged.
1070 - The tag <REV> may only be used in the Subject.
1071 - Your ChangeLog entry will be used as commit message for svn.'
1073 commit_instructions
=
1076 $git_mode && r_before_rev
=
1077 test -z "$extra_repos_info" || extra_repos_info
="
1080 --You must fill this file correctly to continue-- -*- vcs -*-
1082 Subject: ${projname}$r_before_rev<REV>: <TITLE>
1083 From: $FULLNAME <$EMAIL>$to
1086 URL: $repos_url$extra_repos_info
1090 <YYYY>-<MM>-<DD> $FULLNAME <$EMAIL>
1095 --This line, and those below, will be ignored--
1096 $commit_instructions
1097 --Preview of the message that will be sent--
1099 URL: $repos_url$extra_repos_info
1100 Your comments (if any) will appear here.
1103 $YYYY-$MM-$DD $FULLNAME <$EMAIL>
1105 Your ChangeLog entry will appear here.
1110 $svn_diff" >"$tmp_log"
1113 --- Internal stuff, DO NOT change please ---
1114 args: $@" >>"$tmp_log"
1115 echo "extra_files: $extra_files
1116 vi: ft=diff:noet:tw=76:" >>"$tmp_log"
1118 fi # end: if svn-log; then resume? else create template
1119 $edit_changelog && $EDITOR "$tmp_log"
1121 # ------------------ #
1122 # Re-"parse" the log #
1123 # ------------------ #
1125 # hmz this section is a bit messy...
1126 # helper string... !@#$%* escaping \\\\\\...
1127 sed_escape
='s/\\/\\\\/g;s/@/\\@/g;s/&/\\\&/g'
1128 sed_eval_tags
="s/<MM>/$MM/g; s/<DD>/$DD/g; s/<YYYY>/$YYYY/g"
1129 full_log
=`sed '/^--*This line, and those below, will be ignored--*$/,$d;
1130 /^--You must fill this/d' "$tmp_log"`
1131 chlog_entry
=`echo "$full_log" | sed '/^ChangeLog:$/,$!d; //d'`
1132 ensure_not_empty
'ChangeLog entry' "$chlog_entry"
1133 full_log
=`echo "$full_log" | sed '/^ChangeLog:$/,$d'`
1134 mail_comment
=`echo "$full_log" | sed '/^Comment:$/,$!d; //d'`
1135 full_log
=`echo "$full_log" | sed '/^Comment:$/,$d'`
1136 mail_title
=`echo "$full_log" | sed '/^Title: */!d;s///;'`
1137 ensure_not_empty
'commit title' "$mail_title"
1138 # Add a period at the end of the title.
1139 mail_title
=`echo "$mail_title" | sed -e '/ *[.!?]$/!s/ *$/./' \
1140 -e "$sed_eval_tags; $sed_escape"`
1141 sed_eval_tags
="$sed_eval_tags; s@<TITLE>\\.*@$mail_title@g"
1142 mail_comment
=`echo "$mail_comment" | sed "$sed_eval_tags"`
1143 raw_chlog_entry
=$chlog_entry # ChangeLog entry without tags expanded
1144 chlog_entry
=`echo "$chlog_entry" | sed "$sed_eval_tags; 1{
1147 mail_subject
=`echo "$full_log" | sed '/^Subject: */!d;s///'`
1148 ensure_not_empty
'mail subject' "$mail_subject"
1149 mail_to
=`echo "$full_log" | sed '/^To:/!d'`
1151 if test x
"$mail_to" = x
; then
1154 mail_to
=`echo "$mail_to" | sed 's/^To: *//;s///'`
1155 # If there is a <MAILTO> in the 'To:' line, we must expand it.
1159 # Are we meant to send a mail?
1161 '') # No, don't send a mail.
1165 *) # Yes, send a mail.
1166 mail_to
=`echo "$mail_to" | sed "s#<MAILTO>#$mailto#g"`
1170 $send_a_mail && ensure_not_empty
'"To:" field of the mail' "$mail_to"
1173 test -z "$FULLNAME" && warn_env FULLNAME
&& FULLNAME
=$USER
1174 test -z "$EMAIL" && warn_env EMAIL
&& EMAIL
=$USER
1175 myself
=`echo "$FULLNAME <$EMAIL>" | sed "$sed_escape"`
1176 mail_from
=`echo "$full_log" | sed "/^From: */!d;s///;s@<MYSELF>@$myself@g"`
1177 ensure_not_empty
'"From:" field of the mail' "$mail_from"
1179 # ------------------------------------ #
1180 # Sanity checks on the ChangeLog entry #
1181 # ------------------------------------ #
1183 if echo "$chlog_entry" |
grep '<REV>' >/dev
/null
; then
1184 warn
'Using the tag <REV> anywhere else than in the Subject is deprecated.'
1185 yesno
'Continue anyway?' ||
return 1
1188 if echo "$chlog_entry" |
grep ': \.$' >/dev
/null
; then
1189 warn
'It looks like you did not fill all entries in the ChangeLog:'
1190 echo "$chlog_entry" |
grep ': \.$'
1191 yesno
'Continue anyway?' ||
return 1
1194 if echo "$chlog_entry" |
grep '^--* Internal stuff' >/dev
/null
; then
1195 warn
"It looks like you messed up the delimiters and I did not properly
1196 find your ChangeLog entry. Here it is, make sure it is correct:"
1198 yesno
'Continue anyway?' ||
return 1
1201 if echo "$chlog_entry" |
grep -i 'dont[^a-z0-9]' >/dev
/null
; then
1202 warn
"Please avoid typos such as ${lred}dont$std instead of\
1203 ${lgreen}don't$std:"
1204 echo "$chlog_entry" |
grep -n -i 'dont[^a-z0-9]' \
1205 |
sed "s/[dD][oO][nN][tT]/$lred&$std/g"
1206 yesno
'Continue anyway?' ||
return 1
1209 if echo "$chlog_entry" |
grep -i 'cant[^a-z0-9]' >/dev
/null
; then
1210 warn
"Please avoid typos such as ${lred}cant$std instead of\
1211 ${lgreen}can't$std:"
1212 echo "$chlog_entry" |
grep -n -i 'cant[^a-z0-9]' \
1213 |
sed "s/[cC][aA][nN][tT]/$lred&$std/g"
1214 yesno
'Continue anyway?' ||
return 1
1217 if echo "$chlog_entry" |
grep '^.\{80,\}' >/dev
/null
; then
1218 warn
'Please avoid long lines in your ChangeLog entry (80 columns max):'
1219 echo "$chlog_entry" |
grep '^.\{80,\}'
1220 yesno
'Continue anyway?' ||
return 1
1223 if echo "$mail_title" |
grep -i '^[a-z][a-z]*ed[^a-z]' >/dev
/null
; then
1224 warn
'ChangeLog entries should be written in imperative form:'
1225 echo "$mail_title" |
grep -i '^[a-z][a-z]*ed[^a-z]' \
1226 |
sed "s/^\\([a-zA-Z][a-zA-Z]*ed\\)\\([^a-zA-Z]\\)/$lred\\1$std\\2/"
1227 yesno
'Continue anyway?' ||
return 1
1230 case $chlog_entry in
1231 *@FIXME
* |
*[tT
]ype
' '[yY
]our
' '[nN
]ame
*)
1232 warn
'Suspicious ChangeLog entry:'
1233 echo "$chlog_entry" |
grep -i '@FIXME\|[tT]ype [yY]our [nN]ame'
1234 yesno
'Continue anyway?' ||
return 1
1239 *@FIXME
* |
*[tT
]ype
' '[yY
]our
' '[nN
]ame
*)
1240 warn
"Suspicious the From field: $mail_from"
1241 yesno
'Continue anyway?' ||
return 1
1245 # Check whether the user passed -m | --message
1247 while [ $i -lt $# ]; do
1249 # This is not really a reliable way of knowing whether -m | --message was
1250 # passed but hum... Let's assume it'll do :s
1251 if [ x
"$arg" = 'x-m' ] ||
[ x
"$arg" = 'x--message' ]; then
1255 set dummy
"$@" "$arg"
1259 if [ x
"$my_message" = x
]; then
1260 # The title must not be indented in the commit message and must be
1261 # followed by a blank line. This yields much better results with most
1262 # VC-viewer (especially for Git but including for SVN, such as Trac for
1263 # instance). We assume that the title will always be on the 1st line.
1264 sed_git_title
="1s@^[ ]*<TITLE>\\.*@$mail_title\\
1266 # First, remove empty lines at the beginning, if any.
1267 # Remove also the date information (useless in commit messages)
1268 my_message
=`echo "$raw_chlog_entry" \
1270 /^<YYYY>-<[MD][MD]>-<[DM][DM]>/d
1271 /^[1-9][0-9][0-9][0-9]-[0-9][0-9]*-[0-9][0-9]*/d
1275 | sed -e "$sed_git_title" \
1276 -e "$sed_eval_tags; 1{
1280 notice
'you are overriding the commit message.'
1283 # Show suspicious whitespace additions with Git.
1284 $git_mode && git
diff --cached --check
1287 proposal_file
=',proposal'
1288 test -f "$proposal_file" \
1289 && proposal_file
=`get_unique_file_name "$proposal_file"`
1290 sed_tmp
='s/<REV>/???/g;s/\([^.]\) *\.$/\1/'
1291 mail_subject
=`echo "$mail_subject" | sed "$sed_eval_tags; $sed_tmp"`
1293 if [ x
"$mailto" != xdont_send_mails
] && [ x
"$mailto" != xnothx
]; then
1299 Subject: $mail_subject
1309 $svn_diff" >"$proposal_file"
1310 notice
"A proposal of your commit was left in '$proposal_file'"
1317 || notice
'You are using git, unlike SVN, do not forget to git add your
1321 # Change edit_changelog so that we're asking the confirmation below.
1322 $use_log_message_from_file && edit_changelog
=: \
1323 && notice
"You are about to commit the following change:
1328 yesno
'Are you sure you want to commit?' \
1332 # Add the ChangeLog entry
1333 old_chlog
=`get_unique_file_name "$change_log_dir/ChangeLog.old"`
1334 mv "$change_log_dir/ChangeLog" "$old_chlog" || \
1335 abort
'Could not backup ChangeLog'
1336 trap "echo SIGINT; mv \"$old_chlog\" \"$change_log_dir/ChangeLog\";
1338 echo "$chlog_entry" >"$change_log_dir/ChangeLog"
1339 echo >>"$change_log_dir/ChangeLog"
1340 cat "$old_chlog" >>"$change_log_dir/ChangeLog"
1342 # Add extra files such as cwd or ChangeLog to the commit.
1343 tmp_sed
='s/ /\\ /g' # Escape spaces for the shell.
1344 if $git_mode && $use_changelog; then
1345 # Schedule the ChangeLog for the next commit
1346 (cd "$change_log_dir" && git add ChangeLog
) \
1347 || abort
'failed to git add the ChangeLog'
1350 extra_files
=`echo "$extra_files" | sed "$tmp_sed" | tr ':' '\n'`
1353 # Always sign the commits with Git (but not with git-svn).
1354 $using_git_svn ||
$git_mode && set dummy
--signoff "$@" && shift
1356 # Update the Git index if necessary (just in case the user changed his
1357 # working copy in the mean time)
1358 if $git_mode && $git_commit_all; then
1359 (cd $change_log_dir && git add
-v -u) || abort
'`git add -v -u` failed'
1362 # --Commit-- finally! :D
1363 $SVN commit
-m "$my_message" "$@" $extra_files ||
{
1365 mv "$old_chlog" "$change_log_dir/ChangeLog"
1366 abort
"Commit failed, $SVN returned $svn_commit_rv"
1369 printf 'Getting the revision number... '
1371 REV
=`git rev-list --pretty=format:%h HEAD --max-count=1 | sed '1d;q'`
1373 svn_info_tmp
=`$SVN info "$change_log_dir/ChangeLog"`
1374 REV
=`echo "$svn_info_tmp" | sed '/^Revision: /!d;s///'`
1375 test -z "$REV" && REV
=`echo "$svn_info_tmp" \
1376 | sed '/^Last Changed Rev: /!d;s///'`
1378 test -z "$REV" && abort
'Cannot detect the current revision.'
1381 # Let's make sure we have the real diff by asking the ChangeSet we've just
1382 # committed to the server.
1384 # Backup the old stuff in case we fail to get the real diff from the server
1385 # for some reason...
1386 save_svn_diff
=$svn_diff
1387 save_svn_diff_stat
=$svn_diff_stat
1390 svn_diff
=`git diff --ignore-all-space --no-color -C 'HEAD^' HEAD`
1391 svn_diff_stat
=`git diff --stat --ignore-all-space --no-color -C 'HEAD^' HEAD`
1393 notice
'Do not forget to use `git-svn dcommit` to push your commits in SVN'
1395 # Fetch the ChangeSet and filter out the ChangeLog entry. We don't use
1396 # svn diff -c because this option is not portable to older svn versions.
1397 REV_MINUS_ONE
=$
(($REV - 1))
1398 svn_diff
=`svn_diffw -r"$REV_MINUS_ONE:$REV" "$repos_root" \
1399 | $AWK '/^Index: / { if (in_chlog) in_chlog = 0; }
1400 /^Index: .*ChangeLog$/ { in_chlog = 1 }
1401 { if (!in_chlog) print }'`
1402 if [ x
"$svn_diff" = x
]; then
1403 svn_diff
=$save_svn_diff
1404 svn_diff_stat
=$save_svn_diff_stat
1406 if require_diffstat
; then
1407 svn_diff_stat
=`echo "$svn_diff" | diffstat`
1409 svn_diff_stat
='diffstat not available'
1414 # Expand <REV> and remove the final period from the mail subject if there is
1416 sed_tmp
="s/<REV>/$REV/g;"'s/\([^.]\) *\.$/\1/'
1417 mail_subject
=`echo "$mail_subject" | sed "$sed_eval_tags; $sed_tmp"`
1419 mail_file
=`get_unique_file_name "$change_log_dir/+mail"`
1423 Subject: $mail_subject
1433 $svn_diff" |
sed 's/^\.$/ ./' >"$mail_file"
1434 # We change lines with only a `.' because they could mean "end-of-mail"
1437 if $send_a_mail; then
1438 trap 'echo SIGINT; exec < /dev/null' $SIGINT
1439 # FIXME: Move the mail to the +committed right now, in case the user
1440 # CTLR+C the mail-sending-thing, so that the mail will be properly saved
1442 my_sendmail
"$mail_file" "$mail_subject" "$mail_to" \
1443 "X-svn-url: $repos_root
1444 X-svn-revision: $REV"
1445 fi # end do we have to send a mail?
1448 save_mail_file
=`echo "$mail_file" | sed 's/+//'`
1449 mkdir
-p "$change_log_dir/+committed" \
1450 || warn
"Couldn't mkdir -p $change_log_dir/+committed"
1451 if [ -d "$change_log_dir/vcs" ] \
1452 ||
[ -d "$change_log_dir/+committed" ]
1454 mkdir
-p "$change_log_dir/+committed/$REV" \
1455 && mv "$mail_file" "$change_log_dir/+committed/$REV/mail"
1457 return $svn_commit_rv
1460 # svn_diffw [args...]
1463 # Ignore white spaces.
1467 # Extra args for diff (with SVN only, Git has a decent diff).
1470 # If $PAGER contains -R, it most likely understand colors and stuff, so we
1471 # can use it to show the colored diff.
1472 diff_cmd_
='$SVN diff --no-diff-deleted --diff-cmd $DIFF -x $svn_diffarg "$@"'
1473 case $diff_interactive:$PAGER in #(
1474 yes:*-R*) eval "$diff_cmd_" |
$PAGER;;
1475 *) eval "$diff_cmd_"
1480 # svn_diffw [args...]
1483 # Ignore white spaces.
1485 svn_diff
--ignore-all-space "$@"
1492 # svn_mail REV [mails...]
1495 test $# -lt 1 && abort
"Not enough arguments provided;
1496 Try 'svn help mail' for more info."
1500 REV
=`git rev-list --pretty=format:%h 'HEAD^' --max-count=1 | sed '1d;q'`
1502 REV
=`svn_revision || abort 'Cannot get current revision number'`
1503 test -z "$REV" && abort
'Cannot get current revision number'
1504 if [ "$REV" -lt 1 ]; then
1505 abort
'No previous revision.'
1511 REV
=`svn_revision || abort 'Cannot get current revision number'`
1512 test -z "$REV" && abort
'Cannot get current revision number'
1518 found_committed
=0; found
=0
1519 while [ $found -eq 0 ]; do
1520 this_chlog_dir
=`pwd -P`
1521 if [ -d .
/+committed
]; then
1523 if [ -d .
/+committed
/$REV ]; then
1531 # Stop searching when in / ... hmz :P
1532 test x
`pwd` = x
/ && break
1534 if [ $found -eq 0 ]; then
1535 if [ $found_committed -eq 0 ]; then
1536 abort
'Could not find the +committed directory.'
1538 abort
"Could not find the revision $REV in +committed."
1540 abort
'Internal error (should never be here).'
1543 mail_file
=; subject
=; to
=
1544 if [ -f .
/+committed
/$REV/mail ]; then
1545 # svn-wrapper generated file
1546 mail_file
="./+committed/$REV/mail"
1547 subject
=`sed '/^Subject: /!d;s///' $mail_file | sed '1q'`
1548 to
=`sed '/^To: /!d;s///' $mail_file | sed '1q'`
1549 elif [ -f .
/+committed
/$REV/,iform
] && [ -f .
/+committed
/$REV/,message
]
1551 # VCS-generated file
1552 subject
=`sed '/^Subject: /!d;s///;s/^"//;s/"$//' ./+committed/$REV/,iform \
1553 | sed "s/<%= *rev *%>/$REV/g"`
1554 to
=`sed '/^To:/,/^[^-]/!d' ./+committed/$REV/,iform | sed '1d;s/^- //;$d' \
1555 | xargs | sed 's/ */, /g'`
1556 mail_file
=`get_unique_file_name "$TMPDIR/mail.r$REV"`
1557 echo "From: $FULLNAME <$EMAIL>
1560 " >"$mail_file" || abort
"Cannot create $mail_file"
1561 cat .
/+committed
/$REV/,message
>>"$mail_file" \
1562 || abort
"Cannot copy ./+committed/$REV/,message in $mail_file"
1564 abort
"Couldn't find the mail to re-send in `pwd`/+committed/$REV"
1566 if [ $# -gt 0 ]; then
1567 to
=`echo "$*" | sed 's/ */, /g'`
1570 test -z "$to" && abort
'Cannot find the list of recipients.
1571 Please report this bug.'
1572 test -z "$subject" && abort
'Cannot find the subject of the mail.
1573 Please report this bug.'
1575 if yesno
"Re-sending the mail of r$REV
1578 Are you sure?"; then :; else
1585 svn_info_tmp
=`$SVN info`
1586 test $?
-ne 0 && abort
"Failed to get svn info on `pwd`"
1587 repos_root
=`echo "$svn_info_tmp" | sed '/^Repository Root: /!d;s///'`
1588 repos_url
=`echo "$svn_info_tmp" | sed '/^URL: /!d;s///'`
1589 # It looks like svn <1.3 didn't display a "Repository Root" entry.
1590 test -z "$repos_root" && repos_root
=$repos_url
1593 my_sendmail
"$mail_file" "$subject" "$to" \
1594 "X-svn-url: $repos_url
1595 X-svn-revision: $REV"
1601 echo "Using svn-wrapper v$version-g$revision (C) SIGOURE Benoit [GPL]"
1604 # has_prop prop-name [path]
1605 # return value: 0 -> path has the property prop-name set.
1606 # 1 -> path has no property prop-name.
1610 hp_plist
=`$SVN proplist "$2"`
1611 test $?
-ne 0 && return 2
1612 hp_res
=`echo "$hp_plist" | sed "/^ *$1\$/!d"`
1613 test -z "$hp_res" && return 1
1617 # svn_propadd prop-name prop-val [path]
1621 abort
'propadd is only for SVN, not for Git.'
1624 && abort
'Not enough arguments provided;
1625 try `svn help propadd` for more info'
1627 && abort
'Too many arguments provided;
1628 try `svn help propadd` for more info'
1631 test -z "$path" && path
='.' && set dummy
"$@" '.' && shift
1632 has_prop
"$1" "$3" ||
{
1633 test $?
-eq 2 && return 1 # svn error
1634 # no property found:
1635 yesno
"'$path' has no property named '$1', do you want to add it?" \
1636 && $SVN propset
"$@"
1640 current_prop_val
=`$SVN propget "$1" "$3"`
1641 test $?
-ne 0 && abort
"Failed to get the current value of property '$1'."
1643 $SVN propset
"$1" "$current_prop_val
1644 $2" "$3" >/dev
/null || abort
"Failed to add '$3' in the property '$1'."
1646 current_prop_val
=`$SVN propget "$1" "$3" || echo "$current_prop_val
1648 echo "property '$1' updated on '$path', new value:
1652 # svn_propsed prop-name sed-script [path]
1656 abort
'propsed is only for SVN, not for Git.'
1659 && abort
'Not enough arguments provided;
1660 try `svn help propsed` for more info'
1662 && abort
'Too many arguments provided;
1663 try `svn help propsed` for more info'
1666 test -z "$path" && path
='.'
1667 has_prop
"$1" "$3" ||
{
1668 test $?
-eq 2 && return 1 # svn error
1669 # no property found:
1670 abort
"'$path' has no property named '$1'."
1673 prop_val
=`$SVN propget "$1" "$3"`
1674 test $?
-ne 0 && abort
"Failed to get the current value of property '$1'."
1676 prop_val
=`echo "$prop_val" | sed "$2"`
1677 test $?
-ne 0 && abort
"Failed to run the sed script '$2'."
1679 $SVN propset
"$1" "$prop_val" "$3" >/dev
/null \
1680 || abort
"Failed to update the property '$1' with value '$prop_val'."
1682 new_prop_val
=`$SVN propget "$1" "$3" || echo "$prop_val"`
1683 echo "property '$1' updated on '$path', new value:
1687 # svn_revision [args...]
1691 short
=`git rev-list --pretty=format:%h HEAD --max-count=1 | sed '1d;q'`
1692 long
=`git rev-list --pretty=format:%H HEAD --max-count=1 | sed '1d;q'`
1693 echo "$short ($long)"
1695 svn_revision_info_out
=`$SVN info "$@"`
1697 echo "$svn_revision_info_out" |
sed '/^Revision: /!d;s///'
1698 return $svn_revision_rv
1702 # svn_ignore [paths]
1705 if [ $# -eq 0 ]; then # Simply display ignore-list.
1707 test -f .gitignore
&& cat .gitignore
1709 $SVN propget
'svn:ignore'
1711 elif [ $# -eq 1 ]; then
1715 echo "$b" >>"$d/.gitignore"
1716 git add
"$d/.gitignore"
1717 notice
'files ignored in this directory:'
1720 svn_propadd
'svn:ignore' "$b" "$d"
1722 else # Add arguments in svn:ignore.
1723 # This part is a bit tricky:
1724 # For each argument, we find all the other arguments with the same dirname
1725 # $dname and we svn:ignore them all in $dname.
1726 while [ $# -ne 0 ]; do
1728 dname
=`dirname "$1"`
1729 files
=`basename "$1"`
1732 while [ $j -lt $argc ] && [ $# -ne 0 ]; do
1735 this_dname
=`dirname "$this_arg"`
1736 this_file
=`basename "$this_arg"`
1737 if [ x
"$dname" = x
"$this_dname" ]; then
1741 set dummy
"$@" "$this_arg"
1747 echo "$files" >>"$dname"/.gitignore
1748 git add
"$dname"/.gitignore
1749 notice
"files ignored in $dname:"
1750 cat "$dname"/.gitignore
1752 svn_propadd
'svn:ignore' "$files" "$dname"
1761 if [ $# -eq 0 ]; then
1766 Additionnal commands provided by svn-wrapper:
1771 propadd (padd, pa) -- SVN only
1773 propsed (psed) -- SVN only
1782 $SVN help commit |
sed '/^Valid options:/i\
1783 Extra options provided by svn-wrapper:\
1784 \ --dry-run : do not commit, simply generate a patch with what\
1785 \ would have been comitted.\
1786 \ --use-log-file FILE : extract the ChangeLog entry from FILE. This\
1787 \ entry must be formated in a similar fashion to\
1788 \ what svn-wrapper usually asks you to fill in.\
1789 \ The FILE needs to be writable and will be\
1790 \ removed by svn-wrapper upon success.\
1796 echo 'diffstat (ds): Display the histogram from svn diff-output.'
1797 $SVN help diff |
sed '1d;
1798 s/differences*/histogram/;
1799 2,35 s/diff/diffstat/g'
1802 echo "diffw (dw): Display the differences without taking whitespaces\
1804 $SVN help diff |
sed '1d;
1805 2,35 s/diff\([^a-z]\)/diffw\1/g;
1806 /--diff-cmd/,/--no-diff-deleted/d'
1809 what
='svn:ignore property'
1810 $git_mode && what
='.gitignore file'
1812 ignore: Add some files in the $what.
1813 usage: 1. ignore [PATH]
1814 2. ignore FILE [FILES...]
1816 1. Display the value of $what on [PATH].
1817 2. Add some files in the $what of the directory containing them.
1819 When adding ignores, each pattern is ignored in its own directory, e.g.:
1820 $bme ignore dir/file "d2/*.o"
1821 Will put 'file' in the $what of 'dir' and '*.o' in the
1829 echo 'mail: Resend the mail of a given commit.
1830 usage: mail REV [emails]
1832 REV must have an email file associated in +committed/REV.
1833 REV can also be PREV or HEAD.
1835 By default the mail is sent to same email addresses as during the original
1836 commit unless more arguments are given.'
1838 propadd | padd | pa
)
1839 echo 'propadd (padd, pa): Add something in the value of a property.
1840 usage: propadd PROPNAME PROPVAL PATH
1841 This command only works in SVN mode.
1843 PROPVAL will be appended at the end of the property PROPNAME.
1849 echo 'proposal: Alias for: commit --dry-run.
1850 See: svn help commit.'
1853 echo 'propsed (psed): Edit a property with sed.
1854 usage: propsed PROPNAME SED-ARGS PATH
1855 This command only works in SVN mode.
1857 eg: svn propsed svn:externals "s/http/https/" .
1863 echo 'revision (rev): Display the revision number of a local or remote item.'
1864 $SVN help info |
sed '1d;
1865 s/information/revision/g;
1866 s/revision about/the revision of/g;
1867 2,35 s/info/revision/g;
1871 echo 'touch: Touch a file and svn add it.
1872 usage: touch FILE [FILES]...
1877 selfupdate | selfup | self-update | self-up
)
1878 echo 'selfupdate (selfup): Attempt to update svn-wrapper.sh
1885 echo 'version: Display the version info of svn and svn-wrapper.
1896 # svn_status [args...]
1903 svn_status_out
=`$SVN status "$@"`
1905 test -z "$svn_status_out" && return $svn_status_rv
1906 echo "$svn_status_out" |
sed "$sed_svn_st_color"
1907 return $svn_status_rv
1910 # svn_update [args...]
1913 svn_update_out
=`$SVN update "$@"`
1915 echo "$svn_update_out" |
sed "$sed_svn_up_colors"
1916 return $svn_update_rv
1919 # ------------------- #
1920 # `main' starts here. #
1921 # ------------------- #
1923 # Define colors if stdout is a tty.
1926 else # stdout isn't a tty => don't print colors.
1930 # Consider this as a sed function :P.
1934 s@^?\\(......\\)+@+\\1+@
1935 s@^?\\(......\\)\\(.*/\\)+@+\\1\\2+@
1936 s@^?\\(......\\),@,\\1,@
1937 s@^?\\(......\\)\\(.*/\\),@,\\1\\2,@
1938 s/^\\(.\\)C/\\1${lred}C${std}/
1941 s/^?/${lred}?${std}/; t
1942 s/^M/${lgreen}M${std}/; t
1943 s/^A/${lgreen}A${std}/; t
1944 s/^X/${lblue}X${std}/; t
1945 s/^+/${lyellow}+${std}/; t
1946 s/^D/${lyellow}D${std}/; t
1947 s/^,/${lred},${std}/; t
1948 s/^C/${lred}C${std}/; t
1949 s/^I/${purple}I${std}/; t
1950 s/^R/${lblue}R${std}/; t
1951 s/^!/${lred}!${std}/; t
1952 s/^~/${lwhite}~${std}/; t"
1961 s/^\\(.\\)C/\\1${lred}C${std}/
1962 s/^\\(.\\)U/\\1${lgreen}U${std}/
1963 s/^\\(.\\)D/\\1${lred}D${std}/
1966 s/^A/${lgreen}A${std}/; t
1967 s/^U/${lgreen}U${std}/; t
1968 s/^D/${lyellow}D${std}/; t
1969 s/^G/${purple}G${std}/; t
1970 s/^C/${lred}C${std}/; t"
1973 test "x$1" = x--debug
&& shift && set -x
1975 test "x$1" = x--git
&& shift && git_mode
=: && SVN
=git
1977 test "x$1" = x--no-changelog
&& shift && use_changelog
=false
1980 # ------------------------------- #
1981 # Hooks for standard SVN commands #
1982 # ------------------------------- #
1999 # -------------------- #
2000 # Custom SVN commands #
2001 # -------------------- #
2004 if [ -d .git
]; then
2007 require_diffstat
&& $SVN diff --no-diff-deleted "$@" | diffstat
2013 diff_interactive
=yes
2019 diff_interactive
=yes
2027 if $git_mode; then # let Git handle log
2029 else # pipe svn log through PAGER by default
2030 exec $SVN "$@" |
$PAGER
2037 propadd | padd | pa
)
2043 svn_commit
--dry-run "$@"
2055 touch "$@" && $SVN add
"$@"
2057 selfupdate | selfup | self-update | self-up
)
2061 version |
-version |
--version)
2063 set dummy
'--version' "$@"