/* vi:set et sw=2 sts=2 cin cino=t0,f0,(0,{s,>2s,n-s,^-s,e-s:
* Copyright © 2017 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:
* Matthias Clasen
*/
#include "config.h"
#include
#include
#include
#include
#include
#include "libglnx.h"
#include "flatpak-builtins.h"
#include "flatpak-utils-private.h"
#include "flatpak-table-printer.h"
#include "flatpak-variant-impl-private.h"
static gboolean opt_info;
static gboolean opt_branches;
static gboolean opt_subsets;
static gchar *opt_metadata_branch;
static gchar *opt_commits_branch;
static gchar *opt_subset;
static gboolean
ostree_repo_mode_to_string (OstreeRepoMode mode,
const char **out_mode,
GError **error)
{
const char *ret_mode;
switch (mode)
{
case OSTREE_REPO_MODE_BARE:
ret_mode = "bare";
break;
case OSTREE_REPO_MODE_BARE_USER:
ret_mode = "bare-user";
break;
case OSTREE_REPO_MODE_BARE_USER_ONLY:
ret_mode = "bare-user-only";
break;
case OSTREE_REPO_MODE_ARCHIVE:
/* Legacy alias */
ret_mode = "archive-z2";
break;
default:
return glnx_throw (error, "Invalid mode '%d'", mode);
}
*out_mode = ret_mode;
return TRUE;
}
static void
print_info (OstreeRepo *repo,
GVariant *index,
GVariant *summary)
{
const char *title;
const char *comment;
const char *description;
const char *homepage;
const char *icon;
const char *collection_id;
const char *default_branch;
const char *redirect_url;
const char *deploy_collection_id;
const char *authenticator_name;
gboolean authenticator_install = FALSE;
g_autoptr(GVariant) gpg_keys = NULL;
OstreeRepoMode mode;
const char *mode_string = "unknown";
g_autoptr(GVariant) meta = NULL;
g_autoptr(GVariant) refs = NULL;
guint cache_version = 0;
gboolean indexed_deltas = FALSE;
mode = ostree_repo_get_mode (repo);
ostree_repo_mode_to_string (mode, &mode_string, NULL);
g_print (_("Repo mode: %s\n"), mode_string);
if (index)
meta = g_variant_get_child_value (index, 1);
else
meta = g_variant_get_child_value (summary, 1);
g_print (_("Indexed summaries: %s\n"), index != NULL ? _("true") : _("false"));
if (index)
{
VarSummaryIndexRef index_ref = var_summary_index_from_gvariant (index);
VarSummaryIndexSubsummariesRef subsummaries = var_summary_index_get_subsummaries (index_ref);
gsize n_subsummaries = var_summary_index_subsummaries_get_length (subsummaries);
g_print (_("Subsummaries: "));
for (gsize i = 0; i < n_subsummaries; i++)
{
VarSummaryIndexSubsummariesEntryRef entry = var_summary_index_subsummaries_get_at (subsummaries, i);
const char *name = var_summary_index_subsummaries_entry_get_key (entry);
if (i != 0)
g_print (", ");
g_print ("%s", name);
}
g_print ("\n");
}
g_variant_lookup (meta, "xa.cache-version", "u", &cache_version);
g_print (_("Cache version: %d\n"), cache_version);
g_variant_lookup (meta, "ostree.summary.indexed-deltas", "b", &indexed_deltas);
g_print (_("Indexed deltas: %s\n"), indexed_deltas ? _("true") : _("false"));
if (g_variant_lookup (meta, "xa.title", "&s", &title))
g_print (_("Title: %s\n"), title);
if (g_variant_lookup (meta, "xa.comment", "&s", &comment))
g_print (_("Comment: %s\n"), comment);
if (g_variant_lookup (meta, "xa.description", "&s", &description))
g_print (_("Description: %s\n"), description);
if (g_variant_lookup (meta, "xa.homepage", "&s", &homepage))
g_print (_("Homepage: %s\n"), homepage);
if (g_variant_lookup (meta, "xa.icon", "&s", &icon))
g_print (_("Icon: %s\n"), icon);
if (g_variant_lookup (meta, "collection-id", "&s", &collection_id))
g_print (_("Collection ID: %s\n"), collection_id);
if (g_variant_lookup (meta, "xa.default-branch", "&s", &default_branch))
g_print (_("Default branch: %s\n"), default_branch);
if (g_variant_lookup (meta, "xa.redirect-url", "&s", &redirect_url))
g_print (_("Redirect URL: %s\n"), redirect_url);
if (g_variant_lookup (meta, OSTREE_META_KEY_DEPLOY_COLLECTION_ID, "&s", &deploy_collection_id))
g_print (_("Deploy collection ID: %s\n"), deploy_collection_id);
if (g_variant_lookup (meta, "xa.authenticator-name", "&s", &authenticator_name))
g_print (_("Authenticator name: %s\n"), authenticator_name);
if (g_variant_lookup (meta, "xa.authenticator-install", "&s", &authenticator_install))
g_print (_("Authenticator install: %s\n"), authenticator_install ? _("true") : _("false"));
if ((gpg_keys = g_variant_lookup_value (meta, "xa.gpg-keys", G_VARIANT_TYPE_BYTESTRING)) != NULL)
{
const guchar *gpg_data = g_variant_get_data (gpg_keys);
gsize gpg_size = g_variant_get_size (gpg_keys);
g_autofree gchar *gpg_data_checksum = g_compute_checksum_for_data (G_CHECKSUM_SHA256, gpg_data, gpg_size);
g_print (_("GPG key hash: %s\n"), gpg_data_checksum);
}
refs = g_variant_get_child_value (summary, 0);
g_print (_("%zd summary branches\n"), g_variant_n_children (refs));
}
static void
print_branches_for_subsummary (FlatpakTablePrinter *printer,
const char *subsummary,
GVariant *summary)
{
g_autoptr(GVariant) meta = NULL;
guint summary_version = 0;
g_autofree char *subset = NULL;
if (subsummary != NULL)
{
const char *dash = strrchr (subsummary, '-');
if (dash)
subset = g_strndup (subsummary, dash - subsummary);
}
if (opt_subset != NULL)
{
if (subset == NULL ||
strcmp (subset, opt_subset) != 0)
return; /* Not the requested subset, ignore */
}
meta = g_variant_get_child_value (summary, 1);
g_variant_lookup (meta, "xa.summary-version", "u", &summary_version);
if (summary_version == 1)
{
g_autoptr(GVariant) refs = g_variant_get_child_value (summary, 0);
GVariantIter iter;
const char *ref;
GVariant *refdata_iter = NULL;
g_variant_iter_init (&iter, refs);
while (g_variant_iter_next (&iter, "(&s@(taya{sv}))", &ref, &refdata_iter))
{
g_autoptr(GVariant) refdata = refdata_iter;
g_autoptr(GVariant) ref_meta = g_variant_get_child_value (refdata, 2);
g_autoptr(GVariant) data = g_variant_lookup_value (ref_meta, "xa.data", NULL);
guint64 installed_size;
guint64 download_size;
const char *metadata;
const char *eol;
if (data == NULL)
continue;
int old_row = flatpak_table_printer_lookup_row (printer, ref);
if (old_row >= 0)
{
if (subset)
flatpak_table_printer_append_cell_with_comma_unique (printer, old_row, 3, subset);
continue;
}
g_variant_get (data, "(tt&s)", &installed_size, &download_size, &metadata);
g_autofree char *installed = g_format_size (GUINT64_FROM_BE (installed_size));
g_autofree char *download = g_format_size (GUINT64_FROM_BE (download_size));
flatpak_table_printer_set_key (printer, ref);
flatpak_table_printer_add_column (printer, ref);
flatpak_table_printer_add_decimal_column (printer, installed);
flatpak_table_printer_add_decimal_column (printer, download);
/* Subset */
flatpak_table_printer_add_column (printer, subset);
flatpak_table_printer_add_column (printer, ""); /* Options */
if (g_variant_lookup (ref_meta, FLATPAK_SPARSE_CACHE_KEY_ENDOFLINE, "&s", &eol))
flatpak_table_printer_append_with_comma_printf (printer, "eol=%s", eol);
if (g_variant_lookup (ref_meta, FLATPAK_SPARSE_CACHE_KEY_ENDOFLINE_REBASE, "&s", &eol))
flatpak_table_printer_append_with_comma_printf (printer, "eol-rebase=%s", eol);
flatpak_table_printer_finish_row (printer);
}
}
else
{
g_autoptr(GVariant) cache = NULL;
g_autoptr(GVariant) sparse_cache = NULL;
g_variant_lookup (meta, "xa.sparse-cache", "@a{sa{sv}}", &sparse_cache);
cache = g_variant_lookup_value (meta, "xa.cache", NULL);
if (cache)
{
g_autoptr(GVariant) refdata = NULL;
GVariantIter iter;
const char *ref;
guint64 installed_size;
guint64 download_size;
const char *metadata;
refdata = g_variant_get_variant (cache);
g_variant_iter_init (&iter, refdata);
while (g_variant_iter_next (&iter, "{&s(tt&s)}", &ref, &installed_size, &download_size, &metadata))
{
g_autofree char *installed = g_format_size (GUINT64_FROM_BE (installed_size));
g_autofree char *download = g_format_size (GUINT64_FROM_BE (download_size));
int old_row = flatpak_table_printer_lookup_row (printer, ref);
if (old_row >= 0)
{
if (subset)
flatpak_table_printer_append_cell_with_comma_unique (printer, old_row, 3, subset);
continue;
}
flatpak_table_printer_set_key (printer, ref);
flatpak_table_printer_add_column (printer, ref);
flatpak_table_printer_add_decimal_column (printer, installed);
flatpak_table_printer_add_decimal_column (printer, download);
flatpak_table_printer_add_column (printer, subset);
flatpak_table_printer_add_column (printer, ""); /* Options */
if (sparse_cache)
{
g_autoptr(GVariant) sparse = NULL;
if (g_variant_lookup (sparse_cache, ref, "@a{sv}", &sparse))
{
const char *eol;
if (g_variant_lookup (sparse, FLATPAK_SPARSE_CACHE_KEY_ENDOFLINE, "&s", &eol))
flatpak_table_printer_append_with_comma_printf (printer, "eol=%s", eol);
if (g_variant_lookup (sparse, FLATPAK_SPARSE_CACHE_KEY_ENDOFLINE_REBASE, "&s", &eol))
flatpak_table_printer_append_with_comma_printf (printer, "eol-rebase=%s", eol);
}
}
flatpak_table_printer_finish_row (printer);
}
}
}
}
static void
print_branches (OstreeRepo *repo,
GVariant *index,
GVariant *summary)
{
g_autoptr(FlatpakTablePrinter) printer = NULL;
printer = flatpak_table_printer_new ();
flatpak_table_printer_set_column_title (printer, 0, _("Ref"));
flatpak_table_printer_set_column_title (printer, 1, _("Installed"));
flatpak_table_printer_set_column_title (printer, 2, _("Download"));
flatpak_table_printer_set_column_title (printer, 3, _("Subsets"));
flatpak_table_printer_set_column_title (printer, 4, _("Options"));
if (index != NULL)
{
VarSummaryIndexRef index_ref = var_summary_index_from_gvariant (index);
VarSummaryIndexSubsummariesRef subsummaries = var_summary_index_get_subsummaries (index_ref);
gsize n_subsummaries = var_summary_index_subsummaries_get_length (subsummaries);
for (gsize i = 0; i < n_subsummaries; i++)
{
VarSummaryIndexSubsummariesEntryRef entry = var_summary_index_subsummaries_get_at (subsummaries, i);
const char *name = var_summary_index_subsummaries_entry_get_key (entry);
VarSubsummaryRef subsummary = var_summary_index_subsummaries_entry_get_value (entry);
gsize checksum_bytes_len;
const guchar *checksum_bytes;
g_autofree char *digest = NULL;
g_autoptr(GVariant) subsummary_v = NULL;
g_autoptr(GError) error = NULL;
checksum_bytes = var_subsummary_peek_checksum (subsummary, &checksum_bytes_len);
if (G_UNLIKELY (checksum_bytes_len != OSTREE_SHA256_DIGEST_LEN))
{
g_printerr ("Invalid checksum for digested summary\n");
continue;
}
digest = ostree_checksum_from_bytes (checksum_bytes);
subsummary_v = flatpak_repo_load_digested_summary (repo, digest, &error);
if (subsummary_v == NULL)
{
g_printerr ("Failed to load subsummary %s (digest %s)\n", name, digest);
continue;
}
print_branches_for_subsummary (printer, name, subsummary_v);
}
}
else
print_branches_for_subsummary (printer, NULL, summary);
flatpak_table_printer_sort (printer, (GCompareFunc) strcmp);
flatpak_table_printer_print (printer);
}
static void
print_subsets (OstreeRepo *repo,
GVariant *index)
{
g_autoptr(FlatpakTablePrinter) printer = NULL;
printer = flatpak_table_printer_new ();
flatpak_table_printer_set_column_title (printer, 0, _("Subset"));
flatpak_table_printer_set_column_title (printer, 1, _("Digest"));
flatpak_table_printer_set_column_title (printer, 2, _("History length"));
if (index != NULL)
{
VarSummaryIndexRef index_ref = var_summary_index_from_gvariant (index);
VarSummaryIndexSubsummariesRef subsummaries = var_summary_index_get_subsummaries (index_ref);
gsize n_subsummaries = var_summary_index_subsummaries_get_length (subsummaries);
for (gsize i = 0; i < n_subsummaries; i++)
{
VarSummaryIndexSubsummariesEntryRef entry = var_summary_index_subsummaries_get_at (subsummaries, i);
const char *name = var_summary_index_subsummaries_entry_get_key (entry);
VarSubsummaryRef subsummary = var_summary_index_subsummaries_entry_get_value (entry);
gsize checksum_bytes_len;
const guchar *checksum_bytes;
g_autofree char *digest = NULL;
VarArrayofChecksumRef history = var_subsummary_get_history (subsummary);
gsize history_len = var_arrayof_checksum_get_length (history);
if (opt_subset != NULL && !g_str_has_prefix (name, opt_subset))
continue;
checksum_bytes = var_subsummary_peek_checksum (subsummary, &checksum_bytes_len);
if (G_UNLIKELY (checksum_bytes_len != OSTREE_SHA256_DIGEST_LEN))
{
g_printerr ("Invalid checksum for digested summary\n");
continue;
}
digest = ostree_checksum_from_bytes (checksum_bytes);
flatpak_table_printer_add_column (printer, name);
flatpak_table_printer_add_column (printer, digest);
flatpak_table_printer_take_column (printer, g_strdup_printf ("%"G_GSIZE_FORMAT, history_len));
flatpak_table_printer_finish_row (printer);
}
}
flatpak_table_printer_print (printer);
}
static void
print_metadata (OstreeRepo *repo,
GVariant *index,
GVariant *summary,
const char *branch)
{
g_autoptr(GVariant) meta = NULL;
guint summary_version = 0;
guint64 installed_size;
guint64 download_size;
const char *metadata;
GVariantIter iter;
const char *ref;
g_autoptr(GVariant) subsummary_v = NULL;
if (index)
{
g_autofree char *arch = flatpak_get_arch_for_ref (branch);
if (arch != NULL)
{
VarSummaryIndexRef index_ref = var_summary_index_from_gvariant (index);
VarSummaryIndexSubsummariesRef subsummaries = var_summary_index_get_subsummaries (index_ref);
gsize n_subsummaries = var_summary_index_subsummaries_get_length (subsummaries);
for (gsize i = 0; i < n_subsummaries; i++)
{
VarSummaryIndexSubsummariesEntryRef entry = var_summary_index_subsummaries_get_at (subsummaries, i);
const char *name = var_summary_index_subsummaries_entry_get_key (entry);
VarSubsummaryRef subsummary = var_summary_index_subsummaries_entry_get_value (entry);
gsize checksum_bytes_len;
const guchar *checksum_bytes;
if (strcmp (name, arch) == 0)
{
g_autofree char *digest = NULL;
g_autoptr(GError) error = NULL;
checksum_bytes = var_subsummary_peek_checksum (subsummary, &checksum_bytes_len);
if (G_UNLIKELY (checksum_bytes_len != OSTREE_SHA256_DIGEST_LEN))
break;
digest = ostree_checksum_from_bytes (checksum_bytes);
subsummary_v = flatpak_repo_load_digested_summary (repo, digest, &error);
if (subsummary_v == NULL)
g_printerr ("Failed to load subsummary %s (digest %s)\n", name, digest);
break;
}
}
}
}
if (subsummary_v)
summary = subsummary_v;
meta = g_variant_get_child_value (summary, 1);
g_variant_lookup (meta, "xa.summary-version", "u", &summary_version);
if (summary_version == 1)
{
g_autoptr(GVariant) refs = g_variant_get_child_value (summary, 0);
GVariant *refdata_iter = NULL;
g_variant_iter_init (&iter, refs);
while (g_variant_iter_next (&iter, "(&s@(taya{sv}))", &ref, &refdata_iter))
{
g_autoptr(GVariant) refdata = refdata_iter;
g_autoptr(GVariant) ref_meta = g_variant_get_child_value (refdata, 2);
if (strcmp (branch, ref) == 0)
{
g_autoptr(GVariant) data = g_variant_lookup_value (ref_meta, "xa.data", NULL);
if (data)
{
g_variant_get (data, "(tt&s)", &installed_size, &download_size, &metadata);
g_print ("%s\n", metadata);
break;
}
}
}
}
else /* Version 0 */
{
g_autoptr(GVariant) cache = g_variant_lookup_value (meta, "xa.cache", NULL);
if (cache)
{
g_autoptr(GVariant) refdata = g_variant_get_variant (cache);
g_variant_iter_init (&iter, refdata);
while (g_variant_iter_next (&iter, "{&s(tt&s)}", &ref, &installed_size, &download_size, &metadata))
{
if (strcmp (branch, ref) == 0)
{
g_print ("%s\n", metadata);
break;
}
}
}
}
}
static void
dump_indented_lines (const gchar *data)
{
const char * indent = " ";
const gchar *pos;
for (;;)
{
pos = strchr (data, '\n');
if (pos)
{
g_print ("%s%.*s", indent, (int) (pos + 1 - data), data);
data = pos + 1;
}
else
{
if (data[0] != '\0')
g_print ("%s%s\n", indent, data);
break;
}
}
}
static void
dump_deltas_for_commit (GPtrArray *deltas,
const char *checksum)
{
int i;
gboolean header_printed = FALSE;
if (!deltas)
return;
for (i = 0; i < deltas->len; i++)
{
const char *delta = g_ptr_array_index (deltas, i);
if (g_str_equal (delta, checksum))
{
if (!header_printed)
{
g_print ("Static Deltas:\n");
header_printed = TRUE;
}
g_print (" from scratch\n");
}
else if (strchr (delta, '-'))
{
g_auto(GStrv) parts = g_strsplit (delta, "-", 0);
if (g_str_equal (parts[1], checksum))
{
if (!header_printed)
{
g_print ("Static Deltas:\n");
header_printed = TRUE;
}
g_print (" from %s\n", parts[0]);
}
}
}
if (header_printed)
g_print ("\n");
}
static gboolean
dump_commit (const char *commit,
GVariant *variant,
GPtrArray *deltas,
GError **error)
{
const gchar *subject;
const gchar *body;
guint64 timestamp;
g_autofree char *str = NULL;
/* See OSTREE_COMMIT_GVARIANT_FORMAT */
g_variant_get (variant, "(a{sv}aya(say)&s&stayay)", NULL, NULL, NULL,
&subject, &body, ×tamp, NULL, NULL);
timestamp = GUINT64_FROM_BE (timestamp);
str = format_timestamp (timestamp);
g_print ("Commit: %s\n", commit);
g_print ("Date: %s\n", str);
if (subject[0])
{
g_print ("\n");
dump_indented_lines (subject);
}
else
{
g_print ("(no subject)\n");
}
if (body[0])
{
g_print ("\n");
dump_indented_lines (body);
}
g_print ("\n");
dump_deltas_for_commit (deltas, commit);
return TRUE;
}
static gboolean
log_commit (OstreeRepo *repo,
const char *checksum,
gboolean is_recurse,
GPtrArray *deltas,
GError **error)
{
g_autoptr(GVariant) variant = NULL;
g_autofree char *parent = NULL;
gboolean ret = FALSE;
GError *local_error = NULL;
if (!ostree_repo_load_variant (repo, OSTREE_OBJECT_TYPE_COMMIT, checksum,
&variant, &local_error))
{
if (is_recurse && g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))
{
g_print ("<< History beyond this commit not fetched >>\n");
g_clear_error (&local_error);
ret = TRUE;
}
else
{
g_propagate_error (error, local_error);
}
goto out;
}
if (!dump_commit (checksum, variant, deltas, error))
goto out;
/* Get the parent of this commit */
parent = ostree_commit_get_parent (variant);
if (parent && !log_commit (repo, parent, TRUE, deltas, error))
goto out;
ret = TRUE;
out:
return ret;
}
static gboolean
print_commits (OstreeRepo *repo,
const char *collection_id,
const char *ref,
GCancellable *cancellable,
GError **error)
{
g_autofree char *checksum = NULL;
g_autoptr(GPtrArray) deltas = NULL;
if (!ostree_repo_list_static_delta_names (repo, &deltas, NULL, error))
return FALSE;
if (!flatpak_repo_resolve_rev (repo, collection_id, NULL, ref, FALSE, &checksum,
cancellable, error))
return FALSE;
if (!log_commit (repo, checksum, FALSE, deltas, error))
return FALSE;
return TRUE;
}
static GOptionEntry options[] = {
{ "info", 0, 0, G_OPTION_ARG_NONE, &opt_info, N_("Print general information about the repository"), NULL },
{ "branches", 0, 0, G_OPTION_ARG_NONE, &opt_branches, N_("List the branches in the repository"), NULL },
{ "metadata", 0, 0, G_OPTION_ARG_STRING, &opt_metadata_branch, N_("Print metadata for a branch"), N_("BRANCH") },
{ "commits", 0, 0, G_OPTION_ARG_STRING, &opt_commits_branch, N_("Show commits for a branch"), N_("BRANCH") },
{ "subsets", 0, 0, G_OPTION_ARG_NONE, &opt_subsets, N_("Print information about the repo subsets"), NULL },
{ "subset", 0, 0, G_OPTION_ARG_STRING, &opt_subset, N_("Limit information to subsets with this prefix"), NULL },
{ NULL }
};
gboolean
flatpak_builtin_repo (int argc, char **argv,
GCancellable *cancellable, GError **error)
{
g_autoptr(GOptionContext) context = NULL;
g_autoptr(GFile) location = NULL;
g_autoptr(OstreeRepo) repo = NULL;
g_autoptr(GVariant) index = NULL;
g_autoptr(GVariant) summary = NULL;
const char *collection_id;
context = g_option_context_new (_("LOCATION - Repository maintenance"));
g_option_context_set_translation_domain (context, GETTEXT_PACKAGE);
if (!flatpak_option_context_parse (context, options, &argc, &argv, FLATPAK_BUILTIN_FLAG_NO_DIR, NULL, cancellable, error))
return FALSE;
if (argc < 2)
return usage_error (context, _("LOCATION must be specified"), error);
location = g_file_new_for_commandline_arg (argv[1]);
repo = ostree_repo_new (location);
if (!ostree_repo_open (repo, cancellable, error))
return FALSE;
collection_id = ostree_repo_get_collection_id (repo);
index = flatpak_repo_load_summary_index (repo, NULL);
summary = flatpak_repo_load_summary (repo, error);
if (summary == NULL)
{
g_prefix_error (error, "Error getting repository metadata from summary file: ");
return FALSE;
}
if (!opt_info && !opt_branches && !opt_metadata_branch && !opt_commits_branch && !opt_subsets)
opt_info = TRUE;
/* Print out the metadata. */
if (opt_info)
print_info (repo, index, summary);
if (opt_branches)
print_branches (repo, index, summary);
if (opt_metadata_branch)
print_metadata (repo, index, summary, opt_metadata_branch);
if (opt_subsets)
print_subsets (repo, index);
if (opt_commits_branch)
{
if (!print_commits (repo, collection_id, opt_commits_branch, cancellable, error))
return FALSE;
}
return TRUE;
}
gboolean
flatpak_complete_repo (FlatpakCompletion *completion)
{
g_autoptr(GOptionContext) context = NULL;
context = g_option_context_new ("");
if (!flatpak_option_context_parse (context, options, &completion->argc, &completion->argv,
FLATPAK_BUILTIN_FLAG_NO_DIR, NULL, NULL, NULL))
return FALSE;
switch (completion->argc)
{
case 0:
case 1: /* LOCATION */
flatpak_complete_options (completion, global_entries);
flatpak_complete_options (completion, options);
flatpak_complete_dir (completion);
break;
}
return TRUE;
}