diff --git a/src/libostree/ostree-mutable-tree.c b/src/libostree/ostree-mutable-tree.c index ca8be1ee..5f63f113 100644 --- a/src/libostree/ostree-mutable-tree.c +++ b/src/libostree/ostree-mutable-tree.c @@ -221,6 +221,55 @@ ostree_mutable_tree_lookup (OstreeMutableTree *self, return ret; } +gboolean +ostree_mutable_tree_ensure_parent_dirs (OstreeMutableTree *self, + GPtrArray *split_path, + const char *metadata_checksum, + OstreeMutableTree **out_parent, + GError **error) +{ + gboolean ret = FALSE; + int i; + OstreeMutableTree *ret_parent = NULL; + OstreeMutableTree *subdir = self; + + g_assert (metadata_checksum != NULL); + + if (!self->metadata_checksum) + ostree_mutable_tree_set_metadata_checksum (self, metadata_checksum); + + for (i = 0; i+1 < split_path->len; i++) + { + OstreeMutableTree *next; + const char *name = split_path->pdata[i]; + + if (g_hash_table_lookup (subdir->files, name)) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Can't replace file with directory: %s", name); + goto out; + } + + next = g_hash_table_lookup (subdir->subdirs, name); + if (!next) + { + next = ostree_mutable_tree_new (); + ostree_mutable_tree_set_metadata_checksum (next, metadata_checksum); + g_hash_table_insert (subdir->subdirs, g_strdup (name), next); + } + + subdir = next; + } + + ret_parent = g_object_ref (subdir); + + ret = TRUE; + ot_transfer_out_value (out_parent, &ret_parent); + out: + g_clear_object (&ret_parent); + return ret; +} + gboolean ostree_mutable_tree_walk (OstreeMutableTree *self, GPtrArray *split_path, diff --git a/src/libostree/ostree-mutable-tree.h b/src/libostree/ostree-mutable-tree.h index 413da028..e47964d1 100644 --- a/src/libostree/ostree-mutable-tree.h +++ b/src/libostree/ostree-mutable-tree.h @@ -77,6 +77,13 @@ gboolean ostree_mutable_tree_lookup (OstreeMutableTree *self, OstreeMutableTree **out_subdir, GError **error); +gboolean +ostree_mutable_tree_ensure_parent_dirs (OstreeMutableTree *self, + GPtrArray *split_path, + const char *metadata_checksum, + OstreeMutableTree **out_parent, + GError **error); + gboolean ostree_mutable_tree_walk (OstreeMutableTree *self, GPtrArray *split_path, guint start, diff --git a/src/libostree/ostree-repo.c b/src/libostree/ostree-repo.c index 80b81812..71bf0555 100644 --- a/src/libostree/ostree-repo.c +++ b/src/libostree/ostree-repo.c @@ -1585,6 +1585,7 @@ ostree_repo_stage_mtree (OstreeRepo *self, while (g_hash_table_iter_next (&hash_iter, &key, &value)) { const char *name = key; + const char *metadata_checksum; OstreeMutableTree *child_dir = value; char *child_dir_contents_checksum; @@ -1592,10 +1593,13 @@ ostree_repo_stage_mtree (OstreeRepo *self, cancellable, error)) goto out; + g_assert (child_dir_contents_checksum); g_hash_table_replace (dir_contents_checksums, g_strdup (name), child_dir_contents_checksum); /* Transfer ownership */ + metadata_checksum = ostree_mutable_tree_get_metadata_checksum (child_dir); + g_assert (metadata_checksum); g_hash_table_replace (dir_metadata_checksums, g_strdup (name), - g_strdup (ostree_mutable_tree_get_metadata_checksum (child_dir))); + g_strdup (metadata_checksum)); } serialized_tree = create_tree_variant_from_hashes (ostree_mutable_tree_get_files (mtree), @@ -1709,6 +1713,7 @@ stage_libarchive_entry_to_mtree (OstreeRepo *self, struct archive *a, struct archive_entry *entry, OstreeRepoCommitModifier *modifier, + const char *tmp_dir_checksum, GCancellable *cancellable, GError **error) { @@ -1738,8 +1743,19 @@ stage_libarchive_entry_to_mtree (OstreeRepo *self, } else { - if (!ostree_mutable_tree_walk (root, split_path, 0, &parent, error)) - goto out; + if (tmp_dir_checksum) + { + if (!ostree_mutable_tree_ensure_parent_dirs (root, split_path, + tmp_dir_checksum, + &parent, + error)) + goto out; + } + else + { + if (!ostree_mutable_tree_walk (root, split_path, 0, &parent, error)) + goto out; + } basename = (char*)split_path->pdata[split_path->len-1]; } @@ -1854,18 +1870,21 @@ stage_libarchive_entry_to_mtree (OstreeRepo *self, #endif gboolean -ostree_repo_stage_archive_to_mtree (OstreeRepo *self, - GFile *archive_f, - OstreeMutableTree *root, - OstreeRepoCommitModifier *modifier, - GCancellable *cancellable, - GError **error) +ostree_repo_stage_archive_to_mtree (OstreeRepo *self, + GFile *archive_f, + OstreeMutableTree *root, + OstreeRepoCommitModifier *modifier, + gboolean autocreate_parents, + GCancellable *cancellable, + GError **error) { #ifdef HAVE_LIBARCHIVE gboolean ret = FALSE; - struct archive *a; + struct archive *a = NULL; struct archive_entry *entry; int r; + GFileInfo *tmp_dir_info = NULL; + GChecksum *tmp_dir_checksum = NULL; a = archive_read_new (); archive_read_support_compression_all (a); @@ -1887,7 +1906,22 @@ ostree_repo_stage_archive_to_mtree (OstreeRepo *self, goto out; } - if (!stage_libarchive_entry_to_mtree (self, root, a, entry, modifier, cancellable, error)) + if (autocreate_parents && !tmp_dir_checksum) + { + tmp_dir_info = g_file_info_new (); + + g_file_info_set_attribute_uint32 (tmp_dir_info, "unix::uid", archive_entry_uid (entry)); + g_file_info_set_attribute_uint32 (tmp_dir_info, "unix::gid", archive_entry_gid (entry)); + g_file_info_set_attribute_uint32 (tmp_dir_info, "unix::mode", 0755 | S_IFDIR); + + if (!stage_directory_meta (self, tmp_dir_info, NULL, &tmp_dir_checksum, cancellable, error)) + goto out; + } + + if (!stage_libarchive_entry_to_mtree (self, root, a, + entry, modifier, + autocreate_parents ? g_checksum_get_string (tmp_dir_checksum) : NULL, + cancellable, error)) goto out; } if (archive_read_close (a) != ARCHIVE_OK) @@ -1898,7 +1932,10 @@ ostree_repo_stage_archive_to_mtree (OstreeRepo *self, ret = TRUE; out: - (void)archive_read_close (a); + g_clear_object (&tmp_dir_info); + ot_clear_checksum (&tmp_dir_checksum); + if (a) + (void)archive_read_close (a); return ret; #else g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, diff --git a/src/libostree/ostree-repo.h b/src/libostree/ostree-repo.h index 08ebd58c..3777d840 100644 --- a/src/libostree/ostree-repo.h +++ b/src/libostree/ostree-repo.h @@ -158,6 +158,7 @@ gboolean ostree_repo_stage_archive_to_mtree (OstreeRepo *self, GFile *archive, OstreeMutableTree *tree, OstreeRepoCommitModifier *modifier, + gboolean autocreate_parents, GCancellable *cancellable, GError **error); diff --git a/src/ostree/ot-builtin-commit.c b/src/ostree/ot-builtin-commit.c index 5fcc0371..8e525ef2 100644 --- a/src/ostree/ot-builtin-commit.c +++ b/src/ostree/ot-builtin-commit.c @@ -36,6 +36,7 @@ static char *body; static char *parent; static char *branch; static gboolean skip_if_unchanged; +static gboolean tar_autocreate_parents; static char **trees; static gint owner_uid = -1; static gint owner_gid = -1; @@ -50,6 +51,7 @@ static GOptionEntry options[] = { { "tree", 0, 0, G_OPTION_ARG_STRING_ARRAY, &trees, "Overlay the given argument as a tree", "NAME" }, { "owner-uid", 0, 0, G_OPTION_ARG_INT, &owner_uid, "Set file ownership user id", "UID" }, { "owner-gid", 0, 0, G_OPTION_ARG_INT, &owner_gid, "Set file ownership group id", "GID" }, + { "tar-autocreate-parents", 0, 0, G_OPTION_ARG_NONE, &tar_autocreate_parents, "When loading tar archives, automatically create parent directories as needed", NULL }, { "skip-if-unchanged", 0, 0, G_OPTION_ARG_NONE, &skip_if_unchanged, "If the contents are unchanged from previous commit, do nothing", NULL }, { NULL } }; @@ -187,6 +189,7 @@ ostree_builtin_commit (int argc, char **argv, GFile *repo_path, GError **error) { arg = ot_gfile_new_for_path (tree); if (!ostree_repo_stage_archive_to_mtree (repo, arg, mtree, modifier, + tar_autocreate_parents, cancellable, error)) goto out; } diff --git a/tests/t0006-libarchive.sh b/tests/t0006-libarchive.sh index 4173c97a..06a0bf9c 100755 --- a/tests/t0006-libarchive.sh +++ b/tests/t0006-libarchive.sh @@ -100,3 +100,13 @@ assert_file_has_content otherfile "not overwritten" assert_file_has_content subdir/original "original" assert_file_has_content subdir/new "new" echo "ok tar multicommit contents" + +cd ${test_tmpdir}/multicommit-files +tar -c -C files1 -z -f partial.tar.gz subdir/original +$OSTREE commit -s 'partial' -b partial --tar-autocreate-parents --tree=tar=partial.tar.gz +echo "ok tar partial commit" + +cd ${test_tmpdir} +$OSTREE checkout partial partial-checkout +cd partial-checkout +assert_file_has_content subdir/original "original"