#! /usr/bin/env python # # Copyright 2011 - 2019 by Ben Key # # Redistribution and use in source and binary forms, with or without modification, # are permitted provided that the following conditions are met: # # 1. Redistributions of source code must retain the above copyright notice, this # list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright notice, # this list of conditions and the following disclaimer in the documentation and/or # other materials provided with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED # WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF # MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO # EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT # OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING # IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY # OF SUCH DAMAGE. # import getopt import locale import os import stat import sys # Add script directory to sys.path. # This is complicated due to the fact that __file__ is not always defined. # # Note: This must be done before importing p4clean_utils. def GetScriptFile(): """Obtains the full path and file name of the Python script.""" if (hasattr(GetScriptFile, "file")): return GetScriptFile.file ret = "" try: # The easy way. Just use __file__. # Unfortunately, __file__ is not available when cx_freeze is used or in IDLE. ret = os.path.realpath(__file__) except NameError: # The hard way. if (len(sys.argv) > 0 and len(sys.argv[0]) > 0 and os.path.isabs(sys.argv[0])): ret = os.path.realpath(sys.argv[0]) else: ret = os.path.realpath(inspect.getfile(GetScriptFile)) if (not os.path.exists(ret)): # If cx_freeze is used the value of the ret variable at this point is in # the following format: {PathToExeFile}\{NameOfPythonSourceFile}. This # makes it necessary to strip off the file name to get the correct path. ret = os.path.dirname(ret) GetScriptFile.file = ret return GetScriptFile.file def GetScriptDirectory(): """Obtains the path to the directory containing the script.""" if (hasattr(GetScriptDirectory, "dir")): return GetScriptDirectory.dir module_path = GetScriptFile() GetScriptDirectory.dir = os.path.dirname(module_path) return GetScriptDirectory.dir sys.path.insert(0, GetScriptDirectory()) import p4clean_utils __version__ = 1.13 __doc__ = """ p4cleandir version %(version)s, Copyright 2011 - 2019 by Ben Key. p4cleandir is a simple utility designed to remove all files from a Perforce client directory that are not included in the Perforce depot. The Perforce client directory to be cleaned can either be specified via the first command line argument or, if no directory is specified, the current working directory will be used. Usage: p4cleandir.py [options] [directory] Options: --version, --VERSION, -v, -V Print version information and exit successfully. --help, --HELP, -h, -H, -? Print this help message and exit successfully. --list, --LIST, -l, -L List the files only, do not delete them. --client, --CLIENT, -c, -C {lient_name} The name of the Perforce client that you wish to clean. This parameter is useful if you use multiple Perforce clients on one system. """ __version_info__ = """ p4cleandir version %(version)s, Copyright 2011 - 2019 by Ben Key. p4cleandir is a simple utility designed to remove all files from a Perforce client directory that are not included in the Perforce depot. p4cleandir comes with ABSOLUTELY NO WARRANTY. This program is free software; your freedom to use, change and distribute this program is covered by the 2-Clause BSD License. """ def usage(): """Prints the usage information for p4cleandir.""" print(__doc__ % {'version' : __version__}) def version(): """Prints the version information for p4cleandir.""" print(__version_info__ % {'version' : __version__}) def IsDirectoryInPerforce(workspace, dir): """Determines whether or not the specified directory is included in the Perforce depot.""" codePage = p4clean_utils.GetCurrentCodepage()() commandString = "p4 -s fstat \"%s\\...\"" % (dir,) if (workspace != None): commandString = "p4 -c %s -s fstat \"%s\\...\"" % (workspace, dir) command = p4clean_utils.Command(commandString).run(False) if 0 == command.returncode: output = command.output.decode(codePage) errorPos = output.find("error:") if -1 != errorPos: return False return True def CleanDirectory(workspace, dir, ignoreList, listOnly): """Iterates through the specified directory recursively finding all files that are not included in the Perforce depot. Returns a list of the files that were found. If listOnly is false, the files that are not included in the Perforce depot will be deleted as well.""" codePage = p4clean_utils.GetCurrentCodepage()() dirInPerforce = IsDirectoryInPerforce(workspace, dir) filesNotInDepot = [] for item in os.listdir(dir): if p4clean_utils.IsFileInIgnorelist(item, ignoreList): continue fileName = os.path.join(dir, item) if os.path.isfile(fileName): if dirInPerforce: if os.access(fileName, os.W_OK): commandString = "p4 -s fstat \"%s\"" % (fileName,) if (workspace != None): commandString = "p4 -c %s -s fstat \"%s\"" % (workspace, fileName) command = p4clean_utils.Command(commandString).run(False) if 0 == command.returncode: output = command.output.decode(codePage) errorPos = output.find("error:") deleteActionPos = output.find("headAction delete") if -1 != errorPos or -1 != deleteActionPos: filesNotInDepot.append(fileName) if listOnly: print("Found non depot file: %s" % (fileName,)) else: print("Removing file: %s" % (fileName,)) try: os.chmod(fileName, stat.S_IWRITE) os.remove(fileName) except PermissionError: pass else: filesNotInDepot.append(fileName) if listOnly: print("Found non depot file: %s" % (fileName,)) else: print("Removing file: %s" % (fileName,)) os.chmod(fileName, stat.S_IWRITE) os.remove(fileName) else: filesNotInDepot.extend(CleanDirectory(workspace, fileName, ignoreList, listOnly)) if False == dirInPerforce and False == listOnly: print("Removing directory: %s" % (dir,)) try: os.rmdir(dir) except OSError: pass return filesNotInDepot def main(argv = sys.argv): """The main entry point for p4cleandir.""" p4clean_utils.InitLocale() parser = p4clean_utils.CommandLineArgumentsParser(argv) parser.parse_command_line() if parser.should_display_usage(): usage() sys.exit(0) elif parser.should_display_version(): version() sys.exit(0) listOnly = parser.should_list_only() workspaceName = parser.get_client() dir = os.getcwd() if 0 != len(parser.get_args()): dir = parser.get_args()[0] ignoreList = p4clean_utils.LoadIgnoreList(dir) try: filesNotInDepot = CleanDirectory(workspaceName, dir, ignoreList, listOnly) if (0 == len(filesNotInDepot)): sys.exit(1) sys.exit(0) except KeyboardInterrupt: sys.exit(2) if __name__ == "__main__": sys.exit(main(sys.argv))