[ostree/wip/pull-rebase2] Add pull



commit 8be40f3af387bfe07e958ae80985aaffc705432b
Author: Colin Walters <walters verbum org>
Date:   Sat Oct 29 12:01:31 2011 -0400

    Add pull

 Makefile-src.am                                    |   16 +-
 configure.ac                                       |    1 +
 src/libostree/ostree-checkout.c                    |    2 +-
 src/libostree/ostree-core.c                        |  313 ++++++++++-
 src/libostree/ostree-core.h                        |   52 ++-
 src/libostree/ostree-repo.c                        |  582 ++++++++++++++++----
 src/libostree/ostree-repo.h                        |   41 ++-
 src/main.c                                         |    1 +
 src/ostree-http-backend.c                          |   45 ++
 src/ot-builtin-fsck.c                              |  111 ++++-
 src/ot-builtin-init.c                              |   25 +-
 src/ot-builtin-pull.c                              |  365 ++++++++++++
 src/ot-builtin-remote.c                            |   18 +-
 src/ot-builtins.h                                  |    1 +
 tests/Makefile                                     |    8 +-
 tests/libtest.sh                                   |   52 ++
 tests/run-apache.c                                 |  167 ++++++
 tests/t0012-pull.sh                                |   34 ++
 tests/t0013-commit-archive.sh                      |   42 ++
 tests/{ostree-http-server.c => tmpdir-lifecycle.c} |   89 ++--
 20 files changed, 1760 insertions(+), 205 deletions(-)
---
diff --git a/Makefile-src.am b/Makefile-src.am
index 9990ecd..ae28761 100644
--- a/Makefile-src.am
+++ b/Makefile-src.am
@@ -40,8 +40,8 @@ libostree_la_SOURCES = src/libostree/ostree.h \
 	src/libostree/ostree-checkout.c \
 	src/libostree/ostree-checkout.h \
 	$(NULL)
-libostree_la_CFLAGS = -I$(srcdir)/src/libostree -I$(srcdir)/src/libotutil -DLOCALEDIR=\"$(datadir)/locale\" $(GIO_UNIX_CFLAGS)
-libostree_la_LIBADD = libotutil.la $(GIO_UNIX_LIBS)
+libostree_la_CFLAGS = -I$(srcdir)/src/libostree -I$(srcdir)/src/libotutil -DLOCALEDIR=\"$(datadir)/locale\" $(OT_COREBIN_DEP_CFLAGS)
+libostree_la_LIBADD = libotutil.la $(OT_COREBIN_DEP_LIBS)
 
 bin_PROGRAMS += ostree
 
@@ -53,10 +53,18 @@ ostree_SOURCES = src/main.c \
 	src/ot-builtin-init.c \
 	src/ot-builtin-link-file.c \
 	src/ot-builtin-log.c \
+	src/ot-builtin-pull.c \
 	src/ot-builtin-run-triggers.c \
 	src/ot-builtin-remote.c \
 	src/ot-builtin-rev-parse.c \
 	src/ot-builtin-show.c \
 	$(NULL)
-ostree_CFLAGS = -I$(srcdir)/src -I$(srcdir)/src/libostree -I$(srcdir)/src/libotutil -DLOCALEDIR=\"$(datadir)/locale\" $(GIO_UNIX_CFLAGS)
-ostree_LDADD = libotutil.la libostree.la $(GIO_UNIX_LIBS)
+ostree_CFLAGS = -I$(srcdir)/src -I$(srcdir)/src/libostree -I$(srcdir)/src/libotutil -DLOCALEDIR=\"$(datadir)/locale\" $(OT_COREBIN_DEP_CFLAGS)
+ostree_LDADD = libotutil.la libostree.la $(OT_COREBIN_DEP_LIBS)
+
+bin_PROGRAMS += ostree-http-backend
+
+ostree_http_backend_SOURCES = src/ostree-http-backend.c
+ostree_http_backend_CFLAGS = -I$(srcdir)/src -I$(srcdir)/src/libostree -I$(srcdir)/src/libotutil -DLOCALEDIR=\"$(datadir)/locale\" $(OT_COREBIN_DEP_CFLAGS)
+ostree_http_backend_LDADD = libotutil.la libostree.la $(OT_COREBIN_DEP_LIBS)
+
diff --git a/configure.ac b/configure.ac
index be78b1f..92c06d5 100644
--- a/configure.ac
+++ b/configure.ac
@@ -32,6 +32,7 @@ LT_INIT
 PKG_PROG_PKG_CONFIG
 
 PKG_CHECK_MODULES(GIO_UNIX, [gio-unix-2.0 >= 2.28])
+PKG_CHECK_MODULES(OT_COREBIN_DEP, [libsoup-gnome-2.4 >= 2.34.0 gio-unix-2.0 >= 2.28])
 
 AM_PATH_PYTHON
 
diff --git a/src/libostree/ostree-checkout.c b/src/libostree/ostree-checkout.c
index e4f09fb..0569f28 100644
--- a/src/libostree/ostree-checkout.c
+++ b/src/libostree/ostree-checkout.c
@@ -204,7 +204,7 @@ run_trigger (OstreeCheckout *self,
     {
       temp_path = g_build_filename (priv->path, basename, NULL);
       rel_temp_path = g_strconcat ("./", basename, NULL);
-      temp_copy = g_file_new_for_path (temp_path);
+      temp_copy = ot_util_new_file_for_path (temp_path);
 
       if (!g_file_copy (trigger, temp_copy, 0, NULL, NULL, NULL, error))
         goto out;
diff --git a/src/libostree/ostree-core.c b/src/libostree/ostree-core.c
index d92681b..ce54fbc 100644
--- a/src/libostree/ostree-core.c
+++ b/src/libostree/ostree-core.c
@@ -27,13 +27,27 @@
 #include <sys/types.h>
 #include <attr/xattr.h>
 
-static char *
-stat_to_string (struct stat *stbuf)
+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)
 {
-  return g_strdup_printf ("%u:%u:%u",
-                          (guint32)(stbuf->st_mode & ~S_IFMT),
-                          (guint32)stbuf->st_uid, 
-                          (guint32)stbuf->st_gid);
+  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 *
@@ -144,6 +158,7 @@ ostree_get_xattrs_for_path (const char *path,
     }
 
   ret = g_variant_builder_end (&builder);
+  g_variant_ref_sink (ret);
  out:
   if (!ret)
     g_variant_builder_clear (&builder);
@@ -154,9 +169,10 @@ ostree_get_xattrs_for_path (const char *path,
 
 gboolean
 ostree_stat_and_checksum_file (int dir_fd, const char *path,
-                                 GChecksum **out_checksum,
-                                 struct stat *out_stbuf,
-                                 GError **error)
+                               OstreeObjectType objtype,
+                               GChecksum **out_checksum,
+                               struct stat *out_stbuf,
+                               GError **error)
 {
   GChecksum *content_sha256 = NULL;
   GChecksum *content_and_meta_sha256 = NULL;
@@ -202,10 +218,12 @@ ostree_stat_and_checksum_file (int dir_fd, const char *path,
         }
     }
 
-  stat_string = stat_to_string (&stbuf);
-  xattrs = ostree_get_xattrs_for_path (path, error);
-  if (!xattrs)
-    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);
  
@@ -224,6 +242,8 @@ ostree_stat_and_checksum_file (int dir_fd, const char *path,
   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)
@@ -235,6 +255,7 @@ ostree_stat_and_checksum_file (int dir_fd, const char *path,
     }
   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));
     }
