Using triggers to customize behavior

Perforce triggers are user-written programs or scripts that are called by a Perforce server whenever certain operations (such as changelist submits, changes to forms, attempts by users to log in or change passwords) are performed. If the script returns a value of 0, the operation continues; if the script returns any other value, the operation fails.

Triggers allow you to extend or customize Perforce functionality. Consider the following common uses:

  • To validate changelist contents beyond the mechanisms afforded by the Perforce protections table. For example, you can use a pre-submit trigger to ensure that whenever file1 is submitted in a changelist, file2 is also submitted.
  • To perform some action before or after the execution of a particular Perforce command.
  • To validate forms, or to provide customized versions of Perforce forms. For example, you can use form triggers to generate a customized default workspace view when users run the p4 client command, or to ensure that users always enter a meaningful workspace description.
  • To configure Perforce to work with external authentication mechanisms such as LDAP or Active Directory.

    You might prefer to enable LDAP authentication by using an LDAP specification. For more information, see section Authentication options.

  • To retrieve content from data sources archived outside of the Perforce repository.

For simplicity’s sake, this guide refers to trigger scripts and programs as triggers.

Note

If the API level is 79 or greater, canonical filetypes are now displayed by default for all commands that display filetypes. If the API level is 78 or lower, filetype aliases are displayed instead. If your script depends on the display of filetype aliases, you will need either to change the API level or to change your script.

Creating triggers

This section explains the basic workflow used to create a trigger, describes a sample trigger, discusses the trigger definition, and examines a trigger’s execution environment.

To create a trigger and have Perforce execute it, you must do the following:

  1. Write the program or script. Triggers can be written in a shell script such as Perl, Python, or Ruby; or they can be written in any programming language that can interface with Perforce, including UNIX shell and compiled languages like C/C+.

    Triggers have access to trigger variables that can be used to get server state information, execution context, client information, information about the parameters passed to the trigger, and so on. For information about trigger variables, see Trigger script variables.

    Triggers communicate with the server using trigger variables or by using a dictionary of key/value pairs accessed via STDIN and STDOUT. For more information on these methods, see Communication between a trigger and the server.

    Triggers can also use the command-line client (p4.exe) or the Perforce scripting API’s (P4-Ruby, P4-Python, P4-PHP) when data is needed that cannot be accessed by trigger variables. For more information, see APIs for Scripting.

    Triggers can be located on the server’s file system or in the depot itself, for information on using a trigger that is located in the depot, see Storing triggers in the depot.

    Triggers can be written for portability across servers. For more information, see Writing triggers to support multiple Perforce servers.

  2. Use the p4 triggers command to create a trigger definition that determines when the trigger will fire. Trigger definitions are composed of four fields: these specify the trigger name, the event type that must occur, the location of the trigger and, in some cases, some file pattern that must be matched in order to fire the trigger.

    For more information, see Trigger definition.

Warning

When you use trigger scripts, remember that Perforce commands that write data to the depot are dangerous and should be avoided. In particular, do not run the p4 submit command from within a trigger script.

It’s also important to avoid recursion and to watch out for client workspace locks. A trigger running commands as the requesting user could accidentally stall if it hits a lock.

Sample trigger

The following code sample is a bash auth-check type trigger that tries to authenticate a user (passed to the script using the %user% variable) using the Active Directory. If that fails, all users have the same "secret" password, and special user bruno is able to authenticate without a password.

USERNAME=$1
echo "USERNAME is $USERNAME"

# read user-supplied password from stdin
read USERPASS
echo Trying AD authentication for $USERNAME
echo $USERPASS | /home/perforce/p4auth_ad 192.168.100.80 389 DC=ad,DC=foo,DC=com $USERNAME
if [ $? == 0 ]
then
     # Successful AD
     echo Active Directory login successful
     exit 0
fi
# Compare user-supplied password with correct password, "secret"
PASSWORD=secret
if [ "$USERPASS" = $PASSWORD ]
then
     # Success
    exit 0
fi
if [ "$USERNAME" = "bruno" ]
then
    # Always let user bruno in
    exit 0
fi
# Failure
# password $USERPASS for $USERNAME is incorrect;
exit 1

To define this trigger, use the p4 triggers command, and add a line like the following to the triggers form:

bypassad auth-check auth "/home/perforce/bypassad.sh %user%"

The auth-check trigger is fired, if it exists, after a user executes the p4 login command. For authentication triggers, the password is sent on STDIN.

Note

Use an auth-check trigger rather than the service-check trigger for operator users.

Trigger definition

After you have written a trigger, you create the trigger definition by issuing the p4 triggers command and providing trigger information in the triggers form. You must be a Perforce superuser to run this command. The p4 triggers form looks like this:

Triggers:
  relnotecheck change-submit //depot/bld/...  "/usr/bin/rcheck.pl %user%"
  verify_jobs  change-submit //depot/...      "/usr/bin/job.py %change%"

As with all Perforce commands that use forms, field names (such as Triggers:) must be flush left (not indented) and must end with a colon, and field values (that is, the set of lines you add, one for each trigger) must be indented with spaces or tabs on the lines beneath the field name.

Each line in the trigger form you fill out when you use the p4 triggers command has four fields. These are briefly described in the following table. Values for three of these fields vary with the trigger type; these values are described in additional detail in the sections describing each type of trigger. The name field uses the same format for all trigger types.

Field Meaning

name

The user-defined name of the trigger.

To use the same trigger script with multiple file patterns, list the same trigger multiple times on contiguous lines in the trigger table. Use exclusionary mappings to prevent files from activating the trigger script; the order of the trigger entries matters, just as it does when exclusionary mappings are used in views. In this case, only the command of the first such trigger line that matches a path is used.

type

Triggers are divided into ten categories: submit triggers, push triggers, command triggers, journal-rotate triggers, shelve triggers, edge-server triggers, fix triggers, form triggers, authentication triggers, and archive triggers. One or more types is defined for each of these categories. For example, submit triggers include the change-submit, change-content, change-commit, and change-failed types.

Please consult the section describing the category of interest to determine which types relate to that trigger.

path

The use of this field varies with the trigger type. For example, for submit, edge server, and shelve triggers, this field is a file pattern in depot syntax. When a user submits a changelist that contains files that match this pattern, the trigger script executes.

Please consult the section describing the trigger of interest to determine which path is appropriate for that trigger.

command

The trigger for the Perforce server to run when the conditions implied by the trigger definition is satisfied.

You must specify the name of the trigger script or executable in ASCII, even when the server is running in Unicode mode and passes arguments to the trigger script in UTF8.

Specify the trigger in a way that allows the Perforce server to locate and run the command. The command (typically a call to a script) must be quoted, and can take as arguments any argument that your command is capable of parsing, including any applicable Perforce trigger variables.

On those platforms where the operating system does not know how to run the trigger, you will need to specify an interpreter in the command field. For example, Windows does not know how to run .pl files.

lo form-out label  "perl //myscripts/validate.pl"

When your trigger script is stored in the depot, its path must be specified in depot syntax, delimited by percent characters. For example, if your script is stored in the depot as //depot/scripts/myScript.pl, the corresponding value for the command field might be "/usr/bin/perl %//depot/scripts/myScript.pl%" . See Storing triggers in the depot for more information.

Triggers are run in the order listed in the trigger table; if a trigger script fails for a specified type, subsequent trigger scripts also associated with that type are not run.

The p4 triggers command has a very simple syntax:

p4 triggers [ -i | -o ]

  • With no flags, the user’s editor is invoked to specify the trigger definitions.
  • The -i flag reads the trigger table from standard input.
  • The -o flag displays all the trigger definitions stored in the trigger table.

Execution environment

When testing and debugging triggers, remember that any p4 commands invoked from within the script will run within a different environment (P4USER, P4CLIENT, and so on) than that of the calling user. You must therefore take care to initialize the environment you need from within the trigger script and not inherit these values from the current environment. For example:

export P4USER=george
export P4PASSWD=abracadabra
cd /home/pforce/database

p4 admin checkpoint
ls -l checkpoint.* journal*

In general, it is good practice to observe the following guidelines:

  • Wherever possible, use the full path to executables.
  • For path names that contain spaces, use the short path name.

    For example, C:\Program Files\Perforce\p4.exe is most likely located in C:\PROGRA~1\Perforce\p4.exe.

  • Unicode settings affect trigger scripts that communicate with the server. You should check your trigger’s use of file names, directory names, Perforce identifiers, and files that contain Unicode characters, and make sure that these are consistent with the character set used by the server.
  • Login tickets may not be located in the same place as they were during testing; for testing, you can pass in data with p4 login < input.txt.
  • For troubleshooting, log output to a file. For example:

    date /t >> trigger.log
    p4 info >> trigger.log
    C:\PROGRA~1\Perforce\p4.exe -p myServer:1666 info

    If a trigger fails to execute, the event is now logged in the Server log and an error is sent to the user.

  • Perforce commands in trigger scripts are always run by a specific Perforce user. If no user is specified, an extra Perforce license for a user named SYSTEM (or on UNIX, the user that owns the p4d process) is assumed. To prevent this from happening:

    • Pass a %user% argument to the trigger that calls each Perforce command to ensure that each command is called by that user. For example, if Joe submits a changelist that activates trigger script trigger.pl, and trigger.pl calls the p4 changes command, the script can run the command as p4 -u %user% changes.
    • Set P4USER for the account that runs the trigger to the name of an existing user. (If your Perforce server is installed as a service under Windows, note that Windows services cannot have a P4USER value; on Windows, you must therefore pass a user value to each command as described above.)
  • You can access the following environment variables from a trigger: P4USER, P4CLIENT, P4HOST, P4LANGUAGE, CWD, OS.
  • Timeouts associated with the trigger user might affect trigger execution. To prevent an unwanted timeout, place the user running the trigger in a group that will not time out.

    Timeout is the login ticket duration as defined by the group spec of the user the trigger is using to run commands; the ticket is the one created for use with the trigger. For example, the default login ticket duration is 8 hours, so if that is left unchanged for the trigger user, the trigger will have stopped working by the next day. Consider disabling the timeout so the trigger is not concerned about logins while it has access to the ticket file.

  • By default, the Perforce service runs under the Windows local System account. The System account may have different environmental configurations (including not just Perforce-related variables, but PATH settings and file permissions) than the one in which you are using to test or write your trigger.
  • Because Windows requires a real account name and password to access files on a network drive, if the trigger script resides on a network drive, you must configure the service to use a real userid and password to access the script.
  • On Windows, standard input does not default to binary mode. In text mode, line ending translations are performed on standard input, which is inappropriate for binary files.

    If you are using archive triggers against binary files on a Windows machine, you must prevent unwanted line-ending translations by ensuring that standard input is changed to binary mode (O_BINARY).

  • When using triggers on Windows, %formfile% and other variables that use a temp directory should use the TMP and TEMP system variables in Windows, not the user’s TEMP variables.

