From fb179589b968494affb5ce92dd6944df6bfaa7c6 Mon Sep 17 00:00:00 2001 From: Adam Coldrick Date: Thu, 19 Feb 2015 14:37:19 +0000 Subject: Add a class to wrap the OSTree API --- morphlib/__init__.py | 1 + morphlib/ostree.py | 139 +++++++++++++++++++++++++++++++++++++++++++++++++++ without-test-modules | 1 + 3 files changed, 141 insertions(+) create mode 100644 morphlib/ostree.py diff --git a/morphlib/__init__.py b/morphlib/__init__.py index 0c9284d8..e2641402 100644 --- a/morphlib/__init__.py +++ b/morphlib/__init__.py @@ -72,6 +72,7 @@ import morphologyfinder import morphology import morphloader import morphset +import ostree import remoteartifactcache import remoterepocache import repoaliasresolver diff --git a/morphlib/ostree.py b/morphlib/ostree.py new file mode 100644 index 00000000..a2c133f2 --- /dev/null +++ b/morphlib/ostree.py @@ -0,0 +1,139 @@ +from gi.repository import OSTree +from gi.repository import Gio +from gi.repository import GLib + +import os + + +class OSTreeRepo(object): + + """Class to wrap the OSTree API.""" + + OSTREE_GIO_FAST_QUERYINFO = 'standard::name,standard::type,standard::' \ + 'size,standard::is-symlink,standard::syml' \ + 'ink-target,unix::device,unix::inode,unix' \ + '::mode,unix::uid,unix::gid,unix::rdev' + G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS = Gio.FileQueryInfoFlags(1) + cancellable = Gio.Cancellable.new() + + def __init__(self, path, disable_fsync=True): + self.path = path + self.repo = self._open_repo(path, disable_fsync) + + def _open_repo(self, path, disable_fsync=True): + """Create and open and OSTree.Repo, and return it.""" + repo_dir = Gio.file_new_for_path(path) + repo = OSTree.Repo.new(repo_dir) + try: + repo.open(self.cancellable) + except GLib.GError: + if not os.path.exists(path): + os.makedirs(path) + repo.create(OSTree.RepoMode.ARCHIVE_Z2, self.cancellable) + repo.set_disable_fsync(disable_fsync) + return repo + + def refsdir(self): + """Return the abspath to the refs/heads directory in the repo.""" + return os.path.join(os.path.abspath(self.path), 'refs/heads') + + def touch_ref(self, ref): + """Update the mtime of a ref file in repo/refs/heads.""" + os.utime(os.path.join(self.refsdir(), ref), None) + + def resolve_rev(self, branch, allow_noent=True): + """Return the SHA256 corresponding to 'branch'.""" + return self.repo.resolve_rev(branch, allow_noent)[1] + + def read_commit(self, branch): + """Return an OSTree.RepoFile representing a committed tree.""" + return self.repo.read_commit(branch, self.cancellable)[1] + + def query_info(self, file_object): + """Quickly return a Gio.FileInfo for file_object.""" + return file_object.query_info(self.OSTREE_GIO_FAST_QUERYINFO, + self.G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, + self.cancellable) + + def checkout(self, branch, destdir): + """Checkout branch into destdir.""" + checkout_path = destdir + if not os.path.exists(checkout_path): + os.makedirs(checkout_path) + checkout = Gio.file_new_for_path(checkout_path) + + commit = self.read_commit(branch) + commit_info = self.query_info(commit) + self.repo.checkout_tree(0, 1, checkout, commit, + commit_info, self.cancellable) + + def commit(self, subject, srcdir, branch): + """Commit the contents of 'srcdir' to 'branch'.""" + self.repo.prepare_transaction(self.cancellable) + parent = self.resolve_rev(branch) + if parent: + parent_root = self.read_commit(parent) + + mtree = OSTree.MutableTree() + src = Gio.file_new_for_path(srcdir) + self.repo.write_directory_to_mtree(src, mtree, None, self.cancellable) + root = self.repo.write_mtree(mtree, self.cancellable)[1] + if parent and root.equal(parent_root): + return + checksum = self.repo.write_commit(parent, subject, '', None, + root, self.cancellable)[1] + self.repo.transaction_set_ref(None, branch, checksum) + stats = self.repo.commit_transaction(self.cancellable) + + def cat_file(self, ref, path): + """Return the file descriptor of path at ref.""" + commit = self.read_commit(ref) + relative = commit.resolve_relative_path(path) + ret, content, etag = relative.load_contents() + return content + + def list_refs(self, resolved=False): + """Return a list of all refs in the repo.""" + refs = self.repo.list_refs()[1] + if not resolved: + return refs.keys() + return refs + + def delete_ref(self, ref): + """Remove refspec from the repo.""" + if not self.list_refs(ref): + raise Exception("Failed to delete ref, it doesn't exist") + self.repo.set_ref_immediate(None, ref, None, self.cancellable) + + def prune(self): + """Remove unreachable objects from the repo.""" + return self.repo.prune(OSTree.RepoPruneFlags.REFS_ONLY, + -1, self.cancellable) + + def add_remote(self, name, url): + """Add a remote with a given name and url.""" + options_type = GLib.VariantType.new('a{sv}') + options_builder = GLib.VariantBuilder.new(options_type) + options = options_builder.end() + self.repo.remote_add(name, url, options, self.cancellable) + + def remove_remote(self, name): + """Remove a remote with a given name.""" + self.repo.remote_delete(name, self.cancellable) + + def get_remote_url(self, name): + """Return the URL for a remote.""" + return self.repo.remote_get_url(name)[1] + + def list_remotes(self): + """Return a list of all remotes for this repo.""" + return self.repo.remote_list() + + def has_remote(self, name): + """Return True if name is a remote for the repo.""" + return name in self.list_remotes() + + def pull(self, refs, remote): + """Pull ref from remote into the local repo.""" + flags = OSTree.RepoPullFlags.NONE + self.repo.pull(remote, refs, flags, None, self.cancellable) diff --git a/without-test-modules b/without-test-modules index caf10c8b..c3e7f5a2 100644 --- a/without-test-modules +++ b/without-test-modules @@ -54,3 +54,4 @@ distbuild/timer_event_source.py distbuild/worker_build_scheduler.py # Not unit tested, since it needs a full system branch morphlib/buildbranch.py +morphlib/ostree.py -- cgit v1.2.1