#!/bin/bash
declare ThisScript=${0##*/}
declare Version=2.1.3
declare CmdLine="$0 $*"
declare ThisUser=
declare ThisHost=${HOSTNAME%%.*}
declare -i ErrorCount=0
declare -i WarningCount=0
declare -i Debug=0
declare -i NoOp=1
declare ServerTag=
declare KeepBranchesFile=
declare KeepClientsFile=
declare KeepGroupsFile=
declare KeepLabelsFile=
declare KeepUsersFile=
declare AccessLevel=
declare CurrentP4User=
declare UserP4CONFIG=
declare ConfigDir=$PWD
declare Log=
declare TmpFile=
declare H1="=============================================================================="
declare H2="------------------------------------------------------------------------------"
declare UtilsList="awk cat grep ls p4 seq wc"
# Per-phase counters used in the summary. In Dry Run mode "Deleted" counts
# items that *would* have been deleted, since cmd() returns 0 without acting.
declare -i LdapSpecsDeleted=0
declare -i LdapSpecsFailed=0
declare -i ClientsTotal=0
declare -i ClientsWithView=0
declare -i ClientsKeptByFile=0
declare -i ClientsKeptByOwner=0
declare -i ClientsDeleted=0
declare -i ClientsFailed=0
declare -i BranchesTotal=0
declare -i BranchesWithView=0
declare -i BranchesKeptByFile=0
declare -i BranchesKeptByOwner=0
declare -i BranchesDeleted=0
declare -i BranchesFailed=0
declare -i LabelsTotal=0
declare -i LabelsWithView=0
declare -i LabelsKeptByFile=0
declare -i LabelsKeptByOwner=0
declare -i LabelsDeleted=0
declare -i LabelsFailed=0
declare -i UsersTotal=0
declare -i UsersKept=0
declare -i UsersDeleted=0
declare -i UsersFailed=0
declare -i ShelvesDeleted=0
declare -i ShelvesFailed=0
declare -i GroupsTotal=0
declare -i GroupsKept=0
declare -i GroupsDeleted=0
declare -i GroupsFailed=0
declare -i SubmittedCLsAttempted=0
declare -i DepotsTotal=0
declare -i DepotsEmpty=0
declare -i DepotsNonEmpty=0
declare -i DepotsDeleted=0
declare -i DepotsFailed=0
function msg () { echo -e "$*"; }
function dbg () { [[ "$Debug" -eq 0 ]] || msg "DEBUG: $*"; }
function warnmsg () { msg "Warning: ${1:-Unknown Warning}"; WarningCount+=1; }
function errmsg () { msg "Error: ${1:-Unknown Error}"; ErrorCount+=1; }
function bail () { errmsg "${1:-Unknown Error}"; exit "$ErrorCount"; }
# Summary line: fixed-width label column so values line up regardless of
# whether DeletedLabel is "Deleted" or "Would delete".
function sline () { printf " %-42s %d\n" "${1}:" "${2}"; }
function cmd () {
# Usage: cmd <description> <command> [args...]
# Pass an empty string "" as description to suppress the description line.
# Using an array (via "$@") ensures that command arguments with special
# shell characters (e.g. '$', '(', ')') are passed verbatim without word
# splitting or command substitution.
local desc=${1:-}
shift
local -a cmdArray=("$@")
local -i cmdExitCode=0
[[ -n "$desc" ]] && msg "$desc"
msg "Executing: ${cmdArray[*]}"
if [[ "$NoOp" -eq 0 ]]; then
"${cmdArray[@]}"
cmdExitCode=$?
else
msg "NO_OP: Would have run: ${cmdArray[*]}"
fi
return $cmdExitCode
}
#------------------------------------------------------------------------------
# Function: terminate
# shellcheck disable=SC2317
function terminate
{
# Disable signal trapping.
trap - EXIT SIGINT SIGTERM
dbg "$ThisScript: EXITCODE: $ErrorCount"
# Stop logging.
[[ "$Log" == off ]] || msg "\nLog is: $Log\n${H1}"
# With the trap removed, exit with the error count.
exit "$ErrorCount"
}
#------------------------------------------------------------------------------
# Function: usage (required function)
#
# Input:
# $1 - style, either -h (for short form) or -man (for man-page like format).
# The default is -h.
#
# $2 - error message (optional). Specify this if usage() is called due to
# user error, in which case the given message displayed first, followed by the
# standard usage message (short or long depending on $1). If displaying an
# error, usually $1 should be -h so that the longer usage message doesn't
# obscure the error message.
#
# Sample Usage:
# usage
# usage -h
# usage -man
# usage -h "Incorrect command line usage."
#------------------------------------------------------------------------------
function usage
{
declare style=${1:--h}
declare usageErrorMessage=${2:-Unset}
if [[ "$usageErrorMessage" != Unset ]]; then
msg "\\n\\nUsage Error:\\n\\n$usageErrorMessage\\n\\n"
fi
msg "USAGE for $ThisScript version $Version:
$ThisScript <ServerTag> [-L <log>] [-y] [-d|-D]
or
$ThisScript [-h|-man|-V]
"
if [[ $style == -man ]]; then
msg "
DESCRIPTION:
*** WARNING WARNING WARNING WARNING ***
This script could be dangerous if misused. Point it only at non-production servers.
This script trims P4 metadata after a filtered replica is used in a divestiture situation.
It should be pointed to a a commit server that was recently promoted from a filtered forwarding
replica. The replication filtering cleaned up path-based data. This cleans up additional
metadata.
IMPORTANT: See FILES section below for required P4CONFIG files.
Phase 0: Preflight Checks
Phase 1: LDAP Disconnect and Cleanup
Phase 2: Client Cleanup (viewless clients only)
Phase 3: Branch Cleanup (viewless branches only)
Phase 4: Label Cleanup (viewless labels only)
Phase 5: Group Cleanup
Phase 6: Shelved CL Cleanup
Phase 7: User Cleanup
Phase 8: Stream Cleanup (**NOT IMPLEMENTED**)
Phase 9: Job and Fix Cleanup (**NOT IMPLEMENTED**)
Phase 10: Submitted CL Cleanup (empty CLs only)
Phase 11: Depot Cleanup (empty depots only)
OPERATOR TIPS:
1. Run this script in Dry Run mode first. Verify the expected results before running it with -y for a Live Run.
2. Have a HUGE amount of space available for P4JOURNAL bloat that occurs during metadata removal.
EXTRA MANUAL STEPS
This script DOES NOT clean up the following, which must be handled manually:
* Protections table cleanup.
* Triggers table cleanup.
* Extensions removal and associated extension depot obliteration and cert cleanup.
Other spots may be missed as well. Review the results carefully.
OPTIONS:
-y Live operation mode. By default, this script runs in Dry Run/Preview mode,
where commands affecting data are displayed but not executed. Use '-y' to
execute after previewing.
-d Display debugging messages.
-D Set extreme debugging verbosity.
LOGGING:
This script is self-logging. That is, output displayed on the screen
is simultaneously captured in the log file. Using redirection operators
like '> log' or '2>&1' and using 'tee' are unnecessary (but harmless).
HELP OPTIONS:
-h Display short help message
-man Display man-style help message
-V Display version info for this script and its libraries.
FILES:
== P4CONFIG Files
This script requires P4CONFIG files to be in the current directory when
this script starts, named .p4config.<ServerTag>
This P4CONFIG file should contain values for P4PORT, P4USER, P4TICKETS,
and if needed, P4TRUST. These should reference the targeted server,
which is generally a commit server recently promoted from a filtered
forwarding replica.
== Keep Users File
When removing users, any users that should NOT be remove must have the P4USER
account name listed in a \"keep users\" file in the current directory when
this script starts. Users are listed one per line, with no leading or
trailing white space. If a file named keep_users.<ServerTag>.txt exists, it will
be used. Otherwise a file named keep_users.txt will be used. So there can be
a single set of users for each server tag, or each server tag can have its own
set of users.
For example, the keep_users.txt file may look like
this:
admin
bruno
perforce
At the very least, the \"keep users\" file must contain one user with super
access. This file is required and this script will abort during preflight checks if
it does not exist.
== Keep Clients, Branches, Groups and Labels Files.
When removing clients, branches, groups, and labels, any of these that should
NOT be remove must have their names listed in the appropriate file, one entry
per line:
* keep_branches.<ServerTag>.txt OR keep_branches.txt
* keep_clients.<ServerTag>.txt OR keep_clients.txt
* keep_groups.<ServerTag>.txt OR keep_groups.txt
* keep_labels.<ServerTag>.txt OR keep_labels.txt
These optional files must appear in the directory where this script starts to
have effect. The name including <ServerTag> takes precedence if it exists,
otherwise the script falls back to other file name.
In addition to the explicit lists above, any client, branch, or label whose
'Owner:' field names a user listed in the Keep Users file is preserved
automatically. This avoids deleting metadata belonging to users that are
being kept. Note: groups are NOT subject to this owner-based rule; groups
are kept only if listed in the Keep Groups file.
EXAMPLES:
Example 1: Dry Run for instance 17777
echo p4admin > keep_users.17777.txt
$ThisScript 17777
Example 2: Live Run for instance 17777
$ThisScript 17777 -y
"
fi
exit 2
}
#==============================================================================
# Command Line Processing
declare -i shiftArgs=0
set +u
while [[ $# -gt 0 ]]; do
case $1 in
(-h) usage -h;;
(-man|--help) usage -man;;
(-V|--version) msg "$ThisScript version $Version"; exit 0;;
(-y) NoOp=0;;
(-d) Debug=1;;
(-D) Debug=1; set -x;; # Use bash 'set -x' extreme debug mode.
(-*) usage -h "Unknown option ($1).";;
(*)
if [[ -z "$ServerTag" ]]; then
ServerTag="$1"
else
usage -h "Unknown parameter '$1'. ServerTag already set as '$ServerTag'."
fi
;;
esac
# Shift (modify $#) the appropriate number of times.
shift; while [[ $shiftArgs -gt 0 ]]; do
[[ $# -eq 0 ]] && usage -h "Incorrect number of arguments."
shiftArgs=$shiftArgs-1
shift
done
done
set -u
#==============================================================================
# Command Line Verification
[[ -n "$Log" ]] || \
Log="${LOGS:-${HOME:-/tmp}}/${ThisScript%.sh}.$(date +'%Y%m%d-%H%M%S').log"
[[ -z "$ServerTag" ]] && usage -h "The <ServerTag> parameter is required unless."
#==============================================================================
# Main Program
if [[ "$Log" != off ]]; then
exec > >(tee "$Log")
exec 2>&1
touch "$Log" || bail "Couldn't touch log file [$Log]."
msg "${H1}\\nLog is: $Log"
fi
trap terminate EXIT SIGINT SIGTERM
ThisUser=$(id -n -u)
msg "Starting $ThisScript version $Version as $ThisUser@$ThisHost on $(date) as:\\n$CmdLine\\n"
if [[ "$NoOp" -eq 0 ]]; then
msg "Operating in Live Operation mode."
else
msg "Operating in Dry Run/Preview mode."
fi
TmpFile=$(mktemp)
msg "${H2}\nPhase 0: Preflight Checks"
for Util in $UtilsList; do
command -v "$Util" > /dev/null || errmsg "Missing required utility '$Util'. Adjust path?"
done
UserP4CONFIG="$ConfigDir/.p4config.$ServerTag"
if [[ -r "$UserP4CONFIG" ]]; then
msg "\nContents of P4CONFIG [$UserP4CONFIG]:"
cat "$UserP4CONFIG"
else
bail "Missing user P4CONFIG file '$UserP4CONFIG'."
fi
# Look first for keep_users.<ServerTag>.txt files, then for keep_users.txt.
if [[ -r "$PWD/keep_users.$ServerTag.txt" ]]; then
KeepUsersFile="$PWD/keep_users.$ServerTag.txt"
elif [[ -r "$PWD/keep_users.txt" ]]; then
KeepUsersFile="$PWD/keep_users.txt"
else
KeepUsersFile=
fi
# Look first for keep_branches.<ServerTag>.txt files, then for keep_branches.txt.
if [[ -r "$PWD/keep_branches.$ServerTag.txt" ]]; then
KeepBranchesFile="$PWD/keep_branches.$ServerTag.txt"
elif [[ -r "$PWD/keep_branches.txt" ]]; then
KeepBranchesFile="$PWD/keep_branches.txt"
else
KeepBranchesFile=
fi
# Look first for keep_clients.<ServerTag>.txt files, then for keep_clients.txt.
if [[ -r "$PWD/keep_clients.$ServerTag.txt" ]]; then
KeepClientsFile="$PWD/keep_clients.$ServerTag.txt"
elif [[ -r "$PWD/keep_clients.txt" ]]; then
KeepClientsFile="$PWD/keep_clients.txt"
else
KeepClientsFile=
fi
# Look first for keep_groups.<ServerTag>.txt files, then for keep_groups.txt.
if [[ -r "$PWD/keep_groups.$ServerTag.txt" ]]; then
KeepGroupsFile="$PWD/keep_groups.$ServerTag.txt"
elif [[ -r "$PWD/keep_groups.txt" ]]; then
KeepGroupsFile="$PWD/keep_groups.txt"
else
KeepGroupsFile=
fi
# Look first for keep_labels.<ServerTag>.txt files, then for keep_labels.txt.
if [[ -r "$PWD/keep_labels.$ServerTag.txt" ]]; then
KeepLabelsFile="$PWD/keep_labels.$ServerTag.txt"
elif [[ -r "$PWD/keep_labels.txt" ]]; then
KeepLabelsFile="$PWD/keep_labels.txt"
else
KeepLabelsFile=
fi
if [[ -n "$KeepUsersFile" ]]; then
msg "\nContents of Keep Users File [$KeepUsersFile]:"
cat "$KeepUsersFile"
else
bail "Missing Keep Users File '$KeepUsersFile'."
fi
if [[ -n "$KeepBranchesFile" ]]; then
msg "\nContents of Keep Branches File [$KeepBranchesFile]:"
cat "$KeepBranchesFile"
else
msg "\nNo Keep Branches file exists. All viewless branches will be removed."
fi
if [[ -n "$KeepLabelsFile" ]]; then
msg "\nContents of Keep Labels File [$KeepLabelsFile]:"
cat "$KeepLabelsFile"
else
msg "\nNo Keep Labels file exists. All viewless labels will be removed."
fi
if [[ -n "$KeepClientsFile" ]]; then
msg "\nContents of Keep Clients File [$KeepClientsFile]:"
cat "$KeepClientsFile"
else
msg "\nNo Keep Clients file exists. All viewless clients will be removed."
fi
if [[ -n "$KeepGroupsFile" ]]; then
msg "\nContents of Keep Groups File [$KeepGroupsFile]:"
cat "$KeepGroupsFile"
else
msg "\nNo Keep Groups file exists. All groups will be removed."
fi
unset P4ENVIRO
export P4CONFIG="$UserP4CONFIG"
AccessLevel=$(p4 protects -m)
if [[ -n "$AccessLevel" ]]; then
if [[ "$AccessLevel" == super ]]; then
msg "\nVerified: Max Access Level is 'super', as required."
else
errmsg "Max Access Level is '$AccessLevel', but 'super' is required."
fi
else
errmsg "Could not determine max access level using P4CONFIG=$UserP4CONFIG"
fi
# Refuse to run if the operating P4 user is not in the Keep Users file, since
# Phase 7 would otherwise delete the running user mid-script.
CurrentP4User=$(p4 set -q P4USER | sed 's/^P4USER=//')
if [[ -z "$CurrentP4User" ]]; then
# Fall back to p4 info if p4 set didn't return a value.
CurrentP4User=$(p4 -ztag -F %userName% info)
fi
if [[ -n "$CurrentP4User" ]]; then
if grep -Fxq -- "$CurrentP4User" "$KeepUsersFile"; then
msg "\nVerified: Operating P4 user '$CurrentP4User' is listed in Keep Users file."
else
errmsg "Operating P4 user '$CurrentP4User' is NOT listed in Keep Users file '$KeepUsersFile'. Add it before running."
fi
else
errmsg "Could not determine operating P4 user via 'p4 set' or 'p4 info' using P4CONFIG=$UserP4CONFIG"
fi
# Verify the current user has an explicit 'super user' entry in the Protections
# table. Phase 5 deletes all groups; if super access comes solely through a
# group membership, deleting that group would revoke super access mid-script.
ProtectSpec=$(p4 protect -o 2>/dev/null)
if [[ -n "$ProtectSpec" ]]; then
if echo "$ProtectSpec" | grep -qE "^\s+super\s+user\s+${CurrentP4User}(\s|$)"; then
msg "\nVerified: Explicit 'super user $CurrentP4User' entry exists in Protections table."
else
errmsg "No explicit 'super user $CurrentP4User' entry found in Protections table. Deleting all groups (Phase 5) may revoke super access mid-script. Add an explicit 'super user $CurrentP4User * //...' protection entry before running."
fi
else
errmsg "Could not read Protections table via 'p4 protect -o'."
fi
if [[ "$ErrorCount" -eq 0 ]]; then
msg "\nAll preflight checks are OK. Proceeding."
else
bail "Aborting early before any actions were taken due to $ErrorCount failed preflight checks."
fi
#------------------------------------------------------------------------------
msg "${H2}\nPhase 1: LDAP Disconnect and Cleanup"
for i in $(seq 10 -1 1); do
if [[ $(p4 -ztag -F %Type% configure show "auth.ldap.order.$i") == "configure" ]]; then
cmd "" p4 configure unset "auth.ldap.order.$i"
fi
done
if [[ $(p4 -ztag -F %Type% configure show auth.default.method) == "configure" ]]; then
cmd "" p4 configure unset auth.default.method
fi
if p4 -ztag -F %Name% ldaps > "$TmpFile"; then
if [[ -s "$TmpFile" ]]; then
while read -r LDAP; do
if cmd "Removing ldap spec '$LDAP'." p4 -s ldap -d "$LDAP"; then
LdapSpecsDeleted+=1
else
LdapSpecsFailed+=1
errmsg "Failed to remove ldap spec '$LDAP'."
fi
done < "$TmpFile"
else
msg "No ldap specs detected."
fi
rm -f "$TmpFile"
else
errmsg "Could not get list of ldap specs."
fi
#------------------------------------------------------------------------------
msg "${H2}\nPhase 2: Client Cleanup (viewless clients only)"
if [[ -n "$KeepClientsFile" ]]; then
msg "Deleting all viewless clients except those listed in '$KeepClientsFile'."
else
msg "Deleting all viewless clients."
fi
if p4 -ztag -F %client% clients > "$TmpFile"; then
if [[ -s "$TmpFile" ]]; then
while read -r Client; do
ClientsTotal+=1
# Use a temp var so we can check the p4 exit code separately from the value.
# If the p4 call itself fails (e.g. a p4 wrapper script chokes on special chars
# in the client name), skip rather than misclassifying it as viewless.
if ! ClientView=$(p4 -ztag -F %View0% client -o "$Client" 2>/dev/null); then
warnmsg "Could not inspect view for client '$Client' - skipping to avoid unsafe deletion."
continue
fi
if [[ -n "$ClientView" ]]; then
dbg "Ignoring client with non-empty view: '$Client'."
ClientsWithView+=1
continue
fi
if [[ -n "$KeepClientsFile" ]] && grep -Fxq -- "$Client" "$KeepClientsFile"; then
dbg "Ignoring \"keep\" client '$Client'."
ClientsKeptByFile+=1
continue
fi
if ! ClientOwner=$(p4 -ztag -F %Owner% client -o "$Client" 2>/dev/null); then
warnmsg "Could not inspect owner for client '$Client' - skipping to avoid unsafe deletion."
continue
fi
if [[ -n "$ClientOwner" ]] && grep -Fxq -- "$ClientOwner" "$KeepUsersFile"; then
dbg "Ignoring client '$Client' owned by kept user '$ClientOwner'."
ClientsKeptByOwner+=1
continue
fi
if cmd "Removing client '$Client'." p4 -s client -df -Fs "$Client"; then
ClientsDeleted+=1
else
ClientsFailed+=1
errmsg "Failed to remove client '$Client'."
fi
done < "$TmpFile"
else
msg "No clients detected."
fi
rm -f "$TmpFile"
else
errmsg "Could not get list of clients."
fi
#------------------------------------------------------------------------------
msg "${H2}\nPhase 3: Branch Cleanup (viewless branches only)"
if [[ -n "$KeepBranchesFile" ]]; then
msg "Deleting all viewless branches except those listed in '$KeepBranchesFile'."
else
msg "Deleting all viewless branches."
fi
if p4 -ztag -F %branch% branches > "$TmpFile"; then
if [[ -s "$TmpFile" ]]; then
while read -r Branch; do
BranchesTotal+=1
if ! BranchView=$(p4 -ztag -F %View0% branch -o "$Branch" 2>/dev/null); then
warnmsg "Could not inspect view for branch '$Branch' - skipping to avoid unsafe deletion."
continue
fi
if [[ -n "$BranchView" ]]; then
dbg "Ignoring branch with non-empty view: '$Branch'."
BranchesWithView+=1
continue
fi
if [[ -n "$KeepBranchesFile" ]] && grep -Fxq -- "$Branch" "$KeepBranchesFile"; then
dbg "Ignoring \"keep\" branch '$Branch'."
BranchesKeptByFile+=1
continue
fi
if ! BranchOwner=$(p4 -ztag -F %Owner% branch -o "$Branch" 2>/dev/null); then
warnmsg "Could not inspect owner for branch '$Branch' - skipping to avoid unsafe deletion."
continue
fi
if [[ -n "$BranchOwner" ]] && grep -Fxq -- "$BranchOwner" "$KeepUsersFile"; then
dbg "Ignoring branch '$Branch' owned by kept user '$BranchOwner'."
BranchesKeptByOwner+=1
continue
fi
if cmd "Removing viewless branch '$Branch'." p4 -s branch -df "$Branch"; then
BranchesDeleted+=1
else
BranchesFailed+=1
errmsg "Failed to remove branch '$Branch'."
fi
done < "$TmpFile"
else
msg "No branches detected."
fi
rm -f "$TmpFile"
else
errmsg "Could not get list of branches."
fi
#------------------------------------------------------------------------------
msg "${H2}\nPhase 4: Label Cleanup (viewless labels only)"
if [[ -n "$KeepLabelsFile" ]]; then
msg "Deleting all viewless labels except those listed in '$KeepLabelsFile'."
else
msg "Deleting all viewless labels."
fi
if p4 -ztag -F %label% labels > "$TmpFile"; then
if [[ -s "$TmpFile" ]]; then
while read -r Label; do
LabelsTotal+=1
if ! LabelView=$(p4 -ztag -F %View0% label -o "$Label" 2>/dev/null); then
warnmsg "Could not inspect view for label '$Label' - skipping to avoid unsafe deletion."
continue
fi
if [[ -n "$LabelView" ]]; then
dbg "Ignoring label with non-empty view: '$Label'."
LabelsWithView+=1
continue
fi
if [[ -n "$KeepLabelsFile" ]] && grep -Fxq -- "$Label" "$KeepLabelsFile"; then
dbg "Ignoring \"keep\" label '$Label'."
LabelsKeptByFile+=1
continue
fi
if ! LabelOwner=$(p4 -ztag -F %Owner% label -o "$Label" 2>/dev/null); then
warnmsg "Could not inspect owner for label '$Label' - skipping to avoid unsafe deletion."
continue
fi
if [[ -n "$LabelOwner" ]] && grep -Fxq -- "$LabelOwner" "$KeepUsersFile"; then
dbg "Ignoring label '$Label' owned by kept user '$LabelOwner'."
LabelsKeptByOwner+=1
continue
fi
if cmd "Removing viewless label '$Label'." p4 -s label -df "$Label"; then
LabelsDeleted+=1
else
LabelsFailed+=1
errmsg "Failed to remove label '$Label'."
fi
done < "$TmpFile"
else
msg "No labels detected."
fi
rm -f "$TmpFile"
else
errmsg "Could not get list of labels."
fi
#------------------------------------------------------------------------------
msg "${H2}\nPhase 5: Group Cleanup"
# Groups are deleted before users. P4 forbids deleting a user who is the last
# member of a group; removing all groups first avoids that constraint.
if [[ -n "$KeepGroupsFile" ]]; then
msg "Deleting all groups except those listed in '$KeepGroupsFile'."
else
msg "Deleting all groups."
fi
if p4 groups > "$TmpFile"; then
if [[ -s "$TmpFile" ]]; then
while read -r Group; do
GroupsTotal+=1
if [[ -n "$KeepGroupsFile" ]] && grep -Fxq -- "$Group" "$KeepGroupsFile"; then
dbg "Ignoring \"keep\" group '$Group'."
GroupsKept+=1
continue
fi
if cmd "Removing group '$Group'." p4 -s group -d "$Group"; then
GroupsDeleted+=1
else
GroupsFailed+=1
errmsg "Failed to remove group '$Group'."
fi
done < "$TmpFile"
else
msg "No groups detected."
fi
rm -f "$TmpFile"
else
errmsg "Could not get list of groups."
fi
#------------------------------------------------------------------------------
msg "${H2}\nPhase 6: Shelved CL Cleanup"
msg "Deleting all shelved changes."
if p4 -ztag -F %change% changes -r -s shelved > "$TmpFile"; then
if [[ -s "$TmpFile" ]]; then
while read -r ShelvedCL; do
if cmd "" p4 -s shelve -df -c "$ShelvedCL"; then
ShelvesDeleted+=1
else
ShelvesFailed+=1
msg "Failed to remove shelved CL $ShelvedCL"
fi
done < "$TmpFile"
else
msg "No shelves detected."
fi
rm -f "$TmpFile"
else
errmsg "Could not get list of shelved changes."
fi
#------------------------------------------------------------------------------
msg "${H2}\nPhase 7: User Cleanup"
msg "Deleting all users except those listed in '$KeepUsersFile'."
if p4 -ztag -F %User% users -a > "$TmpFile"; then
if [[ -s "$TmpFile" ]]; then
while read -r User; do
UsersTotal+=1
if grep -Fxq -- "$User" "$KeepUsersFile"; then
dbg "Ignoring \"keep\" user '$User'."
UsersKept+=1
continue
fi
if cmd "Removing User '$User'." p4 -s user -df -F -D -y "$User"; then
UsersDeleted+=1
else
UsersFailed+=1
errmsg "Failed to remove user '$User'."
fi
done < "$TmpFile"
else
msg "No users detected."
fi
rm -f "$TmpFile"
else
errmsg "Could not get list of users."
fi
#------------------------------------------------------------------------------
msg "${H2}\nPhase 8: Stream Cleanup (**NOT IMPLEMENTED**)"
warnmsg "Stream cleanup logic has not been implemented."
#------------------------------------------------------------------------------
msg "${H2}\nPhase 9: Job and Fix Cleanup (**NOT IMPLEMENTED**)"
warnmsg "Job and Fix cleanup logic has not been implemented."
#------------------------------------------------------------------------------
msg "${H2}\nPhase 10: Submitted CL Cleanup (empty CLs only)"
msg "Removing empty submitted changelists."
if p4 -ztag -F %change% changes -r -s submitted > "$TmpFile"; then
if [[ -s "$TmpFile" ]]; then
msg "\nThis will attempt to delete every submitted changelist. Only empty submitted changelists will be deleted. It is safe to attempt to delete a non-empty submitted change; that attempt will simply fail."
while read -r SubmittedCL; do
SubmittedCLsAttempted+=1
cmd "" p4 -s change -df "$SubmittedCL"
done < "$TmpFile"
else
errmsg "No changes detected."
fi
rm -f "$TmpFile"
else
errmsg "Could not get list of submitted changes."
fi
#------------------------------------------------------------------------------
msg "${H2}\nPhase 11: Depot Cleanup (empty depots only)"
# Note: don't pipe 'p4 depots' through grep here. A grep with zero matches
# returns non-zero, which would mask a successful 'p4 depots' call and route
# control into the "Could not get list of depots" error branch. Instead,
# capture to TmpFile and filter inside the loop.
if p4 -ztag -F %type%:%name% depots > "$TmpFile"; then
if [[ -s "$TmpFile" ]]; then
while read -r DepotData; do
case "$DepotData" in
local:*|remote:*|stream:*) ;;
*) dbg "Ignoring depot (unwanted type): '$DepotData'."; continue ;;
esac
Depot=${DepotData#*:}
DepotsTotal+=1
DepotFileCount=$(p4 -ztag -F %fileCount% sizes -sah "//$Depot/...")
if [[ "$DepotFileCount" == 0 ]]; then
DepotsEmpty+=1
if cmd "" p4 -s depot -df "$Depot"; then
DepotsDeleted+=1
else
DepotsFailed+=1
msg "Failed to delete empty depot '$Depot'."
fi
else
DepotsNonEmpty+=1
dbg "Ignoring non-empty depot '$Depot'."
fi
done < "$TmpFile"
if [[ "$DepotsTotal" -eq 0 ]]; then
errmsg "No local/remote/stream depots detected."
fi
else
errmsg "No depots detected."
fi
rm -f "$TmpFile"
else
errmsg "Could not get list of depots."
fi
#------------------------------------------------------------------------------
msg "\nTime: $ThisScript ran for $((SECONDS/3600)) hours $((SECONDS%3600/60)) minutes $((SECONDS%60)) seconds.\\n"
if [[ "$NoOp" -eq 0 ]]; then
declare RunMode="Live Operation mode"
declare DeletedLabel="Deleted"
else
declare RunMode="Dry Run/Preview mode"
declare DeletedLabel="Would delete"
fi
msg "${H2}\nSummary (${RunMode}):"
msg "\nLDAP specs:"
sline "${DeletedLabel}" "$LdapSpecsDeleted"
sline "Failed" "$LdapSpecsFailed"
msg "\nClients:"
sline "Total examined" "$ClientsTotal"
sline "Ignored (non-empty view)" "$ClientsWithView"
sline "Kept (listed in keep file)" "$ClientsKeptByFile"
sline "Kept (owned by kept user)" "$ClientsKeptByOwner"
sline "${DeletedLabel}" "$ClientsDeleted"
sline "Failed" "$ClientsFailed"
msg "\nBranches:"
sline "Total examined" "$BranchesTotal"
sline "Ignored (non-empty view)" "$BranchesWithView"
sline "Kept (listed in keep file)" "$BranchesKeptByFile"
sline "Kept (owned by kept user)" "$BranchesKeptByOwner"
sline "${DeletedLabel}" "$BranchesDeleted"
sline "Failed" "$BranchesFailed"
msg "\nLabels:"
sline "Total examined" "$LabelsTotal"
sline "Ignored (non-empty view)" "$LabelsWithView"
sline "Kept (listed in keep file)" "$LabelsKeptByFile"
sline "Kept (owned by kept user)" "$LabelsKeptByOwner"
sline "${DeletedLabel}" "$LabelsDeleted"
sline "Failed" "$LabelsFailed"
msg "\nUsers:"
sline "Total examined" "$UsersTotal"
sline "Kept (listed in keep file)" "$UsersKept"
sline "${DeletedLabel}" "$UsersDeleted"
sline "Failed" "$UsersFailed"
msg "\nShelved changelists:"
sline "${DeletedLabel}" "$ShelvesDeleted"
sline "Failed" "$ShelvesFailed"
msg "\nGroups:"
sline "Total examined" "$GroupsTotal"
sline "Kept (listed in keep file)" "$GroupsKept"
sline "${DeletedLabel}" "$GroupsDeleted"
sline "Failed" "$GroupsFailed"
msg "\nSubmitted changelists:"
sline "Delete attempts (only empties succeed)" "$SubmittedCLsAttempted"
msg "\nDepots:"
sline "Total examined (local/remote/stream)" "$DepotsTotal"
sline "Empty" "$DepotsEmpty"
sline "Non-empty (ignored)" "$DepotsNonEmpty"
sline "${DeletedLabel}" "$DepotsDeleted"
sline "Failed" "$DepotsFailed"
msg "\n${H2}"
if [[ "$ErrorCount" -eq 0 && "$WarningCount" -eq 0 ]]; then
msg "Processing completed successfully (${RunMode}) with no errors or warnings."
elif [[ "$ErrorCount" -eq 0 ]]; then
msg "Processing completed with no errors but $WarningCount warnings (${RunMode})."
else
msg "Processing completed with $ErrorCount errors and $WarningCount warnings (${RunMode})."
fi
exit "$ErrorCount"
| # | Change | User | Description | Committed | |
|---|---|---|---|---|---|
| #16 | 32698 | C. Thomas Tyler |
v2.1.3: Move Group Cleanup before User Cleanup (fix last-member-of-group deletion error). Add preflight check for explicit super user protection entry. Consistent passive-voice phase titles. Fix declare AccessLevel=. |
||
| #15 | 32697 | C. Thomas Tyler | v2.1.2: Fix summary column alignment using printf/sline helper. | ||
| #14 | 32696 | C. Thomas Tyler | v2.1.1: Bump version number (missed in prior submit). | ||
| #13 | 32695 | C. Thomas Tyler |
v2.1.0: Fix shellcheck SC2181 warnings; inline assignment+exit-check into if-! pattern. Update command summary doc. |
||
| #12 | 32693 | C. Thomas Tyler |
v2.1.0: Fix cmd() to use array args, preventing shell expansion of special chars in spec names (e.g. '$(basename_$PWD)'). Add p4 exit-code guards on view/owner checks so a wrapper script failure causes a safe skip rather than a misclassified deletion. |
||
| #11 | 32692 | C. Thomas Tyler | Overhauled. | ||
| #10 | 32691 | C. Thomas Tyler | Minor spelling/typo fixes. | ||
| #9 | 32690 | Perforce maintenance |
Only viewless clients are now removed. Better loggic for handling keep files. |
||
| #8 | 32689 | Perforce maintenance | Cosmetic improvements. | ||
| #7 | 32688 | Perforce maintenance |
Added handling for labels. Fixed bug removing shelves. |
||
| #6 | 32684 | Perforce maintenance | Extended 'keep' logic for branches, clients, and groups. | ||
| #5 | 32683 | Perforce maintenance |
Added timing summary. Refined bits. |
||
| #4 | 32682 | Perforce maintenance | Fixed user removal bug. | ||
| #3 | 32679 | Perforce maintenance | More logic added. | ||
| #2 | 32678 | Perforce maintenance | WIP. | ||
| #1 | 32672 | Perforce maintenance | Added script to trim excess metadata. |