Trigger basics

This section contains information for working with triggers. Detailed information about implementing each type of trigger is found in the sections that follow. The information in this section applies to all types of triggers.

For information about debugging triggers, see http://answers.perforce.com/articles/KB/1249

Communication between a trigger and the server

Triggers can communicate with the server in one of two ways: by using the variables described in Trigger script variables or by using a dictionary of key/value pairs accessed via STDIN and STDOUT. The setting of the triggers.io configuration variable determines which method is used. The method chosen determines the content of STDIN and STDOUT and also affects how trigger failure is handled. The following table summarizes the effect of these settings. Client refers to the client application (Swarm, P4V, P4, etc) that is connected to the server where the trigger executes.

  triggers.io = 0 triggers.io = 1

Trigger succeeds

The trigger communicates with the server using trigger variables.

STDIN is only used by archive or authentication triggers. It is the file content for an archive trigger, and it is the password for an authentication trigger.

The trigger’s STDOUT is sent as an unadorned message to the client for all triggers except archive triggers; for archive triggers, the command’s standard output is the file content.

The trigger should exit with a zero value.

The trigger communicates with the server using STDIN and STDOUT.

STDIN is a textual dictionary of name-value pairs of all the trigger variables except for %clienthost% and %peerhost%.

This setting does not affect STDIN values for archive and authentication triggers.

The trigger should exit with a zero value.

Trigger fails

The trigger’s STDOUT and STDERR are sent to the client as the text of a trigger failure error message.

The trigger should exit with a non-zero value.

STDOUT is a textual dictionary that contains error information. STDERR is merged with STDOUT.

Failure indicates that the trigger script can’t be run, that the output dictionary includes a failure message, or that the output is mis-formatted. The execution error is logged by the server, and the server sends the client the information specified by STDOUT. If no dictionary is provided, the server sends the client a generic message that something has gone wrong.

The dictionary format is a sequence of lines containing key:value pairs. Any non-printable characters must be percent-encoded. Data is expected to be UTF8-encoded on unicode-enabled servers. Here are some examples of how the %client%, %clientprog%, %command%, and %user% variables would be represented in the %dictionary:

client:jgibson-aaaatchoooo
clientprog:P4/LINUX45X86_128/2017.9.MAIN/1773263782 (2017/OCT/09).
command:user-dwim
user:jgibson

The example above shows only a part of the dictionary. When variables are passed in this way, all the variables described in Trigger script variables are passed in STDIN, and the trigger script must read all of STDIN even if the script only references some of these variables. If the script does not read all of STDIN, the script will fail and the server will see errors like this:

write: yourTriggerScript: Broken pipe

The trigger must send back a dictionary to the server via STDOUT. The dictionary must at a minimum contain an action with an optional message. The action is either pass or fail. Non-printable characters must be percent encoded. For example:

action:fail
message:too bad!

Malformed trigger response dictionaries and execution problems are reported to the client with a generic error. A detailed message is recorded in the server log.

The introduction to this section suggested that the two ways of communicating with the server were mutually exclusive. In general, they are. There is one case, however, in which you must specify variables on the command line even if you set triggers.io to 1. This is when you want to reference the %peerhost% or %clienthost% variables. These variables are very expensive to pass. For their values to be included in the dictionary, you must specify one or both on the command line.

The following is a sample Perl program that echoes its input dictionary to the user:

use strict;
use warnings FATAL=>"all";
use open qw/ :std :utf8 /;
use Data::Dumper;
use URI::Escape;

$Data::Dumper::Quotekeys = 0;
$Data::Dumper::Sortkeys  = 1;

my %keys = map { /(.*):(.*)/ } <STDIN>;

print "action:pass\nmessage:" . uri_escape Dumper \ %keys;

The listing begins with some code that sets Perl up for basic Unicode support and adds some error handling. The gist of the program is in line 8. <STDIN> is a file handle that is applied to the map{}, where the map takes one line of input at a time and runs the function between the map’s {}. The expression (.*):(.*) is a regular expression with a pair of capture groups that are split by the colon. No key the server sends has a colon in it, so the first .* will not match. Since most non-printable characters (like newline) are percent-encoded in the dictionary, a trigger can expect every key/value pair to be a single line; hence the single regular expression can extract both the key and the value. The return values of the regular expression are treated as the return values for the map’s function, which is a list of strings. When a list is assigned to a hash, Perl tries to make it into a list of key/value pairs. Because we know it’s an even list, this works and we’ve gotten our data. The print command makes the result dictionary and sends it to the server. Calling it a pass action tells the server to let the command continue and that the message to send the user is the formated hash of the trigger’s input dictionary.

Exceptions

Setting triggers.io to 1 does not affect authentication and archive triggers; these behave as if triggers.io were set to 0 no matter what the actual setting is.

Compatibility with old triggers

When you set the triggers.io variable to 1, it affects how the server runs all scripts, both old and new. If you don’t want to rewrite your old trigger scripts, you can insert a shim between the trigger table and the old trigger script, which collects trigger output and formats it as the server now expects it. That is, the shim runs the old trigger, captures its output and return code, and then emits the appropriate dictionary back to the server. The following Perl script illustrates such a shim:

t form-out label unset "perl shim.pl original_trigger.exe orig_args..."

The shim.pl program might look like this:

use strict;
use warnings FATAL => "all";
use open qw/ :std :utf8 /;
use URI::Escape;
use IPC::Run3;

@_=<STDIN>;
run3 \@ARGV, undef, \$_, \$_;
print 'action:' . (? ? 'fail' : 'pass' ) . "\nmessage:" . uri_escape $_;

Storing triggers in the depot

You can store a trigger in the depot. This has two advantages:

  • It allows you to version the trigger and be able to access prior versions if needed.
  • In a distributed architecture, it enables Perforce to propagate the latest trigger script to every replica without your having to manually update the file in the filesystem of each server.

When you store a trigger in the depot, you must specify the trigger name in a special way in the command field of the trigger definition by enclosing the file path of the file containing the trigger in % signs. If you need to pass additional variables to the trigger, add them in the command field as you usually do. The server will create a temporary file that holds the contents of the file path name you have specified for the command field. (Working with a temporary file is preferable for security reasons and because depot files cannot generally be executed without some further processing.)

The depot file must already exist to be used as a trigger. All file types are acceptable so long as the content is available. For text types on unicode-enabled servers, the temporary file will be in UTF8. Protections on the depot script file must be such that only trusted users can see or write the content.

If the file path name contains spaces or if you need to pass additional parameters, you must enclose the command field in quotes.

In the next trigger definition, note that an interpreter is specified for the trigger. Specifying the interpreter is needed for those platforms where the operating system does not know how to run the trigger. For example, Windows does not know how to run .pl files.

lo form-out label  "perl %//admin/validate.pl%"

In the next trigger definition, the depot path is quoted because of the revision number. The absence of an interpreter value implies that the operating system knows how to run the script directly.

lo form-out branch "%//depot/scripts/validate.exe#123%"

Warning

A depot file path name may not contain reserved characters. This is because the hex replacement contains a percent sign, which is the terminator for a %var%. For example, no file named @myScript can be used because it would be processed as %40myScript inside a var %%40myScript%.

Using multiple triggers

Submit and form triggers are run in the order in which they appear in the triggers table. If you have multiple triggers of the same type that fire on the same path, each is run in the order in which it appears in the triggers table. If one of these triggers fails, no further triggers are executed.

Example 12. Multiple triggers on the same file

All *.c files must pass through the scripts check1.sh, check2.sh, and check3.sh:

Triggers:
  check1 change-submit //depot/src/*.c "/usr/bin/check1.sh %change%"
  check2 change-submit //depot/src/*.c "/usr/bin/check2.sh %change%"
  check3 change-submit //depot/src/*.c "/usr/bin/check3.sh %change%"

If any trigger fails (for instance, check1.sh), the submit fails immediately, and none of the subsequent triggers (that is, check2.sh and check3.sh) are called. Each time a trigger succeeds, the next matching trigger is run.

To link multiple file specifications to the same trigger (and trigger type), list the trigger multiple times in the trigger table.

Example 13. Activating the same trigger for multiple filespecs

Triggers:
  bugcheck change-submit //depot/*.c   "/usr/bin/check4.sh %change%"
  bugcheck change-submit //depot/*.h   "/usr/bin/check4.sh %change%"
  bugcheck change-submit //depot/*.cpp "/usr/bin/check4.sh %change%"

In this case, the bugcheck trigger runs on the *.c files, the *.h files, and the *.cpp files.

Multiple submit triggers of different types that fire on the same path fire in the following order:

  1. change-submit (fired on changelist submission, before file transmission)
  2. change-content triggers (after changelist submission and file transmission)
  3. change-commit triggers (on any automatic changelist renumbering by the server)

Similarly, form triggers of different types are fired in the following order:

  1. form-out (form generation)
  2. form-in (changed form is transmitted to the server)
  3. form-save (validated form is ready for storage in the Perforce database)
  4. form-delete (validated form is already stored in the Perforce database)

Writing triggers to support multiple Perforce servers

To call the same trigger script from more than one Perforce server, use the %serverhost%, %serverip%, and %serverport% variables to make your trigger script more portable.

For instance, if you have a script that uses hardcoded port numbers and addresses…​

#!/bin/sh
# Usage: jobcheck.sh changelist
CHANGE=$1
P4CMD="/usr/local/bin/p4 -p 192.168.0.12:1666"
$P4CMD describe -s $1 | grep "Jobs fixed...\n\n\t" > /dev/null

…​and you call it with the following line in the trigger table…​

jc1 change-submit //depot/qa/... "jobcheck.sh %change%"

…​you can improve portability by changing the script as follows…​

#!/bin/sh
# Usage: jobcheck.sh changelist server:port
CHANGE=$1
P4PORT=$2
P4CMD="/usr/local/bin/p4 -p $P4PORT"
$P4CMD describe -s $1 | grep "Jobs fixed...\n\n\t" > /dev/null

…​and passing the server-specific data as an argument to the trigger script:

jc2 change-submit //depot/qa/... "jobcheck.sh %change% %serverport%"

Note that the %serverport% variable can contain a transport prefix: ssl, tcp6, or ssl6.

For a complete list of variables that apply for each trigger type, see Trigger script variables.

Triggers and distributed architecture

