[ostree] build: Move sources into src/ again



commit 18f0b537a45f12852e4ec6b174440cbfe7702e4d
Author: Colin Walters <walters verbum org>
Date:   Mon Nov 14 15:39:38 2011 -0500

    build: Move sources into src/ again
    
    This is necessary if we want to build when srcdir == builddir,
    otherwise we blow up because "ostree" is a source directory and a
    binary.

 Makefile-libostree.am                              |   24 +-
 Makefile-osbuild.am                                |    6 +-
 Makefile-ostree.am                                 |   30 +-
 Makefile-otutil.am                                 |   24 +-
 src/libostree/ostree-checkout.c                    |  362 ++++
 src/libostree/ostree-checkout.h                    |   60 +
 src/libostree/ostree-core.c                        |  856 +++++++++
 src/libostree/ostree-core.h                        |  155 ++
 src/libostree/ostree-repo-file-enumerator.c        |  143 ++
 src/libostree/ostree-repo-file-enumerator.h        |   55 +
 src/libostree/ostree-repo-file.c                   | 1428 +++++++++++++++
 src/libostree/ostree-repo-file.h                   |  115 ++
 src/libostree/ostree-repo.c                        | 1877 ++++++++++++++++++++
 src/libostree/ostree-repo.h                        |  162 ++
 libotutil/otutil.h => src/libostree/ostree.h       |   10 +-
 {libotutil => src/libotutil}/ot-gio-utils.c        |    0
 {libotutil => src/libotutil}/ot-gio-utils.h        |    0
 {libotutil => src/libotutil}/ot-glib-compat.c      |    0
 {libotutil => src/libotutil}/ot-glib-compat.h      |    0
 {libotutil => src/libotutil}/ot-opt-utils.c        |    0
 {libotutil => src/libotutil}/ot-opt-utils.h        |    0
 {libotutil => src/libotutil}/ot-unix-utils.c       |    0
 {libotutil => src/libotutil}/ot-unix-utils.h       |    0
 {libotutil => src/libotutil}/ot-variant-utils.c    |    0
 {libotutil => src/libotutil}/ot-variant-utils.h    |    0
 {libotutil => src/libotutil}/otutil.h              |    0
 {osbuild => src/osbuild}/main.c                    |    0
 {osbuild => src/osbuild}/ob-builtin-buildone.c     |    0
 {osbuild => src/osbuild}/ob-builtins.h             |    0
 {osbuild => src/osbuild}/osbuild-raw-makeinstall.c |    0
 {osbuild => src/osbuild}/ostree-buildone           |    0
 {osbuild => src/osbuild}/ostree-buildone-make      |    0
 .../ostree-buildone-makeinstall-split-artifacts    |    0
 {ostree => src/ostree}/main.c                      |    0
 {ostree => src/ostree}/ot-builtin-checkout.c       |    0
 {ostree => src/ostree}/ot-builtin-commit.c         |    0
 {ostree => src/ostree}/ot-builtin-compose.c        |    0
 {ostree => src/ostree}/ot-builtin-diff.c           |    0
 {ostree => src/ostree}/ot-builtin-fsck.c           |    0
 {ostree => src/ostree}/ot-builtin-init.c           |    0
 {ostree => src/ostree}/ot-builtin-log.c            |    0
 {ostree => src/ostree}/ot-builtin-pull.c           |    0
 {ostree => src/ostree}/ot-builtin-remote.c         |    0
 {ostree => src/ostree}/ot-builtin-rev-parse.c      |    0
 {ostree => src/ostree}/ot-builtin-run-triggers.c   |    0
 {ostree => src/ostree}/ot-builtin-show.c           |    0
 {ostree => src/ostree}/ot-builtins.h               |    0
 47 files changed, 5259 insertions(+), 48 deletions(-)
---
diff --git a/Makefile-libostree.am b/Makefile-libostree.am
index 84737a7..091d152 100644
--- a/Makefile-libostree.am
+++ b/Makefile-libostree.am
@@ -19,17 +19,17 @@
 
 noinst_LTLIBRARIES += libostree.la
 
-libostree_la_SOURCES = libostree/ostree.h \
-	libostree/ostree-core.c \
-	libostree/ostree-core.h \
-	libostree/ostree-repo.c \
-	libostree/ostree-repo.h \
-	libostree/ostree-repo-file.c \
-	libostree/ostree-repo-file.h \
-	libostree/ostree-repo-file-enumerator.c \
-	libostree/ostree-repo-file-enumerator.h \
-	libostree/ostree-checkout.c \
-	libostree/ostree-checkout.h \
+libostree_la_SOURCES = src/libostree/ostree.h \
+	src/libostree/ostree-core.c \
+	src/libostree/ostree-core.h \
+	src/libostree/ostree-repo.c \
+	src/libostree/ostree-repo.h \
+	src/libostree/ostree-repo-file.c \
+	src/libostree/ostree-repo-file.h \
+	src/libostree/ostree-repo-file-enumerator.c \
+	src/libostree/ostree-repo-file-enumerator.h \
+	src/libostree/ostree-checkout.c \
+	src/libostree/ostree-checkout.h \
 	$(NULL)
-libostree_la_CFLAGS = $(AM_CFLAGS) -I$(srcdir)/libotutil -I$(srcdir)/libostree -DLOCALEDIR=\"$(datadir)/locale\" $(OT_COREBIN_DEP_CFLAGS)
+libostree_la_CFLAGS = $(AM_CFLAGS) -I$(srcdir)/src/libotutil -I$(srcdir)/src/libostree -DLOCALEDIR=\"$(datadir)/locale\" $(OT_COREBIN_DEP_CFLAGS)
 libostree_la_LIBADD = libotutil.la $(OT_COREBIN_DEP_LIBS)
diff --git a/Makefile-osbuild.am b/Makefile-osbuild.am
index 3f2e56b..8c2b1bd 100644
--- a/Makefile-osbuild.am
+++ b/Makefile-osbuild.am
@@ -15,7 +15,7 @@
 # Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 # Boston, MA 02111-1307, USA.
 
-bin_SCRIPTS += osbuild/ostree-buildone \
-	osbuild/ostree-buildone-make \
-	osbuild/ostree-buildone-makeinstall-split-artifacts \
+bin_SCRIPTS += src/osbuild/ostree-buildone \
+	src/osbuild/ostree-buildone-make \
+	src/osbuild/ostree-buildone-makeinstall-split-artifacts \
 	$(NULL)
diff --git a/Makefile-ostree.am b/Makefile-ostree.am
index b39f9bd..0bb7cde 100644
--- a/Makefile-ostree.am
+++ b/Makefile-ostree.am
@@ -19,23 +19,23 @@
 
 bin_PROGRAMS += ostree
 
-ostree_SOURCES = ostree/main.c \
-	ostree/ot-builtins.h \
-	ostree/ot-builtin-checkout.c \
-	ostree/ot-builtin-commit.c \
-	ostree/ot-builtin-compose.c \
-	ostree/ot-builtin-diff.c \
-	ostree/ot-builtin-fsck.c \
-	ostree/ot-builtin-init.c \
-	ostree/ot-builtin-log.c \
-	ostree/ot-builtin-run-triggers.c \
-	ostree/ot-builtin-remote.c \
-	ostree/ot-builtin-rev-parse.c \
-	ostree/ot-builtin-show.c \
+ostree_SOURCES = src/ostree/main.c \
+	src/ostree/ot-builtins.h \
+	src/ostree/ot-builtin-checkout.c \
+	src/ostree/ot-builtin-commit.c \
+	src/ostree/ot-builtin-compose.c \
+	src/ostree/ot-builtin-diff.c \
+	src/ostree/ot-builtin-fsck.c \
+	src/ostree/ot-builtin-init.c \
+	src/ostree/ot-builtin-log.c \
+	src/ostree/ot-builtin-run-triggers.c \
+	src/ostree/ot-builtin-remote.c \
+	src/ostree/ot-builtin-rev-parse.c \
+	src/ostree/ot-builtin-show.c \
 	$(NULL)
 
 if USE_LIBSOUP_GNOME
-ostree_SOURCES += ostree/ot-builtin-pull.c
+ostree_SOURCES += src/ostree/ot-builtin-pull.c
 endif
-ostree_CFLAGS = $(AM_CFLAGS) -I$(srcdir)/libotutil -I$(srcdir)/libostree -I$(srcdir)/ostree  -DLOCALEDIR=\"$(datadir)/locale\" $(OT_COREBIN_DEP_CFLAGS)
+ostree_CFLAGS = $(AM_CFLAGS) -I$(srcdir)/src/libotutil -I$(srcdir)/src/libostree -I$(srcdir)/src/ostree  -DLOCALEDIR=\"$(datadir)/locale\" $(OT_COREBIN_DEP_CFLAGS)
 ostree_LDADD = libotutil.la libostree.la $(OT_COREBIN_DEP_LIBS)
diff --git a/Makefile-otutil.am b/Makefile-otutil.am
index ff729bd..6d4e492 100644
--- a/Makefile-otutil.am
+++ b/Makefile-otutil.am
@@ -20,17 +20,17 @@
 noinst_LTLIBRARIES += libotutil.la
 
 libotutil_la_SOURCES = \
-	libotutil/ot-opt-utils.c \
-	libotutil/ot-opt-utils.h \
-	libotutil/ot-unix-utils.c \
-	libotutil/ot-unix-utils.h \
-	libotutil/ot-variant-utils.c \
-	libotutil/ot-variant-utils.h \
-	libotutil/ot-gio-utils.c \
-	libotutil/ot-gio-utils.h \
-	libotutil/ot-glib-compat.c \
-	libotutil/ot-glib-compat.h \
-	libotutil/otutil.h \
+	src/libotutil/ot-opt-utils.c \
+	src/libotutil/ot-opt-utils.h \
+	src/libotutil/ot-unix-utils.c \
+	src/libotutil/ot-unix-utils.h \
+	src/libotutil/ot-variant-utils.c \
+	src/libotutil/ot-variant-utils.h \
+	src/libotutil/ot-gio-utils.c \
+	src/libotutil/ot-gio-utils.h \
+	src/libotutil/ot-glib-compat.c \
+	src/libotutil/ot-glib-compat.h \
+	src/libotutil/otutil.h \
 	$(NULL)
-libotutil_la_CFLAGS = $(AM_CFLAGS) -I$(srcdir)/libotutil -DLOCALEDIR=\"$(datadir)/locale\" $(GIO_UNIX_CFLAGS)
+libotutil_la_CFLAGS = $(AM_CFLAGS) -I$(srcdir)/src/libotutil -DLOCALEDIR=\"$(datadir)/locale\" $(GIO_UNIX_CFLAGS)
 libotutil_la_LIBADD = $(GIO_UNIX_LIBS)
