From 15ba2007a1d0e009eb0a0536a872adc46651166b Mon Sep 17 00:00:00 2001 From: Dan Nicholson Date: Tue, 12 Sep 2017 12:23:31 -0500 Subject: [PATCH] repo: Add OstreeRepoRemoteChange replace operation Add the OSTREE_REPO_REMOTE_CHANGE_REPLACE operation to the OstreeRepoRemoteChange enum. This operation will add a remote or replace an existing one. It respects the location of the remote configuration file when replacing and the remotes config dir settings when adding a new remote. Closes: #1166 Approved by: cgwalters --- src/libostree/ostree-repo.c | 85 ++++++++++++++++++++++++++++++++ src/libostree/ostree-repo.h | 4 +- tests/test-remotes-config-dir.js | 56 +++++++++++++++++++-- 3 files changed, 141 insertions(+), 4 deletions(-) diff --git a/src/libostree/ostree-repo.c b/src/libostree/ostree-repo.c index fc884481..37a6cdf0 100644 --- a/src/libostree/ostree-repo.c +++ b/src/libostree/ostree-repo.c @@ -1735,6 +1735,88 @@ ostree_repo_remote_delete (OstreeRepo *self, return impl_repo_remote_delete (self, NULL, FALSE, name, cancellable, error); } + +static gboolean +impl_repo_remote_replace (OstreeRepo *self, + GFile *sysroot, + const char *name, + const char *url, + GVariant *options, + GCancellable *cancellable, + GError **error) +{ + g_return_val_if_fail (name != NULL, FALSE); + g_return_val_if_fail (url != NULL, FALSE); + g_return_val_if_fail (options == NULL || g_variant_is_of_type (options, G_VARIANT_TYPE ("a{sv}")), FALSE); + + if (!ostree_validate_remote_name (name, error)) + return FALSE; + + g_autoptr(GError) local_error = NULL; + g_autoptr(OstreeRemote) remote = _ostree_repo_get_remote (self, name, &local_error); + if (remote == NULL) + { + if (!g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND)) + { + g_propagate_error (error, g_steal_pointer (&local_error)); + return FALSE; + } + g_clear_error (&local_error); + if (!impl_repo_remote_add (self, sysroot, FALSE, name, url, options, + cancellable, error)) + return FALSE; + } + else + { + /* Replace the entire option group */ + if (!g_key_file_remove_group (remote->options, remote->group, error)) + return FALSE; + + if (g_str_has_prefix (url, "metalink=")) + g_key_file_set_string (remote->options, remote->group, "metalink", + url + strlen ("metalink=")); + else + g_key_file_set_string (remote->options, remote->group, "url", url); + + if (options != NULL) + keyfile_set_from_vardict (remote->options, remote->group, options); + + /* Write out updated settings */ + if (remote->file != NULL) + { + gsize length; + g_autofree char *data = g_key_file_to_data (remote->options, &length, + NULL); + + if (!g_file_replace_contents (remote->file, data, length, + NULL, FALSE, 0, NULL, + cancellable, error)) + return FALSE; + } + else + { + g_autoptr(GKeyFile) config = ostree_repo_copy_config (self); + + /* Remove the existing group if it exists */ + if (!g_key_file_remove_group (config, remote->group, &local_error)) + { + if (!g_error_matches (local_error, G_KEY_FILE_ERROR, + G_KEY_FILE_ERROR_GROUP_NOT_FOUND)) + { + g_propagate_error (error, g_steal_pointer (&local_error)); + return FALSE; + } + } + + ot_keyfile_copy_group (remote->options, config, remote->group); + if (!ostree_repo_write_config (self, config, error)) + return FALSE; + } + } + + return TRUE; +} + /** * ostree_repo_remote_change: * @self: Repo @@ -1776,6 +1858,9 @@ ostree_repo_remote_change (OstreeRepo *self, case OSTREE_REPO_REMOTE_CHANGE_DELETE_IF_EXISTS: return impl_repo_remote_delete (self, sysroot, TRUE, name, cancellable, error); + case OSTREE_REPO_REMOTE_CHANGE_REPLACE: + return impl_repo_remote_replace (self, sysroot, name, url, options, + cancellable, error); } g_assert_not_reached (); } diff --git a/src/libostree/ostree-repo.h b/src/libostree/ostree-repo.h index 007cbedd..b92b7083 100644 --- a/src/libostree/ostree-repo.h +++ b/src/libostree/ostree-repo.h @@ -166,12 +166,14 @@ gboolean ostree_repo_remote_delete (OstreeRepo *self, * @OSTREE_REPO_REMOTE_CHANGE_ADD_IF_NOT_EXISTS: Like above, but do nothing if the remote exists * @OSTREE_REPO_REMOTE_CHANGE_DELETE: Delete a remote * @OSTREE_REPO_REMOTE_CHANGE_DELETE_IF_EXISTS: Delete a remote, do nothing if the remote does not exist + * @OSTREE_REPO_REMOTE_CHANGE_REPLACE: Add or replace a remote (Since: 2019.1) */ typedef enum { OSTREE_REPO_REMOTE_CHANGE_ADD, OSTREE_REPO_REMOTE_CHANGE_ADD_IF_NOT_EXISTS, OSTREE_REPO_REMOTE_CHANGE_DELETE, - OSTREE_REPO_REMOTE_CHANGE_DELETE_IF_EXISTS + OSTREE_REPO_REMOTE_CHANGE_DELETE_IF_EXISTS, + OSTREE_REPO_REMOTE_CHANGE_REPLACE, } OstreeRepoRemoteChange; _OSTREE_PUBLIC diff --git a/tests/test-remotes-config-dir.js b/tests/test-remotes-config-dir.js index 8cd61844..5588116b 100755 --- a/tests/test-remotes-config-dir.js +++ b/tests/test-remotes-config-dir.js @@ -34,7 +34,7 @@ function assertNotEquals(a, b) { throw new Error("assertion failed " + JSON.stringify(a) + " != " + JSON.stringify(b)); } -print('1..6') +print('1..9') let remotesDir = Gio.File.new_for_path('remotes.d'); remotesDir.make_directory(null); @@ -45,6 +45,10 @@ remoteConfig.set_value('remote "foo"', 'url', 'http://foo') let remoteConfigFile = remotesDir.get_child('foo.conf') remoteConfig.save_to_file(remoteConfigFile.get_path()) +let remoteOptions = GLib.Variant.new('a{sv}', { + 'branches': GLib.Variant.new('as', ['test']), +}); + // Use the full Repo constructor to set remotes-config-dir let repoFile = Gio.File.new_for_path('repo'); let repo = new OSTree.Repo({path: repoFile, @@ -60,7 +64,7 @@ print("ok read-remotes-config-dir"); // Adding a remote should not go in the remotes.d dir unless this is a // system repo or add-remotes-config-dir is set to true -repo.remote_add('bar', 'http://bar', null, null); +repo.remote_add('bar', 'http://bar', remoteOptions, null); remotes = repo.remote_list() assertNotEquals(remotes.indexOf('bar'), -1); assertEquals(remotesDir.get_child('bar.conf').query_exists(null), false); @@ -81,7 +85,7 @@ let repoConfig = repo.copy_config(); repoConfig.set_boolean('core', 'add-remotes-config-dir', true); repo.write_config(repoConfig); repo.reload_config(null); -repo.remote_add('baz', 'http://baz', null, null); +repo.remote_add('baz', 'http://baz', remoteOptions, null); remotes = repo.remote_list() assertNotEquals(remotes.indexOf('baz'), -1); assertEquals(remotesDir.get_child('baz.conf').query_exists(null), true); @@ -114,3 +118,49 @@ try { } print("ok config-remote-in-config-dir-fails"); + +// Replacing a non-existent remote should succeed. This should go in the +// config dir since add-remote-config-dir is set to true above +repo.remote_change(null, OSTree.RepoRemoteChange.REPLACE, + 'nonexistent', 'http://nonexistent', + null, null); +remotes = repo.remote_list(); +assertNotEquals(remotes.indexOf('nonexistent'), -1); +assertEquals(remotesDir.get_child('nonexistent.conf').query_exists(null), true); + +print("ok replace-missing-remote-succeeds"); + +// Test replacing remote options in config dir. This should remove the +// branches setting above +repo.remote_change(null, OSTree.RepoRemoteChange.REPLACE, 'baz', + 'http://baz2', null, null); +remoteConfigFile = remotesDir.get_child('baz.conf'); +remoteConfig = GLib.KeyFile.new(); +remoteConfig.load_from_file(remoteConfigFile.get_path(), + GLib.KeyFileFlags.NONE); +assertEquals(remoteConfig.get_value('remote "baz"', 'url'), 'http://baz2'); +try { + remoteConfig.get_string_list('remote "baz"', 'branches'); + throw new Error('baz remote should not have branches option'); +} catch (e) { + if (!(e.matches(GLib.KeyFileError, GLib.KeyFileError.KEY_NOT_FOUND))) + throw e; +} + +print("ok replace-remote-in-config-dir"); + +// Test replacing remote options in config file. This should remove the +// branches setting above +repo.remote_change(null, OSTree.RepoRemoteChange.REPLACE, 'bar', + 'http://bar2', null, null); +repoConfig = repo.get_config(); +assertEquals(repoConfig.get_value('remote "bar"', 'url'), 'http://bar2'); +try { + repoConfig.get_string_list('remote "bar"', 'branches'); + throw new Error('bar remote should not have branches option'); +} catch (e) { + if (!(e.matches(GLib.KeyFileError, GLib.KeyFileError.KEY_NOT_FOUND))) + throw e; +} + +print("ok replace-remote-in-config-file");