#! /bin/sh # clcommit (GNU cvs-utils) version 0.11 # Written by Gary V. Vaughan # and Alexandre Oliva # Copyright (C) 1999, 2000, 2004, 2006, 2007 Free Software Foundation, Inc. # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # Usage: $progname [-v] [-h] [-f] [-l] [-n] [-q] [-z N] [-C ChangeLog_file] # [-m msg|-F msg_file|-1] [-s addr [--from addr]] [--] [file|dir ...] # -C file --changelog=file extract commit message from specified ChangeLog # -zN --compress=N set compression level (0-9, 0=none, 9=max) # -n --dry-run don't commit anything # --fast same as --force --first # -F file --file=file read commit message from file # -1 --first extract first entry from ChangeLog, no cvs diff # -f --force don't check (unless *followed* by -n), and just # display commit message instead of running $PAGER # --from=addr override default from address in commit email # -l --local don't descend into subdirectories # -m msg --message=msg set commit message # --msg=msg same as -m # -q --quiet run cvs in quiet mode # -s addr --sendmail=addr send a commit email of the differences to ADDR # --signature[=file] add FILE to the end of the email (~/.signature) # -v --version print version information # -h,-? --help print short or long help message # This script eases checking in changes to CVS-maintained projects # with ChangeLog files. It will check that there have been no # conflicting commits in the CVS repository and print which files it # is going to commit to stderr. A list of files to compare and to # check in can be given in the command line. If it is not given, all # files in the current directory (and below, unless `-l' is given) are # considered for check in. # The commit message will be extracted from the differences between a # file named ChangeLog* in the commit list, or named after -C, and the # one in the repository (unless a message was specified with `-m' or # `-F'). An empty message is not accepted (but a blank line is). If # the message is acceptable, it will be presented for verification # (and possible edition) using the $PAGER environment variable (or # `more', if it is not set, or `cat', if the `-f' switch is given). # If $PAGER exits successfully, the modified files (at that moment) # are checked in, unless `-n' was specified, in which case nothing is # checked in. # Report bugs to : ${CVS="cvs"} : ${MAILNOTIFY="./ltdl/config/mailnotify"} : ${MKSTAMP="./ltdl/config/mkstamp"} : ${MV="mv -f"} : ${RM="rm -f"} : ${SED="sed"} SHELL="/bin/sh" dirname="s,/[^/]*$,," basename="s,^.*/,,g" # Work around backward compatibility issue on IRIX 6.5. On IRIX 6.4+, sh # is ksh but when the shell is invoked as "sh" and the current value of # the _XPG environment variable is not equal to 1 (one), the special # positional parameter $0, within a function call, is the name of the # function. progpath="$0" # The name of this program: progname=`echo "$progpath" | $SED "$basename"` PROGRAM=clcommit # Global variables: EXIT_SUCCESS=0 EXIT_FAILURE=1 cvs_flags= update_flags= commit_flags= opt_commit=: opt_update=: opt_first=false sendmail_to= sendmail_from= exit_cmd=: # Locations for important files signature_file= log_file="${TMPDIR-/tmp}/commitlog.$$" $RM "${log_file}*" trap '$RM "${log_file}*"; exit $EXIT_FAILURE' 1 2 15 set -e # func_echo arg... # Echo program name prefixed message. func_echo () { echo $progname: ${1+"$@"} } # func_error arg... # Echo program name prefixed message to standard error. func_error () { echo $progname: ${1+"$@"} 1>&2 } # func_fatal_error arg... # Echo program name prefixed message to standard error, and exit. func_fatal_error () { func_error ${1+"$@"} exit $EXIT_FAILURE } # func_verbose arg... # Echo program name prefixed message in verbose mode only. func_verbose () { $opt_verbose && func_error ${1+"$@"} } # func_fatal_help arg... # Echo program name prefixed message to standard error, followed by # a help hint, and exit. func_fatal_help () { func_error ${1+"$@"} func_fatal_error "Try \`$progname --help' for more information." } # func_missing_arg argname # Echo program name prefixed message to standard error and set global # exit_cmd. func_missing_arg () { func_error "missing argument for $1" exit_cmd=exit } # func_usage # Echo short help message to standard output and exit. func_usage () { $SED '/^# Usage:/,/# -h/ { s/^# //; s/^# *$//; s/\$progname/'$progname'/; p; }; d' < "$progpath" echo echo "run \`$progname --help | more' for full usage" exit $EXIT_SUCCESS } # func_help # Echo long help message to standard output and exit. func_help () { $SED '/^# Usage:/,/# Report bugs to/ { s/^# //; s/^# *$//; s/\$progname/'$progname'/; p; }; d' < "$progpath" exit $EXIT_SUCCESS } # func_version # Echo version message to standard output and exit. func_version () { $SED '/^# '$PROGRAM' (GNU /,/# warranty; / { s/^# //; s/^# *$//; s/\((C)\)[ 0-9,-]*\( [1-9][0-9]*\)/\1\2/; p; }; d' < "$progpath" exit $EXIT_SUCCESS } # Parse options once, thoroughly. This comes as soon as possible in # the script to make things like `commit --version' happen quickly. { # sed scripts: my_sed_single_opt='1s/^\(..\).*$/\1/;q' my_sed_single_rest='1s/^..\(.*\)$/\1/;q' my_sed_long_opt='1s/^\(--[^=]*\)=.*/\1/;q' my_sed_long_arg='1s/^--[^=]*=//' # this just eases exit handling while test $# -gt 0; do opt="$1" shift case $opt in --fast) set -- --force --first ${1+"$@"} ;; -f|--force) opt_update=false; PAGER=cat ;; --from) test $# = 0 && func_missing_arg $opt && break sendmail_from="$1" shift ;; -l|--local) update_flags="$update_flags -l" commit_flags="$commit_flags -l" ;; -m|--message|--msg) test $# = 0 && func_missing_arg $opt && break if $opt_first || test -f "$log_file"; then func_error "you can have at most one of -m, -F and -1" break fi echo "$1" > "$log_file" shift ;; -F|--file) test $# = 0 && func_missing_arg $opt && break if $opt_first || test -f "$log_file"; then func_error "you can have at most one of -m, -F and -1" break fi if cat < "$1" > "$log_file"; then :; else break fi shift ;; -1|--first) if test -f "$log_File"; then func_error "you can have at most one of -m, -F and -1" break fi opt_first=: ;; -C|--[cC]hange[lL]og) test $# = 0 && func_missing_arg $opt && break if test -f "$1"; then :; else func_error "ChangeLog file \`$1' does not exist" break fi ChangeLog="$1" shift ;; -n|--dry-run) opt_commit=false; opt_update=: ;; -q|--quiet) cvs_flags="$cvs_flags -q" ;; -s|--sendmail) test $# = 0 && func_missing_arg $opt && break sendmail_to="$1" shift ;; --signature) test $# = 0 && func_missing_arg $opt && break signature_file="$HOME/.signature" case $1 in -*) ;; *) signature_file="$1"; shift ;; esac if test -f "$signature_file"; then :; else func_error "\`$signature_file': file not found" break fi ;; -z|--compress) test $# = 0 && func_missing_arg $opt && break case "$1" in [0-9]) :;; *) func_error "invalid argument for $opt" break ;; esac cvs_flags="$cvs_flags -z$1" shift ;; # Separate optargs to long options: --message=*|--msg=*|--from=*|--file=*|--[Cc]hange[Ll]og=*|--compress=*|--sendmail=*|--signature=*) arg=`echo "$opt" | $SED "$my_sed_long_arg"` opt=`echo "$opt" | $SED "$my_sed_long_opt"` set -- "$opt" "$arg" ${1+"$@"} ;; # Separate optargs to short options: -m*|-F*|-C*|-s*|-z*) arg=`echo "$opt" |$SED "$my_sed_single_rest"` opt=`echo "$opt" |$SED "$my_sed_single_opt"` set -- "$opt" "$arg" ${1+"$@"} ;; # Separate non-argument short options: -f*|-1*|-n*|-q*) rest=`echo "$opt" |$SED "$my_sed_single_rest"` opt=`echo "$opt" |$SED "$my_sed_single_opt"` set -- "$opt" "-$rest" ${1+"$@"} ;; -\?|-h) func_usage ;; --help) func_help ;; --version) func_version ;; --) break ;; -*) func_fatal_help "unrecognized option \`$opt'" ;; *) set -- "$opt" ${1+"$@"}; break ;; esac done if test -z "$sendmail_to"; then # can't have a from address without a destination address test -n "$sendmail_from" && func_error "can't use --from without --sendmail." && exit_cmd=exit # can't use a signature file without a destination address test -n "$signature_file" && func_error "can't use --signature without --sendmail." && exit_cmd=exit fi # Bail if the options were screwed $exit_cmd $EXIT_FAILURE } $opt_update && { func_error "$progname: checking for conflicts..." if ( $CVS $cvs_flags -q -n update $update_flags ${1+"$@"} | while read line; do echo "$line" echo "$line" >&3 done | grep '^C' ) 3>&1 >/dev/null; then func_fatal_error "some conflicts were found, aborting..." fi } if test -f "$log_file"; then :; else if test -z "$ChangeLog"; then for f in ${1+"$@"}; do case "$f" in ChangeLog* | */ChangeLog*) if test -z "$ChangeLog"; then ChangeLog="$f" else func_fatal_error "multiple ChangeLog files: $ChangeLog and $f" fi ;; esac done fi func_error "$progname: checking commit message..." if $opt_first; then skipping=: sed 's,^,+,' < ${ChangeLog-ChangeLog} | while read line; do case "$line" in "+") if $skipping; then skipping=false; else break; fi;; "+ "*) func_error "*** Warning: lines should start with tabs, not spaces; ignoring line:" echo "$line" | sed 's/^.//' >&2;; "+ "*) $skipping || echo "$line" ;; esac done | sed 's,^\+ ,,' > "$log_file" || exit $EXIT_FAILURE else $CVS $cvs_flags diff -u ${ChangeLog-ChangeLog} | while read line; do case $line in "--- "*) :;; "-"*) func_error "*** Warning: the following line in ChangeLog diff is suspicious:" echo "$line" | sed 's/^.//' >&2;; "+ "*) func_error "*** Warning: lines should start with tabs, not spaces; ignoring line:" echo "$line" | sed 's/^.//' >&2;; "+") echo ;; "+ "*) echo "$line";; esac done | sed -e 's,\+ ,,' -e '/./p' -e '/./d' -e '1d' -e '$d' > "$log_file" \ || exit $EXIT_FAILURE fi # The sed script above removes "+TAB" from the beginning of a line, then # deletes the first and/or the last line, when they happen to be empty fi grep '[^ ]' < "$log_file" > /dev/null || func_fatal_error "empty commit message, aborting" if grep '^$' < "$log_file" > /dev/null; then func_error "*** Warning: blank lines should not appear within commit messages." func_error "*** They should be used to separate distinct commits." fi ${PAGER-more} "$log_file" || exit $EXIT_FAILURE sleep 1 # give the user some time for a ^C filelist=`cvs -nq up 2>/dev/null | grep '^[MAD] ' | sed 's/^. //'` # Do not check for empty $log_file again, even though the user might have # zeroed it out. If s/he did, it was probably intentional. if $opt_commit; then $CVS $cvs_flags commit $commit_flags -F $log_file ${1+"$@"} || exit $EXIT_FAILURE fi # Send a copy of the log_file if sendmail_to was set: if test -n "$sendmail_to"; then notify_file="${log_file}.2" func_error "Mailing commit notification to $sendmail_to" test $# -gt 0 && filelist="$@" { test -f CVS/Root && echo "CVSROOT: `sed -e 's,.*:,,g' CVS/Root`" test -f $MKSTAMP && echo "TIMESTAMP: `$SHELL $MKSTAMP < ./ChangeLog`" test -f CVS/Repository && echo "Module name: `cat CVS/Repository`" test -f CVS/Tag && echo "Branch: `sed -e 's,^T,,;1q' CVS/Tag`" test -f CVS/Root && echo "Changes by: `sed -e 's,:.*$,,g;s,^.*:,,' CVS/Root`" echo "" echo "Log Message:" sed -e 's,^, ,' "$log_file" test -f "$signature_file" && cat "$signature_file" } > "$notify_file" if test -n "$sendmail_from"; then $SHELL $MAILNOTIFY -F "$sendmail_from" -s "`echo $filelist`" -f "$notify_file" -m "text/plain" "$sendmail_to" else $SHELL $MAILNOTIFY -s "`echo $filelist`" -f "$notify_file" -m "text/plain" "$sendmail_to" fi fi $RM "${log_file}*" exit $EXIT_SUCCESS # Local Variables: # mode:shell-script # sh-indentation:2 # End: