Know when to apply strategic incompetence.
-- Jim Tilander

Introduction

One of the annoying things about perforce is the need to be connected to the perforce server at all times. Manipulating files underneath perforce is a recipe for a world of hurt and pain. But there are times when you want to do just this, say you're on a trip with your laptop and of course there's not WiFi hotspot where you are just now, but you still want to code something and later on check it in. In Subversion this would have been a breeze. In perforce it's a little bit harder and has its limitations. More on this shortly.

Other scenarios

Offline work is one common scenario for this functionality, synonymous with the fact that network connectivity may go down or the server itself. You might not be able to check code in because of locking policies. Another common thing is that you get a new drop of a third party library. The library consists of a bunch of files scattered all across the file system and the layout changes from time to time. You want to check everything in one change list, since checking in the incremental changes will leave the build in a broken state. All these scenarios can benefit from post offline sync to perforce.

Steps involved

Starting your work, you should start with simply making the subdir you want to work in writable. Simply remove the read only attribute and go at it, delete files, add new files or edit existing files. After you're done and you've yet again connectivity with the perforce server, run the p4offlinesync.py script in the root directory of your changes. This will give an output with something like this:

 
delete c:\work\experiments\happygames\Shared\Data\Icon.png
edit c:\work\experiments\happygames\Shared\Core\PreCompile.h
add c:\work\experiments\happygames\foobar\HelloBar
add c:\work\experiments\happygames\Shared\Data\Thumbs.db
  

The script

The script has one flag it recognizes, -n. This has the same meaning as for make, basically don't actually perform the actions requested but just print them on the screen so that I might review them. It's a good idea to run this before any operation if you're unsure what's going to happen.

The script uses the cool -G option (G for Guido?) that command line client for perforce provides that turns the output into a marshaled python dictionary. So no more complex regular expressions for parsing the text output.

 
#!/usr/bin/python
#
# Simple python script that tries to sync up with a repository 
# after an offline session with perforce. 
# Read more about this on Perforce's technote:
#
# http://www.perforce.com/perforce/technotes/note002.html
#
#
# This script was downloaded from http://www.tilander.org/aurora
#
# (c) 2006 Jim Tilander
import sys, os, marshal

# Assume that we're on win32 or a UNIX compatible system.
if "win32" == sys.platform:
  FINDCOMMAND = "dir /b /s /a-d"
else:
  FINDCOMMAND = "find . -type f -print"

def doPerforceCommand( command ):
  """ Returns the error code and entries as a tuple."""
  stream = os.popen( command, 'rb' )
  entries = []
  try:
    while True:
      entry = marshal.load(stream)
      entries.append(entry)
  except EOFError:
    pass
  
  code = stream.close()
  return code, entries

def processOutput(results, showDryRun):
  """ Ignores the info codes and prints only the stat actions."""
  for result in results:
    code = result['code']
    if 'info' == code:
      continue
    
    if 'stat' == code:
      would = ""
      if showDryRun:
        would = "Would "
      print '%s%s %s' % (would,result['action'], result['clientFile'])
      continue
    
    print "Unknown dict entry: %s" % str(result)

def main( argv ):
  """ 
    Main function, parses the already stripped argv. 
    Will return a positive number upon failure, zero upon success.
    Hardcoded DOS style find command.
  """
  dryRunFlag = ''
  if len(argv) > 0 and "-n" == argv[0]:
    dryRunFlag = '-n'
  
  commands = [ "p4 diff -sd ... | p4 -G -x - delete %s",
         "p4 diff -se ... | p4 -G -x - edit %s",
         FINDCOMMAND + " | p4 -G -x - add %s" ]
  
  for i, command in enumerate(commands):
    code, result = doPerforceCommand( command % dryRunFlag)
    processOutput(result, len(dryRunFlag) > 0)
    if code:
      print 'Failed to run command "%s"' % (command%dryRunFlag)
      return i

  return 0
  
if __name__ == '__main__':
  sys.exit( main(sys.argv[1:]) )
  
Listing 1: The p4offlinesync.py script.

In closing

So that's really all for this time. Perforce is great, but lacks that good offline capabilities. The downside with this script and with perforce in general, it's very hard to get renames to work properly in offline mode. Maybe next time.

Resources






2008-05-26: Update

I've started a new open source project for all the perforce stuff I've written. Ironically it is hosted on a subversion server. You can see it over here: http://code.google.com/p/p4scripts.

Resources

Comments