[ostree: 4/4] core: INCOMPATIBLE CHANGE: Split archive files in two parts (meta and content)



commit 9a71ab187d1dcfec28e90f9c36c55cc2c54f4f76
Author: Colin Walters <walters verbum org>
Date:   Thu Dec 15 13:11:47 2011 -0500

    core: INCOMPATIBLE CHANGE: Split archive files in two parts (meta and content)
    
    This will allow us to have hardlink checkouts of archives.  A key use
    case here is an archive repo of an OS (with root-owned files etc.)
    where we want to do builds in a user tree.
    
    A positive side effect of doing things this way is that now the SHA256
    checksums for a given file should be identical regardless of whether
    it's stored in an archive or bare repository.

 src/libostree/ostree-core.c         |  307 ++++-----------
 src/libostree/ostree-core.h         |   66 +---
 src/libostree/ostree-repo-file.c    |   16 +-
 src/libostree/ostree-repo.c         |  719 +++++++++++++++++++++--------------
 src/libostree/ostree-repo.h         |   35 ++-
 src/ostree/ostree-pull.c            |  179 +++++++--
 src/ostree/ot-builtin-fsck.c        |   55 ++-
 src/ostree/ot-builtin-local-clone.c |   78 +++-
 src/ostree/ot-builtin-log.c         |    1 -
 src/ostree/ot-builtin-show.c        |    2 +-
 10 files changed, 793 insertions(+), 665 deletions(-)
---
diff --git a/src/libostree/ostree-core.c b/src/libostree/ostree-core.c
index 887108f..3c9e11d 100644
--- a/src/libostree/ostree-core.c
+++ b/src/libostree/ostree-core.c
@@ -245,13 +245,16 @@ ostree_checksum_file_from_input (GFileInfo        *file_info,
       goto out;
     }
 
-  ostree_checksum_update_stat (ret_checksum,
-                               g_file_info_get_attribute_uint32 (file_info, "unix::uid"),
-                               g_file_info_get_attribute_uint32 (file_info, "unix::gid"),
-                               g_file_info_get_attribute_uint32 (file_info, "unix::mode"));
-  /* FIXME - ensure empty xattrs are the same as NULL xattrs */
-  if (xattrs)
-    g_checksum_update (ret_checksum, (guint8*)g_variant_get_data (xattrs), g_variant_get_size (xattrs));
+  if (objtype != OSTREE_OBJECT_TYPE_ARCHIVED_FILE_CONTENT)
+    {
+      ostree_checksum_update_stat (ret_checksum,
+                                   g_file_info_get_attribute_uint32 (file_info, "unix::uid"),
+                                   g_file_info_get_attribute_uint32 (file_info, "unix::gid"),
+                                   g_file_info_get_attribute_uint32 (file_info, "unix::mode"));
+      /* FIXME - ensure empty xattrs are the same as NULL xattrs */
+      if (xattrs)
+        g_checksum_update (ret_checksum, (guint8*)g_variant_get_data (xattrs), g_variant_get_size (xattrs));
+    }
 
   ret = TRUE;
   ot_transfer_out_value (out_checksum, &ret_checksum);
@@ -291,7 +294,7 @@ ostree_checksum_file (GFile            *f,
         goto out;
     }
 
-  if (!OSTREE_OBJECT_TYPE_IS_META(objtype))
+  if (objtype == OSTREE_OBJECT_TYPE_RAW_FILE)
     {
       xattrs = ostree_get_xattrs_for_file (f, error);
       if (!xattrs)
@@ -444,35 +447,34 @@ ostree_set_xattrs (GFile  *f,
 }
 
 gboolean
-ostree_parse_metadata_file (GFile                       *file,
-                            OstreeObjectType            *out_type,
-                            GVariant                   **out_variant,
-                            GError                     **error)
+ostree_map_metadata_file (GFile                       *file,
+                          OstreeObjectType             expected_type,
+                          GVariant                   **out_variant,
+                          GError                     **error)
 {
   gboolean ret = FALSE;
   GVariant *ret_variant = NULL;
   GVariant *container = NULL;
-  guint32 ret_type;
+  guint32 actual_type;
 
-  if (!ot_util_variant_map (file, G_VARIANT_TYPE (OSTREE_SERIALIZED_VARIANT_FORMAT),
+  if (!ot_util_variant_map (file, 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 (!OSTREE_OBJECT_TYPE_IS_META (ret_type))
+                 &actual_type, &ret_variant);
+  ot_util_variant_take_ref (ret_variant);
+  actual_type = GUINT32_FROM_BE (actual_type);
+  if (actual_type != expected_type)
     {
       g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
-                   "Corrupted metadata object '%s'; invalid type %d",
-                   ot_gfile_get_path_cached (file), ret_type);
+                   "Corrupted metadata object '%s'; found type %u, expected %u",
+                   ot_gfile_get_path_cached (file), 
+                   actual_type, (guint32)expected_type);
       goto out;
     }
-  ot_util_variant_take_ref (ret_variant);
 
   ret = TRUE;
-  if (out_type)
-    *out_type = ret_type;
   ot_transfer_out_value(out_variant, &ret_variant);
  out:
   ot_clear_gvariant (&ret_variant);
@@ -541,201 +543,66 @@ ostree_get_relative_object_path (const char *checksum,
   return g_string_free (path, FALSE);
 }
 
-gboolean
-ostree_archive_file_for_input (GOutputStream     *output,
-                               GFileInfo         *finfo,
-                               GInputStream      *instream,
-                               GVariant          *xattrs,
-                               GChecksum        **out_checksum,
-                               GCancellable      *cancellable,
-                               GError           **error)
+GVariant *
+ostree_create_archive_file_metadata (GFileInfo         *finfo,
+                                     GVariant          *xattrs)
 {
-  gboolean ret = FALSE;
-  guint32 uid, gid, mode;
-  guint32 device = 0;
-  guint32 metadata_size_be;
-  const char *target = NULL;
-  guint64 object_size;
-  gboolean pack_builder_initialized = FALSE;
+  guint32 uid, gid, mode, rdev;
   GVariantBuilder pack_builder;
-  GVariant *pack_variant = NULL;
-  gsize bytes_written;
-  GChecksum *ret_checksum = NULL;
+  GVariant *ret = NULL;
 
   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);
+  rdev = g_file_info_get_attribute_uint32 (finfo, G_FILE_ATTRIBUTE_UNIX_RDEV);
 
-  g_variant_builder_init (&pack_builder, G_VARIANT_TYPE (OSTREE_ARCHIVED_FILE_VARIANT_FORMAT));
-  pack_builder_initialized = TRUE;
-  g_variant_builder_add (&pack_builder, "u", GUINT32_TO_BE (0));
+  g_variant_builder_init (&pack_builder, OSTREE_ARCHIVED_FILE_VARIANT_FORMAT);
+  g_variant_builder_add (&pack_builder, "u", GUINT32_TO_BE (0));   /* Version */ 
   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));
-
-  g_variant_builder_add (&pack_builder, "@a(ayay)", xattrs ? xattrs : g_variant_new_array (G_VARIANT_TYPE ("(ayay)"), NULL, 0));
-
-  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_RDEV);
-      object_size = 4;
-    }
-  else if (S_ISFIFO (mode))
-    {
-      object_size = 0;
-    }
-  else
-    {
-      g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
-                   "Invalid mode %u", mode);
-      goto out;
-    }
-
-  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))
-    {
-      if (!ot_gio_splice_and_checksum (output, (GInputStream*)instream,
-                                       out_checksum ? &ret_checksum : NULL,
-                                       cancellable, error))
-        goto out;
-    }
-  else if (S_ISLNK (mode))
-    {
-      if (out_checksum)
-        {
-          ret_checksum = g_checksum_new (G_CHECKSUM_SHA256);
-          g_checksum_update (ret_checksum, (guint8*)target, object_size);
-        }
-      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 (out_checksum)
-        {
-          ret_checksum = g_checksum_new (G_CHECKSUM_SHA256);
-          g_checksum_update (ret_checksum, (guint8*)&device_be, 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))
-    {
-      if (out_checksum)
-        ret_checksum = g_checksum_new (G_CHECKSUM_SHA256);
-    }
+  g_variant_builder_add (&pack_builder, "u", GUINT32_TO_BE (rdev));
+  if (S_ISLNK (mode))
+    g_variant_builder_add (&pack_builder, "s", g_file_info_get_symlink_target (finfo));
   else
-    g_assert_not_reached ();
+    g_variant_builder_add (&pack_builder, "s", "");
 
-  if (ret_checksum)
-    {
-      ostree_checksum_update_stat (ret_checksum, uid, gid, mode);
-      if (xattrs)
-        g_checksum_update (ret_checksum, (guint8*)g_variant_get_data (xattrs), g_variant_get_size (xattrs));
-    }
+  g_variant_builder_add (&pack_builder, "@a(ayay)", xattrs ? xattrs : g_variant_new_array (G_VARIANT_TYPE ("(ayay)"), NULL, 0));
 
-  ret = TRUE;
-  ot_transfer_out_value(out_checksum, &ret_checksum);
- out:
-  if (pack_builder_initialized)
-    g_variant_builder_clear (&pack_builder);
-  ot_clear_gvariant (&pack_variant);
-  ot_clear_checksum (&ret_checksum);
+  ret = g_variant_builder_end (&pack_builder);
+  g_variant_ref_sink (ret);
+  
   return ret;
 }
 
 gboolean
-ostree_parse_archived_file (GFile            *file,
-                            GFileInfo       **out_file_info,
-                            GVariant        **out_xattrs,
-                            GInputStream    **out_content,
-                            GCancellable     *cancellable,
-                            GError          **error)
+ostree_parse_archived_file_meta (GVariant         *metadata,
+                                 GFileInfo       **out_file_info,
+                                 GVariant        **out_xattrs,
+                                 GError          **error)
 {
   gboolean ret = FALSE;
-  char *metadata_buf = NULL;
-  GVariant *metadata = NULL;
   GFileInfo *ret_file_info = NULL;
   GVariant *ret_xattrs = NULL;
-  GInputStream *in = NULL;
-  guint32 metadata_len;
-  guint32 version, uid, gid, mode;
-  guint64 content_len;
-  gsize bytes_read;
+  guint32 version, uid, gid, mode, rdev;
+  const char *symlink_target;
 
-  in = (GInputStream*)g_file_read (file, NULL, error);
-  if (!in)
-    goto out;
-      
-  if (!g_input_stream_read_all (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 archive file; 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 archive file; metadata length %u is larger than maximum %u",
-                   metadata_len, OSTREE_MAX_METADATA_SIZE);
-      goto out;
-    }
-  metadata_buf = g_malloc (metadata_len);
+  g_variant_get (metadata, "(uuuuu&s a(ayay))",
+                 &version, &uid, &gid, &mode, &rdev,
+                 &symlink_target, &ret_xattrs);
+  version = GUINT32_FROM_BE (version);
 
-  if (!g_input_stream_read_all (in, metadata_buf, metadata_len, &bytes_read, NULL, error))
-    goto out;
-  if (bytes_read != metadata_len)
+  if (version != 0)
     {
       g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
-                   "Corrupted archive file; too short while reading metadata");
+                   "Invalid version %d in archived file metadata", version);
       goto out;
     }
 
