diff --git a/app/flatpak-builtins-install.c b/app/flatpak-builtins-install.c index 5305a58f..6a233f06 100644 --- a/app/flatpak-builtins-install.c +++ b/app/flatpak-builtins-install.c @@ -151,8 +151,12 @@ do_install (FlatpakDir *dir, 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, @@ -223,6 +227,7 @@ install_from (FlatpakDir *dir, g_autofree char *remote = NULL; g_autofree char *ref = NULL; g_auto(GStrv) parts = NULL; + const char *slash; FlatpakDir *clone; if (argc < 2) @@ -248,8 +253,8 @@ install_from (FlatpakDir *dir, if (!flatpak_dir_ensure_repo (clone, cancellable, error)) return FALSE; - parts = g_strsplit (ref, "/", 0); - g_print (_("Installing: %s\n"), parts[1]); + slash = strchr (ref, '/'); + g_print (_("Installing: %s\n"), slash + 1); if (!do_install (clone, opt_no_pull, @@ -262,22 +267,37 @@ install_from (FlatpakDir *dir, 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_install (int argc, char **argv, GCancellable *cancellable, GError **error) { g_autoptr(GOptionContext) context = NULL; g_autoptr(FlatpakDir) dir = NULL; const char *repository; - const char *pref = NULL; + char **prefs = NULL; + int i, n_prefs; const char *default_branch = NULL; - g_autofree char *ref = NULL; FlatpakKinds kinds; - FlatpakKinds kind; - g_autofree char *id = NULL; - g_autofree char *arch = NULL; - g_autofree char *branch = NULL; + g_autoptr(GPtrArray) refs = NULL; - context = g_option_context_new (_("REPOSITORY NAME [BRANCH] - Install an application or runtime")); + context = g_option_context_new (_("REPOSITORY 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)) @@ -290,34 +310,58 @@ 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 NAME must be specified"), error); + return usage_error (context, _("REPOSITORY and REF must be specified"), error); if (argc > 4) return usage_error (context, _("Too many arguments"), error); repository = argv[1]; - pref = argv[2]; - if (argc >= 4) - default_branch = argv[3]; + prefs = &argv[2]; + n_prefs = argc - 2; + + /* Backwards compat for old "REPOSITORY NAME [BRANCH]" argument version */ + if (argc == 4 && looks_like_branch (argv[3])) + { + default_branch = argv[3]; + n_prefs = 1; + } kinds = flatpak_kinds_from_bools (opt_app, opt_runtime); - if (!flatpak_split_partial_ref_arg (pref, kinds, opt_arch, default_branch, - &kinds, &id, &arch, &branch, error)) - return FALSE; + refs = g_ptr_array_new_with_free_func (g_free); + for (i = 0; i < n_prefs; i++) + { + const char *pref = prefs[i]; + FlatpakKinds matched_kinds; + g_autofree char *id = NULL; + g_autofree char *arch = NULL; + g_autofree char *branch = NULL; + FlatpakKinds kind; + g_autofree char *ref = NULL; - ref = flatpak_dir_find_remote_ref (dir, repository, id, branch, arch, - kinds, &kind, cancellable, error); - if (ref == NULL) - return FALSE; + if (!flatpak_split_partial_ref_arg (pref, kinds, opt_arch, default_branch, + &matched_kinds, &id, &arch, &branch, error)) + return FALSE; - if (!do_install (dir, - opt_no_pull, - opt_no_deploy, - ref, repository, - (const char **)opt_subpaths, - cancellable, error)) - return FALSE; + ref = flatpak_dir_find_remote_ref (dir, repository, id, branch, arch, + matched_kinds, &kind, cancellable, error); + if (ref == NULL) + return FALSE; + + 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)) + return FALSE; + } return TRUE; } @@ -327,8 +371,6 @@ flatpak_complete_install (FlatpakCompletion *completion) { g_autoptr(GOptionContext) context = NULL; g_autoptr(FlatpakDir) dir = NULL; - g_autoptr(GError) error = NULL; - g_auto(GStrv) refs = NULL; FlatpakKinds kinds; int i; @@ -338,8 +380,6 @@ flatpak_complete_install (FlatpakCompletion *completion) kinds = flatpak_kinds_from_bools (opt_app, opt_runtime); - flatpak_completion_debug ("install argc %d", completion->argc); - switch (completion->argc) { case 0: @@ -358,37 +398,8 @@ flatpak_complete_install (FlatpakCompletion *completion) break; - case 2: /* Name */ - refs = flatpak_dir_find_remote_refs (dir, completion->argv[1], NULL, NULL, - opt_arch, kinds, - NULL, &error); - if (refs == NULL) - flatpak_completion_debug ("find remote refs error: %s", error->message); - for (i = 0; refs != NULL && refs[i] != NULL; i++) - { - g_auto(GStrv) parts = flatpak_decompose_ref (refs[i], NULL); - if (parts) - flatpak_complete_word (completion, "%s ", parts[1]); - } - - break; - - case 3: /* Branch */ - refs = flatpak_dir_find_remote_refs (dir, completion->argv[1], completion->argv[2], NULL, - opt_arch, kinds, - NULL, &error); - if (refs == NULL) - flatpak_completion_debug ("find remote refs error: %s", error->message); - for (i = 0; refs != NULL && refs[i] != NULL; i++) - { - g_auto(GStrv) parts = flatpak_decompose_ref (refs[i], NULL); - if (parts) - flatpak_complete_word (completion, "%s ", parts[3]); - } - - break; - default: + flatpak_complete_partial_remote_ref (completion, kinds, opt_arch, dir, completion->argv[1]); break; } diff --git a/common/flatpak-utils.c b/common/flatpak-utils.c index c4f52008..669765cf 100644 --- a/common/flatpak-utils.c +++ b/common/flatpak-utils.c @@ -684,15 +684,16 @@ flatpak_kinds_from_bools (gboolean app, gboolean runtime) } gboolean -flatpak_split_partial_ref_arg (const char *partial_ref, - FlatpakKinds default_kinds, - const char *default_arch, - const char *default_branch, - FlatpakKinds *out_kinds, - char **out_id, - char **out_arch, - char **out_branch, - GError **error) +_flatpak_split_partial_ref_arg (const char *partial_ref, + gboolean validate, + FlatpakKinds default_kinds, + const char *default_arch, + const char *default_branch, + FlatpakKinds *out_kinds, + char **out_id, + char **out_arch, + char **out_branch, + GError **error) { const char *id_start = NULL; const char *id_end = NULL; @@ -723,7 +724,7 @@ flatpak_split_partial_ref_arg (const char *partial_ref, id_end = next_element (&partial_ref); id = g_strndup (id_start, id_end - id_start); - if (!flatpak_is_valid_name (id, &local_error)) + if (validate && !flatpak_is_valid_name (id, &local_error)) return flatpak_fail (error, "Invalid id %s: %s", id, local_error->message); arch_start = partial_ref; @@ -740,7 +741,7 @@ flatpak_split_partial_ref_arg (const char *partial_ref, else branch = g_strdup (default_branch); - if (branch != NULL && !flatpak_is_valid_branch (branch, &local_error)) + if (validate && branch != NULL && !flatpak_is_valid_branch (branch, &local_error)) return flatpak_fail (error, "Invalid branch %s: %s", branch, local_error->message); if (out_kinds) @@ -755,6 +756,52 @@ flatpak_split_partial_ref_arg (const char *partial_ref, return TRUE; } +gboolean +flatpak_split_partial_ref_arg (const char *partial_ref, + FlatpakKinds default_kinds, + const char *default_arch, + const char *default_branch, + FlatpakKinds *out_kinds, + char **out_id, + char **out_arch, + char **out_branch, + GError **error) +{ + return _flatpak_split_partial_ref_arg (partial_ref, + TRUE, + default_kinds, + default_arch, + default_branch, + out_kinds, + out_id, + out_arch, + out_branch, + error); +} + +gboolean +flatpak_split_partial_ref_arg_novalidate (const char *partial_ref, + FlatpakKinds default_kinds, + const char *default_arch, + const char *default_branch, + FlatpakKinds *out_kinds, + char **out_id, + char **out_arch, + char **out_branch) +{ + return _flatpak_split_partial_ref_arg (partial_ref, + FALSE, + default_kinds, + default_arch, + default_branch, + out_kinds, + out_id, + out_arch, + out_branch, + NULL); +} + + char * flatpak_compose_ref (gboolean app, const char *name, @@ -3851,6 +3898,91 @@ flatpak_complete_ref (FlatpakCompletion *completion, } } +int +find_current_element (const char *str) +{ + int count = 0; + + if (g_str_has_prefix (str, "app/")) + str += strlen ("app/"); + else if (g_str_has_prefix (str, "runtime/")) + str += strlen ("runtime/"); + + while (str != NULL && count <= 3) + { + str = strchr (str, '/'); + count++; + if (str != NULL) + str = str + 1; + } + + return count; +} + +void +flatpak_complete_partial_remote_ref (FlatpakCompletion *completion, + FlatpakKinds kinds, + const char *only_arch, + FlatpakDir *dir, + const char *remote) +{ + FlatpakKinds matched_kinds; + const char *pref; + g_autofree char *id = NULL; + g_autofree char *arch = NULL; + g_autofree char *branch = NULL; + g_auto(GStrv) refs = NULL; + int element; + const char *cur_parts[4] = { NULL }; + g_autoptr(GError) error = NULL; + int i; + + pref = completion->cur; + element = find_current_element (pref); + + flatpak_split_partial_ref_arg_novalidate (pref, kinds, + NULL, NULL, + &matched_kinds, &id, &arch, &branch); + + cur_parts[1] = id; + cur_parts[2] = arch ? arch : ""; + cur_parts[3] = branch ? branch : ""; + + refs = flatpak_dir_find_remote_refs (dir, completion->argv[1], + (element > 1) ? id : NULL, + (element > 3) ? branch : NULL, + (element > 2 )? arch : only_arch, + matched_kinds, NULL, &error); + if (refs == NULL) + flatpak_completion_debug ("find remote refs error: %s", error->message); + for (i = 0; refs != NULL && refs[i] != NULL; i++) + { + int j; + g_autoptr(GString) comp = NULL; + g_auto(GStrv) parts = flatpak_decompose_ref (refs[i], NULL); + if (parts == NULL) + continue; + + if (!g_str_has_prefix (parts[element], cur_parts[element])) + continue; + + comp = g_string_new (pref); + g_string_append (comp, parts[element] + strlen (cur_parts[element])); + + /* Only complete on the last part if the user explicitly adds a / */ + if (element >= 2) + { + for (j = element + 1; j < 4; j++) + { + g_string_append (comp, "/"); + g_string_append (comp, parts[j]); + } + } + + flatpak_complete_word (completion, "%s", comp->str); + } +} + static gboolean switch_already_in_line (FlatpakCompletion *completion, GOptionEntry *entry) diff --git a/common/flatpak-utils.h b/common/flatpak-utils.h index ef04e55d..df5a0326 100644 --- a/common/flatpak-utils.h +++ b/common/flatpak-utils.h @@ -100,6 +100,14 @@ gboolean flatpak_split_partial_ref_arg (const char *partial_ref, char **out_arch, char **out_branch, GError **error); +gboolean flatpak_split_partial_ref_arg_novalidate (const char *partial_ref, + FlatpakKinds default_kinds, + const char *default_arch, + const char *default_branch, + FlatpakKinds *out_kinds, + char **out_id, + char **out_arch, + char **out_branch); char * flatpak_compose_ref (gboolean app, const char *name, @@ -478,6 +486,11 @@ void flatpak_complete_word (FlatpakCompletion *completion, ...); void flatpak_complete_ref (FlatpakCompletion *completion, OstreeRepo *repo); +void flatpak_complete_partial_remote_ref (FlatpakCompletion *completion, + FlatpakKinds kinds, + const char *only_arch, + FlatpakDir *dir, + const char *remote); void flatpak_complete_file (FlatpakCompletion *completion); void flatpak_complete_dir (FlatpakCompletion *completion); void flatpak_complete_options (FlatpakCompletion *completion, diff --git a/doc/flatpak-install.xml b/doc/flatpak-install.xml index 49919bc0..0482bc22 100644 --- a/doc/flatpak-install.xml +++ b/doc/flatpak-install.xml @@ -33,8 +33,7 @@ flatpak install OPTION REMOTE - NAME - BRANCH + REF flatpak install @@ -52,14 +51,21 @@ Installs an application or runtime. REMOTE must name - an existing remote and NAME is the name of the - application or runtime to install. Optionally, BRANCH can - be specified to install a branch other than the default branch. This required - if there are multiple matches in the selected remote. + an existing remote and REF is a reference to the + application or runtime to install. - By default this looks for both apps and runtime with the given NAME in - the specified REMOTE, but you can limit this by using the --app or --runtime option. + Each REF arguments is a full or partial indentifier in the + flatpak ref format, which looks like "(app|runtime)/ID/ARCH/BRANCH". All elements + except ID are optional and can be left out, including the slashes, + so most of the time you need only specify ID. Any part left out will be matched + against what is in the remote, and if there are multiple matches an error message + will list the alternatives. + + + By default this looks for both apps and runtime with the given REF in + the specified REMOTE, but you can limit this by using the --app or + --runtime option, or by supplying the initial element in the REF. Note that flatpak allows one to have multiple branches of an application and runtimes @@ -134,7 +140,7 @@ - The architecture to install for. + The default architecture to install for, if not given explicitly in the REF. @@ -175,15 +181,14 @@ - Only look for an app with the given name. + Assume that all REFs are apps if not explicitly specified. - - Only look for an runtime with the given name. + Assume that all REFs are runtimes if not explicitly specified.