Exemplo n.º 1
0
    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, b"packed-refs")
            try:
                f = GitFile(path, "rb")
            except IOError as e:
                if e.errno == errno.ENOENT:
                    return {}
                raise
            with f:
                first_line = next(iter(f)).rstrip()
                if first_line.startswith(
                        b"# pack-refs") and b" 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
        return self._packed_refs
Exemplo n.º 2
0
 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()
Exemplo n.º 3
0
    def set_symbolic_ref(self,
                         name,
                         other,
                         committer=None,
                         timestamp=None,
                         timezone=None,
                         message=None):
        """Make a ref point at another ref.

        :param name: Name of the ref to set
        :param other: Name of the ref to point at
        :param message: Optional message to describe the change
        """
        self._check_refname(name)
        self._check_refname(other)
        filename = self.refpath(name)
        f = GitFile(filename, "wb")
        try:
            f.write(SYMREF + other + b"\n")
            sha = self.follow(name)[-1]
            self._log(
                name,
                sha,
                sha,
                committer=committer,
                timestamp=timestamp,
                timezone=timezone,
                message=message,
            )
        except BaseException:
            f.abort()
            raise
        else:
            f.close()
Exemplo n.º 4
0
    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:
            with GitFile(filename, "rb") as f:
                header = f.read(len(SYMREF))
                if header == SYMREF:
                    # Read only the first line
                    return header + next(iter(f)).rstrip(b"\r\n")
                else:
                    # Read only the first 40 bytes
                    return header + f.read(40 - len(SYMREF))
        except IOError as e:
            if e.errno in (errno.ENOENT, errno.EISDIR):
                return None
            raise
