def read_loose_ref(self, name): """Read a reference file and return its contents. If the reference file a symbolic reference, only read the first line of the file. Otherwise, only read the first 40 bytes. :param name: the refname to read, relative to refpath :return: The contents of the ref file, or None if the file does not exist. :raises IOError: if any other error occurs """ filename = self.refpath(name) try: f = GitFile(filename, 'rb') try: header = f.read(len(SYMREF)) if header == SYMREF: # Read only the first line return header + iter(f).next().rstrip("\r\n") else: # Read only the first 40 bytes return header + f.read(40 - len(SYMREF)) finally: f.close() except IOError, e: if e.errno == errno.ENOENT: return None raise
def add_if_new(self, name, ref): """Add a new reference only if it does not already exist. This method follows symrefs, and only ensures that the last ref in the chain does not exist. :param name: The refname to set. :param ref: The new sha the refname will refer to. :return: True if the add was successful, False otherwise. """ try: realname, contents = self._follow(name) if contents is not None: return False except KeyError: realname = name self._check_refname(realname) filename = self.refpath(realname) ensure_dir_exists(os.path.dirname(filename)) f = GitFile(filename, 'wb') try: if os.path.exists(filename) or name in self.get_packed_refs(): f.abort() return False try: f.write(ref + "\n") except (OSError, IOError): f.abort() raise finally: f.close() return True
def get_packed_refs(self): """Get contents of the packed-refs file. :return: Dictionary mapping ref names to SHA1s :note: Will return an empty dictionary when no packed-refs file is present. """ # TODO: invalidate the cache on repacking if self._packed_refs is None: # set both to empty because we want _peeled_refs to be # None if and only if _packed_refs is also None. self._packed_refs = {} self._peeled_refs = {} path = os.path.join(self.path, 'packed-refs') try: f = GitFile(path, 'rb') except IOError, e: if e.errno == errno.ENOENT: return {} raise try: first_line = iter(f).next().rstrip() if (first_line.startswith("# pack-refs") and " peeled" in first_line): for sha, name, peeled in read_packed_refs_with_peeled(f): self._packed_refs[name] = sha if peeled: self._peeled_refs[name] = peeled else: f.seek(0) for sha, name in read_packed_refs(f): self._packed_refs[name] = sha finally: f.close()
def remove_if_equals(self, name, old_ref): """Remove a refname only if it currently equals old_ref. This method does not follow symbolic references. It can be used to perform an atomic compare-and-delete operation. :param name: The refname to delete. :param old_ref: The old sha the refname must refer to, or None to delete unconditionally. :return: True if the delete was successful, False otherwise. """ self._check_refname(name) filename = self.refpath(name) ensure_dir_exists(os.path.dirname(filename)) f = GitFile(filename, 'wb') try: if old_ref is not None: orig_ref = self.read_loose_ref(name) if orig_ref is None: orig_ref = self.get_packed_refs().get(name, None) if orig_ref != old_ref: return False # may only be packed try: os.remove(filename) except OSError, e: if e.errno != errno.ENOENT: raise self._remove_packed_ref(name)
def write(self): """Write current contents of index to disk.""" f = GitFile(self._filename, 'wb') try: f = SHA1Writer(f) write_index_dict(f, self._byname) finally: f.close()
def write_pack(filename, objects, num_objects): """Write a new pack data file. :param filename: Path to the new pack file (without .pack extension) :param objects: Iterable over (object, path) tuples to write :param num_objects: Number of objects to write :return: Tuple with checksum of pack file and index file """ f = GitFile(filename + ".pack", 'wb') try: entries, data_sum = write_pack_data(f, objects, num_objects) finally: f.close() entries.sort() f = GitFile(filename + ".idx", 'wb') try: return data_sum, write_pack_index_v2(f, entries, data_sum) finally: f.close()
def from_path(cls, path): f = GitFile(path, 'rb') try: obj = cls.from_file(f) obj._path = path obj._sha = FixedSha(filename_to_hex(path)) obj._file = None obj._magic = None return obj finally: f.close()
def load_pack_index(path): """Load an index file by path. :param filename: Path to the index file :return: A PackIndex loaded from the given path """ f = GitFile(path, 'rb') try: return load_pack_index_file(path, f) finally: f.close()
def _put_named_file(self, path, contents): """Write a file to the control dir with the given name and contents. :param path: The path to the file, relative to the control dir. :param contents: A string to write to the file. """ path = path.lstrip(os.path.sep) f = GitFile(os.path.join(self.controldir(), path), 'wb') try: f.write(contents) finally: f.close()
def create_index_v2(self, filename, progress=None): """Create a version 2 index file for this data file. :param filename: Index filename. :param progress: Progress report function :return: Checksum of index file """ entries = self.sorted_entries(progress=progress) f = GitFile(filename, 'wb') try: return write_pack_index_v2(f, entries, self.calculate_checksum()) finally: f.close()
def read(self): """Read current contents of index from disk.""" if not os.path.exists(self._filename): return f = GitFile(self._filename, 'rb') try: f = SHA1Reader(f) for x in read_index(f): self[x[0]] = tuple(x[1:]) # FIXME: Additional data? f.read(os.path.getsize(self._filename)-f.tell()-20) f.check_sha() finally: f.close()
def __init__(self, filename, file=None, contents=None, size=None): """Create a pack index object. Provide it with the name of the index file to consider, and it will map it whenever required. """ self._filename = filename # Take the size now, so it can be checked each time we map the file to # ensure that it hasn't changed. if file is None: self._file = GitFile(filename, 'rb') else: self._file = file if contents is None: self._contents, self._size = _load_file_contents(self._file, size) else: self._contents, self._size = (contents, size)
def set_symbolic_ref(self, name, other): """Make a ref point at another ref. :param name: Name of the ref to set :param other: Name of the ref to point at """ self._check_refname(name) self._check_refname(other) filename = self.refpath(name) try: f = GitFile(filename, 'wb') try: f.write(SYMREF + other + '\n') except (IOError, OSError): f.abort() raise finally: f.close()
def _remove_packed_ref(self, name): if self._packed_refs is None: return filename = os.path.join(self.path, 'packed-refs') # reread cached refs from disk, while holding the lock f = GitFile(filename, 'wb') try: self._packed_refs = None self.get_packed_refs() if name not in self._packed_refs: return del self._packed_refs[name] if name in self._peeled_refs: del self._peeled_refs[name] write_packed_refs(f, self._packed_refs, self._peeled_refs) f.close() finally: f.abort()
def __init__(self, filename, file=None, size=None): """Create a PackData object representing the pack in the given filename. The file must exist and stay readable until the object is disposed of. It must also stay the same size. It will be mapped whenever needed. Currently there is a restriction on the size of the pack as the python mmap implementation is flawed. """ self._filename = filename self._size = size self._header_size = 12 if file is None: self._file = GitFile(self._filename, 'rb') else: self._file = file (version, self._num_objects) = read_pack_header(self._file.read) self._offset_cache = LRUSizeCache(1024*1024*20, compute_size=_compute_object_size) self.pack = None
def set_if_equals(self, name, old_ref, new_ref): """Set a refname to new_ref only if it currently equals old_ref. This method follows all symbolic references, and can be used to perform an atomic compare-and-swap operation. :param name: The refname to set. :param old_ref: The old sha the refname must refer to, or None to set unconditionally. :param new_ref: The new sha the refname will refer to. :return: True if the set was successful, False otherwise. """ try: realname, _ = self._follow(name) except KeyError: realname = name filename = self.refpath(realname) ensure_dir_exists(os.path.dirname(filename)) f = GitFile(filename, 'wb') try: if old_ref is not None: try: # read again while holding the lock orig_ref = self.read_loose_ref(realname) if orig_ref is None: orig_ref = self.get_packed_refs().get(realname, None) if orig_ref != old_ref: f.abort() return False except (OSError, IOError): f.abort() raise try: f.write(new_ref + "\n") except (OSError, IOError): f.abort() raise finally: f.close() return True
def move_in_pack(self, path): """Move a specific file containing a pack into the pack directory. :note: The file should be on the same file system as the packs directory. :param path: Path to the pack file. """ p = PackData(path) entries = p.sorted_entries() basename = os.path.join( self.pack_dir, "pack-%s" % iter_sha1(entry[0] for entry in entries)) f = GitFile(basename + ".idx", "wb") try: write_pack_index_v2(f, entries, p.get_stored_checksum()) finally: f.close() p.close() os.rename(path, basename + ".pack") final_pack = Pack(basename) self._add_known_pack(final_pack) return final_pack
def _parse_path(self): f = GitFile(self._path, 'rb') try: self._parse_file(f) finally: f.close()
def add_object(self, obj): """Add a single object to this object store. :param obj: Object to add """ dir = os.path.join(self.path, obj.id[:2]) try: os.mkdir(dir) except OSError, e: if e.errno != errno.EEXIST: raise path = os.path.join(dir, obj.id[2:]) if os.path.exists(path): return # Already there, no need to write again f = GitFile(path, 'wb') try: f.write(obj.as_legacy_object()) finally: f.close() @classmethod def init(cls, path): try: os.mkdir(path) except OSError, e: if e.errno != errno.EEXIST: raise os.mkdir(os.path.join(path, "info")) os.mkdir(os.path.join(path, PACKDIR)) return cls(path)