Adding huge improvements. There are still a few more to make to account for computers not setup correctly, but it's functional. Still has the occasional console hang bug. Now also prints out run time. There is one new minor bug, reverting back to the previously set client view.
This commit is contained in:
parent
6236ead338
commit
06b0cbe426
|
@ -11,7 +11,7 @@
|
||||||
# todo: buffer output, after exceeding a certain amount print to the output.
|
# todo: buffer output, after exceeding a certain amount print to the output.
|
||||||
# todo: allow logging output besides console output, or redirection altogether
|
# todo: allow logging output besides console output, or redirection altogether
|
||||||
|
|
||||||
import datetime, inspect, multiprocessing, optparse, os, re, stat, subprocess, sys, threading, traceback
|
import datetime, inspect, marshal, multiprocessing, optparse, os, re, stat, subprocess, sys, threading, time, traceback
|
||||||
|
|
||||||
# trying ntpath, need to test on linux
|
# trying ntpath, need to test on linux
|
||||||
import ntpath
|
import ntpath
|
||||||
|
@ -80,6 +80,13 @@ def match_in_ignore_list( path, ignore_list ):
|
||||||
def call_process( args ):
|
def call_process( args ):
|
||||||
return subprocess.call( args.split( ), stdout=subprocess.PIPE, stderr=subprocess.PIPE )
|
return subprocess.call( args.split( ), stdout=subprocess.PIPE, stderr=subprocess.PIPE )
|
||||||
|
|
||||||
|
def try_call_process( args, path=None ):
|
||||||
|
try:
|
||||||
|
subprocess.check_output( args.split( ), shell=False, cwd=path )
|
||||||
|
return 0
|
||||||
|
except subprocess.CalledProcessError:
|
||||||
|
return 1
|
||||||
|
|
||||||
use_bytearray_str_conversion = type( b"str" ) is not str
|
use_bytearray_str_conversion = type( b"str" ) is not str
|
||||||
def get_str_from_process_stdout( line ):
|
def get_str_from_process_stdout( line ):
|
||||||
if use_bytearray_str_conversion:
|
if use_bytearray_str_conversion:
|
||||||
|
@ -90,6 +97,33 @@ def get_str_from_process_stdout( line ):
|
||||||
def singular_pulural( val, singular, plural ):
|
def singular_pulural( val, singular, plural ):
|
||||||
return singular if val == 1 else plural
|
return singular if val == 1 else plural
|
||||||
|
|
||||||
|
def parse_info_from_command( args, value, path = None ):
|
||||||
|
"""
|
||||||
|
|
||||||
|
:rtype : string
|
||||||
|
"""
|
||||||
|
proc = subprocess.Popen( args.split( ), stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=path )
|
||||||
|
for line in proc.stdout:
|
||||||
|
line = get_str_from_process_stdout( line )
|
||||||
|
|
||||||
|
if not line.startswith( value ):
|
||||||
|
continue
|
||||||
|
return line[ len( value ) : ].strip( )
|
||||||
|
return None
|
||||||
|
|
||||||
|
def get_p4_py_results( args, path = None ):
|
||||||
|
results = []
|
||||||
|
proc = subprocess.Popen( [ 'p4', '-G' ] + args.split( ), stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=path )
|
||||||
|
try:
|
||||||
|
while True:
|
||||||
|
output = marshal.load( proc.stdout )
|
||||||
|
results.append( output )
|
||||||
|
except EOFError:
|
||||||
|
pass
|
||||||
|
finally:
|
||||||
|
proc.stdout.close()
|
||||||
|
return results
|
||||||
|
|
||||||
# Keep these in mind if you have issues:
|
# Keep these in mind if you have issues:
|
||||||
# https://stackoverflow.com/questions/16557908/getting-output-of-a-process-at-runtime
|
# https://stackoverflow.com/questions/16557908/getting-output-of-a-process-at-runtime
|
||||||
# https://stackoverflow.com/questions/4417546/constantly-print-subprocess-output-while-process-is-running
|
# https://stackoverflow.com/questions/4417546/constantly-print-subprocess-output-while-process-is-running
|
||||||
|
@ -115,10 +149,33 @@ def get_client_set( path ):
|
||||||
|
|
||||||
files.add( local_path )
|
files.add( local_path )
|
||||||
|
|
||||||
# TODO: check error to see if the path is not in the client view. Prompt anyway?
|
proc.wait( )
|
||||||
|
|
||||||
|
for line in proc.stderr:
|
||||||
|
raise Exception(line)
|
||||||
|
|
||||||
return files
|
return files
|
||||||
|
|
||||||
|
def get_client_root( ):
|
||||||
|
"""
|
||||||
|
|
||||||
|
:rtype : string
|
||||||
|
"""
|
||||||
|
command = "p4 info"
|
||||||
|
|
||||||
|
proc = subprocess.Popen( command.split( ), stdout=subprocess.PIPE, stderr=subprocess.PIPE )
|
||||||
|
for line in proc.stdout:
|
||||||
|
line = get_str_from_process_stdout( line )
|
||||||
|
|
||||||
|
clientFile_tag = "Client root: "
|
||||||
|
if not line.startswith( clientFile_tag ):
|
||||||
|
continue
|
||||||
|
|
||||||
|
local_path = normpath( line[ len( clientFile_tag ) : ].strip( ) )
|
||||||
|
|
||||||
|
return local_path
|
||||||
|
return None
|
||||||
|
|
||||||
class PTable( list ):
|
class PTable( list ):
|
||||||
def __init__( self, *args ):
|
def __init__( self, *args ):
|
||||||
list.__init__( self, args )
|
list.__init__( self, args )
|
||||||
|
@ -212,18 +269,18 @@ class Console( threading.Thread ):
|
||||||
|
|
||||||
self.queue.task_done( )
|
self.queue.task_done( )
|
||||||
|
|
||||||
class Task( threading.Event ):
|
# class Task( threading.Event ):
|
||||||
def __init__( data, cmd = None ):
|
# def __init__( data, cmd = None ):
|
||||||
threading.Event.__init__( self )
|
# threading.Event.__init__( self )
|
||||||
|
|
||||||
self.cmd = cmd if cmd is None MSG.RUN_FUNCTION
|
# self.cmd = cmd if cmd is None MSG.RUN_FUNCTION
|
||||||
self.data = data
|
# self.data = data
|
||||||
|
|
||||||
def isDone( self ):
|
# def isDone( self ):
|
||||||
return self.isSet()
|
# return self.isSet()
|
||||||
|
|
||||||
def join( self ):
|
# def join( self ):
|
||||||
self.wait( )
|
# self.wait( )
|
||||||
|
|
||||||
class Worker( threading.Thread ):
|
class Worker( threading.Thread ):
|
||||||
def __init__( self, console, queue, files_to_ignore ):
|
def __init__( self, console, queue, files_to_ignore ):
|
||||||
|
@ -257,6 +314,8 @@ class Worker( threading.Thread ):
|
||||||
self.queue.task_done( )
|
self.queue.task_done( )
|
||||||
|
|
||||||
def main( args ):
|
def main( args ):
|
||||||
|
start = time.clock()
|
||||||
|
|
||||||
# check requirements
|
# check requirements
|
||||||
if call_process( 'p4 -V' ) != 0:
|
if call_process( 'p4 -V' ) != 0:
|
||||||
print( 'Perforce Command-line Client(p4) is required for this script.' )
|
print( 'Perforce Command-line Client(p4) is required for this script.' )
|
||||||
|
@ -269,11 +328,60 @@ def main( args ):
|
||||||
parser.add_option( "-t", "--threads", dest="thread_count", help="Number of threads to crawl your drive and poll p4.", default=100 )
|
parser.add_option( "-t", "--threads", dest="thread_count", help="Number of threads to crawl your drive and poll p4.", default=100 )
|
||||||
parser.add_option( "-q", "--quiet", action="store_true", dest="quiet", help="This overrides verbose", default=False )
|
parser.add_option( "-q", "--quiet", action="store_true", dest="quiet", help="This overrides verbose", default=False )
|
||||||
parser.add_option( "-v", "--verbose", action="store_true", dest="verbose", default=True )
|
parser.add_option( "-v", "--verbose", action="store_true", dest="verbose", default=True )
|
||||||
|
parser.add_option( "-i", "--interactive", action="store_true", dest="interactive", default=False )
|
||||||
|
|
||||||
( options, args ) = parser.parse_args( )
|
( options, args ) = parser.parse_args( args )
|
||||||
|
|
||||||
directory = normpath( options.directory if options.directory is not None else os.getcwd( ) )
|
directory = normpath( options.directory if options.directory is not None else os.getcwd( ) )
|
||||||
|
|
||||||
|
# get user
|
||||||
|
print("\nChecking p4 info...")
|
||||||
|
result = get_p4_py_results('info')
|
||||||
|
if len(result) == 0 or b'userName' not in result[0].keys():
|
||||||
|
print("Can't find perforce info, is it even setup?")
|
||||||
|
sys.exit(1)
|
||||||
|
username = get_str_from_process_stdout(result[0][b'userName'])
|
||||||
|
client_host = get_str_from_process_stdout(result[0][b'clientHost'])
|
||||||
|
print("|Done.")
|
||||||
|
|
||||||
|
client_root = get_client_root()
|
||||||
|
ldirectory = directory.lower()
|
||||||
|
workspace_name = None
|
||||||
|
if not ldirectory.startswith(client_root.lower()):
|
||||||
|
print("\nCurrent directory not in client view, checking other workspaces for user '" + username + "' ...")
|
||||||
|
|
||||||
|
workspace_name = parse_info_from_command('p4 info', 'Client name: ')
|
||||||
|
|
||||||
|
# get user workspaces
|
||||||
|
result = get_p4_py_results('workspaces -u ' + username)
|
||||||
|
workspaces = []
|
||||||
|
for r in result:
|
||||||
|
whost = get_str_from_process_stdout(r[b'Host'])
|
||||||
|
if whost is not None and len(whost) != 0 and client_host != whost:
|
||||||
|
continue
|
||||||
|
workspace = {'root': get_str_from_process_stdout(r[b'Root']), 'name': get_str_from_process_stdout(r[b'client'])}
|
||||||
|
workspaces.append(workspace)
|
||||||
|
|
||||||
|
del result
|
||||||
|
|
||||||
|
# check current directory against current workspace, see if it matches existing workspaces.
|
||||||
|
for w in workspaces:
|
||||||
|
wname = w['name']
|
||||||
|
wlower = w['root'].lower()
|
||||||
|
if ldirectory.startswith(wlower):
|
||||||
|
# set current directory, don't forget to revert it back to the existing one
|
||||||
|
print("|Setting client view to: " + wname)
|
||||||
|
|
||||||
|
if try_call_process( 'p4 set P4CLIENT=' + wname ):
|
||||||
|
print("|There was a problem trying to set the p4 client view (workspace).")
|
||||||
|
sys.exit(1)
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
print( "|Couldn't find a workspace root that matches the current directory for the current user." )
|
||||||
|
sys.exit(1)
|
||||||
|
print("|Done.")
|
||||||
|
|
||||||
|
|
||||||
# Files are added from .p4ignore
|
# Files are added from .p4ignore
|
||||||
# Key is the file root, the value is the table of file regexes for that directory.
|
# Key is the file root, the value is the table of file regexes for that directory.
|
||||||
files_to_ignore = PDict()
|
files_to_ignore = PDict()
|
||||||
|
@ -285,11 +393,13 @@ def main( args ):
|
||||||
|
|
||||||
with Console( auto_flush_num=20, auto_flush_time=1000 ) as c:
|
with Console( auto_flush_num=20, auto_flush_time=1000 ) as c:
|
||||||
if not options.quiet:
|
if not options.quiet:
|
||||||
c.writeflush( "Caching files in depot, this may take a little while..." )
|
c.writeflush( "\nCaching files in depot, this may take a little while..." )
|
||||||
|
|
||||||
# TODO: push this off to a thread and walk the directory so we get a headstart.
|
# TODO: push this off to a thread and walk the directory so we get a headstart.
|
||||||
files_in_depot = get_client_set( directory )
|
files_in_depot = get_client_set( directory )
|
||||||
|
|
||||||
|
c.writeflush( "|Done." )
|
||||||
|
|
||||||
# TODO: push a os.walk request off to a thread to build a list of files in the directory; create batch based on directory?
|
# TODO: push a os.walk request off to a thread to build a list of files in the directory; create batch based on directory?
|
||||||
|
|
||||||
# TODO: at this point join on both tasks to wait until they're done
|
# TODO: at this point join on both tasks to wait until they're done
|
||||||
|
@ -299,7 +409,7 @@ def main( args ):
|
||||||
# will at least need to redo how the ignore lists are handled for efficiencies sake.
|
# will at least need to redo how the ignore lists are handled for efficiencies sake.
|
||||||
|
|
||||||
if not options.quiet:
|
if not options.quiet:
|
||||||
c.writeflush( "Checking " + directory)
|
c.writeflush( "\nChecking " + directory)
|
||||||
for root, dirs, files in os.walk( directory ):
|
for root, dirs, files in os.walk( directory ):
|
||||||
ignore_list = get_ignore_list( root, files_to_ignore )
|
ignore_list = get_ignore_list( root, files_to_ignore )
|
||||||
|
|
||||||
|
@ -346,21 +456,35 @@ def main( args ):
|
||||||
if match_in_ignore_list( path, ignore_list ):
|
if match_in_ignore_list( path, ignore_list ):
|
||||||
# add option of using send2trash
|
# add option of using send2trash
|
||||||
if not options.quiet:
|
if not options.quiet:
|
||||||
c.write( "| ignoring " + d )
|
c.write( "| ignoring " + path )
|
||||||
dirs.remove( d )
|
dirs.remove( d )
|
||||||
try:
|
try:
|
||||||
os.rmdir(path)
|
os.rmdir(path)
|
||||||
remove_dir_count += 1
|
remove_dir_count += 1
|
||||||
if not options.quiet:
|
if not options.quiet:
|
||||||
c.write( "| " + d + " was removed." )
|
c.write( "| " + path + " was removed." )
|
||||||
except OSError:
|
except OSError:
|
||||||
# Fails on non-empty directory
|
# Fails on non-empty directory
|
||||||
pass
|
pass
|
||||||
if not options.quiet:
|
if not options.quiet:
|
||||||
c.write( "|Done." )
|
c.write( "|Done." )
|
||||||
|
|
||||||
|
# This needs to happen automatically even when an exception happens, when we leave scope.
|
||||||
|
if workspace_name is not None:
|
||||||
|
c.write("\nReverting back to original client view...")
|
||||||
|
# set workspace back to the original one
|
||||||
|
if try_call_process( 'p4 set P4CLIENT=' + workspace_name ):
|
||||||
|
error_count += 1
|
||||||
if not options.quiet:
|
if not options.quiet:
|
||||||
output = "\nRemoved " + str( remove_file_count ) + singular_pulural( remove_file_count, " file, ", " files, " )
|
c.write("|There was a problem trying to restore the set p4 client view (workspace).")
|
||||||
|
else:
|
||||||
|
if not options.quiet:
|
||||||
|
c.write("|Reverted client view back to '" + workspace_name + "'.")
|
||||||
|
if not options.quiet:
|
||||||
|
c.write("|Done.")
|
||||||
|
|
||||||
|
if not options.quiet:
|
||||||
|
output = "\n[ Removed " + str( remove_file_count ) + singular_pulural( remove_file_count, " file, ", " files, " )
|
||||||
output += str( remove_dir_count ) + singular_pulural( remove_dir_count, " directory", " directories")
|
output += str( remove_dir_count ) + singular_pulural( remove_dir_count, " directory", " directories")
|
||||||
|
|
||||||
if warning_count > 0:
|
if warning_count > 0:
|
||||||
|
@ -368,7 +492,11 @@ def main( args ):
|
||||||
if error_count > 0:
|
if error_count > 0:
|
||||||
output += " w/ " + str( error_count ) + singular_pulural( error_count, " error", " errors" )
|
output += " w/ " + str( error_count ) + singular_pulural( error_count, " error", " errors" )
|
||||||
|
|
||||||
c.write( output + "." )
|
end = time.clock()
|
||||||
|
delta = end - start
|
||||||
|
output += ", and finished in " + str(delta) + "s"
|
||||||
|
|
||||||
|
c.write( output + " ]" )
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
try:
|
try:
|
||||||
|
|
Loading…
Reference in New Issue