#!/usr/bin/python # # This is a beta script! # The author takes no responsibility for the results of using this tool. # # Motivation: # If you are short of storage space on your Perforce server because of big binary files # then sooner or later you get to consider using single head revisions. # Two things I don't like about single head revisions: # * When you integrate a file with a single head revision then it makes a physical copy # even if the file hasn't changed in the integration. So it still wastes storage space. # * There is no way to keep particular revisions if you need them (like milestone builds, etc). # Old revisions always get deleted when new revisions are checked in. # You could have a branch for each revision you need to keep (then those files would be kept # because of the behaviour I described in the previous point). But that would pollute the # integration metadata (especially if you've got a massive amount of files) and # the depot file hierarchy. # I needed a solution that # * made it possible to keep labelled revisions (important revisions are labelled anyway) # * without wasting storage space for unneeded revisions and redundant copies. # # # This script is a more sophisticated alternative for using single head revisions. # Run this script when you want to get rid of unneeded binary revisions, or you just want # to get information on how much storage space is occupied by unneeded files at that moment. # It lists binary file revisions from a repository that can possibly be removed from the server. # Very soon, it will also offer solution for getting rid of those revisions. # File size information and removal is only available if you run on the Perforce server machine. # Otherwise, you just get a list of unneeded files. # See the configuration section for details. # # Tested with Python 2.6.5, P4Python 2009.2 and Perforce server 2008.2 on Windows Vista # # Robert Kovacs (rkovacs7878@gmail.com) # # TODO # Size limit # p4bucket ############################### #### CONFIGURATION SECTION #### P4PORT = "localhost:1666" P4USER = "apa" P4PASSWD = "" HEAD_NEEDED = True # If True then no head revision lbr files will be listed LABELLED_NEEDED = True # If True then no labelled revisions will be listed RECENTLY_MODIFIED_NEEDED = True # If True then no files newer than AGE_LIMIT will be listed AGE_LIMIT = 7 * 24 * 60 * 60 # As seconds. Only used if RECENTLY_MODIFIED_NEEDED is True. #### #### ############################### import P4 import socket import platform import string import datetime import sys import os p4 = None local = None server_root = None revisions = { } # key: #, value: lbr filename with full path lbr_files = { } # key: lbr filename with full path, value is 0 if this revision is unneeded def initialise(): connect_perforce() query_server_root() def connect_perforce(): print("Connecting Perforce...") global p4 p4 = P4.P4() p4.port = P4PORT p4.user = P4USER p4.password = P4PASSWD p4.connect() def query_server_root(): print("Querying server root path...") server_info = p4.run_info()[0] server_host = server_info['serverAddress'].split(':')[0] server_ip = socket.gethostbyname(server_host) host_name = platform.uname()[1] host_ip = socket.gethostbyname(host_name) global local local = server_ip == "127.0.0.1" or server_ip == host_ip # If not local then some functions are disabled global server_root server_root = server_info["serverRoot"] if not server_root.endswith('/') and not server_root.endswith('\\'): server_root += '/' def enumerate_files(): print("Processing depots...") global server_root global revisions for depot in p4.run_depots(): print(" " + depot['name']) lbr_path = server_root + depot['map'].rstrip('.') for file in p4.run_fstat("-Osfc", "//" + depot['name'] + "/..."): if file.has_key('lbrType') and file['lbrType'].startswith("binary") and file.has_key('lbrFile') != None: revisions[file['depotFile'] + "#" + file['headRev']] = { 'lbr_filename': lbr_path + file['lbrFile'][(len(depot['name']) + 3):] + ",d/" + file['lbrRev'] + ".gz", 'mod_time': string.atoi(file['headModTime']) } def collect_needed_lbr_files(): global lbr_files for (filename, revision) in revisions.items(): lbr_files[revision['lbr_filename']] = 0 if HEAD_NEEDED: mark_head_revisions() if LABELLED_NEEDED: mark_labelled_revisions() if RECENTLY_MODIFIED_NEEDED: mark_recently_modified_files() def mark_head_revisions(): print("Processing head revisions...") p4_files = { } for revision in revisions.keys(): tokens = revision.split('#') p4_filename = tokens[0] revision_number = int(tokens[1]) if not p4_files.has_key(p4_filename) or revision_number > p4_files[p4_filename]: p4_files[p4_filename] = revision_number for (p4_filename, revision_number) in p4_files.items(): lbr_files[revisions[p4_filename + "#" + str(revision_number)]['lbr_filename']] += 1 def mark_labelled_revisions(): print("Processing labels...") labels = p4.run_labels() for label in labels: print(" " + label['label']) labelled_revisions = p4.run_files("//...@" + label['label']) for labelled_revision in labelled_revisions: lbr_files[revisions[labelled_revision['depotFile'] + "#" + labelled_revision['rev']]['lbr_filename']] += 1 def mark_recently_modified_files(): print("Processing recently modified files...") now = datetime.datetime.utcnow() - datetime.datetime(1970, 1, 1) earliest = now.days * 24 * 60 * 60 + now.seconds - AGE_LIMIT for (filename, revision) in revisions.items(): if revision['mod_time'] > earliest: lbr_files[revision['lbr_filename']] += 1 def log_unneeded_files(): f = open("filelist.txt", "w") for (lbr_filename, ref_count) in lbr_files.items(): if ref_count == 0: f.write(lbr_filename + "\n") f.close() print("List of unneeded lbr files has been saved to fileslist.txt") def sum_size(): if local: print("Calculating sum file size...") file_count = 0 total_size = 0 for (lbr_filename, ref_count) in lbr_files.items(): if ref_count == 0: file_count += 1 total_size += os.path.getsize(lbr_filename) print("Unneeded " + str(total_size) + " bytes in " + str(file_count) + " files.") else: print("Run this script on the Perforce server if want to know the total size of the unneeded files.") if __name__ == '__main__': initialise() enumerate_files() collect_needed_lbr_files() log_unneeded_files() sum_size()