#!/bin/bash
#------------------------------------------------------------------------------
set -u
# Track 'p4 move' operations over a range of changelists, by detecing them
# and adding move operations to a branch spec, thus enabling merges using the
# branch spec to properly track moves/renames, e.g. with:
#
# p4 merge -n -b r1_to_r2
#
# This script can be called via change-commit trigger to track moves for a
# single changle changelist, or called in 'auto' mode, in which case it tracks
# all changes starting from the last time changes were tracked. A 'p4 key'
# tracks the last time moves were tracked for the branch spec.
# The first entry in the View: field of the branch spec is the actual path;
# all subseuqent entries track moves/renames.
# Limitation: This works only for Stream depots with StreamDepth=1 (the
# default) or Classic Depots with a fixed 2-level directory structure of
# //<Depot>/<StreamRoot>
#------------------------------------------------------------------------------
# Function: track_moves_in_change()
#------------------------------------------------------------------------------
# Input: $1 - Changelist.
# Uses globals $BranchSpec, $BranchSpecFile, $SourceStream, $TargetStream,
# and $StartingChangeKey.
function track_moves_in_change() {
dbg "CALL track_moves_in_change($*)"
declare change=${1:-}
declare file=
declare action=
declare -i i=0
declare -i moveCount=0
[[ -z "$change" ]] && return 1
file=$(p4 -ztag -F %depotFile${i}% describe -s $change)
while [[ -n "$file" ]]; do
action=$(p4 -ztag -F %action${i}% describe -s $change)
if [[ $action == "move/add" ]]; then
fromFile=$(p4 -ztag -F %file0,0% filelog -m1 ${file}@$change,@$change)
# Trim leading stream root path, e.g. //stream/main or //stream/r2, so
# we can apply the stream roots from our branch spec instead. Apply
# the stream root $SourceStream to $fromFile and $TargetStream to $file.
fromFileRelPath=${fromFile#//}
fromFileRelPath=${fromFileRelPath#*/}
fromFileRelPath=${fromFileRelPath#*/}
fromFileDepotPath="$SourceStream/$fromFileRelPath"
fileRelPath=${file#//}
fileRelPath=${fileRelPath#*/}
fileRelPath=${fileRelPath#*/}
fileDepotPath="$TargetStream/$fileRelPath"
# If this is the first time a given file has been moved, we just append
# a line to the branch spec. Branch specs are parsed bottom-up, first
# match wins, so appending to the branch specs means the appended entries
# applying to specific files override the single branch-wide entry at
# the top of the brandch spec ending in '/...'.
echo -e "\t\"$fromFileDepotPath\" \"$fileDepotPath\"" >> $BranchSpecFile
moveCount=$((moveCount+1))
# To do: Handle cases where a file has been renamed multiple times. In
# This case, we need to replace an existing brnach spec entry (rather than
# append one).
fi
i=$((i+1))
file=$(p4 -ztag -F %depotFile${i}% describe -s $change)
done
if [[ $moveCount -ge 0 ]]; then
dbg "Found $moveCount renames among $i file actions in change $change."
dbg "Updating Branch Spec to:\n$(grep -v '^#' $BranchSpecFile)\nEND_FILE\n"
if [[ $MT_VERBOSE -eq 1 ]]; then
p4 branch -i < $BranchSpecFile
else
p4 branch -i < $BranchSpecFile > /dev/null
fi
fi
dbg "Updating key $StartingChangeKey to changelist $change."
if [[ $MT_VERBOSE -eq 1 ]]; then
p4 key $StartingChangeKey $change
else
p4 key $StartingChangeKey $change > /dev/null
fi
}
#------------------------------------------------------------------------------
# Micro functions.
function msg () { [[ $MT_VERBOSE -eq 1 ]] || return 0; echo -e "${1:-Hi}"; }
function dbg () { [[ $MT_DEBUG -eq 1 ]] || return 0; msg "${1:-Hi}"; }
function bail () { msg "Error: ${1:-Unknown Error}"; exit ${2:-1}; }
function cmd () { msg "${2:-Executing command: $1}"; $1; return $?; }
function usage () { msg "Usage:\n\tMoveTracker.sh <branch_spec> auto|<changelist>\n"; exit 1; }
declare Version=1.0.3
declare -i MT_DEBUG=${MT_DEBUG:-0}
declare -i MT_VERBOSE=${MT_VERBOSE:-0}
declare BranchSpec=${1:-Unset}
declare Changelist=${2:-Unset}
declare ChangesToProcess=
declare BranchSpecFile=/tmp/tmp.bspec.$$.$RANDOM
declare StartingChangeKey=
declare StartingChange=
declare EndingChange=
declare TargetPath=
declare SourceStream=
declare TargetStream=
declare -i Updated=0
[[ $Changelist == Unset || $BranchSpec == Unset ]] && usage
p4 branch -o $BranchSpec > $BranchSpecFile ||\
bail "Failed to display branch spec."
dbg "Branch Spec:\n$(grep -v '^#' $BranchSpecFile)\nEND_FILE\n"
StartingChangeKey=MoveTracker.LastChecked.$BranchSpec
# The target path is right-side of the first entry in the View: field of the
# branch spec.
TargetPath=$(p4 -ztag -F %View0% branch -o $BranchSpec)
SourceStream=$TargetPath
TargetStream=$TargetPath
TargetPath=//${TargetPath##*//}
SourceStream=${SourceStream%%/... //*}
TargetStream=//${TargetStream##* //}
TargetStream=${TargetStream%/...}
dbg "TP=[$TargetPath] SS=[$SourceStream] TS=[$TargetStream]"
if [[ $Changelist == auto ]]; then
StartingChange=$(p4 key $StartingChangeKey)
dbg "Starting change key $StartingChangeKey value is $StartingChange."
EndingChange=$(p4 -ztag -F %change% changes -m 1 $TargetPath)
dbg "Looking for list of changes $TargetPath between changes @$StartingChange and @$EndingChange."
ChangesToProcess=$(p4 -ztag -F %change% changes $TargetPath@$StartingChange,@$EndingChange)
dbg "Processing changelists: $ChangesToProcess"
for c in $ChangesToProcess; do
dbg "Auto Tracking moves in change $c."
track_moves_in_change "$c"
done
elif [[ $Changelist =~ ^[0-9]{1}[0-9]*$ ]]; then
dbg "Tracking moves in change $Changelist."
track_moves_in_change "$Changelist"
else
dbg "Unrecognized changelist $Changelist."
exit 1
fi
# Clean up temp files.
/bin/rm -f "$BranchSpecFile"
exit 0