svn_commit [Git mode]: Properly detect empty commits.
[svn-wrapper.git] / svn-wrapper.sh
bloba20a645707d6b605963433aeb84c82915d8621cf
1 #! /bin/sh
2 # Simple wrapper around svn featuring auto-ChangeLog entries and emailing.
3 # $Id$
4 # Copyright (C) 2006 Benoit Sigoure.
6 # This program is free software; you can redistribute it and/or
7 # modify it under the terms of the GNU General Public License
8 # as published by the Free Software Foundation; either version 2
9 # of the License, or (at your option) any later version.
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
19 # USA.
21 # Quick install: alias svn=path/to/svn-wrapper.sh -- that's all.
23 # ------ #
24 # README #
25 # ------ #
27 # This script is a wrapper around the svn command-line client for UNIX. It has
28 # been designed mainly to automagically generate GNU-style ChangeLog entries
29 # when committing and mail them along with a diff and an optional comment from
30 # the author to a list of persons or a mailing list. It has been made so that
31 # it's as much portable as possible and covers as many use-case as possible.
33 # HOWEVER, there will be bugs, there will be cases in which the script doesn't
34 # wrap properly the svn-cli, etc. In this case, you can try to mail me at
35 # <tsuna at lrde dot epita dot fr>. Include the revision of the svn-wrapper
36 # you're using, and full description of what's wrong etc. so I can reproduce
37 # your problem.
39 # If you feel like, you can try to fix/enhance the script yourself. It only
40 # requires some basic Shell-scripting skills. Knowing sed might help :)
42 # ------------- #
43 # DOCUMENTATION #
44 # ------------- #
46 # If you're simply looking for the usage, run svn-wrapper.sh help (or
47 # svn help if you aliased `svn' on svn-wrapper.sh) as usual.
49 # This script is (hopefully) portable, widely commented and self-contained. Do
50 # not hesitate to hack it. It might look rather long (because it does a lot of
51 # things :P) but you should be able to easily locate which part of the code
52 # you're looking for.
54 # The script begins by defining several functions. Then it really starts where
55 # the comment "# `main' starts here. #" is placed.
56 # Some svn commands are hooked (eg, svn st displays colors). Hooks and
57 # extra commands are defined in functions named svn_<command-name>.
59 # ---- #
60 # TODO #
61 # ---- #
63 # * Handle things such as svn ci --force foobar.
64 # * svn automerge + automatic fill in branches/README.branches.
65 # * Automatic proxy configuration depending on the IP in ifconfig?
66 # * Customizable behavior/colors via some ~/.<config>rc file (?)
67 # * Automatically recognize svn cp and svn mv instead of writting "New" and
68 # "Remove" in the template ChangeLog entry (hard). Pair the and new/remove
69 # to prepare a good ChangeLog entry (move to X, copy from X) [even harder].
70 # => svn info says whether an item was copied from a given *URL* or not.
74 # Default values (the user can export them to override them).
75 git_mode=false
76 if [ x"$SVN" = x ]; then
77 if [ -d .git ] || [ -d ../.git ]; then
78 SVN=git
79 git_mode=:
80 else
81 SVN=svn
83 else
84 case "$SVN" in
85 git*) git_mode=:;;
86 esac
88 : ${EDITOR=missing}
89 export EDITOR
90 : ${GPG=gpg}
91 : ${TMPDIR=/tmp}
92 export TMPDIR
93 : ${PAGER=missing}
94 export PAGER
95 # Override the locale.
96 LC_ALL='C'
97 export LC_ALL
98 : ${AWK=missing}
100 # Signal number for traps (using plain signal names is not portable and breaks
101 # on recent Debians that use ash as their default shell).
102 SIGINT=2
104 # Pitfall: some users might be tempted to export SVN=svn-wrapper.sh for some
105 # reason. This is just *wrong*. The following is an attempt to save them from
106 # some troubles.
107 if [ x`basename "$SVN"` = 'xsvn-wrapper.sh' ]; then
108 SVN='svn'
111 # This code comes (mostly) from Autoconf.
112 # The user is always right.
113 if test "${PATH_SEPARATOR+set}" != set; then
114 trap "echo SIGINT; rm -f '$TMPDIR/conf$$.sh'; exit 130" $SIGINT
115 echo "#! /bin/sh" >"$TMPDIR/conf$$.sh"
116 echo "exit 0" >>"$TMPDIR/conf$$.sh"
117 chmod +x "$TMPDIR/conf$$.sh"
118 if (PATH="/nonexistent;."; conf$$.sh) >/dev/null 2>&1; then
119 PATH_SEPARATOR=';'
120 else
121 PATH_SEPARATOR=:
123 rm -f "$TMPDIR/conf$$.sh"
124 # "Remove" the trap
125 trap 'echo SIGINT; exit 130' $SIGINT
128 version='0.3'
129 revision=`sed '/^# $Id[:].*$/!d;
130 s/.*$Id[:] *//;
131 s/ *$ *//;
132 s/[^0-9]*\([0-9][0-9]*\).*/\1/' "$0"`
133 me="$0"
135 # The `main' really starts after the functions definitions.
137 # ---------------- #
138 # Helper functions #
139 # ---------------- #
141 set_colors()
143 red='\e[0;31m'; lred='\e[1;31m'
144 green='\e[0;32m'; lgreen='\e[1;32m'
145 yellow='\e[0;33m'; lyellow='\e[1;33m'
146 blue='\e[0;34m'; lblue='\e[1;34m'
147 purple='\e[0;35m'; lpurple='\e[1;35m'
148 cyan='\e[0;36m'; lcyan='\e[1;36m'
149 grey='\e[0;37m'; lgrey='\e[1;37m'
150 white='\e[0;38m'; lwhite='\e[1;38m'
151 std='\e[m'
154 set_nocolors()
156 red=''; lred=''
157 green=''; lgreen=''
158 yellow=''; lyellow=''
159 blue=''; lblue=''
160 purple=''; lpurple=''
161 cyan=''; lcyan=''
162 grey=''; lgrey=''
163 white=''; lwhite=''
164 std=''
167 # abort err-msg
168 abort()
170 echo "svn-wrapper: ${lred}abort${std}: $@" \
171 | sed '1!s/^[ ]*/ /' >&2
172 exit 1
175 # warn msg
176 warn()
178 echo "svn-wrapper: ${lred}warning${std}: $@" \
179 | sed '1!s/^[ ]*/ /' >&2
182 # notice msg
183 notice()
185 echo "svn-wrapper: ${lyellow}notice${std}: $@" \
186 | sed '1!s/^[ ]*/ /' >&2
189 # yesno question
190 yesno()
192 echo -n "$@ [y/N] "
193 read answer || return 1
194 case "$answer" in
195 y* | Y*) return 0;;
196 *) return 1;;
197 esac
198 return 42 # should never happen...
201 # yesnewproceed what
202 # returns true if `yes' or `proceed', false if `new'.
203 # the answer is stored in $yesnoproceed_res which is /yes|new|proceed/
204 yesnewproceed()
206 echo -n "$@ [(y)es/(p)roceed/(N)ew] "
207 read answer || return 1
208 case "$answer" in
209 y* | Y*) yesnoproceed_res=yes; return 0;;
210 p* | P*) yesnoproceed_res=proceed; return 0;;
211 *) yesnoproceed_res=new; return 1;;
212 esac
213 return 42 # should never happen...
216 # warn_env env-var
217 warn_env()
219 warn "cannot find the environment variable $1
220 You might consider using \`export $1='<FIXME>'\`"
223 # get_unique_file_name file-name
224 get_unique_file_name()
226 test -e "$1" || {
227 echo "$1" && return 0
229 gufn="$1"; i=1
230 while test -e "$gufn.$i"; do
231 i=`expr $i + 1`
232 done
233 echo "$gufn.$i"
236 # ensure_not_empty description value
237 ensure_not_empty()
239 ene_val=`echo "$2" | tr -d ' \t\n'`
240 test x"$ene_val" = x && abort "$1: empty value"
243 # find_prog prog-name
244 # return true if prog-name is in the PATH
245 # echo the full path to prog-name on stdout.
246 # Based on a code from texi2dvi
247 find_prog()
249 save_IFS=$IFS
250 IFS=$PATH_SEPARATOR
251 for dir in $PATH; do
252 IFS=$save_IFS
253 test x"$dir" = x && continue
254 # The basic test for an executable is `test -f $f && test -x $f'.
255 # (`test -x' is not enough, because it can also be true for directories.)
256 # We have to try this both for $1 and $1.exe.
258 # Note: On Cygwin and DJGPP, `test -x' also looks for .exe. On Cygwin,
259 # also `test -f' has this enhancement, bot not on DJGPP. (Both are
260 # design decisions, so there is little chance to make them consistent.)
261 # Thusly, it seems to be difficult to make use of these enhancements.
263 if test -f "$dir/$1" && test -x "$dir/$1"; then
264 echo "$dir/$1"
265 return 0
266 elif test -f "$dir/$1.exe" && test -x "$dir/$1.exe"; then
267 echo "$dir/$1.exe"
268 return 0
270 done
271 return 1
274 # find_progs prog [progs...]
275 # Look in PATH for one of the programs given in argument.
276 # If none of the progs can be found, the string "exit 2" is "returned".
277 find_progs()
279 # This code comes mostly from Autoconf.
280 for fp_prog in "$@"; do
281 fp_res=`find_prog $fp_prog`
282 if [ $? -eq 0 ]; then
283 echo "$fp_res"
284 return 0
286 done
287 echo "exit 2"
288 return 1
291 test x"$EDITOR" = xmissing && EDITOR=`find_progs vim vi emacs nano`
292 test x"$PAGER" = xmissing && PAGER=`find_progs less more`
293 test x"$AWK" = xmissing && AWK=`find_progs gawk mawk nawk awk`
295 # require_diffstat
296 # return true if diffstat is in the PATH
297 require_diffstat()
299 if [ x"$require_diffstat_cache" != x ]; then
300 return $require_diffstat_cache
302 if (echo | diffstat) >/dev/null 2>/dev/null; then :; else
303 warn 'diffstat is not installed on your system or not in your PATH.'
304 test -f /etc/debian_version \
305 && notice 'you might want to `apt-get install diffstat`.'
306 require_diffstat_cache=1
307 return 1
309 require_diffstat_cache=0
310 return 0
313 # require_mail
314 # return 0 -> found a mailer
315 # return !0 -> no mailer found
316 # The full path to the program found is echo'ed on stdout.
317 require_mail()
319 save_PATH="$PATH"
320 PATH="${PATH}${PATH_SEPARATOR}/sbin${PATH_SEPARATOR}/usr/sbin${PATH_SEPARATOR}/usr/libexec"
321 export PATH
322 find_progs sendEmail sendmail mail
323 rv=$?
324 PATH="$save_PATH"
325 export PATH
326 return $rv
329 # my_sendmail mail-file mail-subject mail-to [extra-headers]
330 # mail-to is a comma-separated list of email addresses.
331 # extra-headers is an optionnal argument and will be prepended at the
332 # beginning of the mail headers if the tool used to send mails supports it.
333 # The mail-file may also contain headers. They must be separated from the body
334 # of the mail by a blank line.
335 my_sendmail()
337 test -f "$1" || abort "my_sendmail: Cannot find the mail file: $1"
338 test x"$2" = x && warn 'my_sendmail: Empty subject.'
339 test x"$3" = x && abort 'my_sendmail: No recipient specified.'
341 content_type='Content-type: text/plain'
342 extra_headers="X-Mailer: svn-wrapper v$version (r$revision)
343 Mime-Version: 1.0
344 Content-Transfer-Encoding: 7bit"
345 if test x"$4" != x; then
346 extra_headers="$4
347 $extra_headers"
348 # Remove empty lines.
349 extra_headers=`echo "$extra_headers" | sed '/^[ ]*$/d;s/^[ ]*//'`
352 # If we have a signature, add it.
353 if test -f ~/.signature; then
354 # But don't add it if it is already in the mail.
355 if grep -Fe "`cat ~/.signature`" "$1" >/dev/null; then :; else
356 echo '-- ' >>"$1" && cat ~/.signature >>"$1"
359 # VCS-compat: handle user option 'sign'.
360 if (grep '^sign: false' ~/.vcs) >/dev/null 2>/dev/null; then :; else
361 ($GPG -h) >/dev/null 2>/dev/null
362 gpg_rv=$?
363 if [ -e ~/.gnupg ] || [ -e ~/.gpg ] || [ -e ~/.pgp ] && [ $gpg_rv -lt 42 ]
364 then
365 if grep 'BEGIN PGP SIGNATURE' "$1" >/dev/null; then
366 notice 'message is already GPG-signed'
367 elif yesno "Sign the mail using $GPG ?"; then
368 # Strip the headers
369 sed '1,/^$/d' "$1" >"$1.msg"
370 sed '1,/^$/!d' "$1" >"$1.hdr"
371 # Sign the message, keep only the PGP signature.
372 $GPG --clearsign <"$1.msg" >"$1.tmp" || {
373 rm -f "$1.msg" "$1.hdr" "$1.tmp"
374 abort "\`$GPG' failed (r=$?)"
376 sed '/^--*BEGIN PGP SIGNATURE--*$/,/^--*END PGP SIGNATURE--*$/!d' \
377 "$1.tmp" >"$1.sig"
379 boundary="svn-wrapper-2-$RANDOM"
380 boundary="$boundary$RANDOM"
381 # Prepend some stuff before the PGP signature.
382 echo "
383 --$boundary
384 content-type: application/pgp-signature; x-mac-type=70674453;
385 name=PGP.sig
386 content-description: This is a digitally signed message part
387 content-disposition: inline; filename=PGP.sig
388 content-transfer-encoding: 7bit" | cat - "$1.sig" >"$1.tmp"
389 mv -f "$1.tmp" "$1.sig"
391 # Append some stuff after the PGP signature.
392 echo "
393 --$boundary--" >>"$1.sig"
394 # Re-paste the headers before the signed body and prepend some stuff.
395 echo "This is an OpenPGP/MIME signed message (RFC 2440 and 3156)
396 --$boundary
397 Content-Transfer-Encoding: 8bit
398 Content-Type: text/plain; charset=iso-8859-1; format=flowed
400 | cat "$1.hdr" - "$1.msg" "$1.sig" >"$1.tmp" \
401 && mv -f "$1.tmp" "$1"
402 content_type="Content-Type: multipart/signed;\
403 protocol=\"application/pgp-signature\"; micalg=pgp-sha1;\
404 boundary=\"$boundary\""
405 # Cleanup.
406 rm -f "$1.tmp" "$1.sig" "$1.msg" "$1.hdr"
407 extra_headers="$extra_headers
408 X-Pgp-Agent: `$GPG --version | sed q`"
412 extra_headers="$extra_headers
413 $content_type"
415 mailer=`require_mail`
416 if [ $? -ne 0 ]; then
417 warn 'my_sendmail: No suitable mailer found.'
418 return 1
420 case "$mailer" in
421 */sendmail)
422 to=`echo "$3" | sed 's/,//g'`
423 echo "$extra_headers" | cat - "$1" | $mailer $to;;
424 */mail)
425 cat "$1" | $mailer -s "$2" "$3";;
426 */sendEmail)
427 if [ x"$SMTP" = x ]; then
428 warn 'my_sendmail: (sendEmail) please tell me the SMTP server to use'
429 echo -n 'STMP server: '
430 read SMTP || abort 'could not read the SMTP server'
431 notice "hint: you can export SMTP=$SMTP if you don't want to be asked"
433 sendEmail -f "$FULLNAME <$EMAIL>" -t "$3" -u "$2" \
434 -s "$SMTP" -o message-file="$1";;
435 *) # wtf
436 abort 'my_sendmail: Internal error.';;
437 esac
440 # selfupdate
441 selfupdate()
443 my_url='http://www.lrde.epita.fr/~sigoure/svn-wrapper'
444 # You can use https if you feel paranoiac.
446 echo ">>> Fetching svn-wrapper.sh from $my_url/svn-wrapper.sh"
448 # --------------------- #
449 # Fetch the new version #
450 # --------------------- #
452 tmp_me=`get_unique_file_name "$TMPDIR/svn-wrapper.sh"`
453 if (wget --help) >/dev/null 2>/dev/null; then
454 wget --no-check-certificate "$my_url/svn-wrapper.sh" -O "$tmp_me"
455 my_wget='wget'
456 else
457 curl --help >/dev/null 2>/dev/null
458 if [ $? -gt 42 ]; then
459 abort 'Cannot find wget or curl.
460 How can I download any update without them?'
462 my_wget='curl'
463 curl --insecure "$my_url/svn-wrapper.sh" >"$tmp_me"
466 test -r $tmp_me \
467 || abort "Cannot find the copy of myself I downloaded in $tmp_me"
469 # ---------------------------------------- #
470 # Compare versions and update if necessary #
471 # ---------------------------------------- #
473 my_ver="$revision"
474 tmp_ver=`sed '/^# $Id[:].*$/!d;
475 s/.*$Id[:] *//;
476 s/ *$ *//;
477 s/[^0-9]*\([0-9][0-9]*\).*/\1/' "$tmp_me"`
478 test x"$tmp_ver" = x && abort "Cannot find the version of $tmp_me"
479 if [ "$my_ver" -lt "$tmp_ver" ]; then # There IS an update...
480 echo "An update is available, r$tmp_ver (your version is r$my_ver)"
482 # See the ChangeLog?
483 if yesno "Do you want to see the ChangeLog between r$my_ver and r$tmp_ver?"
484 then
485 my_chlog=`get_unique_file_name "$TMPDIR/ChangeLog"`
486 case $my_wget in
487 wget) wget --no-check-certificate "$my_url/ChangeLog" -O "$my_chlog"
489 curl) curl --insecure "$my_url/ChangeLog" >"$my_chlog"
491 *) abort 'Should never be here.'
493 esac
494 sed "/^r$my_ver/q" "$my_chlog" | $PAGER
495 rm -f "$my_chlog"
498 # Wanna see the diff?
499 if yesno "Do you want to see the diff between r$my_ver and r$tmp_ver?"
500 then
501 (require_diffstat && diff -uw "$me" "$tmp_me" | diffstat;
502 echo
503 diff -uw "$me" "$tmp_me") | $PAGER
506 # Let's go :)
507 if yesno "Overwrite $me (r$my_ver) with $tmp_me (r$tmp_ver)?"; then
508 chmod a+x "$tmp_me"
509 cp -p "$me" "$me.r$my_ver"
510 mv "$tmp_me" "$me" && exit 0
512 rm -f "$tmp_me"
513 return 1
514 elif [ "$my_ver" -gt "$tmp_ver" ]; then
515 echo "Wow, you're more up to date than the master copy :)"
516 echo "Your version is r$my_ver and the master copy is r$tmp_ver."
517 if yesno 'Downgrade?'; then
518 chmod a+x "$tmp_me"
519 mv "$tmp_me" "$me" && exit 0
521 else
522 echo "You're already up to date [r$my_ver] :)"
524 rm -f "$tmp_me"
527 # get_svn_diff_and_diffstat [files to diff]
528 # Helper for svn_commit
529 get_svn_diff_and_diffstat()
531 if $git_mode; then
532 svn_diff=`git diff --ignore-all-space --no-color -B -C --cached`
533 svn_diff_stat=`git diff --stat --ignore-all-space --no-color -B -C --cached`
534 else
535 # Ignore white spaces. Can't svn diff -x -w: svn 1.4 only.
536 svn_diff=`svn_diffw "$@"`
537 test x"$svn_diff" = x && svn_diff=`$SVN diff "$@"`
538 if require_diffstat; then
539 svn_diff_stat=`echo "$svn_diff" | diffstat`
540 else
541 svn_diff_stat='diffstat not available'
546 # Helper. Sets the variables repos_url, git_branch, git_head, repos_root and
547 # extra_repos_info properly.
548 git_get_repos_info_()
550 # FIXME: 1st commit: "fatal: bad default revision 'HEAD'" on stderr
551 repos_url=`git show | sed '/git-svn-id/{
552 s/^[^:]*: *//
553 s/@.*$//
557 test x"$repos_url" = x && repos_url='(git:unknown)'
558 git_branch=`git branch | awk '/^\*/ { print substr($0, 3) }'`
559 if [ x"$git_branch" = x'(no branch)' ]; then
560 yesno 'You are on a detached HEAD, do you really want to continue?' \
561 || return 1
563 git_head=`git-rev-list --pretty=format:%h HEAD --max-count=1 | sed '1d;q'`
564 extra_repos_info="Git branch: $git_branch (HEAD: $git_head)"
565 repos_root=$repos_url
568 # ------------------------------- #
569 # Hooks for standard SVN commands #
570 # ------------------------------- #
572 # svn_commit [args...]
573 # Here is how the commit process goes:
575 # First we look in the arguments passed to commit:
576 # If there are some files or paths, the user wants to commit these only. In
577 # this case, we must search for ChangeLogs from these paths. We might find
578 # more than one ChangeLog, in this case the user will be prompted to pick up
579 # one.
580 # Otherwise (no path passed in the command line) the user just wants to
581 # commit the current working directory.
582 # In any case, we schedule "ChangeLog" for commit.
584 # Alright now that we know which ChangeLog to use, we look in the ChangeLog's
585 # directory if there is a ",svn-log" file which would mean that a previous
586 # commit didn't finish successfully. If there is such a file, the user is
587 # prompted to know whether they want to resume that commit or simply start a
588 # new one.
589 # When the user wants to resume a commit, the ",svn-log" file is loaded and we
590 # retrieve the value of "$@" that was saved in the file.
591 # Otherwise we build a template ChangeLog entry.
592 # Then we open the template ChangeLog entry with $EDITOR so that the user
593 # fills it properly.
594 # Finally, we commit.
595 # Once the commit is sent, we ask the server to know which revision was
596 # commited and we also retrieve the diff. We then send a mail with these.
597 svn_commit()
599 here=`pwd -P`
600 dry_run=no
601 git_commit_all=no
603 # Check if the user passed some paths to commit explicitly
604 # because in this case we must add the ChangeLog to the commit and search
605 # the ChangeLog from the dirname of that file.
606 i=0; search_from=''; add_changelog=no; extra_files=''
607 while [ $i -lt $# ]; do
608 arg="$1"
609 case "$arg" in
610 --dry-run)
611 dry_run=yes
612 shift
613 i=`expr $i + 1`
614 continue
616 -a|--all)
617 git_commit_all=yes
619 esac
620 # If the argument is a valid path: add the ChangeLog in the list of
621 # files to commit
622 if test -e "$arg"; then
623 add_changelog=yes
624 if test -d "$arg"; then
625 search_from_add="$arg"
626 else
627 search_from_add=`dirname "$arg"`
629 search_from="$search_from:$search_from_add"
631 shift
632 set dummy "$@" "$arg"
633 shift
634 i=`expr $i + 1`
635 done
636 if [ $add_changelog = no ]; then
637 # There is no path/file in the command line: the user wants to commit the
638 # current directory. Make it explicit now:
639 extra_files="$here"
641 search_from=`echo "$search_from" | sed 's/^://; s/^$/./'`
643 # ----------------- #
644 # Find ChangeLog(s) #
645 # ----------------- #
647 nb_chlogs=0; change_log_dirs=''
648 save_IFS=$IFS; IFS=':'
649 for dir in $search_from; do
650 IFS=$save_IFS
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=`expr $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 ]; 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=`expr $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 change_log_dir=$change_log_dirs
707 notice "using $change_log_dir/ChangeLog"
710 test -f "$change_log_dir/ChangeLog" \
711 || abort "No such file or directory: $change_log_dir/ChangeLog"
713 # Now we can safely schedule the ChangeLog for the commit.
714 extra_files="$extra_files:$change_log_dir/ChangeLog"
715 if [ -d "$change_log_dir/.git" ] || $git_mode; then
716 SVN=git
717 git_mode=:
718 git_get_repos_info_
719 else
720 svn_st_tmp=`$SVN status "$change_log_dir"`
722 # Warn for files that are not added in the repos.
723 conflicts=`echo "$svn_st_tmp" | sed '/^ *$/d;
724 /^[^?]/d;
725 /^?.......*\/[,+]/d;
726 /^?......[,+]/d'`
727 if test x"$conflicts" != x; then
728 warn "make sure you don't want to \`svn add'
729 any of the following files before committing:"
730 echo "$conflicts" | sed "$sed_svn_st_color"
731 echo -n 'Type [ENTER] to continue :)' && read chiche_is_gay
734 # If there are changes in an svn:externals, advise the user to commit that
735 # first.
736 changed_externals=`echo "$svn_st_tmp" | $AWK \
737 'function printext()
739 if (ext && !printed)
741 print this_ext "\n";
742 printed = 1;
745 BEGIN { this_ext = ""; ext = 0; ext_modified = 0; }
746 /^Performing status on external/ {
747 ext = 1;
748 sub(/.* at ./, ""); sub(/.$/, ""); this_ext = $0;
749 printed = 0;
751 /^[ADMR]/ { ext_modified = ext; printext(); }
752 /^.[M]/ { ext_modified = ext; printext(); }
753 END { exit ext_modified; }'`
754 if [ $? -ne 0 ]; then
755 warn "the following external items have local modifications:
756 $changed_externals"
757 yesno "You are advised to commit them separately first. Continue anyway?" \
758 || return 1
761 # Detect unresolved conflicts / missing files.
762 conflicts=`echo "$svn_st_tmp" | sed '/^[C!]/!d'`
763 test x"$conflicts" != x && abort "there are unresolved conflicts (\`C')
764 and/or missing files (\`!'):
765 $conflicts"
767 svn_info_tmp=`$SVN info "$change_log_dir"`
768 test $? -ne 0 && abort "Failed to get svn info on $change_log_dir"
769 repos_root=`echo "$svn_info_tmp" | sed '/^Repository Root: /!d;s///'`
770 repos_url=`echo "$svn_info_tmp" | sed '/^URL: /!d;s///'`
771 # It looks like svn <1.3 didn't display a "Repository Root" entry.
772 test x"$repos_root" = x && repos_root=$repos_url
775 cd "$here"
777 YYYY=`date '+%Y'`
778 MM=`date '+%m'`
779 DD=`date '+%d'`
781 # VCS-compat: handle user option 'new_user'
782 new_user='yes'
783 grep '^new_user: false' ~/.vcs >/dev/null 2>/dev/null && new_user='no'
785 edit_changelog=yes
786 tmp_log="$change_log_dir/,svn-log"
787 if [ -f "$tmp_log" ] && yesnewproceed "It looks like the last commit did not\
788 terminate successfully.
789 Would you like to resume it or proceed immediately?"; then
790 test x"$yesnoproceed_res" = xproceed && edit_changelog=no
791 echo 'Resuming ...'
792 internal_tags=`sed '/^--- Internal stuff, DO NOT change please ---$/,$!d' \
793 "$tmp_log"`
794 saved_args=`echo "$internal_tags" | sed '/^args: */!d;s///'`
795 extra_files=`echo "$internal_tags" | sed '/^extra_files: */!d;s///'`
796 if [ x"$saved_args" != x ]; then
797 if [ x"$*" != x ] && [ x"$saved_args" != x"$*" ]; then
798 warn "overriding arguments:
799 you invoked $me with the following arguments: $@
800 they have been replaced by these: $saved_args"
801 set dummy $saved_args
802 shift
803 else
804 notice "setting the following arguments: $saved_args"
805 set dummy $saved_args
806 shift
808 elif [ x"$*" != x ]; then
809 warn "overriding arguments:
810 you invoked $me with the following arguments: $@
811 they have been dropped"
814 for i; do
815 case "$i" in
816 -a|--all)
817 git_commit_all=yes
818 if [ $git_mode ]; then
819 (cd $change_log_dir && git-add -v -u) || abort '`git-add -v -u` failed'
822 esac
823 done
825 get_svn_diff_and_diffstat "$@"
827 # Update the file with the new diff/diffstat in case it changed.
828 $AWK 'BEGIN {
829 tlatbwbi_seen = 0;
830 ycewah_seen = 0;
832 /^--This line, and those below, will be ignored--$/ {
833 tlatbwbi_seen = 1;
835 /^ Your ChangeLog entry will appear here\.$/ {
836 if (tlatbwbi_seen) ycewah_seen = 1;
839 if (ycewah_seen != 2) print;
840 if (ycewah_seen == 1) ycewah_seen = 2;
841 }' "$tmp_log" >"$tmp_log.tmp"
842 echo "
844 $svn_diff_stat
846 $svn_diff
848 $internal_tags" >>"$tmp_log.tmp"
849 mv -f "$tmp_log.tmp" "$tmp_log"
851 else # Build the template message.
853 # ------------------------------------ #
854 # Gather info for the template message #
855 # ------------------------------------ #
857 if $git_mode; then
858 projname=`git config svnw.project`
859 if [ x"$projname" = x ] \
860 && [ -f "$change_log_dir/.git/svn/git-svn/unhandled.log" ]
861 then
862 projname=`grep project "$change_log_dir/.git/svn/git-svn/unhandled.log"`
863 sed_tmp='$!d;s/^.*+dir_prop: . project //'
864 projname=`echo "$projname" | sed "$sed_tmp"`
866 if [ x"$projname" = x ]; then
867 warn 'No project name set for this repository.
868 If this is a git-svn repository, do this in a SVN working copy:
869 svn propset project myproj .
870 If this is a real git repository, do this:
871 git config svnw.project myproj'
873 else
874 projname=`$SVN propget project "$change_log_dir"`
876 # Try to be VCS-compatible and find a project name in a *.rb.
877 if [ x"$projname" = x ] && [ -d "$change_log_dir/vcs" ]; then
878 projname=`sed '/common_commit/!d;s/.*"\(.*\)<%= rev.*/\1/' \
879 "$change_log_dir"/vcs/*.rb`
880 test x"$projname" != x && test x$new_user = xyes \
881 && notice "VCS-compat: found project name: $projname
882 in " "$change_log_dir"/vcs/*.rb
884 test x"$projname" != x && projname=`echo "$projname" | sed '/[^ ]$/s/$/ /'`
886 if $git_mode; then
887 mailto=`git config svnw.mailto`
888 if [ x"$mailto" = x ] \
889 && [ -f "$change_log_dir/.git/svn/git-svn/unhandled.log" ]
890 then
891 mailto=`grep mailto "$change_log_dir/.git/svn/git-svn/unhandled.log"`
892 sed_tmp='$!d;s/^.*+dir_prop: . mailto //'
893 mailto=`echo "$mailto" | sed "$sed_tmp"`
895 if [ x"$mailto" = x ]; then
896 warn 'No mailto property set for this repository.
897 If this is a git-svn repository, do this in a SVN working copy:
898 svn propset mailto maintainer1@foo.com,maint2@bar.com .
899 If this is a real git repository, do this:
900 git config svnw.mailto maintainer1@foo.com,maint2@bar.com'
902 else
903 mailto=`$SVN propget mailto "$change_log_dir"`
906 if [ x"$mailto" = x ]; then
907 test x$new_user = xyes \
908 && warn "no svn property mailto found in $change_log_dir
909 You might want to set default email adresses using:
910 svn propset mailto 'somebody@mail.com, foobar@example.com'\
911 $change_log_dir" >&2
912 # Try to be VCS-compatible and find a list of mails in a *.rb.
913 if [ -d "$change_log_dir/vcs" ]; then
914 mailto=`grep '.@.*\..*' "$change_log_dir"/vcs/*.rb \
915 | tr '\n' ' ' \
916 | sed 's/^.*[["]\([^["]*@[^]"]*\)[]"].*$/\1/' | xargs`
917 test x"$mailto" != x && test x$new_user = xyes \
918 && notice "VCS-compat: found mailto: $mailto
919 in " "$change_log_dir"/vcs/*.rb
920 fi # end VCS compat
921 fi # end guess mailto
923 # Ensure that emails are comma-separated.
924 mailto=`echo "$mailto" | sed 's/[ ;]/,/g' | tr -s ',' | sed 's/,/, /g'`
925 test x"$FULLNAME" = x && FULLNAME='Type Your Name Here' \
926 && warn_env FULLNAME
927 test x"$EMAIL" = x && EMAIL='your.mail.here@FIXME.com' && warn_env EMAIL
929 if $git_mode; then
930 if [ $git_commit_all = yes ]; then
931 (cd $change_log_dir && git-add -v -u) || abort '`git-add -v -u` failed'
933 my_git_st=`git-diff -C --raw --cached`
934 test $? -eq 0 || abort 'git-diff failed'
935 # Format: ":<old_mode> <new_mode> <old_sha1> <new_sha1> <status>[
936 # <similarity score>]\t<file-name>"
937 change_log_files=`echo "$my_git_st" | sed '
938 t dummy_sed_1
939 : dummy_sed_1
940 s/^:[0-7 ]* [0-9a-f. ]* M[^ ]* \(.*\)$/ * \1: ./; t
941 s/^:[0-7 ]* [0-9a-f. ]* A[^ ]* \(.*\)$/ * \1: New./; t
942 s/^:[0-7 ]* [0-9a-f. ]* D[^ ]* \(.*\)$/ * \1: Remove./; t
943 s/^:[0-7 ]* [0-9a-f. ]* R[^ ]* \([^ ]*\) \(.*\)$/ * \1: Rename as \2./; t
944 s/^:[0-7 ]* [0-9a-f. ]* T[^ ]* \(.*\)$/ * \1: ./; t
945 s/^:[0-7 ]* [0-9a-f. ]* X[^ ]* \(.*\)$/ * \1: ???./; t
946 s/^:[0-7 ]* [0-9a-f. ]* U[^ ]* \(.*\)$/ * \1: UNMERGED./; t
948 else
949 # --ignore-externals appeared after svn 1.1.1
950 my_svn_st=`$SVN status --ignore-externals "$@" \
951 || $SVN status "$@" | sed '/^Performing status on external/ {
955 # Files to put in the ChangeLog entry.
956 change_log_files=`echo "$my_svn_st" | sed '
957 t dummy_sed_1
958 : dummy_sed_1
959 s/^M......\(.*\)$/ * \1: ./; t
960 s/^A......\(.*\)$/ * \1: New./; t
961 s/^D......\(.*\)$/ * \1: Remove./; t
965 if [ x"$change_log_files" = x ]; then
966 yesno 'Nothing to commit, continue anyway?' || return 1
969 change_log_files=`echo "$change_log_files" | sort -u`
971 get_svn_diff_and_diffstat "$@"
973 # Get any older svn-log out of the way.
974 test -f "$tmp_log" && mv "$tmp_log" `get_unique_file_name "$tmp_log"`
975 # If we can't get an older svn-log out of the way, find a new name...
976 test -f "$tmp_log" && tmp_log=`get_unique_file_name "$tmp_log"`
977 if [ x$new_user = no ]; then
978 commit_instructions='
979 Instructions:
980 - Fill the ChangeLog entry.
981 - If you feel like, write a comment in the "Comment:" section.
982 This comment will only appear in the email, not in the ChangeLog.
983 By default only the location of the repository is in the comment.
984 - Some tags will be replaced. Tags are of the form: <TAG>. Unknown
985 tags will be left unchanged.
986 - The tag <REV> may only be used in the Subject.
987 - Your ChangeLog entry will be used as commit message for svn.'
988 else
989 commit_instructions=''
992 if [ x"$mailto" != xdont_send_mails ] && [ x"$mailto" != xnothx ]; then
993 to="
994 To: $mailto"
996 r_before_rev=r
997 $git_mode && r_before_rev=
998 test x"$extra_repos_info" = x || extra_repos_info="
999 $extra_repos_info"
1000 echo "\
1001 --You must fill this file correctly to continue-- -*- vcs -*-
1002 Title:
1003 Subject: ${projname}$r_before_rev<REV>: <TITLE>
1004 From: $FULLNAME <$EMAIL>$to
1006 Comment:
1007 URL: $repos_url$extra_repos_info
1009 ChangeLog:
1011 <YYYY>-<MM>-<DD> $FULLNAME <$EMAIL>
1013 <TITLE>
1014 $change_log_files
1016 --This line, and those below, will be ignored--
1017 $commit_instructions
1018 --Preview of the message that will be sent--
1020 URL: $repos_url$extra_repos_info
1021 Your comments (if any) will appear here.
1023 ChangeLog:
1024 $YYYY-$MM-$DD $FULLNAME <$EMAIL>
1026 Your ChangeLog entry will appear here.
1029 $svn_diff_stat
1031 $svn_diff" >"$tmp_log"
1033 echo "
1034 --- Internal stuff, DO NOT change please ---
1035 args: $@" >>"$tmp_log"
1036 echo "extra_files: $extra_files
1037 vi: ft=diff:noet:tw=76:" >>"$tmp_log"
1039 fi # end: if svn-log; then resume? else create template
1040 test x"$edit_changelog" = xyes && $EDITOR "$tmp_log"
1042 # ------------------ #
1043 # Re-"parse" the log #
1044 # ------------------ #
1046 # hmz this section is a bit messy...
1047 # helper string... !@#$%* escaping \\\\\\...
1048 sed_escape='s/\\/\\\\/g;s/@/\\@/g;s/&/\\\&/g'
1049 sed_eval_tags="s/<MM>/$MM/g; s/<DD>/$DD/g; s/<YYYY>/$YYYY/g"
1050 full_log=`sed '/^-*This line, and those below, will be ignored-*$/,$d;
1051 /^--You must fill this/d' "$tmp_log"`
1052 chlog_entry=`echo "$full_log" | sed '/^ChangeLog:$/,$!d; //d'`
1053 ensure_not_empty 'ChangeLog entry' "$chlog_entry"
1054 full_log=`echo "$full_log" | sed '/^ChangeLog:$/,$d'`
1055 mail_comment=`echo "$full_log" | sed '/^Comment:$/,$!d; //d'`
1056 full_log=`echo "$full_log" | sed '/^Comment:$/,$d'`
1057 # Add a period at the end of the title.
1058 sed_tmp='/^Title: */!d;s///;/ *\.$/!s/ *$/./'
1059 mail_title=`echo "$full_log" | sed "$sed_tmp"`
1060 ensure_not_empty 'commit title' "$mail_title"
1061 mail_title=`echo "$mail_title" | sed "$sed_eval_tags; $sed_escape"`
1062 sed_eval_tags="$sed_eval_tags; s@<TITLE>\\.*@$mail_title@g"
1063 mail_comment=`echo "$mail_comment" | sed "$sed_eval_tags"`
1064 raw_chlog_entry=$chlog_entry # ChangeLog entry without tags expanded
1065 chlog_entry=`echo "$chlog_entry" | sed "$sed_eval_tags; 1{
1066 /^ *$/d
1068 mail_subject=`echo "$full_log" | sed '/^Subject: */!d;s///'`
1069 ensure_not_empty 'mail subject' "$mail_subject"
1070 mail_to=`echo "$full_log" | sed '/^To:/!d'`
1071 send_a_mail=yes
1072 if test x"$mail_to" = x; then
1073 send_a_mail=no
1074 else
1075 mail_to=`echo "$mail_to" | sed 's/^To: *//'`
1076 ensure_not_empty '"To:" field of the mail' "$mail_to"
1078 mail_from=`echo "$full_log" | sed '/^From: */!d;s///'`
1079 ensure_not_empty '"From:" field of the mail' "$mail_from"
1081 # Sanity checks on the ChangeLog entry #
1082 # ------------------------------------ #
1084 if echo "$chlog_entry" | grep '<REV>' >/dev/null; then
1085 warn 'Using the tag <REV> anywhere else than in the Subject is deprecated.'
1086 yesno 'Continue anyway?' || return 1
1089 if echo "$chlog_entry" | grep ': \.$' >/dev/null; then
1090 warn 'It looks like you did not fill all entries in the ChangeLog:'
1091 echo "$chlog_entry" | grep ': \.$'
1092 yesno 'Continue anyway?' || return 1
1095 if echo "$chlog_entry" | grep '^--* Internal stuff' >/dev/null; then
1096 warn "It looks like you messed up the delimiters and I did not properly
1097 find your ChangeLog entry. Here it is, make sure it is correct:"
1098 echo "$chlog_entry"
1099 yesno 'Continue anyway?' || return 1
1102 if echo "$chlog_entry" | grep -i 'dont[^a-z0-9]' >/dev/null; then
1103 warn "Please avoid typos such as ${lred}dont$std instead of\
1104 ${lgreen}don't$std:"
1105 echo "$chlog_entry" | grep -n -i 'dont[^a-z0-9]' \
1106 | sed "s/[dD][oO][nN][tT]/$lred&$std/g"
1107 yesno 'Continue anyway?' || return 1
1110 if echo "$chlog_entry" | grep -i 'cant[^a-z0-9]' >/dev/null; then
1111 warn "Please avoid typos such as ${lred}cant$std instead of\
1112 ${lgreen}can't$std:"
1113 echo "$chlog_entry" | grep -n -i 'cant[^a-z0-9]' \
1114 | sed "s/[cC][aA][nN][tT]/$lred&$std/g"
1115 yesno 'Continue anyway?' || return 1
1118 if echo "$chlog_entry" | grep '^.\{80,\}' >/dev/null; then
1119 warn 'Please avoid long lines in your ChangeLog entry (80 columns max):'
1120 echo "$chlog_entry" | grep '^.\{80,\}'
1121 yesno 'Continue anyway?' || return 1
1124 if echo "$mail_title" | grep -i '^[a-z][a-z]*ed[^a-z]' >/dev/null; then
1125 warn 'ChangeLog entries should be written in imperative form:'
1126 echo "$mail_title" | grep -i '^[a-z][a-z]*ed[^a-z]' \
1127 | sed "s/^\\([a-zA-Z][a-zA-Z]*ed\\)\\([^a-zA-Z]\\)/$lred\\1$std\\2/"
1128 yesno 'Continue anyway?' || return 1
1131 # Check whether the user passed -m | --message
1133 while [ $i -lt $# ]; do
1134 arg="$1"
1135 # This is not really a reliable way of knowing whether -m | --message was
1136 # passed but hum... Let's assume it'll do :s
1137 if [ x"$arg" = 'x-m' ] || [ x"$arg" = 'x--message' ]; then
1138 my_message="$2"
1140 shift
1141 set dummy "$@" "$arg"
1142 shift
1143 i=`expr $i + 1`
1144 done
1145 if [ x"$my_message" = x ]; then
1146 # The title must not be indented in the commit message and must be
1147 # followed by a blank line. This yields much better results with most
1148 # VC-viewer (especially for Git but including for SVN, such as Trac for
1149 # instance). We assume that the title will always be on the 1st line.
1150 sed_git_title="1s@^[ ]*<TITLE>\\.*@$mail_title\\
1151 @g; $sed_eval_tags"
1152 # First, remove empty lines at the beginning, if any.
1153 # Remove also the date information (useless in commit messages)
1154 my_message=`echo "$raw_chlog_entry" \
1155 | sed -e '1,3 {
1156 /^'"<YYYY>-<[MD][MD]>-<[DM][DM]>"'/d
1157 /^ *$/d
1158 }' \
1159 | sed -e "$sed_git_title" \
1160 -e "$sed_eval_tags; 1{
1161 /^ *$/d
1163 else
1164 notice 'you are overriding the commit message.'
1167 # Show suspicious whitespace additions with Git.
1168 $git_mode && git diff --cached --check
1170 if [ x"$dry_run" = xyes ]; then
1171 proposal_file=',proposal'
1172 test -f "$proposal_file" \
1173 && proposal_file=`get_unique_file_name "$proposal_file"`
1174 sed_tmp='s/<REV>/???/g;s/\([^.]\) *\.$/\1/'
1175 mail_subject=`echo "$mail_subject" | sed "$sed_eval_tags; $sed_tmp"`
1177 if [ x"$mailto" != xdont_send_mails ] && [ x"$mailto" != xnothx ]; then
1178 to="
1179 To: $mailto"
1181 echo "\
1182 From: $mail_from$to
1183 Subject: $mail_subject
1185 $mail_comment
1187 ChangeLog:
1188 $chlog_entry
1191 $svn_diff_stat
1193 $svn_diff" >"$proposal_file"
1194 notice "A proposal of your commit was left in '$proposal_file'"
1195 return 0
1198 # Are you sure?
1199 if $git_mode && [ $git_commit_all = no ]; then
1200 notice 'You are using git, unlike SVN, do not forget to git add your
1201 changes'
1203 test x"$edit_changelog" = xno \
1204 || yesno 'Are you sure you want to commit?' \
1205 || return 1
1207 # Add the ChangeLog entry
1208 old_chlog=`get_unique_file_name "$change_log_dir/ChangeLog.old"`
1209 mv "$change_log_dir/ChangeLog" "$old_chlog" || \
1210 abort 'Could not backup ChangeLog'
1211 trap "echo SIGINT; mv \"$old_chlog\" \"$change_log_dir/ChangeLog\";
1212 exit 130" $SIGINT
1213 echo "$chlog_entry" >"$change_log_dir/ChangeLog"
1214 echo >>"$change_log_dir/ChangeLog"
1215 cat "$old_chlog" >>"$change_log_dir/ChangeLog"
1217 # Add extra files such as cwd or ChangeLog to the commit.
1218 tmp_sed='s/ /\\ /g' # Escape spaces for the shell.
1219 if $git_mode; then
1220 # Schedule the ChangeLog for the next commit
1221 (cd "$change_log_dir" && git add ChangeLog) \
1222 || abort 'failed to git add the ChangeLog'
1223 extra_files=
1224 else
1225 extra_files=`echo "$extra_files" | sed "$tmp_sed" | tr ':' '\n'`
1228 # Update the Git index if necessary (just in case the user changed his
1229 # working copy in the mean time)
1230 if $git_mode && [ $git_commit_all = yes ]; then
1231 (cd $change_log_dir && git-add -v -u) || abort '`git-add -v -u` failed'
1234 # --Commit-- finally! :D
1235 $SVN commit -m "$my_message" "$@" $extra_files || {
1236 svn_commit_rv=$?
1237 mv "$old_chlog" "$change_log_dir/ChangeLog"
1238 abort "Commit failed, $SVN returned $svn_commit_rv"
1241 echo -n 'Getting the revision number... '
1242 if $git_mode; then
1243 REV=`git-rev-list --pretty=format:%h HEAD --max-count=1 | sed '1d;q'`
1244 else
1245 svn_info_tmp=`$SVN info "$change_log_dir/ChangeLog"`
1246 REV=`echo "$svn_info_tmp" | sed '/^Revision: /!d;s///'`
1247 test x"$REV" = x && REV=`echo "$svn_info_tmp" \
1248 | sed '/^Last Changed Rev: /!d;s///'`
1250 test x"$REV" = x && abort 'Cannot detect the current revision.'
1251 echo "$REV"
1253 # Let's make sure we have the real diff by asking the ChangeSet we've just
1254 # committed to the server.
1256 # Backup the old stuff in case we fail to get the real diff from the server
1257 # for some reason...
1258 save_svn_diff=$svn_diff
1259 save_svn_diff_stat=$svn_diff_stat
1261 if $git_mode; then
1262 svn_diff=`git diff --ignore-all-space --no-color -C 'HEAD^' HEAD`
1263 svn_diff_stat=`git diff --stat --ignore-all-space --no-color -C 'HEAD^' HEAD`
1264 grep svn-remote "$change_log_dir/.git/config" >/dev/null 2>&1 && \
1265 notice 'Do not forget to use: git-svn dcommit to push your commits in SVN'
1266 else
1267 # Fetch the ChangeSet and filter out the ChangeLog entry. We don't use
1268 # svn diff -c because this option is not portable to older svn versions.
1269 REV_MINUS_ONE=`expr "$REV" - 1`
1270 svn_diff=`svn_diffw -r"$REV_MINUS_ONE:$REV" "$repos_root" \
1271 | $AWK '/^Index: / { if (in_chlog) in_chlog = 0; }
1272 /^Index: .*ChangeLog$/ { in_chlog = 1 }
1273 { if (!in_chlog) print }'`
1274 if [ x"$svn_diff" = x ]; then
1275 svn_diff=$save_svn_diff
1276 svn_diff_stat=$save_svn_diff_stat
1277 else
1278 if require_diffstat; then
1279 svn_diff_stat=`echo "$svn_diff" | diffstat`
1280 else
1281 svn_diff_stat='diffstat not available'
1286 # Expand <REV> and remove the final period from the mail subject if there is
1287 # only one period.
1288 sed_tmp="s/<REV>/$REV/g;"'s/\([^.]\) *\.$/\1/'
1289 mail_subject=`echo "$mail_subject" | sed "$sed_eval_tags; $sed_tmp"`
1291 mail_file=`get_unique_file_name "$change_log_dir/+mail"`
1292 echo "\
1293 From: $mail_from
1294 To: $mail_to
1295 Subject: $mail_subject
1297 $mail_comment
1299 ChangeLog:
1300 $chlog_entry
1303 $svn_diff_stat
1305 $svn_diff" | sed 's/^\.$/ ./' >"$mail_file"
1306 # We change lines with only a `.' because they could mean "end-of-mail"
1308 # Send the mail
1309 if test x$send_a_mail = xyes; then
1310 trap 'echo SIGINT; exec < /dev/null' $SIGINT
1311 # FIXME: Move the mail to the +committed right now, in case the user
1312 # CTLR+C the mail-sending-thing, so that the mail will be properly saved
1313 # their.
1314 my_sendmail "$mail_file" "$mail_subject" "$mail_to" \
1315 "X-svn-url: $repos_root
1316 X-svn-revision: $REV"
1317 fi # end do we have to send a mail?
1318 rm -f "$tmp_log"
1319 rm -f "$old_chlog"
1320 save_mail_file=`echo "$mail_file" | sed 's/+//'`
1321 mkdir -p "$change_log_dir/+committed" \
1322 || warn "Couldn't mkdir -p $change_log_dir/+committed"
1323 if [ -d "$change_log_dir/vcs" ] \
1324 || [ -d "$change_log_dir/+committed" ]
1325 then
1326 mkdir -p "$change_log_dir/+committed/$REV" \
1327 && mv "$mail_file" "$change_log_dir/+committed/$REV/mail"
1329 return $svn_commit_rv
1332 # svn_merge args...
1333 svn_merge()
1335 i=0; src=''; dst=''; rev1=''; rev2=''; r_seen=0
1336 while [ $i -lt $# ]; do
1337 arg="$1"
1338 if test -d "$arg"; then
1339 if [ x"$src" = x ]; then
1340 src="$arg"
1341 elif [ x"$dst" = x ]; then
1342 dst="$arg"
1344 else
1345 case "$arg" in
1346 -r*:*) rev1=`echo "$arg" | sed 's/^[^PREVHEAD0-9]*//; s/:.*//'`
1347 rev2=`echo "$arg" | sed 's/^.*://; s/[^PREVHEAD0-9]*//'`
1349 -r) r_seen=1 ;;
1350 esac
1352 shift
1353 set dummy "$@" "$arg"
1354 shift
1355 i=`expr $i + 1`
1356 done
1357 if [ x"$src" = x ]; then
1358 exec $SVN merge "$@"
1360 test x"$dst" = x && dst='.'
1362 if yesno "Do you want to use the automerge feature for $src ?";
1363 then :; else
1364 exec $SVN merge "$@"
1366 # FIXME: Incomplete.
1369 # svn_automerge src-path dst-path
1370 svn_automerge()
1372 test -f ChangeLog || abort 'You must use automerge in the folder where the
1373 ChangeLog is.'
1374 amerge=`$SVN propget automerge`
1375 if [ x"$amerge" = x ]; then
1376 # FIXME: Use svn log --stop-on-copy to detect the original branching.
1380 svn_info_tmp=`$SVN info`
1381 REV=`echo "$svn_info_tmp" | sed '/^Revision: /!d;s///'`
1382 test x"$REV" = x && REV=`echo "$svn_info_tmp" \
1383 | sed '/^Last Changed Rev: /!d;s///'`
1384 test x"$REV" = x && abort 'Cannot detect the current revision.'
1386 # FIXME: Incomplete.
1387 if yesno "Are you sure you want to run:
1388 $SVN merge -r$amerge:$REV $1 $2 ?"; then
1389 $SVN merge "-r$amerge:$REV" "$1" "$2"
1393 # svn_diffw [args...]
1394 svn_diffw()
1396 # Ignore white spaces.
1397 if $git_mode; then
1398 git diff --ignore-all-space -C "$@"
1399 else
1400 # Can't svn diff -x -w: svn 1.4 only.
1401 $SVN diff --no-diff-deleted --diff-cmd diff -x -uw "$@"
1405 # svn_mail REV [mails...]
1406 svn_mail()
1408 test $# -lt 1 && abort "Not enough arguments provided;
1409 Try 'svn help mail' for more info."
1410 case "$1" in
1411 PREV)
1412 if $git_mode; then
1413 REV=`git-rev-list --pretty=format:%h 'HEAD^' --max-count=1 | sed '1d;q'`
1414 else
1415 REV=`svn_revision || abort 'Cannot get current revision number'`
1416 test x"$REV" = x && abort 'Cannot get current revision number'
1417 if [ "$REV" -lt 1 ]; then
1418 abort 'No previous revision.'
1420 REV=`expr "$REV" - 1`
1423 HEAD)
1424 REV=`svn_revision || abort 'Cannot get current revision number'`
1425 test x"$REV" = x && abort 'Cannot get current revision number'
1427 *) REV="$1";;
1428 esac
1429 shift
1431 found_committed=0; found=0
1432 while [ $found -eq 0 ]; do
1433 this_chlog_dir=`pwd -P`
1434 if [ -d ./+committed ]; then
1435 found_committed=1
1436 if [ -d ./+committed/$REV ]; then
1437 found=1
1438 else
1439 cd ..
1441 else
1442 cd ..
1444 # Stop searching when in / ... hmz :P
1445 test x`pwd` = x/ && break
1446 done
1447 if [ $found -eq 0 ]; then
1448 if [ $found_committed -eq 0 ]; then
1449 abort 'Could not find the +committed directory.'
1450 else
1451 abort "Could not find the revision $REV in +committed."
1453 abort 'Internal error (should never be here).'
1456 mail_file=''; subject=''; to=''
1457 if [ -f ./+committed/$REV/mail ]; then
1458 # svn-wrapper generated file
1459 mail_file="./+committed/$REV/mail"
1460 subject=`sed '/^Subject: /!d;s///' $mail_file | sed '1q'`
1461 to=`sed '/^To: /!d;s///' $mail_file | sed '1q'`
1462 elif [ -f ./+committed/$REV/,iform ] && [ -f ./+committed/$REV/,message ]
1463 then
1464 # VCS-generated file
1465 subject=`sed '/^Subject: /!d;s///;s/^"//;s/"$//' ./+committed/$REV/,iform \
1466 | sed "s/<%= *rev *%>/$REV/g"`
1467 to=`sed '/^To:/,/^[^-]/!d' ./+committed/$REV/,iform | sed '1d;s/^- //;$d' \
1468 | xargs | sed 's/ */, /g'`
1469 mail_file=`get_unique_file_name "$TMPDIR/mail.r$REV"`
1470 echo "From: $FULLNAME <$EMAIL>
1471 To: $to
1472 Subject: $subject
1473 " >"$mail_file" || abort "Cannot create $mail_file"
1474 cat ./+committed/$REV/,message >>"$mail_file" \
1475 || abort "Cannot copy ./+committed/$REV/,message in $mail_file"
1476 else
1477 abort "Couldn't find the mail to re-send in `pwd`/+committed/$REV"
1479 if [ $# -gt 0 ]; then
1480 to=`echo "$*" | sed 's/ */, /g'`
1483 test x"$to" = x && abort 'Cannot find the list of recipients.
1484 Please report this bug.'
1485 test x"$subject" = x && abort 'Cannot find the subject of the mail.
1486 Please report this bug.'
1488 if yesno "Re-sending the mail of r$REV
1489 Subject: $subject
1490 To: $to
1491 Are you sure?"; then :; else
1492 return 1
1495 if $git_mode; then
1496 git_get_repos_info_
1497 else
1498 svn_info_tmp=`$SVN info`
1499 test $? -ne 0 && abort "Failed to get svn info on `pwd`"
1500 repos_root=`echo "$svn_info_tmp" | sed '/^Repository Root: /!d;s///'`
1501 repos_url=`echo "$svn_info_tmp" | sed '/^URL: /!d;s///'`
1502 # It looks like svn <1.3 didn't display a "Repository Root" entry.
1503 test x"$repos_root" = x && repos_root=$repos_url
1506 my_sendmail "$mail_file" "$subject" "$to" \
1507 "X-svn-url: $repos_url
1508 X-svn-revision: $REV"
1511 # svn_version
1512 svn_version()
1514 echo "Using svn-wrapper v$version (C) SIGOURE Benoit [GPL]"
1515 sed '/^# $Id[:].*$/!d;s/.*$Id[:] *//;s/ *$ *//;s/ \([0-9][0-9]*\)/ (r\1)/' "$me"
1518 # has_prop prop-name [path]
1519 # return value: 0 -> path has the property prop-name set.
1520 # 1 -> path has no property prop-name.
1521 # 2 -> svn error.
1522 has_prop()
1524 hp_plist=`$SVN proplist "$2"`
1525 test $? -ne 0 && return 2
1526 hp_res=`echo "$hp_plist" | sed "/^ *$1\$/!d"`
1527 test x"$hp_res" = x && return 1
1528 return 0
1531 # svn_propadd prop-name prop-val [path]
1532 svn_propadd()
1534 if $git_mode; then
1535 abort 'propadd is only for SVN, not for Git.'
1537 test $# -lt 2 \
1538 && abort 'Not enough arguments provided;
1539 try `svn help propadd` for more info'
1540 test $# -gt 3 \
1541 && abort 'Too many arguments provided;
1542 try `svn help propadd` for more info'
1544 path="$3"
1545 test x"$path" = x && path='.' && set dummy "$@" '.' && shift
1546 has_prop "$1" "$3" || {
1547 test $? -eq 2 && return 1 # svn error
1548 # no property found:
1549 yesno "'$path' has no property named '$1', do you want to add it?" \
1550 && $SVN propset "$@"
1551 return $?
1554 current_prop_val=`$SVN propget "$1" "$3"`
1555 test $? -ne 0 && abort "Failed to get the current value of property '$1'."
1557 $SVN propset "$1" "$current_prop_val
1558 $2" "$3" >/dev/null || abort "Failed to add '$3' in the property '$1'."
1560 current_prop_val=`$SVN propget "$1" "$3" || echo "$current_prop_val
1561 $2"`
1562 echo "property '$1' updated on '$path', new value:
1563 $current_prop_val"
1566 # svn_propsed prop-name sed-script [path]
1567 svn_propsed()
1569 if $git_mode; then
1570 abort 'propsed is only for SVN, not for Git.'
1572 test $# -lt 2 \
1573 && abort 'Not enough arguments provided;
1574 try `svn help propsed` for more info'
1575 test $# -gt 3 \
1576 && abort 'Too many arguments provided;
1577 try `svn help propsed` for more info'
1579 path="$3"
1580 test x"$path" = x && path='.'
1581 has_prop "$1" "$3" || {
1582 test $? -eq 2 && return 1 # svn error
1583 # no property found:
1584 abort "'$path' has no property named '$1'."
1587 prop_val=`$SVN propget "$1" "$3"`
1588 test $? -ne 0 && abort "Failed to get the current value of property '$1'."
1590 prop_val=`echo "$prop_val" | sed "$2"`
1591 test $? -ne 0 && abort "Failed to run the sed script '$2'."
1593 $SVN propset "$1" "$prop_val" "$3" >/dev/null \
1594 || abort "Failed to update the property '$1' with value '$prop_val'."
1596 new_prop_val=`$SVN propget "$1" "$3" || echo "$prop_val"`
1597 echo "property '$1' updated on '$path', new value:
1598 $new_prop_val"
1601 # svn_revision [args...]
1602 svn_revision()
1604 if $git_mode; then
1605 short=`git-rev-list --pretty=format:%h HEAD --max-count=1 | sed '1d;q'`
1606 long=`git-rev-list --pretty=format:%H HEAD --max-count=1 | sed '1d;q'`
1607 echo "$short ($long)"
1608 else
1609 svn_revision_info_out=`$SVN info "$@"`
1610 svn_revision_rv=$?
1611 echo "$svn_revision_info_out" | sed '/^Revision: /!d;s///'
1612 return $svn_revision_rv
1616 # svn_ignore [paths]
1617 svn_ignore()
1619 if [ $# -eq 0 ]; then # Simply display ignore-list.
1620 if $git_mode; then
1621 test -f .gitignore && cat .gitignore
1622 else
1623 $SVN propget 'svn:ignore'
1625 elif [ $# -eq 1 ]; then
1626 b=`basename "$1"`
1627 d=`dirname "$1"`
1628 if $git_mode; then
1629 echo "$b" >>"$d/.gitignore"
1630 git add "$d/.gitignore"
1631 notice 'files ignored in this directory:'
1632 cat "$d/.gitignore"
1633 else
1634 svn_propadd 'svn:ignore' "$b" "$d"
1636 else # Add arguments in svn:ignore.
1637 # This part is a bit tricky:
1638 # For each argument, we find all the other arguments with the same dirname
1639 # $dname and we svn:ignore them all in $dname.
1640 while [ $# -ne 0 ]; do
1641 arg="$1"
1642 dname=`dirname "$1"`
1643 files=`basename "$1"`
1644 shift
1645 j=0; argc=$#
1646 while [ $j -lt $argc ] && [ $# -ne 0 ]; do
1647 this_arg="$1"
1648 shift
1649 this_dname=`dirname "$this_arg"`
1650 this_file=`basename "$this_arg"`
1651 if [ x"$dname" = x"$this_dname" ]; then
1652 files="$files
1653 $this_file"
1654 else
1655 set dummy "$@" "$this_arg"
1656 shift
1658 j=`expr $j + 1`
1659 done
1660 if $git_mode; then
1661 echo "$files" >>"$dname"/.gitignore
1662 git add "$dname"/.gitignore
1663 notice "files ignored in $dname:"
1664 cat "$dname"/.gitignore
1665 else
1666 svn_propadd 'svn:ignore' "$files" "$dname"
1668 done
1672 # svn_help
1673 svn_help()
1675 if [ $# -eq 0 ]; then
1676 svn_version
1677 $SVN help
1678 rv=$?
1679 echo '
1680 Additionnal commands provided by svn-wrapper:
1681 automerge (amerge, am)
1682 diffstat (ds)
1683 diffw (dw)
1684 ignore
1685 mail
1686 propadd (padd, pa)
1687 proposal
1688 propsed (psed)
1689 revision (rev)
1690 touch
1691 selfupdate (selfup)
1692 version'
1693 return $rv
1694 else
1695 case $1 in
1696 automerge | auto-merge | amerge | am)
1697 echo 'automerge (amerge, am): FIXME
1698 usage: automerge FIXME
1700 FIXME' # FIXME
1702 commit | ci)
1703 $SVN help commit | sed '/^Valid options:/a\
1704 \ --dry-run : do not commit, simply generate a patch with what\
1705 \ would have been comitted (svn-wrapper extension).
1708 diffstat | ds)
1709 require_diffstat
1710 echo 'diffstat (ds): Display the histogram from svn diff-output.'
1711 $SVN help diff | sed '1d;
1712 s/differences*/histogram/;
1713 2,35 s/diff/diffstat/g'
1715 diffw | dw)
1716 echo "diffw (dw): Display the differences without taking whitespaces\
1717 into account."
1718 $SVN help diff | sed '1d;
1719 2,35 s/diff\([^a-z]\)/diffw\1/g;
1720 /--diff-cmd/,/--no-diff-deleted/d'
1722 ignore)
1723 echo 'ignore: Add some files in the svn:ignore property.
1724 usage: 1. ignore [PATH]
1725 2. ignore FILE [FILES...] [PATH]
1727 1. Display the value of svn:ignore property on [PATH].
1728 2. Add some files in the svn:ignore property of [PATH].
1730 If you want to add directories in the ignore-list, be careful:
1731 svn ignore foo/ bar/
1732 will add "foo/" in the property svn:ignore within the directory bar!
1733 Instead use:
1734 svn ignore foo/ bar/ .
1735 (It'\''s somewhat like with mv)
1737 Valid options:
1738 None.'
1740 mail)
1741 echo 'mail: Resend the mail of a given commit.
1742 usage: mail REV [emails]
1744 REV must have an email file associated in +committed/REV.
1745 REV can also be PREV or HEAD.
1747 By default the mail is sent to same email addresses as during the original
1748 commit unless more arguments are given.'
1750 propadd | padd | pa)
1751 echo 'propadd (padd, pa): Add something in the value of a property.
1752 usage: propadd PROPNAME PROPVAL PATH
1754 PROPVAL will be appended at the end of the property PROPNAME.
1756 Valid options:
1757 None.'
1759 proposal)
1760 echo 'proposal: Alias for: commit --dry-run.
1761 See: svn help commit.'
1763 propsed | psed)
1764 echo 'propsed (psed): Edit a property with sed.
1765 usage: propsed PROPNAME SED-ARGS PATH
1767 eg: svn propsed svn:externals "s/http/https/" .
1769 Valid options:
1770 None.'
1772 revision | rev)
1773 echo 'revision (rev): Display the revision number of a local or remote item.'
1774 $SVN help info | sed '1d;
1775 s/information/revision/g;
1776 s/revision about/the revision of/g;
1777 2,35 s/info/revision/g;
1778 /-xml/d'
1780 touch)
1781 echo 'touch: Touch a file and svn add it.
1782 usage: touch FILE [FILES]...
1784 Valid options:
1785 None.'
1787 selfupdate | selfup | self-update | self-up)
1788 echo 'selfupdate (selfup): Attempt to update svn-wrapper.sh
1789 usage: selfupdate
1791 Valid options:
1792 None.'
1794 version)
1795 echo 'version: Display the version info of svn and svn-wrapper.
1796 usage: version
1798 Valid options:
1799 None.'
1801 *) $SVN help "$@";;
1802 esac
1806 # svn_status [args...]
1807 svn_status()
1809 if $git_mode; then
1810 git-status "$@"
1811 return $?
1813 svn_status_out=`$SVN status "$@"`
1814 svn_status_rv=$?
1815 test x"$svn_status_out" = x && return $svn_status_rv
1816 echo "$svn_status_out" | sed "$sed_svn_st_color"
1817 return $svn_status_rv
1820 # svn_update [args...]
1821 svn_update()
1823 svn_update_out=`$SVN update "$@"`
1824 svn_update_rv=$?
1825 echo "$svn_update_out" | sed "$sed_svn_up_colors"
1826 return $svn_update_rv
1829 # ------------------- #
1830 # `main' starts here. #
1831 # ------------------- #
1833 # Define colors if stdout is a tty.
1834 if test -t 1; then
1835 set_colors
1836 else # stdout isn't a tty => don't print colors.
1837 set_nocolors
1840 # Consider this as a sed function :P.
1841 sed_svn_st_color="
1842 t dummy_sed_1
1843 : dummy_sed_1
1844 s@^?\\(......\\)+@+\\1+@
1845 s@^?\\(......\\)\\(.*/\\)+@+\\1\\2+@
1846 s@^?\\(......\\),@,\\1,@
1847 s@^?\\(......\\)\\(.*/\\),@,\\1\\2,@
1848 s/^\\(.\\)C/\\1${lred}C${std}/
1849 t dummy_sed_2
1850 : dummy_sed_2
1851 s/^?/${lred}?${std}/; t
1852 s/^M/${lgreen}M${std}/; t
1853 s/^A/${lgreen}A${std}/; t
1854 s/^X/${lblue}X${std}/; t
1855 s/^+/${lyellow}+${std}/; t
1856 s/^D/${lyellow}D${std}/; t
1857 s/^,/${lred},${std}/; t
1858 s/^C/${lred}C${std}/; t
1859 s/^I/${purple}I${std}/; t
1860 s/^R/${lblue}R${std}/; t
1861 s/^!/${lred}!${std}/; t
1862 s/^~/${lwhite}~${std}/; t"
1864 sed_svn_up_colors="
1865 t dummy_sed_1
1866 : dummy_sed_1
1868 /^Updated/ t
1869 /^Fetching/ t
1870 /^External/ t
1871 s/^\\(.\\)C/\\1${lred}C${std}/
1872 s/^\\(.\\)U/\\1${lgreen}U${std}/
1873 s/^\\(.\\)D/\\1${lred}D${std}/
1874 t dummy_sed_2
1875 : dummy_sed_2
1876 s/^A/${lgreen}A${std}/; t
1877 s/^U/${lgreen}U${std}/; t
1878 s/^D/${lyellow}D${std}/; t
1879 s/^G/${purple}G${std}/; t
1880 s/^C/${lred}C${std}/; t"
1882 # For dev's:
1883 test "x$1" = x--debug && shift && set -x
1885 test "x$1" = x--git && shift && git_mode=: && SVN=git
1887 case "$1" in
1888 # ------------------------------- #
1889 # Hooks for standard SVN commands #
1890 # ------------------------------- #
1891 commit | ci)
1892 shift
1893 svn_commit "$@"
1895 help | \? | h)
1896 shift
1897 svn_help "$@"
1899 # FIXME: Incomplete
1900 # merge)
1901 # shift
1902 # svn_merge "$@"
1903 # ;;
1904 status | stat | st)
1905 shift
1906 svn_status "$@"
1908 update | up)
1909 shift
1910 svn_update "$@"
1912 # -------------------- #
1913 # Custom SVN commands #
1914 # -------------------- #
1915 automerge | auto-merge | amerge | am)
1916 shift
1917 if [ $# -ne 2 ]; then
1918 abort "automerge: not enough arguments provided;
1919 try 'svn help automerge' for more info"
1921 svn_automerge "$@"
1923 diffstat | ds)
1924 shift
1925 if [ -d .git ]; then
1926 git diff --stat -C
1927 else
1928 require_diffstat && $SVN diff --no-diff-deleted "$@" | diffstat
1931 diffw | dw)
1932 shift
1933 svn_diffw "$@"
1935 ignore)
1936 shift
1937 svn_ignore "$@"
1939 mail)
1940 shift
1941 svn_mail "$@"
1943 propadd | padd | pa)
1944 shift
1945 svn_propadd "$@"
1947 proposal)
1948 shift
1949 svn_commit --dry-run "$@"
1951 propsed | psed)
1952 shift
1953 svn_propsed "$@"
1955 revision | rev)
1956 shift
1957 svn_revision "$@"
1959 touch)
1960 shift
1961 touch "$@" && $SVN add "$@"
1963 selfupdate | selfup | self-update | self-up)
1964 shift
1965 selfupdate "$@"
1967 version | -version | --version)
1968 shift
1969 set dummy '--version' "$@"
1970 shift
1971 svn_version
1972 exec $SVN "$@"
1974 *) exec $SVN "$@"
1976 esac