Пример #1
0
    def get_changed_files(self, old_commit, new_commit):
        """
        For each file that has changed, yields a three-tuple containing the filename, old content and new content
        """
        if old_commit is not None and not self.pygit.descendant_of(
            new_commit, old_commit
        ):
            raise ValueError("Second commit must be a descendant of first commit")

        old_index = pygit2.Index()
        new_index = pygit2.Index()
        if old_commit is not None:
            old_tree = self.pygit.get(old_commit).tree
            old_index.read_tree(old_tree)
        else:
            # This is a special hash that represents an empty tree
            old_tree = self.pygit.get("4b825dc642cb6eb9a060e54bf8d69288fbee4904")

        new_tree = self.pygit.get(new_commit).tree
        new_index.read_tree(new_tree)

        for patch in self.pygit.diff(old_tree, new_tree):
            if patch.delta.status_char() != "M":
                continue

            if not patch.delta.new_file.path.startswith("locales/"):
                continue

            old_file_oid = old_index[patch.delta.old_file.path].oid
            new_file_oid = new_index[patch.delta.new_file.path].oid
            old_file = self.pygit.get(old_file_oid)
            new_file = self.pygit.get(new_file_oid)
            yield patch.delta.new_file.path, old_file.data, new_file.data
Пример #2
0
    def start_session(self, name: str) -> None:

        if self._current_session_index is not None:
            raise Exception("Index was supposed to be None")
        if self._current_session_branch is not None:
            raise Exception("Session was supposed to be None")

        session_branch = f"session/{name}"
        # If the branch already exists, just append a unique identifier.
        if session_branch in self.repo.branches:
            session_branch = session_branch + "." + uuid.uuid4().hex[:7]

        # Create the session branch.
        self.repo.create_branch(
            session_branch, self.repo.references[self.namespace_ref].peel())

        # Store the session branch and index.
        self._current_session_branch = session_branch

        self._current_session_index = pygit2.Index()

        # Populate the index with the content of the namespace branch.
        namespace_ref = self.repo.references[self.namespace_ref].target
        tree = self.repo[namespace_ref].tree
        self._current_session_index.read_tree(tree)
Пример #3
0
def createCommits(repo, commitsData, destHead, branchName):
    index = pygit2.Index()

    destCommit = destHead
    index.read_tree(destCommit.tree)

    for commitData in commitsData:
        srcCommit = commitData.commit
        tree = commitData.commit.tree
        for file in commitData.files:
            try:
                fileObject = tree[file]
                index.add(
                    pygit2.IndexEntry(file, fileObject.id,
                                      pygit2.GIT_FILEMODE_BLOB))
            except KeyError:
                removeFileFromTree(index, file)

        destTree = index.write_tree(repo)

        diff = repo[destTree].diff_to_tree(destCommit.tree)

        if len(diff):
            destCommitId = repo.create_commit('refs/heads/' + branchName,
                                              srcCommit.author,
                                              srcCommit.committer,
                                              srcCommit.message, destTree,
                                              [destCommit.id])
            destCommit = repo[destCommitId]
            print srcCommit.id
Пример #4
0
    def setUp(self):
        super().setUp()

        # Create a commit that creates some files to change
        index = pygit2.Index()
        self.add_file_to_index(self.repo, index, "test.txt", "this is a test")
        self.first_commit_id = self.make_commit_from_index(
            self.repo, index, "First commit")
