#! /usr/bin/env python # # Copyright 2011-2014 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. # 3. The name of the author may not be used to endorse or promote products derived # from this software without specific prior written permission. # # 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 if "nt" == os.name.lower(): import win32api import win32console import p4clean_utils __version__ = 1.6 __doc__ = """ p4clean version %(version)s, Copyright 2011-2014 by Ben Key. p4clean is a simple utility designed to remove all files from a Perforce client root directory that are not included in the Perforce depot. The directory that is to be cleaned is obtained using the p4 client command. The directory is obtained from the Root entry in the output provided by running "p4 client -o." Usage: p4clean.py [options] 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__ = """ p4clean version %(version)s, Copyright 2011-2014 by Ben Key. p4clean is a simple utility designed to remove all files from a Perforce client area that are not included in the Perforce depot. p4clean 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 p4clean.""" print(__doc__ % {'version' : __version__}) def version(): """Prints the version information for p4clean.""" print(__version_info__ % {'version' : __version__}) class PerforceWorkspaceCleaner: """A class the removes all files from a Perforce client area that are not included in the Perforce depot.""" def __init__(self, workspaceName): self.__foundP4 = p4clean_utils.findP4() self.__workspaceName = workspaceName self.__workspaceRoot = None self.__haveFilesList = [] self.__perforceDirSet = set() def __IsDirectoryInSet(self, dir, dirSet): """Determines whether or not the specified directory is included in the set.""" dirInSet = True dirLower = dir.lower() if not dirLower in dirSet: dirInSet = False for item in dirSet: if 0 == item.find(dirLower): dirInSet = True break return dirInSet def __IsFileInHaveFileList(self, fileName, haveFileList): """Determines whether or not the specified file is included in the have file list.""" ret = True fileNameLower = fileName.lower() if not fileNameLower in haveFileList: ret = False return ret def __CleanDirectory(self, dir, filesNotToDelete, listOnly): """Iterates through the specified directory recursively finding all files that are not included in the list of Perforce client files obtained using the GetHaveFilesList callable object that are not found in the filesNotToDelete list. Returns a list of the files that were found. If listOnly is false, the files that are not included in the Perforce client files list that are not found in the filesNotToDelete list will be deleted as well.""" filesNotInDepot = [] dirInPerforce = self.__IsDirectoryInSet(dir, self.__perforceDirSet) if False == dirInPerforce: print("Directory '%s' not in Perforce client." % (dir, )) for item in os.listdir(dir): if item.lower() in filesNotToDelete: continue fileName = os.path.join(dir, item) if os.path.isfile(fileName): if os.access(fileName, os.W_OK): if False == dirInPerforce or False == self.__IsFileInHaveFileList(fileName, self.__haveFilesList): 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.extend(self.__CleanDirectory(fileName, filesNotToDelete, listOnly)) if False == dirInPerforce and False == listOnly: print("Removing directory: %s" % (dir, )) try: os.rmdir(dir) except OSError: pass return filesNotInDepot def Clean(self, workspace, filesNotToDelete, listOnly): """This function does the work of obtaining the Perforce client directory and calling CleanDirectory to recursively clean that directory.""" if (False == self.__foundP4): print("p4 not found.") return [] self.__workspaceRoot = p4clean_utils.GetPerforceWorkspaceRoot(self.__workspaceName) if None == self.__workspaceRoot: print("Failed to get the Perforce client root.") return [] self.__haveFilesList, self.__perforceDirSet = p4clean_utils.GetPerforceHaveFilesList( self.__workspaceName, self.__workspaceRoot) if (0 == len(self.__haveFilesList) or 0 == len(self.__perforceDirSet)): print("Failed to get the Perforce Have Files List.") return [] filesNotInDepot = self.__CleanDirectory(self.__workspaceRoot, filesNotToDelete, listOnly) return filesNotInDepot def main(argv = sys.argv): """The main entry point for p4clean.""" 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) filesNotToDelete = set() filesNotToDelete.add("devenvw.ini") filesNotToDelete.add("p4-set.bat") p4config = os.getenv("P4CONFIG") if None != p4config: filesNotToDelete.add(p4config.lower()) # TODO: Consider adding a mechanism for allowing the user to specify other files # that should not be deleted. workspaceName = parser.get_workspace() listOnly = parser.should_list_only() workspaceCleaner = PerforceWorkspaceCleaner(workspaceName) filesNotInDepot = workspaceCleaner.Clean(workspaceName, filesNotToDelete, listOnly) if (0 == len(filesNotInDepot)): sys.exit(1) sys.exit(0) if __name__ == "__main__": sys.exit(main(sys.argv))