@@ -249,8 +270,12 @@ ostree_stat_and_checksum_file (int dir_fd, const char *path,
 
   content_and_meta_sha256 = g_checksum_copy (content_sha256);
 
-  g_checksum_update (content_and_meta_sha256, (guint8*)stat_string, strlen (stat_string));
-  g_checksum_update (content_and_meta_sha256, (guint8*)g_variant_get_data (xattrs), g_variant_get_size (xattrs));
+  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;
@@ -267,6 +292,264 @@ ostree_stat_and_checksum_file (int dir_fd, const char *path,
     g_variant_unref (xattrs);
   if (content_sha256)
     g_checksum_free (content_sha256);
+  return ret;
+}
+
+gboolean
+ostree_set_xattrs (const char *path, GVariant *xattrs, 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)
+{
+  GMappedFile *mfile = NULL;
+  gboolean ret = FALSE;
+  GVariant *ret_variant = NULL;
+  GVariant *container = NULL;
+  guint32 ret_type;
+
+  mfile = g_mapped_file_new (path, FALSE, error);
+  if (mfile == NULL)
+    {
+      goto out;
+    }
+  else
+    {
+      container = g_variant_new_from_data (G_VARIANT_TYPE (OSTREE_SERIALIZED_VARIANT_FORMAT),
+                                           g_mapped_file_get_contents (mfile),
+                                           g_mapped_file_get_length (mfile),
+                                           FALSE,
+                                           (GDestroyNotify) g_mapped_file_unref,
+                                           mfile);
+      g_variant_get (container, "(uv)",
+                     &ret_type, &ret_variant);
+      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;
+        }
+      mfile = NULL;
+    }
 
+  ret = TRUE;
+  *out_type = ret_type;
+  *out_variant = ret_variant;
+  ret_variant = NULL;
+ out:
+  if (ret_variant)
+    g_variant_unref (ret_variant);
+  if (container != NULL)
+    g_variant_unref (container);
+  if (mfile != NULL)
+    g_mapped_file_unref (mfile);
   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
+        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
+        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;
+}
+
diff --git a/src/libostree/ostree-core.h b/src/libostree/ostree-core.h
index 6d80941..3c16918 100644
--- a/src/libostree/ostree-core.h
+++ b/src/libostree/ostree-core.h
@@ -39,6 +39,7 @@ typedef enum {
   OSTREE_SERIALIZED_DIRMETA_VARIANT = 3,
   OSTREE_SERIALIZED_XATTR_VARIANT = 4
 } OstreeSerializedVariantType;
+#define OSTREE_SERIALIZED_VARIANT_LAST 4
 
 #define OSTREE_SERIALIZED_VARIANT_FORMAT "(uv)"
 
@@ -83,13 +84,54 @@ typedef enum {
  */
 #define OSTREE_COMMIT_GVARIANT_FORMAT "(ua{sv}ssstss)"
 
-GVariant *ostree_get_xattrs_for_path (const char *path,
-                                        GError    **error);
+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, 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,
-                                          GChecksum **out_checksum,
-                                          struct stat *out_stbuf,
-                                          GError **error);
+                                        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             *path,
+                              OstreeObjectType  objtype,
+                              GCancellable     *cancellable,
+                              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.c b/src/libostree/ostree-repo.c
index 964ce69..a1323c6 100644
--- a/src/libostree/ostree-repo.c
+++ b/src/libostree/ostree-repo.c
@@ -57,12 +57,14 @@ struct _OstreeRepoPrivate {
   char *path;
   GFile *repo_file;
   GFile *local_heads_dir;
+  GFile *remote_heads_dir;
   char *objects_path;
   char *config_path;
 
   gboolean inited;
 
   GKeyFile *config;
+  gboolean archive;
 };
 
 static void
@@ -74,6 +76,7 @@ ostree_repo_finalize (GObject *object)
   g_free (priv->path);
   g_clear_object (&priv->repo_file);
   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)
@@ -140,6 +143,7 @@ ostree_repo_constructor (GType                  gtype,
   
   priv->repo_file = ot_util_new_file_for_path (priv->path);
   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);
@@ -180,19 +184,6 @@ ostree_repo_new (const char *path)
 }
 
 static gboolean
-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;
-}
-
-static gboolean
 parse_rev_file (OstreeRepo     *self,
                 const char     *path,
                 char          **sha256,
@@ -252,7 +243,7 @@ parse_rev_file (OstreeRepo     *self,
     }
   else 
     {
-      if (!validate_checksum_string (rev, error))
+      if (!ostree_validate_checksum_string (rev, error))
         goto out;
     }
 
@@ -304,7 +295,7 @@ resolve_rev (OstreeRepo     *self,
        {
          g_strchomp (ret_rev);
          
-         if (!validate_checksum_string (ret_rev, error))
+         if (!ostree_validate_checksum_string (ret_rev, error))
            goto out;
        }
    }
@@ -357,12 +348,88 @@ write_checksum_file (GFile *parentdir,
   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_unref (priv->config);
+  priv->config = g_key_file_ref (new_config);
+
+  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);
 
@@ -383,9 +450,12 @@ ostree_repo_check (OstreeRepo *self, GError **error)
       goto out;
     }
 
-  version = g_key_file_get_value (priv->config, "core", "repo_version", error);
-  if (!version)
-    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)
     {
@@ -394,6 +464,20 @@ ostree_repo_check (OstreeRepo *self, GError **error)
       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;
@@ -402,6 +486,23 @@ ostree_repo_check (OstreeRepo *self, GError **error)
   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
 import_gvariant_object (OstreeRepo  *self,
                         OstreeSerializedVariantType type,
@@ -463,54 +564,13 @@ load_gvariant_object_unknown (OstreeRepo  *self,
                               GVariant     **out_variant,
                               GError       **error)
 {
-  GMappedFile *mfile = NULL;
   gboolean ret = FALSE;
-  GVariant *ret_variant = NULL;
-  GVariant *container = NULL;
   char *path = NULL;
-  guint32 ret_type;
 
   path = get_object_path (self, sha256, OSTREE_OBJECT_TYPE_META);
-  
-  mfile = g_mapped_file_new (path, FALSE, error);
-  if (mfile == NULL)
-    goto out;
-  else
-    {
-      container = g_variant_new_from_data (G_VARIANT_TYPE (OSTREE_SERIALIZED_VARIANT_FORMAT),
-                                           g_mapped_file_get_contents (mfile),
-                                           g_mapped_file_get_length (mfile),
-                                           FALSE,
-                                           (GDestroyNotify) g_mapped_file_unref,
-                                           mfile);
-      if (!g_variant_is_of_type (container, G_VARIANT_TYPE (OSTREE_SERIALIZED_VARIANT_FORMAT)))
-        {
-          g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
-                       "Corrupted metadata object '%s'", sha256);
-          goto out;
-        }
-      g_variant_get (container, "(uv)",
-                     &ret_type, &ret_variant);
-      mfile = NULL;
-    }
-
-  ret = TRUE;
- out:
-  if (!ret)
-    {
-      if (ret_variant)
-        g_variant_unref (ret_variant);
-    }
-  else
-    {
-      *out_type = ret_type;
-      *out_variant = ret_variant;
-    }
-  if (container != NULL)
-    g_variant_unref (container);
+  ret = ostree_parse_metadata_file (path, out_type, out_variant, error);
   g_free (path);
-  if (mfile != NULL)
-    g_mapped_file_unref (mfile);
+
   return ret;
 }
 
@@ -534,7 +594,6 @@ load_gvariant_object (OstreeRepo  *self,
                    "Corrupted metadata object '%s'; found type %u, expected %u", sha256,
                    type, (guint32)expected_type);
       goto out;
-      
     }
 
   ret = TRUE;
@@ -616,41 +675,26 @@ get_object_path (OstreeRepo  *self,
                  OstreeObjectType type)
 {
   OstreeRepoPrivate *priv = GET_PRIVATE (self);
-  char *checksum_prefix;
-  char *base_path;
   char *ret;
-  const char *type_string;
+  char *relpath;
 
-  checksum_prefix = g_strndup (checksum, 2);
-  base_path = g_build_filename (priv->objects_path, checksum_prefix, checksum + 2, NULL);
-  switch (type)
-    {
-    case OSTREE_OBJECT_TYPE_FILE:
-      type_string = ".file";
-      break;
-    case OSTREE_OBJECT_TYPE_META:
-      type_string = ".meta";
-      break;
-    default:
-      g_assert_not_reached ();
-    }
-  ret = g_strconcat (base_path, type_string, NULL);
-  g_free (base_path);
-  g_free (checksum_prefix);
+  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,
-                                          GChecksum    *checksum,
+                                          const char   *checksum,
                                           OstreeObjectType type,
                                           GError      **error)
 {
   char *checksum_dir = NULL;
   char *object_path = NULL;
 
-  object_path = get_object_path (self, g_checksum_get_string (checksum), type);
+  object_path = get_object_path (self, checksum, type);
   checksum_dir = g_path_get_dirname (object_path);
 
   if (!ot_util_ensure_directory (checksum_dir, FALSE, error))
@@ -662,21 +706,23 @@ prepare_dir_for_checksum_get_object_path (OstreeRepo *self,
 }
 
 static gboolean
-link_one_file (OstreeRepo *self, const char *path, OstreeObjectType type,
-               gboolean ignore_exists, gboolean force,
-               GChecksum **out_checksum,
-               GError **error)
+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;
-  GChecksum *id = NULL;
   DIR *src_dir = NULL;
   DIR *dest_dir = NULL;
   gboolean ret = FALSE;
-  struct stat stbuf;
   char *dest_path = NULL;
 
   src_basename = g_path_get_basename (path);
@@ -689,9 +735,7 @@ link_one_file (OstreeRepo *self, const char *path, OstreeObjectType type,
       goto out;
     }
 
-  if (!ostree_stat_and_checksum_file (dirfd (src_dir), path, &id, &stbuf, error))
-    goto out;
-  dest_path = prepare_dir_for_checksum_get_object_path (self, id, type, error);
+  dest_path = prepare_dir_for_checksum_get_object_path (self, checksum, objtype, error);
   if (!dest_path)
     goto out;
 
@@ -719,7 +763,11 @@ link_one_file (OstreeRepo *self, const char *path, OstreeObjectType type,
           ot_util_set_error_from_errno (error, errno);
           goto out;
         }
+      else
+        *did_exist = TRUE;
     }
