July 27, 2012

Customising Perforce using the P4Broker REWRITE Command

Integration
Traceability

Perforce officially introduced P4Broker in 2011, although the tool had been around for much longer.

P4Broker for the user works in a similar way to the P4Proxy in that the broker runs on a port and looks like a Perforce Server to the client. Requests to the P4Broker are forwarded to the target Perforce Server depending on the rules set up in the broker configuration file. Officially P4Broker supports the rules REJECT, PASS, REDIRECT, RESPOND and FILTER actions. Please see the release notes here for details on these commands.

There is an additional action that is currently undocumented (read unsupported) that offers a whole range of interesting applications: REWRITE.

REWRITE instructs P4Broker to send a different command or commands to the Perforce Server. REWRITE is only available if the action is FILTER, that is, the only way to REWRITE a command is to call a filter script that set the action REWRITE and returns the list of new commands and their options.

For example, I sometimes have the problem that I type in a Perforce command, say, 'p4 integrate' and then realize that I forgot the exact options available. The options are listed in the help output, so I am forced to retype the command as 'p4 help integrate'. Wouldn't it be handy to simply type 'p4 integrate -h'?

To do this with the P4Broker, you have to create a config file first with the command 'p4broker -C > broker.cfg' and then edit the content of this file. At the top are the general options such as P4PORT and P4TARGET. At the bottom of the config file are the list of commands to which the broker should respond; all other commands are passed through without modification.

In my case, I want to respond to all commands as long as the option '-h' is present. I write:

command: *
{
   flags       = -h;
   action      = filter;
   execute     = "python /Perforce/p4broker/rewrite_help.py";
}

The script I invoke is quite simple:

from __future__ import print_function
import sys

args = {}
commands = sys.stdin.readlines()

for cmd in commands:
        (k,v) = cmd.split(':',1)
        args[k] = v

print("action:REWRITE")
print("command: help")
print("arg: %s" % args['command'])

sys.exit(0)

The script receives a set of key/value pairs separated by a colon. On of these keys is 'command', which is the only one I am interested in here. I simply return the action 'REWRITE', followed by the command 'help' with the original command as an argument. As a result, each time I use the option '-h' on any command, I get the help text instead.

There is the obvious catch: what about commands that use the option '-h'? These have to be excluded explicitly before the catch-all 'command: *'. For example, my config file contains these additional lines:

command: filelog
{
    flags       = -h;
    action      = pass;
}

command: fstat
{
    flags       = -h;
    action      = pass;
}

This has the disadvantage that I cannot use my '-h' hack for filelog and fstat, but then this is only a proof-of-concept and not production code.

Here be Dragons!

The REWRITE option has many interesting applications, but abuse of this action can lead to severe problems, so broker config files and scripts should be properly protected and well tested. With great power comes great responsibility, as they say.

Happy hacking!

Sven Erik