72435c1a73
Use VCS functions to check whether the cached/to-be-committed version of the working tree contains the particular directory instead of using the local working tree. Thanks to that, the functions no longer care about stray files.
456 lines
11 KiB
Bash
Executable File
456 lines
11 KiB
Bash
Executable File
#!/bin/sh
|
|
# sunrise-commit -- commit changes to the Sunrise overlay.
|
|
# (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."
|
|
}
|
|
|
|
# -- 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() {
|
|
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 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
|
|
[ -z "$(svn status --depth=empty -- "${1}")" ]
|
|
elif [ ${SC_VCS%-svn} = git ]; then
|
|
[ -z "$(git ls-files -c -- "${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; 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=
|
|
# 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 whether a particular locations have changed, ignoring ChangeLog
|
|
# changes.
|
|
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
|
|
}
|
|
|
|
# Discard any changes to a particular set of files.
|
|
vcs_reset() {
|
|
if [ ${SC_VCS%-svn} = git ]; then
|
|
req git reset -q HEAD "${@}"
|
|
git checkout -f "${@}" 2>/dev/null || req rm -f "${@}"
|
|
elif [ ${SC_VCS} = svn ]; then
|
|
req rm -f "${@}"
|
|
svn up "${@}" >/dev/null 2>&1
|
|
fi
|
|
}
|
|
|
|
# Request VCS to provide a verbose status report.
|
|
vcs_status() {
|
|
if [ ${SC_VCS%-svn} = git ]; then
|
|
git status -s ${1-.} "${@}"
|
|
elif [ ${SC_VCS} = svn ]; then
|
|
svn status "${@}"
|
|
fi
|
|
}
|
|
|
|
# Add particular files to the repository.
|
|
vcs_add() {
|
|
${SC_VCS%-svn} 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%-svn} = git ]; then
|
|
exec git commit -m "${msg}" ${1+-o} "${@}"
|
|
elif [ ${SC_VCS} = svn ]; then
|
|
exec svn commit -m "${msg}" "${@}"
|
|
fi
|
|
}
|
|
|
|
# Call VCS to update the working copy to HEAD revision.
|
|
vcs_update() {
|
|
# Unlike svn, git doesn't push the changes to origin immediately,
|
|
# and that's why we don't force update to it right here.
|
|
if [ ${SC_VCS} = svn ]; then
|
|
svn up || say 'Warning: svn up failed, trying to proceed anyway.'
|
|
fi
|
|
}
|
|
|
|
# Print the help message.
|
|
print_help() {
|
|
cat <<_EOH_
|
|
Synopsis:
|
|
sunrise-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 '%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 'sunrise-commit 0.3'
|
|
exit 0
|
|
;;
|
|
|
|
-c|--changelog)
|
|
# Now changelog entries are implicit -- backwards compat.
|
|
;;
|
|
-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
|
|
commitmsg=${commitmsg+${commitmsg} }${@}
|
|
break
|
|
;;
|
|
-*)
|
|
die "Unknown option: ${1}; see --help." >&2
|
|
;;
|
|
*)
|
|
commitmsg=${commitmsg+${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.'
|
|
|
|
# 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 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
|
|
|
|
vcs_status
|
|
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
|
|
|
|
if [ -n "${noupdate}" ]; then
|
|
sayv "Updating the repository..."
|
|
vcs_update
|
|
fi
|
|
|
|
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
|
|
say "Ready to commit ${WHITE}$(echo ${SC_CHANGE_LIST} | wc -w)${RESET} package removal(s), with commit message:"
|
|
say "${BGREEN}${SC_CP}: ${commitmsg}${RESET}"
|
|
confirm
|
|
|
|
if [ -n "${noupdate}" ]; then
|
|
sayv "Updating the repository..."
|
|
vcs_update ${SC_CHANGE_LIST}
|
|
fi
|
|
|
|
# XXX: a consistency check on *DEPENDs, package.mask
|
|
|
|
vcs_commit "${noprepend-${SC_CP}: }${commitmsg}" ${SC_CHANGE_LIST}
|
|
;;
|
|
esac
|
|
}
|
|
|
|
main "${@}"
|