1#!/bin/sh -e 2# 3# rmport - remove port(s) from mports. 4# 5# Copyright 2006-2007 Vasil Dimov 6# Copyright 2012-2018 Chris Rees 7# Copyright 2016-2023 René Ladan 8# All rights reserved. 9# 10# Redistribution and use in source and binary forms, with or without 11# modification, are permitted provided that the following conditions 12# are met: 13# 1. Redistributions of source code must retain the above copyright 14# notice, this list of conditions and the following disclaimer. 15# 2. Redistributions in binary form must reproduce the above copyright 16# notice, this list of conditions and the following disclaimer in the 17# documentation and/or other materials provided with the distribution. 18# 19# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 20# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 22# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 23# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 25# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 26# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 27# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 28# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 29# SUCH DAMAGE. 30# 31# Authors: 32# Originally written by Vasil Dimov <vd@FreeBSD.org> 33# Others: 34# Chris Rees <crees@FreeBSD.org> 35# René Ladan <rene@FreeBSD.org> 36# 37 38EDITOR=${EDITOR:-/usr/bin/vi} 39PORTSDIR=${PORTSDIR:-/usr/mports} 40INDEX=${PORTSDIR}/$(make -C ${PORTSDIR} -V INDEXFILE) 41 42TODAY=$(date -u +%Y-%m-%d) 43 44SED="sed -i .orig -E" 45# use ~/.ssh/config to set up the desired username if different than $LOGNAME 46GITREPO=${GITREPO:git@github.com:MidnightBSD/mports.git} 47 48if [ -n "$(command -v git 2>/dev/null)" ]; then 49 GIT=git 50else 51 echo "git(1) not found. Please install devel/git." 52 exit 66 53fi 54 55log() 56{ 57 echo "==> $*" >&2 58} 59 60escape() 61{ 62 # escape characters that may appear in ports' names and 63 # break regular expressions 64 echo "${1}" |sed -E 's/(\+|\.)/\\\1/g' 65} 66 67pkgname() 68{ 69 make -C ${PORTSDIR}/${1} -V PKGNAME 70} 71 72ask() 73{ 74 question=${1} 75 76 answer=x 77 while [ "${answer}" != "y" ] && [ "${answer}" != "n" ] ; do 78 read -p "${question}? [yn]" answer 79 done 80 81 echo ${answer} 82} 83 84# return category/port if arg is directly port's directory on the filesystem 85find_catport() 86{ 87 arg=${1} 88 89 if [ -d "${PORTSDIR}/${arg}" ] ; then 90 # arg is category/port 91 echo ${arg} 92 elif [ -d "${arg}" ] ; then 93 # arg is the port's directory somewhere in the filesystem 94 # either absolute or relative 95 96 # get the full path 97 rp=$(realpath ${arg}) 98 99 category=$(basename $(dirname ${rp})) 100 port=$(basename ${rp}) 101 echo ${category}/${port} 102 else 103 echo "What do you mean by '${arg}'?" >&2 104 exit 64 105 fi 106} 107 108find_expired() 109{ 110 for category in $(make -C ${PORTSDIR} -V SUBDIR); do 111 for port in $(make -C ${PORTSDIR}/${category} -V SUBDIR); do 112 DATE="$(make -C ${PORTSDIR}/${category}/${port} -V EXPIRATION_DATE)" 113 # shellcheck disable=SC2039 114 if [ -n "${DATE}" ] && [ ! "${DATE}" \> "$${TODAY}" ] ; then 115 if [ "$1" = 1 ] ; then 116 echo -n "${DATE} ${category}/${port}: " 117 make -C ${PORTSDIR}/${category}/${port} -V DEPRECATED 118 else 119 echo "${category}/${port}" 120 fi 121 fi 122 done 123 done 124} 125 126# check if some ports depend on the given port 127# XXX Very Little Chance (tm) for breaking INDEX exists: 128# /usr/ports/INDEX may be outdated and not contain recently added dependencies 129check_dep_core() 130{ 131 catport=${1} 132 alltorm=${2} 133 pkgname=$(pkgname ${catport}) 134 135 rmpkgs="" 136 rmcatports="" 137 for torm in ${alltorm} ; do 138 torm="$(echo ${torm} | sed 's/\/$//')" 139 rmpkgs="${rmpkgs:+${rmpkgs}|}$(pkgname ${torm})" 140 rmcatports="${rmcatports:+${rmcatports}|}${PORTSDIR}/${torm}/" 141 done 142 143 err=0 144 145 deps=$(grep -E "${pkgname}" ${INDEX} |grep -vE "^(${rmpkgs})" || :) 146 if [ -n "${deps}" ] ; then 147 log "${catport}: some port(s) depend on ${pkgname}:" 148 echo "${deps}" >&2 149 err=1 150 fi 151 152 # check if some Makefiles mention the port to be deleted 153 portdir_grep="^[^#].*/$(basename ${catport})([[:space:]]|@|/|$)" 154 r="$(${GIT} grep '${portdir_grep}' -- '**Makefile*' 'Mk/' \ 155 |grep -vE "^(${rmcatports})" || :)" 156 if [ -n "${r}" ] ; then 157 if [ ${err} -eq 1 ] ; then 158 echo >&2 159 fi 160 log "${catport}: some Makefiles mention ${portdir_grep}:" 161 echo "${r}" >&2 162 err=1 163 fi 164 165 return ${err} 166} 167 168check_dep() 169{ 170 catport=${1} 171 persist=${2} 172 alltorm=${3} 173 174 log "${catport}: checking dependencies" 175 176 err=0 177 178 res="$(check_dep_core ${catport} "${alltorm}" 2>&1)" || err=1 179 180 if [ ${err} -eq 0 ] ; then 181 return 0 182 fi 183 184 echo "${res}" |${PAGER:-less} 185 186 if [ ${persist} -eq 0 ] ; then 187 return 0 188 fi 189 190 echo "" >&2 191 echo "you can skip ${catport} and continue with the rest or remove it anyway" >&2 192 answer=$(ask "do you want to skip ${catport}") 193 if [ "${answer}" = "y" ] ; then 194 return 1 195 else 196 return 0 197 fi 198} 199 200# query Bugzilla and return the result 201get_PRs() 202{ 203 catport=${1} 204 synopsis=${2} 205 206 log "${catport}: getting PRs having ${synopsis} in the synopsis" 207 208 url="https://bugs.freebsd.org/bugzilla/buglist.cgi?quicksearch=${synopsis}" 209 210 raw="$(fetch -q -T 20 -o - "${url}")" 211 212 if [ -z "${raw}" ] ; then 213 log "${catport}: empty result from URL: ${url}" 214 exit 67 215 fi 216 217 printf "%s" "${raw}" \ 218 |sed -ne 's,^[[:space:]]*.a href="show_bug.cgi?id=\([0-9][0-9]*\)".\([^0-9][^<]*\).*,\1: \2,p' \ 219 |sort 220} 221 222# check if any PRs exist that are related to the port 223check_PRs() 224{ 225 catport=${1} 226 synopsis=${2} 227 228 PRs="$(get_PRs ${catport} "${synopsis}")" || exit 229 230 if [ -n "${PRs}" ] ; then 231 log "${catport}: PRs found, related to ${synopsis}:" 232 printf "%s\n" "${PRs}" >&2 233 234 echo "you can skip ${catport} and continue with the rest or remove it anyway" >&2 235 answer=$(ask "do you want to skip ${catport}") 236 if [ "${answer}" = "y" ] ; then 237 return 1 238 else 239 return 0 240 fi 241 fi 242 243 return 0 244} 245 246# add port's entry to ports/MOVED 247edit_MOVED() 248{ 249 catport=${1} 250 251 DEPRECATED="$(make -C ${PORTSDIR}/${catport} -V DEPRECATED)" 252 DEPRECATED=${DEPRECATED:+: ${DEPRECATED}} 253 if [ -n "$(make -C ${PORTSDIR}/${catport} -V EXPIRATION_DATE)" ] ; then 254 REASON="Has expired${DEPRECATED}" 255 else 256 REASON="Removed${DEPRECATED}" 257 fi 258 259 log "${catport}: adding entry to ports/MOVED" 260 261 echo "${catport}||${TODAY}|${REASON}" >> MOVED 262 ${GIT} add MOVED 263} 264 265# remove port from category/Makefile 266edit_Makefile() 267{ 268 cat=${1} 269 port=${2} 270 271 log "${cat}/${port}: removing from ${cat}/Makefile" 272 273 portesc=$(escape ${port}) 274 275 ${SED} -e "/^[[:space:]]*SUBDIR[[:space:]]*\+=[[:space:]]*${portesc}([[:space:]]+#.*)?$/d" ${cat}/Makefile 276 ${GIT} add ${cat}/Makefile 277} 278 279# remove port's files 280rm_port() 281{ 282 catport=${1} 283 284 log "${catport}: scheduling port removal" 285 286 echo ${catport} >> ${gitrmlist} 287} 288 289append_Template() 290{ 291 catport=${1} 292 293 msg=${catport} 294 295 EXPIRATION_DATE=$(make -C ${PORTSDIR}/${catport} -V EXPIRATION_DATE) 296 if [ -n "${EXPIRATION_DATE}" ] ; then 297 msg="${EXPIRATION_DATE} ${msg}" 298 fi 299 300 DEPRECATED="$(make -C ${PORTSDIR}/${catport} -V DEPRECATED)" 301 if [ -n "${DEPRECATED}" ] ; then 302 msg="${msg}: ${DEPRECATED}" 303 fi 304 305 log "${catport}: adding entry to commit message template" 306 307 echo "${msg}" >> ${gitlog} 308} 309 310# update, ask for confirmation and make a commit 311commit() 312{ 313 ${GIT} commit --file=${gitlog} 314 answer=$(ask "Do you want to tweak the commit message") 315 if [ "${answer}" = "y" ] ; then 316 ${GIT} pull --ff-only --rebase 2>&1 317 ${GIT} commit --amend # modify final commit message 318 echo "All done, check the result and push when everything is OK." 319 fi 320} 321 322cleanup() 323{ 324 log "cleaning up" 325 326 rm -f ${gitlog} ${gitrmlist} 327} 328 329usage() 330{ 331 echo "Usage:" >&2 332 echo "" >&2 333 echo "find expired ports:" >&2 334 echo "${0} -F" >&2 335 echo "" >&2 336 echo "remove port(s):" >&2 337 echo "${0} category1/port1 [ category2/port2 ... ]" >&2 338 echo "" >&2 339 echo "remove all expired ports (as returned by -F):" >&2 340 echo "${0} -a" >&2 341 echo "" >&2 342 echo "just check dependencies:" >&2 343 echo "${0} -d category/port" >&2 344 echo "" >&2 345 echo "just check if any related PRs exist:" >&2 346 echo "${0} -p synopsis" >&2 347 348 exit 64 349} 350 351# main 352 353trap cleanup 1 2 3 15 354 355if [ ! -r ${INDEX} ] ; then 356 echo "${INDEX} not readable, exiting" >&2 357 exit 66 358fi 359 360git_dir="$(${GIT} rev-parse --git-dir)" 361exitcode=$? 362if [ ${exitcode} -ne 0 ] ; then 363 echo "not at a git boundary" >&2 364 exit 365else 366 cd "${git_dir}/.." || exit 1 367fi 368if ! ${GIT} diff --exit-code remotes/origin/main ; then 369 echo "you have local commits or are behind origin/main, exiting" >&2 370 exit 65 371fi 372 373if [ ${#} -eq 0 ] || [ "${1}" = "-h" ] || [ "${1}" = "--help" ] ; then 374 usage 375fi 376 377if [ ${1} = "-d" ] ; then 378 if [ ${#} -ne 2 ] ; then 379 usage 380 fi 381 catport=$(find_catport ${2}) 382 check_dep ${catport} 0 ${catport} 383 exit 384fi 385 386if [ ${1} = "-p" ] ; then 387 if [ ${#} -ne 2 ] ; then 388 usage 389 fi 390 get_PRs "dummy" ${2} 391 exit 392fi 393 394if [ ${1} = "-F" ] ; then 395 if [ ${#} -ne 1 ] ; then 396 usage 397 fi 398 find_expired 1 399 exit 400fi 401 402if [ ${1} = "-a" ] ; then 403 if [ ${#} -ne 1 ] ; then 404 usage 405 fi 406 ${0} $(find_expired 0) 407 exit 408fi 409 410gitlog=$(mktemp -t gitlog) 411gitrmlist=$(mktemp -t gitrmlist) 412 413if [ $# -eq 1 ] ; then 414 topic="${1%/}" 415 plural="" 416 colon="" 417else 418 log "/!\\ Removing multiple ports at once, commit topic will be generic /!\\" 419 topic="cleanup" 420 plural="s" 421 colon=":" 422fi 423 424echo "${topic}: Remove expired port${plural}${colon}" > ${gitlog} 425echo "" >> ${gitlog} 426 427for catport in $* ; do 428 # convert to category/port 429 catport=$(find_catport ${catport}) 430 cat=$(dirname ${catport}) 431 port=$(basename ${catport}) 432 # remove any trailing slashes 433 catport="${cat}/${port}" 434 pkgname=$(pkgname ${catport}) 435 436 if ! check_dep ${catport} 1 "${*}" ; then 437 continue 438 fi 439 440 if ! check_PRs ${catport} ${port} ; then 441 continue 442 fi 443 444 # everything seems ok, edit the files 445 446 edit_MOVED ${catport} 447 448 edit_Makefile ${cat} ${port} 449 450 append_Template ${catport} 451 452 rm_port ${catport} 453done 454 455if [ -s ${gitrmlist} ] ; then 456 ${GIT} rm -r $(cat ${gitrmlist}) 457else 458 log "No port directories to remove" 459fi 460 461# give a chance to the committer to edit files by hand and recreate/review 462# the diff afterwards 463answer=y 464while [ "${answer}" = "y" ] ; do 465 ${GIT} diff --staged --irreversible-delete 466 467 echo "" >&2 468 echo "you can now edit files by hand" >&2 469 answer=$(ask "do you want to recreate the diff") 470 if [ "${answer}" = "y" ] ; then 471 ${GIT} add MOVED 472 fi 473done 474 475commit 476 477cleanup 478