p4scripts/p4RemoveUnversioned.py
Brian 266c5555ba Improved crawling speed, also cleaned up the output.
So the speed of the script is much faster than before, though it
actually still has much room for improvement, it will just be more
complicated.  Calling 'p4 fstat' on the entire directory will give you
everything you need up front, it's just they're in depot paths, which
makes thing a little annoying to parse when you have workspace mappings
that move things around so the local path may differ from the depot
path, and it becomes harder to determine 100% that you're referring to
the same file.  And I don't want to have to call p4 on every file to be
sure of that, what I'm doing now is the easiest safest way to be sure of
that, as far as I know.

Another way to speed this up is to add thread crawlers, I'm just not yet
sure with HDDs and SSDs how many threads is a good idea to use.
2014-05-08 19:05:07 -06:00

153 lines
No EOL
4.9 KiB
Python

#!/usr/bin/python
# -*- coding: utf8 -*-
# todo: have a backup feature, make sure files are moved to the recycle bin or a temporary file.
# todo: switch to faster method of calling p4 fstat on an entire directory and parsing it's output
# todo: add option of using send2trash
import inspect, os, re, subprocess, sys, traceback
re_remove_comment = re.compile( "#.*$" )
def remove_comment( s ):
return re.sub( re_remove_comment, "", s )
try: input = raw_input
except: pass
def PressEnter( ):
print( "\nPress ENTER to continue..." )
s=input( "" )
def main( ):
# check requirement
if os.system( 'p4 > Nul' ) != 0:
print 'Perforce Command-line Client(p4) is required for this script.'
sys.exit( 1 )
# Files are added from .p4ignore
# Key is the file root, the value is the table of file regexes for that directory.
files_to_ignore = {}
def get_ignore_list( path ):
# have to split path and test top directory
dirs = path.split( os.sep )
ignore_list = [ ]
for i, val in enumerate( dirs ):
path_to_find = os.sep.join( dirs[ : i + 1] )
if path_to_find in files_to_ignore:
ignore_list = ignore_list + files_to_ignore[ path_to_find ]
# ignore_list = [ r for p in [ os.sep.join( dirs[ : i + 1] ) for i, val in enumerate( dirs ) ] if p in files_to_ignore for r in files_to_ignore[ p ] ]
return ignore_list
def match_in_ignore_list( path, ignore_list ):
for r in ignore_list:
if re.match( r, path ):
return True
return False
root = "."
root_full_path = os.getcwd()
p4_ignore = ".p4ignore"
# make sure script doesn't delete itself
files_to_ignore[ root ] = [ re.compile( os.path.join( re.escape( root + os.sep ), os.path.basename( __file__ ) ) ) ]
for root, dirs, files in os.walk( root ):
print ( os.linesep + "Checking '" + root + "' ...")
if p4_ignore in files:
file_regexes = []
# Should automatically ignore .p4ignore even if it's not specified, otherwise it'll be deleted.
path = os.path.join( root, p4_ignore )
with open( path ) as f:
for line in f:
new_line = remove_comment( line.strip( ) )
if len( new_line ) > 0:
file_regexes.append( re.compile( os.path.join( re.escape( root + os.sep ), new_line ) ) )
print( "|appending ignores from " + path )
files_to_ignore[ root ] = file_regexes
ignore_list = get_ignore_list( root )
#command = "p4 have \"" + root + os.sep + "*\""
command = "p4 fstat *"
print("|" + command)
os.chdir( root )
proc = subprocess.Popen( command, stdout=subprocess.PIPE, stderr=subprocess.PIPE )
(out, err) = proc.communicate()
os.chdir( root_full_path )
# For ease we're doing a weird solution, rebuilding the file list. This is so we only need to parse unadded files.
files = []
for line in err.split( os.linesep ):
if len(line) == 0:
continue
# # dirty hack that grabs the filename from the ends of the printed out (not err) "depo_path - local_path"
# # I could use regex to verify the expected string, but that will just slow us down.
# basename = os.path.basename( line )
i = line.rfind( ' - ')
if i >= 0:
basename = line[ : i ]
path = os.path.join( root, basename )
if not os.path.isdir( path ):
for file in files:
if file == basename:
files.append( file )
break
for file in files:
path = os.path.join( root, file )
if match_in_ignore_list( path, ignore_list ):
print( "| ignoring " + path )
continue
print( "| " + file + " is unversioned, removing it." )
os.remove( path )
dirs_copy = dirs
for d in dirs_copy:
path = os.path.join( root, d )
if match_in_ignore_list( path, ignore_list ):
# add option of using send2trash
print( "| ignoring " + d )
dirs.remove( d )
print( "|Done." )
print( os.linesep + "Removing empty directories...")
# remove empty directories
for root, dirs, files in os.walk( '.', topdown=False ):
for d in dirs:
try:
os.rmdir(d)
print( "|" + d + " was removed." )
except OSError:
# Fails on non-empty directory
pass
print( "|Done." )
if __name__ == "__main__":
try:
main( )
except:
print( "Unexpected error!" )
traceback.print_exc( file = sys.stdout )
PressEnter()