Triggers installed on the master server must also exist on any of its replicas.

  • The trigger definition is automatically propagated to all replicas.
  • It is your responsibility to make sure that the program file that implements the trigger exists on every replica where the trigger might be activated. Its location on every replica must correspond to the location provided in the command field of the trigger definition.

    You can do this either by placing the trigger script in the same location in the file system on every server, or you can do it by storing it in the depot on the master or commit server and using depot syntax to specify the file name. In this case, the file is automatically propagated to all the replicas. For more information, see Storing triggers in the depot.

Triggers installed on the replicas must have the same execution environment for the triggers and the trigger bodies. This might typically include trigger login tickets or trigger script runtimes like Perl or Python.

Note

Edge servers have triggers that fire between client and edge server communication, and between edge server and commit server communication. For more information, see Helix Versioning Engine Administrator Guide: Multi-site Deployment.

Triggering on submits

To configure Perforce to run trigger scripts when users submit changelists, use submit triggers: these are triggers of type change-submit, change-content, and change-commit. You can also use change-failed triggers for the p4 submit or the p4 populate command.

You might want to take into consideration file locking behavior associated with submits: Before committing a changelist, p4 submit briefly locks all files being submitted. If any file cannot be locked or submitted, the files are left open in a numbered pending changelist. By default, the files in a failed submit operation are left locked unless the submit.unlocklocked configurable is set. Files are unlocked even if they were manually locked prior to submit if submit fails when submit.unlocklocked is set.

The following table describes the fields of a submit trigger. For sample definitions, see the subsequent sections, describing each trigger subtype.

Field Meaning

type

  • change-submit: Execute a submit trigger after changelist creation, but before file transfer. Trigger may not access file contents.
  • change-content: Execute a submit trigger after changelist creation and file transfer, but before file commit.

    To obtain file contents, use the revision specifier @=change (where change is the changelist number of the pending changelist as passed to the script in the %changelist% variable) with commands such as p4 diff2, p4 files, p4 fstat, and p4 print.

  • change-commit: Execute a submit trigger after changelist creation, file transfer, and changelist commit.
  • change-failed: Execute a submit trigger if the p4 submit or the p4 populate command fails. This trigger only fires on errors that occur after a commit process has started. It does not fire for early usage errors, or due to errors from the submit form. That is, if an edge or change trigger could have run, then the change-failed trigger will fire if that commit fails.

When using p4 diff2 in a change-content trigger:

  • The first file argument can be either file@change or file#headrev, but NOT file@=change.
  • The second file argument (typically the change being submitted) must use the file@=change syntax to report differences successfully. (Using file@change without the equals sign reports the file revisions as identical, which is wrong.)

For example, to submit a file //depot/foo as change 1001, and the previously submitted change was 1000, with a head revision of 25, both these revision specifier formats should work correctly if generated and called in the trigger script:

  p4 diff2 //depot/foo@1000 file@=1001
p4 diff2 //depot/foo#25 file@=1001

path

A file pattern in depot syntax.

When a user submits a changelist that contains any files that match this file pattern, the trigger specified in the command field is run. Use exclusionary mappings to prevent triggers from running on specified files.

command

The trigger for the Perforce server to run when a user submits a changelist that contains any file patterns specified by path. Specify the command in a way that allows the Perforce server account to locate and run the command. The command (typically a call to a script) must be quoted, and can take as arguments anything that your command is capable of parsing, including any applicable Perforce trigger variables.

When your trigger script is stored in the depot, its path must be specified in depot syntax, delimited by percent characters. For example, if your script is stored in the depot as //depot/scripts/myScript.pl, the corresponding value for the command field might be "/usr/bin/perl %//depot/scripts/myScript.pl%". See Storing triggers in the depot for more information.

For change-submit and change-content triggers (and their corresponding edge server triggers), changelist submission does not continue if the trigger fails. For change-commit triggers, changelist submission succeeds regardless of trigger success or failure, but subsequent change-commit triggers do not fire if the script fails.

Even when a change-submit or change-content trigger script succeeds, the submit can fail because of subsequent trigger failures, or for other reasons. Use change-submit and change-content triggers only for validation, and use change-commit triggers for operations that are contingent on the successful completion of the submit.

Be aware of edge cases: for example, if a client workspace has the revertunchanged option set, and a user runs p4 submit on a changelist with no changed files, a changelist has been submitted with files contents, but no changes are actually committed. (That is, a change-submit trigger fires, a change-content trigger fires, but a change-commit trigger does not.)

Change-submit triggers

Use the change-submit trigger type to create triggers that fire after changelist creation, but before files are transferred to the server. Because change-submit triggers fire before files are transferred to the server, these triggers cannot access file contents. Change-submit triggers are useful for integration with reporting tools or systems that do not require access to file contents.

In addition to the p4 submit command, the p4 populate command, which does an implicit submit as part of its branching action, fires a change-submit trigger to allow for validation before submission.

Example 14. The following change-submit trigger is an MS-DOS batch file that rejects a changelist if the submitter has not assigned a job to the changelist. This trigger fires only on changelist submission attempts that affect at least one file in the //depot/qa branch.

@echo off

rem REMINDERS
rem - If necessary, set Perforce environment vars or use config file
rem - Set PATH or use full paths (C:\PROGRA~1\Perforce\p4.exe)
rem - Use short pathnames for paths with spaces, or quotes
rem - For troubleshooting, log output to file, for instance:
rem - C:\PROGRA~1\Perforce\p4 info >> trigger.log