Exemplo n.º 5
0
    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.
        """
        with PackData(path) as p:
            entries = p.sorted_entries()
            basename = self._get_pack_basepath(entries)
            with GitFile(basename + ".idx", "wb") as f:
                write_pack_index_v2(f, entries, p.get_stored_checksum())
        if self._pack_cache is None or self._pack_cache_stale():
            self._update_pack_cache()
        try:
            return self._pack_cache[basename]
        except KeyError:
            pass
        target_pack = basename + ".pack"
        if sys.platform == "win32":
            # Windows might have the target pack file lingering. Attempt
            # removal, silently passing if the target does not exist.
            try:
                os.remove(target_pack)
            except (IOError, OSError) as e:
                if e.errno != errno.ENOENT:
                    raise
        os.rename(path, target_pack)
        final_pack = Pack(basename)
        self._add_known_pack(basename, final_pack)
        return final_pack
Exemplo n.º 6
0
    def set_if_equals(
        self,
        name,
        old_ref,
        new_ref,
        committer=None,
        timestamp=None,
        timezone=None,
        message=None,
    ):
        """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.
        :param message: Set message for reflog
        :return: True if the set was successful, False otherwise.
        """
        self._check_refname(name)
        try:
            realnames, _ = self.follow(name)
            realname = realnames[-1]
        except (KeyError, IndexError):
            realname = name
        filename = self.refpath(realname)
        ensure_dir_exists(os.path.dirname(filename))
        with GitFile(filename, "wb") as f:
            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, ZERO_SHA)
                    if orig_ref != old_ref:
                        f.abort()
                        return False
                except (OSError, IOError):
                    f.abort()
                    raise
            try:
                f.write(new_ref + b"\n")
            except (OSError, IOError):
                f.abort()
                raise
            self._log(
                realname,
                old_ref,
                new_ref,
                committer=committer,
                timestamp=timestamp,
                timezone=timezone,
                message=message,
            )
        return True
Exemplo n.º 7
0
 def _read_alternate_paths(self):
     try:
         f = GitFile(os.path.join(self.path, INFODIR, "alternates"), "rb")
     except (OSError, IOError) as e:
         if e.errno == errno.ENOENT:
             return
         raise
     with f:
         for line in f.readlines():
             line = line.rstrip(b"\n")
             if line[0] == b"#":
                 continue
             if os.path.isabs(line):
                 yield line.decode(sys.getfilesystemencoding())
             else:
                 yield os.path.join(self.path, line).decode(
                     sys.getfilesystemencoding())
Exemplo n.º 8
0
 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]] = IndexEntry(*x[1:])
         # FIXME: Additional data?
         f.read(os.path.getsize(self._filename) - f.tell() - 20)
         f.check_sha()
     finally:
         f.close()
Exemplo n.º 9
0
    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)
        with GitFile(os.path.join(self.controldir(), path), "wb") as f:
            f.write(contents)
Exemplo n.º 10
0
 def stashes(self):
     reflog_path = os.path.join(self._repo.commondir(), "logs", self._ref)
     try:
         with GitFile(reflog_path, "rb") as f:
             return reversed(list(read_reflog(f)))
     except EnvironmentError as e:
         if e.errno == errno.ENOENT:
             return []
         raise
Exemplo n.º 11
0
    def get_description(self):
        """Retrieve the description of this repository.

        :return: A string describing the repository or None.
        """
        path = os.path.join(self._controldir, "description")
        try:
            with GitFile(path, "rb") as f:
                return f.read()
        except (IOError, OSError) as e:
            if e.errno != errno.ENOENT:
                raise
            return None
Exemplo n.º 12
0
    def add_object(self, obj):
        """Add a single object to this object store.

        :param obj: Object to add
        """
        path = self._get_shafile_path(obj.id)
        dir = os.path.dirname(path)
        try:
            os.mkdir(dir)
        except OSError as e:
            if e.errno != errno.EEXIST:
                raise
        if os.path.exists(path):
            return  # Already there, no need to write again
        with GitFile(path, "wb") as f:
            f.write(obj.as_legacy_object())
Exemplo n.º 13
0
    def add_if_new(self,
                   name,
                   ref,
                   committer=None,
                   timestamp=None,
                   timezone=None,
                   message=None):
        """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.
        :param message: Optional message for reflog
        :return: True if the add was successful, False otherwise.
        """
        try:
            realnames, contents = self.follow(name)
            if contents is not None:
                return False
            realname = realnames[-1]
        except (KeyError, IndexError):
            realname = name
        self._check_refname(realname)
        filename = self.refpath(realname)
        ensure_dir_exists(os.path.dirname(filename))
        with GitFile(filename, "wb") as f:
            if os.path.exists(filename) or name in self.get_packed_refs():
                f.abort()
                return False
            try:
                f.write(ref + b"\n")
            except (OSError, IOError):
                f.abort()
                raise
            else:
                self._log(
                    name,
                    None,
                    ref,
                    committer=committer,
                    timestamp=timestamp,
                    timezone=timezone,
                    message=message,
                )
        return True
Exemplo n.º 14
0
    def add_alternate_path(self, path):
        """Add an alternate path to this object store.
        """
        try:
            os.mkdir(os.path.join(self.path, INFODIR))
        except OSError as e:
            if e.errno != errno.EEXIST:
                raise
        alternates_path = os.path.join(self.path, INFODIR, "alternates")
        with GitFile(alternates_path, "wb") as f:
            try:
                orig_f = open(alternates_path, "rb")
            except (OSError, IOError) as e:
                if e.errno != errno.ENOENT:
                    raise
            else:
                with orig_f:
                    f.write(orig_f.read())
            f.write(path.encode(sys.getfilesystemencoding()) + b"\n")

        if not os.path.isabs(path):
            path = os.path.join(self.path, path)
        self.alternates.append(DiskObjectStore(path))
Exemplo n.º 15
0
    def _remove_packed_ref(self, name):
        if self._packed_refs is None:
            return
        filename = os.path.join(self.path, b"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()
Exemplo n.º 16
0
    def remove_if_equals(self,
                         name,
                         old_ref,
                         committer=None,
                         timestamp=None,
                         timezone=None,
                         message=None):
        """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.
        :param message: Optional message
        :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, ZERO_SHA)
                if orig_ref != old_ref:
                    return False

            # remove the reference file itself
            try:
                os.remove(filename)
            except OSError as e:
                if e.errno != errno.ENOENT:  # may only be packed
                    raise

            self._remove_packed_ref(name)
            self._log(
                name,
                old_ref,
                None,
                committer=committer,
                timestamp=timestamp,
                timezone=timezone,
                message=message,
            )
        finally:
            # never write, we just wanted the lock
            f.abort()

        # outside of the lock, clean-up any parent directory that might now
        # be empty. this ensures that re-creating a reference of the same
        # name of what was previously a directory works as expected
        parent = name
        while True:
            try:
                parent, _ = parent.rsplit(b"/", 1)
            except ValueError:
                break

            parent_filename = self.refpath(parent)
            try:
                os.rmdir(parent_filename)
            except OSError:
                # this can be caused by the parent directory being
                # removed by another process, being not empty, etc.
                # in any case, this is non fatal because we already
                # removed the reference, just ignore it
                break

        return True
