#!/p4/common/perl/bin/perl -w =comment This is an example command trigger illustrating using the Perforce Map API to allow a trigger to decide if the user command that triggered the trigger has arguments that fall within the trigger's defined depot scope. E.g.: Triggers: no_sync command pre-user-sync "%//triggers/nosync.pl% //hidden/..." For this configuration, the trigger could allow 'p4 sync //depot/file', but not '//hidden/notes.txt'. It's the equivalent of the file pattern that other trigger types use. ################################################################################ Copyright (c) 2014, Perforce Software, Inc. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL PERFORCE SOFTWARE, INC. BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ################################################################################ User contributed content on the Perforce Public Depot is not supported by Perforce, although it may be supported by its author. This applies to all contributions even those submitted by Perforce employees. =cut ################################################################################ # todo: altroots? error handling (e.g. clients without a view, map translation # errors. server connection credentials. use strict; use warnings FATAL => 'all'; use feature ':5.14'; # s///r use open qw / :std :utf8 /; use URI::Escape; use P4; # Trigger -> server comms. Uses the triggers.io=1 configurable. sub dict { print 'action:' . shift . "\nmessage:" . uri_escape shift // ''; exit } sub fail { dict 'fail', @_ } sub pass { dict 'pass', @_ } # c:\path\to\file -> c:/path/to/file sub normalize { ( $_[ 0 ] // $_ ) =~ s/\\/\//rg } my ( $allowed, $client, $cwd, $root_map, $view_map ); # A local filesystem path or a path in client syntax. Depot paths pass through. sub is_local (_) { ! m,^//, } sub is_client(_) { m,^//$client/, } # An absolute path in local syntax. sub is_absolute(_) { /^\// or # /file /^[a-zA-Z]:(:?\\|\/)/ or # n:\file or n:/file /^\\\\\?\\/ or # \\?\ /^\\/ # \file } # E.g.: file, /file, /path/to/file -> //ws/file | //ws/... -> //ws/... sub local_to_client(_) { return $_ unless is_local; $_ = normalize ! is_absolute() ? "$cwd/$_" : $_; $_ = $root_map->Translate( $_ ); } # //ws/file -> //depot/file | //depot/file -> //depot/file sub client_to_depot(_) { is_client() ? $view_map->Translate( $_ ) : $_ } ################################################################################ my %cmd = map { /(.*?):(.*)/ } ; my $p4 = new P4; my $rcs_re = qr /^\$\S+: (.*) \$$/; $p4->SetProg ( '$File: //depot/dev/jgibson/utils/cmd_trig_by_path.pl $' =~ $rcs_re ); $p4->SetVersion( '@' . join '', '$Change: 965903 $' =~ $rcs_re ); $p4->SetPort( $cmd{ serverport } ); $p4->Connect or fail "Failed to connect to Perforce Server:\n" . $p4->Errors; $client = $cmd{ client }; my $spec = $p4->FetchClient( $client ); $p4->Disconnect; ################################################################################ # Commands without arguments are treated as running on everything. my @args = map { uri_unescape $_ } split ',', $cmd{ argsQuoted } // '//...'; my $root = normalize $$spec{ Root }; $cwd = normalize $cmd{ clientcwd }; # Append a trailing slash and elipses so it can be part of a map. $root .= $root =~ m,/$, ? '...' : '/...'; # Mappings to take user-supplied command arguments and convert them into # depot syntax so the trigger can decide if they reference the assigned # part of the depot. ( $root_map, $view_map, $allowed ) = map { new P4::Map } 0, 1, 2; # /local/workspace/path/... -> //workspace/path/... $root_map->Insert( $root, "//$client/..." ); # For each line of the client view: //workspace/... -> //depot/... map { $view_map->Insert( reverse split / (\/\/.*)/ ) } @{ $$spec{ View } }; # Hardcoded paths that this trigger will consider. Must use ellipses wildcard. # E.g. //depot/some/dir/... or //depot/file.txt my @allowed_dpaths = qw ( ); # Also take from our arguments so multiple triggers can be set differently. $allowed->Insert( $_ ) for ( @allowed_dpaths, @ARGV ); # Default to allowing everything if there's no configuration. $allowed->Insert( '//...' ) unless $allowed->AsArray; ################################################################################ # 'p4 sync' became 'p4 sync //...', which now becomes 'p4 sync //client/...' @args = map { $_ eq '//...' ? "//$client/..." : $_ } @args; # Actually perform the conversion of arguments. my @dargs = map { client_to_depot local_to_client } @args; # Deny the command if all the arguments don't fall under the trigger's # configured depot paths. To match other trigger behavior, the criteria # would be that we'd continue executing the trigger if at least one of the # paths matched. map { fail "nope: $_" unless $allowed->Includes( $_ ) } @dargs; # Ok, fine, if you must. pass;