Пример #5
0
def test_write_feature_performance(
    repo_version,
    archive,
    source_gpkg,
    table,
    data_archive,
    tmp_path,
    cli_runner,
    chdir,
    benchmark,
    request,
):
    """ Per-feature import performance. """
    param_ids = H.parameter_ids(request)

    with data_archive(archive) as data:
        # list tables
        repo_path = tmp_path / "data.sno"
        repo_path.mkdir()

        benchmark.group = f"test_write_feature_performance - {param_ids[-1]}"

        with chdir(repo_path):
            r = cli_runner.invoke(["init", "--repo-version", repo_version])
            assert r.exit_code == 0, r

            repo = pygit2.Repository(str(repo_path))

            source = OgrImportSource.open(data / source_gpkg, table=table)
            with source:
                dataset = structure.DatasetStructure.for_version(repo_version)(
                    None, table)
                feature_iter = itertools.cycle(source.features())

                index = pygit2.Index()

                if repo_version == "1":
                    kwargs = {
                        "geom_cols": source.geom_cols,
                        "field_cid_map": dataset.get_field_cid_map(source),
                        "primary_key": source.primary_key,
                        "cast_primary_key": False,
                    }
                elif repo_version == "2":
                    kwargs = {"schema": source.schema}

                def _write_feature():
                    feature = next(feature_iter)
                    dest_path, dest_data = dataset.encode_feature(
                        feature, **kwargs)
                    blob_id = repo.create_blob(dest_data)
                    entry = pygit2.IndexEntry(f"{dataset.path}/{dest_path}",
                                              blob_id,
                                              pygit2.GIT_FILEMODE_BLOB)
                    index.add(entry)

                benchmark(_write_feature)
Пример #6
0
 def workdir_diff(self):
     # This is the main reason to hold onto this context throughout an entire diff -
     # we can reuse this result for more than one dataset.
     index = pygit2.Index(str(self.workdir_index_path))
     index._repo = self.repo
     return index.diff_to_workdir(
         pygit2.GIT_DIFF_INCLUDE_UNTRACKED
         | pygit2.GIT_DIFF_UPDATE_INDEX
         # GIT_DIFF_UPDATE_INDEX just updates timestamps in the index to make the diff quicker next time
         # none of the paths or hashes change, and the end result stays the same.
     )
Пример #7
0
    def add_from_other_repo(self,
                            other_repo,
                            committish,
                            src="",
                            dest="",
                            exclude=()):
        """Copies files from another repository over to this one."""
        def _copy_blob(blob_id):
            # Copy object over to this repo
            local_id = self.repo.create_blob(other_repo[blob_id].read_raw())
            # Two blobs with same content MUST have the same id in both repos
            assert local_id == blob_id

        commit = resolve_committish(other_repo, committish)
        node = commit.tree
        src = posixpath.normpath(src)
        dest = posixpath.normpath(dest)

        # Traverse down to the proper subtree or blob
        if src != ".":
            for part in src.split(os.sep):
                if not isinstance(node, pygit2.Tree):
                    # We reached a leaf and hence can't descend further
                    raise KeyError(part)
                node /= part

        if isinstance(node, pygit2.Blob):
            # Copy only a single file
            if dest == ".":
                # When nothing is specified as destination, keep the file's name
                dest = posixpath.split(src)[1]
            _copy_blob(node.id)
            self.index.add(pygit2.IndexEntry(dest, node.id, node.filemode))
            return

        assert isinstance(node, pygit2.Tree)
        # Read tree into in-memory index and then copy the entries over
        index = pygit2.Index()
        index.read_tree(node)
        for entry in index:
            # Check if file (or the directory the file is in) is in exclude list
            for path in exclude:
                if entry.path == path or entry.path.startswith(f"{path}/"):
                    break
            else:
                _copy_blob(entry.id)
                self.index.add(
                    pygit2.IndexEntry(
                        posixpath.normpath(posixpath.join(dest, entry.path)),
                        entry.id,
                        entry.mode,
                    ))
Пример #8
0
    def setUp(self):
        super().setUp()

        # Create a commit that creates some files to change
        index = pygit2.Index()
        self.add_file_to_index(self.repo, index, "locales/test.txt",
                               "this is a test")
        self.add_file_to_index(self.repo, index, "locales/test-change.txt",
                               "this is a test")
        self.add_file_to_index(self.repo, index, "test-change.txt",
                               "this is a test")
        self.add_file_to_index(self.repo, index, "locales/test-delete.txt",
                               "this is a test")
        self.first_commit_id = self.make_commit_from_index(
            self.repo, index, "First commit")

        # Create a second commit with some files changed:
        # test.txt remains the same
        # test-change.txt was changed
        # test-delete.txt was deleted
        # test-added.txt was added
        index = pygit2.Index()
        self.add_file_to_index(self.repo, index, "locales/test.txt",
                               "this is a test")
        self.add_file_to_index(
            self.repo,
            index,
            "locales/test-change.txt",
            "this is a test that has been changed",
        )
        self.add_file_to_index(self.repo, index, "test-change.txt",
                               "this is a test that has been changed")
        self.add_file_to_index(self.repo, index, "locales/test-added.txt",
                               "this is a test")
        self.second_commit_id = self.make_commit_from_index(
            self.repo, index, "Second commit")