Exemplo n.º 17
0
    def _complete_thin_pack(self, f, path, copier, indexer):
        """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 f: Open file object for the pack.
        :param path: Path to the pack file.
        :param copier: A PackStreamCopier to use for writing pack data.
        :param indexer: A PackIndexer for indexing the pack.
        """
        entries = list(indexer)

        # Update the header with the new number of objects.
        f.seek(0)
        write_pack_header(f, len(entries) + len(indexer.ext_refs()))

        # Must flush before reading (http://bugs.python.org/issue3207)
        f.flush()

        # Rescan the rest of the pack, computing the SHA with the new header.
        new_sha = compute_file_sha(f, end_ofs=-20)

        # Must reposition before writing (http://bugs.python.org/issue3207)
        f.seek(0, os.SEEK_CUR)

        # Complete the pack.
        for ext_sha in indexer.ext_refs():
            assert len(ext_sha) == 20
            type_num, data = self.get_raw(ext_sha)
            offset = f.tell()
            crc32 = write_pack_object(f, type_num, data, sha=new_sha)
            entries.append((ext_sha, offset, crc32))
        pack_sha = new_sha.digest()
        f.write(pack_sha)
        f.close()

        # Move the pack in.
        entries.sort()
        pack_base_name = self._get_pack_basepath(entries)
        target_pack = pack_base_name + ".pack"
        if sys.platform == "win32":
            # Windows might have the target pack file lingering. Attempt
            # removal, silently passing if the target does not exist.
            try:
                os.remove(target_pack)
            except (IOError, OSError) as e:
                if e.errno != errno.ENOENT:
                    raise
        os.rename(path, target_pack)

        # Write the index.
        index_file = GitFile(pack_base_name + ".idx", "wb")
        try:
            write_pack_index_v2(index_file, entries, pack_sha)
            index_file.close()
        finally:
            index_file.abort()

        # Add the pack to the store and return it.
        final_pack = Pack(pack_base_name)
        final_pack.check_length_and_checksum()
        self._add_known_pack(pack_base_name, final_pack)
        return final_pack
Exemplo n.º 18
0
 def from_path(cls, path):
     """Open a SHA file from disk."""
     with GitFile(path, "rb") as f:
         return cls.from_file(f)
Exemplo n.º 19
0
 def write_to_path(self, path=None):
     """Write configuration to a file on disk."""
     if path is None:
         path = self.path
     with GitFile(path, "wb") as f:
         self.write_to_file(f)
Exemplo n.º 20
0
 def from_path(cls, path):
     """Read configuration from a file on disk."""
     with GitFile(path, "rb") as f:
         ret = cls.from_file(f)
         ret.path = path
         return ret