February 12, 2009

Some simple command line and P4Python tricks


In one of my roles as Perforce Support engineer, I encounter many little problems customer face when using Perforce. It is a very rewarding challenge to solve these problems with a short and elegant solution.

Ideally, these little problems should be solvable with a single P4 command line statement, and I would like to present some of these statement I came up with in this blog. Occasionally, a problem cannot be solved with a simple single statement, which is when I whip one of my other hats out of the drawer (the developer's hat - complete with bells and whistles) and turn to one of my favourite tools: P4Python.

So, first a couple of simple problems, easily solved using the P4 command line.

Create an empty depot

This one is so simple that it is embarrassing to mention, and will only offer something for the Perforce novice. The problem I faced was a porting exercise from PVCS, with the additional challenge that all data was supposed to go into a separate depot different from the standard //depot. How do you create a new depot without Perforce opening an editor with the depot spec? Easy:

p4 depot -o newdepot | p4 depot -i

There! I said this was embarrassingly easy, I can hear my colleagues laughing already for even mentioning it. OK, for a greater challenge, one that is not quite as obvious. Create a new workspace from a template Oh, please! That one is trivial as well. We all know the -t option to "p4 client":

p4 client -t template-ws new-ws

Yep. Leaves the problem that this workspace has the current directory as the root directory, something that can be inconvenient, especially when many workspaces are created in a script. How do you set the working directory for a Perforce command? Turns out there is a seldom used option for P4: "-d directory". This option defines the working directory for each command. Armed with that knowledge, the problem is easily solved again:

p4 -d new-ws-root client -t template-ws new-ws

Renaming a user

For this problem, I'd like to use P4Python. I am not going to present the whole script, just the general idea. We start with two names, the old name and the new name. We also assume the script is executed by a user who has admin rights in the Perforce server via the protection table. Here is a rough draft of that script:

import P4

p4 = P4.P4()

userSpec = p4.fetch_user(oldName)
userSpec._user = newName
userSpec._fullname = newFullName
p4.save_user(userSpec, "-f")

# now delete the old user
p4.delete_user("-f", oldName)

The clever bit in this (except for using P4Python, of course!) is the way "-f" is parsed into "save_user" and "delete_user". Bear in mind that P4.save_user() is implemented in P4Python as

def __getattr__( self, name):
# ...
> elif name.startswith("save_"):
>   cmd = name[len("save_"):]
>   return lambda *args: self.__save(cmd, *args)

def __save(self, cmd, *args):
>   self.input = args[0]
>   return self.run(cmd, "-i", args[1:])

The argument cmd here is "user". The method P4.delete_user(), on the other hand, is implemented this way:

elif name.startswith("delete_"):
>  cmd = name[len("delete_"):]
>  return lambda *args: self.run(cmd, "-d", *args)

That is why the "-f" option appears on different places in the different P4 commands.