#! /usr/bin/perl -w use strict; =head1 Notices Originally developed for Perforce by VIZIM (www.vizim.com) Copyright (c) 2014 Perforce Software, Inc. and VIZIM Worldwide, Inc. All rights reserved. Please see LICENSE.txt in top-level folder of this distribution for license information =cut use v5.14.0; # Earliest version testing was performed against use File::Path; use File::Basename; use File::Spec::Functions qw(rel2abs catfile); use IO::Handle; use Time::Local; use Getopt::Long; my $APPNAME = 'TFSRenames.pl'; my $APPWHAT = "TFS rename extraction; Version 1.03"; my $APPNOTICES = "Copyright (c) 2014 Perforce Software, Inc. and VIZIM Worldwide, Inc. All rights reserved. See LICENSE.txt for license information."; =head1 TFS rename extraction tool This tool extracts rename information from TFS based on server format file names and changeset information for the target of a rename. The output of the tool is used by TFSAssociate.pl =cut =head2 Operational Context Workspace context of collection for renames. =cut #################### # # Program constants and globals # #################### $| = 1; # force STDOUT to keep logs up to date in case of failure/ abort #################### # # Messaging support # #################### my $pathLog = ''; my $hLog = undef; my $ifERRORReported = 0; my $errorContext = undef; sub Msg($) { print "$_[0]\n"; print $hLog "$_[0]\n" if defined $hLog; } sub MsgERROR($) { print STDERR "$errorContext\n" if defined $errorContext; print STDERR "***" . $_[0] . "***\n"; print $hLog "$errorContext\n" if defined $errorContext && defined $hLog; print $hLog "***" . $_[0] . "***\n" if defined $hLog; $errorContext = undef; $ifERRORReported = 1; return 1; } sub MsgERRORResponse($@) { my ($error, @results) = @_; MsgERROR( $error ); Msg( "Response details:" ); foreach my $line (@results) { chomp $line; Msg( ".. $line" ); } } sub CoordinateExit(;$) { my $exitCode = $_[0]; $exitCode = $ifERRORReported if ! defined $exitCode; $errorContext = undef; Msg( "\n" ); MsgERROR( "Processing terminated with errors" ) if $exitCode != 0; Msg( ">>> Processing completed without errors" ) if $exitCode == 0; close $hLog if defined $hLog; exit( $exitCode ); } sub OptionUsage(;$) { my $errorMessage = $_[0]; MsgERROR( $errorMessage ) if defined $errorMessage; print "$APPWHAT $APPNOTICES Usage: $APPNAME -V $APPNAME [-h|-?] $APPNAME [options] UNRESOLVED Options: -log LOG - Create a copy of all message output in the log file LOG. Default is output to STDOUT only. Arguments: UNRESOLVED - Unresolved renames to process "; exit 0; } sub OptionVersion() { print "$APPWHAT\n"; exit 0; } #################### # # General utility functions # #################### sub utilAssurePathFile($) { my $FilePath = $_[0]; my ($name, $Dir, $suffix) = fileparse( $FilePath, (qr(\.[^\.]+),qr(\.))); unless( -e $Dir ) { mkpath( $Dir, 0, 0777 ); } } #################### # # Extraction and extraction support # #################### sub RunTFCommand($;$) { my ($command, $exitGood) = @_; $exitGood = 0 unless defined $exitGood; return (0, ()) if $command eq ''; my @results = `tf $command 2\>\&1`; my $exitCode = $? >> 8; MsgERRORResponse( "tf exit code $exitCode was not expected $exitGood", @results ) if $exitCode != 0 && $exitCode != $exitGood; return ($exitCode, @results); } # # Individual rename history command... # # tf history /format:detailed /itemmode /noprompt /version:C$version~$version $reference # sub RenameTargetInfo($$) { my ($sourceReference, $sourceVersion) = @_; my $context = "Can't access history for C$sourceVersion of '$sourceReference'"; my ($exitCode, @results) = RunTFCommand( "history /format:detailed /itemmode /noprompt /version:C$sourceVersion\~$sourceVersion \"$sourceReference\"", 100 ); if( $exitCode == 0 ) { my $targetAction = ''; my $targetReference = ''; my $isItems = 0; foreach my $line (@results) { if( $isItems && $line =~ m!\s+(rename.*) (\$/.+)$! ) { ($targetAction, $targetReference) = ($1, $2); last; } elsif( $line =~ m!^Items! ) { $isItems = 1; } elsif( $line =~ m!^\S! ) { $isItems = 0; } } if( $targetReference eq '' ) { MsgERRORResponse( "Can't identify rename source", @results ); return 0; } print "$sourceVersion $sourceReference -> $targetReference\n"; } else { MsgERROR( $context ); return 0; } return 1; } #################### # # Main processing point # #################### my $optHelp = 0; my $optVersion = 0; Getopt::Long::Configure( "auto_abbrev", "no_ignore_case" ); OptionUsage( "Invalid specification" ) unless( GetOptions( "help|?" => \$optHelp, "log=s" => \$pathLog, "Version" => \$optVersion ) ); # Help and version are one description and we're done OptionVersion() if $optVersion; OptionUsage() if $optHelp || scalar @ARGV == 0; # Don't understand anything but 1 argument OptionUsage( "Don't understand anything but 1 argument" ) if( scalar @ARGV != 1 ); # Establish a log if( $pathLog ne '' ) { $pathLog = rel2abs( $pathLog ); utilAssurePathFile( $pathLog ); Msg("Activity logged in: $pathLog"); open $hLog, '>', $pathLog; } # Identify the processing. Msg( "$APPNAME - $APPWHAT" ); Msg( "Generated" . localtime() ); # Process the unresolved file my $hUnresolved; if( ! open( $hUnresolved, '<', $ARGV[0] ) ) { MsgERROR( "Can't open unresolved file '$ARGV[0]' - $!" ); CoordinateExit(); } while (<$hUnresolved>) { chomp; my ($type, $changeset, $path) = ( '', 0, '' ); ($type, $changeset, $path) = ($1, $2, $3) if m!^(R.) (\d+) (.+)$!; next unless $type eq 'RF' || $type eq 'RP'; RenameTargetInfo( $path, $changeset ); } close $hUnresolved; CoordinateExit();