-  metadata = g_variant_new_from_data (G_VARIANT_TYPE (OSTREE_ARCHIVED_FILE_VARIANT_FORMAT),
-                                      metadata_buf, metadata_len, FALSE,
-                                      (GDestroyNotify)g_free,
-                                      metadata_buf);
-  metadata_buf = NULL;
-
-  g_variant_get (metadata, "(uuuu a(ayay)t)",
-                 &version, &uid, &gid, &mode,
-                 &ret_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);
+  rdev = GUINT32_FROM_BE (rdev);
 
   ret_file_info = g_file_info_new ();
   g_file_info_set_attribute_uint32 (ret_file_info, "standard::type", ot_gfile_type_for_mode (mode));
@@ -743,46 +610,22 @@ ostree_parse_archived_file (GFile            *file,
   g_file_info_set_attribute_uint32 (ret_file_info, "unix::uid", uid);
   g_file_info_set_attribute_uint32 (ret_file_info, "unix::gid", gid);
   g_file_info_set_attribute_uint32 (ret_file_info, "unix::mode", mode);
-  g_file_info_set_attribute_uint64 (ret_file_info, "standard::size", content_len);
 
   if (S_ISREG (mode))
     {
-      g_file_info_set_attribute_uint64 (ret_file_info, "standard::size", content_len);
+      ;
     }
   else if (S_ISLNK (mode))
     {
-      char target[PATH_MAX+1];
-      if (!g_input_stream_read_all (in, target, sizeof(target)-1, &bytes_read, cancellable, error))
-        goto out;
-      target[bytes_read] = '\0';
-
-      g_file_info_set_attribute_boolean (ret_file_info, "standard::is-symlink", TRUE);
-      g_file_info_set_attribute_byte_string (ret_file_info, "standard::symlink-target", target);
-
-      g_input_stream_close (in, cancellable, error);
-      g_clear_object (&in);
+      g_file_info_set_attribute_byte_string (ret_file_info, "standard::symlink-target", symlink_target);
     }
   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 archive file; too short while reading device id");
-          goto out;
-        }
-      dev = GUINT32_FROM_BE (dev);
-      g_file_info_set_attribute_uint32 (ret_file_info, "unix::rdev", dev);
-      g_input_stream_close (in, cancellable, error);
-      g_clear_object (&in);
+      g_file_info_set_attribute_uint32 (ret_file_info, "unix::rdev", rdev);
     }
   else if (S_ISFIFO (mode))
     {
-      g_input_stream_close (in, cancellable, error);
-      g_clear_object (&in);
+      ;
     }
   else
     {
@@ -794,12 +637,9 @@ ostree_parse_archived_file (GFile            *file,
   ret = TRUE;
   ot_transfer_out_value(out_file_info, &ret_file_info);
   ot_transfer_out_value(out_xattrs, &ret_xattrs);
-  ot_transfer_out_value(out_content, &in);
  out:
   g_clear_object (&ret_file_info);
   ot_clear_gvariant (&ret_xattrs);
-  g_clear_object (&in);
-  ot_clear_gvariant (&metadata);
   return ret;
 }
 
@@ -818,7 +658,11 @@ ostree_create_file_from_input (GFile            *dest_file,
   GFileOutputStream *out = NULL;
   guint32 uid, gid, mode;
   GChecksum *ret_checksum = NULL;
-  gboolean is_meta = OSTREE_OBJECT_TYPE_IS_META (objtype);
+  gboolean is_meta;
+  gboolean is_archived_content;
+
+  is_meta = OSTREE_OBJECT_TYPE_IS_META (objtype);
+  is_archived_content = objtype == OSTREE_OBJECT_TYPE_ARCHIVED_FILE_CONTENT;
 
   if (g_cancellable_set_error_if_cancelled (cancellable, error))
     return FALSE;
@@ -829,14 +673,13 @@ ostree_create_file_from_input (GFile            *dest_file,
     }
   else
     {
-      mode = S_IFREG | 0666;
+      mode = S_IFREG | 0664;
     }
   dest_path = ot_gfile_get_path_cached (dest_file);
 
   if (S_ISDIR (mode))
     {
-      if (mkdir (ot_gfile_get_path_cached (dest_file),
-                 g_file_info_get_attribute_uint32 (finfo, "unix::mode")) < 0)
+      if (mkdir (ot_gfile_get_path_cached (dest_file), mode) < 0)
         {
           ot_util_set_error_from_errno (error, errno);
           goto out;
@@ -907,7 +750,7 @@ ostree_create_file_from_input (GFile            *dest_file,
       goto out;
     }
 
-  if (finfo != NULL)
+  if (finfo != NULL && !is_meta && !is_archived_content)
     {
       uid = g_file_info_get_attribute_uint32 (finfo, "unix::uid");
       gid = g_file_info_get_attribute_uint32 (finfo, "unix::gid");
@@ -918,11 +761,6 @@ ostree_create_file_from_input (GFile            *dest_file,
           goto out;
         }
     }
-  else
-    {
-      uid = geteuid ();
-      gid = getegid ();
-    }
 
   if (!S_ISLNK (mode))
     {
@@ -940,9 +778,14 @@ ostree_create_file_from_input (GFile            *dest_file,
         goto out;
     }
 
-  if (ret_checksum && !is_meta)
+  if (ret_checksum && !is_meta && !is_archived_content)
     {
-      ostree_checksum_update_stat (ret_checksum, uid, gid, mode);
+      g_assert (finfo != NULL);
+
+      ostree_checksum_update_stat (ret_checksum,
+                                   g_file_info_get_attribute_uint32 (finfo, "unix::uid"),
+                                   g_file_info_get_attribute_uint32 (finfo, "unix::gid"),
+                                   mode);
       if (xattrs)
         g_checksum_update (ret_checksum, (guint8*)g_variant_get_data (xattrs), g_variant_get_size (xattrs));
     }
@@ -965,14 +808,14 @@ create_tmp_string (const char *dirpath,
   GString *tmp_name = NULL;
 
   if (!prefix)
-    prefix = "tmp-";
+    prefix = "tmp";
   if (!suffix)
-    suffix = ".tmp";
+    suffix = "tmp";
 
   tmp_name = g_string_new (dirpath);
   g_string_append_c (tmp_name, '/');
   g_string_append (tmp_name, prefix);
-  g_string_append (tmp_name, "XXXXXX");
+  g_string_append (tmp_name, "-XXXXXXXXXXXX.");
   g_string_append (tmp_name, suffix);
 
   return tmp_name;
diff --git a/src/libostree/ostree-core.h b/src/libostree/ostree-core.h
index 88513b2..0772ca9 100644
--- a/src/libostree/ostree-core.h
+++ b/src/libostree/ostree-core.h
@@ -33,16 +33,17 @@ G_BEGIN_DECLS
 
 typedef enum {
   OSTREE_OBJECT_TYPE_RAW_FILE = 1,   /* .raw */
-  OSTREE_OBJECT_TYPE_ARCHIVED_FILE = 2,  /* .archive */
-  OSTREE_OBJECT_TYPE_DIR_TREE = 3,  /* .dirtree */
-  OSTREE_OBJECT_TYPE_DIR_META = 4,  /* .dirmeta */
-  OSTREE_OBJECT_TYPE_COMMIT = 5     /* .commit */
+  OSTREE_OBJECT_TYPE_ARCHIVED_FILE_CONTENT = 2,  /* .archive-content */
+  OSTREE_OBJECT_TYPE_ARCHIVED_FILE_META = 3,  /* .archive-meta */
+  OSTREE_OBJECT_TYPE_DIR_TREE = 4,  /* .dirtree */
+  OSTREE_OBJECT_TYPE_DIR_META = 5,  /* .dirmeta */
+  OSTREE_OBJECT_TYPE_COMMIT = 6     /* .commit */
 } OstreeObjectType;
 
-#define OSTREE_OBJECT_TYPE_IS_META(t) (t >= 3 && t <= 5)
+#define OSTREE_OBJECT_TYPE_IS_META(t) (t >= 3 && t <= 6)
 #define OSTREE_OBJECT_TYPE_LAST OSTREE_OBJECT_TYPE_COMMIT
 
-#define OSTREE_SERIALIZED_VARIANT_FORMAT "(uv)"
+#define OSTREE_SERIALIZED_VARIANT_FORMAT G_VARIANT_TYPE("(uv)")
 
 /*
  * xattr objects:
@@ -116,10 +117,10 @@ GVariant *ostree_wrap_metadata_variant (OstreeObjectType type, GVariant *metadat
 gboolean ostree_set_xattrs (GFile *f, GVariant *xattrs,
                             GCancellable *cancellable, GError **error);
 
-gboolean ostree_parse_metadata_file (GFile                       *file,
-                                     OstreeObjectType            *out_type,
-                                     GVariant                   **out_variant,
-                                     GError                     **error);
+gboolean ostree_map_metadata_file (GFile                       *file,
+                                   OstreeObjectType             expected_type,
+                                   GVariant                   **out_variant,
+                                   GError                     **error);
 
 gboolean ostree_checksum_file_from_input (GFileInfo        *file_info,
                                           GVariant         *xattrs,
@@ -150,32 +151,6 @@ gboolean ostree_checksum_file_async_finish (GFile          *f,
 GVariant *ostree_create_directory_metadata (GFileInfo *dir_info,
                                             GVariant  *xattrs);
 
-/* Archived 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_ARCHIVED_FILE_VARIANT_FORMAT "(uuuua(ayay)t)"
-
-gboolean  ostree_archive_file_for_input (GOutputStream     *output,
-                                         GFileInfo         *finfo,
-                                         GInputStream      *input,
-                                         GVariant          *xattrs,
-                                         GChecksum        **out_checksum,
-                                         GCancellable     *cancellable,
-                                         GError          **error);
-
 gboolean ostree_create_file_from_input (GFile          *file,
                                         GFileInfo      *finfo,
                                         GVariant       *xattrs,
@@ -205,18 +180,13 @@ gboolean ostree_create_temp_regular_file (GFile            *dir,
                                           GCancellable     *cancellable,
                                           GError          **error);
 
-gboolean ostree_parse_archived_file (GFile            *file,
-                                     GFileInfo       **out_file_info,
-                                     GVariant        **out_xattrs,
-                                     GInputStream    **out_content,
-                                     GCancellable     *cancellable,
-                                     GError          **error);
-
-gboolean ostree_unpack_object (GFile             *file,
-                               OstreeObjectType  objtype,
-                               GFile             *dest,    
-                               GChecksum   **out_checksum,
-                               GError      **error);
+GVariant *ostree_create_archive_file_metadata (GFileInfo   *file_info,
+                                               GVariant    *xattrs);
+
+gboolean ostree_parse_archived_file_meta (GVariant         *data,
+                                          GFileInfo       **out_file_info,
+                                          GVariant        **out_xattrs,
+                                          GError          **error);
 
 
 #endif /* _OSTREE_REPO */
diff --git a/src/libostree/ostree-repo-file.c b/src/libostree/ostree-repo-file.c
index 7d11c11..55f6a98 100644
--- a/src/libostree/ostree-repo-file.c
+++ b/src/libostree/ostree-repo-file.c
@@ -307,6 +307,7 @@ _ostree_repo_file_get_xattrs (OstreeRepoFile  *self,
 {
   gboolean ret = FALSE;
   GVariant *ret_xattrs = NULL;
+  GVariant *metadata = NULL;
   GFile *local_file = NULL;
 
   if (!_ostree_repo_file_ensure_resolved (self, error))
@@ -317,7 +318,12 @@ _ostree_repo_file_get_xattrs (OstreeRepoFile  *self,
   else if (ostree_repo_get_mode (self->repo) == OSTREE_REPO_MODE_ARCHIVE)
     {
       local_file = _ostree_repo_file_nontree_get_local (self);
-      if (!ostree_parse_archived_file (local_file, NULL, &ret_xattrs, NULL, cancellable, error))
+      
+      if (!ostree_map_metadata_file (local_file, OSTREE_OBJECT_TYPE_ARCHIVED_FILE_META,
+                                     &metadata, error))
+        goto out;
+
+      if (!ostree_parse_archived_file_meta (metadata, NULL, &ret_xattrs, error))
         goto out;
     }
   else
@@ -330,6 +336,7 @@ _ostree_repo_file_get_xattrs (OstreeRepoFile  *self,
   ot_transfer_out_value(out_xattrs, &ret_xattrs);
  out:
   ot_clear_gvariant (&ret_xattrs);
+  ot_clear_gvariant (&metadata);
   g_clear_object (&local_file);
   return ret;
 }
@@ -1002,6 +1009,7 @@ _ostree_repo_file_tree_query_child (OstreeRepoFile  *self,
   const char *name = NULL;
   gboolean ret = FALSE;
   GFileInfo *ret_info = NULL;
+  GVariant *archive_metadata = NULL;
   GVariant *files_variant = NULL;
   GVariant *dirs_variant = NULL;
   GVariant *tree_child_metadata = NULL;
@@ -1030,7 +1038,10 @@ _ostree_repo_file_tree_query_child (OstreeRepoFile  *self,
 
       if (ostree_repo_get_mode (self->repo) == OSTREE_REPO_MODE_ARCHIVE)
 	{
-          if (!ostree_parse_archived_file (local_child, &ret_info, NULL, NULL, cancellable, error))
+          if (!ostree_map_metadata_file (local_child, OSTREE_OBJECT_TYPE_ARCHIVED_FILE_META,
+                                         &archive_metadata, error))
+            goto out;
+          if (!ostree_parse_archived_file_meta (archive_metadata, &ret_info, NULL, error))
             goto out;
 	}
       else
@@ -1086,6 +1097,7 @@ _ostree_repo_file_tree_query_child (OstreeRepoFile  *self,
   g_clear_object (&local_child);
   if (matcher)
     g_file_attribute_matcher_unref (matcher);
+  ot_clear_gvariant (&archive_metadata);
   ot_clear_gvariant (&tree_child_metadata);
   ot_clear_gvariant (&files_variant);
   ot_clear_gvariant (&dirs_variant);
diff --git a/src/libostree/ostree-repo.c b/src/libostree/ostree-repo.c
index 6a8ead8..c658f60 100644
--- a/src/libostree/ostree-repo.c
+++ b/src/libostree/ostree-repo.c
@@ -60,9 +60,12 @@ struct _OstreeRepoPrivate {
   char *config_path;
 
   gboolean inited;
+  gboolean in_transaction;
 
   GKeyFile *config;
   OstreeRepoMode mode;
+
+  GHashTable *pending_transaction_tmpfiles;
 };
 
 static void
@@ -78,6 +81,7 @@ ostree_repo_finalize (GObject *object)
   g_clear_object (&priv->remote_heads_dir);
   g_free (priv->objects_path);
   g_free (priv->config_path);
+  g_hash_table_destroy (priv->pending_transaction_tmpfiles);
   if (priv->config)
     g_key_file_free (priv->config);
 
@@ -175,6 +179,11 @@ ostree_repo_class_init (OstreeRepoClass *klass)
 static void
 ostree_repo_init (OstreeRepo *self)
 {
+  OstreeRepoPrivate *priv = GET_PRIVATE (self);
+  
+  priv->pending_transaction_tmpfiles = g_hash_table_new_full (g_str_hash, g_str_equal,
+                                                              g_free,
+                                                              g_free);
 }
 
 OstreeRepo*
@@ -647,7 +656,7 @@ get_objtype_for_repo_file (OstreeRepo *self)
 {
   OstreeRepoPrivate *priv = GET_PRIVATE (self);
   if (priv->mode == OSTREE_REPO_MODE_ARCHIVE)
-    return OSTREE_OBJECT_TYPE_ARCHIVED_FILE;
+    return OSTREE_OBJECT_TYPE_ARCHIVED_FILE_META;
   else
     return OSTREE_OBJECT_TYPE_RAW_FILE;
 }
@@ -659,64 +668,260 @@ ostree_repo_get_file_object_path (OstreeRepo   *self,
   return ostree_repo_get_object_path (self, checksum, get_objtype_for_repo_file (self));
 }
 
+static char *
+create_checksum_and_objtype (const char *checksum,
+                             OstreeObjectType objtype)
+{
+  return g_strconcat (checksum, ".", ostree_object_type_to_string (objtype), NULL);
+}
+
+gboolean      
+ostree_repo_has_object (OstreeRepo           *self,
+                        OstreeObjectType      objtype,
+                        const char           *checksum,
+                        gboolean             *out_have_object,
+                        GCancellable         *cancellable,
+                        GError             **error)
+{
+  gboolean ret = FALSE;
+  OstreeRepoPrivate *priv = GET_PRIVATE (self);
+  char *tmp_key = NULL;
+  GFile *object_path = NULL;
+
+  tmp_key = create_checksum_and_objtype (checksum, objtype);
+
+  if (g_hash_table_lookup (priv->pending_transaction_tmpfiles, tmp_key))
+    {
+      *out_have_object = TRUE;
+    }
+  else
+    {
+      object_path = ostree_repo_get_object_path (self, checksum, objtype);
+      
+      *out_have_object = g_file_query_exists (object_path, cancellable);
+    }
+  
+  ret = TRUE;
+  /* out: */
+  g_free (tmp_key);
+  g_clear_object (&object_path);
+  return ret;
+}
+
+static GFileInfo *
+dup_file_info_owned_by_me (GFileInfo  *file_info)
+{
+  GFileInfo *ret = g_file_info_dup (file_info);
+
+  g_file_info_set_attribute_uint32 (ret, "unix::uid", geteuid ());
+  g_file_info_set_attribute_uint32 (ret, "unix::gid", getegid ());
+
+  return ret;
+}
+
 static gboolean
-ostree_repo_stage_object (OstreeRepo         *self,
-                          OstreeObjectType    objtype,
-                          GFileInfo          *file_info,
-                          GVariant           *xattrs,
-                          GInputStream       *input,
-                          GFile             **out_tmpname,
-                          GChecksum         **out_checksum,
-                          GCancellable       *cancellable,
-                          GError            **error)
+stage_object (OstreeRepo         *self,
+              OstreeObjectType    objtype,
+              GFileInfo          *file_info,
+              GVariant           *xattrs,
+              GInputStream       *input,
+              const char         *expected_checksum,
+              GChecksum         **out_checksum,
+              GCancellable       *cancellable,
+              GError            **error);
+
+static gboolean
+impl_stage_archive_file_object_from_raw (OstreeRepo         *self,
+                                         GFileInfo          *file_info,
+                                         GVariant           *xattrs,
+                                         GInputStream       *input,
+                                         const char         *expected_checksum,
+                                         GChecksum         **out_checksum,
+                                         GCancellable       *cancellable,
+                                         GError            **error)
 {
   gboolean ret = FALSE;
   OstreeRepoPrivate *priv = GET_PRIVATE (self);
-  GOutputStream *temp_out = NULL;
   GChecksum *ret_checksum = NULL;
+  GVariant *archive_metadata = NULL;
+  GFileInfo *temp_info = NULL;
+  GFile *meta_temp_file = NULL;
+  GFile *content_temp_file = NULL;
+  GVariant *serialized = NULL;
+  GInputStream *mem = NULL;
+  const char *actual_checksum;
+  
+  archive_metadata = ostree_create_archive_file_metadata (file_info, xattrs);
+  
+  serialized = ostree_wrap_metadata_variant (OSTREE_OBJECT_TYPE_ARCHIVED_FILE_META, archive_metadata);
+  mem = g_memory_input_stream_new_from_data (g_variant_get_data (serialized),
+                                             g_variant_get_size (serialized),
+                                             NULL);
+
+  if (!ostree_create_temp_file_from_input (priv->tmp_dir,
+                                           "archive-tmp-", NULL,
+                                           NULL, NULL, mem,
+                                           OSTREE_OBJECT_TYPE_ARCHIVED_FILE_META,
+                                           &meta_temp_file,
+                                           NULL,
+                                           cancellable, error))
+    goto out;
+
+  temp_info = dup_file_info_owned_by_me (file_info);
+  if (!ostree_create_temp_file_from_input (priv->tmp_dir,
+                                           "archive-tmp-", NULL,
+                                           temp_info, NULL, input,
+                                           OSTREE_OBJECT_TYPE_ARCHIVED_FILE_CONTENT,
+                                           &content_temp_file,
+                                           out_checksum ? &ret_checksum : NULL,
+                                           cancellable, error))
+    goto out;
+
+  if (ret_checksum)
+    {
+      ostree_checksum_update_stat (ret_checksum,
+                                   g_file_info_get_attribute_uint32 (file_info, "unix::uid"),
+                                   g_file_info_get_attribute_uint32 (file_info, "unix::gid"),
+                                   g_file_info_get_attribute_uint32 (file_info, "unix::mode"));
+      /* FIXME - ensure empty xattrs are the same as NULL xattrs */
+      if (xattrs)
+        g_checksum_update (ret_checksum, (guint8*)g_variant_get_data (xattrs), g_variant_get_size (xattrs));
+    }
+
+  if (expected_checksum)
+    {
+      if (strcmp (g_checksum_get_string (ret_checksum), expected_checksum) != 0)
+        {
+          g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+                       "Corrupted %s object %s (actual checksum is %s)",
+                       ostree_object_type_to_string (OSTREE_OBJECT_TYPE_RAW_FILE),
+                       expected_checksum, g_checksum_get_string (ret_checksum));
+          goto out;
+        }
+      actual_checksum = expected_checksum;
+    }
+  else
+    actual_checksum = g_checksum_get_string (ret_checksum);
+
+  g_hash_table_insert (priv->pending_transaction_tmpfiles,
+                       create_checksum_and_objtype (actual_checksum, OSTREE_OBJECT_TYPE_ARCHIVED_FILE_CONTENT),
+                       g_strdup (ot_gfile_get_basename_cached (content_temp_file)));
+  g_hash_table_insert (priv->pending_transaction_tmpfiles,
+                       create_checksum_and_objtype (actual_checksum, OSTREE_OBJECT_TYPE_ARCHIVED_FILE_META),
+                       g_strdup (ot_gfile_get_basename_cached (meta_temp_file)));
+
+  ret = TRUE;
+  ot_transfer_out_value (out_checksum, &ret_checksum);
+ out:
+  ot_clear_gvariant (&serialized);
+  ot_clear_gvariant (&archive_metadata);
+  g_clear_object (&mem);
+  g_clear_object (&temp_info);
+  g_clear_object (&meta_temp_file);
+  g_clear_object (&content_temp_file);
+  ot_clear_checksum (&ret_checksum);
+  return ret;
+}
+
+static gboolean
+stage_object (OstreeRepo         *self,
+              OstreeObjectType    objtype,
+              GFileInfo          *file_info,
+              GVariant           *xattrs,
+              GInputStream       *input,
+              const char         *expected_checksum,
+              GChecksum         **out_checksum,
+              GCancellable       *cancellable,
+              GError            **error)
+{
+  gboolean ret = FALSE;
+  OstreeRepoPrivate *priv = GET_PRIVATE (self);
+  GChecksum *ret_checksum = NULL;
+  GFileInfo *temp_info = NULL;
   GFile *temp_file = NULL;
-  GFile *ret_tmpname = NULL;
+  gboolean already_exists;
+  const char *actual_checksum;
 
+  g_return_val_if_fail (priv->in_transaction, FALSE);
+  
   if (g_cancellable_set_error_if_cancelled (cancellable, error))
     return FALSE;
 
-  if (!OSTREE_OBJECT_TYPE_IS_META(objtype) && priv->mode == OSTREE_REPO_MODE_ARCHIVE)
-    {
-      if (!ostree_create_temp_regular_file (priv->tmp_dir,
-                                            "archive-tmp-", NULL,
-                                            &temp_file, &temp_out,
-                                            cancellable, error))
-        goto out;
-      
-      if (!ostree_archive_file_for_input (temp_out, file_info, input, xattrs,
-                                          &ret_checksum, cancellable, error))
-        goto out;
+  g_assert (expected_checksum || out_checksum);
 
-      if (!g_output_stream_close (temp_out, cancellable, error))
+  if (expected_checksum)
+    {
+      if (!ostree_repo_has_object (self, objtype, expected_checksum, &already_exists, cancellable, error))
         goto out;
     }
   else
+    already_exists = FALSE;
+
+  g_assert (objtype != OSTREE_OBJECT_TYPE_ARCHIVED_FILE_CONTENT);
+  g_assert (objtype != OSTREE_OBJECT_TYPE_ARCHIVED_FILE_META);
+
+  if (objtype == OSTREE_OBJECT_TYPE_RAW_FILE)
     {
-      if (!ostree_create_temp_file_from_input (priv->tmp_dir,
-                                               "store-tmp-", NULL,
-                                               file_info, xattrs, input,
-                                               objtype,
-                                               &temp_file, &ret_checksum,
-                                               cancellable, error))
-        goto out;
+      g_assert (file_info != NULL);
+      if (g_file_info_get_file_type (file_info) == G_FILE_TYPE_REGULAR)
+        g_assert (input != NULL);
+    }
+  else if (OSTREE_OBJECT_TYPE_IS_META (objtype))
+    {
+      g_assert (xattrs == NULL);
+      g_assert (input != NULL);
     }
 
-  ret_tmpname = g_object_ref (temp_file);
-  g_clear_object (&temp_file);
+  if (!already_exists)
+    {
+       if (objtype == OSTREE_OBJECT_TYPE_RAW_FILE && priv->mode == OSTREE_REPO_MODE_ARCHIVE)
+        {
+          if (!impl_stage_archive_file_object_from_raw (self, file_info, xattrs, input,
+                                                        expected_checksum,
+                                                        out_checksum ? &ret_checksum : NULL,
+                                                        cancellable, error))
+            goto out;
+        }
+      else 
+        {
+          if (!ostree_create_temp_file_from_input (priv->tmp_dir,
+                                                   "store-tmp-", NULL,
+                                                   file_info, xattrs, input,
+                                                   objtype,
+                                                   &temp_file,
+                                                   out_checksum ? &ret_checksum : NULL,
+                                                   cancellable, error))
+            goto out;
+      
+          if (!ret_checksum)
+            actual_checksum = expected_checksum;
+          else
+            {
+              actual_checksum = g_checksum_get_string (ret_checksum);
+              if (expected_checksum && strcmp (actual_checksum, expected_checksum) != 0)
+                {
+                  g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+                               "Corrupted %s object %s (actual checksum is %s)",
+                               ostree_object_type_to_string (objtype),
+                               expected_checksum, actual_checksum);
+                  goto out;
+                }
+            }
+          
+          g_hash_table_insert (priv->pending_transaction_tmpfiles,
+                               create_checksum_and_objtype (actual_checksum, objtype),
+                               g_strdup (ot_gfile_get_basename_cached (temp_file)));
+          g_clear_object (&temp_file);
+        }
+    }
   
   ret = TRUE;
   ot_transfer_out_value(out_checksum, &ret_checksum);
-  ot_transfer_out_value(out_tmpname, &ret_tmpname);
  out:
   if (temp_file)
     (void) unlink (ot_gfile_get_path_cached (temp_file));
   g_clear_object (&temp_file);
-  g_clear_object (&temp_out);
+  g_clear_object (&temp_info);
   ot_clear_checksum (&ret_checksum);
   return ret;
 }
@@ -726,7 +931,6 @@ commit_staged_file (OstreeRepo         *self,
                     GFile              *file,
                     const char         *checksum,
                     OstreeObjectType    objtype,
-                    gboolean           *did_exist,
                     GCancellable       *cancellable,
                     GError            **error)
 {
@@ -740,12 +944,9 @@ commit_staged_file (OstreeRepo         *self,
   if (!ot_gfile_ensure_directory (checksum_dir, FALSE, error))
     goto out;
   
-  *did_exist = FALSE;
   if (link (ot_gfile_get_path_cached (file), ot_gfile_get_path_cached (dest_file)) < 0)
     {
-      if (errno == EEXIST)
-        *did_exist = TRUE;
-      else
+      if (errno != EEXIST)
         {
           ot_util_set_error_from_errno (error, errno);
           g_prefix_error (error, "Storing file '%s': ",
@@ -762,114 +963,73 @@ commit_staged_file (OstreeRepo         *self,
   return ret;
 }
 
-static gboolean
-stage_and_commit_from_input (OstreeRepo        *self,
-                             OstreeObjectType   objtype,
-                             GFileInfo         *file_info,
-                             GVariant          *xattrs,
-                             GInputStream      *input,
-                             GChecksum        **out_checksum,
-                             GCancellable      *cancellable,
-                             GError           **error)
+gboolean
+ostree_repo_prepare_transaction (OstreeRepo     *self,
+                                 GCancellable   *cancellable,
+                                 GError        **error)
 {
-  gboolean ret = FALSE;
-  GFile *tmp_file = NULL;
-  GChecksum *ret_checksum = NULL;
-  gboolean did_exist;
-
-  if (!ostree_repo_stage_object (self, objtype,
-                                 file_info, xattrs, input,
-                                 &tmp_file, &ret_checksum,
-                                 cancellable, error))
-    goto out;
+  OstreeRepoPrivate *priv = GET_PRIVATE (self);
+  
+  g_return_val_if_fail (priv->in_transaction == FALSE, FALSE);
 
-  if (!commit_staged_file (self, tmp_file, g_checksum_get_string (ret_checksum),
-                           objtype, &did_exist, cancellable, error))
-    goto out;
-  g_clear_object (&tmp_file);
+  priv->in_transaction = TRUE;
 
-  ret = TRUE;
-  ot_transfer_out_value(out_checksum, &ret_checksum);
- out:
-  if (tmp_file)
-    (void) unlink (ot_gfile_get_path_cached (tmp_file));
-  g_clear_object (&tmp_file);
-  ot_clear_checksum (&ret_checksum);
-  return ret;
+  return TRUE;
 }
 
-static gboolean
-commit_file (OstreeRepo      *self,
-             GFile           *file,
-             const char      *expected_checksum,
-             GChecksum      **out_checksum,
-             GCancellable    *cancellable,
-             GError         **error)
+gboolean      
+ostree_repo_commit_transaction (OstreeRepo     *self,
+                                GCancellable   *cancellable,
+                                GError        **error)
 {
   gboolean ret = FALSE;
-  GFileInfo *file_info = NULL;
-  GVariant *xattrs = NULL;
-  GInputStream *input = NULL;
-  GFile *tmp_file = NULL;
-  GChecksum *ret_checksum = NULL;
-  gboolean did_exist;
-  OstreeObjectType objtype;
-
-  g_assert (expected_checksum || out_checksum);
+  OstreeRepoPrivate *priv = GET_PRIVATE (self);
+  GFile *f = NULL;
+  GHashTableIter iter;
+  gpointer key, value;
+  char *checksum = NULL;
 
-  file_info = g_file_query_info (file, OSTREE_GIO_FAST_QUERYINFO,
-                                 G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
-                                 cancellable, error);
-  if (!file_info)
-    goto out;
+  g_return_val_if_fail (priv->in_transaction == TRUE, FALSE);
 
-  xattrs = ostree_get_xattrs_for_file (file, error);
-  if (!xattrs)
-    goto out;
+  priv->in_transaction = FALSE;
 
-  if (g_file_info_get_file_type (file_info) == G_FILE_TYPE_REGULAR)
+  g_hash_table_iter_init (&iter, priv->pending_transaction_tmpfiles);
+  while (g_hash_table_iter_next (&iter, &key, &value))
     {
-      input = (GInputStream*)g_file_read (file, cancellable, error);
-      if (!input)
-        goto out;
-    }
-
-  objtype = get_objtype_for_repo_file (self);
+      const char *checksum_and_type = key;
+      const char *filename = value;
+      const char *type_str;
+      OstreeObjectType objtype;
 
-  if (!ostree_repo_stage_object (self, objtype,
-                                 file_info, xattrs, input,
-                                 &tmp_file, out_checksum ? &ret_checksum : NULL,
-                                 cancellable, error))
-    goto out;
+      type_str = strrchr (checksum_and_type, '.');
+      g_assert (type_str);
+      g_free (checksum);
+      checksum = g_strndup (checksum_and_type, type_str - checksum_and_type);
 
-  if (expected_checksum == NULL)
-    expected_checksum = g_checksum_get_string (ret_checksum);
+      objtype = ostree_object_type_from_string (type_str + 1);
 
-  if (!commit_staged_file (self, tmp_file, expected_checksum, objtype, 
-                           &did_exist, cancellable, error))
-    goto out;
-  g_clear_object (&tmp_file);
+      g_clear_object (&f);
+      f = g_file_get_child (priv->tmp_dir, filename);
+      
+      if (!commit_staged_file (self, f, checksum, objtype, cancellable, error))
+        goto out;
+    }
 
   ret = TRUE;
-  ot_transfer_out_value(out_checksum, &ret_checksum);
  out:
-  if (tmp_file)
-    (void) unlink (ot_gfile_get_path_cached (tmp_file));
-  g_clear_object (&tmp_file);
-  g_clear_object (&file_info);
-  ot_clear_gvariant (&xattrs);
-  g_clear_object (&input);
-  ot_clear_checksum (&ret_checksum);
+  g_free (checksum);
+  g_hash_table_remove_all (priv->pending_transaction_tmpfiles);
+  g_clear_object (&f);
   return ret;
 }
 
 static gboolean
-import_gvariant_object (OstreeRepo         *self,
-                        OstreeObjectType    type,
-                        GVariant           *variant,
-                        GChecksum         **out_checksum,
-                        GCancellable       *cancellable,
-                        GError            **error)
+stage_gvariant_object (OstreeRepo         *self,
+                       OstreeObjectType    type,
+                       GVariant           *variant,
+                       GChecksum         **out_checksum,
+                       GCancellable       *cancellable,
+                       GError            **error)
 {
   gboolean ret = FALSE;
   GChecksum *ret_checksum = NULL;
@@ -881,10 +1041,9 @@ import_gvariant_object (OstreeRepo         *self,
                                              g_variant_get_size (serialized),
                                              NULL);
   
-  if (!stage_and_commit_from_input (self, type,
-                                    NULL, NULL, mem,
-                                    &ret_checksum,
-                                    cancellable, error))
+  if (!stage_object (self, type,
+                     NULL, NULL, mem,
+                     NULL, &ret_checksum, cancellable, error))
     goto out;
 
   ret = TRUE;
@@ -904,40 +1063,46 @@ ostree_repo_load_variant (OstreeRepo  *self,
                           GError       **error)
 {
   gboolean ret = FALSE;
-  OstreeObjectType type;
+  OstreeRepoPrivate *priv = GET_PRIVATE (self);
   GFile *object_path = NULL;
+  GFile *tmpfile = NULL;
   GVariant *ret_variant = NULL;
+  char *pending_key = NULL;
+  const char *pending_tmpfile;
 
   g_return_val_if_fail (OSTREE_OBJECT_TYPE_IS_META (expected_type), FALSE);
 
-  object_path = ostree_repo_get_object_path (self, sha256, expected_type);
-
-  if (!ostree_parse_metadata_file (object_path, &type, &ret_variant, error))
-    goto out;
-
-  if (type != expected_type)
+  pending_key = create_checksum_and_objtype (sha256, expected_type);
+  if ((pending_tmpfile = g_hash_table_lookup (priv->pending_transaction_tmpfiles, pending_key)) != NULL)
     {
-      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;
+      tmpfile = g_file_get_child (priv->tmp_dir, pending_tmpfile);
+      if (!ostree_map_metadata_file (tmpfile, expected_type, &ret_variant, error))
+        goto out;
+    }
+  else
+    {
+      object_path = ostree_repo_get_object_path (self, sha256, expected_type);
+      if (!ostree_map_metadata_file (object_path, expected_type, &ret_variant, error))
+        goto out;
     }
 
   ret = TRUE;
-  ot_transfer_out_value(out_variant, &ret_variant);
+  ot_transfer_out_value (out_variant, &ret_variant);
  out:
   g_clear_object (&object_path);
+  g_clear_object (&tmpfile);
+  g_free (pending_key);
   ot_clear_gvariant (&ret_variant);
   return ret;
 }
 
 static gboolean
-import_directory_meta (OstreeRepo   *self,
-                       GFileInfo    *file_info,
-                       GVariant     *xattrs,
-                       GChecksum   **out_checksum,
-                       GCancellable *cancellable,
-                       GError      **error)
+stage_directory_meta (OstreeRepo   *self,
+                      GFileInfo    *file_info,
+                      GVariant     *xattrs,
+                      GChecksum   **out_checksum,
+                      GCancellable *cancellable,
+                      GError      **error)
 {
   gboolean ret = FALSE;
   GChecksum *ret_checksum = NULL;
@@ -948,8 +1113,8 @@ import_directory_meta (OstreeRepo   *self,
 
   dirmeta = ostree_create_directory_metadata (file_info, xattrs);
   
-  if (!import_gvariant_object (self, OSTREE_OBJECT_TYPE_DIR_META, 
-                               dirmeta, &ret_checksum, cancellable, error))
+  if (!stage_gvariant_object (self, OSTREE_OBJECT_TYPE_DIR_META, 
+                              dirmeta, &ret_checksum, cancellable, error))
     goto out;
 
   ret = TRUE;
@@ -981,107 +1146,40 @@ ostree_repo_get_object_path (OstreeRepo  *self,
 
 gboolean      
 ostree_repo_store_object_trusted (OstreeRepo   *self,
-                                  GFile        *file,
-                                  const char   *checksum,
                                   OstreeObjectType objtype,
+                                  const char   *checksum,
+                                  GFileInfo        *file_info,
+                                  GVariant         *xattrs,
+                                  GInputStream     *input,
                                   GCancellable *cancellable,
                                   GError      **error)
 {
-  gboolean ret = FALSE;
-  GFile *local_object = NULL;
-  GInputStream *input = NULL;
-
-  local_object = ostree_repo_get_object_path (self, checksum, objtype);
-
-  if (!g_file_query_exists (local_object, cancellable))
-    { 
-      if (!OSTREE_OBJECT_TYPE_IS_META (objtype))
-        {
-          if (!commit_file (self, file, checksum, NULL, cancellable, error))
-            goto out;
-        }
-      else
-        {
-          input = (GInputStream*)g_file_read (file, cancellable, error);
-          if (!input)
-            goto out;
-
-          if (!stage_and_commit_from_input (self, objtype,
-                                            NULL, NULL, input,
-                                            NULL, cancellable, error))
-            goto out;
-        }
-    }
-
-  ret = TRUE;
- out:
-  g_clear_object (&input);
-  g_clear_object (&local_object);
-  return ret;
+  return stage_object (self, objtype,
+                       file_info, xattrs, input,
+                       checksum, NULL, cancellable, error);
 }
 
 gboolean
-ostree_repo_store_archived_file (OstreeRepo       *self,
-                                 const char       *expected_checksum,
-                                 const char       *path,
-                                 OstreeObjectType  objtype,
-                                 gboolean         *did_exist,
-                                 GError          **error)
+ostree_repo_store_object (OstreeRepo       *self,
+                          OstreeObjectType  objtype,
+                          const char       *expected_checksum,
+                          GFileInfo        *file_info,
+                          GVariant         *xattrs,
+                          GInputStream     *input,
+                          GCancellable     *cancellable,
+                          GError          **error)
 {
   gboolean ret = FALSE;
-  GChecksum *checksum = NULL;
-  GFileInfo *file_info = NULL;
-  GFile *tmp_file = NULL;
-  GFile *src = NULL;
-  GInputStream *input = NULL;
-  GVariant *xattrs = NULL;
-  OstreeObjectType dest_objtype;
-
-  src = ot_gfile_new_for_path (path);
-
-  if (OSTREE_OBJECT_TYPE_IS_META(objtype))
-    {
-      dest_objtype = objtype;
-      input = (GInputStream*)g_file_read (src, NULL, error);
-      if (!input)
-        goto out;
-    }
-  else
-    {
-      /* Ensure we convert archived files to bare, or vice versa */
-      dest_objtype = get_objtype_for_repo_file (self);
-      if (!ostree_parse_archived_file (src, &file_info, &xattrs, &input, NULL, error))
-        goto out;
-    }
-
-  if (!ostree_repo_stage_object (self, dest_objtype,
-                                 file_info, xattrs, input,
-                                 &tmp_file, &checksum, NULL, 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 (!commit_staged_file (self, tmp_file, g_checksum_get_string (checksum),
-                           dest_objtype, did_exist, NULL, error))
+  GChecksum *actual_checksum = NULL;
+  
+  if (!stage_object (self, objtype,
+                     file_info, xattrs, input,
+                     expected_checksum, &actual_checksum, cancellable, error))
     goto out;
-  g_clear_object (&tmp_file);
 
   ret = TRUE;
  out:
-  if (tmp_file)
-    (void) unlink (ot_gfile_get_path_cached (tmp_file));
-  g_clear_object (&tmp_file);
-  g_clear_object (&src);
-  g_clear_object (&file_info);
-  ot_clear_gvariant (&xattrs);
-  g_clear_object (&input);
-  ot_clear_checksum (&checksum);
+  ot_clear_checksum (&actual_checksum);
   return ret;
 }
 
@@ -1124,16 +1222,17 @@ ostree_repo_write_ref (OstreeRepo  *self,
 }
 
 static gboolean
-import_commit (OstreeRepo *self,
-               const char   *branch,
-               const char   *parent,
-               const char   *subject,
-               const char   *body,
-               GVariant     *metadata,
-               const char   *root_contents_checksum,
-               const char   *root_metadata_checksum,
-               GChecksum   **out_commit,
-               GError      **error)
+do_commit_write_ref (OstreeRepo *self,
+                     const char   *branch,
+                     const char   *parent,
+                     const char   *subject,
+                     const char   *body,
+                     GVariant     *metadata,
+                     const char   *root_contents_checksum,
+                     const char   *root_metadata_checksum,
+                     GChecksum   **out_commit,
+                     GCancellable *cancellable,
+                     GError      **error)
 {
   gboolean ret = FALSE;
   GChecksum *ret_commit = NULL;
@@ -1153,8 +1252,11 @@ import_commit (OstreeRepo *self,
                           root_contents_checksum,
                           root_metadata_checksum);
   g_variant_ref_sink (commit);
-  if (!import_gvariant_object (self, OSTREE_OBJECT_TYPE_COMMIT,
-                               commit, &ret_commit, NULL, error))
+  if (!stage_gvariant_object (self, OSTREE_OBJECT_TYPE_COMMIT,
+                              commit, &ret_commit, NULL, error))
+    goto out;
+
+  if (!ostree_repo_commit_transaction (self, cancellable, error))
     goto out;
 
   if (!ostree_repo_write_ref (self, NULL, branch, g_checksum_get_string (ret_commit), error))
@@ -1240,13 +1342,13 @@ create_tree_variant_from_hashes (GHashTable            *file_checksums,
 }
 
 static gboolean
-import_directory_recurse (OstreeRepo           *self,
-                          GFile                *base,
-                          GFile                *dir,
-                          GChecksum           **out_contents_checksum,
-                          GChecksum           **out_metadata_checksum,
-                          GCancellable         *cancellable,
-                          GError              **error)
+stage_directory_recurse (OstreeRepo           *self,
+                         GFile                *base,
+                         GFile                *dir,
+                         GChecksum           **out_contents_checksum,
+                         GChecksum           **out_metadata_checksum,
+                         GCancellable         *cancellable,
+                         GError              **error)
 {
   gboolean ret = FALSE;
   GError *temp_error = NULL;
@@ -1273,7 +1375,8 @@ import_directory_recurse (OstreeRepo           *self,
   if (!xattrs)
     goto out;
 
-  if (!import_directory_meta (self, child_info, xattrs, &ret_metadata_checksum, cancellable, error))
+  if (!stage_directory_meta (self, child_info, xattrs, &ret_metadata_checksum,
+                             cancellable, error))
     goto out;
   
   g_clear_object (&child_info);
@@ -1304,8 +1407,8 @@ import_directory_recurse (OstreeRepo           *self,
           GChecksum *child_dir_metadata_checksum = NULL;
           GChecksum *child_dir_contents_checksum = NULL;
 
-          if (!import_directory_recurse (self, base, child, &child_dir_contents_checksum,
-                                         &child_dir_metadata_checksum, cancellable, error))
+          if (!stage_directory_recurse (self, base, child, &child_dir_contents_checksum,
+                                        &child_dir_metadata_checksum, cancellable, error))
             goto out;
 
           g_hash_table_replace (dir_contents_checksums, g_strdup (name),
@@ -1332,9 +1435,9 @@ import_directory_recurse (OstreeRepo           *self,
           if (!xattrs)
             goto out;
 
-          if (!stage_and_commit_from_input (self, get_objtype_for_repo_file (self),
-                                            child_info, xattrs, file_input,
-                                            &child_file_checksum, cancellable, error))
+          if (!stage_object (self, OSTREE_OBJECT_TYPE_RAW_FILE,
+                             child_info, xattrs, file_input, NULL,
+                             &child_file_checksum, cancellable, error))
             goto out;
 
           g_hash_table_replace (file_checksums, g_strdup (name),
@@ -1353,9 +1456,9 @@ import_directory_recurse (OstreeRepo           *self,
                                                      dir_contents_checksums,
                                                      dir_metadata_checksums);
 
-  if (!import_gvariant_object (self, OSTREE_OBJECT_TYPE_DIR_TREE,
-                               serialized_tree, &ret_contents_checksum,
-                               cancellable, error))
+  if (!stage_gvariant_object (self, OSTREE_OBJECT_TYPE_DIR_TREE,
+                              serialized_tree, &ret_contents_checksum,
+                              cancellable, error))
     goto out;
 
   ot_transfer_out_value(out_metadata_checksum, &ret_metadata_checksum);
@@ -1405,19 +1508,22 @@ ostree_repo_commit_directory (OstreeRepo *self,
   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 (!ostree_repo_prepare_transaction (self, cancellable, error))
+    goto out;
+
   if (parent == NULL)
     parent = branch;
 
   if (!ostree_repo_resolve_rev (self, parent, TRUE, &current_head, error))
     goto out;
 
-  if (!import_directory_recurse (self, dir, dir, &root_contents_checksum, &root_metadata_checksum, cancellable, error))
+  if (!stage_directory_recurse (self, dir, dir, &root_contents_checksum, &root_metadata_checksum, cancellable, error))
     goto out;
 
-  if (!import_commit (self, branch, current_head, subject, body, metadata,
-                      g_checksum_get_string (root_contents_checksum),
-                      g_checksum_get_string (root_metadata_checksum),
-                      &ret_commit_checksum, error))
+  if (!do_commit_write_ref (self, branch, current_head, subject, body, metadata,
+                            g_checksum_get_string (root_contents_checksum),
+                            g_checksum_get_string (root_metadata_checksum),
+                            &ret_commit_checksum, cancellable, error))
     goto out;
   
   ret = TRUE;
@@ -1491,9 +1597,10 @@ import_libarchive_entry_file (OstreeRepo           *self,
   if (g_file_info_get_file_type (file_info) == G_FILE_TYPE_REGULAR)
     archive_stream = ostree_libarchive_input_stream_new (a);
   
-  if (!stage_and_commit_from_input (self, get_objtype_for_repo_file (self),
-                                    file_info, NULL, archive_stream,
-                                    &ret_checksum, cancellable, error))
+  if (!stage_object (self, OSTREE_OBJECT_TYPE_RAW_FILE,
+                     file_info, NULL, archive_stream,
+                     NULL, &ret_checksum,
+                     cancellable, error))
     goto out;
 
   ret = TRUE;
@@ -1611,9 +1718,9 @@ file_tree_import_recurse (OstreeRepo           *self,
                                                      dir_contents_checksums,
                                                      dir_metadata_checksums);
       
-  if (!import_gvariant_object (self, OSTREE_OBJECT_TYPE_DIR_TREE,
-                               serialized_tree, &ret_contents_checksum_obj,
-                               cancellable, error))
+  if (!stage_gvariant_object (self, OSTREE_OBJECT_TYPE_DIR_TREE,
+                              serialized_tree, &ret_contents_checksum_obj,
+                              cancellable, error))
     goto out;
   ret_contents_checksum = g_strdup (g_checksum_get_string (ret_contents_checksum_obj));
 
@@ -1772,7 +1879,7 @@ import_libarchive (OstreeRepo           *self,
             {
             }
 
-          if (!import_directory_meta (self, file_info, NULL, &tmp_checksum, cancellable, error))
+          if (!stage_directory_meta (self, file_info, NULL, &tmp_checksum, cancellable, error))
             goto out;
 
           if (parent == NULL)
@@ -1879,6 +1986,9 @@ ostree_repo_commit_tarfile (OstreeRepo *self,
   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 (!ostree_repo_prepare_transaction (self, cancellable, error))
+    goto out;
+
   if (parent == NULL)
     parent = branch;
 
@@ -1888,8 +1998,9 @@ ostree_repo_commit_tarfile (OstreeRepo *self,
   if (!import_libarchive (self, path, &root_contents_checksum, &root_metadata_checksum, cancellable, error))
     goto out;
 
-  if (!import_commit (self, branch, current_head, subject, body, metadata,
-                      root_contents_checksum, root_metadata_checksum, &ret_commit_checksum, error))
+  if (!do_commit_write_ref (self, branch, current_head, subject, body, metadata,
+                            root_contents_checksum, root_metadata_checksum, &ret_commit_checksum,
+                            cancellable, error))
     goto out;
   
   ret = TRUE;
@@ -1947,8 +2058,10 @@ iter_object_dir (OstreeRepo             *self,
       
       if (g_str_has_suffix (name, ".file"))
         objtype = OSTREE_OBJECT_TYPE_RAW_FILE;
-      else if (g_str_has_suffix (name, ".archive"))
-        objtype = OSTREE_OBJECT_TYPE_ARCHIVED_FILE;
+      else if (g_str_has_suffix (name, ".archive-meta"))
+        objtype = OSTREE_OBJECT_TYPE_ARCHIVED_FILE_META;
+      else if (g_str_has_suffix (name, ".archive-content"))
+        objtype = OSTREE_OBJECT_TYPE_ARCHIVED_FILE_CONTENT;
       else if (g_str_has_suffix (name, ".dirtree"))
         objtype = OSTREE_OBJECT_TYPE_DIR_TREE;
       else if (g_str_has_suffix (name, ".dirmeta"))
@@ -2098,13 +2211,15 @@ checkout_tree (OstreeRepo               *self,
   OstreeRepoPrivate *priv = GET_PRIVATE (self);
   gboolean ret = FALSE;
   GError *temp_error = NULL;
+  GVariant *archive_metadata = NULL;
   GFileInfo *file_info = NULL;
-  GInputStream *packed_input = NULL;
   GVariant *xattrs = NULL;
   GFileEnumerator *dir_enum = NULL;
   GFile *src_child = NULL;
   GFile *dest_path = NULL;
   GFile *object_path = NULL;
+  GFile *content_object_path = NULL;
+  GInputStream *content_input = NULL;
 
   if (!_ostree_repo_file_get_xattrs (source, &xattrs, NULL, error))
     goto out;
@@ -2131,7 +2246,10 @@ checkout_tree (OstreeRepo               *self,
       name = g_file_info_get_attribute_byte_string (file_info, "standard::name"); 
       type = g_file_info_get_attribute_uint32 (file_info, "standard::type");
 
+      g_clear_object (&dest_path);
       dest_path = g_file_get_child (destination, name);
+
+      g_clear_object (&src_child);
       src_child = g_file_get_child ((GFile*)source, name);
 
       if (type == G_FILE_TYPE_DIRECTORY)
@@ -2143,20 +2261,36 @@ checkout_tree (OstreeRepo               *self,
         {
           const char *checksum = _ostree_repo_file_get_checksum ((OstreeRepoFile*)src_child);
 
-          object_path = ostree_repo_get_object_path (self, checksum, get_objtype_for_repo_file (self));
+          g_clear_object (&object_path);
 
           if (priv->mode == OSTREE_REPO_MODE_ARCHIVE)
             {
-              if (!ostree_parse_archived_file (object_path, NULL, &xattrs, &packed_input,
-                                               cancellable, error))
+              if (!ostree_repo_load_variant (self, OSTREE_OBJECT_TYPE_ARCHIVED_FILE_META, checksum, &archive_metadata, error))
                 goto out;
+              
+              ot_clear_gvariant (&xattrs);
+              if (!ostree_parse_archived_file_meta (archive_metadata, NULL, &xattrs, error))
+                goto out;
+              
+              g_clear_object (&content_object_path);
+              content_object_path = ostree_repo_get_object_path (self, checksum, OSTREE_OBJECT_TYPE_ARCHIVED_FILE_CONTENT);
+
+              g_clear_object (&content_input);
+              if (g_file_info_get_file_type (file_info) == G_FILE_TYPE_REGULAR)
+                {
+                  content_input = (GInputStream*)g_file_read (content_object_path, cancellable, error);
+                  if (!content_input)
+                    goto out;
+                }
 
               if (!checkout_file_from_input (dest_path, mode, file_info, xattrs, 
-                                             packed_input, cancellable, error))
+                                             content_input, cancellable, error))
                 goto out;
             }
           else
             {
+              object_path = ostree_repo_get_object_path (self, checksum, OSTREE_OBJECT_TYPE_RAW_FILE);
+
               if (link (ot_gfile_get_path_cached (object_path), ot_gfile_get_path_cached (dest_path)) < 0)
                 {
                   ot_util_set_error_from_errno (error, errno);
@@ -2165,12 +2299,7 @@ checkout_tree (OstreeRepo               *self,
             }
         }
 
-      g_clear_object (&object_path);
-      g_clear_object (&dest_path);
       g_clear_object (&file_info);
-      g_clear_object (&src_child);
-      g_clear_object (&packed_input);
-      ot_clear_gvariant (&xattrs);
     }
   if (file_info == NULL && temp_error != NULL)
     {
@@ -2182,10 +2311,12 @@ checkout_tree (OstreeRepo               *self,
  out:
   g_clear_object (&dir_enum);
   g_clear_object (&file_info);
-  g_clear_object (&packed_input);
   ot_clear_gvariant (&xattrs);
+  ot_clear_gvariant (&archive_metadata);
   g_clear_object (&src_child);
   g_clear_object (&object_path);
+  g_clear_object (&content_object_path);
+  g_clear_object (&content_input);
   g_clear_object (&dest_path);
   g_free (dest_path);
   return ret;
diff --git a/src/libostree/ostree-repo.h b/src/libostree/ostree-repo.h
index d01134b..03f687b 100644
--- a/src/libostree/ostree-repo.h
+++ b/src/libostree/ostree-repo.h
@@ -79,17 +79,36 @@ GFile *       ostree_repo_get_object_path (OstreeRepo   *self,
 GFile *       ostree_repo_get_file_object_path (OstreeRepo   *self,
                                                 const char   *object);
 
-gboolean      ostree_repo_store_archived_file (OstreeRepo       *self,
-                                               const char       *expected_checksum,
-                                               const char       *path,
-                                               OstreeObjectType  objtype,
-                                               gboolean         *did_exist,
-                                               GError          **error);
+gboolean      ostree_repo_prepare_transaction (OstreeRepo     *self,
+                                               GCancellable   *cancellable,
+                                               GError        **error);
+
+gboolean      ostree_repo_commit_transaction (OstreeRepo     *self,
+                                              GCancellable   *cancellable,
+                                              GError        **error);
+
+gboolean      ostree_repo_has_object (OstreeRepo           *self,
+                                      OstreeObjectType      objtype,
+                                      const char           *checksum,
+                                      gboolean             *out_have_object,
+                                      GCancellable         *cancellable,
+                                      GError             **error);
+
+gboolean      ostree_repo_store_object (OstreeRepo       *self,
+                                        OstreeObjectType  objtype,
+                                        const char       *expected_checksum,
+                                        GFileInfo        *file_info,
+                                        GVariant         *xattrs,
+                                        GInputStream     *content,
+                                        GCancellable     *cancellable,
+                                        GError          **error);
 
 gboolean      ostree_repo_store_object_trusted (OstreeRepo   *self,
-                                                GFile        *file,
-                                                const char   *checksum,
                                                 OstreeObjectType objtype,
+                                                const char   *checksum,
+                                                GFileInfo        *file_info,
+                                                GVariant         *xattrs,
+                                                GInputStream     *content,
                                                 GCancellable *cancellable,
                                                 GError      **error);
 
diff --git a/src/ostree/ostree-pull.c b/src/ostree/ostree-pull.c
index 5ca9d13..3c7a87e 100644
--- a/src/ostree/ostree-pull.c
+++ b/src/ostree/ostree-pull.c
@@ -58,17 +58,18 @@ static gboolean
 fetch_uri (OstreeRepo  *repo,
            SoupSession *soup,
            SoupURI     *uri,
-           char       **temp_filename,
+           const char  *tmp_prefix,
+           GFile      **out_temp_filename,
            GError     **error)
 {
   gboolean ret = FALSE;
   SoupMessage *msg = NULL;
   guint response;
-  char *template = NULL;
-  int fd;
   SoupBuffer *buf = NULL;
-  GFile *tempf = NULL;
   char *uri_string = NULL;
+  GFile *ret_temp_filename = NULL;
+  GOutputStream *output_stream = NULL;
+  gsize bytes_written;
   
   uri_string = soup_uri_to_string (uri, FALSE);
   log_verbose ("Fetching %s", uri_string);
@@ -83,75 +84,123 @@ fetch_uri (OstreeRepo  *repo,
       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_gfile_new_for_path (template);
+  if (!ostree_create_temp_regular_file (ostree_repo_get_tmpdir (repo),
+                                        tmp_prefix, NULL,
+                                        &ret_temp_filename,
+                                        &output_stream,
+                                        NULL, error))
+    goto out;
 
   buf = soup_message_body_flatten (msg->response_body);
 
-  if (!g_file_replace_contents (tempf, buf->data, buf->length, NULL, FALSE, 0, NULL, NULL, error))
+  if (!g_output_stream_write_all (output_stream, buf->data, buf->length,
+                                  &bytes_written, NULL, error))
     goto out;
-  
-  *temp_filename = template;
-  template = NULL;
 
+  if (!g_output_stream_close (output_stream, NULL, error))
+    goto out;
+  
   ret = TRUE;
+  ot_transfer_out_value (out_temp_filename, &ret_temp_filename);
  out:
+  if (ret_temp_filename)
+    (void) unlink (ot_gfile_get_path_cached (ret_temp_filename));
+  g_clear_object (&ret_temp_filename);
   g_free (uri_string);
-  g_free (template);
   g_clear_object (&msg);
-  g_clear_object (&tempf);
   return ret;
 }
 
 static gboolean
-store_object (OstreeRepo  *repo,
+fetch_object (OstreeRepo  *repo,
               SoupSession *soup,
               SoupURI     *baseuri,
-              const char  *object,
+              const char  *checksum,
               OstreeObjectType objtype,
               gboolean    *did_exist,
+              GFile      **out_file,
               GError     **error)
 {
   gboolean ret = FALSE;
-  char *filename = NULL;
+  GFile *ret_file = NULL;
   char *objpath = NULL;
   char *relpath = NULL;
   SoupURI *obj_uri = NULL;
+  gboolean exists;
 
   g_assert (objtype != OSTREE_OBJECT_TYPE_RAW_FILE);
 
-  objpath = ostree_get_relative_object_path (object, objtype);
-  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))
+  if (!ostree_repo_has_object (repo, objtype, checksum, &exists, NULL, error))
     goto out;
 
-  if (!ostree_repo_store_archived_file (repo, object, filename, objtype, did_exist, error))
-    goto out;
+  if (!exists)
+    {
+      objpath = ostree_get_relative_object_path (checksum, objtype);
+      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, ostree_object_type_to_string (objtype), &ret_file, error))
+        goto out;
+
+      *did_exist = FALSE;
+    }
+  else
+    *did_exist = TRUE;
 
   ret = TRUE;
+  ot_transfer_out_value (out_file, &ret_file);
  out:
   if (obj_uri)
     soup_uri_free (obj_uri);
-  if (filename)
-    (void) unlink (filename);
-  g_free (filename);
+  g_clear_object (&ret_file);
   g_free (objpath);
   g_free (relpath);
   return ret;
 }
 
 static gboolean
+store_object (OstreeRepo  *repo,
+              SoupSession *soup,
+              SoupURI     *baseuri,
+              const char  *checksum,
+              OstreeObjectType objtype,
+              gboolean    *did_exist,
+              GError     **error)
+{
+  gboolean ret = FALSE;
+  GFile *filename = NULL;
+  GFileInfo *file_info = NULL;
+  GInputStream *input = NULL;
+
+  if (!fetch_object (repo, soup, baseuri, checksum, objtype, did_exist, &filename, error))
+    goto out;
+
+  if (!*did_exist)
+    {
+      file_info = g_file_query_info (filename, OSTREE_GIO_FAST_QUERYINFO,
+                                     G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, NULL, error);
+      if (!file_info)
+        goto out;
+      
+      input = (GInputStream*)g_file_read (filename, NULL, error);
+      if (!input)
+        goto out;
+  
+      if (!ostree_repo_store_object (repo, objtype, checksum, file_info, NULL, input, NULL, error))
+        goto out;
+    }
+
+  ret = TRUE;
+ out:
+  if (filename)
+    (void) unlink (ot_gfile_get_path_cached (filename));
+  g_clear_object (&file_info);
+  g_clear_object (&input);
+  return ret;
+}
+
+static gboolean
 store_tree_recurse (OstreeRepo   *repo,
                     SoupSession  *soup,
                     SoupURI      *base_uri,
@@ -164,6 +213,12 @@ store_tree_recurse (OstreeRepo   *repo,
   GVariant *dirs_variant = NULL;
   gboolean did_exist;
   int i, n;
+  GVariant *archive_metadata = NULL;
+  GFileInfo *archive_file_info = NULL;
+  GVariant *archive_xattrs = NULL;
+  GFile *meta_file = NULL;
+  GFile *content_file = NULL;
+  GInputStream *input = NULL;
 
   if (!store_object (repo, soup, base_uri, rev, OSTREE_OBJECT_TYPE_DIR_TREE, &did_exist, error))
     goto out;
@@ -187,7 +242,41 @@ store_tree_recurse (OstreeRepo   *repo,
 
           g_variant_get_child (files_variant, i, "(&s&s)", &filename, &checksum);
 
-          if (!store_object (repo, soup, base_uri, checksum, OSTREE_OBJECT_TYPE_ARCHIVED_FILE, &did_exist, error))
+          g_clear_object (&meta_file);
+
+          if (!fetch_object (repo, soup, base_uri, checksum,
+                             OSTREE_OBJECT_TYPE_ARCHIVED_FILE_META,
+                             &did_exist,
+                             &meta_file,
+                             error))
+            goto out;
+
+          g_clear_object (&content_file);
+          if (!fetch_object (repo, soup, base_uri, checksum,
+                             OSTREE_OBJECT_TYPE_ARCHIVED_FILE_CONTENT,
+                             &did_exist,
+                             &content_file,
+                             error))
+            goto out;
+
+          if (!ostree_map_metadata_file (meta_file, OSTREE_OBJECT_TYPE_ARCHIVED_FILE_META,
+                                         &archive_metadata, error))
+            goto out;
+
+          if (!ostree_parse_archived_file_meta (archive_metadata, &archive_file_info, &archive_xattrs, error))
+            goto out;
+
+          if (g_file_info_get_file_type (archive_file_info) == G_FILE_TYPE_REGULAR)
+            {
+              input = (GInputStream*)g_file_read (content_file, NULL, error);
+              if (!input)
+                goto out;
+            }
+
+          if (!ostree_repo_store_object (repo, OSTREE_OBJECT_TYPE_RAW_FILE,
+                                         checksum,
+                                         archive_file_info, archive_xattrs, input,
+                                         NULL, error))
             goto out;
         }
       
@@ -214,6 +303,10 @@ store_tree_recurse (OstreeRepo   *repo,
   ot_clear_gvariant (&tree);
   ot_clear_gvariant (&files_variant);
   ot_clear_gvariant (&dirs_variant);
+  ot_clear_gvariant (&archive_metadata);
+  ot_clear_gvariant (&archive_xattrs);
+  g_clear_object (&archive_file_info);
+  g_clear_object (&input);
   return ret;
 }
 
@@ -269,7 +362,6 @@ ostree_builtin_pull (int argc, char **argv, const char *repo_path, GError **erro
   char *key = NULL;
   char *baseurl = NULL;
   char *refpath = NULL;
-  char *temppath = NULL;
   GFile *tempf = NULL;
   char *remote_ref = NULL;
   char *original_rev = NULL;
@@ -326,9 +418,8 @@ ostree_builtin_pull (int argc, char **argv, const char *repo_path, GError **erro
                                              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))
+  if (!fetch_uri (repo, soup, target_uri, "ref-", &tempf, error))
     goto out;
-  tempf = ot_gfile_new_for_path (temppath);
 
   if (!ot_gfile_load_contents_utf8 (tempf, &rev, NULL, NULL, error))
     goto out;
@@ -342,9 +433,15 @@ ostree_builtin_pull (int argc, char **argv, const char *repo_path, GError **erro
     {
       if (!ostree_validate_checksum_string (rev, error))
         goto out;
+
+      if (!ostree_repo_prepare_transaction (repo, NULL, error))
+        goto out;
       
       if (!store_commit_recurse (repo, soup, base_uri, rev, error))
         goto out;
+
+      if (!ostree_repo_commit_transaction (repo, NULL, error))
+        goto out;
       
       if (!ostree_repo_write_ref (repo, remote, branch, rev, error))
         goto out;
@@ -356,9 +453,9 @@ ostree_builtin_pull (int argc, char **argv, const char *repo_path, GError **erro
  out:
   if (context)
     g_option_context_free (context);
-  if (temppath)
-    (void) unlink (temppath);
-  g_free (temppath);
+  if (tempf)
+    (void) unlink (ot_gfile_get_path_cached (tempf));
+  g_clear_object (&tempf);
   g_free (key);
   g_free (rev);
   g_free (remote_ref);
diff --git a/src/ostree/ot-builtin-fsck.c b/src/ostree/ot-builtin-fsck.c
index 033a8ec..4ca0aa7 100644
--- a/src/ostree/ot-builtin-fsck.c
+++ b/src/ostree/ot-builtin-fsck.c
@@ -35,37 +35,53 @@ static GOptionEntry options[] = {
 };
 
 typedef struct {
+  OstreeRepo *repo;
   guint n_objects;
   gboolean had_error;
 } OtFsckData;
 
 static gboolean
 checksum_archived_file (OtFsckData   *data,
+                        const char   *exp_checksum,
                         GFile        *file,
                         GChecksum   **out_checksum,
                         GError      **error)
 {
   gboolean ret = FALSE;
   GChecksum *ret_checksum = NULL;
-  GInputStream *in = NULL;
+  GVariant *archive_metadata = NULL;
   GVariant *xattrs = NULL;
+  GFile *content_path = NULL;
+  GInputStream *content_input = NULL;
   GFileInfo *file_info = NULL;
   char buf[8192];
   gsize bytes_read;
   guint32 mode;
 
-  if (!ostree_parse_archived_file (file, &file_info, &xattrs, &in, NULL, error))
+  if (!ostree_map_metadata_file (file, OSTREE_OBJECT_TYPE_ARCHIVED_FILE_META, &archive_metadata, error))
     goto out;
 
+  if (!ostree_parse_archived_file_meta (archive_metadata, &file_info, &xattrs, error))
+    goto out;
+
+  content_path = ostree_repo_get_object_path (data->repo, exp_checksum, OSTREE_OBJECT_TYPE_ARCHIVED_FILE_CONTENT);
+
+  if (g_file_info_get_file_type (file_info) == G_FILE_TYPE_REGULAR)
+    {
+      content_input = (GInputStream*)g_file_read (content_path, NULL, error);
+      if (!content_input)
+        goto out;
+    }
+
   ret_checksum = g_checksum_new (G_CHECKSUM_SHA256);
 
   mode = g_file_info_get_attribute_uint32 (file_info, "unix::mode");
   if (S_ISREG (mode))
     {
-      g_assert (in != NULL);
+      g_assert (content_input != NULL);
       do
         {
-          if (!g_input_stream_read_all (in, buf, sizeof(buf), &bytes_read, NULL, error))
+          if (!g_input_stream_read_all (content_input, buf, sizeof(buf), &bytes_read, NULL, error))
             goto out;
           g_checksum_update (ret_checksum, (guint8*)buf, bytes_read);
         }
@@ -97,9 +113,11 @@ checksum_archived_file (OtFsckData   *data,
   ot_transfer_out_value (out_checksum, &ret_checksum);
  out:
   ot_clear_checksum (&ret_checksum);
-  g_clear_object (&in);
   g_clear_object (&file_info);
   ot_clear_gvariant (&xattrs);
+  ot_clear_gvariant (&archive_metadata);
+  g_clear_object (&content_path);
+  g_clear_object (&content_input);
   return ret;
 }
 
@@ -112,40 +130,38 @@ object_iter_callback (OstreeRepo    *repo,
                       gpointer       user_data)
 {
   OtFsckData *data = user_data;
-  const char *path = NULL;
   GChecksum *real_checksum = NULL;
   GError *error = NULL;
 
-  path = ot_gfile_get_path_cached (objf);
-
   /* 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_repo_get_mode (repo) == OSTREE_REPO_MODE_ARCHIVE
-      && !OSTREE_OBJECT_TYPE_IS_META (objtype))
+  if (objtype == OSTREE_OBJECT_TYPE_ARCHIVED_FILE_META)
     {
-      if (!g_str_has_suffix (path, ".archive"))
+      if (!g_str_has_suffix (ot_gfile_get_path_cached (objf), ".archive-meta"))
         {
           g_set_error (&error, G_IO_ERROR, G_IO_ERROR_FAILED,
                        "Invalid archive filename '%s'",
-                       path);
+                       ot_gfile_get_path_cached (objf));
           goto out;
         }
-      if (!checksum_archived_file (data, objf, &real_checksum, &error))
+      if (!checksum_archived_file (data, exp_checksum, objf, &real_checksum, &error))
         goto out;
     }
+  else if (objtype == OSTREE_OBJECT_TYPE_ARCHIVED_FILE_CONTENT)
+    ; /* Handled above */
   else
     {
       if (!ostree_checksum_file (objf, objtype, &real_checksum, NULL, &error))
         goto out;
     }
 
-  if (strcmp (exp_checksum, g_checksum_get_string (real_checksum)) != 0)
+  if (real_checksum && strcmp (exp_checksum, g_checksum_get_string (real_checksum)) != 0)
     {
       data->had_error = TRUE;
-      g_printerr ("ERROR: corrupted object '%s' expected checksum: %s\n",
-                  exp_checksum, g_checksum_get_string (real_checksum));
+      g_printerr ("ERROR: corrupted object '%s'; actual checksum: %s\n",
+                  ot_gfile_get_path_cached (objf), g_checksum_get_string (real_checksum));
     }
 
   data->n_objects++;
@@ -173,13 +189,14 @@ ostree_builtin_fsck (int argc, char **argv, const char *repo_path, GError **erro
   if (!g_option_context_parse (context, &argc, &argv, error))
     goto out;
 
-  data.n_objects = 0;
-  data.had_error = FALSE;
-
   repo = ostree_repo_new (repo_path);
   if (!ostree_repo_check (repo, error))
     goto out;
 
+  data.repo = repo;
+  data.n_objects = 0;
+  data.had_error = FALSE;
+
   if (!ostree_repo_iter_objects (repo, object_iter_callback, &data, error))
     goto out;
 
diff --git a/src/ostree/ot-builtin-local-clone.c b/src/ostree/ot-builtin-local-clone.c
index 132821d..7f883e7 100644
--- a/src/ostree/ot-builtin-local-clone.c
+++ b/src/ostree/ot-builtin-local-clone.c
@@ -25,7 +25,8 @@
 #include "ot-builtins.h"
 #include "ostree.h"
 
-#include <glib/gi18n.h>
+#include <unistd.h>
+#include <stdlib.h>
 
 static GOptionEntry options[] = {
   { NULL }
@@ -94,34 +95,67 @@ object_iter_callback (OstreeRepo   *repo,
                       gpointer      user_data)
 {
   OtLocalCloneData *data = user_data;
-  GError *error = NULL;
-  gboolean did_exist;
-
-  if (ostree_repo_get_mode (data->src_repo) == OSTREE_REPO_MODE_ARCHIVE)
+  GError *real_error = NULL;
+  GError **error = &real_error;
+  GFile *content_path = NULL;
+  GFileInfo *archive_info = NULL;
+  GVariant *archive_metadata = NULL;
+  GVariant *xattrs = NULL;
+  GInputStream *input = NULL;
+
+  if (objtype == OSTREE_OBJECT_TYPE_RAW_FILE)
+    xattrs = ostree_get_xattrs_for_file (objfile, error);
+  
+  if (objtype == OSTREE_OBJECT_TYPE_ARCHIVED_FILE_CONTENT)
+    ;
+  else if (objtype == OSTREE_OBJECT_TYPE_ARCHIVED_FILE_META)
     {
-      if (!ostree_repo_store_archived_file (data->dest_repo, checksum,
-                                            ot_gfile_get_path_cached (objfile),
-                                            objtype,
-                                            &did_exist,
-                                            &error))
+      if (!ostree_repo_load_variant (repo, OSTREE_OBJECT_TYPE_ARCHIVED_FILE_META, checksum, &archive_metadata, error))
+        goto out;
+
+      if (!ostree_parse_archived_file_meta (archive_metadata, &archive_info, &xattrs, error))
+        goto out;
+
+      content_path = ostree_repo_get_object_path (repo, checksum, OSTREE_OBJECT_TYPE_ARCHIVED_FILE_CONTENT);
+
+      if (g_file_info_get_file_type (archive_info) == G_FILE_TYPE_REGULAR)
+        {
+          input = (GInputStream*)g_file_read (content_path, NULL, error);
+          if (!input)
+            goto out;
+        }
+      
+      if (!ostree_repo_store_object_trusted (data->dest_repo, OSTREE_OBJECT_TYPE_RAW_FILE, checksum,
+                                             archive_info, xattrs, input,
+                                             NULL, error))
         goto out;
     }
   else
     {
-      if (!ostree_repo_store_object_trusted (data->dest_repo,
-                                             objfile, 
-                                             checksum,
-                                             objtype,
-                                             NULL,
-                                             &error))
+      if (g_file_info_get_file_type (file_info) == G_FILE_TYPE_REGULAR)
+        {
+          input = (GInputStream*)g_file_read (objfile, NULL, error);
+          if (!input)
+            goto out;
+        }
+
+      if (!ostree_repo_store_object_trusted (data->dest_repo, objtype, checksum,
+                                             file_info, xattrs, input,
+                                             NULL, error))
         goto out;
     }
 
  out:
-  if (error != NULL)
+  ot_clear_gvariant (&archive_metadata);
+  ot_clear_gvariant (&xattrs);
+  g_clear_object (&archive_info);
+  g_clear_object (&input);
+  g_clear_object (&content_path);
+  if (real_error != NULL)
     {
-      g_printerr ("%s\n", error->message);
-      g_clear_error (&error);
+      g_printerr ("%s\n", real_error->message);
+      g_clear_error (error);
+      exit (1);
     }
 }
 
@@ -183,8 +217,14 @@ ostree_builtin_local_clone (int argc, char **argv, const char *repo_path, GError
 
   data.uids_differ = g_file_info_get_attribute_uint32 (src_info, "unix::uid") != g_file_info_get_attribute_uint32 (dest_info, "unix::uid");
 
+  if (!ostree_repo_prepare_transaction (data.dest_repo, NULL, error))
+    goto out;
+
   if (!ostree_repo_iter_objects (data.src_repo, object_iter_callback, &data, error))
     goto out;
+
+  if (!ostree_repo_commit_transaction (data.dest_repo, NULL, error))
+    goto out;
   
   src_dir = g_file_resolve_relative_path (src_repo_dir, "refs/heads");
   dest_dir = g_file_resolve_relative_path (dest_repo_dir, "refs/heads");
diff --git a/src/ostree/ot-builtin-log.c b/src/ostree/ot-builtin-log.c
index 9fa7b02..9f55fb3 100644
--- a/src/ostree/ot-builtin-log.c
+++ b/src/ostree/ot-builtin-log.c
@@ -69,7 +69,6 @@ ostree_builtin_log (int argc, char **argv, const char *repo_path, GError **error
 
   while (TRUE)
     {
-      OstreeObjectType type;
       char *formatted = NULL;
       guint32 version;
       const char *parent;
diff --git a/src/ostree/ot-builtin-show.c b/src/ostree/ot-builtin-show.c
index ab5b5c7..c25dc8c 100644
--- a/src/ostree/ot-builtin-show.c
+++ b/src/ostree/ot-builtin-show.c
@@ -124,7 +124,7 @@ show_repo_meta (OstreeRepo  *repo,
             g_print ("%s", buf);
           } while (bytes_read > 0);
         }
-      else if (objtype == OSTREE_OBJECT_TYPE_ARCHIVED_FILE)
+      else if (objtype == OSTREE_OBJECT_TYPE_ARCHIVED_FILE_CONTENT)
         {
           g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
                        "Can't show archived files yet");



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