diff --git a/Makefile-libostree-defines.am b/Makefile-libostree-defines.am index 6214633d..ac5eaa00 100644 --- a/Makefile-libostree-defines.am +++ b/Makefile-libostree-defines.am @@ -43,6 +43,7 @@ libostree_public_headers += \ src/libostree/ostree-ref.h \ src/libostree/ostree-remote.h \ src/libostree/ostree-repo-finder.h \ + src/libostree/ostree-repo-finder-config.h \ $(NULL) endif diff --git a/Makefile-libostree.am b/Makefile-libostree.am index a9331dd4..4b968abe 100644 --- a/Makefile-libostree.am +++ b/Makefile-libostree.am @@ -155,10 +155,12 @@ libostree_1_la_SOURCES += \ src/libostree/ostree-ref.h \ src/libostree/ostree-remote.h \ src/libostree/ostree-repo-finder.h \ + src/libostree/ostree-repo-finder-config.h \ $(NULL) else # if ENABLE_EXPERIMENTAL_API libostree_1_la_SOURCES += \ src/libostree/ostree-repo-finder.c \ + src/libostree/ostree-repo-finder-config.c \ $(NULL) endif @@ -235,7 +237,7 @@ OSTree_1_0_gir_INCLUDES = Gio-2.0 OSTree_1_0_gir_CFLAGS = $(libostree_1_la_CFLAGS) OSTree_1_0_gir_LIBS = libostree-1.la OSTree_1_0_gir_SCANNERFLAGS = --warn-all --identifier-prefix=Ostree --symbol-prefix=ostree -OSTree_1_0_gir_FILES = $(libostreeinclude_HEADERS) $(filter-out %-private.h %/ostree-soup-uri.h %/ostree-repo-finder.h,$(libostree_1_la_SOURCES)) +OSTree_1_0_gir_FILES = $(libostreeinclude_HEADERS) $(filter-out %-private.h %/ostree-soup-uri.h %/ostree-repo-finder.h %/ostree-repo-finder-config.h,$(libostree_1_la_SOURCES)) INTROSPECTION_GIRS += OSTree-1.0.gir gir_DATA += OSTree-1.0.gir typelib_DATA += OSTree-1.0.typelib diff --git a/Makefile-tests.am b/Makefile-tests.am index 4261fa7c..1cdf8826 100644 --- a/Makefile-tests.am +++ b/Makefile-tests.am @@ -193,6 +193,12 @@ _installed_or_uninstalled_test_programs = tests/test-varint tests/test-ot-unix-u tests/test-gpg-verify-result tests/test-checksum tests/test-lzma tests/test-rollsum \ tests/test-basic-c tests/test-sysroot-c tests/test-pull-c +if ENABLE_EXPERIMENTAL_API +test_programs += \ + tests/test-repo-finder-config \ + $(NULL) +endif + # An interactive tool noinst_PROGRAMS += tests/test-rollsum-cli @@ -219,6 +225,10 @@ tests_test_rollsum_SOURCES = src/libostree/ostree-rollsum.c tests/test-rollsum.c tests_test_rollsum_CFLAGS = $(TESTS_CFLAGS) $(OT_DEP_ZLIB_CFLAGS) tests_test_rollsum_LDADD = $(bupsplitpath) $(TESTS_LDADD) $(OT_DEP_ZLIB_LIBS) +tests_test_repo_finder_config_SOURCES = tests/test-repo-finder-config.c +tests_test_repo_finder_config_CFLAGS = $(TESTS_CFLAGS) +tests_test_repo_finder_config_LDADD = $(TESTS_LDADD) + tests_test_mutable_tree_CFLAGS = $(TESTS_CFLAGS) tests_test_mutable_tree_LDADD = $(TESTS_LDADD) diff --git a/apidoc/ostree-experimental-sections.txt b/apidoc/ostree-experimental-sections.txt index 7e9ed084..93210d56 100644 --- a/apidoc/ostree-experimental-sections.txt +++ b/apidoc/ostree-experimental-sections.txt @@ -49,6 +49,14 @@ ostree_repo_finder_get_type ostree_repo_finder_result_get_type +
+ostree-repo-finder-config +OstreeRepoFinderConfig +ostree_repo_finder_config_new + +ostree_repo_finder_config_get_type +
+
ostree-misc-experimental ostree_repo_get_collection_id diff --git a/src/libostree/libostree-experimental.sym b/src/libostree/libostree-experimental.sym index cda34322..79fcdbe9 100644 --- a/src/libostree/libostree-experimental.sym +++ b/src/libostree/libostree-experimental.sym @@ -47,6 +47,8 @@ global: ostree_collection_ref_new; ostree_repo_find_remotes_async; ostree_repo_find_remotes_finish; + ostree_repo_finder_config_get_type; + ostree_repo_finder_config_new; ostree_repo_finder_get_type; ostree_repo_finder_resolve_async; ostree_repo_finder_resolve_all_async; diff --git a/src/libostree/ostree-autocleanups.h b/src/libostree/ostree-autocleanups.h index 1f7716b2..ac0aa1fb 100644 --- a/src/libostree/ostree-autocleanups.h +++ b/src/libostree/ostree-autocleanups.h @@ -64,6 +64,7 @@ G_DEFINE_AUTOPTR_CLEANUP_FUNC (OstreeCollectionRef, ostree_collection_ref_free) G_DEFINE_AUTO_CLEANUP_FREE_FUNC (OstreeCollectionRefv, ostree_collection_ref_freev, NULL) G_DEFINE_AUTOPTR_CLEANUP_FUNC (OstreeRemote, ostree_remote_unref) G_DEFINE_AUTOPTR_CLEANUP_FUNC (OstreeRepoFinder, g_object_unref) +G_DEFINE_AUTOPTR_CLEANUP_FUNC (OstreeRepoFinderConfig, g_object_unref) G_DEFINE_AUTOPTR_CLEANUP_FUNC (OstreeRepoFinderResult, ostree_repo_finder_result_free) G_DEFINE_AUTO_CLEANUP_FREE_FUNC (OstreeRepoFinderResultv, ostree_repo_finder_result_freev, NULL) #endif /* OSTREE_ENABLE_EXPERIMENTAL_API */ diff --git a/src/libostree/ostree-core-private.h b/src/libostree/ostree-core-private.h index a8fbb8e1..4c117110 100644 --- a/src/libostree/ostree-core-private.h +++ b/src/libostree/ostree-core-private.h @@ -187,6 +187,9 @@ G_DEFINE_AUTO_CLEANUP_FREE_FUNC (OstreeCollectionRefv, ostree_collection_ref_fre G_DEFINE_AUTOPTR_CLEANUP_FUNC (OstreeRepoFinder, g_object_unref) G_DEFINE_AUTOPTR_CLEANUP_FUNC (OstreeRepoFinderResult, ostree_repo_finder_result_free) G_DEFINE_AUTO_CLEANUP_FREE_FUNC (OstreeRepoFinderResultv, ostree_repo_finder_result_freev, NULL) + +#include "ostree-repo-finder-config.h" +G_DEFINE_AUTOPTR_CLEANUP_FUNC (OstreeRepoFinderConfig, g_object_unref) #endif G_END_DECLS diff --git a/src/libostree/ostree-repo-finder-config.c b/src/libostree/ostree-repo-finder-config.c new file mode 100644 index 00000000..79a63536 --- /dev/null +++ b/src/libostree/ostree-repo-finder-config.c @@ -0,0 +1,235 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- + * + * Copyright © 2017 Endless Mobile, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + * Authors: + * - Philip Withnall + */ + +#include "config.h" + +#include +#include +#include +#include +#include + +#include "ostree-remote-private.h" +#include "ostree-repo.h" +#include "ostree-repo-private.h" +#include "ostree-repo-finder.h" +#include "ostree-repo-finder-config.h" + +/** + * SECTION:ostree-repo-finder-config + * @title: OstreeRepoFinderConfig + * @short_description: Finds remote repositories from ref names using the local + * repository configuration files + * @stability: Unstable + * @include: libostree/ostree-repo-finder-config.h + * + * #OstreeRepoFinderConfig is an implementation of #OstreeRepoFinder which looks + * refs up in locally configured remotes and returns remote URIs. + * Duplicate remote URIs are combined into a single #OstreeRepoFinderResult + * which lists multiple refs. + * + * For all the locally configured remotes which have an `collection-id` specified + * (see [ostree.repo-config(5)](man:ostree.repo-config(5))), it finds the + * intersection of their refs and the set of refs to resolve. If the + * intersection is non-empty, that remote is returned as a result. Remotes which + * do not have their `collection-id` key configured are ignored. + * + * Since: 2017.8 + */ + +static void ostree_repo_finder_config_iface_init (OstreeRepoFinderInterface *iface); + +struct _OstreeRepoFinderConfig +{ + GObject parent_instance; +}; + +G_DEFINE_TYPE_WITH_CODE (OstreeRepoFinderConfig, ostree_repo_finder_config, G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (OSTREE_TYPE_REPO_FINDER, ostree_repo_finder_config_iface_init)) + +static gint +results_compare_cb (gconstpointer a, + gconstpointer b) +{ + const OstreeRepoFinderResult *result_a = *((const OstreeRepoFinderResult **) a); + const OstreeRepoFinderResult *result_b = *((const OstreeRepoFinderResult **) b); + + return ostree_repo_finder_result_compare (result_a, result_b); +} + +static void +ostree_repo_finder_config_resolve_async (OstreeRepoFinder *finder, + const OstreeCollectionRef * const *refs, + OstreeRepo *parent_repo, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + g_autoptr(GTask) task = NULL; + g_autoptr(GPtrArray) results = NULL; + const gint priority = 100; /* arbitrarily chosen; lower than the others */ + gsize i, j; + g_autoptr(GHashTable) repo_name_to_refs = NULL; /* (element-type utf8 GHashTable) */ + GHashTable *supported_ref_to_checksum; /* (element-type OstreeCollectionRef utf8) */ + GHashTableIter iter; + const gchar *remote_name; + g_auto(GStrv) remotes = NULL; + gsize n_remotes = 0; + + task = g_task_new (finder, cancellable, callback, user_data); + g_task_set_source_tag (task, ostree_repo_finder_config_resolve_async); + results = g_ptr_array_new_with_free_func ((GDestroyNotify) ostree_repo_finder_result_free); + repo_name_to_refs = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, + (GDestroyNotify) g_hash_table_unref); + + /* List all remotes in this #OstreeRepo and see which of their ref lists + * intersect with @refs. */ + remotes = ostree_repo_remote_list (parent_repo, (guint *) &n_remotes); + + g_debug ("%s: Checking %" G_GSIZE_FORMAT " remotes", G_STRFUNC, n_remotes); + + for (i = 0; i < n_remotes; i++) + { + g_autoptr(GError) local_error = NULL; + g_autoptr(GHashTable) remote_refs = NULL; /* (element-type utf8 utf8) */ + const gchar *checksum; + g_autofree gchar *remote_collection_id = NULL; + + remote_name = remotes[i]; + + if (!ostree_repo_get_remote_option (parent_repo, remote_name, "collection-id", + NULL, &remote_collection_id, &local_error) || + !ostree_validate_collection_id (remote_collection_id, &local_error)) + { + g_debug ("Ignoring remote ‘%s’ due to no valid collection ID being configured for it: %s", + remote_name, local_error->message); + g_clear_error (&local_error); + continue; + } + + if (!ostree_repo_remote_list_refs (parent_repo, remote_name, &remote_refs, + cancellable, &local_error)) + { + g_debug ("Ignoring remote ‘%s’ due to error loading its refs: %s", + remote_name, local_error->message); + g_clear_error (&local_error); + continue; + } + + for (j = 0; refs[j] != NULL; j++) + { + if (g_strcmp0 (refs[j]->collection_id, remote_collection_id) == 0 && + g_hash_table_lookup_extended (remote_refs, refs[j]->ref_name, NULL, (gpointer *) &checksum)) + { + /* The requested ref is listed in the refs for this remote. Add + * the remote to the results, and the ref to its + * @supported_ref_to_checksum. */ + g_debug ("Resolved ref (%s, %s) to remote ‘%s’.", + refs[j]->collection_id, refs[j]->ref_name, remote_name); + + supported_ref_to_checksum = g_hash_table_lookup (repo_name_to_refs, remote_name); + + if (supported_ref_to_checksum == NULL) + { + supported_ref_to_checksum = g_hash_table_new_full (ostree_collection_ref_hash, + ostree_collection_ref_equal, + NULL, g_free); + g_hash_table_insert (repo_name_to_refs, (gpointer) remote_name, supported_ref_to_checksum /* transfer */); + } + + g_hash_table_insert (supported_ref_to_checksum, + (gpointer) refs[j], g_strdup (checksum)); + } + } + } + + /* Aggregate the results. */ + g_hash_table_iter_init (&iter, repo_name_to_refs); + + while (g_hash_table_iter_next (&iter, (gpointer *) &remote_name, (gpointer *) &supported_ref_to_checksum)) + { + g_autoptr(GError) local_error = NULL; + OstreeRemote *remote; + + /* We don’t know what last-modified timestamp the remote has without + * making expensive HTTP queries, so leave that information blank. We + * assume that the configuration which says the refs and commits in + * @supported_ref_to_checksum are in the repository is correct; the code + * in ostree_repo_find_remotes_async() will check that. */ + remote = _ostree_repo_get_remote_inherited (parent_repo, remote_name, &local_error); + if (remote == NULL) + { + g_debug ("Configuration for remote ‘%s’ could not be found. Ignoring.", + remote_name); + continue; + } + + g_ptr_array_add (results, ostree_repo_finder_result_new (remote, finder, priority, supported_ref_to_checksum, 0)); + } + + g_ptr_array_sort (results, results_compare_cb); + + g_task_return_pointer (task, g_steal_pointer (&results), (GDestroyNotify) g_ptr_array_unref); +} + +static GPtrArray * +ostree_repo_finder_config_resolve_finish (OstreeRepoFinder *finder, + GAsyncResult *result, + GError **error) +{ + g_return_val_if_fail (g_task_is_valid (result, finder), NULL); + return g_task_propagate_pointer (G_TASK (result), error); +} + +static void +ostree_repo_finder_config_init (OstreeRepoFinderConfig *self) +{ + /* Nothing to see here. */ +} + +static void +ostree_repo_finder_config_class_init (OstreeRepoFinderConfigClass *klass) +{ + /* Nothing to see here. */ +} + +static void +ostree_repo_finder_config_iface_init (OstreeRepoFinderInterface *iface) +{ + iface->resolve_async = ostree_repo_finder_config_resolve_async; + iface->resolve_finish = ostree_repo_finder_config_resolve_finish; +} + +/** + * ostree_repo_finder_config_new: + * + * Create a new #OstreeRepoFinderConfig. + * + * Returns: (transfer full): a new #OstreeRepoFinderConfig + * Since: 2017.8 + */ +OstreeRepoFinderConfig * +ostree_repo_finder_config_new (void) +{ + return g_object_new (OSTREE_TYPE_REPO_FINDER_CONFIG, NULL); +} diff --git a/src/libostree/ostree-repo-finder-config.h b/src/libostree/ostree-repo-finder-config.h new file mode 100644 index 00000000..28e6fc84 --- /dev/null +++ b/src/libostree/ostree-repo-finder-config.h @@ -0,0 +1,55 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- + * + * Copyright © 2017 Endless Mobile, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + * Authors: + * - Philip Withnall + */ + +#pragma once + +#include +#include +#include + +#include "ostree-repo-finder.h" +#include "ostree-types.h" + +G_BEGIN_DECLS + +#define OSTREE_TYPE_REPO_FINDER_CONFIG (ostree_repo_finder_config_get_type ()) + +/* Manually expanded version of the following, omitting autoptr support (for GLib < 2.44): +_OSTREE_PUBLIC +G_DECLARE_FINAL_TYPE (OstreeRepoFinderConfig, ostree_repo_finder_config, OSTREE, REPO_FINDER_CONFIG, GObject) */ + +_OSTREE_PUBLIC +GType ostree_repo_finder_config_get_type (void); + +G_GNUC_BEGIN_IGNORE_DEPRECATIONS +typedef struct _OstreeRepoFinderConfig OstreeRepoFinderConfig; +typedef struct { GObjectClass parent_class; } OstreeRepoFinderConfigClass; + +static inline OstreeRepoFinderConfig *OSTREE_REPO_FINDER_CONFIG (gpointer ptr) { return G_TYPE_CHECK_INSTANCE_CAST (ptr, ostree_repo_finder_config_get_type (), OstreeRepoFinderConfig); } +static inline gboolean OSTREE_IS_REPO_FINDER_CONFIG (gpointer ptr) { return G_TYPE_CHECK_INSTANCE_TYPE (ptr, ostree_repo_finder_config_get_type ()); } +G_GNUC_END_IGNORE_DEPRECATIONS + +_OSTREE_PUBLIC +OstreeRepoFinderConfig *ostree_repo_finder_config_new (void); + +G_END_DECLS diff --git a/src/libostree/ostree-repo-pull.c b/src/libostree/ostree-repo-pull.c index b4c565a8..56e4839e 100644 --- a/src/libostree/ostree-repo-pull.c +++ b/src/libostree/ostree-repo-pull.c @@ -41,6 +41,7 @@ #ifdef OSTREE_ENABLE_EXPERIMENTAL_API #include "ostree-repo-finder.h" +#include "ostree-repo-finder-config.h" #endif /* OSTREE_ENABLE_EXPERIMENTAL_API */ #include @@ -4085,6 +4086,7 @@ ostree_repo_find_remotes_async (OstreeRepo *self, g_autoptr(FindRemotesData) data = NULL; GMainContext *context; OstreeRepoFinder *default_finders[4] = { NULL, }; + g_autoptr(OstreeRepoFinder) finder_config = NULL; g_return_if_fail (OSTREE_IS_REPO (self)); g_return_if_fail (is_valid_collection_ref_array (refs)); @@ -4103,6 +4105,10 @@ ostree_repo_find_remotes_async (OstreeRepo *self, /* Are we using #OstreeRepoFinders provided by the user, or the defaults? */ if (finders == NULL) { + finder_config = OSTREE_REPO_FINDER (ostree_repo_finder_config_new ()); + + default_finders[0] = finder_config; + finders = default_finders; } diff --git a/src/libostree/ostree.h b/src/libostree/ostree.h index 935707e3..8d547041 100644 --- a/src/libostree/ostree.h +++ b/src/libostree/ostree.h @@ -38,6 +38,7 @@ #ifdef OSTREE_ENABLE_EXPERIMENTAL_API #include #include +#include #endif /* OSTREE_ENABLE_EXPERIMENTAL_API */ #include diff --git a/tests/.gitignore b/tests/.gitignore index 6fc06881..bf31dd01 100644 --- a/tests/.gitignore +++ b/tests/.gitignore @@ -14,4 +14,5 @@ test-mutable-tree test-ot-opt-utils test-ot-tool-util test-ot-unix-utils +test-repo-finder-config test-rollsum-cli diff --git a/tests/test-repo-finder-config.c b/tests/test-repo-finder-config.c new file mode 100644 index 00000000..dc083754 --- /dev/null +++ b/tests/test-repo-finder-config.c @@ -0,0 +1,327 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- + * + * Copyright © 2017 Endless Mobile, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + * Authors: + * - Philip Withnall + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include + +#include "libostreetest.h" +#include "ostree-autocleanups.h" +#include "ostree-repo-finder.h" +#include "ostree-repo-finder-config.h" + +/* Test fixture. Creates a temporary directory. */ +typedef struct +{ + OstreeRepo *parent_repo; /* owned */ + int working_dfd; /* owned */ + GFile *working_dir; /* owned */ +} Fixture; + +static void +setup (Fixture *fixture, + gconstpointer test_data) +{ + g_autofree gchar *tmp_name = NULL; + g_autoptr(GError) error = NULL; + + tmp_name = g_strdup ("test-repo-finder-config-XXXXXX"); + glnx_mkdtempat_open_in_system (tmp_name, 0700, &fixture->working_dfd, &error); + g_assert_no_error (error); + + g_test_message ("Using temporary directory: %s", tmp_name); + + glnx_shutil_mkdir_p_at (fixture->working_dfd, "repo", 0700, NULL, &error); + g_assert_no_error (error); + + g_autoptr(GFile) tmp_dir = g_file_new_for_path (g_get_tmp_dir ()); + fixture->working_dir = g_file_get_child (tmp_dir, tmp_name); + + fixture->parent_repo = ot_test_setup_repo (NULL, &error); + g_assert_no_error (error); +} + +static void +teardown (Fixture *fixture, + gconstpointer test_data) +{ + glnx_fd_close int parent_repo_dfd = -1; + g_autoptr(GError) error = NULL; + + /* Recursively remove the temporary directory. */ + glnx_shutil_rm_rf_at (fixture->working_dfd, ".", NULL, NULL); + + close (fixture->working_dfd); + fixture->working_dfd = -1; + + /* The repo also needs its source files to be removed. This is the inverse + * of setup_test_repository() in libtest.sh. */ + g_autofree gchar *parent_repo_path = g_file_get_path (ostree_repo_get_path (fixture->parent_repo)); + glnx_opendirat (-1, parent_repo_path, TRUE, &parent_repo_dfd, &error); + g_assert_no_error (error); + + glnx_shutil_rm_rf_at (parent_repo_dfd, "../files", NULL, NULL); + glnx_shutil_rm_rf_at (parent_repo_dfd, "../repo", NULL, NULL); + + g_clear_object (&fixture->working_dir); + g_clear_object (&fixture->parent_repo); +} + +/* Test the object constructor works at a basic level. */ +static void +test_repo_finder_config_init (void) +{ + g_autoptr(OstreeRepoFinderConfig) finder = NULL; + + /* Default everything. */ + finder = ostree_repo_finder_config_new (); +} + +static void +result_cb (GObject *source_object, + GAsyncResult *result, + gpointer user_data) +{ + GAsyncResult **result_out = user_data; + *result_out = g_object_ref (result); +} + +/* Test that no remotes are found if there are no config files in the refs + * directory. */ +static void +test_repo_finder_config_no_configs (Fixture *fixture, + gconstpointer test_data) +{ + g_autoptr(OstreeRepoFinderConfig) finder = NULL; + g_autoptr(GMainContext) context = NULL; + g_autoptr(GAsyncResult) result = NULL; + g_autoptr(GPtrArray) results = NULL; /* (element-type OstreeRepoFinderResult) */ + g_autoptr(GError) error = NULL; + const OstreeCollectionRef ref1 = { "org.example.Os", "exampleos/x86_64/standard" }; + const OstreeCollectionRef ref2 = { "org.example.Os", "exampleos/x86_64/buildmaster/standard" }; + const OstreeCollectionRef * const refs[] = { &ref1, &ref2, NULL }; + + context = g_main_context_new (); + g_main_context_push_thread_default (context); + + finder = ostree_repo_finder_config_new (); + + ostree_repo_finder_resolve_async (OSTREE_REPO_FINDER (finder), refs, + fixture->parent_repo, NULL, result_cb, &result); + + while (result == NULL) + g_main_context_iteration (context, TRUE); + + results = ostree_repo_finder_resolve_finish (OSTREE_REPO_FINDER (finder), + result, &error); + g_assert_no_error (error); + g_assert_nonnull (results); + g_assert_cmpuint (results->len, ==, 0); + + g_main_context_pop_thread_default (context); +} + +/* Add configuration for a remote named @remote_name, at @remote_uri, with a + * remote collection ID of @collection_id, to the given @repo. */ +static void +assert_create_remote_config (OstreeRepo *repo, + const gchar *remote_name, + const gchar *remote_uri, + const gchar *collection_id) +{ + g_autoptr(GError) error = NULL; + g_autoptr(GVariant) options = NULL; + + if (collection_id != NULL) + options = g_variant_new_parsed ("@a{sv} { 'collection-id': <%s> }", + collection_id); + + ostree_repo_remote_add (repo, remote_name, remote_uri, options, NULL, &error); + g_assert_no_error (error); +} + +static gchar *assert_create_remote (Fixture *fixture, + const gchar *collection_id, + ...) G_GNUC_NULL_TERMINATED; + +/* Create a new repository in a temporary directory with its collection ID set + * to @collection_id, and containing the refs given in @... (which must be + * %NULL-terminated). Return the `file://` URI of the new repository. */ +static gchar * +assert_create_remote (Fixture *fixture, + const gchar *collection_id, + ...) +{ + va_list args; + g_autoptr(GError) error = NULL; + const gchar *repo_name = (collection_id != NULL) ? collection_id : "no-collection"; + + glnx_shutil_mkdir_p_at (fixture->working_dfd, repo_name, 0700, NULL, &error); + g_assert_no_error (error); + + g_autoptr(GFile) repo_path = g_file_get_child (fixture->working_dir, repo_name); + g_autoptr(OstreeRepo) repo = ostree_repo_new (repo_path); + ostree_repo_set_collection_id (repo, collection_id, &error); + g_assert_no_error (error); + ostree_repo_create (repo, OSTREE_REPO_MODE_ARCHIVE_Z2, NULL, &error); + g_assert_no_error (error); + + /* Set up the refs from @.... */ + va_start (args, collection_id); + + for (const gchar *ref_name = va_arg (args, const gchar *); + ref_name != NULL; + ref_name = va_arg (args, const gchar *)) + { + OstreeCollectionRef collection_ref = { (gchar *) collection_id, (gchar *) ref_name }; + g_autofree gchar *checksum = NULL; + g_autoptr(OstreeMutableTree) mtree = NULL; + g_autoptr(OstreeRepoFile) repo_file = NULL; + + mtree = ostree_mutable_tree_new (); + ostree_repo_write_dfd_to_mtree (repo, AT_FDCWD, ".", mtree, NULL, NULL, &error); + g_assert_no_error (error); + ostree_repo_write_mtree (repo, mtree, (GFile **) &repo_file, NULL, &error); + g_assert_no_error (error); + + ostree_repo_write_commit (repo, NULL /* no parent */, ref_name, ref_name, + NULL /* no metadata */, repo_file, &checksum, + NULL, &error); + g_assert_no_error (error); + + if (collection_id != NULL) + ostree_repo_set_collection_ref_immediate (repo, &collection_ref, checksum, NULL, &error); + else + ostree_repo_set_ref_immediate (repo, NULL, ref_name, checksum, NULL, &error); + g_assert_no_error (error); + } + + va_end (args); + + /* Update the summary. */ + ostree_repo_regenerate_summary (repo, NULL /* no metadata */, NULL, &error); + g_assert_no_error (error); + + return g_file_get_uri (repo_path); +} + +/* Test resolving the refs against a collection of config files, which contain + * valid, invalid or duplicate repo information. */ +static void +test_repo_finder_config_mixed_configs (Fixture *fixture, + gconstpointer test_data) +{ + g_autoptr(OstreeRepoFinderConfig) finder = NULL; + g_autoptr(GMainContext) context = NULL; + g_autoptr(GAsyncResult) result = NULL; + g_autoptr(GPtrArray) results = NULL; /* (element-type OstreeRepoFinderResult) */ + g_autoptr(GError) error = NULL; + gsize i; + const OstreeCollectionRef ref0 = { "org.example.Collection0", "exampleos/x86_64/ref0" }; + const OstreeCollectionRef ref1 = { "org.example.Collection0", "exampleos/x86_64/ref1" }; + const OstreeCollectionRef ref2 = { "org.example.Collection1", "exampleos/x86_64/ref1" }; + const OstreeCollectionRef ref3 = { "org.example.Collection1", "exampleos/x86_64/ref2" }; + const OstreeCollectionRef ref4 = { "org.example.Collection2", "exampleos/x86_64/ref3" }; + const OstreeCollectionRef * const refs[] = { &ref0, &ref1, &ref2, &ref3, &ref4, NULL }; + + context = g_main_context_new (); + g_main_context_push_thread_default (context); + + /* Put together various ref configuration files. */ + g_autofree gchar *collection0_uri = assert_create_remote (fixture, "org.example.Collection0", + "exampleos/x86_64/ref0", + "exampleos/x86_64/ref1", + NULL); + g_autofree gchar *collection1_uri = assert_create_remote (fixture, "org.example.Collection1", + "exampleos/x86_64/ref2", + NULL); + g_autofree gchar *no_collection_uri = assert_create_remote (fixture, NULL, + "exampleos/x86_64/ref3", + NULL); + + assert_create_remote_config (fixture->parent_repo, "remote0", collection0_uri, "org.example.Collection0"); + assert_create_remote_config (fixture->parent_repo, "remote1", collection1_uri, "org.example.Collection1"); + assert_create_remote_config (fixture->parent_repo, "remote0-copy", collection0_uri, "org.example.Collection0"); + assert_create_remote_config (fixture->parent_repo, "remote1-bad-copy", collection1_uri, "org.example.NotCollection1"); + assert_create_remote_config (fixture->parent_repo, "remote2", no_collection_uri, NULL); + + finder = ostree_repo_finder_config_new (); + + /* Resolve the refs. */ + ostree_repo_finder_resolve_async (OSTREE_REPO_FINDER (finder), refs, + fixture->parent_repo, NULL, result_cb, &result); + + while (result == NULL) + g_main_context_iteration (context, TRUE); + + results = ostree_repo_finder_resolve_finish (OSTREE_REPO_FINDER (finder), + result, &error); + g_assert_no_error (error); + g_assert_nonnull (results); + g_assert_cmpuint (results->len, ==, 3); + + /* Check that the results are correct: the invalid refs should have been + * ignored, and the valid results canonicalised and deduplicated. */ + for (i = 0; i < results->len; i++) + { + const OstreeRepoFinderResult *result = g_ptr_array_index (results, i); + + if (g_strcmp0 (ostree_remote_get_name (result->remote), "remote0") == 0 || + g_strcmp0 (ostree_remote_get_name (result->remote), "remote0-copy") == 0) + { + g_assert_cmpuint (g_hash_table_size (result->ref_to_checksum), ==, 2); + g_assert_true (g_hash_table_contains (result->ref_to_checksum, &ref0)); + g_assert_true (g_hash_table_contains (result->ref_to_checksum, &ref1)); + } + else if (g_strcmp0 (ostree_remote_get_name (result->remote), "remote1") == 0) + { + g_assert_cmpuint (g_hash_table_size (result->ref_to_checksum), ==, 1); + g_assert_true (g_hash_table_contains (result->ref_to_checksum, &ref3)); + } + else + { + g_assert_not_reached (); + } + } + + g_main_context_pop_thread_default (context); +} + +int main (int argc, char **argv) +{ + setlocale (LC_ALL, ""); + g_test_init (&argc, &argv, NULL); + + g_test_add_func ("/repo-finder-config/init", test_repo_finder_config_init); + g_test_add ("/repo-finder-config/no-configs", Fixture, NULL, setup, + test_repo_finder_config_no_configs, teardown); + g_test_add ("/repo-finder-config/mixed-configs", Fixture, NULL, setup, + test_repo_finder_config_mixed_configs, teardown); + + return g_test_run(); +}