diff --git a/src/ostree/ostree-pull.c b/src/ostree/ostree-pull.c index ec0adda0..ad108eb4 100644 --- a/src/ostree/ostree-pull.c +++ b/src/ostree/ostree-pull.c @@ -1,6 +1,6 @@ /* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- * - * Copyright (C) 2011 Colin Walters + * Copyright (C) 2011,2012 Colin Walters * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -83,6 +83,7 @@ fetch_uri (OstreeRepo *repo, SoupURI *uri, const char *tmp_prefix, GFile **out_temp_filename, + GCancellable *cancellable, GError **error) { gboolean ret = FALSE; @@ -136,6 +137,43 @@ fetch_uri (OstreeRepo *repo, return ret; } +static gboolean +fetch_uri_contents_utf8 (OstreeRepo *repo, + SoupSession *soup, + SoupURI *uri, + char **out_contents, + GCancellable *cancellable, + GError **error) +{ + gboolean ret = FALSE; + GFile *tmpf = NULL; + char *ret_contents = NULL; + gsize len; + + if (!fetch_uri (repo, soup, uri, "tmp-", &tmpf, cancellable, error)) + goto out; + + if (!g_file_load_contents (tmpf, cancellable, &ret_contents, &len, NULL, error)) + goto out; + + if (!g_utf8_validate (ret_contents, -1, NULL)) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Invalid UTF-8"); + goto out; + } + + ret = TRUE; + ot_transfer_out_value (out_contents, &ret_contents); + out: + if (tmpf) + (void) unlink (ot_gfile_get_path_cached (tmpf)); + g_clear_object (&tmpf); + g_free (ret_contents); + return ret; +} + + static gboolean fetch_object (OstreeRepo *repo, SoupSession *soup, @@ -143,6 +181,7 @@ fetch_object (OstreeRepo *repo, const char *checksum, OstreeObjectType objtype, GFile **out_temp_path, + GCancellable *cancellable, GError **error) { gboolean ret = FALSE; @@ -156,7 +195,8 @@ fetch_object (OstreeRepo *repo, relpath = g_build_filename (soup_uri_get_path (obj_uri), objpath, NULL); soup_uri_set_path (obj_uri, relpath); - if (!fetch_uri (repo, soup, obj_uri, ostree_object_type_to_string (objtype), &ret_temp_path, error)) + if (!fetch_uri (repo, soup, obj_uri, ostree_object_type_to_string (objtype), &ret_temp_path, + cancellable, error)) goto out; ret = TRUE; @@ -178,6 +218,7 @@ fetch_and_store_object (OstreeRepo *repo, OstreeObjectType objtype, gboolean *out_is_pending, GVariant **out_metadata, + GCancellable *cancellable, GError **error) { gboolean ret = FALSE; @@ -197,25 +238,25 @@ fetch_and_store_object (OstreeRepo *repo, if (!(stored_path || pending_path)) { - if (!fetch_object (repo, soup, baseuri, checksum, objtype, &temp_path, error)) + if (!fetch_object (repo, soup, baseuri, checksum, objtype, &temp_path, cancellable, error)) goto out; } if (temp_path) { file_info = g_file_query_info (temp_path, OSTREE_GIO_FAST_QUERYINFO, - G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, NULL, error); + G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, cancellable, error); if (!file_info) goto out; - input = (GInputStream*)g_file_read (temp_path, NULL, error); + input = (GInputStream*)g_file_read (temp_path, cancellable, error); if (!input) goto out; } if (pending_path || temp_path) { - if (!ostree_repo_stage_object (repo, objtype, checksum, file_info, NULL, input, NULL, error)) + if (!ostree_repo_stage_object (repo, objtype, checksum, file_info, NULL, input, cancellable, error)) goto out; log_verbose ("Staged object: %s.%s", checksum, ostree_object_type_to_string (objtype)); @@ -253,6 +294,7 @@ fetch_and_store_tree_recurse (OstreeRepo *repo, SoupSession *soup, SoupURI *base_uri, const char *rev, + GCancellable *cancellable, GError **error) { gboolean ret = FALSE; @@ -270,7 +312,8 @@ fetch_and_store_tree_recurse (OstreeRepo *repo, GFile *pending_path = NULL; GInputStream *input = NULL; - if (!fetch_and_store_object (repo, soup, base_uri, rev, OSTREE_OBJECT_TYPE_DIR_TREE, &is_pending, &tree, error)) + if (!fetch_and_store_object (repo, soup, base_uri, rev, OSTREE_OBJECT_TYPE_DIR_TREE, + &is_pending, &tree, cancellable, error)) goto out; if (!is_pending) @@ -302,13 +345,13 @@ fetch_and_store_tree_recurse (OstreeRepo *repo, if (ostree_repo_get_mode (repo) == OSTREE_REPO_MODE_BARE) { if (!ostree_repo_find_object (repo, OSTREE_OBJECT_TYPE_RAW_FILE, checksum, - &stored_path, &pending_path, NULL, error)) + &stored_path, &pending_path, cancellable, error)) goto out; } else { if (!ostree_repo_find_object (repo, OSTREE_OBJECT_TYPE_ARCHIVED_FILE_CONTENT, checksum, - &stored_path, &pending_path, NULL, error)) + &stored_path, &pending_path, cancellable, error)) goto out; } @@ -321,6 +364,7 @@ fetch_and_store_tree_recurse (OstreeRepo *repo, if (!fetch_object (repo, soup, base_uri, checksum, OSTREE_OBJECT_TYPE_ARCHIVED_FILE_META, &meta_temp_path, + cancellable, error)) goto out; @@ -337,10 +381,11 @@ fetch_and_store_tree_recurse (OstreeRepo *repo, if (!fetch_object (repo, soup, base_uri, checksum, OSTREE_OBJECT_TYPE_ARCHIVED_FILE_CONTENT, &content_temp_path, + cancellable, error)) goto out; - input = (GInputStream*)g_file_read (content_temp_path, NULL, error); + input = (GInputStream*)g_file_read (content_temp_path, cancellable, error); if (!input) goto out; } @@ -353,7 +398,7 @@ fetch_and_store_tree_recurse (OstreeRepo *repo, if (!ostree_repo_stage_object (repo, OSTREE_OBJECT_TYPE_RAW_FILE, checksum, archive_file_info, archive_xattrs, input, - NULL, error)) + cancellable, error)) goto out; } @@ -386,10 +431,11 @@ fetch_and_store_tree_recurse (OstreeRepo *repo, if (!ostree_validate_checksum_string (meta_checksum, error)) goto out; - if (!fetch_and_store_object (repo, soup, base_uri, meta_checksum, OSTREE_OBJECT_TYPE_DIR_META, NULL, NULL, error)) + if (!fetch_and_store_object (repo, soup, base_uri, meta_checksum, OSTREE_OBJECT_TYPE_DIR_META, + NULL, NULL, cancellable, error)) goto out; - if (!fetch_and_store_tree_recurse (repo, soup, base_uri, tree_checksum, error)) + if (!fetch_and_store_tree_recurse (repo, soup, base_uri, tree_checksum, cancellable, error)) goto out; } } @@ -423,6 +469,7 @@ fetch_and_store_commit_recurse (OstreeRepo *repo, SoupSession *soup, SoupURI *base_uri, const char *rev, + GCancellable *cancellable, GError **error) { gboolean ret = FALSE; @@ -431,7 +478,8 @@ fetch_and_store_commit_recurse (OstreeRepo *repo, const char *tree_meta_checksum; gboolean is_pending; - if (!fetch_and_store_object (repo, soup, base_uri, rev, OSTREE_OBJECT_TYPE_COMMIT, &is_pending, &commit, error)) + if (!fetch_and_store_object (repo, soup, base_uri, rev, OSTREE_OBJECT_TYPE_COMMIT, + &is_pending, &commit, cancellable, error)) goto out; if (!is_pending) @@ -442,10 +490,12 @@ fetch_and_store_commit_recurse (OstreeRepo *repo, g_variant_get_child (commit, 6, "&s", &tree_contents_checksum); g_variant_get_child (commit, 7, "&s", &tree_meta_checksum); - if (!fetch_and_store_object (repo, soup, base_uri, tree_meta_checksum, OSTREE_OBJECT_TYPE_DIR_META, NULL, NULL, error)) + if (!fetch_and_store_object (repo, soup, base_uri, tree_meta_checksum, OSTREE_OBJECT_TYPE_DIR_META, + NULL, NULL, cancellable, error)) goto out; - if (!fetch_and_store_tree_recurse (repo, soup, base_uri, tree_contents_checksum, error)) + if (!fetch_and_store_tree_recurse (repo, soup, base_uri, tree_contents_checksum, + cancellable, error)) goto out; } @@ -454,79 +504,64 @@ fetch_and_store_commit_recurse (OstreeRepo *repo, ot_clear_gvariant (&commit); return ret; } - + static gboolean -ostree_builtin_pull (int argc, char **argv, GFile *repo_path, GError **error) +fetch_ref_contents (OstreeRepo *repo, + SoupSession *soup, + SoupURI *base_uri, + const char *ref, + char **out_contents, + GCancellable *cancellable, + GError **error) { - GOptionContext *context; gboolean ret = FALSE; - OstreeRepo *repo = NULL; - const char *remote; - const char *branch; - char *key = NULL; - char *baseurl = NULL; + char *ret_contents = NULL; char *refpath = NULL; - GFile *tempf = NULL; - char *remote_ref = NULL; - char *original_rev = NULL; - GKeyFile *config = NULL; - SoupURI *base_uri = NULL; SoupURI *target_uri = NULL; - SoupSession *soup = NULL; - char *rev = NULL; - context = g_option_context_new ("REMOTE BRANCH - Download data from remote repository"); - g_option_context_add_main_entries (context, options, NULL); - - if (!g_option_context_parse (context, &argc, &argv, error)) + target_uri = soup_uri_copy (base_uri); + refpath = g_build_filename (soup_uri_get_path (target_uri), "refs", "heads", ref, NULL); + soup_uri_set_path (target_uri, refpath); + + if (!fetch_uri_contents_utf8 (repo, soup, target_uri, &ret_contents, cancellable, error)) goto out; - repo = ostree_repo_new (repo_path); - if (!ostree_repo_check (repo, error)) + g_strchomp (ret_contents); + + if (!ostree_validate_checksum_string (ret_contents, error)) goto out; - if (argc < 3) - { - ot_util_usage_error (context, "REMOTE and BRANCH must be specified", error); - goto out; - } + ret = TRUE; + ot_transfer_out_value (out_contents, &ret_contents); + out: + g_free (refpath); + g_free (ret_contents); + if (target_uri) + soup_uri_free (target_uri); + return ret; +} - remote = argv[1]; - branch = argv[2]; +static gboolean +pull_one_commit (OstreeRepo *repo, + const char *remote, + const char *branch, + const char *rev, + SoupSession *soup, + SoupURI *base_uri, + GCancellable *cancellable, + GError **error) +{ + gboolean ret = FALSE; + char *key = NULL; + char *remote_ref = NULL; + char *baseurl = NULL; + char *original_rev = NULL; remote_ref = g_strdup_printf ("%s/%s", remote, branch); if (!ostree_repo_resolve_rev (repo, remote_ref, TRUE, &original_rev, error)) goto out; - config = ostree_repo_get_config (repo); - - key = g_strdup_printf ("remote \"%s\"", remote); - baseurl = g_key_file_get_string (config, key, "url", error); - if (!baseurl) - goto out; - base_uri = soup_uri_new (baseurl); - if (!base_uri) - { - g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, - "Failed to parse url '%s'", baseurl); - goto out; - } - target_uri = soup_uri_copy (base_uri); - g_free (refpath); - refpath = g_build_filename (soup_uri_get_path (target_uri), "refs", "heads", branch, NULL); - soup_uri_set_path (target_uri, refpath); - - soup = soup_session_sync_new_with_options (SOUP_SESSION_USER_AGENT, "ostree ", - SOUP_SESSION_ADD_FEATURE_BY_TYPE, SOUP_TYPE_COOKIE_JAR, - NULL); - if (!fetch_uri (repo, soup, target_uri, "ref-", &tempf, error)) - goto out; - - if (!ot_gfile_load_contents_utf8 (tempf, &rev, NULL, NULL, error)) - goto out; - g_strchomp (rev); - if (original_rev && strcmp (rev, original_rev) == 0) { g_print ("No changes in %s\n", remote_ref); @@ -539,10 +574,10 @@ ostree_builtin_pull (int argc, char **argv, GFile *repo_path, GError **error) if (!ostree_repo_prepare_transaction (repo, NULL, error)) goto out; - if (!fetch_and_store_commit_recurse (repo, soup, base_uri, rev, error)) + if (!fetch_and_store_commit_recurse (repo, soup, base_uri, rev, cancellable, error)) goto out; - if (!ostree_repo_commit_transaction (repo, NULL, error)) + if (!ostree_repo_commit_transaction (repo, cancellable, error)) goto out; if (!ostree_repo_write_ref (repo, remote, branch, rev, error)) @@ -550,25 +585,184 @@ ostree_builtin_pull (int argc, char **argv, GFile *repo_path, GError **error) g_print ("remote %s is now %s\n", remote_ref, rev); } - + ret = TRUE; out: + g_free (key); + g_free (remote_ref); + g_free (baseurl); + g_free (original_rev); + return ret; +} + +static gboolean +parse_ref_summary (const char *contents, + GHashTable **out_refs, + GError **error) +{ + gboolean ret = FALSE; + GHashTable *ret_refs = NULL; + char **lines = NULL; + char **iter = NULL; + char *ref = NULL; + char *sha256 = NULL; + + ret_refs = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); + + lines = g_strsplit_set (contents, "\n", -1); + for (iter = lines; *iter; iter++) + { + const char *line = *iter; + const char *spc; + + if (!*line) + continue; + + spc = strchr (line, ' '); + if (!spc) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Invalid ref summary file; missing ' ' in line"); + goto out; + } + + g_free (ref); + ref = g_strdup (spc + 1); + if (!ostree_validate_rev (ref, error)) + goto out; + + g_free (sha256); + sha256 = g_strndup (line, spc - line); + if (!ostree_validate_checksum_string (sha256, error)) + goto out; + + g_hash_table_replace (ret_refs, ref, sha256); + /* Transfer ownership */ + ref = NULL; + sha256 = NULL; + } + + ret = TRUE; + ot_transfer_out_value (out_refs, &ret_refs); + out: + if (ret_refs) + g_hash_table_unref (ret_refs); + g_strfreev (lines); + return ret; +} + +static gboolean +ostree_builtin_pull (int argc, char **argv, GFile *repo_path, GError **error) +{ + GOptionContext *context; + gboolean ret = FALSE; + OstreeRepo *repo = NULL; + const char *remote; + const char *branch; + SoupSession *soup = NULL; + char *path = NULL; + char *baseurl = NULL; + char *summary_data = NULL; + SoupURI *base_uri = NULL; + SoupURI *summary_uri = NULL; + GKeyFile *config = NULL; + GCancellable *cancellable = NULL; + GHashTable *refs_to_fetch = NULL; + GHashTableIter hash_iter; + gpointer key, value; + char *branch_rev = NULL; + + context = g_option_context_new ("REMOTE [BRANCH] - Download data from remote repository"); + g_option_context_add_main_entries (context, options, NULL); + + if (!g_option_context_parse (context, &argc, &argv, error)) + goto out; + + repo = ostree_repo_new (repo_path); + if (!ostree_repo_check (repo, error)) + goto out; + + if (argc < 2) + { + ot_util_usage_error (context, "REMOTE must be specified", error); + goto out; + } + + remote = argv[1]; + if (argc == 2) + branch = NULL; + else + branch = argv[2]; + + soup = soup_session_sync_new_with_options (SOUP_SESSION_USER_AGENT, "ostree ", + SOUP_SESSION_ADD_FEATURE_BY_TYPE, SOUP_TYPE_COOKIE_JAR, + NULL); + + config = ostree_repo_get_config (repo); + + key = g_strdup_printf ("remote \"%s\"", remote); + baseurl = g_key_file_get_string (config, key, "url", error); + if (!baseurl) + goto out; + base_uri = soup_uri_new (baseurl); + + if (!base_uri) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Failed to parse url '%s'", baseurl); + goto out; + } + + if (branch != NULL) + { + char *contents; + + if (!fetch_ref_contents (repo, soup, base_uri, branch, &contents, cancellable, error)) + goto out; + + /* Transfer ownership of contents */ + refs_to_fetch = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); + g_hash_table_insert (refs_to_fetch, g_strdup (branch), contents); + } + else + { + summary_uri = soup_uri_copy (base_uri); + path = g_build_filename (soup_uri_get_path (summary_uri), "refs", "summary", NULL); + soup_uri_set_path (summary_uri, path); + + if (!fetch_uri_contents_utf8 (repo, soup, summary_uri, &summary_data, cancellable, error)) + goto out; + + if (!parse_ref_summary (summary_data, &refs_to_fetch, error)) + goto out; + } + + g_hash_table_iter_init (&hash_iter, refs_to_fetch); + + while (g_hash_table_iter_next (&hash_iter, &key, &value)) + { + const char *ref = key; + const char *sha256 = value; + + if (!pull_one_commit (repo, remote, ref, sha256, soup, base_uri, cancellable, error)) + goto out; + } + + ret = TRUE; + out: + if (refs_to_fetch) + g_hash_table_unref (refs_to_fetch); + g_free (path); + g_free (baseurl); + g_free (summary_data); + g_free (branch_rev); if (context) g_option_context_free (context); - if (tempf) - (void) unlink (ot_gfile_get_path_cached (tempf)); - g_clear_object (&tempf); - g_free (key); - g_free (rev); - g_free (remote_ref); - g_free (original_rev); - g_free (baseurl); - g_free (refpath); g_clear_object (&soup); if (base_uri) soup_uri_free (base_uri); - if (target_uri) - soup_uri_free (target_uri); + if (summary_uri) + soup_uri_free (summary_uri); g_clear_object (&repo); g_clear_object (&soup); return ret;