Пример #9
0
def test_write_feature_performance(
    archive,
    source_gpkg,
    table,
    data_archive,
    tmp_path,
    cli_runner,
    chdir,
    benchmark,
    request,
):
    """ Per-feature import performance. """
    param_ids = H.parameter_ids(request)

    with data_archive(archive) as data:
        # list tables
        repo_path = tmp_path / "repo"
        repo_path.mkdir()

        benchmark.group = f"test_write_feature_performance - {param_ids[-1]}"

        with chdir(repo_path):
            r = cli_runner.invoke(["init"])
            assert r.exit_code == 0, r

            repo = KartRepo(repo_path)

            source = TableImportSource.open(data / source_gpkg, table=table)
            with source:
                dataset = TableV3.new_dataset_for_writing(
                    table, source.schema, MemoryRepo())
                feature_iter = itertools.cycle(list(source.features()))

                index = pygit2.Index()

                encode_kwargs = {"schema": source.schema}

                def _write_feature():
                    feature = next(feature_iter)
                    dest_path, dest_data = dataset.encode_feature(
                        feature, **encode_kwargs)
                    blob_id = repo.create_blob(dest_data)
                    entry = pygit2.IndexEntry(f"{dataset.path}/{dest_path}",
                                              blob_id,
                                              pygit2.GIT_FILEMODE_BLOB)
                    index.add(entry)

                benchmark(_write_feature)
Пример #10
0
    def write(self, path):
        """
        Serialise this MergeIndex to the given path.
        Regular entries, conflicts, and resolves are each serialised separately,
        so that they can be roundtripped accurately.
        """
        index = pygit2.Index(str(path))
        index.clear()

        for e in self.entries.values():
            index.add(pygit2.IndexEntry(e.path, e.id, e.mode))
        for e in self._serialise_conflicts():
            index.add(pygit2.IndexEntry(e.path, e.id, e.mode))
        for e in self._serialise_resolves():
            index.add(pygit2.IndexEntry(e.path, e.id, e.mode))
        index.write()
Пример #11
0
    def __init__(self,
                 graph: "SakDbGraph",
                 name: str,
                 path: Path,
                 branch: str = "master") -> None:
        super(SakDbNamespaceGit, self).__init__(graph, name)
        self.repo = pygit2.init_repository(path, True)

        self.namespace_branch = branch
        self.namespace_ref = f"refs/heads/{branch}"

        self._last_hash_for_populated_objs: str = ""

        self._current_session_index: Optional[pygit2.Index] = None
        self._current_session_branch: Optional[str] = None

        if self.namespace_ref not in self.repo.references:
            # author = pygit2.Signature("a b", "a@b")
            author = self.repo.default_signature
            committer = author
            commit_message = "Initial commit"

            index = pygit2.Index()
            tid = index.write_tree(self.repo)

            self.repo.create_commit(self.namespace_ref, author, committer,
                                    commit_message, tid, [])

        # Read the version and check if it is compatible with the current implementation.
        try:
            version = self.get_metadata("version")
        except Exception:
            version = None

        if version is not None:
            if not self._validate_version(version):
                raise Exception(
                    f"Repo version ({version}) not supported, please update the system."
                )
        else:
            # If no version was found, just store the current version.
            if self.graph is None:
                raise Exception("No graph configured")
            with self.graph.session(msg="Set version"):
                self.set_metadata("version", VERSION)
Пример #12
0
    def rollback(self) -> None:
        # Reset the session banch to the namespace branch.

        if self._current_session_branch is None:
            raise Exception(
                "Something went wrong. The session branch is not set")

        # Move the session branch back to the namespace.
        branch = self.repo.branches[self.namespace_branch]
        session_branch = self.repo.branches[self._current_session_branch]
        session_branch.set_target(branch.target)

        # Reset the index.
        namespace_ref = self.repo.references[self.namespace_ref].target
        tree = self.repo[namespace_ref].tree

        self._current_session_index = pygit2.Index()
        self._current_session_index.read_tree(tree)
