diff options
| author | FluorescentCIAAfricanAmerican <[email protected]> | 2020-04-22 12:56:21 -0400 |
|---|---|---|
| committer | FluorescentCIAAfricanAmerican <[email protected]> | 2020-04-22 12:56:21 -0400 |
| commit | 3bf9df6b2785fa6d951086978a3e66f49427166a (patch) | |
| tree | 2c0f1f0c63c4832882bc93814ebd2c2b1c6224e5 /devtools/vcodepickle.py | |
| download | archived-source-engine-2018-hl2-src-master.tar.xz archived-source-engine-2018-hl2-src-master.zip | |
Diffstat (limited to 'devtools/vcodepickle.py')
| -rw-r--r-- | devtools/vcodepickle.py | 1034 |
1 files changed, 1034 insertions, 0 deletions
diff --git a/devtools/vcodepickle.py b/devtools/vcodepickle.py new file mode 100644 index 0000000..3de469b --- /dev/null +++ b/devtools/vcodepickle.py @@ -0,0 +1,1034 @@ +#!/usr/bin/env python + +import p4helpers +import sys +import os +import shutil +import pickle +import zipfile +import zlib +import datetime +import subprocess +import getopt +import glob +import struct +try: + import wx +except: + pass + +# +# Set program defaults. +# +g_ChangelistInfoFilename = '__CHANGELISTINFO__' + +RUNMODE_BACKUP = 0 +RUNMODE_RESTORE = 1 +RUNMODE_EMPTY = 2 +RUNMODE_NONE = 3 + +g_RunMode = RUNMODE_NONE +g_ZipFilename = '' +g_bAutoRevert = False +g_bBinsOnly = False +g_Changelist = [] +g_bShowUI = False +g_bPickleAllChanges = False +g_bVerbose = True +g_IncludeGlobPatterns = [] +g_ExcludeGlobPatterns = [] +g_ChangelistComment = '' +g_DefaultFilename = None + +g_bCreateDiff = False +g_DiffDir = '' + +g_SettingsFilename = 'vcodepickle.cfg' +g_Settings = {} + +g_UISaveDirKey = 'uisavedir' + +# These are used with -changetree if the user wants to restore the changelist to +# a different tree than it originally came from. +g_FromTree = None +g_ToTree = None + +g_QueuedWarnings = [] + + +# +# Pickle data for various file versions supported by vcodepickle. +# +class CV2Data: + changelistComment = None + +class CV3Data: + relativeToDepotFileMap = None + + +def ShowUsage(): + print "vcodepickle.py [options] <--backup [options] or --restore or --diff or --empty> [--file filename] [--changelist <changelist> | --bins]" + print "" + print "-q/--quiet sshhhhhh." + print "-b/--backup pickle local changes to file" + print "-r/--restore apply pickled edits to client" + print "--empty create a valid, but empty, pickle, used by buildbot" + print "-d/--diff produce a diff of the changes in a vcodepickled zipfile" + print "-f/--file filename pickle file - optional for backup, required for restore" + print "-c/--changelist <#|all> in backup mode, pickle specified changelist(s)" + print " in restore mode, reapply changes to the specified changelist" + print " defaults to 'default'" + print " --prompt if using --backup, this asks where to backup (and makes suggestions)" + print " if using --restore, this brings up a file open dialog" + print "" + print "backup options:" + print "" + print " --bins pickle binaries from the auto-checkout changelist" + print " --revert in backup mode, revert open files after pickling" + print " -e/--exclude <globpat> a glob pattern of files to exclude from pickling" + print " -i/--include <globpat> a glob pattern of files to include in the code pickle" + print "" + print "restore options:" + print "" + print " --changetree <src> <dst> allows you to restore to a Perforce tree different from the original one" + print "" + print "diff options:" + print "" + print " --diffdir <directory> specify where the diff files will go" + print "" + print 'ex: vcodepickle.py --backup --changelist all --exclude "bin/*"' + print ' creates a zip file of all files not matching "bin/*" in all changelists' + print "" + print "ex: vcodepickle.py --restore --file vcodepickle_2009_01_20.zip -c 342421" + print " restore from the specified file into changelist 342421" + print "" + print "ex: vcodepickle.py --diff --file VCP_2009-03-26__17h-01m-25s.zip" + print " creates a diff of the pickled changes in the specified file" + sys.exit( 1 ) + + +def DefaultPickleDirName( username ): + return '\\\\fileserver\\user\\' + username + '\\vcodepickles' + + +def DefaultPickleDiffName( username ): + return '\\\\fileserver\\user\\' + username + '\\vcodepicklediff' + +def CreateChangelist( comment ): + po = subprocess.Popen( 'p4 change -i', shell=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE ) + po.stdin.write( 'Change: new\n\nDescription:\n\t' + comment.replace('\n','\n\t') + '\n\n' ) + po.stdin.close() + theOutput = po.stdout.readline() + ret = po.wait() + if ret == 0: + return int( theOutput.split( ' ' )[1] ) + else: + print "Creating a changelist failed." + print theOutput + sys.exit( 1 ) + +def GetChangelistComment( nChangelist ): + po = subprocess.Popen( 'p4 change -o ' + nChangelist, shell=True, stdout=subprocess.PIPE ) + lines = po.stdout.readlines() + ret = po.wait() + if ret != 0: + print "Couldn't get changelist description for change " + nChangelist + sys.exit( 1 ) + + comment = '' + bFound = False + for line in lines: + if bFound == True: + if line[0] == '\t': + comment += line[1:] + else: + break + else: + if line[:12] == 'Description:': + bFound = True + + return comment + + +def chomp( str ): + while 1: + str2 = str.lstrip( '\n' ).lstrip( '\r' ).rstrip( '\n' ).rstrip( '\r' ) + if len( str2 ) == len( str ): + return str + else: + str = str2 + + +def AddQueuedWarning( s ): + global g_QueuedWarnings + g_QueuedWarnings.append( s ) + +def PrintQueuedWarnings(): + global g_QueuedWarnings + if len( g_QueuedWarnings ) > 0: + print "\n\n===== WARNINGS =====\n" + nWarning = 1 + for warning in g_QueuedWarnings: + print "%d:\n%s\n" % (nWarning, warning) + nWarning += 1 + + +def DoesFileExistInPerforce( filename ): + x = p4helpers.ReadPerforceOutput( 'p4 -G fstat ' + filename ) + return (x[1][0]['code'] != 'error') + +# +# The settings file is just a bunch of key/value pairs on different lines separated by = +# +def LoadSettingsFile( filename ): + ret = {} + try: + f = open( filename, 'rt' ) + except IOError: + return {} + + for line in f.readlines(): + line = chomp( line ) + parts = line.split( '=' ) + if len( parts ) > 0: + ret[parts[0]] = parts[1] + f.close() + return ret; + +def SaveSettingsFile( filename, thedict ): + f = open( filename, 'wt' ) + for (k,v) in thedict.items(): + f.write( '%s=%s\n' % (k, v) ) + f.close() + + +def SetZipFilename( filename ): + global g_ZipFilename + g_ZipFilename = filename + if g_ZipFilename[-4:].lower() != '.zip': + g_ZipFilename += '.zip' + + +def GetReadableChangelistComment( str ): + alphabet = 'abcdefghijklmnopqrstuvwxyz' + + # Strip out all lists of repeating nonalphabetic characters + tempstr = '' + prevchar = '' + for i in range(0,len(str)): + testchar = str[i:i+1] + if testchar != prevchar or alphabet.find(testchar) != -1: + tempstr += testchar + prevchar = testchar + + str = tempstr + str = str[:100] + + outstr = '' + validchars = alphabet + alphabet.upper() + '0123456789_' + prevchar = '' + for i in range(0,len(str)): + testchar = str[i:i+1] + if validchars.find( testchar ) == -1: + if prevchar != '-': + outstr += '-' + else: + outstr += testchar + prevchar = outstr[-1:] + + return outstr + + +def ShowUIForFilename(): + global g_Settings + global g_SettingsFilename + global g_ZipFilename + global g_DefaultFilename + + # + # Get the directory to save to. + # + username = os.environ['username'] + defaultdirname = DefaultPickleDirName(username) + + if not g_Settings.has_key( g_UISaveDirKey ): + g_Settings[g_UISaveDirKey] = defaultdirname + + if g_Settings[g_UISaveDirKey] == 'default': + theinput = raw_input( '\nDirectory\n=========\n(Leave blank for "%s")\n> ' % ( defaultdirname ) ) + else: + theinput = raw_input( '\nDirectory\n=========\n(Leave blank for "%s")\n(Type "default" for "%s"\n--> ' % ( g_Settings[g_UISaveDirKey], defaultdirname ) ) + + if theinput == '': + theDirName = g_Settings[g_UISaveDirKey] + else: + g_Settings[g_UISaveDirKey] = theinput + theDirName = theinput + + if theDirName.lower() == 'default': + theDirName = defaultdirname + + # + # Make sure it exists.. create it if necessary. + # + if os.access( theDirName, os.F_OK ) != True: + theinput = raw_input( "Directory %s doesn't exist. Create? (Y/n) " % theDirName ) + if theinput == '' or theinput.lower() == 'y': + os.makedirs( theDirName ) + else: + print "Exiting." + sys.exit( 1 ) + + # + # Get the filename to save to. + # + theinput = raw_input( '\nFilename\n========\n(Leave blank for "%s")\n> ' % ( g_DefaultFilename ) ) + + if theinput == '': + theZipFilename = g_DefaultFilename + else: + theZipFilename = theinput + + # + # Construct the actual zip filename we'll use. + # + SetZipFilename( os.path.join( theDirName, theZipFilename ) ) + + + # + # Check if it already exists. + # + if os.access( g_ZipFilename, os.F_OK ) == True: + theinput = raw_input( "%s already exists. Overwrite? (y/N)" % g_ZipFilename ) + if theinput == '' or theinput.lower() == 'n': + print "Exiting." + sys.exit(1) + + # + # Save the settings back out. + # + SaveSettingsFile( g_SettingsFilename, g_Settings ) + + + +class VCodePickle: + theZipFile = None + filesDeleted = {} + filesAdded = {} + filesEdited = {} + changelistComment = '' + relativeToDepotFileMap = {} # Maps src\engine\cl_main.cpp to //valvegames/main/src/engine/cl_main.cpp (obviously //valvegames/main/src changes based on the root) + + def Load( self, zipFilename ): + self.theZipFile = zipfile.ZipFile( zipFilename, 'r', zipfile.ZIP_DEFLATED ) + + # Load the added and deleted lists. + changelistinfo = self.theZipFile.read( g_ChangelistInfoFilename + '.TXT' ) + (self.filesDeleted, self.filesAdded, self.filesEdited) = pickle.loads( changelistinfo ) + + self.changelistComment = 'vcodepickle: ' + os.path.basename(g_ZipFilename) + + # Load version2 data. + try: + ci2 = pickle.loads( self.theZipFile.read( g_ChangelistInfoFilename + 'v2.TXT' ) ) + self.changelistComment = ci2.changelistComment + except KeyError: + pass + + # Load version3 data + try: + ci3 = pickle.loads( self.theZipFile.read( g_ChangelistInfoFilename + 'v3.TXT' ) ) + self.relativeToDepotFileMap = ci3.relativeToDepotFileMap + except KeyError: + pass + + + +## +# Given a 'zip' instance, copy data from the 'name' to the +# 'out' stream. + +def explode( out, zip, name ): + zinfo = zip.getinfo(name) + + stringFileHeader = "PK\003\004" # magic number for file header + structFileHeader = "<4s2B4HlLL2H" # 12 items, file header record, 30 bytes + _FH_FILENAME_LENGTH = 10 + _FH_EXTRA_FIELD_LENGTH = 11 + + filepos = zip.fp.tell() + zip.fp.seek(zinfo.header_offset, 0) + + # Skip the file header: + fheader = zip.fp.read(30) + if fheader[0:4] != stringFileHeader: + raise zipfile.BadZipfile, "Bad magic number for file header" + + fheader = struct.unpack(structFileHeader, fheader) + fname = zip.fp.read(fheader[_FH_FILENAME_LENGTH]) + if fheader[_FH_EXTRA_FIELD_LENGTH]: + zip.fp.read(fheader[_FH_EXTRA_FIELD_LENGTH]) + + if fname != zinfo.orig_filename: + raise zipfile.BadZipfile, 'File name in directory "%s" and header "%s" differ.' % ( zinfo.orig_filename, fname) + + if zinfo.compress_type == zipfile.ZIP_STORED: + decoder = None + elif zinfo.compress_type == zipfile.ZIP_DEFLATED: + if not zlib: + raise RuntimeError, "De-compression requires the (missing) zlib module" + decoder = zlib.decompressobj(-zlib.MAX_WBITS) + else: + raise zipfile.BadZipfile,"Unsupported compression method %d for file %s" % (zinfo.compress_type, name) + + size = zinfo.compress_size + + while 1: + data = zip.fp.read(min(size, 8192)) + if not data: + break + size -= len(data) + if decoder: + data = decoder.decompress(data) + out.write(data) + + if decoder: + out.write(decoder.decompress('Z')) + out.write(decoder.flush()) + + zip.fp.seek(filepos, 0) + + + +def WriteFileFromZip( theZipFile, srcFilename, sDestFilename ): +# thebytes = theZipFile.read( srcFilename ) + + try: + f = open( sDestFilename, 'wb') + except: + os.makedirs( os.path.dirname( sDestFilename ) ) + f = open( sDestFilename, 'wb' ) + + explode( f, theZipFile, srcFilename ); +# f.write( thebytes ) + f.close() + + +def PerforceToZipFilename( sP4Filename ): + if sP4Filename[0] != '/' or sP4Filename[1] != '/': + print "PerforceToZipFilename: invalid filename (%s)" % (sP4Filename) + + return sP4Filename[2:] + + +def CheckFilesOpenedForEdit( thePickle, theDict ): + ret = [] + for k in theDict.keys(): + sDepotFilename = thePickle.relativeToDepotFileMap[k] + if p4helpers.GetClientFileAction( sDepotFilename ) != 'none': + ret.append( sDepotFilename ) + + return ret + + + +def HandleNewTree( thePickle ): + global g_FromTree, g_ToTree + + if g_FromTree == None: + return + + print "-changetree is translating these files:\n" + + for k in thePickle.relativeToDepotFileMap.keys(): + sFromFilename = thePickle.relativeToDepotFileMap[k] + + if sFromFilename[:len(g_FromTree)].lower() == g_FromTree.lower(): + sToFilename = g_ToTree + sFromFilename[len(g_FromTree):] + + print sFromFilename + "\n\t-> " + sToFilename + "\n" + + thePickle.relativeToDepotFileMap[k] = sToFilename + else: + print "Used -changetree [%s] [%s], but file %s does not come from the source tree." % (g_FromTree, g_ToTree, sFilename) + sys.exit( 1 ) + + # Translate the revision number to a changelist number for edited files. + newEdited = {} + for k in thePickle.filesEdited.keys(): + thePickle.filesEdited[k] = "head" + + sDepotFilename = thePickle.relativeToDepotFileMap[k] + if DoesFileExistInPerforce( sDepotFilename ): + newEdited[k] = thePickle.filesEdited[k] + else: + AddQueuedWarning( "%s was edited in the old tree, but doesn't exist in the new tree. This was treated as an add instead." % sDepotFilename ) + thePickle.filesAdded[k] = thePickle.filesEdited[k] + + thePickle.filesEdited = newEdited + + print "\n" + + +def PromptForOpenZipFilename( sClientRoot, promptDir ): + if not sys.modules.has_key( "wx" ): + print >>sys.stderr, "Couldn't load wx, no UI available." + sys.exit( 1 ) + + temp_app = wx.PySimpleApp() + dialog = wx.FileDialog ( None, style = wx.OPEN ) + dialog.SetMessage( "Select code pickle" ); + dialog.SetDirectory(promptDir) + dialog.SetWildcard( "VCodePickle Zips (*.zip)|*.zip|All Files (*.*)|*.*" ); + + if dialog.ShowModal() == wx.ID_OK: + print 'Selected: %s\n' % (dialog.GetPath()) + return dialog.GetPath() + else: + print 'Cancelled.' + return None + + +def DoRestore( sClientRoot, sZipFilename ): + global g_bBinsOnly + thePickle = VCodePickle() + thePickle.Load( sZipFilename ) + + # First make sure they don't have any files open for edit that we want to mess with. + print "Checking the code pickle...\n" + + openedForEdit = CheckFilesOpenedForEdit( thePickle, thePickle.filesAdded ) + CheckFilesOpenedForEdit( thePickle, thePickle.filesEdited ) + if len( openedForEdit ) > 0 and g_bBinsOnly==False: + print "You have one or more files open locally that are in the vcodepickle you are trying to restore:\n" + for f in openedForEdit: + print "\t" + f + print "\nPlease revert the specified file(s) and retry the vcodepickle command you were doing." + return + + changelistNum = None + if ( g_bBinsOnly == False ): + changelistNum = CreateChangelist( thePickle.changelistComment ) + else: + changelistNum = 'default' + + HandleNewTree( thePickle ) + + if g_bVerbose: print "Adding %d files..." % len( thePickle.filesAdded ) + for k in thePickle.filesAdded.keys(): + sDepotFilename = thePickle.relativeToDepotFileMap[k] + + if DoesFileExistInPerforce( sDepotFilename ): + sLocalClientFilename = p4helpers.GetClientFileInfo( sDepotFilename )[0] + AddQueuedWarning( "%s was added in the code pickle, but already exists in Perforce. We'll treat it as an edit." % sLocalClientFilename ); + + p4helpers.CheckPerforceReturn( 'p4 sync %s' % (sDepotFilename) ) + p4helpers.CheckPerforceReturn( 'p4 edit -c %s %s' % (changelistNum, sDepotFilename) ) + WriteFileFromZip( thePickle.theZipFile, k.replace('\\','/'), sLocalClientFilename ) + else: + # Note: We do the "p4 add" first because we don't yet know the local filename for this depot filename, + # and there's no way to get it if the file doesn't already exist. + # + # Perforce will let you do a "p4 add" on a file that doesn't exist in your local tree yet, then you can + # do a "p4 fstat" (p4helpers.GetClientFileInfo) on it to find its local filename. + sCmd = 'p4 add -c %s %s' % (changelistNum, sDepotFilename) + p4helpers.CheckPerforceReturn( sCmd ) + + # Get the local filename and copy from the zip file to that. + sLocalClientFilename = p4helpers.GetClientFileInfo( sDepotFilename ) + if sLocalClientFilename == None: + AddQueuedWarning( "Unable to add %s. It probably isn't in your client spec." % sDepotFilename ) + else: + sLocalClientFilename = sLocalClientFilename[0] + WriteFileFromZip( thePickle.theZipFile, k.replace('\\','/'), sLocalClientFilename ) + + if g_bVerbose: print "\t" + k + + if g_bVerbose: print "Deleting %d files..." % len( thePickle.filesDeleted ) + for k in thePickle.filesDeleted.keys(): + sDepotFilename = thePickle.relativeToDepotFileMap[k] + p4helpers.CheckPerforceReturn( 'p4 delete -c %s %s ' % (changelistNum, sDepotFilename) ) + if g_bVerbose: print "\t" + k + + if g_bVerbose: print "Editing %d files..." % len( thePickle.filesEdited ) + for k in thePickle.filesEdited.keys(): + revisionNumber = thePickle.filesEdited[k] + sDepotFilename = thePickle.relativeToDepotFileMap[k] + sLocalClientFilename = p4helpers.GetClientFileInfo( sDepotFilename )[0] + + # the zipfiles like forward slashes + internalfilename = k.replace('\\','/') + + # Sync to the revision they had it at, edit, copy the file over, and sync to the latest so Perforce forces a resolve. + p4helpers.CheckPerforceReturn( 'p4 sync %s#%s' % (sDepotFilename, revisionNumber) ) + p4helpers.CheckPerforceReturn( 'p4 edit -c %s %s' % (changelistNum, sDepotFilename) ) + WriteFileFromZip( thePickle.theZipFile, internalfilename, sLocalClientFilename ) + p4helpers.CheckPerforceReturn( 'p4 sync %s' % sDepotFilename ) + if g_bVerbose: print "\t%s#%s" % (k, revisionNumber) + + thePickle.theZipFile.close() + + PrintQueuedWarnings() + + if g_FromTree != None: + print "\n" + print " WARNING!!!!! -changetree edited the HEAD revision of the files in new tree." + print " So if they have been edited since you checked them out for your" + print " code pickle, then you must MANUALLY merge those changes into the new version." + + +def SetupBinChangelists(): + global g_Changelist + clientInfo = p4helpers.GetClientInfo() + pendingChanges = p4helpers.GetPendingChanges( clientInfo[ 'Client' ] ) + for changeList in pendingChanges: + if ( changeList[ 'desc' ].find( 'Visual Studio Auto Checkout' ) <> -1 ): + g_Changelist.append( changeList[ 'change' ] ) + +def AutoFillChangelistArray(): + global g_bBinsOnly + global g_RunMode + global g_bPickleAllChanges + global g_Changelist + + if ( g_bBinsOnly and g_RunMode == RUNMODE_BACKUP ): + SetupBinChangelists() + + elif g_bPickleAllChanges: + clientInfo = p4helpers.GetClientInfo() + pendingChanges = p4helpers.GetPendingChanges( clientInfo[ 'Client' ] ) + for changeList in pendingChanges: + g_Changelist.append( changeList['change'] ) + + g_Changelist.append( 'default' ) + + elif len( g_Changelist ) == 0 and g_RunMode == RUNMODE_BACKUP: + g_Changelist.append( 'default' ) + + +def CheckFileExcludedByPatterns( sFilename ): + global g_IncludeGlobPatterns + global g_ExcludeGlobPatterns + + bInclude = ( len( g_IncludeGlobPatterns ) == 0 ) # include by default + for includePattern in g_IncludeGlobPatterns: + if glob.fnmatch.fnmatch( sFilename, includePattern ): + bInclude = True + break + if not bInclude: + if g_bVerbose: print "skipping %s, doesn't match any include pattern" % sFilename + return True + + for excludePattern in g_ExcludeGlobPatterns: + if glob.fnmatch.fnmatch( sFilename, excludePattern ): + if g_bVerbose: print "skipping %s matches exclude pattern %s" % ( sFilename, excludePattern ) + return True + + return False + + + +def DoBackup( sClientRoot, sZipFilename, bAutoRevert ): + global g_Changelist + global g_ChangelistInfoFilename + global g_ChangelistComment + + filesDeleted = {} + filesAdded = {} + filesEdited = {} + + # + # Filter out all the files that aren't of the changelist we want. + # + openedFiles = p4helpers.ReadPerforceOutput( 'p4 -G opened' )[1] + changelistfiles = [x for x in openedFiles if ( x['change'] in g_Changelist ) ] + + # Ok, there's something to do. Open the zip file. + theZipFile = zipfile.ZipFile( sZipFilename, 'w', zipfile.ZIP_DEFLATED ) + + # + # + # + relativeToDepotFileMap = {} + + for openedFile in changelistfiles: + # Get the local client filename. + depotFile = openedFile['depotFile'] # (//valvegames/main/src/etc....) + x = p4helpers.GetClientFileInfo( depotFile ) + + localFilename = x[0] + sFriendlyName = localFilename[ len(sClientRoot)+1: ] # ONLY for printing messages + + # Get the filename in the zipfile. We're just going to use the depot filename and make it palatable to the zipfile. + # You could easily restore the depot filename from this filename, but for now we're just looking it up in relativeToDepotFileMap on the receiving end. + sFilenameInZip = PerforceToZipFilename( depotFile ) + + # Check include and exclude patterns and skip this file if necessary. + if CheckFileExcludedByPatterns( sFilenameInZip ): + continue + + relativeToDepotFileMap[sFilenameInZip] = depotFile + + # Don't do anything if it's a delete. + action = x[2] + + bCopy = True + if action == 'delete': + filesDeleted[sFilenameInZip] = 1 + bCopy = False + print "%s (DELETE)" % (sFriendlyName) + else: + if os.access( localFilename, os.F_OK ) == True: + theZipFile.write( localFilename, sFilenameInZip ) + else: + print "\n**\n** WARNING: File '%s' is in the changelist but not on disk. It will not be in the zipfile.\n**\n" % sFriendlyName + + if action == 'add': + filesAdded[sFilenameInZip] = 1 + print "%s (ADD)" % (sFriendlyName) + else: + filesEdited[sFilenameInZip] = openedFile['rev'] + print "%s#%s" % (sFriendlyName, openedFile['rev']) + + # Revert the file.. + if bAutoRevert == True: + p4helpers.CheckPerforceReturn( 'p4 revert ' + localFilename ) + + + # Save the metadata about add/delete/edit and file revisions. + data = pickle.dumps( [filesDeleted, filesAdded, filesEdited] ) + theZipFile.writestr( g_ChangelistInfoFilename + '.TXT', data ) + + # Save v2 data. + ci2 = CV2Data() + ci2.changelistComment = g_ChangelistComment + data = pickle.dumps( ci2 ) + theZipFile.writestr( g_ChangelistInfoFilename + 'v2.TXT', data ) + + # Save v3 data. + ci3 = CV3Data() + ci3.relativeToDepotFileMap = relativeToDepotFileMap + data = pickle.dumps( ci3 ) + theZipFile.writestr( g_ChangelistInfoFilename + 'v3.TXT', data ) + + + # + # Write a human-readable changelist file. + # + theZipFile.writestr( 'ReadableChangelist.txt', g_ChangelistComment ) + + theZipFile.close() + + +def DoEmptyPickle( sZipFilename ): + global g_ChangelistComment + + filesDeleted = {} + filesAdded = {} + filesEdited = {} + + relativeToDepotFileMap = {} + + # Ok, there's something to do. Open the zip file. + theZipFile = zipfile.ZipFile( sZipFilename, 'w', zipfile.ZIP_DEFLATED ) + + # Save the metadata about add/delete/edit and file revisions. + data = pickle.dumps( [filesDeleted, filesAdded, filesEdited] ) + theZipFile.writestr( g_ChangelistInfoFilename + '.TXT', data ) + + # Save v2 data. + ci2 = CV2Data() + ci2.changelistComment = g_ChangelistComment + data = pickle.dumps( ci2 ) + theZipFile.writestr( g_ChangelistInfoFilename + 'v2.TXT', data ) + + # Save v3 data. + ci3 = CV3Data() + ci3.relativeToDepotFileMap = relativeToDepotFileMap + data = pickle.dumps( ci3 ) + theZipFile.writestr( g_ChangelistInfoFilename + 'v3.TXT', data ) + + + # + # Write a human-readable changelist file. + # + theZipFile.writestr( 'ReadableChangelist.txt', g_ChangelistComment ) + + theZipFile.close() + + + +def DoDiff( sZipFilename, sDiffDir ): + # Load the zipfile. + thePickle = VCodePickle() + thePickle.Load( sZipFilename ) + + sTempFilesDir = os.path.join( sDiffDir, "tempfiles" ) + + # Get rid of the old directory. + if os.access( sDiffDir, os.F_OK ) == True: + theinput = raw_input( "Warning: This will clear out %s. Proceed (y/N)? " % sDiffDir ) + if theinput == '' or theinput.lower() == 'n': + print "Exiting." + sys.exit( 0 ) + + # Delete all the files. + # This method is lame but if we use unlink() on a network path, you won't be able to delete them + # next time around or even from that command line. You can delete them from an explorer window, but + # not from the command prompt anymore. Strange. + os.system( 'rd /s /q "%s"' % sDiffDir ) + + # Create the directories. + os.makedirs( sTempFilesDir ) + + # Dump out the changelist description. + f = open( os.path.join( sDiffDir, "changelist.txt" ), 'wt' ) + f.write( thePickle.changelistComment ) + f.close() + + # Dump out all the files. + for sFilename in thePickle.filesEdited.keys(): + # The zipfile utils like forward slashes. + sInternalFilename = sFilename.replace('\\','/') + + # Write the file to tempfiles\basefilename + sEditedFilename = os.path.join( sTempFilesDir, os.path.basename( sInternalFilename ) ) + WriteFileFromZip( thePickle.theZipFile, sInternalFilename, sEditedFilename ) + + # Have Perforce write out the prior version. + nFileRevision = int( thePickle.filesEdited[sFilename] ) + sPriorFilename = os.path.join( sTempFilesDir, os.path.basename( sInternalFilename ) ) + '.prev' + p4helpers.ReadPerforceOutput( 'p4 -G print -o %s %s#%d' % (sPriorFilename, thePickle.relativeToDepotFileMap[sFilename], nFileRevision) ) + + # Write out a batch file with the diff command. + sBatchFilename = os.path.join( sDiffDir, sInternalFilename.replace( '/', '-' ) ) + '.bat' + fp = open( sBatchFilename, 'wt' ) + sPercent = '%' + fp.write( """ + @echo off + setlocal + """ ) + fp.write( "set sPriorFilename=%s\n" % sPriorFilename ) + fp.write( "set sEditedFilename=%s\n" % sEditedFilename ) + + fp.write( """ + rem // ------------------------------------------------------------------------------------------------------------------------ // + rem // First, check if a P4DIFF environment variable exists. + rem // ------------------------------------------------------------------------------------------------------------------------ // + set diffProg=%P4DIFF% + if "%diffprog%" == "" ( + goto CheckRegistry + ) + + :DoDiff + "%diffProg%" %sPriorFilename% %sEditedFilename% + goto End + + rem // ------------------------------------------------------------------------------------------------------------------------ // + rem // No P4DIFF? Ok, check for what P4Win has setup as its diff program. + rem // The nasty for command there basically runs the reg command, does away with stderr output, pipes the output + rem // to FINDSTR to strip out everything but the second line which contains the result we want. Then it takes the relevant + rem // part of the end of that line, which is what we care about. + rem // ------------------------------------------------------------------------------------------------------------------------ // + :CheckRegistry + for /F "tokens=2* delims=REG_SZ" %%a in ('reg query HKEY_CURRENT_USER\Software\Perforce\P4win\Options /v DiffApp 2^>nul ^| FINDSTR DiffApp') do set initialValue=%%a + if "%initialValue%" == "" ( + set diffProg=p4diff.exe + ) else ( + for /F "tokens=*" %%s in ("%initialValue%") do set diffProg=%%s + ) + goto DoDiff + + :End + + """ ) + + # Now the batch file references the diff program. Just add the arguments we care about. + fp.close() + + print "Wrote %s" % sBatchFilename + + thePickle.theZipFile.close() + + # Open up that directory. + os.system( 'start %s' % sDiffDir ) + + + + +def main(): + global g_bVerbose + global g_RunMode + global g_ZipFilename + global g_bAutoRevert + global g_bBinsOnly + global g_bPickleAllChanges + global g_Changelist + global g_IncludeGlobPatterns + global g_ExcludeGlobPatterns + global g_ChangelistInfoFilename + global g_ChangelistComment + global g_DiffDir + global g_bShowUI + global g_DefaultFilename + global g_bCreateDiff + + + # + # Parse out the command line. + # + try: + opts, args = getopt.getopt( sys.argv[1:], "bdrf:c:ri:e:?q", + [ "prompt", "diff", "diffdir=", "backup", "restore", "file=", "bins", "changelist=", "revert", "include=", "exclude=", "quiet", "help", "empty" ] ) + except getopt.GetoptError, e: + print "" + print "Argument error: ", e + print "" + ShowUsage() + sys.exit(1) + + bPrompt = False + for o, a in opts: + if o in ( "-?", "--help" ): + ShowUsage() + sys.exit(1) + if o in ( "-q", "--quiet" ): + g_bVerbose = False + if o in ( "--prompt" ): + bPrompt = True + if o in ( "-b", "--backup" ): + assert( g_RunMode == RUNMODE_NONE ) + g_RunMode = RUNMODE_BACKUP + if o in ( "-r", "--restore" ): + assert( g_RunMode == RUNMODE_NONE ) + g_RunMode = RUNMODE_RESTORE + if o in ( "-e", "--empty" ): + assert( g_RunMode == RUNMODE_NONE ) + g_RunMode = RUNMODE_EMPTY + if o in ( "-d", "--diff" ): + g_bCreateDiff = True + if o.lower() == "--diffdir" : + g_DiffDir = a + if o in ( "-f", "--file" ): + g_ZipFilename = a + if g_ZipFilename[-4:].lower() != '.zip': + g_ZipFilename += '.zip' + if o in ( "-c", "--changelist" ): + if a in ( 'all', '*' ): + g_bPickleAllChanges = True + else: + g_Changelist.append( a ) + if o.lower() == "--bins": + g_bBinsOnly = True + if o in ( "--revert" ): + g_bAutoRevert = True + if o in ( "-i", "--include" ): + g_IncludeGlobPatterns.append( a ) + if o in ( "-e", "--exclude" ): + g_ExcludeGlobPatterns.append( a ) + + if g_RunMode == RUNMODE_NONE and not g_bCreateDiff: + ShowUsage() + sys.exit( 1 ) + + if ( g_RunMode == RUNMODE_RESTORE or g_RunMode == RUNMODE_EMPTY ) and len( g_ZipFilename ) == 0 and not bPrompt: + print >>sys.stderr, "\n*** file parameter is required in restore/empty mode ***\n\n\n" + ShowUsage() + sys.exit( 1 ) + + if len(g_Changelist) and g_bBinsOnly and g_RunMode == RUNMODE_BACKUP: + print >>sys.stderr, "\n*** -c and --bins are mutally exclusive ***\n\n\n" + ShowUsage() + sys.exit( 1 ) + + if ( len( g_IncludeGlobPatterns ) or len( g_ExcludeGlobPatterns ) ) and g_RunMode != RUNMODE_BACKUP: + print >>sys.stderr, "\n*** -i/-e apply only in backup mode ***\n\n\n" + ShowUsage() + sys.exit(1) + + g_Settings = LoadSettingsFile( g_SettingsFilename ) + + # Auto-fill g_Changelist if necessary. + AutoFillChangelistArray() + + + if g_RunMode == RUNMODE_BACKUP: + # + # Get the string for the changelist. + # + sDate = datetime.datetime.now().strftime("%Y-%m-%d__%Hh-%Mm-%Ss") + if g_Changelist[0] == 'default': + g_ChangelistComment = 'vcodepickle: ' + os.path.basename(g_ZipFilename) + g_DefaultFilename = 'VCP_' + sDate + '.zip' + else: + g_ChangelistComment = GetChangelistComment( g_Changelist[0] ) + partOfChangelistComment = GetReadableChangelistComment( g_ChangelistComment ) + g_DefaultFilename = 'VCP_' + partOfChangelistComment + '_' + sDate + '.zip' + + # + # Generate a default zip filename if they didn't specify one. + # + # Either we'll use a mixture of the current date and their changelist text + # and if it was launched from a Perforce tool (used the -ui) parameter, + # we'll let them specify where to save it (and default to \\fileserver\user\username\[the date and the changelist comment]) + # + if bPrompt == True: + ShowUIForFilename() + else: + # + # Non-ui mode. Just use what they entered on the command line or use the default if they didn't enter anything. + # + if len( g_ZipFilename ) == 0: + SetZipFilename( g_DefaultFilename ) + + + g_ClientInfo = p4helpers.GetClientInfo() + + # Get the root directory without a trailing slash. + g_ClientRoot = g_ClientInfo['Root'] + if g_ClientRoot[-1:] == '\\' or g_ClientRoot[-1:] == '/': + g_ClientRoot = g_ClientRoot[:-1] + + if g_bVerbose: + print "\n---- vcodepickle ----" + print "zipfile : " + g_ZipFilename + print "p4 client root : " + g_ClientRoot + if g_bAutoRevert == True: + print "-revert specified" + print "-----------------------" + print "" + + + + if g_RunMode == RUNMODE_BACKUP: + DoBackup( g_ClientRoot, g_ZipFilename, g_bAutoRevert ) + if g_RunMode == RUNMODE_EMPTY: + DoEmptyPickle( g_ZipFilename ) + + elif g_RunMode == RUNMODE_RESTORE: + if bPrompt: + username = os.environ['username'] + g_ZipFilename = PromptForOpenZipFilename( g_ClientRoot, DefaultPickleDirName(username) ) + if g_ZipFilename != None: + DoRestore( g_ClientRoot, g_ZipFilename ) + else: + DoRestore( g_ClientRoot, g_ZipFilename ) + + if g_bCreateDiff: + print "\n\nPreparing diff...\n" + username = os.environ['username'] + if g_DiffDir == '': + g_DiffDir = DefaultPickleDiffName(username) + + if bPrompt and ( g_RunMode != RUNMODE_BACKUP ): # If RUNMODE_BACKUP, then g_ZipFilename is already set to a valid thing. + g_ZipFilename = PromptForOpenZipFilename( g_ClientRoot, DefaultPickleDirName(username) ) + if g_ZipFilename != None: + DoDiff( g_ZipFilename, g_DiffDir ) + else: + DoDiff( g_ZipFilename, g_DiffDir ) + + + +if __name__ == '__main__': + main() + + |