534 lines
14 KiB
Bash
534 lines
14 KiB
Bash
#!/bin/sh
|
|
# go-commit -- a Gentoo repository commit helper
|
|
# (c) 2010 Michał Górny <gentoo@mgorny.alt.pl>
|
|
# Released under the terms of the 3-clause BSD license.
|
|
|
|
# -- output helpers --
|
|
|
|
# Output the message to STDERR.
|
|
say() {
|
|
echo "${@}" >&2
|
|
}
|
|
|
|
# Output the error message and abort the script with non-zero status.
|
|
die() {
|
|
say "${RED}${@}${RESET}"
|
|
exit 1
|
|
}
|
|
|
|
# Output the debug message if --verbose was used.
|
|
sayv() {
|
|
[ -n "${SC_VERBOSE}" ] && say "${GREEN}${@}${RESET}"
|
|
}
|
|
|
|
# Execute the command and die with simple error message if it fails.
|
|
req() {
|
|
"${@}" || die "'${@}' failed."
|
|
}
|
|
|
|
# Append the arguments to an IFS-separated list variable whose name was
|
|
# passed as the first arg.
|
|
array_append() {
|
|
local varname
|
|
varname=${1}
|
|
shift
|
|
|
|
eval "set -- \${${varname}} \"\${@}\"; ${varname}=\${*}"
|
|
}
|
|
|
|
# -- POSIX compat --
|
|
|
|
# Check whether 'local' is supported.
|
|
local_supported() {
|
|
PATH= local test 2>/dev/null
|
|
}
|
|
|
|
# If it is not, declare dummy local() function unsetting the variables.
|
|
local_supported || eval 'local() {
|
|
unset "${@}"
|
|
}'
|
|
|
|
# -- 'look around' functions --
|
|
|
|
# See if we're in a repo, and what VCS are we using.
|
|
find_repo() {
|
|
: ${SC_WANT_CHANGELOG=}
|
|
|
|
if svn info >/dev/null 2>&1; then
|
|
SC_VCS=svn
|
|
SC_WANT_CHANGELOG=1
|
|
elif cvs status -l >/dev/null 2>&1; then
|
|
SC_VCS=cvs
|
|
SC_WANT_CHANGELOG=1
|
|
elif hg tip >/dev/null 2>&1; then
|
|
SC_VCS=hg
|
|
else
|
|
local remotes ret
|
|
remotes=$(git branch -r 2>/dev/null)
|
|
ret=${?}
|
|
|
|
if [ ${ret} -ne 127 ] && [ ${ret} -ne 128 ]; then
|
|
if echo "${remotes}" | grep git-svn >/dev/null 2>&1; then
|
|
SC_WANT_CHANGELOG=1
|
|
fi
|
|
SC_VCS=git
|
|
else
|
|
die 'Unable to find any familiar repository type (are you inside the repo?).'
|
|
fi
|
|
fi
|
|
|
|
sayv "Ok, we're in the ${SC_VCS} working tree. Let's see what I can do around here..."
|
|
}
|
|
|
|
# Check whether a particular directory has been completely removed
|
|
# from the repo.
|
|
is_whole_dir_removed() {
|
|
if [ ${SC_VCS} = svn ]; then
|
|
[ "$(svn status --depth=empty -- "${1}" | wc -l)" = 1 ]
|
|
elif [ ${SC_VCS} = git ]; then
|
|
[ -z "$(git ls-files -c -- "${1}")" ]
|
|
elif [ ${SC_VCS} = hg ]; then
|
|
[ -z "$(hg status -madc "${1}")" ]
|
|
elif [ ${SC_VCS} = cvs ]; then
|
|
[ -z "$(cvs -Q status -R "${1}" 2>/dev/null | grep '^File:' | grep -v 'Status: Locally Removed$')" ]
|
|
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} = git ]; then
|
|
list=$(git diff-index --relative --name-only --diff-filter=D HEAD)
|
|
elif [ ${SC_VCS} = hg ]; then
|
|
list=$(hg status -nr .)
|
|
elif [ ${SC_VCS} = cvs ]; then
|
|
list=$(cvs -n -q up 2>/dev/null | sed -n -e 's/^R//p')
|
|
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} && array_append olist "${dir}"
|
|
done
|
|
|
|
[ -z "${olist}" ] && return 1
|
|
|
|
SC_CHANGE_LIST=${olist}
|
|
return 0
|
|
}
|
|
|
|
# Look around for ebuilds; determine the scenario we're working on.
|
|
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 found 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=
|
|
SC_REMOVED_PACKAGE_LIST=
|
|
# 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}
|
|
array_append SC_REMOVED_PACKAGE_LIST "${category}${pkg}"
|
|
done
|
|
|
|
SC_ROOT=${category:+../}
|
|
|
|
# Replace with the filtered version, placing all atoms
|
|
# relative to SC_ROOT.
|
|
SC_CHANGE_LIST=
|
|
for pkg in ${SC_REMOVED_PACKAGE_LIST}; do
|
|
array_append SC_CHANGE_LIST "${SC_ROOT}${pkg}"
|
|
done
|
|
|
|
local sdir
|
|
for sdir in eclass licenses profiles; do
|
|
check_for_changes ${SC_ROOT}${sdir} >/dev/null && SC_CHANGE_LIST="${SC_CHANGE_LIST} ${SC_ROOT}${sdir}"
|
|
done
|
|
SC_SCENARIO=package-removal
|
|
sayv "We're removing ${SC_CP}."
|
|
else
|
|
die 'No familiar scenario found.'
|
|
fi
|
|
}
|
|
|
|
# -- VCS helpers --
|
|
|
|
# Check whether a particular locations have changed, ignoring ChangeLog
|
|
# changes.
|
|
check_for_changes() {
|
|
local output
|
|
|
|
if [ ${SC_VCS} = git ]; then
|
|
output=$(git diff-index --name-only --relative HEAD -- "${@}")
|
|
elif [ ${SC_VCS} = hg ]; then
|
|
output=$(hg status -- ${1-.} "${@}")
|
|
elif [ ${SC_VCS} = svn ]; then
|
|
output=$(svn status -- "${@}")
|
|
elif [ ${SC_VCS} = cvs ]; then
|
|
# `U' indicates a remote, incoming update.
|
|
output=$(cvs -n -q update -R -- "${@}" 2>/dev/null | grep -v '^U')
|
|
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
|
|
}
|
|
|
|
# Discard any changes to a particular set of files.
|
|
vcs_reset() {
|
|
if [ ${SC_VCS} = git ]; then
|
|
git checkout HEAD -- "${@}" 2>/dev/null || req rm -f -- "${@}"
|
|
elif [ ${SC_VCS} = hg ]; then
|
|
[ -n "$(hg status -au "${@}")" ] && req rm -f -- "${@}"
|
|
hg revert --no-backup -- "${@}" 2>/dev/null
|
|
elif [ ${SC_VCS} = svn ]; then
|
|
req rm -f -- "${@}"
|
|
svn revert -- "${@}" >/dev/null
|
|
elif [ ${SC_VCS} = cvs ]; then
|
|
# cvs update -C does exist, but it sometimes doesn't
|
|
# work.
|
|
req rm -f -- "${@}"
|
|
cvs update -- "${@}" >/dev/null 2>&1
|
|
fi
|
|
}
|
|
|
|
# Request VCS to provide a verbose status report.
|
|
vcs_status() {
|
|
if [ ${SC_VCS} = git ]; then
|
|
git status -s -- ${1-.} "${@}"
|
|
elif [ ${SC_VCS} = hg ]; then
|
|
hg status -- ${1-.} "${@}"
|
|
elif [ ${SC_VCS} = svn ]; then
|
|
svn status -- "${@}"
|
|
elif [ ${SC_VCS} = cvs ]; then
|
|
cvs -n -q up -- "${@}" 2>/dev/null | grep -v '^U'
|
|
fi
|
|
}
|
|
|
|
# Add particular files to the repository.
|
|
vcs_add() {
|
|
${SC_VCS} add -- "${@}"
|
|
}
|
|
|
|
# Commit the specified objects using the commit message provided
|
|
# as the first argument. Does not return.
|
|
vcs_commit() {
|
|
local msg
|
|
msg=${1}
|
|
shift
|
|
|
|
if [ ${SC_VCS} = git ]; then
|
|
exec git commit -m "${msg}" ${1+-o} -- "${@}"
|
|
elif [ ${SC_VCS} = hg ]; then
|
|
exec hg commit -m "${msg}" -- ${1-.} "${@}"
|
|
elif [ ${SC_VCS} = svn ]; then
|
|
exec svn commit -m "${msg}" -- "${@}"
|
|
elif [ ${SC_VCS} = cvs ]; then
|
|
exec cvs commit -m "${msg}" -- "${@}"
|
|
fi
|
|
}
|
|
|
|
# Call VCS to update the working copy to HEAD revision.
|
|
vcs_update() {
|
|
# Unlike svn, DVCSes don't push the changes to their origins immediately.
|
|
# That's why we don't force update to it right here.
|
|
if [ ${SC_VCS} = svn ]; then
|
|
sayv "Updating the repository..."
|
|
svn up -- "${@}" || say "Warning: ${SC_VCS} up failed, trying to proceed anyway."
|
|
elif [ ${SC_VCS} = cvs ]; then
|
|
sayv "Updating the repository..."
|
|
cvs up -d -P -- "${@}" || say "Warning: ${SC_VCS} up failed, trying to proceed anyway."
|
|
fi
|
|
}
|
|
|
|
# Print the help message.
|
|
print_help() {
|
|
cat <<_EOH_
|
|
Synopsis:
|
|
go-commit [options] [--] <commit message>
|
|
|
|
Options:
|
|
-?, -h, --help print this message,
|
|
-V, --version print version string,
|
|
|
|
-c, --changelog backwards compat (ignored),
|
|
-C, --nocolor disable colorful output,
|
|
-d, --noupdate disable updating the repository,
|
|
-f, --force force repoman to proceed with the commit,
|
|
-m, --noformat do not prepend the commit message with package names,
|
|
-q, --quiet backwards compat (ignored),
|
|
-t, --trivial trivial changes (do not add a ChangeLog entry),
|
|
-v, --verbose enable verbose output.
|
|
_EOH_
|
|
}
|
|
|
|
# Request confirmation before committing. Abort if it is not granted.
|
|
confirm() {
|
|
while true; do
|
|
local answ
|
|
printf '\n%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 "Your response '${answ}' not understood, try again."
|
|
esac
|
|
done
|
|
}
|
|
|
|
# Guess what!
|
|
main() {
|
|
local commitmsg force monochrome noprepend noupdate trivial
|
|
unset SC_VERBOSE
|
|
|
|
# Command-line parsing.
|
|
while [ ${#} -gt 0 ]; do
|
|
case "${1}" in
|
|
--help|-\?|-h)
|
|
print_help
|
|
exit 0
|
|
;;
|
|
--version|-V)
|
|
echo '@PACKAGE_STRING@'
|
|
exit 0
|
|
;;
|
|
|
|
-c|--changelog)
|
|
SC_WANT_CHANGELOG=1
|
|
;;
|
|
-C|--nocolor)
|
|
monochrome=1
|
|
;;
|
|
-d|--noupdate)
|
|
noupdate=1
|
|
;;
|
|
-f|--force)
|
|
force=1
|
|
;;
|
|
-m|--noformat)
|
|
noprepend=
|
|
;;
|
|
-q|--quiet)
|
|
;;
|
|
-t|--trivial)
|
|
trivial=1
|
|
;;
|
|
-v|--verbose)
|
|
SC_VERBOSE=1
|
|
;;
|
|
|
|
--)
|
|
shift
|
|
array_append commitmsg "${@}"
|
|
break
|
|
;;
|
|
-*)
|
|
die "Unknown option: ${1}; see --help." >&2
|
|
;;
|
|
*)
|
|
array_append commitmsg "${1}"
|
|
;;
|
|
esac
|
|
shift
|
|
done
|
|
|
|
# Initialize colors.
|
|
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.'
|
|
|
|
# Look around.
|
|
find_repo
|
|
find_ebuilds
|
|
|
|
case ${SC_SCENARIO} in
|
|
# Committing changes within the ebuild directory.
|
|
# This includes committing new ebuilds.
|
|
ebuild-commit)
|
|
check_for_changes || die 'No changes found to commit.'
|
|
|
|
if [ -z "${noupdate}" ]; then
|
|
vcs_update
|
|
fi
|
|
|
|
# With DVCS repos, we do not do ChangeLogs by default...
|
|
# ...at least unless they're already there.
|
|
if [ -n "${SC_WANT_CHANGELOG}" ] || [ -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?"
|
|
|
|
# Geos_One-specific checks.
|
|
if [ "$(cat ../../profiles/repo_name 2>/dev/null)" = "go" ]; then
|
|
[ -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
|
|
fi
|
|
|
|
if [ -z "${trivial}" ]; then
|
|
sayv '...and appending to it.'
|
|
echangelog --vcs ${SC_VCS} "${commitmsg}" \
|
|
|| die 'Please correct the problems shown 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, *<title>Gentoo Bug \([0-9]*\) - \(.*\)</title>,Bug ${cbn}: ${BGREEN}\2${RESET},p" \
|
|
-e "s, *<title>Gentoo \(Invalid Bug ID\)</title>,Bug ${cbn}: ${YELLOW}!! \1${RESET},p"
|
|
done
|
|
echo
|
|
fi
|
|
fi
|
|
|
|
if [ ${SC_VCS} != cvs ] || [ -n "${noupdate}" ]; then
|
|
vcs_status
|
|
fi
|
|
echo
|
|
|
|
# Since commit 32264c3, repoman supports '--ask' option,
|
|
# which requests user confirmation before the commit.
|
|
# We like that, because it does it in the right place.
|
|
#
|
|
# If user is using earlier repoman version, we need to
|
|
# request that confirmation ourselves. As we would like
|
|
# the user to see 'repoman full' results first, we need
|
|
# to call it ourselves. Moreover, it requires Manifest to be
|
|
# up-to-date, so we need to call 'repoman manifest' too.
|
|
#
|
|
# That's pretty sad, because it means we're wasting time
|
|
# calling the same repoman functions twice (once manually,
|
|
# then within 'repoman commit'). That's why we would be
|
|
# happy if user updated his/her Portage, and we'd like to
|
|
# encourage him/her to do so -- but we'll have to delay that
|
|
# until a new Portage version is released.
|
|
|
|
local old_repoman
|
|
repoman --version -a >/dev/null 2>&1
|
|
if [ ${?} -eq 2 ]; then
|
|
old_repoman=
|
|
|
|
#say "${GREEN}Please consider updating portage to newer version.${RESET}"
|
|
#say
|
|
req repoman manifest
|
|
if ! repoman full; then
|
|
[ -n "${force}" ] || die 'Please correct the problems shown by repoman.'
|
|
fi
|
|
confirm
|
|
fi
|
|
|
|
# In CVS, we don't prepend the package name to the commit message.
|
|
[ ${SC_VCS} = cvs ] && noprepend=
|
|
|
|
sayv "Now, let's let repoman do its job..."
|
|
exec repoman commit ${old_repoman--a} ${force+-f} -m "${noprepend-${SC_CP}: }${commitmsg}"
|
|
;;
|
|
|
|
# Clean removal of a package set.
|
|
package-removal)
|
|
vcs_status ${SC_CHANGE_LIST}
|
|
echo
|
|
|
|
local pkg regex
|
|
regex=
|
|
for pkg in ${SC_REMOVED_PACKAGE_LIST}; do
|
|
regex=${regex:+${regex}\|}${pkg}
|
|
done
|
|
|
|
say "${GREEN}Grepping for package references...${RESET}"
|
|
# -n is for line numbers, -C would be non-POSIX
|
|
if grep -n "${regex}" ${SC_ROOT}*/*/*.ebuild ${SC_ROOT}*/*/metadata.xml ${SC_ROOT}profiles/package.mask 2>/dev/null; then
|
|
echo
|
|
[ -n "${force}" ] || die 'Please remove the removed package references or use --force.'
|
|
else
|
|
echo
|
|
fi
|
|
|
|
say "Ready to commit ${WHITE}$(echo ${SC_REMOVED_PACKAGE_LIST} | wc -w)${RESET} package removal(s), with commit message:"
|
|
say "${BGREEN}${SC_CP}: ${commitmsg}${RESET}"
|
|
confirm
|
|
|
|
if [ -z "${noupdate}" ]; then
|
|
vcs_update ${SC_CHANGE_LIST}
|
|
fi
|
|
|
|
vcs_commit "${noprepend-${SC_CP}: }${commitmsg}" ${SC_CHANGE_LIST}
|
|
;;
|
|
esac
|
|
}
|
|
|
|
main "${@}"
|