[ostree/wip/packfile-rebase2: 4/11] repack: creating raw packfiles
- From: Colin Walters <walters src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [ostree/wip/packfile-rebase2: 4/11] repack: creating raw packfiles
- Date: Thu, 22 Mar 2012 03:14:14 +0000 (UTC)
commit 4d262b23a495fb130742377dc76107b15006136d
Author: Colin Walters <walters verbum org>
Date: Sun Mar 18 12:13:37 2012 -0400
repack: creating raw packfiles
src/libostree/README.md | 15 +-
src/libostree/ostree-core.h | 28 ++-
src/ostree/ot-builtin-repack.c | 540 ++++++++++++++++++++++++++++++++++++----
3 files changed, 525 insertions(+), 58 deletions(-)
---
diff --git a/src/libostree/README.md b/src/libostree/README.md
index 04d1462..3ab4730 100644
--- a/src/libostree/README.md
+++ b/src/libostree/README.md
@@ -15,7 +15,7 @@ overhead. In contrast, a git repository stores copies of
zlib-compressed data.
Key differences versus git
-----------------------------
+--------------------------
* As mentioned above, extended attributes and owner uid/gid are versioned
* SHA256 instead of SHA1
@@ -28,3 +28,16 @@ While this is still in planning, I plan to heavily optimize OSTree for
versioning ELF operating systems. In industry jargon, this would be
"content-aware storage".
+Related work in storage
+-----------------------
+
+git: http://git-scm.com/
+Venti: http://plan9.bell-labs.com/magic/man2html/6/venti
+Elephant FS: http://www.hpl.hp.com/personal/Alistair_Veitch/papers/elephant-hotos/index.html
+
+Compression
+-----------
+
+xdelta: http://xdelta.org/
+Bsdiff: http://www.daemonology.net/bsdiff/
+xz: http://tukaani.org/xz/
diff --git a/src/libostree/ostree-core.h b/src/libostree/ostree-core.h
index 062648e..ab1b3a1 100644
--- a/src/libostree/ostree-core.h
+++ b/src/libostree/ostree-core.h
@@ -97,12 +97,36 @@ typedef enum {
*/
#define OSTREE_ARCHIVED_FILE_VARIANT_FORMAT G_VARIANT_TYPE ("(uuuuusa(ayay))")
+/* Pack super index
+ * s - OSTSUPERPACKINDEX
+ * u - Version
+ * a{sv} - Metadata
+ * a(say) - (pack file checksum, bloom filter)
+ */
+#define OSTREE_PACK_SUPER_INDEX_VARIANT_FORMAT G_VARIANT_TYPE ("(sua{sv}a(say))")
+
+/* Pack index
+ * s - OSTPACKINDEX
+ * u - Version
+ * a{sv} - Metadata
+ * a(st) - (checksum, offset into packfile)
+ */
+#define OSTREE_PACK_INDEX_VARIANT_FORMAT G_VARIANT_TYPE ("(sua{sv}a(st))")
+
/* Pack files
+ * s - OSTPACKFILE
* u - Version
* a{sv} - Metadata
- * a(ysay) - Contents (flags, checksum, content)
+ * u - number of entries
+ *
+ * Repeating tuple of:
+ * <padding to alignment of 8>
+ * (yst) - Contents (flags, checksum, objtype, length)
+ * <raw data>
*/
-#define OSTREE_PACK_FILE_VARIANT_FORMAT G_VARIANT_TYPE ("(ua{sv}a(ysay))")
+#define OSTREE_PACK_FILE_VARIANT_FORMAT G_VARIANT_TYPE ("(sua{sv}t)")
+
+#define OSTREE_PACK_FILE_CONTENT_VARIANT_FORMAT G_VARIANT_TYPE ("(yst)")
gboolean ostree_validate_checksum_string (const char *sha256,
GError **error);
diff --git a/src/ostree/ot-builtin-repack.c b/src/ostree/ot-builtin-repack.c
index dad2c7d..09e935c 100644
--- a/src/ostree/ot-builtin-repack.c
+++ b/src/ostree/ot-builtin-repack.c
@@ -28,27 +28,38 @@
#include <glib/gi18n.h>
#include <glib/gprintf.h>
+#include <gio/gunixinputstream.h>
#include <gio/gunixoutputstream.h>
#define DEFAULT_PACK_SIZE_BYTES (50*1024*1024)
+static gboolean opt_analyze_only;
static char* opt_pack_size;
-static char* opt_compression;
+static char* opt_int_compression;
+
+typedef enum {
+ OT_INTERNAL_COMPRESSION_NONE,
+ OT_INTERNAL_COMPRESSION_GZIP
+} OtInternalCompressionType;
static GOptionEntry options[] = {
{ "pack-size", 0, 0, G_OPTION_ARG_STRING, &opt_pack_size, "Maximum uncompressed size of packfiles in bytes; may be suffixed with k, m, or g", "BYTES" },
- { "compression", 0, 0, G_OPTION_ARG_STRING, &opt_compression, "Compress generated packfiles using COMPRESSION; may be one of 'gzip', 'xz'", "COMPRESSION" },
+ { "internal-compression", 0, 0, G_OPTION_ARG_STRING, &opt_int_compression, "Compress objects using COMPRESSION; may be one of 'gzip', 'xz'", "COMPRESSION" },
+ { "analyze-only", 0, 0, G_OPTION_ARG_NONE, &opt_analyze_only, "Just analyze current state", NULL },
{ NULL }
};
typedef struct {
OstreeRepo *repo;
+
+ guint64 pack_size;
+ OtInternalCompressionType int_compression;
+
guint n_commits;
guint n_dirmeta;
guint n_dirtree;
guint n_files;
GPtrArray *objects;
- GArray *object_sizes;
gboolean had_error;
GError **error;
} OtRepackData;
@@ -59,26 +70,41 @@ typedef struct {
GPid compress_child_pid;
} OtBuildRepackFile;
+static GPtrArray *
+get_xz_args (void)
+{
+ GPtrArray *ret = g_ptr_array_new ();
+
+ g_ptr_array_add (ret, "xz");
+ g_ptr_array_add (ret, "--memlimit-compress=512M");
+
+ return ret;
+}
+
static void
compressor_child_setup (gpointer user_data)
{
int stdout_fd = GPOINTER_TO_INT (user_data);
- dup2 (stdout_fd, 1);
+ if (dup2 (stdout_fd, 1) < 0)
+ g_assert_not_reached ();
+ (void) close (stdout_fd);
}
static gboolean
-build_repack_file_init (OtBuildRepackFile *self,
- GFile *destfile,
- GPtrArray *compressor_argv,
- GCancellable *cancellable,
- GError **error)
+create_compressor_subprocess (OtBuildRepackFile *self,
+ GPtrArray *compressor_argv,
+ GFile *destfile,
+ GOutputStream **out_output,
+ GCancellable *cancellable,
+ GError **error)
{
gboolean ret = FALSE;
+ GOutputStream *ret_output = NULL;
int stdin_pipe_fd;
int target_stdout_fd;
- target_stdout_fd = open (ot_gfile_get_path_cached (destfile), O_WRONLY | O_CREAT);
+ target_stdout_fd = open (ot_gfile_get_path_cached (destfile), O_WRONLY);
if (target_stdout_fd < 0)
{
ot_util_set_error_from_errno (error, errno);
@@ -93,32 +119,15 @@ build_repack_file_init (OtBuildRepackFile *self,
(void) close (target_stdout_fd);
- self->out = g_unix_output_stream_new (stdin_pipe_fd, TRUE);
+ ret_output = g_unix_input_stream_new (stdin_pipe_fd, TRUE);
ret = TRUE;
+ ot_transfer_out_value (out_output, &ret_output);
out:
+ g_clear_object (&ret_output);
return ret;
}
-static inline int
-size_to_bucket (guint64 objsize)
-{
- int off;
- if (objsize < 128)
- return 0;
-
- objsize >>= 7;
- off = 0;
- while (objsize > 0)
- {
- off++;
- objsize >>= 1;
- }
- if (off > 23)
- return 23;
- return off;
-}
-
static void
object_iter_callback (OstreeRepo *repo,
const char *checksum,
@@ -129,12 +138,8 @@ object_iter_callback (OstreeRepo *repo,
{
gboolean ret = FALSE;
OtRepackData *data = user_data;
- char *key;
- GError **error = data->error;
guint64 objsize;
- int bucket;
- GVariant *archive_meta = NULL;
- GFileInfo *archive_info = NULL;
+ GVariant *objdata = NULL;
switch (objtype)
{
@@ -156,21 +161,404 @@ object_iter_callback (OstreeRepo *repo,
break;
}
- objsize = g_file_info_get_size (file_info);
+ /* For archived content, only count regular files */
+ if (!(objtype == OSTREE_OBJECT_TYPE_ARCHIVED_FILE_CONTENT
+ && g_file_info_get_file_type (file_info) != G_FILE_TYPE_REGULAR))
+ {
+ objsize = g_file_info_get_size (file_info);
- g_ptr_array_add (data->objects,
+ objdata = g_variant_new ("(tsu)", objsize, checksum, (guint32)objtype);
+ g_ptr_array_add (data->objects, g_variant_ref_sink (objdata));
+ objdata = NULL; /* Transfer ownership */
+ }
- bucket = size_to_bucket (objsize);
- data->object_bucket_count[bucket]++;
- data->object_bucket_size[bucket] += objsize;
-
ret = TRUE;
/* out: */
- ot_clear_gvariant (&archive_meta);
- g_clear_object (&archive_info);
+ ot_clear_gvariant (&objdata);
data->had_error = !ret;
}
+static gint
+compare_object_data_by_size (gconstpointer ap,
+ gconstpointer bp)
+{
+ GVariant *a = *(void **)ap;
+ GVariant *b = *(void **)bp;
+ guint64 a_size;
+ guint64 b_size;
+
+ g_variant_get_child (a, 0, "t", &a_size);
+ g_variant_get_child (b, 0, "t", &b_size);
+ if (a == b)
+ return 0;
+ else if (a > b)
+ return 1;
+ else
+ return -1;
+}
+
+static gboolean
+write_aligned_variant (GOutputStream *output,
+ GVariant *variant,
+ GChecksum *checksum,
+ guint64 *inout_offset,
+ GCancellable *cancellable,
+ GError **error)
+{
+ gboolean ret = FALSE;
+ guint padding;
+ gsize bytes_written;
+ char padding_nuls[7] = {0, 0, 0, 0, 0, 0, 0};
+
+ padding = 8 - ((*inout_offset) & 7);
+
+ if (padding > 0)
+ {
+ g_checksum_update (checksum, (guchar*) padding_nuls, padding);
+ if (!g_output_stream_write_all (output, padding_nuls, padding, &bytes_written,
+ cancellable, error))
+ goto out;
+ g_assert (bytes_written == padding);
+ *inout_offset += padding;
+ }
+
+ g_checksum_update (checksum, (guchar*) g_variant_get_data (variant),
+ g_variant_get_size (variant));
+ if (!g_output_stream_write_all (output, g_variant_get_data (variant),
+ g_variant_get_size (variant), &bytes_written,
+ cancellable, error))
+ goto out;
+ *inout_offset += bytes_written;
+
+ ret = TRUE;
+ out:
+ return ret;
+}
+
+static gboolean
+create_pack_file (OtRepackData *data,
+ GPtrArray *objects,
+ GCancellable *cancellable,
+ GError **error)
+{
+ gboolean ret = FALSE;
+ GFile *pack_dir = NULL;
+ GFile *index_temppath = NULL;
+ GOutputStream *index_out = NULL;
+ GFile *pack_temppath = NULL;
+ GOutputStream *pack_out = NULL;
+ GFile *object_path = NULL;
+ GFileInfo *object_file_info = NULL;
+ GFileInputStream *object_input = NULL;
+ GConverter *compressor = NULL;
+ GConverterOutputStream *compressed_object_output = NULL;
+ guint i;
+ guint64 offset;
+ gsize bytes_read;
+ gsize bytes_written;
+ GVariantBuilder index_content_builder;
+ gboolean index_content_builder_initialized = FALSE;
+ GVariant *pack_header = NULL;
+ GVariant *object_header = NULL;
+ GVariant *index_content = NULL;
+ GChecksum *pack_checksum = NULL;
+ char *pack_name = NULL;
+ GFile *pack_file_path = NULL;
+ GFile *pack_index_path = NULL;
+
+ if (g_cancellable_set_error_if_cancelled (cancellable, error))
+ return FALSE;
+
+ if (!ostree_create_temp_regular_file (ostree_repo_get_tmpdir (data->repo),
+ "pack-index", NULL,
+ &index_temppath,
+ &index_out,
+ cancellable, error))
+ goto out;
+
+ if (!ostree_create_temp_regular_file (ostree_repo_get_tmpdir (data->repo),
+ "pack-content", NULL,
+ &pack_temppath,
+ &pack_out,
+ cancellable, error))
+ goto out;
+
+ offset = 0;
+ pack_checksum = g_checksum_new (G_CHECKSUM_SHA256);
+
+ g_variant_builder_init (&index_content_builder, G_VARIANT_TYPE ("a(st)"));
+ index_content_builder_initialized = TRUE;
+
+ pack_header = g_variant_new ("(su a{sv}u)",
+ "OSTPACK", GUINT32_TO_BE (0),
+ g_variant_new_array (G_VARIANT_TYPE ("{sv}"), NULL, 0),
+ objects->len);
+
+ if (!write_aligned_variant (pack_out, pack_header, pack_checksum, &offset,
+ cancellable, error))
+ goto out;
+
+ for (i = 0; i < objects->len; i++)
+ {
+ GVariant *object_data = objects->pdata[i];
+ guint64 objsize;
+ const char *checksum;
+ guint32 objtype_u32;
+ OstreeObjectType objtype;
+ char buf[4096];
+ guint64 obj_bytes_written;
+ GOutputStream *write_pack_out;
+
+ g_variant_get (object_data, "(t&su)", &objsize, &checksum, &objtype_u32);
+
+ objtype = (OstreeObjectType) objtype_u32;
+
+ ot_clear_gvariant (&object_header);
+ object_header = g_variant_new ("(yst)", GUINT32_TO_BE (0), checksum, (guint32)objtype, objsize);
+
+ if (!write_aligned_variant (pack_out, object_header, pack_checksum,
+ &offset, cancellable, error))
+ goto out;
+
+ g_clear_object (&object_path);
+ object_path = ostree_repo_get_object_path (data->repo, checksum, objtype);
+
+ g_clear_object (&object_input);
+ object_input = g_file_read (object_path, cancellable, error);
+ if (!object_input)
+ goto out;
+
+ if (data->int_compression != OT_INTERNAL_COMPRESSION_NONE)
+ {
+ g_clear_object (&compressor);
+ switch (data->int_compression)
+ {
+ case OT_INTERNAL_COMPRESSION_GZIP:
+ compressor = (GConverter*)g_zlib_compressor_new (G_ZLIB_COMPRESSOR_FORMAT_GZIP, 8);
+ break;
+ default:
+ g_assert_not_reached ();
+ }
+
+ g_clear_object (&compressed_object_output);
+ compressed_object_output = (GConverterOutputStream*)g_object_new (G_TYPE_CONVERTER_OUTPUT_STREAM,
+ "converter", compressor,
+ "base-stream", pack_out,
+ "close-base-stream", FALSE,
+ NULL);
+ write_pack_out = (GOutputStream*)compressed_object_output;
+ }
+ else
+ write_pack_out = (GOutputStream*)pack_out;
+
+ obj_bytes_written = 0;
+ do
+ {
+ if (!g_input_stream_read_all ((GInputStream*)object_input, buf, sizeof(buf), &bytes_read, cancellable, error))
+ goto out;
+ g_checksum_update (pack_checksum, (guint8*)buf, bytes_read);
+ if (bytes_read > 0)
+ {
+ if (!g_output_stream_write_all (write_pack_out, buf, bytes_read, &bytes_written, cancellable, error))
+ goto out;
+ offset += bytes_written;
+ obj_bytes_written += bytes_written;
+ }
+ }
+ while (bytes_read > 0);
+
+ if (compressed_object_output)
+ {
+ if (!g_output_stream_flush ((GOutputStream*)compressed_object_output, cancellable, error))
+ goto out;
+ }
+
+ g_assert_cmpint (obj_bytes_written, ==, objsize);
+
+ g_variant_builder_add (&index_content_builder, "(st)", checksum, offset);
+ }
+
+ if (!g_output_stream_close (pack_out, cancellable, error))
+ goto out;
+
+ pack_dir = g_file_resolve_relative_path (ostree_repo_get_path (data->repo),
+ "objects/pack");
+
+ pack_name = g_strconcat ("ostpack-", g_checksum_get_string (pack_checksum), ".data", NULL);
+ pack_file_path = g_file_get_child (pack_dir, pack_name);
+
+ if (rename (ot_gfile_get_path_cached (pack_temppath),
+ ot_gfile_get_path_cached (pack_file_path)) < 0)
+ {
+ ot_util_set_error_from_errno (error, errno);
+ g_prefix_error (error, "Failed to rename pack file '%s' to '%s': ",
+ ot_gfile_get_path_cached (pack_temppath),
+ ot_gfile_get_path_cached (pack_file_path));
+ goto out;
+ }
+ g_clear_object (&pack_temppath);
+
+ index_content = g_variant_new ("(su a{sv}@a(st))",
+ "OSTPACKINDEX", GUINT32_TO_BE(0),
+ g_variant_new_array (G_VARIANT_TYPE ("{sv}"), NULL, 0),
+ g_variant_builder_end (&index_content_builder));
+ index_content_builder_initialized = FALSE;
+
+ if (!g_output_stream_write_all (index_out,
+ g_variant_get_data (index_content),
+ g_variant_get_size (index_content),
+ &bytes_written,
+ cancellable,
+ error))
+ goto out;
+
+ if (!g_output_stream_close (index_out, cancellable, error))
+ goto out;
+
+ g_free (pack_name);
+ pack_name = g_strconcat ("ostpack-", g_checksum_get_string (pack_checksum), ".index", NULL);
+ pack_index_path = g_file_get_child (pack_dir, pack_name);
+
+ if (rename (ot_gfile_get_path_cached (index_temppath),
+ ot_gfile_get_path_cached (pack_index_path)) < 0)
+ {
+ ot_util_set_error_from_errno (error, errno);
+ g_prefix_error (error, "Failed to rename pack file '%s' to '%s': ",
+ ot_gfile_get_path_cached (index_temppath),
+ ot_gfile_get_path_cached (pack_index_path));
+ goto out;
+ }
+ g_clear_object (&index_temppath);
+
+ ret = TRUE;
+ out:
+ if (index_temppath)
+ (void) unlink (ot_gfile_get_path_cached (index_temppath));
+ g_clear_object (&index_temppath);
+ g_clear_object (&index_out);
+ if (pack_temppath)
+ (void) unlink (ot_gfile_get_path_cached (pack_temppath));
+ g_clear_object (&pack_temppath);
+ g_clear_object (&pack_out);
+ g_clear_object (&object_path);
+ g_clear_object (&object_input);
+ g_clear_object (&compressor);
+ g_clear_object (&compressed_object_output);
+ g_clear_object (&object_file_info);
+ ot_clear_gvariant (&object_header);
+ if (pack_checksum)
+ g_checksum_free (pack_checksum);
+ g_clear_object (&pack_dir);
+ ot_clear_gvariant (&index_content);
+ g_free (pack_name);
+ g_clear_object (&pack_file_path);
+ g_clear_object (&pack_index_path);
+ if (index_content_builder_initialized)
+ g_variant_builder_clear (&index_content_builder);
+ return ret;
+}
+
+/**
+ * cluster_objects_stupidly:
+ *
+ * Just sorts by size currently.
+ *
+ * Returns: [Array of [Array of object data]]. Free with g_ptr_array_unref().
+ */
+static GPtrArray *
+cluster_objects_stupidly (OtRepackData *data)
+{
+ GPtrArray *ret = NULL;
+ GPtrArray *objects = data->objects;
+ guint i;
+ guint64 current_size;
+ guint current_offset;
+
+ g_ptr_array_sort (data->objects, compare_object_data_by_size);
+
+ ret = g_ptr_array_new ();
+
+ current_size = 0;
+ current_offset = 0;
+ for (i = 0; i < objects->len; i++)
+ {
+ GVariant *objdata = objects->pdata[i];
+ guint64 objsize;
+
+ g_variant_get_child (objdata, 0, "t", &objsize);
+
+ if (current_size + objsize > data->pack_size)
+ {
+ guint j;
+ GPtrArray *current = g_ptr_array_new ();
+ for (j = current_offset; j < i; j++)
+ {
+ g_ptr_array_add (current, objects->pdata[j]);
+ }
+ g_ptr_array_add (ret, current);
+ current_size = objsize;
+ current_offset = i;
+ }
+ else if (objsize > data->pack_size)
+ {
+ break;
+ }
+ else
+ {
+ current_size += objsize;
+ }
+ }
+
+ return ret;
+}
+
+static gboolean
+parse_size_spec_with_suffix (const char *spec,
+ guint64 *out_size,
+ GError **error)
+{
+ gboolean ret = FALSE;
+ char *endptr = NULL;
+ guint64 ret_size;
+
+ ret_size = g_ascii_strtoull (spec, &endptr, 10);
+
+ if (endptr && *endptr)
+ {
+ char suffix = *endptr;
+
+ switch (suffix)
+ {
+ case 'k':
+ case 'K':
+ {
+ ret_size *= 1024;
+ break;
+ }
+ case 'm':
+ case 'M':
+ {
+ ret_size *= (1024 * 1024);
+ break;
+ }
+ case 'g':
+ case 'G':
+ {
+ ret_size *= (1024 * 1024 * 1024);
+ break;
+ }
+ default:
+ g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+ "Invalid size suffix '%c'", suffix);
+ goto out;
+ }
+ }
+
+ ret = TRUE;
+ *out_size = ret_size;
+ out:
+ return ret;
+}
gboolean
ostree_builtin_repack (int argc, char **argv, GFile *repo_path, GError **error)
@@ -180,8 +568,9 @@ ostree_builtin_repack (int argc, char **argv, GFile *repo_path, GError **error)
gboolean ret = FALSE;
OstreeRepo *repo = NULL;
GCancellable *cancellable = NULL;
- int i;
+ guint i;
guint64 total_size;
+ GPtrArray *clusters = NULL;
memset (&data, 0, sizeof (data));
@@ -197,8 +586,29 @@ ostree_builtin_repack (int argc, char **argv, GFile *repo_path, GError **error)
data.repo = repo;
data.error = error;
- data.objects = g_ptr_array_new_with_free_func (g_free);
- data.object_sizes = g_array_new (FALSE, FALSE, sizeof (guint64));
+ data.objects = g_ptr_array_new_with_free_func ((GDestroyNotify)g_variant_unref);
+ if (opt_pack_size)
+ {
+ if (!parse_size_spec_with_suffix (opt_pack_size, &data.pack_size, error))
+ goto out;
+ }
+ else
+ {
+ data.pack_size = DEFAULT_PACK_SIZE_BYTES;
+ }
+ if (opt_int_compression)
+ {
+ if (strcmp (opt_int_compression, "gzip") == 0)
+ data.int_compression = OT_INTERNAL_COMPRESSION_GZIP;
+ else
+ {
+ g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+ "Invalid internal compression '%s'", opt_int_compression);
+ goto out;
+ }
+ }
+ else
+ data.int_compression = OT_INTERNAL_COMPRESSION_NONE;
if (!ostree_repo_iter_objects (repo, object_iter_callback, &data, error))
goto out;
@@ -212,23 +622,43 @@ ostree_builtin_repack (int argc, char **argv, GFile *repo_path, GError **error)
g_print ("Files: %u\n", data.n_files);
total_size = 0;
- for (i = 0; i < G_N_ELEMENTS(data.object_bucket_size); i++)
+ for (i = 0; i < data.objects->len; i++)
{
- int size;
- if (i == 0)
- size = 128;
- else
- size = 1 << (i + 7);
- g_print ("%d: %" G_GUINT64_FORMAT " objects, %" G_GUINT64_FORMAT " bytes\n",
- size, data.object_bucket_count[i], data.object_bucket_size[i]);
- total_size += data.object_bucket_size[i];
+ GVariant *objdata = data.objects->pdata[i];
+ guint64 size;
+
+ g_variant_get_child (objdata, 0, "t", &size);
+
+ total_size += size;
}
g_print ("Total size: %" G_GUINT64_FORMAT "\n", total_size);
+ g_print ("\n");
+ g_print ("Using pack size: %" G_GUINT64_FORMAT "\n", data.pack_size);
+
+ clusters = cluster_objects_stupidly (&data);
+
+ g_print ("Going to create %u packfiles\n", clusters->len);
+
+ for (i = 0; i < clusters->len; i++)
+ {
+ GPtrArray *cluster = clusters->pdata[i];
+
+ g_print ("%u: %u objects\n", i, cluster->len);
+
+ if (!opt_analyze_only)
+ {
+ if (!create_pack_file (&data, cluster, cancellable, error))
+ goto out;
+ }
+ }
+
ret = TRUE;
out:
if (context)
g_option_context_free (context);
g_clear_object (&repo);
+ if (clusters)
+ g_ptr_array_unref (clusters);
return ret;
}
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]