Пример #13
0
def archive_head(repo, out=None):
    commit = repo.get(repo.head.target)
    timestamp = datetime.datetime.fromtimestamp(commit.commit_time)
    tree = commit.peel(pygit2.Tree)

    if out is None:
        out = '%s.zip' % (commit.id.hex[:8])

    if not timestamp:
        timestamp = datetime.datetime.now()

    index = pygit2.Index()
    index.read_tree(tree)

    zip_file = zipfile.ZipFile(out, 'w')
    for entry in index:
        info = zipfile.ZipInfo(entry.path, timestamp.timetuple())
        content = repo.get(entry.id).read_raw()
        zip_file.writestr(info, content)
Пример #14
0
    def commit_translation(self,
                           repo,
                           resource,
                           translation,
                           modify_locale_po=None,
                           commit_message=None):
        translation_po = translation.export_po()

        if modify_locale_po:
            modify_locale_po(translation_po)

        index = pygit2.Index()
        self.add_file_to_index(
            repo,
            index,
            f"templates/{resource.path}.pot",
            str(translation.source.export_po()),
        )
        self.add_file_to_index(repo, index, f"locales/fr/{resource.path}.po",
                               str(translation_po))
        return self.make_commit_from_index(
            repo, index, commit_message or "Added a translation")
Пример #15
0
    def __enter__(self):
        self._count += 1
        if self._count != 1:
            return self

        self.lock.__enter__()

        # First we create an empty index
        self.index = pygit2.Index()

        if self.ref is not None:
            try:
                ref = self.pygit2_repo.lookup_reference(self.ref)
            except KeyError:
                self.parents = []
            else:
                self.parents = [ref.peel().id]
                if not self.initial_empty:
                    self.index.read_tree(ref.peel().tree)
        else:
            self.parents = []
        return self
Пример #16
0
    def test_copy_unmanaged_files(self):
        # Create some files
        index = pygit2.Index()
        self.add_file_to_index(
            self.repo,
            index,
            "locales/test.txt",
            "this file is managed because it's in locales",
        )
        self.add_file_to_index(
            self.repo,
            index,
            "templates/test.txt",
            "this file is managed because it's in templates",
        )
        self.add_file_to_index(self.repo, index, "l10n.toml",
                               "this one is also managed")
        self.add_file_to_index(self.repo, index, "README.md",
                               "this is an unmanaged file")
        self.add_file_to_index(self.repo, index, "docs/foo.md",
                               "this is also an unmanaged file")
        self.make_commit_from_index(self.repo, index, "First commit")

        writer = self.repo.writer()
        writer.copy_unmanaged_files(self.repo.reader())
        writer.commit("Copied unmanaged files")

        # Checkunmanaged files were copied
        self.assert_file_in_tree(self.repo.gitpython.head.commit.tree,
                                 "README.md")
        # FIXME self.assert_file_in_tree(self.repo.gitpython.head.commit.tree, "docs/foo.md")

        # Check managed files weren't
        self.assert_file_not_in_tree(self.repo.gitpython.head.commit.tree,
                                     "locales/test.txt")
        self.assert_file_not_in_tree(self.repo.gitpython.head.commit.tree,
                                     "templates/test.txt")
        self.assert_file_not_in_tree(self.repo.gitpython.head.commit.tree,
                                     "l10n.toml")
Пример #17
0
    def create_tree_from_diff(self, diff, orig_tree=None):
        """
        Given a tree and a diff, returns a new tree created by applying the diff.

        Doesn't create any commits or modify the working copy at all.

        If orig_tree is not None, the diff is applied from that tree.
        Otherwise, uses the tree at the head of the repo.
        """
        if orig_tree is None:
            orig_tree = self.tree

        git_index = pygit2.Index()
        git_index.read_tree(orig_tree)

        for ds in self.iter_at(orig_tree):
            ds.write_index(diff[ds], git_index, self.repo)

        L.info("Writing tree...")
        new_tree_oid = git_index.write_tree(self.repo)
        L.info(f"Tree sha: {new_tree_oid}")
        return new_tree_oid
