From e45129e68d3680b4c2b72fe9316d75668f82ac6c Mon Sep 17 00:00:00 2001 From: Dan Nicholson Date: Thu, 13 Feb 2020 09:12:35 -0700 Subject: [PATCH 1/4] lib/pull: Move logic for skipping scratch deltas The rest of the logic for what delta to choose is in `get_best_static_delta_start_for`, so move the logic for skipping scratch deltas there, too. This makes the handling in `initiate_request` more straightforward by returning no match when it should be skipped so that it goes directly into the object pull path. --- src/libostree/ostree-repo-pull.c | 34 +++++++++++++++++--------------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/src/libostree/ostree-repo-pull.c b/src/libostree/ostree-repo-pull.c index 586b34fe3b..c9e7a94b68 100644 --- a/src/libostree/ostree-repo-pull.c +++ b/src/libostree/ostree-repo-pull.c @@ -2582,11 +2582,12 @@ typedef struct { * See the enum in `DeltaSearchResult` for available result types. */ static gboolean -get_best_static_delta_start_for (OtPullData *pull_data, - const char *to_revision, - DeltaSearchResult *out_result, - GCancellable *cancellable, - GError **error) +get_best_static_delta_start_for (OtPullData *pull_data, + const char *to_revision, + const char *cur_revision, + DeltaSearchResult *out_result, + GCancellable *cancellable, + GError **error) { /* Array of possible from checksums */ g_autoptr(GPtrArray) candidates = g_ptr_array_new_with_free_func (g_free); @@ -2688,6 +2689,15 @@ get_best_static_delta_start_for (OtPullData *pull_data, out_result->result = DELTA_SEARCH_RESULT_FROM; memcpy (out_result->from_revision, newest_candidate, OSTREE_SHA256_STRING_LEN+1); } + + /* If a from-scratch delta is available, we don’t want to use it if the ref + * already exists locally, since we are likely only a few commits out of + * date; so doing an object pull is likely more bandwidth efficient. + */ + if (out_result->result == DELTA_SEARCH_RESULT_SCRATCH && + cur_revision != NULL) + out_result->result = DELTA_SEARCH_RESULT_NO_MATCH; + return TRUE; } @@ -3404,7 +3414,8 @@ initiate_request (OtPullData *pull_data, DeltaSearchResult deltares; /* Look for a delta to @to_revision in the summary data */ - if (!get_best_static_delta_start_for (pull_data, to_revision, &deltares, + if (!get_best_static_delta_start_for (pull_data, to_revision, + delta_from_revision, &deltares, pull_data->cancellable, error)) return FALSE; @@ -3425,16 +3436,7 @@ initiate_request (OtPullData *pull_data, enqueue_one_static_delta_superblock_request (pull_data, deltares.from_revision, to_revision, ref); break; case DELTA_SEARCH_RESULT_SCRATCH: - { - /* If a from-scratch delta is available, we don’t want to use it if - * the ref already exists locally, since we are likely only a few - * commits out of date; so doing an object pull is likely more - * bandwidth efficient. */ - if (delta_from_revision != NULL) - queue_scan_one_metadata_object (pull_data, to_revision, OSTREE_OBJECT_TYPE_COMMIT, NULL, 0, ref); - else - enqueue_one_static_delta_superblock_request (pull_data, NULL, to_revision, ref); - } + enqueue_one_static_delta_superblock_request (pull_data, NULL, to_revision, ref); break; case DELTA_SEARCH_RESULT_UNCHANGED: { From 597726ea88da851b773078a9b97f010d560e946b Mon Sep 17 00:00:00 2001 From: Dan Nicholson Date: Thu, 13 Feb 2020 10:39:06 -0700 Subject: [PATCH 2/4] lib/pull: Only skip scratch deltas when normal commit is reachable In e7305bbc8a262d595c7d1c507be7fba0e5a88d06 a heuristic was added that if a scratch delta exists but you already have a commit on that ref that an object pull should be preferred. The idea is that if you already have something on the ref, then it's likely that an object pull require less data to be fetched than pulling the entire new commit. However, this doesn't take into account the commit state. If the existing commit is partial, then it might be better to fetch the scratch delta. On the other hand, if the existing commit is partial only because a metadata only pull has been done, then the parent commit might be normal and then you'd want to prefer an object pull again. Instead of considering just the HEAD local commit, walk the parents and check if any of them are normal commits. Only then use an object pull. --- src/libostree/ostree-repo-pull.c | 49 +++++++++++-- tests/pull-test.sh | 115 +++++++++++++++++++++++++++++-- 2 files changed, 156 insertions(+), 8 deletions(-) diff --git a/src/libostree/ostree-repo-pull.c b/src/libostree/ostree-repo-pull.c index c9e7a94b68..e6a15b1956 100644 --- a/src/libostree/ostree-repo-pull.c +++ b/src/libostree/ostree-repo-pull.c @@ -2552,6 +2552,42 @@ process_one_static_delta (OtPullData *pull_data, return TRUE; } +static gboolean +normal_commit_reachable (OstreeRepo *self, + const char *cur_revision) +{ + g_autofree char *rev = g_strdup (cur_revision); + while (rev != NULL) + { + g_autoptr(GVariant) commit = NULL; + OstreeRepoCommitState commitstate; + g_autoptr(GError) local_error = NULL; + + if (!ostree_repo_load_commit (self, rev, &commit, &commitstate, &local_error)) + { + /* Ignore issues with the commit since we're going to try to pull a + * scratch delta, anyways. Just dump out the error unless it's the + * likely issue that the parent commit didn't exist. + */ + if (!g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND)) + g_debug ("Couldn't load commit %s: %s", rev, local_error->message); + + g_clear_error (&local_error); + break; + } + + /* Is this a normal commit? */ + if ((commitstate & OSTREE_REPO_COMMIT_STATE_PARTIAL) == 0) + return TRUE; + + g_free (rev); + rev = ostree_commit_get_parent (commit); + } + + /* No normal commit found in history */ + return FALSE; +} + /* * DELTA_SEARCH_RESULT_UNCHANGED: * We already have the commit. @@ -2691,12 +2727,17 @@ get_best_static_delta_start_for (OtPullData *pull_data, } /* If a from-scratch delta is available, we don’t want to use it if the ref - * already exists locally, since we are likely only a few commits out of - * date; so doing an object pull is likely more bandwidth efficient. + * or any of its ancestors already exists locally and is not partial. In + * that case only some of the objects in the new commit may be needed, so + * doing an object pull is likely more bandwidth efficient. */ if (out_result->result == DELTA_SEARCH_RESULT_SCRATCH && - cur_revision != NULL) - out_result->result = DELTA_SEARCH_RESULT_NO_MATCH; + cur_revision != NULL && + normal_commit_reachable (pull_data->repo, cur_revision)) + { + g_debug ("Found normal commit in history of %s", cur_revision); + out_result->result = DELTA_SEARCH_RESULT_NO_MATCH; + } return TRUE; } diff --git a/tests/pull-test.sh b/tests/pull-test.sh index 2cfd8e02f7..d0ba4dcd1b 100644 --- a/tests/pull-test.sh +++ b/tests/pull-test.sh @@ -54,12 +54,13 @@ function verify_initial_contents() { assert_file_has_content baz/cow '^moo$' } +num_tests=39 +# 3 tests needs GPG support +num_gpg_tests=3 if has_gpgme; then - echo "1..34" -else - # 3 tests needs GPG support - echo "1..31" + num_tests=$((num_tests + num_gpg_tests)) fi +echo "1..${num_tests}" # Try both syntaxes repo_init --no-gpg-verify @@ -604,3 +605,109 @@ if ${CMD_PREFIX} ostree --repo=repo pull origin main 2>err.txt; then fi assert_file_has_content_literal err.txt 'error: Fetching checksum for ref ((empty), main): Invalid rev lots of html here lots of html here lots of html here lots of' echo "ok pull got HTML for a ref" + +# Restore ref for subsequent tests +mv ostree-srv/gnomerepo/refs/heads/main{.orig,} + +# Test scratch deltas with partial current ref +# https://github.com/ostreedev/ostree/issues/2004 +rm ostree-srv/gnomerepo/deltas -rf +${CMD_PREFIX} ostree --repo=ostree-srv/gnomerepo refs --delete scratch +rm scratch-files -rf +mkdir scratch-files +echo "stuff for rev1" > scratch-files/rev1 +${CMD_PREFIX} ostree --repo=ostree-srv/gnomerepo commit ${COMMIT_ARGS} -b scratch -s rev1 scratch-files +rev1=$(${CMD_PREFIX} ostree --repo=ostree-srv/gnomerepo rev-parse scratch) +echo "stuff for rev2" > scratch-files/rev2 +${CMD_PREFIX} ostree --repo=ostree-srv/gnomerepo commit ${COMMIT_ARGS} -b scratch -s rev2 scratch-files +rev2=$(${CMD_PREFIX} ostree --repo=ostree-srv/gnomerepo rev-parse scratch) +echo "stuff for rev3" > scratch-files/rev3 +${CMD_PREFIX} ostree --repo=ostree-srv/gnomerepo commit ${COMMIT_ARGS} -b scratch -s rev3 scratch-files +rev3=$(${CMD_PREFIX} ostree --repo=ostree-srv/gnomerepo rev-parse scratch) +rm scratch-files -rf + +# Make a scratch delta and then corrupt it so we can tell when the pull is +# fetching the delta and not falling back to an object pull. +${CMD_PREFIX} ostree --repo=ostree-srv/gnomerepo static-delta --empty generate scratch +${CMD_PREFIX} ostree --repo=ostree-srv/gnomerepo summary -u +superblock=$(find ostree-srv/gnomerepo/deltas -name superblock) +echo FAIL > ${superblock} + +# First pull with no existing ref. This should fail because it tries to +# pull the scratch delta. +repo_init --no-gpg-verify +if ${CMD_PREFIX} ostree --repo=repo pull origin scratch 2>err.txt; then + assert_not_reached "pull of corrupt scratch delta unexpectedly succeeded" +fi +assert_file_has_content err.txt "Invalid checksum for static delta" +echo "ok scratch delta (no existing ref)" + +# Pull with a partial HEAD ref. This should fail because it tries to pull the +# scratch delta. +repo_init --no-gpg-verify +${CMD_PREFIX} ostree --repo=repo pull origin --commit-metadata-only scratch +if ${CMD_PREFIX} ostree --repo=repo pull origin scratch 2>err.txt; then + assert_not_reached "pull of corrupt scratch delta unexpectedly succeeded" +fi +assert_file_has_content err.txt "Invalid checksum for static delta" +echo "ok scratch delta (partial HEAD only)" + +# Pull with a partial HEAD ref and partial parent. This should fail because it +# tries to pull the scratch delta. +repo_init --no-gpg-verify +${CMD_PREFIX} ostree --repo=repo pull origin --commit-metadata-only scratch@${rev2} +${CMD_PREFIX} ostree --repo=repo pull origin --commit-metadata-only scratch +if ${CMD_PREFIX} ostree --repo=repo pull origin scratch 2>err.txt; then + assert_not_reached "pull of corrupt scratch delta unexpectedly succeeded" +fi +assert_file_has_content err.txt "Invalid checksum for static delta" +echo "ok scratch delta (partial HEAD and partial parent)" + +# Pull with a partial grandparent. This should fail +# because it tries to pull the scratch delta. This is basically testing that +# there aren't issues with a commit that has not parent. +repo_init --no-gpg-verify +${CMD_PREFIX} ostree --repo=repo pull origin --commit-metadata-only scratch@${rev1} +if ${CMD_PREFIX} ostree --repo=repo pull origin scratch 2>err.txt; then + assert_not_reached "pull of corrupt scratch delta unexpectedly succeeded" +fi +assert_file_has_content err.txt "Invalid checksum for static delta" +echo "ok scratch delta (partial grandparent)" + +# Pull with a partial HEAD ref and normal parent. This should succeed with an +# object pull. +repo_init --no-gpg-verify +${CMD_PREFIX} ostree --repo=repo pull origin scratch@${rev2} +${CMD_PREFIX} ostree --repo=repo pull origin --commit-metadata-only scratch +${CMD_PREFIX} ostree --repo=repo pull origin scratch +echo "ok scratch delta (partial HEAD and normal parent)" + +# Pull with a partial HEAD ref, partial parent and normal grandparent. This +# should succeed with an object pull. +repo_init --no-gpg-verify +${CMD_PREFIX} ostree --repo=repo pull origin scratch@${rev1} +${CMD_PREFIX} ostree --repo=repo pull origin --commit-metadata-only scratch@${rev2} +${CMD_PREFIX} ostree --repo=repo pull origin --commit-metadata-only scratch +${CMD_PREFIX} ostree --repo=repo pull origin scratch +echo "ok scratch delta (partial HEAD, partial parent and normal grandparent)" + +# Pull with a partial HEAD ref and normal grandparent. Ideally this +# would use an object pull, but since the grandparent can't be reached +# by the HEAD commit it will result in a scratch delta. +repo_init --no-gpg-verify +${CMD_PREFIX} ostree --repo=repo pull origin scratch@${rev1} +${CMD_PREFIX} ostree --repo=repo pull origin --commit-metadata-only scratch +if ${CMD_PREFIX} ostree --repo=repo pull origin scratch 2>err.txt; then + assert_not_reached "pull of corrupt scratch delta unexpectedly succeeded" +fi +assert_file_has_content err.txt "Invalid checksum for static delta" +echo "ok scratch delta (partial HEAD and normal grandparent)" + +# Pull with a normal parent. This should succeed with an object pull. +repo_init --no-gpg-verify +${CMD_PREFIX} ostree --repo=repo pull origin scratch@${rev2} +${CMD_PREFIX} ostree --repo=repo pull origin scratch +echo "ok scratch delta (normal parent)" + +# Clean up the deltas so we don't leave a corrupt one around +rm ostree-srv/gnomerepo/deltas -rf From e304473aaf54f6a7faf546516a2cd869a65ed14d Mon Sep 17 00:00:00 2001 From: Dan Nicholson Date: Thu, 13 Feb 2020 10:59:01 -0700 Subject: [PATCH 3/4] lib/pull: Honor require-static-deltas for scratch deltas If a scratch delta exists and the caller has required static deltas, use it instead of the heuristic about existing commits. --- src/libostree/ostree-repo-pull.c | 10 ++++++---- tests/pull-test.sh | 12 +++++++++++- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/src/libostree/ostree-repo-pull.c b/src/libostree/ostree-repo-pull.c index e6a15b1956..0a373b681c 100644 --- a/src/libostree/ostree-repo-pull.c +++ b/src/libostree/ostree-repo-pull.c @@ -2726,12 +2726,14 @@ get_best_static_delta_start_for (OtPullData *pull_data, memcpy (out_result->from_revision, newest_candidate, OSTREE_SHA256_STRING_LEN+1); } - /* If a from-scratch delta is available, we don’t want to use it if the ref - * or any of its ancestors already exists locally and is not partial. In - * that case only some of the objects in the new commit may be needed, so - * doing an object pull is likely more bandwidth efficient. + /* If a from-scratch delta is available and deltas aren't required, we don’t + * want to use it if the ref or any of its ancestors already exists locally + * and is not partial. In that case only some of the objects in the new + * commit may be needed, so doing an object pull is likely more bandwidth + * efficient. */ if (out_result->result == DELTA_SEARCH_RESULT_SCRATCH && + !pull_data->require_static_deltas && cur_revision != NULL && normal_commit_reachable (pull_data->repo, cur_revision)) { diff --git a/tests/pull-test.sh b/tests/pull-test.sh index d0ba4dcd1b..4f2b833130 100644 --- a/tests/pull-test.sh +++ b/tests/pull-test.sh @@ -54,7 +54,7 @@ function verify_initial_contents() { assert_file_has_content baz/cow '^moo$' } -num_tests=39 +num_tests=40 # 3 tests needs GPG support num_gpg_tests=3 if has_gpgme; then @@ -709,5 +709,15 @@ ${CMD_PREFIX} ostree --repo=repo pull origin scratch@${rev2} ${CMD_PREFIX} ostree --repo=repo pull origin scratch echo "ok scratch delta (normal parent)" +# Pull with a normal parent but require static deltas. This should fail +# because it tries to pull the scratch delta. +repo_init --no-gpg-verify +${CMD_PREFIX} ostree --repo=repo pull origin scratch@${rev2} +if ${CMD_PREFIX} ostree --repo=repo pull --require-static-deltas origin scratch 2>err.txt; then + assert_not_reached "pull of corrupt scratch delta unexpectedly succeeded" +fi +assert_file_has_content err.txt "Invalid checksum for static delta" +echo "ok scratch delta (require static deltas with normal parent)" + # Clean up the deltas so we don't leave a corrupt one around rm ostree-srv/gnomerepo/deltas -rf From 04a55cb2c81693a2a978d14516c59953c5320476 Mon Sep 17 00:00:00 2001 From: Dan Nicholson Date: Thu, 20 Feb 2020 13:25:48 -0700 Subject: [PATCH 4/4] lib/pull: Skip scratch deltas if normal commit with same bindings found If a normal commit cannot be found in the history of the current ref, try to find one by looking at the collection and ref bindings in all the commits in the repo. This will only work if the remote is using collection IDs since otherwise just looking at the ref bindings in the local commits would be ambiguous about what remote the commit came from. If a normal commit with matching bindings is found, prefer an object pull. With this change it's possible to have a partial local HEAD commit (from a previous metadata only pull, for example) and broken history to a normal ancestor and correctly use the heuristic that an object pull should be preferred. This is a common case since user repos are likely to have missed intervening commits on the same remote ref. --- src/libostree/ostree-repo-pull.c | 113 +++++++++++++++++++++++++++---- tests/pull-test.sh | 81 +++++++++++++++++++++- 2 files changed, 180 insertions(+), 14 deletions(-) diff --git a/src/libostree/ostree-repo-pull.c b/src/libostree/ostree-repo-pull.c index 0a373b681c..822bdd2952 100644 --- a/src/libostree/ostree-repo-pull.c +++ b/src/libostree/ostree-repo-pull.c @@ -2588,6 +2588,84 @@ normal_commit_reachable (OstreeRepo *self, return FALSE; } +static gboolean +normal_bound_commit_exists (OtPullData *pull_data, + const OstreeCollectionRef *ref, + GCancellable *cancellable) +{ + g_return_val_if_fail (ref != NULL, FALSE); + + g_autofree char *collection_id = NULL; + if (ref->collection_id != NULL) + collection_id = g_strdup (ref->collection_id); + else + { + collection_id = get_remote_repo_collection_id (pull_data); + if (collection_id == NULL) + return FALSE; + } + + g_autoptr(GHashTable) objects = NULL; + g_autoptr(GError) list_error = NULL; + if (!ostree_repo_list_objects (pull_data->repo, OSTREE_REPO_LIST_OBJECTS_ALL, + &objects, cancellable, &list_error)) + { + g_debug ("Couldn't list objects: %s", list_error->message); + return FALSE; + } + + GLNX_HASH_TABLE_FOREACH (objects, GVariant*, serialized_key) + { + const char *checksum; + OstreeObjectType objtype; + ostree_object_name_deserialize (serialized_key, &checksum, &objtype); + if (objtype != OSTREE_OBJECT_TYPE_COMMIT) + continue; + + g_autoptr(GVariant) commit = NULL; + OstreeRepoCommitState commitstate; + g_autoptr(GError) local_error = NULL; + if (!ostree_repo_load_commit (pull_data->repo, checksum, &commit, + &commitstate, &local_error)) + { + /* Ignore issues with the commit since we're going to try to pull a + * scratch delta, anyways. + */ + g_debug ("Couldn't load commit %s: %s", checksum, local_error->message); + continue; + } + + /* Is this a partial commit? */ + if ((commitstate & OSTREE_REPO_COMMIT_STATE_PARTIAL) > 0) + continue; + + /* If the commit has the same collection binding and the ref is included + * in the ref bindings, we have a match. + */ + g_autoptr(GVariant) metadata = g_variant_get_child_value (commit, 0); + const char *collection_id_binding; + if (!g_variant_lookup (metadata, + OSTREE_COMMIT_META_KEY_COLLECTION_BINDING, + "&s", + &collection_id_binding)) + continue; + if (!g_str_equal (collection_id_binding, collection_id)) + continue; + + g_autofree const char **ref_bindings = NULL; + if (!g_variant_lookup (metadata, + OSTREE_COMMIT_META_KEY_REF_BINDING, + "^a&s", + &ref_bindings)) + continue; + if (g_strv_contains ((const char *const *) ref_bindings, ref->ref_name)) + return TRUE; + } + + /* No normal commit found with same bindings */ + return FALSE; +} + /* * DELTA_SEARCH_RESULT_UNCHANGED: * We already have the commit. @@ -2618,12 +2696,13 @@ typedef struct { * See the enum in `DeltaSearchResult` for available result types. */ static gboolean -get_best_static_delta_start_for (OtPullData *pull_data, - const char *to_revision, - const char *cur_revision, - DeltaSearchResult *out_result, - GCancellable *cancellable, - GError **error) +get_best_static_delta_start_for (OtPullData *pull_data, + const OstreeCollectionRef *ref, + const char *to_revision, + const char *cur_revision, + DeltaSearchResult *out_result, + GCancellable *cancellable, + GError **error) { /* Array of possible from checksums */ g_autoptr(GPtrArray) candidates = g_ptr_array_new_with_free_func (g_free); @@ -2733,12 +2812,20 @@ get_best_static_delta_start_for (OtPullData *pull_data, * efficient. */ if (out_result->result == DELTA_SEARCH_RESULT_SCRATCH && - !pull_data->require_static_deltas && - cur_revision != NULL && - normal_commit_reachable (pull_data->repo, cur_revision)) - { - g_debug ("Found normal commit in history of %s", cur_revision); - out_result->result = DELTA_SEARCH_RESULT_NO_MATCH; + !pull_data->require_static_deltas) + { + if (cur_revision != NULL && + normal_commit_reachable (pull_data->repo, cur_revision)) + { + g_debug ("Found normal commit in history of %s", cur_revision); + out_result->result = DELTA_SEARCH_RESULT_NO_MATCH; + } + else if (ref != NULL && + normal_bound_commit_exists (pull_data, ref, cancellable)) + { + g_debug ("Found normal commit with same bindings as %s", ref->ref_name); + out_result->result = DELTA_SEARCH_RESULT_NO_MATCH; + } } return TRUE; @@ -3457,7 +3544,7 @@ initiate_request (OtPullData *pull_data, DeltaSearchResult deltares; /* Look for a delta to @to_revision in the summary data */ - if (!get_best_static_delta_start_for (pull_data, to_revision, + if (!get_best_static_delta_start_for (pull_data, ref, to_revision, delta_from_revision, &deltares, pull_data->cancellable, error)) return FALSE; diff --git a/tests/pull-test.sh b/tests/pull-test.sh index 4f2b833130..8d3a345bae 100644 --- a/tests/pull-test.sh +++ b/tests/pull-test.sh @@ -54,7 +54,7 @@ function verify_initial_contents() { assert_file_has_content baz/cow '^moo$' } -num_tests=40 +num_tests=44 # 3 tests needs GPG support num_gpg_tests=3 if has_gpgme; then @@ -721,3 +721,82 @@ echo "ok scratch delta (require static deltas with normal parent)" # Clean up the deltas so we don't leave a corrupt one around rm ostree-srv/gnomerepo/deltas -rf + +# Test using scratch deltas with unreachable commits but matching ref bindings. +# Add a collection ID to the remote to make some commits with appropriate +# bindings. +${CMD_PREFIX} ostree --repo=ostree-srv/gnomerepo config set core.collection-id org.example.Repo +rm ostree-srv/gnomerepo/deltas -rf +${CMD_PREFIX} ostree --repo=ostree-srv/gnomerepo refs --delete scratch +rm scratch-files -rf +mkdir scratch-files +echo "stuff for rev1" > scratch-files/rev1 +${CMD_PREFIX} ostree --repo=ostree-srv/gnomerepo commit ${COMMIT_ARGS} -b scratch -s rev1 scratch-files +rev1=$(${CMD_PREFIX} ostree --repo=ostree-srv/gnomerepo rev-parse scratch) +echo "stuff for rev2" > scratch-files/rev2 +${CMD_PREFIX} ostree --repo=ostree-srv/gnomerepo commit ${COMMIT_ARGS} -b scratch -s rev2 scratch-files +rev2=$(${CMD_PREFIX} ostree --repo=ostree-srv/gnomerepo rev-parse scratch) +echo "stuff for rev3" > scratch-files/rev3 +${CMD_PREFIX} ostree --repo=ostree-srv/gnomerepo commit ${COMMIT_ARGS} -b scratch -s rev3 scratch-files +rev3=$(${CMD_PREFIX} ostree --repo=ostree-srv/gnomerepo rev-parse scratch) +rm scratch-files -rf + +# Make a scratch delta and then corrupt it so we can tell when the pull is +# fetching the delta and not falling back to an object pull. +${CMD_PREFIX} ostree --repo=ostree-srv/gnomerepo static-delta --empty generate scratch +${CMD_PREFIX} ostree --repo=ostree-srv/gnomerepo summary -u +superblock=$(find ostree-srv/gnomerepo/deltas -name superblock) +echo FAIL > ${superblock} + +# Pull with a partial HEAD ref and partial unreachable commit with matching ref +# bindings. This will search all commits for one bound to the requested ref and +# find the partial grandparent. This should fail because it tries to pull the +# scratch delta. +repo_init --no-gpg-verify +${CMD_PREFIX} ostree --repo=repo pull origin --commit-metadata-only scratch@${rev1} +${CMD_PREFIX} ostree --repo=repo pull origin --commit-metadata-only scratch +if ${CMD_PREFIX} ostree --repo=repo pull origin scratch 2>err.txt; then + assert_not_reached "pull of corrupt scratch delta unexpectedly succeeded" +fi +assert_file_has_content err.txt "Invalid checksum for static delta" +echo "ok scratch delta (partial HEAD and partial bound grandparent)" + +# Pull with a partial HEAD ref and normal unreachable commit with matching ref +# bindings. This will search all commits for one bound to the requested ref and +# find the normal grandparent. This will succeed with an object pull. +repo_init --no-gpg-verify +${CMD_PREFIX} ostree --repo=repo pull origin scratch@${rev1} +${CMD_PREFIX} ostree --repo=repo pull origin --commit-metadata-only scratch +${CMD_PREFIX} ostree --repo=repo pull origin scratch +echo "ok scratch delta (partial HEAD and normal bound grandparent)" + +# Pull the HEAD by revision with a normal unreachable commit with matching ref +# bindings but collection-id not set for the remote. Since the pull is by +# revision, it will not have the collection ID set in the requested ref and the +# search for the bound grandparent will fail. This should fail because it tries +# to pull the scratch delta. +repo_init --no-gpg-verify +${CMD_PREFIX} ostree --repo=repo pull origin scratch@${rev1} +${CMD_PREFIX} ostree --repo=repo pull origin --commit-metadata-only scratch@${rev3} +if ${CMD_PREFIX} ostree --repo=repo pull origin scratch@${rev3} 2>err.txt; then + assert_not_reached "pull of corrupt scratch delta unexpectedly succeeded" +fi +assert_file_has_content err.txt "Invalid checksum for static delta" +echo "ok scratch delta (partial HEAD by revision, normal bound grandparent and no remote collection-id)" + +# Set the remote's collection ID and pull the HEAD by revision with a normal +# unreachable commit with matching ref bindings but collection-id not set for +# the remote. Since the pull is by revision, it will not have the collection ID +# set in the requested ref, but the collection ref configured for the remote +# will be used. This will succeed with an object pull. +repo_init --no-gpg-verify --collection-id=org.example.Repo +${CMD_PREFIX} ostree --repo=repo pull origin scratch@${rev1} +${CMD_PREFIX} ostree --repo=repo pull origin --commit-metadata-only scratch@${rev3} +${CMD_PREFIX} ostree --repo=repo pull origin scratch@${rev3} +echo "ok scratch delta (partial HEAD by revision, normal bound grandparent and remote collection-id)" + +# Remove the remote repo's collection ID +${CMD_PREFIX} ostree --repo=ostree-srv/gnomerepo config unset core.collection-id + +# Clean up the deltas so we don't leave a corrupt one around +rm ostree-srv/gnomerepo/deltas -rf