Advertisement
Guest User

Untitled

a guest
Apr 9th, 2018
97
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 22.68 KB | None | 0 0
  1. __author__ = "EmilClaeson"
  2.  
  3. import sys
  4. import os
  5. import subprocess
  6. import re
  7. import time
  8. import datetime
  9. import os.path, time
  10.  
  11. Regex_FileInfo = re.compile('([^#]+)#(\d+) - (\S+) (\S+ \S+) \((.+)\)') # <FILENAME>, <REV>, <OP>, <CHANGELIST>, <TYPE>
  12. Regex_Change = re.compile('(default) change|change (\d+)') # <DEFAULT>, <NUMBER>
  13. Regex_Created = re.compile('Change (\d+) created.') # <CHANGELIST>
  14. Regex_FileLog = re.compile('#(\d+) change (\d+) (\S+) on (\S+) (\S+) by (\S+) \((.+)\) \'(.*)\'') # <REV>, <CHANGELIST>, <DATE>, <TIME>, <CLIENT>, <TYPE>, <DESC>
  15. Regex_Where_Template = '(//.+) //.+ (__ROOT__.+)$' # <IGNORE>, <DEPOT>, <LOCAL>
  16. Regex_Have = re.compile('([^#]+)#(\d+) - (.+)') # <DEPOT>, <REV>, <LOCAL>
  17. Regex_Sizes = re.compile('([^#]+)#\d+ (\d+) bytes') # <DEPOT>, <SiZE>
  18. Regex_Sync = re.compile('([^#]+)#(\d+) - (.+)') # <DEPOT>, <REV>, <OP + LOCAL>
  19. Regex_Changes = re.compile('Change (\d+) on (\S+) (\S+) by (\S+)\s*(\S+)?') # <CHANGELIST>, <DATE>, <TIME>, <CLIENT>, <STATUS>
  20. Regex_Describe = re.compile('([^#]+)#(\d+) (\S+)') # <DEPOT>, <REV>, <OP>
  21.  
  22. class LogEntry:
  23. def __init__(self, Entry):
  24. Revision, ChangeList, Operation, Date, Time, Client, Type, Description = Entry
  25. self.Revision = int(Revision)
  26. self.ChangeList = int(ChangeList)
  27. self.Operation = Operation
  28. self.Date = Date
  29. self.Time = Time
  30. self.Client = Client
  31. self.Type = Type
  32. self.Description = Description
  33. DateTime = parse_datetime( '%s %s' % (Date, Time) )
  34. self.TimeStamp = time.mktime( DateTime.timetuple() )
  35.  
  36. def DebugLog(Message):
  37. if os.environ.get('P4DEBUG', '') == '1':
  38. print >> sys.stderr, '[P4DEBUG] %s' % Message
  39.  
  40. def RunCommand(Args, bInput = None, bEnvironment = None, bSilent = False):
  41. Command = [ 'p4' ] + Args
  42. DebugLog((' '.join(Command)))
  43. stdin = subprocess.PIPE if input else None
  44. stderr = subprocess.STDOUT if bSilent else None
  45. bEnvironment['PWD'] = os.getcwd() # fix cygwin style working directory
  46.  
  47. # stdin & stderr breaks unreal python implementation, temporary disabled
  48. #SubProcess = subprocess.Popen(Command, stdout = subprocess.PIPE, stdin = stdin, stderr = stderr, bEnvironment = bEnvironment, shell=True)
  49.  
  50. SubProcess = subprocess.Popen(Command, stdout = subprocess.PIPE, shell = True)
  51. (Output, Error) = SubProcess.communicate(input)
  52. DebugLog(SubProcess.returncode)
  53. if SubProcess.returncode != 0:
  54. raise Exception("ERROR: command '{}' running in {} exited with error status {}".format(Command, os.getcwd(), SubProcess.returncode))
  55. return Output
  56.  
  57. def ParseChangelist(Text):
  58. Match = Regex_Change.match(Text)
  59. (Default, Number) = Match.groups()
  60. return Default if Default else Number
  61.  
  62. # walk over a list of file info, returned by commands like "p4 opened" and "p4 files"
  63. def ParseFilelist(Lines):
  64. for Line in Lines:
  65. Match = Regex_FileInfo.match(Line)
  66. if not Match:
  67. raise Exception('ERROR: Could not parse output from p4 command! <<< %s >>>' % (Line))
  68. else:
  69. (Filename, Revision, Operation, Changelist, Filetype) = Match.groups()
  70. Changelist = ParseChangelist(Changelist)
  71. yield (Filename, Revision, Operation, Changelist, Filetype)
  72.  
  73. # parses perforce "Y/m/d H:M:S" and returns datetime object.
  74. def ParseDatetime(PerforceDateTime):
  75. return datetime.datetime.strptime(PerforceDateTime, '%Y/%m/%d %H:%M:%S')
  76.  
  77. # returns (PerforceUser, PerforceClient)
  78. def GetConnectionInfo():
  79. ConnectionInfo = info()
  80. PerforceUser = ConnectionInfo['User name']
  81. PerforceClient = ConnectionInfo['Client name']
  82. return (PerforceUser, PerforceClient)
  83.  
  84. class Perforce:
  85. def __init__(self, Port = None, User = None, Client = None):
  86. self.Environment = dict(os.environ)
  87. if Port: self.Environment['P4PORT'] = str(Port)
  88. if Client: self.Environment['P4CLIENT'] = str(Client)
  89. if User: self.Environment['P4USER'] = str(User)
  90. self.ValidConnection = False
  91.  
  92. try:
  93. self.Info = self.GetInfo()
  94. self.Port = self.Info['Server address']
  95. self.User = self.Info['User name']
  96. self.Client = self.Info['Client name']
  97. self.ClientMapping = ''
  98. except:
  99. return None
  100.  
  101. def IsValid(self):
  102. return self.ValidConnection
  103.  
  104. def CheckForAdd(self, FilePath):
  105. FilePath = os.path.normpath(FilePath)
  106. if os.path.isfile(FilePath) and os.access(FilePath, os.W_OK) and not [x for x in self.Have([FilePath])]:
  107. self.MarkForAdd([FilePath])
  108. return True
  109. else:
  110. self.Sync([FilePath], bForce = True)
  111. self.CheckOut(FilePath)
  112.  
  113. def CheckOutLatest(self, FilePath, bForceSync = True):
  114. FilePath = os.path.normpath(FilePath)
  115. if os.path.isfile(FilePath):
  116. for x in self.have([FilePath]):
  117. self.Sync([FilePath], bForce = bForceSync)
  118. self.CheckOut(FilePath)
  119.  
  120. def _RunCommand(self, Args, bInput = None, bSilent = False):
  121. return RunCommand(Args, bInput = bInput, bEnvironment = self.Environment, bSilent = bSilent)
  122.  
  123. def GetInfo(self):
  124. Result = {}
  125. Info = self._RunCommand(['info'])
  126. for Line in Info.splitlines():
  127. try:
  128. self.ValidConnection = True
  129. (Key, Value) = Line.split(':', 1)
  130. except ValueError:
  131. self.ValidConnection = False
  132. print "Failed to get key value pair for line %s" %Line
  133. Result[Key] = Value.strip()
  134. return Result
  135.  
  136. # return login stat (true / false)
  137. def IsLoggedIn(self):
  138. try:
  139. self._RunCommand(['client', '-o'], bSilent = True) # run dummy command
  140. return True
  141. except:
  142. return False
  143.  
  144. # run login command. if no password is given, the script will stop and wait for user input.
  145. def Login(self, Password = None):
  146. try:
  147. self._RunCommand(['login'], input = Password)
  148. return True
  149. except:
  150. return False
  151.  
  152. # returns list of (filename, revision, operation, changelist, filetype) tuples.
  153. def Opened(self, Args = ''):
  154. Output = self._RunCommand(['opened' ] + list(Args))
  155. Output = Output.splitlines()
  156. for FileList in ParseFilelist(Output):
  157. yield FileList
  158.  
  159. # returns list of (filename, revision, operation, changelist, filetype) tuples.
  160. def Files(self, Args):
  161. Output = self._RunCommand(['files'] + list(Args))
  162. Output = Output.splitlines()
  163. for x in ParseFilelist(Output):
  164. yield x
  165.  
  166. def FindFilesInDirectory(self, Args):
  167. Output = self._RunCommand(['files'] + list(Args))
  168. Output = Output.splitlines()
  169. return [x for x in ParseFilelist(Output)]
  170.  
  171. def ReOpen(self, Args):
  172. Output = self._RunCommand(['reopen'] + Args)
  173. return Output
  174.  
  175. def MarkForAdd(self, Args = '', bPreview = False):
  176. Argument = ['add']
  177. if bPreview: Argument.append('-n')
  178. Output = self._RunCommand(Argument + Args)
  179. if bPreview:
  180. if Output.find('currently opened for add') != -1:
  181. return True
  182. else:
  183. return False
  184. return Output
  185.  
  186. def Move(self, SourcePath, TargetPath):
  187. # p4 [g-opts] rename [-c change] [-f -n -k] [-t filetype] SourcePath TargetPath
  188. self.CheckOut(SourcePath)
  189. RenameArg = ['move', '-f'] + [SourcePath] + [TargetPath]
  190. Output = self._RunCommand(RenameArg)
  191. return Output
  192.  
  193. def Integrate(self, SourcePath, TargetPath):
  194. # p4 [g-opts] rename [-c change] [-f -n -k] [-t filetype] SourcePath TargetPath
  195. IntegrateArg = ['integrate'] + [SourcePath] + [TargetPath]
  196. Output = self._RunCommand(IntegrateArg)
  197. return Output
  198.  
  199. # generates list of (filepath, [LogEntry]) tuples.
  200. def FileLog(self, Files):
  201. FileInfo = self._RunCommand(['filelog', '-t'] + list(Files))
  202. Current = (None, [])
  203. for Line in FileInfo.splitlines():
  204. if Line[:8] == '... ... ':
  205. continue
  206. elif Line[:4] == '... ':
  207. Match = Regex_FileLog.match(Line[4:])
  208. if not Match:
  209. raise Exception("Could not parse filelog line: %s\n(filter: %s)\n(Files: %s)\n" % (Line, Regex_FileLog.pattern, ' '.join(Files)))
  210. Current[1].append(LogEntry(Match.groups()))
  211. else:
  212. if Current[0]:
  213. yield Current
  214. Current = (Line, [])
  215. if Current[0]:
  216. yield Current
  217.  
  218. # generates list of (depot-file, revision, local-file) tuples.
  219. def Have(self, Args = ''):
  220. Output = self._RunCommand(['have'] + list(Args))
  221. Output = Output.splitlines()
  222. for Line in Output:
  223. Match = Regex_Have.match(Line)
  224. if not Match:
  225. raise Exception('ERROR: Could not parse output from p4 command! <<< %s >>>' % (Line))
  226. else:
  227. (Depot, Revision, Local) = Match.groups()
  228. yield (Depot, Revision, Local)
  229.  
  230. # generates list of (depot-file, size) tuples.
  231. def Sizes(self, Args = ''):
  232. Output = self._RunCommand(['sizes'] + list(Args))
  233. Output = Output.splitlines()
  234. for Line in Output:
  235. Match = Regex_Sizes.match(Line)
  236. if not Match:
  237. raise Exception('ERROR: Could not parse output from p4 command! <<< %s >>>' % (Line))
  238. else:
  239. yield (Match.group(1), int(Match.group(2)))
  240.  
  241. # returns list of (depot-file, revision) tuples.
  242. def Sync(self, Args = '', bPreview = False, bForce = False, bSilent = True, Revision = None):
  243. Result = []
  244. Argument = ['sync']
  245. if bPreview:
  246. Argument.append('-n')
  247. if bForce:
  248. Argument.append('-f')
  249. if Revision != None:
  250. Args = ['%s#%d'%(x, Revision) for x in list(Args)]
  251. Argument += list(Args)
  252. Output = self._RunCommand(Argument, bSilent = bSilent)
  253. Output = Output.splitlines()
  254. for Line in Output:
  255. if not bSilent:
  256. print Line
  257. Match = Regex_Sync.match(Line)
  258. if Match:
  259. Result.append((Match.group(1), int(Match.group(2))))
  260. return Result
  261.  
  262. # open file for edit
  263. def CheckOut(self, Filepath, Changelist = None, bPreview = False, setExclusive=False):
  264. Argument = ['edit']
  265. if Changelist:
  266. Argument += ['-c', str(Changelist)]
  267. if setExclusive:
  268. Argument += ['-t', '+l']
  269. if bPreview: Argument.append('-n')
  270. Argument += [Filepath]
  271. return self._RunCommand(Argument)
  272.  
  273. # submit a changelist
  274. def Submit(self, Changelist):
  275. return self._RunCommand(['submit', '-c', str(Changelist)])
  276.  
  277. # gets the contents of a file at specific revision
  278. def GetRevision(self, Filepath, Revision):
  279. Argument = '%s#%d' % (Filepath, Revision)
  280. return self._RunCommand(['print', '-q', Argument])
  281.  
  282. def GetRevisionByDate(self, Filepath, Date):
  283. Argument = '%s@%s' % (Filepath, Date)
  284. return self._RunCommand(['print', '-q', Argument])
  285.  
  286. def Revert(self, Filepath, Changelist = None):
  287. Argument = ['revert']
  288. if Changelist:
  289. Argument += ['-c', str(Changelist)]
  290. Argument += [Filepath]
  291. return self._RunCommand(Argument)
  292.  
  293. def Resolve(self, Filepath, bPreview = True):
  294. Argument = ['resolve']
  295. if bPreview:
  296. Argument += ['-n']
  297. Argument += [Filepath]
  298. return self._RunCommand(Argument)
  299.  
  300. def IsFileInDepot(self, FilePath):
  301. try:
  302. if self.MapFile(FilePath):
  303. return True
  304. return False
  305. except:
  306. return False
  307.  
  308. def IsFileMarkedForDelete(self, FilePath):
  309. try:
  310. return self.IsMarkedForDelete(FilePath)
  311. except:
  312. return False
  313.  
  314. def IsCheckedOutByUser(self, Status):
  315. if os.path.isfile(Status):
  316. Status = self.CheckOut(Status, bPreview = True)
  317. if not Status:
  318. return False
  319. if isinstance(Status, str):
  320. if 'also opened by' in Status:
  321. return True
  322. return False
  323.  
  324. def IsCheckedOutByMe(self, Status):
  325. if os.path.isfile(Status):
  326. Status = self.CheckOut(Status, bPreview = True)
  327. if not Status:
  328. return False
  329. if isinstance(Status, str):
  330. if 'currently opened for edit' in Status:
  331. return True
  332. return False
  333.  
  334. # returns (depot_path, local_path) tuple
  335. def MapFile(self, FilePath):
  336. Regex_Where = Regex_Where_Template.replace('__ROOT__', self.Info['Client root'])
  337. Regex_Where = Regex_Where.replace('\\', '/')
  338. Line = self._RunCommand(['where', FilePath]).splitlines()[0].rstrip()
  339. Line = Line.replace('\\', '/')
  340.  
  341. # where will for some computers return a leading
  342. if Line.startswith('-'):
  343. Line = Line[1:]
  344. Match = re.match(Regex_Where, Line)
  345.  
  346. if not Match:
  347. raise Exception("could not parse: %s" % Line)
  348. return Match.groups()
  349.  
  350. # generates list of (depot_path, local_path) tuples
  351. def MapFiles(self, Files):
  352. Regex_Where = re.compile(Regex_Where_Template.replace('__ROOT__', self.Info['Client root']))
  353. Output = self._RunCommand(['where'] + list(Files))
  354. for Line in Output.splitlines():
  355. if Line[0] == '-':
  356. continue
  357. Match = Regex_Where.match(Line)
  358. if not Match:
  359. raise Exception("could not parse: %s" % Line)
  360. yield Match.groups()
  361.  
  362. # returns (depot_path, local_path) tuple for directory
  363. def MapDirectory(self, DirectoryPath):
  364. Remote, Local = self.MapFile('%s...'%DirectoryPath)
  365. return Remote.rstrip('.'), Local.rstrip('.')
  366.  
  367. def GetClientMapping(self, bForceUpdate = False):
  368. if self.ClientMapping and not bForceUpdate:
  369. return self.ClientMapping
  370. FolderPath = self.Info['Current directory']
  371. Result = self._RunCommand(['where', FolderPath])
  372. Lines = [x for x in Result.splitlines() if x]
  373. if len(Lines) > 1:
  374. Mapping = [x for x in Lines if x.find('raw') == -1]
  375. if Mapping:
  376. Mapping = Mapping[0]
  377. else:
  378. Mapping = self._RunCommand(['where', FilePath]).splitlines()[0].rstrip()
  379.  
  380. Mapping = Mapping.replace('\\', '/')
  381. if Mapping.startswith('-'):
  382. Mapping = Mapping[1:]
  383.  
  384. Regex_Where = Regex_Where_Template.replace('__ROOT__', self.Info['Client root']).replace('\\', '/')
  385. Match = re.match(Regex_Where, Mapping)
  386.  
  387. if Match:
  388. self.ClientMapping = Match.groups()[0].rsplit('/', 2)[0]
  389. return self.ClientMapping
  390. else:
  391. raise Exception("could not parse: %s" %Mapping)
  392.  
  393.  
  394. #
  395. # generates list of ((changelist, date, time, user@client, status), description) for all changelists match the given cirterias
  396. #
  397. # - status: "pending" or "submitted"
  398. # - since: date or date/time
  399. # - until: date or date/time
  400. # - path: path filter
  401. # - client: name of workspace
  402. #
  403. def Changes(self, Status = None, Since = None, Until = None, FilePath = None, Client = None):
  404. Args = ['changes', '-l', '-t']
  405. if Status:
  406. Args += ['-s', Status]
  407. if Client:
  408. Args += ['-c', Client]
  409. Filter = []
  410. if FilePath:
  411. Filter.append(FilePath)
  412. if Since:
  413. Filter.append('@%s,%s' % (Since, Until if Until else 'now'))
  414. elif Until:
  415. Filter.append('@%s' % (Until))
  416. if len(Filter):
  417. Args += [''.join(Filter)]
  418. Output = self._RunCommand(Args).splitlines()
  419. Current, Description = (None, [])
  420. for Line in Output:
  421. Line = Line.strip()
  422. if not Line:
  423. continue
  424. Match = Regex_Changes.match(Line)
  425. if Match:
  426. if Current:
  427. yield (Current, '\n'.join(Description))
  428. Current, Description = (Match.groups(), [])
  429. else:
  430. Line = unicode(Line, 'iso-8859-1')
  431. Description.append(Line)
  432. if Current:
  433. yield (Current, '\n'.join(Description))
  434.  
  435. # generates [ ((changelist, date, client, status), description) ] for all pending changelists on current client with matching description.
  436. def GetPendingChangeLists(self, Description):
  437. for Info, Description in self.Changes(Status = 'pending', Client = self.Client):
  438. if Description.find(Description) != -1:
  439. yield (Info, Description)
  440.  
  441. # returns changelist number for created changelist.
  442. def CreateChangeList(self, Description):
  443. Template = '''
  444. # A Perforce Change Specification.
  445. #
  446. # Change: The change number. 'new' on a new changelist.
  447. # Date: The date this specification was last modified.
  448. # Client: The client on which the changelist was created. Read-only.
  449. # User: The user who created the changelist.
  450. # Status: Either 'pending' or 'submitted'. Read-only.
  451. # Type: Either 'public' or 'restricted'. Default is 'public'.
  452. # Description: Comments about the changelist. Required.
  453. # Jobs: What opened jobs are to be closed by this changelist.
  454. # You may delete jobs from this list. (New changelists only.)
  455. # Files: What opened files from the default changelist are to be added
  456. # to this changelist. You may delete files from this list.
  457. # (New changelists only.)
  458.  
  459. Change: new
  460.  
  461. Client: __P4CLIENT__
  462.  
  463. User: __P4USER__
  464.  
  465. Status: new
  466.  
  467. Description:
  468. __DESCRIPTION__
  469.  
  470. Files:
  471.  
  472. '''
  473. Template = Template.lstrip()
  474. Template = Template.replace('__P4CLIENT__', self.Client)
  475. Template = Template.replace('__P4USER__', self.user)
  476. Template = Template.replace('__DESCRIPTION__', Description.replace('\n', '\n\t'))
  477.  
  478. Output = self._RunCommand(['change', '-i'], Template)
  479. Match = Regex_Created.match(Output)
  480. Changelist = int(Match.group(1))
  481. return Changelist
  482.  
  483. # generates list of files affected by a changelist [ (filepath, rev, op) ]
  484. def GetChangeListFiles(self, Changelist):
  485. Output = self._RunCommand(['describe', '-s', str(Changelist) ]).splitlines()
  486. Header = True
  487. for Line in Output:
  488. if Line[0:4] != '... ':
  489. continue
  490. Match = Regex_Describe.match(Line[4:])
  491. FileName, Revision, Operation = Match.groups()
  492. FileName = unicode(FileName, 'iso-8859-1')
  493. yield (FileName, Revision, Operation)
  494.  
  495. # returns info about a perforce user { key: value }
  496. def UserInfo(self, User):
  497. Output = self._RunCommand(['user', '-o', User]).splitlines()
  498. Result = {}
  499. for Line in Output:
  500. Line = Line.strip()
  501. if not Line or Line[0] == '#':
  502. continue
  503. (key, value) = Line.split(':', 1)
  504. Result[key.lower()] = unicode(value.strip(), 'iso-8859-1')
  505. return Result
  506.  
  507. def CheckVersion(self, FileName = ''):
  508. FileRevisions = self._RunCommand(['changes', FileName]) # Returns (changeList) (submitDate) (Submitter) (Object)
  509. if FileRevisions:
  510. print FileRevisions
  511. print 'file Exists on Perforce'
  512. else:
  513. print 'File does not exist on perforce'
  514.  
  515. def IsMarkedForDelete(self, FileName = ''):
  516. Result = self._RunCommand(['files', FileName])
  517.  
  518. if 'move/delete' in Result:
  519. DepotFile = self.mapfile(FileName)[0].replace('raw', '')
  520.  
  521. FileInfo = self._RunCommand(['filelog', DepotFile])
  522. for Line in FileInfo.splitlines():
  523. if Line[:8] == '... ... ':
  524. NewDirectory = Line.split(' ')[-1].partition('#')[0]
  525. NewDirectory = NewDirectory.replace(self.GetClientMapping(), self.GetClientRoot()).replace('/', '\\')
  526. return NewDirectory
  527.  
  528. elif 'delete' in Result:
  529. return True
  530. else:
  531. return False
  532.  
  533. def GetClientRoot(self):
  534. if self.ValidConnection:
  535. if self.Info:
  536. return self.Info['Client root'][:-1]
  537. if "PROJECTDISC" in os.environ:
  538. return os.environ["PROJECTDISC"]
  539. return 'Q:'
  540.  
  541. # TODO: Needs to be able to specify if we should include SubFolders
  542. def GetFilesInDirectory(self, FolderPath, Filter = 'fbx', bSplitBasePath = False):
  543. ClientMapping = self.GetClientMapping()
  544. ClientRoot = self.GetClientRoot()
  545.  
  546. if not FolderPath.startswith('//'):
  547. FolderPath = '%s/%s/...' %(ClientMapping, FolderPath[FolderPath.find(':')+2:])
  548. FolderPath = FolderPath.replace('\\', '/')
  549. Files = self.FindFilesInDirectory([FolderPath])
  550. FileNames = []
  551. FileRoots = []
  552.  
  553. for File in Files:
  554. Operation = File[2]
  555. FileName = File[0].replace('%23', '#')
  556. if Operation.find('delete') != -1:
  557. continue
  558.  
  559. NameSplit = FileName.rsplit('.',1)
  560. FileType = NameSplit[1] if len(NameSplit) > 1 else ''
  561. if Filter and FileType in Filter or not Filter:
  562. FilePath = '%s%s.%s' %(ClientRoot, NameSplit[0].replace(ClientMapping, '').replace('/', '\\'), Filter)
  563. if bSplitBasePath:
  564. FileNames.append(os.path.basename(FilePath))
  565. FileRoots.append(os.path.dirname(FilePath))
  566. else:
  567. FileNames.append(FilePath)
  568.  
  569. if bSplitBasePath:
  570. return FileNames, FileRoots
  571. return FileNames
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement