diff --git a/p4RemoveUnversioned.py b/p4RemoveUnversioned.py index bb8805c..28bae51 100644 --- a/p4RemoveUnversioned.py +++ b/p4RemoveUnversioned.py @@ -11,7 +11,7 @@ # todo: buffer output, after exceeding a certain amount print to the output. # 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 import ntpath @@ -80,6 +80,13 @@ def match_in_ignore_list( path, ignore_list ): def call_process( args ): 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 def get_str_from_process_stdout( line ): if use_bytearray_str_conversion: @@ -90,6 +97,33 @@ def get_str_from_process_stdout( line ): def singular_pulural( val, singular, 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: # 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 @@ -115,10 +149,33 @@ def get_client_set( 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 +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 ): def __init__( self, *args ): list.__init__( self, args ) @@ -212,18 +269,18 @@ class Console( threading.Thread ): self.queue.task_done( ) -class Task( threading.Event ): - def __init__( data, cmd = None ): - threading.Event.__init__( self ) +# class Task( threading.Event ): + # def __init__( data, cmd = None ): + # threading.Event.__init__( self ) - self.cmd = cmd if cmd is None MSG.RUN_FUNCTION - self.data = data + # self.cmd = cmd if cmd is None MSG.RUN_FUNCTION + # self.data = data - def isDone( self ): - return self.isSet() + # def isDone( self ): + # return self.isSet() - def join( self ): - self.wait( ) + # def join( self ): + # self.wait( ) class Worker( threading.Thread ): def __init__( self, console, queue, files_to_ignore ): @@ -257,6 +314,8 @@ class Worker( threading.Thread ): self.queue.task_done( ) def main( args ): + start = time.clock() + # check requirements if call_process( 'p4 -V' ) != 0: 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( "-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( "-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( ) ) + # 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 # Key is the file root, the value is the table of file regexes for that directory. files_to_ignore = PDict() @@ -285,11 +393,13 @@ def main( args ): with Console( auto_flush_num=20, auto_flush_time=1000 ) as c: 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. 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: 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. if not options.quiet: - c.writeflush( "Checking " + directory) + c.writeflush( "\nChecking " + directory) for root, dirs, files in os.walk( directory ): ignore_list = get_ignore_list( root, files_to_ignore ) @@ -346,21 +456,35 @@ def main( args ): if match_in_ignore_list( path, ignore_list ): # add option of using send2trash if not options.quiet: - c.write( "| ignoring " + d ) + c.write( "| ignoring " + path ) dirs.remove( d ) try: os.rmdir(path) remove_dir_count += 1 if not options.quiet: - c.write( "| " + d + " was removed." ) + c.write( "| " + path + " was removed." ) except OSError: # Fails on non-empty directory pass if not options.quiet: 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: + 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 = "\nRemoved " + str( remove_file_count ) + singular_pulural( remove_file_count, " file, ", " files, " ) + 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") if warning_count > 0: @@ -368,7 +492,11 @@ def main( args ): if error_count > 0: 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__": try: