#!/bin/sh # sunrise-commit -- a helper script for Sunrise commiters. # (c) 2010 Michał Górny # Released under the terms of the 3-clause BSD license. # Few output helpers. say() { echo "${@}" >&2 } die() { say "${RED}${@}${RESET}" exit 1 } sayv() { [ -n "${SC_VERBOSE}" ] && say "${GREEN}${@}${RESET}" } req() { "$@" || die "$@ failed." } # POSIX compat. local_supported() { local test 2>/dev/null } local_supported || eval 'local() { unset ${@} }' # See if we're in a repo, and what VCS are we using. find_repo() { svn info >/dev/null 2>&1 if [ ${?} -eq 0 ]; then SC_VCS=svn else local remotes remotes=$(git branch -r 2>/dev/null) if [ ${?} -ne 127 -a ${?} -ne 128 ]; then echo "${remotes}" | grep git-svn >/dev/null 2>&1 if [ ${?} -eq 0 ]; then SC_VCS=git-svn else SC_VCS=git fi else die 'Unable to find any familiar repository type (are you inside the repo?).' fi fi sayv "Ok, we're in ${SC_VCS} working tree. Let's see what I can do around here..." } is_whole_dir_removed() { if [ ${SC_VCS} = svn ]; then local flist flist=$(find "${1}" \( -name '.svn' -prune -o ! -name "${1}" -print \)) [ -z "${flist}" ] elif [ ${SC_VCS%-svn} = git ]; then [ ! -e "${1}" ] fi } # Check whether we're having a clean package removal. is_package_removal() { local fields list [ -d profiles ] && fields=1-2 || fields=1 if [ ${SC_VCS%-svn} = git ]; then list=$(git diff-index --relative --name-only --diff-filter=D HEAD) elif [ ${SC_VCS} = svn ]; then list=$(svn status -q | sed -n -e 's/^D //p') fi list=$(echo "${list}" | cut -d / -f ${fields} | sort | uniq) # 1) We have to have any removes. [ -z "${list}" ] && return 1 # Few more checks. local dir olist for dir in ${list}; do # 2) These removes have to remove whole directories. is_whole_dir_removed ${dir} && olist=${olist+${olist} }${dir} done [ -z "${olist}" ] && return 1 SC_CHANGE_LIST=${olist} return 0 } # Look around for ebuilds. find_ebuilds() { # POSIX is fun -- look for ebuilds in the current directory. if [ -n "$(find \( -name '*.ebuild' -print -o ! -name '.' -prune \))" ]; then local stripped # Get CATEGORY and PN. stripped=${PWD%/*} stripped=${stripped%/*} SC_CP=${PWD#${stripped}/} SC_SCENARIO=ebuild-commit sayv "We have some ebuilds for ${SC_CP} here." elif is_package_removal; then local cplist category pkg rootprefix # We can either have the category on the list or in PWD. if [ -d profiles ]; then category= else local stripped stripped=${PWD%/*} category=${PWD#${stripped}/}/ fi SC_CP= # Now we can have multiple packages around. for pkg in ${SC_CHANGE_LIST}; do if [ -z "${category}" ]; then case ${pkg} in eclass/*|licenses/*|local/*|profiles/*|scripts/*) continue ;; esac fi SC_CP=${SC_CP:+${SC_CP}, }${category}${pkg} done local root sdir root=${category:+../} for sdir in eclass licenses profiles; do check_for_changes ${root}${sdir} >/dev/null && SC_CHANGE_LIST="${SC_CHANGE_LIST} ${root}${sdir}" done SC_SCENARIO=package-removal sayv "We're removing ${SC_CP}." else die 'No familiar scenario found.' fi } # VCS helpers. check_for_changes() { local output if [ ${SC_VCS%-svn} = git ]; then output=$(git diff-index --name-only --relative HEAD "$@") elif [ ${SC_VCS} = svn ]; then output=$(svn status "$@") fi [ -z "${output}" ] && return 1 # We do not care about user mangling ChangeLog, we will reset it anyway. echo "${output}" | grep -v ChangeLog >/dev/null } vcs_reset() { if [ ${SC_VCS%-svn} = git ]; then req git reset -q HEAD "${1}" git checkout -f "${1}" 2>/dev/null || req rm -f ChangeLog elif [ ${SC_VCS} = svn ]; then req rm -f ChangeLog svn up ChangeLog >/dev/null 2>&1 fi } vcs_status() { if [ ${SC_VCS%-svn} = git ]; then git status -s ${1-.} "${@}" elif [ ${SC_VCS} = svn ]; then svn status "${@}" fi } vcs_add() { ${SC_VCS%-svn} add "$@" } vcs_commit() { local msg msg=${1} shift if [ ${SC_VCS%-svn} = git ]; then exec git commit -m "${msg}" ${1+-o} "${@}" elif [ ${SC_VCS} = svn ]; then exec svn commit -m "${msg}" "${@}" fi } print_help() { cat <<_EOH_ Synopsis: sunrise-commit [options] [--] Options: -?, -h, --help print this message, -V, --version print version string, -c, --changelog backwards compat (ignored), -C, --nocolor disable colorful output, -f, --force force repoman to proceed with the commit, -t, --trivial trivial changes (do not add a ChangeLog entry), -v, --verbose enable verbose output. _EOH_ } confirm() { while true; do local answ printf '%s' "${WHITE}Commit changes?${RESET} [${BGREEN}Yes${RESET}/${RED}No${RESET}] ${GREEN}" >&2 read answ printf '%s' "${RESET}" case "${answ}" in [yY]|[yY][eE]|[yY][eE][sS]) break ;; [nN]|[nN][oO]) die 'Aborting.' ;; *) say "Response '${answ}' not understood, try again." esac done } # Guess what! main() { local commitmsg force monochrome trivial unset SC_VERBOSE while [ ${#} -gt 0 ]; do case "${1}" in --help|-\?|-h) print_help exit 0 ;; --version|-V) echo 'sunrise-commit 0.3' exit 0 ;; -c|--changelog) # Now changelog entries are implicit -- backwards compat. ;; -C|--nocolor) monochrome=1 ;; -f|--force) force=1 ;; -t|--trivial) trivial=1 ;; -v|--verbose) SC_VERBOSE=1 ;; --) shift commitmsg=${commitmsg+${commitmsg} }${@} break ;; -*) die "Unknown option: ${1}; see --help." >&2 ;; *) commitmsg=${commitmsg+${commitmsg} }${1} ;; esac shift done if [ -n "${monochrome}" ]; then RESET= RED= GREEN= BGREEN= YELLOW= WHITE= else local esc esc=$(printf '\033[') RESET=${esc}0m RED="${esc}1;31m" GREEN=${esc}32m BGREEN="${esc}1;32m" YELLOW="${esc}1;33m" WHITE="${esc}1;37m" fi [ -n "${commitmsg}" ] || die 'No commit message provided.' find_repo find_ebuilds case ${SC_SCENARIO} in ebuild-commit) check_for_changes || die 'No changes found to commit.' # With native git repos, we do not do ChangeLogs by default... # ...at least unless they're already there. if [ ${SC_VCS} != git -o -f ChangeLog ]; then sayv 'Cleaning up the ChangeLog...' vcs_reset ChangeLog local bns # Let's take a lucky guess bugnumbers consist of 4+ digits. bns=$(echo "${commitmsg}" | grep -o -E '[0-9]{4,}') # Creating a new ChangeLog? Let's take a look at the commit message. if [ ! -f ChangeLog ]; then [ -n "${trivial}" ] && die "Trivial change for an initial commit? You're joking, right?" [ -z "${bns}" ] && die 'Please supply the bug number in the initial commit message!' if [ ! -f metadata.xml ]; then req cp ../../skel.metadata.xml metadata.xml # Output similar to echangelog. diff -dup /dev/null metadata.xml req vcs_add metadata.xml fi fi if [ -z "${trivial}" ]; then sayv '...and appending to it.' echangelog "${commitmsg}" || die 'Please correct the problems pointed by echangelog.' echo fi if [ -n "${bns}" ]; then local bn cbn for bn in ${bns}; do cbn=#${WHITE}${bn}${NORMAL} wget -q http://bugs.gentoo.org/show_bug.cgi?id=${bn} -O - | sed -n \ -e "s, *Gentoo Bug \([0-9]*\) - \(.*\),Bug ${cbn}: ${BGREEN}\2${RESET},p" \ -e "s, *Gentoo \(Invalid Bug ID\),Bug ${cbn}: ${YELLOW}!! \1${RESET},p" done echo fi fi vcs_status echo # Do we have repoman new enough? local old_repoman repoman --version -a >/dev/null 2>&1 if [ $? -eq 2 ]; then old_repoman= say "${GREEN}Please update portage to newer version in order to have repoman supporting" say "--ask option and thus delay the following question until after 'repoman full'.${RESET}" say confirm fi sayv "Now, let's let repoman do its job..." exec repoman commit ${old_repoman--a} ${force+-f} -m "${SC_CP}: ${commitmsg}" ;; package-removal) vcs_status ${SC_CHANGE_LIST} echo say "Ready to commit ${WHITE}$(echo ${SC_CHANGE_LIST} | wc -w)${RESET} package removal(s), with message:" say "${BGREEN}${SC_CP}: ${commitmsg}${RESET}" confirm vcs_commit "${SC_CP}: ${commitmsg}" ${SC_CHANGE_LIST} ;; esac } main "${@}"