if not x%1==x goto doit
echo Usage is %0[change#]

:doit
p4 describe -s %1|findstr "Jobs fixed..." > nul
if errorlevel 1 echo No jobs found for changelist %1
p4 describe -s %1|findstr "Jobs fixed..." > nul

To use the trigger, add the following line to your triggers table:

sample1   change-submit //depot/qa/...   "jobcheck.bat %changelist%"

Every time a changelist is submitted that affects any files under //depot/qa, the jobcheck.bat file is called. If the string “Jobs fixed…​” (followed by two newlines and a tab character) is detected, the script assumes that a job has been attached to the changelist and permits changelist submission to continue. Otherwise, the submit is rejected.

The second findstr command ensures that the final error level of the trigger script is the same as the error level that determines whether to output the error message.

Change-content triggers

Use the change-content trigger type to create triggers that fire after changelist creation and file transfer, but prior to committing the submit to the database. Change-content triggers can access file contents by using the p4 diff2, p4 files, p4 fstat, and p4 print commands with the @=change revision specifier, where change is the number of the pending changelist as passed to the trigger script in the %changelist% variable.

Use change-content triggers to validate file contents as part of changelist submission and to abort changelist submission if the validation fails.

Even when a change-submit or change-content trigger script succeeds, the submit can fail because of subsequent trigger failures, or for other reasons. Use change-submit and change-content triggers only for validation, and use change-commit triggers for operations that are contingent on the successful completion of the submit.

Example 15. The following change-content trigger is a Bourne shell script that ensures that every file in every changelist contains a copyright notice for the current year.

The script assumes the existence of a client workspace called copychecker that includes all of //depot/src. This workspace does not have to be synced.

#!/bin/sh
# Set target string, files to search, location of p4 executable...
TARGET="Copyright 'date +%Y' Example Company"
DEPOT_PATH="//depot/src/..."
CHANGE=$1
P4CMD="/usr/local/bin/p4 -p 1666 -c copychecker"
XIT=0
echo ""
# For each file, strip off #version and other non-filename info
# Use sed to swap spaces w/"%" to obtain single arguments for "for"
for FILE in '$P4CMD files $DEPOT_PATH@=$CHANGE | \
  sed -e 's/\(.*\)\#[0-9]* - .*$/\1/' -e 's/ /%/g''
do
  # Undo the replacement to obtain filename...
  FILE="'echo $FILE | sed -e 's/%/ /g''"
# ...and use @= specifier to access file contents:
  # p4 print -q //depot/src/file.c@=12345
  if $P4CMD print -q "$FILE@=$CHANGE" | grep "$TARGET" > /dev/null
  then echo ""
  else
      echo "Submit fails: '$TARGET' not found in $FILE"
      XIT=1
  fi
done
exit $XIT

To use the trigger, add the following line to your triggers table:

sample2  change-content //depot/src/... "copydate.sh %change%"

The trigger fires when any changelist with at least one file in //depot/src is submitted. The corresponding DEPOT_PATH defined in the script ensures that of all the files in the triggering changelist, only those files actually under //depot/src are checked.

Change-commit triggers

Use the change-commit trigger type to create triggers that fire after changelist creation, file transfer, and changelist commission to the database. Use change-commit triggers for processes that assume (or require) the successful submission of a changelist.

Warning

When a change-commit trigger fires, any file in the committed changelist has already been submitted and could be changed by a user while the change-commit trigger executes.

Example 16. A change-commit trigger that sends emails to other users who have files open in the submitted changelist.

#!/bin/sh
# mailopens.sh - Notify users when open files are updated
changelist=$1
workspace=$2
user=$3
p4 fstat @$changelist,@$changelist | while read line
do
  # Parse out the name/value pair.
  name='echo $line | sed 's/[\. ]\+\([^ ]\+\) .\+/\1/''
  value='echo $line | sed 's/[\. ]\+[^ ]\+ \(.\+\)/\1/''
  if [ "$name" = "depotFile" ]
  then
    # Line is "... depotFile <depotFile>". Parse to get depotFile.
    depotfile=$value
  elif [ "'echo $name | cut -b-9'" = "otherOpen" -a \
    "$name" != "otherOpen" ]
  then
    # Line is "... ... otherOpen[0-9]+ <otherUser@otherWorkspace>".
    # Parse to get otherUser and otherWorkspace.
    otheruser='echo $value | sed 's/\(.\+\)@.\+/\1/''
    otherworkspace='echo $value | sed 's/.\+@\(.\+\)/\1/''
    # Get email address of the other user from p4 user -o.
    othermail='p4 user -o $otheruser | grep Email: \
      | grep -v \# | cut -b8-'

    # Mail other user that a file they have open has been updated
    mail -s "$depotfile was just submitted" $othermail <<EOM
The Perforce file: $depotfile
was just submitted in changelist $changelist by Perforce user $user
from the $workspace workspace.  You have been sent this message
because you have this file open in the $otherworkspace workspace.
EOM
  fi
done
exit 0

To use the trigger, add the following line to your triggers table:

sample3  change-commit //... "mailopens.sh %change% %client% %user%"

Whenever a user submits a changelist, any users with open files affected by that changelist receive an email notification.

Triggering on pushes and fetches

To configure Perforce to run trigger scripts when the p4 push, p4 unzip, or p4 fetch commands are invoked, use push triggers: these include triggers of type push-submit, push-content, and push-commit.

This section describes the triggers that can be used when initiating a push or fetch. See Additional triggers for push and fetch commands for a description of the triggers that can be used by the server receiving the pushed items or responding to the fetch request.

Because during a push, the local server acts as the client of the shared server, there are many similarities between the processing of submits and that of pushes:

  • Push actions are atomic: either everything is pushed or nothing is pushed.
  • Pushes occur in three distinct phases and different types of push triggers are appropriate for each phase.

(It is also the case that push triggers differ from change triggers; these differences affect the possible content of push triggers and influence the kind of trigger you want to use to customize the processing of changes. We will describe these differences shortly.)

The following figure illustrates the path of submitted files, via a changelist, from the client, to the local server, and finally, to the shared server. It also shows the types of triggers that may be run during each phase of these processes. There is no requirement that any triggers be run at any point in the submission or push process: the figure includes all possible types of triggers to illustrate the similarities between submits and pushes.

Figure 3. Change and push triggers

Change and push triggers

The three phases of submits and pushes include the following:

  1. Metadata is sent.

    Following this phase, a change-submit or push-submit trigger may test to see whether the user is allowed to perform the action, whether the file type is acceptable, and so on. Anything one can query about the metadata is actionable.

  2. Files are sent but changes are not yet committed.

    Following this phase, a content-submit or push-submit trigger may parse the contents of the files and take appropriate action depending on what it discovers. During this phase, one might look to see whether the submitted files adhere to coding conventions or other policies.

  3. The changes are committed.

    Following this phase, the commit is irrevocable, but the trigger may take some action: send a notification, do some clean up, and so on.

Turning to look at the differences between submits and pushes, we discover the following:

  • While both submits and pushes are atomic, a submit encompasses a single changelist; a push may contain multiple changelists. Thus the failure of a push is more costly.
  • Submits are unidirectional; pushes (which might happen as the result of a p4 push, p4 fetch, or p4 unzip) are bidirectional; depending on the command that causes the trigger to execute, either the local server or the shared server might play the role of client.
  • During the first phase of a push, metadata is read into memory, which limits the data that the push-commit trigger (which is a separate process with its own per-instance memory) can access. See Push-submit triggers for more information.
  • If a submit fails, you’re left with work in progress that you can change and retry. Having a push operation fail requires that you retrace your steps prior to the submit to the local server. For this reason, you might want to run triggers during the submit operation rather than the push operation if possible.
  • Change triggers are involved in the processing of p4 submit commands only. Push triggers are invoked in the context of three somewhat different scenarios: the execution of p4 push, p4 fetch, or p4 unzip commands.

You should keep these differences in mind when you decide how to define your triggers and at what stage to run them.

The following table describes the fields of a push trigger. For sample definitions, see the subsequent sections, describing each push trigger type.

Field Meaning

type

  • push-submit: Execute this trigger after changelist creation, but before file transfer. Trigger may not access file contents.
  • push-content: Execute this trigger after changelist creation and file transfer, but before file commit.

    To obtain file contents, use the revision specifier @=change (where change is the changelist number of the pending changelist as passed to the script in the %changelist% variable) with commands such as p4 diff2, p4 files, p4 fstat, and p4 print.

  • push-commit: Execute this trigger after changelist creation, file transfer, and changelist commit.

path

A file pattern in depot syntax.

When a user uses the p4 push, p4 unzip, or p4 fetch commands to submit a changelist that contains any files that match this file pattern, the trigger specified in the command field is run. Use exclusionary mappings to prevent triggers from running on specified files.

command

The trigger for the Perforce server to run when a user invokes the p4 push, p4 unzip, or p4 fetch commands to submit a changelist that contains any file patterns specified by path. Specify the command in a way that allows the Perforce server account to locate and run the command. The command (typically a call to a script) must be quoted, and can take as arguments anything that your command is capable of parsing, including any applicable Perforce trigger variables.

When your trigger script is stored in the depot, its path must be specified in depot syntax, delimited by percent characters. For example, if your script is stored in the depot as //depot/scripts/myScript.pl, the corresponding value for the command field might be "/usr/bin/perl %//depot/scripts/myScript.pl%" . See Storing triggers in the depot for more information.

For push-submit and push-content triggers, changelist submission does not continue if the trigger fails. For push-commit triggers, changelist submission succeeds regardless of trigger success or failure, but subsequent push-commit triggers do not fire if the script fails.

Even when a push-submit or push-content trigger script succeeds, the submission that caused the trigger to run can fail because of subsequent trigger failures, or for other reasons. Use push-submit and push-content triggers only for validation, and use push-commit triggers or for operations that are contingent on the successful completion of the push or fetch.

Push-submit triggers

Use the push-submit trigger type to create triggers that fire after changelist creation, but before files are transferred to the shared server. Because push-submit triggers fire before files are transferred to the server, these triggers cannot access file contents. Push-submit triggers are useful for integration with reporting tools or systems that do not require access to file contents.

As mentioned in the previous section where submit and push processing was described, push processing limits the commands you can run in a push-submit trigger. Please use the following commands only:

p4 change -o %changelist%

p4 describe -s %changelist%

p4 files //path/...@=%changelist%

p4 fstat //path/...@=%changelist%

Example 17. The following push-submit trigger is an MS-DOS batch file that rejects a changelist being pushed if the changelist description does not contain a line of the form Reviewed and signed off by: XXXXXXXX .

@echo off

if not x%1==x goto doit
echo Usage is %0[change#]
exit 1
:doit
p4 describe -s %1 | findstr "Reviewed and signed off" > nul
if errorlevel 1 echo "Changelist %1 missing review information."

To use the trigger, add the following line to your triggers table:

sample1   push-submit //depot/qa/...   "reviewcheck.bat %changelist%"

Every time a changelist is pushed that affects any files under //depot/qa, the reviewcheck.bat file is called. If the string "Reviewed and signed off" is detected, the script assumes that the required review has happened and permits the changelist push to continue. Otherwise the push is rejected.

Note

The p4 change and p4 describe commands do not display associated fixes when run from the push-submit or push-content triggers, even if the changes being pushed have associated fixes that are added as part of the push.

Push-content triggers

Use the push-content trigger type to create triggers that fire after changelist creation and file transfer, but prior to committing the push to the database. Push-content triggers can access file contents by using the p4 diff2, p4 files, p4 fstat, and p4 print commands with the @=change revision specifier, where change is the number of the pending changelist as passed to the trigger script in the %changelist% variable.

Use push-content triggers to validate file contents as part of changelist submission and to abort changelist submission if the validation fails.

Even when a push-submit or push-content trigger script succeeds, the push can fail because of subsequent trigger failures, or for other reasons. Use push-submit and push-content triggers only for validation, and use push-commit triggers for operations that are contingent on the successful completion of the push.

Example 18. The following push-content trigger is a Bourne shell script that ensures that every file in every changelist contains a copyright notice for the current year.

The script assumes the existence of a client workspace called copychecker that includes all of //depot/src. This workspace does not have to be synced.

#!/bin/sh
# Set target string, files to search, location of p4 executable...
TARGET="Copyright 'date +%Y' Example Company"
DEPOT_PATH="//depot/src/..."
CHANGE=$1
P4CMD="/usr/local/bin/p4 -p 1666 -c copychecker"
XIT=0
echo ""
# For each file, strip off #version and other non-filename info
# Use sed to swap spaces w/"%" to obtain single arguments for "for"
for FILE in '$P4CMD files $DEPOT_PATH@=$CHANGE | \
  sed -e 's/\(.*\)\#[0-9]* - .*$/\1/' -e 's/ /%/g''
do
  # Undo the replacement to obtain filename...
  FILE="'echo $FILE | sed -e 's/%/ /g''"
# ...and use @= specifier to access file contents:
  # p4 print -q //depot/src/file.c@=12345
  if $P4CMD print -q "$FILE@=$CHANGE" | grep "$TARGET" > /dev/null
  then echo ""
  else
      echo "Submit fails: '$TARGET' not found in $FILE"
      XIT=1
  fi
done
exit $XIT

To use the trigger, add the following line to your triggers table:

sample2  push-content //depot/src/... "copydate.sh %change%"

The trigger fires when any changelist with at least one file in //depot/src is pushed. The corresponding DEPOT_PATH defined in the script ensures that of all the files in the triggering changelist, only those files actually under //depot/src are checked.

Note

The p4 change and p4 describe commands do not display associated fixes when run from the push-submit or push-content triggers, even if the changes being pushed have associated fixes that are added as part of the push.

Push-commit triggers

Use the push-commit trigger type to create triggers that fire after changelist creation, file transfer, and changelist commission to the database. Use push-commit triggers for processes that assume (or require) the successful push of a changelist.

Example 19. A push-commit trigger that sends emails to other users who have files open in the pushed changelist.

#!/bin/sh
# mailopens.sh - Notify users when open files are updated
changelist=$1
workspace=$2
user=$3
p4 fstat @$changelist,@$changelist | while read line
do
  # Parse out the name/value pair.
  name='echo $line | sed 's/[\. ]\+\([^ ]\+\) .\+/\1/''
  value='echo $line | sed 's/[\. ]\+[^ ]\+ \(.\+\)/\1/''
  if [ "$name" = "depotFile" ]
  then
    # Line is "... depotFile <depotFile>". Parse to get depotFile.
    depotfile=$value
  elif [ "'echo $name | cut -b-9'" = "otherOpen" -a \
    "$name" != "otherOpen" ]
  then
    # Line is "... ... otherOpen[0-9]+ <otherUser@otherWorkspace>".
    # Parse to get otherUser and otherWorkspace.
    otheruser='echo $value | sed 's/\(.\+\)@.\+/\1/''
    otherworkspace='echo $value | sed 's/.\+@\(.\+\)/\1/''
    # Get email address of the other user from p4 user -o.
    othermail='p4 user -o $otheruser | grep Email: \
      | grep -v \# | cut -b8-'

    # Mail other user that a file they have open has been updated
    mail -s "$depotfile was just submitted" $othermail <<EOM
The Perforce file: $depotfile
was just pushed in changelist $changelist by Perforce user $user
from the $workspace workspace.  You have been sent this message
because you have this file open in the $otherworkspace workspace.
EOM
  fi
done
exit 0

To use the trigger, add the following line to your triggers table:

sample3  push-commit //... "mailopens.sh %change% %client% %user%"

Whenever a user pushes a changelist, any users with open files affected by that changelist receive an email notification.

The section Triggering before or after commands describes some additional options you have for triggers with push and fetch actions.

Triggering before or after commands

Triggers of type command allow you to configure Perforce to run a trigger before or after a given command executes. Generally, you might want to execute a script before a command runs to prevent that command from running; you might want to run a script after a command if you want to connect its action with that of another tool or process.

Note

You may use command type triggers with p4 push and p4 fetch commands.

The following table describes the fields of the command trigger.

Field Meaning

type

command

The command to execute is specified in the path field.

path

The`pre-user-command` value specifies the command before which the trigger should execute. The post-user-command value specifies the command after which the trigger should execute. command can be a regular expression. For additional information about the grammar of regular expressions, see p4 help grep.

Here are examples of possible path values:

pre-user-login              \\ before the login command
post-user-(add|edit)        \\ after the add or edit command
pre-user-obliterate         \\ before the obliterate command
(pre|post)-user-sync        \\ before or after the sync command

If you want to match a command name that’s a substring of another valid command, you should use the end-of-line meta-character to terminate matching. For example, use change$ so you don’t also match changes.

For additional information about path values with p4 push and p4 change commands, see Additional triggers for push and fetch commands.

You cannot create a pre-user-info trigger.`

command

The trigger for the Perforce server to run when the condition implied by path is satisfied.

Specify the command in a way that allows the Perforce server to locate and run the command. The command (typically a call to a script) must be quoted, and can take as arguments anything that your command is capable of parsing, including any applicable Perforce trigger variable.

When your trigger script is stored in the depot, its path must be specified in depot syntax, delimited by percent characters. For example, if your script is stored in the depot as //depot/scripts/myScript.pl, the corresponding value for the command field might be "/usr/bin/perl %//depot/scripts/myScript.pl%" . See Storing triggers in the depot for more information.

Parsing the input dictionary

One thing you might need to do in a command trigger is to parse the input dictionary. The following code sample does just that, putting the key/value store in a Perl data structure ready for access, and it shows how to send data back to the server.

use strict
use warnings FATAL => "all";
use open qw / :std :utf8 /;
use Data::Dumper;
use URI::Escape;

$Data::Dumper::Quotekeys = 0;
$Data::Dumper::Sortkeys = 1;

my %keys = map
{  /([^:]*):(.*)/  }
<STDIN>;

print "action:pass\nmessage:"  . uri_escape Dumper \ %keys;

The listing is a bit bigger than it needs to be in order to illustrate good trigger coding practice: it begins with some code that sets Perl up for basic Unicode support and adds some error handling. The gist of the program is in line 8. <STDIN> is a file handle that is applied to the map{}, where the map takes one line of input at a time and runs the function between the map’s {}. The expression (.*):(.*) is a regular expression with a pair of capture groups that are split by the colon. No key the server sends has a colon in it, so the first .* will not match. Since most non-printable characters (like newline) are percent-encoded in the dictionary, a trigger can expect every key/value pair to be a single line; hence the single regular expression can extract both the key and the value. The return values of the regular expression are treated as the return values for the map’s function, which is a list of strings. When a list is assigned to a hash, Perl tries to make it into a list of key/value pairs. Because we know it’s an even list, this works and we’ve gotten our data.

The print command makes the result dictionary and sends it to the server. Calling it a pass action tells the server to let the command continue and that the message to send the user is the formated hash of the trigger’s input dictionary.

After you write the script, you can add it to the trigger table by editing the p4 triggers form.

Triggers:
  myTrig command  post-user-move "perl /usr/bin/test.pl "

After the p4 move command executes, this trigger fires.

Additional triggers for push and fetch commands

The section Triggering on pushes and fetches describes the triggers that you can run during the various phases of the p4 push and p4 fetch commands. These are triggers that are run by the server initiating the push or the fetch. However, for every initiator, there is a responder:

  • For every push by server A to server B, there is a server B receiving the items pushed by A.
  • For every fetch by server A from server B, there is a sever B that is being fetched from.

This creates additional trigger opportunities for the server receiving the push and the server responding to the fetch request. You can use command type triggers to take advantage of these opportunities. Within this context, pre-user and post-user actions refer to the server initiating the push or fetch; pre-rmt and post-rmt actions refer to the responding server. The following table lists the triggers that can be used by the responding, or remote, server.

Trigger Meaning

pre-rmt-Push

Run this trigger on the remote server before it receives pushed content.

post-rmt-Push

Run this trigger on the remote server after it receives pushed content.

Two special variables are available for use with post remote push triggers:

  • %%firstPushedChange%% specifies the first new changelist number
  • %%lastPushedChange%% specifies the last new changelist number

pre-rmt-Fetch

Run this trigger on the remote server before it responds to a fetch request.

post-rmt-Fetch

Run this trigger on the remote server after it responds to a fetch request.

Triggering on journal rotation

To configure Perforce to run trigger scripts when journals are rotated, use the journal-rotate and journal-rotate-lock type triggers. Journal-rotate triggers are executed after the journal is rotated on a running server, but only if journals are rotated with the p4 admin journal or p4 admin checkpoint commands. Journal rotate triggers will not execute when journals are rotated with the p4d -jc or p4d --jj commands.

Journal-rotate triggers allow you to run maintenance routines on servers after the journal has been rotated, either while the database tables are still locked or after the locks have been released. These triggers are intended to be used on replicas or edge servers where journal rotation is triggered by journal records. The server must be running for these triggers to be invoked.

The following table describes the fields of a journal-rotate trigger:

Field Meaning

type

  • journal-rotate-lock: Execute the trigger after the journal is rotated but while the database files are still locked.
  • journal-rotate: Execute the trigger after the journal is rotated and data base file locks are released.

path

The server on which the triggers should be run. One of the following:

  • any
  • clusterid - run on all the replicas, or master in the cluster.
  • serverid- run on the specified server

command

The trigger for the Perforce server to run when the server matching path is found for the trigger type. Specify the command in a way that allows the Perforce server account to locate and run the command. The command (typically a call to a script) must be quoted, and can take as arguments anything that your command is capable of parsing, including any applicable Perforce trigger variables.

Journal-rotate triggers can process two variables: %journal% and %checkpoint%. These specify the names of the rotated journal and the new checkpoint if a checkpoint was taken. If no checkpoint was taken, %checkpoint% is an empty string.

When your trigger script is stored in the depot, its path must be specified in depot syntax, delimited by percent characters. For example, if your script is stored in the depot as //depot/scripts/myScript.pl, the corresponding value for the command field might be "/usr/bin/perl %//depot/scripts/myScript.pl%" . See Storing triggers in the depot for more information.

Triggering on shelving events

To configure Perforce to run trigger scripts when users work with shelved files, use shelve triggers: these are triggers of type shelve-submit, shelve-commit, and shelve-delete.

The following table describes the fields of a shelving type trigger:

Field Meaning

type

  • shelve-submit: Execute a pre-shelve trigger after changelist has been created and files locked, but prior to file transfer.
  • shelve-commit: Execute a post-shelve trigger after files are shelved.
  • shelve-delete: Execute a shelve trigger prior to discarding shelved files.

path

A file pattern in depot syntax.

If a shelve contains any files in the specified path, the trigger fires. To prevent some shelving operations from firing these triggers, use an exclusionary mapping in the path.

command

The trigger for the Perforce server to run when a matching path applies for the trigger type. Specify the command in a way that allows the Perforce server account to locate and run the command. The command (typically a call to a script) must be quoted, and can take as arguments anything that your command is capable of parsing, including any applicable Perforce trigger variables.

When your trigger script is stored in the depot, its path must be specified in depot syntax, delimited by percent characters. For example, if your script is stored in the depot as //depot/scripts/myScript.pl, the corresponding value for the command field might be "/usr/bin/perl %//depot/scripts/myScript.pl%" . See Storing triggers in the depot for more information.

Shelve-submit triggers

The shelve-submit trigger works like the change-submit trigger; it fires after the shelved changelist is created, but before before files are transferred to the server. Shelve-submit triggers are useful for integration with reporting tools or systems that do not require access to file contents.

Example 20. A site administrator wants to prohibit the shelving of large disk images; the following shelve-submit trigger rejects a shelving operation if the changelist contains .iso files.

#!/bin/sh

# shelve1.sh - Disallow shelving of certain file types

# This trigger always fails: when used as a shelve-submit trigger
# with a specified path field, guarantees that files matching that
# path are not shelved

echo "shelve1.sh: Shelving operation disabled by trigger script."

exit 1

To use the trigger, add the following line to your triggers table, specifying the path for which shelving is to be prohibited in the appropriate field, for example:

shelving1   shelve-submit   //....iso   shelve1.sh

Every time a changelist is submitted that affects any .iso files in the depot, the shelve1.sh script runs, and rejects the request to shelve the disk image files.

Shelve-commit triggers

Use the shelve-commit trigger to create triggers that fire after shelving and file transfer. Use shelve-commit triggers for processes that assume (or require) the successful submission of a shelving operation.

Example 21. A shelve-commit trigger that notifies a user (in this case, reviewers) about a shelved changelist.

#!/bin/sh
# shelve2.sh - Send email to reviewers when open files are shelved
changelist=$1
workspace=$2
user=$3

mail -s "shelve2.sh: Files available for review" reviewers << EOM
   $user has created shelf from $workspace in $changelist"
EOM

exit 0

To use the trigger, add the following line to your triggers table:

shelving2  shelve-commit //... "shelve2.sh %change% %client% %user%"

Whenever a user shelves a changelist, reviewers receive an email notification.

Shelve-delete triggers

Use the shelve-delete trigger to create triggers that fire after users discard shelved files.

Example 22. A shelve-delete trigger that notifies reviewers that shelved files have been abandoned.

#!/bin/sh
# shelve3.sh - Send email to reviewers when files deleted from shelf
changelist=$1
workspace=$2
user=$3

mail -s "shelve3.sh: Shelf $changelist deleted" reviewers << EOM
   $user has deleted shelved changelist $changelist"
EOM

exit 0

To use the trigger, add the following line to your triggers table:

shelving3  shelve-delete //... "shelve3.sh %change% %client% %user%"

Whenever a user deletes files from the shelf, reviewers receive an email notification. A more realistic example might check an external (or internal) data source to verify that code review was complete complete before permitting the user to delete the shelved files.

Triggering on fixes

To configure Perforce to run trigger scripts when users add or delete fixes from changelists, use fix triggers: these are triggers of type fix-add and fix-delete.

The special variable %jobs% is available for expansion with fix triggers; it expands to one argument for every job listed on the p4 fix command line (or in the Jobs: field of a p4 change or p4 submit form), and must therefore be the last argument supplied to the trigger script.

Note

Fix-add triggers might be also be run following the submission of a changelist if the job associated with the changelist exists both on the personal and the shared servers. For more information on push triggers, see Triggering on pushes and fetches.

The following table describes the fields used for a fix trigger definition.

Field Meaning

type

  • fix-add: Execute fix trigger prior to adding a fix.
  • fix-delete: Execute fix trigger prior to deleting a fix.

path

Use fix as the path value.

command

The trigger for the Perforce server to run when a user adds or deletes a fix. Specify the command in a way that allows the Perforce server account to locate and run the command. The command (typically a call to a script) must be quoted, and can take as arguments any argument that your command is capable of parsing, including any applicable Perforce trigger variables.

When your trigger script is stored in the depot, its path must be specified in depot syntax, delimited by percent characters. For example, if your script is stored in the depot as //depot/scripts/myScript.pl, the corresponding value for the command field might be "/usr/bin/perl %//depot/scripts/myScript.pl%" . See Storing triggers in the depot for more information.

For fix-add and fix-delete triggers, fix addition or deletion continues whether the script succeeds or fails.

Fix-add and fix-delete triggers

Example 23. The following script, when copied to fixadd.sh and fixdel.sh, fires when users attempt to add or remove fix records, whether by using the p4 fix command, or by modifying the Jobs: field of the forms presented by the p4 change and p4 submit commands.

#!/bin/bash
# fixadd.sh, fixdel.sh - illustrate fix-add and fix-delete triggers

COMMAND=$0
CHANGE=$1
NUMJOBS=$(($# - 1 ))

echo $COMMAND: fired against $CHANGE with $NUMJOBS job arguments.
echo $COMMAND: Arguments were: $*

These fix-add and fix-delete triggers fire whenever users attempt to add (or delete) fix records from changelists. To use the trigger, add the following lines to the trigger table:

sample4   fix-add    fix "fixadd.sh %change% %jobs%"
sample5   fix-delete fix "fixdel.sh %change% %jobs%"

Using both copies of the script, observe that fixadd.sh is triggered by p4 fix, the fixdel.sh script is triggered by p4 fix -d, and either script may be triggered by manually adding (or deleting) job numbers from within the Jobs: field in a changelist form - either by means of p4 change or as part of the p4 submit process.

Because the %jobs% variable is expanded to one argument for every job listed on the p4 fix command line (or in the Jobs: field of a p4 change or p4 submit form), it must be the last argument supplied to any fix-add or fix-delete trigger script.

Triggering on forms

To configure Perforce to run trigger scripts when users edit forms, use form triggers: these are triggers of type form-save, form-in, form-out, form-delete, and form-commit.

Use form triggers to generate customized field values for users, to validate data provided on forms, to notify other users of attempted changes to form data, and to otherwise interact with process control and management tools.

The %specdef% variable is defined for form triggers: it is expanded to the spec string of the form in question. This allows derived APIs to parse forms as part of triggers by loading the spec string as an argument.

If you write a trigger that fires on trigger forms, and the trigger fails in such a way that the p4 triggers command no longer works, the only recourse is to remove the db.triggers file in the server root directory.

The following table describes the fields of a form trigger definition:

Field Meaning

type

  • form-save: Execute a form trigger after the form contents are parsed, but before the contents are stored in the Perforce database. The trigger cannot modify the form specified in %formfile% variable.
  • form-out: Execute form trigger upon generation of form to end user. The trigger can modify the form.
  • form-in: Execute form trigger on edited form before contents are parsed and validated by the Perforce server. The trigger can modify the form.
  • form-delete: Execute form trigger after the form contents are parsed, but before the form is deleted from the Perforce database. The trigger cannot modify the form.
  • form-commit: Execute form trigger after the form has been committed for access to automatically-generated fields such as jobname, dates, etc.

path

The name of the type of form, (branch, change, client, depot, group, job, label, protect, server, spec, stream, triggers, typemap, or user).

command

The trigger for the Perforce server to run when the type of form specified in the path field is processed.

Specify the command in a way that allows the Perforce server account to locate and run the command. The command (typically a call to a script) must be quoted, and can take as arguments any argument that your command is capable of parsing, including any applicable Perforce trigger variables.

When your trigger script is stored in the depot, its path must be specified in depot syntax, delimited by percent characters. For example, if your script is stored in the depot as //depot/scripts/myScript.pl, the corresponding value for the command field might be "/usr/bin/perl %//depot/scripts/myScript.pl%" . See Storing triggers in the depot for more information.

For form-in, form-out, form-save, and form-delete triggers, the data in the specification becomes part of the Perforce database if the script succeeds. Otherwise, the database is not updated.

Form-save triggers

Use the form-save trigger type to create triggers that fire when users send changed forms to the server. Form-save triggers are called after the form has been parsed by the server but before the changed form is stored in the Perforce metadata.

Example 24. To prohibit certain users from modifying their client workspaces, add the users to a group called lockedws and use the following form-save trigger.

This trigger denies attempts to change client workspace specifications for users in the lockedws group, outputs an error message containing the user name, IP address of the user’s workstation, and the name of the workspace on which a modification was attempted, and notifies an administrator.

#!/bin/bash
NOAUTH=lockedws
USERNAME=$1
WSNAME=$2
IPADDR=$3

GROUPS='p4 groups "$1"'

if echo "$GROUPS" | grep -qs $NOAUTH
then
   echo "$USERNAME ($IPADDR) in $NOAUTH may not change $WSNAME"
   mail -s "User $1 workspace mod denial" admin@127.0.0.1
   exit 1
else
   exit 0
fi

This form-save trigger fires on client forms only. To use the trigger, add the following line to the trigger table:

sample6   form-save  client  "ws_lock.sh %user% %client% %clientip%"

Users whose names appear in the output of p4 groups lockedws have changes to their client workspaces parsed by the server, and even if those changes are syntactically correct, the attempted change to the workspace is denied, and an administrator is notified of the attempt.

Form-out triggers

Use the form-out trigger type to create triggers that fire whenever the Perforce server generates a form for display to the user.

Warning

Never use a Perforce command in a form-out trigger that fires the same form-out trigger, or infinite recursion will result. For example, never run p4 job -o from within a form-out trigger script that fires on job forms.

Example 25. The default Perforce client workspace view maps the entire depot //depot/... to the user’s client workspace. To prevent novice users from attempting to sync the entire depot, this Perl script changes a default workspace view of //depot/... in the p4 client form to map only the current release codeline of //depot/releases/main/...

#!/usr/bin/perl
# default_ws.pl - Customize the default client workspace view.
$p4 = "p4 -p localhost:1666";
$formname = $ARGV[0];  # from %formname% in trigger table
$formfile = $ARGV[1];  # from %formfile% in trigger table
# Default server-generated workspace view and modified view
# (Note: this script assumes that //depot is the only depot defined)
$defaultin = "\t//depot/... //$formname/...\n";
$defaultout = "\t//depot/releases/main/... //$formname/...\n";
# Check "p4 clients": if workspace exists, exit w/o changing view.
# (This example is inefficient if there are millions of workspaces)
open CLIENTS, "$p4 clients |" or die "Couldn't get workspace list";
while ( <CLIENTS> )
{
        if ( /^Client $formname .*/ ) { exit 0; }
}
# Build a modified workspace spec based on contents of %formfile%
$modifiedform = "";
open FORM, $formfile or die "Trigger couldn't read form tempfile";
while ( <FORM> )
{       ## Do the substitution as appropriate.
        if ( m:$defaultin: ) { $_ = "$defaultout"; }
        $modifiedform .= $_;
}
# Write the modified spec back to the %formfile%,
open MODFORM, ">$formfile" or die "Couldn't write form tempfile";
print MODFORM $modifiedform;
exit 0;

This form-out trigger fires on client workspace forms only. To use the trigger, add the following line to the trigger table:

sample7   form-out  client  "default_ws.pl %formname% %formfile%"

New users creating client workspaces are presented with your customized default view.

Form-in triggers

Use the form-in trigger type to create triggers that fire when a user attempts to send a form to the server, but before the form is parsed by the Perforce server.

Example 26. All users permitted to edit jobs have been placed in a designated group called jobbers. The following Python script runs p4 group -o jobbers with the -G (Python marshaled objects) flag to determine if the user who triggered the script is in the jobbers group.

import sys, os, marshal

# Configure for your environment
tuser = "triggerman"   # trigger username
job_group = "jobbers"  # Perforce group of users who may edit jobs

# Get trigger input args
user = sys.argv[1]

# Get user list
# Use global -G flag to get output as marshaled Python dictionary
CMD = "p4 -G -u %s -p 1666 group -o %s" % \
        (tuser, job_group)
result = {}
result = marshal.load(os.popen(CMD, 'r'))

job_users = []
for k in result.keys():
        if k[:4] == 'User': # user key format: User0, User1, ...
                u = result[k]
                job_users.append(u)

# Compare current user to job-editing users.
if not user in job_users:
        print "\n\t>>> You don't have permission to edit jobs."
        print "\n\t>>> You must be a member of '%s'.\n" % job_group
        sys.exit(1)
else: # user is in job_group -- OK to create/edit jobs
        sys.exit(0)

This form-in trigger fires on job forms only. To use the trigger, add the following line to the trigger table:

sample8   form-in  job  "python jobgroup.py %user%"

If the user is in the jobbers group, the form-in trigger succeeds, and the changed job is passed to the Perforce server for parsing. Otherwise, an error message is displayed, and changes to the job are rejected.

Form-delete triggers

Use the form-delete trigger type to create triggers that fire when users attempt to delete a form, after the form is parsed by the Perforce server, but before the form is deleted from the Perforce database.

Example 27. An administrator wants to enforce a policy that users are not to delete jobs from the system, but must instead mark such jobs as closed.

#!/bin/sh

echo "Jobs may not be deleted. Please mark jobs as closed instead."
exit 1

This form-delete trigger fires on job forms only. To use the trigger, add the following line to the trigger table:

sample9   form-delete  job  "nodeljob.sh"

Whenever a user attempts to delete a job, the request to delete the job is rejected, and the user is shown an error message.

Form-commit triggers

Unlike the other form triggers, the form-commit trigger fires after a form is committed to the database. Use these triggers for processes that assume (or require) the successful submission of a form. In the case of job forms, the job’s name is not set until after the job has been committed to the database; the form-commit trigger is the only way to obtain the name of a new job as part of the process of job creation.

Example 28. The following script, when copied to newjob.sh, shows how to get a job name during the process of job creation, and also reports the status of changelists associated with job fixes.

#!/bin/sh
# newjob.sh - illustrate form-commit trigger

COMMAND=$0
USER=$1
FORM=$2
ACTION=$3

echo $COMMAND: User $USER, formname $FORM, action $ACTION >> log.txt

To use the trigger, add the following line to the trigger table:

sample10  form-commit  job   "newjob.sh %user% %formname% %action%"

Use the %action% variable to distinguish whether or not a change to a job was prompted by a user directly working with a job by means of p4 job, or indirectly by means of fixing the job within the context of p4 fix or the Jobs: field of a changelist.

The simplest case is the creation of a new job (or a change to an existing job) with the p4 job command; the trigger fires, and the script reports the user, the name of the newly-created (or edited) job. In these cases, the %action% variable is null.

The trigger also fires when users add or delete jobs to changelists, and it does so regardless of whether the changed jobs are being manipulated by means of p4 fix, p4 fix -d, or by editing the Jobs: field of the changelist form provided by p4 change or p4 submit form). In these cases, the %action% variable holds the status of the changelist (pending or submitted) to which the jobs are being added or deleted. The form-commit trigger does not run if zero jobs are attached to the changelist.

Because the %action% variable is not always set, it must be the last argument supplied to any form-commit trigger script.

Triggering to use external authentication

To configure Perforce to work with an external authentication manager (such as LDAP or Active Directory), use authentication triggers (auth-check, auth-check-sso, service-check, and auth-set). These triggers fire on the p4 login and p4 passwd commands, respectively.

Note

You might prefer to enable LDAP authentication by using an LDAP specification. This option is recommended: it is easier to use, no external scripts are required, it provides greater flexibility in defining bind methods, it allows users who are not in the LDAP directory to be authenticated against Perforce’s internal user database, and it is more secure. For more information, see Authentication options.

That being said, you also have the option of using auth-check-sso triggers when LDAP authentication is enabled. In this case, users authenticated by LDAP can define a client-side SSO script instead of being prompted for a password. If the trigger succeeds, the active LDAP configurations are used to confirm that the user exists in at least one LDAP server. The user must also pass the group authorization check if it is configured. Triggers of type auth-check-sso will not be called for users who do not authenticate against LDAP.

Authentication triggers differ from changelist and form triggers in that passwords typed by the user as part of the authentication process are supplied to authentication scripts as standard input; never on the command line. (The only arguments passed on the command line are those common to all trigger types, such as %user%, %clientip%, and so on.)

Warning

Be sure to spell the trigger name correctly when you add the trigger to the trigger table because a misspelling can result in all users being locked out of Perforce.

Be sure to fully test your trigger and trigger table invocation prior to deployment in a production environment.

Contact Perforce Technical Support if you need assistance with restoring access to your server.

The examples in this book are for illustrative purposes only. For a more detailed discussion, including links to sample code for an LDAP environment, see "Setting Up External Authentication Triggers" in the Perforce knowledge base:

http://answers.perforce.com/articles/KB_Article/Setting-Up-External-Authentication-Triggers

You must restart the Perforce server after adding an auth-check (or service-check) trigger in order for it to take effect. You can, however, change an existing auth-check trigger table entry (or trigger script) without restarting the server.

After an auth-check trigger is in place and the server restarted, the Perforce security configurable is ignored; because authentication is now under the control of the trigger script, the server’s default mechanism for password strength requirements is redundant.

The following table describes the fields of an authentication trigger definition.

Field Meaning

type

  • auth-check: Execute an authentication check trigger to verify a user’s password against an external password manager during login, or when setting a new password. If an auth-check trigger is present, the Perforce security configurable (and any associated password strength requirement) is ignored, as authentication is now controlled by the trigger script.

    You must restart the Perforce server after adding an auth-check trigger.

  • auth-check-sso: Facilitate a single sign-on user authentication.
  • auth-set: Execute an authentication set trigger to send a new password to an external password manager.
  • service-check: Execute a trigger to verify the password of a service user, rather than a standard user. Service check triggers work in the same way that auth-check triggers do. Do not use this type of trigger for an operator user; use the auth-check type trigger instead.

    You must restart the Perforce server after adding a service-check trigger.

path

Use auth as the path value.

command

The trigger for the Perforce server to run. See the following sections about specific authentication trigger types for more information on when the trigger is fired. In most cases, it is when the p4 login command executes.

Specify the command in a way that allows the Perforce server account to locate and run the command. The command (typically a call to a script) must be quoted, and can take as arguments any argument that your command is capable of parsing, including any applicable Perforce trigger variables.

When your trigger script is stored in the depot, its path must be specified in depot syntax, delimited by percent characters. For example, if your script is stored in the depot as //depot/scripts/myScript.pl, the corresponding value for the command field might be "/usr/bin/perl %//depot/scripts/myScript.pl%" . See Storing triggers in the depot for more information.

For auth-check and service-check triggers (fired by p4 login from standard/operator users and service users respectively), the user’s typed password is supplied to the trigger command as standard input. If the trigger executes successfully, the Perforce ticket is issued. The user name is available as %user% to be passed on the command line.

For auth-check-sso triggers, (fired by p4 login for all users) the output of the client-side script (specified by P4LOGINSSO) is sent to the server-side script in cleartext.

For auth-set triggers, (fired by p4 passwd, but only after also passing an auth-check trigger check) the user’s old password and new password are passed to the trigger as standard input. The user name is available as %user% to be passed on the command line.

Auth-check and service-check triggers

Triggers of type auth-check fire when standard or operator users run the p4 login command. Similarly, service-check triggers fire when service users users run the p4 login command. If the script returns 0, login is successful, and a ticket file is created for the user.

The service-check trigger works exactly like an auth-check trigger, but applies only to users whose Type: has been set to service. The service-check trigger type is used by Perforce administrators who want to use LDAP to authenticate other Perforce servers in replicated and other multi-server environments.

Warning

If you are using auth-check triggers, the Perforce superuser must also be able to authenticate against the remote authentication database. (If you, as the Perforce superuser, cannot use the trigger, you may find yourself locked out of your own server, and will have to (temporarily) overwrite your auth-check trigger with a script that always passes in order to resolve the situation.)

Example 29. A trivial authentication-checking script.

All users must enter the password "secret" before being granted login tickets. Passwords supplied by the user are sent to the script on STDIN.

#!/bin/bash
# checkpass.sh - a trivial authentication-checking script

# in this trivial example, all users have the same "secret" password
USERNAME=$1
PASSWORD=secret

# read user-supplied password from stdin
read USERPASS

# compare user-supplied password with correct password
if [ "$USERPASS" = $PASSWORD ]
then
    # Success
    exit 0
fi

# Failure
echo checkpass.sh: password $USERPASS for $USERNAME is incorrect
exit 1

This auth-check trigger fires whenever users run p4 login. To use the trigger, add the following line to the trigger table:

sample11  auth-check  auth  "checkpass.sh %user%"

Users who enter the "secret" password are granted login tickets.

Single signon and auth-check-sso triggers

Triggers of type auth-check-sso fire when standard users run the p4 login command. Two scripts are run: a client-side script is run on the user’s workstation, and its output is passed (in plaintext) to the Perforce Server, where the server-side script runs.

  • On the user’s client workstation, a script (whose location is specified by the P4LOGINSSO environment variable) is run to obtain the user’s credentials or other information verifiable by the Perforce Server. The P4LOGINSSO contains the name of the client-side script and zero or more of the following trigger variables, passed as parameters to the script: %user%, %serverAddress%, and %P4PORT%. For example:

    export P4LOGINSSO="/path/to/sso-client.sh %user% %serverAddress% %P4PORT%"

    Where %user% is the Perforce client user, %serverAddress% is the address of the target Perforce server, and %P4PORT% is an intermediary between the client and the server.

  • On the server, the output of the client-side script is passed to the server-side script as standard input. The server-side script specified in the trigger table runs, and the server returns an exit status of 0 if successful.

    With a distributed configuration in which a proxy or broker acts as an intermediary between the client and the server, the %serverAddress% variable will hold the address/port of the server and the %P4PORT% variable will hold the port of the intermediary. It is up to the script to decide what to do with this information.

Example 30. Interaction between client-side and server-side scripts.

An auth-check-sso trigger fires whenever users run p4 login. The system administrator might add the following line to the trigger table to specify the script that should run on the server side:

sample13  auth-check-sso  auth  "serverside.sh %user%"

and each end user sets the following environment variable on the client side:

export P4LOGINSSO=/usr/local/bin/clientside.sh %serverAddress%

When the user attempts to log on, the P4LOGINSSO script runs on the user’s workstation:

##!/bin/bash
# clientside.sh - a client-side authentication script
#
# if we use %serverAddress% in the command-line like this:
#    p4 -E P4LOGINSSO=clientside.sh %serverAddress%
# then this script receives the serverAddress as $1, and the user
# can use it for multiple connections to different Perforce servers.
#
# In this example, we simulate a client-side authentication process
# based on whether the user is connecting to the same Perforce Server
# as is already configured in his or her environment.
# (We also output debugging information to a local file.)

input_saddr=$1

env_saddr=`p4 info | grep "Server address" | awk '{printf "%s", $3}'`

if test "$input_saddr" == "$env_saddr"
  then
    # User is connected to the server specified by P4PORT - pass
    echo "sso pass"; echo pass "$input_saddr" >> debug.txt; exit 0
  else
    # User is attempting to connect to another server - fail
    echo "no pass"; echo fail "$input_saddr" >> debug.txt; exit 1
fi

If the user is connected to the same Perforce Server as specified by P4PORT (that is, if the server address passed from the Server to this script matches the server that appears in the output of a plain p4 info command), client-side authentication succeeds. If the user is connected to another Perforce Server (for example, by running p4 -p host:port login against a different Perforce Server), client-side authentication fails.

The server-side script is as follows:

#!/bin/bash
#
# serverside.sh - a server-side authentication script
#

if test $# -eq 0
  then
    echo "No user name passed in.";
    exit 1;
fi

read msg </dev/stdin

if test "$msg" == ""
  then
    echo "1, no stdin"
    exit 1
fi

if test "$msg" == "sso pass"
  then
    exit 0
  else
    exit 1
fi

In a more realistic example, the end user’s P4LOGINSSO script points to a clientside.sh script that contacts an authentication service to obtain a token of some sort. The client-side script then passes this token to Perforce Server’s trigger script, and serverside.sh uses the single-signon service to validate the token.

In this example, clientside.sh merely checks whether the user is using the same connection as specified by P4PORT, and the output of clientside.sh is trivially checked for the string "sso pass"; if the string is present, the user is permitted to log on.

Triggering for external authentication

Triggers of type auth-set fire when users (standard users or service users) run the p4 passwd command and successfully validate their old password with an auth-check (or service-check) trigger. The process is as follows:

  1. A user invokes p4 passwd.
  2. The Perforce server prompts the user to enter his or her old password.
  3. The Perforce server fires an auth-check trigger to validate the old password against the external authentication service.
  4. The script associated with the auth-check trigger runs. If the auth-check trigger fails, the process ends immediately: the user is not prompted for a new password, and the auth-set trigger never fires.
  5. If the auth-check trigger succeeds, the server prompts the user for a new password.
  6. The Perforce server fires an auth-set trigger and supplies the trigger script with both the old password and the new password on the standard input, separated by a newline.

    Note

    In most cases, users in an external authentication environment will continue to set their passwords without use of Perforce. The auth-set trigger type is included mainly for completeness.

Because the Perforce server must validate the user’s current password, you must have a properly functioning auth-check trigger before attempting to write an auth-set trigger.

Example 31. A trivial authentication-setting script

#!/bin/bash
# setpass.sh - a trivial authentication-setting script

USERNAME=$1

read OLDPASS
read NEWPASS

echo setpass.sh: $USERNAME attempted to change $OLDPASS to $NEWPASS

This auth-set trigger fires after users run p4 passwd and successfully pass the external authentication required by the auth-check trigger. To use the trigger, add the following two lines to the trigger table:

sample11  auth-check  auth  "checkpass.sh %user%"
sample12  auth-set    auth  "setpass.sh %user%"

This trivial example doesn’t actually change any passwords; it merely reports back what the user attempted to do.

Triggering to affect archiving

The archive trigger type is used in conjunction with the +X filetype modifier in order to replace the mechanism by which the Perforce Server archives files within the repository. They are used for storing, managing, or generating content archived outside of the Perforce repository. See Execution environment for platform-specific considerations.

The following table describes the fields of an archive trigger definition:

Field Meaning

type

archive: Execute the script when a user accesses any file with a filetype containing the +X filetype modifier. The script can read, write, or delete files in the archive.

The script is run once per file requested.

For read operations, scripts should deliver the file to the user on standard output. For write operations, scripts receive the file on standard input.

path

A file pattern to match the name of the file being accessed in the archive.

command

The trigger for the Perforce server to run when a file matching path is found in the archive.

Specify the command in a way that allows the Perforce server account to locate and run the command. The command (typically a call to a script) must be quoted, and can take as arguments any argument that your command is capable of parsing, including any applicable Perforce trigger variables.

When your trigger script is stored in the depot, its path must be specified in depot syntax, delimited by percent characters. For example, if your script is stored in the depot as //depot/scripts/myScript.pl, the corresponding value for the command field might be "/usr/bin/perl %//depot/scripts/myScript.pl%" . See Storing triggers in the depot for more information.

If the command succeeds, the command’s standard output is the file content. If the command fails, the command standard output is sent to the client as the text of a trigger failure error message.

Example 32. An archive trigger

This archive trigger fires when users access files that have the +X (archive) modifier set.

#!/bin/sh
# archive.sh - illustrate archive trigger

OP=$1
FILE=$2
REV=$3

if [ "$OP" = read ]
then
    cat ${FILE}${REV}
fi

if [ "$OP" = delete ]
then
    rm ${FILE}${REV}
fi

if [ "$OP" = write ]
then
    # Create new file from user's submission via stdin
    while read LINE; do
        echo ${LINE} >> ${FILE}${REV}
    done
    ls -t ${FILE}* |
    {
        read first; read second;
        cmp -s $first $second
        if [ $? -eq 0 ]
        then
            # Files identical, remove file, replace with symlink.
            rm ${FILE}${REV}
            ln -s $second $first
        fi
    }
fi

To use the trigger, add the following line to the trigger table:

arch  archive  path  "archive.sh %op% %file% %rev%"

When the user attempts to submit (write) a file of type +X in the specified path, if there are no changes between the current revision and the previous revision, the current revision is replaced with a symlink pointing to the previous revision.

Trigger script variables

You can use trigger script variables to pass data to a trigger script. All data is passed as a string; it is up to the trigger to interpret and use these appropriately.

It is also possible to have the server and trigger communicate using STDIN and STDOUT. For more information, see Communication between a trigger and the server.

The maxError…​ variables refer to circumstances that prevented the server from completing a command; for example, an operating system resource issue. Note also that client-side errors are not always visible to the server and might not be included in the maxError count.

The terminated and termType variables indicate whether the command exited early and why.

Note

The processing of unknown variables has changed. Previously, unknown variables were removed from the trigger invocation. Currently they are left as is. This preserves the trigger argument ordering, and might be a clue to authors that data they assumed to be available is not.

Argument Description Available for type

%action%

Either null or a string reflecting an action taken to a changelist or job.

For example,"pending change 123 added" or "submitted change 124 deleted" are possible %action% values on change forms, and "job000123 created" or "job000123 edited" are possible %action% values for job forms.

form-commit

%argc%

Command argument count.

all except archive

%args%

Command argument string.

all except archive

%argsQuoted%

Command argument string that contains the command arguments as a percent-encoded comma-separated list.

all except archive

%changelist%, %change%

The number of the changelist being submitted. The abbreviated form %change% is equivalent to %changelist%.

A change-submit trigger is passed the pending changelist number; a change-commit trigger receives the committed changelist number.

A shelve-commit or shelve-delete trigger receives the changelist number of the shelf.

change-submit
push-submit
change-content
push-content
change-commit
push-commit
fix-add,
fix-delete,
form-commit,
shelve-commit,
shelve-delete

%changeroot%

The root path of files submitted.

change-commit

push-commit

%client%

Triggering user’s client workspace name.

all

%clientcwd%

Client’s current working directory.

all except archive

%clienthost%

Hostname of the user’s workstation (even if connected through a proxy, broker, replica, or an edge server.)

all

%clientip%

The IP address of the user’s workstation (even if connected through a proxy, broker, replica, or an edge server.)

all

%clientprog%

The name of the user’s client application. For example, P4V, P4Win, etc.

all

%clientversion%

The version of the user’s client application.

all

%command%

Command name.

all except archive

%file%

Path of archive file based on depot’s Map: field. If the Map: field is relative to P4ROOT, the %file% is a server-side path relative to P4ROOT. If the Map: field is an absolute path, the %file% is an absolute server-side path.

archive

%firstPushedChange%

First new changelist number.

See Additional triggers for push and fetch commands for more information.

command

%formfile%

Path to temporary form specification file. To modify the form from an in or out trigger, overwrite this file. The file is read-only for triggers of type save and delete.

form-commit,
form-save,
form-in,
form-out,
form-delete

%formname%

Name of form (for instance, a branch name or a changelist number).

form-commit,
form-save,
form-in,
form-out,
form-delete

%formtype%

Type of form (for instance, branch, change, and so on).

form-commit,
form-save,
form-in,
form-out,
form-delete

%groups%

List of groups to which the user belongs, space-separated.

all except archive

%intermediateService%

A broker or proxy is present.

all except archive

%jobs%

A string of job numbers, expanded to one argument for each job number specified on a p4 fix command or for each job number added to (or removed from) the Jobs: field in a p4 submit, or p4 change form.

fix-add,
fix-delete

%lastPushedChange%

Last new changelist number.

See Additional triggers for push and fetch commands for more information.

command

%maxErrorSeverity%

One of empty, error, or warning.

all except archive

%maxErrorText%

Error number and text.

all except archive

%maxLockTime%

A user-specified value that specifies the number of milliseconds for the longest permissible database lock. If this variable is set, it means the user has overridden the group setting for this value.

all except archive

%maxResults%

A user-specified value that specifies the amount of data buffered during command execution. If this variable is set, it means the user has overridden the group setting for this value.

all except archive

%maxScanRows%

A user-specified value that specifies the maximum number of rows scanned in a single operation. If this variable is set, it means the user has overridden the group setting for this value.

all except archive

%oldchangelist%

If a changelist is renumbered on submit, this variable contains the old changelist number.

change-commit

push-commit

%op%

Operation: read, write, or delete.

archive

%peerhost%

If the command was sent through a proxy, broker, replica, or edge server, the hostname of the proxy, broker, replica, or edge server. (If the command was sent directly, %peerhost% matches %clienthost%)

all

%peerip%

If the command was sent through a proxy, broker, replica, or edge server, the IP address of the proxy, broker, replica, or edge server. (If the command was sent directly, %peerip% matches %clientip%)

all

%P4PORT%

The host port to which the client connects. If the client connects to the server through an intermediary, this will hold the port number of the intermediary. If there’s no intermediary, this will hold the same value as the %serverAddress% variable.

auth-check-sso (client-side script only)

%quote%

A double quote character.

all

%rev%

Revision of archive file

archive

%serverAddress%

The IP address and port of the Perforce server, passable only in the context of a client-side script specified by P4LOGINSSO.

auth-check-sso (client-side script only)

%serverhost%

Hostname of the Perforce server.

all

%serverid%

The value of the Perforce server’s server.id. See p4 serverid in the P4 Command Reference for details.

all

%serverip%

The IP address of the server.

all

%servername%

The value of the Perforce server’s P4NAME.

all

%serverport%

The transport, IP address and port of the Perforce server, in the format prefix:ip_address:port.

prefix can be one of ssl, tcp6, or ssl6. This means that the command p4 -p %serverport% can be used to connect to the server no matter which type of connection the server uses.

all

%serverroot%

The P4ROOT directory of the Perforce server.

all

%serverservices%

A string specifying the role of the server. One of the following:

  • standard
  • replica
  • broker
  • proxy
  • commit-server
  • edge-server
  • forwarding-replica
  • build-server
  • P4AUTH
  • P4CHANGE

all except archive

%serverVersion%

Version string for the server that terminated if the command exited early. Reason for termination is given in %termType%.

all except archive

%specdef%

Expanded to the spec string of the form in question.

form

%submitserverid%

If this is not a distributed installation, %submitserverid% is always empty.

In a distributed installation, for any change trigger:

  • if the submit was run on the commit server, %submitserverid% equals %serverid%.
  • if the submit was run on the edge server, %submitserverid% does not equal %serverid%. In this case, %submitserverid% holds the edge server’s server id.

If there is a forwarding replica between the commit server and the edge server, then %submitserverid% actually holds the forwarding replica’s server id.

See p4 serverid in the P4 Command Reference for details.

change-submit,
change-content,
change-commit,

Not available for push-* triggers.

%terminated%

The value of 0 indicates that the command completed. A value of 1 indicates that the command did not complete.

 

%termType%

The reason for early termination. This might be one of the following:

  • 'p4 monitor terminate'
  • client disconnect
  • maxScanRows
  • maxLockTime
  • maxResults

See also %serverVersion%.

all except archive

%triggerMeta_action%

Command to execute when trigger is fired. Last field of trigger definition. Set only when you run a script from the depot.

all except archive

%triggerMeta_depotFile%

Third field in trigger definition. Its meaning varies with the trigger type. For a change-submit trigger, it is the path for which the trigger is expected to match. For a form-out trigger, it might be the form type to which the trigger is expected to apply. See the description of the trigger types for more information on the meaning of this field.

all except archive

%triggerMeta_name%

Trigger name: first field from trigger definition. Set only when you run a script from the depot.

all except archive

%triggerMeta_trigger%

Trigger type: second field in trigger definition. Set only when you run a script from the depot.

all except archive

%user%

Perforce username of the triggering user.

all