#!/bin/bash #============================================================================== # Copyright and license info is available in the LICENSE file included with # the Server Deployment Package (SDP), and also available online: # https://swarm.workshop.perforce.com/projects/perforce-software-sdp/view/main/LICENSE #------------------------------------------------------------------------------ #============================================================================== # Declarations and Environment export P4U_HOME=${P4U_HOME:-/p4/common/bin} export P4U_LIB=${P4U_LIB:-/p4/common/lib} export P4U_ENV=$P4U_LIB/p4u_env.sh export P4U_LOG=Unset export VERBOSITY=${VERBOSITY:-3} # Environment isolation. For stability and security reasons, prepend # PATH to include dirs where known-good scripts exist. # known/tested PATH and, by implication, executables on the PATH. export PATH=$P4U_HOME:$PATH:~/bin:. [[ -r "$P4U_ENV" ]] || { echo -e "\nError: Cannot load environment from: $P4U_ENV\n\n" exit 1 } declare BASH_LIBS=$P4U_ENV BASH_LIBS+=" $P4U_LIB/libcore.sh" BASH_LIBS+=" $P4U_LIB/libp4u.sh" for bash_lib in $BASH_LIBS; do source $bash_lib ||\ { echo -e "\nFATAL: Failed to load bash lib [$bash_lib]. Aborting.\n"; exit 1; } done declare Version=1.1.0 declare -i SilentMode=0 export VERBOSITY=3 #============================================================================== # Local Functions #------------------------------------------------------------------------------ # Function: find_evil_twins_between_paths # # Input: # $1 - Path 1, should look like //stream_depot/stream/... # $2 - Path 2, should look like //stream_depot/stream/... # # Behaviours: # Sets Problems=1 if check couldn't be done right for some reason. # Increments $EvilTwinCount if evil twins were detected. # # Returns: Count of evil twins found, zero if none are found. # #------------------------------------------------------------------------------ function find_evil_twins_between_paths () { vvmsg "CALL: find_evil_twins_between_paths ($*)" declare p1=${1:-Unset} declare p2=${2:-Unset} declare s2= declare -i count [[ $p1 == Unset || $p2 == Unset ]] && bail "find_evil_twins_between_paths(): BAD USAGE." s2=${p2%/...} vmsg "Switching workspace to stream $s2" p4 client -f -s -S "$s2" msg "Searching for evil twins between [$p1] and [$p2] ..." vmsg "Executing: p4 integ -Ro -n $p1 $p2" p4 -s integ -Ro -n "$p1" "$p2" | grep "without -i flag" > $TmpFile 2>&1 [[ $? -ne 0 ]] && Problems=1 count=0 if [[ -s "$TmpFile" ]]; then msg "The following files are evil twins:" while read line; do [[ "$line" == "info:"* ]] || continue line=${line#info: } line=${line%% - can\'t integrate from *} echo $line count=$((count+1)) done < $TmpFile msg "$count evil twins were found here." EvilTwinCount=$((EvilTwinCount+count)) fi return 0 } #------------------------------------------------------------------------------ # Function: find_evil_twins_in_changelist # # Input: # $1 - Changelist # $2 - User # $3 - Workspace # # Behaviours: # Sets Problems=1 if check couldn't be done right for some reason. # Increments $EvilTwinCount if evil twins were detected. # # Returns: Count of evil twins found, zero if none are found. # #------------------------------------------------------------------------------ function find_evil_twins_in_changelist () { vvmsg "CALL: find_evil_twins_in_changelist ($*)" declare c=${1:-Unset} declare u=${2:-Unset} declare w=${3:-Unset} declare stream= currentStream= currentStreamType= declare path= # Return immediately if the user worksapce is not a stream worksapce. currentStream=$(p4 -ztag -F %Stream% client -o $w) [[ -z "$currentStream" ]] && return 0 currentStreamType=$(p4 -ztag -F %Type% stream -o $currentStream) # If our current stream is a virtual stream, go thru the stream # parent heirarchy until we hit the first non-virtual stream. while [[ "$currentStreamType" == virtual ]]; do currentStream=$(p4 -ztag -F %Parent% stream -o $currentStream) currentStreamType=$(p4 -ztag -F %Type% stream -o $currentStream) done p4 -ztag -F "%Type%:%Stream%" streams "//$StreamDepot/*" > $StreamListFile export P4CLIENT=tmp.auto.$u.${THISSCRIPT%.sh} echo -e "Client: $P4CLIENT\n\nOwner: $P4USER\n\nDescription:\n\tUsed by $THISSCRIPT\n\nRoot: $P4TMP/${THISSCRIPT%.sh}\n\nStream: $currentStream\n" > $TmpFile p4 client -f -i < $TmpFile > /dev/null 2>&1 ||\ bail "$THISSCRIPT (Internal Trigger Error): Failed to create temp client using this spec:\n$(cat $TmpFile)\n" while read stream; do [[ $stream == "virtual:"* ]] && continue stream=${stream##*:} path="$stream/..." done < $StreamListFile } #------------------------------------------------------------------------------ # Function: terminate function terminate { # Disable signal trapping. trap - EXIT SIGINT SIGTERM # Don't litter. cleanTrash vvmsg "$THISSCRIPT: EXITCODE: $Problems" # Stop logging. [[ "${P4U_LOG}" == off ]] || stoplog # With the trap removed, exit. exit $Problems } #------------------------------------------------------------------------------ # Function: usage (required function) # # Input: # $1 - style, either -h (for short form) or -man (for man-page like format). #------------------------------------------------------------------------------ function usage { declare style=${1:--h} echo "USAGE for $THISSCRIPT v$Version: Stand-Alone (Detection) Mode: $THISSCRIPT -d <stream_depot> [-s //source/stream] [-t //target/stream] [-i <instance>] [-L <log>] [-si] [-v<n>] [-n] [-D] Trigger (Prevention) Mode: $THISSCRIPT -c <changelist> -u <user> -w <workspace> [-L <log>] [-si] [-v<n>] [-n] [-D] or $THISSCRIPT [-h|-man|-V] " if [[ $style == -man ]]; then echo -e " DESCRIPTION: Detect \"evil twins\" in a given stream depot. This can be a stand-alone reporting script, or can operate as a trigger to prevent evil twins from being created in stream depots. An evil twin is a file with the path relative to the root of the stream as some previously existing file, but which was created by a separate 'p4 add' command rather than being created in the normal way, by merging from stream to stream. When viewed in the P4V Revision Graph, normally related files would appear on the same Revision graph, while evil twins would each have their own Revision Graph, with disconnected histories. Evil twins cause a bit of complexity when merging, since the merge must be done as a baseless merge, requiring the '-i' flag to the 'p4 merge' command. After doing a baseless merge and then resolving and submitting, the evil twin, actually a long lost relative, is reunited with its siblings. This script finds evil twins that already exist, as a reporting script. As a trigger, it prevents newly added files from becoming evil in the first place. ARGUMENTS (Prevention/Trigger Mode Only): -c <changelist> Specify the changelist in trigger mode. This argument is required in trigger mode; usage of '-c change' implies trigger mode. -u <user> Specify the user that owns the changelist. This argument is required in trigger mode. (It could be looked up from the changelist, but passing it in is faster, and trigger mode optimizes for speed). -w <workspace> Specify the workspace associated with the changelist. This argument is required in trigger mode. (It could be looked up from the changelist, but passing it in is faster, and trigger mode optimizes for speed). ARGUMENTS (Detection/Stand Alone Mode Only): -d <stream_sdepot> Specify a stream depot to process, e.g. -d fgs to process the //fgs stream depot. This argument is required. -s //source/stream Specify a particular source stream to search. By default, all non-virtual streams are checked. Be aware that if neither -s nor -t are given, a very large numbers of streams may be checked, as all possible combinations of source and target stream are checked. -t //target/stream Specify a particular target stream to search. By default, all non-virtual streams are checked. Be aware that if neither -s nor -t are given, a very large numbers of streams may be checked, as all possible combinations of source and target stream are checked. -i <instance> Specify the SDP instance name. This is required unless the SDP environment has previously been loaded in the current shell, i.e. by sourcing the SDP p4_vars file and specifying the instance. GENERAL ARGUMENTS: -v<n> Set verbosity 1-5 (-v1 = quiet, -v5 = highest). -L <log> Specify the path to a log file, or the special value 'off' to disable logging. All output (stdout and stderr) are captured in the log. NOTE: This script is self-logging. That is, output displayed on the screen is simultaneously captured in the log file. Do not run this script with redirection operators like '> log' or '2>&1', and do not use 'tee.' -si Operate silently. All output (stdout and stderr) is redirected to the log only; no output appears on the terminal. This cannot be used with '-L off'. -n No-Op. Prints commands instead of running them. -D Set extreme debugging verbosity. HELP OPTIONS: -h Display short help message -man Display man-style help message -V Dispay version info for this script and its libraries. EXAMPLES: Stand-Alone (Detection) Mode: Search for evil twins between streams //fgs/DevA and //fgs/DevB. $THISSCRIPT -s //fgs/DevA -t //fgs/DevB Search for all possible evil twins in the //fgs stream depot: $THISSCRIPT -d fgs Trigger (Prevention) Mode: Enable this with an entry in the triggers table like this example: NoEvilTwins change-submit //... \"/p4/common/bin/triggers/EvilTwinDetector.sh -c %changelist% -u %user% -w %client%\" To test, apply the trigger to a more narrow path than //... " fi exit 1 } #============================================================================== # Command Line Processing declare Change=Unset declare User=Unset declare Workspace=Unset declare StreamListFile=/tmp/tmp.StreamList.$$.$RANDOM declare SearchedPathsFile=/tmp/tmp.SearchedPaths.$$.$RANDOM declare TmpFile=/tmp/tmp.EvilTwins.$$.$RANDOM declare TmpFile2=/tmp/tmp.EvilTwins.2.$$.$RANDOM declare StreamDepot=Unset declare SourceStream=Unset declare SourcePath= declare TargetStream=Unset declare TargetPath= declare DepotTypeCheck=Unset declare -i PathwayCount=0 declare -i EvilTwinCount=0 GARBAGE+=" $StreamListFile $SearchedPathsFile $TmpFile $TmpFile2" declare -i shiftArgs=0 set +u while [[ $# -gt 0 ]]; do case $1 in (-c) Change=$2; export P4U_LOG=off; shiftArgs=1;; (-u) User=$2; shiftArgs=1;; (-w) Workspace=$2; shiftArgs=1;; (-s) SourceStream=$2; shiftArgs=1;; (-t) TargetStream=$2; shiftArgs=1;; (-d) StreamDepot=$2; shiftArgs=1;; (-h) usage -h;; (-man) usage -man;; (-V) show_versions; exit 1;; (-v1) export VERBOSITY=1;; (-v2) export VERBOSITY=2;; (-v3) export VERBOSITY=3;; (-v4) export VERBOSITY=4;; (-v5) export VERBOSITY=5;; (-i) export SDP_INSTANCE=$2; shiftArgs=1;; (-L) export P4U_LOG=$2; shiftArgs=1;; (-si) SilentMode=1;; (-n) export NO_OP=1;; (-D) set -x;; # Debug; use 'set -x' mode. (*) usageError "Unknown arg ($1).";; esac # Shift (modify $#) the appropriate number of times. shift; while [[ $shiftArgs -gt 0 ]]; do [[ $# -eq 0 ]] && usageError "Bad usage." shiftArgs=$shiftArgs-1 shift done done set -u #============================================================================== # Command Line Verification [[ $SilentMode -eq 1 && $P4U_LOG == off ]] && \ usageError "Cannot use '-si' with '-L off'." if [[ $Change == Unset ]]; then if [[ "$SourceStream" != Unset ]]; then [[ "$SourceStream" != "//"* ]] && \ usageError "The source stream specified with '-s' must be of the form //depot/stream." # Determine stream depot from source stream if it wasn't provided with '-d'. if [[ $StreamDepot == Unset ]]; then StreamDepot=${SourceStream#//} StreamDepot=${StreamDepot%%/*} fi fi if [[ "$TargetStream" != Unset ]]; then [[ "$TargetStream" != "//"* ]] && \ usageError "The target stream specified with '-t' must be of the form //depot/stream." # Determine stream depot from target stream if it wasn't provided with '-d'. if [[ $StreamDepot == Unset ]]; then StreamDepot=${TargetStream#//} StreamDepot=${StreamDepot%%/*} fi fi [[ $StreamDepot == Unset ]] && \ usageError "The '-d <stream_depot>' parameter is required." else [[ $Change =~ ^[1-9]{1}[0-9]*$ ]] ||\ usageError "The value specified with '-c' must be a purely numeric integer value; [$Change] is not valid." [[ $User == Unset ]] &&\ usageError "The '-u <user>' parameter is required when '-c <change>' is specified, in trigger mode." [[ $Workspace == Unset ]] &&\ usageError "The '-w <workspace>' parameter is required when '-c <change>' is specified, in trigger mode." fi export SDP_INSTANCE=${SDP_INSTANCE:-Unset} [[ $SDP_INSTANCE == Unset ]] && \ usageError "The SDP environment must be loaded, or the '-i <instance>' parameter provided." # Load and then tweak SDP environment. source p4_vars "$SDP_INSTANCE" export P4ENVIRO=/dev/null/.p4enviro unset P4CONFIG [[ $P4U_LOG == Unset ]] && \ export P4U_LOG="${LOGS}/${THISSCRIPT%.sh}.$(date +'%Y%m%d-%H%M').log" #============================================================================== # Main Program trap terminate EXIT SIGINT SIGTERM declare -i Problems=0 if [[ "${P4U_LOG}" != off ]]; then touch ${P4U_LOG} || bail "Couldn't touch log file [${P4U_LOG}]." # Redirect stdout and stderr to a log file. if [[ $SilentMode -eq 0 ]]; then exec > >(tee ${P4U_LOG}) exec 2>&1 else exec >${P4U_LOG} exec 2>&1 fi initlog fi if [[ $Change != Unset ]]; then find_evil_twins_in_changelist "$Change" "$User" "$Workspace" else msg "$THISSCRIPT v$Version started at $(date)." # If depot was specified as '//x', normalize to 'x'. [[ "$StreamDepot" == "//"* ]] && StreamDepot=${StreamDepot#//} DepotTypeCheck=$(p4 -ztag -F %Type% depot -o $StreamDepot 2>/dev/null) if [[ -n "$DepotTypeCheck" ]]; then [[ "$DepotTypeCheck" != "stream" ]] && \ bail "The depot specified by '-d' must be of type 'stream', not $DepotTypeCheck." else bail "Could not determine depot type for depot $StreamDepot. Aborting." fi msg "Getting list of streams in //$StreamDepot." p4 -ztag -F "%Type%:%Stream%" streams "//$StreamDepot/*" > $StreamListFile export P4CLIENT=tmp.auto.$P4USER.${THISSCRIPT%.sh} # Generate a temp workspace. Set the view to the //spec depot just because # we know it exists. The view is reset later when the workspace is # associated with a stream. echo -e "Client: $P4CLIENT\n\nOwner: $P4USER\n\nDescription:\n\tUsed by $THISSCRIPT\n\nRoot: $P4TMP/${THISSCRIPT%.sh}\n\nView:\n\t//spec/... //$P4CLIENT/spec/..." > $TmpFile msg "Using generated temporary workspace $P4CLIENT." p4 -s client -f -i < $TmpFile ||\ bail "Failed to create temp client using this spec:\n$(cat $TmpFile)\n" if [[ $SourceStream == Unset && $TargetStream == Unset ]]; then touch $SearchedPathsFile || bail "Could not touch temp file $SearchedPathsFile." while read SourceStream; do [[ $SourceStream == "virtual:"* ]] && continue SourceStream=${SourceStream##*:} SourcePath="$SourceStream/..." while read TargetStream; do [[ "$TargetStream" == "virtual:"* ]] && continue TargetStream=${TargetStream##*:} [[ "$SourceStream" == "$TargetStream" ]] && continue TargetPath="$TargetStream/..." # Direction doesn't matter for evil twin detection, so we only need to search # in a single direction. Avoid redundant checking by checknig the # "searched paths" temp file, which contains a list of already searched paths. grep "${SourceStream#//}:${TargetStream#//}\$" $SearchedPathsFile > /dev/null 2>&1 [[ $? -eq 0 ]] && continue grep "${TargetStream#//}:${SourceStream#//}\$" $SearchedPathsFile > /dev/null 2>&1 [[ $? -eq 0 ]] && continue find_evil_twins_between_paths "$SourcePath" "$TargetPath" ||\ Problems=1 PathwayCount=$((PathwayCount+1)) echo "${SourceStream#//}:${TargetStream#//}" >> $SearchedPathsFile done < $StreamListFile done < $StreamListFile elif [[ $SourceStream != Unset && $TargetStream != Unset ]]; then SourcePath="$SourceStream/..." TargetPath="$TargetStream/..." find_evil_twins_between_paths "$SourcePath" "$TargetPath" ||\ Problems=1 PathwayCount=1 else bail "Specifying only '-s' or '-t', but not both, is not (yet?) supported." fi if [[ $Problems -eq 0 ]]; then msg "${H}\nAll processing completed successfully.\n" else msg "${H}\nProcessing completed, but with errors. Scan above output carefully.\n" fi # Illustrate using $SECONDS to display runtime of a script. msg "Found $EvilTwinCount evil twins searching $PathwayCount pathway(s) in $(($SECONDS/3600)) hours $(($SECONDS%3600/60)) minutes $(($SECONDS%60)) seconds.\n" fi # See the terminate() function, which is really where this script exits. exit $EvilTwinCount
# | Change | User | Description | Committed | |
---|---|---|---|---|---|
#4 | 26652 | Robert Cowham |
This is Tom's change: Introduced new 'Unsupported' directory to clarify that some files in the SDP are not officially supported. These files are samples for illustration, to provide examples, or are deprecated but not yet ready for removal from the package. The Maintenance and many SDP triggers have been moved under here, along with other SDP scripts and triggers. Added comments to p4_vars indicating that it should not be edited directly. Added reference to an optional site_global_vars file that, if it exists, will be sourced to provide global user settings without needing to edit p4_vars. As an exception to the refactoring, the totalusers.py Maintenance script will be moved to indicate that it is supported. Removed settings to support long-sunset P4Web from supported structure. Structure under new .../Unsupported folder is: Samples/bin Sample scripts. Samples/triggers Sample trigger scripts. Samples/triggers/tests Sample trigger script tests. Samples/broker Sample broker filter scripts. Deprecated/triggers Deprecated triggers. To Do in a subsequent change: Make corresponding doc changes. |
||
#3 | 25193 | C. Thomas Tyler |
EvilTwinDetector.sh v2.1.3: * Narrowed focus to stream depots only, as only in stream depots is there sufficient metadata detect evil twins. * Updated evil twin detection logic. * Updated docs and command line usage. * Better reporting. * Added capability to fix evil twins with baseless integrations. |
||
#2 | 24781 | C. Thomas Tyler |
EvilTwinDetector.sh v1.1.0: * Implemented trigger mode to prevent evil triggers. * Refined output. * Enhanced docs. * Fixed typos/grammatical errors in a few error messages. Bypassing code review for now as this change is about to be merged with and succeeded by a different, bigger change made elsewhere. |
||
#1 | 20062 | C. Thomas Tyler | Added Evil Twin Detector maintenance script. |