#! /usr/bin/env python3.3 """Code to provide the Perforce Server's Librarian with all it needs to store file revisions. """ import logging import os import p4gf_config from p4gf_hex_str import md5_int from p4gf_l10n import _ import p4gf_squee_value LOG = logging.getLogger("p4gf_fast_push.librarian") class LibrarianStore: """BigStore of sha1->LibrarianRecord. """ def __init__(self, file_path, store_how): # pylint:disable=invalid-name if store_how == p4gf_config.VALUE_FAST_PUSH_WORKING_STORAGE_DICT: self._d = {} elif store_how == p4gf_config.VALUE_FAST_PUSH_WORKING_STORAGE_SQLITE_MEMORY: self._d = p4gf_squee_value.SqueeValueDict(file_path) elif store_how == p4gf_config.VALUE_FAST_PUSH_WORKING_STORAGE_SQLITE_SINGLE_TABLE: self._d = p4gf_squee_value.SqueeValue(file_path) else: raise RuntimeError(_("Unsupported store_how {store_how}") .format(store_how=store_how)) def get(self, sha1): """Return any record for the given sha1, or None.""" return self._d.get(sha1) def store( self, * , sha1 , md5 , byte_ct , lbr_file_type ): """Store one librarian record for the given sha1. Return the record for the newly stored entry. Does not check for previous existence. It is the caller's responsibility to do so. Zig slightly prefers an explicit setter function like this to a lower-level setter where callers are responsible for choosing the element type. Gives us a chance to enforce an invariant: all elements are LibrarianRecords. Keep records tiny: intentionally ignore lbr_path here. Outer code has the type (blob/tree) and sha1. Let it programmatically generate lbr_path from that. """ # I know the comment header says we don't check, # and once I switch to BigStore I won't. But for # now, let's catch any bugs. assert sha1 not in self._d lbr_record = LibrarianRecord( md5 = md5 , byte_ct = byte_ct , lbr_file_type = lbr_file_type) self._d[sha1] = lbr_record return lbr_record # ---------------------------------------------------------------------------- class LibrarianRecord: """What the Perforce Server's Librarian requires for any single file revision. Keep this struct tiny. We retain millions of these in a BigStore. Intentionally omits the librarian file path. Path strings are long, expensive to store, and rarely necessary. Usually the path is generated from the same sha1 used to look up this record, or used only once then discarded. Intentionally do NOT store lbr_path here. lbr_path is programmatically generated from tree/blob type and sha1. """ def __init__( self, * , md5 , byte_ct , lbr_file_type ): self.md5 = md5_int(md5) self.byte_ct = int(byte_ct) self.lbr_file_type = int(lbr_file_type) # -- module-wide ------------------------------------------------------------- def lbr_rev_str(change_num = 1): """1234 ==> "1.1234" lbrRev fields all seem to be "1.nnn" where "nnn" was the change_num that submitted them. """ return "1.{}".format(change_num) def depot_to_archive_path(depot_path, change_num = 1): """ Return the path within the zip archive where a librarian file goes. Just appends "/1.1234" to the lbr_path. blobs and other files where we force the librarian rev with `p4 unzip --retain-lbr-revisions` should always use change_num = 1. """ return os.path.join(depot_path[2:], lbr_rev_str(change_num))