diff --git a/src/libostree/ostree-checkout.c b/src/libostree/ostree-checkout.c
new file mode 100644
index 0000000..0d58498
--- /dev/null
+++ b/src/libostree/ostree-checkout.c
@@ -0,0 +1,362 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
+ *
+ * Copyright (C) 2011 Colin Walters <walters verbum org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Colin Walters <walters verbum org>
+ */
+
+#include "config.h"
+
+#include "ostree.h"
+#include "otutil.h"
+
+enum {
+  PROP_0,
+
+  PROP_REPO,
+  PROP_PATH
+};
+
+G_DEFINE_TYPE (OstreeCheckout, ostree_checkout, G_TYPE_OBJECT)
+
+#define GET_PRIVATE(o) \
+  (G_TYPE_INSTANCE_GET_PRIVATE ((o), OSTREE_TYPE_CHECKOUT, OstreeCheckoutPrivate))
+
+typedef struct _OstreeCheckoutPrivate OstreeCheckoutPrivate;
+
+struct _OstreeCheckoutPrivate {
+  OstreeRepo *repo;
+  char *path;
+};
+
+static void
+ostree_checkout_finalize (GObject *object)
+{
+  OstreeCheckout *self = OSTREE_CHECKOUT (object);
+  OstreeCheckoutPrivate *priv = GET_PRIVATE (self);
+
+  g_free (priv->path);
+  g_clear_object (&priv->repo);
+
+  G_OBJECT_CLASS (ostree_checkout_parent_class)->finalize (object);
+}
+
+static void
+ostree_checkout_set_property(GObject         *object,
+			   guint            prop_id,
+			   const GValue    *value,
+			   GParamSpec      *pspec)
+{
+  OstreeCheckout *self = OSTREE_CHECKOUT (object);
+  OstreeCheckoutPrivate *priv = GET_PRIVATE (self);
+
+  switch (prop_id)
+    {
+    case PROP_PATH:
+      priv->path = g_value_dup_string (value);
+      break;
+    case PROP_REPO:
+      priv->repo = g_value_dup_object (value);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+    }
+}
+
+static void
+ostree_checkout_get_property(GObject         *object,
+			   guint            prop_id,
+			   GValue          *value,
+			   GParamSpec      *pspec)
+{
+  OstreeCheckout *self = OSTREE_CHECKOUT (object);
+  OstreeCheckoutPrivate *priv = GET_PRIVATE (self);
+
+  switch (prop_id)
+    {
+    case PROP_PATH:
+      g_value_set_string (value, priv->path);
+      break;
+    case PROP_REPO:
+      g_value_set_object (value, priv->repo);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+    }
+}
+
+static GObject *
+ostree_checkout_constructor (GType                  gtype,
+                           guint                  n_properties,
+                           GObjectConstructParam *properties)
+{
+  GObject *object;
+  GObjectClass *parent_class;
+  OstreeCheckoutPrivate *priv;
+
+  parent_class = G_OBJECT_CLASS (ostree_checkout_parent_class);
+  object = parent_class->constructor (gtype, n_properties, properties);
+
+  priv = GET_PRIVATE (object);
+
+  g_assert (priv->path != NULL);
+  
+  return object;
+}
+
+static void
+ostree_checkout_class_init (OstreeCheckoutClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  g_type_class_add_private (klass, sizeof (OstreeCheckoutPrivate));
+
+  object_class->constructor = ostree_checkout_constructor;
+  object_class->get_property = ostree_checkout_get_property;
+  object_class->set_property = ostree_checkout_set_property;
+  object_class->finalize = ostree_checkout_finalize;
+
+  g_object_class_install_property (object_class,
+                                   PROP_PATH,
+                                   g_param_spec_string ("path", "", "",
+                                                        NULL,
+                                                        G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+
+  g_object_class_install_property (object_class,
+                                   PROP_REPO,
+                                   g_param_spec_object ("repo", "", "",
+                                                        OSTREE_TYPE_REPO,
+                                                        G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+}
+
+static void
+ostree_checkout_init (OstreeCheckout *self)
+{
+}
+
+OstreeCheckout*
+ostree_checkout_new (OstreeRepo  *repo,
+                     const char  *path)
+{
+  return g_object_new (OSTREE_TYPE_CHECKOUT, "repo", repo, "path", path, NULL);
+}
+
+static gboolean
+executable_exists_in_checkout (const char *path,
+                               const char *executable)
+{
+  int i;
+  const char *subdirs[] = {"bin", "sbin", "usr/bin", "usr/sbin"};
+
+  for (i = 0; i < G_N_ELEMENTS (subdirs); i++)
+    {
+      char *possible_path = g_build_filename (path, subdirs[i], executable, NULL);
+      gboolean exists;
+      
+      exists = g_file_test (possible_path, G_FILE_TEST_EXISTS);
+      g_free (possible_path);
+
+      if (exists)
+        return TRUE;
+    }
+
+  return FALSE;
+}
+
+static gboolean
+run_trigger (OstreeCheckout *self,
+             GFile          *trigger,
+             gboolean        requires_chroot,
+             GError        **error)
+{
+  OstreeCheckoutPrivate *priv = GET_PRIVATE (self);
+  gboolean ret = FALSE;
+  char *path = NULL;
+  char *temp_path = NULL;
+  char *rel_temp_path = NULL;
+  GFile *temp_copy = NULL;
+  char *basename = NULL;
+  GPtrArray *args = NULL;
+  int estatus;
+
+  path = g_file_get_path (trigger);
+  basename = g_path_get_basename (path);
+
+  args = g_ptr_array_new ();
+  
+  if (requires_chroot)
+    {
+      temp_path = g_build_filename (priv->path, basename, NULL);
+      rel_temp_path = g_strconcat ("./", basename, NULL);
+      temp_copy = ot_util_new_file_for_path (temp_path);
+
+      if (!g_file_copy (trigger, temp_copy, 0, NULL, NULL, NULL, error))
+        goto out;
+
+      g_ptr_array_add (args, "chroot");
+      g_ptr_array_add (args, ".");
+      g_ptr_array_add (args, rel_temp_path);
+      g_ptr_array_add (args, NULL);
+    }
+  else
+    {
+      g_ptr_array_add (args, path);
+      g_ptr_array_add (args, NULL);
+    }
+      
+  g_print ("Running trigger: %s\n", path);
+  if (!g_spawn_sync (priv->path,
+                     (char**)args->pdata,
+                     NULL,
+                     G_SPAWN_SEARCH_PATH,
+                     NULL, NULL, NULL, NULL,
+                     &estatus,
+                     error))
+    {
+      g_prefix_error (error, "Failed to run trigger %s: ", basename);
+      goto out;
+    }
+
+  ret = TRUE;
+ out:
+  if (requires_chroot && temp_path)
+    (void)unlink (temp_path);
+    
+  g_free (path);
+  g_free (basename);
+  g_free (temp_path);
+  g_free (rel_temp_path);
+  g_clear_object (&temp_copy);
+  if (args)
+    g_ptr_array_free (args, TRUE);
+  return ret;
+}
+
+static gboolean
+check_trigger (OstreeCheckout *self,
+               GFile          *trigger,
+               GError        **error)
+{
+  OstreeCheckoutPrivate *priv = GET_PRIVATE (self);
+  gboolean ret = FALSE;
+  GInputStream *instream = NULL;
+  GDataInputStream *datain = NULL;
+  GError *temp_error = NULL;
+  char *line;
+  gsize len;
+  gboolean requires_chroot = TRUE;
+  gboolean matches = FALSE;
+
+  instream = (GInputStream*)g_file_read (trigger, NULL, error);
+  if (!instream)
+    goto out;
+  datain = g_data_input_stream_new (instream);
+
+  while ((line = g_data_input_stream_read_line (datain, &len, NULL, &temp_error)) != NULL)
+    {
+      if (g_str_has_prefix (line, "# IfExecutable: "))
+        {
+          char *executable = g_strdup (line + strlen ("# IfExecutable: "));
+          g_strchomp (executable);
+          matches = executable_exists_in_checkout (priv->path, executable);
+          g_free (executable);
+        }
+
+      g_free (line);
+    }
+  if (line == NULL && temp_error != NULL)
+    {
+      g_propagate_error (error, temp_error);
+      goto out;
+    }
+  if (matches)
+    {
+      if (!run_trigger (self, trigger, requires_chroot, error))
+        goto out;
+    }
+  
+  ret = TRUE;
+ out:
+  g_clear_object (&instream);
+  g_clear_object (&datain);
+  return ret;
+}
+
+gboolean
+ostree_checkout_run_triggers (OstreeCheckout *self,
+                              GError        **error)
+{
+  gboolean ret = FALSE;
+  GError *temp_error = NULL;
+  char *triggerdir_path = NULL;
+  GFile *triggerdir = NULL;
+  GFileInfo *file_info = NULL;
+  GFileEnumerator *enumerator = NULL;
+
+  triggerdir_path = g_build_filename (LIBEXECDIR, "ostree", "triggers.d", NULL);
+  triggerdir = ot_util_new_file_for_path (triggerdir_path);
+
+  enumerator = g_file_enumerate_children (triggerdir, OSTREE_GIO_FAST_QUERYINFO, 
+                                          G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
+                                          NULL, 
+                                          error);
+  if (!enumerator)
+    goto out;
+
+  while ((file_info = g_file_enumerator_next_file (enumerator, NULL, &temp_error)) != NULL)
+    {
+      const char *name;
+      guint32 type;
+      char *child_path = NULL;
+      GFile *child = NULL;
+      gboolean success;
+
+      name = g_file_info_get_attribute_byte_string (file_info, "standard::name"); 
+      type = g_file_info_get_attribute_uint32 (file_info, "standard::type");
+      
+      if (type == G_FILE_TYPE_REGULAR && g_str_has_suffix (name, ".trigger"))
+        {
+          child_path = g_build_filename (triggerdir_path, name, NULL);
+          child = ot_util_new_file_for_path (child_path);
+
+          success = check_trigger (self, child, error);
+        }
+      else
+        success = TRUE;
+
+      g_object_unref (file_info);
+      g_free (child_path);
+      g_clear_object (&child);
+      if (!success)
+        goto out;
+    }
+  if (file_info == NULL && temp_error != NULL)
+    {
+      g_propagate_error (error, temp_error);
+      goto out;
+    }
+
+  ret = TRUE;
+ out:
+  g_free (triggerdir_path);
+  g_clear_object (&triggerdir);
+  g_clear_object (&enumerator);
+  return ret;
+}
diff --git a/src/libostree/ostree-checkout.h b/src/libostree/ostree-checkout.h
new file mode 100644
index 0000000..042cdc8
--- /dev/null
+++ b/src/libostree/ostree-checkout.h
@@ -0,0 +1,60 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
+ *
+ * Copyright (C) 2011 Colin Walters <walters verbum org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Colin Walters <walters verbum org>
+ */
+
+#ifndef _OSTREE_CHECKOUT
+#define _OSTREE_CHECKOUT
+
+#include <ostree-repo.h>
+
+G_BEGIN_DECLS
+
+#define OSTREE_TYPE_CHECKOUT ostree_checkout_get_type()
+#define OSTREE_CHECKOUT(obj) \
+  (G_TYPE_CHECK_INSTANCE_CAST ((obj), OSTREE_TYPE_CHECKOUT, OstreeCheckout))
+#define OSTREE_CHECKOUT_CLASS(klass) \
+  (G_TYPE_CHECK_CLASS_CAST ((klass), OSTREE_TYPE_CHECKOUT, OstreeCheckoutClass))
+#define OSTREE_IS_CHECKOUT(obj) \
+  (G_TYPE_CHECK_INSTANCE_TYPE ((obj), OSTREE_TYPE_CHECKOUT))
+#define OSTREE_IS_CHECKOUT_CLASS(klass) \
+  (G_TYPE_CHECK_CLASS_TYPE ((klass), OSTREE_TYPE_CHECKOUT))
+#define OSTREE_CHECKOUT_GET_CLASS(obj) \
+  (G_TYPE_INSTANCE_GET_CLASS ((obj), OSTREE_TYPE_CHECKOUT, OstreeCheckoutClass))
+
+typedef struct {
+  GObject parent;
+} OstreeCheckout;
+
+typedef struct {
+  GObjectClass parent_class;
+} OstreeCheckoutClass;
+
+GType ostree_checkout_get_type (void);
+
+OstreeCheckout* ostree_checkout_new (OstreeRepo  *repo,
+                                     const char  *path);
+
+gboolean ostree_checkout_run_triggers (OstreeCheckout *checkout,
+                                       GError        **error);
+
+G_END_DECLS
+
+#endif /* _OSTREE_CHECKOUT */
diff --git a/src/libostree/ostree-core.c b/src/libostree/ostree-core.c
new file mode 100644
index 0000000..ca0fb1f
--- /dev/null
+++ b/src/libostree/ostree-core.c
@@ -0,0 +1,856 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
+ *
+ * Copyright (C) 2011 Colin Walters <walters verbum org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Colin Walters <walters verbum org>
+ */
+
+#include "config.h"
+
+#include "ostree.h"
+#include "otutil.h"
+
+#include <sys/types.h>
+#include <attr/xattr.h>
+
+gboolean
+ostree_validate_checksum_string (const char *sha256,
+                                 GError    **error)
+{
+  if (strlen (sha256) != 64)
+    {
+      g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+                   "Invalid rev '%s'", sha256);
+      return FALSE;
+    }
+  return TRUE;
+}
+
+
+void
+ostree_checksum_update_stat (GChecksum *checksum, guint32 uid, guint32 gid, guint32 mode)
+{
+  guint32 perms = (mode & ~S_IFMT);
+  g_checksum_update (checksum, (guint8*) &uid, 4);
+  g_checksum_update (checksum, (guint8*) &gid, 4);
+  g_checksum_update (checksum, (guint8*) &perms, 4);
+}
+
+static char *
+canonicalize_xattrs (char *xattr_string, size_t len)
+{
+  char *p;
+  GSList *xattrs = NULL;
+  GSList *iter;
+  GString *result;
+
+  result = g_string_new (0);
+
+  p = xattr_string;
+  while (p < xattr_string+len)
+    {
+      xattrs = g_slist_prepend (xattrs, p);
+      p += strlen (p) + 1;
+    }
+
+  xattrs = g_slist_sort (xattrs, (GCompareFunc) strcmp);
+  for (iter = xattrs; iter; iter = iter->next)
+    g_string_append (result, iter->data);
+
+  g_slist_free (xattrs);
+  return g_string_free (result, FALSE);
+}
+
+static gboolean
+read_xattr_name_array (const char *path,
+                       const char *xattrs,
+                       size_t      len,
+                       GVariantBuilder *builder,
+                       GError  **error)
+{
+  gboolean ret = FALSE;
+  const char *p;
+
+  p = xattrs;
+  while (p < xattrs+len)
+    {
+      ssize_t bytes_read;
+      char *buf;
+
+      bytes_read = lgetxattr (path, p, NULL, 0);
+      if (bytes_read < 0)
+        {
+          ot_util_set_error_from_errno (error, errno);
+          goto out;
+        }
+      if (bytes_read == 0)
+        continue;
+
+      buf = g_malloc (bytes_read);
+      if (lgetxattr (path, p, buf, bytes_read) < 0)
+        {
+          ot_util_set_error_from_errno (error, errno);
+          g_free (buf);
+          goto out;
+        }
+      
+      g_variant_builder_add (builder, "(@ay ay)",
+                             g_variant_new_bytestring (p),
+                             g_variant_new_from_data (G_VARIANT_TYPE ("ay"),
+                                                      buf, bytes_read, FALSE, g_free, buf));
+
+      p = p + strlen (p) + 1;
+    }
+  
+  ret = TRUE;
+ out:
+  return ret;
+}
+
+GVariant *
+ostree_get_xattrs_for_path (const char *path,
+                              GError    **error)
+{
+  GVariant *ret = NULL;
+  GVariantBuilder builder;
+  char *xattr_names = NULL;
+  char *xattr_names_canonical = NULL;
+  ssize_t bytes_read;
+
+  g_variant_builder_init (&builder, G_VARIANT_TYPE ("a(ayay)"));
+
+  bytes_read = llistxattr (path, NULL, 0);
+
+  if (bytes_read < 0)
+    {
+      if (errno != ENOTSUP)
+        {
+          ot_util_set_error_from_errno (error, errno);
+          goto out;
+        }
+    }
+  else if (bytes_read > 0)
+    {
+      xattr_names = g_malloc (bytes_read);
+      if (llistxattr (path, xattr_names, bytes_read) < 0)
+        {
+          ot_util_set_error_from_errno (error, errno);
+          goto out;
+        }
+      xattr_names_canonical = canonicalize_xattrs (xattr_names, bytes_read);
+      
+      if (!read_xattr_name_array (path, xattr_names_canonical, bytes_read, &builder, error))
+        goto out;
+    }
+
+  ret = g_variant_builder_end (&builder);
+  g_variant_ref_sink (ret);
+ out:
+  if (!ret)
+    g_variant_builder_clear (&builder);
+  g_free (xattr_names);
+  g_free (xattr_names_canonical);
+  return ret;
+}
+
+gboolean
+ostree_stat_and_checksum_file (int dir_fd, const char *path,
+                               OstreeObjectType objtype,
+                               GChecksum **out_checksum,
+                               struct stat *out_stbuf,
+                               GError **error)
+{
+  GChecksum *content_sha256 = NULL;
+  GChecksum *content_and_meta_sha256 = NULL;
+  char *stat_string = NULL;
+  ssize_t bytes_read;
+  GVariant *xattrs = NULL;
+  int fd = -1;
+  DIR *temp_dir = NULL;
+  char *basename = NULL;
+  gboolean ret = FALSE;
+  char *symlink_target = NULL;
+  char *device_id = NULL;
+  struct stat stbuf;
+
+  basename = g_path_get_basename (path);
+
+  if (dir_fd == -1)
+    {
+      char *dirname = g_path_get_dirname (path);
+      temp_dir = opendir (dirname);
+      if (temp_dir == NULL)
+        {
+          ot_util_set_error_from_errno (error, errno);
+          g_free (dirname);
+        }
+      g_free (dirname);
+      dir_fd = dirfd (temp_dir);
+    }
+
+  if (fstatat (dir_fd, basename, &stbuf, AT_SYMLINK_NOFOLLOW) < 0)
+    {
+      ot_util_set_error_from_errno (error, errno);
+      goto out;
+    }
+
+  if (S_ISREG(stbuf.st_mode))
+    {
+      fd = ot_util_open_file_read_at (dir_fd, basename, error);
+      if (fd < 0)
+        {
+          ot_util_set_error_from_errno (error, errno);
+          goto out;
+        }
+    }
+
+  if (objtype == OSTREE_OBJECT_TYPE_FILE)
+    {
+      xattrs = ostree_get_xattrs_for_path (path, error);
+      if (!xattrs)
+        goto out;
+    }
+
+  content_sha256 = g_checksum_new (G_CHECKSUM_SHA256);
+ 
+  if (S_ISREG(stbuf.st_mode))
+    {
+      guint8 buf[8192];
+
+      while ((bytes_read = read (fd, buf, sizeof (buf))) > 0)
+        g_checksum_update (content_sha256, buf, bytes_read);
+      if (bytes_read < 0)
+        {
+          ot_util_set_error_from_errno (error, errno);
+          goto out;
+        }
+    }
+  else if (S_ISLNK(stbuf.st_mode))
+    {
+      symlink_target = g_malloc (PATH_MAX);
+
+      g_assert (objtype == OSTREE_OBJECT_TYPE_FILE);
+      
+      bytes_read = readlinkat (dir_fd, basename, symlink_target, PATH_MAX);
+      if (bytes_read < 0)
+        {
+          ot_util_set_error_from_errno (error, errno);
+          goto out;
+        }
+      g_checksum_update (content_sha256, (guint8*)symlink_target, bytes_read);
+    }
+  else if (S_ISCHR(stbuf.st_mode) || S_ISBLK(stbuf.st_mode))
+    {
+      g_assert (objtype == OSTREE_OBJECT_TYPE_FILE);
+      device_id = g_strdup_printf ("%u", (guint)stbuf.st_rdev);
+      g_checksum_update (content_sha256, (guint8*)device_id, strlen (device_id));
+    }
+  else if (S_ISFIFO(stbuf.st_mode))
+    {
+      g_assert (objtype == OSTREE_OBJECT_TYPE_FILE);
+    }
+  else
+    {
+      g_set_error (error, G_IO_ERROR,
+                   G_IO_ERROR_FAILED,
+                   "Unsupported file '%s' (must be regular, symbolic link, fifo, or character/block device)",
+                   path);
+      goto out;
+    }
+
+  content_and_meta_sha256 = g_checksum_copy (content_sha256);
+
+  if (objtype == OSTREE_OBJECT_TYPE_FILE)
+    {
+      ostree_checksum_update_stat (content_and_meta_sha256, stbuf.st_uid,
+                                   stbuf.st_gid, stbuf.st_mode);
+      g_checksum_update (content_and_meta_sha256, (guint8*)g_variant_get_data (xattrs), g_variant_get_size (xattrs));
+    }
+
+  *out_stbuf = stbuf;
+  *out_checksum = content_and_meta_sha256;
+  ret = TRUE;
+ out:
+  if (fd >= 0)
+    close (fd);
+  if (temp_dir != NULL)
+    closedir (temp_dir);
+  g_free (symlink_target);
+  g_free (basename);
+  g_free (stat_string);
+  if (xattrs)
+    g_variant_unref (xattrs);
+  if (content_sha256)
+    g_checksum_free (content_sha256);
+  return ret;
+}
+
+gboolean
+ostree_set_xattrs (const char *path, GVariant *xattrs, GCancellable *cancellable, GError **error)
+{
+  gboolean ret = FALSE;
+  int i, n;
+
+  n = g_variant_n_children (xattrs);
+  for (i = 0; i < n; i++)
+    {
+      const guint8* name;
+      GVariant *value;
+      const guint8* value_data;
+      gsize value_len;
+      gboolean loop_err;
+
+      g_variant_get_child (xattrs, i, "(^&ay ay)",
+                           &name, &value);
+      value_data = g_variant_get_fixed_array (value, &value_len, 1);
+      
+      loop_err = lsetxattr (path, (char*)name, (char*)value_data, value_len, XATTR_REPLACE) < 0;
+      
+      g_variant_unref (value);
+      if (loop_err)
+        {
+          ot_util_set_error_from_errno (error, errno);
+          goto out;
+        }
+    }
+
+  ret = TRUE;
+ out:
+  return ret;
+}
+
+gboolean
+ostree_parse_metadata_file (const char                  *path,
+                            OstreeSerializedVariantType *out_type,
+                            GVariant                   **out_variant,
+                            GError                     **error)
+{
+  GFile *pathf = NULL;
+  gboolean ret = FALSE;
+  GVariant *ret_variant = NULL;
+  GVariant *container = NULL;
+  guint32 ret_type;
+
+  pathf = ot_util_new_file_for_path (path);
+  if (!ot_util_variant_map (pathf, G_VARIANT_TYPE (OSTREE_SERIALIZED_VARIANT_FORMAT),
+                            &container, error))
+    goto out;
+
+  g_variant_get (container, "(uv)",
+                 &ret_type, &ret_variant);
+  ret_type = GUINT32_FROM_BE (ret_type);
+  if (ret_type <= 0 || ret_type > OSTREE_SERIALIZED_VARIANT_LAST)
+    {
+      g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+                       "Corrupted metadata object '%s'; invalid type %d", path, ret_type);
+      goto out;
+    }
+
+  ret = TRUE;
+  *out_type = ret_type;
+  *out_variant = ot_util_variant_take_ref (ret_variant);
+  ret_variant = NULL;
+ out:
+  if (ret_variant)
+    g_variant_unref (ret_variant);
+  if (container != NULL)
+    g_variant_unref (container);
+  g_clear_object (&pathf);
+  return ret;
+}
+
+char *
+ostree_get_relative_object_path (const char *checksum,
+                                 OstreeObjectType type,
+                                 gboolean         archive)
+{
+  GString *path;
+  const char *type_string;
+
+  g_assert (strlen (checksum) == 64);
+
+  path = g_string_new ("objects/");
+
+  g_string_append_len (path, checksum, 2);
+  g_string_append_c (path, '/');
+  g_string_append (path, checksum + 2);
+  switch (type)
+    {
+    case OSTREE_OBJECT_TYPE_FILE:
+      if (archive)
+        type_string = ".packfile";
+      else
+        type_string = ".file";
+      break;
+    case OSTREE_OBJECT_TYPE_META:
+      type_string = ".meta";
+      break;
+    default:
+      g_assert_not_reached ();
+    }
+  g_string_append (path, type_string);
+  return g_string_free (path, FALSE);
+}
+
+gboolean
+ostree_pack_object (GOutputStream     *output,
+                    GFile             *file,
+                    OstreeObjectType  objtype,
+                    GCancellable     *cancellable,
+                    GError          **error)
+{
+  gboolean ret = FALSE;
+  char *path = NULL;
+  GFileInfo *finfo = NULL;
+  GFileInputStream *instream = NULL;
+  gboolean pack_builder_initialized = FALSE;
+  GVariantBuilder pack_builder;
+  GVariant *pack_variant = NULL;
+  GVariant *xattrs = NULL;
+  gsize bytes_written;
+
+  path = g_file_get_path (file);
+
+  finfo = g_file_query_info (file, "standard::type,standard::size,standard::is-symlink,standard::symlink-target,unix::*",
+                             G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, cancellable, error);
+  if (!finfo)
+    goto out;
+
+  if (objtype == OSTREE_OBJECT_TYPE_META)
+    {
+      guint64 object_size_be = GUINT64_TO_BE ((guint64)g_file_info_get_size (finfo));
+      if (!g_output_stream_write_all (output, &object_size_be, 8, &bytes_written, cancellable, error))
+        goto out;
+
+      instream = g_file_read (file, NULL, error);
+      if (!instream)
+        goto out;
+      
+      if (g_output_stream_splice (output, (GInputStream*)instream, 0, cancellable, error) < 0)
+        goto out;
+    }
+  else
+    {
+      guint32 uid, gid, mode;
+      guint32 device = 0;
+      guint32 metadata_size_be;
+      const char *target = NULL;
+      guint64 object_size;
+
+      uid = g_file_info_get_attribute_uint32 (finfo, G_FILE_ATTRIBUTE_UNIX_UID);
+      gid = g_file_info_get_attribute_uint32 (finfo, G_FILE_ATTRIBUTE_UNIX_GID);
+      mode = g_file_info_get_attribute_uint32 (finfo, G_FILE_ATTRIBUTE_UNIX_MODE);
+
+      g_variant_builder_init (&pack_builder, G_VARIANT_TYPE (OSTREE_PACK_FILE_VARIANT_FORMAT));
+      pack_builder_initialized = TRUE;
+      g_variant_builder_add (&pack_builder, "u", GUINT32_TO_BE (0));
+      g_variant_builder_add (&pack_builder, "u", GUINT32_TO_BE (uid));
+      g_variant_builder_add (&pack_builder, "u", GUINT32_TO_BE (gid));
+      g_variant_builder_add (&pack_builder, "u", GUINT32_TO_BE (mode));
+
+      xattrs = ostree_get_xattrs_for_path (path, error);
+      if (!xattrs)
+        goto out;
+      g_variant_builder_add (&pack_builder, "@a(ayay)", xattrs);
+
+      if (S_ISREG (mode))
+        {
+          object_size = (guint64)g_file_info_get_size (finfo);
+        }
+      else if (S_ISLNK (mode))
+        {
+          target = g_file_info_get_attribute_byte_string (finfo, G_FILE_ATTRIBUTE_STANDARD_SYMLINK_TARGET);
+          object_size = strlen (target);
+        }
+      else if (S_ISBLK (mode) || S_ISCHR (mode))
+        {
+          device = g_file_info_get_attribute_uint32 (finfo, G_FILE_ATTRIBUTE_UNIX_DEVICE);
+          object_size = 4;
+        }
+      else if (S_ISFIFO (mode))
+        {
+          object_size = 0;
+        }
+      else
+        g_assert_not_reached ();
+
+      g_variant_builder_add (&pack_builder, "t", GUINT64_TO_BE (object_size));
+      pack_variant = g_variant_builder_end (&pack_builder);
+      pack_builder_initialized = FALSE;
+
+      metadata_size_be = GUINT32_TO_BE (g_variant_get_size (pack_variant));
+
+      if (!g_output_stream_write_all (output, &metadata_size_be, 4,
+                                      &bytes_written, cancellable, error))
+        goto out;
+      g_assert (bytes_written == 4);
+
+      if (!g_output_stream_write_all (output, g_variant_get_data (pack_variant), g_variant_get_size (pack_variant),
+                                      &bytes_written, cancellable, error))
+        goto out;
+
+      if (S_ISREG (mode))
+        {
+          instream = g_file_read (file, NULL, error);
+          if (!instream)
+            goto out;
+          bytes_written = g_output_stream_splice (output, (GInputStream*)instream, 0, cancellable, error);
+          if (bytes_written < 0)
+            goto out;
+          if (bytes_written != object_size)
+            {
+              g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+                           "File size changed unexpectedly");
+              goto out;
+            }
+        }
+      else if (S_ISLNK (mode))
+        {
+          if (!g_output_stream_write_all (output, target, object_size,
+                                          &bytes_written, cancellable, error))
+            goto out;
+        }
+      else if (S_ISBLK (mode) || S_ISCHR (mode))
+        {
+          guint32 device_be = GUINT32_TO_BE (device);
+          g_assert (object_size == 4);
+          if (!g_output_stream_write_all (output, &device_be, object_size,
+                                          &bytes_written, cancellable, error))
+            goto out;
+          g_assert (bytes_written == 4);
+        }
+      else if (S_ISFIFO (mode))
+        {
+        }
+      else
+        g_assert_not_reached ();
+    }
+  
+  ret = TRUE;
+ out:
+  g_free (path);
+  g_clear_object (&finfo);
+  g_clear_object (&instream);
+  if (xattrs)
+    g_variant_unref (xattrs);
+  if (pack_builder_initialized)
+    g_variant_builder_clear (&pack_builder);
+  if (pack_variant)
+    g_variant_unref (pack_variant);
+  return ret;
+}
+
+static gboolean
+splice_and_checksum (GOutputStream  *out,
+                     GInputStream   *in,
+                     GChecksum      *checksum,
+                     GCancellable   *cancellable,
+                     GError        **error)
+{
+  gboolean ret = FALSE;
+  
+  if (checksum != NULL)
+    {
+      gsize bytes_read, bytes_written;
+      char buf[4096];
+      do
+        {
+          if (!g_input_stream_read_all (in, buf, sizeof(buf), &bytes_read, cancellable, error))
+            goto out;
+          if (checksum)
+            g_checksum_update (checksum, (guint8*)buf, bytes_read);
+          if (!g_output_stream_write_all (out, buf, bytes_read, &bytes_written, cancellable, error))
+            goto out;
+        }
+      while (bytes_read > 0);
+    }
+  else
+    {
+      if (g_output_stream_splice (out, in, 0, cancellable, error) < 0)
+        goto out;
+    }
+
+  ret = TRUE;
+ out:
+  return ret;
+}
+
+static gboolean
+unpack_meta (const char   *path,
+             const char   *dest_path,    
+             GChecksum   **out_checksum,
+             GError      **error)
+{
+  gboolean ret = FALSE;
+  GFile *file = NULL;
+  GFile *dest_file = NULL;
+  GFileInputStream *in = NULL;
+  GChecksum *ret_checksum = NULL;
+  GFileOutputStream *out = NULL;
+
+  file = ot_util_new_file_for_path (path);
+  dest_file = ot_util_new_file_for_path (dest_path);
+
+  if (out_checksum)
+    ret_checksum = g_checksum_new (G_CHECKSUM_SHA256);
+
+  in = g_file_read (file, NULL, error);
+  if (!in)
+    goto out;
+
+  out = g_file_replace (dest_file, NULL, FALSE, 0, NULL, error);
+  if (!out)
+    goto out;
+
+  if (!splice_and_checksum ((GOutputStream*)out, (GInputStream*)in, ret_checksum, NULL, error))
+    goto out;
+
+  if (!g_output_stream_close ((GOutputStream*)out, NULL, error))
+    goto out;
+
+  ret = TRUE;
+  if (out_checksum)
+    *out_checksum = ret_checksum;
+  ret_checksum = NULL;
+ out:
+  if (!ret)
+    (void) unlink (dest_path);
+  if (ret_checksum)
+    g_checksum_free (ret_checksum);
+  g_clear_object (&file);
+  g_clear_object (&dest_file);
+  g_clear_object (&in);
+  return ret;
+}
+
+gboolean
+ostree_parse_packed_file (GFile            *file,
+                          GVariant    **out_metadata,
+                          GInputStream **out_content,
+                          GCancellable *cancellable,
+                          GError      **error)
+{
+  gboolean ret = FALSE;
+  char *metadata_buf = NULL;
+  GVariant *ret_metadata = NULL;
+  GFileInputStream *in = NULL;
+  guint32 metadata_len;
+  gsize bytes_read;
+
+  in = g_file_read (file, NULL, error);
+  if (!in)
+    goto out;
+      
+  if (!g_input_stream_read_all ((GInputStream*)in, &metadata_len, 4, &bytes_read, NULL, error))
+    goto out;
+  if (bytes_read != 4)
+    {
+      g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+                   "Corrupted packfile; too short while reading metadata length");
+      goto out;
+    }
+      
+  metadata_len = GUINT32_FROM_BE (metadata_len);
+  if (metadata_len > OSTREE_MAX_METADATA_SIZE)
+    {
+      g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+                   "Corrupted packfile; metadata length %u is larger than maximum %u",
+                   metadata_len, OSTREE_MAX_METADATA_SIZE);
+      goto out;
+    }
+  metadata_buf = g_malloc (metadata_len);
+
+  if (!g_input_stream_read_all ((GInputStream*)in, metadata_buf, metadata_len, &bytes_read, NULL, error))
+    goto out;
+  if (bytes_read != metadata_len)
+    {
+      g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+                   "Corrupted packfile; too short while reading metadata");
+      goto out;
+    }
+
+  ret_metadata = g_variant_new_from_data (G_VARIANT_TYPE (OSTREE_PACK_FILE_VARIANT_FORMAT),
+                                          metadata_buf, metadata_len, FALSE,
+                                          (GDestroyNotify)g_free,
+                                          metadata_buf);
+  metadata_buf = NULL;
+
+  ret = TRUE;
+  *out_metadata = ret_metadata;
+  ret_metadata = NULL;
+  *out_content = (GInputStream*)in;
+  in = NULL;
+ out:
+  g_clear_object (&in);
+  if (ret_metadata)
+   g_variant_unref (ret_metadata);
+  return ret;
+}
+
+static gboolean
+unpack_file (const char   *path,
+             const char   *dest_path,    
+             GChecksum   **out_checksum,
+             GError      **error)
+{
+  gboolean ret = FALSE;
+  GFile *file = NULL;
+  GFile *dest_file = NULL;
+  GVariant *metadata = NULL;
+  GVariant *xattrs = NULL;
+  GInputStream *in = NULL;
+  GFileOutputStream *out = NULL;
+  GChecksum *ret_checksum = NULL;
+  guint32 version, uid, gid, mode;
+  guint64 content_len;
+  gsize bytes_read;
+
+  file = ot_util_new_file_for_path (path);
+
+  if (!ostree_parse_packed_file (file, &metadata, &in, NULL, error))
+    goto out;
+
+  g_variant_get (metadata, "(uuuu a(ayay)t)",
+                 &version, &uid, &gid, &mode,
+                 &xattrs, &content_len);
+  uid = GUINT32_FROM_BE (uid);
+  gid = GUINT32_FROM_BE (gid);
+  mode = GUINT32_FROM_BE (mode);
+  content_len = GUINT64_FROM_BE (content_len);
+
+  dest_file = ot_util_new_file_for_path (dest_path);
+      
+  if (out_checksum)
+    ret_checksum = g_checksum_new (G_CHECKSUM_SHA256);
+
+  if (S_ISREG (mode))
+    {
+      out = g_file_replace (dest_file, NULL, FALSE, 0, NULL, error);
+      if (!out)
+        goto out;
+
+      if (!splice_and_checksum ((GOutputStream*)out, in, ret_checksum, NULL, error))
+        goto out;
+
+      if (!g_output_stream_close ((GOutputStream*)out, NULL, error))
+        goto out;
+    }
+  else if (S_ISLNK (mode))
+    {
+      char target[PATH_MAX+1];
+
+      if (!g_input_stream_read_all (in, target, sizeof(target)-1, &bytes_read, NULL, error))
+        goto out;
+      target[bytes_read] = '\0';
+      if (ret_checksum)
+        g_checksum_update (ret_checksum, (guint8*)target, bytes_read);
+      if (symlink (target, dest_path) < 0)
+        {
+          ot_util_set_error_from_errno (error, errno);
+          goto out;
+        }
+    }
+  else if (S_ISCHR (mode) || S_ISBLK (mode))
+    {
+      guint32 dev;
+
+      if (!g_input_stream_read_all (in, &dev, 4, &bytes_read, NULL, error))
+        goto out;
+      if (bytes_read != 4)
+        {
+          g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+                       "Corrupted packfile; too short while reading device id");
+          goto out;
+        }
+      dev = GUINT32_FROM_BE (dev);
+      if (ret_checksum)
+        g_checksum_update (ret_checksum, (guint8*)&dev, 4);
+      if (mknod (dest_path, mode, dev) < 0)
+        {
+          ot_util_set_error_from_errno (error, errno);
+          goto out;
+        }
+    }
+  else if (S_ISFIFO (mode))
+    {
+      if (mkfifo (dest_path, mode) < 0)
+        {
+          ot_util_set_error_from_errno (error, errno);
+          goto out;
+        }
+    }
+  else
+    {
+      g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+                   "Corrupted packfile; invalid mode %u", mode);
+      goto out;
+    }
+
+  if (!S_ISLNK (mode))
+    {
+      if (chmod (dest_path, mode) < 0)
+        {
+          ot_util_set_error_from_errno (error, errno);
+          goto out;
+        }
+    }
+
+  if (!ostree_set_xattrs (dest_path, xattrs, NULL, error))
+    goto out;
+
+  if (ret_checksum)
+    {
+      ostree_checksum_update_stat (ret_checksum, uid, gid, mode);
+      g_checksum_update (ret_checksum, (guint8*)g_variant_get_data (xattrs), g_variant_get_size (xattrs));
+    }
+
+  ret = TRUE;
+  if (out_checksum)
+    *out_checksum = ret_checksum;
+  ret_checksum = NULL;
+ out:
+  if (!ret)
+    (void) unlink (dest_path);
+  if (ret_checksum)
+    g_checksum_free (ret_checksum);
+  g_clear_object (&file);
+  g_clear_object (&dest_file);
+  g_clear_object (&in);
+  g_clear_object (&out);
+  if (metadata)
+   g_variant_unref (metadata);
+  if (xattrs)
+    g_variant_unref (xattrs);
+  return ret;
+}
+
+gboolean
+ostree_unpack_object (const char   *path,
+                      OstreeObjectType  objtype,
+                      const char   *dest_path,    
+                      GChecksum   **out_checksum,
+                      GError      **error)
+{
+  if (objtype == OSTREE_OBJECT_TYPE_META)
+    return unpack_meta (path, dest_path, out_checksum, error);
+  else
+    return unpack_file (path, dest_path, out_checksum, error);
+}
+  
+
+
diff --git a/src/libostree/ostree-core.h b/src/libostree/ostree-core.h
new file mode 100644
index 0000000..d67f42f
--- /dev/null
+++ b/src/libostree/ostree-core.h
@@ -0,0 +1,155 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
+ *
+ * Copyright (C) 2011 Colin Walters <walters verbum org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Colin Walters <walters verbum org>
+ */
+
+#ifndef _OSTREE_CORE
+#define _OSTREE_CORE
+
+#include <otutil.h>
+
+G_BEGIN_DECLS
+
+#define OSTREE_MAX_METADATA_SIZE (1 << 26)
+
+#define OSTREE_GIO_FAST_QUERYINFO "standard::name,standard::type,standard::is-symlink,standard::symlink-target,standard::is-hidden,unix::*"
+
+#define OSTREE_EMPTY_STRING_SHA256 "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855";
+
+typedef enum {
+  OSTREE_OBJECT_TYPE_FILE = 1,
+  OSTREE_OBJECT_TYPE_META = 2,
+} OstreeObjectType;
+
+typedef enum {
+  OSTREE_SERIALIZED_TREE_VARIANT = 1,
+  OSTREE_SERIALIZED_COMMIT_VARIANT = 2,
+  OSTREE_SERIALIZED_DIRMETA_VARIANT = 3,
+  OSTREE_SERIALIZED_XATTR_VARIANT = 4
+} OstreeSerializedVariantType;
+#define OSTREE_SERIALIZED_VARIANT_LAST 4
+
+#define OSTREE_SERIALIZED_VARIANT_FORMAT "(uv)"
+
+/*
+ * xattr objects:
+ * a(ayay) - array of (name, value) pairs, both binary data, though name is a bytestring
+ */
+#define OSTREE_XATTR_GVARIANT_FORMAT "a(ayay)"
+
+#define OSTREE_DIR_META_VERSION 0
+/*
+ * dirmeta objects:
+ * u - Version
+ * u - uid
+ * u - gid
+ * u - mode
+ * a(ayay) - xattrs
+ */
+#define OSTREE_DIRMETA_GVARIANT_FORMAT "(uuuua(ayay))"
+
+#define OSTREE_TREE_VERSION 0
+/*
+ * Tree objects:
+ * u - Version
+ * a{sv} - Metadata
+ * a(ss) - array of (filename, checksum) for files
+ * a(sss) - array of (dirname, tree_checksum, meta_checksum) for directories
+ */
+#define OSTREE_TREE_GVARIANT_FORMAT "(ua{sv}a(ss)a(sss)"
+
+#define OSTREE_COMMIT_VERSION 0
+/*
+ * Commit objects:
+ * u - Version
+ * a{sv} - Metadata
+ * s - parent checksum (empty string for initial)
+ * s - subject 
+ * s - body
+ * t - Timestamp in seconds since the epoch (UTC)
+ * s - Root tree contents
+ * s - Root tree metadata
+ */
+#define OSTREE_COMMIT_GVARIANT_FORMAT "(ua{sv}ssstss)"
+
+gboolean ostree_validate_checksum_string (const char *sha256,
+                                          GError    **error);
+
+char *ostree_get_relative_object_path (const char *checksum,
+                                       OstreeObjectType type,
+                                       gboolean         archive);
+
+GVariant *ostree_get_xattrs_for_path (const char   *path,
+                                      GError     **error);
+
+gboolean ostree_set_xattrs (const char *path, GVariant *xattrs,
+                            GCancellable *cancellable, GError **error);
+
+gboolean ostree_parse_metadata_file (const char                  *path,
+                                     OstreeSerializedVariantType *out_type,
+                                     GVariant                   **out_variant,
+                                     GError                     **error);
+
+gboolean ostree_stat_and_checksum_file (int dirfd, const char *path,
+                                        OstreeObjectType type,
+                                        GChecksum **out_checksum,
+                                        struct stat *out_stbuf,
+                                        GError **error);
+
+/* Packed files:
+ *
+ * guint32 metadata_length [metadata gvariant] [content]
+ *
+ * metadata variant:
+ * u - Version
+ * u - uid
+ * u - gid
+ * u - mode
+ * a(ayay) - xattrs
+ * t - content length
+ *
+ * And then following the end of the variant is the content.  If
+ * symlink, then this is the target; if device, then device ID as
+ * network byte order uint32.
+ */
+#define OSTREE_PACK_FILE_VARIANT_FORMAT "(uuuua(ayay)t)"
+
+gboolean  ostree_pack_object (GOutputStream     *output,
+                              GFile             *file,
+                              OstreeObjectType  objtype,
+                              GCancellable     *cancellable,
+                              GError          **error);
+
+gboolean ostree_parse_packed_file (GFile            *file,
+                                   GVariant    **out_metadata,
+                                   GInputStream **out_content,
+                                   GCancellable *cancellable,
+                                   GError      **error);
+
+gboolean ostree_unpack_object (const char   *path,
+                               OstreeObjectType  objtype,
+                               const char   *dest_path,    
+                               GChecksum   **out_checksum,
+                               GError      **error);
+
+void ostree_checksum_update_stat (GChecksum *checksum, guint32 uid, guint32 gid, guint32 mode);
+
+
+#endif /* _OSTREE_REPO */
diff --git a/src/libostree/ostree-repo-file-enumerator.c b/src/libostree/ostree-repo-file-enumerator.c
new file mode 100644
index 0000000..7858bde
--- /dev/null
+++ b/src/libostree/ostree-repo-file-enumerator.c
@@ -0,0 +1,143 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
+ *
+ * Copyright (C) 2011 Colin Walters <walters verbum org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Colin Walters <walters verbum org>
+ */
+
+#include "config.h"
+
+#include "ostree-repo-file-enumerator.h"
+#include <string.h>
+
+struct _OstreeRepoFileEnumerator
+{
+  GFileEnumerator parent;
+
+  OstreeRepoFile *dir;
+  char *attributes;
+  GFileQueryInfoFlags flags;
+
+  int index;
+};
+
+#define ostree_repo_file_enumerator_get_type _ostree_repo_file_enumerator_get_type
+G_DEFINE_TYPE (OstreeRepoFileEnumerator, ostree_repo_file_enumerator, G_TYPE_FILE_ENUMERATOR);
+
+static GFileInfo *ostree_repo_file_enumerator_next_file (GFileEnumerator  *enumerator,
+						     GCancellable     *cancellable,
+						     GError          **error);
+static gboolean   ostree_repo_file_enumerator_close     (GFileEnumerator  *enumerator,
+						     GCancellable     *cancellable,
+						     GError          **error);
+
+
+static void
+ostree_repo_file_enumerator_dispose (GObject *object)
+{
+  OstreeRepoFileEnumerator *self;
+
+  self = OSTREE_REPO_FILE_ENUMERATOR (object);
+
+  g_clear_object (&self->dir);
+  g_free (self->attributes);
+  
+  if (G_OBJECT_CLASS (ostree_repo_file_enumerator_parent_class)->dispose)
+    G_OBJECT_CLASS (ostree_repo_file_enumerator_parent_class)->dispose (object);
+}
+
+static void
+ostree_repo_file_enumerator_finalize (GObject *object)
+{
+  OstreeRepoFileEnumerator *self;
+
+  self = OSTREE_REPO_FILE_ENUMERATOR (object);
+  (void)self;
+
+  G_OBJECT_CLASS (ostree_repo_file_enumerator_parent_class)->finalize (object);
+}
+
+
+static void
+ostree_repo_file_enumerator_class_init (OstreeRepoFileEnumeratorClass *klass)
+{
+  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+  GFileEnumeratorClass *enumerator_class = G_FILE_ENUMERATOR_CLASS (klass);
+  
+  gobject_class->finalize = ostree_repo_file_enumerator_finalize;
+  gobject_class->dispose = ostree_repo_file_enumerator_dispose;
+
+  enumerator_class->next_file = ostree_repo_file_enumerator_next_file;
+  enumerator_class->close_fn = ostree_repo_file_enumerator_close;
+}
+
+static void
+ostree_repo_file_enumerator_init (OstreeRepoFileEnumerator *self)
+{
+}
+
+GFileEnumerator *
+_ostree_repo_file_enumerator_new (OstreeRepoFile       *dir,
+				  const char           *attributes,
+				  GFileQueryInfoFlags   flags,
+				  GCancellable         *cancellable,
+				  GError              **error)
+{
+  OstreeRepoFileEnumerator *self;
+  
+  self = g_object_new (OSTREE_TYPE_REPO_FILE_ENUMERATOR,
+		       "container", dir,
+		       NULL);
+
+  self->dir = g_object_ref (dir);
+  self->attributes = g_strdup (attributes);
+  self->flags = flags;
+  
+  return G_FILE_ENUMERATOR (self);
+}
+
+static GFileInfo *
+ostree_repo_file_enumerator_next_file (GFileEnumerator  *enumerator,
+				       GCancellable     *cancellable,
+				       GError          **error)
+{
+  OstreeRepoFileEnumerator *self = OSTREE_REPO_FILE_ENUMERATOR (enumerator);
+  gboolean ret = FALSE;
+  GFileInfo *info = NULL;
+
+  if (!_ostree_repo_file_tree_query_child (self->dir, self->index,
+                                           self->attributes, self->flags,
+                                           &info, cancellable, error))
+    goto out;
+
+  self->index++;
+
+  ret = TRUE;
+ out:
+  if (!ret)
+    g_clear_object (&info);
+  return info;
+}
+
+static gboolean
+ostree_repo_file_enumerator_close (GFileEnumerator  *enumerator,
+				   GCancellable     *cancellable,
+				   GError          **error)
+{
+  return TRUE;
+}
diff --git a/src/libostree/ostree-repo-file-enumerator.h b/src/libostree/ostree-repo-file-enumerator.h
new file mode 100644
index 0000000..499a01b
--- /dev/null
+++ b/src/libostree/ostree-repo-file-enumerator.h
@@ -0,0 +1,55 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
+ *
+ * Copyright (C) 2011 Colin Walters <walters verbum org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Colin Walters <walters verbum org>
+ */
+
+#ifndef _OSTREE_REPO_FILE_ENUMERATOR
+#define _OSTREE_REPO_FILE_ENUMERATOR
+
+#include "ostree-repo-file.h"
+
+G_BEGIN_DECLS
+
+#define OSTREE_TYPE_REPO_FILE_ENUMERATOR         (_ostree_repo_file_enumerator_get_type ())
+#define OSTREE_REPO_FILE_ENUMERATOR(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), OSTREE_TYPE_REPO_FILE_ENUMERATOR, OstreeRepoFileEnumerator))
+#define OSTREE_REPO_FILE_ENUMERATOR_CLASS(k)     (G_TYPE_CHECK_CLASS_CAST((k), OSTREE_TYPE_REPO_FILE_ENUMERATOR, OstreeRepoFileEnumeratorClass))
+#define OSTREE_IS_REPO_FILE_ENUMERATOR(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), OSTREE_TYPE_REPO_FILE_ENUMERATOR))
+#define OSTREE_IS_REPO_FILE_ENUMERATOR_CLASS(k)  (G_TYPE_CHECK_CLASS_TYPE ((k), OSTREE_TYPE_REPO_FILE_ENUMERATOR))
+#define OSTREE_REPO_FILE_ENUMERATOR_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), OSTREE_TYPE_REPO_FILE_ENUMERATOR, OstreeRepoFileEnumeratorClass))
+
+typedef struct _OstreeRepoFileEnumerator        OstreeRepoFileEnumerator;
+typedef struct _OstreeRepoFileEnumeratorClass   OstreeRepoFileEnumeratorClass;
+
+struct _OstreeRepoFileEnumeratorClass
+{
+  GFileEnumeratorClass parent_class;
+};
+
+GType   _ostree_repo_file_enumerator_get_type (void) G_GNUC_CONST;
+
+GFileEnumerator * _ostree_repo_file_enumerator_new      (OstreeRepoFile       *dir,
+							 const char           *attributes,
+							 GFileQueryInfoFlags   flags,
+							 GCancellable         *cancellable,
+							 GError              **error);
+
+G_END_DECLS
+
+#endif
diff --git a/src/libostree/ostree-repo-file.c b/src/libostree/ostree-repo-file.c
new file mode 100644
index 0000000..a3d5e79
--- /dev/null
+++ b/src/libostree/ostree-repo-file.c
@@ -0,0 +1,1428 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
+ *
+ * Copyright (C) 2011 Colin Walters <walters verbum org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Colin Walters <walters verbum org>
+ */
+
+#include "config.h"
+
+#include "ostree-repo-file-enumerator.h"
+
+static void ostree_repo_file_file_iface_init (GFileIface *iface);
+
+static void
+tree_replace_contents (OstreeRepoFile  *self,
+                       GVariant        *new_files,
+                       GVariant        *new_dirs);
+
+struct _OstreeRepoFile
+{
+  GObject parent_instance;
+
+  OstreeRepo *repo;
+
+  char *commit;
+  GError *commit_resolve_error;
+  
+  OstreeRepoFile *parent;
+  int index;
+  char *name;
+
+  char *tree_contents_checksum;
+  GVariant *tree_contents;
+  char *tree_metadata_checksum;
+  GVariant *tree_metadata;
+};
+
+#define ostree_repo_file_get_type _ostree_repo_file_get_type
+G_DEFINE_TYPE_WITH_CODE (OstreeRepoFile, ostree_repo_file, G_TYPE_OBJECT,
+			 G_IMPLEMENT_INTERFACE (G_TYPE_FILE,
+						ostree_repo_file_file_iface_init))
+
+static void
+ostree_repo_file_finalize (GObject *object)
+{
+  OstreeRepoFile *self;
+
+  self = OSTREE_REPO_FILE (object);
+
+  if (self->tree_contents)
+    g_variant_unref (self->tree_contents);
+  if (self->tree_metadata)
+    g_variant_unref (self->tree_metadata);
+  g_free (self->tree_contents_checksum);
+  g_free (self->tree_metadata_checksum);
+  g_free (self->commit);
+  g_free (self->name);
+
+  G_OBJECT_CLASS (ostree_repo_file_parent_class)->finalize (object);
+}
+
+static void
+ostree_repo_file_dispose (GObject *object)
+{
+  OstreeRepoFile *self;
+
+  self = OSTREE_REPO_FILE (object);
+
+  g_clear_object (&self->repo);
+  g_clear_object (&self->parent);
+
+  if (G_OBJECT_CLASS (ostree_repo_file_parent_class)->dispose)
+    G_OBJECT_CLASS (ostree_repo_file_parent_class)->dispose (object);
+}
+
+static void
+ostree_repo_file_class_init (OstreeRepoFileClass *klass)
+{
+  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+  gobject_class->finalize = ostree_repo_file_finalize;
+  gobject_class->dispose = ostree_repo_file_dispose;
+}
+
+static void
+ostree_repo_file_init (OstreeRepoFile *self)
+{
+  self->index = -1;
+}
+
+static gboolean
+set_error_noent (GFile *self, GError **error)
+{
+  char *path = g_file_get_path (self);
+  g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND,
+               "No such file or directory: %s", path);
+  g_free (path);
+  return FALSE;
+}
+
+GFile * 
+_ostree_repo_file_new_root (OstreeRepo  *repo,
+                            const char  *commit)
+{
+  OstreeRepoFile *self;
+
+  g_return_val_if_fail (repo != NULL, NULL);
+  g_return_val_if_fail (commit != NULL, NULL);
+  g_return_val_if_fail (strlen (commit) == 64, NULL);
+
+  self = g_object_new (OSTREE_TYPE_REPO_FILE, NULL);
+  self->repo = g_object_ref (repo);
+  self->commit = g_strdup (commit);
+
+  return G_FILE (self);
+}
+
+
+GFile *
+_ostree_repo_file_new_child (OstreeRepoFile *parent,
+                             const char  *name)
+{
+  OstreeRepoFile *self;
+  
+  self = g_object_new (OSTREE_TYPE_REPO_FILE, NULL);
+  self->repo = g_object_ref (parent->repo);
+  self->parent = g_object_ref (parent);
+  self->name = g_strdup (name);
+
+  return G_FILE (self);
+}
+
+OstreeRepoFile *
+_ostree_repo_file_new_empty_tree (OstreeRepo  *repo)
+{
+  OstreeRepoFile *self;
+  
+  self = g_object_new (OSTREE_TYPE_REPO_FILE, NULL);
+  self->repo = g_object_ref (repo);
+
+  tree_replace_contents (self, NULL, NULL);
+
+  return self;
+}
+
+static gboolean
+do_resolve_commit (OstreeRepoFile  *self,
+                   GError         **error)
+{
+  gboolean ret = FALSE;
+  GVariant *commit = NULL;
+  GVariant *root_contents = NULL;
+  GVariant *root_metadata = NULL;
+  const char *tree_contents_checksum;
+  const char *tree_meta_checksum;
+
+  g_assert (self->parent == NULL);
+
+  if (!ostree_repo_load_variant_checked (self->repo, OSTREE_SERIALIZED_COMMIT_VARIANT,
+                                         self->commit, &commit, error))
+    goto out;
+
+  /* PARSE OSTREE_SERIALIZED_COMMIT_VARIANT */
+  g_variant_get_child (commit, 6, "&s", &tree_contents_checksum);
+  g_variant_get_child (commit, 7, "&s", &tree_meta_checksum);
+
+  if (!ostree_repo_load_variant_checked (self->repo, OSTREE_SERIALIZED_TREE_VARIANT,
+                                         tree_contents_checksum, &root_contents,
+					 error))
+    goto out;
+
+  if (!ostree_repo_load_variant_checked (self->repo, OSTREE_SERIALIZED_DIRMETA_VARIANT,
+                                         tree_meta_checksum, &root_metadata,
+					 error))
+    goto out;
+  
+  self->tree_metadata = root_metadata;
+  root_metadata = NULL;
+  self->tree_contents = root_contents;
+  root_contents = NULL;
+
+ out:
+  if (commit)
+    g_variant_unref (commit);
+  if (root_metadata)
+    g_variant_unref (root_metadata);
+  if (root_contents)
+    g_variant_unref (root_contents);
+  return ret;
+}
+
+static gboolean
+do_resolve_nonroot (OstreeRepoFile     *self,
+                    GError            **error)
+{
+  gboolean ret = FALSE;
+  GVariant *container = NULL;
+  GVariant *tree_contents = NULL;
+  GVariant *tree_metadata = NULL;
+  gboolean is_dir;
+  int i;
+
+  i = _ostree_repo_file_tree_find_child (self->parent, self->name, &is_dir, &container);
+  
+  if (i < 0)
+    {
+      set_error_noent ((GFile*)self, error);
+      goto out;
+    }
+
+  if (is_dir)
+    {
+      const char *name;
+      const char *content_checksum;
+      const char *metadata_checksum;
+      GVariant *files_variant;
+
+      files_variant = g_variant_get_child_value (self->parent->tree_contents, 2);
+      self->index = g_variant_n_children (files_variant) + i;
+      g_variant_unref (files_variant);
+
+      g_variant_get_child (container, i, "(&s&s&s)",
+                           &name, &content_checksum, &metadata_checksum);
+          
+      if (!ostree_repo_load_variant_checked (self->repo, OSTREE_SERIALIZED_TREE_VARIANT,
+                                             content_checksum, &tree_contents,
+                                             error))
+        goto out;
+          
+      if (!ostree_repo_load_variant_checked (self->repo, OSTREE_SERIALIZED_DIRMETA_VARIANT,
+                                             metadata_checksum, &tree_metadata,
+                                             error))
+        goto out;
+
+      self->tree_contents = tree_contents;
+      tree_contents = NULL;
+      self->tree_metadata = tree_metadata;
+      tree_metadata = NULL;
+    }
+  else
+    self->index = i;
+
+  ret = TRUE;
+ out:
+  if (container)
+    g_variant_unref (container);
+  if (tree_metadata)
+    g_variant_unref (tree_metadata);
+  if (tree_contents)
+    g_variant_unref (tree_contents);
+  return ret;
+}
+
+gboolean
+_ostree_repo_file_ensure_resolved (OstreeRepoFile  *self,
+                                   GError         **error)
+{
+  if (self->commit_resolve_error != NULL)
+    goto out;
+
+  if (self->parent == NULL)
+    {
+      if (self->tree_contents == NULL)
+        (void)do_resolve_commit (self, &(self->commit_resolve_error));
+    }
+  else if (self->index == -1)
+    {
+      (void)do_resolve_nonroot (self, &(self->commit_resolve_error));
+    }
+  
+ out:
+  if (self->commit_resolve_error)
+    {
+      if (error)
+	*error = g_error_copy (self->commit_resolve_error);
+      return FALSE;
+    }
+  else
+    return TRUE;
+}
+
+gboolean
+_ostree_repo_file_get_xattrs (OstreeRepoFile  *self,
+                              GVariant       **out_xattrs,
+                              GCancellable    *cancellable,
+                              GError         **error)
+{
+  gboolean ret = FALSE;
+  GVariant *ret_xattrs = NULL;
+  GVariant *metadata = NULL;
+  GInputStream *input = NULL;
+  GFile *local_file = NULL;
+
+  if (!_ostree_repo_file_ensure_resolved (self, error))
+    goto out;
+
+  if (self->tree_metadata)
+    ret_xattrs = g_variant_get_child_value (self->tree_metadata, 4);
+  else if (ostree_repo_is_archive (self->repo))
+    {
+      local_file = _ostree_repo_file_nontree_get_local (self);
+      if (!ostree_parse_packed_file (local_file, &metadata, &input, cancellable, error))
+        goto out;
+      ret_xattrs = g_variant_get_child_value (metadata, 4);
+    }
+  else
+    {
+      local_file = _ostree_repo_file_nontree_get_local (self);
+      ret_xattrs = ostree_get_xattrs_for_path (ot_gfile_get_path_cached (local_file), error);
+    }
+
+  ret = TRUE;
+  *out_xattrs = ret_xattrs;
+  ret_xattrs = NULL;
+ out:
+  if (ret_xattrs)
+    g_variant_unref (ret_xattrs);
+  if (metadata)
+    g_variant_unref (metadata);
+  g_clear_object (&input);
+  g_clear_object (&local_file);
+  return ret;
+}
+
+GVariant *
+_ostree_repo_file_tree_get_contents (OstreeRepoFile  *self)
+{
+  return self->tree_contents;
+}
+
+GVariant *
+_ostree_repo_file_tree_get_metadata (OstreeRepoFile  *self)
+{
+  return self->tree_metadata;
+}
+
+void
+_ostree_repo_file_tree_set_metadata (OstreeRepoFile *self,
+                                     const char     *checksum,
+                                     GVariant       *metadata)
+{
+  if (self->tree_metadata)
+    g_variant_unref (self->tree_metadata);
+  self->tree_metadata = g_variant_ref (metadata);
+  g_free (self->tree_metadata_checksum);
+  self->tree_metadata_checksum = g_strdup (checksum);
+}
+
+void
+_ostree_repo_file_make_empty_tree (OstreeRepoFile  *self)
+{
+  tree_replace_contents (self, NULL, NULL);
+}
+
+void
+_ostree_repo_file_tree_set_content_checksum (OstreeRepoFile  *self,
+                                             const char      *checksum)
+{
+  g_assert (self->parent == NULL);
+  g_free (self->tree_contents_checksum);
+  self->tree_contents_checksum = g_strdup (checksum);
+}
+
+const char *
+_ostree_repo_file_tree_get_content_checksum (OstreeRepoFile  *self)
+{
+  g_assert (self->parent == NULL);
+  return self->tree_contents_checksum;
+}
+
+GFile *
+_ostree_repo_file_nontree_get_local (OstreeRepoFile  *self)
+{
+  const char *checksum;
+  char *path;
+  GFile *ret;
+
+  g_assert (!ostree_repo_is_archive (self->repo));
+
+  checksum = _ostree_repo_file_nontree_get_checksum (self);
+  path = ostree_repo_get_object_path (self->repo, checksum, OSTREE_OBJECT_TYPE_FILE);
+  ret = ot_util_new_file_for_path (path);
+  g_free (path);
+  
+  return ret;
+}
+
+OstreeRepo *
+_ostree_repo_file_get_repo (OstreeRepoFile  *self)
+{
+  return self->repo;
+}
+
+OstreeRepoFile *
+_ostree_repo_file_get_root (OstreeRepoFile  *self)
+{
+  OstreeRepoFile *parent = self;
+
+  while (parent->parent)
+    parent = parent->parent;
+  return parent;
+}
+
+const char *
+_ostree_repo_file_nontree_get_checksum (OstreeRepoFile  *self)
+{
+  int n;
+  gboolean is_dir;
+
+  g_assert (self->parent);
+
+  n = _ostree_repo_file_tree_find_child (self->parent, self->name, &is_dir, NULL);
+  g_assert (n >= 0 && !is_dir);
+  
+  return _ostree_repo_file_tree_get_child_checksum (self->parent, n);
+}
+
+const char *
+_ostree_repo_file_tree_get_child_checksum (OstreeRepoFile  *self,
+                                           int n)
+{
+  GVariant *files_variant;
+  const char *checksum;
+
+  g_assert (self->tree_contents);
+
+  files_variant = g_variant_get_child_value (self->tree_contents, 2);
+
+  g_variant_get_child (files_variant, n, "(@s&s)", NULL, &checksum);
+
+  g_variant_unref (files_variant);
+
+  return checksum;
+}
+
+static gboolean
+ostree_repo_file_is_native (GFile *file)
+{
+  return FALSE;
+}
+
+static gboolean
+ostree_repo_file_has_uri_scheme (GFile      *file,
+				 const char *uri_scheme)
+{
+  return g_ascii_strcasecmp (uri_scheme, "ostree") == 0;
+}
+
+static char *
+ostree_repo_file_get_uri_scheme (GFile *file)
+{
+  return g_strdup ("ostree");
+}
+
+static char *
+ostree_repo_file_get_basename (GFile *file)
+{
+  OstreeRepoFile *self = OSTREE_REPO_FILE (file);
+  return g_strdup (self->name);
+}
+
+static char *
+ostree_repo_file_get_path (GFile *file)
+{
+  OstreeRepoFile *self = OSTREE_REPO_FILE (file);
+  OstreeRepoFile *parent;
+  GString *buf;
+  GSList *parents;
+  GSList *iter;
+
+  buf = g_string_new ("");
+  parents = NULL;
+
+  for (parent = self->parent; parent; parent = parent->parent)
+    parents = g_slist_prepend (parents, parent);
+
+  if (parents->next)
+    {
+      for (iter = parents->next; iter; iter = iter->next)
+        {
+          parent = iter->data;
+          g_string_append_c (buf, '/');
+          g_string_append (buf, parent->name);
+        }
+    }
+  g_string_append_c (buf, '/');
+  g_string_append (buf, self->name);
+
+  g_slist_free (parents);
+
+  return g_string_free (buf, FALSE);
+}
+
+static char *
+ostree_repo_file_get_uri (GFile *file)
+{
+  OstreeRepoFile *self = OSTREE_REPO_FILE (file);
+  char *path;
+  char *uri_path;
+  char *ret;
+
+  path = g_file_get_path (file);
+  uri_path = g_filename_to_uri (path, NULL, NULL);
+  g_free (path);
+  g_assert (g_str_has_prefix (uri_path, "file://"));
+  ret = g_strconcat ("ostree://", self->commit, uri_path+strlen("file://"), NULL);
+  g_free (uri_path);
+
+  return ret;
+}
+
+static char *
+ostree_repo_file_get_parse_name (GFile *file)
+{
+  return ostree_repo_file_get_uri (file);
+}
+
+static GFile *
+ostree_repo_file_get_parent (GFile *file)
+{
+  OstreeRepoFile *self = OSTREE_REPO_FILE (file);
+
+  return g_object_ref (self->parent);
+}
+
+static GFile *
+ostree_repo_file_dup (GFile *file)
+{
+  OstreeRepoFile *self = OSTREE_REPO_FILE (file);
+
+  if (self->parent)
+    return _ostree_repo_file_new_child (self->parent, self->name);
+  else
+    return _ostree_repo_file_new_root (self->repo, self->commit);
+}
+
+static guint
+ostree_repo_file_hash (GFile *file)
+{
+  OstreeRepoFile *self = OSTREE_REPO_FILE (file);
+  
+  if (self->parent)
+    return g_file_hash (self->parent) + g_str_hash (self->name);
+  else
+    return g_str_hash (self->commit);
+}
+
+static gboolean
+ostree_repo_file_equal (GFile *file1,
+                        GFile *file2)
+{
+  OstreeRepoFile *self1 = OSTREE_REPO_FILE (file1);
+  OstreeRepoFile *self2 = OSTREE_REPO_FILE (file2);
+
+  if (self1->parent && self2->parent)
+    {
+      return g_str_equal (self1->name, self2->name)
+        && g_file_equal ((GFile*)self1->parent, (GFile*)self2->parent);
+    }
+  else if (!self1->parent && !self2->parent)
+    {
+      return g_str_equal (self1->commit, self2->commit);
+    }
+  else
+    return FALSE;
+}
+
+static const char *
+match_prefix (const char *path, 
+              const char *prefix)
+{
+  int prefix_len;
+
+  prefix_len = strlen (prefix);
+  if (strncmp (path, prefix, prefix_len) != 0)
+    return NULL;
+  
+  /* Handle the case where prefix is the root, so that
+   * the IS_DIR_SEPRARATOR check below works */
+  if (prefix_len > 0 &&
+      G_IS_DIR_SEPARATOR (prefix[prefix_len-1]))
+    prefix_len--;
+  
+  return path + prefix_len;
+}
+
+static gboolean
+ostree_repo_file_prefix_matches (GFile *parent,
+				 GFile *descendant)
+{
+  const char *remainder;
+  char *parent_path;
+  char *descendant_path;
+
+  parent_path = g_file_get_path (parent);
+  descendant_path = g_file_get_path (descendant);
+  remainder = match_prefix (descendant_path, parent_path);
+  g_free (parent_path);
+  g_free (descendant_path);
+  if (remainder != NULL && G_IS_DIR_SEPARATOR (*remainder))
+    return TRUE;
+  return FALSE;
+}
+
+static char *
+ostree_repo_file_get_relative_path (GFile *parent,
+				    GFile *descendant)
+{
+  const char *remainder;
+  char *parent_path;
+  char *descendant_path;
+
+  parent_path = g_file_get_path (parent);
+  descendant_path = g_file_get_path (descendant);
+  remainder = match_prefix (descendant_path, parent_path);
+  g_free (parent_path);
+  g_free (descendant_path);
+  
+  if (remainder != NULL && G_IS_DIR_SEPARATOR (*remainder))
+    return g_strdup (remainder + 1);
+  return NULL;
+}
+
+static GFile *
+ostree_repo_file_resolve_relative_path (GFile      *file,
+					const char *relative_path)
+{
+  OstreeRepoFile *self = OSTREE_REPO_FILE (file);
+  OstreeRepoFile *parent;
+  char *filename;
+  const char *rest;
+  GFile *ret;
+
+  if (g_path_is_absolute (relative_path) && self->parent)
+    {
+      g_assert (*relative_path == '/');
+      return ostree_repo_file_resolve_relative_path ((GFile*)_ostree_repo_file_get_root (self),
+                                                     relative_path+1);
+    }
+
+  rest = strchr (relative_path, '/');
+  if (rest)
+    {
+      rest += 1;
+      filename = g_strndup (relative_path, rest - relative_path);
+    }
+  else
+    filename = g_strdup (relative_path);
+
+  parent = (OstreeRepoFile*)_ostree_repo_file_new_child (self, filename);
+  g_free (filename);
+    
+  if (!rest)
+    ret = (GFile*)parent;
+  else
+    {
+      ret = ostree_repo_file_resolve_relative_path ((GFile*)parent, rest);
+      g_clear_object (&parent);
+    }
+  return ret;
+}
+
+static GFileEnumerator *
+ostree_repo_file_enumerate_children (GFile                *file,
+				     const char           *attributes,
+				     GFileQueryInfoFlags   flags,
+				     GCancellable         *cancellable,
+				     GError              **error)
+{
+  OstreeRepoFile *self = OSTREE_REPO_FILE (file);
+  return _ostree_repo_file_enumerator_new (self,
+					   attributes, flags,
+					   cancellable, error);
+}
+
+static GFile *
+ostree_repo_file_get_child_for_display_name (GFile        *file,
+					 const char   *display_name,
+					 GError      **error)
+{
+  return g_file_get_child (file, display_name);
+}
+
+static GFile *
+get_child_local_file (OstreeRepo   *repo,
+                      const char   *checksum)
+{
+  char *path;
+  GFile *ret;
+
+  path = ostree_repo_get_object_path (repo, checksum, OSTREE_OBJECT_TYPE_FILE);
+  ret = ot_util_new_file_for_path (path);
+  g_free (path);
+  
+  return ret;
+}
+
+static gboolean
+query_child_info_file_nonarchive (OstreeRepo       *repo,
+                                  const char       *checksum,
+                                  GFileAttributeMatcher *matcher,
+                                  GFileInfo        *info,
+                                  GCancellable     *cancellable,
+                                  GError          **error)
+{
+  gboolean ret = FALSE;
+  GFileInfo *local_info = NULL;
+  GFile *local_file = NULL;
+  int i ;
+  const char *mapped_boolean[] = {
+    "standard::is-symlink"
+  };
+  const char *mapped_string[] = {
+  };
+  const char *mapped_byte_string[] = {
+    "standard::symlink-target"
+  };
+  const char *mapped_uint32[] = {
+    "standard::type",
+    "unix::device",
+    "unix::mode",
+    "unix::nlink",
+    "unix::uid",
+    "unix::gid",
+    "unix::rdev"
+  };
+  const char *mapped_uint64[] = {
+    "standard::size",
+    "standard::allocated-size",
+    "unix::inode"
+  };
+
+  if (!(g_file_attribute_matcher_matches (matcher, "unix::mode")
+        || g_file_attribute_matcher_matches (matcher, "standard::type")))
+    {
+      ret = TRUE;
+      goto out;
+    }
+
+  local_file = get_child_local_file (repo, checksum);
+  local_info = g_file_query_info (local_file,
+				  OSTREE_GIO_FAST_QUERYINFO,
+				  G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
+				  cancellable,
+				  error);
+  if (!local_info)
+    goto out;
+
+  for (i = 0; i < G_N_ELEMENTS (mapped_boolean); i++)
+    g_file_info_set_attribute_boolean (info, mapped_boolean[i], g_file_info_get_attribute_boolean (local_info, mapped_boolean[i]));
+
+  for (i = 0; i < G_N_ELEMENTS (mapped_string); i++)
+    {
+      const char *string = g_file_info_get_attribute_string (local_info, mapped_string[i]);
+      if (string)
+        g_file_info_set_attribute_string (info, mapped_string[i], string);
+    }
+
+  for (i = 0; i < G_N_ELEMENTS (mapped_byte_string); i++)
+    {
+      const char *byte_string = g_file_info_get_attribute_byte_string (local_info, mapped_byte_string[i]);
+      if (byte_string)
+        g_file_info_set_attribute_byte_string (info, mapped_byte_string[i], byte_string);
+    }
+
+  for (i = 0; i < G_N_ELEMENTS (mapped_uint32); i++)
+    g_file_info_set_attribute_uint32 (info, mapped_uint32[i], g_file_info_get_attribute_uint32 (local_info, mapped_uint32[i]));
+
+  for (i = 0; i < G_N_ELEMENTS (mapped_uint64); i++)
+    g_file_info_set_attribute_uint64 (info, mapped_uint64[i], g_file_info_get_attribute_uint64 (local_info, mapped_uint64[i]));
+  
+  ret = TRUE;
+ out:
+  g_clear_object (&local_info);
+  g_clear_object (&local_file);
+  return ret;
+}
+
+static gboolean
+query_child_info_file_archive (OstreeRepo       *repo,
+                               const char       *checksum,
+                               GFileAttributeMatcher *matcher,
+                               GFileInfo        *info,
+                               GCancellable     *cancellable,
+                               GError          **error)
+{
+  gboolean ret = FALSE;
+  GFile *local_file = NULL;
+  GVariant *metadata = NULL;
+  GInputStream *input = NULL;
+  guint32 version, uid, gid, mode;
+  guint64 content_len;
+  guint32 file_type;
+  gsize bytes_read;
+  char *buf = NULL;
+
+  local_file = get_child_local_file (repo, checksum);
+
+  if (!ostree_parse_packed_file (local_file, &metadata, &input, cancellable, error))
+    goto out;
+
+  g_variant_get (metadata, "(uuuu a(ayay)t)",
+                 &version, &uid, &gid, &mode,
+                 NULL, &content_len);
+  uid = GUINT32_FROM_BE (uid);
+  gid = GUINT32_FROM_BE (gid);
+  mode = GUINT32_FROM_BE (mode);
+  content_len = GUINT64_FROM_BE (content_len);
+
+  g_file_info_set_attribute_boolean (info, "standard::is-symlink",
+                                     S_ISLNK (mode));
+  if (S_ISLNK (mode))
+    file_type = G_FILE_TYPE_SYMBOLIC_LINK;
+  else if (S_ISREG (mode))
+    file_type = G_FILE_TYPE_REGULAR;
+  else if (S_ISBLK (mode) || S_ISCHR(mode) || S_ISFIFO(mode))
+    file_type = G_FILE_TYPE_SPECIAL;
+  else
+    {
+      g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+                   "Corrupted packfile %s: Invalid mode", checksum);
+      goto out;
+    }
+  g_file_info_set_attribute_uint32 (info, "standard::type", file_type);
+
+  g_file_info_set_attribute_uint32 (info, "unix::uid", uid);
+  g_file_info_set_attribute_uint32 (info, "unix::gid", gid);
+  g_file_info_set_attribute_uint32 (info, "unix::mode", mode);
+
+  if (file_type == G_FILE_TYPE_REGULAR)
+    {
+      g_file_info_set_attribute_uint64 (info, "standard::size", content_len);
+    }
+  else if (file_type == G_FILE_TYPE_SYMBOLIC_LINK)
+    {
+      gsize len = MIN (PATH_MAX, content_len) + 1;
+      buf = g_malloc (len);
+
+      if (!g_input_stream_read_all (input, buf, len, &bytes_read, cancellable, error))
+        goto out;
+      buf[bytes_read] = '\0';
+
+      g_file_info_set_attribute_byte_string (info, "standard::symlink-target", buf);
+    }
+  else if (file_type == G_FILE_TYPE_SPECIAL)
+    {
+      guint32 device;
+
+      if (!g_input_stream_read_all (input, &device, 4, &bytes_read, cancellable, error))
+        goto out;
+
+      device = GUINT32_FROM_BE (device);
+      g_file_info_set_attribute_uint32 (info, "unix::device", device);
+    }
+
+  ret = TRUE;
+ out:
+  g_free (buf);
+  if (metadata)
+    g_variant_unref (metadata);
+  g_clear_object (&local_file);
+  g_clear_object (&input);
+  return ret;
+}
+
+static void
+set_info_from_dirmeta (GFileInfo  *info,
+                       GVariant   *metadata)
+{
+  guint32 version, uid, gid, mode;
+
+  g_file_info_set_attribute_uint32 (info, "standard::type", G_FILE_TYPE_DIRECTORY);
+
+  /* PARSE OSTREE_SERIALIZED_DIRMETA_VARIANT */
+  g_variant_get (metadata, "(uuuu a(ayay))",
+                 &version, &uid, &gid, &mode,
+                 NULL);
+  version = GUINT32_FROM_BE (version);
+  uid = GUINT32_FROM_BE (uid);
+  gid = GUINT32_FROM_BE (gid);
+  mode = GUINT32_FROM_BE (mode);
+
+  g_file_info_set_attribute_uint32 (info, "unix::uid", uid);
+  g_file_info_set_attribute_uint32 (info, "unix::gid", gid);
+  g_file_info_set_attribute_uint32 (info, "unix::mode", mode);
+}
+
+static gboolean
+query_child_info_dir (OstreeRepo         *repo,
+                      const char         *metadata_checksum,
+                      GFileAttributeMatcher *matcher,
+                      GFileQueryInfoFlags flags,
+                      GFileInfo        *info,
+                      GCancellable    *cancellable,
+                      GError         **error)
+{
+  gboolean ret = FALSE;
+  GVariant *metadata = NULL;
+
+  if (!g_file_attribute_matcher_matches (matcher, "unix::mode"))
+    {
+      ret = TRUE;
+      goto out;
+    }
+
+  if (!ostree_repo_load_variant_checked (repo, OSTREE_SERIALIZED_DIRMETA_VARIANT,
+                                         metadata_checksum, &metadata, error))
+    goto out;
+
+  set_info_from_dirmeta (info, metadata);
+  
+  ret = TRUE;
+ out:
+  if (metadata)
+    g_variant_unref (metadata);
+  return ret;
+}
+
+static gboolean
+bsearch_in_file_variant (GVariant  *variant,
+                         const char *name,
+                         int        *out_pos)
+{
+  int i, n;
+  int m;
+
+  i = 0;
+  n = g_variant_n_children (variant) - 1;
+  m = 0;
+
+  while (i <= n)
+    {
+      GVariant *child;
+      const char *cur;
+      int cmp;
+
+      m = i + ((n - i) / 2);
+
+      child = g_variant_get_child_value (variant, m);
+      g_variant_get_child (child, 0, "&s", &cur, NULL);      
+
+      cmp = strcmp (cur, name);
+      if (cmp < 0)
+        i = m + 1;
+      else if (cmp > 0)
+        n = m - 1;
+      else
+        {
+          g_variant_unref (child);
+          *out_pos = m;
+          return TRUE;
+        }
+      g_variant_unref (child);
+    }
+
+  *out_pos = m;
+  return FALSE;
+}
+
+static GVariant *
+remove_variant_child (GVariant *variant,
+                      int       n)
+{
+  GVariantBuilder builder;
+  GVariantIter *iter;
+  int i;
+  GVariant *child;
+
+  g_variant_builder_init (&builder, g_variant_get_type (variant));
+  iter = g_variant_iter_new (variant);
+
+  i = 0;
+  while ((child = g_variant_iter_next_value (iter)) != NULL)
+    {
+      if (i != n)
+        g_variant_builder_add_value (&builder, child);
+      g_variant_unref (child);
+    }
+  g_variant_iter_free (iter);
+  
+  return g_variant_builder_end (&builder);
+}
+
+static GVariant *
+insert_variant_child (GVariant  *variant,
+                      int        n,
+                      GVariant  *item)
+{
+  GVariantBuilder builder;
+  GVariantIter *iter;
+  int i;
+  GVariant *child;
+
+  g_variant_builder_init (&builder, g_variant_get_type (variant));
+  iter = g_variant_iter_new (variant);
+
+  i = 0;
+  while ((child = g_variant_iter_next_value (iter)) != NULL)
+    {
+      if (i == n)
+        g_variant_builder_add_value (&builder, item);
+      g_variant_builder_add_value (&builder, child);
+      g_variant_unref (child);
+    }
+  g_variant_iter_free (iter);
+  
+  return g_variant_builder_end (&builder);
+}
+
+static void
+tree_replace_contents (OstreeRepoFile  *self,
+                       GVariant        *new_files,
+                       GVariant        *new_dirs)
+{
+  guint version;
+  GVariant *metadata;
+  GVariant *tmp_files = NULL;
+  GVariant *tmp_dirs = NULL;
+  
+  if (!(new_files || new_dirs) && self->tree_contents)
+    return;
+  else if (!self->tree_contents)
+    {
+      version = GUINT32_TO_BE (0);
+      metadata = g_variant_new_array (G_VARIANT_TYPE ("{sv}"), NULL, 0);
+      tmp_dirs = g_variant_new_array (G_VARIANT_TYPE ("(ss)"), NULL, 0);
+      tmp_files = g_variant_new_array (G_VARIANT_TYPE ("(ss)"), NULL, 0);
+    }
+  else
+    {
+      g_variant_get_child (self->tree_contents, 0, "u", &version);
+      metadata = g_variant_get_child_value (self->tree_contents, 1);
+      if (!new_files)
+        tmp_files = g_variant_get_child_value (self->tree_contents, 2);
+      if (!new_dirs)
+        tmp_dirs = g_variant_get_child_value (self->tree_contents, 3);
+    }
+
+  if (self->tree_contents)
+    g_variant_unref (self->tree_contents);
+  self->tree_contents = g_variant_new ("(u a{sv}@a(ss)@a(sss))", version, metadata,
+                                       new_files ? new_files : tmp_files,
+                                       new_dirs ? new_dirs : tmp_dirs);
+
+  g_variant_unref (metadata);
+  if (tmp_files)
+    g_variant_unref (tmp_files);
+  if (tmp_dirs)
+    g_variant_unref (tmp_dirs);
+}
+
+void
+_ostree_repo_file_tree_remove_child (OstreeRepoFile  *self,
+                                     const char      *name)
+{
+  int i;
+  GVariant *files_variant;
+  GVariant *new_files_variant = NULL;
+  GVariant *dirs_variant;
+  GVariant *new_dirs_variant = NULL;
+
+  files_variant = g_variant_get_child_value (self->tree_contents, 2);
+  dirs_variant = g_variant_get_child_value (self->tree_contents, 3);
+
+  if (bsearch_in_file_variant (files_variant, name, &i))
+    {
+      new_files_variant = remove_variant_child (files_variant, i);
+    }
+  else
+    {
+      if (bsearch_in_file_variant (dirs_variant, name, &i))
+        {
+          new_dirs_variant = remove_variant_child (dirs_variant, i);
+        }
+    }
+
+  tree_replace_contents (self, new_files_variant, new_dirs_variant);
+
+  g_variant_unref (files_variant);
+  g_variant_unref (dirs_variant);
+}
+
+void
+_ostree_repo_file_tree_add_file (OstreeRepoFile  *self,
+                                 const char      *name,
+                                 const char      *checksum)
+{
+  int n;
+  GVariant *files_variant;
+  GVariant *new_files_variant;
+
+  files_variant = g_variant_get_child_value (self->tree_contents, 2);
+
+  if (!bsearch_in_file_variant (files_variant, name, &n))
+    {
+      new_files_variant = insert_variant_child (files_variant, n,
+                                                g_variant_new ("(ss)", name, checksum));
+      g_variant_ref_sink (new_files_variant);
+      tree_replace_contents (self, new_files_variant, NULL);
+      g_variant_unref (new_files_variant);
+    }
+  g_variant_unref (files_variant);
+}
+
+void
+_ostree_repo_file_tree_add_dir (OstreeRepoFile  *self,
+                                const char      *name,
+                                const char      *content_checksum,
+                                const char      *metadata_checksum)
+{
+  int n;
+  GVariant *dirs_variant;
+  GVariant *new_dirs_variant;
+
+  dirs_variant = g_variant_get_child_value (self->tree_contents, 3);
+
+  if (!bsearch_in_file_variant (dirs_variant, name, &n))
+    {
+      new_dirs_variant = insert_variant_child (dirs_variant, n,
+                                               g_variant_new ("(sss)", name, content_checksum,
+                                                              metadata_checksum));
+      g_variant_ref_sink (new_dirs_variant);
+      tree_replace_contents (self, NULL, new_dirs_variant);
+      g_variant_unref (new_dirs_variant);
+    }
+  g_variant_unref (dirs_variant);
+}
+
+int
+_ostree_repo_file_tree_find_child  (OstreeRepoFile  *self,
+                                    const char      *name,
+                                    gboolean        *is_dir,
+                                    GVariant       **out_container)
+{
+  int i;
+  GVariant *files_variant = NULL;
+  GVariant *dirs_variant = NULL;
+  GVariant *ret_container = NULL;
+
+  files_variant = g_variant_get_child_value (self->tree_contents, 2);
+  dirs_variant = g_variant_get_child_value (self->tree_contents, 3);
+
+  i = -1;
+  if (bsearch_in_file_variant (files_variant, name, &i))
+    {
+      *is_dir = FALSE;
+      ret_container = files_variant;
+      files_variant = NULL;
+    }
+  else
+    {
+      if (bsearch_in_file_variant (dirs_variant, name, &i))
+        {
+          *is_dir = TRUE;
+          ret_container = dirs_variant;
+          dirs_variant = NULL;
+        }
+      else
+        {
+          i = -1;
+        }
+    }
+  if (ret_container && out_container)
+    {
+      *out_container = ret_container;
+      ret_container = NULL;
+    }
+  if (ret_container)
+    g_variant_unref (ret_container);
+  if (files_variant)
+    g_variant_unref (files_variant);
+  if (dirs_variant)
+    g_variant_unref (dirs_variant);
+  return i;
+}
+
+gboolean
+_ostree_repo_file_tree_query_child (OstreeRepoFile  *self,
+                                    int              n,
+                                    const char      *attributes,
+                                    GFileQueryInfoFlags flags,
+                                    GFileInfo      **out_info,
+                                    GCancellable    *cancellable,
+                                    GError         **error)
+{
+  const char *name = NULL;
+  gboolean ret = FALSE;
+  GFileInfo *ret_info = NULL;
+  GVariant *files_variant = NULL;
+  GVariant *dirs_variant = NULL;
+  GVariant *tree_child_metadata = NULL;
+  GFileAttributeMatcher *matcher = NULL;
+  int c;
+
+  if (!_ostree_repo_file_ensure_resolved (self, error))
+    goto out;
+
+  matcher = g_file_attribute_matcher_new (attributes);
+
+  ret_info = g_file_info_new ();
+
+  g_assert (self->tree_contents);
+
+  files_variant = g_variant_get_child_value (self->tree_contents, 2);
+  dirs_variant = g_variant_get_child_value (self->tree_contents, 3);
+
+  c = g_variant_n_children (files_variant);
+  if (n < c)
+    {
+      const char *checksum;
+
+      g_variant_get_child (files_variant, n, "(&s&s)", &name, &checksum);
+
+      if (ostree_repo_is_archive (self->repo))
+	{
+	  if (!query_child_info_file_archive (self->repo, checksum, matcher, ret_info,
+                                              cancellable, error))
+	    goto out;
+	}
+      else
+	{
+	  if (!query_child_info_file_nonarchive (self->repo, checksum, matcher, ret_info,
+                                                 cancellable, error))
+	    goto out;
+	}
+    }
+  else
+    {
+      const char *tree_checksum;
+      const char *meta_checksum;
+
+      n -= c;
+
+      c = g_variant_n_children (dirs_variant);
+
+      if (n < c)
+        {
+          g_variant_get_child (dirs_variant, n, "(&s&s&s)",
+                               &name, &tree_checksum, &meta_checksum);
+
+          if (!query_child_info_dir (self->repo, meta_checksum,
+                                     matcher, flags, ret_info,
+                                     cancellable, error))
+            goto out;
+        }
+      else
+        n -= c;
+    }
+
+  if (name)
+    {
+      g_file_info_set_attribute_byte_string (ret_info, "standard::name",
+                                             name);
+      g_file_info_set_attribute_string (ret_info, "standard::display-name",
+                                        name);
+      if (*name == '.')
+        g_file_info_set_is_hidden (ret_info, TRUE);
+    }
+  else
+    {
+      g_clear_object (&ret_info);
+    }
+
+  ret = TRUE;
+  *out_info = ret_info;
+  ret_info = NULL;
+ out:
+  g_clear_object (&ret_info);
+  if (matcher)
+    g_file_attribute_matcher_unref (matcher);
+  if (tree_child_metadata)
+    g_variant_unref (tree_child_metadata);
+  g_variant_unref (files_variant);
+  g_variant_unref (dirs_variant);
+  return ret;
+}
+
+static GFileInfo *
+ostree_repo_file_query_info (GFile                *file,
+			     const char           *attributes,
+			     GFileQueryInfoFlags   flags,
+			     GCancellable         *cancellable,
+			     GError              **error)
+{
+  OstreeRepoFile *self = OSTREE_REPO_FILE (file);
+  gboolean ret = FALSE;
+  GFileInfo *info = NULL;
+
+  if (!_ostree_repo_file_ensure_resolved (self, error))
+    goto out;
+
+  if (!self->parent)
+    {
+      info = g_file_info_new ();
+      set_info_from_dirmeta (info, self->tree_metadata);
+    }
+  else
+    {
+      if (!_ostree_repo_file_tree_query_child (self->parent, self->index, 
+                                               attributes, flags, 
+                                               &info, cancellable, error))
+        goto out;
+    }
+      
+  ret = TRUE;
+ out:
+  if (!ret)
+    g_clear_object (&info);
+  return info;
+}
+
+static GFileAttributeInfoList *
+ostree_repo_file_query_settable_attributes (GFile         *file,
+					GCancellable  *cancellable,
+					GError       **error)
+{
+  return g_file_attribute_info_list_new ();
+}
+
+static GFileAttributeInfoList *
+ostree_repo_file_query_writable_namespaces (GFile         *file,
+					GCancellable  *cancellable,
+					GError       **error)
+{
+  return g_file_attribute_info_list_new ();
+}
+
+static GFileInputStream *
+ostree_repo_file_read (GFile         *file,
+		       GCancellable  *cancellable,
+		       GError       **error)
+{
+  gboolean ret = FALSE;
+  GFile *local_file = NULL;
+  GFileInputStream *ret_stream = NULL;
+  OstreeRepoFile *self = OSTREE_REPO_FILE (file);
+
+  if (self->tree_contents)
+    {
+      g_set_error_literal (error, G_IO_ERROR,
+			   G_IO_ERROR_IS_DIRECTORY,
+			   "Can't open directory");
+      goto out;
+    }
+
+  if (ostree_repo_is_archive (self->repo))
+    {
+      g_set_error_literal (error, G_IO_ERROR,
+			   G_IO_ERROR_NOT_SUPPORTED,
+			   "Can't open archived file (yet)");
+      goto out;
+    }
+  else
+    {
+      local_file = _ostree_repo_file_nontree_get_local (self);
+      ret_stream = g_file_read (local_file, cancellable, error);
+      if (!ret_stream)
+	goto out;
+    }
+  
+  ret = TRUE;
+ out:
+  g_clear_object (&local_file);
+  if (!ret)
+    g_clear_object (&ret_stream);
+  return ret_stream;
+}
+
+static void
+ostree_repo_file_file_iface_init (GFileIface *iface)
+{
+  iface->dup = ostree_repo_file_dup;
+  iface->hash = ostree_repo_file_hash;
+  iface->equal = ostree_repo_file_equal;
+  iface->is_native = ostree_repo_file_is_native;
+  iface->has_uri_scheme = ostree_repo_file_has_uri_scheme;
+  iface->get_uri_scheme = ostree_repo_file_get_uri_scheme;
+  iface->get_basename = ostree_repo_file_get_basename;
+  iface->get_path = ostree_repo_file_get_path;
+  iface->get_uri = ostree_repo_file_get_uri;
+  iface->get_parse_name = ostree_repo_file_get_parse_name;
+  iface->get_parent = ostree_repo_file_get_parent;
+  iface->prefix_matches = ostree_repo_file_prefix_matches;
+  iface->get_relative_path = ostree_repo_file_get_relative_path;
+  iface->resolve_relative_path = ostree_repo_file_resolve_relative_path;
+  iface->get_child_for_display_name = ostree_repo_file_get_child_for_display_name;
+  iface->set_display_name = NULL;
+  iface->enumerate_children = ostree_repo_file_enumerate_children;
+  iface->query_info = ostree_repo_file_query_info;
+  iface->query_filesystem_info = NULL;
+  iface->find_enclosing_mount = NULL;
+  iface->query_settable_attributes = ostree_repo_file_query_settable_attributes;
+  iface->query_writable_namespaces = ostree_repo_file_query_writable_namespaces;
+  iface->set_attribute = NULL;
+  iface->set_attributes_from_info = NULL;
+  iface->read_fn = ostree_repo_file_read;
+  iface->append_to = NULL;
+  iface->create = NULL;
+  iface->replace = NULL;
+  iface->open_readwrite = NULL;
+  iface->create_readwrite = NULL;
+  iface->replace_readwrite = NULL;
+  iface->delete_file = NULL;
+  iface->trash = NULL;
+  iface->make_directory = NULL;
+  iface->make_symbolic_link = NULL;
+  iface->copy = NULL;
+  iface->move = NULL;
+  iface->monitor_dir = NULL;
+  iface->monitor_file = NULL;
+
+  iface->supports_thread_contexts = TRUE;
+}
diff --git a/src/libostree/ostree-repo-file.h b/src/libostree/ostree-repo-file.h
new file mode 100644
index 0000000..2074965
--- /dev/null
+++ b/src/libostree/ostree-repo-file.h
@@ -0,0 +1,115 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
+ *
+ * Copyright (C) 2011 Colin Walters <walters verbum org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Colin Walters <walters verbum org>
+ */
+
+#ifndef _OSTREE_REPO_FILE
+#define _OSTREE_REPO_FILE
+
+#include "ostree-repo.h"
+
+G_BEGIN_DECLS
+
+#define OSTREE_TYPE_REPO_FILE         (_ostree_repo_file_get_type ())
+#define OSTREE_REPO_FILE(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), OSTREE_TYPE_REPO_FILE, OstreeRepoFile))
+#define OSTREE_REPO_FILE_CLASS(k)     (G_TYPE_CHECK_CLASS_CAST((k), OSTREE_TYPE_REPO_FILE, OstreeRepoFileClass))
+#define OSTREE_IS_LOCAL_FILE(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), OSTREE_TYPE_REPO_FILE))
+#define OSTREE_IS_LOCAL_FILE_CLASS(k)  (G_TYPE_CHECK_CLASS_TYPE ((k), OSTREE_TYPE_REPO_FILE))
+#define OSTREE_REPO_FILE_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), OSTREE_TYPE_REPO_FILE, OstreeRepoFileClass))
+
+typedef struct _OstreeRepoFile        OstreeRepoFile;
+typedef struct _OstreeRepoFileClass   OstreeRepoFileClass;
+
+struct _OstreeRepoFileClass
+{
+  GObjectClass parent_class;
+};
+
+GType   _ostree_repo_file_get_type (void) G_GNUC_CONST;
+
+GFile * _ostree_repo_file_new_root (OstreeRepo  *repo,
+                                    const char  *commit);
+
+OstreeRepoFile * _ostree_repo_file_new_empty_tree (OstreeRepo  *repo);
+
+GFile * _ostree_repo_file_new_child (OstreeRepoFile *parent,
+                                     const char  *name);
+
+gboolean _ostree_repo_file_ensure_resolved (OstreeRepoFile  *self,
+                                            GError         **error);
+
+gboolean _ostree_repo_file_get_xattrs (OstreeRepoFile  *self,
+                                       GVariant       **out_xattrs,
+                                       GCancellable    *cancellable,
+                                       GError         **error);
+
+OstreeRepo * _ostree_repo_file_get_repo (OstreeRepoFile  *self);
+OstreeRepoFile * _ostree_repo_file_get_root (OstreeRepoFile  *self);
+
+void _ostree_repo_file_make_empty_tree (OstreeRepoFile  *self);
+
+void _ostree_repo_file_tree_set_metadata (OstreeRepoFile *self,
+                                          const char     *checksum,
+                                          GVariant       *metadata);
+
+void _ostree_repo_file_tree_set_content_checksum (OstreeRepoFile  *self,
+                                                  const char      *checksum);
+const char *_ostree_repo_file_tree_get_content_checksum (OstreeRepoFile  *self);
+
+gboolean _ostree_repo_file_is_tree (OstreeRepoFile  *self);
+
+const char * _ostree_repo_file_nontree_get_checksum (OstreeRepoFile  *self);
+
+GFile *_ostree_repo_file_nontree_get_local (OstreeRepoFile  *self);
+
+void    _ostree_repo_file_tree_remove_child (OstreeRepoFile  *self,
+                                             const char      *name);
+
+void    _ostree_repo_file_tree_add_file (OstreeRepoFile  *self,
+                                         const char      *name,
+                                         const char      *checksum);
+
+void    _ostree_repo_file_tree_add_dir (OstreeRepoFile  *self,
+                                        const char      *name,
+                                        const char      *content_checksum,
+                                        const char      *metadata_checksum);
+
+int     _ostree_repo_file_tree_find_child  (OstreeRepoFile  *self,
+                                            const char      *name,
+                                            gboolean        *is_dir,
+                                            GVariant       **out_container);
+
+const char *_ostree_repo_file_tree_get_child_checksum (OstreeRepoFile  *self,
+                                                       int n);
+
+gboolean _ostree_repo_file_tree_query_child (OstreeRepoFile  *self,
+                                             int              n,
+                                             const char      *attributes,
+                                             GFileQueryInfoFlags flags,
+                                             GFileInfo      **out_info,
+                                             GCancellable    *cancellable,
+                                             GError         **error);
+
+GVariant *_ostree_repo_file_tree_get_contents (OstreeRepoFile  *self);
+GVariant *_ostree_repo_file_tree_get_metadata (OstreeRepoFile  *self);
+
+G_END_DECLS
+
+#endif
diff --git a/src/libostree/ostree-repo.c b/src/libostree/ostree-repo.c
new file mode 100644
index 0000000..2bdb56d
--- /dev/null
+++ b/src/libostree/ostree-repo.c
@@ -0,0 +1,1877 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
+ *
+ * Copyright (C) 2011 Colin Walters <walters verbum org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Colin Walters <walters verbum org>
+ */
+
+#define _GNU_SOURCE
+
+#include "config.h"
+
+#include "ostree.h"
+#include "otutil.h"
+#include "ostree-repo-file-enumerator.h"
+
+#include <gio/gunixoutputstream.h>
+#include <gio/gunixinputstream.h>
+
+enum {
+  PROP_0,
+
+  PROP_PATH
+};
+
+G_DEFINE_TYPE (OstreeRepo, ostree_repo, G_TYPE_OBJECT)
+
+#define GET_PRIVATE(o) \
+  (G_TYPE_INSTANCE_GET_PRIVATE ((o), OSTREE_TYPE_REPO, OstreeRepoPrivate))
+
+typedef struct _OstreeRepoPrivate OstreeRepoPrivate;
+
+struct _OstreeRepoPrivate {
+  char *path;
+  GFile *repo_file;
+  GFile *tmp_dir;
+  GFile *local_heads_dir;
+  GFile *remote_heads_dir;
+  char *objects_path;
+  char *config_path;
+
+  gboolean inited;
+
+  GKeyFile *config;
+  gboolean archive;
+};
+
+static void
+ostree_repo_finalize (GObject *object)
+{
+  OstreeRepo *self = OSTREE_REPO (object);
+  OstreeRepoPrivate *priv = GET_PRIVATE (self);
+
+  g_free (priv->path);
+  g_clear_object (&priv->repo_file);
+  g_clear_object (&priv->tmp_dir);
+  g_clear_object (&priv->local_heads_dir);
+  g_clear_object (&priv->remote_heads_dir);
+  g_free (priv->objects_path);
+  g_free (priv->config_path);
+  if (priv->config)
+    g_key_file_free (priv->config);
+
+  G_OBJECT_CLASS (ostree_repo_parent_class)->finalize (object);
+}
+
+static void
+ostree_repo_set_property(GObject         *object,
+			   guint            prop_id,
+			   const GValue    *value,
+			   GParamSpec      *pspec)
+{
+  OstreeRepo *self = OSTREE_REPO (object);
+  OstreeRepoPrivate *priv = GET_PRIVATE (self);
+
+  switch (prop_id)
+    {
+    case PROP_PATH:
+      priv->path = g_value_dup_string (value);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+    }
+}
+
+static void
+ostree_repo_get_property(GObject         *object,
+			   guint            prop_id,
+			   GValue          *value,
+			   GParamSpec      *pspec)
+{
+  OstreeRepo *self = OSTREE_REPO (object);
+  OstreeRepoPrivate *priv = GET_PRIVATE (self);
+
+  switch (prop_id)
+    {
+    case PROP_PATH:
+      g_value_set_string (value, priv->path);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+    }
+}
+
+static GObject *
+ostree_repo_constructor (GType                  gtype,
+                           guint                  n_properties,
+                           GObjectConstructParam *properties)
+{
+  GObject *object;
+  GObjectClass *parent_class;
+  OstreeRepoPrivate *priv;
+
+  parent_class = G_OBJECT_CLASS (ostree_repo_parent_class);
+  object = parent_class->constructor (gtype, n_properties, properties);
+
+  priv = GET_PRIVATE (object);
+
+  g_assert (priv->path != NULL);
+  
+  priv->repo_file = ot_util_new_file_for_path (priv->path);
+  priv->tmp_dir = g_file_resolve_relative_path (priv->repo_file, "tmp");
+  priv->local_heads_dir = g_file_resolve_relative_path (priv->repo_file, "refs/heads");
+  priv->remote_heads_dir = g_file_resolve_relative_path (priv->repo_file, "refs/remotes");
+  
+  priv->objects_path = g_build_filename (priv->path, "objects", NULL);
+  priv->config_path = g_build_filename (priv->path, "config", NULL);
+
+  return object;
+}
+
+static void
+ostree_repo_class_init (OstreeRepoClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  g_type_class_add_private (klass, sizeof (OstreeRepoPrivate));
+
+  object_class->constructor = ostree_repo_constructor;
+  object_class->get_property = ostree_repo_get_property;
+  object_class->set_property = ostree_repo_set_property;
+  object_class->finalize = ostree_repo_finalize;
+
+  g_object_class_install_property (object_class,
+                                   PROP_PATH,
+                                   g_param_spec_string ("path",
+                                                        "",
+                                                        "",
+                                                        NULL,
+                                                        G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+}
+
+static void
+ostree_repo_init (OstreeRepo *self)
+{
+}
+
+OstreeRepo*
+ostree_repo_new (const char *path)
+{
+  return g_object_new (OSTREE_TYPE_REPO, "path", path, NULL);
+}
+
+static gboolean
+parse_rev_file (OstreeRepo     *self,
+                const char     *path,
+                char          **sha256,
+                GError        **error) G_GNUC_UNUSED;
+
+static gboolean
+parse_rev_file (OstreeRepo     *self,
+                const char     *path,
+                char          **sha256,
+                GError        **error)
+{
+  OstreeRepoPrivate *priv = GET_PRIVATE (self);
+  GError *temp_error = NULL;
+  gboolean ret = FALSE;
+  char *rev = NULL;
+
+  rev = ot_util_get_file_contents_utf8 (path, &temp_error);
+  if (rev == NULL)
+    {
+      if (g_error_matches (temp_error, G_FILE_ERROR, G_FILE_ERROR_NOENT))
+        {
+          g_clear_error (&temp_error);
+        }
+      else
+        {
+          g_propagate_error (error, temp_error);
+          goto out;
+        }
+    }
+  else
+    {
+      g_strchomp (rev);
+    }
+
+  if (g_str_has_prefix (rev, "ref: "))
+    {
+      GFile *ref;
+      char *ref_path;
+      char *ref_sha256;
+      gboolean subret;
+
+      ref = g_file_resolve_relative_path (priv->local_heads_dir, rev + 5);
+      ref_path = g_file_get_path (ref);
+
+      subret = parse_rev_file (self, ref_path, &ref_sha256, error);
+      g_clear_object (&ref);
+      g_free (ref_path);
+        
+      if (!subret)
+        {
+          g_free (ref_sha256);
+          goto out;
+        }
+      
+      g_free (rev);
+      rev = ref_sha256;
+    }
+  else 
+    {
+      if (!ostree_validate_checksum_string (rev, error))
+        goto out;
+    }
+
+  *sha256 = rev;
+  rev = NULL;
+  ret = TRUE;
+ out:
+  g_free (rev);
+  return ret;
+}
+
+static gboolean
+resolve_rev (OstreeRepo     *self,
+             const char     *rev,
+             gboolean        allow_noent,
+             char          **sha256,
+             GError        **error)
+{
+  OstreeRepoPrivate *priv = GET_PRIVATE (self);
+  gboolean ret = FALSE;
+  char *tmp = NULL;
+  char *tmp2 = NULL;
+  char *ret_rev = NULL;
+  GFile *child = NULL;
+  char *child_path = NULL;
+  GError *temp_error = NULL;
+  GVariant *commit = NULL;
+
+  if (strlen (rev) == 0)
+    {
+      g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+                   "Invalid empty rev");
+      goto out;
+    }
+  else if (strlen (rev) == 64)
+    {
+      ret_rev = g_strdup (rev);
+    }
+  else if (g_str_has_suffix (rev, "^"))
+    {
+      tmp = g_strdup (rev);
+      tmp[strlen(tmp) - 1] = '\0';
+
+      if (!resolve_rev (self, tmp, allow_noent, &tmp2, error))
+        goto out;
+
+      if (!ostree_repo_load_variant_checked (self, OSTREE_SERIALIZED_COMMIT_VARIANT, tmp2, &commit, error))
+        goto out;
+      
+      g_variant_get_child (commit, 2, "s", &ret_rev);
+      if (strlen (ret_rev) == 0)
+        {
+          g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+                       "Commit %s has no parent", tmp2);
+          goto out;
+
+        }
+    }
+  else
+    {
+      child = g_file_get_child (priv->local_heads_dir, rev);
+      child_path = g_file_get_path (child);
+      if (!ot_util_gfile_load_contents_utf8 (child, NULL, &ret_rev, NULL, &temp_error))
+        {
+          if (allow_noent && g_error_matches (temp_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))
+            {
+              g_free (ret_rev);
+              ret_rev = NULL;
+            }
+          else
+            {
+              g_propagate_error (error, temp_error);
+              g_prefix_error (error, "Couldn't open ref '%s': ", child_path);
+              goto out;
+            }
+        }
+      else
+        {
+          g_strchomp (ret_rev);
+         
+          if (!ostree_validate_checksum_string (ret_rev, error))
+            goto out;
+        }
+    }
+
+  *sha256 = ret_rev;
+  ret_rev = NULL;
+  ret = TRUE;
+ out:
+  if (commit)
+    g_variant_unref (commit);
+  g_free (tmp);
+  g_free (tmp2);
+  g_clear_object (&child);
+  g_free (child_path);
+  g_free (ret_rev);
+  return ret;
+}
+
+gboolean
+ostree_repo_resolve_rev (OstreeRepo     *self,
+                         const char     *rev,
+                         char          **sha256,
+                         GError        **error)
+{
+  g_return_val_if_fail (rev != NULL, FALSE);
+  return resolve_rev (self, rev, FALSE, sha256, error);
+}
+
+static gboolean
+write_checksum_file (GFile *parentdir,
+                     const char *name,
+                     const char *sha256,
+                     GError **error)
+{
+  gboolean ret = FALSE;
+  GFile *child = NULL;
+  GOutputStream *out = NULL;
+  gsize bytes_written;
+
+  child = g_file_get_child (parentdir, name);
+
+  if ((out = (GOutputStream*)g_file_replace (child, NULL, FALSE, 0, NULL, error)) == NULL)
+    goto out;
+  if (!g_output_stream_write_all (out, sha256, strlen (sha256), &bytes_written, NULL, error))
+    goto out;
+  if (!g_output_stream_write_all (out, "\n", 1, &bytes_written, NULL, error))
+    goto out;
+  if (!g_output_stream_close (out, NULL, error))
+    goto out;
+
+  ret = TRUE;
+ out:
+  g_clear_object (&child);
+  g_clear_object (&out);
+  return ret;
+}
+
+/**
+ * ostree_repo_get_config:
+ * @self:
+ *
+ * Returns: (transfer none): The repository configuration; do not modify
+ */
+GKeyFile *
+ostree_repo_get_config (OstreeRepo *self)
+{
+  OstreeRepoPrivate *priv = GET_PRIVATE (self);
+
+  g_return_val_if_fail (priv->inited, NULL);
+
+  return priv->config;
+}
+
+/**
+ * ostree_repo_copy_config:
+ * @self:
+ *
+ * Returns: (transfer full): A newly-allocated copy of the repository config
+ */
+GKeyFile *
+ostree_repo_copy_config (OstreeRepo *self)
+{
+  OstreeRepoPrivate *priv = GET_PRIVATE (self);
+  GKeyFile *copy;
+  char *data;
+  gsize len;
+
+  g_return_val_if_fail (priv->inited, NULL);
+
+  copy = g_key_file_new ();
+  data = g_key_file_to_data (priv->config, &len, NULL);
+  if (!g_key_file_load_from_data (copy, data, len, 0, NULL))
+    g_assert_not_reached ();
+  g_free (data);
+  return copy;
+}
+
+/**
+ * ostree_repo_write_config:
+ * @self:
+ * @new_config: Overwrite the config file with this data.  Do not change later!
+ * @error: a #GError
+ *
+ * Save @new_config in place of this repository's config file.  Note
+ * that @new_config should not be modified after - this function
+ * simply adds a reference.
+ */
+gboolean
+ostree_repo_write_config (OstreeRepo *self,
+                          GKeyFile   *new_config,
+                          GError    **error)
+{
+  OstreeRepoPrivate *priv = GET_PRIVATE (self);
+  char *data = NULL;
+  gsize len;
+  gboolean ret = FALSE;
+
+  g_return_val_if_fail (priv->inited, FALSE);
+
+  data = g_key_file_to_data (new_config, &len, error);
+  if (!g_file_set_contents (priv->config_path, data, len, error))
+    goto out;
+  
+  g_key_file_free (priv->config);
+  priv->config = g_key_file_new ();
+  if (!g_key_file_load_from_data (priv->config, data, len, 0, error))
+    goto out;
+
+  ret = TRUE;
+ out:
+  g_free (data);
+  return ret;
+}
+
+gboolean
+ostree_repo_check (OstreeRepo *self, GError **error)
+{
+  OstreeRepoPrivate *priv = GET_PRIVATE (self);
+  gboolean ret = FALSE;
+  char *version = NULL;;
+  GError *temp_error = NULL;
+
+  g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+  if (priv->inited)
+    return TRUE;
+
+  if (!g_file_test (priv->objects_path, G_FILE_TEST_IS_DIR))
+    {
+      g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+                   "Couldn't find objects directory '%s'", priv->objects_path);
+      goto out;
+    }
+  
+  priv->config = g_key_file_new ();
+  if (!g_key_file_load_from_file (priv->config, priv->config_path, 0, error))
+    {
+      g_prefix_error (error, "Couldn't parse config file: ");
+      goto out;
+    }
+
+  version = g_key_file_get_value (priv->config, "core", "repo_version", &temp_error);
+  if (temp_error)
+    {
+      g_propagate_error (error, temp_error);
+      goto out;
+    }
+
+  if (strcmp (version, "0") != 0)
+    {
+      g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+                   "Invalid repository version '%s'", version);
+      goto out;
+    }
+
+  priv->archive = g_key_file_get_boolean (priv->config, "core", "archive", &temp_error);
+  if (temp_error)
+    {
+      if (g_error_matches (temp_error, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_NOT_FOUND))
+        {
+          g_clear_error (&temp_error);
+        }
+      else
+        {
+          g_propagate_error (error, temp_error);
+          goto out;
+        }
+    }
+
+  priv->inited = TRUE;
+  
+  ret = TRUE;
+ out:
+  g_free (version);
+  return ret;
+}
+
+const char *
+ostree_repo_get_path (OstreeRepo  *self)
+{
+  OstreeRepoPrivate *priv = GET_PRIVATE (self);
+  return priv->path;
+}
+
+gboolean      
+ostree_repo_is_archive (OstreeRepo  *self)
+{
+  OstreeRepoPrivate *priv = GET_PRIVATE (self);
+
+  g_return_val_if_fail (priv->inited, FALSE);
+
+  return priv->archive;
+}
+
+static gboolean
+write_gvariant_to_tmp (OstreeRepo  *self,
+                       OstreeSerializedVariantType type,
+                       GVariant    *variant,
+                       GChecksum    **out_checksum,
+                       GError       **error)
+{
+  OstreeRepoPrivate *priv = GET_PRIVATE (self);
+  GVariant *serialized = NULL;
+  gboolean ret = FALSE;
+  gsize bytes_written;
+  char *tmp_name = NULL;
+  char *dest_name = NULL;
+  int fd = -1;
+  GUnixOutputStream *stream = NULL;
+  GChecksum *checksum = NULL;
+
+  serialized = g_variant_new ("(uv)", GUINT32_TO_BE ((guint32)type), variant);
+
+  tmp_name = g_build_filename (ot_gfile_get_path_cached (priv->tmp_dir), "variant-tmp-XXXXXX", NULL);
+  fd = g_mkstemp (tmp_name);
+  if (fd < 0)
+    {
+      ot_util_set_error_from_errno (error, errno);
+      goto out;
+    }
+
+  checksum = g_checksum_new (G_CHECKSUM_SHA256);
+
+  stream = (GUnixOutputStream*)g_unix_output_stream_new (fd, FALSE);
+  if (!g_output_stream_write_all ((GOutputStream*)stream,
+                                  g_variant_get_data (serialized),
+                                  g_variant_get_size (serialized),
+                                  &bytes_written,
+                                  NULL,
+                                  error))
+    goto out;
+
+  g_checksum_update (checksum, (guint8*)g_variant_get_data (serialized), g_variant_get_size (serialized));
+
+  if (!g_output_stream_close ((GOutputStream*)stream,
+                              NULL, error))
+    goto out;
+
+  dest_name = g_build_filename (ot_gfile_get_path_cached (priv->tmp_dir), g_checksum_get_string (checksum), NULL);
+  if (rename (tmp_name, dest_name) < 0)
+    {
+      ot_util_set_error_from_errno (error, errno);
+      goto out;
+    }
+
+  ret = TRUE;
+  *out_checksum = checksum;
+  checksum = NULL;
+ out:
+  /* Unconditionally unlink; if we suceeded, there's a new link, if not, clean up. */
+  (void) unlink (tmp_name);
+  if (fd != -1)
+    close (fd);
+  if (checksum)
+    g_checksum_free (checksum);
+  if (serialized != NULL)
+    g_variant_unref (serialized);
+  g_free (tmp_name);
+  g_free (dest_name);
+  g_clear_object (&stream);
+  return ret;
+}
+
+static gboolean
+import_gvariant_object (OstreeRepo  *self,
+                        OstreeSerializedVariantType type,
+                        GVariant       *variant,
+                        GChecksum    **out_checksum,
+                        GError       **error)
+{
+  gboolean ret = FALSE;
+  OstreeRepoPrivate *priv = GET_PRIVATE (self);
+  char *tmp_name = NULL;
+  GChecksum *ret_checksum = NULL;
+  gboolean did_exist;
+  
+  if (!write_gvariant_to_tmp (self, type, variant, &ret_checksum, error))
+    goto out;
+
+  tmp_name = g_build_filename (ot_gfile_get_path_cached (priv->tmp_dir),
+                               g_checksum_get_string (ret_checksum), NULL);
+
+  if (!ostree_repo_store_object_trusted (self, tmp_name,
+                                         g_checksum_get_string (ret_checksum),
+                                         OSTREE_OBJECT_TYPE_META,
+                                         TRUE, FALSE, &did_exist, error))
+    goto out;
+
+  ret = TRUE;
+  *out_checksum = ret_checksum;
+  ret_checksum = NULL;
+ out:
+  (void) unlink (tmp_name);
+  g_free (tmp_name);
+  if (ret_checksum)
+    g_checksum_free (ret_checksum);
+  return ret;
+}
+
+gboolean
+ostree_repo_load_variant_checked (OstreeRepo  *self,
+                                  OstreeSerializedVariantType expected_type,
+                                  const char    *sha256, 
+                                  GVariant     **out_variant,
+                                  GError       **error)
+{
+  gboolean ret = FALSE;
+  OstreeSerializedVariantType type;
+  GVariant *ret_variant = NULL;
+
+  if (!ostree_repo_load_variant (self, sha256, &type, &ret_variant, error))
+    goto out;
+
+  if (type != expected_type)
+    {
+      g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+                   "Corrupted metadata object '%s'; found type %u, expected %u", sha256,
+                   type, (guint32)expected_type);
+      goto out;
+    }
+
+  ret = TRUE;
+  *out_variant = ret_variant;
+  ret_variant = NULL;
+ out:
+  if (ret_variant)
+    g_variant_unref (ret_variant);
+  return ret;
+}
+
+static gboolean
+import_directory_meta (OstreeRepo  *self,
+                       const char *path,
+                       GVariant  **out_variant,
+                       GChecksum **out_checksum,
+                       GError    **error)
+{
+  gboolean ret = FALSE;
+  struct stat stbuf;
+  GChecksum *ret_checksum = NULL;
+  GVariant *dirmeta = NULL;
+  GVariant *xattrs = NULL;
+
+  if (lstat (path, &stbuf) < 0)
+    {
+      ot_util_set_error_from_errno (error, errno);
+      goto out;
+    }
+  
+  if (!S_ISDIR(stbuf.st_mode))
+    {
+      g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+                   "Not a directory: '%s'", path);
+      goto out;
+    }
+
+  xattrs = ostree_get_xattrs_for_path (path, error);
+  if (!xattrs)
+    goto out;
+
+  dirmeta = g_variant_new ("(uuuu a(ayay))",
+                           OSTREE_DIR_META_VERSION,
+                           GUINT32_TO_BE ((guint32)stbuf.st_uid),
+                           GUINT32_TO_BE ((guint32)stbuf.st_gid),
+                           GUINT32_TO_BE ((guint32)stbuf.st_mode),
+                           xattrs);
+  g_variant_ref_sink (dirmeta);
+
+  if (!import_gvariant_object (self, OSTREE_SERIALIZED_DIRMETA_VARIANT, 
+                               dirmeta, &ret_checksum, error))
+        goto out;
+
+  ret = TRUE;
+ out:
+  if (!ret)
+    {
+      if (ret_checksum)
+        g_checksum_free (ret_checksum);
+      if (dirmeta != NULL)
+        g_variant_unref (dirmeta);
+    }
+  else
+    {
+      *out_checksum = ret_checksum;
+      *out_variant = dirmeta;
+    }
+  if (xattrs)
+    g_variant_unref (xattrs);
+  return ret;
+}
+
+char *
+ostree_repo_get_object_path (OstreeRepo  *self,
+                             const char    *checksum,
+                             OstreeObjectType type)
+{
+  OstreeRepoPrivate *priv = GET_PRIVATE (self);
+  char *ret;
+  char *relpath;
+
+  relpath = ostree_get_relative_object_path (checksum, type, priv->archive);
+  ret = g_build_filename (priv->path, relpath, NULL);
+  g_free (relpath);
+ 
+  return ret;
+}
+
+static char *
+prepare_dir_for_checksum_get_object_path (OstreeRepo *self,
+                                          const char   *checksum,
+                                          OstreeObjectType type,
+                                          GError      **error)
+{
+  char *checksum_dir = NULL;
+  char *object_path = NULL;
+
+  object_path = ostree_repo_get_object_path (self, checksum, type);
+  checksum_dir = g_path_get_dirname (object_path);
+
+  if (!ot_util_ensure_directory (checksum_dir, FALSE, error))
+    goto out;
+  
+ out:
+  g_free (checksum_dir);
+  return object_path;
+}
+
+static gboolean
+link_object_trusted (OstreeRepo   *self,
+                     const char   *path,
+                     const char   *checksum,
+                     OstreeObjectType objtype,
+                     gboolean      ignore_exists,
+                     gboolean      force,
+                     gboolean     *did_exist,
+                     GError      **error)
+{
+  char *src_basename = NULL;
+  char *src_dirname = NULL;
+  char *dest_basename = NULL;
+  char *tmp_dest_basename = NULL;
+  char *dest_dirname = NULL;
+  DIR *src_dir = NULL;
+  DIR *dest_dir = NULL;
+  gboolean ret = FALSE;
+  char *dest_path = NULL;
+
+  src_basename = g_path_get_basename (path);
+  src_dirname = g_path_get_dirname (path);
+
+  src_dir = opendir (src_dirname);
+  if (src_dir == NULL)
+    {
+      ot_util_set_error_from_errno (error, errno);
+      goto out;
+    }
+
+  dest_path = prepare_dir_for_checksum_get_object_path (self, checksum, objtype, error);
+  if (!dest_path)
+    goto out;
+
+  dest_basename = g_path_get_basename (dest_path);
+  dest_dirname = g_path_get_dirname (dest_path);
+  dest_dir = opendir (dest_dirname);
+  if (dest_dir == NULL)
+    {
+      ot_util_set_error_from_errno (error, errno);
+      goto out;
+    }
+
+  if (force)
+    {
+      tmp_dest_basename = g_strconcat (dest_basename, ".tmp", NULL);
+      (void) unlinkat (dirfd (dest_dir), tmp_dest_basename, 0);
+    }
+  else
+    tmp_dest_basename = g_strdup (dest_basename);
+  
+  if (linkat (dirfd (src_dir), src_basename, dirfd (dest_dir), tmp_dest_basename, 0) < 0)
+    {
+      if (errno != EEXIST || !ignore_exists)
+        {
+          ot_util_set_error_from_errno (error, errno);
+          goto out;
+        }
+      else
+        *did_exist = TRUE;
+    }
+  else
+    *did_exist = FALSE;
+
+  if (force)
+    {
+      if (renameat (dirfd (dest_dir), tmp_dest_basename, 
+                    dirfd (dest_dir), dest_basename) < 0)
+        {
+          ot_util_set_error_from_errno (error, errno);
+          goto out;
+        }
+      (void) unlinkat (dirfd (dest_dir), tmp_dest_basename, 0);
+    }
+
+  ret = TRUE;
+ out:
+  if (src_dir != NULL)
+    closedir (src_dir);
+  if (dest_dir != NULL)
+    closedir (dest_dir);
+  g_free (src_basename);
+  g_free (src_dirname);
+  g_free (dest_basename);
+  g_free (tmp_dest_basename);
+  g_free (dest_dirname);
+  g_free (dest_path);
+  return ret;
+}
+
+static gboolean
+archive_file_trusted (OstreeRepo   *self,
+                      const char   *path,
+                      const char   *checksum,
+                      OstreeObjectType objtype,
+                      gboolean      ignore_exists,
+                      gboolean      force,
+                      gboolean     *did_exist,
+                      GError      **error)
+{
+  GFile *infile = NULL;
+  GFile *outfile = NULL;
+  GFileOutputStream *out = NULL;
+  gboolean ret = FALSE;
+  char *dest_path = NULL;
+  char *dest_tmp_path = NULL;
+
+  infile = ot_util_new_file_for_path (path);
+
+  dest_path = prepare_dir_for_checksum_get_object_path (self, checksum, objtype, error);
+  if (!dest_path)
+    goto out;
+
+  dest_tmp_path = g_strconcat (dest_path, ".tmp", NULL);
+
+  outfile = ot_util_new_file_for_path (dest_tmp_path);
+  out = g_file_replace (outfile, NULL, FALSE, 0, NULL, error);
+  if (!out)
+    goto out;
+
+  if (!ostree_pack_object ((GOutputStream*)out, infile, objtype, NULL, error))
+    goto out;
+  
+  if (!g_output_stream_close ((GOutputStream*)out, NULL, error))
+    goto out;
+
+  if (rename (dest_tmp_path, dest_path) < 0)
+    {
+      ot_util_set_error_from_errno (error, errno);
+      goto out;
+    }
+
+  ret = TRUE;
+ out:
+  g_free (dest_path);
+  g_free (dest_tmp_path);
+  g_clear_object (&infile);
+  g_clear_object (&outfile);
+  g_clear_object (&out);
+  return ret;
+}
+  
+gboolean      
+ostree_repo_store_object_trusted (OstreeRepo   *self,
+                                  const char   *path,
+                                  const char   *checksum,
+                                  OstreeObjectType objtype,
+                                  gboolean      ignore_exists,
+                                  gboolean      force,
+                                  gboolean     *did_exist,
+                                  GError      **error)
+{
+  OstreeRepoPrivate *priv = GET_PRIVATE (self);
+  if (priv->archive && objtype == OSTREE_OBJECT_TYPE_FILE)
+    return archive_file_trusted (self, path, checksum, objtype, ignore_exists, force, did_exist, error);
+  else
+    return link_object_trusted (self, path, checksum, objtype, ignore_exists, force, did_exist, error);
+}
+
+gboolean
+ostree_repo_store_packfile (OstreeRepo       *self,
+                            const char       *expected_checksum,
+                            const char       *path,
+                            OstreeObjectType  objtype,
+                            GError          **error)
+{
+  OstreeRepoPrivate *priv = GET_PRIVATE (self);
+  gboolean ret = FALSE;
+  GString *tempfile_path = NULL;
+  GChecksum *checksum = NULL;
+  gboolean did_exist;
+
+  tempfile_path = g_string_new (priv->path);
+  g_string_append_printf (tempfile_path, "/tmp-unpack-%s", expected_checksum);
+  
+  if (!ostree_unpack_object (path, objtype, tempfile_path->str, &checksum, error))
+    goto out;
+
+  if (strcmp (g_checksum_get_string (checksum), expected_checksum) != 0)
+    {
+      g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+                   "Corrupted object %s (actual checksum is %s)",
+                   expected_checksum, g_checksum_get_string (checksum));
+      goto out;
+    }
+
+  if (!ostree_repo_store_object_trusted (self, tempfile_path ? tempfile_path->str : path,
+                                         expected_checksum,
+                                         objtype,
+                                         TRUE, FALSE, &did_exist, error))
+    goto out;
+
+  ret = TRUE;
+ out:
+  if (tempfile_path)
+    {
+      (void) unlink (tempfile_path->str);
+      g_string_free (tempfile_path, TRUE);
+    }
+  if (checksum)
+    g_checksum_free (checksum);
+  return ret;
+}
+
+typedef struct _ParsedTreeData ParsedTreeData;
+typedef struct _ParsedDirectoryData ParsedDirectoryData;
+
+static void parsed_tree_data_free (ParsedTreeData *pdata);
+
+struct _ParsedDirectoryData {
+  ParsedTreeData *tree_data;
+  char *metadata_sha256;
+  GVariant *meta_data;
+};
+
+static void
+parsed_directory_data_free (ParsedDirectoryData *pdata)
+{
+  if (pdata == NULL)
+    return;
+  parsed_tree_data_free (pdata->tree_data);
+  g_free (pdata->metadata_sha256);
+  g_variant_unref (pdata->meta_data);
+  g_free (pdata);
+}
+
+struct _ParsedTreeData {
+  GHashTable *files;  /* char* filename -> char* checksum */
+  GHashTable *directories;  /* char* dirname -> ParsedDirectoryData* */
+};
+
+static ParsedTreeData *
+parsed_tree_data_new (void)
+{
+  ParsedTreeData *ret = g_new0 (ParsedTreeData, 1);
+  ret->files = g_hash_table_new_full (g_str_hash, g_str_equal,
+                                      (GDestroyNotify)g_free, 
+                                      (GDestroyNotify)g_free);
+  ret->directories = g_hash_table_new_full (g_str_hash, g_str_equal,
+                                            (GDestroyNotify)g_free, 
+                                            (GDestroyNotify)parsed_directory_data_free);
+  return ret;
+}
+
+static void
+parsed_tree_data_free (ParsedTreeData *pdata)
+{
+  if (pdata == NULL)
+    return;
+  g_hash_table_destroy (pdata->files);
+  g_hash_table_destroy (pdata->directories);
+  g_free (pdata);
+}
+
+static GVariant *
+create_empty_gvariant_dict (void)
+{
+  GVariantBuilder builder;
+  g_variant_builder_init (&builder, G_VARIANT_TYPE("a{sv}"));
+  return g_variant_builder_end (&builder);
+}
+
+static gboolean
+import_parsed_tree (OstreeRepo    *self,
+                    ParsedTreeData  *tree,
+                    GChecksum      **out_checksum,
+                    GError         **error)
+{
+  gboolean ret = FALSE;
+  GVariant *serialized_tree = NULL;
+  gboolean builders_initialized = FALSE;
+  GVariantBuilder files_builder;
+  GVariantBuilder dirs_builder;
+  GHashTableIter hash_iter;
+  GSList *sorted_filenames = NULL;
+  GSList *iter;
+  gpointer key, value;
+
+  g_variant_builder_init (&files_builder, G_VARIANT_TYPE ("a(ss)"));
+  g_variant_builder_init (&dirs_builder, G_VARIANT_TYPE ("a(sss)"));
+  builders_initialized = TRUE;
+
+  g_hash_table_iter_init (&hash_iter, tree->files);
+  while (g_hash_table_iter_next (&hash_iter, &key, &value))
+    {
+      const char *name = key;
+      sorted_filenames = g_slist_prepend (sorted_filenames, (char*)name);
+    }
+
+  sorted_filenames = g_slist_sort (sorted_filenames, (GCompareFunc)strcmp);
+
+  for (iter = sorted_filenames; iter; iter = iter->next)
+    {
+      const char *name = iter->data;
+      const char *value;
+
+      value = g_hash_table_lookup (tree->files, name);
+      g_variant_builder_add (&files_builder, "(ss)", name, value);
+    }
+
+  g_slist_free (sorted_filenames);
+  sorted_filenames = NULL;
+
+  g_hash_table_iter_init (&hash_iter, tree->directories);
+  while (g_hash_table_iter_next (&hash_iter, &key, &value))
+    {
+      const char *name = key;
+      sorted_filenames = g_slist_prepend (sorted_filenames, (char*)name);
+    }
+
+  sorted_filenames = g_slist_sort (sorted_filenames, (GCompareFunc)strcmp);
+
+  for (iter = sorted_filenames; iter; iter = iter->next)
+    {
+      const char *name = iter->data;
+      GChecksum *dir_checksum = NULL;
+      ParsedDirectoryData *dir;
+
+      dir = g_hash_table_lookup (tree->directories, name);
+
+      if (!import_parsed_tree (self, dir->tree_data, &dir_checksum, error))
+        goto out;
+
+      g_variant_builder_add (&dirs_builder, "(sss)",
+                             name, g_checksum_get_string (dir_checksum), dir->metadata_sha256);
+      g_checksum_free (dir_checksum);
+    }
+
+  g_slist_free (sorted_filenames);
+  sorted_filenames = NULL;
+
+  serialized_tree = g_variant_new ("(u a{sv}@a(ss)@a(sss))",
+                                   GUINT32_TO_BE (0),
+                                   create_empty_gvariant_dict (),
+                                   g_variant_builder_end (&files_builder),
+                                   g_variant_builder_end (&dirs_builder));
+  builders_initialized = FALSE;
+  g_variant_ref_sink (serialized_tree);
+  if (!import_gvariant_object (self, OSTREE_SERIALIZED_TREE_VARIANT, serialized_tree, out_checksum, error))
+    goto out;
+  
+  ret = TRUE;
+ out:
+  g_slist_free (sorted_filenames);
+  if (builders_initialized)
+    {
+      g_variant_builder_clear (&files_builder);
+      g_variant_builder_clear (&dirs_builder);
+    }
+  if (serialized_tree)
+    g_variant_unref (serialized_tree);
+  return ret;
+}
+
+static gboolean
+check_path (const char *filename,
+            GError    **error)
+{
+  gboolean ret = FALSE;
+
+  if (!*filename)
+    {
+      g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+                   "Invalid empty filename");
+      goto out;
+    }
+
+  if (strcmp (filename, ".") == 0)
+    {
+      g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+                   "Self-reference '.' in filename '%s' not allowed (yet)", filename);
+      goto out;
+    }
+  
+  if (ot_util_filename_has_dotdot (filename))
+    {
+      g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+                   "Path uplink '..' in filename '%s' not allowed (yet)", filename);
+      goto out;
+    }
+  
+  if (g_path_is_absolute (filename))
+    {
+      g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+                   "Absolute filename '%s' not allowed (yet)", filename);
+      goto out;
+    }
+
+  ret = TRUE;
+ out:
+  return ret;
+}
+
+static gboolean
+add_one_directory_to_tree_and_import (OstreeRepo   *self,
+                                      const char     *basename,
+                                      const char     *abspath,
+                                      ParsedTreeData *tree,
+                                      ParsedDirectoryData **dir, /*inout*/
+                                      GError        **error)
+{
+  gboolean ret = FALSE;
+  GVariant *dirmeta = NULL;
+  GChecksum *dir_meta_checksum = NULL;
+  ParsedDirectoryData *dir_value = *dir;
+  
+  g_assert (tree != NULL);
+
+  if (!import_directory_meta (self, abspath, &dirmeta, &dir_meta_checksum, error))
+    goto out;
+
+  if (dir_value)
+    {
+      g_variant_unref (dir_value->meta_data);
+      dir_value->meta_data = dirmeta;
+    }
+  else
+    {
+      dir_value = g_new0 (ParsedDirectoryData, 1);
+      dir_value->tree_data = parsed_tree_data_new ();
+      dir_value->metadata_sha256 = g_strdup (g_checksum_get_string (dir_meta_checksum));
+      dir_value->meta_data = dirmeta;
+      g_hash_table_insert (tree->directories, g_strdup (basename), dir_value);
+    }
+
+  ret = TRUE;
+  *dir = dir_value;
+ out:
+  if (dir_meta_checksum)
+    g_checksum_free (dir_meta_checksum);
+  return ret;
+}
+
+static gboolean
+add_one_file_to_tree_and_import (OstreeRepo   *self,
+                                 const char     *basename,
+                                 const char     *abspath,
+                                 ParsedTreeData *tree,
+                                 GError        **error)
+{
+  gboolean ret = FALSE;
+  GChecksum *checksum = NULL;
+  struct stat stbuf;
+  gboolean did_exist;
+  
+  g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+  g_assert (tree != NULL);
+
+  if (!ostree_stat_and_checksum_file (-1, abspath, OSTREE_OBJECT_TYPE_FILE, &checksum, &stbuf, error))
+    goto out;
+
+  if (!ostree_repo_store_object_trusted (self, abspath, g_checksum_get_string (checksum),
+                                         OSTREE_OBJECT_TYPE_FILE, TRUE, FALSE, &did_exist, error))
+    goto out;
+
+  g_hash_table_replace (tree->files, g_strdup (basename),
+                        g_strdup (g_checksum_get_string (checksum)));
+
+  ret = TRUE;
+ out:
+  if (checksum)
+    g_checksum_free (checksum);
+  return ret;
+}
+
+static gboolean
+add_one_path_to_tree_and_import (OstreeRepo     *self,
+                                 const char     *base,
+                                 const char     *filename,
+                                 ParsedTreeData *tree,
+                                 GError        **error)
+{
+  gboolean ret = FALSE;
+  GPtrArray *components = NULL;
+  struct stat stbuf;
+  char *component_abspath = NULL;
+  ParsedTreeData *current_tree = tree;
+  const char *component = NULL;
+  const char *file_sha1;
+  char *abspath = NULL;
+  ParsedDirectoryData *dir;
+  int i;
+  gboolean is_directory;
+
+  if (!check_path (filename, error))
+    goto out;
+
+  abspath = g_build_filename (base, filename, NULL);
+
+  if (lstat (abspath, &stbuf) < 0)
+    {
+      ot_util_set_error_from_errno (error, errno);
+      goto out;
+    }
+  is_directory = S_ISDIR(stbuf.st_mode);
+       
+  if (components)
+    g_ptr_array_free (components, TRUE);
+  components = ot_util_path_split (filename);
+  g_assert (components->len > 0);
+
+  current_tree = tree;
+  for (i = 0; i < components->len; i++)
+    {
+      component = components->pdata[i];
+      g_free (component_abspath);
+      component_abspath = ot_util_path_join_n (base, components, i);
+      file_sha1 = g_hash_table_lookup (current_tree->files, component);
+      dir = g_hash_table_lookup (current_tree->directories, component);
+
+      g_assert_cmpstr (component, !=, ".");
+
+      if (i < components->len - 1)
+        {
+          if (file_sha1 != NULL)
+            {
+              g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+                           "Encountered non-directory '%s' in '%s'",
+                           component,
+                           filename);
+              goto out;
+            }
+          /* Implicitly add intermediate directories */
+          if (!add_one_directory_to_tree_and_import (self, component,
+                                                     component_abspath, current_tree, &dir,
+                                                     error))
+            goto out;
+          g_assert (dir != NULL);
+          current_tree = dir->tree_data;
+        }
+      else if (is_directory)
+        {
+          if (file_sha1 != NULL)
+            {
+              g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+                           "File '%s' can't be overwritten by directory",
+                           filename);
+              goto out;
+            }
+          if (!add_one_directory_to_tree_and_import (self, component,
+                                                     abspath, current_tree, &dir,
+                                                     error))
+            goto out;
+        }
+      else 
+        {
+          g_assert (!is_directory);
+          if (dir != NULL)
+            {
+              g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+                           "File '%s' can't be overwritten by directory",
+                           filename);
+              goto out;
+            }
+          if (!add_one_file_to_tree_and_import (self, component, abspath,
+                                                current_tree, error))
+            goto out;
+        }
+    }
+
+  ret = TRUE;
+ out:
+  if (components)
+    g_ptr_array_unref (components);
+  g_free (component_abspath);
+  g_free (abspath);
+  return ret;
+}
+
+gboolean      
+ostree_repo_write_ref (OstreeRepo  *self,
+                       gboolean     is_local,
+                       const char  *name,
+                       const char  *rev,
+                       GError     **error)
+{
+  OstreeRepoPrivate *priv = GET_PRIVATE (self);
+  return write_checksum_file (is_local ? priv->local_heads_dir : priv->remote_heads_dir, 
+                              name, rev, error);
+}
+
+static gboolean
+commit_parsed_tree (OstreeRepo *self,
+                    const char   *branch,
+                    const char   *parent,
+                    const char   *subject,
+                    const char   *body,
+                    GVariant     *metadata,
+                    ParsedDirectoryData *root,
+                    GChecksum   **out_commit,
+                    GError      **error)
+{
+  gboolean ret = FALSE;
+  GChecksum *root_checksum = NULL;
+  GChecksum *ret_commit = NULL;
+  GVariant *commit = NULL;
+  GDateTime *now = NULL;
+
+  g_assert (branch != NULL);
+  g_assert (subject != NULL);
+
+  if (!import_parsed_tree (self, root->tree_data, &root_checksum, error))
+    goto out;
+
+  now = g_date_time_new_now_utc ();
+  commit = g_variant_new ("(u a{sv}ssstss)",
+                          GUINT32_TO_BE (OSTREE_COMMIT_VERSION),
+                          metadata ? metadata : create_empty_gvariant_dict (),
+                          parent ? parent : "",
+                          subject, body ? body : "",
+                          GUINT64_TO_BE (g_date_time_to_unix (now)),
+                          g_checksum_get_string (root_checksum),
+                          root->metadata_sha256);
+  g_variant_ref_sink (commit);
+  if (!import_gvariant_object (self, OSTREE_SERIALIZED_COMMIT_VARIANT,
+                               commit, &ret_commit, error))
+    goto out;
+
+  if (!ostree_repo_write_ref (self, TRUE, branch, g_checksum_get_string (ret_commit), error))
+    goto out;
+
+  ret = TRUE;
+  *out_commit = ret_commit;
+ out:
+  if (root_checksum)
+    g_checksum_free (root_checksum);
+  if (commit)
+    g_variant_unref (commit);
+  if (now)
+    g_date_time_unref (now);
+  return ret;
+}
+
+static gboolean
+import_root (OstreeRepo           *self,
+             const char           *base,
+             ParsedDirectoryData **out_root,
+             GError              **error)
+{
+  gboolean ret = FALSE;
+  ParsedDirectoryData *ret_root = NULL;
+  GVariant *root_metadata = NULL;
+  GChecksum *root_meta_checksum = NULL;
+
+  if (!import_directory_meta (self, base, &root_metadata, &root_meta_checksum, error))
+    goto out;
+
+  ret_root = g_new0 (ParsedDirectoryData, 1);
+  ret_root->tree_data = parsed_tree_data_new ();
+  ret_root->meta_data = root_metadata;
+  root_metadata = NULL;
+  ret_root->metadata_sha256 = g_strdup (g_checksum_get_string (root_meta_checksum));
+
+  ret = TRUE;
+  *out_root = ret_root;
+  ret_root = NULL;
+ out:
+  if (root_metadata)
+    g_variant_unref (root_metadata);
+  if (root_meta_checksum)
+    g_checksum_free (root_meta_checksum);
+  parsed_directory_data_free (ret_root);
+  return ret;
+}
+
+gboolean      
+ostree_repo_commit_from_filelist_fd (OstreeRepo *self,
+                                     const char   *branch,
+                                     const char   *parent,
+                                     const char   *subject,
+                                     const char   *body,
+                                     GVariant     *metadata,
+                                     const char   *base,
+                                     int           fd,
+                                     char          separator,
+                                     GChecksum   **out_commit,
+                                     GError      **error)
+{
+  OstreeRepoPrivate *priv = GET_PRIVATE (self);
+  gboolean ret = FALSE;
+  ParsedDirectoryData *root = NULL;
+  GChecksum *ret_commit_checksum = NULL;
+  GUnixInputStream *in = NULL;
+  GDataInputStream *datain = NULL;
+  char *filename = NULL;
+  gsize filename_len;
+  GError *temp_error = NULL;
+  GVariant *root_metadata = NULL;
+  GChecksum *root_meta_checksum = NULL;
+  char *current_head = NULL;
+
+  g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+  g_return_val_if_fail (priv->inited, FALSE);
+  g_return_val_if_fail (branch != NULL, FALSE);
+  g_return_val_if_fail (subject != NULL, FALSE);
+  g_return_val_if_fail (metadata == NULL || g_variant_is_of_type (metadata, G_VARIANT_TYPE ("a{sv}")), FALSE);
+
+  if (parent == NULL)
+    parent = branch;
+
+  /* We're overwriting the tree */
+  if (!import_root (self, base, &root, error))
+    goto out;
+
+  if (!resolve_rev (self, parent, TRUE, &current_head, error))
+    goto out;
+
+  in = (GUnixInputStream*)g_unix_input_stream_new (fd, FALSE);
+  datain = g_data_input_stream_new ((GInputStream*)in);
+
+  while ((filename = g_data_input_stream_read_upto (datain, &separator, 1,
+                                                    &filename_len, NULL, &temp_error)) != NULL)
+    {
+      if (!g_data_input_stream_read_byte (datain, NULL, &temp_error))
+        {
+          if (temp_error != NULL)
+            {
+              g_propagate_prefixed_error (error, temp_error, "%s", "While reading filelist: ");
+              goto out;
+            }
+        }
+      if (!add_one_path_to_tree_and_import (self, base, filename, root->tree_data, error))
+        goto out;
+      g_free (filename);
+      filename = NULL;
+    }
+  if (filename == NULL && temp_error != NULL)
+    {
+      g_propagate_prefixed_error (error, temp_error, "%s", "While reading filelist: ");
+      goto out;
+    }
+  if (!commit_parsed_tree (self, branch, current_head, subject, body, metadata,
+                           root, &ret_commit_checksum, error))
+    goto out;
+  
+  ret = TRUE;
+  *out_commit = ret_commit_checksum;
+  ret_commit_checksum = NULL;
+ out:
+  if (ret_commit_checksum)
+    g_checksum_free (ret_commit_checksum);
+  g_free (current_head);
+  if (root_metadata)
+    g_variant_unref (root_metadata);
+  if (root_meta_checksum)
+    g_checksum_free (root_meta_checksum);
+  g_clear_object (&datain);
+  g_clear_object (&in);
+  g_free (filename);
+  parsed_directory_data_free (root);
+  return ret;
+  
+}
+
+static gboolean
+iter_object_dir (OstreeRepo   *self,
+                 GFile          *dir,
+                 OstreeRepoObjectIter  callback,
+                 gpointer                user_data,
+                 GError                **error)
+{
+  gboolean ret = FALSE;
+  GError *temp_error = NULL;
+  GFileEnumerator *enumerator = NULL;
+  GFileInfo *file_info = NULL;
+  char *dirpath = NULL;
+
+  dirpath = g_file_get_path (dir);
+
+  enumerator = g_file_enumerate_children (dir, OSTREE_GIO_FAST_QUERYINFO, 
+                                          G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
+                                          NULL, 
+                                          error);
+  if (!enumerator)
+    goto out;
+  
+  while ((file_info = g_file_enumerator_next_file (enumerator, NULL, &temp_error)) != NULL)
+    {
+      const char *name;
+      guint32 type;
+      name = g_file_info_get_attribute_byte_string (file_info, "standard::name"); 
+      type = g_file_info_get_attribute_uint32 (file_info, "standard::type");
+      
+      if (type != G_FILE_TYPE_DIRECTORY
+          && (g_str_has_suffix (name, ".meta")
+              || g_str_has_suffix (name, ".file")
+              || g_str_has_suffix (name, ".packfile")))
+        {
+          char *dot;
+          char *path;
+          
+          dot = strrchr (name, '.');
+          g_assert (dot);
+          
+          if ((dot - name) == 62)
+            {
+              path = g_build_filename (dirpath, name, NULL);
+              callback (self, path, file_info, user_data);
+              g_free (path);
+            }
+        }
+
+      g_object_unref (file_info);
+    }
+  if (file_info == NULL && temp_error != NULL)
+    {
+      g_propagate_error (error, temp_error);
+      goto out;
+    }
+  if (!g_file_enumerator_close (enumerator, NULL, error))
+    goto out;
+
+  ret = TRUE;
+ out:
+  g_free (dirpath);
+  return ret;
+}
+
+gboolean
+ostree_repo_iter_objects (OstreeRepo  *self,
+                            OstreeRepoObjectIter callback,
+                            gpointer       user_data,
+                            GError        **error)
+{
+  OstreeRepoPrivate *priv = GET_PRIVATE (self);
+  GFile *objectdir = NULL;
+  GFileEnumerator *enumerator = NULL;
+  gboolean ret = FALSE;
+  GFileInfo *file_info = NULL;
+  GError *temp_error = NULL;
+
+  g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+  g_return_val_if_fail (priv->inited, FALSE);
+
+  objectdir = ot_util_new_file_for_path (priv->objects_path);
+  enumerator = g_file_enumerate_children (objectdir, OSTREE_GIO_FAST_QUERYINFO, 
+                                          G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
+                                          NULL, 
+                                          error);
+  if (!enumerator)
+    goto out;
+
+  while ((file_info = g_file_enumerator_next_file (enumerator, NULL, &temp_error)) != NULL)
+    {
+      const char *name;
+      guint32 type;
+
+      name = g_file_info_get_attribute_byte_string (file_info, "standard::name"); 
+      type = g_file_info_get_attribute_uint32 (file_info, "standard::type");
+      
+      if (strlen (name) == 2 && type == G_FILE_TYPE_DIRECTORY)
+        {
+          GFile *objdir = g_file_get_child (objectdir, name);
+          if (!iter_object_dir (self, objdir, callback, user_data, error))
+            {
+              g_object_unref (objdir);
+              goto out;
+            }
+          g_object_unref (objdir);
+        }
+      g_object_unref (file_info);
+    }
+  if (file_info == NULL && temp_error != NULL)
+    {
+      g_propagate_error (error, temp_error);
+      goto out;
+    }
+  if (!g_file_enumerator_close (enumerator, NULL, error))
+    goto out;
+
+  ret = TRUE;
+ out:
+  g_clear_object (&file_info);
+  g_clear_object (&enumerator);
+  g_clear_object (&objectdir);
+  return ret;
+}
+
+gboolean
+ostree_repo_load_variant (OstreeRepo *self,
+                          const char   *sha256,
+                          OstreeSerializedVariantType *out_type,
+                          GVariant    **out_variant,
+                          GError      **error)
+{
+  gboolean ret = FALSE;
+  OstreeSerializedVariantType ret_type;
+  GVariant *ret_variant = NULL;
+  char *path = NULL;
+
+  g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+  path = ostree_repo_get_object_path (self, sha256, OSTREE_OBJECT_TYPE_META);
+  if (!ostree_parse_metadata_file (path, &ret_type, &ret_variant, error))
+    goto out;
+
+  ret = TRUE;
+  *out_type = ret_type;
+  *out_variant = ret_variant;
+  ret_variant = NULL;
+ out:
+  if (ret_variant)
+    g_variant_unref (ret_variant);
+  g_free (path);
+  return ret;
+}
+
+static gboolean
+checkout_tree (OstreeRepo    *self,
+               OstreeRepoFile *dir,
+               const char      *destination,
+               GCancellable    *cancellable,
+               GError         **error);
+
+static gboolean
+checkout_one_directory (OstreeRepo  *self,
+                        const char *destination,
+                        const char *dirname,
+                        OstreeRepoFile *dir,
+                        GFileInfo      *dir_info,
+                        GCancellable    *cancellable,
+                        GError         **error)
+{
+  gboolean ret = FALSE;
+  char *dest_path = NULL;
+  GVariant *xattr_variant = NULL;
+
+  dest_path = g_build_filename (destination, dirname, NULL);
+
+  if (!_ostree_repo_file_get_xattrs (dir, &xattr_variant, NULL, error))
+    goto out;
+
+  if (mkdir (dest_path, (mode_t)g_file_info_get_attribute_uint32 (dir_info, "unix::mode")) < 0)
+    {
+      ot_util_set_error_from_errno (error, errno);
+      g_prefix_error (error, "Failed to create directory '%s': ", dest_path);
+      goto out;
+    }
+
+  if (!ostree_set_xattrs (dest_path, xattr_variant, cancellable, error))
+    goto out;
+      
+  if (!checkout_tree (self, dir, dest_path, cancellable, error))
+    goto out;
+
+  ret = TRUE;
+ out:
+  g_free (dest_path);
+  if (xattr_variant)
+    g_variant_unref (xattr_variant);
+  return ret;
+}
+
+static gboolean
+checkout_tree (OstreeRepo    *self,
+               OstreeRepoFile *dir,
+               const char      *destination,
+               GCancellable    *cancellable,
+               GError         **error)
+{
+  OstreeRepoPrivate *priv = GET_PRIVATE (self);
+  gboolean ret = FALSE;
+  GError *temp_error = NULL;
+  GFileInfo *file_info = NULL;
+  GFileEnumerator *dir_enum = NULL;
+  GFile *child = NULL;
+  char *object_path = NULL;
+  char *dest_path = NULL;
+
+  dir_enum = g_file_enumerate_children ((GFile*)dir, OSTREE_GIO_FAST_QUERYINFO, 
+                                        G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
+                                        cancellable, 
+                                        error);
+  if (!dir_enum)
+    goto out;
+
+  while ((file_info = g_file_enumerator_next_file (dir_enum, cancellable, &temp_error)) != NULL)
+    {
+      const char *name;
+      guint32 type;
+
+      name = g_file_info_get_attribute_byte_string (file_info, "standard::name"); 
+      type = g_file_info_get_attribute_uint32 (file_info, "standard::type");
+
+      child = g_file_get_child ((GFile*)dir, name);
+
+      if (type == G_FILE_TYPE_DIRECTORY)
+        {
+          if (!checkout_one_directory (self, destination, name, (OstreeRepoFile*)child, file_info, cancellable, error))
+            goto out;
+        }
+      else
+        {
+          const char *checksum = _ostree_repo_file_nontree_get_checksum ((OstreeRepoFile*)child);
+
+          dest_path = g_build_filename (destination, name, NULL);
+          object_path = ostree_repo_get_object_path (self, checksum, OSTREE_OBJECT_TYPE_FILE);
+
+          if (priv->archive)
+            {
+              if (!ostree_unpack_object (object_path, OSTREE_OBJECT_TYPE_FILE, dest_path, NULL, error))
+                goto out;
+            }
+          else
+            {
+              if (link (object_path, dest_path) < 0)
+                {
+                  ot_util_set_error_from_errno (error, errno);
+                  goto out;
+                }
+            }
+        }
+
+      g_free (object_path);
+      object_path = NULL;
+      g_free (dest_path);
+      dest_path = NULL;
+      g_clear_object (&file_info);
+      g_clear_object (&child);
+    }
+  if (file_info == NULL && temp_error != NULL)
+    {
+      g_propagate_error (error, temp_error);
+      goto out;
+    }
+
+  ret = TRUE;
+ out:
+  g_clear_object (&dir_enum);
+  g_clear_object (&file_info);
+  g_clear_object (&child);
+  g_free (object_path);
+  g_free (dest_path);
+  return ret;
+}
+
+gboolean
+ostree_repo_checkout (OstreeRepo *self,
+                      const char   *rev,
+                      const char   *destination,
+                      GCancellable *cancellable,
+                      GError      **error)
+{
+  gboolean ret = FALSE;
+  char *resolved = NULL;
+  OstreeRepoFile *root = NULL;
+  GFileInfo *root_info = NULL;
+
+  if (g_file_test (destination, G_FILE_TEST_EXISTS))
+    {
+      g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+                   "Destination path '%s' already exists",
+                   destination);
+      goto out;
+    }
+
+  if (!resolve_rev (self, rev, FALSE, &resolved, error))
+    goto out;
+
+  root = (OstreeRepoFile*)_ostree_repo_file_new_root (self, resolved);
+  if (!_ostree_repo_file_ensure_resolved (root, error))
+    goto out;
+
+  root_info = g_file_query_info ((GFile*)root, OSTREE_GIO_FAST_QUERYINFO,
+                                 G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
+                                 NULL, error);
+  if (!root_info)
+    goto out;
+
+  if (!checkout_one_directory (self, destination, NULL, root, root_info, cancellable, error))
+    goto out;
+
+  ret = TRUE;
+ out:
+  g_free (resolved);
+  g_clear_object (&root);
+  g_clear_object (&root_info);
+  return ret;
+}
+
+gboolean
+ostree_repo_diff (OstreeRepo     *self,
+                  const char     *ref,
+                  GFile          *target,
+                  GPtrArray     **out_modified,
+                  GPtrArray     **out_removed,
+                  GPtrArray     **out_added,
+                  GCancellable   *cancellable,
+                  GError        **error)
+{
+  gboolean ret = FALSE;
+  GPtrArray *ret_modified = NULL;
+  GPtrArray *ret_removed = NULL;
+  GPtrArray *ret_added = NULL;
+
+  g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
+               "Not implemented yet");
+  goto out;
+
+  ret = TRUE;
+ out:
+  if (ret_modified)
+    g_ptr_array_free (ret_modified, TRUE);
+  if (ret_removed)
+    g_ptr_array_free (ret_removed, TRUE);
+  if (ret_added)
+    g_ptr_array_free (ret_added, TRUE);
+  return ret;
+}
diff --git a/src/libostree/ostree-repo.h b/src/libostree/ostree-repo.h
new file mode 100644
index 0000000..47a896e
--- /dev/null
+++ b/src/libostree/ostree-repo.h
@@ -0,0 +1,162 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
+ *
+ * Copyright (C) 2011 Colin Walters <walters verbum org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Colin Walters <walters verbum org>
+ */
+
+#ifndef _OSTREE_REPO
+#define _OSTREE_REPO
+
+#include "ostree-core.h"
+
+G_BEGIN_DECLS
+
+#define OSTREE_TYPE_REPO ostree_repo_get_type()
+#define OSTREE_REPO(obj) \
+  (G_TYPE_CHECK_INSTANCE_CAST ((obj), OSTREE_TYPE_REPO, OstreeRepo))
+#define OSTREE_REPO_CLASS(klass) \
+  (G_TYPE_CHECK_CLASS_CAST ((klass), OSTREE_TYPE_REPO, OstreeRepoClass))
+#define OSTREE_IS_REPO(obj) \
+  (G_TYPE_CHECK_INSTANCE_TYPE ((obj), OSTREE_TYPE_REPO))
+#define OSTREE_IS_REPO_CLASS(klass) \
+  (G_TYPE_CHECK_CLASS_TYPE ((klass), OSTREE_TYPE_REPO))
+#define OSTREE_REPO_GET_CLASS(obj) \
+  (G_TYPE_INSTANCE_GET_CLASS ((obj), OSTREE_TYPE_REPO, OstreeRepoClass))
+
+typedef struct {
+  GObject parent;
+} OstreeRepo;
+
+typedef struct {
+  GObjectClass parent_class;
+} OstreeRepoClass;
+
+GType ostree_repo_get_type (void);
+
+OstreeRepo* ostree_repo_new (const char *path);
+
+gboolean      ostree_repo_check (OstreeRepo  *self, GError **error);
+
+const char *  ostree_repo_get_path (OstreeRepo  *self);
+
+gboolean      ostree_repo_is_archive (OstreeRepo  *self);
+
+GKeyFile *    ostree_repo_get_config (OstreeRepo *self);
+
+GKeyFile *    ostree_repo_copy_config (OstreeRepo *self);
+
+gboolean      ostree_repo_write_config (OstreeRepo *self,
+                                        GKeyFile   *new_config,
+                                        GError    **error);
+
+char *        ostree_repo_get_object_path (OstreeRepo   *self,
+                                           const char   *object,
+                                           OstreeObjectType type);
+
+gboolean      ostree_repo_store_packfile (OstreeRepo       *self,
+                                           const char       *expected_checksum,
+                                           const char       *path,
+                                           OstreeObjectType  objtype,
+                                           GError          **error);
+
+gboolean      ostree_repo_store_object_trusted (OstreeRepo   *self,
+                                                const char   *path,
+                                                const char   *checksum,
+                                                OstreeObjectType objtype,
+                                                gboolean      ignore_exists,
+                                                gboolean      force,
+                                                gboolean     *did_exist,
+                                                GError      **error);
+
+gboolean      ostree_repo_resolve_rev (OstreeRepo  *self,
+                                       const char  *rev,
+                                       char       **out_resolved,
+                                       GError     **error);
+
+gboolean      ostree_repo_write_ref (OstreeRepo  *self,
+                                     gboolean     is_local,
+                                     const char  *name,
+                                     const char  *rev,
+                                     GError     **error);
+
+gboolean      ostree_repo_load_variant (OstreeRepo *self,
+                                          const char   *sha256,
+                                          OstreeSerializedVariantType *out_type,
+                                          GVariant    **out_variant,
+                                          GError      **error);
+
+gboolean      ostree_repo_load_variant_checked (OstreeRepo  *self,
+                                                OstreeSerializedVariantType expected_type,
+                                                const char    *sha256, 
+                                                GVariant     **out_variant,
+                                                GError       **error);
+
+gboolean      ostree_repo_commit_from_filelist_fd (OstreeRepo   *self,
+                                                   const char   *branch,
+                                                   const char   *parent,
+                                                   const char   *subject,
+                                                   const char   *body,
+                                                   GVariant     *metadata,
+                                                   const char   *base,
+                                                   int           fd,
+                                                   char          separator,
+                                                   GChecksum   **out_commit,
+                                                   GError      **error);
+
+gboolean      ostree_repo_checkout (OstreeRepo *self,
+                                    const char   *ref,
+                                    const char   *destination,
+                                    GCancellable   *cancellable,
+                                    GError      **error);
+
+typedef struct {
+  guint content_differs : 1;
+  guint xattrs_differs : 1;
+  guint unused : 30;
+
+  GFileInfo *src_info;
+  GFileInfo *target_info;
+
+  char *src_file_checksum;
+  char *target_file_checksum;
+
+  GVariant *src_xattrs;
+  GVariant *target_xattrs;
+} OstreeRepoDiffItem;
+
+gboolean      ostree_repo_diff (OstreeRepo     *self,
+                                const char     *ref,
+                                GFile          *target,
+                                GPtrArray     **out_modified, /* OstreeRepoDiffItem */
+                                GPtrArray     **out_removed, /* OstreeRepoDiffItem */
+                                GPtrArray     **out_added, /* OstreeRepoDiffItem */
+                                GCancellable   *cancellable,
+                                GError        **error);
+
+typedef void (*OstreeRepoObjectIter) (OstreeRepo *self, const char *path,
+                                        GFileInfo *fileinfo, gpointer user_data);
+
+gboolean     ostree_repo_iter_objects (OstreeRepo  *self,
+                                         OstreeRepoObjectIter callback,
+                                         gpointer       user_data,
+                                         GError        **error);
+
+G_END_DECLS
+
+#endif /* _OSTREE_REPO */
diff --git a/libotutil/otutil.h b/src/libostree/ostree.h
similarity index 85%
copy from libotutil/otutil.h
copy to src/libostree/ostree.h
index a93dc84..d683080 100644
--- a/libotutil/otutil.h
+++ b/src/libostree/ostree.h
@@ -20,12 +20,10 @@
  * Author: Colin Walters <walters verbum org>
  */
 