Пример #18
0
    def read(cls, path):
        """Deserialise a MergeIndex from the given path."""
        index = pygit2.Index(str(path))
        if index.conflicts:
            raise RuntimeError("pygit2.Index conflicts should be empty")
        entries = {}
        conflicts = {}
        resolves = {}
        for e in index:
            if e.path.startswith(".conflicts/"):
                key, conflict_part = cls._deserialise_conflict_part(e)
                conflicts.setdefault(key, AncestorOursTheirs.EMPTY)
                conflicts[key] |= conflict_part
            elif e.path.startswith(".resolves/"):
                key, resolve_part = cls._deserialise_resolve_part(e)
                resolves.setdefault(key, [])
                if resolve_part:
                    resolves[key] += [resolve_part]
            else:
                entries[e.path] = cls._ensure_entry(e)

        return MergeIndex(entries, conflicts, resolves)
Пример #19
0
    def write_resolved_tree(self, repo):
        """
        Write all the merged entries and the resolved conflicts to a tree in the given repo.
        Resolved conflicts will be written the same as merged entries in the resulting tree.
        Only works when all conflicts are resolved.
        """
        assert not self.unresolved_conflicts
        index = pygit2.Index()

        # Entries that were merged automatically by libgit2, often trivially:
        for e in self.entries.values():
            index.add(pygit2.IndexEntry(e.path, e.id, e.mode))

        # libgit2 leaves entries in the main part of the index, even if they are conflicts.
        # We make sure this index only contains merged entries and resolved conflicts.
        index.remove_all(list(self._conflicts_paths()))

        # Entries that have been explicitly selected to resolve conflicts:
        for e in self._resolves_entries():
            index.add(pygit2.IndexEntry(e.path, e.id, e.mode))

        return index.write_tree(repo)
Пример #20
0
    def commit(
        self,
        wcdiff,
        message,
        *,
        author=None,
        committer=None,
        allow_empty=False,
    ):
        """
        Update the repository structure and write the updated data to the tree
        as a new commit, setting HEAD to the new commit.
        NOTE: Doesn't update working-copy meta or tracking tables, this is the
        responsibility of the caller.
        """
        tree = self.tree

        git_index = pygit2.Index()
        git_index.read_tree(tree)

        new_tree_oid = self.create_tree_from_diff(wcdiff)
        L.info("Committing...")
        user = self.repo.default_signature
        # this will also update the ref (branch) to point to the current commit
        new_commit = self.repo.create_commit(
            "HEAD",  # reference_name
            author or user,  # author
            committer or user,  # committer
            message,  # message
            new_tree_oid,  # tree
            [self.repo.head.target],  # parents
        )
        L.info(f"Commit: {new_commit}")

        # TODO: update reflog
        return new_commit
Пример #21
0
    def test_bare_index(self):
        index = pygit2.Index(os.path.join(self.repo.path, 'index'))
        assert [x.hex for x in index] == [x.hex for x in self.repo.index]

        with pytest.raises(pygit2.GitError):
            index.add('bye.txt')
