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:
-
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.
-
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 |
---|---|
|
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
|
|
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 Please consult the section describing the category of interest to determine which types relate to that trigger. |
|
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. |
|
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 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 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
|
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 inC:\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
. - If you are using LDAP authentication, or authentication
triggers, you must authenticate using tickets (as with security
level 3). It then follows that you cannot store a plaintext
password value in
P4PASSWD
: you should setP4PASSWD
to a ticket value obtained fromp4 login -p
instead. -
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 thep4d
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 scripttrigger.pl
, andtrigger.pl
calls thep4 changes
command, the script can run the command asp4 -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 aP4USER
value; on Windows, you must therefore pass a user value to each command as described above.)
- Pass a
- 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. TheSystem
account may have different environmental configurations (including not just Perforce-related variables, butPATH
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 theTMP
andTEMP
system variables in Windows, not the user’sTEMP
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.
- Communication between a trigger and the server describes how to select the method used for communication and how to parse dictionary input.
- Storing triggers in the depot describes how to format depot paths if you want to run a trigger from the depot.
- Using multiple triggers explains how Perforce interprets and processes the trigger table when it includes multiple trigger definitions.
- Writing triggers to support multiple Perforce servers describes how you can write a trigger so that it is portable across Perforce servers.
- Triggers and distributed architecture explains the issues you must address when locating triggers on replicas.
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 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.)
Multiple files can be loaded from the depot. In the next trigger definition, two depot paths are provided. Multiple depot paths may be used to load multiple files out of the depot when the trigger executes. For example, the triggers script might require a configuration file that is stored next to the script in the depot:
lo form-out label "perl %//admin/validate.pl% %//admin/validate.conf%"
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:
change-submit
(fired on changelist submission, before file transmission)change-content
triggers (after changelist submission and file transmission)change-commit
triggers (on any automatic changelist renumbering by the server)
Similarly, form triggers of different types are fired in the following order:
form-out
(form generation)form-in
(changed form is transmitted to the server)form-save
(validated form is ready for storage in the Perforce database)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 |
---|---|
|
When using
For example, to submit a file p4 diff2 //depot/foo@1000 file@=1001 p4 diff2 //depot/foo#25 file@=1001 |
|
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 |
|
The trigger for the Perforce server to run when a user submits a
changelist that contains any file patterns specified by 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 For |
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 -e "$changelist" //... | while read -r 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" ] && \ [ "$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
The three phases of submits and pushes include the following:
-
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.
-
Files are sent but changes are not yet committed.
Following this phase, a
content-submit
orpush-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. -
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
, orp4 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 ofp4 push
,p4 fetch
, orp4 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 |
---|---|
|
|
|
A file pattern in depot syntax. When a user uses the |
|
The trigger for the Perforce server to run when a user invokes the
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 For |
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 |
---|---|
|
The command to execute is specified in the |
|
The`pre-user-command`
value specifies the command before which the trigger should execute.
The 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 For additional information about path values with You cannot create a |
|
The trigger for the Perforce server to run when the condition implied
by Specify the command in a way that allows the Perforce server to locate
and run the command. The 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 |
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 |
---|---|
|
Run this trigger on the remote server before it receives pushed content. |
|
Run this trigger on the remote server after it receives pushed content. Two special variables are available for use with post remote push triggers:
|
|
Run this trigger on the remote server before it responds to a fetch request. |
|
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 |
---|---|
|
|
|
The server on which the triggers should be run. One of the following:
|
|
The trigger for the Perforce server to run when the server matching
Journal-rotate triggers can process two 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 |
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 |
---|---|
|
|
|
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. |
|
The trigger for the Perforce server to run when a matching 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 |
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 |
---|---|
|
|
|
Use |
|
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 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 For |
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 |
---|---|
|
|
|
The name of the type of form, ( |
|
The trigger for the Perforce server to run when the type of form
specified in the Specify the command in a way that allows the Perforce server account to
locate and run the command. The 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 For |
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 |
---|---|
|
|
|
Use |
|
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 Specify the command in a way that allows the Perforce server account to
locate and run the command. The 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 For For For |
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. TheP4LOGINSSO
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:
- A user invokes
p4 passwd
. - The Perforce server prompts the user to enter his or her old password.
- The Perforce server fires an
auth-check
trigger to validate the old password against the external authentication service. - The script associated with the
auth-check
trigger runs. If theauth-check
trigger fails, the process ends immediately: the user is not prompted for a new password, and theauth-set
trigger never fires. - If the
auth-check
trigger succeeds, the server prompts the user for a new password. -
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 |
---|---|
|
The script is run once per file requested. For |
|
A file pattern to match the name of the file being accessed in the archive. |
|
The trigger for the Perforce server to run when a file matching
Specify the command in a way that allows the Perforce server account to
locate and run the command. The 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 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 |
---|---|---|
|
Either null or a string reflecting an action taken to a changelist or job. For example," |
|
|
Command argument count. |
all except archive |
|
Command argument string. |
all except archive |
|
Command argument string that contains the command arguments as a percent-encoded comma-separated list. |
all except archive |
|
The number of the changelist being submitted. The abbreviated form
A A |
|
|
The root path of files submitted. |
|
|
Triggering user’s client workspace name. |
all |
|
Client’s current working directory. |
all except archive |
|
Hostname of the user’s workstation (even if connected through a proxy, broker, replica, or an edge server.) |
all |
|
The IP address of the user’s workstation (even if connected through a proxy, broker, replica, or an edge server.) |
all |
|
The name of the user’s client application. For example, P4V, P4Win, etc. |
all |
|
The version of the user’s client application. |
all |
|
Command name. |
all except archive |
|
Path of archive file based on depot’s |
|
|
First new changelist number. See Additional triggers for push and fetch commands for more information. |
|
|
Path to temporary form specification file. To modify the form from an
|
|
|
Name of form (for instance, a branch name or a changelist number). |
|
|
Type of form (for instance, |
|
|
List of groups to which the user belongs, space-separated. |
all except archive |
|
A broker or proxy is present. |
all except archive |
|
A string of job numbers, expanded to one argument for each job number
specified on a |
|
|
Last new changelist number. See Additional triggers for push and fetch commands for more information. |
|
|
One of |
all except archive |
|
Error number and text. |
all except archive |
|
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 |
|
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 |
|
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 |
|
If a changelist is renumbered on submit, this variable contains the old changelist number. |
|
|
Operation: |
|
|
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, |
all |
|
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, |
all |
|
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 |
|
|
A double quote character. |
all |
|
Revision of archive file |
|
|
The IP address and port of the Perforce server, passable only in the
context of a client-side script specified by |
|
|
Hostname of the Perforce server. |
all |
|
The value of the Perforce server’s |
all |
|
The IP address of the server. |
all |
|
The value of the Perforce server’s |
all |
|
The transport, IP address and port of the Perforce server, in the
format
|
all |
|
The |
all |
|
A string specifying the role of the server. One of the following:
|
all except archive |
|
Version string for the server that terminated if the command exited
early. Reason for termination is given in |
all except archive |
|
Expanded to the spec string of the form in question. |
form |
|
If this is not a distributed installation, In a distributed installation, for any change trigger:
If there is a forwarding replica between
the commit server and the edge server, then See |
Not available for push-* triggers. |
|
The value of 0 indicates that the command completed. A value of 1 indicates that the command did not complete. |
|
|
The reason for early termination. This might be one of the following:
See also |
all except archive |
|
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 |
|
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 |
|
Trigger name: first field from trigger definition. Set only when you run a script from the depot. |
all except archive |
|
Trigger type: second field in trigger definition. Set only when you run a script from the depot. |
all except archive |
|
Perforce username of the triggering user. |
all |