diff --git a/app/Makefile.am.inc b/app/Makefile.am.inc
index ee24d231b..08edf6327 100644
--- a/app/Makefile.am.inc
+++ b/app/Makefile.am.inc
@@ -5,6 +5,10 @@ bin_PROGRAMS += \
flatpak_SOURCES = \
app/flatpak-main.c \
app/flatpak-builtins.h \
+ app/flatpak-builtins-utils.h \
+ app/flatpak-builtins-utils.c \
+ app/flatpak-transaction.h \
+ app/flatpak-transaction.c \
app/flatpak-builtins-add-remote.c \
app/flatpak-builtins-delete-remote.c \
app/flatpak-builtins-list-remotes.c \
diff --git a/app/flatpak-builtins-install.c b/app/flatpak-builtins-install.c
index 5c40de40f..9f0ee3549 100644
--- a/app/flatpak-builtins-install.c
+++ b/app/flatpak-builtins-install.c
@@ -32,6 +32,8 @@
#include "libglnx/libglnx.h"
#include "flatpak-builtins.h"
+#include "flatpak-builtins-utils.h"
+#include "flatpak-transaction.h"
#include "flatpak-utils.h"
#include "lib/flatpak-error.h"
#include "flatpak-chain-input-stream.h"
@@ -53,7 +55,7 @@ static GOptionEntry options[] = {
{ "no-pull", 0, 0, G_OPTION_ARG_NONE, &opt_no_pull, N_("Don't pull, only install from local cache"), NULL },
{ "no-deploy", 0, 0, G_OPTION_ARG_NONE, &opt_no_deploy, N_("Don't deploy, only download to local cache"), NULL },
{ "no-related", 0, 0, G_OPTION_ARG_NONE, &opt_no_related, N_("Don't install related refs"), NULL },
- { "no-deps", 0, 0, G_OPTION_ARG_NONE, &opt_no_deps, N_("Don't verify runtime dependencies"), NULL },
+ { "no-deps", 0, 0, G_OPTION_ARG_NONE, &opt_no_deps, N_("Don't verify/install runtime dependencies"), NULL },
{ "runtime", 0, 0, G_OPTION_ARG_NONE, &opt_runtime, N_("Look for runtime with the specified name"), NULL },
{ "app", 0, 0, G_OPTION_ARG_NONE, &opt_app, N_("Look for app with the specified name"), NULL },
{ "bundle", 0, 0, G_OPTION_ARG_NONE, &opt_bundle, N_("Install from local bundle file"), NULL },
@@ -142,78 +144,6 @@ install_bundle (FlatpakDir *dir,
return TRUE;
}
-static gboolean
-do_install (FlatpakDir *dir,
- gboolean no_pull,
- gboolean no_deploy,
- const char *ref,
- const char *remote_name,
- const char **opt_subpaths,
- GCancellable *cancellable,
- GError **error)
-{
- g_autoptr(GPtrArray) related = NULL;
- const char *slash;
- int i;
-
- slash = strchr (ref, '/');
- g_print (_("Installing: %s\n"), slash + 1);
-
- if (!flatpak_dir_install (dir,
- opt_no_pull,
- opt_no_deploy,
- ref, remote_name,
- (const char **)opt_subpaths,
- NULL,
- cancellable, error))
- return FALSE;
-
- if (!opt_no_related)
- {
- g_autoptr(GError) local_error = NULL;
-
- if (opt_no_pull)
- related = flatpak_dir_find_local_related (dir, ref, remote_name, NULL, &local_error);
- else
- related = flatpak_dir_find_remote_related (dir, ref, remote_name, NULL, &local_error);
- if (related == NULL)
- {
- g_printerr (_("Warning: Problem looking for related refs: %s\n"), local_error->message);
- g_clear_error (&local_error);
- }
- else
- {
- for (i = 0; i < related->len; i++)
- {
- FlatpakRelated *rel = g_ptr_array_index (related, i);
- g_auto(GStrv) parts = NULL;
-
- if (!rel->download)
- continue;
-
- parts = g_strsplit (rel->ref, "/", 0);
-
- g_print (_("Installing related: %s\n"), parts[1]);
-
- if (!flatpak_dir_install_or_update (dir,
- opt_no_pull,
- opt_no_deploy,
- rel->ref, remote_name,
- (const char **)rel->subpaths,
- NULL,
- cancellable, &local_error))
- {
- if (!g_error_matches (local_error, FLATPAK_ERROR, FLATPAK_ERROR_ALREADY_INSTALLED))
- g_printerr (_("Warning: Failed to install related ref %s: %s\n"),
- rel->ref, local_error->message);
- g_clear_error (&local_error);
- }
- }
- }
- }
-
- return TRUE;
-}
static gboolean
install_from (FlatpakDir *dir,
@@ -232,6 +162,7 @@ install_from (FlatpakDir *dir,
g_auto(GStrv) parts = NULL;
const char *slash;
FlatpakDir *clone;
+ g_autoptr(FlatpakTransaction) transaction = NULL;
if (argc < 2)
return usage_error (context, _("Filename must be specified"), error);
@@ -259,30 +190,13 @@ install_from (FlatpakDir *dir,
slash = strchr (ref, '/');
g_print (_("Installing: %s\n"), slash + 1);
- if (!do_install (clone,
- opt_no_pull,
- opt_no_deploy,
- ref, remote,
- (const char **)opt_subpaths,
- cancellable, error))
+ transaction = flatpak_transaction_new (dir, opt_no_pull, opt_no_deploy,
+ !opt_no_deps, !opt_no_related);
+
+ if (!flatpak_transaction_add_install (transaction, remote, ref, (const char **)opt_subpaths, error))
return FALSE;
- return TRUE;
-}
-
-static gboolean
-looks_like_branch (const char *branch)
-{
- /* In particular, / is not a valid branch char, so
- this lets us distinguish full or partial refs as
- non-branches. */
- if (!flatpak_is_valid_branch (branch, NULL))
- return FALSE;
-
- /* Dots are allowed in branches, but not really used much, while
- they are required for app ids, so thats a good check to
- distinguish the two */
- if (strchr (branch, '.') != NULL)
+ if (!flatpak_transaction_run (transaction, TRUE, cancellable, error))
return FALSE;
return TRUE;
@@ -293,16 +207,17 @@ flatpak_builtin_install (int argc, char **argv, GCancellable *cancellable, GErro
{
g_autoptr(GOptionContext) context = NULL;
g_autoptr(FlatpakDir) dir = NULL;
- const char *repository;
+ const char *remote;
char **prefs = NULL;
int i, n_prefs;
g_autofree char *target_branch = NULL;
g_autofree char *default_branch = NULL;
FlatpakKinds kinds;
+ g_autoptr(FlatpakTransaction) transaction = NULL;
g_autoptr(GPtrArray) refs = NULL;
g_autoptr(GHashTable) refs_hash = NULL;
- context = g_option_context_new (_("REPOSITORY REF... - Install applications or runtimes"));
+ context = g_option_context_new (_("REMOTE REF... - Install applications or runtimes"));
g_option_context_set_translation_domain (context, GETTEXT_PACKAGE);
if (!flatpak_option_context_parse (context, options, &argc, &argv, 0, &dir, cancellable, error))
@@ -315,24 +230,25 @@ flatpak_builtin_install (int argc, char **argv, GCancellable *cancellable, GErro
return install_from (dir, context, argc, argv, cancellable, error);
if (argc < 3)
- return usage_error (context, _("REPOSITORY and REF must be specified"), error);
+ return usage_error (context, _("REMOTE and REF must be specified"), error);
- repository = argv[1];
+ remote = argv[1];
prefs = &argv[2];
n_prefs = argc - 2;
- /* Backwards compat for old "REPOSITORY NAME [BRANCH]" argument version */
+ /* Backwards compat for old "REMOTE NAME [BRANCH]" argument version */
if (argc == 4 && looks_like_branch (argv[3]))
{
target_branch = g_strdup (argv[3]);
n_prefs = 1;
}
- default_branch = flatpak_dir_get_remote_default_branch (dir, repository);
+ default_branch = flatpak_dir_get_remote_default_branch (dir, remote);
kinds = flatpak_kinds_from_bools (opt_app, opt_runtime);
- refs = g_ptr_array_new_with_free_func (g_free);
- refs_hash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
+ transaction = flatpak_transaction_new (dir, opt_no_pull, opt_no_deploy,
+ !opt_no_deps, !opt_no_related);
+
for (i = 0; i < n_prefs; i++)
{
const char *pref = prefs[i];
@@ -342,54 +258,24 @@ flatpak_builtin_install (int argc, char **argv, GCancellable *cancellable, GErro
g_autofree char *branch = NULL;
FlatpakKinds kind;
g_autofree char *ref = NULL;
- g_autofree char *metadata = NULL;
- g_autoptr(GKeyFile) metakey = NULL;
+ g_autofree char *runtime_ref = NULL;
if (!flatpak_split_partial_ref_arg (pref, kinds, opt_arch, target_branch,
&matched_kinds, &id, &arch, &branch, error))
return FALSE;
- ref = flatpak_dir_find_remote_ref (dir, repository, id, branch, default_branch, arch,
+ ref = flatpak_dir_find_remote_ref (dir, remote, id, branch, default_branch, arch,
matched_kinds, &kind, cancellable, error);
if (ref == NULL)
return FALSE;
- metakey = g_key_file_new ();
- if (!opt_no_deps &&
- flatpak_dir_fetch_ref_cache (dir, repository, ref, NULL, NULL, &metadata, cancellable, NULL) &&
- g_key_file_load_from_data (metakey, metadata, -1, 0, NULL))
- {
- g_autofree char *runtime_ref = NULL;
- g_autofree char *full_runtime_ref = NULL;
- runtime_ref = g_key_file_get_string (metakey, "Application", "runtime", NULL);
- full_runtime_ref = g_strconcat ("runtime/", runtime_ref, NULL);
- if (runtime_ref && !g_hash_table_lookup_extended (refs_hash, full_runtime_ref, NULL, NULL))
- {
- g_autoptr(FlatpakDeploy) deploy = flatpak_find_deploy_for_ref (full_runtime_ref, NULL, NULL);
-
- if (deploy == NULL)
- return flatpak_fail (error,
- "The Application %s requires the runtime %s which is not installed",
- id, runtime_ref);
- }
- }
-
- if (g_hash_table_insert (refs_hash, g_strdup (ref), NULL))
- g_ptr_array_add (refs, g_steal_pointer (&ref));
- }
-
- for (i = 0; i < refs->len; i++)
- {
- const char *ref = g_ptr_array_index (refs, i);
- if (!do_install (dir,
- opt_no_pull,
- opt_no_deploy,
- ref, repository,
- (const char **)opt_subpaths,
- cancellable, error))
+ if (!flatpak_transaction_add_install (transaction, remote, ref, (const char **)opt_subpaths, error))
return FALSE;
}
+ if (!flatpak_transaction_run (transaction, TRUE, cancellable, error))
+ return FALSE;
+
return TRUE;
}
diff --git a/app/flatpak-builtins-uninstall.c b/app/flatpak-builtins-uninstall.c
index 2531411c8..1f6e804f1 100644
--- a/app/flatpak-builtins-uninstall.c
+++ b/app/flatpak-builtins-uninstall.c
@@ -30,6 +30,7 @@
#include "libglnx/libglnx.h"
#include "flatpak-builtins.h"
+#include "flatpak-builtins-utils.h"
#include "flatpak-utils.h"
static char *opt_arch;
@@ -49,24 +50,6 @@ static GOptionEntry options[] = {
{ NULL }
};
-static gboolean
-looks_like_branch (const char *branch)
-{
- /* In particular, / is not a valid branch char, so
- this lets us distinguish full or partial refs as
- non-branches. */
- if (!flatpak_is_valid_branch (branch, NULL))
- return FALSE;
-
- /* Dots are allowed in branches, but not really used much, while
- they are required for app ids, so thats a good check to
- distinguish the two */
- if (strchr (branch, '.') != NULL)
- return FALSE;
-
- return TRUE;
-}
-
gboolean
flatpak_builtin_uninstall (int argc, char **argv, GCancellable *cancellable, GError **error)
{
diff --git a/app/flatpak-builtins-update.c b/app/flatpak-builtins-update.c
index 1b18357ce..30acb70ed 100644
--- a/app/flatpak-builtins-update.c
+++ b/app/flatpak-builtins-update.c
@@ -30,6 +30,8 @@
#include "libglnx/libglnx.h"
#include "flatpak-builtins.h"
+#include "flatpak-builtins-utils.h"
+#include "flatpak-transaction.h"
#include "flatpak-utils.h"
#include "flatpak-error.h"
@@ -40,6 +42,7 @@ static gboolean opt_force_remove;
static gboolean opt_no_pull;
static gboolean opt_no_deploy;
static gboolean opt_no_related;
+static gboolean opt_no_deps;
static gboolean opt_runtime;
static gboolean opt_app;
static gboolean opt_appstream;
@@ -51,6 +54,7 @@ static GOptionEntry options[] = {
{ "no-pull", 0, 0, G_OPTION_ARG_NONE, &opt_no_pull, N_("Don't pull, only update from local cache"), NULL },
{ "no-deploy", 0, 0, G_OPTION_ARG_NONE, &opt_no_deploy, N_("Don't deploy, only download to local cache"), NULL },
{ "no-related", 0, 0, G_OPTION_ARG_NONE, &opt_no_related, N_("Don't update related refs"), NULL},
+ { "no-deps", 0, 0, G_OPTION_ARG_NONE, &opt_no_deps, N_("Don't verify/install runtime dependencies"), NULL },
{ "runtime", 0, 0, G_OPTION_ARG_NONE, &opt_runtime, N_("Look for runtime with the specified name"), NULL },
{ "app", 0, 0, G_OPTION_ARG_NONE, &opt_app, N_("Look for app with the specified name"), NULL },
{ "appstream", 0, 0, G_OPTION_ARG_NONE, &opt_appstream, N_("Update appstream for remote"), NULL },
@@ -96,138 +100,6 @@ update_appstream (FlatpakDir *dir, const char *remote, GCancellable *cancellable
return TRUE;
}
-static gboolean
-do_update (FlatpakDir * dir,
- const char *ref,
- GCancellable *cancellable,
- GError **error)
-{
- g_auto(GStrv) parts = flatpak_decompose_ref (ref, error);
- g_autofree char *repository = NULL;
- g_autoptr(GPtrArray) related = NULL;
- g_autoptr(GError) update_error = NULL;
- int i;
-
- if (parts == NULL)
- return FALSE;
-
- repository = flatpak_dir_get_origin (dir, ref, cancellable, error);
- if (repository == NULL)
- return FALSE;
-
- if (strcmp (parts[0], "app") == 0)
- g_print (_("Updating application %s, branch %s\n"), parts[1], parts[3]);
- else
- g_print (_("Updating runtime %s, branch %s\n"), parts[1], parts[3]);
-
- if (flatpak_dir_get_remote_disabled (dir, repository))
- {
- g_print (_("Remote %s disabled\n"), repository);
- return TRUE;
- }
-
- if (!flatpak_dir_update (dir,
- opt_no_pull,
- opt_no_deploy,
- ref, repository, opt_commit, (const char **)opt_subpaths,
- NULL,
- cancellable, &update_error))
- {
- if (g_error_matches (update_error, FLATPAK_ERROR,
- FLATPAK_ERROR_ALREADY_INSTALLED))
- g_print (_("No updates.\n"));
- else
- g_printerr ("Error updating: %s\n", update_error->message);
- }
- else
- {
- g_autoptr(GVariant) deploy_data = NULL;
- g_autofree char *commit = NULL;
- deploy_data = flatpak_dir_get_deploy_data (dir, ref, NULL, NULL);
- commit = g_strndup (flatpak_deploy_data_get_commit (deploy_data), 12);
- g_print (_("Now at %s.\n"), commit);
- }
-
-
- if (!opt_no_related)
- {
- g_autoptr(GError) local_error = NULL;
-
- if (opt_no_pull)
- related = flatpak_dir_find_local_related (dir, ref, repository, NULL,
- &local_error);
- else
- related = flatpak_dir_find_remote_related (dir, ref, repository, NULL,
- &local_error);
- if (related == NULL)
- {
- g_printerr (_("Warning: Problem looking for related refs: %s\n"),
- local_error->message);
- g_clear_error (&local_error);
- }
- else
- {
- for (i = 0; i < related->len; i++)
- {
- FlatpakRelated *rel = g_ptr_array_index (related, i);
- g_autoptr(GError) local_error = NULL;
- g_auto(GStrv) parts = NULL;
-
- if (!rel->download)
- continue;
-
- parts = g_strsplit (rel->ref, "/", 0);
-
- g_print (_("Updating related: %s\n"), parts[1]);
-
- if (!flatpak_dir_install_or_update (dir,
- opt_no_pull,
- opt_no_deploy,
- rel->ref, repository,
- (const char **)rel->subpaths,
- NULL,
- cancellable, &local_error))
- {
- if (g_error_matches (local_error, FLATPAK_ERROR,
- FLATPAK_ERROR_ALREADY_INSTALLED))
- g_print (_("No updates.\n"));
- else
- g_printerr ("Error updating: %s\n", local_error->message);
- g_clear_error (&local_error);
- }
- else
- {
- g_autoptr(GVariant) deploy_data = NULL;
- g_autofree char *commit = NULL;
- deploy_data = flatpak_dir_get_deploy_data (dir, rel->ref, NULL, NULL);
- commit = g_strndup (flatpak_deploy_data_get_commit (deploy_data), 12);
- g_print (_("Now at %s.\n"), commit);
- }
- }
- }
- }
-
- return TRUE;
-}
-
-static gboolean
-looks_like_branch (const char *branch)
-{
- /* In particular, / is not a valid branch char, so
- this lets us distinguish full or partial refs as
- non-branches. */
- if (!flatpak_is_valid_branch (branch, NULL))
- return FALSE;
-
- /* Dots are allowed in branches, but not really used much, while
- they are required for app ids, so thats a good check to
- distinguish the two */
- if (strchr (branch, '.') != NULL)
- return FALSE;
-
- return TRUE;
-}
-
gboolean
flatpak_builtin_update (int argc,
char **argv,
@@ -239,10 +111,8 @@ flatpak_builtin_update (int argc,
char **prefs = NULL;
int i, j, n_prefs;
const char *default_branch = NULL;
- gboolean failed = FALSE;
FlatpakKinds kinds;
- g_autoptr(GHashTable) update_refs_hash = NULL;
- g_autoptr(GPtrArray) update_refs = NULL;
+ g_autoptr(FlatpakTransaction) transaction = NULL;
context = g_option_context_new (_("[REF...] - Update applications or runtimes"));
g_option_context_set_translation_domain (context, GETTEXT_PACKAGE);
@@ -263,10 +133,12 @@ flatpak_builtin_update (int argc,
n_prefs = 1;
}
- update_refs = g_ptr_array_new_with_free_func (g_free);
- update_refs_hash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
+ transaction = flatpak_transaction_new (dir, opt_no_pull, opt_no_deploy,
+ !opt_no_deps, !opt_no_related);
kinds = flatpak_kinds_from_bools (opt_app, opt_runtime);
+ g_print ("Looking for updates...\n");
+
for (j = 0; j == 0 || j < n_prefs; j++)
{
const char *pref = NULL;
@@ -313,8 +185,8 @@ flatpak_builtin_update (int argc,
continue;
found = TRUE;
- if (g_hash_table_insert (update_refs_hash, g_strdup (refs[i]), NULL))
- g_ptr_array_add (update_refs, g_strdup (refs[i]));
+ if (!flatpak_transaction_add_update (transaction, refs[i], (const char **)opt_subpaths, error))
+ return FALSE;
}
}
@@ -344,8 +216,8 @@ flatpak_builtin_update (int argc,
continue;
found = TRUE;
- if (g_hash_table_insert (update_refs_hash, g_strdup (refs[i]), NULL))
- g_ptr_array_add (update_refs, g_strdup (refs[i]));
+ if (!flatpak_transaction_add_update (transaction, refs[i], (const char **)opt_subpaths, error))
+ return FALSE;
}
}
@@ -357,19 +229,8 @@ flatpak_builtin_update (int argc,
}
}
- for (i = 0; i < update_refs->len; i++)
- {
- const char *ref = (char *)g_ptr_array_index (update_refs, i);
- g_autoptr(GError) local_error = NULL;
- if (!do_update (dir, ref, cancellable, &local_error))
- {
- g_printerr (_("Error updating: %s\n"), local_error->message);
- failed = TRUE;
- }
- }
-
- if (failed)
- return flatpak_fail (error, _("One or more updates failed"));
+ if (!flatpak_transaction_run (transaction, FALSE, cancellable, error))
+ return FALSE;
return TRUE;
}
diff --git a/app/flatpak-builtins-utils.c b/app/flatpak-builtins-utils.c
new file mode 100644
index 000000000..7fa5e4858
--- /dev/null
+++ b/app/flatpak-builtins-utils.c
@@ -0,0 +1,45 @@
+/*
+ * Copyright © 2014 Red Hat, Inc
+ *
+ * This program 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.1 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, see .
+ *
+ * Authors:
+ * Alexander Larsson
+ */
+
+#include "config.h"
+
+#include
+
+#include "flatpak-builtins-utils.h"
+#include "flatpak-utils.h"
+
+
+gboolean
+looks_like_branch (const char *branch)
+{
+ /* In particular, / is not a valid branch char, so
+ this lets us distinguish full or partial refs as
+ non-branches. */
+ if (!flatpak_is_valid_branch (branch, NULL))
+ return FALSE;
+
+ /* Dots are allowed in branches, but not really used much, while
+ they are required for app ids, so thats a good check to
+ distinguish the two */
+ if (strchr (branch, '.') != NULL)
+ return FALSE;
+
+ return TRUE;
+}
diff --git a/app/flatpak-builtins-utils.h b/app/flatpak-builtins-utils.h
new file mode 100644
index 000000000..11a90e214
--- /dev/null
+++ b/app/flatpak-builtins-utils.h
@@ -0,0 +1,29 @@
+/*
+ * Copyright © 2014 Red Hat, Inc
+ *
+ * This program 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.1 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, see .
+ *
+ * Authors:
+ * Alexander Larsson
+ */
+
+#ifndef __FLATPAK_BUILTINS_UTILS_H__
+#define __FLATPAK_BUILTINS_UTILS_H__
+
+#include
+#include "libglnx/libglnx.h"
+
+gboolean looks_like_branch (const char *branch);
+
+#endif /* __FLATPAK_BUILTINS_UTILS_H__ */
diff --git a/app/flatpak-transaction.c b/app/flatpak-transaction.c
new file mode 100644
index 000000000..198e33a77
--- /dev/null
+++ b/app/flatpak-transaction.c
@@ -0,0 +1,546 @@
+/*
+ * Copyright © 2016 Red Hat, Inc
+ *
+ * This program 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.1 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, see .
+ *
+ * Authors:
+ * Alexander Larsson
+ */
+
+#include "config.h"
+
+#include
+
+#include "flatpak-transaction.h"
+#include "flatpak-utils.h"
+#include "flatpak-builtins-utils.h"
+#include "flatpak-error.h"
+
+typedef struct FlatpakTransactionOp FlatpakTransactionOp;
+
+struct FlatpakTransactionOp {
+ char *remote;
+ char *ref;
+ char **subpaths;
+ char *commit; /* TODO: handle */
+ gboolean update;
+ gboolean install;
+ gboolean non_fatal;
+};
+
+struct FlatpakTransaction {
+ FlatpakDir *dir;
+ GHashTable *refs;
+ GList *ops;
+
+ gboolean no_pull;
+ gboolean no_deploy;
+ gboolean add_deps;
+ gboolean add_related;
+};
+
+
+static gboolean
+ref_is_installed (const char *ref)
+{
+ g_autoptr(FlatpakDeploy) deploy = NULL;
+ deploy = flatpak_find_deploy_for_ref (ref, NULL, NULL);
+ return deploy != NULL;
+}
+
+static gboolean
+dir_ref_is_installed (FlatpakDir *dir, const char *ref, char **remote_out)
+{
+ g_autoptr(GVariant) deploy_data = NULL;
+
+ deploy_data = flatpak_dir_get_deploy_data (dir, ref, NULL, NULL);
+ if (deploy_data == NULL)
+ return FALSE;
+
+ if (remote_out)
+ *remote_out = g_strdup (flatpak_deploy_data_get_origin (deploy_data));
+ return TRUE;
+}
+
+static char *
+transaction_fetch_runtime_ref (FlatpakDir *dir, const char *remote, const char *ref)
+{
+ g_autofree char *metadata = NULL;
+ g_autoptr(GKeyFile) metakey = NULL;
+ g_autofree char *runtime_ref = NULL;
+
+ if (!g_str_has_prefix (ref, "app/"))
+ return NULL;
+
+ if (!flatpak_dir_fetch_ref_cache (dir, remote, ref, NULL, NULL, &metadata, NULL, NULL))
+ return NULL;
+
+ metakey = g_key_file_new ();
+ if (!g_key_file_load_from_data (metakey, metadata, -1, 0, NULL))
+ return NULL;
+
+ runtime_ref = g_key_file_get_string (metakey, "Application", "runtime", NULL);
+
+ return g_steal_pointer (&runtime_ref);
+}
+
+
+static FlatpakTransactionOp *
+flatpak_transaction_operation_new (const char *remote,
+ const char *ref,
+ const char **subpaths,
+ gboolean install,
+ gboolean update)
+{
+ FlatpakTransactionOp *self = g_new0 (FlatpakTransactionOp, 1);
+
+ self->remote = g_strdup (remote);
+ self->ref = g_strdup (ref);
+ self->subpaths = g_strdupv ((char **)subpaths);
+ self->update = update;
+ self->install = install;
+
+ return self;
+}
+
+static void
+flatpak_transaction_operation_free (FlatpakTransactionOp *self)
+{
+ g_free (self->remote);
+ g_free (self->ref);
+ g_free (self->commit);
+ g_strfreev (self->subpaths);
+ g_free (self);
+}
+
+FlatpakTransaction *
+flatpak_transaction_new (FlatpakDir *dir,
+ gboolean no_pull,
+ gboolean no_deploy,
+ gboolean add_deps,
+ gboolean add_related)
+{
+ FlatpakTransaction *t = g_new0 (FlatpakTransaction, 1);
+
+ t->dir = g_object_ref (dir);
+ t->refs = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
+
+ t->no_pull = no_pull;
+ t->no_deploy = no_deploy;
+ t->add_deps = add_deps;
+ t->add_related = add_related;
+ return t;
+}
+
+void
+flatpak_transaction_free (FlatpakTransaction *self)
+{
+ g_hash_table_unref (self->refs);
+ g_list_free_full (self->ops, (GDestroyNotify)flatpak_transaction_operation_free);
+ g_object_unref (self->dir);
+ g_free (self);
+}
+
+gboolean
+flatpak_transaction_contains_ref (FlatpakTransaction *self,
+ const char *ref)
+{
+ FlatpakTransactionOp *op;
+
+ op = g_hash_table_lookup (self->refs, ref);
+
+ return op != NULL;
+}
+
+
+static char *
+subpaths_to_string (const char **subpaths)
+{
+ GString *s = NULL;
+ int i;
+
+ if (subpaths == NULL)
+ return g_strdup ("[$old]");
+
+ if (*subpaths == 0)
+ return g_strdup ("[*]");
+
+ s = g_string_new ("[");
+ for (i = 0; subpaths[i] != NULL; i++)
+ {
+ if (i != 0)
+ g_string_append (s, ", ");
+ g_string_append (s, subpaths[i]);
+ }
+ g_string_append (s, "]");
+
+ return g_string_free (s, FALSE);
+}
+
+FlatpakTransactionOp *
+flatpak_transaction_add_op (FlatpakTransaction *self,
+ const char *remote,
+ const char *ref,
+ const char **subpaths,
+ gboolean install,
+ gboolean update)
+{
+ FlatpakTransactionOp *op;
+ g_autofree char *subpaths_str = NULL;
+ const char *opname;
+
+ if (install)
+ {
+ if (update)
+ opname = "install/update";
+ else
+ opname = "install";
+ }
+ else
+ opname = "update";
+ subpaths_str = subpaths_to_string (subpaths);
+ g_debug ("Transaction: %s %s:%s%s",
+ opname, remote, ref, subpaths_str);
+
+ op = g_hash_table_lookup (self->refs, ref);
+ if (op != NULL)
+ {
+ /* Only override subpaths if already specified,
+ we always want the un-subpathed to win if specified. */
+ if (op->subpaths != NULL && op->subpaths[0] != NULL && subpaths != NULL)
+ {
+ g_strfreev (op->subpaths);
+ op->subpaths = g_strdupv ((char **)subpaths);
+ }
+
+ return op;
+ }
+
+ op = flatpak_transaction_operation_new (remote, ref, subpaths, install, update);
+ g_hash_table_insert (self->refs, g_strdup (ref), op);
+ self->ops = g_list_prepend (self->ops, op);
+
+ return op;
+}
+
+static char *
+ask_for_remote (const char **remotes)
+{
+ int n_remotes = g_strv_length ((char **)remotes);
+ int chosen = 0;
+ int i;
+
+ if (n_remotes == 1)
+ {
+ if (flatpak_yes_no_prompt (_("Found in remote %s, do you want to install it?"), remotes[0]))
+ chosen = 1;
+ }
+ else
+ {
+ g_print (_("Found in several remotes:\n"));
+ for (i = 0; remotes[i] != NULL; i++)
+ {
+ g_print ("%d) %s\n", i + 1, remotes[i]);
+ }
+ chosen = flatpak_number_prompt (0, n_remotes, _("Which do you want to install (0 to abort)?"));
+ }
+
+ if (chosen == 0)
+ return NULL;
+
+ return g_strdup (remotes[chosen-1]);
+}
+
+static gboolean
+add_related (FlatpakTransaction *self,
+ const char *remote,
+ const char *ref,
+ GError **error)
+{
+ g_autoptr(GPtrArray) related = NULL;
+ g_autoptr(GError) local_error = NULL;
+ int i;
+
+ if (!self->add_related)
+ return TRUE;
+
+ if (self->no_pull)
+ related = flatpak_dir_find_local_related (self->dir, ref, remote, NULL, &local_error);
+ else
+ related = flatpak_dir_find_remote_related (self->dir, ref, remote, NULL, &local_error);
+ if (related == NULL)
+ {
+ g_printerr (_("Warning: Problem looking for related refs: %s\n"), local_error->message);
+ g_clear_error (&local_error);
+ }
+ else
+ {
+ for (i = 0; i < related->len; i++)
+ {
+ FlatpakRelated *rel = g_ptr_array_index (related, i);
+ FlatpakTransactionOp *op;
+
+ if (!rel->download)
+ continue;
+
+ op = flatpak_transaction_add_op (self, remote, rel->ref,
+ (const char **)rel->subpaths,
+ TRUE, TRUE);
+ op->non_fatal = TRUE;
+ }
+ }
+
+ return TRUE;
+}
+
+static gboolean
+add_deps (FlatpakTransaction *self,
+ const char *remote,
+ const char *ref,
+ GError **error)
+{
+ g_autofree char *runtime_ref = NULL;
+ g_autofree char *full_runtime_ref = NULL;
+ g_autofree char *runtime_remote = NULL;
+ const char *pref;
+
+ runtime_ref = transaction_fetch_runtime_ref (self->dir, remote, ref);
+ if (runtime_ref == NULL)
+ return TRUE;
+
+ pref = strchr (ref, '/') + 1;
+
+ full_runtime_ref = g_strconcat ("runtime/", runtime_ref, NULL);
+
+ if (!flatpak_transaction_contains_ref (self, full_runtime_ref))
+ {
+ if (!ref_is_installed (full_runtime_ref))
+ {
+ g_auto(GStrv) remotes = NULL;
+
+ g_print (_("Required runtime for %s (%s) is not installed, searching...\n"),
+ pref, runtime_ref);
+
+ remotes = flatpak_dir_search_for_dependency (self->dir, full_runtime_ref, NULL, NULL);
+ if (remotes == NULL || *remotes == NULL)
+ {
+ g_print (_("The required runtime %s was not found in a configured remote.\n"),
+ runtime_ref);
+ }
+ else
+ {
+ runtime_remote = ask_for_remote ((const char **)remotes);
+ }
+
+ if (runtime_remote == NULL)
+ return flatpak_fail (error,
+ "The Application %s requires the runtime %s which is not installed",
+ pref, runtime_ref);
+
+ flatpak_transaction_add_op (self, runtime_remote, full_runtime_ref, NULL, TRUE, TRUE);
+ }
+ else
+ {
+ /* Update if in same dir */
+ if (dir_ref_is_installed (self->dir, full_runtime_ref, &runtime_remote))
+ {
+ flatpak_transaction_add_op (self, runtime_remote, full_runtime_ref, NULL, FALSE, TRUE);
+ }
+ }
+ }
+
+ if (runtime_remote != NULL &&
+ !add_related (self, runtime_remote, full_runtime_ref, error))
+ return FALSE;
+
+ return TRUE;
+}
+
+static gboolean
+flatpak_transaction_add_ref (FlatpakTransaction *self,
+ const char *remote,
+ const char *ref,
+ const char **subpaths,
+ gboolean is_update,
+ GError **error)
+{
+ g_autofree char *origin = NULL;
+ const char *pref;
+
+ pref = strchr (ref, '/') + 1;
+
+ if (is_update)
+ {
+ if (!dir_ref_is_installed (self->dir, ref, &origin))
+ {
+ g_set_error (error, FLATPAK_ERROR, FLATPAK_ERROR_NOT_INSTALLED,
+ _("%s not installed"), pref);
+ return FALSE;
+ }
+
+ if (flatpak_dir_get_remote_disabled (self->dir, origin))
+ {
+ g_debug (_("Remote %s disabled, ignoring %s update"), origin, pref);
+ return TRUE;
+ }
+ remote = origin;
+ }
+ else
+ {
+ g_assert (remote != NULL);
+ if (dir_ref_is_installed (self->dir, ref, NULL))
+ {
+ g_set_error (error, FLATPAK_ERROR, FLATPAK_ERROR_ALREADY_INSTALLED,
+ _("%s already installed"), pref);
+ return FALSE;
+ }
+ }
+
+ if (self->add_deps)
+ add_deps (self, remote, ref, error);
+
+ flatpak_transaction_add_op (self, remote, ref, subpaths, !is_update, is_update);
+
+ if (!add_related (self, remote, ref, error))
+ return FALSE;
+
+ return TRUE;
+}
+
+gboolean
+flatpak_transaction_add_install (FlatpakTransaction *self,
+ const char *remote,
+ const char *ref,
+ const char **subpaths,
+ GError **error)
+{
+ const char *all_paths[] = { NULL };
+
+ /* If we install with no special args pull all subpaths */
+ if (subpaths == NULL)
+ subpaths = all_paths;
+
+ return flatpak_transaction_add_ref (self, remote, ref, subpaths, FALSE, error);
+}
+
+gboolean
+flatpak_transaction_add_update (FlatpakTransaction *self,
+ const char *ref,
+ const char **subpaths,
+ GError **error)
+{
+ return flatpak_transaction_add_ref (self, NULL, ref, subpaths, TRUE, error);
+}
+
+
+gboolean
+flatpak_transaction_run (FlatpakTransaction *self,
+ gboolean stop_on_first_error,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GList *l;
+ gboolean succeeded = TRUE;
+
+ self->ops = g_list_reverse (self->ops);
+
+ for (l = self->ops; l != NULL; l = l->next)
+ {
+ FlatpakTransactionOp *op = l->data;
+ g_autoptr(GError) local_error = NULL;
+ gboolean res;
+ g_autofree char *subpaths_str = subpaths_to_string ((const char **)op->subpaths);
+ const char *pref;
+ const char *opname;
+
+ if (op->install && op->update)
+ {
+ if (dir_ref_is_installed (self->dir, op->ref, NULL))
+ op->install = FALSE;
+ else
+ op->update = FALSE;
+ }
+
+ pref = strchr (op->ref, '/') + 1;
+
+ if (op->install)
+ {
+ opname = _("install");
+ g_print (_("Installing: %s from %s\n"), pref, op->remote);
+ res = TRUE;
+ res = flatpak_dir_install (self->dir,
+ self->no_pull,
+ self->no_deploy,
+ op->ref, op->remote,
+ (const char **)op->subpaths,
+ NULL,
+ cancellable, &local_error);
+ }
+ else /* update */
+ {
+ opname = _("update");
+ g_print (_("Updating: %s from %s\n"), pref, op->remote);
+ res = flatpak_dir_update (self->dir,
+ self->no_pull,
+ self->no_deploy,
+ op->ref, op->remote, op->commit,
+ (const char **)op->subpaths,
+ NULL,
+ cancellable, &local_error);
+
+ if (res)
+ {
+ g_autoptr(GVariant) deploy_data = NULL;
+ g_autofree char *commit = NULL;
+ deploy_data = flatpak_dir_get_deploy_data (self->dir, op->ref, NULL, NULL);
+ commit = g_strndup (flatpak_deploy_data_get_commit (deploy_data), 12);
+ g_print (_("Now at %s.\n"), commit);
+ }
+
+ /* Handle noop-updates */
+ if (!res && g_error_matches (local_error, FLATPAK_ERROR, FLATPAK_ERROR_ALREADY_INSTALLED))
+ {
+ g_print (_("No updates.\n"));
+ res = TRUE;
+ g_clear_error (&local_error);
+ }
+ }
+
+ if (!res)
+ {
+ if (op->non_fatal)
+ {
+ g_printerr (_("Warning: Failed to %s %s: %s\n"),
+ opname, pref, local_error->message);
+ }
+ else if (!stop_on_first_error)
+ {
+ g_printerr (_("Error: Failed to %s %s: %s\n"),
+ opname, pref, local_error->message);
+ if (succeeded)
+ {
+ succeeded = FALSE;
+ flatpak_fail (error, _("One or more operations failed"));
+ }
+ }
+ else
+ {
+ g_propagate_error (error, local_error);
+ return FALSE;
+ }
+ }
+ }
+
+ return succeeded;
+}
diff --git a/app/flatpak-transaction.h b/app/flatpak-transaction.h
new file mode 100644
index 000000000..5ca22383b
--- /dev/null
+++ b/app/flatpak-transaction.h
@@ -0,0 +1,54 @@
+/*
+ * Copyright © 2016 Red Hat, Inc
+ *
+ * This program 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.1 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, see .
+ *
+ * Authors:
+ * Alexander Larsson
+ */
+
+#ifndef __FLATPAK_TRANSACTION_H__
+#define __FLATPAK_TRANSACTION_H__
+
+#include
+#include "libglnx/libglnx.h"
+
+#include "flatpak-dir.h"
+
+typedef struct FlatpakTransaction FlatpakTransaction;
+
+FlatpakTransaction *flatpak_transaction_new (FlatpakDir *dir,
+ gboolean no_pull,
+ gboolean no_deploy,
+ gboolean add_deps,
+ gboolean add_related);
+void flatpak_transaction_free (FlatpakTransaction *self);
+gboolean flatpak_transaction_run (FlatpakTransaction *self,
+ gboolean stop_on_first_errror,
+ GCancellable *cancellable,
+ GError **error);
+gboolean flatpak_transaction_add_install (FlatpakTransaction *self,
+ const char *remote,
+ const char *ref,
+ const char **subpaths,
+ GError **error);
+gboolean flatpak_transaction_add_update (FlatpakTransaction *self,
+ const char *ref,
+ const char **subpaths,
+ GError **error);
+
+
+G_DEFINE_AUTOPTR_CLEANUP_FUNC (FlatpakTransaction, flatpak_transaction_free)
+
+#endif /* __FLATPAK_TRANSACTION_H__ */
diff --git a/common/flatpak-dir.c b/common/flatpak-dir.c
index 408aeb5ec..d32983211 100644
--- a/common/flatpak-dir.c
+++ b/common/flatpak-dir.c
@@ -4227,6 +4227,29 @@ flatpak_dir_remote_fetch_summary (FlatpakDir *self,
return TRUE;
}
+gboolean
+flatpak_dir_remote_has_ref (FlatpakDir *self,
+ const char *remote,
+ const char *ref)
+{
+ g_autoptr(GBytes) summary_bytes = NULL;
+ g_autoptr(GVariant) summary = NULL;
+ g_autoptr(GError) local_error = NULL;
+
+ if (!flatpak_dir_remote_fetch_summary (self, remote,
+ &summary_bytes,
+ NULL, &local_error))
+ {
+ g_debug ("Can't get summary for remote %s: %s\n", remote, local_error->message);
+ return FALSE;
+ }
+
+ summary = g_variant_ref_sink (g_variant_new_from_bytes (OSTREE_SUMMARY_GVARIANT_FORMAT,
+ summary_bytes, FALSE));
+
+ return flatpak_summary_lookup_ref (summary, ref, NULL);
+}
+
/* This duplicates ostree_repo_list_refs so it can use flatpak_dir_remote_fetch_summary
and get caching */
static gboolean
@@ -4978,6 +5001,66 @@ flatpak_dir_list_remotes (FlatpakDir *self,
return res;
}
+char **
+flatpak_dir_list_enumerated_remotes (FlatpakDir *self,
+ GCancellable *cancellable,
+ GError **error)
+{
+ g_autoptr(GPtrArray) res = g_ptr_array_new_with_free_func (g_free);
+ g_auto(GStrv) remotes = NULL;
+ int i;
+
+ remotes = flatpak_dir_list_remotes (self, cancellable, error);
+ if (remotes == NULL)
+ return NULL;
+
+ for (i = 0; remotes != NULL && remotes[i] != NULL; i++)
+ {
+ const char *remote = remotes[i];
+
+ if (flatpak_dir_get_remote_disabled (self, remote))
+ continue;
+
+ if (flatpak_dir_get_remote_noenumerate (self, remote))
+ continue;
+
+ g_ptr_array_add (res, g_strdup (remote));
+ }
+
+ g_ptr_array_add (res, NULL);
+ return (char **)g_ptr_array_free (g_steal_pointer (&res), FALSE);
+}
+
+char **
+flatpak_dir_search_for_dependency (FlatpakDir *self,
+ const char *runtime_ref,
+ GCancellable *cancellable,
+ GError **error)
+{
+ g_autoptr(GPtrArray) found = g_ptr_array_new_with_free_func (g_free);
+ g_auto(GStrv) remotes = NULL;
+ int i;
+
+ remotes = flatpak_dir_list_enumerated_remotes (self, cancellable, error);
+ if (remotes == NULL)
+ return NULL;
+
+ for (i = 0; remotes != NULL && remotes[i] != NULL; i++)
+ {
+ const char *remote = remotes[i];
+
+ if (flatpak_dir_get_remote_nodeps (self, remote))
+ continue;
+
+ if (flatpak_dir_remote_has_ref (self, remote, runtime_ref))
+ g_ptr_array_add (found, g_strdup (remote));
+ }
+
+ g_ptr_array_add (found, NULL);
+
+ return (char **)g_ptr_array_free (g_steal_pointer (&found), FALSE);
+}
+
gboolean
flatpak_dir_remove_remote (FlatpakDir *self,
gboolean force_remove,
diff --git a/common/flatpak-dir.h b/common/flatpak-dir.h
index 3d25fbf9c..4ce7082c1 100644
--- a/common/flatpak-dir.h
+++ b/common/flatpak-dir.h
@@ -153,6 +153,13 @@ GFile * flatpak_dir_get_if_deployed (FlatpakDir *self,
const char *ref,
const char *checksum,
GCancellable *cancellable);
+gboolean flatpak_dir_remote_has_ref (FlatpakDir *self,
+ const char *remote,
+ const char *ref);
+char ** flatpak_dir_search_for_dependency (FlatpakDir *self,
+ const char *runtime_ref,
+ GCancellable *cancellable,
+ GError **error);
char * flatpak_dir_find_remote_ref (FlatpakDir *self,
const char *remote,
const char *name,
@@ -395,6 +402,9 @@ gboolean flatpak_dir_create_remote_for_ref_file (FlatpakDir *self,
char **flatpak_dir_list_remotes (FlatpakDir *self,
GCancellable *cancellable,
GError **error);
+char **flatpak_dir_list_enumerated_remotes (FlatpakDir *self,
+ GCancellable *cancellable,
+ GError **error);
gboolean flatpak_dir_modify_remote (FlatpakDir *self,
const char *remote_name,
GKeyFile *config,
diff --git a/common/flatpak-utils.c b/common/flatpak-utils.c
index 30ed6b1c5..f6ab785ae 100644
--- a/common/flatpak-utils.c
+++ b/common/flatpak-utils.c
@@ -3823,6 +3823,75 @@ flatpak_allocate_tmpdir (int tmpdir_dfd,
return TRUE;
}
+gboolean
+flatpak_yes_no_prompt (const char *prompt, ...)
+{
+ char buf[512];
+ va_list var_args;
+ gchar *s;
+
+ va_start (var_args, prompt);
+ s = g_strdup_vprintf (prompt, var_args);
+
+ while (TRUE)
+ {
+ g_print ("%s %s: ", s, "[y/n]");
+ if (fgets (buf, sizeof (buf), stdin) == NULL)
+ return FALSE;
+
+ g_strstrip (buf);
+ if (g_ascii_strcasecmp (buf, "y") == 0 ||
+ g_ascii_strcasecmp (buf, "yes") == 0)
+ return TRUE;
+
+ if (g_ascii_strcasecmp (buf, "n") == 0 ||
+ g_ascii_strcasecmp (buf, "no") == 0)
+ return FALSE;
+ }
+}
+
+static gboolean
+is_number (const char *s)
+{
+ while (*s != 0)
+ {
+ if (!g_ascii_isdigit (*s))
+ return FALSE;
+ s++;
+ }
+
+ return TRUE;
+}
+
+long
+flatpak_number_prompt (int min, int max, const char *prompt, ...)
+{
+ char buf[512];
+ va_list var_args;
+ gchar *s;
+
+ va_start (var_args, prompt);
+ s = g_strdup_vprintf (prompt, var_args);
+
+ while (TRUE)
+ {
+ g_print ("%s [%d-%d]: ", s, min, max);
+ if (fgets (buf, sizeof (buf), stdin) == NULL)
+ return FALSE;
+
+ g_strstrip (buf);
+
+ if (is_number (buf))
+ {
+ long res = strtol (buf, NULL, 10);
+
+ if (res >= min && res <= max)
+ return res;
+ }
+ }
+}
+
+
/* Uncomment to get debug traces in /tmp/flatpak-completion-debug.txt (nice
* to not have it interfere with stdout/stderr)
*/
diff --git a/common/flatpak-utils.h b/common/flatpak-utils.h
index 3b9bebe75..57bdc9fa5 100644
--- a/common/flatpak-utils.h
+++ b/common/flatpak-utils.h
@@ -467,6 +467,9 @@ gboolean flatpak_allocate_tmpdir (int tmpdir_dfd,
GError **error);
+gboolean flatpak_yes_no_prompt (const char *prompt, ...);
+long flatpak_number_prompt (int min, int max, const char *prompt, ...);
+
typedef struct {
char *shell_cur;
char *cur;
diff --git a/doc/flatpak-update.xml b/doc/flatpak-update.xml
index 301981dda..e26c60b6f 100644
--- a/doc/flatpak-update.xml
+++ b/doc/flatpak-update.xml
@@ -157,6 +157,14 @@
+
+
+
+
+ Don't update or install runtime dependencies when installing.
+
+
+