Пример #22
0
    def commit(self, key=None, value=None, add=True, **kwargs):
        """Commit the index to the working tree.

        >>> repo.add('foo', 'my very special bar')
        >>> repo.commit()
        >>> foo = repo.show('foo')
        u'my very special bar'

        If a key and value are specified, will add them immediately before
        committing them.

        >>> repo.commit('fast', 'and easy, too!')
        >>> foo = repo.show('fast')
        u'and easy, too!'

        :param key: The key
        :type key: string
        :param value:  The value of the key.
        :type value: anything that runs through :func:`json.dumps`
        :param author:
            (optional) The signature for the author of the first commit.
            Defaults to global author.
        :param message:
            (optional) Message for first commit.  Defaults to "adding [key]" if
            there was no prior value.
        :type message: string
        :param author:
            (optional) The signature for the committer of the first commit.
            Defaults to git's `--global` `author.name` and `author.email`.
        :type author: pygit2.Signature
        :param committer:
            (optional) The signature for the committer of the first commit.
            Defaults to author.
        :type committer: pygit2.Signature
        :param parents:
            (optional) The parents of this commit.  Defaults to the last commit
            for this key if it already exists, or an empty list if not.
        :type parents: list of :class:`Commit <jsongit.wrappers.Commit>`

        :raises:
            :class:`NotJsonError <jsongit.NotJsonError>`
            :class:`InvalidKeyError <jsongit.InvalidKeyError>`
        """
        keys = [key] if key is not None else [e.path for e in self._repo.index]
        message = kwargs.pop('message', '')
        parents = kwargs.pop('parents', None)
        author = kwargs.pop(
            'author', utils.signature(self._global_name, self._global_email))
        committer = kwargs.pop('committer', author)
        if kwargs:
            raise TypeError("Unknown keyword args %s" % kwargs)
        if key is None and value is not None:
            raise InvalidKeyError()

        if parents is not None:
            for parent in parents:
                if parent.repo != self:
                    raise DifferentRepoError()

        if add is True and key is not None and value is not None:
            self.add(key, value)

        repo_head = self._repo_head()
        tree_id = self._repo.index.write_tree()
        self._repo.create_commit(self._head_target(), author, committer,
                                 message, tree_id,
                                 [repo_head.oid] if repo_head else [])

        # TODO This will create some keys but not others if there is a bad key
        for key in keys:
            if parents is None:
                parents = [self.head(key)] if self.committed(key) else []
            try:
                # create a single-entry tree for the commit.
                blob_id = self._navigate_tree(tree_id, key)
                idx = pygit2.Index('')
                idx.add(
                    pygit2.IndexEntry(key, blob_id, pygit2.GIT_FILEMODE_BLOB))
                key_tree_id = idx.write_tree(self._repo)
                self._repo.create_commit(self._key2ref(key), author, committer,
                                         message, key_tree_id,
                                         [parent.oid for parent in parents])
            except (pygit2.GitError, OSError) as e:
                if (str(e).startswith('Failed to create reference')
                        or 'directory' in str(e)):
                    raise InvalidKeyError(e)
                else:
                    raise e
Пример #23
0
 def __init__(self, repo, base_committish=None):
     self.repo = repo
     self.index = pygit2.Index()
     self.load_base(base_committish)
Пример #24
0
def convert(repo, branch, product_name, get_branch):
    def add_module(module):
        index.add(
            pygit2.IndexEntry(module.path,
                              module.get().oid, pygit2.GIT_FILEMODE_COMMIT))

    # Find first commit in source branch
    # and create child
    current = CommitPtr(branch, "", product_name)
    current.index = len(current.list) - 1
    heap = [current]
    parents = []

    while heap:

        current = heap[0]
        print(product_name,
              datetime.datetime.utcfromtimestamp(current.get().committer.time))

        if current.path == "":
            parents.append(current.get().oid)
            heap = []
            index = pygit2.Index()
            index.read_tree(repo[current.get().tree_id])

            # Read modules
            blob_id = index[".gitmodules"].id

            text = repo[blob_id].read_raw().decode("latin1")
            modules = ""
            for name, path, tag, url in sorted(
                    x.groups() for x in __parse_modules_re.finditer(text)):

                # Load tag or branch
                if tag is None:
                    tag = "master"

                module = CommitPtr(get_branch(name, url, tag), path,
                                   posixpath.basename(path))

                # Find latest commit before current
                while module.index < len(module.list) and module > current:
                    module.index += 1

                if module.index < len(module.list):
                    add_module(module)

                # Add symbolic links for moved components
                link = path_map.get(module.path)
                if link is not None and current.get(
                ).committer.time < rearrange_date:
                    index.add(
                        pygit2.IndexEntry(
                            link,
                            repo.create_blob(
                                posixpath.relpath(module.path,
                                                  posixpath.dirname(link))),
                            pygit2.GIT_FILEMODE_LINK))

                module.index -= 1
                if module.index >= 0:
                    heapq.heappush(heap, module)

        else:  # current_path == ""
            heapq.heappop(heap)
            add_module(current)

        parents = [
            repo.create_commit(None,
                               current.get().author,
                               current.get().committer,
                               current.name + ": " + current.get().message,
                               index.write_tree(repo), parents)
        ]

        current.index -= 1
        if current.index >= 0:
            heapq.heappush(heap, current)

    print(product_name + " conversion finished")
    return parents[0]
Пример #25
0
args = parse_args()
repo = git.Repository('.')

# FIXME: This assumes a ref pointing to a commit
oid = repo.lookup_reference(args.treeish).resolve().target
commit = repo[oid]
timestamp = commit.committer.time
tree = commit.tree