+  else
+    *did_exist = FALSE;
 
   if (force)
     {
@@ -732,12 +780,8 @@ link_one_file (OstreeRepo *self, const char *path, OstreeObjectType type,
       (void) unlinkat (dirfd (dest_dir), tmp_dest_basename, 0);
     }
 
-  *out_checksum = id;
-  id = NULL;
   ret = TRUE;
  out:
-  if (id != NULL)
-    g_checksum_free (id);
   if (src_dir != NULL)
     closedir (src_dir);
   if (dest_dir != NULL)
@@ -750,12 +794,108 @@ link_one_file (OstreeRepo *self, const char *path, OstreeObjectType type,
   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);
+}
+
+static gboolean
+link_one_file (OstreeRepo *self, const char *path, OstreeObjectType type,
+               gboolean ignore_exists, gboolean force,
+               GChecksum **out_checksum,
+               GError **error)
+{
+  gboolean ret = FALSE;
+  struct stat stbuf;
+  GChecksum *id = NULL;
+  gboolean did_exist;
+
+  if (!ostree_stat_and_checksum_file (-1, path, type, &id, &stbuf, error))
+    goto out;
+
+  if (!ostree_repo_store_object_trusted (self, path, g_checksum_get_string (id), type,
+                                         ignore_exists, force, &did_exist, error))
+    goto out;
+
+  *out_checksum = id;
+  id = NULL;
+  ret = TRUE;
+ out:
+  if (id != NULL)
+    g_checksum_free (id);
+  return ret;
+}
+
 gboolean
 ostree_repo_link_file (OstreeRepo *self,
-                         const char   *path,
-                         gboolean      ignore_exists,
-                         gboolean      force,
-                         GError      **error)
+                       const char   *path,
+                       gboolean      ignore_exists,
+                       gboolean      force,
+                       GError      **error)
 {
   OstreeRepoPrivate *priv = GET_PRIVATE (self);
   GChecksum *checksum = NULL;
@@ -770,6 +910,227 @@ ostree_repo_link_file (OstreeRepo *self,
   return TRUE;
 }
 
+static gboolean
+unpack_and_checksum_packfile (OstreeRepo   *self,
+                              const char   *path,
+                              gchar       **out_filename,
+                              GChecksum   **out_checksum,
+                              GError      **error)
+{
+  OstreeRepoPrivate *priv = GET_PRIVATE (self);
+  gboolean ret = FALSE;
+  GFile *file = NULL;
+  char *temp_path = NULL;
+  GFile *temp_file = NULL;
+  GFileOutputStream *temp_out = NULL;
+  char *metadata_buf = NULL;
+  GVariant *metadata = NULL;
+  GVariant *xattrs = NULL;
+  GFileInputStream *in = NULL;
+  GChecksum *ret_checksum = NULL;
+  guint32 metadata_len;
+  guint32 version, uid, gid, mode;
+  guint64 content_len;
+  gsize bytes_read, bytes_written;
+  char buf[8192];
+  int temp_fd = -1;
+
+  file = ot_util_new_file_for_path (path);
+
+  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);
+  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;
+    }
+
+  metadata = g_variant_new_from_data (G_VARIANT_TYPE (OSTREE_PACK_FILE_VARIANT_FORMAT),
+                                      metadata_buf, metadata_len, FALSE, NULL, NULL);
+      
+  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);
+
+  temp_path = g_build_filename (priv->path, "tmp-packfile-XXXXXX");
+  temp_file = ot_util_new_file_for_path (temp_path);
+      
+  ret_checksum = g_checksum_new (G_CHECKSUM_SHA256);
+
+  if (S_ISREG (mode))
+    {
+      temp_fd = g_mkstemp (temp_path);
+      if (temp_fd < 0)
+        {
+          ot_util_set_error_from_errno (error, errno);
+          goto out;
+        }
+      close (temp_fd);
+      temp_fd = -1;
+      temp_out = g_file_replace (temp_file, NULL, FALSE, 0, NULL, error);
+      if (!temp_out)
+        goto out;
+
+      do
+        {
+          if (!g_input_stream_read_all ((GInputStream*)in, buf, sizeof(buf), &bytes_read, NULL, error))
+            goto out;
+          g_checksum_update (ret_checksum, (guint8*)buf, bytes_read);
+          if (!g_output_stream_write_all ((GOutputStream*)temp_out, buf, bytes_read, &bytes_written, NULL, error))
+            goto out;
+        }
+      while (bytes_read > 0);
+
+      if (!g_output_stream_close ((GOutputStream*)temp_out, NULL, error))
+        goto out;
+    }
+  else if (S_ISLNK (mode))
+    {
+      g_assert (sizeof (buf) > PATH_MAX);
+
+      if (!g_input_stream_read_all ((GInputStream*)in, buf, sizeof(buf), &bytes_read, NULL, error))
+        goto out;
+      buf[bytes_read] = '\0';
+      if (symlink (buf, temp_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 ((GInputStream*)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 (mknod (temp_path, mode, dev) < 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 (temp_path, mode) < 0)
+        {
+          ot_util_set_error_from_errno (error, errno);
+          goto out;
+        }
+    }
+
+  if (!ostree_set_xattrs (temp_path, xattrs, error))
+    goto out;
+
+  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;
+  *out_checksum = ret_checksum;
+  ret_checksum = NULL;
+  *out_filename = temp_path;
+  temp_path = NULL;
+ out:
+  if (ret_checksum)
+    g_checksum_free (ret_checksum);
+  g_free (metadata_buf);
+  if (temp_path)
+    (void) unlink (temp_path);
+  g_free (temp_path);
+  g_clear_object (&file);
+  g_clear_object (&in);
+  g_clear_object (&temp_file);
+  g_clear_object (&temp_out);
+  if (metadata)
+   g_variant_unref (metadata);
+  if (xattrs)
+    g_variant_unref (xattrs);
+  return ret;
+}
+
+gboolean
+ostree_repo_store_packfile (OstreeRepo       *self,
+                            const char       *expected_checksum,
+                            const char       *path,
+                            OstreeObjectType  objtype,
+                            GError          **error)
+{
+  gboolean ret = FALSE;
+  char *tempfile = NULL;
+  GChecksum *checksum = NULL;
+  struct stat stbuf;
+  gboolean did_exist;
+
+  if (objtype == OSTREE_OBJECT_TYPE_META)
+    {
+      if (!ostree_stat_and_checksum_file (-1, path, objtype, &checksum, &stbuf, error))
+        goto out;
+    }
+  else
+    {
+      if (!unpack_and_checksum_packfile (self, path, &tempfile, &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 ? tempfile : path,
+                                         expected_checksum,
+                                         objtype,
+                                         TRUE, FALSE, &did_exist, error))
+    goto out;
+
+  ret = TRUE;
+ out:
+  if (tempfile)
+    (void) unlink (tempfile);
+  g_free (tempfile);
+  if (checksum)
+    g_checksum_free (checksum);
+  return ret;
+}
+
 typedef struct _ParsedTreeData ParsedTreeData;
 typedef struct _ParsedDirectoryData ParsedDirectoryData;
 
@@ -839,6 +1200,7 @@ parse_tree (OstreeRepo    *self,
                              sha256, &tree_variant, error))
     goto out;
 
+  /* PARSE OSTREE_SERIALIZED_TREE_VARIANT */
   g_variant_get (tree_variant, "(u a{sv}@a(ss)@a(sss))",
                  &version, &meta_variant, &files_variant, &dirs_variant);
 
@@ -922,6 +1284,7 @@ load_commit_and_trees (OstreeRepo   *self,
                              commit_sha256, &ret_commit, error))
     goto out;
 
+  /* PARSE OSTREE_SERIALIZED_COMMIT_VARIANT */
   g_variant_get_child (ret_commit, 6, "&s", &tree_contents_checksum);
   g_variant_get_child (ret_commit, 7, "&s", &tree_meta_checksum);
 
@@ -1366,6 +1729,18 @@ add_files_to_tree_and_import (OstreeRepo   *self,
   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,
@@ -1377,7 +1752,6 @@ commit_parsed_tree (OstreeRepo *self,
                     GChecksum   **out_commit,
                     GError      **error)
 {
-  OstreeRepoPrivate *priv = GET_PRIVATE (self);
   gboolean ret = FALSE;
   GChecksum *root_checksum = NULL;
   GChecksum *ret_commit = NULL;
@@ -1403,7 +1777,7 @@ commit_parsed_tree (OstreeRepo *self,
                                commit, &ret_commit, error))
     goto out;
 
-  if (!write_checksum_file (priv->local_heads_dir, branch, g_checksum_get_string (ret_commit), error))
+  if (!ostree_repo_write_ref (self, TRUE, branch, g_checksum_get_string (ret_commit), error))
     goto out;
 
   ret = TRUE;
@@ -1648,7 +2022,8 @@ iter_object_dir (OstreeRepo   *self,
       
       if (type != G_FILE_TYPE_DIRECTORY
           && (g_str_has_suffix (name, ".meta")
-              || g_str_has_suffix (name, ".file")))
+              || g_str_has_suffix (name, ".file")
+              || g_str_has_suffix (name, ".packfile")))
         {
           char *dot;
           char *path;
@@ -1789,6 +2164,7 @@ checkout_one_directory (OstreeRepo  *self,
 
   dest_path = g_build_filename (destination, dirname, NULL);
       
+  /* PARSE OSTREE_SERIALIZED_DIRMETA_VARIANT */
   g_variant_get (dir->meta_data, "(uuuu a(ayay))",
                  &version, &uid, &gid, &mode,
                  &xattr_variant);
diff --git a/src/libostree/ostree-repo.h b/src/libostree/ostree-repo.h
index c0159ea..15d5036 100644
--- a/src/libostree/ostree-repo.h
+++ b/src/libostree/ostree-repo.h
@@ -53,17 +53,50 @@ 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);
+
 gboolean      ostree_repo_link_file (OstreeRepo *self,
-                                       const char   *path,
-                                       gboolean      ignore_exists,
-                                       gboolean      force,
-                                       GError      **error);
+                                     const char   *path,
+                                     gboolean      ignore_exists,
+                                     gboolean      force,
+                                     GError      **error);
+
+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,
diff --git a/src/main.c b/src/main.c
index f32e741..ad5dfd8 100644
--- a/src/main.c
+++ b/src/main.c
@@ -33,6 +33,7 @@ static OstreeBuiltin builtins[] = {
   { "commit", ostree_builtin_commit, 0 },
   { "link-file", ostree_builtin_link_file, 0 },
   { "log", ostree_builtin_log, 0 },
+  { "pull", ostree_builtin_pull, 0 },
   { "fsck", ostree_builtin_fsck, 0 },
   { "remote", ostree_builtin_remote, 0 },
   { "rev-parse", ostree_builtin_rev_parse, 0 },
diff --git a/src/ostree-http-backend.c b/src/ostree-http-backend.c
new file mode 100644
index 0000000..ebde526
--- /dev/null
+++ b/src/ostree-http-backend.c
@@ -0,0 +1,45 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
+ *
+ * Copyright (C) 2011 Colin Walters <walters verbum org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; 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 <libsoup/soup-gnome.h>
+#include <ostree.h>
+
+#include <string.h>
+
+int
+main (int     argc,
+      char  **argv)
+{
+  const char *repo_root;
+
+  g_type_init ();
+
+  repo_root = g_getenv ("OSTREE_REPO_PREFIX");
+  if (!repo_root)
+    {
+      g_printerr ("OSTREE_REPO_PREFIX not set\n");
+      return 1;
+    }
+
+  return 0;
+}
diff --git a/src/ot-builtin-fsck.c b/src/ot-builtin-fsck.c
index 766d17f..3a21752 100644
--- a/src/ot-builtin-fsck.c
+++ b/src/ot-builtin-fsck.c
@@ -39,6 +39,82 @@ typedef struct {
   guint n_objects;
 } HtFsckData;
 
+static gboolean
+checksum_packed_file (HtFsckData   *data,
+                      const char   *path,
+                      GChecksum   **out_checksum,
+                      GError      **error)
+{
+  gboolean ret = FALSE;
+  GChecksum *ret_checksum = NULL;
+  GFile *file = NULL;
+  char *metadata_buf = NULL;
+  GVariant *metadata = NULL;
+  GVariant *xattrs = NULL;
+  GFileInputStream *in = NULL;
+  guint32 metadata_len;
+  guint32 version, uid, gid, mode;
+  guint64 content_len;
+  gsize bytes_read;
+  char buf[8192];
+
+  file = ot_util_new_file_for_path (path);
+
+  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;
+      
+  metadata_len = GUINT32_FROM_BE (metadata_len);
+      
+  metadata_buf = g_malloc (metadata_len);
+      
+  if (!g_input_stream_read_all ((GInputStream*)in, metadata_buf, metadata_len, &bytes_read, NULL, error))
+    goto out;
+
+  metadata = g_variant_new_from_data (G_VARIANT_TYPE (OSTREE_PACK_FILE_VARIANT_FORMAT),
+                                      metadata_buf, metadata_len, FALSE, NULL, NULL);
+      
+  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);
+
+  ret_checksum = g_checksum_new (G_CHECKSUM_SHA256);
+
+  do
+    {
+      if (!g_input_stream_read_all ((GInputStream*)in, buf, sizeof(buf), &bytes_read, NULL, error))
+        goto out;
+      g_checksum_update (ret_checksum, (guint8*)buf, bytes_read);
+    }
+  while (bytes_read > 0);
+
+  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;
+  *out_checksum = ret_checksum;
+  ret_checksum = NULL;
+ out:
+  if (ret_checksum)
+    g_checksum_free (ret_checksum);
+  g_free (metadata_buf);
+  g_clear_object (&file);
+  g_clear_object (&in);
+  if (metadata)
+   g_variant_unref (metadata);
+  if (xattrs)
+    g_variant_unref (xattrs);
+  return ret;
+}
+                    
+
 static void
 object_iter_callback (OstreeRepo  *repo,
                       const char    *path,
@@ -53,25 +129,46 @@ object_iter_callback (OstreeRepo  *repo,
   char *checksum_prefix = NULL;
   char *checksum_string = NULL;
   char *filename_checksum = NULL;
+  gboolean packed = FALSE;
+  OstreeObjectType objtype;
   char *dot;
 
-  dirname = g_path_get_dirname (path);
-  checksum_prefix = g_path_get_basename (dirname);
-  
   /* nlinks = g_file_info_get_attribute_uint32 (file_info, "unix::nlink");
      if (nlinks < 2 && !quiet)
      g_printerr ("note: floating object: %s\n", path); */
 
-  if (!ostree_stat_and_checksum_file (-1, path, &checksum, &stbuf, &error))
-    goto out;
+  if (g_str_has_suffix (path, ".meta"))
+    objtype = OSTREE_OBJECT_TYPE_META;
+  else if (g_str_has_suffix (path, ".file"))
+    objtype = OSTREE_OBJECT_TYPE_FILE;
+  else if (g_str_has_suffix (path, ".packfile"))
+    {
+      objtype = OSTREE_OBJECT_TYPE_FILE;
+     packed = TRUE;
+    }
+  else
+    g_assert_not_reached ();
+
+  if (packed && objtype == OSTREE_OBJECT_TYPE_FILE)
+    {
+      if (!checksum_packed_file (data, path, &checksum, &error))
+        goto out;
+    }
+  else
+    {
+      if (!ostree_stat_and_checksum_file (-1, path, objtype, &checksum, &stbuf, &error))
+        goto out;
+    }
 
   filename_checksum = g_strdup (g_file_info_get_name (file_info));
   dot = strrchr (filename_checksum, '.');
   g_assert (dot != NULL);
   *dot = '\0';
-
+  
+  dirname = g_path_get_dirname (path);
+  checksum_prefix = g_path_get_basename (dirname);
   checksum_string = g_strconcat (checksum_prefix, filename_checksum, NULL);
-
+  
   if (strcmp (checksum_string, g_checksum_get_string (checksum)) != 0)
     {
       g_printerr ("ERROR: corrupted object '%s' expected checksum: %s\n",
diff --git a/src/ot-builtin-init.c b/src/ot-builtin-init.c
index ef4530b..d16b57c 100644
--- a/src/ot-builtin-init.c
+++ b/src/ot-builtin-init.c
@@ -27,8 +27,11 @@
 #include <glib/gi18n.h>
 
 static char *repo_path;
+static gboolean archive;
+
 static GOptionEntry options[] = {
   { "repo", 0, 0, G_OPTION_ARG_FILENAME, &repo_path, "Repository path", NULL },
+  { "archive", 0, 0, G_OPTION_ARG_NONE, &archive, "Initialize repository as archive", NULL },
   { NULL }
 };
 
@@ -44,6 +47,7 @@ ostree_builtin_init (int argc, char **argv, const char *prefix, GError **error)
   GFile *repodir = NULL;
   GFile *child = NULL;
   GFile *grandchild = NULL;
+  GString *config_data = NULL;
 
   context = g_option_context_new ("- Initialize a new empty repository");
   g_option_context_add_main_entries (context, options, NULL);
@@ -54,12 +58,15 @@ ostree_builtin_init (int argc, char **argv, const char *prefix, GError **error)
   if (repo_path == NULL)
     repo_path = ".";
 
-  repodir = g_file_new_for_path (repo_path);
+  repodir = ot_util_new_file_for_path (repo_path);
 
   child = g_file_get_child (repodir, "config");
+
+  config_data = g_string_new (DEFAULT_CONFIG_CONTENTS);
+  g_string_append_printf (config_data, "archive=%s\n", archive ? "true" : "false");
   if (!g_file_replace_contents (child,
-                                DEFAULT_CONFIG_CONTENTS,
-                                strlen (DEFAULT_CONFIG_CONTENTS),
+                                config_data->str,
+                                config_data->len,
                                 NULL, FALSE, 0, NULL,
                                 NULL, error))
     goto out;
@@ -73,18 +80,20 @@ ostree_builtin_init (int argc, char **argv, const char *prefix, GError **error)
   child = g_file_get_child (repodir, "refs");
   if (!g_file_make_directory (child, NULL, error))
     goto out;
+
   grandchild = g_file_get_child (child, "heads");
   if (!g_file_make_directory (grandchild, NULL, error))
     goto out;
-  g_clear_object (&child);
   g_clear_object (&grandchild);
 
-  child = g_file_get_child (repodir, "tags");
-  if (!g_file_make_directory (child, NULL, error))
+  grandchild = g_file_get_child (child, "remotes");
+  if (!g_file_make_directory (grandchild, NULL, error))
     goto out;
+  g_clear_object (&grandchild);
+
   g_clear_object (&child);
 
-  child = g_file_get_child (repodir, "remotes");
+  child = g_file_get_child (repodir, "tags");
   if (!g_file_make_directory (child, NULL, error))
     goto out;
   g_clear_object (&child);
@@ -93,6 +102,8 @@ ostree_builtin_init (int argc, char **argv, const char *prefix, GError **error)
  out:
   if (context)
     g_option_context_free (context);
+  if (config_data)
+    g_string_free (config_data, TRUE);
   g_clear_object (&repodir);
   g_clear_object (&child);
   g_clear_object (&grandchild);
diff --git a/src/ot-builtin-pull.c b/src/ot-builtin-pull.c
new file mode 100644
index 0000000..a9ba6e0
--- /dev/null
+++ b/src/ot-builtin-pull.c
@@ -0,0 +1,365 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
+ *
+ * Copyright (C) 2011 Colin Walters <walters verbum org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; 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 "ot-builtins.h"
+#include "ostree.h"
+
+#include <glib/gi18n.h>
+
+#include <libsoup/soup-gnome.h>
+
+static char *repo_path;
+
+static GOptionEntry options[] = {
+  { "repo", 0, 0, G_OPTION_ARG_FILENAME, &repo_path, "Repository path", "repo" },
+  { NULL }
+};
+
+static void
+usage_error (GOptionContext *context, const char *message, GError **error)
+{
+  gchar *help = g_option_context_get_help (context, TRUE, NULL);
+  g_printerr ("%s\n", help);
+  g_free (help);
+  g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+                       message);
+}
+
+static gboolean
+fetch_uri (OstreeRepo  *repo,
+           SoupSession *soup,
+           SoupURI     *uri,
+           char       **temp_filename,
+           GError     **error)
+{
+  gboolean ret = FALSE;
+  SoupMessage *msg = NULL;
+  guint response;
+  char *template = NULL;
+  int fd;
+  SoupBuffer *buf = NULL;
+  GFile *tempf = NULL;
+  
+  msg = soup_message_new_from_uri (SOUP_METHOD_GET, uri);
+  
+  response = soup_session_send_message (soup, msg);
+  if (response != 200)
+    {
+      char *uri_string = soup_uri_to_string (uri, FALSE);
+      g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+                   "Failed to retrieve '%s': %d %s",
+                   uri_string, response, msg->reason_phrase);
+      g_free (uri_string);
+      goto out;
+    }
+
+  template = g_strdup_printf ("%s/tmp-fetchXXXXXX", ostree_repo_get_path (repo));
+  
+  fd = g_mkstemp (template);
+  if (fd < 0)
+    {
+      ot_util_set_error_from_errno (error, errno);
+      goto out;
+    }
+  close (fd);
+  tempf = ot_util_new_file_for_path (template);
+
+  buf = soup_message_body_flatten (msg->response_body);
+
+  if (!g_file_replace_contents (tempf, buf->data, buf->length, NULL, FALSE, 0, NULL, NULL, error))
+    goto out;
+  
+  *temp_filename = template;
+  template = NULL;
+
+  ret = TRUE;
+ out:
+  g_free (template);
+  g_clear_object (&msg);
+  g_clear_object (&tempf);
+  return ret;
+}
+
+static gboolean
+store_object (OstreeRepo  *repo,
+              SoupSession *soup,
+              SoupURI     *baseuri,
+              const char  *object,
+              OstreeObjectType objtype,
+              gboolean    *did_exist,
+              GError     **error)
+{
+  gboolean ret = FALSE;
+  char *filename = NULL;
+  char *objpath = NULL;
+  char *relpath = NULL;
+  SoupURI *obj_uri = NULL;
+
+  objpath = ostree_get_relative_object_path (object, objtype, TRUE);
+  obj_uri = soup_uri_copy (baseuri);
+  relpath = g_build_filename (soup_uri_get_path (obj_uri), objpath, NULL);
+  soup_uri_set_path (obj_uri, relpath);
+
+  if (!fetch_uri (repo, soup, obj_uri, &filename, error))
+    goto out;
+
+  if (!ostree_repo_store_packfile (repo, object, filename, objtype, error))
+    goto out;
+
+  ret = TRUE;
+ out:
+  if (obj_uri)
+    soup_uri_free (obj_uri);
+  if (filename)
+    (void) unlink (filename);
+  g_free (filename);
+  g_free (objpath);
+  g_free (relpath);
+  return ret;
+}
+
+static gboolean
+store_tree_recurse (OstreeRepo   *repo,
+                    SoupSession  *soup,
+                    SoupURI      *base_uri,
+                    const char   *rev,
+                    GError      **error)
+{
+  gboolean ret = FALSE;
+  GVariant *tree = NULL;
+  GVariant *files_variant = NULL;
+  GVariant *dirs_variant = NULL;
+  OstreeSerializedVariantType metatype;
+  gboolean did_exist;
+  int i, n;
+
+  if (!store_object (repo, soup, base_uri, rev, OSTREE_OBJECT_TYPE_META, &did_exist, error))
+    goto out;
+
+  if (!did_exist)
+    {
+      if (!ostree_repo_load_variant (repo, rev, &metatype, &tree, error))
+        goto out;
+      
+      if (metatype != OSTREE_SERIALIZED_TREE_VARIANT)
+        {
+          g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+                       "Tree metadata '%s' has wrong type %d, expected %d",
+                       rev, metatype, OSTREE_SERIALIZED_TREE_VARIANT);
+          goto out;
+        }
+      
+      /* PARSE OSTREE_SERIALIZED_TREE_VARIANT */
+      g_variant_get_child (tree, 2, "@a(ss)", &files_variant);
+      g_variant_get_child (tree, 3, "@a(sss)", &dirs_variant);
+      
+      n = g_variant_n_children (files_variant);
+      for (i = 0; i < n; i++)
+        {
+          const char *filename;
+          const char *checksum;
+
+          g_variant_get_child (files_variant, i, "(ss)", &filename, &checksum);
+
+          if (!store_object (repo, soup, base_uri, checksum, OSTREE_OBJECT_TYPE_FILE, &did_exist, error))
+            goto out;
+        }
+      
+      for (i = 0; i < n; i++)
+        {
+          const char *dirname;
+          const char *tree_checksum;
+          const char *meta_checksum;
+
+          g_variant_get_child (dirs_variant, i, "(sss)",
+                               &dirname, &tree_checksum, &meta_checksum);
+
+          if (!store_tree_recurse (repo, soup, base_uri, tree_checksum, error))
+            goto out;
+
+          if (!store_object (repo, soup, base_uri, meta_checksum, OSTREE_OBJECT_TYPE_META, &did_exist, error))
+            goto out;
+        }
+    }
+
+  ret = TRUE;
+ out:
+  if (tree)
+    g_variant_unref (tree);
+  if (files_variant)
+    g_variant_unref (files_variant);
+  if (dirs_variant)
+    g_variant_unref (dirs_variant);
+  return ret;
+}
+
+static gboolean
+store_commit_recurse (OstreeRepo   *repo,
+                      SoupSession  *soup,
+                      SoupURI      *base_uri,
+                      const char   *rev,
+                      GError      **error)
+{
+  gboolean ret = FALSE;
+  GVariant *commit = NULL;
+  OstreeSerializedVariantType metatype;
+  const char *tree_contents_checksum;
+  const char *tree_meta_checksum;
+  gboolean did_exist;
+
+  if (!store_object (repo, soup, base_uri, rev, OSTREE_OBJECT_TYPE_META, &did_exist, error))
+    goto out;
+
+  if (!did_exist)
+    {
+      if (!ostree_repo_load_variant (repo, rev, &metatype, &commit, error))
+        goto out;
+      
+      if (metatype != OSTREE_SERIALIZED_COMMIT_VARIANT)
+        {
+          g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+                       "Commit '%s' has wrong type %d, expected %d",
+                       rev, metatype, OSTREE_SERIALIZED_COMMIT_VARIANT);
+          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 (!store_object (repo, soup, base_uri, tree_meta_checksum, OSTREE_OBJECT_TYPE_META, &did_exist, error))
+        goto out;
+      
+      if (!store_tree_recurse (repo, soup, base_uri, tree_contents_checksum, error))
+        goto out;
+    }
+
+  ret = TRUE;
+ out:
+  if (commit)
+    g_variant_unref (commit);
+  return ret;
+}
+                      
+gboolean
+ostree_builtin_pull (int argc, char **argv, const char *prefix, GError **error)
+{
+  GOptionContext *context;
+  gboolean ret = FALSE;
+  OstreeRepo *repo = NULL;
+  const char *remote;
+  const char *branch;
+  char *remote_branch_ref_path = NULL;
+  char *key = NULL;
+  char *baseurl = NULL;
+  char *refpath = NULL;
+  char *temppath = NULL;
+  GKeyFile *config = NULL;
+  SoupURI *base_uri = NULL;
+  SoupURI *target_uri = NULL;
+  SoupSession *soup = NULL;
+  char *rev = NULL;
+
+  context = g_option_context_new ("REMOTE BRANCH - Download data from remote repository");
+  g_option_context_add_main_entries (context, options, NULL);
+
+  if (!g_option_context_parse (context, &argc, &argv, error))
+    goto out;
+
+  if (repo_path == NULL)
+    repo_path = ".";
+
+  repo = ostree_repo_new (repo_path);
+  if (!ostree_repo_check (repo, error))
+    goto out;
+
+  if (argc < 3)
+    {
+      usage_error (context, "REMOTE and BRANCH must be specified", error);
+      goto out;
+    }
+
+  remote = argv[1];
+  branch = argv[2];
+
+  config = ostree_repo_get_config (repo);
+
+  key = g_strdup_printf ("remote \"%s\"", remote);
+  baseurl = g_key_file_get_string (config, key, "url", error);
+  if (!baseurl)
+    goto out;
+  base_uri = soup_uri_new (baseurl);
+  if (!base_uri)
+    {
+      g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+                   "Failed to parse url '%s'", baseurl);
+      goto out;
+    }
+  target_uri = soup_uri_copy (base_uri);
+  g_free (refpath);
+  refpath = g_build_filename (soup_uri_get_path (target_uri), "refs", "heads", branch, NULL);
+  soup_uri_set_path (target_uri, refpath);
+  
+  soup = soup_session_sync_new_with_options (SOUP_SESSION_USER_AGENT, "ostree ",
+                                             SOUP_SESSION_ADD_FEATURE_BY_TYPE, SOUP_TYPE_GNOME_FEATURES_2_26,
+                                             SOUP_SESSION_ADD_FEATURE_BY_TYPE, SOUP_TYPE_CONTENT_DECODER,
+                                             SOUP_SESSION_ADD_FEATURE_BY_TYPE, SOUP_TYPE_COOKIE_JAR,
+                                             NULL);
+  if (!fetch_uri (repo, soup, target_uri, &temppath, error))
+    goto out;
+
+  rev = ot_util_get_file_contents_utf8 (temppath, error);
+  if (!rev)
+    goto out;
+  g_strchomp (rev);
+
+  if (!ostree_validate_checksum_string (rev, error))
+    goto out;
+
+  if (!store_commit_recurse (repo, soup, base_uri, rev, error))
+    goto out;
+
+  if (!ostree_repo_write_ref (repo, FALSE, branch, rev, error))
+    goto out;
+ 
+  ret = TRUE;
+ out:
+  if (context)
+    g_option_context_free (context);
+  if (temppath)
+    (void) unlink (temppath);
+  g_free (temppath);
+  g_free (key);
+  g_free (rev);
+  g_free (baseurl);
+  g_free (refpath);
+  g_free (remote_branch_ref_path);
+  g_clear_object (&soup);
+  if (base_uri)
+    soup_uri_free (base_uri);
+  if (target_uri)
+    soup_uri_free (target_uri);
+  g_clear_object (&repo);
+  g_clear_object (&soup);
+  return ret;
+}
diff --git a/src/ot-builtin-remote.c b/src/ot-builtin-remote.c
index f26d683..40f7c29 100644
--- a/src/ot-builtin-remote.c
+++ b/src/ot-builtin-remote.c
@@ -51,9 +51,6 @@ ostree_builtin_remote (int argc, char **argv, const char *prefix, GError **error
   OstreeRepo *repo = NULL;
   OstreeCheckout *checkout = NULL;
   const char *op;
-  gsize len;
-  char *config_path = NULL;
-  char *data = NULL;
   GKeyFile *config = NULL;
 
   context = g_option_context_new ("OPERATION [args] - Control remote repository configuration");
@@ -77,10 +74,7 @@ ostree_builtin_remote (int argc, char **argv, const char *prefix, GError **error
 
   op = argv[1];
 
-  config = g_key_file_new ();
-  config_path = g_build_filename (repo_path, "config", NULL);
-  if (!g_key_file_load_from_file (config, config_path, 0, error))
-    goto out;
+  config = ostree_repo_copy_config (repo);
 
   if (!strcmp (op, "add"))
     {
@@ -92,25 +86,23 @@ ostree_builtin_remote (int argc, char **argv, const char *prefix, GError **error
         }
       key = g_strdup_printf ("remote \"%s\"", argv[2]);
       g_key_file_set_string (config, key, "url", argv[3]);
+      g_free (key);
     }
   else
     {
       usage_error (context, "Unknown operation", error);
       goto out;
     }
- 
-  data = g_key_file_to_data (config, &len, error);
-  if (!g_file_set_contents (config_path, data, len, error))
-    goto out;
 
+  if (!ostree_repo_write_config (repo, config, error))
+    goto out;
+ 
   ret = TRUE;
  out:
   if (context)
     g_option_context_free (context);
   if (config)
     g_key_file_unref (config);
-  g_free (data);
-  g_free (config_path);
   g_clear_object (&repo);
   g_clear_object (&checkout);
   return ret;
diff --git a/src/ot-builtins.h b/src/ot-builtins.h
index 1da3db9..1373f60 100644
--- a/src/ot-builtins.h
+++ b/src/ot-builtins.h
@@ -41,6 +41,7 @@ gboolean ostree_builtin_commit (int argc, char **argv, const char *prefix, GErro
 gboolean ostree_builtin_init (int argc, char **argv, const char *prefix, GError **error);
 gboolean ostree_builtin_log (int argc, char **argv, const char *prefix, GError **error);
 gboolean ostree_builtin_link_file (int argc, char **argv, const char *prefix, GError **error);
+gboolean ostree_builtin_pull (int argc, char **argv, const char *prefix, GError **error);
 gboolean ostree_builtin_run_triggers (int argc, char **argv, const char *prefix, GError **error);
 gboolean ostree_builtin_fsck (int argc, char **argv, const char *prefix, GError **error);
 gboolean ostree_builtin_show (int argc, char **argv, const char *prefix, GError **error);
diff --git a/tests/Makefile b/tests/Makefile
index c24db2c..6beba04 100644
--- a/tests/Makefile
+++ b/tests/Makefile
@@ -20,7 +20,13 @@
 
 TESTS = $(wildcard t[0-9][0-9][0-9][0-9]-*.sh)
 
-all:
+all: tmpdir-lifecycle run-apache
+
+tmpdir-lifecycle: tmpdir-lifecycle.c Makefile
+	gcc $(CFLAGS) `pkg-config --cflags --libs gio-unix-2.0` -o $@ $<
+
+run-apache: run-apache.c Makefile
+	gcc $(CFLAGS) `pkg-config --cflags --libs gio-unix-2.0` -o $@ $<
 
 check:
 	@for test in $(TESTS); do \
diff --git a/tests/libtest.sh b/tests/libtest.sh
index cedf537..9601ec2 100644
--- a/tests/libtest.sh
+++ b/tests/libtest.sh
@@ -18,6 +18,9 @@
 #
 # Author: Colin Walters <walters verbum org>
 
+cd `dirname $0`
+SRCDIR=`pwd`
+cd -
 TMPDIR=${TMPDIR:-/tmp}
 export TMPDIR
 test_tmpdir=`mktemp -d "$TMPDIR/ostree-tests.XXXXXXXXXX"`
@@ -91,4 +94,53 @@ setup_test_repository2 () {
     ostree fsck -q $ot_repo
 }
 
+setup_fake_remote_repo1() {
+    oldpwd=`pwd`
+    mkdir ostree-srv
+    cd ostree-srv
+    mkdir gnomerepo
+    ostree init --archive --repo=gnomerepo
+    mkdir gnomerepo-files
+    cd gnomerepo-files 
+    echo first > firstfile
+    mkdir baz
+    echo moo > baz/cow
+    echo alien > baz/saucer
+    find | grep -v '^\.$' | ostree commit --repo=${test_tmpdir}/ostree-srv/gnomerepo -b main -s "A remote commit" -m "Some Commit body" --from-stdin
+    mkdir baz/deeper
+    ostree commit --repo=${test_tmpdir}/ostree-srv/gnomerepo -b main -s "Add deeper" --add=baz/deeper
+    echo hi > baz/deeper/ohyeah
+    mkdir baz/another/
+    echo x > baz/another/y
+    find | grep -v '^\.$' | ostree commit --repo=${test_tmpdir}/ostree-srv/gnomerepo -b main -s "The rest" --from-stdin
+    cd ..
+    rm -rf gnomerepo-files
+    
+    cd ${test_tmpdir}
+    mkdir ${test_tmpdir}/httpd
+    cd httpd
+    cp $(command -v ostree-http-backend) .
+    chmod a+x ostree-http-backend
+    cat >httpd.conf <<EOF
+ServerRoot ${test_tmpdir}/httpd
+PidFile pid
+LogLevel crit
+ErrorLog log
+LockFile lock
+ServerName localhost
+
+LoadModule alias_module modules/mod_alias.so
+LoadModule cgi_module modules/mod_cgi.so
+LoadModule env_module modules/mod_env.so
+
+StartServers 1
+
+# SetEnv OSTREE_REPO_PREFIX ${test_tmpdir}/ostree-srv
+Alias /ostree/ ${test_tmpdir}/ostree-srv/
+# ScriptAlias /ostree/  ${test_tmpdir}/httpd/ostree-http-backend/
+EOF
+    ${SRCDIR}/tmpdir-lifecycle ${SRCDIR}/run-apache `pwd`/httpd.conf ${test_tmpdir}/httpd-address
+    cd ${oldpwd} 
+}
+
 trap 'die' EXIT
diff --git a/tests/run-apache.c b/tests/run-apache.c
new file mode 100644
index 0000000..b5f7e5c
--- /dev/null
+++ b/tests/run-apache.c
@@ -0,0 +1,167 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
+ *
+ * Copyright (C) 2011 Colin Walters <walters verbum org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; 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 <gio/gio.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/types.h>
+#include <errno.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <sys/stat.h>
+
+/* Taken from gnome-user-share src/httpd.c under the GPLv2 */
+static int
+get_port (void)
+{
+  int sock;
+  int saved_errno;
+  struct sockaddr_in addr;
+  int reuse;
+  socklen_t len;
+
+  sock = socket (PF_INET, SOCK_STREAM, 0);
+  if (sock < 0)
+    {
+      return -1;
+    }
+  
+  memset (&addr, 0, sizeof (addr));
+  addr.sin_family = AF_INET;
+  addr.sin_port = 0;
+  addr.sin_addr.s_addr = htonl (INADDR_LOOPBACK);
+
+  reuse = 1;
+  setsockopt (sock, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof (reuse));
+  if (bind (sock, (struct sockaddr *)&addr, sizeof (addr)) == -1)
+    {
+      saved_errno = errno;
+      close (sock);
+      errno = saved_errno;
+      return -1;
+    }
+
+  len = sizeof (addr);
+  if (getsockname (sock, (struct sockaddr *)&addr, &len) == -1)
+    {
+      saved_errno = errno;
+      close (sock);
+      errno = saved_errno;
+      return -1;
+    }
+
+#if defined(__FreeBSD__) || defined(__NetBSD__) || defined(__APPLE__)
+  /* XXX This exposes a potential race condition, but without this,
+   * httpd will not start on the above listed platforms due to the fact
+   * that SO_REUSEADDR is also needed when Apache binds to the listening
+   * socket.  At this time, Apache does not support that socket option.
+   */
+  close (sock);
+#endif
+  return ntohs (addr.sin_port);
+}
+
+static const char *known_httpd_modules_locations [] = {
+  "/usr/libexec/apache2",
+  "/usr/lib/apache2/modules",
+  "/usr/lib64/httpd/modules",
+  "/usr/lib/httpd/modules",
+  NULL
+};
+
+static gchar*
+get_httpd_modules_path ()
+{
+  int i;
+
+  for (i = 0; known_httpd_modules_locations[i]; i++)
+    {
+      if (g_file_test (known_httpd_modules_locations[i], G_FILE_TEST_IS_EXECUTABLE)
+	  && g_file_test (known_httpd_modules_locations[i], G_FILE_TEST_IS_DIR))
+	{
+	  return g_strdup (known_httpd_modules_locations[i]);
+	}
+    }
+  return NULL;
+}
+
+int
+main (int     argc,
+      char  **argv)
+{
+  int port;
+  char *listen;
+  char *address_string;
+  GError *error = NULL;
+  GPtrArray *httpd_argv;
+  char *modules;
+
+  if (argc != 3)
+    {
+      fprintf (stderr, "usage: run-apache CONF PORTFILE");
+      return 1;
+    }
+
+  g_type_init ();
+
+  port = get_port ();
+  if (port == -1)
+    {
+      perror ("Failed to bind port");
+      return 1;
+    }
+
+  httpd_argv = g_ptr_array_new ();
+  g_ptr_array_add (httpd_argv, "httpd");
+  g_ptr_array_add (httpd_argv, "-f");
+  g_ptr_array_add (httpd_argv, argv[1]);
+  g_ptr_array_add (httpd_argv, "-C");
+  listen = g_strdup_printf ("Listen 127.0.0.1:%d", port);
+  g_ptr_array_add (httpd_argv, listen);
+  g_ptr_array_add (httpd_argv, NULL);
+
+  address_string = g_strdup_printf ("http://127.0.0.1:%d\n";, port);
+  
+  if (!g_file_set_contents (argv[2], address_string, -1, &error))
+    {
+      g_printerr ("%s\n", error->message);
+      return 1;
+    }
+
+  setenv ("LANG", "C", 1);
+  modules = get_httpd_modules_path ();
+  if (modules == NULL)
+    {
+      g_printerr ("Failed to find httpd modules\n");
+      return 1;
+    }
+  if (symlink (modules, "modules") < 0)
+    {
+      perror ("failed to make modules symlink");
+      return 1;
+    }
+  execvp ("httpd", (char**)httpd_argv->pdata);
+  perror ("Failed to run httpd");
+  return 1;
+}
diff --git a/tests/t0012-pull.sh b/tests/t0012-pull.sh
new file mode 100755
index 0000000..fe70782
--- /dev/null
+++ b/tests/t0012-pull.sh
@@ -0,0 +1,34 @@
+#!/bin/bash
+#
+# Copyright (C) 2011 Colin Walters <walters verbum org>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; 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>
+
+set -e
+set -x
+
+. libtest.sh
+
+echo '1..1'
+
+setup_fake_remote_repo1
+cd ${test_tmpdir}
+mkdir repo
+ostree init --repo=repo
+ostree remote --repo=repo add origin $(cat httpd-address)/ostree/gnomerepo
+ostree pull --repo=repo origin main
+echo "ok pull"
diff --git a/tests/t0013-commit-archive.sh b/tests/t0013-commit-archive.sh
new file mode 100755
index 0000000..7f6ffc9
--- /dev/null
+++ b/tests/t0013-commit-archive.sh
@@ -0,0 +1,42 @@
+#!/bin/bash
+#
+# Copyright (C) 2011 Colin Walters <walters verbum org>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; 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>
+
+set -e
+
+. libtest.sh
+
+echo '1..4'
+
+mkdir files
+cd files
+echo first > firstfile
+echo second > secondfile
+ln -s foo bar
+
+mkdir ../repo
+repo="--repo=../repo"
+ostree init --archive $repo
+echo 'ok init'
+ostree commit $repo -b test -s "Test Commit 1" -m "Commit body first" --add=firstfile
+echo 'ok commit 1'
+ostree commit $repo -b test -s "Test Commit 2" -m "Commit body first" --add=secondfile --add=bar
+echo 'ok commit 2'
+ostree fsck -q $repo
+echo 'ok fsck'
diff --git a/tests/ostree-http-server.c b/tests/tmpdir-lifecycle.c
similarity index 52%
rename from tests/ostree-http-server.c
rename to tests/tmpdir-lifecycle.c
index 63edda5..95aae63 100644
--- a/tests/ostree-http-server.c
+++ b/tests/tmpdir-lifecycle.c
@@ -1,5 +1,7 @@
 /* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
  *
+ * Kill a child process when the current directory is deleted
+ *
  * Copyright (C) 2011 Colin Walters <walters verbum org>
  *
  * This program is free software; you can redistribute it and/or modify
@@ -19,35 +21,16 @@
  * Author: Colin Walters <walters verbum org>
  */
 
-#include <libsoup/soup-gnome.h>
-#include <sys/types.h>
+#include <gio/gio.h>
 #include <unistd.h>
 #include <stdlib.h>
+#include <string.h>
 
-static void
-request_callback (SoupServer *server, SoupMessage *msg,
-		  const char *path, GHashTable *query,
-		  SoupClientContext *context, gpointer data)
-{
-  if (msg->method == SOUP_METHOD_GET) 
-    {
-      GFile *file;
-      char *content;
-      gsize len;
-      
-      /* Strip leading / */
-      file = g_file_new_for_path (path + 1);
-      
-      if (g_file_load_contents (file, NULL, &content, &len, NULL, NULL))
-	soup_message_set_response (msg, "application/octet-stream", SOUP_MEMORY_TAKE, content, len);
-      else
-	soup_message_set_status (msg, SOUP_STATUS_NOT_FOUND);
-    }
-  else
-    {
-      soup_message_set_status (msg, SOUP_STATUS_METHOD_NOT_ALLOWED);
-    }
-}
+struct TmpdirLifecyleData {
+  GMainLoop *loop;
+  GPid pid;
+  gboolean exited;
+};
 
 static void
 on_dir_changed (GFileMonitor  *mon,
@@ -56,49 +39,65 @@ on_dir_changed (GFileMonitor  *mon,
 		GFileMonitorEvent  event,
 		gpointer user_data)
 {
-  GMainLoop *loop = user_data;
+  struct TmpdirLifecyleData *data = user_data;
 
   if (event == G_FILE_MONITOR_EVENT_DELETED)
-    g_main_loop_quit (loop);
+    g_main_loop_quit (data->loop);
+}
+
+static void
+on_child_exited (GPid  pid,
+                 int status,
+                 gpointer user_data)
+{
+  struct TmpdirLifecyleData *data = user_data;
+
+  data->exited = TRUE;
+  g_main_loop_quit (data->loop);
 }
 
 int
 main (int     argc,
       char  **argv)
 {
-  SoupAddress *addr;
-  SoupServer *server;
-  GMainLoop *loop;
   GFileMonitor *monitor;
   GFile *curdir;
   GError *error = NULL;
+  GPtrArray *new_argv;
+  int i;
+  GPid pid;
+  struct TmpdirLifecyleData data;
 
   g_type_init ();
 
-  loop = g_main_loop_new (NULL, TRUE);
+  memset (&data, 0, sizeof (data));
+
+  data.loop = g_main_loop_new (NULL, TRUE);
 
   curdir = g_file_new_for_path (".");
   monitor = g_file_monitor_directory (curdir, 0, NULL, &error);
   if (!monitor)
     exit (1);
   g_signal_connect (monitor, "changed",
-		    G_CALLBACK (on_dir_changed), loop);
-
-  addr = soup_address_new ("127.0.0.1", SOUP_ADDRESS_ANY_PORT);
-  soup_address_resolve_sync (addr, NULL);
+		    G_CALLBACK (on_dir_changed), &data);
 
-  server = soup_server_new (SOUP_SERVER_INTERFACE, addr,
-			    SOUP_SERVER_ASYNC_CONTEXT, g_main_loop_get_context (loop),
-			    NULL);
-  soup_server_add_handler (server, NULL,
-			   request_callback,
-			   NULL, NULL);
+  new_argv = g_ptr_array_new ();
+  for (i = 1; i < argc; i++)
+    g_ptr_array_add (new_argv, argv[i]);
+  g_ptr_array_add (new_argv, NULL);
 
-  soup_server_run_async (server);
+  if (!g_spawn_async (NULL, (char**)new_argv->pdata, NULL, G_SPAWN_SEARCH_PATH | G_SPAWN_DO_NOT_REAP_CHILD,
+                      NULL, NULL, &pid, &error))
+    {
+      g_printerr ("%s\n", error->message);
+      return 1;
+    }
+  g_child_watch_add (pid, on_child_exited, &data);
 
-  g_print ("http://127.0.0.1:%ld\n";, (long)soup_server_get_port (server));
+  g_main_loop_run (data.loop);
 
-  g_main_loop_run (loop);
+  if (!data.exited)
+    kill (data.pid, SIGTERM);
 
   return 0;
 }



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