#---------------------------------------------------------------------------- # shelve changelist in Perforce # # $Id: $ # # Copyright 2005, Chris Stoy. All Rights Reserved. # # License: # This file and any derivatives or translations of it may be freely # copied and redistributed so long as: # 1) This license and copyright notice are not changed. # 2) Any fixes or enhancements are reported back to either the # author (cstoy@nc.rr.com). # and any of: # a) The source is redistributed with it. # b) The source is compiled unmodified, and instructions for finding # the original source are included. # c) The source is made available by some other means. # #---------------------------------------------------------------------------- import sys import os import stat import time import shutil import P4 import getopt VERSION = "\np4_shelve modified by Andrew Chan based on shawn_hladky version (//guest/shawn_hladky/p4_shelve/...) 2009/09/14\n" #---------------------------------------------------------------------------- def p4_shelve(p4c, changeListID, shelfRoot, verbose=False ): # get the data/time string we need for the branch ltime = time.localtime() datetime = time.strftime( "%Y%m%d_%H%M", ltime ) p4User = p4c.user.replace(" ", "_") #---------------------------------------------------------------------------- # get client spec info client = p4c.fetch_client() clientName = client["Client"] clientRootDir = client["Root"] clientView = client["View"] #---------------------------------------------------------------------------- # get changelist we wish to shelve changelist = p4c.fetch_change(changeListID) try: origFiles = changelist["Files"] except KeyError: print "No files to shelve." return #---------------------------------------------------------------------------- # save the current changelist as a numbered changelist origDescription = changelist["Description"] changelist["Description"] = "Pre-shelving changelist" result = p4c.save_change(changelist)[0] origChangeListNumber = result.split()[1] if verbose: print "Shelving changelist %s" % origChangeListNumber #---------------------------------------------------------------------------- # get depot path for the shelve shelfDepotPath = "%s/%s/shelves/%s" % ( shelfRoot, p4User, datetime ) # build the mapping of files from original depot to branch location branchView = [] branchFiles = [] for origFile in origFiles: # the shelve path will be the shelfRoot plus the full path of the original # minus the superfulus initial '/' filePath = origFile[2:] if filePath.find(" ") == -1: #new. Check if the filePath contains space branchedFile = "%s/%s" % ( shelfDepotPath, filePath ) else: branchedFile = '\"%s/%s\"' % ( shelfDepotPath, filePath ) origFile = '\"' + origFile + '\"' branchFiles.append(branchedFile) mapping = "%s %s" % ( origFile, branchedFile ) branchView.append(mapping) #---------------------------------------------------------------------------- # Create branch spec for all files in changelist to shelve depot branchSpecName = "shelve_%s_%s" % ( p4User, datetime ) if verbose: print "Creating shelf branch spec '%s'" % branchSpecName branchSpec = p4c.fetch_branch(branchSpecName) branchSpec["Description"] = "Shelved changelist for user %s on %s\n\n%s" % (p4c.user, time.asctime(ltime), origDescription) branchSpec["View"] = branchView p4c.save_branch(branchSpec) #---------------------------------------------------------------------------- # Integrate using this branch spec. if verbose: print "Integrating using branch spec %s and client %s" % (branchSpecName, "//...@%s" % clientName ) p4c.run_integrate( "-b", branchSpecName, "//...@%s" % clientName ) #---------------------------------------------------------------------------- # submit the branched files (needed to maintain correct integration history) branchChangeList = p4c.fetch_change() # a list of file that will be submitted from the default changelist. fileToSubmit = [] # check which file in default changelist should be submitted. # Reason: in case of shelving a numbered changelist while there is an openned # file in default changelist, we only want to submit the change related to # shelving numbered changelist, not the openned file in default changelist. try: for file in branchChangeList["Files"]: if shelfDepotPath in file: fileToSubmit.append(file) if fileToSubmit == []: print "No file to shelve in default changelist." else: branchChangeList["Files"] = fileToSubmit branchChangeList["Description"] = "Shelved changelist for user %s on %s" % (p4c.user, time.asctime(ltime)) result = p4c.save_submit( branchChangeList ) except KeyError: # there are no files in the changelist, so just pass pass #---------------------------------------------------------------------------- # copy original file contents over branched files for mapping in branchView: # check if there is space within the file path if mapping.count(" ") == 1: files = mapping.split(" ") else: files = mapping.split('\" \"') files[0] = files[0][1:] # removing double quote because p4c.run_fstat can't take double quote files[1] = files[1][:-1] origResult = p4c.run_fstat(files[0]) origLocalFile = origResult[0]["clientFile"] action = origResult[0]["action"] result = p4c.run_where(files[1])[0] branchLocalFile = result["path"].replace("\\","/") # if file is opened for integrate/branch, we're in a real quandry. It would be some serious work # to try and re-open it for branch in all the correct-ness of the original intent. # However, the most likely use-case for this is shelve, un-shelve, and try to re-shelve the same set of files # In this case it's as simple as treating them as add/edit respectively if action in ("add", "branch"): if verbose: print "Adding %s" % ( branchLocalFile ) path = branchLocalFile.rsplit("/", 1)[0]; try: if verbose: print "Making dir: %s" % path os.makedirs( path ) except OSError: # ignore directory exists error pass shutil.copy2( origLocalFile, branchLocalFile ) # change the new file to readonly so that when the user unshelve the change, # the user won't get "can't clobber writable file" error. if action == "add": if verbose: print "Setting " + origLocalFile + " to read only" os.chmod(origLocalFile, 0555) p4c.run_add( branchLocalFile ) elif action in ("edit", "integrate"): if verbose: print "Copying %s to %s" % ( origLocalFile, branchLocalFile ) p4c.run_edit( branchLocalFile ) shutil.copy2( origLocalFile, branchLocalFile ) elif action == "delete": if verbose: print "Deleting %s" % ( branchLocalFile ) p4c.run_delete( branchLocalFile ) # submit branched files with changes to Perforce branchChangeList = p4c.fetch_change() # a list of file that will be submitted from the default changelist. fileToSubmit = [] # check which file in default changelist should be submitted. # Reason: in case of shelving a numbered changelist while there is an openned # file in default changelist, we only want to submit the change related to # shelving numbered changelist, not the openned file in default changelist. for file in branchChangeList["Files"]: if shelfDepotPath in file: fileToSubmit.append(file) if fileToSubmit == []: print "No file to shelve." return else: branchChangeList["Files"] = fileToSubmit branchChangeList["Description"] = "Changelist shelved for user %s on %s" % (p4c.user, time.asctime(ltime)) result = p4c.save_submit( branchChangeList ) #---------------------------------------------------------------------------- # revert files in main branch and delete the pending changelist for mapping in branchView: # check if there is space within the file path if mapping.count(" ") == 1: files = mapping.split(" ") else: files = mapping.split('\" \"') files[0] = files[0][1:] files[1] = files[1][:-1] result = p4c.run_revert(files[0])[0] p4c.run_change( "-d", origChangeListNumber ) # all done print "Changelist Shelved using branch %s" % branchSpecName #---------------------------------------------------------------------------- # prints the usage message. If short is true, prints abreviated # help message. #---------------------------------------------------------------------------- def usage(short=True): print "Usage: p4_shelve [options]\n" if short : print "Try 'p4_shelve --help' for more information." else: print "Shelves a changelist for later editing." print "" print "Shelving a changelist will create a branch of the files in the" print "current changelist and move all the changes to that branch. It" print "will then revert the files in the current changelist while preserving" print "any changes in the branch." print "" print "Use 'p4_unshelve' to restore a shelved changelist." print "" print "Options:" print " -h, --help Prints this help message" print " -v, --verbose Prints verbose messages while shelving" print " -p, --port Perforce Port depot is on" print " -c, --client Perforce Client that maps the depot files" print " -u, --user Perforce User that owns the client" print " -P, --password Perforce Password for the user" print " -e, --changelist Perforce Change List ID to shelve" print " -s, --shelfroot Depot path where the shelved files should go." print " -r, --keepopen Files in pending changelist will not be reverted shelved." print "" print "Note: Uses the default P4 settings for Perforce options not supplied." print "\nReport bugs to Chris Stoy (cstoy@nc.rr.com)" print VERSION #----------------------------------------------------------- # Main entry point #----------------------------------------------------------- if __name__ == "__main__": options = [] input = [] for x in sys.argv[1:]: if x.startswith("-"): options.append(x) else: input.append(x) try: opts, args = getopt.getopt( options, "p:c:u:P:e:s:hv", ["port=", "client=", "user=", "password=", "changelist=", "shelfroot=", "help", "verbose"] ) except getopt.GetoptError, intr: print "Argument error: ", intr usage() sys.exit(2) shelfRoot = "//depot/shelves" # depot path to where the shelves are stored for each user changeListID = "" # changelist number for changelist we wish to shelve, or "" to shelve default verbose = False p4c = P4.P4() #p4c.parse_forms() #Commented out for new p4python API # we got our options. for o, a in opts: if o in ("-h", "--help"): usage(False) sys.exit(0) if o in ("-p", "--port"): p4c.port = a if o in ("-c", "--client"): p4c.client = a if o in ("-u", "--user"): p4c.user = a if o in ("-P", "--password"): p4c.password = a if o in ("-e", "--changelist"): if a != "default": changeListID = a if o in ("-v", "--verbose"): verbose = True if o in ("-s", "--shelfroot"): shelfRoot = a if len(input) > 0: print "Incorrect Usage." print input usage() sys.exit(2) # connect to P4 p4c.connect() p4c.exception_level = 1 if verbose: print "Settings:" print "\tP4 Port = %s" % p4c.port print "\tP4 Client = %s" % p4c.client print "\tP4 User = %s" % p4c.user print "\tChangeListID = %s" % changeListID print "\tShelf Root = %s" % shelfRoot # do the shelving try: p4_shelve(p4c, changeListID, shelfRoot, verbose) except p4.P4Error: print "P4 Error:" for e in p4c.errors: print "\t%s" % e