filename = args.output
if filename == None:
    filename = "%s.tar" % (args.treeish)

out = tarfile.open(filename, mode='w')

index = git.Index()
index.read_tree(tree)

for entry in index:
    content = repo[entry.id].read_raw()
    info = tarfile.TarInfo(entry.name)
    info.size = len(content)
    info.mtime = timestamp
    info.uname = info.gname = 'root'  # just because git does this
    if entry.mode == SYMLINK:
        info.type = tarfile.SYMTYPE
        info.linkname = content
        info.mode = 0777  # symlinks get placeholder

    tar.addfile(info, StringIO(content))
Пример #26
0
def upgrade(source, dest, layer):
    """
    Upgrade a v0.0/v0.1 Sno repository to Sno v0.2
    """
    source = Path(source)
    dest = Path(dest)

    if dest.exists():
        raise click.BadParameter(f"'{dest}': already exists",
                                 param_hint="DEST")

    source_repo = pygit2.Repository(str(source))
    if not source_repo or not source_repo.is_bare:
        raise click.BadParameter(f"'{source}': not an existing repository",
                                 param_hint="SOURCE")

    try:
        source_tree = source_repo.head.peel(pygit2.Tree) / layer
    except KeyError:
        raise click.BadParameter(f"'{layer}' not found in source repository",
                                 param_hint="SOURCE")

    try:
        version_data = json.loads((source_tree / "meta" / "version").data)
        version = tuple([int(v) for v in version_data["version"].split(".")])
    except Exception:
        raise click.BadParameter("Error getting source repository version",
                                 param_hint="SOURCE")

    if version >= (0, 2):
        raise click.BadParameter(
            f"Expecting version <0.2, got {version_data['version']}",
            param_hint="SOURCE",
        )

    # action!
    click.secho(f"Initialising {dest} ...", bold=True)
    dest.mkdir()
    dest_repo = pygit2.init_repository(str(dest), bare=True)

    # walk _all_ references
    source_walker = source_repo.walk(
        source_repo.head.target,
        pygit2.GIT_SORT_TOPOLOGICAL | pygit2.GIT_SORT_REVERSE)
    for ref in source_repo.listall_reference_objects():
        source_walker.push(ref.resolve().target)

    commit_map = {}

    click.secho("\nWriting new commits ...", bold=True)
    for i, source_commit in enumerate(source_walker):
        dest_parents = []
        for parent_id in source_commit.parent_ids:
            try:
                dest_parents.append(commit_map[parent_id.hex])
            except KeyError:
                raise ValueError(
                    f"Commit {i} ({source_commit.id}): Haven't seen parent ({parent_id})"
                )

        source_tree = source_commit.peel(pygit2.Tree) / layer

        sqlite_table_info = json.loads(
            (source_tree / "meta" / "sqlite_table_info").data.decode("utf8"))
        field_cid_map = {r["name"]: r["cid"] for r in sqlite_table_info}

        gpkg_geometry_columns = json.loads(
            (source_tree / "meta" /
             "gpkg_geometry_columns").data.decode("utf8"))
        geom_field = (gpkg_geometry_columns["column_name"]
                      if gpkg_geometry_columns else None)

        pk_field = None
        for field in sqlite_table_info:
            if field["pk"]:
                pk_field = field["name"]
                break
        else:
            if sqlite_table_info[0]["type"] == "INTEGER":
                pk_field = sqlite_table_info[0]["name"]
            else:
                raise ValueError("No primary key field found")

        if i == 0:
            click.echo(
                f"  {layer}: Geometry={geom_field} PrimaryKey={pk_field}")

        dataset = Dataset1(None, layer)
        version = json.dumps(Dataset1.VERSION_CONTENTS).encode("utf8")

        feature_count = 0

        index = pygit2.Index()
        for top_tree, top_path, subtree_names, blob_names in walk_tree(
                source_tree):
            if top_path == "meta":
                # copy meta across as-is
                for blob_name in blob_names:
                    if blob_name == "version":
                        # except version which we update
                        dest_blob = dest_repo.create_blob(version)

                    else:
                        source_blob = top_tree / blob_name
                        dest_blob = dest_repo.create_blob(source_blob.data)

                    index.add(
                        pygit2.IndexEntry(
                            f"{layer}/.sno-table/{top_path}/{blob_name}",
                            dest_blob,
                            pygit2.GIT_FILEMODE_BLOB,
                        ))

                for col, cid in field_cid_map.items():
                    dest_blob = dest_repo.create_blob(json.dumps(cid))
                    index.add(
                        pygit2.IndexEntry(
                            f"{layer}/.sno-table/meta/fields/{col}",
                            dest_blob,
                            pygit2.GIT_FILEMODE_BLOB,
                        ))

                dest_blob = dest_repo.create_blob(json.dumps(pk_field))
                index.add(
                    pygit2.IndexEntry(
                        f"{layer}/.sno-table/meta/primary_key",
                        dest_blob,
                        pygit2.GIT_FILEMODE_BLOB,
                    ))

            elif re.match(
                    r"^features/[a-f0-9]{4}/([a-f0-9]{8}-(?:[a-f0-9]{4}-){3}[a-f0-9]{12})$",
                    top_path,
            ):
                # feature path
                source_feature_dict = {}
                for attr in blob_names:
                    source_blob = top_tree / attr
                    if attr == geom_field:
                        source_feature_dict[attr] = source_blob.data
                    else:
                        source_feature_dict[attr] = json.loads(
                            source_blob.data.decode("utf8"))

                kwargs = {
                    "field_cid_map": field_cid_map,
                    "geom_cols": [geom_field],
                    "primary_key": pk_field,
                    "cast_primary_key": False,
                }

                dest_path, dest_data = dataset.encode_feature(
                    source_feature_dict, **kwargs)
                blob_id = dest_repo.create_blob(dest_data)
                entry = pygit2.IndexEntry(dest_path, blob_id,
                                          pygit2.GIT_FILEMODE_BLOB)
                index.add(entry)

                feature_count += 1

            elif top_path == "" or re.match(r"^features(/[a-f0-9]{4})?$",
                                            top_path):
                pass
            else:
                raise ValueError(f"Unexpected path: '{top_path}'")

        dest_tree = index.write_tree(dest_repo)
        dest_commit = dest_repo.create_commit(
            "HEAD",
            source_commit.author,
            source_commit.committer,
            source_commit.message,
            dest_tree,
            dest_parents,
            # source_commit.message_encoding,
        )
        commit_map[source_commit.hex] = dest_commit.hex

        commit_time = datetime.fromtimestamp(source_commit.commit_time)
        click.echo(
            f"  {i}: {source_commit.hex[:8]} → {dest_commit.hex[:8]} ({commit_time}; {source_commit.committer.name}; {feature_count} rows)"
        )

    click.echo(f"{i+1} commits processed.")

    click.secho("\nUpdating references ...", bold=True)
    for ref in source_repo.listall_reference_objects():
        if ref.type == pygit2.GIT_REF_OID:
            # real references
            target = commit_map[ref.target.hex]
            dest_repo.references.create(ref.name, target, True)  # overwrite
            click.echo(f"  {ref.name} ({ref.target.hex[:8]} → {target[:8]})")

    for ref in source_repo.listall_reference_objects():
        if ref.type == pygit2.GIT_REF_SYMBOLIC:
            dest_repo.references.create(ref.name, ref.target)
            click.echo(f"  {ref.name} → {ref.target}")

    click.secho("\nCompacting repository ...", bold=True)
    subprocess.check_call(["git", "-C", str(dest), "gc"])

    click.secho("\nUpgrade complete", fg="green", bold=True)
Пример #27
0
    def test_bare_index(self):
        index = pygit2.Index(os.path.join(self.repo.path, 'index'))
        self.assertEqual([x.hex for x in index],
                         [x.hex for x in self.repo.index])

        self.assertRaises(pygit2.GitError, lambda: index.add('bye.txt'))
Пример #28
0
 def __init__(self, repo, repo_is_empty):
     self.repo = repo
     self.repo_is_empty = repo_is_empty
     self.index = pygit2.Index()
Пример #29
0
 def __init__(self, repo):
     self.repo = repo
     self.index = pygit2.Index()
     self.last_commit = self.repo.head.target
     self.index.read_tree(self.repo.get(self.last_commit).tree)