-#ifndef __OSTREE_UTIL_H__
+#ifndef __OSTREE_H__
 
-#include <ot-gio-utils.h>
-#include <ot-glib-compat.h>
-#include <ot-opt-utils.h>
-#include <ot-unix-utils.h>
-#include <ot-variant-utils.h>
+#include <ostree-core.h>
+#include <ostree-repo.h>
+#include <ostree-checkout.h>
 
 #endif
diff --git a/libotutil/ot-gio-utils.c b/src/libotutil/ot-gio-utils.c
similarity index 100%
rename from libotutil/ot-gio-utils.c
rename to src/libotutil/ot-gio-utils.c
diff --git a/libotutil/ot-gio-utils.h b/src/libotutil/ot-gio-utils.h
similarity index 100%
rename from libotutil/ot-gio-utils.h
rename to src/libotutil/ot-gio-utils.h
diff --git a/libotutil/ot-glib-compat.c b/src/libotutil/ot-glib-compat.c
similarity index 100%
rename from libotutil/ot-glib-compat.c
rename to src/libotutil/ot-glib-compat.c
diff --git a/libotutil/ot-glib-compat.h b/src/libotutil/ot-glib-compat.h
similarity index 100%
rename from libotutil/ot-glib-compat.h
rename to src/libotutil/ot-glib-compat.h
diff --git a/libotutil/ot-opt-utils.c b/src/libotutil/ot-opt-utils.c
similarity index 100%
rename from libotutil/ot-opt-utils.c
rename to src/libotutil/ot-opt-utils.c
diff --git a/libotutil/ot-opt-utils.h b/src/libotutil/ot-opt-utils.h
similarity index 100%
rename from libotutil/ot-opt-utils.h
rename to src/libotutil/ot-opt-utils.h
diff --git a/libotutil/ot-unix-utils.c b/src/libotutil/ot-unix-utils.c
similarity index 100%
rename from libotutil/ot-unix-utils.c
rename to src/libotutil/ot-unix-utils.c
diff --git a/libotutil/ot-unix-utils.h b/src/libotutil/ot-unix-utils.h
similarity index 100%
rename from libotutil/ot-unix-utils.h
rename to src/libotutil/ot-unix-utils.h
diff --git a/libotutil/ot-variant-utils.c b/src/libotutil/ot-variant-utils.c
similarity index 100%
rename from libotutil/ot-variant-utils.c
rename to src/libotutil/ot-variant-utils.c
diff --git a/libotutil/ot-variant-utils.h b/src/libotutil/ot-variant-utils.h
similarity index 100%
rename from libotutil/ot-variant-utils.h
rename to src/libotutil/ot-variant-utils.h
diff --git a/libotutil/otutil.h b/src/libotutil/otutil.h
similarity index 100%
rename from libotutil/otutil.h
rename to src/libotutil/otutil.h
diff --git a/osbuild/main.c b/src/osbuild/main.c
similarity index 100%
rename from osbuild/main.c
rename to src/osbuild/main.c
diff --git a/osbuild/ob-builtin-buildone.c b/src/osbuild/ob-builtin-buildone.c
similarity index 100%
rename from osbuild/ob-builtin-buildone.c
rename to src/osbuild/ob-builtin-buildone.c
diff --git a/osbuild/ob-builtins.h b/src/osbuild/ob-builtins.h
similarity index 100%
rename from osbuild/ob-builtins.h
rename to src/osbuild/ob-builtins.h
diff --git a/osbuild/osbuild-raw-makeinstall.c b/src/osbuild/osbuild-raw-makeinstall.c
similarity index 100%
rename from osbuild/osbuild-raw-makeinstall.c
rename to src/osbuild/osbuild-raw-makeinstall.c
diff --git a/osbuild/ostree-buildone b/src/osbuild/ostree-buildone
similarity index 100%
rename from osbuild/ostree-buildone
rename to src/osbuild/ostree-buildone
diff --git a/osbuild/ostree-buildone-make b/src/osbuild/ostree-buildone-make
similarity index 100%
rename from osbuild/ostree-buildone-make
rename to src/osbuild/ostree-buildone-make
diff --git a/osbuild/ostree-buildone-makeinstall-split-artifacts b/src/osbuild/ostree-buildone-makeinstall-split-artifacts
similarity index 100%
rename from osbuild/ostree-buildone-makeinstall-split-artifacts
rename to src/osbuild/ostree-buildone-makeinstall-split-artifacts
diff --git a/ostree/main.c b/src/ostree/main.c
similarity index 100%
rename from ostree/main.c
rename to src/ostree/main.c
diff --git a/ostree/ot-builtin-checkout.c b/src/ostree/ot-builtin-checkout.c
similarity index 100%
rename from ostree/ot-builtin-checkout.c
rename to src/ostree/ot-builtin-checkout.c
diff --git a/ostree/ot-builtin-commit.c b/src/ostree/ot-builtin-commit.c
similarity index 100%
rename from ostree/ot-builtin-commit.c
rename to src/ostree/ot-builtin-commit.c
diff --git a/ostree/ot-builtin-compose.c b/src/ostree/ot-builtin-compose.c
similarity index 100%
rename from ostree/ot-builtin-compose.c
rename to src/ostree/ot-builtin-compose.c
diff --git a/ostree/ot-builtin-diff.c b/src/ostree/ot-builtin-diff.c
similarity index 100%
rename from ostree/ot-builtin-diff.c
rename to src/ostree/ot-builtin-diff.c
diff --git a/ostree/ot-builtin-fsck.c b/src/ostree/ot-builtin-fsck.c
similarity index 100%
rename from ostree/ot-builtin-fsck.c
rename to src/ostree/ot-builtin-fsck.c
diff --git a/ostree/ot-builtin-init.c b/src/ostree/ot-builtin-init.c
similarity index 100%
rename from ostree/ot-builtin-init.c
rename to src/ostree/ot-builtin-init.c
diff --git a/ostree/ot-builtin-log.c b/src/ostree/ot-builtin-log.c
similarity index 100%
rename from ostree/ot-builtin-log.c
rename to src/ostree/ot-builtin-log.c
diff --git a/ostree/ot-builtin-pull.c b/src/ostree/ot-builtin-pull.c
similarity index 100%
rename from ostree/ot-builtin-pull.c
rename to src/ostree/ot-builtin-pull.c
diff --git a/ostree/ot-builtin-remote.c b/src/ostree/ot-builtin-remote.c
similarity index 100%
rename from ostree/ot-builtin-remote.c
rename to src/ostree/ot-builtin-remote.c
diff --git a/ostree/ot-builtin-rev-parse.c b/src/ostree/ot-builtin-rev-parse.c
similarity index 100%
rename from ostree/ot-builtin-rev-parse.c
rename to src/ostree/ot-builtin-rev-parse.c
diff --git a/ostree/ot-builtin-run-triggers.c b/src/ostree/ot-builtin-run-triggers.c
similarity index 100%
rename from ostree/ot-builtin-run-triggers.c
rename to src/ostree/ot-builtin-run-triggers.c
diff --git a/ostree/ot-builtin-show.c b/src/ostree/ot-builtin-show.c
similarity index 100%
rename from ostree/ot-builtin-show.c
rename to src/ostree/ot-builtin-show.c
diff --git a/ostree/ot-builtins.h b/src/ostree/ot-builtins.h
similarity index 100%
rename from ostree/ot-builtins.h
rename to src/ostree/ot-builtins.h



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]