Team Fortress 2 Source Code as on 22/4/2020
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1034 lines
31 KiB

  1. #!/usr/bin/env python
  2. import p4helpers
  3. import sys
  4. import os
  5. import shutil
  6. import pickle
  7. import zipfile
  8. import zlib
  9. import datetime
  10. import subprocess
  11. import getopt
  12. import glob
  13. import struct
  14. try:
  15. import wx
  16. except:
  17. pass
  18. #
  19. # Set program defaults.
  20. #
  21. g_ChangelistInfoFilename = '__CHANGELISTINFO__'
  22. RUNMODE_BACKUP = 0
  23. RUNMODE_RESTORE = 1
  24. RUNMODE_EMPTY = 2
  25. RUNMODE_NONE = 3
  26. g_RunMode = RUNMODE_NONE
  27. g_ZipFilename = ''
  28. g_bAutoRevert = False
  29. g_bBinsOnly = False
  30. g_Changelist = []
  31. g_bShowUI = False
  32. g_bPickleAllChanges = False
  33. g_bVerbose = True
  34. g_IncludeGlobPatterns = []
  35. g_ExcludeGlobPatterns = []
  36. g_ChangelistComment = ''
  37. g_DefaultFilename = None
  38. g_bCreateDiff = False
  39. g_DiffDir = ''
  40. g_SettingsFilename = 'vcodepickle.cfg'
  41. g_Settings = {}
  42. g_UISaveDirKey = 'uisavedir'
  43. # These are used with -changetree if the user wants to restore the changelist to
  44. # a different tree than it originally came from.
  45. g_FromTree = None
  46. g_ToTree = None
  47. g_QueuedWarnings = []
  48. #
  49. # Pickle data for various file versions supported by vcodepickle.
  50. #
  51. class CV2Data:
  52. changelistComment = None
  53. class CV3Data:
  54. relativeToDepotFileMap = None
  55. def ShowUsage():
  56. print "vcodepickle.py [options] <--backup [options] or --restore or --diff or --empty> [--file filename] [--changelist <changelist> | --bins]"
  57. print ""
  58. print "-q/--quiet sshhhhhh."
  59. print "-b/--backup pickle local changes to file"
  60. print "-r/--restore apply pickled edits to client"
  61. print "--empty create a valid, but empty, pickle, used by buildbot"
  62. print "-d/--diff produce a diff of the changes in a vcodepickled zipfile"
  63. print "-f/--file filename pickle file - optional for backup, required for restore"
  64. print "-c/--changelist <#|all> in backup mode, pickle specified changelist(s)"
  65. print " in restore mode, reapply changes to the specified changelist"
  66. print " defaults to 'default'"
  67. print " --prompt if using --backup, this asks where to backup (and makes suggestions)"
  68. print " if using --restore, this brings up a file open dialog"
  69. print ""
  70. print "backup options:"
  71. print ""
  72. print " --bins pickle binaries from the auto-checkout changelist"
  73. print " --revert in backup mode, revert open files after pickling"
  74. print " -e/--exclude <globpat> a glob pattern of files to exclude from pickling"
  75. print " -i/--include <globpat> a glob pattern of files to include in the code pickle"
  76. print ""
  77. print "restore options:"
  78. print ""
  79. print " --changetree <src> <dst> allows you to restore to a Perforce tree different from the original one"
  80. print ""
  81. print "diff options:"
  82. print ""
  83. print " --diffdir <directory> specify where the diff files will go"
  84. print ""
  85. print 'ex: vcodepickle.py --backup --changelist all --exclude "bin/*"'
  86. print ' creates a zip file of all files not matching "bin/*" in all changelists'
  87. print ""
  88. print "ex: vcodepickle.py --restore --file vcodepickle_2009_01_20.zip -c 342421"
  89. print " restore from the specified file into changelist 342421"
  90. print ""
  91. print "ex: vcodepickle.py --diff --file VCP_2009-03-26__17h-01m-25s.zip"
  92. print " creates a diff of the pickled changes in the specified file"
  93. sys.exit( 1 )
  94. def DefaultPickleDirName( username ):
  95. return '\\\\fileserver\\user\\' + username + '\\vcodepickles'
  96. def DefaultPickleDiffName( username ):
  97. return '\\\\fileserver\\user\\' + username + '\\vcodepicklediff'
  98. def CreateChangelist( comment ):
  99. po = subprocess.Popen( 'p4 change -i', shell=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE )
  100. po.stdin.write( 'Change: new\n\nDescription:\n\t' + comment.replace('\n','\n\t') + '\n\n' )
  101. po.stdin.close()
  102. theOutput = po.stdout.readline()
  103. ret = po.wait()
  104. if ret == 0:
  105. return int( theOutput.split( ' ' )[1] )
  106. else:
  107. print "Creating a changelist failed."
  108. print theOutput
  109. sys.exit( 1 )
  110. def GetChangelistComment( nChangelist ):
  111. po = subprocess.Popen( 'p4 change -o ' + nChangelist, shell=True, stdout=subprocess.PIPE )
  112. lines = po.stdout.readlines()
  113. ret = po.wait()
  114. if ret != 0:
  115. print "Couldn't get changelist description for change " + nChangelist
  116. sys.exit( 1 )
  117. comment = ''
  118. bFound = False
  119. for line in lines:
  120. if bFound == True:
  121. if line[0] == '\t':
  122. comment += line[1:]
  123. else:
  124. break
  125. else:
  126. if line[:12] == 'Description:':
  127. bFound = True
  128. return comment
  129. def chomp( str ):
  130. while 1:
  131. str2 = str.lstrip( '\n' ).lstrip( '\r' ).rstrip( '\n' ).rstrip( '\r' )
  132. if len( str2 ) == len( str ):
  133. return str
  134. else:
  135. str = str2
  136. def AddQueuedWarning( s ):
  137. global g_QueuedWarnings
  138. g_QueuedWarnings.append( s )
  139. def PrintQueuedWarnings():
  140. global g_QueuedWarnings
  141. if len( g_QueuedWarnings ) > 0:
  142. print "\n\n===== WARNINGS =====\n"
  143. nWarning = 1
  144. for warning in g_QueuedWarnings:
  145. print "%d:\n%s\n" % (nWarning, warning)
  146. nWarning += 1
  147. def DoesFileExistInPerforce( filename ):
  148. x = p4helpers.ReadPerforceOutput( 'p4 -G fstat ' + filename )
  149. return (x[1][0]['code'] != 'error')
  150. #
  151. # The settings file is just a bunch of key/value pairs on different lines separated by =
  152. #
  153. def LoadSettingsFile( filename ):
  154. ret = {}
  155. try:
  156. f = open( filename, 'rt' )
  157. except IOError:
  158. return {}
  159. for line in f.readlines():
  160. line = chomp( line )
  161. parts = line.split( '=' )
  162. if len( parts ) > 0:
  163. ret[parts[0]] = parts[1]
  164. f.close()
  165. return ret;
  166. def SaveSettingsFile( filename, thedict ):
  167. f = open( filename, 'wt' )
  168. for (k,v) in thedict.items():
  169. f.write( '%s=%s\n' % (k, v) )
  170. f.close()
  171. def SetZipFilename( filename ):
  172. global g_ZipFilename
  173. g_ZipFilename = filename
  174. if g_ZipFilename[-4:].lower() != '.zip':
  175. g_ZipFilename += '.zip'
  176. def GetReadableChangelistComment( str ):
  177. alphabet = 'abcdefghijklmnopqrstuvwxyz'
  178. # Strip out all lists of repeating nonalphabetic characters
  179. tempstr = ''
  180. prevchar = ''
  181. for i in range(0,len(str)):
  182. testchar = str[i:i+1]
  183. if testchar != prevchar or alphabet.find(testchar) != -1:
  184. tempstr += testchar
  185. prevchar = testchar
  186. str = tempstr
  187. str = str[:100]
  188. outstr = ''
  189. validchars = alphabet + alphabet.upper() + '0123456789_'
  190. prevchar = ''
  191. for i in range(0,len(str)):
  192. testchar = str[i:i+1]
  193. if validchars.find( testchar ) == -1:
  194. if prevchar != '-':
  195. outstr += '-'
  196. else:
  197. outstr += testchar
  198. prevchar = outstr[-1:]
  199. return outstr
  200. def ShowUIForFilename():
  201. global g_Settings
  202. global g_SettingsFilename
  203. global g_ZipFilename
  204. global g_DefaultFilename
  205. #
  206. # Get the directory to save to.
  207. #
  208. username = os.environ['username']
  209. defaultdirname = DefaultPickleDirName(username)
  210. if not g_Settings.has_key( g_UISaveDirKey ):
  211. g_Settings[g_UISaveDirKey] = defaultdirname
  212. if g_Settings[g_UISaveDirKey] == 'default':
  213. theinput = raw_input( '\nDirectory\n=========\n(Leave blank for "%s")\n> ' % ( defaultdirname ) )
  214. else:
  215. theinput = raw_input( '\nDirectory\n=========\n(Leave blank for "%s")\n(Type "default" for "%s"\n--> ' % ( g_Settings[g_UISaveDirKey], defaultdirname ) )
  216. if theinput == '':
  217. theDirName = g_Settings[g_UISaveDirKey]
  218. else:
  219. g_Settings[g_UISaveDirKey] = theinput
  220. theDirName = theinput
  221. if theDirName.lower() == 'default':
  222. theDirName = defaultdirname
  223. #
  224. # Make sure it exists.. create it if necessary.
  225. #
  226. if os.access( theDirName, os.F_OK ) != True:
  227. theinput = raw_input( "Directory %s doesn't exist. Create? (Y/n) " % theDirName )
  228. if theinput == '' or theinput.lower() == 'y':
  229. os.makedirs( theDirName )
  230. else:
  231. print "Exiting."
  232. sys.exit( 1 )
  233. #
  234. # Get the filename to save to.
  235. #
  236. theinput = raw_input( '\nFilename\n========\n(Leave blank for "%s")\n> ' % ( g_DefaultFilename ) )
  237. if theinput == '':
  238. theZipFilename = g_DefaultFilename
  239. else:
  240. theZipFilename = theinput
  241. #
  242. # Construct the actual zip filename we'll use.
  243. #
  244. SetZipFilename( os.path.join( theDirName, theZipFilename ) )
  245. #
  246. # Check if it already exists.
  247. #
  248. if os.access( g_ZipFilename, os.F_OK ) == True:
  249. theinput = raw_input( "%s already exists. Overwrite? (y/N)" % g_ZipFilename )
  250. if theinput == '' or theinput.lower() == 'n':
  251. print "Exiting."
  252. sys.exit(1)
  253. #
  254. # Save the settings back out.
  255. #
  256. SaveSettingsFile( g_SettingsFilename, g_Settings )
  257. class VCodePickle:
  258. theZipFile = None
  259. filesDeleted = {}
  260. filesAdded = {}
  261. filesEdited = {}
  262. changelistComment = ''
  263. relativeToDepotFileMap = {} # Maps src\engine\cl_main.cpp to //valvegames/main/src/engine/cl_main.cpp (obviously //valvegames/main/src changes based on the root)
  264. def Load( self, zipFilename ):
  265. self.theZipFile = zipfile.ZipFile( zipFilename, 'r', zipfile.ZIP_DEFLATED )
  266. # Load the added and deleted lists.
  267. changelistinfo = self.theZipFile.read( g_ChangelistInfoFilename + '.TXT' )
  268. (self.filesDeleted, self.filesAdded, self.filesEdited) = pickle.loads( changelistinfo )
  269. self.changelistComment = 'vcodepickle: ' + os.path.basename(g_ZipFilename)
  270. # Load version2 data.
  271. try:
  272. ci2 = pickle.loads( self.theZipFile.read( g_ChangelistInfoFilename + 'v2.TXT' ) )
  273. self.changelistComment = ci2.changelistComment
  274. except KeyError:
  275. pass
  276. # Load version3 data
  277. try:
  278. ci3 = pickle.loads( self.theZipFile.read( g_ChangelistInfoFilename + 'v3.TXT' ) )
  279. self.relativeToDepotFileMap = ci3.relativeToDepotFileMap
  280. except KeyError:
  281. pass
  282. ##
  283. # Given a 'zip' instance, copy data from the 'name' to the
  284. # 'out' stream.
  285. def explode( out, zip, name ):
  286. zinfo = zip.getinfo(name)
  287. stringFileHeader = "PK\003\004" # magic number for file header
  288. structFileHeader = "<4s2B4HlLL2H" # 12 items, file header record, 30 bytes
  289. _FH_FILENAME_LENGTH = 10
  290. _FH_EXTRA_FIELD_LENGTH = 11
  291. filepos = zip.fp.tell()
  292. zip.fp.seek(zinfo.header_offset, 0)
  293. # Skip the file header:
  294. fheader = zip.fp.read(30)
  295. if fheader[0:4] != stringFileHeader:
  296. raise zipfile.BadZipfile, "Bad magic number for file header"
  297. fheader = struct.unpack(structFileHeader, fheader)
  298. fname = zip.fp.read(fheader[_FH_FILENAME_LENGTH])
  299. if fheader[_FH_EXTRA_FIELD_LENGTH]:
  300. zip.fp.read(fheader[_FH_EXTRA_FIELD_LENGTH])
  301. if fname != zinfo.orig_filename:
  302. raise zipfile.BadZipfile, 'File name in directory "%s" and header "%s" differ.' % ( zinfo.orig_filename, fname)
  303. if zinfo.compress_type == zipfile.ZIP_STORED:
  304. decoder = None
  305. elif zinfo.compress_type == zipfile.ZIP_DEFLATED:
  306. if not zlib:
  307. raise RuntimeError, "De-compression requires the (missing) zlib module"
  308. decoder = zlib.decompressobj(-zlib.MAX_WBITS)
  309. else:
  310. raise zipfile.BadZipfile,"Unsupported compression method %d for file %s" % (zinfo.compress_type, name)
  311. size = zinfo.compress_size
  312. while 1:
  313. data = zip.fp.read(min(size, 8192))
  314. if not data:
  315. break
  316. size -= len(data)
  317. if decoder:
  318. data = decoder.decompress(data)
  319. out.write(data)
  320. if decoder:
  321. out.write(decoder.decompress('Z'))
  322. out.write(decoder.flush())
  323. zip.fp.seek(filepos, 0)
  324. def WriteFileFromZip( theZipFile, srcFilename, sDestFilename ):
  325. # thebytes = theZipFile.read( srcFilename )
  326. try:
  327. f = open( sDestFilename, 'wb')
  328. except:
  329. os.makedirs( os.path.dirname( sDestFilename ) )
  330. f = open( sDestFilename, 'wb' )
  331. explode( f, theZipFile, srcFilename );
  332. # f.write( thebytes )
  333. f.close()
  334. def PerforceToZipFilename( sP4Filename ):
  335. if sP4Filename[0] != '/' or sP4Filename[1] != '/':
  336. print "PerforceToZipFilename: invalid filename (%s)" % (sP4Filename)
  337. return sP4Filename[2:]
  338. def CheckFilesOpenedForEdit( thePickle, theDict ):
  339. ret = []
  340. for k in theDict.keys():
  341. sDepotFilename = thePickle.relativeToDepotFileMap[k]
  342. if p4helpers.GetClientFileAction( sDepotFilename ) != 'none':
  343. ret.append( sDepotFilename )
  344. return ret
  345. def HandleNewTree( thePickle ):
  346. global g_FromTree, g_ToTree
  347. if g_FromTree == None:
  348. return
  349. print "-changetree is translating these files:\n"
  350. for k in thePickle.relativeToDepotFileMap.keys():
  351. sFromFilename = thePickle.relativeToDepotFileMap[k]
  352. if sFromFilename[:len(g_FromTree)].lower() == g_FromTree.lower():
  353. sToFilename = g_ToTree + sFromFilename[len(g_FromTree):]
  354. print sFromFilename + "\n\t-> " + sToFilename + "\n"
  355. thePickle.relativeToDepotFileMap[k] = sToFilename
  356. else:
  357. print "Used -changetree [%s] [%s], but file %s does not come from the source tree." % (g_FromTree, g_ToTree, sFilename)
  358. sys.exit( 1 )
  359. # Translate the revision number to a changelist number for edited files.
  360. newEdited = {}
  361. for k in thePickle.filesEdited.keys():
  362. thePickle.filesEdited[k] = "head"
  363. sDepotFilename = thePickle.relativeToDepotFileMap[k]
  364. if DoesFileExistInPerforce( sDepotFilename ):
  365. newEdited[k] = thePickle.filesEdited[k]
  366. else:
  367. AddQueuedWarning( "%s was edited in the old tree, but doesn't exist in the new tree. This was treated as an add instead." % sDepotFilename )
  368. thePickle.filesAdded[k] = thePickle.filesEdited[k]
  369. thePickle.filesEdited = newEdited
  370. print "\n"
  371. def PromptForOpenZipFilename( sClientRoot, promptDir ):
  372. if not sys.modules.has_key( "wx" ):
  373. print >>sys.stderr, "Couldn't load wx, no UI available."
  374. sys.exit( 1 )
  375. temp_app = wx.PySimpleApp()
  376. dialog = wx.FileDialog ( None, style = wx.OPEN )
  377. dialog.SetMessage( "Select code pickle" );
  378. dialog.SetDirectory(promptDir)
  379. dialog.SetWildcard( "VCodePickle Zips (*.zip)|*.zip|All Files (*.*)|*.*" );
  380. if dialog.ShowModal() == wx.ID_OK:
  381. print 'Selected: %s\n' % (dialog.GetPath())
  382. return dialog.GetPath()
  383. else:
  384. print 'Cancelled.'
  385. return None
  386. def DoRestore( sClientRoot, sZipFilename ):
  387. global g_bBinsOnly
  388. thePickle = VCodePickle()
  389. thePickle.Load( sZipFilename )
  390. # First make sure they don't have any files open for edit that we want to mess with.
  391. print "Checking the code pickle...\n"
  392. openedForEdit = CheckFilesOpenedForEdit( thePickle, thePickle.filesAdded ) + CheckFilesOpenedForEdit( thePickle, thePickle.filesEdited )
  393. if len( openedForEdit ) > 0 and g_bBinsOnly==False:
  394. print "You have one or more files open locally that are in the vcodepickle you are trying to restore:\n"
  395. for f in openedForEdit:
  396. print "\t" + f
  397. print "\nPlease revert the specified file(s) and retry the vcodepickle command you were doing."
  398. return
  399. changelistNum = None
  400. if ( g_bBinsOnly == False ):
  401. changelistNum = CreateChangelist( thePickle.changelistComment )
  402. else:
  403. changelistNum = 'default'
  404. HandleNewTree( thePickle )
  405. if g_bVerbose: print "Adding %d files..." % len( thePickle.filesAdded )
  406. for k in thePickle.filesAdded.keys():
  407. sDepotFilename = thePickle.relativeToDepotFileMap[k]
  408. if DoesFileExistInPerforce( sDepotFilename ):
  409. sLocalClientFilename = p4helpers.GetClientFileInfo( sDepotFilename )[0]
  410. AddQueuedWarning( "%s was added in the code pickle, but already exists in Perforce. We'll treat it as an edit." % sLocalClientFilename );
  411. p4helpers.CheckPerforceReturn( 'p4 sync %s' % (sDepotFilename) )
  412. p4helpers.CheckPerforceReturn( 'p4 edit -c %s %s' % (changelistNum, sDepotFilename) )
  413. WriteFileFromZip( thePickle.theZipFile, k.replace('\\','/'), sLocalClientFilename )
  414. else:
  415. # Note: We do the "p4 add" first because we don't yet know the local filename for this depot filename,
  416. # and there's no way to get it if the file doesn't already exist.
  417. #
  418. # Perforce will let you do a "p4 add" on a file that doesn't exist in your local tree yet, then you can
  419. # do a "p4 fstat" (p4helpers.GetClientFileInfo) on it to find its local filename.
  420. sCmd = 'p4 add -c %s %s' % (changelistNum, sDepotFilename)
  421. p4helpers.CheckPerforceReturn( sCmd )
  422. # Get the local filename and copy from the zip file to that.
  423. sLocalClientFilename = p4helpers.GetClientFileInfo( sDepotFilename )
  424. if sLocalClientFilename == None:
  425. AddQueuedWarning( "Unable to add %s. It probably isn't in your client spec." % sDepotFilename )
  426. else:
  427. sLocalClientFilename = sLocalClientFilename[0]
  428. WriteFileFromZip( thePickle.theZipFile, k.replace('\\','/'), sLocalClientFilename )
  429. if g_bVerbose: print "\t" + k
  430. if g_bVerbose: print "Deleting %d files..." % len( thePickle.filesDeleted )
  431. for k in thePickle.filesDeleted.keys():
  432. sDepotFilename = thePickle.relativeToDepotFileMap[k]
  433. p4helpers.CheckPerforceReturn( 'p4 delete -c %s %s ' % (changelistNum, sDepotFilename) )
  434. if g_bVerbose: print "\t" + k
  435. if g_bVerbose: print "Editing %d files..." % len( thePickle.filesEdited )
  436. for k in thePickle.filesEdited.keys():
  437. revisionNumber = thePickle.filesEdited[k]
  438. sDepotFilename = thePickle.relativeToDepotFileMap[k]
  439. sLocalClientFilename = p4helpers.GetClientFileInfo( sDepotFilename )[0]
  440. # the zipfiles like forward slashes
  441. internalfilename = k.replace('\\','/')
  442. # Sync to the revision they had it at, edit, copy the file over, and sync to the latest so Perforce forces a resolve.
  443. p4helpers.CheckPerforceReturn( 'p4 sync %s#%s' % (sDepotFilename, revisionNumber) )
  444. p4helpers.CheckPerforceReturn( 'p4 edit -c %s %s' % (changelistNum, sDepotFilename) )
  445. WriteFileFromZip( thePickle.theZipFile, internalfilename, sLocalClientFilename )
  446. p4helpers.CheckPerforceReturn( 'p4 sync %s' % sDepotFilename )
  447. if g_bVerbose: print "\t%s#%s" % (k, revisionNumber)
  448. thePickle.theZipFile.close()
  449. PrintQueuedWarnings()
  450. if g_FromTree != None:
  451. print "\n"
  452. print " WARNING!!!!! -changetree edited the HEAD revision of the files in new tree."
  453. print " So if they have been edited since you checked them out for your"
  454. print " code pickle, then you must MANUALLY merge those changes into the new version."
  455. def SetupBinChangelists():
  456. global g_Changelist
  457. clientInfo = p4helpers.GetClientInfo()
  458. pendingChanges = p4helpers.GetPendingChanges( clientInfo[ 'Client' ] )
  459. for changeList in pendingChanges:
  460. if ( changeList[ 'desc' ].find( 'Visual Studio Auto Checkout' ) <> -1 ):
  461. g_Changelist.append( changeList[ 'change' ] )
  462. def AutoFillChangelistArray():
  463. global g_bBinsOnly
  464. global g_RunMode
  465. global g_bPickleAllChanges
  466. global g_Changelist
  467. if ( g_bBinsOnly and g_RunMode == RUNMODE_BACKUP ):
  468. SetupBinChangelists()
  469. elif g_bPickleAllChanges:
  470. clientInfo = p4helpers.GetClientInfo()
  471. pendingChanges = p4helpers.GetPendingChanges( clientInfo[ 'Client' ] )
  472. for changeList in pendingChanges:
  473. g_Changelist.append( changeList['change'] )
  474. g_Changelist.append( 'default' )
  475. elif len( g_Changelist ) == 0 and g_RunMode == RUNMODE_BACKUP:
  476. g_Changelist.append( 'default' )
  477. def CheckFileExcludedByPatterns( sFilename ):
  478. global g_IncludeGlobPatterns
  479. global g_ExcludeGlobPatterns
  480. bInclude = ( len( g_IncludeGlobPatterns ) == 0 ) # include by default
  481. for includePattern in g_IncludeGlobPatterns:
  482. if glob.fnmatch.fnmatch( sFilename, includePattern ):
  483. bInclude = True
  484. break
  485. if not bInclude:
  486. if g_bVerbose: print "skipping %s, doesn't match any include pattern" % sFilename
  487. return True
  488. for excludePattern in g_ExcludeGlobPatterns:
  489. if glob.fnmatch.fnmatch( sFilename, excludePattern ):
  490. if g_bVerbose: print "skipping %s matches exclude pattern %s" % ( sFilename, excludePattern )
  491. return True
  492. return False
  493. def DoBackup( sClientRoot, sZipFilename, bAutoRevert ):
  494. global g_Changelist
  495. global g_ChangelistInfoFilename
  496. global g_ChangelistComment
  497. filesDeleted = {}
  498. filesAdded = {}
  499. filesEdited = {}
  500. #
  501. # Filter out all the files that aren't of the changelist we want.
  502. #
  503. openedFiles = p4helpers.ReadPerforceOutput( 'p4 -G opened' )[1]
  504. changelistfiles = [x for x in openedFiles if ( x['change'] in g_Changelist ) ]
  505. # Ok, there's something to do. Open the zip file.
  506. theZipFile = zipfile.ZipFile( sZipFilename, 'w', zipfile.ZIP_DEFLATED )
  507. #
  508. #
  509. #
  510. relativeToDepotFileMap = {}
  511. for openedFile in changelistfiles:
  512. # Get the local client filename.
  513. depotFile = openedFile['depotFile'] # (//valvegames/main/src/etc....)
  514. x = p4helpers.GetClientFileInfo( depotFile )
  515. localFilename = x[0]
  516. sFriendlyName = localFilename[ len(sClientRoot)+1: ] # ONLY for printing messages
  517. # Get the filename in the zipfile. We're just going to use the depot filename and make it palatable to the zipfile.
  518. # 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.
  519. sFilenameInZip = PerforceToZipFilename( depotFile )
  520. # Check include and exclude patterns and skip this file if necessary.
  521. if CheckFileExcludedByPatterns( sFilenameInZip ):
  522. continue
  523. relativeToDepotFileMap[sFilenameInZip] = depotFile
  524. # Don't do anything if it's a delete.
  525. action = x[2]
  526. bCopy = True
  527. if action == 'delete':
  528. filesDeleted[sFilenameInZip] = 1
  529. bCopy = False
  530. print "%s (DELETE)" % (sFriendlyName)
  531. else:
  532. if os.access( localFilename, os.F_OK ) == True:
  533. theZipFile.write( localFilename, sFilenameInZip )
  534. else:
  535. print "\n**\n** WARNING: File '%s' is in the changelist but not on disk. It will not be in the zipfile.\n**\n" % sFriendlyName
  536. if action == 'add':
  537. filesAdded[sFilenameInZip] = 1
  538. print "%s (ADD)" % (sFriendlyName)
  539. else:
  540. filesEdited[sFilenameInZip] = openedFile['rev']
  541. print "%s#%s" % (sFriendlyName, openedFile['rev'])
  542. # Revert the file..
  543. if bAutoRevert == True:
  544. p4helpers.CheckPerforceReturn( 'p4 revert ' + localFilename )
  545. # Save the metadata about add/delete/edit and file revisions.
  546. data = pickle.dumps( [filesDeleted, filesAdded, filesEdited] )
  547. theZipFile.writestr( g_ChangelistInfoFilename + '.TXT', data )
  548. # Save v2 data.
  549. ci2 = CV2Data()
  550. ci2.changelistComment = g_ChangelistComment
  551. data = pickle.dumps( ci2 )
  552. theZipFile.writestr( g_ChangelistInfoFilename + 'v2.TXT', data )
  553. # Save v3 data.
  554. ci3 = CV3Data()
  555. ci3.relativeToDepotFileMap = relativeToDepotFileMap
  556. data = pickle.dumps( ci3 )
  557. theZipFile.writestr( g_ChangelistInfoFilename + 'v3.TXT', data )
  558. #
  559. # Write a human-readable changelist file.
  560. #
  561. theZipFile.writestr( 'ReadableChangelist.txt', g_ChangelistComment )
  562. theZipFile.close()
  563. def DoEmptyPickle( sZipFilename ):
  564. global g_ChangelistComment
  565. filesDeleted = {}
  566. filesAdded = {}
  567. filesEdited = {}
  568. relativeToDepotFileMap = {}
  569. # Ok, there's something to do. Open the zip file.
  570. theZipFile = zipfile.ZipFile( sZipFilename, 'w', zipfile.ZIP_DEFLATED )
  571. # Save the metadata about add/delete/edit and file revisions.
  572. data = pickle.dumps( [filesDeleted, filesAdded, filesEdited] )
  573. theZipFile.writestr( g_ChangelistInfoFilename + '.TXT', data )
  574. # Save v2 data.
  575. ci2 = CV2Data()
  576. ci2.changelistComment = g_ChangelistComment
  577. data = pickle.dumps( ci2 )
  578. theZipFile.writestr( g_ChangelistInfoFilename + 'v2.TXT', data )
  579. # Save v3 data.
  580. ci3 = CV3Data()
  581. ci3.relativeToDepotFileMap = relativeToDepotFileMap
  582. data = pickle.dumps( ci3 )
  583. theZipFile.writestr( g_ChangelistInfoFilename + 'v3.TXT', data )
  584. #
  585. # Write a human-readable changelist file.
  586. #
  587. theZipFile.writestr( 'ReadableChangelist.txt', g_ChangelistComment )
  588. theZipFile.close()
  589. def DoDiff( sZipFilename, sDiffDir ):
  590. # Load the zipfile.
  591. thePickle = VCodePickle()
  592. thePickle.Load( sZipFilename )
  593. sTempFilesDir = os.path.join( sDiffDir, "tempfiles" )
  594. # Get rid of the old directory.
  595. if os.access( sDiffDir, os.F_OK ) == True:
  596. theinput = raw_input( "Warning: This will clear out %s. Proceed (y/N)? " % sDiffDir )
  597. if theinput == '' or theinput.lower() == 'n':
  598. print "Exiting."
  599. sys.exit( 0 )
  600. # Delete all the files.
  601. # This method is lame but if we use unlink() on a network path, you won't be able to delete them
  602. # next time around or even from that command line. You can delete them from an explorer window, but
  603. # not from the command prompt anymore. Strange.
  604. os.system( 'rd /s /q "%s"' % sDiffDir )
  605. # Create the directories.
  606. os.makedirs( sTempFilesDir )
  607. # Dump out the changelist description.
  608. f = open( os.path.join( sDiffDir, "changelist.txt" ), 'wt' )
  609. f.write( thePickle.changelistComment )
  610. f.close()
  611. # Dump out all the files.
  612. for sFilename in thePickle.filesEdited.keys():
  613. # The zipfile utils like forward slashes.
  614. sInternalFilename = sFilename.replace('\\','/')
  615. # Write the file to tempfiles\basefilename
  616. sEditedFilename = os.path.join( sTempFilesDir, os.path.basename( sInternalFilename ) )
  617. WriteFileFromZip( thePickle.theZipFile, sInternalFilename, sEditedFilename )
  618. # Have Perforce write out the prior version.
  619. nFileRevision = int( thePickle.filesEdited[sFilename] )
  620. sPriorFilename = os.path.join( sTempFilesDir, os.path.basename( sInternalFilename ) ) + '.prev'
  621. p4helpers.ReadPerforceOutput( 'p4 -G print -o %s %s#%d' % (sPriorFilename, thePickle.relativeToDepotFileMap[sFilename], nFileRevision) )
  622. # Write out a batch file with the diff command.
  623. sBatchFilename = os.path.join( sDiffDir, sInternalFilename.replace( '/', '-' ) ) + '.bat'
  624. fp = open( sBatchFilename, 'wt' )
  625. sPercent = '%'
  626. fp.write( """
  627. @echo off
  628. setlocal
  629. """ )
  630. fp.write( "set sPriorFilename=%s\n" % sPriorFilename )
  631. fp.write( "set sEditedFilename=%s\n" % sEditedFilename )
  632. fp.write( """
  633. rem // ------------------------------------------------------------------------------------------------------------------------ //
  634. rem // First, check if a P4DIFF environment variable exists.
  635. rem // ------------------------------------------------------------------------------------------------------------------------ //
  636. set diffProg=%P4DIFF%
  637. if "%diffprog%" == "" (
  638. goto CheckRegistry
  639. )
  640. :DoDiff
  641. "%diffProg%" %sPriorFilename% %sEditedFilename%
  642. goto End
  643. rem // ------------------------------------------------------------------------------------------------------------------------ //
  644. rem // No P4DIFF? Ok, check for what P4Win has setup as its diff program.
  645. rem // The nasty for command there basically runs the reg command, does away with stderr output, pipes the output
  646. rem // to FINDSTR to strip out everything but the second line which contains the result we want. Then it takes the relevant
  647. rem // part of the end of that line, which is what we care about.
  648. rem // ------------------------------------------------------------------------------------------------------------------------ //
  649. :CheckRegistry
  650. 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
  651. if "%initialValue%" == "" (
  652. set diffProg=p4diff.exe
  653. ) else (
  654. for /F "tokens=*" %%s in ("%initialValue%") do set diffProg=%%s
  655. )
  656. goto DoDiff
  657. :End
  658. """ )
  659. # Now the batch file references the diff program. Just add the arguments we care about.
  660. fp.close()
  661. print "Wrote %s" % sBatchFilename
  662. thePickle.theZipFile.close()
  663. # Open up that directory.
  664. os.system( 'start %s' % sDiffDir )
  665. def main():
  666. global g_bVerbose
  667. global g_RunMode
  668. global g_ZipFilename
  669. global g_bAutoRevert
  670. global g_bBinsOnly
  671. global g_bPickleAllChanges
  672. global g_Changelist
  673. global g_IncludeGlobPatterns
  674. global g_ExcludeGlobPatterns
  675. global g_ChangelistInfoFilename
  676. global g_ChangelistComment
  677. global g_DiffDir
  678. global g_bShowUI
  679. global g_DefaultFilename
  680. global g_bCreateDiff
  681. #
  682. # Parse out the command line.
  683. #
  684. try:
  685. opts, args = getopt.getopt( sys.argv[1:], "bdrf:c:ri:e:?q",
  686. [ "prompt", "diff", "diffdir=", "backup", "restore", "file=", "bins", "changelist=", "revert", "include=", "exclude=", "quiet", "help", "empty" ] )
  687. except getopt.GetoptError, e:
  688. print ""
  689. print "Argument error: ", e
  690. print ""
  691. ShowUsage()
  692. sys.exit(1)
  693. bPrompt = False
  694. for o, a in opts:
  695. if o in ( "-?", "--help" ):
  696. ShowUsage()
  697. sys.exit(1)
  698. if o in ( "-q", "--quiet" ):
  699. g_bVerbose = False
  700. if o in ( "--prompt" ):
  701. bPrompt = True
  702. if o in ( "-b", "--backup" ):
  703. assert( g_RunMode == RUNMODE_NONE )
  704. g_RunMode = RUNMODE_BACKUP
  705. if o in ( "-r", "--restore" ):
  706. assert( g_RunMode == RUNMODE_NONE )
  707. g_RunMode = RUNMODE_RESTORE
  708. if o in ( "-e", "--empty" ):
  709. assert( g_RunMode == RUNMODE_NONE )
  710. g_RunMode = RUNMODE_EMPTY
  711. if o in ( "-d", "--diff" ):
  712. g_bCreateDiff = True
  713. if o.lower() == "--diffdir" :
  714. g_DiffDir = a
  715. if o in ( "-f", "--file" ):
  716. g_ZipFilename = a
  717. if g_ZipFilename[-4:].lower() != '.zip':
  718. g_ZipFilename += '.zip'
  719. if o in ( "-c", "--changelist" ):
  720. if a in ( 'all', '*' ):
  721. g_bPickleAllChanges = True
  722. else:
  723. g_Changelist.append( a )
  724. if o.lower() == "--bins":
  725. g_bBinsOnly = True
  726. if o in ( "--revert" ):
  727. g_bAutoRevert = True
  728. if o in ( "-i", "--include" ):
  729. g_IncludeGlobPatterns.append( a )
  730. if o in ( "-e", "--exclude" ):
  731. g_ExcludeGlobPatterns.append( a )
  732. if g_RunMode == RUNMODE_NONE and not g_bCreateDiff:
  733. ShowUsage()
  734. sys.exit( 1 )
  735. if ( g_RunMode == RUNMODE_RESTORE or g_RunMode == RUNMODE_EMPTY ) and len( g_ZipFilename ) == 0 and not bPrompt:
  736. print >>sys.stderr, "\n*** file parameter is required in restore/empty mode ***\n\n\n"
  737. ShowUsage()
  738. sys.exit( 1 )
  739. if len(g_Changelist) and g_bBinsOnly and g_RunMode == RUNMODE_BACKUP:
  740. print >>sys.stderr, "\n*** -c and --bins are mutally exclusive ***\n\n\n"
  741. ShowUsage()
  742. sys.exit( 1 )
  743. if ( len( g_IncludeGlobPatterns ) or len( g_ExcludeGlobPatterns ) ) and g_RunMode != RUNMODE_BACKUP:
  744. print >>sys.stderr, "\n*** -i/-e apply only in backup mode ***\n\n\n"
  745. ShowUsage()
  746. sys.exit(1)
  747. g_Settings = LoadSettingsFile( g_SettingsFilename )
  748. # Auto-fill g_Changelist if necessary.
  749. AutoFillChangelistArray()
  750. if g_RunMode == RUNMODE_BACKUP:
  751. #
  752. # Get the string for the changelist.
  753. #
  754. sDate = datetime.datetime.now().strftime("%Y-%m-%d__%Hh-%Mm-%Ss")
  755. if g_Changelist[0] == 'default':
  756. g_ChangelistComment = 'vcodepickle: ' + os.path.basename(g_ZipFilename)
  757. g_DefaultFilename = 'VCP_' + sDate + '.zip'
  758. else:
  759. g_ChangelistComment = GetChangelistComment( g_Changelist[0] )
  760. partOfChangelistComment = GetReadableChangelistComment( g_ChangelistComment )
  761. g_DefaultFilename = 'VCP_' + partOfChangelistComment + '_' + sDate + '.zip'
  762. #
  763. # Generate a default zip filename if they didn't specify one.
  764. #
  765. # Either we'll use a mixture of the current date and their changelist text
  766. # and if it was launched from a Perforce tool (used the -ui) parameter,
  767. # we'll let them specify where to save it (and default to \\fileserver\user\username\[the date and the changelist comment])
  768. #
  769. if bPrompt == True:
  770. ShowUIForFilename()
  771. else:
  772. #
  773. # Non-ui mode. Just use what they entered on the command line or use the default if they didn't enter anything.
  774. #
  775. if len( g_ZipFilename ) == 0:
  776. SetZipFilename( g_DefaultFilename )
  777. g_ClientInfo = p4helpers.GetClientInfo()
  778. # Get the root directory without a trailing slash.
  779. g_ClientRoot = g_ClientInfo['Root']
  780. if g_ClientRoot[-1:] == '\\' or g_ClientRoot[-1:] == '/':
  781. g_ClientRoot = g_ClientRoot[:-1]
  782. if g_bVerbose:
  783. print "\n---- vcodepickle ----"
  784. print "zipfile : " + g_ZipFilename
  785. print "p4 client root : " + g_ClientRoot
  786. if g_bAutoRevert == True:
  787. print "-revert specified"
  788. print "-----------------------"
  789. print ""
  790. if g_RunMode == RUNMODE_BACKUP:
  791. DoBackup( g_ClientRoot, g_ZipFilename, g_bAutoRevert )
  792. if g_RunMode == RUNMODE_EMPTY:
  793. DoEmptyPickle( g_ZipFilename )
  794. elif g_RunMode == RUNMODE_RESTORE:
  795. if bPrompt:
  796. username = os.environ['username']
  797. g_ZipFilename = PromptForOpenZipFilename( g_ClientRoot, DefaultPickleDirName(username) )
  798. if g_ZipFilename != None:
  799. DoRestore( g_ClientRoot, g_ZipFilename )
  800. else:
  801. DoRestore( g_ClientRoot, g_ZipFilename )
  802. if g_bCreateDiff:
  803. print "\n\nPreparing diff...\n"
  804. username = os.environ['username']
  805. if g_DiffDir == '':
  806. g_DiffDir = DefaultPickleDiffName(username)
  807. if bPrompt and ( g_RunMode != RUNMODE_BACKUP ): # If RUNMODE_BACKUP, then g_ZipFilename is already set to a valid thing.
  808. g_ZipFilename = PromptForOpenZipFilename( g_ClientRoot, DefaultPickleDirName(username) )
  809. if g_ZipFilename != None:
  810. DoDiff( g_ZipFilename, g_DiffDir )
  811. else:
  812. DoDiff( g_ZipFilename, g_DiffDir )
  813. if __name__ == '__main__':
  814. main()